From 65df7bca985fd493b81cb653faebf0226c4e99e3 Mon Sep 17 00:00:00 2001 From: sh940701 Date: Tue, 22 Sep 2026 04:37:30 +0900 Subject: [PATCH 1/8] fix(claude): keep routed model windows without disabling compact Mint Claude Code aliases as ocx-claude- so the picker still accepts them, while an unrecognized id no longer starts with claude- and gets locked to the 200k default. Stop pairing maxContextTokens with DISABLE_COMPACT. Legacy claude-ocx aliases keep decoding. --- src/claude/alias.ts | 66 +++++++++++-------- src/claude/gateway-cache.ts | 6 +- src/cli/claude.ts | 7 +- .../claude-agents-inject.test.ts | 52 +++++++-------- tests/claude-integration/claude-alias.test.ts | 53 ++++++++------- tests/claude-integration/claude-cli.test.ts | 10 +-- .../claude-context-windows.test.ts | 6 +- .../claude-desktop-discovery.test.ts | 2 +- .../claude-messages-endpoint.test.ts | 4 +- .../claude-model-info.test.ts | 14 ++-- .../claude-models-discovery.test.ts | 4 +- .../claude-native-passthrough.test.ts | 4 +- 12 files changed, 126 insertions(+), 102 deletions(-) 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/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-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); From 65b7225fec2ba86515f9a8981330d0f40f3be65c Mon Sep 17 00:00:00 2001 From: sh940701 Date: Tue, 22 Sep 2026 04:54:46 +0900 Subject: [PATCH 2/8] fix(claude): stop system env from disabling compact Dock, launchd, and shell launches injected DISABLE_COMPACT whenever maxContextTokens was set, so those sessions still lost compact. Inject the window only, and document the ocx-claude alias plus the one-time picker switch for saved claude-ocx ids. --- docs-site/src/content/docs/fr/guides/claude-code.md | 12 ++++++------ docs-site/src/content/docs/guides/claude-code.md | 12 ++++++------ docs-site/src/content/docs/ja/guides/claude-code.md | 10 +++++----- docs-site/src/content/docs/ko/guides/claude-code.md | 12 ++++++------ .../docs/reference/configuration/providers.md | 2 +- docs-site/src/content/docs/ru/guides/claude-code.md | 10 +++++----- docs-site/src/content/docs/tr/guides/claude-code.md | 12 ++++++------ .../src/content/docs/zh-cn/guides/claude-code.md | 12 ++++++------ .../src/content/docs/zh-tw/guides/claude-code.md | 6 +++--- src/server/system-env-shell.ts | 1 - src/server/system-env.ts | 1 - src/types/config.ts | 12 ++++++------ tests/server/system-env.test.ts | 10 +++++----- 13 files changed, 55 insertions(+), 57 deletions(-) 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..1bbae8f7b17 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 @@ -305,7 +305,7 @@ par `claude` ou `anthropic`, opencodex expose les modèles routés sous forme d' | 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 @@ -315,7 +315,7 @@ Chaque entrée porte un nom d'affichage explicite, comme `gemini-3-pro (gemini)` (échelle d'effort de raisonnement et types de réflexion) dans la structure officielle ModelInfo. Le mode passerelle tierce de Claude Desktop peut ainsi proposer son sélecteur d'effort. Les véritables modèles Anthropic conservent leurs identifiants canoniques. La date synthétique 2026 désigne un emplacement interne, et non une date de publication. Les -anciens alias hachés et les identifiants `claude-ocx---` des configurations antérieures sont +anciens alias hachés et les identifiants `ocx-claude---` des configurations antérieures sont toujours résolus. Si le sélecteur situé au bas de Claude Desktop ne modifie pas le modèle d'une conversation 3P déjà en cours, @@ -338,9 +338,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..b074943b0d7 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 @@ -380,7 +380,7 @@ with `claude` or `anthropic`, opencodex exposes routed models as stable, reversi | 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 @@ -390,7 +390,7 @@ Each entry carries an honest display name such as `gemini-3-pro (gemini)`, plus 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. +and `ocx-claude---` ids from older configs still resolve. A saved `claude-ocx-` id still routes, but Claude Code keeps its 200k accounting for that id. Pick the new `ocx-claude-` id once 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 +413,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..dacb0696b02 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 ルーティングが無効なときのネイティブフォールバック @@ -179,7 +179,7 @@ Claude Code 2.1.129 以降は `GET /v1/models?limit=1000` でゲートウェイ | 画面 | 形式 | 例 | | --- | --- | --- | -| 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 +201,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..d87cc1af9a8 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인 모델에는 +해시 별칭과 `ocx-claude---` 별칭도 계속 해석됩니다. 컨텍스트가 1M인 모델에는 `…[1m]` 행이 하나 더 생깁니다 — 이걸 고르면 Claude Code가 그 모델의 컨텍스트를 1M로 계산합니다 (자동 요약 유지, 프록시가 표식을 떼고 라우팅). 선택하면 Claude Code의 `settings.json` `model` 필드에 저장되고, 인바운드 요청에서 @@ -213,7 +213,7 @@ Claude Code 2.1.129 이상은 `GET /v1/models?limit=1000`에서 게이트웨이 | 화면 | 형식 | 예시 | | --- | --- | --- | -| 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 +234,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..c1dc4cff430 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 выключена @@ -168,7 +168,7 @@ Claude Code 2.1.129+ обнаруживает модели шлюза через | Интерфейс | Формат | Пример | | --- | --- | --- | -| 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 +189,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..15f8964c231 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"`. @@ -319,7 +319,7 @@ kimlikleri kabul ettiğinden, opencodex yönlendirilen modelleri kararlı, tersi | 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 @@ -331,7 +331,7 @@ Desktop'ın üçüncü taraf ağ geçidi modunun çaba seçicisini sunabilmesi i ModelInfo biçiminde tam model yeteneklerini (akıl yürütme çabası merdiveni, düşünme türleri) taşır. Gerçek Anthropic modelleri kurallı kimliklerini korur. Sentetik 2026 tarihi bir çıkış tarihi değil, dahili bir yuvadır. Eski karma -takma adlar ve eski yapılandırmalardan gelen `claude-ocx---` +takma adlar ve eski yapılandırmalardan gelen `ocx-claude---` kimlikleri hala çözümlenir. Claude Desktop'ın altbilgi seçicisi zaten çalışan bir 3P görüşmesi için modeli @@ -361,10 +361,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..aa985254180 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 路由关闭时的原生回退 @@ -139,7 +139,7 @@ apply、轮换/恢复或直接 disconnect 均可处理,无需新参数或事 每个条目带有诚实的显示名(如 `gemini-3-pro (gemini)`),并以官方 ModelInfo 形态附带模型能力 信息(推理强度梯度、thinking 类型),使 Claude Desktop 的第三方网关模式能够启用推理强度选择 UI。真实 Anthropic 模型保留其原始 id。合成的 2026 日期是内部槽位,不是发布日期。旧版哈希 -别名和 `claude-ocx---` 别名仍可解析。拥有 1M 上下文的模型会多出一行 `…[1m]`: +别名和 `ocx-claude---` 别名仍可解析。拥有 1M 上下文的模型会多出一行 `…[1m]`: 选中后 Claude Code 会按 1M 计算该模型的上下文(自动压缩保留,代理在路由前去掉该标记)。 选中后会保存到 Claude Code 的 `settings.json` `model` 字段;入站请求会将别名解析回路由 模型。旧版 Claude Code 中选择器保持原生 — 通过 `ANTHROPIC_MODEL` 设置槽位,或直接在 `/model` @@ -151,7 +151,7 @@ opencodex 会将已路由模型公开为稳定且可逆的别名: | 界面 | 格式 | 示例 | | --- | --- | --- | -| 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 +169,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..9277db46310 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 路由關閉時的原生回退 @@ -227,7 +227,7 @@ opencodex 會將已路由模型公開為穩定且可逆的別名: | 介面 | 格式 | 示例 | | --- | --- | --- | -| Claude Code CLI | `claude-ocx---` | `claude-ocx-native--gpt-5.6-sol` | +| Claude Code CLI | `ocx-claude---` | `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/*` @@ -236,7 +236,7 @@ user-agent 會獲得易讀的 CLI 形式,其他用戶端會獲得 Desktop 雜 每個條目帶有誠實的顯示名(如 `gemini-3-pro (gemini)`),並以官方 ModelInfo 形態附帶完整模型 能力(推理強度階梯、thinking 型別),使 Claude Desktop 的第三方閘道器模式能夠提供其推理強度 選擇器。真實 Anthropic 模型保留其規範 id。合成的 2026 日期是內部槽位,不是釋出日期。舊版雜湊 -別名與較舊設定中的 `claude-ocx---` id 仍可解析。 +別名與較舊設定中的 `ocx-claude---` id 仍可解析。 擁有權威 1M 上下文視窗的模型會多出一個 `…[1m]` 選擇器列:選中後 Claude Code 會按完整 1M 上下文 計算該模型(自動壓縮仍開啟)——代理在路由前會去掉該標記。 選中後會儲存到 Claude Code 的 `settings.json` `model` 欄位;入站請求會將別名解析回路由 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/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 () => { From 2ef32368e261c596f9d2d24c3abdad20a5e48e95 Mon Sep 17 00:00:00 2001 From: sh940701 Date: Tue, 22 Sep 2026 05:04:36 +0900 Subject: [PATCH 3/8] docs(claude): correct picker rule and legacy alias names The guides still said the picker only accepts ids that begin with claude or anthropic, which excludes the new ocx-claude ids. State the 2.1.278 rule, and keep claude-ocx as the name of the legacy prefix. --- docs-site/src/content/docs/fr/guides/claude-code.md | 5 ++--- docs-site/src/content/docs/guides/claude-code.md | 3 +-- docs-site/src/content/docs/ja/guides/claude-code.md | 3 +-- docs-site/src/content/docs/ko/guides/claude-code.md | 4 ++-- docs-site/src/content/docs/ru/guides/claude-code.md | 3 +-- docs-site/src/content/docs/tr/guides/claude-code.md | 5 ++--- docs-site/src/content/docs/zh-cn/guides/claude-code.md | 5 ++--- docs-site/src/content/docs/zh-tw/guides/claude-code.md | 5 ++--- 8 files changed, 13 insertions(+), 20 deletions(-) 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 1bbae8f7b17..54ec1ed64d3 100644 --- a/docs-site/src/content/docs/fr/guides/claude-code.md +++ b/docs-site/src/content/docs/fr/guides/claude-code.md @@ -300,8 +300,7 @@ 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 | | --- | --- | --- | @@ -315,7 +314,7 @@ Chaque entrée porte un nom d'affichage explicite, comme `gemini-3-pro (gemini)` (échelle d'effort de raisonnement et types de réflexion) dans la structure officielle ModelInfo. Le mode passerelle tierce de Claude Desktop peut ainsi proposer son sélecteur d'effort. Les véritables modèles Anthropic conservent leurs identifiants canoniques. La date synthétique 2026 désigne un emplacement interne, et non une date de publication. Les -anciens alias hachés et les identifiants `ocx-claude---` des configurations antérieures sont +anciens alias hachés et les identifiants `claude-ocx---` des configurations antérieures sont toujours résolus. Si le sélecteur situé au bas de Claude Desktop ne modifie pas le modèle d'une conversation 3P déjà en cours, diff --git a/docs-site/src/content/docs/guides/claude-code.md b/docs-site/src/content/docs/guides/claude-code.md index b074943b0d7..e476ebe142d 100644 --- a/docs-site/src/content/docs/guides/claude-code.md +++ b/docs-site/src/content/docs/guides/claude-code.md @@ -375,8 +375,7 @@ 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 | | --- | --- | --- | 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 dacb0696b02..2558f81bb7a 100644 --- a/docs-site/src/content/docs/ja/guides/claude-code.md +++ b/docs-site/src/content/docs/ja/guides/claude-code.md @@ -174,8 +174,7 @@ 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-` で始まらない安定した可逆エイリアスとして公開します。 | 画面 | 形式 | 例 | | --- | --- | --- | 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 d87cc1af9a8..e8a20e1dd74 100644 --- a/docs-site/src/content/docs/ko/guides/claude-code.md +++ b/docs-site/src/content/docs/ko/guides/claude-code.md @@ -200,7 +200,7 @@ import/export는 로컬 설정만 다뤄요. 허브 프로필을 바꾸지 않 능력 정보(추론 강도 사다리, thinking 타입)를 실어 보냅니다 — Claude Desktop의 서드파티 게이트웨이 모드가 추론 강도 선택 UI를 열 수 있게 하기 위해서입니다. 실제 Anthropic 모델은 원래 id를 그대로 유지합니다. 합성된 2026 날짜는 내부 슬롯이며 출시일이 아닙니다. 구버전의 -해시 별칭과 `ocx-claude---` 별칭도 계속 해석됩니다. 컨텍스트가 1M인 모델에는 +해시 별칭과 `claude-ocx---` 별칭도 계속 해석됩니다. 컨텍스트가 1M인 모델에는 `…[1m]` 행이 하나 더 생깁니다 — 이걸 고르면 Claude Code가 그 모델의 컨텍스트를 1M로 계산합니다 (자동 요약 유지, 프록시가 표식을 떼고 라우팅). 선택하면 Claude Code의 `settings.json` `model` 필드에 저장되고, 인바운드 요청에서 @@ -208,7 +208,7 @@ import/export는 로컬 설정만 다뤄요. 허브 프로필을 바꾸지 않 지정하거나 `/model`에 라우팅 id를 직접 입력하세요 (Claude Code는 문자열을 그대로 통과시킵니다). Claude Code 2.1.129 이상은 `GET /v1/models?limit=1000`에서 게이트웨이 모델을 찾아 기본 `/model` -선택기의 "From gateway" 항목에 표시해요. 선택기는 `claude` 또는 `anthropic`으로 시작하는 ID만 +선택기의 "From gateway" 항목에 표시해요. Claude Code 2.1.278 선택기는 `claude` 또는 `anthropic`을 포함한 ID를 받습니다. `claude-`로 시작하는 모르는 ID는 compact를 끄지 않으면 200k로 계산되므로, 라우팅 모델은 `claude`를 포함하되 `claude-`로 시작하지 않는 안정적인 가역 별칭으로 노출합니다. 선택기는 받으므로, opencodex는 라우팅 모델을 안정적이고 되돌릴 수 있는 별칭으로 노출해요. | 화면 | 형식 | 예시 | 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 c1dc4cff430..aee871d97df 100644 --- a/docs-site/src/content/docs/ru/guides/claude-code.md +++ b/docs-site/src/content/docs/ru/guides/claude-code.md @@ -163,8 +163,7 @@ 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-`: | Интерфейс | Формат | Пример | | --- | --- | --- | 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 15f8964c231..837fbe2c858 100644 --- a/docs-site/src/content/docs/tr/guides/claude-code.md +++ b/docs-site/src/content/docs/tr/guides/claude-code.md @@ -313,8 +313,7 @@ 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 | @@ -331,7 +330,7 @@ Desktop'ın üçüncü taraf ağ geçidi modunun çaba seçicisini sunabilmesi i ModelInfo biçiminde tam model yeteneklerini (akıl yürütme çabası merdiveni, düşünme türleri) taşır. Gerçek Anthropic modelleri kurallı kimliklerini korur. Sentetik 2026 tarihi bir çıkış tarihi değil, dahili bir yuvadır. Eski karma -takma adlar ve eski yapılandırmalardan gelen `ocx-claude---` +takma adlar ve eski yapılandırmalardan gelen `claude-ocx---` kimlikleri hala çözümlenir. Claude Desktop'ın altbilgi seçicisi zaten çalışan bir 3P görüşmesi için modeli 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 aa985254180..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 @@ -139,15 +139,14 @@ apply、轮换/恢复或直接 disconnect 均可处理,无需新参数或事 每个条目带有诚实的显示名(如 `gemini-3-pro (gemini)`),并以官方 ModelInfo 形态附带模型能力 信息(推理强度梯度、thinking 类型),使 Claude Desktop 的第三方网关模式能够启用推理强度选择 UI。真实 Anthropic 模型保留其原始 id。合成的 2026 日期是内部槽位,不是发布日期。旧版哈希 -别名和 `ocx-claude---` 别名仍可解析。拥有 1M 上下文的模型会多出一行 `…[1m]`: +别名和 `claude-ocx---` 别名仍可解析。拥有 1M 上下文的模型会多出一行 `…[1m]`: 选中后 Claude Code 会按 1M 计算该模型的上下文(自动压缩保留,代理在路由前去掉该标记)。 选中后会保存到 Claude Code 的 `settings.json` `model` 字段;入站请求会将别名解析回路由 模型。旧版 Claude Code 中选择器保持原生 — 通过 `ANTHROPIC_MODEL` 设置槽位,或直接在 `/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 计算,因此 opencodex 会将已路由模型公开为包含 `claude`、但不以 `claude-` 开头的稳定且可逆别名: | 界面 | 格式 | 示例 | | --- | --- | --- | 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 9277db46310..48737501550 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 @@ -222,8 +222,7 @@ 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-` 開頭的穩定且可逆別名: | 介面 | 格式 | 示例 | | --- | --- | --- | @@ -236,7 +235,7 @@ user-agent 會獲得易讀的 CLI 形式,其他用戶端會獲得 Desktop 雜 每個條目帶有誠實的顯示名(如 `gemini-3-pro (gemini)`),並以官方 ModelInfo 形態附帶完整模型 能力(推理強度階梯、thinking 型別),使 Claude Desktop 的第三方閘道器模式能夠提供其推理強度 選擇器。真實 Anthropic 模型保留其規範 id。合成的 2026 日期是內部槽位,不是釋出日期。舊版雜湊 -別名與較舊設定中的 `ocx-claude---` id 仍可解析。 +別名與較舊設定中的 `claude-ocx---` id 仍可解析。 擁有權威 1M 上下文視窗的模型會多出一個 `…[1m]` 選擇器列:選中後 Claude Code 會按完整 1M 上下文 計算該模型(自動壓縮仍開啟)——代理在路由前會去掉該標記。 選中後會儲存到 Claude Code 的 `settings.json` `model` 欄位;入站請求會將別名解析回路由 From a39cf57f02deae5e92ccd0923ace14a087b7c9d7 Mon Sep 17 00:00:00 2001 From: sh940701 Date: Tue, 22 Sep 2026 05:06:54 +0900 Subject: [PATCH 4/8] docs(claude): document the escaped alias in zh-tw The Traditional Chinese guide listed only ocx-claude and said model ids cannot contain a slash. Slash and tilde ids use ocx-claude2 with the existing escapes. --- docs-site/src/content/docs/zh-tw/guides/claude-code.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 48737501550..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 @@ -226,7 +226,7 @@ Claude Code 2.1.129+ 透過 `GET /v1/models?limit=1000` 發現閘道器模型, | 介面 | 格式 | 示例 | | --- | --- | --- | -| Claude Code CLI | `ocx-claude---` | `ocx-claude-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/*` @@ -242,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 雜湊別名 → From f9a67aed325a2eb1998d6b3e1c0269756e88f52a Mon Sep 17 00:00:00 2001 From: sh940701 Date: Tue, 22 Sep 2026 05:15:47 +0900 Subject: [PATCH 5/8] docs(claude): drop the duplicated Korean picker sentence The selector paragraph already states the 2.1.278 rule. The leftover fragment repeated the alias sentence and did not parse. --- docs-site/src/content/docs/ko/guides/claude-code.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 e8a20e1dd74..3b9c5531640 100644 --- a/docs-site/src/content/docs/ko/guides/claude-code.md +++ b/docs-site/src/content/docs/ko/guides/claude-code.md @@ -208,8 +208,7 @@ import/export는 로컬 설정만 다뤄요. 허브 프로필을 바꾸지 않 지정하거나 `/model`에 라우팅 id를 직접 입력하세요 (Claude Code는 문자열을 그대로 통과시킵니다). Claude Code 2.1.129 이상은 `GET /v1/models?limit=1000`에서 게이트웨이 모델을 찾아 기본 `/model` -선택기의 "From gateway" 항목에 표시해요. Claude Code 2.1.278 선택기는 `claude` 또는 `anthropic`을 포함한 ID를 받습니다. `claude-`로 시작하는 모르는 ID는 compact를 끄지 않으면 200k로 계산되므로, 라우팅 모델은 `claude`를 포함하되 `claude-`로 시작하지 않는 안정적인 가역 별칭으로 노출합니다. 선택기는 -받으므로, opencodex는 라우팅 모델을 안정적이고 되돌릴 수 있는 별칭으로 노출해요. +선택기의 "From gateway" 항목에 표시해요. Claude Code 2.1.278 선택기는 `claude` 또는 `anthropic`을 포함한 ID를 받습니다. `claude-`로 시작하는 모르는 ID는 compact를 끄지 않으면 200k로 계산되므로, 라우팅 모델은 `claude`를 포함하되 `claude-`로 시작하지 않는 안정적인 가역 별칭으로 노출합니다. | 화면 | 형식 | 예시 | | --- | --- | --- | From 7fea4d0ccdb3243e4246a9fc9a93b03ed299ba52 Mon Sep 17 00:00:00 2001 From: sh940701 Date: Tue, 22 Sep 2026 05:17:40 +0900 Subject: [PATCH 6/8] docs(claude): name the legacy alias prefix correctly The English guide called ocx-claude an old-config alias. That is the current prefix. Older configs use claude-ocx and claude-ocx2. --- docs-site/src/content/docs/guides/claude-code.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs-site/src/content/docs/guides/claude-code.md b/docs-site/src/content/docs/guides/claude-code.md index e476ebe142d..3e21368ccec 100644 --- a/docs-site/src/content/docs/guides/claude-code.md +++ b/docs-site/src/content/docs/guides/claude-code.md @@ -388,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 `ocx-claude---` ids from older configs still resolve. A saved `claude-ocx-` id still routes, but Claude Code keeps its 200k accounting for that id. Pick the new `ocx-claude-` id once so the real context window and compact both apply. +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 `claude-ocx-` id still routes, but Claude Code keeps its 200k accounting for that id. Pick the new `ocx-claude-` id once 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 From 8921a1c2196b275d0152894ea0fe2c2086ae1d17 Mon Sep 17 00:00:00 2001 From: sh940701 Date: Tue, 22 Sep 2026 05:25:41 +0900 Subject: [PATCH 7/8] docs(claude): map both legacy alias prefixes Saved claude-ocx ids move to ocx-claude. Saved escaped claude-ocx2 ids move to ocx-claude2. The English and Korean guides now say both. --- docs-site/src/content/docs/guides/claude-code.md | 2 +- docs-site/src/content/docs/ko/guides/claude-code.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs-site/src/content/docs/guides/claude-code.md b/docs-site/src/content/docs/guides/claude-code.md index 3e21368ccec..2d89dfcfcc2 100644 --- a/docs-site/src/content/docs/guides/claude-code.md +++ b/docs-site/src/content/docs/guides/claude-code.md @@ -388,7 +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---` / `claude-ocx2---` ids from older configs still resolve. A saved `claude-ocx-` id still routes, but Claude Code keeps its 200k accounting for that id. Pick the new `ocx-claude-` id once so the real context window and compact both apply. +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 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 3b9c5531640..de6183b3d52 100644 --- a/docs-site/src/content/docs/ko/guides/claude-code.md +++ b/docs-site/src/content/docs/ko/guides/claude-code.md @@ -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` 필드에 저장되고, 인바운드 요청에서 From 6207b9473490bba44a17a80c91c184986117a85d Mon Sep 17 00:00:00 2001 From: sh940701 Date: Wed, 23 Sep 2026 13:00:30 +0900 Subject: [PATCH 8/8] test(claude): expect the ocx-claude- family wherever discovery mints aliases --- .../claude-integration/claude-management-api.test.ts | 2 +- tests/providers/cursor/cursor-fast-listing.test.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) 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/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", () => {