diff --git a/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.test.ts b/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.test.ts index af95c5acf..83fc1a8e9 100644 --- a/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.test.ts +++ b/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.test.ts @@ -246,6 +246,32 @@ describe('AnthropicContentGenerator', () => { expect(headers['anthropic-beta']).toBe('experimental-x'); }); + it('honors customHeaders[anthropic-beta] under mixed-case keys (Anthropic-Beta / ANTHROPIC-BETA)', async () => { + // HTTP header names are case-insensitive; Anthropic SDK lower-cases + // headers when merging. Make sure our merge logic also matches + // case-insensitively so the user-configured beta flag isn't silently + // overwritten by the per-request value. + const headersUpper = await callOnce({ + ...baseConfig, + reasoning: { effort: 'medium' }, + customHeaders: { 'ANTHROPIC-BETA': 'experimental-x' }, + }); + expect(headersUpper['anthropic-beta']).toContain('experimental-x'); + expect(headersUpper['anthropic-beta']).toContain( + 'interleaved-thinking-2025-05-14', + ); + + const headersTitle = await callOnce({ + ...baseConfig, + reasoning: { effort: 'medium' }, + customHeaders: { 'Anthropic-Beta': 'experimental-y' }, + }); + expect(headersTitle['anthropic-beta']).toContain('experimental-y'); + expect(headersTitle['anthropic-beta']).toContain( + 'interleaved-thinking-2025-05-14', + ); + }); + it('dedupes beta flags so duplicates from customHeaders are not repeated', async () => { const headers = await callOnce({ ...baseConfig, diff --git a/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.ts b/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.ts index e08c88093..1e3b498b9 100644 --- a/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.ts +++ b/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.ts @@ -208,20 +208,17 @@ export class AnthropicContentGenerator implements ContentGenerator { * * User-supplied `customHeaders['anthropic-beta']` flags are merged in (and * deduped) so the per-request override doesn't wipe out the existing - * customHeaders escape hatch for unrelated beta features. + * customHeaders escape hatch for unrelated beta features. The lookup is + * case-insensitive — HTTP header names are case-insensitive by spec, so a + * user-configured `Anthropic-Beta` or `ANTHROPIC-BETA` is honored too. */ private buildPerRequestHeaders( anthropicRequest: MessageCreateParamsWithThinking, ): Record | undefined { const betas: string[] = []; - const userBeta = - this.contentGeneratorConfig.customHeaders?.['anthropic-beta']; - if (typeof userBeta === 'string' && userBeta) { - for (const flag of userBeta.split(',')) { - const trimmed = flag.trim(); - if (trimmed) betas.push(trimmed); - } + for (const flag of this.collectCustomBetaFlags()) { + betas.push(flag); } if (anthropicRequest.thinking) { @@ -236,6 +233,28 @@ export class AnthropicContentGenerator implements ContentGenerator { return { 'anthropic-beta': unique.join(',') }; } + /** + * Read every customHeaders entry whose key (case-insensitively) is + * `anthropic-beta` and yield the comma-separated flags from each. Multiple + * matching entries are concatenated; later ones may produce duplicates + * which the caller dedupes. + */ + private collectCustomBetaFlags(): string[] { + const customHeaders = this.contentGeneratorConfig.customHeaders; + if (!customHeaders) return []; + + const flags: string[] = []; + for (const [key, value] of Object.entries(customHeaders)) { + if (key.toLowerCase() !== 'anthropic-beta') continue; + if (typeof value !== 'string' || !value) continue; + for (const flag of value.split(',')) { + const trimmed = flag.trim(); + if (trimmed) flags.push(trimmed); + } + } + return flags; + } + private async buildRequest( request: GenerateContentParameters, ): Promise {