Skip to content

Commit fffcdd3

Browse files
committed
fix(chat): Update FsRead toolspec, return truncated output is file is too large
1 parent b222e0b commit fffcdd3

File tree

7 files changed

+111
-16
lines changed

7 files changed

+111
-16
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,7 @@ export class ChatController {
746746
session.setFsWriteBackup(toolUse.toolUseId, backup)
747747
}
748748
const output = await ToolUtils.invoke(tool, chatStream)
749-
ToolUtils.validateOutput(output)
749+
ToolUtils.validateOutput(output, tool.type)
750750

751751
toolResults.push({
752752
content: [

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as vscode from 'vscode'
66
import { getLogger } from '../../shared/logger/logger'
77
import fs from '../../shared/fs/fs'
88
import { Writable } from 'stream'
9-
import { InvokeOutput, OutputKind, sanitizePath, CommandValidation } from './toolShared'
9+
import { InvokeOutput, OutputKind, sanitizePath, CommandValidation, fsReadToolResponseSize } from './toolShared'
1010
import { isInDirectory } from '../../shared/filesystemUtilities'
1111

1212
export interface FsReadParams {
@@ -118,11 +118,24 @@ export class FsRead {
118118
}
119119

120120
private createOutput(content: string): InvokeOutput {
121+
if (content.length > fsReadToolResponseSize) {
122+
this.logger.info(
123+
`The file is too large, truncating output to the first ${fsReadToolResponseSize} characters.`
124+
)
125+
content = this.truncateContent(content)
126+
}
121127
return {
122128
output: {
123129
kind: OutputKind.Text,
124130
content: content,
125131
},
126132
}
127133
}
134+
135+
private truncateContent(content: string): string {
136+
if (content.length > fsReadToolResponseSize) {
137+
return content.substring(0, fsReadToolResponseSize)
138+
}
139+
return content
140+
}
128141
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
import path from 'path'
77
import fs from '../../shared/fs/fs'
88

9-
export const maxToolResponseSize = 200_000
9+
export const defaultMaxToolResponseSize = 100_000
10+
export const listDirectoryToolResponseSize = 30_000
11+
export const fsReadToolResponseSize = 200_000
1012

1113
export enum OutputKind {
1214
Text = 'text',

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ import { FsRead, FsReadParams } from './fsRead'
77
import { FsWrite, FsWriteParams } from './fsWrite'
88
import { CommandValidation, ExecuteBash, ExecuteBashParams } from './executeBash'
99
import { ToolResult, ToolResultContentBlock, ToolResultStatus, ToolUse } from '@amzn/codewhisperer-streaming'
10-
import { InvokeOutput, maxToolResponseSize } from './toolShared'
10+
import {
11+
InvokeOutput,
12+
defaultMaxToolResponseSize,
13+
listDirectoryToolResponseSize,
14+
fsReadToolResponseSize,
15+
} from './toolShared'
1116
import { ListDirectory, ListDirectoryParams } from './listDirectory'
1217

1318
export enum ToolType {
@@ -63,9 +68,20 @@ export class ToolUtils {
6368
}
6469
}
6570

66-
static validateOutput(output: InvokeOutput): void {
71+
static validateOutput(output: InvokeOutput, toolType: ToolType): void {
72+
let maxToolResponseSize = defaultMaxToolResponseSize
73+
switch (toolType) {
74+
case ToolType.FsRead:
75+
maxToolResponseSize = fsReadToolResponseSize
76+
break
77+
case ToolType.ListDirectory:
78+
maxToolResponseSize = listDirectoryToolResponseSize
79+
break
80+
default:
81+
break
82+
}
6783
if (output.output.content.length > maxToolResponseSize) {
68-
throw Error(`Tool output exceeds maximum character limit of ${maxToolResponseSize}`)
84+
throw Error(`${toolType} output exceeds maximum character limit of ${maxToolResponseSize}`)
6985
}
7086
}
7187

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"fsRead": {
33
"name": "fsRead",
4-
"description": "A tool for reading a file.\n * This tool returns the contents of a file, and the optional `readRange` determines what range of lines will be read from the specified file.",
4+
"description": "A tool for reading a file.\n * This tool returns the contents of a file, and the optional `readRange` determines what range of lines will be read from the specified file.\n * If the file exceeds 200K characters, this tool will only read the first 200K characters of the file.",
55
"inputSchema": {
66
"type": "object",
77
"properties": {
@@ -10,7 +10,7 @@
1010
"type": "string"
1111
},
1212
"readRange": {
13-
"description": "Optional parameter when reading files.\n * If none is given, the full file is shown. If provided, the file will be shown in the indicated line number range, e.g. [11, 12] will show lines 11 and 12. Indexing at 1 to start. Setting `[startLine, -1]` shows all lines from `startLine` to the end of the file. If the whole file is too large, try reading 4000 lines at once, for example: after reading [1, 4000], read [4000, 8000] next and repeat. You should read atleast 250 lines per invocation of the tool. In some cases, if reading a range of lines results in too many invocations instead attempt to read 4000 lines.",
13+
"description": "Optional parameter when reading files.\n * If none is given, the full file is shown. If provided, the file will be shown in the indicated line number range, e.g. [11, 12] will show lines 11 and 12. Indexing at 1 to start.\n * Setting `[startLine, -1]` shows all lines from `startLine` to the end of the file.",
1414
"items": {
1515
"type": "integer"
1616
},

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { TestFolder } from '../../testUtil'
88
import path from 'path'
99
import * as vscode from 'vscode'
1010
import sinon from 'sinon'
11+
import { fsReadToolResponseSize } from '../../../codewhispererChat/tools/toolShared'
1112

1213
describe('FsRead Tool', () => {
1314
let testFolder: TestFolder
@@ -37,6 +38,20 @@ describe('FsRead Tool', () => {
3738
assert.strictEqual(result.output.content, fileContent, 'File content should match exactly')
3839
})
3940

41+
it('truncate output if too large', async () => {
42+
const fileContent = 'A'.repeat(fsReadToolResponseSize + 10)
43+
const filePath = await testFolder.write('largeFile.txt', fileContent)
44+
const fsRead = new FsRead({ path: filePath })
45+
await fsRead.validate()
46+
const result = await fsRead.invoke(process.stdout)
47+
assert.strictEqual(result.output.kind, 'text', 'Output kind should be "text"')
48+
assert.strictEqual(
49+
result.output.content.length,
50+
fsReadToolResponseSize,
51+
'Output should be truncated to the max size'
52+
)
53+
})
54+
4055
it('reads partial lines of a file', async () => {
4156
const fileContent = 'A\nB\nC\nD\nE\nF'
4257
const filePath = await testFolder.write('partialFile.txt', fileContent)

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

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@
66
import assert from 'assert'
77
import * as sinon from 'sinon'
88
import { Writable } from 'stream'
9-
import { sanitizePath, OutputKind, InvokeOutput } from '../../../codewhispererChat/tools/toolShared'
9+
import {
10+
sanitizePath,
11+
OutputKind,
12+
InvokeOutput,
13+
listDirectoryToolResponseSize,
14+
fsReadToolResponseSize,
15+
defaultMaxToolResponseSize,
16+
} from '../../../codewhispererChat/tools/toolShared'
1017
import { ToolUtils, Tool, ToolType } from '../../../codewhispererChat/tools/toolUtils'
1118
import { FsRead } from '../../../codewhispererChat/tools/fsRead'
1219
import { FsWrite } from '../../../codewhispererChat/tools/fsWrite'
@@ -158,25 +165,67 @@ describe('ToolUtils', function () {
158165
})
159166

160167
describe('validateOutput', function () {
161-
it('does not throw error if output is within size limit', function () {
168+
it('does not throw error if output is within size limit for fsRead', function () {
162169
const output: InvokeOutput = {
163170
output: {
164171
kind: OutputKind.Text,
165-
content: 'a'.repeat(150_000),
172+
content: 'a'.repeat(fsReadToolResponseSize - 1),
166173
},
167174
}
168-
assert.doesNotThrow(() => ToolUtils.validateOutput(output))
175+
assert.doesNotThrow(() => ToolUtils.validateOutput(output, ToolType.FsRead))
169176
})
170-
it('throws error if output exceeds max size', function () {
177+
it('throws error if output exceeds max size for fsRead', function () {
171178
const output: InvokeOutput = {
172179
output: {
173180
kind: OutputKind.Text,
174-
content: 'a'.repeat(200_001), // 200,001 characters
181+
content: 'a'.repeat(fsReadToolResponseSize + 1),
175182
},
176183
}
177-
assert.throws(() => ToolUtils.validateOutput(output), {
184+
assert.throws(() => ToolUtils.validateOutput(output, ToolType.FsRead), {
178185
name: 'Error',
179-
message: 'Tool output exceeds maximum character limit of 200000',
186+
message: `fsRead output exceeds maximum character limit of ${fsReadToolResponseSize}`,
187+
})
188+
})
189+
it('does not throw error if output is within size limit for listDirectory', function () {
190+
const output: InvokeOutput = {
191+
output: {
192+
kind: OutputKind.Text,
193+
content: 'a'.repeat(listDirectoryToolResponseSize - 1),
194+
},
195+
}
196+
assert.doesNotThrow(() => ToolUtils.validateOutput(output, ToolType.ListDirectory))
197+
})
198+
it('throws error if output exceeds max size for listDirectory', function () {
199+
const output: InvokeOutput = {
200+
output: {
201+
kind: OutputKind.Text,
202+
content: 'a'.repeat(listDirectoryToolResponseSize + 1),
203+
},
204+
}
205+
assert.throws(() => ToolUtils.validateOutput(output, ToolType.ListDirectory), {
206+
name: 'Error',
207+
message: `listDirectory output exceeds maximum character limit of ${listDirectoryToolResponseSize}`,
208+
})
209+
})
210+
it('does not throw error if output is within size limit for fsWrite', function () {
211+
const output: InvokeOutput = {
212+
output: {
213+
kind: OutputKind.Text,
214+
content: 'a'.repeat(defaultMaxToolResponseSize - 1),
215+
},
216+
}
217+
assert.doesNotThrow(() => ToolUtils.validateOutput(output, ToolType.FsWrite))
218+
})
219+
it('does not throw error if output is within size limit for fsWrite', function () {
220+
const output: InvokeOutput = {
221+
output: {
222+
kind: OutputKind.Text,
223+
content: 'a'.repeat(defaultMaxToolResponseSize + 1),
224+
},
225+
}
226+
assert.throws(() => ToolUtils.validateOutput(output, ToolType.FsWrite), {
227+
name: 'Error',
228+
message: `fsWrite output exceeds maximum character limit of ${defaultMaxToolResponseSize}`,
180229
})
181230
})
182231
})

0 commit comments

Comments
 (0)