@@ -169,6 +169,82 @@ function wrapFetchWithAnthropicCacheControl(baseFetch: typeof fetch): typeof fet
169169 return Object . assign ( cachingFetch , baseFetch ) as typeof fetch ;
170170}
171171
172+ /**
173+ * Wrap fetch to fix Anthropic API responses from proxies that incorrectly
174+ * return `citations: null` instead of omitting the field or returning an array.
175+ *
176+ * The Vercel AI SDK's Anthropic provider has strict validation that expects
177+ * `citations` to be an array when present. Some API proxies normalize the response
178+ * and set `citations: null` on all content blocks, which causes validation errors:
179+ * "Invalid input: expected array, received null"
180+ *
181+ * This wrapper intercepts the response, parses the JSON, removes null citations
182+ * fields from content blocks, and returns a fixed response.
183+ */
184+ function wrapFetchWithCitationsNullFix ( baseFetch : typeof fetch ) : typeof fetch {
185+ const fixingFetch = async (
186+ input : Parameters < typeof fetch > [ 0 ] ,
187+ init ?: Parameters < typeof fetch > [ 1 ]
188+ ) : Promise < Response > => {
189+ const response = await baseFetch ( input , init ) ;
190+
191+ // Only fix successful JSON responses
192+ const contentType = response . headers . get ( "content-type" ) ?? "" ;
193+ if ( ! response . ok || ! contentType . includes ( "application/json" ) ) {
194+ return response ;
195+ }
196+
197+ try {
198+ const json = ( await response . json ( ) ) as {
199+ content ?: Array < { citations ?: unknown } > ;
200+ } ;
201+
202+ // Fix citations: null in content blocks
203+ if ( Array . isArray ( json . content ) ) {
204+ let modified = false ;
205+ for ( const block of json . content ) {
206+ if (
207+ block &&
208+ typeof block === "object" &&
209+ "citations" in block &&
210+ block . citations === null
211+ ) {
212+ delete block . citations ;
213+ modified = true ;
214+ }
215+ }
216+
217+ if ( modified ) {
218+ const fixedBody = JSON . stringify ( json ) ;
219+ const fixedHeaders = new Headers ( response . headers ) ;
220+ fixedHeaders . set ( "content-length" , String ( fixedBody . length ) ) ;
221+ return new Response ( fixedBody , {
222+ status : response . status ,
223+ statusText : response . statusText ,
224+ headers : fixedHeaders ,
225+ } ) ;
226+ }
227+ }
228+
229+ // No fix needed, but we already consumed the body - reconstruct
230+ return new Response ( JSON . stringify ( json ) , {
231+ status : response . status ,
232+ statusText : response . statusText ,
233+ headers : response . headers ,
234+ } ) ;
235+ } catch {
236+ // Can't fix - response body was consumed but failed to parse
237+ // This shouldn't happen for valid JSON, but return a failed response
238+ return new Response ( null , {
239+ status : 500 ,
240+ statusText : "Failed to parse response JSON" ,
241+ } ) ;
242+ }
243+ } ;
244+
245+ return Object . assign ( fixingFetch , baseFetch ) as typeof fetch ;
246+ }
247+
172248/**
173249 * Get fetch function for provider - use custom if provided, otherwise unlimited timeout default
174250 */
@@ -429,11 +505,13 @@ export class AIService extends EventEmitter {
429505
430506 // Lazy-load Anthropic provider to reduce startup time
431507 const { createAnthropic } = await PROVIDER_REGISTRY . anthropic ( ) ;
432- // Wrap fetch to inject cache_control on tools and messages
433- // (SDK doesn't translate providerOptions to cache_control for these)
434508 // Use getProviderFetch to preserve any user-configured custom fetch (e.g., proxies)
509+ // Then chain wrappers:
510+ // 1. citationsFix: removes `citations: null` from proxy responses (breaks SDK validation)
511+ // 2. cacheControl: injects cache_control on tools/messages (SDK doesn't translate providerOptions)
435512 const baseFetch = getProviderFetch ( providerConfig ) ;
436- const fetchWithCacheControl = wrapFetchWithAnthropicCacheControl ( baseFetch ) ;
513+ const fetchWithCitationsFix = wrapFetchWithCitationsNullFix ( baseFetch ) ;
514+ const fetchWithCacheControl = wrapFetchWithAnthropicCacheControl ( fetchWithCitationsFix ) ;
437515 const provider = createAnthropic ( {
438516 ...normalizedConfig ,
439517 headers,
0 commit comments