fix(llm-client): handle empty string arguments in Anthropic convertMessages#1879
Conversation
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.
✅ Deploy Preview for agent-tars-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for tarko ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
|
|
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: Context: the - 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 The downstream callers in Reproducer: npx @agent-tars/cli@latest \
--provider anthropic \
--model claude-sonnet-4-5 \
--apiKey $ANTHROPIC_API_KEY \
--headless \
--input "what is ui tars" |
Problem
When running the CLI with
--provider anthropicagainst any prompt that triggers more than ~3 tool-call iterations, the agent crashes with:The error originates in
convertMessagesinsidemultimodal/tarko/llm-client/src/handlers/anthropic.ts.Root cause
The Anthropic streaming API emits a
content_block_startevent for tool calls whose input is an empty object{}. The streaming accumulator increateCompletionResponseStreaming(line ~130) stores the arguments as""(empty string) rather than"{}"when the input block is empty:On the next loop iteration,
convertMessagesreplays the full message history and hits:A second issue in the same block: when an assistant message has
content: ""alongsidetool_calls, the code pushes{ type: 'text', text: '' }into the Anthropic request, which the API rejects with a 400messages: text content blocks must be non-empty.Fix
Two one-line guards in the
assistantbranch ofconvertMessages:The
|| '{}'fallback is already applied by the downstream callers intool-processor.ts— this bringsconvertMessagesin line with them.Reproducer
With the fix applied the agent completes successfully and returns a full answer.