-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Fix/anthropic beta header merge #10158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Fix/anthropic beta header merge #10158
Conversation
| // Check if the key is mergeable (case-insensitive) and already exists | ||
| if (mergeableLowerCaseKeys.includes(key.toLowerCase()) && result[key]) { | ||
| const existingValues = result[key].split(',').map(val => val.trim()); | ||
| const newValues = value.split(',').map(val => val.trim()); | ||
| // Combine and deduplicate values | ||
| const allValues = Array.from(new Set([...existingValues, ...newValues])); | ||
| result[key] = allValues.join(','); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // Check if the key is mergeable (case-insensitive) and already exists | |
| if (mergeableLowerCaseKeys.includes(key.toLowerCase()) && result[key]) { | |
| const existingValues = result[key].split(',').map(val => val.trim()); | |
| const newValues = value.split(',').map(val => val.trim()); | |
| // Combine and deduplicate values | |
| const allValues = Array.from(new Set([...existingValues, ...newValues])); | |
| result[key] = allValues.join(','); | |
| // Check if the key is mergeable (case-insensitive) | |
| if (mergeableLowerCaseKeys.includes(key.toLowerCase())) { | |
| // Find existing key with case-insensitive comparison | |
| const existingKey = Object.keys(result).find( | |
| k => k.toLowerCase() === key.toLowerCase(), | |
| ); | |
| if (existingKey && result[existingKey]) { | |
| const existingValues = result[existingKey]!.split(',').map(val => val.trim()); | |
| const newValues = value.split(',').map(val => val.trim()); | |
| // Combine and deduplicate values | |
| const allValues = Array.from(new Set([...existingValues, ...newValues])); | |
| result[existingKey] = allValues.join(','); | |
| } else { | |
| result[key] = value; | |
| } |
The case-sensitivity check for mergeable headers is incomplete. The code checks if a key is mergeable using case-insensitive comparison (key.toLowerCase()) but then checks if the key exists in the result using exact casing (result[key]), which will fail to find existing keys with different casing.
View Details
Analysis
Case-insensitive header merging fails with different casing
What fails: combineAndMergeMergeableHeaders() in packages/provider-utils/src/combine-headers.ts fails to merge headers when the same logical header is provided with different casing (e.g., 'Anthropic-Beta' and 'anthropic-beta').
How to reproduce:
const result = combineAndMergeMergeableHeaders({
headers: [
{ 'Anthropic-Beta': 'value1' },
{ 'anthropic-beta': 'value2' }
],
mergeableKeys: ['anthropic-beta']
});Result: Returns object with both keys present:
{
'Anthropic-Beta': 'value1',
'anthropic-beta': 'value2'
}Expected: Single merged header:
{
'Anthropic-Beta': 'value1,value2'
}Root cause: Line 32 checked if a mergeable key exists using case-insensitive comparison (key.toLowerCase()) but then searched for it in the result object using exact case matching (result[key]). This caused case-mismatched headers to not be found and merged.
Impact: Users providing headers with different casing will end up with duplicate headers that may not be properly combined, potentially causing API request failures.
Background
See #10018 for more details:
While upgrading to AI SDK v6 beta, we noticed that our custom
anthropic-betaheader (context-1m-2025-08-07) is unexpectedly being removed when making requests to Anthropic.After some investigation, we believe this behavior is caused by how betas are inferred and how headers are combined internally.
AnthropicMessagesLanguageModelautomatically infers beta headers:ai/packages/anthropic/src/anthropic-messages-language-model.ts
Lines 318 to 332 in e81d017
Later, the headers are merged using
combineHeaders:ai/packages/anthropic/src/anthropic-messages-language-model.ts
Lines 397 to 401 in e81d017
ai/packages/provider-utils/src/combine-headers.ts
Lines 1 to 11 in e81d017
If multiple headers contain the
anthropic-betakey, only the last one wins.However, according to Anthropic’s API, multiple beta flags should be comma-separated.
Recently, a new inferred beta was introduced:
fine-grained-tool-streaming-2025-05-14#9853
As a result, our explicitly passed beta (e.g.
context-1m-2025-08-07) gets overwritten.Summary
Fixed an issue where custom anthropic-beta headers provided by users were being overwritten instead of merged with SDK-managed beta features (like
fine-grained-tool-streaming).
@ai-sdk/provider-utils: ImplementedcombineAndMergeMergeableHeaders()function that intelligently merges comma-separated header values for specified keys instead of overwriting them@ai-sdk/anthropic: Updated header combination logic to usecombineAndMergeMergeableHeaders()instead ofcombineHeaders(), ensuring customanthropic-betaheaders are properly merged with SDK-managed beta featuresSpecial thanks to @AVtheking for providing the implementation direction and sample code in the comment.
Manual Verification
Added a unit test to ensure that when a custom
anthropic-betaheader withcontext-1m-2025-08-07is provided, triggeringfine-grained-tool-streaming-2025-05-14does not cause thecontext-1m-2025-08-07value to be lost.Checklist
pnpm changesetin the project root)Future Work
anthropic-beta.Related Issues
Fixes #10018