66 generateId ,
77 getAnthropicApiKeyFromEnv ,
88} from '../utils'
9+ import { ANTHROPIC_STRUCTURED_OUTPUT_MODELS } from '../model-meta'
910import type {
1011 ANTHROPIC_MODELS ,
1112 AnthropicChatModelProviderOptionsByName ,
@@ -21,6 +22,7 @@ import type {
2122 DocumentBlockParam ,
2223 ImageBlockParam ,
2324 MessageParam ,
25+ RawMessageStreamEvent ,
2426 TextBlockParam ,
2527 URLImageSource ,
2628 URLPDFSource ,
@@ -121,7 +123,7 @@ export class AnthropicTextAdapter<
121123 try {
122124 const requestParams = this . mapCommonOptionsToAnthropic ( options )
123125
124- const stream = await this . client . beta . messages . create (
126+ const stream = await this . client . messages . create (
125127 { ...requestParams , stream : true } ,
126128 {
127129 signal : options . request ?. signal ,
@@ -147,20 +149,85 @@ export class AnthropicTextAdapter<
147149 }
148150
149151 /**
150- * Generate structured output using Anthropic's tool-based approach.
151- * Anthropic doesn't have native structured output, so we use a tool with the schema
152- * and force the model to call it.
153- * The outputSchema is already JSON Schema (converted in the ai layer).
152+ * Generate structured output.
153+ * Uses Anthropic's native `output_config` with `json_schema` for Claude 4+ models.
154+ * Falls back to a tool-use workaround for older models that lack native support.
154155 */
155156 async structuredOutput (
156157 options : StructuredOutputOptions < AnthropicTextProviderOptions > ,
157158 ) : Promise < StructuredOutputResult < unknown > > {
158159 const { chatOptions, outputSchema } = options
159-
160160 const requestParams = this . mapCommonOptionsToAnthropic ( chatOptions )
161161
162- // Create a tool that will capture the structured output
163- // Anthropic's SDK requires input_schema with type: 'object' literal
162+ if ( ANTHROPIC_STRUCTURED_OUTPUT_MODELS . has ( chatOptions . model ) ) {
163+ return this . nativeStructuredOutput ( requestParams , chatOptions , outputSchema )
164+ }
165+
166+ return this . toolBasedStructuredOutput ( requestParams , chatOptions , outputSchema )
167+ }
168+
169+ /**
170+ * Native structured output using `output_config.format` with `json_schema`.
171+ * Supported by Claude 4+ models.
172+ */
173+ private async nativeStructuredOutput (
174+ requestParams : InternalTextProviderOptions ,
175+ chatOptions : StructuredOutputOptions < AnthropicTextProviderOptions > [ 'chatOptions' ] ,
176+ outputSchema : StructuredOutputOptions < AnthropicTextProviderOptions > [ 'outputSchema' ] ,
177+ ) : Promise < StructuredOutputResult < unknown > > {
178+ const createParams = {
179+ ...requestParams ,
180+ stream : false as const ,
181+ output_config : {
182+ format : {
183+ type : 'json_schema' as const ,
184+ schema : outputSchema ,
185+ } ,
186+ } ,
187+ }
188+
189+ try {
190+ const response = await this . client . messages . create ( createParams , {
191+ signal : chatOptions . request ?. signal ,
192+ headers : chatOptions . request ?. headers ,
193+ } )
194+
195+ const rawText = response . content
196+ . map ( ( b ) => {
197+ if ( b . type === 'text' ) {
198+ return b . text
199+ }
200+ return ''
201+ } )
202+ . join ( '' )
203+
204+ let parsed : unknown
205+ try {
206+ parsed = JSON . parse ( rawText )
207+ } catch {
208+ throw new Error (
209+ `Failed to parse structured output JSON. Content: ${ rawText . slice ( 0 , 200 ) } ${ rawText . length > 200 ? '...' : '' } ` ,
210+ )
211+ }
212+
213+ return { data : parsed , rawText }
214+ } catch ( error : unknown ) {
215+ const err = error as Error
216+ throw new Error (
217+ `Structured output generation failed: ${ err . message || 'Unknown error occurred' } ` ,
218+ )
219+ }
220+ }
221+
222+ /**
223+ * Tool-based structured output fallback for older models (Claude 3.x).
224+ * Creates a tool with the output schema and forces the model to call it.
225+ */
226+ private async toolBasedStructuredOutput (
227+ requestParams : InternalTextProviderOptions ,
228+ chatOptions : StructuredOutputOptions < AnthropicTextProviderOptions > [ 'chatOptions' ] ,
229+ outputSchema : StructuredOutputOptions < AnthropicTextProviderOptions > [ 'outputSchema' ] ,
230+ ) : Promise < StructuredOutputResult < unknown > > {
164231 const structuredOutputTool = {
165232 name : 'structured_output' ,
166233 description :
@@ -173,7 +240,6 @@ export class AnthropicTextAdapter<
173240 }
174241
175242 try {
176- // Make non-streaming request with tool_choice forced to our structured output tool
177243 const response = await this . client . messages . create (
178244 {
179245 ...requestParams ,
@@ -187,7 +253,6 @@ export class AnthropicTextAdapter<
187253 } ,
188254 )
189255
190- // Extract the tool use content from the response
191256 let parsed : unknown = null
192257 let rawText = ''
193258
@@ -200,7 +265,6 @@ export class AnthropicTextAdapter<
200265 }
201266
202267 if ( parsed === null ) {
203- // Fallback: try to extract text content and parse as JSON
204268 rawText = response . content
205269 . map ( ( b ) => {
206270 if ( b . type === 'text' ) {
@@ -218,10 +282,7 @@ export class AnthropicTextAdapter<
218282 }
219283 }
220284
221- return {
222- data : parsed ,
223- rawText,
224- }
285+ return { data : parsed , rawText }
225286 } catch ( error : unknown ) {
226287 const err = error as Error
227288 throw new Error (
@@ -454,7 +515,7 @@ export class AnthropicTextAdapter<
454515 }
455516
456517 private async * processAnthropicStream (
457- stream : AsyncIterable < Anthropic_SDK . Beta . BetaRawMessageStreamEvent > ,
518+ stream : AsyncIterable < RawMessageStreamEvent > ,
458519 model : string ,
459520 genId : ( ) => string ,
460521 ) : AsyncIterable < StreamChunk > {
0 commit comments