refactor: consolidate single-source routing and protocol helpers

This commit is contained in:
Cita
2026-03-22 21:27:09 +08:00
parent 96e7614835
commit 9290e51ecc
64 changed files with 3571 additions and 2681 deletions
+33 -51
View File
@@ -4,7 +4,6 @@ import { db, schema } from '../../db/index.js';
import {
ACCOUNT_TOKEN_VALUE_STATUS_MASKED_PENDING,
ACCOUNT_TOKEN_VALUE_STATUS_READY,
ensureDefaultTokenForAccount,
isMaskedPendingAccountToken,
isMaskedTokenValue,
isUsableAccountToken,
@@ -14,17 +13,17 @@ import {
repairDefaultToken,
resolveAccountTokenValueStatus,
setDefaultToken,
syncTokensFromUpstream,
} from '../../services/accountTokenService.js';
import { getAdapter } from '../../services/platforms/index.js';
import { getCredentialModeFromExtraConfig, getProxyUrlFromExtraConfig, resolvePlatformUserId } from '../../services/accountExtraConfig.js';
import { startBackgroundTask } from '../../services/backgroundTaskService.js';
import { withAccountProxyOverride } from '../../services/siteProxy.js';
import { type ModelRefreshResult } from '../../services/modelService.js';
import {
rebuildTokenRoutesFromAvailability,
refreshModelsForAccount,
type ModelRefreshResult,
} from '../../services/modelService.js';
type CoverageBatchRebuildResult,
convergeAccountMutation,
refreshAccountCoverageBatch,
} from '../../services/accountMutationWorkflow.js';
type AccountWithSiteRow = {
accounts: typeof schema.accounts.$inferSelect;
@@ -65,9 +64,7 @@ type CoverageRefreshFailureItem = {
};
type CoverageRefreshItem = ModelRefreshResult | CoverageRefreshFailureItem;
type CoverageRefreshRebuildResult =
| { success: true; result: Awaited<ReturnType<typeof rebuildTokenRoutesFromAvailability>> }
| { success: false; error: string };
type CoverageRefreshRebuildResult = CoverageBatchRebuildResult;
const TOKEN_SYNC_TIMEOUT_MS = 15_000;
const SYNC_ALL_BATCH_SIZE = 3;
@@ -231,7 +228,11 @@ async function executeAccountTokenSync(row: AccountWithSiteRow): Promise<SyncExe
if (!row.accounts.accessToken) {
if (row.accounts.apiToken) {
ensureDefaultTokenForAccount(accountId, row.accounts.apiToken, { name: 'default', source: 'legacy' });
await convergeAccountMutation({
accountId,
preferredApiToken: row.accounts.apiToken,
defaultTokenSource: 'legacy',
});
}
return {
...base,
@@ -296,7 +297,11 @@ async function executeAccountTokenSync(row: AccountWithSiteRow): Promise<SyncExe
};
}
const synced = await syncTokensFromUpstream(accountId, tokens);
const convergence = await convergeAccountMutation({
accountId,
upstreamTokens: tokens,
});
const synced = convergence.tokenSync!;
if ((synced.maskedPending || 0) > 0) {
return {
...base,
@@ -390,49 +395,26 @@ async function executeSyncAllAccountTokens() {
}
async function refreshCoverageForAccounts(accountIds: number[]) {
const uniqueAccountIds = Array.from(new Set(
accountIds.filter((id) => Number.isFinite(id) && id > 0),
));
const result = await refreshAccountCoverageBatch({
accountIds,
batchSize: SYNC_ALL_BATCH_SIZE,
mapFailure: buildCoverageRefreshFailureItem,
});
if (uniqueAccountIds.length === 0) {
return { refresh: [], rebuild: null };
result.refresh.forEach((item) => {
if ((item as CoverageRefreshFailureItem).reason === 'coverage_refresh_failed') {
const failed = item as CoverageRefreshFailureItem;
console.warn(`[account-tokens] coverage refresh failed for account ${failed.accountId}: ${failed.errorMessage}`);
}
});
if (result.rebuild && !result.rebuild.success) {
console.warn(`[account-tokens] token route rebuild failed after coverage refresh: ${result.rebuild.error}`);
}
const refresh: CoverageRefreshItem[] = [];
for (let offset = 0; offset < uniqueAccountIds.length; offset += SYNC_ALL_BATCH_SIZE) {
const batch = uniqueAccountIds.slice(offset, offset + SYNC_ALL_BATCH_SIZE);
const settled = await Promise.allSettled(
batch.map(async (accountId) => refreshModelsForAccount(accountId)),
);
settled.forEach((result, index) => {
if (result.status === 'fulfilled') {
refresh.push(result.value);
return;
}
const accountId = batch[index] || 0;
const errorMessage = result.reason instanceof Error ? result.reason.message : String(result.reason || 'coverage refresh failed');
refresh.push(buildCoverageRefreshFailureItem(accountId, errorMessage));
console.warn(`[account-tokens] coverage refresh failed for account ${accountId}: ${errorMessage}`);
});
}
let rebuild: CoverageRefreshRebuildResult | null = null;
try {
rebuild = {
success: true,
result: await rebuildTokenRoutesFromAvailability(),
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error || 'route rebuild failed');
rebuild = {
success: false,
error: errorMessage,
};
console.warn(`[account-tokens] token route rebuild failed after coverage refresh: ${errorMessage}`);
}
return { refresh, rebuild };
return {
refresh: result.refresh as CoverageRefreshItem[],
rebuild: result.rebuild as CoverageRefreshRebuildResult | null,
};
}
function buildCoverageRefreshFailureItem(
+45 -65
View File
@@ -3,8 +3,8 @@ import { db, schema, runtimeDbDialect } from '../../db/index.js';
import { and, eq, gte, lt, sql } from 'drizzle-orm';
import { refreshBalance } from '../../services/balanceService.js';
import { getAdapter } from '../../services/platforms/index.js';
import { refreshModelsForAccount, rebuildTokenRoutesFromAvailability } from '../../services/modelService.js';
import { ensureDefaultTokenForAccount, syncTokensFromUpstream } from '../../services/accountTokenService.js';
import { rebuildTokenRoutesFromAvailability } from '../../services/modelService.js';
import { convergeAccountMutation } from '../../services/accountMutationWorkflow.js';
import {
getCredentialModeFromExtraConfig,
getProxyUrlFromExtraConfig,
@@ -153,37 +153,29 @@ async function initializeAccountInBackground({
rebuiltRoutes: false,
};
if (tokenType === 'session' && apiToken) {
try {
await ensureDefaultTokenForAccount(accountId, apiToken, { name: 'default', source: 'manual' });
} catch {}
}
let fetchedUpstreamTokens: Array<{ name?: string | null; key?: string | null; enabled?: boolean | null; tokenGroup?: string | null }> = [];
if (tokenType === 'session' && accessToken) {
try {
const syncedTokens = await adapter.getApiTokens(site.url, accessToken, platformUserId);
summary.syncedTokenCount = Array.isArray(syncedTokens) ? syncedTokens.length : 0;
if (summary.syncedTokenCount > 0) {
await syncTokensFromUpstream(accountId, syncedTokens);
}
fetchedUpstreamTokens = Array.isArray(syncedTokens) ? syncedTokens : [];
} catch {}
}
if (tokenType === 'session') {
try {
await refreshBalance(accountId);
summary.refreshedBalance = true;
} catch {}
}
if (skipModelFetch !== true) {
try {
await refreshModelsForAccount(accountId);
summary.refreshedModels = true;
await rebuildTokenRoutesFromAvailability();
summary.rebuiltRoutes = true;
} catch {}
}
const convergence = await convergeAccountMutation({
accountId,
preferredApiToken: tokenType === 'session' ? apiToken : null,
defaultTokenSource: 'manual',
ensurePreferredTokenBeforeSync: tokenType === 'session',
upstreamTokens: fetchedUpstreamTokens,
refreshBalance: tokenType === 'session',
refreshModels: skipModelFetch !== true,
rebuildRoutes: skipModelFetch !== true,
continueOnError: true,
});
summary.refreshedBalance = convergence.refreshedBalance;
summary.refreshedModels = convergence.refreshedModels;
summary.rebuiltRoutes = convergence.rebuiltRoutes;
return summary;
}
@@ -612,22 +604,16 @@ export async function accountsRoutes(app: FastifyInstance) {
return { success: false, message: 'account create failed' };
}
if (apiTokens.length > 0) {
try {
await syncTokensFromUpstream(result.id, apiTokens);
} catch { }
} else if (preferredApiToken) {
try {
await ensureDefaultTokenForAccount(result.id, preferredApiToken, { name: 'default', source: 'sync' });
} catch { }
}
// Auto-refresh balance
try { await refreshBalance(result.id); } catch { }
try {
await refreshModelsForAccount(result.id);
await rebuildTokenRoutesFromAvailability();
} catch { }
await convergeAccountMutation({
accountId: result.id,
preferredApiToken,
defaultTokenSource: 'sync',
upstreamTokens: apiTokens,
refreshBalance: true,
refreshModels: true,
rebuildRoutes: true,
continueOnError: true,
});
const account = await db.select().from(schema.accounts).where(eq(schema.accounts.id, result.id)).get();
return {
@@ -1043,19 +1029,15 @@ export async function accountsRoutes(app: FastifyInstance) {
await db.update(schema.accounts).set(updates).where(eq(schema.accounts.id, accountId)).run();
if (nextApiToken) {
try {
await ensureDefaultTokenForAccount(accountId, nextApiToken, { name: 'default', source: 'sync' });
} catch {}
}
try {
await refreshBalance(accountId);
} catch {}
try {
await refreshModelsForAccount(accountId);
await rebuildTokenRoutesFromAvailability();
} catch {}
await convergeAccountMutation({
accountId,
preferredApiToken: nextApiToken,
defaultTokenSource: 'sync',
refreshBalance: true,
refreshModels: true,
rebuildRoutes: true,
continueOnError: true,
});
const latest = await db.select().from(schema.accounts).where(eq(schema.accounts.id, accountId)).get();
return {
@@ -1332,16 +1314,14 @@ export async function accountsRoutes(app: FastifyInstance) {
? explicitNextMode
: (hasSessionTokenValue(nextAccessToken) ? 'session' : 'apikey');
if (nextCredentialMode !== 'apikey' && typeof updates.apiToken === 'string' && updates.apiToken.trim()) {
try {
await ensureDefaultTokenForAccount(id, updates.apiToken, { name: 'default', source: 'manual' });
} catch { }
}
try {
await refreshModelsForAccount(id);
await rebuildTokenRoutesFromAvailability();
} catch { }
await convergeAccountMutation({
accountId: id,
preferredApiToken: nextCredentialMode !== 'apikey' ? updates.apiToken : null,
defaultTokenSource: 'manual',
refreshModels: true,
rebuildRoutes: true,
continueOnError: true,
});
return await db.select().from(schema.accounts).where(eq(schema.accounts.id, id)).get();
});
+9 -9
View File
@@ -243,11 +243,11 @@ describe('oauth routes', { timeout: 15_000 }, () => {
const parsed = JSON.parse(accounts[0]?.extraConfig || '{}');
expect(parsed.oauth).toMatchObject({
provider: 'antigravity',
email: 'antigravity-user@example.com',
projectId: 'antigravity-auto-project',
refreshToken: 'antigravity-refresh-token',
});
expect(parsed.oauth).not.toHaveProperty('provider');
expect(parsed.oauth).not.toHaveProperty('projectId');
expect(String(fetchMock.mock.calls[2]?.[0] || '')).toContain('/v1internal:loadCodeAssist');
expect(String(fetchMock.mock.calls[3]?.[0] || '')).toContain('/v1internal:onboardUser');
@@ -419,14 +419,15 @@ describe('oauth routes', { timeout: 15_000 }, () => {
expect(JSON.parse(accounts[0]?.extraConfig || '{}')).toMatchObject({
credentialMode: 'session',
oauth: {
provider: 'codex',
accountId: 'chatgpt-account-123',
email: 'codex-user@example.com',
planType: 'plus',
refreshToken: 'oauth-refresh-token',
idToken: jwt,
},
});
const codexStoredOauth = JSON.parse(accounts[0]?.extraConfig || '{}').oauth;
expect(codexStoredOauth).not.toHaveProperty('provider');
expect(codexStoredOauth).not.toHaveProperty('accountId');
const models = await db.select().from(schema.modelAvailability).all();
const modelNames = models.map((row) => row.modelName);
@@ -932,11 +933,11 @@ describe('oauth routes', { timeout: 15_000 }, () => {
const parsed = JSON.parse(accounts[0]?.extraConfig || '{}');
expect(parsed.oauth).toMatchObject({
provider: 'gemini-cli',
email: 'gemini-user@example.com',
projectId: 'first-project-id',
refreshToken: 'gemini-refresh-token',
});
expect(parsed.oauth).not.toHaveProperty('provider');
expect(parsed.oauth).not.toHaveProperty('projectId');
expect(String(fetchMock.mock.calls[1]?.[0] || '')).toContain('cloudresourcemanager.googleapis.com/v1/projects');
expect(String(fetchMock.mock.calls[2]?.[0] || '')).toContain('/v1internal:loadCodeAssist');
@@ -1048,8 +1049,9 @@ describe('oauth routes', { timeout: 15_000 }, () => {
const parsed = JSON.parse(accounts[0]?.extraConfig || '{}');
expect(parsed.oauth).toMatchObject({
projectId: 'gen-lang-client-0123456789',
refreshToken: 'gemini-refresh-token',
});
expect(parsed.oauth).not.toHaveProperty('projectId');
expect(String(fetchMock.mock.calls[2]?.[0] || '')).toContain('/v1internal:loadCodeAssist');
expect(String(fetchMock.mock.calls[3]?.[0] || '')).toContain('/v1internal:onboardUser');
@@ -1525,12 +1527,10 @@ describe('oauth routes', { timeout: 15_000 }, () => {
items: expect.arrayContaining([
expect.objectContaining({
provider: 'codex',
email: 'team-user@example.com',
accountKey: 'chatgpt-team-account-a',
}),
expect.objectContaining({
provider: 'codex',
email: 'team-user@example.com',
accountKey: 'chatgpt-team-account-b',
}),
]),
+8 -8
View File
@@ -9,12 +9,13 @@ import {
import { normalizeRouteRoutingStrategy } from '../../services/routeRoutingStrategy.js';
import { invalidateTokenRouterCache, matchesModelPattern, tokenRouter } from '../../services/tokenRouter.js';
import { startBackgroundTask } from '../../services/backgroundTaskService.js';
import {
clearRouteDecisionSnapshot,
clearRouteDecisionSnapshots,
parseRouteDecisionSnapshot,
saveRouteDecisionSnapshots,
} from '../../services/routeDecisionSnapshotStore.js';
import {
clearRouteDecisionSnapshot,
clearRouteDecisionSnapshots,
parseRouteDecisionSnapshot,
saveRouteDecisionSnapshots,
} from '../../services/routeDecisionSnapshotStore.js';
import { normalizeTokenRouteMode, type RouteMode } from '../../../shared/tokenRouteContract.js';
function isExactModelPattern(modelPattern: string): boolean {
const normalized = modelPattern.trim();
@@ -23,14 +24,13 @@ function isExactModelPattern(modelPattern: string): boolean {
return !/[\*\?]/.test(normalized);
}
type RouteMode = 'pattern' | 'explicit_group';
type RouteRow = typeof schema.tokenRoutes.$inferSelect & {
routeMode: RouteMode;
sourceRouteIds: number[];
};
function normalizeRouteMode(routeMode: unknown): RouteMode {
return routeMode === 'explicit_group' ? 'explicit_group' : 'pattern';
return normalizeTokenRouteMode(routeMode);
}
function isExplicitGroupRoute(route: Pick<RouteRow, 'routeMode'> | Pick<typeof schema.tokenRoutes.$inferSelect, 'routeMode'>): boolean {
@@ -2,7 +2,10 @@ import { extractClaudeCodeSessionId as extractClaudeCodeSessionIdViaProfile } fr
import {
detectCliProfile,
} from '../../proxy-core/cliProfiles/registry.js';
import { isCodexResponsesSurface as isCodexResponsesSurfaceViaProfile } from '../../proxy-core/cliProfiles/codexProfile.js';
import {
detectCodexOfficialClientApp as detectCodexOfficialClientAppViaProfile,
isCodexResponsesSurface as isCodexResponsesSurfaceViaProfile,
} from '../../proxy-core/cliProfiles/codexProfile.js';
import type { CliProfileId } from '../../proxy-core/cliProfiles/types.js';
export type DownstreamClientKind = CliProfileId;
@@ -43,58 +46,6 @@ type DownstreamProtocolClientApp = {
clientConfidence: DownstreamClientConfidence;
};
type HeaderPrefixMatcherRule = {
id: string;
name: string;
userAgentPrefixes?: string[];
originatorPrefixes?: string[];
};
const codexOfficialClientAppRules: HeaderPrefixMatcherRule[] = [
{
id: 'codex_cli_rs',
name: 'Codex CLI',
userAgentPrefixes: ['codex_cli_rs/'],
originatorPrefixes: ['codex_cli_rs'],
},
{
id: 'codex_vscode',
name: 'Codex VSCode',
userAgentPrefixes: ['codex_vscode/'],
originatorPrefixes: ['codex_vscode'],
},
{
id: 'codex_app',
name: 'Codex App',
userAgentPrefixes: ['codex_app/'],
originatorPrefixes: ['codex_app'],
},
{
id: 'codex_chatgpt_desktop',
name: 'Codex Desktop',
userAgentPrefixes: ['codex_chatgpt_desktop/', 'codex desktop/'],
originatorPrefixes: ['codex_chatgpt_desktop', 'codex desktop'],
},
{
id: 'codex_atlas',
name: 'Codex Atlas',
userAgentPrefixes: ['codex_atlas/'],
originatorPrefixes: ['codex_atlas'],
},
{
id: 'codex_exec',
name: 'Codex Exec',
userAgentPrefixes: ['codex_exec/'],
originatorPrefixes: ['codex_exec'],
},
{
id: 'codex_sdk_ts',
name: 'Codex SDK TS',
userAgentPrefixes: ['codex_sdk_ts/'],
originatorPrefixes: ['codex_sdk_ts'],
},
];
function isRecord(value: unknown): value is Record<string, unknown> {
return !!value && typeof value === 'object' && !Array.isArray(value);
}
@@ -138,10 +89,6 @@ function headerIncludes(headers: NormalizedClientHeaders, key: string, expectedF
return (headers[key.trim().toLowerCase()] || []).some((value) => value.trim().toLowerCase().includes(normalizedExpected));
}
function headerMatchesPrefixes(headers: NormalizedClientHeaders, key: string, prefixes: string[]): boolean {
return prefixes.some((prefix) => headerIncludes(headers, key, prefix));
}
function normalizeClientDisplayName(value: string): string | null {
const trimmed = value.trim();
if (!trimmed) return null;
@@ -276,25 +223,6 @@ function detectExplicitClientSelfReport(headers: NormalizedClientHeaders): Downs
return null;
}
function detectCodexOfficialClientApp(headers: NormalizedClientHeaders): DownstreamProtocolClientApp | null {
for (const rule of codexOfficialClientAppRules) {
const matchesOriginator = Array.isArray(rule.originatorPrefixes)
&& headerMatchesPrefixes(headers, 'originator', rule.originatorPrefixes);
const matchesUserAgent = Array.isArray(rule.userAgentPrefixes)
&& headerMatchesPrefixes(headers, 'user-agent', rule.userAgentPrefixes);
if (!matchesOriginator && !matchesUserAgent) continue;
return {
clientAppId: rule.id,
clientAppName: rule.name,
clientConfidence: 'exact',
};
}
return null;
}
function detectProtocolClientApp(input: {
clientKind: DownstreamClientKind;
headers?: Record<string, unknown>;
@@ -313,8 +241,13 @@ function detectProtocolClientApp(input: {
clientConfidence: 'exact',
};
case 'codex': {
const headers = normalizeHeaders(input.headers);
return detectCodexOfficialClientApp(headers) || {
const clientApp = detectCodexOfficialClientAppViaProfile(input.headers);
return (clientApp
? {
...clientApp,
clientConfidence: 'exact' as const,
}
: null) || {
clientAppId: 'codex',
clientAppName: 'Codex',
clientConfidence: 'heuristic',
@@ -562,9 +562,7 @@ async function handleResponsesWebsocketConnection(
...(supportsIncrementalInput ? { [RESPONSES_WEBSOCKET_MODE_HEADER]: 'incremental' } : {}),
};
const providerHeaders = buildOauthProviderHeaders({
extraConfig: typeof codexWebsocketChannel.account.extraConfig === 'string'
? codexWebsocketChannel.account.extraConfig
: null,
account: codexWebsocketChannel.account,
downstreamHeaders,
});
const prepared = buildUpstreamEndpointRequest({
+9 -212
View File
@@ -1,4 +1,4 @@
import { createHash, randomUUID } from 'node:crypto';
import { randomUUID } from 'node:crypto';
import {
rankConversationFileEndpoints,
type ConversationFileInputSummary,
@@ -27,6 +27,14 @@ import {
promoteResponsesCandidateAfterLegacyChatError,
shouldPreferResponsesAfterLegacyChatError,
} from '../../transformers/shared/endpointCompatibility.js';
import {
buildClaudeRuntimeHeaders,
buildCodexRuntimeHeaders,
buildGeminiCliRuntimeHeaders,
buildGeminiCliUserAgent,
getInputHeader,
headerValueToString,
} from '../../proxy-core/providers/headerUtils.js';
export {
buildMinimalJsonHeadersForCompatibility,
isEndpointDispatchDeniedError,
@@ -104,23 +112,6 @@ function isClaudeFamilyModel(modelName: string): boolean {
return normalized === 'claude' || normalized.startsWith('claude-') || normalized.includes('claude');
}
function headerValueToString(value: unknown): string | null {
if (typeof value === 'string') {
const trimmed = value.trim();
return trimmed || null;
}
if (Array.isArray(value)) {
for (const item of value) {
if (typeof item !== 'string') continue;
const trimmed = item.trim();
if (trimmed) return trimmed;
}
}
return null;
}
const HOP_BY_HOP_HEADERS = new Set([
'connection',
'keep-alive',
@@ -146,11 +137,7 @@ const BLOCKED_PASSTHROUGH_HEADERS = new Set([
'sec-websocket-extensions',
]);
const CODEX_CLIENT_VERSION = '0.101.0';
const CODEX_DEFAULT_USER_AGENT = 'codex_cli_rs/0.101.0 (Mac OS 26.0.1; arm64) Apple_Terminal/464';
const ANTIGRAVITY_RUNTIME_USER_AGENT = 'antigravity/1.19.6 darwin/arm64';
const CLAUDE_DEFAULT_USER_AGENT = 'claude-cli/2.1.63 (external, cli)';
const CLAUDE_DEFAULT_BETA_HEADER = 'claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,context-management-2025-06-27,prompt-caching-scope-2026-01-05';
function shouldSkipPassthroughHeader(key: string): boolean {
return HOP_BY_HOP_HEADERS.has(key) || BLOCKED_PASSTHROUGH_HEADERS.has(key);
@@ -222,81 +209,6 @@ function extractResponsesPassthroughHeaders(
return forwarded;
}
function getInputHeader(
headers: Record<string, unknown> | Record<string, string> | undefined,
key: string,
): string | null {
if (!headers) return null;
for (const [candidateKey, candidateValue] of Object.entries(headers)) {
if (candidateKey.toLowerCase() !== key.toLowerCase()) continue;
return headerValueToString(candidateValue);
}
return null;
}
function parseGeminiCliUserAgentRuntime(userAgent: string | null): {
version: string;
platform: string;
arch: string;
} | null {
if (!userAgent) return null;
const match = /^GeminiCLI\/([^/]+)\/[^ ]+ \(([^;]+); ([^)]+)\)$/i.exec(userAgent.trim());
if (!match) return null;
return {
version: match[1] || '0.31.0',
platform: match[2] || 'win32',
arch: match[3] || 'x64',
};
}
function buildGeminiCLIUserAgent(modelName: string, existingUserAgent?: string | null): string {
const parsed = parseGeminiCliUserAgentRuntime(existingUserAgent ?? null);
const version = parsed?.version || '0.31.0';
const platform = parsed?.platform || 'win32';
const arch = parsed?.arch || 'x64';
const effectiveModel = asTrimmedString(modelName) || 'unknown';
return `GeminiCLI/${version}/${effectiveModel} (${platform}; ${arch})`;
}
function uuidFromSeed(seed: string): string {
const hash = createHash('sha1').update(seed).digest();
const bytes = new Uint8Array(hash.subarray(0, 16));
bytes[6] = (bytes[6]! & 0x0f) | 0x50;
bytes[8] = (bytes[8]! & 0x3f) | 0x80;
const hex = Array.from(bytes, (value) => value.toString(16).padStart(2, '0')).join('');
return [
hex.slice(0, 8),
hex.slice(8, 12),
hex.slice(12, 16),
hex.slice(16, 20),
hex.slice(20, 32),
].join('-');
}
function mergeClaudeBetaHeader(
explicitValue: string | null,
extraBetas: string[] = [],
): string {
const source = explicitValue || CLAUDE_DEFAULT_BETA_HEADER;
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) {
const normalized = entry.trim();
if (!normalized || seen.has(normalized)) continue;
seen.add(normalized);
merged.push(normalized);
}
}
return merged.join(',');
}
function extractClaudeBetasFromBody(body: Record<string, unknown>): {
body: Record<string, unknown>;
betas: string[];
@@ -327,80 +239,6 @@ function extractClaudeBetasFromBody(body: Record<string, unknown>): {
};
}
function buildCodexRuntimeHeaders(input: {
baseHeaders: Record<string, string>;
providerHeaders?: Record<string, string>;
explicitSessionId?: string | null;
continuityKey?: string | null;
}): Record<string, string> {
const authorization = (
getInputHeader(input.baseHeaders, 'authorization')
|| getInputHeader(input.baseHeaders, 'Authorization')
|| ''
);
const originator = getInputHeader(input.providerHeaders, 'originator') || 'codex_cli_rs';
const accountId = getInputHeader(input.providerHeaders, 'chatgpt-account-id');
const version = getInputHeader(input.baseHeaders, 'version') || CODEX_CLIENT_VERSION;
const userAgent = getInputHeader(input.baseHeaders, 'user-agent') || CODEX_DEFAULT_USER_AGENT;
const explicitSessionId = asTrimmedString(input.explicitSessionId);
const continuityKey = asTrimmedString(input.continuityKey);
const sessionId = (
getInputHeader(input.baseHeaders, 'session_id')
|| getInputHeader(input.baseHeaders, 'session-id')
|| explicitSessionId
|| (continuityKey ? uuidFromSeed(`metapi:codex:${continuityKey}`) : null)
|| randomUUID()
);
const conversationId = (
getInputHeader(input.baseHeaders, 'conversation_id')
|| getInputHeader(input.baseHeaders, 'conversation-id')
|| explicitSessionId
|| (continuityKey ? sessionId : null)
);
return {
Authorization: authorization,
'Content-Type': 'application/json',
...(accountId ? { 'Chatgpt-Account-Id': accountId } : {}),
Originator: originator,
Version: version,
Session_id: sessionId,
...(conversationId ? { Conversation_id: conversationId } : {}),
'User-Agent': userAgent,
Accept: 'text/event-stream',
Connection: 'Keep-Alive',
};
}
function buildGeminiCliRuntimeHeaders(input: {
baseHeaders: Record<string, string>;
providerHeaders?: Record<string, string>;
modelName: string;
stream: boolean;
}): Record<string, string> {
const apiClient = (
getInputHeader(input.providerHeaders, 'x-goog-api-client')
|| getInputHeader(input.baseHeaders, 'x-goog-api-client')
);
const userAgent = buildGeminiCLIUserAgent(
input.modelName,
getInputHeader(input.providerHeaders, 'user-agent') || getInputHeader(input.baseHeaders, 'user-agent'),
);
const headers: Record<string, string> = {
Authorization: input.baseHeaders.Authorization,
'Content-Type': 'application/json',
'User-Agent': userAgent,
};
if (apiClient) {
headers['X-Goog-Api-Client'] = apiClient;
}
if (input.stream) {
headers.Accept = 'text/event-stream';
}
return headers;
}
function buildAntigravityRuntimeHeaders(input: {
baseHeaders: Record<string, string>;
stream: boolean;
@@ -414,47 +252,6 @@ function buildAntigravityRuntimeHeaders(input: {
return headers;
}
function buildClaudeRuntimeHeaders(input: {
baseHeaders: Record<string, string>;
claudeHeaders: Record<string, string>;
anthropicVersion: string;
stream: boolean;
isClaudeOauthUpstream: boolean;
tokenValue: string;
extraBetas?: string[];
}): Record<string, string> {
const anthropicBeta = mergeClaudeBetaHeader(
getInputHeader(input.claudeHeaders, 'anthropic-beta'),
input.extraBetas,
);
const headers: Record<string, string> = {
...input.baseHeaders,
...input.claudeHeaders,
'anthropic-version': input.anthropicVersion,
...(anthropicBeta ? { 'anthropic-beta': anthropicBeta } : {}),
'Anthropic-Dangerous-Direct-Browser-Access': 'true',
'X-App': 'cli',
'X-Stainless-Retry-Count': getInputHeader(input.claudeHeaders, 'x-stainless-retry-count') || '0',
'X-Stainless-Runtime-Version': getInputHeader(input.claudeHeaders, 'x-stainless-runtime-version') || 'v24.3.0',
'X-Stainless-Package-Version': getInputHeader(input.claudeHeaders, 'x-stainless-package-version') || '0.74.0',
'X-Stainless-Runtime': getInputHeader(input.claudeHeaders, 'x-stainless-runtime') || 'node',
'X-Stainless-Lang': getInputHeader(input.claudeHeaders, 'x-stainless-lang') || 'js',
'X-Stainless-Arch': getInputHeader(input.claudeHeaders, 'x-stainless-arch') || 'x64',
'X-Stainless-Os': getInputHeader(input.claudeHeaders, 'x-stainless-os') || 'Windows',
'X-Stainless-Timeout': getInputHeader(input.claudeHeaders, 'x-stainless-timeout') || '600',
'User-Agent': getInputHeader(input.claudeHeaders, 'user-agent') || CLAUDE_DEFAULT_USER_AGENT,
Connection: 'keep-alive',
Accept: input.stream ? 'text/event-stream' : 'application/json',
'Accept-Encoding': 'gzip, deflate, br, zstd',
};
if (input.isClaudeOauthUpstream) {
headers.Authorization = `Bearer ${input.tokenValue}`;
} else {
headers['x-api-key'] = input.tokenValue;
}
return headers;
}
function ensureStreamAcceptHeader(
headers: Record<string, string>,
stream: boolean,