@@ -37,13 +37,43 @@ export const anthropicThinkingWithStructuredResponseMiddleware: LanguageModelV3M
3737 wrapGenerate : async ( { doGenerate} ) => {
3838 const result = await doGenerate ( ) ;
3939
40- // Extract the JSON tool call (conforming to the schema) and return it as text response.
40+ // Anthropic's Thinking mode cannot be used with forced tool calls. To work around this,
41+ // we use `toolChoice: 'auto'` in the `transformParams` above. However, this causes
42+ // the model to return a `finishReason` of `tool-calls` instead of `stop`.
43+ //
44+ // The Vercel AI SDK's high-level `generateText` (and `generateObject`) logic is strict:
45+ // it only attempts to parse structured output if the `finishReason` is exactly `stop`.
46+ // If it sees `tool-calls`, it assumes the conversation is ongoing (waiting for tool
47+ // execution) and returns an empty output, which triggers `AI_NoOutputGeneratedError`.
48+ //
49+ // This fixes this by:
50+ // 1. Finding the optional 'json' tool call.
51+ // 2. Extracting its raw JSON input and injecting it as a standard text response.
52+ // 3. Manually overriding the `finishReason` to `stop`.
53+ // 4. Removing the tool call metadata so the SDK doesn't expect a tool result.
54+ const newContent : typeof result . content = [ ] ;
55+ let jsonToolCallFound = false ;
56+
4157 for ( const r of result . content ) {
4258 if ( r . type === 'tool-call' && r . toolName === 'json' ) {
43- result . content . push ( { type : 'text' , text : r . input } ) ;
59+ newContent . push ( { type : 'text' , text : r . input } ) ;
60+ jsonToolCallFound = true ;
61+ } else {
62+ newContent . push ( r ) ;
4463 }
4564 }
4665
66+ if ( jsonToolCallFound ) {
67+ result . content = newContent ;
68+
69+ // We override the finish reason to 'stop' to allow the AI SDK to parse the
70+ // text content as a structured object.
71+ result . finishReason = {
72+ unified : 'stop' ,
73+ raw : result . finishReason . raw ,
74+ } ;
75+ }
76+
4777 return result ;
4878 } ,
4979} ;
0 commit comments