fix(ai): codex-client mirrors openai-client structured-error pattern

P14. CodexClient previously threw a bare `Error("Codex request failed
(500)")` on upstream failure, which made it impossible to triage from
logs. Mirror the openai-client.ts:42-58 pattern: pull the body
(truncated 500 chars), mask any leaked `sk-` / Bearer token, and tag
the Error with `httpStatus`, `upstream`, `model`, and `bodyExcerpt`
fields. Glitchtip and Wide Events now have the same shape for both
providers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.7 committed 2026-05-09 02:01:56 +02:00
1 parent 4a159d2b6c
commit dcc697c33d
1 file changed
+31 -2
+31 -2
View File
@@ -25,18 +25,47 @@ export class CodexClient implements AIProvider {
this.accessToken = await this.onTokenRefresh();
const retryAttempt = await this.doRequest(params);
if (!retryAttempt.ok) {
throw new Error(`Codex request failed after token refresh (${retryAttempt.status})`);
throw await this.buildUpstreamError(
retryAttempt,
"Codex request failed after token refresh",
);
}
return this.parseResponse(retryAttempt);
}
if (!firstAttempt.ok) {
throw new Error(`Codex request failed (${firstAttempt.status})`);
throw await this.buildUpstreamError(firstAttempt, "Codex request failed");
}
return this.parseResponse(firstAttempt);
}
/**
* Build a structured error from a non-OK upstream response. Mirrors the
* pattern used in `OpenAIClient.generateCompletion` so logs and Glitchtip
* issues for both providers carry the same fields (httpStatus, upstream,
* model, bodyExcerpt). The body excerpt is truncated to 500 chars and any
* `sk-…` / `Bearer …` token is masked before it reaches log shipping.
*/
private async buildUpstreamError(
res: Response,
message: string,
): Promise<Error> {
const rawBody = await res.text().catch(() => "");
const bodyExcerpt = rawBody
.slice(0, 500)
.replace(/sk-(?:ant-)?[A-Za-z0-9_-]{8,}/g, "sk-***redacted***")
.replace(/Bearer\s+[A-Za-z0-9_.-]+/gi, "Bearer ***redacted***");
const err = new Error(`${message} (${res.status})`);
Object.assign(err, {
httpStatus: res.status,
upstream: "codex",
model: CODEX_MODEL,
bodyExcerpt,
});
return err;
}
private async doRequest(params: CompletionParams): Promise<Response> {
return fetch(CODEX_ENDPOINT, {
method: "POST",