Skip to content
Draft
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 @@ -154,6 +154,8 @@ sauvegarde dont le contenu diffère, puis réécrit en identifiants sans préfix
| `unsafeAllowNativeLocalExec?` | `boolean` | Ancien booléen de Cursor, équivalent à `nativeLocalExec: "on"` uniquement lorsque le champ plus récent n'est pas défini. |
| `nativeLocalExec?` | `"off" \| "codex-sandbox" \| "on"` | Politique d'exécution locale de Cursor. `off` est la valeur par défaut ; actuellement, `codex-sandbox` échoue de manière sûre comme `off`. |

Pour les requêtes Responses traduites utilisant un pool de plusieurs clés, chaque invocation du fournisseur routé partage une limite de rotations de clés API entre la récupération initiale, les tours des ponts de recherche web, d’image et de vidéo, et les requêtes de continuation jusqu’à la fin de la réponse : au maximum `N - 1` rotations si la clé initiale appartient au pool, ou `N` si elle est extérieure au pool, où `N` est la taille du pool avant le premier envoi. L’expiration d’un délai de refroidissement ou l’agrandissement ultérieur du pool ne renouvelle pas cette limite. Les budgets d’envoi existants applicables peuvent arrêter les tentatives plus tôt ; les ponts sidecar conservent leurs propres limites de reprise après réinitialisation de connexion et de nouvelle tentative vers la même cible, sans que cette limite de rotation leur impose le budget d’envois physiques du core. Si la rotation est refusée, le dernier 429 enregistre toujours le délai de refroidissement de la clé en échec, mais aucune clé de remplacement n’est sélectionnée et la réponse suit le traitement d’erreur existant.

Les fournisseurs à clé API peuvent détenir une clé littérale ou une référence à une variable d'environnement. Les fournisseurs OAuth utilisent le
magasin d'identifiants alimenté par `ocx login` ; le comportement de lancement de Claude Code avec abonnement est
configuré sous [`claudeCode.authMode`](/fr/reference/configuration/server/#claude-code-claudecode).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ account を削除しても mapping は保持され、同じ id を再追加す
| `unsafeAllowNativeLocalExec?` | `boolean` |カーソルのレガシー ブール値。新しいフィールドが設定されていない場合のみ、`nativeLocalExec: "on"` と同等です。 |
| `nativeLocalExec?` | `"off" \| "codex-sandbox" \| "on"` |カーソルのローカル実行ポリシー。 `off` がデフォルトです。 `codex-sandbox` は現在、`off` と同様にフェールクローズされます。 |

複数キーのプールを使用する変換済み Responses リクエストでは、ルーティング先プロバイダーの呼び出しごとに、初期リカバリー、ウェブ検索・画像・動画ブリッジの各ラウンド、応答完了のための後続リクエストで API キーの切り替え上限を共有し、最初の送信前のプールサイズを `N` として、最初のキーがそのプール内にある場合は最大 `N - 1` 回、プール外にある場合は最大 `N` 回に固定されます。クールダウンの満了や後からのプール拡張で、この上限が補充されることはありません。適用対象となる既存の送信予算によって、再試行がさらに早く停止する場合もあります。サイドカーブリッジは接続リセット時と同一対象への再試行にそれぞれ既存の上限を維持し、この切り替え上限によって core の物理送信予算がブリッジに新たに適用されることはありません。切り替えが拒否された場合も、最後の 429 に対する失敗したキーのクールダウンは記録されますが、代わりのキーは選択されず、応答には既存のエラー処理が適用されます。

API キープロバイダーは、リテラルキーまたは環境参照を保持する場合があります。 OAuth プロバイダーは、`ocx login` によって設定された資格情報ストアを使用します。サブスクリプションに基づくクロード コードの起動動作は、[`claudeCode.authMode`](/reference/configuration/server/#claude-code) で構成されます。

## プロバイダーによるアウトバウンドの安全性診断
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ managed map을 활성화하면 privacy-safe selector를 만들고, 이후 계정
| `unsafeAllowNativeLocalExec?` | `boolean` | Cursor 레거시 불리언입니다. 더 새로운 필드가 설정되지 않았을 때만 `nativeLocalExec: "on"`과 같습니다. |
| `nativeLocalExec?` | `"off" \| "codex-sandbox" \| "on"` | Cursor 로컬 실행 정책입니다. 기본값은 `off`입니다. `codex-sandbox`는 현재 `off`처럼 실패를 닫습니다. |

여러 키가 있는 풀을 사용하는 변환된 Responses 요청에서는 라우팅된 프로바이더 호출마다 최초 복구, 웹 검색·이미지·비디오 브리지의 각 라운드, 응답 완료를 위한 후속 요청이 API 키 회전 한도를 공유하며, 첫 전송 전의 풀 크기를 `N`으로 고정하고 최초 키가 해당 풀에 있으면 최대 `N - 1`회, 풀 밖에 있으면 최대 `N`회 회전할 수 있습니다. 쿨다운이 만료되거나 이후 풀이 커져도 이 한도는 충전되지 않습니다. 적용 대상인 기존 전송 예산에 따라 재시도가 더 일찍 멈출 수 있습니다. 사이드카 브리지는 자체 연결 재설정 및 동일 대상 재시도 한도를 유지하며, 이 회전 한도가 브리지에 core의 물리적 전송 예산을 새로 적용하지는 않습니다. 회전이 거부되면 마지막 429에 따른 실패 키의 쿨다운은 기록하지만 대체 키를 선택하지 않으며, 응답은 기존 오류 처리 방식으로 마무리합니다.

API 키 공급자는 리터럴 키나 환경 참조를 둘 수 있습니다. OAuth 공급자는 `ocx login`으로 채워지는 자격 증명 저장소를 사용합니다. 구독 기반 Claude Code 실행 동작은 [`claudeCode.authMode`](/reference/configuration/server/#claude-code)에서 설정합니다.

## 공급자 진단용 외부 요청 안전성
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@ Providers can expose a built-in shorthand, such as `agy` for `google-antigravity
| `unsafeAllowNativeLocalExec?` | `boolean` | Cursor legacy boolean, equivalent to `nativeLocalExec: "on"` only when the newer field is unset. |
| `nativeLocalExec?` | `"off" \| "codex-sandbox" \| "on"` | Cursor local-exec policy. `off` is default; `codex-sandbox` currently fails closed like `off`. |

For translated Responses requests using a multi-key pool, each routed provider invocation shares its API-key rotation allowance between initial recovery, web-search/image/video bridge rounds, and terminal continuations: at most `N - 1` rotations if the initial key belongs to the pool, or `N` if it is outside the pool, where `N` is the pool size before the first send. Cooldown expiry or later pool growth does not replenish this allowance. Existing applicable send budgets may stop retries sooner; sidecar bridges retain their own connection-reset and same-target retry limits, and this rotation cap does not impose the core's physical-send budget on those bridges. Once rotation is refused, the last 429 still records the failed key's cooldown, but no replacement key is selected and the response follows the existing error handling.

With `webSearchBridge` enabled, a search continuation stays bound to the API-key selection that
served the first request. Changing the selected key, its reference or resolved value, authentication
mode, or base URL during search or provider pacing ends the turn with a bridge error before another
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ cross-route credential fallback не существует. Строки API GPT-
| `unsafeAllowNativeLocalExec?` | `boolean` | Legacy boolean Cursor, эквивалентен `nativeLocalExec: "on"` только если новое поле не задано. |
| `nativeLocalExec?` | `"off" \| "codex-sandbox" \| "on"` | Политика local-exec для Cursor. `off` — дефолт; `codex-sandbox` сейчас ведёт себя fail-closed как `off`. |

Для преобразованных запросов Responses с пулом из нескольких API-ключей каждый вызов выбранного маршрутизацией провайдера использует общий предел переключений для первоначального восстановления, раундов мостов веб-поиска, изображений и видео, а также последующих запросов, завершающих ответ: не более `N - 1`, если начальный ключ входит в пул, или `N`, если он находится вне пула; `N` фиксируется как размер пула до первой отправки. Истечение периода ожидания или последующее расширение пула не восстанавливает этот лимит. Применимые существующие бюджеты отправки могут остановить повторы раньше; мосты sidecar сохраняют собственные пределы повторов после сброса соединения и повторных запросов к той же цели, а этот предел переключений не распространяет на них бюджет физических отправок core. Если переключение запрещено, для последнего 429 всё равно записывается период ожидания отказавшего ключа, но другой ключ не выбирается, а ответ обрабатывается существующим механизмом обработки ошибок.

Провайдеры с API-key могут хранить literal key или environment-reference. OAuth-провайдеры
используют credential store, заполняемый через `ocx login`; поведение subscription-backed launcher'а
Claude Code настраивается через
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ alanlı seçilmiş kimlikleri yalın kimliklere yeniden yazar.
| `unsafeAllowNativeLocalExec?` | `boolean` | Cursor eski boolean değeri, yalnızca daha yeni alan ayarlanmadığında `nativeLocalExec: "on"` değerine eşdeğerdir. |
| `nativeLocalExec?` | `"off" \| "codex-sandbox" \| "on"` | Cursor yerel yürütme politikası. `off` varsayılandır; `codex-sandbox` şu anda `off` gibi kapalı olarak başarısız olur. |

Birden çok anahtar içeren havuz kullanan dönüştürülmüş Responses isteklerinde, yönlendirilen sağlayıcının her çağrısı ilk kurtarma, web araması/görüntü/video köprüsü turları ve yanıtı tamamlayan devam istekleri arasında bir API anahtarı değiştirme sınırını paylaşır: başlangıç anahtarı havuzdaysa en fazla `N - 1`, havuz dışındaysa en fazla `N` değişim yapılabilir; `N`, ilk gönderimden önceki havuz boyutuna sabitlenir. Bekleme süresinin dolması veya havuzun sonradan büyümesi bu hakkı yenilemez. Geçerli mevcut gönderim bütçeleri yeniden denemeleri daha erken durdurabilir; sidecar köprüleri bağlantı sıfırlaması ve aynı hedefe yeniden deneme için kendi sınırlarını korur ve bu anahtar değiştirme sınırı, core'un fiziksel gönderim bütçesini bu köprülere uygulamaz. Anahtar değişimi reddedildiğinde son 429 için başarısız anahtarın bekleme süresi yine kaydedilir, ancak yerine başka bir anahtar seçilmez ve yanıt mevcut hata işleme yolunu izler.

API anahtarı sağlayıcıları değişmez bir anahtar veya bir ortam referansı
tutabilir. OAuth sağlayıcıları `ocx login` tarafından doldurulan kimlik bilgisi
deposunu kullanır; abonelik destekli Claude Code başlatma davranışı
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ selector,而不是分配一个新名称。
| `unsafeAllowNativeLocalExec?` | `boolean` | Cursor 旧布尔值;仅当更新字段未设置时,等同于 `nativeLocalExec: "on"`。 |
| `nativeLocalExec?` | `"off" \| "codex-sandbox" \| "on"` | Cursor 本地执行策略。`off` 是默认值;`codex-sandbox` 目前会像 `off` 一样失败关闭。 |

对于使用多密钥池的转换后 Responses 请求,每次路由到提供商的调用,其初始恢复、网页搜索/图像/视频桥接的各轮调用以及用于完成响应的后续请求共同使用 API 密钥轮换额度:初始密钥在池中时最多轮换 `N - 1` 次,不在池中时最多轮换 `N` 次,其中 `N` 固定为首次发送前的密钥池大小。冷却期结束或随后扩大密钥池都不会补充此额度。现有且适用的发送预算可能让重试更早停止;sidecar 桥接仍保留自身的连接重置重试和同目标重试限制,此轮换额度不会将 core 的实际发送预算新增应用于这些桥接。轮换被拒绝时,仍会根据最后一个 429 记录失败密钥的冷却期,但不会选择替代密钥,响应继续按现有错误处理方式处理。

API key 提供者可以持有字面量 key,或环境引用。OAuth 提供者使用由 `ocx login` 填充的凭据存储;基于订阅的 Claude Code 启动行为在 [`claudeCode.authMode`](/reference/configuration/server/#claude-code) 下配置。

## 提供者诊断出站安全性
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ ocx models provider openrouter on
| `unsafeAllowNativeLocalExec?` | `boolean` | Cursor 舊版布林值,僅在較新欄位未設定時等同於 `nativeLocalExec: "on"`。 |
| `nativeLocalExec?` | `"off" \| "codex-sandbox" \| "on"` | Cursor 本機執行政策。`off` 為預設;`codex-sandbox` 目前像 `off` 般 fail closed。 |

對於使用多金鑰集區的轉換後 Responses 要求,每次路由至提供者的呼叫,其初始復原、網頁搜尋/影像/影片橋接的各輪呼叫以及用於完成回應的後續要求共同使用 API 金鑰輪替額度:初始金鑰在集區中時最多輪替 `N - 1` 次,不在集區中時最多輪替 `N` 次,其中 `N` 固定為首次傳送前的金鑰池大小。冷卻期結束或之後擴大金鑰池都不會補充此額度。現有且適用的傳送預算可能讓重試更早停止;sidecar 橋接仍保留自身的連線重設重試和同目標重試限制,此輪替額度不會將 core 的實際傳送預算新增套用至這些橋接。輪替遭拒時,仍會根據最後一個 429 記錄失敗金鑰的冷卻期,但不會選取替代金鑰,回應繼續依現有錯誤處理方式處理。

API-key 供應商可持有字面值金鑰或環境參考。OAuth 供應商使用由 `ocx login` 填入的憑證存放;訂閱支援的 Claude Code 啟動行為在 [`claudeCode.authMode`](/zh-tw/reference/configuration/server/#claude-code) 下設定。

## 供應商診斷對外安全
Expand Down
15 changes: 13 additions & 2 deletions src/providers/key-failover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ function rotateKeyAfterFailure(
now = Date.now(),
attemptedKey?: string,
attemptedSelection?: ProviderApiKeySelection,
allowRotation = true,
): OcxProviderConfig | null {
const provider = config.providers[providerName];
if (!provider) return null;
Expand All @@ -382,6 +383,10 @@ function rotateKeyAfterFailure(
? pool.find(entry => entry.id === attemptedSelection.entryId && entry.key === failedKey)
: pool.find(entry => entry.key === failedKey);

// A spent request still records the failed key, but must not select or persist an
// unattempted replacement. Keep the fresh identity check and the changed:false path.
if (!allowRotation) return { changed: false, value: { failedId: failedEntry?.id } };

if (freshProvider.apiKey !== failedKey) {
const activeEntry = pool.find(entry => entry.key === freshProvider.apiKey);
if (activeEntry && !isKeyInCooldown(providerName, activeEntry.id, now)) {
Expand Down Expand Up @@ -410,6 +415,7 @@ function rotateKeyAfterFailure(
}, attemptedSelection);
if (outcome.status === "unavailable") return null;
if (outcome.status === "superseded") {
if (!allowRotation) return null;
// A newer manual selection (including A→B→A) owns subsequent dispatch. Reusing the
// same failed key here would loop forever; preserve its original failure instead.
return outcome.provider.apiKey !== failedKey ? structuredClone(outcome.provider) : null;
Expand All @@ -425,6 +431,7 @@ function rotateKeyAfterFailure(
keyCooldowns.set(cooldownKey(providerName, outcome.value.failedId), { cooldownUntil: now + cooldownMs });
sweepExpiredOnWrite(now);
}
if (!allowRotation) return null;
if ("exhaustedCount" in outcome.value) {
console.warn(`[key-failover] ${providerName}: all ${outcome.value.exhaustedCount} keys in cooldown after ${failureStatus}; returning the upstream status to the client`);
return null;
Expand All @@ -448,8 +455,9 @@ export function rotateKeyOn429(
now = Date.now(),
attemptedKey?: string,
attemptedSelection?: ProviderApiKeySelection,
allowRotation = true,
): OcxProviderConfig | null {
return rotateKeyAfterFailure(config, providerName, 429, retryAfterHeader, now, attemptedKey, attemptedSelection);
return rotateKeyAfterFailure(config, providerName, 429, retryAfterHeader, now, attemptedKey, attemptedSelection, allowRotation);
}

/**
Expand Down Expand Up @@ -486,6 +494,8 @@ interface RotateProviderTransportOptions {
attemptedKey?: string;
attemptedSelection?: ProviderApiKeySelection;
promptCacheKey?: string;
/** False records a proven 429 cooldown without changing the selected key or returning a retry. */
allowRotation?: boolean;
}

/**
Expand All @@ -507,6 +517,7 @@ export function rotateProviderTransportOn429(
options.now,
options.attemptedKey,
options.attemptedSelection ?? routedProvider._apiKeyAttempt,
options.allowRotation,
);
if (!rotated) return null;
return applyRotatedTransport(providerName, routedProvider, rotated, options.promptCacheKey);
Expand All @@ -517,7 +528,7 @@ export function rotateProviderTransportOn401(
config: OcxConfig,
providerName: string,
routedProvider: OcxProviderTransport,
options: Omit<RotateProviderTransportOptions, "retryAfter"> = {},
options: Omit<RotateProviderTransportOptions, "retryAfter" | "allowRotation"> = {},
): OcxProviderTransport | null {
const rotated = rotateKeyOn401(config, providerName, options.now, options.attemptedKey,
options.attemptedSelection ?? routedProvider._apiKeyAttempt);
Expand Down
Loading
Loading