diff --git a/devlog/_plan/260808_context_cap_per_provider/evidence/01-models-context-caps.png b/devlog/_plan/260808_context_cap_per_provider/evidence/01-models-context-caps.png new file mode 100644 index 000000000..38eddee67 Binary files /dev/null and b/devlog/_plan/260808_context_cap_per_provider/evidence/01-models-context-caps.png differ diff --git a/docs-site/src/content/docs/guides/model-routing.md b/docs-site/src/content/docs/guides/model-routing.md index c1616e9e5..fdce20d79 100644 --- a/docs-site/src/content/docs/guides/model-routing.md +++ b/docs-site/src/content/docs/guides/model-routing.md @@ -85,9 +85,11 @@ Routing and catalog visibility are separate controls: - `provider.disabled: true` removes that provider from catalog discovery. Explicit `provider/model` requests fail, and `defaultModel` / `models[]` scans skip it. - `providerContextCaps` applies per-provider Codex-visible context caps. `contextCapValue` is the - shared dashboard value (350,000 by default), but it does nothing by itself until a provider is - present in `providerContextCaps`. Caps only lower a known context window; they never raise one or - change the upstream model's actual limit. + dashboard default (350,000 by default), but it does nothing by itself until a provider is + present in `providerContextCaps`. Changing the dashboard value re-points every enabled provider + only when "apply to every routed provider" is toggled on; otherwise each provider keeps its own + cap. Caps only lower a known context window; they never raise one or change the upstream model's + actual limit. ```json { diff --git a/docs-site/src/content/docs/ja/guides/model-routing.md b/docs-site/src/content/docs/ja/guides/model-routing.md index 1268afc17..3fb9e5f55 100644 --- a/docs-site/src/content/docs/ja/guides/model-routing.md +++ b/docs-site/src/content/docs/ja/guides/model-routing.md @@ -87,7 +87,9 @@ model ID は変更しません。`openai-apikey/` は API key transport 失敗し、`defaultModel` / `models[]` 検査でもスキップします。 - `providerContextCaps` はプロバイダーごとに Codex に表示するコンテキスト上限を指定します。 `contextCapValue` はダッシュボードが併用する値でデフォルトは 350,000 です。ただしこの値だけを設定しても - 変化はなく、`providerContextCaps` にプロバイダーが含まれていて初めて適用されます。既知のコンテキスト + 変化はなく、`providerContextCaps` にプロバイダーが含まれていて初めて適用されます。ダッシュボードの値を + 変更すると、「すべてのルーティング対象プロバイダーに適用」がオンになっている場合にのみ、有効なすべての + プロバイダーに再適用されます。それ以外の場合、各プロバイダーは独自の上限を維持します。既知のコンテキスト サイズを下げるだけで、上げたり上流モデルの実際の上限を変えたりはしません。 ```json diff --git a/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md b/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md index b0e0fa29f..9c6e6edf7 100644 --- a/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md @@ -253,7 +253,7 @@ native-main トラフィックまたはジャーナル復旧を受け入れる | `disable ` | `--native`、`--json` | Codex から 1 つのモデルを非表示にします。 | | `provider ` | `--json` | 1 つのプロバイダーのすべてのモデルを 1 回の書き込みで有効または無効にします。 | | `selected ` | `--set `、`--clear`、`--json` |プロバイダー モデルのホワイトリストを読み取るか置き換えます。 `--clear` はホワイトリストを削除し、すべてのモデルが提供されるようにします。 | -| `context \|provider \|all >` | `--json` |コンテキスト ウィンドウ キャップをグローバルに、またはプロバイダーごとに読み取りまたは設定します。 | +| `context [--set-all]\|provider on [--value ]\|provider off\|all >` | `--json` |コンテキスト ウィンドウ キャップをグローバルに、またはプロバイダーごとに読み取りまたは設定します。 `value --set-all` はすべてのルーティング済みプロバイダーにも値を再適用します(ダッシュボードのトグルと同様)。指定しない場合は既定値のみが変更されます。 `provider ... on --value ` はそのプロバイダーのみに個別のキャップを設定します(`--value` は `on` でのみ使用できます)。 | | `shadow [model\|-]` | `--enabled `、`--json` | Codex のバックグラウンド ヘルパー呼び出しの置換モデルを読み取るか、設定します。 `-` はモデルをクリアします。 `status` は `sourceModels` も報告し、プロキシがインターセプトするヘルパースラッグを示します (デフォルト: `gpt-5.6-luna`; 0.144.x 以前のクライアントが使用した `gpt-5.4-mini` は明示的な `sourceModels` オーバーライドで復元できます)。 | ```bash diff --git a/docs-site/src/content/docs/ja/reference/configuration/providers.md b/docs-site/src/content/docs/ja/reference/configuration/providers.md index 57903e853..b2d37c7e3 100644 --- a/docs-site/src/content/docs/ja/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ja/reference/configuration/providers.md @@ -13,7 +13,7 @@ description: プロバイダー エントリ、認証、エンドポイント、 | `openaiProviderTierVersion?` | `2` |移行によって設定される |単一のオプション対応 OpenAI プロジェクションを完了としてマークします。 | | `disabledModels?` | `string[]` | — | Codex catalog と `/v1/models` から非表示にする model。直接の proxy 呼び出しはブロックしません。routed id は一覧から削除されます。account-qualified native id は該当する selector row だけを非表示にし、bare native GPT id は bare row とその model の全 account-selector row を非表示にします。Models ページに表示されるのは bare native 行と routed 行だけです。selector-qualified 行を 1 つだけ非表示にするには、この設定フィールドを直接編集してください。 | | `providerContextCaps?` | `Record` | `{}` |プロバイダーごとの Codex に表示されるコンテキストの上限。キャップは既知のコンテキスト ウィンドウを下げるだけです。 | -| `contextCapValue?` | `number` | `350000` |ダッシュボードのコンテキストキャップ コントロールで使用される値。これを変更すると、有効になっているすべての `providerContextCaps` エントリが更新されます。 | +| `contextCapValue?` | `number` | `350000` |ダッシュボードのコンテキストキャップ コントロールで使用される既定値。「すべてのルーティング済みプロバイダーに適用」がオンになっている場合のみ、変更によってすべてのルーティング済みプロバイダー(`providerContextCaps` エントリがまだないプロバイダーを含む)に値が適用されます。それ以外では各プロバイダーは独自のキャップを保持します。 | | `codexAccounts?` | `CodexAccount[]` | `[]` | ChatGPT/Codex プール アカウントのメタデータは Codex Auth によって管理されます。秘密は`codex-accounts.json`に別に住んでいます。 | | `pausedCodexAccountIds?` | `string[]` | `[]` |再開するまでプールの選択から除外されるアカウント (一時停止時のメイン `__main__` アカウントを含む)。 | | `codexAccountNamespaces?` | `Record` | — | 任意の公開 model selector を保存済み Codex アカウント target に対応付ける任意の map。target が存在する各 selector は Codex picker に個別の `/` row を追加し、各 row はそのアカウントだけを使用します。selector が 1 つでも有効な場合、bare native row は picker で非表示になりますが、明示的に無効化されない限り id は引き続き routing でき、raw `/v1/models` にも表示されます。 | diff --git a/docs-site/src/content/docs/ko/guides/model-routing.md b/docs-site/src/content/docs/ko/guides/model-routing.md index 187c40a20..f9b1eff92 100644 --- a/docs-site/src/content/docs/ko/guides/model-routing.md +++ b/docs-site/src/content/docs/ko/guides/model-routing.md @@ -86,8 +86,10 @@ fallback하지 않습니다. 실패하고, `defaultModel` / `models[]` 검사에서도 건너뜁니다. - `providerContextCaps`는 프로바이더별로 Codex에 표시할 컨텍스트 상한을 지정합니다. `contextCapValue`는 대시보드가 함께 쓰는 값이며 기본값은 350,000입니다. 다만 이 값만 설정해서는 - 아무 변화가 없고 `providerContextCaps`에 프로바이더가 들어 있어야 적용됩니다. 이미 알려진 컨텍스트 - 크기를 낮추기만 하며, 더 키우거나 업스트림 모델의 실제 한도를 바꾸지는 않습니다. + 아무 변화가 없고 `providerContextCaps`에 프로바이더가 들어 있어야 적용됩니다. 대시보드 값을 변경하면 + '모든 라우팅 대상 프로바이더에 적용' 토글이 켜져 있을 때만 모든 활성 프로바이더에 다시 적용되며, + 그렇지 않으면 각 프로바이더는 자체 한도를 유지합니다. 이미 알려진 컨텍스트 크기를 낮추기만 하며, + 더 키우거나 업스트림 모델의 실제 한도를 바꾸지는 않습니다. ```json { diff --git a/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md b/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md index b761a3aed..5f9624b80 100644 --- a/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md @@ -252,7 +252,7 @@ native-main 트래픽이나 저널 복구를 허용하기 전에 수명 주기 | `disable ` | `--native`, `--json` | Codex에서 하나의 모델을 숨깁니다. | | `provider ` | `--json` | 한 제공자의 모든 모델을 한 번의 쓰기로 활성화하거나 비활성화합니다. | | `selected ` | `--set `, `--clear`, `--json` | 제공자 모델 허용 목록을 읽거나 교체합니다. `--clear`는 허용 목록을 제거해 모든 모델을 제공하도록 합니다. | -| `context \|provider \|all >` | `--json` | 전역 또는 제공자별로 컨텍스트 창 한도를 읽거나 설정합니다. | +| `context [--set-all]\|provider on [--value ]\|provider off\|all >` | `--json` | 전역 또는 제공자별로 컨텍스트 창 한도를 읽거나 설정합니다. `value --set-all`은 모든 라우팅된 공급자에도 값을 다시 적용합니다(대시보드 토글과 동일). 지정하지 않으면 값은 기본값만 변경됩니다. `provider ... on --value `는 해당 제공자에만 별도 한도를 설정합니다(`--value`는 `on`에서만 사용할 수 있습니다). | | `shadow [model\|-]` | `--enabled `, `--json` | Codex의 백그라운드 헬퍼 호출에 사용할 대체 모델을 읽거나 설정합니다. `-`는 모델을 지웁니다. `status`는 프록시가 가로채는 헬퍼 슬러그인 `sourceModels`도 보고합니다(기본값: `gpt-5.6-luna`; 0.144.x 이하 클라이언트가 사용한 `gpt-5.4-mini`는 명시적인 `sourceModels` 재정의로 복원할 수 있습니다). | ```bash diff --git a/docs-site/src/content/docs/ko/reference/configuration/providers.md b/docs-site/src/content/docs/ko/reference/configuration/providers.md index 684ea712f..b291397e0 100644 --- a/docs-site/src/content/docs/ko/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ko/reference/configuration/providers.md @@ -13,7 +13,7 @@ description: 공급자 항목, 인증, 엔드포인트, 모델 카탈로그, 할 | `openaiProviderTierVersion?` | `2` | 마이그레이션으로 설정됨 | 옵션을 인식하는 단일 OpenAI 투영이 완료되었음을 표시합니다. | | `disabledModels?` | `string[]` | — | Codex catalog와 `/v1/models`에서는 숨기지만 직접 proxy 호출은 차단하지 않습니다. routed id는 목록에서 제거됩니다. account-qualified native id는 해당 selector row만 숨기고, bare native GPT id는 bare row와 그 model의 모든 account-selector row를 숨깁니다. Models 페이지에는 bare native 행과 routed 행만 표시됩니다. selector-qualified 행 하나만 숨기려면 이 설정 필드에 직접 추가하세요. | | `providerContextCaps?` | `Record` | `{}` | 공급자별 Codex 표시 컨텍스트 상한입니다. 상한은 이미 알려진 컨텍스트 윈도만 낮춥니다. | -| `contextCapValue?` | `number` | `350000` | 대시보드의 컨텍스트 상한 컨트롤이 사용하는 값입니다. 이 값을 바꾸면 활성화된 모든 `providerContextCaps` 항목이 함께 갱신됩니다. | +| `contextCapValue?` | `number` | `350000` | 대시보드의 컨텍스트 상한 컨트롤이 사용하는 기본값입니다. "모든 라우팅된 공급자에 적용" 토글이 켜져 있을 때만 값을 변경하면 기존 `providerContextCaps` 항목이 없는 공급자를 포함해 모든 라우팅된 공급자에 값이 적용됩니다. 그렇지 않으면 각 공급자는 자체 상한을 유지합니다. | | `codexAccounts?` | `CodexAccount[]` | `[]` | Codex Auth가 관리하는 ChatGPT/Codex 풀 계정 메타데이터입니다. 비밀 정보는 `codex-accounts.json`에 따로 저장됩니다. | | `pausedCodexAccountIds?` | `string[]` | `[]` | 일시 중지된 `__main__` 계정을 포함해, 재개될 때까지 Pool 선택에서 제외되는 계정입니다. | | `codexAccountNamespaces?` | `Record` | — | 임의의 공개 model selector를 저장된 Codex 계정 target에 연결하는 선택적 map입니다. target이 존재하는 각 selector는 Codex picker에 별도의 `/` row를 추가하며, 각 row는 해당 계정만 사용합니다. selector가 하나라도 활성화되면 bare native row는 picker에서 숨겨지지만, 명시적으로 비활성화하지 않는 한 해당 id는 계속 routing 가능하고 raw `/v1/models`에 표시됩니다. | diff --git a/docs-site/src/content/docs/reference/cli/providers-accounts.md b/docs-site/src/content/docs/reference/cli/providers-accounts.md index 7fe218226..dd6c48f2a 100644 --- a/docs-site/src/content/docs/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/reference/cli/providers-accounts.md @@ -341,7 +341,7 @@ proxy to be running (`ocx start`, or an installed service). | `disable ` | `--native`, `--json` | Hide one model from Codex. | | `provider ` | `--json` | Enable or disable every model of one provider in a single write. | | `selected ` | `--set `, `--clear`, `--json` | Read or replace the provider model allowlist. `--clear` removes the allowlist so every model is offered. | -| `context \|provider \|all >` | `--json` | Read or set the context-window cap, globally or per provider. | +| `context [--set-all]\|provider on [--value ]\|provider off\|all >` | `--json` | Read or set the context-window cap, globally or per provider. `value --set-all` also re-points every routed provider (like the dashboard toggle); without it the value only becomes the default. `provider ... on --value ` sets an explicit cap for that provider only (`--value` is valid with `on` only). | | `shadow [model\|-]` | `--enabled `, `--json` | Read or set the replacement model for Codex's background helper calls. `-` clears the model. `status` also reports `sourceModels`, the helper slugs the proxy intercepts (default: `gpt-5.6-luna`; clients through 0.144.x used `gpt-5.4-mini`, which an explicit `sourceModels` override can restore). | ```bash diff --git a/docs-site/src/content/docs/reference/configuration/providers.md b/docs-site/src/content/docs/reference/configuration/providers.md index aca630a1f..ce98d94a8 100644 --- a/docs-site/src/content/docs/reference/configuration/providers.md +++ b/docs-site/src/content/docs/reference/configuration/providers.md @@ -14,7 +14,7 @@ authenticated. | `openaiProviderTierVersion?` | `2` | set by migration | Marks the single option-aware OpenAI projection as complete. | | `disabledModels?` | `string[]` | — | Models hidden from Codex's catalog and `/v1/models`, but not blocked from direct proxy calls. A routed id is removed from listings. An account-qualified native id hides only that selector row; a bare native GPT id hides the bare row and every account-selector row for that model. The dashboard Models page exposes only routed and bare native rows; use this configuration field directly to hide one selector-qualified row. | | `providerContextCaps?` | `Record` | `{}` | Per-provider Codex-visible context caps. A cap only lowers a known context window. | -| `contextCapValue?` | `number` | `350000` | Value used by the dashboard context-cap controls; changing it updates every enabled `providerContextCaps` entry. | +| `contextCapValue?` | `number` | `350000` | Default value used by the dashboard context-cap controls. Changing it applies the value to every routed provider — including providers without an existing `providerContextCaps` entry — only when "apply to every routed provider" is toggled on; otherwise each provider keeps its own cap. | | `codexAccounts?` | `CodexAccount[]` | `[]` | ChatGPT/Codex pool account metadata managed by Codex Auth. Secrets live separately in `codex-accounts.json`. | | `pausedCodexAccountIds?` | `string[]` | `[]` | Accounts excluded from Pool selection until resumed, including the main `__main__` account when paused. | | `codexAccountNamespaces?` | `Record` | — | Optional map from an arbitrary public model selector to a stored Codex account target. Each selector whose target is present adds separate `/` rows to the Codex picker; each row uses only that account. With any selector active, bare native rows are hidden in the picker, but their ids remain routable and listed by raw `/v1/models` unless explicitly disabled. | diff --git a/docs-site/src/content/docs/ru/guides/model-routing.md b/docs-site/src/content/docs/ru/guides/model-routing.md index 6aef7ebe3..eb4178cd3 100644 --- a/docs-site/src/content/docs/ru/guides/model-routing.md +++ b/docs-site/src/content/docs/ru/guides/model-routing.md @@ -91,10 +91,12 @@ description: Как opencodex решает, какой провайдер буд - `provider.disabled: true` убирает провайдера из обнаружения каталога. Явные запросы `provider/model` завершаются ошибкой, а проверки `defaultModel` / `models[]` его пропускают. - `providerContextCaps` задаёт видимые для Codex лимиты контекста по провайдерам. - `contextCapValue` — общее значение для дашборда (по умолчанию 350 000), но сам по себе он - ничего не делает, пока провайдер не указан в `providerContextCaps`. Лимиты только понижают - известное контекстное окно; они никогда не повышают его и не меняют фактический предел - вышестоящей модели. + `contextCapValue` — значение по умолчанию для дашборда (по умолчанию 350 000), но сам по себе он + ничего не делает, пока провайдер не указан в `providerContextCaps`. Изменение значения на дашборде + переназначает все включённые провайдеры только при включённом переключателе «применить ко всем + маршрутизируемым провайдерам»; в противном случае каждый провайдер сохраняет собственный лимит. + Лимиты только понижают известное контекстное окно; они никогда не повышают его и не меняют + фактический предел вышестоящей модели. ```json { diff --git a/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md b/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md index a99dec555..15189c25a 100644 --- a/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md @@ -316,7 +316,7 @@ management API и требуют, чтобы прокси уже работал | `disable ` | `--native`, `--json` | Скрыть одну модель от Codex. | | `provider ` | `--json` | Включить или выключить сразу все модели одного провайдера одним действием. | | `selected ` | `--set `, `--clear`, `--json` | Прочитать или заменить allowlist моделей провайдера. `--clear` удаляет allowlist, и тогда доступны все модели. | -| `context \|provider \|all >` | `--json` | Прочитать или задать context-window cap глобально либо по провайдерам. | +| `context [--set-all]\|provider on [--value ]\|provider off\|all >` | `--json` | Прочитать или задать context-window cap глобально либо по провайдерам. `value --set-all` также переустанавливает значение для всех маршрутизируемых провайдеров (как переключатель дашборда); без него меняется только значение по умолчанию. `provider ... on --value ` задаёт отдельный cap только для этого провайдера (`--value` допустим только с `on`). | | `shadow [model\|-]` | `--enabled `, `--json` | Прочитать или задать модель-замену для background helper-call'ов Codex. `-` очищает модель. `status` также показывает `sourceModels` — helper-slug'и, которые перехватывает proxy (по умолчанию `gpt-5.6-luna`; `gpt-5.4-mini` для клиентов до 0.144.x включительно можно восстановить явным переопределением `sourceModels`). | ```bash diff --git a/docs-site/src/content/docs/ru/reference/configuration/providers.md b/docs-site/src/content/docs/ru/reference/configuration/providers.md index abf75d650..78e6275ef 100644 --- a/docs-site/src/content/docs/ru/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ru/reference/configuration/providers.md @@ -14,7 +14,7 @@ description: Записи провайдеров, аутентификация, | `openaiProviderTierVersion?` | `2` | set by migration | Отмечает, что единая projection OpenAI с учётом режима уже завершена. | | `disabledModels?` | `string[]` | — | Модели, скрытые из каталога Codex и `/v1/models`, но не заблокированные для прямых вызовов прокси. Routed-id удаляются из списков. Account-qualified native-id скрывает только строку этого селектора; bare native GPT-id скрывает bare-строку и строки всех селекторов аккаунтов для этой модели. Страница Models показывает только bare native- и routed-строки; чтобы скрыть одну selector-qualified строку, задайте это поле конфигурации напрямую. | | `providerContextCaps?` | `Record` | `{}` | Context cap'ы, видимые Codex, по каждому провайдеру. Cap может только понижать известное context window. | -| `contextCapValue?` | `number` | `350000` | Значение, используемое элементами управления context-cap в дашборде; его изменение обновляет все включённые записи `providerContextCaps`. | +| `contextCapValue?` | `number` | `350000` | Значение по умолчанию для элементов управления context-cap в дашборде. При изменении значение применяется ко всем маршрутизируемым провайдерам — включая провайдеров без существующей записи `providerContextCaps` — только при включённом переключателе «применить ко всем маршрутизируемым провайдерам»; в противном случае каждый провайдер сохраняет собственный лимит. | | `codexAccounts?` | `CodexAccount[]` | `[]` | Метаданные аккаунтов пула ChatGPT/Codex, которыми управляет Codex Auth. Секреты живут отдельно в `codex-accounts.json`. | | `pausedCodexAccountIds?` | `string[]` | `[]` | Аккаунты, исключённые из выбора Pool до снятия паузы, включая основной аккаунт `__main__`, если он поставлен на паузу. | | `codexAccountNamespaces?` | `Record` | — | Необязательное сопоставление произвольного публичного селектора модели с сохранённым аккаунтом Codex. Каждый селектор с существующей целью добавляет в model picker Codex отдельные строки `/`; каждая строка использует только этот аккаунт. Если активен хотя бы один селектор, bare native-строки скрываются в picker, но их id остаются маршрутизируемыми и перечисляются raw `/v1/models`, если они не отключены явно. | diff --git a/docs-site/src/content/docs/zh-cn/guides/model-routing.md b/docs-site/src/content/docs/zh-cn/guides/model-routing.md index b3105376a..70f798b9d 100644 --- a/docs-site/src/content/docs/zh-cn/guides/model-routing.md +++ b/docs-site/src/content/docs/zh-cn/guides/model-routing.md @@ -74,9 +74,10 @@ transport;这些凭证路径互不 fallback。 目录和 `/v1/models` 输出的模型范围。 - `provider.disabled: true` 会把该提供商排除在目录发现之外。显式 `provider/model` 请求会失败, `defaultModel` / `models[]` 扫描也会跳过它。 -- `providerContextCaps` 为各提供商设置 Codex 可见的上下文上限。`contextCapValue` 是仪表盘共用的值, - 默认为 350,000;但只有 `providerContextCaps` 中列出了提供商时才会生效。上限只能降低已知上下文, - 不会把它调高,也不会改变上游模型的实际限制。 +- `providerContextCaps` 为各提供商设置 Codex 可见的上下文上限。`contextCapValue` 是仪表盘的默认值, + 默认为 350,000;但只有 `providerContextCaps` 中列出了提供商时才会生效。仅当勾选“应用到所有已路由的 + 提供方”时,修改仪表盘值才会重新指向所有已启用提供商;否则每个提供商保留自己的上限。上限只能降低 + 已知上下文,不会把它调高,也不会改变上游模型的实际限制。 ```json { diff --git a/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md b/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md index 20f4205e4..aae5d6608 100644 --- a/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md @@ -286,7 +286,7 @@ v1 恢复矩阵覆盖的是事务文件通过重命名发布后 OpenCodex 进程 | `disable ` | `--native`, `--json` | 对 Codex 隐藏一个模型。 | | `provider ` | `--json` | 一次写入中启用或禁用某个提供方的全部模型。 | | `selected ` | `--set `, `--clear`, `--json` | 读取或替换提供方模型允许列表。`--clear` 会移除允许列表,使所有模型都可提供。 | -| `context \|provider \|all >` | `--json` | 读取或设置上下文窗口上限,可全局设置或按提供方设置。 | +| `context [--set-all]\|provider on [--value ]\|provider off\|all >` | `--json` | 读取或设置上下文窗口上限,可全局设置或按提供方设置。`value --set-all` 还会把值重新应用到所有已路由提供方(等同于仪表板开关);不加它则只改变默认值。`provider ... on --value ` 仅为该提供方设置独立上限(`--value` 仅可用于 `on`)。 | | `shadow [model\|-]` | `--enabled `, `--json` | 读取或设置 Codex 后台辅助调用所替换的模型。`-` 会清除该模型。`status` 还会报告 `sourceModels`,即代理拦截的辅助器 slug(默认值:`gpt-5.6-luna`;0.144.x 及更早客户端使用的 `gpt-5.4-mini` 可通过显式 `sourceModels` 覆盖恢复)。 | ```bash diff --git a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md index 7e664ce58..ef3d4d286 100644 --- a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md +++ b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md @@ -13,7 +13,7 @@ description: 提供者条目、身份验证、端点、模型目录、配额、 | `openaiProviderTierVersion?` | `2` | 由迁移设置 | 标记单一、可感知选项的 OpenAI 投影已完成。 | | `disabledModels?` | `string[]` | — | 从 Codex catalog 和 `/v1/models` 中隐藏、但不阻止直接 proxy 调用的 model。routed id 会从列表中移除。account-qualified native id 只隐藏对应 selector row;bare native GPT id 会隐藏 bare row 以及该 model 的所有 account-selector row。Models 页面只显示裸原生行和路由行;若只隐藏一个 selector-qualified 行,请直接设置此配置字段。 | | `providerContextCaps?` | `Record` | `{}` | 按提供者设置、对 Codex 可见的上下文上限。上限只会降低已知的上下文窗口。 | -| `contextCapValue?` | `number` | `350000` | 仪表板上下文上限控件使用的值;修改它会更新所有已启用的 `providerContextCaps` 条目。 | +| `contextCapValue?` | `number` | `350000` | 仪表板上下文上限控件使用的默认值。仅当勾选“应用到所有已路由的提供方”时,修改它才会把值应用到所有已路由提供方(包括没有现有 `providerContextCaps` 条目的提供方);否则每个提供方保留自己的上限。 | | `codexAccounts?` | `CodexAccount[]` | `[]` | 由 Codex Auth 管理的 ChatGPT/Codex 池账户元数据。密钥单独存放在 `codex-accounts.json` 中。 | | `pausedCodexAccountIds?` | `string[]` | `[]` | 在恢复之前从 Pool 选择中排除的账户,包括被暂停时的主 `__main__` 账户。 | | `codexAccountNamespaces?` | `Record` | — | 将任意公开 model selector 映射到已保存 Codex account target 的可选配置。target 存在的每个 selector 都会在 Codex picker 中添加独立的 `/` row,且每个 row 只使用对应账户。只要有 selector 生效,bare native row 就会在 picker 中隐藏;但除非显式禁用,其 id 仍可路由,并继续列在 raw `/v1/models` 中。 | diff --git a/gui/src/pages/Models.tsx b/gui/src/pages/Models.tsx index 1872503f4..8958a7622 100644 --- a/gui/src/pages/Models.tsx +++ b/gui/src/pages/Models.tsx @@ -142,6 +142,8 @@ export default function Models({ apiBase }: { apiBase: string }) { const [contextCapValue, setContextCapValue] = useState(() => cached?.contextCapValue ?? 350_000); const [customCap, setCustomCap] = useState(""); const [showCustom, setShowCustom] = useState(false); + const [providerCapCustomOpen, setProviderCapCustomOpen] = useState>({}); + const [providerCapCustomDraft, setProviderCapCustomDraft] = useState>({}); const initialCollapsed = readCollapsedProviders(); const [collapsed, setCollapsed] = useState>(() => initialCollapsed ?? new Set()); const needsDefaultCollapseRef = useRef(initialCollapsed === null); @@ -566,7 +568,9 @@ export default function Models({ apiBase }: { apiBase: string }) { setBusy(true); busyRef.current = true; setStatus(""); - const enabled = contextCaps[provider] !== contextCapValue; + // Send the desired next state, not the current one: clicking the switch turns a + // currently-unset cap on (enabled: true) and a currently-set cap off (enabled: false). + const enabled = contextCaps[provider] === undefined; try { const r = await fetch(`${apiBase}/api/provider-context-caps`, { method: "PUT", @@ -635,33 +639,68 @@ export default function Models({ apiBase }: { apiBase: string }) { } }; + const allCapped = useMemo( + () => { + // Cap aggregate counts routed providers only; the single native group has no cap + // switch. Zero-row routed providers are included: they can still hold a per-provider + // cap (e.g. a custom model added later), and excluding them would let "set all" + // silently overwrite that cap when the global value changes. Saved caps of providers + // that are no longer in `groups` (e.g. disabled after receiving a custom cap) are + // also counted: the management API rewrites every key in providerContextCaps when + // setAll is true, so any saved cap that differs from the current value must keep the + // aggregate off. + const routed = groups.filter(group => !group.native); + return routed.length > 0 + && routed.every(group => contextCaps[group.provider] === contextCapValue) + && Object.keys(contextCaps).every(key => contextCaps[key] === contextCapValue); + }, + [groups, contextCaps, contextCapValue], + ); + const setGlobalCap = (value: number) => { - if (!Number.isFinite(value) || value <= 0) return; - void putCap({ value: Math.floor(value) }); + if (!Number.isSafeInteger(value) || value <= 0) return; + // Only when "apply to every routed provider" is checked does the new value re-point every + // provider; otherwise it just becomes the default for future toggles and providers keep + // their own values. + void putCap(allCapped ? { value, setAll: true } : { value }); + }; + + const onSelectProviderCap = (provider: string, raw: string) => { + if (raw === CUSTOM_OPTION) { + setProviderCapCustomOpen(prev => ({ ...prev, [provider]: true })); + setProviderCapCustomDraft(prev => ({ ...prev, [provider]: String(contextCaps[provider] ?? contextCapValue) })); + return; + } + setProviderCapCustomOpen(prev => ({ ...prev, [provider]: false })); + const value = Number(raw); + if (Number.isSafeInteger(value) && value > 0 && value !== contextCaps[provider]) { + void putCap({ provider, enabled: true, value }); + } + }; + + const applyProviderCustomCap = (provider: string) => { + const value = Number((providerCapCustomDraft[provider] ?? "").replace(/[_,\s]/g, "")); + // Fractional values are rejected (the server floors, so 0.5 would silently become 0). + // The editor stays open when validation fails. + if (!Number.isSafeInteger(value) || value <= 0) { publishFeedback(false, t("models.capSaveFailed")); return; } + setProviderCapCustomOpen(prev => ({ ...prev, [provider]: false })); + void putCap({ provider, enabled: true, value }); }; const onSelectCap = (raw: string) => { if (raw === CUSTOM_OPTION) { setShowCustom(true); setCustomCap(String(contextCapValue)); return; } setShowCustom(false); const value = Number(raw); - if (Number.isFinite(value) && value > 0 && value !== contextCapValue) setGlobalCap(value); + if (Number.isSafeInteger(value) && value > 0 && value !== contextCapValue) setGlobalCap(value); }; const applyCustomCap = () => { const value = Number(customCap.replace(/[_,\s]/g, "")); - if (!Number.isFinite(value) || value <= 0) { publishFeedback(false, t("models.capSaveFailed")); return; } + if (!Number.isSafeInteger(value) || value <= 0) { publishFeedback(false, t("models.capSaveFailed")); return; } setShowCustom(false); setGlobalCap(value); }; - const allCapped = useMemo( - () => { - // Cap aggregate counts routed providers only; the single native group has no cap switch. - const routed = groups.filter(group => !group.native && group.rows.length > 0); - return routed.length > 0 && routed.every(group => contextCaps[group.provider] === contextCapValue); - }, - [groups, contextCaps, contextCapValue], - ); const setAll = () => { void putCap({ setAll: !allCapped }); }; const saveShadowCall = async (patch: Partial) => { @@ -882,7 +921,8 @@ export default function Models({ apiBase }: { apiBase: string }) { disabled.has(model.namespaced), ); const activeCount = rows.filter(isVisible).length; - const capOn = contextCaps[provider] === contextCapValue; + const capOn = contextCaps[provider] !== undefined; + const providerCap = contextCaps[provider] ?? contextCapValue; const isNative = native; const discoveryFailure = liveModels && discovery?.status === "failed" ? discovery : undefined; const q = (search[provider] ?? "").trim().toLowerCase(); @@ -966,8 +1006,43 @@ export default function Models({ apiBase }: { apiBase: string }) { {!isNative && <> - toggleProviderCap(provider)} disabled={busy} label={t("models.capValue", { value: fmtK(contextCapValue) })} /> - {t("models.capValue", { value: fmtK(contextCapValue) })} + toggleProviderCap(provider)} disabled={busy} label={t("models.capValue", { value: fmtK(providerCap) })} /> + {capOn && ( + <> + setProviderCapCustomDraft(prev => ({ ...prev, [provider]: e.target.value }))} + onKeyDown={e => { if (e.key === "Enter") applyProviderCustomCap(provider); }} + disabled={busy} + aria-label={t("models.customPlaceholder")} + /> + + + )} + + )} } @@ -1208,7 +1283,7 @@ export default function Models({ apiBase }: { apiBase: string }) {
{t("models.contextCapLabel")}