[codex] add proxy debug tracing (#299)

* add proxy debug tracing

* fix proxy debug response header typing

* extend proxy debug tracing to side paths

* Refine proxy debug trace review fixes

* Stabilize proxy debug attempt indexing

* Tighten proxy debug trace accuracy
This commit is contained in:
Cita
2026-03-28 20:05:48 +08:00
committed by GitHub
parent 3bf3cd1622
commit 4d4684a39b
35 changed files with 6669 additions and 495 deletions
@@ -0,0 +1,177 @@
import { readRuntimeResponseText } from '../proxy-core/executors/types.js';
import type {
EndpointAttemptSuccessContext,
} from '../routes/proxy/endpointFlow.js';
import {
finalizeProxyDebugTrace,
insertProxyDebugAttempt,
normalizeProxyDebugResponseHeaders,
startProxyDebugTraceSession,
updateProxyDebugAttempt,
updateProxyDebugTraceCandidates,
updateProxyDebugTraceSelection,
type ProxyDebugTraceSession,
} from './proxyDebugTraceStore.js';
type MutableProxyDebugTraceSession = ProxyDebugTraceSession & {
nextAttemptIndex?: number;
};
function parseDebugTextPayload(rawText: string): unknown {
if (!rawText) return null;
try {
return JSON.parse(rawText);
} catch {
return rawText;
}
}
export async function startSurfaceProxyDebugTrace(input: {
downstreamPath: string;
clientKind?: string | null;
sessionId?: string | null;
traceHint?: string | null;
requestedModel?: string | null;
downstreamApiKeyId?: number | null;
requestHeaders?: Record<string, unknown>;
requestBody?: unknown;
}): Promise<ProxyDebugTraceSession | null> {
try {
return await startProxyDebugTraceSession(input);
} catch (error) {
console.warn('[proxy-debug] failed to create trace session', error);
return null;
}
}
export async function safeUpdateSurfaceProxyDebugSelection(
session: ProxyDebugTraceSession | null,
input: Parameters<typeof updateProxyDebugTraceSelection>[1],
): Promise<void> {
if (!session) return;
try {
await updateProxyDebugTraceSelection(session.traceId, input);
} catch (error) {
console.warn('[proxy-debug] failed to update selection', error);
}
}
export async function safeUpdateSurfaceProxyDebugCandidates(
session: ProxyDebugTraceSession | null,
input: Parameters<typeof updateProxyDebugTraceCandidates>[1],
): Promise<void> {
if (!session) return;
try {
await updateProxyDebugTraceCandidates(session.traceId, input);
} catch (error) {
console.warn('[proxy-debug] failed to update endpoint candidates', error);
}
}
export async function safeInsertSurfaceProxyDebugAttempt(
session: ProxyDebugTraceSession | null,
input: Omit<Parameters<typeof insertProxyDebugAttempt>[0], 'traceId'>,
): Promise<void> {
if (!session) return;
try {
await insertProxyDebugAttempt({
...input,
traceId: session.traceId,
requestHeaders: session.options.captureHeaders ? input.requestHeaders : null,
requestBody: session.options.captureBodies ? input.requestBody : null,
responseHeaders: session.options.captureHeaders ? input.responseHeaders : null,
responseBody: session.options.captureBodies ? input.responseBody : null,
maxBodyBytes: session.options.maxBodyBytes,
});
} catch (error) {
console.warn('[proxy-debug] failed to insert attempt', error);
}
}
export function reserveSurfaceProxyDebugAttemptBase(
session: ProxyDebugTraceSession | null,
span: number,
): number {
if (!session) return 0;
const mutableSession = session as MutableProxyDebugTraceSession;
const base = mutableSession.nextAttemptIndex ?? 0;
const normalizedSpan = Number.isFinite(span)
? Math.max(1, Math.trunc(span))
: 1;
mutableSession.nextAttemptIndex = base + normalizedSpan;
return base;
}
export async function safeFinalizeSurfaceProxyDebugTrace(
session: ProxyDebugTraceSession | null,
input: Parameters<typeof finalizeProxyDebugTrace>[1],
): Promise<void> {
if (!session) return;
try {
await finalizeProxyDebugTrace(session.traceId, {
...input,
finalResponseHeaders: session.options.captureHeaders ? input.finalResponseHeaders : null,
finalResponseBody: session.options.captureBodies ? input.finalResponseBody : null,
maxBodyBytes: session.options.maxBodyBytes,
});
} catch (error) {
console.warn('[proxy-debug] failed to finalize trace', error);
}
}
export async function safeUpdateSurfaceProxyDebugAttempt(
session: ProxyDebugTraceSession | null,
attemptIndex: number,
input: Parameters<typeof updateProxyDebugAttempt>[2],
): Promise<void> {
if (!session) return;
try {
await updateProxyDebugAttempt(session.traceId, attemptIndex, input);
} catch (error) {
console.warn('[proxy-debug] failed to update attempt', error);
}
}
export async function captureSurfaceProxyDebugSuccessResponseBody(
session: ProxyDebugTraceSession | null,
ctx: EndpointAttemptSuccessContext,
): Promise<unknown> {
if (!session?.options.captureBodies) return null;
const contentType = (ctx.response.headers.get('content-type') || '').toLowerCase();
const requestBody = ctx.request.body as Record<string, unknown> | undefined;
const isStream = ctx.request.runtime?.stream === true
|| requestBody?.stream === true
|| contentType.includes('text/event-stream');
if (isStream) return null;
try {
const rawText = await readRuntimeResponseText(ctx.response.clone());
return parseDebugTextPayload(rawText);
} catch {
return null;
}
}
export function buildSurfaceProxyDebugResponseHeaders(
response:
| Headers
| Record<string, unknown>
| { headers?: Headers | Record<string, unknown> | null | undefined }
| null
| undefined,
): Record<string, unknown> | null {
if (!response) return null;
if (typeof response === 'object' && 'headers' in response) {
const responseHeaders = (response as {
headers?: Headers | Record<string, unknown> | null | undefined;
}).headers;
return normalizeProxyDebugResponseHeaders(responseHeaders ?? null);
}
return normalizeProxyDebugResponseHeaders(response);
}
export function parseSurfaceProxyDebugTextPayload(rawText: string): unknown {
return parseDebugTextPayload(rawText);
}