Skip to content
Merged
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
4 changes: 4 additions & 0 deletions docs-site/src/content/docs/fr/guides/combos.md
Original file line number Diff line number Diff line change
Expand Up @@ -374,3 +374,7 @@ gérer un refus de politique ou corriger l’origine de la demande rejetée. Les
Exception aux erreurs 400 terminales : un rejet structuré de `user`, une valeur non prise en charge pour `reasoning.effort`/`reasoning_effort`, un rejet d’entrée d’image propre à un modèle (`param: input`), ou le refus d’un média d’entrée que la route ne peut pas lire (`unsupported_input_modality`) peut faire passer le combo à la cible admissible suivante avant le début de la sortie, sans délai de refroidissement. Le refus de sécurité, l’annulation et une sortie déjà commencée restent non rejouables.

[Canonical compatibility details](/guides/combos/#request-local-target-compatibility).

## Réservation d’une tentative de récupération

Une tentative de récupération activée par `cooldownReprobe: { enabled: true }` appartient uniquement à la requête qui l’a réservée ; les autres requêtes restent soumises aux délais de refroidissement enregistrés. La réservation est conservée pendant l’attente et la sélection JEV. Une cible redevenue utilisable pendant l’attente peut être réessayée si son délai enregistré n’a pas changé. Un nouvel enregistrement annule la réservation de cette cible. Un succès ne supprime que le délai inchangé de la cible ayant répondu ; les cibles non essayées et les nouveaux échecs conservent leurs délais. La désactivation du fournisseur, les quotas, l’admissibilité de la requête et l’annulation restent vérifiés. Voir les [réglages et limites](/guides/combos/#retry-all-cooled-targets-opt-in).
7 changes: 7 additions & 0 deletions docs-site/src/content/docs/guides/combos.md
Original file line number Diff line number Diff line change
Expand Up @@ -1068,3 +1068,10 @@ enforced. Explicit provider Retry-After values may therefore be retried early on
when the operator enables this option. Preview/list operations never clear cooldowns.
The interval is tracked per combo in memory and resets on proxy restart or combo
selection-state reset. Existing cooldown-wait behavior is unchanged when disabled.

A recovery pass belongs only to the request that reserved it; other requests still
observe the saved cooldowns. That reservation follows the request through cooldown
waits and JEV selection. A target that becomes usable again during a wait can still
be retried if its cooldown has not changed. A newer cooldown cancels that target's
reservation. Success clears only the unchanged cooldown of the target that answered;
untried targets and newer failures keep their cooldowns.
4 changes: 4 additions & 0 deletions docs-site/src/content/docs/ja/guides/combos.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,3 +273,7 @@ ocx combo remove <id> --yes
通常の 400 エラーは終了扱いですが、`user` の明示的な拒否、`reasoning.effort`/`reasoning_effort` の非対応値、モデル固有の画像入力拒否(`param: input`)、またはルートが読み取れない入力メディアの拒否(`unsupported_input_modality`)を示す構造化エラーでは、出力開始前に次の適格なターゲットへ進めます。この不一致ではクールダウンを記録しません。安全ポリシーによる拒否、キャンセル、出力開始後の再実行は禁止のままです。

[Canonical compatibility details](/guides/combos/#request-local-target-compatibility).

## クールダウン回復の予約

`cooldownReprobe: { enabled: true }` で有効にした回復試行は、予約したリクエストだけに属し、他のリクエストには保存済みのクールダウンが引き続き適用されます。予約はクールダウン待機と JEV 選択を経ても維持されます。待機中に再び利用可能になったターゲットは、クールダウンが変わっていなければ再試行できますが、新しいクールダウンの記録はそのターゲットの予約を無効にします。成功時は応答したターゲットの変更されていないクールダウンだけを解除し、未試行のターゲットや新しい失敗のクールダウンは残します。プロバイダーの無効化、クォータ、リクエストの適格性、キャンセルの確認は引き続き有効です。[設定と制限](/guides/combos/#retry-all-cooled-targets-opt-in)を参照してください。
4 changes: 4 additions & 0 deletions docs-site/src/content/docs/ko/guides/combos.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,7 @@ opencodex 인스턴스에 기록했는지 확인하세요.
일반적인 400 오류는 종료되지만, `user`를 명시적으로 거부하거나 `reasoning.effort`/`reasoning_effort`의 지원되지 않는 값, 모델별 이미지 입력 거부(`param: input`) 또는 경로가 읽을 수 없는 입력 미디어 거부(`unsupported_input_modality`)를 나타내는 구조화된 오류는 출력 시작 전에 다음 적격 대상으로 넘어갈 수 있습니다. 이 경우 쿨다운을 기록하지 않습니다. 보안 정책 거부, 취소 및 이미 시작된 출력은 재실행하지 않습니다.

[Canonical compatibility details](/guides/combos/#request-local-target-compatibility).

## 쿨다운 복구 예약

`cooldownReprobe: { enabled: true }`로 활성화한 복구 시도는 이를 예약한 요청에만 속하며, 다른 요청에는 저장된 쿨다운이 계속 적용됩니다. 예약은 쿨다운 대기와 JEV 선택을 거쳐 유지됩니다. 대기 중 다시 사용 가능해진 대상은 기존 쿨다운이 바뀌지 않았다면 재시도할 수 있지만, 새 쿨다운 기록은 그 대상의 예약을 무효화합니다. 성공하면 응답한 대상의 변경되지 않은 쿨다운만 해제하며, 시도하지 않은 대상과 새로운 실패의 쿨다운은 유지합니다. 공급자 비활성화, 할당량, 요청 적격성 및 취소 검사는 계속 적용됩니다. [설정과 제한](/guides/combos/#retry-all-cooled-targets-opt-in)을 참고하세요.
4 changes: 4 additions & 0 deletions docs-site/src/content/docs/ru/guides/combos.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,3 +335,7 @@ CLI и дашборд показывают точное сообщение ва
Исключение для завершающих ошибок 400: структурированный отказ от `user`, неподдерживаемое значение `reasoning.effort`/`reasoning_effort`, специфичный для модели отказ входного изображения (`param: input`) или отказ маршрута, который не может прочитать входные медиаданные (`unsupported_input_modality`) позволяет до начала вывода перейти к следующей допустимой цели без периода охлаждения. Отказ политики безопасности, отмена и уже начавшийся вывод по-прежнему запрещают повторное выполнение.

[Canonical compatibility details](/guides/combos/#request-local-target-compatibility).

## Резервирование восстановления после cooldown

Попытка восстановления, включённая через `cooldownReprobe: { enabled: true }`, принадлежит только зарезервировавшему её запросу; для других запросов сохранённые cooldown продолжают действовать. Резервирование сохраняется при ожидании cooldown и выборе JEV. Если цель снова становится доступной во время ожидания, её можно повторно попробовать, пока запись cooldown не изменилась. Новая запись отменяет резервирование этой цели. Успех снимает только неизменённый cooldown ответившей цели; для непроверенных целей и новых сбоев cooldown сохраняется. Проверки отключения провайдера, квоты, допустимости запроса и отмены остаются в силе. См. [настройки и ограничения](/guides/combos/#retry-all-cooled-targets-opt-in).
4 changes: 4 additions & 0 deletions docs-site/src/content/docs/tr/guides/combos.md
Original file line number Diff line number Diff line change
Expand Up @@ -414,3 +414,7 @@ reddedilen istek kaynağını düzeltin. Kombolar bu durumlar için atlama yapma
Sonlandırıcı 400 hatalarının dar bir istisnası vardır: `user` alanını açıkça reddeden, `reasoning.effort`/`reasoning_effort` için desteklenmeyen değer bildiren, modele özgü görüntü girdisini reddeden (`param: input`) veya rotanın okuyamadığı girdi medyasını reddeden (`unsupported_input_modality`) yapılandırılmış hata, çıktı başlamadan önce sonraki uygun hedefe geçebilir. Bu uyumsuzluk için bekleme süresi kaydedilmez. Güvenlik politikası reddi, iptal ve başlamış çıktı yeniden yürütülmez.

[Canonical compatibility details](/guides/combos/#request-local-target-compatibility).

## Soğuma kurtarma rezervasyonu

`cooldownReprobe: { enabled: true }` ile etkinleştirilen kurtarma denemesi yalnızca onu ayıran isteğe aittir; diğer istekler kayıtlı soğuma sürelerine uymaya devam eder. Rezervasyon, soğuma beklemesi ve JEV seçimi boyunca korunur. Bekleme sırasında yeniden kullanılabilir olan hedef, soğuma kaydı değişmediyse tekrar denenebilir. Yeni bir soğuma kaydı o hedefin rezervasyonunu geçersiz kılar. Başarı yalnızca yanıt veren hedefin değişmemiş soğuma kaydını kaldırır; denenmeyen hedefler ve yeni hatalar soğuma kayıtlarını korur. Sağlayıcının devre dışı olması, kota, istek uygunluğu ve iptal kontrolleri geçerliliğini korur. [Ayarlar ve sınırlar](/guides/combos/#retry-all-cooled-targets-opt-in) bölümüne bakın.
4 changes: 4 additions & 0 deletions docs-site/src/content/docs/zh-cn/guides/combos.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,3 +303,7 @@ combo id 不存在。响应是 HTTP 404,类型为 `invalid_request_error`。
一般 400 错误仍会终止请求,但明确拒绝 `user`、对 `reasoning.effort`/`reasoning_effort` 返回不支持值、返回模型特定图像输入拒绝(`param: input`),或拒绝该路由无法读取的输入媒体(`unsupported_input_modality`)的结构化错误,可让 combo 在输出开始前尝试下一个符合条件的目标,而不记录冷却时间。安全策略拒绝、取消以及已经开始的输出仍不可重放。

[Canonical compatibility details](/guides/combos/#request-local-target-compatibility).

## 冷却恢复预约

通过 `cooldownReprobe: { enabled: true }` 启用的恢复尝试只属于预约它的请求;其他请求仍受已保存的冷却限制。预约会在冷却等待和 JEV 选择期间保留。目标在等待期间重新可用时,只要其冷却记录没有变化,就仍可重试;新的冷却记录会使该目标的预约失效。成功后,只清除实际响应目标未改变的冷却记录;未尝试的目标和新的失败仍保留冷却。提供方禁用、配额、请求资格和取消检查仍然生效。请参阅[设置与限制](/guides/combos/#retry-all-cooled-targets-opt-in)。
4 changes: 4 additions & 0 deletions docs-site/src/content/docs/zh-tw/guides/combos.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,3 +300,7 @@ Combo id 未知。回應為 HTTP 404 並帶 type `invalid_request_error`。執
一般 400 錯誤仍會終止請求,但明確拒絕 `user`、對 `reasoning.effort`/`reasoning_effort` 回傳不支援值、回傳模型特定影像輸入拒絕(`param: input`),或拒絕該路由無法讀取的輸入媒體(`unsupported_input_modality`)的結構化錯誤,可讓 combo 在輸出開始前嘗試下一個符合條件的目標,而不記錄冷卻時間。安全政策拒絕、取消及已開始的輸出仍不可重播。

[Canonical compatibility details](/guides/combos/#request-local-target-compatibility).

## 冷卻復原預約

透過 `cooldownReprobe: { enabled: true }` 啟用的復原嘗試只屬於預約它的請求;其他請求仍受已儲存的冷卻限制。預約會在冷卻等待與 JEV 選擇期間保留。目標在等待期間重新可用時,只要其冷卻紀錄沒有變更,就仍可重試;新的冷卻紀錄會使該目標的預約失效。成功後,只清除實際回應目標未變更的冷卻紀錄;未嘗試的目標與新的失敗仍保留冷卻。供應商停用、配額、請求資格與取消檢查仍然生效。請參閱[設定與限制](/guides/combos/#retry-all-cooled-targets-opt-in)。
40 changes: 40 additions & 0 deletions src/combos/failover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {

interface TargetCooldown {
cooldownUntil: number;
revision: number;
status?: number;
}

Expand Down Expand Up @@ -44,6 +45,8 @@ const HTTP_MONTH_INDEX: Record<string, number> = {

/** Map<`${comboId}\0${provider/model}`, TargetCooldown> */
const targetCooldowns = new Map<string, TargetCooldown>();
// Never reset: an in-flight reservation may still refer to an entry cleared from the map.
let nextCooldownRevision = 0;
let lastReconciledGeneration = 0;
let liveComboTargets = new Set<string>();

Expand Down Expand Up @@ -163,6 +166,42 @@ export function isComboTargetInCooldown(
return true;
}

/** The live cooldown expiry for one target, or undefined once it is absent or elapsed. */
export function comboTargetCooldownExpiry(
comboId: string,
target: Pick<OcxComboTarget, "provider" | "model">,
now = Date.now(),
): number | undefined {
const key = cooldownMapKey(comboId, target);
const entry = targetCooldowns.get(key);
if (!entry) return undefined;
if (entry.cooldownUntil <= now) {
targetCooldowns.delete(key);
return undefined;
}
return entry.cooldownUntil;
}

/** Identity of the current live write, independent of its possibly repeated expiry. */
export function comboTargetCooldownRevision(
comboId: string,
target: Pick<OcxComboTarget, "provider" | "model">,
now = Date.now(),
): number | undefined {
return isComboTargetInCooldown(comboId, target, now)
? targetCooldowns.get(cooldownMapKey(comboId, target))?.revision : undefined;
}

/** Lift only the reserved write; a replacement with even the same expiry survives. */
export function clearComboTargetCooldownIfUnchanged(
comboId: string,
target: Pick<OcxComboTarget, "provider" | "model">,
expectedRevision: number,
): void {
const key = cooldownMapKey(comboId, target);
if (targetCooldowns.get(key)?.revision === expectedRevision) targetCooldowns.delete(key);
}

export function isTransientRequestRateLimit(input: {
status?: number;
code?: string | null;
Expand Down Expand Up @@ -262,6 +301,7 @@ export function coolComboTarget(
message: options?.message,
}) ? COMBO_REQUEST_RATE_COOLDOWN_MS : DEFAULT_COOLDOWN_MS);
targetCooldowns.set(cooldownMapKey(comboId, target), {
revision: ++nextCooldownRevision,
// Local fallbacks are capped at ten minutes; explicit server delays at one day.
cooldownUntil: now + (serverDelayMs ?? Math.min(Math.max(cooldownMs, 1), MAX_COOLDOWN_MS)),
status: options?.status,
Expand Down
1 change: 1 addition & 0 deletions src/combos/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export {
pickComboTarget,
pickComboTargetWithWait,
quotaInactiveReason,
settleComboReprobeSuccess,
snapshotComboQuotaCooldowns,
tryPickComboModel,
UnknownComboError,
Expand Down
Loading
Loading