fix: finish review-driven single-source cleanup

This commit is contained in:
Cita
2026-03-23 11:26:01 +08:00
parent 29c9fcbd29
commit 328b492ab3
21 changed files with 926 additions and 320 deletions
@@ -25,7 +25,7 @@ describe('provider header utils', () => {
).toBe('beta-a,beta-b,beta-c');
expect(
mergeClaudeBetaHeader('custom-a,custom-b', 'beta-a,beta-b', ['beta-c']),
).toBe('custom-a,custom-b');
).toBe('beta-a,beta-b,custom-a,custom-b,beta-c');
});
it('preserves gemini cli runtime metadata when rebuilding user agents', async () => {
@@ -79,9 +79,12 @@ describe('provider header utils', () => {
const headers = buildClaudeRuntimeHeaders({
baseHeaders: {
'Content-Type': 'application/json',
authorization: 'Bearer stale-token',
},
claudeHeaders: {
'anthropic-beta': 'custom-beta',
'x-api-key': 'stale-api-key',
'user-agent': 'custom-agent',
},
anthropicVersion: '2023-06-01',
stream: true,
@@ -90,9 +93,13 @@ describe('provider header utils', () => {
});
expect(headers['anthropic-version']).toBe('2023-06-01');
expect(headers['anthropic-beta']).toBe('custom-beta');
expect(headers['anthropic-beta']).toContain('claude-code-20250219');
expect(headers['anthropic-beta']).toContain('custom-beta');
expect(headers.Authorization).toBe('Bearer oauth-token');
expect(headers.authorization).toBeUndefined();
expect(headers['x-api-key']).toBeUndefined();
expect(headers['user-agent']).toBeUndefined();
expect(headers['User-Agent']).toBe('custom-agent');
expect(headers.Accept).toBe('text/event-stream');
});
+37 -11
View File
@@ -32,6 +32,24 @@ export function getInputHeader(
return null;
}
function normalizeLowerCaseHeaderMap(
sources: Array<Record<string, unknown> | Record<string, string> | undefined>,
shouldSkip: (key: string) => boolean,
): Record<string, string> {
const normalized: Record<string, string> = {};
for (const source of sources) {
if (!source) continue;
for (const [key, value] of Object.entries(source)) {
const normalizedValue = headerValueToString(value);
if (!normalizedValue) continue;
const normalizedKey = key.toLowerCase();
if (shouldSkip(normalizedKey)) continue;
normalized[normalizedKey] = normalizedValue;
}
}
return normalized;
}
export function uuidFromSeed(seed: string): string {
const derived = pbkdf2Sync(seed, 'metapi-runtime-header-seed', 10_000, 16, 'sha256');
const bytes = new Uint8Array(derived);
@@ -52,17 +70,10 @@ export function mergeClaudeBetaHeader(
defaultValue: string,
extraBetas: string[] = [],
): string {
const source = explicitValue || defaultValue;
const seen = new Set<string>();
const merged: string[] = [];
for (const entry of source.split(',')) {
const normalized = entry.trim();
if (!normalized || seen.has(normalized)) continue;
seen.add(normalized);
merged.push(normalized);
}
if (!explicitValue) {
for (const entry of extraBetas) {
for (const source of [defaultValue, explicitValue ?? '', ...extraBetas]) {
for (const entry of source.split(',')) {
const normalized = entry.trim();
if (!normalized || seen.has(normalized)) continue;
seen.add(normalized);
@@ -187,9 +198,24 @@ export function buildClaudeRuntimeHeaders(input: {
input.defaultBetaHeader || CLAUDE_DEFAULT_BETA_HEADER,
input.extraBetas,
);
const passthroughHeaders = normalizeLowerCaseHeaderMap(
[input.baseHeaders, input.claudeHeaders],
(key) => (
key === 'accept'
|| key === 'accept-encoding'
|| key === 'anthropic-beta'
|| key === 'anthropic-dangerous-direct-browser-access'
|| key === 'anthropic-version'
|| key === 'authorization'
|| key === 'connection'
|| key === 'user-agent'
|| key === 'x-api-key'
|| key === 'x-app'
|| key.startsWith('x-stainless-')
),
);
const headers: Record<string, string> = {
...input.baseHeaders,
...input.claudeHeaders,
...passthroughHeaders,
'anthropic-version': input.anthropicVersion,
...(anthropicBeta ? { 'anthropic-beta': anthropicBeta } : {}),
'Anthropic-Dangerous-Direct-Browser-Access': 'true',