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.
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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/')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user