Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,16 @@ Les fournisseurs désactivés sont exclus. Un espace de noms explicite qui dési

### Redirections des modèles bloqués

`blockedModelRedirects` est un `Record<string, string>` facultatif de premier niveau associant des remplacements exacts d’identifiants de modèle résolus ; il est non défini par défaut. Il s’applique après l’ordre de résolution ci-dessus : une correspondance conserve la route du fournisseur et du compte déjà sélectionnée, ne remplace que l’identifiant du modèle en amont et enregistre le motif de routage `blocked-model-redirect`. L’omission de la clé ne modifie pas le routage.
`blockedModelRedirects` est un `Record<string, string>` facultatif de premier niveau définissant les remplacements d'identifiants de modèle, non défini par défaut. Lorsqu'un modèle entrant correspond à une clé, il est redirigé vers le modèle de substitution cible. Les modèles cibles peuvent être résolus au sein du même fournisseur ou réacheminés vers un autre fournisseur (par exemple `google-antigravity/gemini-3.8-flash-high`), avec prise en charge des redirections en chaîne multi-sauts (jusqu'à une profondeur maximale de 5 sauts avec détection de boucle). Le motif de routage est enregistré sous `blocked-model-redirect`. L'omission de la clé ne modifie pas le routage.

La correspondance est également effectuée sur le modèle natif résolu à partir d'un alias ; les alias menant à des modèles natifs bloqués sont donc redirigés en conséquence. Les redirections inter-fournisseurs qualifiées par un compte nécessitent une clé exacte (par exemple `side/gpt-5.6-terra`) ; les clés non qualifiées échouent de manière sécurisée (fail closed) sous un espace de noms de compte, et les destinations inter-fournisseurs utilisent directement le fournisseur cible sans hériter des identifiants ou informations d'authentification du fournisseur source, des champs de compte (`codexAccountId`, `codexAccountNamespace`) ni des quotas.

```json
{
"blockedModelRedirects": { "gpt-5.6-terra": "gpt-5.6-luna" }
"blockedModelRedirects": {
"gpt-5.6-terra": "gpt-5.6-luna",
"gpt-5.6-anon": "google-antigravity/gemini-3.8-flash-high"
}
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,16 @@ opencodex は、要求されたモデルを次の順序で解決します。

### ブロック対象モデルのリダイレクト

`blockedModelRedirects` は、完全一致する解決済みモデル ID の置換を指定する任意のトップレベル `Record<string, string>` で、デフォルトでは未設定です。上記の解決順序の後に適用されます。一致した場合、すでに選択されたプロバイダーとアカウントのルートは維持され、上流モデル ID のみが置き換えられ、ルート理由として `blocked-model-redirect` が記録されます。このキーを省略すると、ルーティングは変更されません。
`blockedModelRedirects` は、モデル ID の置換を指定する任意のトップレベル `Record<string, string>` で、デフォルトでは未設定です。受信したモデルがキーに一致した場合、対象の置換モデルにリダイレクトされます。対象モデルは同一プロバイダー内での置換だけでなく、別プロバイダーへの再ルーティング(例: `google-antigravity/gemini-3.8-flash-high`)も可能で、複数ホップの連鎖リダイレクト(ループ検出および最大 5 ホップ制限)をサポートします。ルート理由として `blocked-model-redirect` が記録されます。このキーを省略すると、ルーティングは変更されません。
Comment thread
coderabbitai[bot] marked this conversation as resolved.

マッチングはエイリアスから解決されたネイティブ モデルに対しても実行されるため、ブロック対象のネイティブ モデルに解決されるエイリアスも同様にリダイレクトされます。アカウント修飾されたクロスプロバイダー リダイレクトには完全一致するキー(例: `side/gpt-5.6-terra`)が必要であり、アカウント名前空間付きのベア キーは fail closed します。クロスプロバイダーの宛先は、ソース プロバイダーの資格情報や認証情報、アカウント フィールド(`codexAccountId`、`codexAccountNamespace`)、クォータを継承せずにターゲット プロバイダーを直接使用します。

```json
{
"blockedModelRedirects": { "gpt-5.6-terra": "gpt-5.6-luna" }
"blockedModelRedirects": {
"gpt-5.6-terra": "gpt-5.6-luna",
"gpt-5.6-anon": "google-antigravity/gemini-3.8-flash-high"
}
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,16 @@ opencodex는 요청된 model을 다음 순서로 해석합니다:

### 차단된 모델 리디렉션

`blockedModelRedirects`는 기본적으로 설정되지 않는 선택적 최상위 `Record<string, string>`이며, 정확히 일치하는 해석된 모델 ID의 대체값을 정의합니다. 위 해석 순서가 끝난 후 적용됩니다. 일치하면 이미 선택된 공급자와 계정 경로는 유지하고 업스트림 모델 ID만 교체하며, 경로 사유를 `blocked-model-redirect`로 기록합니다. 이 키를 생략하면 라우팅이 바뀌지 않습니다.
`blockedModelRedirects`는 기본적으로 설정되지 않는 선택적 최상위 `Record<string, string>`이며, 모델 ID의 대체값을 정의합니다. 수신된 모델이 키와 일치하면 대상 대체 모델로 리디렉션됩니다. 대상 모델은 동일한 공급자 내에서 대체되거나 다른 공급자로 교차 재라우팅(예: `google-antigravity/gemini-3.8-flash-high`)될 수 있으며, 멀티홉 체인 리디렉션(사이클 감지 및 최대 5홉 깊이 제한)을 지원합니다. 경로 사유는 `blocked-model-redirect`로 기록됩니다. 이 키를 생략하면 라우팅이 바뀌지 않습니다.

매칭은 별칭에서 확인된 네이티브 모델에 대해서도 수행되므로 차단된 네이티브 모델로 해석되는 별칭도 적절히 리디렉션됩니다. 계정 한정 교차 공급자 리디렉션에는 정확한 키(예: `side/gpt-5.6-terra`)가 필요하며, 계정 네임스페이스 요청에서 베어 키는 fail closed 합니다. 교차 공급자 대상은 소스 공급자의 자격 증명, 인증 정보, 소스 계정 필드(`codexAccountId`, `codexAccountNamespace`) 또는 할당량을 상속하지 않고 대상 공급자를 직접 사용합니다.

```json
{
"blockedModelRedirects": { "gpt-5.6-terra": "gpt-5.6-luna" }
"blockedModelRedirects": {
"gpt-5.6-terra": "gpt-5.6-luna",
"gpt-5.6-anon": "google-antigravity/gemini-3.8-flash-high"
}
}
```

Expand Down
17 changes: 12 additions & 5 deletions docs-site/src/content/docs/reference/configuration/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,21 @@ more than one provider, so use explicit namespaces when a bare model could be am

### Blocked-model redirects

`blockedModelRedirects` is an optional top-level `Record<string, string>` of exact resolved
model-id replacements, unset by default. It runs **after** the resolution order above: a match
keeps the provider and account route already selected, replaces only the upstream model id, and
records the route reason `blocked-model-redirect`. Omitting the key leaves routing unchanged.
`blockedModelRedirects` is an optional top-level `Record<string, string>` of exact model-id
replacements, unset by default. When an incoming model matches a key, it is redirected to the
target replacement model. Target models can resolve to the same provider or re-route across providers
(e.g. `google-antigravity/gemini-3.8-flash-high`), with multi-hop chained redirects supported (up to
a maximum depth of 5 hops with cycle detection). The route reason is recorded as
`blocked-model-redirect`. Omitting the key leaves routing unchanged.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Matching is also performed against the native model resolved from an alias, so aliases resolving to blocked native models redirect accordingly. Account-qualified cross-provider redirects require an exact key (e.g. `side/gpt-5.6-terra`); bare keys fail closed when account-namespaced, and cross-provider destinations use the target provider directly without inheriting source provider credentials, authentication material, account fields (`codexAccountId`, `codexAccountNamespace`), or quotas.

```json
{
"blockedModelRedirects": { "gpt-5.6-terra": "gpt-5.6-luna" }
"blockedModelRedirects": {
"gpt-5.6-terra": "gpt-5.6-luna",
"gpt-5.6-anon": "google-antigravity/gemini-3.8-flash-high"
}
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,21 @@ opencodex разрешает запрошенную модель в следую

### Перенаправления заблокированных моделей

`blockedModelRedirects` — необязательный верхнеуровневый `Record<string, string>` точных замен
разрешённых идентификаторов моделей; по умолчанию не задан. Он применяется после описанного выше
порядка разрешения: при совпадении уже выбранный маршрут провайдера и аккаунта сохраняется, заменяется
только идентификатор вышестоящей модели, а причиной маршрута записывается `blocked-model-redirect`.
Если ключ отсутствует, маршрутизация не меняется.
`blockedModelRedirects` — необязательный верхнеуровневый `Record<string, string>` замен
идентификаторов моделей; по умолчанию не задан. При совпадении входящей модели с ключом запрос
перенаправляется на целевую модель. Целевая модель может находиться у того же провайдера или
перемаршрутизироваться к другому провайдеру (например, `google-antigravity/gemini-3.8-flash-high`),
с поддержкой многозвенных цепочек перенаправления (до 5 переходов с обнаружением циклов). Причиной
маршрута записывается `blocked-model-redirect`. Если ключ отсутствует, маршрутизация не меняется.

Сопоставление также выполняется с нативной моделью, полученной из псевдонима, поэтому псевдонимы, ведущие к заблокированным нативным моделям, перенаправляются соответствующим образом. Для перенаправлений между провайдерами с указанием аккаунта требуется точный ключ (например, `side/gpt-5.6-terra`); голые ключи в пространстве имён аккаунта завершаются ошибкой (fail closed), а целевые маршруты другого провайдера используют целевой провайдер напрямую без наследования учетных данных исходного провайдера, материалов аутентификации, полей аккаунта (`codexAccountId`, `codexAccountNamespace`) или квот.

```json
{
"blockedModelRedirects": { "gpt-5.6-terra": "gpt-5.6-luna" }
"blockedModelRedirects": {
"gpt-5.6-terra": "gpt-5.6-luna",
"gpt-5.6-anon": "google-antigravity/gemini-3.8-flash-high"
}
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,23 @@ olabileceğinde açık ad alanları kullanın.

### Engellenen model yeniden yönlendirmeleri

`blockedModelRedirects`, varsayılan olarak ayarlanmamış, tam çözümlenmiş model
kimliği değiştirmelerinden oluşan isteğe bağlı üst düzey bir
`Record<string, string>` eşlemesidir. Yukarıdaki çözümleme sırasından sonra
çalışır: bir eşleşme önceden seçilmiş sağlayıcı ve hesap rotasını korur, yalnızca
yukarı akış model kimliğini değiştirir ve rota nedenini
`blocked-model-redirect` olarak kaydeder. Anahtarın atlanması yönlendirmeyi
değiştirmez.
`blockedModelRedirects`, varsayılan olarak ayarlanmamış, model kimliği
değiştirmelerinden oluşan isteğe bağlı üst düzey bir `Record<string, string>`
eşlemesidir. Gelen bir model anahtarla eşleştiğinde, hedef yedek modele yeniden
yönlendirilir. Hedef modeller aynı sağlayıcı içinde çözülebilir veya farklı bir
sağlayıcıya yeniden yönlendirilebilir (ör. `google-antigravity/gemini-3.8-flash-high`).
Döngü algılama ve en fazla 5 atlama derinliği ile çoklu atlamalı zincirleme
yeniden yönlendirmeler desteklenir. Rota nedeni `blocked-model-redirect` olarak
kaydedilir. Anahtarın atlanması yönlendirmeyi değiştirmez.

Eşleştirme ayrıca bir takma addan çözümlenen yerel model için de gerçekleştirilir; bu nedenle engellenen yerel modellere çözümlenen takma adlar da uygun şekilde yeniden yönlendirilir. Hesap nitelikli sağlayıcılar arası yeniden yönlendirmeler tam bir anahtar gerektirir (ör. `side/gpt-5.6-terra`); hesap ad alanında yalın anahtarlar güvenli şekilde başarısız olur (fail closed) ve sağlayıcılar arası hedefler, kaynak sağlayıcı kimlik bilgilerini, kimlik doğrulama materyallerini, kaynak hesap alanlarını (`codexAccountId`, `codexAccountNamespace`) veya kotaları devralmadan doğrudan hedef sağlayıcıyı kullanır.

```json
{
"blockedModelRedirects": { "gpt-5.6-terra": "gpt-5.6-luna" }
"blockedModelRedirects": {
"gpt-5.6-terra": "gpt-5.6-luna",
"gpt-5.6-anon": "google-antigravity/gemini-3.8-flash-high"
}
}
```

Expand Down Expand Up @@ -311,4 +317,3 @@ değişmeden ayrıştırılır. Geçmiş dizini tek kullanımlıktır -
otomatik bir yeniden oluşturmayı tetikler; `ocx logs rebuild-index` bunu zorlar.
Bu sistemdeki hiçbir şey ağırlıkları, bütçeleri veya aday kümelerini otomatik
olarak ayarlamaz.

Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,16 @@ opencodex 按以下顺序解析请求的模型:

### 被阻止模型重定向

`blockedModelRedirects` 是可选的顶层 `Record<string, string>`,用于精确替换已解析的模型 ID,默认未设置。它在上述解析顺序之后运行:匹配后会保留已选定的提供方和账户路由,仅替换上游模型 ID,并记录路由原因 `blocked-model-redirect`。省略该键则路由保持不变。
`blockedModelRedirects` 是可选的顶层 `Record<string, string>`,用于定义模型 ID 的替换规则,默认未设置。当传入的模型匹配键值时,将被重定向至目标替代模型。目标模型可在同一提供方内替换或跨提供方重新路由(例如 `google-antigravity/gemini-3.8-flash-high`),并支持多跳链式重定向(具有循环检测和最多 5 跳深度限制)。路由原因记录为 `blocked-model-redirect`。省略该键则路由保持不变。

匹配也会针对从别名解析出的原生模型进行,因此解析为被阻止原生模型的别名也会相应重定向。限定账户的跨提供方重定向需要精确键值(例如 `side/gpt-5.6-terra`);在账户命名空间下裸键会 fail closed,且跨提供方目标直接使用目标提供方,不继承来源提供方凭据、身份验证资料、来源账户字段(`codexAccountId`、`codexAccountNamespace`)或配额。

```json
{
"blockedModelRedirects": { "gpt-5.6-terra": "gpt-5.6-luna" }
"blockedModelRedirects": {
"gpt-5.6-terra": "gpt-5.6-luna",
"gpt-5.6-anon": "google-antigravity/gemini-3.8-flash-high"
}
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,16 @@ opencodex 依此順序解析請求的模型:

### 封鎖模型重新導向

`blockedModelRedirects` 是選用的頂層 `Record<string, string>`,用於精確替換已解析的模型 id,預設不設定。它在上述解析順序後執行:符合時會保留已選取的供應商與帳號路由,僅替換上游模型 id,並記錄路由原因 `blocked-model-redirect`。省略此鍵時,路由維持不變。
`blockedModelRedirects` 是選用的頂層 `Record<string, string>`,用於定義模型 ID 的替換規則,預設不設定。當傳入的模型符合鍵值時,將重新導向至目標替代模型。目標模型可維持在相同供應商或重新路由至其他供應商(例如 `google-antigravity/gemini-3.8-flash-high`),並支援多跳鏈式重新導向(具備循環偵測與最多 5 次跳轉深度限制)。路由原因將記錄為 `blocked-model-redirect`。省略此鍵時,路由維持不變。

比對亦會針對別名解析出的原生模型進行,因此解析為受封鎖原生模型的別名也會依規則重新導向。帳號限定的跨供應商重新導向需要精確鍵值(例如 `side/gpt-5.6-terra`);在帳號命名空間下裸鍵會 fail closed,且跨供應商目標直接使用目標供應商,不繼承來源供應商憑證、身分驗證資料、來源帳號欄位(`codexAccountId`、`codexAccountNamespace`)或配額。

```json
{
"blockedModelRedirects": { "gpt-5.6-terra": "gpt-5.6-luna" }
"blockedModelRedirects": {
"gpt-5.6-terra": "gpt-5.6-luna",
"gpt-5.6-anon": "google-antigravity/gemini-3.8-flash-high"
}
}
```

Expand Down
50 changes: 49 additions & 1 deletion src/lib/shadow-call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,55 @@ export function resolveBlockedModelRedirect(
if (!config?.blockedModelRedirects || typeof config.blockedModelRedirects !== "object") {
return undefined;
}
return config.blockedModelRedirects[modelId];
if (!Object.prototype.hasOwnProperty.call(config.blockedModelRedirects, modelId)) {
return undefined;
}
const target = config.blockedModelRedirects[modelId];
return typeof target === "string" && target.length > 0 ? target : undefined;
}

/**
* Resolves blocked model redirects recursively with cycle and depth detection.
*/
export interface BlockedModelRedirectState {
visited: Set<string>;
edges: number;
}

/**
* Resolves blocked model redirects recursively with cycle and depth detection.
*/
export function resolveBlockedModelRedirectChain(
config: { blockedModelRedirects?: Record<string, string> } | undefined,
modelId: string,
state?: BlockedModelRedirectState,
): { targetModel: string; redirected: boolean } {
if (!config?.blockedModelRedirects || typeof config.blockedModelRedirects !== "object") {
return { targetModel: modelId, redirected: false };
}
const redirectState = state ?? { visited: new Set<string>(), edges: 0 };
const { visited } = redirectState;
let current = modelId;
let redirected = false;

while (true) {
const next = resolveBlockedModelRedirect(config, current);
if (!next || next === current) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
break;
}
if (visited.has(current)) {
throw new Error(`Blocked model redirect cycle detected: ${[...visited, current].join(" -> ")}`);
}
visited.add(current);
redirectState.edges += 1;
if (redirectState.edges > 5) {
throw new Error(`Blocked model redirect exceeded maximum redirect depth (5): ${[...visited, next].join(" -> ")}`);
}
current = next;
redirected = true;
}

return { targetModel: current, redirected };
}

/** Normalize a persisted `sourceModels` override; falls back to the defaults. */
Expand Down
Loading
Loading