mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-15 09:41:20 +00:00
fix(core): case-insensitive customHeaders[anthropic-beta] merge
Address yiliang114 review feedback (#3788). HTTP header names are case-insensitive by spec, and the Anthropic SDK lower-cases them during merge. Previously buildPerRequestHeaders only read the lower-case `anthropic-beta` key from customHeaders, so a user-configured `Anthropic-Beta` or `ANTHROPIC-BETA` would be silently overwritten by the per-request computed value. Replace the direct dict lookup with collectCustomBetaFlags() which walks all customHeaders entries and matches the key case-insensitively. Multiple matching entries (unlikely but possible) are concatenated; the existing dedupe pass handles any duplicates. Add a regression test for both `Anthropic-Beta` and `ANTHROPIC-BETA` key shapes. 73 tests pass; lint + typecheck clean.
This commit is contained in:
parent
4b81d674df
commit
0d8b5de18a
2 changed files with 53 additions and 8 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<string, string> | 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<MessageCreateParamsWithThinking> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue