Skip to content

Commit 567a954

Browse files
authored
Merge pull request aws#6842 from ctlai95/fs-write
feat(chat): add fsWrite tool create command
2 parents 3fb3d0b + 5015e24 commit 567a954

File tree

3 files changed

+204
-0
lines changed

3 files changed

+204
-0
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { InvokeOutput, OutputKind, sanitizePath } from './toolShared'
7+
import { getLogger } from '../../shared/logger/logger'
8+
import vscode from 'vscode'
9+
import { fs } from '../../shared/fs/fs'
10+
11+
interface BaseCommand {
12+
path: string
13+
}
14+
15+
export interface CreateCommand extends BaseCommand {
16+
command: 'create'
17+
fileText?: string
18+
newStr?: string
19+
}
20+
21+
export interface StrReplaceCommand extends BaseCommand {
22+
command: 'str_replace'
23+
oldStr: string
24+
newStr: string
25+
}
26+
27+
export interface InsertCommand extends BaseCommand {
28+
command: 'insert'
29+
insertLine: number
30+
newStr: string
31+
}
32+
33+
export interface AppendCommand extends BaseCommand {
34+
command: 'append'
35+
newStr: string
36+
}
37+
38+
export type FsWriteCommand = CreateCommand | StrReplaceCommand | InsertCommand | AppendCommand
39+
40+
export class FsWrite {
41+
private static readonly logger = getLogger('fsWrite')
42+
43+
public static async invoke(command: FsWriteCommand): Promise<InvokeOutput> {
44+
const sanitizedPath = sanitizePath(command.path)
45+
46+
switch (command.command) {
47+
case 'create':
48+
await this.handleCreate(command, sanitizedPath)
49+
break
50+
case 'str_replace':
51+
await this.handleStrReplace(command, sanitizedPath)
52+
break
53+
case 'insert':
54+
await this.handleInsert(command, sanitizedPath)
55+
break
56+
case 'append':
57+
await this.handleAppend(command, sanitizedPath)
58+
break
59+
}
60+
61+
return {
62+
output: {
63+
kind: OutputKind.Text,
64+
content: '',
65+
},
66+
}
67+
}
68+
69+
private static async handleCreate(command: CreateCommand, sanitizedPath: string): Promise<void> {
70+
const content = this.getCreateCommandText(command)
71+
72+
const fileExists = await fs.existsFile(sanitizedPath)
73+
const actionType = fileExists ? 'Replacing' : 'Creating'
74+
75+
await vscode.window.withProgress(
76+
{
77+
location: vscode.ProgressLocation.Notification,
78+
title: `${actionType}: ${sanitizedPath}`,
79+
cancellable: false,
80+
},
81+
async () => {
82+
await fs.writeFile(sanitizedPath, content)
83+
}
84+
)
85+
}
86+
87+
private static async handleStrReplace(command: StrReplaceCommand, sanitizedPath: string): Promise<void> {
88+
// TODO
89+
}
90+
91+
private static async handleInsert(command: InsertCommand, sanitizedPath: string): Promise<void> {
92+
// TODO
93+
}
94+
95+
private static async handleAppend(command: AppendCommand, sanitizedPath: string): Promise<void> {
96+
// TODO
97+
}
98+
99+
private static getCreateCommandText(command: CreateCommand): string {
100+
if (command.fileText) {
101+
return command.fileText
102+
}
103+
if (command.newStr) {
104+
this.logger.warn('Required field `fileText` is missing, use the provided `newStr` instead')
105+
return command.newStr
106+
}
107+
this.logger.warn('No content provided for the create command')
108+
return ''
109+
}
110+
}

packages/core/src/shared/logger/logger.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export type LogTopic =
1515
| 'chat'
1616
| 'stepfunctions'
1717
| 'fsRead'
18+
| 'fsWrite'
1819

1920
class ErrorLog {
2021
constructor(
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
import { CreateCommand, FsWrite } from '../../../codewhispererChat/tools/fsWrite'
6+
import { TestFolder } from '../../testUtil'
7+
import path from 'path'
8+
import assert from 'assert'
9+
import { fs } from '../../../shared/fs/fs'
10+
import { InvokeOutput, OutputKind } from '../../../codewhispererChat/tools/toolShared'
11+
12+
describe('FsWrite Tool', function () {
13+
let testFolder: TestFolder
14+
const expectedOutput: InvokeOutput = {
15+
output: {
16+
kind: OutputKind.Text,
17+
content: '',
18+
},
19+
}
20+
21+
before(async function () {
22+
testFolder = await TestFolder.create()
23+
})
24+
25+
describe('create', function () {
26+
it('creates a new file with fileText content', async function () {
27+
const filePath = path.join(testFolder.path, 'file1.txt')
28+
const fileExists = await fs.existsFile(filePath)
29+
assert.ok(!fileExists)
30+
31+
const command: CreateCommand = {
32+
command: 'create',
33+
fileText: 'Hello World',
34+
path: filePath,
35+
}
36+
const output = await FsWrite.invoke(command)
37+
38+
const content = await fs.readFileText(filePath)
39+
assert.strictEqual(content, 'Hello World')
40+
41+
assert.deepStrictEqual(output, expectedOutput)
42+
})
43+
44+
it('replaces existing file with fileText content', async function () {
45+
const filePath = path.join(testFolder.path, 'file1.txt')
46+
const fileExists = await fs.existsFile(filePath)
47+
assert.ok(fileExists)
48+
49+
const command: CreateCommand = {
50+
command: 'create',
51+
fileText: 'Goodbye',
52+
path: filePath,
53+
}
54+
const output = await FsWrite.invoke(command)
55+
56+
const content = await fs.readFileText(filePath)
57+
assert.strictEqual(content, 'Goodbye')
58+
59+
assert.deepStrictEqual(output, expectedOutput)
60+
})
61+
62+
it('uses newStr when fileText is not provided', async function () {
63+
const filePath = path.join(testFolder.path, 'file2.txt')
64+
65+
const command: CreateCommand = {
66+
command: 'create',
67+
newStr: 'Hello World',
68+
path: filePath,
69+
}
70+
const output = await FsWrite.invoke(command)
71+
72+
const content = await fs.readFileText(filePath)
73+
assert.strictEqual(content, 'Hello World')
74+
75+
assert.deepStrictEqual(output, expectedOutput)
76+
})
77+
78+
it('creates an empty file when no content is provided', async function () {
79+
const filePath = path.join(testFolder.path, 'file3.txt')
80+
81+
const command: CreateCommand = {
82+
command: 'create',
83+
path: filePath,
84+
}
85+
const output = await FsWrite.invoke(command)
86+
87+
const content = await fs.readFileText(filePath)
88+
assert.strictEqual(content, '')
89+
90+
assert.deepStrictEqual(output, expectedOutput)
91+
})
92+
})
93+
})

0 commit comments

Comments
 (0)