fix: address codex review findings

This commit is contained in:
Cita
2026-03-21 19:45:29 +08:00
parent 1d8889001f
commit d9e550c279
10 changed files with 638 additions and 167 deletions
@@ -147,6 +147,9 @@ describe('codexWebsocketRuntime', () => {
id: 'resp-incomplete',
model: parsed.model || 'gpt-5.4',
status: 'incomplete',
incomplete_details: {
reason: 'max_output_tokens',
},
},
}));
};
@@ -154,18 +157,34 @@ describe('codexWebsocketRuntime', () => {
const { createCodexWebsocketRuntime, CodexWebsocketRuntimeError } = await import('./codexWebsocketRuntime.js');
const runtime = createCodexWebsocketRuntime();
await expect(runtime.sendRequest({
sessionId: 'exec-session-incomplete',
requestUrl: upstreamWsUrl,
headers: {
Authorization: 'Bearer oauth-access-token',
'OpenAI-Beta': 'responses_websockets=2026-02-06',
},
body: {
model: 'gpt-5.4',
input: [],
},
})).rejects.toBeInstanceOf(CodexWebsocketRuntimeError);
let error: unknown;
try {
await runtime.sendRequest({
sessionId: 'exec-session-incomplete',
requestUrl: upstreamWsUrl,
headers: {
Authorization: 'Bearer oauth-access-token',
'OpenAI-Beta': 'responses_websockets=2026-02-06',
},
body: {
model: 'gpt-5.4',
input: [],
},
});
} catch (caught) {
error = caught;
}
expect(error).toBeInstanceOf(CodexWebsocketRuntimeError);
expect(error).toMatchObject({
message: 'max_output_tokens',
status: 502,
});
expect((error as CodexWebsocketRuntimeError).events).toEqual([
expect.objectContaining({
type: 'response.incomplete',
}),
]);
});
it('treats top-level error frames as terminal websocket failures', async () => {
@@ -182,17 +201,36 @@ describe('codexWebsocketRuntime', () => {
const { createCodexWebsocketRuntime, CodexWebsocketRuntimeError } = await import('./codexWebsocketRuntime.js');
const runtime = createCodexWebsocketRuntime();
await expect(runtime.sendRequest({
sessionId: 'exec-session-error',
requestUrl: upstreamWsUrl,
headers: {
Authorization: 'Bearer oauth-access-token',
'OpenAI-Beta': 'responses_websockets=2026-02-06',
},
body: {
model: 'gpt-5.4',
input: [],
},
})).rejects.toBeInstanceOf(CodexWebsocketRuntimeError);
let error: unknown;
try {
await runtime.sendRequest({
sessionId: 'exec-session-error',
requestUrl: upstreamWsUrl,
headers: {
Authorization: 'Bearer oauth-access-token',
'OpenAI-Beta': 'responses_websockets=2026-02-06',
},
body: {
model: 'gpt-5.4',
input: [],
},
});
} catch (caught) {
error = caught;
}
expect(error).toBeInstanceOf(CodexWebsocketRuntimeError);
expect(error).toMatchObject({
message: 'account mismatch',
status: 502,
});
expect((error as CodexWebsocketRuntimeError).events).toEqual([
expect.objectContaining({
type: 'error',
error: expect.objectContaining({
message: 'account mismatch',
}),
}),
]);
});
});
@@ -34,6 +34,32 @@ function isFailureTerminalEvent(payload: Record<string, unknown>): boolean {
return type === 'response.failed' || type === 'response.incomplete' || type === 'error';
}
function asFiniteNumber(value: unknown): number | undefined {
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
}
function extractFailureTerminalStatus(payload: Record<string, unknown>): number {
const response = isRecord(payload.response) ? payload.response : null;
const responseError = response && isRecord(response.error) ? response.error : null;
const topLevelError = isRecord(payload.error) ? payload.error : null;
const candidates = [
payload.status,
payload.statusCode,
payload.code,
topLevelError?.status,
topLevelError?.statusCode,
topLevelError?.code,
responseError?.status,
responseError?.statusCode,
responseError?.code,
];
for (const candidate of candidates) {
const status = asFiniteNumber(candidate);
if (status !== undefined) return status;
}
return 502;
}
function extractTerminalErrorMessage(payload: Record<string, unknown>): string {
const type = asTrimmedString(payload.type);
if (type === 'error' && isRecord(payload.error)) {
@@ -243,7 +269,7 @@ async function sendSessionRequest(
void closeSocket(socket);
reject(new CodexWebsocketRuntimeError(extractTerminalErrorMessage(parsed), {
events: [...events],
status: typeof parsed.status === 'number' && Number.isFinite(parsed.status) ? parsed.status : undefined,
status: extractFailureTerminalStatus(parsed),
payload: parsed,
}));
return;
@@ -135,6 +135,19 @@ function carriesResponsesFileUrlInput(value: unknown): boolean {
return Object.values(value).some((entry) => carriesResponsesFileUrlInput(entry));
}
function shouldRefreshOauthResponsesRequest(input: {
oauthProvider?: string;
status: number;
response: Response;
rawErrText: string;
}): boolean {
if (input.status === 401) return true;
if (input.status !== 403 || input.oauthProvider !== 'codex') return false;
const authenticate = input.response.headers.get('www-authenticate') || '';
const combined = `${authenticate}\n${input.rawErrText || ''}`;
return /\b(invalid_token|expired_token|expired|invalid|unauthorized|account mismatch|authentication)\b/i.test(combined);
}
type UsageSummary = ReturnType<typeof parseProxyUsage>;
export async function handleOpenAiResponsesSurfaceRequest(
@@ -314,7 +327,12 @@ export async function handleOpenAiResponsesSurfaceRequest(
dispatchRequest,
});
const tryRecover = async (ctx: Parameters<NonNullable<typeof endpointStrategy.tryRecover>>[0]) => {
if ((ctx.response.status === 401 || ctx.response.status === 403) && oauth) {
if (oauth && shouldRefreshOauthResponsesRequest({
oauthProvider: oauth.provider,
status: ctx.response.status,
response: ctx.response,
rawErrText: ctx.rawErrText || '',
})) {
try {
const refreshed = await refreshOauthAccessTokenSingleflight(selected.account.id);
selected.tokenValue = refreshed.accessToken;