diff --git a/docs-site/src/content/docs/fr/guides/claude-code.md b/docs-site/src/content/docs/fr/guides/claude-code.md index 45851d1fc0d..54ec1ed64d3 100644 --- a/docs-site/src/content/docs/fr/guides/claude-code.md +++ b/docs-site/src/content/docs/fr/guides/claude-code.md @@ -57,7 +57,7 @@ ocx claude | `ANTHROPIC_DEFAULT_{OPUS,SONNET,FABLE}_MODEL` | `claudeCode.tierModels.*` (facultatif) | | `CLAUDE_CODE_ALWAYS_ENABLE_EFFORT` | `1` lorsque `alwaysEnableEffort` est activé (conditionnel) | | `ENABLE_TOOL_SEARCH` | `claudeCode.toolSearch` lorsqu'il est défini (conditionnel ; désactivé par défaut) | -| `CLAUDE_CODE_MAX_CONTEXT_TOKENS` / `DISABLE_COMPACT` | Remplacement du contexte hérité lorsque `maxContextTokens` est défini (conditionnel) | +| `CLAUDE_CODE_MAX_CONTEXT_TOKENS` | Remplacement du contexte hérité lorsque `maxContextTokens` est défini (conditionnel) | Les variables que vous exportez vous-même gagnent toujours. Les arguments supplémentaires passent par : `ocx claude -p "hello"`. Une exception porte sur *l'origine* d'une variable, et non sur sa priorité. L'environnement d'exécution Bun fourni @@ -300,12 +300,11 @@ pas les copies externes ; révoquez-la séparément sur le hub si nécessaire. ## Le sélecteur /model (« Depuis la passerelle ») Claude Code 2.1.129+ découvre les modèles de passerelle via `GET /v1/models?limit=1000` et les répertorie dans -le sélecteur natif `/model` intitulé « Depuis la passerelle ». Comme ce sélecteur n'accepte que les identifiants commençant -par `claude` ou `anthropic`, opencodex expose les modèles routés sous forme d'alias stables et réversibles : +le sélecteur natif `/model` intitulé « Depuis la passerelle ». Claude Code 2.1.278 accepte un identifiant qui contient `claude` ou `anthropic`. Un identifiant inconnu qui commence par `claude-` est compté à 200k sauf si le compactage est désactivé, donc opencodex expose les modèles routés sous forme d'alias stables et réversibles qui contiennent `claude` sans commencer par `claude-` : | Surface | Format | Exemple | | --- | --- | --- | -| Claude Code CLI | `claude-ocx---` (simple) ou `claude-ocx2-…` (échappé) | `claude-ocx-native--gpt-5.6-sol` | +| Claude Code CLI | `ocx-claude---` (simple) ou `ocx-claude2-…` (échappé) | `ocx-claude-native--gpt-5.6-sol` | | Claude Desktop 3P | `claude-opus-4-8-` (hachage base36 de 3 caractères) | `claude-opus-4-8-ncb` | Le proxy choisit la famille pour chaque requête : `?ids=cli` ou `?ids=desktop` est prioritaire ; à défaut, l'agent utilisateur @@ -338,9 +337,9 @@ résolu vers le modèle routé. Avec les anciennes versions de Claude Code, le s `ANTHROPIC_MODEL` ou tapez n'importe quel identifiant routé avec `/model` (Claude Code fait passer les chaînes). **Règles de grammaire des alias :** le fournisseur ne doit contenir ni `/` ni `--`, et ne doit pas être égal à `native`. -Les identifiants de modèle simples, sans `/` ni `~`, conservent le préfixe v1 `claude-ocx-…`. Ceux qui contiennent `/` ou -`~` utilisent le préfixe v2 `claude-ocx2-…` avec des échappements (`/` → `~s`, `~` → `~t`), par exemple : -`openrouter/anthropic/claude-opus-4-8` → `claude-ocx2-openrouter--anthropic~sclaude-opus-4-8`. +Les identifiants de modèle simples, sans `/` ni `~`, conservent le préfixe v1 `ocx-claude-…`. Ceux qui contiennent `/` ou +`~` utilisent le préfixe v2 `ocx-claude2-…` avec des échappements (`/` → `~s`, `~` → `~t`), par exemple : +`openrouter/anthropic/claude-opus-4-8` → `ocx-claude2-openrouter--anthropic~sclaude-opus-4-8`. Les alias v1 décodent littéralement (donc un identifiant de modèle historique qui contenait les séquences de deux caractères `~s` / `~t` est conservé) ; les alias v2 développent les échappements. Les routes impossibles à représenter sous une forme lisible utilisent l'alias haché. Les identifiants de modèle peuvent contenir `--` (la résolution se sépare uniquement au premier diff --git a/docs-site/src/content/docs/guides/claude-code.md b/docs-site/src/content/docs/guides/claude-code.md index 02d3d78875c..2d89dfcfcc2 100644 --- a/docs-site/src/content/docs/guides/claude-code.md +++ b/docs-site/src/content/docs/guides/claude-code.md @@ -83,7 +83,7 @@ ocx claude | `ANTHROPIC_DEFAULT_{OPUS,SONNET,FABLE}_MODEL` | `claudeCode.tierModels.*` (optional) | | `CLAUDE_CODE_ALWAYS_ENABLE_EFFORT` | `1` when `alwaysEnableEffort` is on (conditional) | | `ENABLE_TOOL_SEARCH` | `claudeCode.toolSearch` when set (conditional; off by default — see [MCP tool schemas fill the context](#troubleshooting)) | -| `CLAUDE_CODE_MAX_CONTEXT_TOKENS` / `DISABLE_COMPACT` | Legacy context override when `maxContextTokens` is set (conditional) | +| `CLAUDE_CODE_MAX_CONTEXT_TOKENS` | Legacy context override when `maxContextTokens` is set (conditional) | Variables you export yourself always win. Extra arguments pass through: `ocx claude -p "hello"`. One exception is about *where* a variable comes from, not about precedence. The bundled Bun @@ -375,12 +375,11 @@ arbitrary external copies; revoke separately on the hub if desired. ## The /model picker ("From gateway") Claude Code 2.1.129+ discovers gateway models via `GET /v1/models?limit=1000` and lists them in -the native `/model` picker labeled "From gateway". Because the picker only accepts ids beginning -with `claude` or `anthropic`, opencodex exposes routed models as stable, reversible aliases: +the native `/model` picker labeled "From gateway". Claude Code 2.1.278 accepts a picker id that contains `claude` or `anthropic`. An unrecognized id that starts with `claude-` is accounted at 200k unless compact is disabled, so opencodex exposes routed models as stable, reversible aliases that contain `claude` but do not start with `claude-`: | Surface | Format | Example | | --- | --- | --- | -| Claude Code CLI | `claude-ocx---` (plain) or `claude-ocx2-…` (escaped) | `claude-ocx-native--gpt-5.6-sol` | +| Claude Code CLI | `ocx-claude---` (plain) or `ocx-claude2-…` (escaped) | `ocx-claude-native--gpt-5.6-sol` | | Claude Desktop 3P | `claude-opus-4-8-` (3-char base36 hash) | `claude-opus-4-8-ncb` | The proxy picks the family per request: `?ids=cli` or `?ids=desktop` wins; otherwise the @@ -389,8 +388,7 @@ Both families decode forever — a model saved in `settings.json` under either f Each entry carries an honest display name such as `gemini-3-pro (gemini)`, plus full model capabilities (reasoning-effort ladder, thinking types) in the official ModelInfo shape so Claude Desktop's third-party gateway mode can offer its effort selector. Real Anthropic models keep their -canonical ids. The synthetic 2026 date is an internal slot, not a release date. Legacy hash aliases -and `claude-ocx---` ids from older configs still resolve. +canonical ids. The synthetic 2026 date is an internal slot, not a release date. Legacy hash aliases and `claude-ocx---` / `claude-ocx2---` ids from older configs still resolve. A saved legacy id still routes, but Claude Code keeps its 200k accounting for that id. Pick `ocx-claude-` for a saved `claude-ocx-` id, and `ocx-claude2-` for a saved escaped `claude-ocx2-` id, so the real context window and compact both apply. If Claude Desktop's footer picker does not change the model for an already-running 3P conversation, you can try `/model `, but this workaround may also fail on affected Desktop @@ -413,9 +411,9 @@ slots via `ANTHROPIC_MODEL` or type any routed id with `/model` (Claude Code passes strings through). **Alias grammar rules:** provider must not contain `/` or `--` or equal `native`. -Plain model ids (no `/` or `~`) keep the v1 prefix `claude-ocx-…`. Model ids that contain `/` or -`~` mint the v2 prefix `claude-ocx2-…` with escapes (`/` → `~s`, `~` → `~t`), e.g. -`openrouter/anthropic/claude-opus-4-8` → `claude-ocx2-openrouter--anthropic~sclaude-opus-4-8`. +Plain model ids (no `/` or `~`) keep the v1 prefix `ocx-claude-…`. Model ids that contain `/` or +`~` mint the v2 prefix `ocx-claude2-…` with escapes (`/` → `~s`, `~` → `~t`), e.g. +`openrouter/anthropic/claude-opus-4-8` → `ocx-claude2-openrouter--anthropic~sclaude-opus-4-8`. v1 aliases decode literally (so a historical model id that contained the two-char sequences `~s` / `~t` is preserved); v2 aliases expand the escapes. Routes that the readable form cannot express fall back to the hashed alias. Model ids MAY contain `--` (resolution splits on the first diff --git a/docs-site/src/content/docs/ja/guides/claude-code.md b/docs-site/src/content/docs/ja/guides/claude-code.md index c8ca64b3fee..2558f81bb7a 100644 --- a/docs-site/src/content/docs/ja/guides/claude-code.md +++ b/docs-site/src/content/docs/ja/guides/claude-code.md @@ -26,7 +26,7 @@ ocx claude | `ANTHROPIC_DEFAULT_{OPUS,SONNET,FABLE}_MODEL` | `claudeCode.tierModels.*` (任意) | | `CLAUDE_CODE_ALWAYS_ENABLE_EFFORT` | `alwaysEnableEffort` がオンなら `1` (条件付き) | | `ENABLE_TOOL_SEARCH` | `claudeCode.toolSearch` が設定されている場合 (条件付き、既定はオフ) | -| `CLAUDE_CODE_MAX_CONTEXT_TOKENS` / `DISABLE_COMPACT` | `maxContextTokens` が設定された場合の従来コンテキスト上書き値 (条件付き) | +| `CLAUDE_CODE_MAX_CONTEXT_TOKENS` | `maxContextTokens` が設定された場合の従来コンテキスト上書き値 (条件付き) | 直接 export した変数が常に優先します。追加引数はそのまま渡されます: `ocx claude -p "hello"`。 ### Claude ルーティングが無効なときのネイティブフォールバック @@ -174,12 +174,11 @@ Claude Code CLI 互換性は英語版ドキュメントを参照してくださ ## /model ピッカー("From gateway") Claude Code 2.1.129 以降は `GET /v1/models?limit=1000` でゲートウェイモデルを探し、デフォルトの `/model` -ピッカーの "From gateway" 項目に表示します。ピッカーは `claude` または `anthropic` で始まる ID のみ -受け付けるため、opencodex はルーティングモデルを安定で元に戻せるエイリアスとして公開します。 +ピッカーの "From gateway" 項目に表示します。Claude Code 2.1.278 のピッカーは `claude` または `anthropic` を含む ID を受け付けます。`claude-` で始まる未知の ID は compact を無効にしない限り 200k として計算されるため、opencodex はルーティングモデルを `claude` を含みつつ `claude-` で始まらない安定した可逆エイリアスとして公開します。 | 画面 | 形式 | 例 | | --- | --- | --- | -| Claude Code CLI | `claude-ocx---` (plain) または `claude-ocx2-…` (escaped) | `claude-ocx-native--gpt-5.6-sol` | +| Claude Code CLI | `ocx-claude---` (plain) または `ocx-claude2-…` (escaped) | `ocx-claude-native--gpt-5.6-sol` | | Claude Desktop 3P | `claude-opus-4-8-` (3 桁の base36 ハッシュ) | `claude-opus-4-8-ncb` | プロキシはリクエストごとに系列を選びます。`?ids=cli` または `?ids=desktop` が優先し、指定しないと @@ -201,9 +200,9 @@ OpenCodex の Claude Desktop プロファイルで希望するデフォルトモ **Logs → requestedModel** で確認してください。 **エイリアス構文ルール:** provider には `/` や `--` を含められず `native` と同じでもいけません。 -`/` も `~` も含まない plain な model ID は v1 接頭辞 `claude-ocx-…` のままです。`/` または `~` を含む -model ID は v2 接頭辞 `claude-ocx2-…` で発行し、エスケープします(`/` → `~s`、`~` → `~t`)。例: -`openrouter/anthropic/claude-opus-4-8` → `claude-ocx2-openrouter--anthropic~sclaude-opus-4-8`。 +`/` も `~` も含まない plain な model ID は v1 接頭辞 `ocx-claude-…` のままです。`/` または `~` を含む +model ID は v2 接頭辞 `ocx-claude2-…` で発行し、エスケープします(`/` → `~s`、`~` → `~t`)。例: +`openrouter/anthropic/claude-opus-4-8` → `ocx-claude2-openrouter--anthropic~sclaude-opus-4-8`。 v1 エイリアスはリテラルにデコードします(歴史的に model ID に含まれていた 2 文字列 `~s` / `~t` も保持)。 v2 エイリアスはエスケープを展開します。読みやすい形式で表現できないルートはハッシュエイリアスに 置き換えます。モデル ID には `--` を含め**られます**(解析時は最初の `--` だけを基準に分割します)。 diff --git a/docs-site/src/content/docs/ko/guides/claude-code.md b/docs-site/src/content/docs/ko/guides/claude-code.md index 88bf75acae5..de6183b3d52 100644 --- a/docs-site/src/content/docs/ko/guides/claude-code.md +++ b/docs-site/src/content/docs/ko/guides/claude-code.md @@ -26,7 +26,7 @@ ocx claude | `ANTHROPIC_DEFAULT_{OPUS,SONNET,FABLE}_MODEL` | `claudeCode.tierModels.*` (선택 사항) | | `CLAUDE_CODE_ALWAYS_ENABLE_EFFORT` | `alwaysEnableEffort`가 켜져 있으면 `1` (조건부) | | `ENABLE_TOOL_SEARCH` | `claudeCode.toolSearch`가 설정된 경우 (조건부, 기본값은 꺼짐) | -| `CLAUDE_CODE_MAX_CONTEXT_TOKENS` / `DISABLE_COMPACT` | `maxContextTokens`가 설정된 경우 기존 컨텍스트 재정의 값 (조건부) | +| `CLAUDE_CODE_MAX_CONTEXT_TOKENS` | `maxContextTokens`가 설정된 경우 기존 컨텍스트 재정의 값 (조건부) | 직접 내보낸 변수가 항상 우선해요. 추가 인자는 그대로 전달돼요: `ocx claude -p "hello"`. ### Claude 라우팅이 꺼져 있을 때의 네이티브 폴백 @@ -200,7 +200,7 @@ import/export는 로컬 설정만 다뤄요. 허브 프로필을 바꾸지 않 능력 정보(추론 강도 사다리, thinking 타입)를 실어 보냅니다 — Claude Desktop의 서드파티 게이트웨이 모드가 추론 강도 선택 UI를 열 수 있게 하기 위해서입니다. 실제 Anthropic 모델은 원래 id를 그대로 유지합니다. 합성된 2026 날짜는 내부 슬롯이며 출시일이 아닙니다. 구버전의 -해시 별칭과 `claude-ocx---` 별칭도 계속 해석됩니다. 컨텍스트가 1M인 모델에는 +해시 별칭과 `claude-ocx---`, `claude-ocx2---` 별칭도 계속 해석됩니다. 저장된 `claude-ocx-`는 `ocx-claude-`로, 이스케이프된 `claude-ocx2-`는 `ocx-claude2-`로 한 번 다시 고르면 실제 컨텍스트 창과 compact가 함께 적용됩니다. 컨텍스트가 1M인 모델에는 `…[1m]` 행이 하나 더 생깁니다 — 이걸 고르면 Claude Code가 그 모델의 컨텍스트를 1M로 계산합니다 (자동 요약 유지, 프록시가 표식을 떼고 라우팅). 선택하면 Claude Code의 `settings.json` `model` 필드에 저장되고, 인바운드 요청에서 @@ -208,12 +208,11 @@ import/export는 로컬 설정만 다뤄요. 허브 프로필을 바꾸지 않 지정하거나 `/model`에 라우팅 id를 직접 입력하세요 (Claude Code는 문자열을 그대로 통과시킵니다). Claude Code 2.1.129 이상은 `GET /v1/models?limit=1000`에서 게이트웨이 모델을 찾아 기본 `/model` -선택기의 "From gateway" 항목에 표시해요. 선택기는 `claude` 또는 `anthropic`으로 시작하는 ID만 -받으므로, opencodex는 라우팅 모델을 안정적이고 되돌릴 수 있는 별칭으로 노출해요. +선택기의 "From gateway" 항목에 표시해요. Claude Code 2.1.278 선택기는 `claude` 또는 `anthropic`을 포함한 ID를 받습니다. `claude-`로 시작하는 모르는 ID는 compact를 끄지 않으면 200k로 계산되므로, 라우팅 모델은 `claude`를 포함하되 `claude-`로 시작하지 않는 안정적인 가역 별칭으로 노출합니다. | 화면 | 형식 | 예시 | | --- | --- | --- | -| Claude Code CLI | `claude-ocx---` (plain) 또는 `claude-ocx2-…` (escaped) | `claude-ocx-native--gpt-5.6-sol` | +| Claude Code CLI | `ocx-claude---` (plain) 또는 `ocx-claude2-…` (escaped) | `ocx-claude-native--gpt-5.6-sol` | | Claude Desktop 3P | `claude-opus-4-8-` (3자리 base36 해시) | `claude-opus-4-8-ncb` | 프록시는 요청마다 계열을 골라요. `?ids=cli` 또는 `?ids=desktop`이 우선하고, 지정하지 않으면 @@ -234,9 +233,9 @@ OpenCodex의 Claude Desktop 프로필에서 원하는 기본 모델을 선택하 클라이언트가 실제로 무엇을 보내는지는 **Logs → requestedModel**에서 확인하세요. **별칭 문법 규칙:** provider에는 `/`나 `--`를 넣을 수 없고 `native`와 같아도 안 돼요. `/`와 `~`가 -없는 plain model ID는 v1 접두사 `claude-ocx-…`를 유지해요. `/` 또는 `~`가 있는 model ID는 v2 -접두사 `claude-ocx2-…`로 만들고 이스케이프해요(`/` → `~s`, `~` → `~t`). 예: -`openrouter/anthropic/claude-opus-4-8` → `claude-ocx2-openrouter--anthropic~sclaude-opus-4-8`. +없는 plain model ID는 v1 접두사 `ocx-claude-…`를 유지해요. `/` 또는 `~`가 있는 model ID는 v2 +접두사 `ocx-claude2-…`로 만들고 이스케이프해요(`/` → `~s`, `~` → `~t`). 예: +`openrouter/anthropic/claude-opus-4-8` → `ocx-claude2-openrouter--anthropic~sclaude-opus-4-8`. v1 별칭은 리터럴로 디코딩해요(예전 model ID에 들어 있던 두 글자 시퀀스 `~s` / `~t`도 그대로 보존). v2 별칭은 이스케이프를 펼쳐요. 읽기 쉬운 형식으로 표현할 수 없는 라우트는 해시 별칭으로 대체해요. 모델 ID에는 `--`를 넣을 **수 있어요**(해석할 때 첫 번째 `--`만 기준으로 나눠요). `--`가 포함된 diff --git a/docs-site/src/content/docs/reference/configuration/providers.md b/docs-site/src/content/docs/reference/configuration/providers.md index 3f296a66d87..575bfbcebc2 100644 --- a/docs-site/src/content/docs/reference/configuration/providers.md +++ b/docs-site/src/content/docs/reference/configuration/providers.md @@ -600,7 +600,7 @@ and for `claude-opus-5` its regular family is quarantined upstream. | Surface | `fastMode: true` | |---|---| | Codex | rows stay umbrella rows; the app's Fast toggle selects the variant | -| Claude Code (`?ids=cli`) | lists the fast identity, e.g. `claude-ocx-cursor--claude-opus-5-thinking-fast` | +| Claude Code (`?ids=cli`) | lists the fast identity, e.g. `ocx-claude-cursor--claude-opus-5-thinking-fast` | | OpenAI `/v1/models` | lists `cursor/claude-opus-5-thinking-fast` | | Claude Desktop (3P) | unchanged — its aliases are hashed from the model name | | Dashboard `/api/models` | row ids unchanged; they are the enable/disable keys | diff --git a/docs-site/src/content/docs/ru/guides/claude-code.md b/docs-site/src/content/docs/ru/guides/claude-code.md index bbb81d3ac44..aee871d97df 100644 --- a/docs-site/src/content/docs/ru/guides/claude-code.md +++ b/docs-site/src/content/docs/ru/guides/claude-code.md @@ -27,7 +27,7 @@ ocx claude | `ANTHROPIC_DEFAULT_{OPUS,SONNET,FABLE}_MODEL` | `claudeCode.tierModels.*` (необязательно) | | `CLAUDE_CODE_ALWAYS_ENABLE_EFFORT` | `1`, когда включён `alwaysEnableEffort` (условно) | | `ENABLE_TOOL_SEARCH` | `claudeCode.toolSearch`, когда задан (условно; по умолчанию выключено) | -| `CLAUDE_CODE_MAX_CONTEXT_TOKENS` / `DISABLE_COMPACT` | Устаревшее переопределение контекста, когда задан `maxContextTokens` (условно) | +| `CLAUDE_CODE_MAX_CONTEXT_TOKENS` | Устаревшее переопределение контекста, когда задан `maxContextTokens` (условно) | Переменные, которые вы экспортируете сами, всегда имеют приоритет. Дополнительные аргументы передаются как есть: `ocx claude -p "hello"`. ### Нативный запасной запуск, когда маршрутизация Claude выключена @@ -163,12 +163,11 @@ Claude Desktop: работающий процесс может хранить п Claude Code 2.1.129+ обнаруживает модели шлюза через `GET /v1/models?limit=1000` и показывает их в нативном селекторе `/model` в разделе «From gateway». Поскольку селектор принимает только id, -начинающиеся с `claude` или `anthropic`, opencodex публикует маршрутизируемые модели как -стабильные обратимые алиасы: +Claude Code 2.1.278 принимает id, содержащий `claude` или `anthropic`. Неизвестный id, начинающийся с `claude-`, учитывается как 200k, если compact не отключён. Поэтому opencodex публикует маршрутизируемые модели как стабильные обратимые алиасы, которые содержат `claude`, но не начинаются с `claude-`: | Интерфейс | Формат | Пример | | --- | --- | --- | -| Claude Code CLI | `claude-ocx---` (plain) или `claude-ocx2-…` (escaped) | `claude-ocx-native--gpt-5.6-sol` | +| Claude Code CLI | `ocx-claude---` (plain) или `ocx-claude2-…` (escaped) | `ocx-claude-native--gpt-5.6-sol` | | Claude Desktop 3P | `claude-opus-4-8-` (3-символьный base36-хеш) | `claude-opus-4-8-ncb` | Прокси выбирает семейство для каждого запроса: приоритет у `?ids=cli` или `?ids=desktop`; иначе @@ -189,10 +188,10 @@ user-agent `claude-code/*` получает читаемую CLI-форму, а запросе. Проверьте, что отправляет клиент, в **Logs → requestedModel**. **Правила грамматики алиасов:** provider не может содержать `/` или `--` и не может быть равен -`native`. Обычные id моделей (без `/` и `~`) остаются с префиксом v1 `claude-ocx-…`. Id с `/` -или `~` выпускаются с префиксом v2 `claude-ocx2-…` и экранированием (`/` → `~s`, `~` → `~t`), +`native`. Обычные id моделей (без `/` и `~`) остаются с префиксом v1 `ocx-claude-…`. Id с `/` +или `~` выпускаются с префиксом v2 `ocx-claude2-…` и экранированием (`/` → `~s`, `~` → `~t`), например `openrouter/anthropic/claude-opus-4-8` → -`claude-ocx2-openrouter--anthropic~sclaude-opus-4-8`. Алиасы v1 декодируются литерально (исторические +`ocx-claude2-openrouter--anthropic~sclaude-opus-4-8`. Алиасы v1 декодируются литерально (исторические двухсимвольные последовательности `~s` / `~t` в id модели сохраняются); алиасы v2 раскрывают экранирование. Маршруты, которые невозможно выразить читаемой формой, откатываются на хешированный алиас. Id моделей МОГУТ содержать `--` (при разрешении разбиение выполняется только diff --git a/docs-site/src/content/docs/tr/guides/claude-code.md b/docs-site/src/content/docs/tr/guides/claude-code.md index a2257ae9775..837fbe2c858 100644 --- a/docs-site/src/content/docs/tr/guides/claude-code.md +++ b/docs-site/src/content/docs/tr/guides/claude-code.md @@ -65,7 +65,7 @@ bağlanmış olarak Claude Code'u başlatır: | `ANTHROPIC_DEFAULT_{OPUS,SONNET,FABLE}_MODEL` | `claudeCode.tierModels.*` (isteğe bağlı) | | `CLAUDE_CODE_ALWAYS_ENABLE_EFFORT` | `alwaysEnableEffort` açık olduğunda `1` (koşullu) | | `ENABLE_TOOL_SEARCH` | `claudeCode.toolSearch` ayarlandığında (koşullu; varsayılan olarak kapalı) | -| `CLAUDE_CODE_MAX_CONTEXT_TOKENS` / `DISABLE_COMPACT` | `maxContextTokens` ayarlandığında eski bağlam geçersiz kılma (koşullu) | +| `CLAUDE_CODE_MAX_CONTEXT_TOKENS` | `maxContextTokens` ayarlandığında eski bağlam geçersiz kılma (koşullu) | Kendi dışa aktardığınız değişkenler her zaman önceliklidir. Ekstra argümanlar doğrudan iletilir: `ocx claude -p "hello"`. @@ -313,13 +313,12 @@ gerekirse anahtarı hub'da ayrıca iptal edin. Claude Code 2.1.129+, `GET /v1/models?limit=1000` aracılığıyla ağ geçidi modellerini keşfeder ve bunları yerel `/model` seçicisinde "From gateway" -etiketiyle listeler. Seçici yalnızca `claude` veya `anthropic` ile başlayan -kimlikleri kabul ettiğinden, opencodex yönlendirilen modelleri kararlı, tersine +etiketiyle listeler. Claude Code 2.1.278, `claude` veya `anthropic` içeren bir kimliği kabul eder. `claude-` ile başlayan tanınmayan bir kimlik, compact kapatılmadıkça 200k sayılır. opencodex yönlendirilen modelleri `claude` içeren ama `claude-` ile başlamayan kararlı, tersine çevrilebilir takma adlar olarak sunar: | Yüzey | Format | Örnek | | --- | --- | --- | -| Claude Code CLI | `claude-ocx---` (düz) veya `claude-ocx2-…` (kaçışlı) | `claude-ocx-openai--gpt-5.6-sol` | +| Claude Code CLI | `ocx-claude---` (düz) veya `ocx-claude2-…` (kaçışlı) | `ocx-claude-openai--gpt-5.6-sol` | | Claude Desktop 3P | `claude-opus-4-8-` (3 karakterli base36 karması) | `claude-opus-4-8-ncb` | Proxy, istek başına aileyi seçer: `?ids=cli` veya `?ids=desktop` kazanır; aksi @@ -361,10 +360,10 @@ Code dizeleri doğrudan iletir). **Takma ad dilbilgisi kuralları:** sağlayıcı `/` veya `--` içeremez veya `native` değerine eşit olamaz. Düz model kimlikleri (`/` veya `~` içermeyen) v1 -önekini `claude-ocx-…` korur. `/` veya `~` içeren model kimlikleri, kaçışlarla -(`/` → `~s`, `~` → `~t`) v2 önekini `claude-ocx2-…` basar, örn. +önekini `ocx-claude-…` korur. `/` veya `~` içeren model kimlikleri, kaçışlarla +(`/` → `~s`, `~` → `~t`) v2 önekini `ocx-claude2-…` basar, örn. `openrouter/anthropic/claude-opus-4-8` → -`claude-ocx2-openrouter--anthropic~sclaude-opus-4-8`. v1 takma adları harfi +`ocx-claude2-openrouter--anthropic~sclaude-opus-4-8`. v1 takma adları harfi harfine çözülür (böylece `~s` / `~t` iki karakterli dizilerini içeren geçmiş bir model kimliği korunur); v2 takma adları kaçışları genişletir. Okunabilir formun ifade edemediği rotalar karma takma ada geri döner. Model kimlikleri `--` diff --git a/docs-site/src/content/docs/zh-cn/guides/claude-code.md b/docs-site/src/content/docs/zh-cn/guides/claude-code.md index 25695885e96..cf609f7c8ec 100644 --- a/docs-site/src/content/docs/zh-cn/guides/claude-code.md +++ b/docs-site/src/content/docs/zh-cn/guides/claude-code.md @@ -26,7 +26,7 @@ ocx claude | `ANTHROPIC_DEFAULT_{OPUS,SONNET,FABLE}_MODEL` | `claudeCode.tierModels.*`(可选) | | `CLAUDE_CODE_ALWAYS_ENABLE_EFFORT` | 启用 `alwaysEnableEffort` 时设为 `1`(条件注入) | | `ENABLE_TOOL_SEARCH` | 设置了 `claudeCode.toolSearch` 时注入(条件注入,默认关闭) | -| `CLAUDE_CODE_MAX_CONTEXT_TOKENS` / `DISABLE_COMPACT` | 设置 `maxContextTokens` 时使用的旧版上下文覆盖项(条件注入) | +| `CLAUDE_CODE_MAX_CONTEXT_TOKENS` | 设置 `maxContextTokens` 时使用的旧版上下文覆盖项(条件注入) | 你自行导出的变量始终优先。额外参数会直接透传:`ocx claude -p "hello"`。 ### Claude 路由关闭时的原生回退 @@ -146,12 +146,11 @@ UI。真实 Anthropic 模型保留其原始 id。合成的 2026 日期是内部 中输入任意路由 id(Claude Code 会原样传递字符串)。 Claude Code 2.1.129+ 通过 `GET /v1/models?limit=1000` 发现网关模型,并在原生 `/model` -选择器中以“From gateway”标签列出。由于选择器只接受以 `claude` 或 `anthropic` 开头的 ID, -opencodex 会将已路由模型公开为稳定且可逆的别名: +选择器中以“From gateway”标签列出。Claude Code 2.1.278 接受包含 `claude` 或 `anthropic` 的 ID。以 `claude-` 开头的未知 ID 在不关闭 compact 时按 200k 计算,因此 opencodex 会将已路由模型公开为包含 `claude`、但不以 `claude-` 开头的稳定且可逆别名: | 界面 | 格式 | 示例 | | --- | --- | --- | -| Claude Code CLI | `claude-ocx---`(plain)或 `claude-ocx2-…`(escaped) | `claude-ocx-native--gpt-5.6-sol` | +| Claude Code CLI | `ocx-claude---`(plain)或 `ocx-claude2-…`(escaped) | `ocx-claude-native--gpt-5.6-sol` | | Claude Desktop 3P | `claude-opus-4-8-`(3 字符 base36 哈希) | `claude-opus-4-8-ncb` | 代理会按请求选择别名族:`?ids=cli` 或 `?ids=desktop` 优先;否则,`claude-code/*` @@ -169,9 +168,9 @@ Claude Desktop 1.46388.4 时,无论通过底部选择器还是 `/model` 更改 而是根据每个请求携带的模型 ID 进行路由。请在 **Logs → requestedModel** 中确认客户端实际发送的内容。 **别名语法规则:**provider 不得包含 `/` 或 `--`,也不得等于 `native`。 -不含 `/` 或 `~` 的普通 model ID 继续使用 v1 前缀 `claude-ocx-…`。包含 `/` 或 `~` 的 model ID -会使用 v2 前缀 `claude-ocx2-…` 并转义(`/` → `~s`,`~` → `~t`),例如 -`openrouter/anthropic/claude-opus-4-8` → `claude-ocx2-openrouter--anthropic~sclaude-opus-4-8`。 +不含 `/` 或 `~` 的普通 model ID 继续使用 v1 前缀 `ocx-claude-…`。包含 `/` 或 `~` 的 model ID +会使用 v2 前缀 `ocx-claude2-…` 并转义(`/` → `~s`,`~` → `~t`),例如 +`openrouter/anthropic/claude-opus-4-8` → `ocx-claude2-openrouter--anthropic~sclaude-opus-4-8`。 v1 别名按字面解码(历史上 model ID 中包含的两字符序列 `~s` / `~t` 会被保留);v2 别名会展开转义。 易读形式无法表达的路由会回退到哈希别名。模型 ID **可以**包含 `--`(解析时只按第一个 `--` 分割); 含 `--` 的原生 slug 会回退到哈希形式。 diff --git a/docs-site/src/content/docs/zh-tw/guides/claude-code.md b/docs-site/src/content/docs/zh-tw/guides/claude-code.md index d9b7240c2ef..9689dda1373 100644 --- a/docs-site/src/content/docs/zh-tw/guides/claude-code.md +++ b/docs-site/src/content/docs/zh-tw/guides/claude-code.md @@ -52,7 +52,7 @@ ocx claude | `ANTHROPIC_DEFAULT_{OPUS,SONNET,FABLE}_MODEL` | `claudeCode.tierModels.*`(可選) | | `CLAUDE_CODE_ALWAYS_ENABLE_EFFORT` | 啟用 `alwaysEnableEffort` 時設為 `1`(條件注入) | | `ENABLE_TOOL_SEARCH` | 設定 `claudeCode.toolSearch` 時注入(條件注入,預設關閉) | -| `CLAUDE_CODE_MAX_CONTEXT_TOKENS` / `DISABLE_COMPACT` | 設定 `maxContextTokens` 時使用的舊版上下文覆蓋項(條件注入) | +| `CLAUDE_CODE_MAX_CONTEXT_TOKENS` | 設定 `maxContextTokens` 時使用的舊版上下文覆蓋項(條件注入) | 你自行匯出的變數始終優先。額外引數會直接透傳:`ocx claude -p "hello"`。 ### Claude 路由關閉時的原生回退 @@ -222,12 +222,11 @@ apply、輪換/復原或直接 disconnect 均可處理,無須新參數或事 ## /model 選擇器(“From gateway”) Claude Code 2.1.129+ 透過 `GET /v1/models?limit=1000` 發現閘道器模型,並在原生 `/model` -選擇器中以“From gateway”標籤列出。由於選擇器只接受以 `claude` 或 `anthropic` 開頭的 ID, -opencodex 會將已路由模型公開為穩定且可逆的別名: +選擇器中以“From gateway”標籤列出。Claude Code 2.1.278 接受包含 `claude` 或 `anthropic` 的 ID。以 `claude-` 開頭的未知 ID 在不關閉 compact 時按 200k 計算,因此 opencodex 會將已路由模型公開為包含 `claude`、但不以 `claude-` 開頭的穩定且可逆別名: | 介面 | 格式 | 示例 | | --- | --- | --- | -| Claude Code CLI | `claude-ocx---` | `claude-ocx-native--gpt-5.6-sol` | +| Claude Code CLI | `ocx-claude---`(plain)或 `ocx-claude2-…`(escaped) | `ocx-claude-native--gpt-5.6-sol` | | Claude Desktop 3P | `claude-opus-4-8-`(3 字元 base36 雜湊) | `claude-opus-4-8-ncb` | 代理會按請求選擇別名族:`?ids=cli` 或 `?ids=desktop` 優先;否則,`claude-code/*` @@ -243,8 +242,11 @@ user-agent 會獲得易讀的 CLI 形式,其他用戶端會獲得 Desktop 雜 模型。在較舊的 Claude Code 版本中,選擇器保持原生——可透過 `ANTHROPIC_MODEL` 設定槽位,或在 `/model` 中輸入任意已路由 id(Claude Code 會原樣傳遞字串)。 -**別名語法規則:**provider 不得包含 `/` 或 `--`,也不得等於 `native`;model 不得包含 -`/`。易讀形式無法表達的路由會回退到雜湊別名。模型 ID **可以**包含 `--`(解析時只按第一個 +**別名語法規則:**provider 不得包含 `/` 或 `--`,也不得等於 `native`。 +不含 `/` 或 `~` 的一般 model ID 使用 v1 前綴 `ocx-claude-…`。包含 `/` 或 `~` 的 model ID +使用 v2 前綴 `ocx-claude2-…` 並跳脫(`/` → `~s`,`~` → `~t`),例如 +`openrouter/anthropic/claude-opus-4-8` → `ocx-claude2-openrouter--anthropic~sclaude-opus-4-8`。 +易讀形式無法表達的路由會回退到雜湊別名。模型 ID **可以**包含 `--`(解析時只按第一個 `--` 拆分);包含 `--` 的原生 slug 會回退到雜湊形式。 **模型解析順序:**移除 `[1m]` 標記 → 解碼易讀別名 → 解碼 Desktop 雜湊別名 → diff --git a/src/claude/alias.ts b/src/claude/alias.ts index db7d74e0773..ddb38fb2020 100644 --- a/src/claude/alias.ts +++ b/src/claude/alias.ts @@ -1,25 +1,26 @@ /** * Gateway model-discovery aliases (devlog/260711_claude_inbound/020, 003 G1-G6). * - * Claude Code's /model picker only lists discovery entries whose id literally - * begins with `claude` or `anthropic`, so routed models are exposed as - * `claude-ocx---` with an honest display_name. Aliases must be - * deterministic, reversible, and STABLE across releases (picker selections - * persist to Claude Code's settings.json `model` field). + * Claude Code's /model picker accepts discovery ids containing `claude` or + * `anthropic`. New routed models are exposed as `ocx-claude---` + * with an honest display_name. The id must contain `claude` so the picker keeps + * it, but must not START with `claude-`: Claude Code 2.1.278 treats an + * unrecognized `claude-` id as its own model and ignores + * CLAUDE_CODE_MAX_CONTEXT_TOKENS unless DISABLE_COMPACT=1. Starting with + * `ocx-claude-` keeps the real window and leaves compact enabled. * - * Versioned prefixes: - * - `claude-ocx-` (v1) — legacy / plain model ids with no `/` or `~`. Decode - * is literal (no escape expansion), so a persisted model id that literally - * contained the two-char sequences `~s` / `~t` keeps resolving. - * - `claude-ocx2-` (v2) — used whenever the model id needs escape encoding - * (`/` → `~s`, `~` → `~t`). Decode expands those escapes. New slash/tilde - * models always mint v2 so they cannot collide with v1 literals. + * Persisted ids stay decodable: + * - `ocx-claude-` — current plain ids. Decode is literal. + * - `claude-ocx-` (v1) — legacy plain ids. Decode stays literal, so a persisted + * model id that contained the two-char sequences `~s` / `~t` keeps resolving. + * - `claude-ocx2-` (v2) — escape encoding (`/` → `~s`, `~` → `~t`). Decode + * expands those escapes. New slash/tilde models still mint v2. * * Reversibility rules: * - providers containing `--` or `/` are not aliased (split boundary safety); * - model ids MAY contain `/` or `~` — minted under the v2 prefix with escapes * (e.g. openrouter `anthropic/claude-opus-4-8` → - * `claude-ocx2-openrouter--anthropic~sclaude-opus-4-8`); + * `ocx-claude2-openrouter--anthropic~sclaude-opus-4-8`); * - model ids MAY contain `--` (resolve splits on the FIRST `--` only); * - native OpenAI slugs use the pseudo-provider `native` and resolve back to * the bare slug; a real provider named "native" is therefore never aliased. @@ -27,15 +28,19 @@ import { desktop3pAlias } from "./desktop-3p"; -/** Legacy / plain readable prefix (literal model portion on decode). */ +/** Current plain prefix. Contains "claude" but does not start with "claude-". */ +export const CLAUDE_ALIAS_PREFIX_CURRENT = "ocx-claude-"; +/** Current escape-encoded prefix (`~s`/`~t` expanded on decode). */ +export const CLAUDE_ALIAS_PREFIX_CURRENT_V2 = "ocx-claude2-"; +/** Legacy plain prefix. Still decoded; no longer minted. */ export const CLAUDE_ALIAS_PREFIX_V1 = "claude-ocx-"; -/** Escape-encoded readable prefix (`~s`/`~t` expanded on decode). */ +/** Legacy escape-encoded prefix. Still decoded; no longer minted. */ export const CLAUDE_ALIAS_PREFIX_V2 = "claude-ocx2-"; /** * Current write prefix for plain (unescaped) model ids. - * Escape-needing models mint {@link CLAUDE_ALIAS_PREFIX_V2} instead. + * Escape-needing models mint {@link CLAUDE_ALIAS_PREFIX_CURRENT_V2} instead. */ -export const CLAUDE_ALIAS_PREFIX = CLAUDE_ALIAS_PREFIX_V1; +export const CLAUDE_ALIAS_PREFIX = CLAUDE_ALIAS_PREFIX_CURRENT; /** Encoded `/` inside the model portion of a v2 Claude Code alias. */ const CLAUDE_ALIAS_SLASH_ENC = "~s"; @@ -90,9 +95,9 @@ export function aliasForRoute(provider: string, modelId: string): string | null if (!provider || provider.includes("--") || provider.includes("/") || provider === NATIVE_PSEUDO_PROVIDER) return null; if (!modelId) return null; if (modelNeedsEscapeEncoding(modelId)) { - return `${CLAUDE_ALIAS_PREFIX_V2}${provider}--${encodeModelId(modelId)}`; + return `${CLAUDE_ALIAS_PREFIX_CURRENT_V2}${provider}--${encodeModelId(modelId)}`; } - return `${CLAUDE_ALIAS_PREFIX_V1}${provider}--${modelId}`; + return `${CLAUDE_ALIAS_PREFIX_CURRENT}${provider}--${modelId}`; } /** Alias for a native OpenAI slug (bare model id, no provider namespace). */ @@ -100,9 +105,9 @@ export function aliasForNative(slug: string): string | null { // Reject "/" — native ids are bare slugs. Literal `~` is fine via v2 + ~t. if (!slug || slug.includes("/") || slug.includes("--")) return null; if (modelNeedsEscapeEncoding(slug)) { - return `${CLAUDE_ALIAS_PREFIX_V2}${NATIVE_PSEUDO_PROVIDER}--${encodeModelId(slug)}`; + return `${CLAUDE_ALIAS_PREFIX_CURRENT_V2}${NATIVE_PSEUDO_PROVIDER}--${encodeModelId(slug)}`; } - return `${CLAUDE_ALIAS_PREFIX_V1}${NATIVE_PSEUDO_PROVIDER}--${slug}`; + return `${CLAUDE_ALIAS_PREFIX_CURRENT}${NATIVE_PSEUDO_PROVIDER}--${slug}`; } /** @@ -110,7 +115,15 @@ export function aliasForNative(slug: string): string | null { * routed -> "/", native -> bare slug. Null when not an alias. */ export function resolveAlias(id: string): string | null { - // Check v2 before v1 for clarity (prefixes are disjoint: ocx2 vs ocx-). + // Current v2 before legacy v2, then plain prefixes. `ocx-claude2-` and + // `claude-ocx2-` are disjoint from their plain siblings. + if (id.startsWith(CLAUDE_ALIAS_PREFIX_CURRENT_V2)) { + const parts = splitAlias(id, CLAUDE_ALIAS_PREFIX_CURRENT_V2); + if (!parts) return null; + const model = decodeEscapedModelId(parts.model); + if (!model) return null; + return parts.provider === NATIVE_PSEUDO_PROVIDER ? model : `${parts.provider}/${model}`; + } if (id.startsWith(CLAUDE_ALIAS_PREFIX_V2)) { const parts = splitAlias(id, CLAUDE_ALIAS_PREFIX_V2); if (!parts) return null; @@ -118,8 +131,9 @@ export function resolveAlias(id: string): string | null { if (!model) return null; return parts.provider === NATIVE_PSEUDO_PROVIDER ? model : `${parts.provider}/${model}`; } - if (id.startsWith(CLAUDE_ALIAS_PREFIX_V1)) { - const parts = splitAlias(id, CLAUDE_ALIAS_PREFIX_V1); + for (const prefix of [CLAUDE_ALIAS_PREFIX_CURRENT, CLAUDE_ALIAS_PREFIX_V1]) { + if (!id.startsWith(prefix)) continue; + const parts = splitAlias(id, prefix); if (!parts) return null; // Literal decode — preserves pre-escape aliases whose model id contained // the two-char sequences ~s / ~t. @@ -131,11 +145,11 @@ export function resolveAlias(id: string): string | null { /** * Claude Code (CLI) surface alias — devlog 050 + audit 051 #2. * - * The readable `claude-ocx*` form when representable; otherwise the desktop-3p + * The readable `ocx-claude*` form when representable; otherwise the desktop-3p * hash so the model still appears in discovery (collisions follow the same * first-wins policy as the desktop registry — audit 051 #1). Real Anthropic * models pass through unchanged (they must keep hitting the sk-ant passthrough). - * Both families keep decoding forever in resolveInboundModel, so ids persisted + * Old `claude-ocx*` ids keep decoding forever in resolveInboundModel, so ids persisted * in Claude Code's settings.json never break when the surface style changes. */ export function claudeCodeAlias(provider: string, modelId: string): string { diff --git a/src/claude/gateway-cache.ts b/src/claude/gateway-cache.ts index 366017a04ba..1f41edee008 100644 --- a/src/claude/gateway-cache.ts +++ b/src/claude/gateway-cache.ts @@ -7,7 +7,9 @@ * refresh its picker list itself — it reads whatever cache exists. We therefore * pre-write the cache in the exact on-disk schema the CLI uses: * { baseUrl, fetchedAt, models: [{ id, display_name? }] } (mode 0600) - * mirroring its `/^(claude|anthropic)/i` usable-id filter. The picker validates + * mirroring the picker rule that the id must contain `claude` or `anthropic`. + * Current aliases are `ocx-claude-*`, so an anchored `^(claude|anthropic)` filter + * would drop every newly minted routed model. The picker validates * only `baseUrl === ANTHROPIC_BASE_URL`, so a foreign base URL is simply ignored. */ import { mkdirSync, writeFileSync } from "node:fs"; @@ -49,7 +51,7 @@ export function claudeConfigDir(): string { export function writeGatewayModelCache(baseUrl: string, models: readonly GatewayModelRow[], configDir = claudeConfigDir()): string | null { try { // Mirror the CLI's usable-id filter so our file matches what it would cache. - const usable = models.filter(m => /^(claude|anthropic)/i.test(m.id)); + const usable = models.filter(m => /(claude|anthropic)/i.test(m.id)); const cacheDir = join(configDir, "cache"); mkdirSync(cacheDir, { recursive: true }); const path = join(cacheDir, "gateway-models.json"); diff --git a/src/cli/claude.ts b/src/cli/claude.ts index 846a8c921e6..c42f51b2027 100644 --- a/src/cli/claude.ts +++ b/src/cli/claude.ts @@ -373,12 +373,13 @@ export function buildClaudeEnv( // worse than the problem. So this stays opt-in per config rather than // unconditional, and setDefault keeps an operator's own export. setDefault("ENABLE_TOOL_SEARCH", claudeToolSearchEnv(config.claudeCode?.toolSearch)); - // Context-window override: the official pair — MAX_CONTEXT_TOKENS alone is ignored - // for recognized claude-shaped ids unless DISABLE_COMPACT=1 rides along (devlog 135). const maxCtx = config.claudeCode?.maxContextTokens; if (typeof maxCtx === "number" && Number.isFinite(maxCtx) && maxCtx > 0) { setDefault("CLAUDE_CODE_MAX_CONTEXT_TOKENS", String(Math.floor(maxCtx))); - setDefault("DISABLE_COMPACT", "1"); + // Claude Code 2.1.278 honors this without DISABLE_COMPACT when the model id + // does not start with "claude-" (gF). Current ocx-claude aliases qualify. + // A persisted claude-ocx id is still claude-shaped, so that one session keeps + // the 200k accounting until the picker is moved to the new id. } // Auto-context (devlog 260712 020): min(believed window, env) inside the CLI means // one global env acts as a per-model floor — [1m]-marked models compact here while diff --git a/src/server/system-env-shell.ts b/src/server/system-env-shell.ts index b8e1b0176ef..04964bf0478 100644 --- a/src/server/system-env-shell.ts +++ b/src/server/system-env-shell.ts @@ -120,7 +120,6 @@ export function writeShellEnvFile( const maxCtx = config.claudeCode?.maxContextTokens; if (typeof maxCtx === "number" && Number.isFinite(maxCtx) && maxCtx > 0) { lines.push(conditional("CLAUDE_CODE_MAX_CONTEXT_TOKENS", String(Math.floor(maxCtx)))); - lines.push(conditional("DISABLE_COMPACT", "1")); } // Auto-context (devlog 260712 020): same contract as `ocx claude` / launchctl. const autoShell = auto ?? resolveAutoContext(config.claudeCode); diff --git a/src/server/system-env.ts b/src/server/system-env.ts index fc628a8f96a..e2e5f6504d7 100644 --- a/src/server/system-env.ts +++ b/src/server/system-env.ts @@ -303,7 +303,6 @@ export async function injectSystemEnv( const maxCtx = config.claudeCode?.maxContextTokens; if (typeof maxCtx === "number" && Number.isFinite(maxCtx) && maxCtx > 0) { injectLever("CLAUDE_CODE_MAX_CONTEXT_TOKENS", String(Math.floor(maxCtx))); - injectLever("DISABLE_COMPACT", "1"); } // Auto-context (devlog 260712 020): user-wins lever, inert when maxContextTokens set. if (auto.enabled) injectLever("CLAUDE_CODE_AUTO_COMPACT_WINDOW", String(auto.compactWindow)); diff --git a/src/types/config.ts b/src/types/config.ts index 6fc6939cce8..3a8943ae031 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -81,10 +81,11 @@ export interface OcxClaudeCodeConfig { */ authModeMigratedAt?: string; /** - * Context-window override for Claude Code/Desktop clients (devlog 136 B6): - * injected as CLAUDE_CODE_MAX_CONTEXT_TOKENS + DISABLE_COMPACT=1 (the official - * env pair — recognized claude-shaped ids need both). WARNING: DISABLE_COMPACT - * turns off auto-compaction. Unset = client defaults. + * Context-window override for Claude Code/Desktop clients (devlog 136 B6). + * Injected as CLAUDE_CODE_MAX_CONTEXT_TOKENS only. Current ocx-claude aliases do + * not start with claude-, so Claude Code 2.1.278 honors the window without + * DISABLE_COMPACT. A persisted claude-ocx id is still claude-shaped and keeps + * the 200k accounting until the picker selects the new id. Unset = client defaults. */ maxContextTokens?: number; /** @@ -131,8 +132,7 @@ export interface OcxClaudeCodeConfig { * (Claude Code then accounts 1M) and CLAUDE_CODE_AUTO_COMPACT_WINDOW is injected * so compaction fires at the real budget. 2.1.207 semantics (binary-verified): * effective compact window = min(believed window, env) — one global env behaves - * like a per-model floor. Default: enabled. Inert while maxContextTokens is set - * (the legacy DISABLE_COMPACT pair takes rule-1 precedence in the CLI). + * like a per-model floor. Default: enabled. Inert while maxContextTokens is set. */ autoContext?: boolean; /** Compact-window tokens for auto-context. Default 829_800 (AUTO_COMPACT_WINDOW_DEFAULT). */ diff --git a/tests/claude-integration/claude-agents-inject.test.ts b/tests/claude-integration/claude-agents-inject.test.ts index 2945e81238b..4716efb9446 100644 --- a/tests/claude-integration/claude-agents-inject.test.ts +++ b/tests/claude-integration/claude-agents-inject.test.ts @@ -31,9 +31,9 @@ function generatedBodies(config: OcxConfig, dir: string): string[] { describe("buildClaudeAgentDefs (devlog 070 + audit 071)", () => { test("roster + pinned self mark only authoritative 1M windows; name collision suffix", () => { - const windows = { "claude-ocx-native--gpt-5.6-sol": 372_000, "claude-ocx-cursor--gpt-5.6-sol": 1_000_000 }; + const windows = { "ocx-claude-native--gpt-5.6-sol": 372_000, "ocx-claude-cursor--gpt-5.6-sol": 1_000_000 }; const dir = tempDir(); - writeFileSync(join(dir, "settings.json"), JSON.stringify({ model: "claude-ocx-native--gpt-5.6-sol[1m]" })); + writeFileSync(join(dir, "settings.json"), JSON.stringify({ model: "ocx-claude-native--gpt-5.6-sol[1m]" })); const defs = buildClaudeAgentDefs(cfg({ subagentModels: ["gpt-5.6-sol", "cursor/gpt-5.6-sol"], claudeCode: { autoContext: true }, @@ -41,10 +41,10 @@ describe("buildClaudeAgentDefs (devlog 070 + audit 071)", () => { const byName = Object.fromEntries(defs.map(d => [d.name, d])); // 372K >= 350K compact default marks the MAIN session (env slots pair with the // compact window), but a generated subagent has no such pairing — it stays bare. - expect(byName["ocx-gpt-5-6-sol"]!.model).toBe("claude-ocx-native--gpt-5.6-sol"); - expect(byName["ocx-gpt-5-6-sol-2"]!.model).toBe("claude-ocx-cursor--gpt-5.6-sol[1m]"); // collision suffix + expect(byName["ocx-gpt-5-6-sol"]!.model).toBe("ocx-claude-native--gpt-5.6-sol"); + expect(byName["ocx-gpt-5-6-sol-2"]!.model).toBe("ocx-claude-cursor--gpt-5.6-sol[1m]"); // collision suffix // Self pins the picker-saved default but cannot inherit an unsafe auto-context marker. - expect(byName["ocx-self"]!.model).toBe("claude-ocx-native--gpt-5.6-sol"); + expect(byName["ocx-self"]!.model).toBe("ocx-claude-native--gpt-5.6-sol"); expect(defs).toHaveLength(3); // Dispatcher directive (live repro: model:"fable" override broke inherit). for (const d of defs) expect(d.description).toContain("`model` argument is ignored"); @@ -81,47 +81,47 @@ describe("buildClaudeAgentDefs (devlog 070 + audit 071)", () => { const catalog = await fetchProviderModels("kimi", kimi, 0); const windows = buildClaudeContextWindows([], catalog); const dir = tempDir(); - writeFileSync(join(dir, "settings.json"), JSON.stringify({ model: "claude-ocx-kimi--k3[1m]" })); + writeFileSync(join(dir, "settings.json"), JSON.stringify({ model: "ocx-claude-kimi--k3[1m]" })); const defs = buildClaudeAgentDefs(config, windows, dir); const models = Object.fromEntries(defs.map(def => [def.name, def.model])); - expect(windows["claude-ocx-kimi--k3"]).toBe(262_144); - expect(windows["claude-ocx-kimi--k3[1m]"]).toBe(1_048_576); + expect(windows["ocx-claude-kimi--k3"]).toBe(262_144); + expect(windows["ocx-claude-kimi--k3[1m]"]).toBe(1_048_576); expect(models).toEqual({ - "ocx-k3-1m": "claude-ocx-kimi--k3[1m]", - "ocx-self": "claude-ocx-kimi--k3[1m]", + "ocx-k3-1m": "ocx-claude-kimi--k3[1m]", + "ocx-self": "ocx-claude-kimi--k3[1m]", }); // A provider cap below 1M unmarks the same selector. const cappedCatalog = await fetchProviderModels("kimi", kimi, 0, 350_000); const cappedWindows = buildClaudeContextWindows([], cappedCatalog); const cappedDir = tempDir(); - writeFileSync(join(cappedDir, "settings.json"), JSON.stringify({ model: "claude-ocx-kimi--k3[1m]" })); + writeFileSync(join(cappedDir, "settings.json"), JSON.stringify({ model: "ocx-claude-kimi--k3[1m]" })); const cappedDefs = buildClaudeAgentDefs(config, cappedWindows, cappedDir); - expect(cappedWindows["claude-ocx-kimi--k3[1m]"]).toBe(350_000); + expect(cappedWindows["ocx-claude-kimi--k3[1m]"]).toBe(350_000); expect(Object.fromEntries(cappedDefs.map(def => [def.name, def.model]))).toEqual({ - "ocx-k3-1m": "claude-ocx-kimi--k3", - "ocx-self": "claude-ocx-kimi--k3", + "ocx-k3-1m": "ocx-claude-kimi--k3", + "ocx-self": "ocx-claude-kimi--k3", }); }); test("marker case is honored and unknown windows keep the selector as-was", () => { - const windows = { "claude-ocx-cursor--gpt-5.6-sol": 1_000_000 }; + const windows = { "ocx-claude-cursor--gpt-5.6-sol": 1_000_000 }; const dir = tempDir(); // Uppercase [1M] spelling is a genuine marker (the CLI matches /\[1m\]/i). - writeFileSync(join(dir, "settings.json"), JSON.stringify({ model: "claude-ocx-cursor--gpt-5.6-sol[1M]" })); + writeFileSync(join(dir, "settings.json"), JSON.stringify({ model: "ocx-claude-cursor--gpt-5.6-sol[1M]" })); const defs = buildClaudeAgentDefs(cfg({ subagentModels: ["cursor/gpt-5.6-sol", "cursor/unknown-model"] }), windows, dir); const byName = Object.fromEntries(defs.map(d => [d.name, d])); - expect(byName["ocx-gpt-5-6-sol"]!.model).toBe("claude-ocx-cursor--gpt-5.6-sol[1m]"); + expect(byName["ocx-gpt-5-6-sol"]!.model).toBe("ocx-claude-cursor--gpt-5.6-sol[1m]"); // Incomplete metadata: no window entry -> selector preserved, never unmarked. - expect(byName["ocx-unknown-model"]!.model).toBe("claude-ocx-cursor--unknown-model"); - expect(byName["ocx-self"]!.model).toBe("claude-ocx-cursor--gpt-5.6-sol[1M]"); + expect(byName["ocx-unknown-model"]!.model).toBe("ocx-claude-cursor--unknown-model"); + expect(byName["ocx-self"]!.model).toBe("ocx-claude-cursor--gpt-5.6-sol[1M]"); }); test("placeholder guidance recommends haiku, never sonnet (issue #252)", () => { const dir = tempDir(); - writeFileSync(join(dir, "settings.json"), JSON.stringify({ model: "claude-ocx-native--gpt-5.6-sol" })); + writeFileSync(join(dir, "settings.json"), JSON.stringify({ model: "ocx-claude-native--gpt-5.6-sol" })); const defs = buildClaudeAgentDefs(cfg({ subagentModels: ["gpt-5.6-sol"] }), {}, dir); expect(defs.length).toBeGreaterThan(0); for (const d of defs) { @@ -165,7 +165,7 @@ describe("buildClaudeAgentDefs (devlog 070 + audit 071)", () => { const levels = ["low", "medium", "high", "xhigh", "max"] as const; for (const effort of levels) { const dir = tempDir(); - writeFileSync(join(dir, "settings.json"), JSON.stringify({ model: "claude-ocx-native--gpt-5.6-sol" })); + writeFileSync(join(dir, "settings.json"), JSON.stringify({ model: "ocx-claude-native--gpt-5.6-sol" })); const defs = buildClaudeAgentDefs(cfg({ subagentModels: ["gpt-5.6-sol"], claudeCode: { subagentEffort: effort }, @@ -194,7 +194,7 @@ describe("buildClaudeAgentDefs (devlog 070 + audit 071)", () => { test("generated routed agents refuse the default blocked skill before its bundle expands", () => { const dir = tempDir(); - writeFileSync(join(dir, "settings.json"), JSON.stringify({ model: "claude-ocx-native--gpt-5.6-sol" })); + writeFileSync(join(dir, "settings.json"), JSON.stringify({ model: "ocx-claude-native--gpt-5.6-sol" })); const bodies = generatedBodies(cfg({ subagentModels: ["gpt-5.6-sol"] }), dir); expect(bodies).toHaveLength(2); // roster + ocx-self for (const body of bodies) { @@ -205,7 +205,7 @@ describe("buildClaudeAgentDefs (devlog 070 + audit 071)", () => { test("generated blocked-skill guard mirrors custom names and honors explicit opt-out", () => { const customDir = tempDir(); - writeFileSync(join(customDir, "settings.json"), JSON.stringify({ model: "claude-ocx-native--gpt-5.6-sol" })); + writeFileSync(join(customDir, "settings.json"), JSON.stringify({ model: "ocx-claude-native--gpt-5.6-sol" })); const customBodies = generatedBodies(cfg({ subagentModels: ["gpt-5.6-sol"], claudeCode: { blockedSkills: [" My-Skill "] }, @@ -218,7 +218,7 @@ describe("buildClaudeAgentDefs (devlog 070 + audit 071)", () => { } const offDir = tempDir(); - writeFileSync(join(offDir, "settings.json"), JSON.stringify({ model: "claude-ocx-native--gpt-5.6-sol" })); + writeFileSync(join(offDir, "settings.json"), JSON.stringify({ model: "ocx-claude-native--gpt-5.6-sol" })); const offBodies = generatedBodies(cfg({ subagentModels: ["gpt-5.6-sol"], claudeCode: { blockedSkills: [] }, @@ -280,7 +280,7 @@ describe("syncClaudeAgentDefs ownership contract (audit 071 #2/#3)", () => { test("writes, overwrites, and prunes ONLY marker-verified ocx files", () => { const dir = tempDir(); - writeFileSync(join(dir, "settings.json"), JSON.stringify({ model: "claude-ocx-native--gpt-5.6-sol" })); + writeFileSync(join(dir, "settings.json"), JSON.stringify({ model: "ocx-claude-native--gpt-5.6-sol" })); const defs = buildClaudeAgentDefs(cfg({ subagentModels: ["gpt-5.6-sol"] }), {}, dir); expect(syncClaudeAgentDefs(defs, dir)!.length).toBe(2); const agentsDir = join(dir, "agents"); @@ -330,7 +330,7 @@ describe("syncClaudeAgentDefs ownership contract (audit 071 #2/#3)", () => { test("injectClaudeAgentDefs prunes owned files when disabled (audit 071 #3)", () => { const dir = tempDir(); - writeFileSync(join(dir, "settings.json"), JSON.stringify({ model: "claude-ocx-native--gpt-5.6-sol" })); + writeFileSync(join(dir, "settings.json"), JSON.stringify({ model: "ocx-claude-native--gpt-5.6-sol" })); injectClaudeAgentDefs(cfg({ subagentModels: ["gpt-5.6-sol"] }), {}, dir); expect(readdirSync(join(dir, "agents")).length).toBe(2); injectClaudeAgentDefs(cfg({ subagentModels: ["gpt-5.6-sol"], claudeCode: { injectAgents: false } }), {}, dir); diff --git a/tests/claude-integration/claude-alias.test.ts b/tests/claude-integration/claude-alias.test.ts index c5c94833257..912cbd01a69 100644 --- a/tests/claude-integration/claude-alias.test.ts +++ b/tests/claude-integration/claude-alias.test.ts @@ -4,7 +4,7 @@ import { aliasForRoute, CLAUDE_ALIAS_PREFIX, CLAUDE_ALIAS_PREFIX_V1, - CLAUDE_ALIAS_PREFIX_V2, + CLAUDE_ALIAS_PREFIX_CURRENT_V2, claudeCodeAlias, claudeCodeNativeAlias, resolveAlias, @@ -26,8 +26,9 @@ describe("claude discovery aliases", () => { for (const [provider, model] of cases) { const alias = aliasForRoute(provider, model); expect(alias).not.toBeNull(); - expect(alias!.startsWith("claude")).toBe(true); // picker prefix rule (003 G3) - expect(alias!.startsWith(CLAUDE_ALIAS_PREFIX_V1)).toBe(true); // plain → v1 + expect(alias!.includes("claude")).toBe(true); // picker accepts claude anywhere + expect(alias!.startsWith("claude-")).toBe(false); // claude- prefix locks unknown models at 200k + expect(alias!.startsWith(CLAUDE_ALIAS_PREFIX)).toBe(true); // plain ids mint the current prefix expect(resolveAlias(alias!)).toBe(`${provider}/${model}`); } }); @@ -40,10 +41,10 @@ describe("claude discovery aliases", () => { test("native slugs with literal '~' mint v2 and round-trip via ~t", () => { const alias = aliasForNative("gpt~special"); - expect(alias).toBe(`${CLAUDE_ALIAS_PREFIX_V2}native--gpt~tspecial`); + expect(alias).toBe(`${CLAUDE_ALIAS_PREFIX_CURRENT_V2}native--gpt~tspecial`); expect(resolveAlias(alias!)).toBe("gpt~special"); - expect(claudeCodeNativeAlias("gpt~special")).toBe(`${CLAUDE_ALIAS_PREFIX_V2}native--gpt~tspecial`); - expect(resolveInboundModel(`${CLAUDE_ALIAS_PREFIX_V2}native--gpt~tspecial`, undefined)).toBe("gpt~special"); + expect(claudeCodeNativeAlias("gpt~special")).toBe(`${CLAUDE_ALIAS_PREFIX_CURRENT_V2}native--gpt~tspecial`); + expect(resolveInboundModel(`${CLAUDE_ALIAS_PREFIX_CURRENT_V2}native--gpt~tspecial`, undefined)).toBe("gpt~special"); }); test("non-representable shapes are skipped, not mangled", () => { @@ -58,10 +59,10 @@ describe("claude discovery aliases", () => { test("model ids with '/' mint v2 (~s) and round-trip (OpenRouter-shaped)", () => { const alias = aliasForRoute("openrouter", "anthropic/claude-opus-4-8"); - expect(alias).toBe(`${CLAUDE_ALIAS_PREFIX_V2}openrouter--anthropic~sclaude-opus-4-8`); + expect(alias).toBe(`${CLAUDE_ALIAS_PREFIX_CURRENT_V2}openrouter--anthropic~sclaude-opus-4-8`); expect(resolveAlias(alias!)).toBe("openrouter/anthropic/claude-opus-4-8"); expect(claudeCodeAlias("openrouter", "meta-llama/llama-3.3-70b-instruct:free")).toBe( - `${CLAUDE_ALIAS_PREFIX_V2}openrouter--meta-llama~sllama-3.3-70b-instruct:free`, + `${CLAUDE_ALIAS_PREFIX_CURRENT_V2}openrouter--meta-llama~sllama-3.3-70b-instruct:free`, ); expect(resolveAlias(claudeCodeAlias("openrouter", "meta-llama/llama-3.3-70b-instruct:free"))).toBe( "openrouter/meta-llama/llama-3.3-70b-instruct:free", @@ -69,8 +70,8 @@ describe("claude discovery aliases", () => { }); test("literal '~' mints v2 (~t); v1 bare '~' and literal ~s/~t still resolve", () => { - expect(aliasForRoute("demo", "old~model")).toBe(`${CLAUDE_ALIAS_PREFIX_V2}demo--old~tmodel`); - expect(resolveAlias(`${CLAUDE_ALIAS_PREFIX_V2}demo--old~tmodel`)).toBe("demo/old~model"); + expect(aliasForRoute("demo", "old~model")).toBe(`${CLAUDE_ALIAS_PREFIX_CURRENT_V2}demo--old~tmodel`); + expect(resolveAlias(`${CLAUDE_ALIAS_PREFIX_CURRENT_V2}demo--old~tmodel`)).toBe("demo/old~model"); // Pre-escape v1 aliases kept literal tildes in the model portion. expect(resolveAlias(`${CLAUDE_ALIAS_PREFIX_V1}demo--old~model`)).toBe("demo/old~model"); // v1 literal ~s / ~t are preserved (the versioned-prefix compatibility fix). @@ -79,12 +80,12 @@ describe("claude discovery aliases", () => { }); test("v2 reserved escapes round-trip / and ~ without colliding with v1 literals", () => { - expect(aliasForRoute("demo", "a/b")).toBe(`${CLAUDE_ALIAS_PREFIX_V2}demo--a~sb`); - expect(resolveAlias(`${CLAUDE_ALIAS_PREFIX_V2}demo--a~sb`)).toBe("demo/a/b"); - expect(aliasForRoute("demo", "a~b")).toBe(`${CLAUDE_ALIAS_PREFIX_V2}demo--a~tb`); - expect(resolveAlias(`${CLAUDE_ALIAS_PREFIX_V2}demo--a~tb`)).toBe("demo/a~b"); - expect(aliasForRoute("demo", "a~/b")).toBe(`${CLAUDE_ALIAS_PREFIX_V2}demo--a~t~sb`); - expect(resolveAlias(`${CLAUDE_ALIAS_PREFIX_V2}demo--a~t~sb`)).toBe("demo/a~/b"); + expect(aliasForRoute("demo", "a/b")).toBe(`${CLAUDE_ALIAS_PREFIX_CURRENT_V2}demo--a~sb`); + expect(resolveAlias(`${CLAUDE_ALIAS_PREFIX_CURRENT_V2}demo--a~sb`)).toBe("demo/a/b"); + expect(aliasForRoute("demo", "a~b")).toBe(`${CLAUDE_ALIAS_PREFIX_CURRENT_V2}demo--a~tb`); + expect(resolveAlias(`${CLAUDE_ALIAS_PREFIX_CURRENT_V2}demo--a~tb`)).toBe("demo/a~b"); + expect(aliasForRoute("demo", "a~/b")).toBe(`${CLAUDE_ALIAS_PREFIX_CURRENT_V2}demo--a~t~sb`); + expect(resolveAlias(`${CLAUDE_ALIAS_PREFIX_CURRENT_V2}demo--a~t~sb`)).toBe("demo/a~/b"); // Same wire bytes under v1 stay literal — no silent remap to slash/tilde. expect(resolveAlias(`${CLAUDE_ALIAS_PREFIX_V1}demo--a~sb`)).toBe("demo/a~sb"); @@ -97,8 +98,8 @@ describe("claude discovery aliases", () => { expect(resolveAlias(`${CLAUDE_ALIAS_PREFIX_V1}noseparator`)).toBeNull(); expect(resolveAlias(`${CLAUDE_ALIAS_PREFIX_V1}p--`)).toBeNull(); expect(resolveAlias(`${CLAUDE_ALIAS_PREFIX_V1}--m`)).toBeNull(); - expect(resolveAlias(`${CLAUDE_ALIAS_PREFIX_V2}noseparator`)).toBeNull(); - expect(resolveAlias(`${CLAUDE_ALIAS_PREFIX_V2}p--`)).toBeNull(); + expect(resolveAlias(`${CLAUDE_ALIAS_PREFIX_CURRENT_V2}noseparator`)).toBeNull(); + expect(resolveAlias(`${CLAUDE_ALIAS_PREFIX_CURRENT_V2}p--`)).toBeNull(); }); test("no collisions across a registry-shaped corpus", () => { @@ -121,14 +122,18 @@ describe("claude discovery aliases", () => { describe("claudeCodeAlias — readable-or-hash shared helper (devlog 050 / audit 051 #2)", () => { test("readable form when representable; both forms decode to the same route", () => { - expect(claudeCodeAlias("gemini", "gemini-3-pro")).toBe("claude-ocx-gemini--gemini-3-pro"); - expect(claudeCodeNativeAlias("gpt-5.6-sol")).toBe("claude-ocx-native--gpt-5.6-sol"); + expect(claudeCodeAlias("gemini", "gemini-3-pro")).toBe("ocx-claude-gemini--gemini-3-pro"); + expect(claudeCodeNativeAlias("gpt-5.6-sol")).toBe("ocx-claude-native--gpt-5.6-sol"); expect(resolveInboundModel(claudeCodeAlias("gemini", "gemini-3-pro"), undefined)).toBe("gemini/gemini-3-pro"); expect(resolveInboundModel(claudeCodeNativeAlias("gpt-5.6-sol"), undefined)).toBe("gpt-5.6-sol"); // Readable id with the [1m] context marker (picker variant row) decodes too — // strip happens before alias resolution, case-insensitively (audit 051 #4). expect(resolveInboundModel("claude-ocx-native--gpt-5.6-sol[1m]", undefined)).toBe("gpt-5.6-sol"); expect(resolveInboundModel("claude-ocx-gemini--gemini-3-pro[1M]", undefined)).toBe("gemini/gemini-3-pro"); + expect(resolveInboundModel("ocx-claude-native--gpt-5.6-sol[1m]", undefined)).toBe("gpt-5.6-sol"); + expect(resolveInboundModel("claude-ocx2-openrouter--anthropic~sclaude-opus-4-8", undefined)).toBe( + "openrouter/anthropic/claude-opus-4-8", + ); }); test("anthropic canonical ids pass through unchanged (native passthrough preserved)", () => { @@ -138,10 +143,10 @@ describe("claudeCodeAlias — readable-or-hash shared helper (devlog 050 / audit test("slash-containing model ids stay readable under v2 (no desktop-3p hash)", () => { expect(claudeCodeAlias("openrouter", "anthropic/claude-opus-4-8")).toBe( - "claude-ocx2-openrouter--anthropic~sclaude-opus-4-8", + "ocx-claude2-openrouter--anthropic~sclaude-opus-4-8", ); - expect(claudeCodeAlias("mock", "path/model")).toBe("claude-ocx2-mock--path~smodel"); - expect(resolveInboundModel("claude-ocx2-openrouter--anthropic~sclaude-opus-4-8", undefined)).toBe( + expect(claudeCodeAlias("mock", "path/model")).toBe("ocx-claude2-mock--path~smodel"); + expect(resolveInboundModel("ocx-claude2-openrouter--anthropic~sclaude-opus-4-8", undefined)).toBe( "openrouter/anthropic/claude-opus-4-8", ); }); @@ -157,6 +162,6 @@ describe("claudeCodeAlias — readable-or-hash shared helper (devlog 050 / audit ]) { expect(id).toMatch(/^claude-opus-4-8-[a-z][0-9a-z]{2}$/); } - expect(claudeCodeAlias("mock", "has~tilde")).toBe("claude-ocx2-mock--has~ttilde"); + expect(claudeCodeAlias("mock", "has~tilde")).toBe("ocx-claude2-mock--has~ttilde"); }); }); diff --git a/tests/claude-integration/claude-cli.test.ts b/tests/claude-integration/claude-cli.test.ts index 6e73cc8ed0f..9965bef55e9 100644 --- a/tests/claude-integration/claude-cli.test.ts +++ b/tests/claude-integration/claude-cli.test.ts @@ -363,16 +363,16 @@ describe("ocx claude env assembly", () => { expect(env.CLAUDE_CODE_AUTO_COMPACT_WINDOW).toBe("829800"); }); - test("opt-in levers: alwaysEnableEffort=1, maxContextTokens injects the official pair", () => { + test("opt-in levers: maxContextTokens sets the window without disabling compact", () => { const env = buildClaudeEnv(cfg({ claudeCode: { alwaysEnableEffort: true, maxContextTokens: 1_000_000 }, }), 10100, {}); expect(env.CLAUDE_CODE_ALWAYS_ENABLE_EFFORT).toBe("1"); expect(env.CLAUDE_CODE_MAX_CONTEXT_TOKENS).toBe("1000000"); - // MAX_CONTEXT_TOKENS alone is ignored for recognized claude-shaped ids; the - // official pair requires DISABLE_COMPACT (exact name, no CLAUDE_CODE_ prefix). - expect(env.DISABLE_COMPACT).toBe("1"); - // Legacy override wins rule-1 inside the CLI -> auto-context stays inert. + // Current ocx-claude ids do not start with claude-, so Claude Code honors + // the window without DISABLE_COMPACT. Do not inject it. + expect(env.DISABLE_COMPACT).toBeUndefined(); + // maxContextTokens still disables the auto-compact window env. expect(env.CLAUDE_CODE_AUTO_COMPACT_WINDOW).toBeUndefined(); }); diff --git a/tests/claude-integration/claude-context-windows.test.ts b/tests/claude-integration/claude-context-windows.test.ts index cc5536303ec..f678ccf7178 100644 --- a/tests/claude-integration/claude-context-windows.test.ts +++ b/tests/claude-integration/claude-context-windows.test.ts @@ -15,7 +15,8 @@ describe("claude context-window map (devlog 260712 B2)", () => { const map = buildClaudeContextWindows([], routed); expect(map["cursor/gpt-5.6-luna"]).toBe(1_000_000); expect(map[desktop3pAlias("cursor", "gpt-5.6-luna")]).toBe(1_000_000); - expect(map["claude-ocx-cursor--gpt-5.6-luna"]).toBe(1_000_000); + expect(map["ocx-claude-cursor--gpt-5.6-luna"]).toBe(1_000_000); + expect(map["claude-ocx-cursor--gpt-5.6-luna"]).toBeUndefined(); expect(map["mock/small-model"]).toBe(128_000); expect(map["mock/no-window"]).toBeUndefined(); }); @@ -27,7 +28,8 @@ describe("claude context-window map (devlog 260712 B2)", () => { // slug passed here does not register. expect(map["gpt-5.6-sol"]).toBe(272_000); expect(map[desktop3pAlias("native", "gpt-5.6-sol")]).toBe(272_000); - expect(map["claude-ocx-native--gpt-5.6-sol"]).toBe(272_000); + expect(map["ocx-claude-native--gpt-5.6-sol"]).toBe(272_000); + expect(map["claude-ocx-native--gpt-5.6-sol"]).toBeUndefined(); expect(map["gpt-5.5"]).toBe(272_000); expect(map["gpt-5.3-codex-spark"]).toBeUndefined(); expect(map["gpt-5.4"]).toBeUndefined(); diff --git a/tests/claude-integration/claude-desktop-discovery.test.ts b/tests/claude-integration/claude-desktop-discovery.test.ts index e9820cdbbe4..655146f5347 100644 --- a/tests/claude-integration/claude-desktop-discovery.test.ts +++ b/tests/claude-integration/claude-desktop-discovery.test.ts @@ -191,7 +191,7 @@ describe("Desktop snapshot through authenticated model discovery", () => { expect(resolveDesktop3pAlias("claude-opus-4-8-20260304")).toBe("test/model-155"); const cli = await request("?flavor=anthropic&ids=cli"); expect(cli.status).toBe(200); - expect((await cli.json() as { data: Array<{ id: string }> }).data.some(model => model.id.startsWith("claude-ocx-test--"))).toBe(true); + expect((await cli.json() as { data: Array<{ id: string }> }).data.some(model => model.id.startsWith("ocx-claude-test--"))).toBe(true); const openai = await request(""); expect(openai.status).toBe(200); const openaiBody = await openai.json() as { object: string; data: unknown[]; version?: number }; diff --git a/tests/claude-integration/claude-management-api.test.ts b/tests/claude-integration/claude-management-api.test.ts index 9f5fd1b590b..be92c1d4b22 100644 --- a/tests/claude-integration/claude-management-api.test.ts +++ b/tests/claude-integration/claude-management-api.test.ts @@ -82,7 +82,7 @@ test("GET /api/claude-code returns defaults + available + aliases", async () => expect(d.modelMap).toEqual({}); expect(d.available).toContain("mock/test-model"); // Aliases preview uses the readable CLI-surface family (devlog 050 / audit 051 #2). - expect(d.aliases.some((a: { id: string }) => a.id === "claude-ocx-mock--test-model")).toBe(true); + expect(d.aliases.some((a: { id: string }) => a.id === "ocx-claude-mock--test-model")).toBe(true); expect(typeof d.port).toBe("number"); } finally { await server.stop(true); diff --git a/tests/claude-integration/claude-messages-endpoint.test.ts b/tests/claude-integration/claude-messages-endpoint.test.ts index c39530d8709..54809b653a0 100644 --- a/tests/claude-integration/claude-messages-endpoint.test.ts +++ b/tests/claude-integration/claude-messages-endpoint.test.ts @@ -1675,7 +1675,7 @@ test("generated agent effort directive restores exact xhigh and max after Claude max_tokens: 32000, stream: true, system: [ - { type: "text", text: "" }, + { type: "text", text: "" }, { type: "text", text: `` }, ], thinking: { type: "enabled", budget_tokens: 31999 }, @@ -1735,7 +1735,7 @@ test("generated agent effort directive preserves routed Anthropic structured out max_tokens: 32000, stream: true, system: [ - { type: "text", text: "" }, + { type: "text", text: "" }, { type: "text", text: "" }, ], thinking: { type: "enabled", budget_tokens: 31999 }, diff --git a/tests/claude-integration/claude-model-info.test.ts b/tests/claude-integration/claude-model-info.test.ts index 7794a730299..6f8fb8cf60f 100644 --- a/tests/claude-integration/claude-model-info.test.ts +++ b/tests/claude-integration/claude-model-info.test.ts @@ -79,7 +79,7 @@ describe("anthropic-flavor ModelInfo discovery entries (devlog 130 B4b)", () => expect(infos.map(info => info.id)).toEqual([ "claude-fable-5-1", - "claude-ocx-native--claude-fable-5-1[1m]", + "ocx-claude-native--claude-fable-5-1[1m]", ]); expect(infos[1]!.display_name).toBe("claude-fable-5-1 (anthropic) · 1M"); expect(infos[1]!.max_input_tokens).toBe(1_000_000); @@ -191,7 +191,7 @@ describe("anthropic-flavor ModelInfo discovery entries (devlog 130 B4b)", () => expect(variants[0]!.display_name.includes("claude-big-5")).toBe(true); }); - test("readable id style serves claude-ocx ids with hash fallback + readable [1m] variants (devlog 050)", () => { + test("readable id style serves ocx-claude ids with hash fallback + readable [1m] variants (devlog 050)", () => { const auto = { enabled: true, compactWindow: 350_000 }; const infos = buildAnthropicModelInfos(["gpt-5.5"], [ { provider: "cursor", id: "gpt-5.6-luna", contextWindow: 1_000_000 }, @@ -199,16 +199,16 @@ describe("anthropic-flavor ModelInfo discovery entries (devlog 130 B4b)", () => { provider: "weird--provider", id: "m1", contextWindow: 128_000 }, // unrepresentable -> hash fallback ], auto, "readable"); const ids = infos.map(i => i.id); - expect(ids).toContain("claude-ocx-native--gpt-5.5"); + expect(ids).toContain("ocx-claude-native--gpt-5.5"); // 272k native: NO [1m] variant under the authoritative-window contract. - expect(ids).not.toContain("claude-ocx-native--gpt-5.5[1m]"); - expect(ids).toContain("claude-ocx-cursor--gpt-5.6-luna"); - expect(ids).toContain("claude-ocx-cursor--gpt-5.6-luna[1m]"); + expect(ids).not.toContain("ocx-claude-native--gpt-5.5[1m]"); + expect(ids).toContain("ocx-claude-cursor--gpt-5.6-luna"); + expect(ids).toContain("ocx-claude-cursor--gpt-5.6-luna[1m]"); expect(ids).toContain("claude-opus-4-8"); // anthropic canonical passthrough expect(ids.some(id => /^claude-opus-4-8-[a-z][0-9a-z]{2}$/.test(id))).toBe(true); // fallback row survives // Default style stays hashed (desktop contract untouched). const hashed = buildAnthropicModelInfos(["gpt-5.6-sol"], [], auto); - expect(hashed.map(i => i.id).some(id => id.startsWith("claude-ocx-"))).toBe(false); + expect(hashed.map(i => i.id).some(id => id.startsWith("ocx-claude-"))).toBe(false); }); }); diff --git a/tests/claude-integration/claude-models-discovery.test.ts b/tests/claude-integration/claude-models-discovery.test.ts index 4134277120d..ec6c6e147bd 100644 --- a/tests/claude-integration/claude-models-discovery.test.ts +++ b/tests/claude-integration/claude-models-discovery.test.ts @@ -98,7 +98,7 @@ test("anthropic-version header flips /v1/models to the discovery contract", asyn expect(ids).toContain(mockAlias); // Every entry must satisfy the picker prefix rule (003 G3). for (const entry of json.data) { - expect(entry.id.startsWith("claude") || entry.id.startsWith("anthropic")).toBe(true); + expect(entry.id.includes("claude") || entry.id.includes("anthropic")).toBe(true); expect(typeof entry.display_name).toBe("string"); // Full ModelInfo contract (devlog 130 B4b): capabilities ride discovery. expect(entry.type).toBe("model"); @@ -141,7 +141,7 @@ test("per-surface id style: ?ids= wins, claude-code UA gets readable, unknown UA saveConfig(configWithStaticModels()); const server = await startDiscoveryServer(); try { - const readable = "claude-ocx-mock--test-model"; + const readable = "ocx-claude-mock--test-model"; // 1) explicit ?ids=cli -> readable let json = await fetch(new URL("/v1/models?flavor=anthropic&ids=cli", server.url)).then(r => r.json()) as { data: { id: string }[] }; expect(json.data.some(m => m.id === readable)).toBe(true); diff --git a/tests/claude-integration/claude-native-passthrough.test.ts b/tests/claude-integration/claude-native-passthrough.test.ts index 44cf9333f46..e0cb2a16fec 100644 --- a/tests/claude-integration/claude-native-passthrough.test.ts +++ b/tests/claude-integration/claude-native-passthrough.test.ts @@ -213,7 +213,7 @@ test("Fable 1M picker alias preserves native passthrough on both Messages endpoi const upstream = mockAnthropicUpstream(captured); saveConfig(cfg(upstream.url.toString().replace(/\/$/, ""))); const server = startServer(0); - const pickerModel = "claude-ocx-native--claude-fable-5-1"; + const pickerModel = "ocx-claude-native--claude-fable-5-1"; try { const messagesWithoutMarker = await fetch(new URL("/v1/messages", server.url), { method: "POST", @@ -409,7 +409,7 @@ test("alias/mapped models and non-anthropic credentials do NOT pass through", as const alias = await fetch(new URL("/v1/messages", server.url), { method: "POST", headers: OAUTH_HEADERS, - body: JSON.stringify({ model: "claude-ocx-mock--test-model", max_tokens: 10, messages: [{ role: "user", content: "x" }] }), + body: JSON.stringify({ model: "ocx-claude-mock--test-model", max_tokens: 10, messages: [{ role: "user", content: "x" }] }), }); expect(alias.status).not.toBe(200); diff --git a/tests/providers/cursor/cursor-fast-listing.test.ts b/tests/providers/cursor/cursor-fast-listing.test.ts index a53683a67f9..077c98f2c6d 100644 --- a/tests/providers/cursor/cursor-fast-listing.test.ts +++ b/tests/providers/cursor/cursor-fast-listing.test.ts @@ -49,22 +49,22 @@ describe("global fast switch lists -fast identities outside Codex", () => { test("Claude Code discovery lists the umbrella id with the switch off", () => { expect(listIds([cursorModel("claude-opus-5")], false)) - .toContain("claude-ocx-cursor--claude-opus-5"); + .toContain("ocx-claude-cursor--claude-opus-5"); expect(listIds([cursorModel("claude-opus-5")], undefined)) - .toContain("claude-ocx-cursor--claude-opus-5"); + .toContain("ocx-claude-cursor--claude-opus-5"); }); test("Claude Code discovery lists the fast identity with the switch on", () => { expect(listIds([cursorModel("claude-opus-5")], true)) - .toContain("claude-ocx-cursor--claude-opus-5-thinking-fast"); + .toContain("ocx-claude-cursor--claude-opus-5-thinking-fast"); expect(listIds([cursorModel("grok-4.6", 500_000)], true)) - .toContain("claude-ocx-cursor--grok-4.6-fast"); + .toContain("ocx-claude-cursor--grok-4.6-fast"); expect(listIds([cursorModel("grok-4.7", 500_000)], true)) - .toContain("claude-ocx-cursor--grok-4.7-fast"); + .toContain("ocx-claude-cursor--grok-4.7-fast"); }); test("the switch leaves a base without a fast variant alone", () => { - expect(listIds([cursorModel("kimi-k3")], true)).toContain("claude-ocx-cursor--kimi-k3"); + expect(listIds([cursorModel("kimi-k3")], true)).toContain("ocx-claude-cursor--kimi-k3"); }); test("Desktop 3P hashed aliases are untouched by the switch", () => { diff --git a/tests/server/system-env.test.ts b/tests/server/system-env.test.ts index 6a3d61cb7aa..e99ee016ecd 100644 --- a/tests/server/system-env.test.ts +++ b/tests/server/system-env.test.ts @@ -553,16 +553,16 @@ describe("systemEnv lever keys (devlog 136 B6)", () => { expect(await injectSystemEnv(4096, leverConfig)).toEqual({ injected: true }); const setCalls = launchctlCommands(); expect(setCalls).toContain("launchctl setenv CLAUDE_CODE_MAX_CONTEXT_TOKENS 1000000"); - expect(setCalls).toContain("launchctl setenv DISABLE_COMPACT 1"); + expect(setCalls.some(c => c.includes("DISABLE_COMPACT"))).toBe(false); expect(setCalls).toContain("launchctl setenv CLAUDE_CODE_ALWAYS_ENABLE_EFFORT 1"); const trackingWrite = writes.filter(w => w.path.includes("system-env-port")).at(-1); expect(JSON.parse(trackingWrite!.data).injectedKeys).toEqual(expect.arrayContaining([ - "CLAUDE_CODE_MAX_CONTEXT_TOKENS", "DISABLE_COMPACT", "CLAUDE_CODE_ALWAYS_ENABLE_EFFORT", + "CLAUDE_CODE_MAX_CONTEXT_TOKENS", "CLAUDE_CODE_ALWAYS_ENABLE_EFFORT", ])); // Shell env file: lever keys are CONDITIONAL exports so a shell-only user value wins. const shellWrite = writes.find(w => w.path.includes("claude-env.sh")); expect(shellWrite!.data).toContain(`[ -z "\${CLAUDE_CODE_MAX_CONTEXT_TOKENS+x}" ] && export CLAUDE_CODE_MAX_CONTEXT_TOKENS='1000000'`); - expect(shellWrite!.data).toContain(`[ -z "\${DISABLE_COMPACT+x}" ] && export DISABLE_COMPACT='1'`); + expect(shellWrite!.data).not.toContain("DISABLE_COMPACT"); expect(shellWrite!.data).toContain(`[ -z "\${CLAUDE_CODE_ALWAYS_ENABLE_EFFORT+x}" ] && export CLAUDE_CODE_ALWAYS_ENABLE_EFFORT='1'`); }); @@ -572,11 +572,11 @@ describe("systemEnv lever keys (devlog 136 B6)", () => { expect(await injectSystemEnv(4096, leverConfig)).toEqual({ injected: true }); const setCalls = launchctlCommands(); expect(setCalls).not.toContain("launchctl setenv CLAUDE_CODE_MAX_CONTEXT_TOKENS 1000000"); - expect(setCalls).toContain("launchctl setenv DISABLE_COMPACT 1"); + expect(setCalls.some(c => c.includes("DISABLE_COMPACT"))).toBe(false); const trackingWrite = writes.filter(w => w.path.includes("system-env-port")).at(-1); const keys = JSON.parse(trackingWrite!.data).injectedKeys as string[]; expect(keys).not.toContain("CLAUDE_CODE_MAX_CONTEXT_TOKENS"); - expect(keys).toContain("DISABLE_COMPACT"); + expect(keys).not.toContain("DISABLE_COMPACT"); }); test("levers disabled: no lever keys injected or exported", async () => {