Route provider-executed tool results into the assistant message in Prompt.fromResponseParts - #6836
Conversation
…ompt.fromResponseParts Ports Effect-TS#5944 to the v4 line: Prompt.ToolResultPart gains providerExecuted (decoding default false) and fromResponseParts keeps provider-executed results beside their calls in the assistant message. Round-tripping a conversation containing a provider web_search no longer sends an orphaned function_call_output for a ws_ call id, which OpenAI rejects with HTTP 400 "No tool call found for function call output with call_id ws_...". Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 7bf381a The changes in this PR will be included in the next version bump. This PR includes changesets to release 30 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 |
There was a problem hiding this comment.
ℹ️ One minor doc suggestion inline — otherwise looks solid.
Reviewed changes
ToolResultPartgainsproviderExecutedfield with decoding defaultfalse, matching the existingToolCallPartpatternfromResponsePartsroutes provider-executed results into the assistant message so they stay paired with theirToolCallPart, which unbreaks round-trips where the provider had dropped the call- Mechanical
providerExecuted: falseadditions at all framework construction sites and test fixtures
All tests pass (Prompt.test.ts 16/16, LanguageModel.test.ts 33/33, LanguageModelTrackerLifecycle.test.ts 6/6, ResponseIdTracker.test.ts 24/24).
Note: 1 inline comment(s) dropped because they did not anchor to lines inside the PR diff:
packages/effect/src/unstable/ai/Prompt.ts:2003(RIGHT) — line 2003 (RIGHT) is not inside a diff hunk
DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏
Bundle Size AnalysisGenerated from PR build output; treat the content below as untrusted.
|
…assistant message A history built via Prompt.fromResponseParts containing a provider-executed web_search call/result pair serializes to server_tool_use and web_search_tool_result blocks in the assistant turn, with no client tool_result block emitted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Added Anthropic serialization test for provider-executed
web_searchparts — validates thatPrompt.fromResponseParts-routed provider-executed tool results serialize toserver_tool_use/web_search_tool_resultblocks in the assistant message (nottool_resultblocks), end-to-end throughLanguageModel.generateText
DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

Summary
Ports #5944 to the v4 line.
Prompt.fromResponsePartsplaces provider-executed tool results (e.g. OpenAIweb_search) in atoolmessage like framework results, so round-tripping a conversation sends them back to OpenAI asfunction_call_outputitems whose correspondingweb_search_callwas dropped from the request (prepareMessagesskips provider-executed tool calls). OpenAI rejects the request:The first turn with a provider web search succeeds; every follow-up turn that round-trips the history fails, deterministically. This is exactly #5939, which #5944 fixed on the v3 line (
@effect/ai) -- neither of that PR's two changes exists in the v4 rewrite.Changes
Prompt.ToolResultPart/ToolResultPartEncoded/ schema gainproviderExecuted(decoding defaultfalse), mirroringToolCallPart.fromResponsePartsroutes tool results withproviderExecuted: trueintoassistantParts, keeping the provider call/result pair together in the assistant message. Framework results still formtoolmessages.@effect/ai-openai'sprepareMessagesassistant branch has a tool-result case ("Assistant tool-result parts are always provider executed") that skips provider results or emitsitem_referenceunderstore: true-- it was dead code until now, sincefromResponsePartsnever delivered results to it.LanguageModel.ts(providerExecuted: false), the JSDoc examples, and existing test fixtures.fromResponsePartskeeps a provider-executed call/result pair in the assistant message while a framework result still lands in the tool message; and on the Anthropic side, a history built viafromResponsePartscontaining a provider-executedweb_searchpair serializes toserver_tool_use+web_search_tool_resultblocks in the assistant turn with no clienttool_resultblock (the assistant-branch serializer case this change activates)."effect": patch.Validation
pnpm test packages/effect/test/unstable/ai packages/ai/openai/test/OpenAiLanguageModel.test.ts packages/ai/openai-compat/test/OpenAiLanguageModel.test.ts packages/ai/anthropic-- 642/642 across 24 files, run at the branch tip.pnpm check-- clean.pnpm lint-fixapplied.Notes
The
store: true/item_referencepath still cannot engage for web search because the streamed and non-streamedweb_search_callparts carry nometadata.openai.itemId(unlike e.g. reasoning and apply_patch parts) andfromResponsePartsdrops response-part metadata for all part types. With this PR the pair is coherently omitted from the request, which unbreaks the round-trip; carrying item ids through foritem_referencereplay would be a follow-up.🤖 Generated with Claude Code