Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ délégation v1/base/v2 et de ses mécanismes de repli.

## Préchauffage des comptes Codex

L’ajout ou la réauthentification vérifie normalement le compte avant son enregistrement par une petite requête attendant `response.completed`. Le modèle par défaut est `gpt-5.4-mini`, avec un essai sur `gpt-5.5` en cas de HTTP 400. Les erreurs publiques contiennent des catégories fixes, sans corps de réponse brut.
L’ajout ou la réauthentification vérifie normalement le compte avant son enregistrement par une petite requête attendant `response.completed`. Le modèle par défaut est `gpt-5.4-mini`, avec un essai sur `gpt-5.5` et `gpt-5.6-luna` en cas de HTTP 400 ou HTTP 404. Les erreurs publiques contiennent des catégories fixes, sans corps de réponse brut.

Si la lecture authentifiée des quotas avec le nouveau jeton OAuth confirme un quota de 5 heures, hebdomadaire ou mensuel épuisé, le compte est enregistré sans appel au modèle et affiche **Validation en attente**. Il reste exclu du routage après un redémarrage ou un renouvellement du jeton. Après récupération du quota, actualisez les quotas : une lecture récente et complète avec de la capacité disponible permet une petite requête de validation. Seule sa réussite active le compte. Tout échec conserve la restriction. Les lectures passives ne déclenchent pas cette requête. Un quota inconnu à l’inscription conserve la vérification habituelle.

Expand Down
2 changes: 1 addition & 1 deletion docs-site/src/content/docs/guides/codex-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,7 @@ Catalog sync makes the selected sub-agent models available to Codex; see [Codex

## Codex account warmup

When a ChatGPT account is added or reauthenticated, OpenCodex normally verifies it before saving with a small streaming request to the Codex Responses backend. It waits for `response.completed`, defaults to `gpt-5.4-mini`, and retries with `gpt-5.5` on HTTP 400. Public errors contain fixed failure categories rather than raw upstream response bodies.
When a ChatGPT account is added or reauthenticated, OpenCodex normally verifies it before saving with a small streaming request to the Codex Responses backend. It waits for `response.completed`, defaults to `gpt-5.4-mini`, and retries with `gpt-5.5` and `gpt-5.6-luna` on HTTP 400 or HTTP 404. Public errors contain fixed failure categories rather than raw upstream response bodies.

If the new OAuth credential's authenticated usage lookup confirms an exhausted 5-hour, weekly, or monthly quota, the account is saved without this model request and shows **Validation pending**. It cannot serve pool requests, even after a restart or token refresh. Once quota recovers, **Refresh quotas** finishes validation: a fresh, complete usage reading with headroom permits one small model request, and only a completed response enables the account. Failed or incomplete readings and failed validation preserve the restriction. Passive account polling does not trigger deferred validation. Unknown usage during initial registration retains the normal warmup gate.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ ocx service install # persistent: auto-starts on login and respawns on crash

## Codex アカウントのウォームアップ

アカウントの追加・再認証では通常、保存前に小さなモデルリクエストで `response.completed` を確認します。既定モデルは `gpt-5.4-mini` で、HTTP 400 の場合は `gpt-5.5` で再試行します。公開エラーには固定の分類のみを表示し、生の応答本文は公開しません。
アカウントの追加・再認証では通常、保存前に小さなモデルリクエストで `response.completed` を確認します。既定モデルは `gpt-5.4-mini` で、HTTP 400 または HTTP 404 の場合は `gpt-5.5` および `gpt-5.6-luna` で再試行します。公開エラーには固定の分類のみを表示し、生の応答本文は公開しません。

新しい OAuth トークンによる使用量取得で5時間・週次・月次の上限到達が確認された場合、モデルを呼ばずに保存し、**検証待ち**と表示します。再起動やトークン更新後も使用できません。上限回復後に使用量を更新すると、十分な空き容量を示す完全な最新情報を確認してから小さなモデルリクエストを送り、完了した場合のみ使用可能になります。取得や検証の失敗では待機状態を維持します。通常の状態ポーリングは検証リクエストを送りません。初回登録時の使用量が不明な場合は通常の検証が必要です。

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ catalog sync는 선택된 서브에이전트 모델을 Codex가 쓸 수 있게

## Codex 계정 워밍업

ChatGPT 계정을 추가하거나 재인증할 때 OpenCodex는 일반적으로 저장 전에 작은 모델 요청으로 확인합니다. `gpt-5.4-mini`의 `response.completed`를 기다리며 HTTP 400이면 `gpt-5.5`로 재시도합니다. 오류에는 고정된 실패 분류만 표시하고 원본 응답 본문은 노출하지 않습니다.
ChatGPT 계정을 추가하거나 재인증할 때 OpenCodex는 일반적으로 저장 전에 작은 모델 요청으로 확인합니다. `gpt-5.4-mini`의 `response.completed`를 기다리며 HTTP 400 또는 HTTP 404이면 `gpt-5.5`와 `gpt-5.6-luna`로 재시도합니다. 오류에는 고정된 실패 분류만 표시하고 원본 응답 본문은 노출하지 않습니다.

새 OAuth 토큰으로 인증된 사용량 조회에서 5시간·주간·월간 한도 소진이 확인되면 모델 요청 없이 계정을 저장하고 **검증 대기**로 표시합니다. 재시작이나 토큰 갱신 후에도 요청에 사용되지 않습니다. 한도 회복 후 **사용량 새로고침**을 실행하면, 여유가 있는 완전한 최신 사용량을 확인한 뒤 작은 모델 요청을 보내고 완료 응답을 받아야 계정을 사용할 수 있습니다. 조회나 검증 실패 시 대기 상태를 유지합니다. 일반적인 화면 상태 조회는 이 모델 요청을 보내지 않습니다. 최초 등록 때 사용량이 불명확하면 기존 워밍업 검증이 필요합니다.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ v1/base/v2 при делегировании и fallback — в

## Прогрев аккаунтов Codex

При добавлении или повторной аутентификации аккаунт обычно проверяется до сохранения небольшим запросом, ожидающим `response.completed`. По умолчанию используется `gpt-5.4-mini`, при HTTP 400 — повтор с `gpt-5.5`. Публичные ошибки содержат фиксированные категории без исходного тела ответа.
При добавлении или повторной аутентификации аккаунт обычно проверяется до сохранения небольшим запросом, ожидающим `response.completed`. По умолчанию используется `gpt-5.4-mini`, при HTTP 400 или HTTP 404 — повтор с `gpt-5.5` и `gpt-5.6-luna`. Публичные ошибки содержат фиксированные категории без исходного тела ответа.

Если запрос квоты с новым OAuth-токеном подтверждает исчерпание пятичасовой, недельной или месячной квоты, аккаунт сохраняется без вызова модели со статусом **Ожидает проверки**. Перезапуск и обновление токена не включают маршрутизацию. После восстановления квоты обновите её: полные свежие данные с доступной ёмкостью разрешают небольшой проверочный запрос. Только успешное завершение включает аккаунт. Ошибки сохраняют ограничение. Пассивный опрос не отправляет такой запрос. Неизвестная квота при регистрации требует обычной проверки.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ Arayüzü](/tr/guides/sub-agent-surface/) sayfasına bakın.

## Codex hesap ısınması

Hesap ekleme veya yeniden kimlik doğrulama, normalde kaydetmeden önce `response.completed` bekleyen küçük bir model isteğiyle doğrulanır. Varsayılan model `gpt-5.4-mini` olup HTTP 400 durumunda `gpt-5.5` denenir. Genel hatalar ham yanıt gövdesi yerine sabit hata kategorilerini içerir.
Hesap ekleme veya yeniden kimlik doğrulama, normalde kaydetmeden önce `response.completed` bekleyen küçük bir model isteğiyle doğrulanır. Varsayılan model `gpt-5.4-mini` olup HTTP 400 veya HTTP 404 durumunda `gpt-5.5` ve `gpt-5.6-luna` denenir. Genel hatalar ham yanıt gövdesi yerine sabit hata kategorilerini içerir.

Yeni OAuth belirteciyle yapılan kota sorgusu 5 saatlik, haftalık veya aylık kotanın tükendiğini doğrularsa hesap model çağrısı olmadan kaydedilir ve **Doğrulama bekleniyor** gösterilir. Yeniden başlatma veya belirteç yenileme yönlendirmeyi açmaz. Kota geri geldiğinde kotaları yenileyin: kullanılabilir kapasite gösteren eksiksiz güncel veri küçük bir doğrulama isteğine izin verir. Yalnızca tamamlanan yanıt hesabı etkinleştirir. Hatalarda kısıtlama korunur. Pasif sorgulama bu isteği göndermez. İlk kayıtta bilinmeyen kota normal doğrulamayı gerektirir.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ fallback 行为,参见 [Sub-agent Surface](/guides/sub-agent-surface/)。

## Codex 账号预热

添加或重新认证账号时,通常会在保存前发送一个小型模型请求并等待 `response.completed`。默认使用 `gpt-5.4-mini`,HTTP 400 时改用 `gpt-5.5` 重试。公开错误仅包含固定分类,不包含原始响应正文。
添加或重新认证账号时,通常会在保存前发送一个小型模型请求并等待 `response.completed`。默认使用 `gpt-5.4-mini`,HTTP 400 或 HTTP 404 时改用 `gpt-5.5` 与 `gpt-5.6-luna` 重试。公开错误仅包含固定分类,不包含原始响应正文。

如果新 OAuth 凭据的已认证用量查询确认5小时、每周或每月额度耗尽,则不调用模型而直接保存账号,显示**等待验证**。重启或刷新令牌也不会使其可用。额度恢复后刷新额度:只有完整的最新用量显示有余额,才会发送一个小型验证请求;请求完成后账号才可用于路由。查询或验证失败将保留等待状态。普通状态轮询不会发送该请求。初次注册时用量未知仍需常规预热验证。

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ ocx service install # 常駐:登入時自動啟動,崩潰後自動重新

## Codex 帳號預熱

新增或重新驗證帳號時,通常會在儲存前傳送小型模型請求並等待 `response.completed`。預設使用 `gpt-5.4-mini`,HTTP 400 時改用 `gpt-5.5` 重試。公開錯誤僅包含固定分類,不包含原始回應本文。
新增或重新驗證帳號時,通常會在儲存前傳送小型模型請求並等待 `response.completed`。預設使用 `gpt-5.4-mini`,HTTP 400 或 HTTP 404 時改用 `gpt-5.5` 與 `gpt-5.6-luna` 重試。公開錯誤僅包含固定分類,不包含原始回應本文。

若新 OAuth 憑證的已驗證用量查詢確認5小時、每週或每月額度耗盡,則不呼叫模型而直接儲存帳號,顯示**等待驗證**。重新啟動或更新權杖也不會使其可用。額度恢復後重新整理額度:只有完整的最新用量顯示有餘額,才會傳送小型驗證請求;請求完成後帳號才可用於路由。查詢或驗證失敗將保留等待狀態。一般狀態輪詢不會傳送該請求。首次註冊時用量未知仍需一般預熱驗證。

Expand Down
14 changes: 10 additions & 4 deletions src/codex/warmup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export interface CodexWarmupOptions {

const CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
const DEFAULT_MODEL = "gpt-5.4-mini";
const FALLBACK_MODELS = ["gpt-5.5"];
const FALLBACK_MODELS = ["gpt-5.5", "gpt-5.6-luna"];
const isRetryableWarmupStatus = (status?: number): boolean => status === 400 || status === 404;
const DEFAULT_TIMEOUT_MS = 30_000;
const MAX_TIMEOUT_MS = 0x7fff_ffff;
const MAX_ERROR_BODY_BYTES = 2048;
Expand Down Expand Up @@ -286,16 +287,21 @@ export async function warmCodexAccount(options: CodexWarmupOptions): Promise<voi
await tryWarmup(options, primaryModel);
return;
} catch (err) {
// Retry with fallback models on 400 (model may not be available for this account).
if (!(err instanceof CodexWarmupError) || err.status !== 400) throw err;
// Retry with fallback models on 400 or 404 (model may not be provisioned or available for this account).
if (!(err instanceof CodexWarmupError) || !isRetryableWarmupStatus(err.status)) throw err;
let lastErr = err;
for (const fallback of FALLBACK_MODELS) {
if (fallback === primaryModel) continue;
try {
await tryWarmup(options, fallback);
return;
} catch (retryErr) {
if (retryErr instanceof CodexWarmupError) lastErr = retryErr;
if (retryErr instanceof CodexWarmupError) {
lastErr = retryErr;
if (!isRetryableWarmupStatus(retryErr.status)) throw retryErr;
} else {
throw retryErr;
}
}
}
throw lastErr;
Expand Down
50 changes: 50 additions & 0 deletions tests/codex-integration/warmup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,54 @@ describe("codex warmup improvements", () => {
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(parsedBodies.map(body => body.model)).toEqual(["gpt-5.4-mini", "gpt-5.5"]);
});
test("warmCodexAccount retries FALLBACK_MODELS on HTTP 404 and falls through to gpt-5.6-luna", async () => {
const parsedBodies: Record<string, unknown>[] = [];
const fetchMock = mock(async (_input: RequestInfo | URL, init?: RequestInit) => {
const body = JSON.parse(String(init?.body)) as Record<string, unknown>;
parsedBodies.push(body);

if (body.model === "gpt-5.4-mini") {
return new Response(JSON.stringify({ detail: "model not found" }), { status: 404 });
}
if (body.model === "gpt-5.5") {
return new Response(JSON.stringify({ detail: "model not supported for free tier" }), { status: 400 });
}
if (body.model === "gpt-5.6-luna") {
return sseResponse();
}
return new Response("unexpected model", { status: 500 });
});
const fetchSpy = spyOn(globalThis, "fetch").mockImplementation(fetchMock as unknown as typeof fetch);

try {
await warmCodexAccount({ accessToken: "access-test", chatgptAccountId: "acct-test" });
} finally {
fetchSpy.mockRestore();
}

expect(fetchMock).toHaveBeenCalledTimes(3);
expect(parsedBodies.map(body => body.model)).toEqual(["gpt-5.4-mini", "gpt-5.5", "gpt-5.6-luna"]);
});

test("warmCodexAccount does not retry on 401 and immediately fails", async () => {
const parsedBodies: Record<string, unknown>[] = [];
const fetchMock = mock(async (_input: RequestInfo | URL, init?: RequestInit) => {
const body = JSON.parse(String(init?.body)) as Record<string, unknown>;
parsedBodies.push(body);
return new Response(JSON.stringify({ detail: "unauthorized" }), { status: 401 });
});
const fetchSpy = spyOn(globalThis, "fetch").mockImplementation(fetchMock as unknown as typeof fetch);

try {
await warmCodexAccount({ accessToken: "access-test", chatgptAccountId: "acct-test" });
expect.unreachable("expected warmCodexAccount to throw");
} catch (err) {
expect(err).toBeInstanceOf(CodexWarmupError);
expect((err as CodexWarmupError).status).toBe(401);
} finally {
fetchSpy.mockRestore();
}

expect(fetchMock).toHaveBeenCalledTimes(1);
});
});
Loading