fix: unwrap Anthropic $PARAMETER_NAME wrapper in tool responses (fixes #1986)#2026
fix: unwrap Anthropic $PARAMETER_NAME wrapper in tool responses (fixes #1986)#2026kagura-agent wants to merge 6 commits into
Conversation
🦋 Changeset detectedLatest commit: 9ad7d1c The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
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 |
|
This PR is from an external contributor and must be approved by a stagehand team member with write access before CI can run. |
There was a problem hiding this comment.
1 issue found across 4 files
Confidence score: 3/5
- A recoverable wrapper-unwrapping path in
packages/core/lib/v3/llm/aisdk.tscan emit both error and success LLM response events for the same requestId, which may confuse flow records and downstream consumers. - Given the medium severity and clear behavioral change in event logging, there is some user-impacting risk despite being limited in scope.
- Pay close attention to
packages/core/lib/v3/llm/aisdk.ts- duplicate error/success logging for a single requestId.
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="packages/core/lib/v3/llm/aisdk.ts">
<violation number="1" location="packages/core/lib/v3/llm/aisdk.ts:310">
P2: Recoverable wrapper-unwrapping now logs two LLM response events (error then success) for the same requestId, creating conflicting flow records.</violation>
</file>
Architecture diagram
sequenceDiagram
participant App as Stagehand / App
participant AISDK as AISDK Wrapper (aisdk.ts)
participant Anthropic as AnthropicClient
participant Tool as unwrapToolResponse Utility
participant Schema as Zod Schema
participant API as Anthropic API
participant Logger as FlowLogger
Note over App, API: Primary Flow: Proactive Unwrapping in Anthropic Client
App->>Anthropic: createChatCompletion(options)
Anthropic->>API: Send Request (tool_use)
API-->>Anthropic: Response with {$PARAM: {...}}
Anthropic->>Anthropic: Find tool_use content
Anthropic->>Tool: NEW: unwrapToolResponse(toolUse.input)
Note right of Tool: Strips single keys starting with "$"
Tool-->>Anthropic: Unwrapped data object
Anthropic-->>App: Return cleaned response
Note over App, API: Secondary Flow: Recovery Path in AISDK Wrapper
App->>AISDK: act() / extract()
AISDK->>AISDK: generateObject()
alt CHANGED: AI_NoObjectGeneratedError caught
AISDK->>Tool: NEW: unwrapToolResponse(parsedErrorText)
alt Successful Unwrap
AISDK->>Schema: NEW: Re-validate unwrapped data
alt Schema Valid
AISDK->>Logger: NEW: logLlmResponse (Recovery)
AISDK-->>App: Return Validated Data
else Schema Still Invalid
AISDK-->>App: Throw original NoObjectGeneratedError
end
else No "$" wrapper found
AISDK-->>App: Throw original NoObjectGeneratedError
end
else Success path
AISDK-->>App: Return Data
end
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
|
couple fixes needed but otherwise great fix, thanks for working on this! |
… on recovery When wrapper-unwrapping recovery succeeds, only the success log is now emitted. The error log is deferred to after the recovery attempt, so it only fires when the error is actually thrown.
Per reviewer feedback, inline the utility into AnthropicClient.ts instead of keeping it as a standalone file. Update imports in aisdk.ts and the unit test accordingly.
|
Done! Moved |
|
The latest approval by @pirate could not refresh the mirrored PR automatically (missing-previous-source). The external PR stays open, and the mirrored PR should be updated manually before work continues. |
|
The CI failures appear to be pre-existing infrastructure issues unrelated to this change:
All code-related checks pass: Build ✅, Lint ✅, CLI Tests ✅, and all core unit tests ✅ (including the new |
|
Second ping — this has been open for 11 days. Is there anything I should adjust? Happy to iterate on the approach or close if it's not needed. 🙏 |
|
Closing this as it's been 13 days with no maintainer response after two pings. Happy to reopen or recreate if this fix is still desired — just let me know. Thanks! 🙏 |
|
Following through on my earlier comment — closing this PR as it's been inactive for too long. Happy to reopen if the fix is still desired. |
|
I re-opened it because the issue is valid still, please dont re-close it. |
Problem
When using Anthropic models (e.g.,
anthropic/claude-sonnet-4-5) withact()orextract(), Claude wraps tool_use output in a{ $PARAMETER_NAME: { ... } }envelope. This causes Zod schema validation to fail withAI_NoObjectGeneratedErrorbecause all expected fields appear undefined at the top level.Reported in #1986.
Fix
Add a small
unwrapToolResponse()helper that detects single-key objects with a$-prefixed key and strips the wrapper:AnthropicClient.ts— unwraptoolUse.inputbefore returning to callersaisdk.ts— catchNoObjectGeneratedError, attempt to JSON-parseerr.text, unwrap and re-validate against the schema. If recovery succeeds, return normally; if not, throw the original errorThe helper is defensive: it only unwraps when the object has exactly one
$-prefixed key, so non-Anthropic responses pass through unchanged.Changes
unwrapToolResponse.ts$PARAMETER_NAMEwrapperAnthropicClient.tsunwrapToolResponse()totoolUse.inputaisdk.tsNoObjectGeneratedErrorcatch blockunwrapToolResponse.test.tsTesting
Summary by cubic
Fixes schema validation failures for Anthropic tool responses by unwrapping the
$PARAMETER_NAMEenvelope soact()/extract()return a flat object for models likeanthropic/claude-sonnet-4-5. Defers error logging to avoid duplicate LLM response logs when recovery succeeds (fixes #1986).unwrapToolResponse()inAnthropicClient.tsto strip single$-prefixed wrappers; no-op otherwise.toolUse.input; inaisdkadded recovery to JSON-parse error text, unwrap, re-validate, and return; deferred error logging so only the success log emits on recovery.Written for commit 9ad7d1c. Summary will update on new commits. Review in cubic