Skip to content

fix(llm-client): handle empty string arguments in Anthropic convertMessages#1879

Closed
GusCayresMindsight wants to merge 1 commit into
bytedance:mainfrom
GusCayresMindsight:fix/anthropic-empty-tool-arguments
Closed

fix(llm-client): handle empty string arguments in Anthropic convertMessages#1879
GusCayresMindsight wants to merge 1 commit into
bytedance:mainfrom
GusCayresMindsight:fix/anthropic-empty-tool-arguments

Conversation

@GusCayresMindsight

Copy link
Copy Markdown

Problem

When running the CLI with --provider anthropic against any prompt that triggers more than ~3 tool-call iterations, the agent crashes with:

SyntaxError: Unexpected end of JSON input

The error originates in convertMessages inside multimodal/tarko/llm-client/src/handlers/anthropic.ts.

Root cause

The Anthropic streaming API emits a content_block_start event for tool calls whose input is an empty object {}. The streaming accumulator in createCompletionResponseStreaming (line ~130) stores the arguments as "" (empty string) rather than "{}" when the input block is empty:

arguments: isEmptyObject(chunk.content_block.input) ? '' : JSON.stringify(chunk.content_block.input),

On the next loop iteration, convertMessages replays the full message history and hits:

input: JSON.parse(toolCall.function.arguments),  // JSON.parse("") → throws

A second issue in the same block: when an assistant message has content: "" alongside tool_calls, the code pushes { type: 'text', text: '' } into the Anthropic request, which the API rejects with a 400 messages: text content blocks must be non-empty.

Fix

Two one-line guards in the assistant branch of convertMessages:

// 1. fall back to "{}" when arguments is an empty string
input: JSON.parse(toolCall.function.arguments || '{}'),

// 2. skip the text block when content is an empty string
if (typeof message.content === 'string' && message.content) {

The || '{}' fallback is already applied by the downstream callers in tool-processor.ts — this brings convertMessages in line with them.

Reproducer

npx @agent-tars/cli@latest \
  --provider anthropic \
  --model claude-sonnet-4-5 \
  --apiKey $ANTHROPIC_API_KEY \
  --headless \
  --input "what is ui tars"

With the fix applied the agent completes successfully and returns a full answer.

When the Anthropic streaming API emits a tool call whose input is an
empty object, the streaming accumulator stores the arguments as ""
(empty string) rather than "{}". On the next loop iteration,
convertMessages replays the history and hits JSON.parse(""), which
throws SyntaxError: Unexpected end of JSON input, aborting the agent.

Two guards in the assistant branch of convertMessages:

1. JSON.parse(toolCall.function.arguments || '{}')
   Prevents the parse error when arguments is an empty string.
   The downstream callers in tool-processor.ts already apply this
   fallback; this brings convertMessages in line with them.

2. typeof message.content === 'string' && message.content
   Prevents pushing a {type:'text', text:''} block when the assistant
   message has an empty content string alongside tool_calls.
   The Anthropic API rejects empty text content blocks with a 400.

Reproducer: run the CLI with --provider anthropic against any prompt
that requires more than ~3 tool-call iterations.
@netlify

netlify Bot commented May 6, 2026

Copy link
Copy Markdown

Deploy Preview for agent-tars-docs ready!

Name Link
🔨 Latest commit 1628fd6
🔍 Latest deploy log https://app.netlify.com/projects/agent-tars-docs/deploys/69fb9c262179e10008ab737c
😎 Deploy Preview https://deploy-preview-1879--agent-tars-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented May 6, 2026

Copy link
Copy Markdown

Deploy Preview for tarko ready!

Name Link
🔨 Latest commit 1628fd6
🔍 Latest deploy log https://app.netlify.com/projects/tarko/deploys/69fb9c26abe72a0008f21973
😎 Deploy Preview https://deploy-preview-1879--tarko.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@GusCayresMindsight

Copy link
Copy Markdown
Author

Closing this PR without signing the CLA due to corporate IP policy constraints.

The fix is offered here as a reference under the project's existing Apache 2.0 license — maintainers are welcome to apply it directly.


File: multimodal/tarko/llm-client/src/handlers/anthropic.ts

Context: the assistant branch inside convertMessages (around line 415).

-      if (typeof message.content === 'string') {
+      // Anthropic rejects empty text content blocks; skip when content is "".
+      if (typeof message.content === 'string' && message.content) {
         currentParams.push({
           text: message.content,
           type: 'text',
         });
       }

       if (Array.isArray(message.tool_calls)) {
         const convertedContent: Array<ToolUseBlockParam> = message.tool_calls.map((toolCall) => {
           return {
             id: toolCall.id,
-            input: JSON.parse(toolCall.function.arguments),
+            // Anthropic streaming emits "" for arguments when the tool input is
+            // an empty object; JSON.parse("") throws — fall back to "{}".
+            input: JSON.parse(toolCall.function.arguments || '{}'),
             name: toolCall.function.name,
             type: 'tool_use',
           };
         });

Root cause: the streaming accumulator stores arguments as "" (empty string) when Anthropic emits a content_block_start whose input is {}. On the next iteration convertMessages replays the history and hits JSON.parse("")SyntaxError: Unexpected end of JSON input. The second guard prevents a 400 from the Anthropic API when an empty string content block is sent alongside tool calls.

The downstream callers in tool-processor.ts already apply the || '{}' fallback — this just brings convertMessages in line with them.

Reproducer:

npx @agent-tars/cli@latest \
  --provider anthropic \
  --model claude-sonnet-4-5 \
  --apiKey $ANTHROPIC_API_KEY \
  --headless \
  --input "what is ui tars"

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.

2 participants