Skip to content

fix: parse XML-like function.arguments from OpenAI-compatible models#12038

Open
octo-patch wants to merge 1 commit intocontinuedev:mainfrom
octo-patch:fix/issue-11453-xml-tool-arguments
Open

fix: parse XML-like function.arguments from OpenAI-compatible models#12038
octo-patch wants to merge 1 commit intocontinuedev:mainfrom
octo-patch:fix/issue-11453-xml-tool-arguments

Conversation

@octo-patch
Copy link
Copy Markdown
Contributor

@octo-patch octo-patch commented Apr 5, 2026

Fixes #11453

Problem

Some OpenAI-compatible models (e.g. local Qwen endpoints) return tool_calls[].function.arguments in an XML-like format rather than JSON:

<tool_call>
<function=ls>
<parameter=dirPath>src</parameter>
<parameter=recursive>True</parameter>
</function>
</tool_call>

Continue assumed JSON-only arguments in all tool-call parsing paths. When JSON.parse() failed, it silently returned {}, causing all required-argument validation to fail and tool calls to never execute correctly.

Solution

Add parseXmlToolCallArgs() to core/tools/parseArgs.ts as a fallback when JSON parsing fails. The function:

  • Extracts <parameter=name>value</parameter> pairs via regex
  • Applies scalar coercion: "true"/"false" → boolean, numeric strings → number, JSON-parseable strings → parsed value, everything else stays as a string

safeParseToolCallArgs() now tries XML parsing before returning {}.

The CLI streaming path (extensions/cli/src/stream/streamChatResponse.ts) also gets the same fallback: after all streaming chunks arrive, any tool call whose argumentsStr was never JSON-parseable is retried through parseXmlToolCallArgs().

Testing

  • Added unit tests in core/tools/parseArgs.vitest.ts covering:
    • Full XML-wrapped payload with boolean and string params
    • Boolean coercion (True/False)
    • Numeric coercion (integer and float)
    • Plain string values that don't coerce
    • Existing JSON path is unchanged (no regression)

Summary by cubic

Fixes tool-call parsing when some OpenAI-compatible models return XML-like function.arguments, so tools receive correct parameters. Adds a fallback parser with simple type coercion and uses it in core parsing and CLI streaming.

  • Bug Fixes
    • Added XML-like fallback in safeParseToolCallArgs(): parses <parameter=name>value</parameter> and coerces booleans, numbers, and JSON literals.
    • Applied the same fallback in the CLI streaming path after the stream ends for calls that never produced valid JSON.
    • Added tests covering XML parsing, value coercion, and no-regression for JSON inputs.

Written for commit f175f3b. Summary will update on new commits.

…ixes continuedev#11453)

Some OpenAI-compatible models (e.g. local Qwen endpoints) return
tool_calls[].function.arguments in an XML-like format rather than JSON:

  <parameter=name>value</parameter>

Continue assumed JSON-only arguments, causing tool-call execution to
silently fail with empty args when JSON.parse() returned {}.

Changes:
- core/tools/parseArgs.ts: add parseXmlToolCallArgs() and fall back to
  it in safeParseToolCallArgs() when JSON parsing fails. Scalar
  coercion applied: true/false to boolean, numeric strings to number,
  JSON-parseable strings to parsed value, otherwise string.
- core/tools/parseArgs.vitest.ts: add regression tests for the new
  XML-fallback path.
- extensions/cli/src/stream/streamChatResponse.ts: apply the same
  XML fallback after streaming ends for any tool call whose accumulated
  argumentsStr was never JSON-parseable.
@octo-patch octo-patch requested a review from a team as a code owner April 5, 2026 04:23
@octo-patch octo-patch requested review from sestinj and removed request for a team April 5, 2026 04:23
@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Apr 5, 2026
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="core/tools/parseArgs.ts">

<violation number="1" location="core/tools/parseArgs.ts:12">
P2: XML numeric coercion accepts non-finite numbers (e.g., `Infinity`), and downstream `getNumberArg` does not reject them.</violation>

<violation number="2" location="core/tools/parseArgs.ts:31">
P1: Model-controlled XML parameter names can pollute object prototype via `__proto__`, enabling inherited-arg presence bypasses.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

* <parameter=…> tags are found so callers can detect a failed parse.
*/
export function parseXmlToolCallArgs(argsStr: string): Record<string, any> {
const result: Record<string, any> = {};
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Model-controlled XML parameter names can pollute object prototype via __proto__, enabling inherited-arg presence bypasses.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At core/tools/parseArgs.ts, line 31:

<comment>Model-controlled XML parameter names can pollute object prototype via `__proto__`, enabling inherited-arg presence bypasses.</comment>

<file context>
@@ -1,5 +1,46 @@
+ * <parameter=…> tags are found so callers can detect a failed parse.
+ */
+export function parseXmlToolCallArgs(argsStr: string): Record<string, any> {
+  const result: Record<string, any> = {};
+  const paramRegex = /<parameter=([^>]+)>([\s\S]*?)<\/parameter>/g;
+  let match: RegExpExecArray | null;
</file context>
Suggested change
const result: Record<string, any> = {};
const result: Record<string, any> = Object.create(null);
Fix with Cubic

if (value.toLowerCase() === "false") return false;

// Numeric coercion — only if the string is entirely numeric
if (value.trim() !== "" && !isNaN(Number(value))) return Number(value);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: XML numeric coercion accepts non-finite numbers (e.g., Infinity), and downstream getNumberArg does not reject them.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At core/tools/parseArgs.ts, line 12:

<comment>XML numeric coercion accepts non-finite numbers (e.g., `Infinity`), and downstream `getNumberArg` does not reject them.</comment>

<file context>
@@ -1,5 +1,46 @@
+  if (value.toLowerCase() === "false") return false;
+
+  // Numeric coercion — only if the string is entirely numeric
+  if (value.trim() !== "" && !isNaN(Number(value))) return Number(value);
+
+  // JSON coercion (for embedded arrays/objects/quoted strings)
</file context>
Suggested change
if (value.trim() !== "" && !isNaN(Number(value))) return Number(value);
const num = Number(value);
if (value.trim() !== "" && Number.isFinite(num)) return num;
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

Agent tool calls fail when OpenAI-compatible models return XML-like function.arguments

1 participant