@@ -169,6 +169,83 @@ 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 AI bridge proxies (e.g., Coder's
178+ * aibridge) normalize the response and set `citations: null` on all content blocks,
179+ * which causes validation errors like:
180+ * "Invalid input: expected array, received null"
181+ *
182+ * This wrapper intercepts the response, parses the JSON, removes null citations
183+ * fields from content blocks, and returns a fixed response.
184+ */
185+ function wrapFetchWithCitationsNullFix ( baseFetch : typeof fetch ) : typeof fetch {
186+ const fixingFetch = async (
187+ input : Parameters < typeof fetch > [ 0 ] ,
188+ init ?: Parameters < typeof fetch > [ 1 ]
189+ ) : Promise < Response > => {
190+ const response = await baseFetch ( input , init ) ;
191+
192+ // Only fix successful JSON responses
193+ const contentType = response . headers . get ( "content-type" ) ?? "" ;
194+ if ( ! response . ok || ! contentType . includes ( "application/json" ) ) {
195+ return response ;
196+ }
197+
198+ try {
199+ const json = ( await response . json ( ) ) as {
200+ content ?: Array < { citations ?: unknown } > ;
201+ } ;
202+
203+ // Fix citations: null in content blocks
204+ if ( Array . isArray ( json . content ) ) {
205+ let modified = false ;
206+ for ( const block of json . content ) {
207+ if (
208+ block &&
209+ typeof block === "object" &&
210+ "citations" in block &&
211+ block . citations === null
212+ ) {
213+ delete block . citations ;
214+ modified = true ;
215+ }
216+ }
217+
218+ if ( modified ) {
219+ const fixedBody = JSON . stringify ( json ) ;
220+ const fixedHeaders = new Headers ( response . headers ) ;
221+ fixedHeaders . set ( "content-length" , String ( fixedBody . length ) ) ;
222+ return new Response ( fixedBody , {
223+ status : response . status ,
224+ statusText : response . statusText ,
225+ headers : fixedHeaders ,
226+ } ) ;
227+ }
228+ }
229+
230+ // No fix needed, but we already consumed the body - reconstruct
231+ return new Response ( JSON . stringify ( json ) , {
232+ status : response . status ,
233+ statusText : response . statusText ,
234+ headers : response . headers ,
235+ } ) ;
236+ } catch {
237+ // Can't fix - response body was consumed but failed to parse
238+ // This shouldn't happen for valid JSON, but return a failed response
239+ return new Response ( null , {
240+ status : 500 ,
241+ statusText : "Failed to parse response JSON" ,
242+ } ) ;
243+ }
244+ } ;
245+
246+ return Object . assign ( fixingFetch , baseFetch ) as typeof fetch ;
247+ }
248+
172249/**
173250 * Get fetch function for provider - use custom if provided, otherwise unlimited timeout default
174251 */
@@ -429,11 +506,13 @@ export class AIService extends EventEmitter {
429506
430507 // Lazy-load Anthropic provider to reduce startup time
431508 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)
434509 // Use getProviderFetch to preserve any user-configured custom fetch (e.g., proxies)
510+ // Then chain wrappers:
511+ // 1. citationsFix: removes `citations: null` from API proxy responses (breaks SDK validation)
512+ // 2. cacheControl: injects cache_control on tools/messages (SDK doesn't translate providerOptions)
435513 const baseFetch = getProviderFetch ( providerConfig ) ;
436- const fetchWithCacheControl = wrapFetchWithAnthropicCacheControl ( baseFetch ) ;
514+ const fetchWithCitationsFix = wrapFetchWithCitationsNullFix ( baseFetch ) ;
515+ const fetchWithCacheControl = wrapFetchWithAnthropicCacheControl ( fetchWithCitationsFix ) ;
437516 const provider = createAnthropic ( {
438517 ...normalizedConfig ,
439518 headers,
0 commit comments