Repository navigation
fix(agent-core-v2): stop advertising unavailable ReadMediaFile to non-multimodal models #3046
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
eb33399
3172f5e
015bd35
7a6feff
346eac1
436bf92
51ebff5
e4032e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@moonshot-ai/kimi-code": patch | ||
| --- | ||
|
|
||
| Stop pointing models without image or video input capability to the unavailable ReadMediaFile tool. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<string> { | |
| 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<string, unknown> = 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.`, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
| }; | ||
| } | ||
|
|
||
|
|
@@ -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); | ||
|
|
||
Large diffs are not rendered by default.
Large diffs are not rendered by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a multimodal model registers
ReadMediaFilebut the workspace, profile, global, or session policy disables it,AgentToolSelectService.activeEntries()removes the tool from the model request and the executor guard rejects calls, while this registry-only check still returns true. Consequently, the new description and rejection messages continue directing the model to an unavailable tool in exactly those denylist scenarios; determine availability through the effectiveIAgentToolPolicyServiceas well as registry presence.AGENTS.md reference: packages/agent-core-v2/AGENTS.md:L7-L7
Useful? React with 👍 / 👎.