From e7b4b4c20d90a70917d0b147bff1875c803dd53f Mon Sep 17 00:00:00 2001 From: can1357 Date: Wed, 13 May 2026 04:42:43 +0200 Subject: [PATCH] fix(coding-agent): fixed read tool streaming routing and result expansion behavior - Updated event-controller read-tool streaming handling to wait for a parseable target before routing tool calls, avoiding early component binding for unresolved arguments. - Allowed internal-URL read calls to bypass the regular read grouping path and fall through to direct tool execution. - Adjusted read tool rendering to honor the computed `expanded` flag for completed output instead of forcing expanded output. --- .../src/modes/controllers/event-controller.ts | 33 +++++++++++-------- packages/coding-agent/src/tools/fetch.ts | 5 +-- packages/coding-agent/src/tools/gh.ts | 13 ++++---- packages/coding-agent/src/tools/read.ts | 7 ++-- 4 files changed, 28 insertions(+), 30 deletions(-) diff --git a/packages/coding-agent/src/modes/controllers/event-controller.ts b/packages/coding-agent/src/modes/controllers/event-controller.ts index 9b7b783a0..1bfa15f83 100644 --- a/packages/coding-agent/src/modes/controllers/event-controller.ts +++ b/packages/coding-agent/src/modes/controllers/event-controller.ts @@ -277,21 +277,26 @@ export class EventController { for (const content of this.ctx.streamingMessage.content) { if (content.type !== "toolCall") continue; - if ( - content.name === "read" && - readArgsHaveTarget(content.arguments) && - !readArgsTargetInternalUrl(content.arguments) - ) { - this.#trackReadToolCall(content.id, content.arguments); - const component = this.ctx.pendingTools.get(content.id); - if (component) { - component.updateArgs(content.arguments, content.id); - } else { - const group = this.#getReadGroup(); - group.updateArgs(content.arguments, content.id); - this.ctx.pendingTools.set(content.id, group); + if (content.name === "read") { + if (!readArgsHaveTarget(content.arguments)) { + // Args still streaming — defer until path is parseable so we can route to the + // read group (regular files) vs ToolExecutionComponent (internal URLs). + // Creating either component now would lock the read into the wrong shape. + continue; } - continue; + if (!readArgsTargetInternalUrl(content.arguments)) { + this.#trackReadToolCall(content.id, content.arguments); + const component = this.ctx.pendingTools.get(content.id); + if (component) { + component.updateArgs(content.arguments, content.id); + } else { + const group = this.#getReadGroup(); + group.updateArgs(content.arguments, content.id); + this.ctx.pendingTools.set(content.id, group); + } + continue; + } + // Internal URL read falls through to ToolExecutionComponent below. } // Preserve the raw partial JSON for renderers that need to surface fields before the JSON object closes. diff --git a/packages/coding-agent/src/tools/fetch.ts b/packages/coding-agent/src/tools/fetch.ts index 055e74d21..6839b382e 100644 --- a/packages/coding-agent/src/tools/fetch.ts +++ b/packages/coding-agent/src/tools/fetch.ts @@ -1378,10 +1378,7 @@ export function renderReadUrlResult( const outputBlock = new CachedOutputBlock(); return { render: (width: number) => - outputBlock.render( - { header, state: "error", sections: [{ lines: errorLines }], width }, - uiTheme, - ), + outputBlock.render({ header, state: "error", sections: [{ lines: errorLines }], width }, uiTheme), invalidate: () => outputBlock.invalidate(), }; } diff --git a/packages/coding-agent/src/tools/gh.ts b/packages/coding-agent/src/tools/gh.ts index 53dc74ad2..88ca0fcd9 100644 --- a/packages/coding-agent/src/tools/gh.ts +++ b/packages/coding-agent/src/tools/gh.ts @@ -2674,7 +2674,6 @@ export function parsePrUnifiedDiff(text: string): PrDiffPayload { return { unified: text, files }; } - interface ParsedDiffHeaderToken { value: string; nextIndex: number; @@ -2719,7 +2718,7 @@ function parseDiffQuotedEscape(text: string, slashIndex: number): ParsedDiffHead case "v": return { value: "\v", nextIndex: slashIndex + 2 }; case "\\": - case "\"": + case '"': return { value: next, nextIndex: slashIndex + 2 }; default: return { value: next, nextIndex: slashIndex + 2 }; @@ -2727,11 +2726,11 @@ function parseDiffQuotedEscape(text: string, slashIndex: number): ParsedDiffHead } function parseDiffQuotedToken(text: string, startIndex: number): ParsedDiffHeaderToken | undefined { - if (text.charAt(startIndex) !== "\"") return undefined; + if (text.charAt(startIndex) !== '"') return undefined; let value = ""; for (let i = startIndex + 1; i < text.length; i += 1) { const ch = text.charAt(i); - if (ch === "\"") return { value, nextIndex: i + 1 }; + if (ch === '"') return { value, nextIndex: i + 1 }; if (ch !== "\\") { value += ch; continue; @@ -2759,7 +2758,7 @@ function stripPrDiffPathPrefix(value: string, prefix: "a/" | "b/"): string | und function parsePrDiffHeaderPaths(header: string): { oldPath?: string; newPath?: string } { const trail = header.slice("diff --git ".length); - if (trail.startsWith("\"")) { + if (trail.startsWith('"')) { const oldToken = parseDiffQuotedToken(trail, 0); if (!oldToken) return {}; const newToken = parseDiffHeaderToken(trail, oldToken.nextIndex); @@ -2786,8 +2785,8 @@ function isPrDiffFileHeaderLine(line: string): boolean { line === "+++ /dev/null" || line.startsWith("--- a/") || line.startsWith("+++ b/") || - line.startsWith("--- \"a/") || - line.startsWith("+++ \"b/") + line.startsWith('--- "a/') || + line.startsWith('+++ "b/') ); } diff --git a/packages/coding-agent/src/tools/read.ts b/packages/coding-agent/src/tools/read.ts index 7a47c9520..eca4a3b7b 100644 --- a/packages/coding-agent/src/tools/read.ts +++ b/packages/coding-agent/src/tools/read.ts @@ -1840,10 +1840,7 @@ export const readToolRenderer = { const outputBlock = new CachedOutputBlock(); return { render: (width: number) => - outputBlock.render( - { header, state: "error", sections: [{ lines: errorLines }], width }, - uiTheme, - ), + outputBlock.render({ header, state: "error", sections: [{ lines: errorLines }], width }, uiTheme), invalidate: () => outputBlock.invalidate(), }; } @@ -1938,7 +1935,7 @@ export const readToolRenderer = { title, status: "complete", output: warningLines.length > 0 ? warningLines.join("\n") : undefined, - expanded: true, + expanded, width, }, uiTheme,