Bug Description:
extractJsonStringField in packages/ai-chat-ui/src/common/toolcall-utils.ts tries JSON.parse on the whole string first and only falls back to a regex when it throws:
try {
const parsed = JSON.parse(json);
...
} catch {
const regex = new RegExp('"' + fieldName + '"\\s*:\\s*"([^"]*)"?');
return regex.exec(json)?.[1];
}
The fallback exists for streaming, where the arguments are truncated and therefore invalid JSON — so during streaming the parse always throws, and JSON.parse still scans the entire string before failing. It is called from getArgumentsLabel in the body of ToolCallContent (toolcall-part-renderer.tsx), i.e. on every render while the arguments grow, making the label quadratic in argument size.
Measured with 64-char deltas: 1 MB of arguments → ~7 s of CPU, 2 MB → ~27 s. Conflated tree updates bring this down in practice, but it is pure waste — the regex alone answers in microseconds.
Suggested fix: try the regex first and fall back to JSON.parse (or skip the parse when the string is not yet balanced).
Separately, the regex matches the first "<field>": "..." anywhere in the string, including inside a content value — writing a JSON file that itself has a "path" property shows the wrong label. Cosmetic, but wrong.
Steps to Reproduce:
- In agent mode, have the agent call
writeFileContent with a multi-megabyte content argument (e.g. an embedded base64 image).
- Profile the frontend while the arguments stream in —
JSON.parse under extractJsonStringField dominates.
Additional Information
- Operating System: macOS 26.6.2
- Theia Version: 1.75.0
Bug Description:
extractJsonStringFieldinpackages/ai-chat-ui/src/common/toolcall-utils.tstriesJSON.parseon the whole string first and only falls back to a regex when it throws:The fallback exists for streaming, where the arguments are truncated and therefore invalid JSON — so during streaming the parse always throws, and
JSON.parsestill scans the entire string before failing. It is called fromgetArgumentsLabelin the body ofToolCallContent(toolcall-part-renderer.tsx), i.e. on every render while the arguments grow, making the label quadratic in argument size.Measured with 64-char deltas: 1 MB of arguments → ~7 s of CPU, 2 MB → ~27 s. Conflated tree updates bring this down in practice, but it is pure waste — the regex alone answers in microseconds.
Suggested fix: try the regex first and fall back to
JSON.parse(or skip the parse when the string is not yet balanced).Separately, the regex matches the first
"<field>": "..."anywhere in the string, including inside acontentvalue — writing a JSON file that itself has a"path"property shows the wrong label. Cosmetic, but wrong.Steps to Reproduce:
writeFileContentwith a multi-megabytecontentargument (e.g. an embedded base64 image).JSON.parseunderextractJsonStringFielddominates.Additional Information