From eb33399e494251d6d8078e622693fa7e201b8ddb Mon Sep 17 00:00:00 2001 From: 7Sageer <125936732+7Sageer@users.noreply.github.com> Date: Tue, 18 Aug 2026 10:28:59 +0000 Subject: [PATCH 1/6] fix(agent-core-v2): stop advertising unavailable ReadMediaFile to non-multimodal models --- .changeset/tidy-media-tool-hints.md | 5 + .../src/agent/tools/os/read/read.md | 2 +- .../src/agent/tools/os/read/readTool.ts | 48 +++++++--- .../src/features/plan/profile/plan.ts | 2 +- .../test/agent/loop/loop.test.ts | 4 +- .../os/backends/node-local/tools/read.test.ts | 96 ++++++++++++++++++- packages/agent-core-v2/test/tool/tool.test.ts | 10 +- 7 files changed, 143 insertions(+), 24 deletions(-) create mode 100644 .changeset/tidy-media-tool-hints.md diff --git a/.changeset/tidy-media-tool-hints.md b/.changeset/tidy-media-tool-hints.md new file mode 100644 index 00000000000..dbef5678be2 --- /dev/null +++ b/.changeset/tidy-media-tool-hints.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Stop pointing models without image or video input capability to the unavailable ReadMediaFile tool. diff --git a/packages/agent-core-v2/src/agent/tools/os/read/read.md b/packages/agent-core-v2/src/agent/tools/os/read/read.md index 8cfab273b0a..edb450c78bf 100644 --- a/packages/agent-core-v2/src/agent/tools/os/read/read.md +++ b/packages/agent-core-v2/src/agent/tools/os/read/read.md @@ -8,7 +8,7 @@ When you need several files, prefer to read them in parallel: emit multiple `Rea - Returns up to ${MAX_LINES} lines or ${MAX_BYTES_KB} KB per call, whichever comes first; lines longer than ${MAX_LINE_LENGTH} chars are truncated mid-line. - Page larger files with `line_offset` (1-based start line) and `n_lines`. Omit `n_lines` to read up to the ${MAX_LINES}-line cap. - Sensitive files (`.env` files, credential stores, SSH private keys, and similar secrets) are refused to protect secrets; do not attempt to read them. Templates and public keys are exempt: `.env.example` / `.env.sample` / `.env.template` and public SSH keys such as `id_rsa.pub` read normally. -- UTF-8 text files are read directly. UTF-16 LE/BE text files (with or without a BOM) are detected automatically and transcoded to UTF-8 for display; the status block notes the detected encoding, and Edit/Write on such a file still expect UTF-8 — convert its encoding first (e.g. `iconv` via Bash). Other encodings (e.g. GBK), binary files, and files containing NUL bytes are refused; use `ReadMediaFile` for images or video, and Bash or an MCP tool for other binary formats. +- UTF-8 text files are read directly. UTF-16 LE/BE text files (with or without a BOM) are detected automatically and transcoded to UTF-8 for display; the status block notes the detected encoding, and Edit/Write on such a file still expect UTF-8 — convert its encoding first (e.g. `iconv` via Bash). Other encodings (e.g. GBK), binary files, and files containing NUL bytes are refused; ${BINARY_FORMATS_HINT} - Negative line_offset reads from the end of the file (for example, -100 reads the last 100 lines); the absolute value cannot exceed ${MAX_LINES}. - Output format: `\t` per line. - A `...` status block is appended after the file content; it summarizes how much was read (line and byte counts, truncation, line-ending notes) and is not part of the file itself. diff --git a/packages/agent-core-v2/src/agent/tools/os/read/readTool.ts b/packages/agent-core-v2/src/agent/tools/os/read/readTool.ts index 887df614530..61007f8779f 100644 --- a/packages/agent-core-v2/src/agent/tools/os/read/readTool.ts +++ b/packages/agent-core-v2/src/agent/tools/os/read/readTool.ts @@ -1,5 +1,6 @@ import type { IHostFileSystem } from '#/os/interface/hostFileSystem'; import { IAgentRuntimeService, inspectAgentRuntime } from '#/agent/runtimeBinding/agentRuntime'; +import { IAgentToolRegistryService } from '#/agent/toolRegistry/toolRegistry'; import { RuntimeWorkspaceView } from '#/runtime/runtimeWorkspaceView'; import { unwrapErrorCause } from '#/_base/errors/errors'; import { ISessionSkillCatalog } from '#/session/sessionSkillCatalog/skillCatalog'; @@ -180,11 +181,17 @@ async function* decodedLines(lines: readonly string[]): AsyncGenerator { yield* lines; } -function notReadableFileOutput(path: string): string { +function notReadableFileOutput(path: string, mediaReadable: boolean): string { + if (mediaReadable) { + return ( + `"${path}" is not readable as UTF-8 text. ` + + 'If it is an image or video, use ReadMediaFile. ' + + 'For other binary formats, use Bash or an MCP tool if available.' + ); + } return ( `"${path}" is not readable as UTF-8 text. ` + - 'If it is an image or video, use ReadMediaFile. ' + - 'For other binary formats, use Bash or an MCP tool if available.' + 'For binary formats, use Bash or an MCP tool if available.' ); } @@ -196,23 +203,36 @@ function notUtf8DecodableFileOutput(path: string): string { ); } -const READ_DESCRIPTION = renderPrompt(readDescriptionTemplate, { - MAX_LINES, - MAX_BYTES_KB: MAX_BYTES / 1024, - MAX_LINE_LENGTH, -}); +function binaryFormatsHint(mediaReadable: boolean): string { + return mediaReadable + ? 'use `ReadMediaFile` for images or video, and Bash or an MCP tool for other binary formats.' + : 'use Bash or an MCP tool for binary formats.'; +} export class ReadTool implements IReadTool { declare readonly _serviceBrand: undefined; readonly name = 'Read' as const; - readonly description = READ_DESCRIPTION; readonly parameters: Record = toInputJsonSchema(ReadInputSchema); constructor( @IAgentRuntimeService private readonly runtime: IAgentRuntimeService, @ISessionWorkspaceContext private readonly workspaceCtx: ISessionWorkspaceContext, @ISessionSkillCatalog private readonly skillCatalog: ISessionSkillCatalog, + @IAgentToolRegistryService private readonly toolRegistry: IAgentToolRegistryService, ) {} + get description(): string { + return renderPrompt(readDescriptionTemplate, { + MAX_LINES, + MAX_BYTES_KB: MAX_BYTES / 1024, + MAX_LINE_LENGTH, + BINARY_FORMATS_HINT: binaryFormatsHint(this.mediaReadable()), + }); + } + + private mediaReadable(): boolean { + return this.toolRegistry.resolve('ReadMediaFile') !== undefined; + } + private workspaceConfig(view: RuntimeWorkspaceView): WorkspaceConfig { return { workspaceDir: view.workDir, additionalDirs: view.additionalDirs }; } @@ -275,7 +295,9 @@ export class ReadTool implements IReadTool { if (fileType.kind === 'image' || fileType.kind === 'video') { return { isError: true, - output: `"${args.path}" is a ${fileType.kind} file. Use ReadMediaFile to read image or video files.`, + output: this.mediaReadable() + ? `"${args.path}" is a ${fileType.kind} file. Use ReadMediaFile to read image or video files.` + : `"${args.path}" is a ${fileType.kind} file. The current model cannot read image or video files; use Bash or an MCP tool to inspect or convert it.`, }; } @@ -298,7 +320,7 @@ export class ReadTool implements IReadTool { } else if (fileType.kind === 'unknown') { return { isError: true, - output: notReadableFileOutput(args.path), + output: notReadableFileOutput(args.path, this.mediaReadable()), }; } else { lines = fs.readLines(safePath, { errors: 'strict' }); @@ -353,7 +375,7 @@ export class ReadTool implements IReadTool { for await (const rawLine of lines) { if (containsNulByte(rawLine)) { - return { isError: true, output: notReadableFileOutput(displayPath) }; + return { isError: true, output: notReadableFileOutput(displayPath, this.mediaReadable()) }; } currentLineNo += 1; updateLineEndingFlags(flags, rawLine); @@ -411,7 +433,7 @@ export class ReadTool implements IReadTool { for await (const rawLine of lines) { if (containsNulByte(rawLine)) { - return { isError: true, output: notReadableFileOutput(displayPath) }; + return { isError: true, output: notReadableFileOutput(displayPath, this.mediaReadable()) }; } currentLineNo += 1; updateLineEndingFlags(flags, rawLine); diff --git a/packages/agent-core-v2/src/features/plan/profile/plan.ts b/packages/agent-core-v2/src/features/plan/profile/plan.ts index 534042eb9c6..9b0db6017b7 100644 --- a/packages/agent-core-v2/src/features/plan/profile/plan.ts +++ b/packages/agent-core-v2/src/features/plan/profile/plan.ts @@ -22,7 +22,7 @@ const PLAN_ROLE = '1. What you already know from the information provided\n' + '2. What questions remain unanswered that would benefit from explore agent investigation\n' + '3. Your implementation plan (either preliminary if questions remain, or final if sufficient context exists)\n\n' + - 'You are a read-only planning agent: you can read and search files (Read, Glob, Grep, ReadMediaFile) ' + + 'You are a read-only planning agent: you can read and search files (Read, Glob, Grep) ' + 'and consult the web (WebSearch, FetchURL), but you have no shell and no file-editing tools. ' + 'Where the general instructions tell you to make changes with tools, that does not apply to you — ' + 'do not attempt to run commands or modify files. Your deliverable is the plan itself, returned as ' + diff --git a/packages/agent-core-v2/test/agent/loop/loop.test.ts b/packages/agent-core-v2/test/agent/loop/loop.test.ts index feec8518e44..ada9d1e2877 100644 --- a/packages/agent-core-v2/test/agent/loop/loop.test.ts +++ b/packages/agent-core-v2/test/agent/loop/loop.test.ts @@ -134,8 +134,8 @@ describe('Agent loop', () => { [emit] turn.step.started { "time": "