Skip to content

Conversation

@chentsulin
Copy link

@chentsulin chentsulin commented Nov 11, 2025

Background

See #10018 for more details:

While upgrading to AI SDK v6 beta, we noticed that our custom anthropic-beta header (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.

AnthropicMessagesLanguageModel automatically infers beta headers:

if (
anthropicOptions?.mcpServers &&
anthropicOptions.mcpServers.length > 0
) {
betas.add('mcp-client-2025-04-04');
}
if (
anthropicOptions?.container &&
anthropicOptions.container.skills &&
anthropicOptions.container.skills.length > 0
) {
betas.add('code-execution-2025-08-25');
betas.add('skills-2025-10-02');
betas.add('files-api-2025-04-14');

Later, the headers are merged using combineHeaders:

return combineHeaders(
await resolve(this.config.headers),
betas.size > 0 ? { 'anthropic-beta': Array.from(betas).join(',') } : {},
headers,
);

export function combineHeaders(
...headers: Array<Record<string, string | undefined> | undefined>
): Record<string, string | undefined> {
return headers.reduce(
(combinedHeaders, currentHeaders) => ({
...combinedHeaders,
...(currentHeaders ?? {}),
}),
{},
) as Record<string, string | undefined>;
}

If multiple headers contain the anthropic-beta key, 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: Implemented combineAndMergeMergeableHeaders() function that intelligently merges comma-separated header values for specified keys instead of overwriting them
  • @ai-sdk/anthropic: Updated header combination logic to use combineAndMergeMergeableHeaders() instead of combineHeaders(), ensuring custom anthropic-beta headers are properly merged with SDK-managed beta features

Special 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-beta header with context-1m-2025-08-07 is provided, triggering fine-grained-tool-streaming-2025-05-14 does not cause the context-1m-2025-08-07 value to be lost.

Checklist

  • Tests have been added / updated (for bug fixes / features)
  • Documentation has been added / updated (for bug fixes / features)
  • A patch changeset for relevant packages has been added (for bug fixes / features - run pnpm changeset in the project root)
  • I have reviewed this pull request (self-review)

Future Work

  • Check if there are any other headers that have the same overwriting issue as anthropic-beta.

Related Issues

Fixes #10018

Comment on lines +31 to +37
// 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(',');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Anthropic provider: custom anthropic-beta header dropped due to overwrite behavior

1 participant