fix: address review feedback and code scanning alerts

This commit is contained in:
Cita
2026-03-22 23:41:45 +08:00
parent 32d7037629
commit 5d78658af9
38 changed files with 929 additions and 344 deletions
@@ -2,6 +2,11 @@ import { describe, expect, it } from 'vitest';
import { detectCodexOfficialClientApp } from './codexProfile.js';
describe('detectCodexOfficialClientApp', () => {
it('returns null for missing or empty headers', () => {
expect(detectCodexOfficialClientApp()).toBeNull();
expect(detectCodexOfficialClientApp({})).toBeNull();
});
it('detects official Codex clients from originator prefixes', () => {
expect(detectCodexOfficialClientApp({
originator: 'codex_exec',
@@ -20,6 +25,30 @@ describe('detectCodexOfficialClientApp', () => {
});
});
it('matches headers case-insensitively and from header arrays', () => {
expect(detectCodexOfficialClientApp({
originator: 'CODEX_EXEC',
})).toEqual({
clientAppId: 'codex_exec',
clientAppName: 'Codex Exec',
});
expect(detectCodexOfficialClientApp({
'user-agent': 'Mozilla/5.0 CODEX_chatgpt_desktop/1.2',
})).toEqual({
clientAppId: 'codex_chatgpt_desktop',
clientAppName: 'Codex Desktop',
});
expect(detectCodexOfficialClientApp({
'user-agent': ['Mozilla/5.0', 'codex_chatgpt_desktop/1.2'],
})).toEqual({
clientAppId: 'codex_chatgpt_desktop',
clientAppName: 'Codex Desktop',
});
expect(detectCodexOfficialClientApp({
originator: ['ignored', 'other-client'],
})).toBeNull();
});
it('returns null for non-official Codex clients', () => {
expect(detectCodexOfficialClientApp({
'user-agent': 'OpenClaw/1.0',
@@ -66,33 +66,43 @@ const CODEX_OFFICIAL_CLIENT_APP_RULES = [
},
] as const;
function headerValueToString(value: unknown): string | null {
function headerValueToStrings(value: unknown): string[] {
if (typeof value === 'string') {
const trimmed = value.trim();
return trimmed || null;
return trimmed ? [trimmed] : [];
}
if (Array.isArray(value)) {
const values: string[] = [];
for (const item of value) {
if (typeof item !== 'string') continue;
const trimmed = item.trim();
if (trimmed) return trimmed;
if (trimmed) values.push(trimmed);
}
return values;
}
return null;
return [];
}
function headerValueToString(value: unknown): string | null {
return headerValueToStrings(value)[0] || null;
}
function getHeaderValue(headers: Record<string, unknown> | undefined, targetKey: string): string | null {
if (!headers) return null;
return getHeaderValues(headers, targetKey)[0] || null;
}
function getHeaderValues(headers: Record<string, unknown> | undefined, targetKey: string): string[] {
if (!headers) return [];
const normalizedTarget = targetKey.trim().toLowerCase();
const values: string[] = [];
for (const [rawKey, rawValue] of Object.entries(headers)) {
if (rawKey.trim().toLowerCase() !== normalizedTarget) continue;
return headerValueToString(rawValue);
values.push(...headerValueToStrings(rawValue));
}
return null;
return values;
}
function hasHeaderPrefix(headers: Record<string, unknown> | undefined, prefix: string): boolean {
@@ -104,16 +114,18 @@ function hasHeaderPrefix(headers: Record<string, unknown> | undefined, prefix: s
});
}
function matchesHeaderPrefixes(value: string | null, prefixes: string[]): boolean {
const normalizedValue = value?.trim().toLowerCase() || '';
if (!normalizedValue) return false;
function matchesHeaderPrefixes(value: string | string[] | null, prefixes: readonly string[]): boolean {
const values = Array.isArray(value)
? value.map((item) => item.trim().toLowerCase()).filter(Boolean)
: [value?.trim().toLowerCase() || ''].filter(Boolean);
if (values.length === 0) return false;
return prefixes.some((prefix) => {
return values.some((normalizedValue) => prefixes.some((prefix) => {
const normalizedPrefix = prefix.trim().toLowerCase();
if (!normalizedPrefix) return false;
return normalizedValue.startsWith(normalizedPrefix)
|| normalizedValue.includes(normalizedPrefix);
});
}));
}
function isCodexPath(path: string): boolean {
@@ -127,10 +139,8 @@ export function detectCodexOfficialClientApp(
headers?: Record<string, unknown>,
): CodexOfficialClientApp | null {
for (const rule of CODEX_OFFICIAL_CLIENT_APP_RULES) {
const matchesOriginator = Array.isArray(rule.originatorPrefixes)
&& matchesHeaderPrefixes(getHeaderValue(headers, 'originator'), [...rule.originatorPrefixes]);
const matchesUserAgent = Array.isArray(rule.userAgentPrefixes)
&& matchesHeaderPrefixes(getHeaderValue(headers, 'user-agent'), [...rule.userAgentPrefixes]);
const matchesOriginator = matchesHeaderPrefixes(getHeaderValues(headers, 'originator'), rule.originatorPrefixes);
const matchesUserAgent = matchesHeaderPrefixes(getHeaderValues(headers, 'user-agent'), rule.userAgentPrefixes);
if (!matchesOriginator && !matchesUserAgent) continue;
return {
clientAppId: rule.id,
@@ -156,9 +166,9 @@ export function isCodexRequest(input: DetectCliProfileInput): boolean {
const headers = input.headers;
if (!headers) return false;
const originator = getHeaderValue(headers, 'originator');
const originator = getHeaderValues(headers, 'originator');
if (matchesHeaderPrefixes(originator, CODEX_OFFICIAL_CLIENT_ORIGINATOR_PREFIXES)) return true;
if (matchesHeaderPrefixes(getHeaderValue(headers, 'user-agent'), CODEX_OFFICIAL_CLIENT_USER_AGENT_PREFIXES)) return true;
if (matchesHeaderPrefixes(getHeaderValues(headers, 'user-agent'), CODEX_OFFICIAL_CLIENT_USER_AGENT_PREFIXES)) return true;
if (getHeaderValue(headers, 'openai-beta')) return true;
if (hasHeaderPrefix(headers, 'x-stainless-')) return true;
if (getCodexSessionId(headers)) return true;
@@ -101,7 +101,7 @@ describe('provider header utils', () => {
const headers = buildGeminiCliRuntimeHeaders({
baseHeaders: {
Authorization: 'Bearer test',
authorization: 'Bearer test',
},
providerHeaders: {
'x-goog-api-client': 'gl-node/22 gccl/0.31.0',
@@ -1,4 +1,4 @@
import { createHash, randomUUID } from 'node:crypto';
import { pbkdf2Sync, randomUUID } from 'node:crypto';
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';
@@ -33,8 +33,8 @@ export function getInputHeader(
}
export function uuidFromSeed(seed: string): string {
const hash = createHash('sha1').update(seed).digest();
const bytes = new Uint8Array(hash.subarray(0, 16));
const derived = pbkdf2Sync(seed, 'metapi-runtime-header-seed', 10_000, 16, 'sha256');
const bytes = new Uint8Array(derived);
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('');
@@ -153,7 +153,7 @@ export function buildCodexRuntimeHeaders(input: {
);
return {
Authorization: authorization,
...(authorization ? { Authorization: authorization } : {}),
'Content-Type': 'application/json',
...(accountId ? { 'Chatgpt-Account-Id': accountId } : {}),
Originator: originator,
@@ -221,6 +221,7 @@ export function buildGeminiCliRuntimeHeaders(input: {
modelName: string;
stream: boolean;
}): Record<string, string> {
const authorization = getInputHeader(input.baseHeaders, 'authorization');
const apiClient = (
getInputHeader(input.providerHeaders, 'x-goog-api-client')
|| getInputHeader(input.baseHeaders, 'x-goog-api-client')
@@ -231,7 +232,7 @@ export function buildGeminiCliRuntimeHeaders(input: {
);
const headers: Record<string, string> = {
Authorization: input.baseHeaders.Authorization,
...(authorization ? { Authorization: authorization } : {}),
'Content-Type': 'application/json',
'User-Agent': userAgent,
};
@@ -808,7 +808,7 @@ export async function handleClaudeCountTokensSurfaceRequest(
retryCount += 1;
continue;
}
return reply.code(failureOutcome.status).type(contentType).send(payload);
return reply.code(failureOutcome.status).send(failureOutcome.payload);
}
tokenRouter.recordSuccess(selected.channel.id, latency, 0, modelName);
+5 -17
View File
@@ -8,7 +8,10 @@ import {
softDeleteProxyFileByPublicIdForOwner,
} from '../../services/proxyFileStore.js';
import { ensureMultipartBufferParser, parseMultipartFormData } from '../../routes/proxy/multipart.js';
import { isSupportedConversationFileMimeType } from '../../../shared/conversationFileTypes.js';
import {
isSupportedConversationFileMimeType,
resolveConversationFileMimeType,
} from '../../../shared/conversationFileTypes.js';
function invalidRequest(reply: FastifyReply, message: string) {
return reply.code(400).send({ error: { message, type: 'invalid_request_error' } });
@@ -44,21 +47,6 @@ function toFileObject(record: {
};
}
function inferMimeTypeFromFilename(filename: string): string {
const normalized = filename.trim().toLowerCase();
if (normalized.endsWith('.pdf')) return 'application/pdf';
if (normalized.endsWith('.txt')) return 'text/plain';
if (normalized.endsWith('.md') || normalized.endsWith('.markdown')) return 'text/markdown';
if (normalized.endsWith('.json')) return 'application/json';
if (normalized.endsWith('.png')) return 'image/png';
if (normalized.endsWith('.jpg') || normalized.endsWith('.jpeg')) return 'image/jpeg';
if (normalized.endsWith('.gif')) return 'image/gif';
if (normalized.endsWith('.webp')) return 'image/webp';
if (normalized.endsWith('.wav')) return 'audio/wav';
if (normalized.endsWith('.mp3')) return 'audio/mpeg';
return 'application/octet-stream';
}
export async function filesProxyRoute(app: FastifyInstance) {
ensureMultipartBufferParser(app);
@@ -79,7 +67,7 @@ export async function filesProxyRoute(app: FastifyInstance) {
}
const filename = fileEntry.name || 'upload.bin';
const mimeType = (fileEntry.type || inferMimeTypeFromFilename(filename)).trim().toLowerCase();
const mimeType = resolveConversationFileMimeType(fileEntry.type, filename);
if (!isSupportedConversationFileMimeType(mimeType)) {
return invalidRequest(reply, `unsupported file mime type: ${mimeType || 'application/octet-stream'}`);
}
@@ -715,46 +715,50 @@ export async function handleOpenAiResponsesSurfaceRequest(
usage: parsedUsage,
serializationMode: isCompactRequest ? 'compact' : 'response',
});
const resolvedUsage = await resolveProxyUsageWithSelfLogFallback({
site: selected.site,
account: selected.account,
tokenValue: selected.tokenValue,
tokenName: selected.tokenName,
modelName: selected.actualModel || requestedModel,
requestStartedAtMs: startTime,
requestEndedAtMs: startTime + latency,
localLatencyMs: latency,
usage: {
promptTokens: parsedUsage.promptTokens,
completionTokens: parsedUsage.completionTokens,
totalTokens: parsedUsage.totalTokens,
},
});
const { estimatedCost, billingDetails } = await resolveProxyLogBilling({
site: selected.site,
account: selected.account,
modelName: selected.actualModel || requestedModel,
parsedUsage,
resolvedUsage,
});
try {
const resolvedUsage = await resolveProxyUsageWithSelfLogFallback({
site: selected.site,
account: selected.account,
tokenValue: selected.tokenValue,
tokenName: selected.tokenName,
modelName: selected.actualModel || requestedModel,
requestStartedAtMs: startTime,
requestEndedAtMs: startTime + latency,
localLatencyMs: latency,
usage: {
promptTokens: parsedUsage.promptTokens,
completionTokens: parsedUsage.completionTokens,
totalTokens: parsedUsage.totalTokens,
},
});
const { estimatedCost, billingDetails } = await resolveProxyLogBilling({
site: selected.site,
account: selected.account,
modelName: selected.actualModel || requestedModel,
parsedUsage,
resolvedUsage,
});
tokenRouter.recordSuccess(selected.channel.id, latency, estimatedCost, modelName);
recordDownstreamCostUsage(request, estimatedCost);
await failureToolkit.log({
selected,
modelRequested: requestedModel,
status: 'success',
httpStatus: 200,
latencyMs: latency,
errorMessage: null,
retryCount,
promptTokens: resolvedUsage.promptTokens,
completionTokens: resolvedUsage.completionTokens,
totalTokens: resolvedUsage.totalTokens,
estimatedCost,
billingDetails,
upstreamPath: successfulUpstreamPath,
});
tokenRouter.recordSuccess(selected.channel.id, latency, estimatedCost, modelName);
recordDownstreamCostUsage(request, estimatedCost);
await failureToolkit.log({
selected,
modelRequested: requestedModel,
status: 'success',
httpStatus: 200,
latencyMs: latency,
errorMessage: null,
retryCount,
promptTokens: resolvedUsage.promptTokens,
completionTokens: resolvedUsage.completionTokens,
totalTokens: resolvedUsage.totalTokens,
estimatedCost,
billingDetails,
upstreamPath: successfulUpstreamPath,
});
} catch (error) {
console.error('[responses] post-response bookkeeping failed:', error);
}
return reply.send(downstreamData);
} catch (err: any) {
const failureOutcome = await failureToolkit.handleExecutionError({
@@ -206,7 +206,7 @@ export function createSurfaceFailureToolkit(input: {
retryCount: number;
}): Promise<SurfaceFailureOutcome> {
const rawErrText = args.rawErrText || args.errText;
tokenRouter.recordFailure(args.selected.channel.id, {
await tokenRouter.recordFailure(args.selected.channel.id, {
status: args.status,
errorText: rawErrText,
modelName: args.modelName,
@@ -269,7 +269,7 @@ export function createSurfaceFailureToolkit(input: {
totalTokens?: number;
upstreamPath?: string | null;
}): Promise<SurfaceFailureOutcome> {
tokenRouter.recordFailure(args.selected.channel.id, {
await tokenRouter.recordFailure(args.selected.channel.id, {
status: args.failure.status,
errorText: args.failure.reason,
modelName: args.modelName,
@@ -318,7 +318,7 @@ export function createSurfaceFailureToolkit(input: {
latencyMs: number;
retryCount: number;
}): Promise<SurfaceFailureOutcome> {
tokenRouter.recordFailure(args.selected.channel.id, {
await tokenRouter.recordFailure(args.selected.channel.id, {
errorText: args.errorMessage,
modelName: args.modelName,
});
@@ -368,13 +368,13 @@ export function createSurfaceFailureToolkit(input: {
}) {
const errorMessage = args.errorMessage || 'stream processing failed';
if (typeof args.runtimeFailureStatus === 'number') {
tokenRouter.recordFailure(args.selected.channel.id, {
await tokenRouter.recordFailure(args.selected.channel.id, {
status: args.runtimeFailureStatus,
errorText: errorMessage,
modelName: args.modelName,
});
} else {
tokenRouter.recordFailure(args.selected.channel.id, args.modelName);
await tokenRouter.recordFailure(args.selected.channel.id, args.modelName);
}
await log({
selected: args.selected,