import type { AIProvider, CompletionParams, CompletionResult } from "./types"; const CODEX_ENDPOINT = "https://chatgpt.com/backend-api/codex/responses"; const CODEX_MODEL = "gpt-5.3-codex"; interface CodexClientConfig { accessToken: string; onTokenRefresh: () => Promise; } export class CodexClient implements AIProvider { readonly type = "codex" as const; private accessToken: string; private onTokenRefresh: () => Promise; constructor(config: CodexClientConfig) { this.accessToken = config.accessToken; this.onTokenRefresh = config.onTokenRefresh; } async generateCompletion(params: CompletionParams): Promise { const firstAttempt = await this.doRequest(params); if (firstAttempt.status === 401) { 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})`); } return this.parseResponse(retryAttempt); } if (!firstAttempt.ok) { throw new Error(`Codex request failed (${firstAttempt.status})`); } return this.parseResponse(firstAttempt); } private async doRequest(params: CompletionParams): Promise { return fetch(CODEX_ENDPOINT, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${this.accessToken}`, }, body: JSON.stringify({ model: CODEX_MODEL, instructions: params.systemPrompt, input: params.userPrompt, stream: false, }), }); } private async parseResponse(res: Response): Promise { const json = await res.json(); const messageOutput = (json.output as Array<{ type: string; content?: Array<{ type: string; text?: string }> }>)?.find( (o) => o.type === "message", ); const textContent = messageOutput?.content?.find( (c) => c.type === "output_text", ); if (!textContent?.text) { throw new Error("Codex returned empty content"); } return { content: textContent.text, tokensUsed: json.usage?.total_tokens ?? null, model: CODEX_MODEL, providerType: "codex", }; } }