Skip to content

fix: guard streaming tool call JSON.parse to prevent generator crashes#644

Merged
MarvelNwachukwu merged 2 commits into
mainfrom
fix-streaming-json-parse
Mar 25, 2026
Merged

fix: guard streaming tool call JSON.parse to prevent generator crashes#644
MarvelNwachukwu merged 2 commits into
mainfrom
fix-streaming-json-parse

Conversation

@MarvelNwachukwu

@MarvelNwachukwu MarvelNwachukwu commented Mar 19, 2026

Copy link
Copy Markdown
Contributor

Pull Request

Description

Bare JSON.parse calls on streaming tool call arguments in OpenAiLlm and AnthropicLlm could throw an uncaught SyntaxError when the API returns malformed JSON (e.g. truncated streams, network issues). This crashes the entire async generator, losing all accumulated state — text, other tool calls, and usage metadata. This PR wraps each call in a safeParseToolArgs helper that catches parse errors, logs a warning, and falls back to empty args so the response can still be yielded.

Related Issue

Fixes #650

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • Tests

How Has This Been Tested?

  • Added test in anthropic-llm.test.ts: streaming with truncated tool call JSON completes without crashing, returns empty args
  • Added test in openai-llm.test.ts: openAiMessageToLlmResponse with malformed tool call JSON returns empty args
  • All 63 existing + new tests pass (pnpm test -- anthropic-llm, pnpm test -- openai-llm)
  • pnpm build --filter=@iqai/adk succeeds with no type errors

Checklist

  • My code follows the code style of this project
  • I have added tests to cover my changes
  • All new and existing tests passed
  • My changes generate no new warnings
  • I have checked for potential breaking changes and addressed them

…rator crashes

Malformed streaming tool call arguments from OpenAI or Anthropic APIs would
throw an uncaught SyntaxError, terminating the async generator and losing all
accumulated state. Now falls back to empty args with a warning log.
@vercel

vercel Bot commented Mar 19, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
adk-typescript-docs Ready Ready Preview Mar 20, 2026 0:03am
adk-web Ready Ready Preview Mar 20, 2026 0:03am

Request Review

@changeset-bot

changeset-bot Bot commented Mar 19, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d35873a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@iqai/adk Patch
@iqai/adk-cli Patch
@iqai/mcp-docs Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the stability and resilience of the LLM streaming functionality by implementing robust error handling for malformed JSON in tool call arguments. By preventing generator crashes caused by invalid JSON, it ensures that streaming operations can continue uninterrupted, preserving valuable state and improving the overall reliability of the system.

Highlights

  • Robust JSON Parsing: Introduced a safeParseToolArgs helper function to guard JSON.parse calls for streaming tool call arguments in OpenAiLlm and AnthropicLlm.
  • Crash Prevention: The new helper prevents uncaught SyntaxError exceptions from malformed JSON, which previously caused async generator crashes and loss of accumulated state.
  • Improved Error Handling: The safeParseToolArgs function catches parsing errors, logs a warning, and falls back to providing empty arguments, ensuring the LLM response can still be yielded.
  • Comprehensive Testing: Added new tests for both Anthropic and OpenAI LLMs to specifically cover scenarios with truncated or malformed tool call JSON, verifying that the system handles these cases gracefully without crashing.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request effectively prevents potential crashes caused by malformed JSON in streaming tool call arguments by introducing a safeParseToolArgs helper function. The implementation is solid and includes tests to validate the fix. I've included a suggestion to enhance maintainability by centralizing the new helper function.

Comment on lines +10 to +23
function safeParseToolArgs(
json: string | undefined,
logger: Logger,
): Record<string, unknown> {
try {
return JSON.parse(json || "{}");
} catch (error) {
logger.warn("Failed to parse tool call arguments, using empty args", {
rawArgs: json,
error: String(error),
});
return {};
}
}

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.

medium

This safeParseToolArgs function is also defined in packages/adk/src/models/openai-llm.ts. To avoid code duplication and improve maintainability, consider extracting this function into a shared utility file (e.g., in a new packages/adk/src/models/utils.ts or a similar existing one) and importing it where needed.

Addresses review feedback to deduplicate the helper. Also adds changeset.
@MarvelNwachukwu

Copy link
Copy Markdown
Contributor Author

@claude review

@claude

claude Bot commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MarvelNwachukwu's task —— View job


Code Review

I've reviewed the PR that fixes streaming JSON parse crashes in OpenAI and Anthropic LLMs. Here's my comprehensive analysis:

Todo List:

  • Read and analyze the core implementation changes
  • Review the test coverage and quality
  • Check for potential issues or improvements
  • Provide comprehensive review feedback

Overall Assessment: Strong Fix with Good Implementation

This PR effectively addresses a critical bug where malformed JSON in streaming tool call arguments could crash async generators. The implementation is solid and well-tested.

🎯 What This Fixes

  • Problem: Bare JSON.parse calls in streaming responses could throw SyntaxError when APIs return malformed JSON (truncated streams, network issues)
  • Impact: Crashes destroyed entire async generators, losing accumulated state (text, tool calls, usage metadata)
  • Solution: safeParseToolArgs helper that gracefully handles parse failures and falls back to empty args

Implementation Strengths

1. Smart Shared Utility Approach

The PR correctly extracted safeParseToolArgs into packages/adk/src/models/llm-utils.ts, avoiding code duplication across OpenAI/Anthropic implementations. This addresses the earlier review feedback perfectly.

2. Robust Error Handling

export function safeParseToolArgs(
  json: string | undefined,
  logger: Logger,
): Record<string, unknown> {
  try {
    return JSON.parse(json || "{}");
  } catch (error) {
    logger.warn("Failed to parse tool call arguments, using empty args", {
      rawArgs: json,
      error: String(error),
    });
    return {};
  }
}
  • Handles both undefined and malformed JSON gracefully
  • Provides useful logging with raw args and error details for debugging
  • Falls back to empty object {} allowing responses to continue yielding

3. Comprehensive Integration

  • Anthropic: Applied to streaming input_json_delta parsing (anthropic-llm.ts:395)
  • OpenAI: Applied to all tool call argument parsing locations (openai-llm.ts:327, openai-llm.ts:428, openai-llm.ts:455)
  • Maintains existing functionality while adding crash protection

Test Coverage Excellence

Anthropic Test (anthropic-llm.test.ts)

  • Scenario: Streaming with truncated JSON '{"location": "Tok' (missing closing brace)
  • Verification: Generator completes without crashing, returns empty args {}
  • Realistic: Simulates actual network/API truncation issues

OpenAI Test (openai-llm.test.ts)

  • Scenario: openAiMessageToLlmResponse with malformed JSON '{"location": "Tok'
  • Verification: Returns LlmResponse with empty args instead of crashing
  • Coverage: Tests the utility function integration

🔍 Code Quality Observations

Good Practices

  • Type safety maintained with Record<string, unknown> return type
  • Logger integration for debugging malformed JSON cases
  • Backwards compatible - existing functionality unchanged
  • Clean imports and consistent code style

Error Resilience

The fallback strategy is well-designed:

  • Empty args {} allow tool calls to proceed with default behavior
  • Logging preserves debugging information without exposing sensitive data
  • Async generators continue yielding, preserving partial responses

📋 Minor Suggestions

  1. Consider Enhanced Logging Context: The logging is good, but could optionally include tool name/id for better debugging:

    logger.warn("Failed to parse tool call arguments", {
      toolId: toolId, // if available in context
      toolName: toolName, // if available in context  
      rawArgs: json,
      error: String(error),
    });
  2. Documentation: The JSDoc comment is clear. Consider adding a usage example in the comment.

Performance & Security

  • Performance: Minimal overhead - try/catch only on parse failures
  • Security: Safe handling of potentially malicious/malformed input
  • Memory: No memory leaks - proper error cleanup

🚀 Deployment Readiness

  • Addresses critical crash scenario
  • Maintains backward compatibility
  • Comprehensive test coverage
  • Follows existing code patterns
  • Proper changeset documentation
  • No breaking changes

🎯 Verdict: LGTM - Ready to Merge

This is a well-implemented bug fix that:

  1. Solves a real production stability issue
  2. Uses proper error handling patterns
  3. Includes thorough test coverage
  4. Follows the codebase's architectural patterns
  5. Maintains full backward compatibility

The shared utility approach is exactly the right architectural decision, and the fallback to empty args is a sensible strategy that allows streaming to continue gracefully.

Recommendation: ✅ Approve and merge

@MarvelNwachukwu MarvelNwachukwu requested a review from Timonwa March 20, 2026 00:39

@Timonwa Timonwa 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.

Nice work @MarvelNwachukwu! Since safeParseToolArgs is now in a shared llm-utils.ts module, should it also be applied to anthropic-llm.ts and other LLM providers, or is OpenAI the only one that can send malformed JSON during streaming?

Timonwa

This comment was marked as duplicate.

@Timonwa Timonwa self-requested a review March 23, 2026 13:27
@MarvelNwachukwu

Copy link
Copy Markdown
Contributor Author

Thanks @Timonwa! Good question — yes, safeParseToolArgs is already applied to both anthropic-llm.ts and openai-llm.ts. Both files import from the shared llm-utils.ts module. The google-llm.ts provider doesn't have bare JSON.parse calls on streaming tool args, so it didn't need the fix. No remaining unprotected parse calls across any model file.

@MarvelNwachukwu MarvelNwachukwu merged commit baa133e into main Mar 25, 2026
6 checks passed
@Timonwa

Timonwa commented Mar 27, 2026

Copy link
Copy Markdown
Contributor

cool.

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.

Streaming tool calls crash on malformed JSON arguments

2 participants