Skip to content

Commit abd074f

Browse files
authored
Merge pull request aws#6853 from ctlai95/fs-write-integrate
feat(chat): add fsWrite tool to agentic loop
2 parents 34d5ae7 + 65112e6 commit abd074f

File tree

4 files changed

+83
-20
lines changed

4 files changed

+83
-20
lines changed

packages/core/src/codewhispererChat/controllers/chat/controller.ts

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ import { randomUUID } from '../../../shared/crypto'
5757
import { LspController } from '../../../amazonq/lsp/lspController'
5858
import { CodeWhispererSettings } from '../../../codewhisperer/util/codewhispererSettings'
5959
import { getSelectedCustomization } from '../../../codewhisperer/util/customizationUtil'
60-
import { getHttpStatusCode, AwsClientResponseError } from '../../../shared/errors'
60+
import { getHttpStatusCode, AwsClientResponseError, ToolkitError } from '../../../shared/errors'
6161
import { uiEventRecorder } from '../../../amazonq/util/eventRecorder'
6262
import { telemetry } from '../../../shared/telemetry/telemetry'
6363
import { isSsoConnection } from '../../../auth/connection'
@@ -84,6 +84,8 @@ import { ChatSession } from '../../clients/chat/v0/chat'
8484
import { ChatHistoryManager } from '../../storages/chatHistory'
8585
import { amazonQTabSuffix } from '../../../shared/constants'
8686
import { FsRead, FsReadParams } from '../../tools/fsRead'
87+
import { InvokeOutput, OutputKind } from '../../tools/toolShared'
88+
import { FsWrite, FsWriteCommand } from '../../tools/fsWrite'
8789

8890
export interface ChatControllerMessagePublishers {
8991
readonly processPromptChatMessage: MessagePublisher<PromptMessage>
@@ -894,7 +896,7 @@ export class ChatController {
894896
}
895897
session.setToolUse(undefined)
896898

897-
let result: any
899+
let result: InvokeOutput | undefined = undefined
898900
const toolResults: ToolResult[] = []
899901
try {
900902
switch (toolUse.name) {
@@ -910,22 +912,22 @@ export class ChatController {
910912
result = await fsRead.invoke()
911913
break
912914
}
913-
// case 'fs_write': {
914-
// const fsWrite = new FsWrite(toolUse.input as unknown as FsWriteParams)
915-
// const ctx = new DefaultContext()
916-
// result = await fsWrite.invoke(ctx, process.stdout)
917-
// break
918-
// }
919-
// case 'open_file': {
920-
// result = await openFile(toolUse.input as unknown as OpenFileParams)
921-
// break
922-
// }
915+
case 'fsWrite': {
916+
const input = toolUse.input as unknown as FsWriteCommand
917+
await FsWrite.validate(input)
918+
result = await FsWrite.invoke(input)
919+
break
920+
}
923921
default:
922+
getLogger().warn('Received invalid tool: %s', toolUse.name)
924923
break
925924
}
925+
if (!result) {
926+
throw new ToolkitError('Failed to execute tool and get results')
927+
}
926928
toolResults.push({
927929
content: [
928-
result.output.kind === 'text'
930+
result.output.kind === OutputKind.Text
929931
? { text: result.output.content }
930932
: { json: result.output.content },
931933
],

packages/core/src/codewhispererChat/tools/fsWrite.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export interface CreateCommand extends BaseCommand {
1919
}
2020

2121
export interface StrReplaceCommand extends BaseCommand {
22-
command: 'str_replace'
22+
command: 'strReplace'
2323
oldStr: string
2424
newStr: string
2525
}
@@ -47,7 +47,7 @@ export class FsWrite {
4747
case 'create':
4848
await this.handleCreate(command, sanitizedPath)
4949
break
50-
case 'str_replace':
50+
case 'strReplace':
5151
await this.handleStrReplace(command, sanitizedPath)
5252
break
5353
case 'insert':
@@ -66,6 +66,32 @@ export class FsWrite {
6666
}
6767
}
6868

69+
public static async validate(command: FsWriteCommand): Promise<void> {
70+
switch (command.command) {
71+
case 'create':
72+
if (!command.path) {
73+
throw new Error('Path must not be empty')
74+
}
75+
break
76+
case 'strReplace':
77+
case 'insert': {
78+
const fileExists = await fs.existsFile(command.path)
79+
if (!fileExists) {
80+
throw new Error('The provided path must exist in order to replace or insert contents into it')
81+
}
82+
break
83+
}
84+
case 'append':
85+
if (!command.path) {
86+
throw new Error('Path must not be empty')
87+
}
88+
if (!command.newStr) {
89+
throw new Error('Content to append must not be empty')
90+
}
91+
break
92+
}
93+
}
94+
6995
private static async handleCreate(command: CreateCommand, sanitizedPath: string): Promise<void> {
7096
const content = this.getCreateCommandText(command)
7197

packages/core/src/codewhispererChat/tools/tool_index.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,40 @@
1919
},
2020
"required": ["path"]
2121
}
22+
},
23+
"fsWrite": {
24+
"name": "fsWrite",
25+
"description": "A tool for creating and editing files\n * The `create` command will override the file at `path` if it already exists as a file, and otherwise create a new file\n * The `append` command will add content to the end of an existing file, automatically adding a newline if the file doesn't end with one. The file must exist.\n Notes for using the `strReplace` command:\n * The `oldStr` parameter should match EXACTLY one or more consecutive lines from the original file. Be mindful of whitespaces!\n * If the `oldStr` parameter is not unique in the file, the replacement will not be performed. Make sure to include enough context in `oldStr` to make it unique\n * The `newStr` parameter should contain the edited lines that should replace the `oldStr`. The `insert` command will insert `newStr` after `insertLine` and place it on its own line.",
26+
"inputSchema": {
27+
"type": "object",
28+
"properties": {
29+
"command": {
30+
"type": "string",
31+
"enum": ["create", "strReplace", "insert", "append"],
32+
"description": "The commands to run. Allowed options are: `create`, `strReplace`, `insert`, `append`."
33+
},
34+
"fileText": {
35+
"description": "Required parameter of `create` command, with the content of the file to be created.",
36+
"type": "string"
37+
},
38+
"insertLine": {
39+
"description": "Required parameter of `insert` command. The `newStr` will be inserted AFTER the line `insertLine` of `path`.",
40+
"type": "integer"
41+
},
42+
"newStr": {
43+
"description": "Required parameter of `strReplace` command containing the new string. Required parameter of `insert` command containing the string to insert. Required parameter of `append` command containing the content to append to the file.",
44+
"type": "string"
45+
},
46+
"oldStr": {
47+
"description": "Required parameter of `strReplace` command containing the string in `path` to replace.",
48+
"type": "string"
49+
},
50+
"path": {
51+
"description": "Absolute path to file or directory, e.g. `/repo/file.py` or `/repo`.",
52+
"type": "string"
53+
}
54+
},
55+
"required": ["command", "path"]
56+
}
2257
}
2358
}

packages/core/src/test/codewhispererChat/tools/fsWrite.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ describe('FsWrite Tool', function () {
107107
await fs.writeFile(filePath, 'Hello World')
108108

109109
const command: StrReplaceCommand = {
110-
command: 'str_replace',
110+
command: 'strReplace',
111111
path: filePath,
112112
oldStr: 'Hello',
113113
newStr: 'Goodbye',
@@ -124,7 +124,7 @@ describe('FsWrite Tool', function () {
124124
const filePath = path.join(testFolder.path, 'file1.txt')
125125

126126
const command: StrReplaceCommand = {
127-
command: 'str_replace',
127+
command: 'strReplace',
128128
path: filePath,
129129
oldStr: 'Invalid',
130130
newStr: 'Goodbye',
@@ -138,7 +138,7 @@ describe('FsWrite Tool', function () {
138138
await fs.writeFile(filePath, 'Hello Hello World')
139139

140140
const command: StrReplaceCommand = {
141-
command: 'str_replace',
141+
command: 'strReplace',
142142
path: filePath,
143143
oldStr: 'Hello',
144144
newStr: 'Goodbye',
@@ -155,7 +155,7 @@ describe('FsWrite Tool', function () {
155155
await fs.writeFile(filePath, 'Text with special chars: .*+?^${}()|[]\\')
156156

157157
const command: StrReplaceCommand = {
158-
command: 'str_replace',
158+
command: 'strReplace',
159159
path: filePath,
160160
oldStr: '.*+?^${}()|[]\\',
161161
newStr: 'REPLACED',
@@ -173,7 +173,7 @@ describe('FsWrite Tool', function () {
173173
await fs.writeFile(filePath, 'Line 1\n Indented line\nLine 3')
174174

175175
const command: StrReplaceCommand = {
176-
command: 'str_replace',
176+
command: 'strReplace',
177177
path: filePath,
178178
oldStr: ' Indented line\n',
179179
newStr: ' Double indented\n',

0 commit comments

Comments
 (0)