Skip to content

feat(server): parse <function_calls> and <invoke> XML tool emissions - #623

Closed
dpavlin wants to merge 1 commit into
Luce-Org:mainfrom
dpavlin:feat/parse-function-calls-invoke-xml
Closed

feat(server): parse <function_calls> and <invoke> XML tool emissions#623
dpavlin wants to merge 1 commit into
Luce-Org:mainfrom
dpavlin:feat/parse-function-calls-invoke-xml

Conversation

@dpavlin

@dpavlin dpavlin commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds support for Claude/Anthropic/Agentic XML tool call emissions using <function_calls><invoke name="..."><param name="...">...</param></invoke></function_calls> as well as standalone <invoke name="..."> blocks.

Motivation

When models (such as DeepSeek-V4 or agentic fine-tunes) emit multiple parallel tool calls (e.g. reading multiple files in a batch), they frequently emit <function_calls><invoke name="read"><param name="path">...</param></invoke>...</function_calls>. Previously, these XML blocks were unparsed and leaked directly into visible assistant text.

Changes

  1. Parser Pattern 12 (tool_parser.cpp):
    • Added re_function_calls_block() and re_invoke_xml() to extract tool names and parameters from <invoke> tags.
    • Supports parameter formats: <param name="...">...\</param>, <parameter name="...">...\</parameter>, <param=...>, and inline JSON bodies.
    • Automatically coerces arguments against declared tool JSON schemas via convert_param_value.
    • Removes <function_calls>...</function_calls> and <invoke>...</invoke> blocks from visible output without stripping surrounding natural language.
  2. Opener Detection & Holdback (find_tool_syntax_start / tool_syntax_holdback):
    • Added <function_calls> and <invoke openers to streaming holdback so streamed tool calls are properly intercepted before reaching the client.
  3. Unit Tests (test_server_unit.cpp):
    • test_parse_function_calls_multi_invoke_xml: Verifies multi-file <invoke> parsing and text stripping.
    • test_parse_bare_invoke_xml: Verifies standalone <invoke> with multiple arguments.
    • test_find_tool_syntax_start_invoke: Verifies opener offsets.

Verification

  • Built with -DCMAKE_BUILD_TYPE=RelWithDebInfo on ROCm/HIP.
  • Unit tests: 364/364 passed (100%).
  • Verified live with DeepSeek-V4-Flash and pi agent.

Review in cubic

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 3 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread server/src/server/tool_parser.cpp Outdated
Comment thread server/src/server/tool_parser.cpp Outdated
Comment thread server/src/server/tool_parser.cpp Outdated
@dpavlin
dpavlin force-pushed the feat/parse-function-calls-invoke-xml branch from 28614dc to 33d5cfd Compare August 18, 2026 12:45
@dpavlin

dpavlin commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review

@dpavlin I have started the AI code review. It will take a few minutes to complete.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 3 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread server/src/server/tool_parser.cpp
Comment thread server/src/server/tool_parser.cpp Outdated
Comment thread server/src/server/tool_parser.cpp Outdated
Comment thread server/test/test_server_unit.cpp
@dpavlin
dpavlin force-pushed the feat/parse-function-calls-invoke-xml branch from 33d5cfd to a4543aa Compare August 18, 2026 13:06
@dpavlin

dpavlin commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review

@dpavlin I have started the AI code review. It will take a few minutes to complete.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 3 files

Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.

Re-trigger cubic

@dpavlin
dpavlin force-pushed the feat/parse-function-calls-invoke-xml branch from a4543aa to 83a200f Compare August 18, 2026 13:23
@dpavlin

dpavlin commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review

@dpavlin I have started the AI code review. It will take a few minutes to complete.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 3 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread server/src/server/tool_parser.cpp Outdated
Comment thread server/src/server/tool_parser.cpp Outdated
@dpavlin
dpavlin force-pushed the feat/parse-function-calls-invoke-xml branch from 83a200f to b1bc46f Compare August 18, 2026 13:36
@dpavlin

dpavlin commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review

@dpavlin I have started the AI code review. It will take a few minutes to complete.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 3 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread server/src/server/tool_parser.cpp Outdated
@dpavlin
dpavlin force-pushed the feat/parse-function-calls-invoke-xml branch from b1bc46f to 4a71527 Compare August 18, 2026 14:17
@dpavlin

dpavlin commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review

@dpavlin I have started the AI code review. It will take a few minutes to complete.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 3 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread server/src/server/tool_parser.cpp Outdated
Comment thread server/src/server/tool_parser.cpp Outdated
@dpavlin
dpavlin force-pushed the feat/parse-function-calls-invoke-xml branch from 4a71527 to 3c2f27a Compare August 18, 2026 14:28
@dpavlin

dpavlin commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review

@dpavlin I have started the AI code review. It will take a few minutes to complete.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 3 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread server/src/server/tool_parser.cpp Outdated
Comment thread server/src/server/tool_parser.cpp Outdated
Comment thread server/src/server/tool_parser.cpp Outdated
@dpavlin
dpavlin force-pushed the feat/parse-function-calls-invoke-xml branch from 3c2f27a to 6bb855a Compare August 18, 2026 14:44
@dpavlin

dpavlin commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review

@dpavlin I have started the AI code review. It will take a few minutes to complete.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 3 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread server/src/server/tool_parser.cpp Outdated
@dpavlin
dpavlin force-pushed the feat/parse-function-calls-invoke-xml branch from 6bb855a to b193ceb Compare August 18, 2026 14:53
@cubic-dev-ai

cubic-dev-ai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review

@dpavlin I have started the AI code review. It will take a few minutes to complete.

@dpavlin
dpavlin force-pushed the feat/parse-function-calls-invoke-xml branch from 9048e73 to 8792f75 Compare August 18, 2026 15:12

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 issues found and verified against the latest diff

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="server/src/server/tool_parser.cpp">

<violation number="1" location="server/src/server/tool_parser.cpp:104">
P2: When an `<invoke>` opener contains more than 25 whitespace characters or arrives with `name` split after the 32-byte window, the emitter flushes part of the opener as assistant text before `is_invoke_open_at()` recognizes it. Preserve potential `<invoke` prefixes dynamically, or impose and enforce a matching maximum whitespace bound instead of relying on this fixed holdback.</violation>

<violation number="2" location="server/src/server/tool_parser.cpp:700">
P2: When an invoke argument is a schema-typed string whose JSON value is `"null"`, this new coercion path changes it to JSON null and sends the tool the wrong argument. Apply the null sentinel only when the schema permits null; preserve JSON strings for string-typed properties.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread server/src/server/tool_parser.cpp
Comment thread server/src/server/tool_parser.cpp Outdated
return false;
}

static constexpr size_t INVOKE_OPEN_HOLDBACK = 32;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: When an <invoke> opener contains more than 25 whitespace characters or arrives with name split after the 32-byte window, the emitter flushes part of the opener as assistant text before is_invoke_open_at() recognizes it. Preserve potential <invoke prefixes dynamically, or impose and enforce a matching maximum whitespace bound instead of relying on this fixed holdback.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/server/tool_parser.cpp, line 104:

<comment>When an `<invoke>` opener contains more than 25 whitespace characters or arrives with `name` split after the 32-byte window, the emitter flushes part of the opener as assistant text before `is_invoke_open_at()` recognizes it. Preserve potential `<invoke` prefixes dynamically, or impose and enforce a matching maximum whitespace bound instead of relying on this fixed holdback.</comment>

<file context>
@@ -100,12 +101,39 @@ static bool declared_tool_open_at(const std::string & text, size_t pos,
     return false;
 }
 
+static constexpr size_t INVOKE_OPEN_HOLDBACK = 32;
+
+static bool is_invoke_open_at(const std::string & text, size_t idx) {
</file context>

@dpavlin
dpavlin force-pushed the feat/parse-function-calls-invoke-xml branch from 8792f75 to b81a6e6 Compare August 18, 2026 15:48
@dpavlin

dpavlin commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review

@dpavlin I have started the AI code review. It will take a few minutes to complete.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 existing issue remains and 1 new issue found across 4 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="server/src/server/tool_parser.cpp">

<violation number="1" location="server/src/server/tool_parser.cpp:120">
P2: In reasoning (and content) mode, `find_tool_syntax_start` now treats any literal `<function_calls>` prefix or `<invoke name|tool =` sequence as a tool opener. If reasoning/quoted prose mentions these forms for a declared tool name, the buffer is diverted to TOOL_BUFFER and, when the call is incomplete or unparseable, `emit_finish` suppresses the whole buffered text instead of leaking it. That silently deletes legitimate model output that merely quoted the XML syntax. Consider gating interception on the full `<invoke` block being parseable as an allowed call (e.g. require a closing `</invoke>`/`</function_calls>` and valid args), or fall back to emitting the buffered text as content when the tool parse fails, rather than dropping it.</violation>
</file>

Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.

Re-trigger cubic

Comment thread server/src/server/sse_emitter.cpp
Comment thread server/src/server/tool_parser.cpp Outdated
if (text.compare(idx, sizeof(TOOL_OPEN) - 1, TOOL_OPEN) == 0 ||
text.compare(idx, sizeof(FUNCTION_CALL_OPEN) - 1, FUNCTION_CALL_OPEN) == 0 ||
text.compare(idx, sizeof(FUNCTION_CALLS_OPEN) - 1, FUNCTION_CALLS_OPEN) == 0 ||
is_invoke_open_at(text, idx) ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: In reasoning (and content) mode, find_tool_syntax_start now treats any literal <function_calls> prefix or <invoke name|tool = sequence as a tool opener. If reasoning/quoted prose mentions these forms for a declared tool name, the buffer is diverted to TOOL_BUFFER and, when the call is incomplete or unparseable, emit_finish suppresses the whole buffered text instead of leaking it. That silently deletes legitimate model output that merely quoted the XML syntax. Consider gating interception on the full <invoke block being parseable as an allowed call (e.g. require a closing </invoke>/</function_calls> and valid args), or fall back to emitting the buffered text as content when the tool parse fails, rather than dropping it.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/server/tool_parser.cpp, line 120:

<comment>In reasoning (and content) mode, `find_tool_syntax_start` now treats any literal `<function_calls>` prefix or `<invoke name|tool =` sequence as a tool opener. If reasoning/quoted prose mentions these forms for a declared tool name, the buffer is diverted to TOOL_BUFFER and, when the call is incomplete or unparseable, `emit_finish` suppresses the whole buffered text instead of leaking it. That silently deletes legitimate model output that merely quoted the XML syntax. Consider gating interception on the full `<invoke` block being parseable as an allowed call (e.g. require a closing `</invoke>`/`</function_calls>` and valid args), or fall back to emitting the buffered text as content when the tool parse fails, rather than dropping it.</comment>

<file context>
@@ -100,12 +101,23 @@ static bool declared_tool_open_at(const std::string & text, size_t pos,
         if (text.compare(idx, sizeof(TOOL_OPEN) - 1, TOOL_OPEN) == 0 ||
             text.compare(idx, sizeof(FUNCTION_CALL_OPEN) - 1, FUNCTION_CALL_OPEN) == 0 ||
+            text.compare(idx, sizeof(FUNCTION_CALLS_OPEN) - 1, FUNCTION_CALLS_OPEN) == 0 ||
+            is_invoke_open_at(text, idx) ||
             text.compare(idx, sizeof(FUNCTION_OPEN) - 1, FUNCTION_OPEN) == 0 ||
             text.compare(idx, sizeof(BARE_FUNCTION_OPEN) - 1, BARE_FUNCTION_OPEN) == 0 ||
</file context>

Comment thread server/test/test_server_unit.cpp
@dpavlin
dpavlin force-pushed the feat/parse-function-calls-invoke-xml branch from b81a6e6 to 9fd860c Compare August 18, 2026 16:37
@dpavlin

dpavlin commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review

@dpavlin I have started the AI code review. It will take a few minutes to complete.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 existing issue remains and 1 new issue found across 4 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="server/src/server/sse_emitter.cpp">

<violation number="1" location="server/src/server/sse_emitter.cpp:632">
P2: When a tool block appears inside `<think>` and more reasoning follows the block before `</think>`, this path exposes that reasoning as visible assistant content. Split `parsed.cleaned_text` at `</think>` and route the prefix through the reasoning emission path before emitting only the suffix as content.</violation>
</file>

Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.

Re-trigger cubic

Comment thread server/src/server/sse_emitter.cpp Outdated
@dpavlin
dpavlin force-pushed the feat/parse-function-calls-invoke-xml branch from 9fd860c to ba8cd44 Compare August 18, 2026 16:50
@dpavlin

dpavlin commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review

@dpavlin I have started the AI code review. It will take a few minutes to complete.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 existing issue remains and no new issues found across 4 files

Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.

Re-trigger cubic

Comment thread server/src/server/sse_emitter.cpp Outdated
Comment thread server/src/server/tool_parser.cpp
Comment thread server/src/server/sse_emitter.cpp
@dpavlin
dpavlin force-pushed the feat/parse-function-calls-invoke-xml branch from ba8cd44 to 592b299 Compare August 18, 2026 17:47
@dpavlin

dpavlin commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review

@dpavlin I have started the AI code review. It will take a few minutes to complete.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 existing issue remains and no new issues found across 5 files

Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.

Re-trigger cubic

Comment thread server/src/server/sse_emitter.cpp
Comment thread server/src/server/tool_parser.cpp Outdated
Comment thread server/src/server/tool_parser.cpp Outdated
@dpavlin
dpavlin force-pushed the feat/parse-function-calls-invoke-xml branch from 592b299 to 8bb661f Compare August 18, 2026 18:13
@dpavlin

dpavlin commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review

@dpavlin I have started the AI code review. It will take a few minutes to complete.

@dpavlin

dpavlin commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai please report all issues, including existing ones

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai please report all issues, including existing ones

@dpavlin I have started the AI code review. It will take a few minutes to complete.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 existing issues remain and 3 new issues found across 5 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="server/src/server/sse_emitter.cpp">

<violation number="1" location="server/src/server/sse_emitter.cpp:19">
P3: This newly added `trim_ws` is unused and duplicates the parser's existing helper, adding dead code without affecting XML parsing. Remove this helper.</violation>
</file>

<file name="server/test/test_server_unit.cpp">

<violation number="1" location="server/test/test_server_unit.cpp:884">
P3: The new invoke-parsing tests never exercise the `<param=...>` / `<parameter=...>` equals-attribute forms or the `<parameter name="...">` variant inside `<invoke>`, even though the PR claims these formats are supported and `re_invoke_param_xml` implements them. Every added invoke test uses only `<param name="...">`. Add a case for `<invoke name="read"><param=path>foo.go</param></invoke>` and at least one `<parameter name=...>` / `<parameter=...>` so the claimed formats are actually verified rather than only the name-attribute path.</violation>
</file>

<file name="server/src/server/tool_parser.cpp">

<violation number="1" location="server/src/server/tool_parser.cpp:250">
P2: This change to the shared convert_param_value helper alters `"null"` handling for all existing tool-call patterns, not just the new <invoke> parser. Previously the literal string "null" always became JSON null (checked first, before the props lookup); now it only becomes null when the field is nullable, otherwise the 4-char string is kept. For numeric or unlisted fields, "null" now stays a string (and integer coercion fails), changing output for existing JSON/XML parsers beyond the intended invoke feature. Scope the new behavior to the invoke path, or gate it explicitly, to avoid silent regressions for other clients.</violation>
</file>

Requires human review: Auto-approval blocked because this review re-detected 2 unresolved issues already reported by Cubic.

Re-trigger cubic

Comment thread server/src/server/tool_parser.cpp Outdated
allows_null = true;
}

if (val == "null") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: This change to the shared convert_param_value helper alters "null" handling for all existing tool-call patterns, not just the new parser. Previously the literal string "null" always became JSON null (checked first, before the props lookup); now it only becomes null when the field is nullable, otherwise the 4-char string is kept. For numeric or unlisted fields, "null" now stays a string (and integer coercion fails), changing output for existing JSON/XML parsers beyond the intended invoke feature. Scope the new behavior to the invoke path, or gate it explicitly, to avoid silent regressions for other clients.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/server/tool_parser.cpp, line 250:

<comment>This change to the shared convert_param_value helper alters `"null"` handling for all existing tool-call patterns, not just the new <invoke> parser. Previously the literal string "null" always became JSON null (checked first, before the props lookup); now it only becomes null when the field is nullable, otherwise the 4-char string is kept. For numeric or unlisted fields, "null" now stays a string (and integer coercion fails), changing output for existing JSON/XML parsers beyond the intended invoke feature. Scope the new behavior to the invoke path, or gate it explicitly, to avoid silent regressions for other clients.</comment>

<file context>
@@ -151,55 +166,90 @@ size_t tool_syntax_holdback(const json & tools) {
+        allows_null = true;
+    }
+
+    if (val == "null") {
+        return allows_null ? json(nullptr) : json(val);
+    }
</file context>

static constexpr size_t THINK_OPEN_LEN = 7;
static constexpr size_t THINK_CLOSE_LEN = 8;

static std::string trim_ws(const std::string & v) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: This newly added trim_ws is unused and duplicates the parser's existing helper, adding dead code without affecting XML parsing. Remove this helper.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/server/sse_emitter.cpp, line 19:

<comment>This newly added `trim_ws` is unused and duplicates the parser's existing helper, adding dead code without affecting XML parsing. Remove this helper.</comment>

<file context>
@@ -16,6 +16,14 @@ static const char THINK_CLOSE[] = "</think>";
 static constexpr size_t THINK_OPEN_LEN  = 7;
 static constexpr size_t THINK_CLOSE_LEN = 8;
 
+static std::string trim_ws(const std::string & v) {
+    const char * ws = " \t\r\n";
+    const size_t a = v.find_first_not_of(ws);
</file context>

Comment thread server/test/test_server_unit.cpp Outdated
" Let me read the remaining key files I haven't seen yet to fully understand the codebase.");
}

TEST_CASE(ServerUnitFixture, test_parse_bare_invoke_xml) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: The new invoke-parsing tests never exercise the <param=...> / <parameter=...> equals-attribute forms or the <parameter name="..."> variant inside <invoke>, even though the PR claims these formats are supported and re_invoke_param_xml implements them. Every added invoke test uses only <param name="...">. Add a case for <invoke name="read"><param=path>foo.go</param></invoke> and at least one <parameter name=...> / <parameter=...> so the claimed formats are actually verified rather than only the name-attribute path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/test/test_server_unit.cpp, line 884:

<comment>The new invoke-parsing tests never exercise the `<param=...>` / `<parameter=...>` equals-attribute forms or the `<parameter name="...">` variant inside `<invoke>`, even though the PR claims these formats are supported and `re_invoke_param_xml` implements them. Every added invoke test uses only `<param name="...">`. Add a case for `<invoke name="read"><param=path>foo.go</param></invoke>` and at least one `<parameter name=...>` / `<parameter=...>` so the claimed formats are actually verified rather than only the name-attribute path.</comment>

<file context>
@@ -774,6 +809,395 @@ TEST_CASE(ServerUnitFixture, test_parse_space_function_tool_xml_rejects_malforme
+                " Let me read the remaining key files I haven't seen yet to fully understand the codebase.");
+}
+
+TEST_CASE(ServerUnitFixture, test_parse_bare_invoke_xml) {
+    const std::string text =
+        "Reading configuration:\n"
</file context>

Add parser pattern 12 supporting <function_calls><invoke name="...">
<param name="...">...</param></invoke></function_calls> format emitted by
DeepSeek-V4.

Handles variable/arbitrary tool invocations directly in the text within
<function_calls> containers, supports JSON Schema union types (e.g.
["integer", "null"]) while preserving string types and converting null sentinels
appropriately, validates required parameters across all invoke body formats, intercepts
tool calls during streaming thinking mode, ensures correct thinking block
lifecycles via emit_reasoning_delta, and preserves tool buffer entry mode to route unclosed
<think> reasoning suffixes back to reasoning delta.
@dpavlin
dpavlin force-pushed the feat/parse-function-calls-invoke-xml branch from 8bb661f to d5b93f6 Compare August 18, 2026 18:46
@dpavlin

dpavlin commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

this has turned into unmanageable mess. I will try again from scratch.

@dpavlin dpavlin closed this Aug 18, 2026
@dpavlin
dpavlin deleted the feat/parse-function-calls-invoke-xml branch August 18, 2026 19:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant