Skip to content

Honor Claude Code's provider model mapping for Claude sessions - #308

Open
dcdyouget wants to merge 1 commit into
zeronsh:mainfrom
dcdyouget:zeron/claude-provider-models
Open

Honor Claude Code's provider model mapping for Claude sessions#308
dcdyouget wants to merge 1 commit into
zeronsh:mainfrom
dcdyouget:zeron/claude-provider-models

Conversation

@dcdyouget

@dcdyouget dcdyouget commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Summary

cc-switch (and Anthropic-compatible gateways) switch Claude Code's provider by rewriting the env block of ~/.claude/settings.json:

{
  "env": {
    "ANTHROPIC_BASE_URL": "https://api.deepseek.com/anthropic",
    "ANTHROPIC_MODEL": "deepseek-v4-flash",
    "ANTHROPIC_DEFAULT_OPUS_MODEL": "deepseek-v4-flash[1M]",
    "ANTHROPIC_DEFAULT_SONNET_MODEL": "deepseek-v4-flash[1M]",
    "ANTHROPIC_DEFAULT_HAIKU_MODEL": "deepseek-v4-flash"
  }
}

Claude Code applies the ANTHROPIC_DEFAULT_*_MODEL mapping only to family aliases (fable, opus, sonnet, haiku). A full model id passed via --model (claude-opus-5) is sent to the provider verbatim — verified against a local mock Anthropic endpoint. Zeron's curated catalog is exactly those full ids, so the mapping was bypassed: the model picker kept showing Claude names, requests carried the Claude model name, and providers that validate strictly failed with model_not_found (see #305).

Refs #305.

Changes

  • New crates/harness/src/claude/settings.rs — reads the user-level Claude settings env ($CLAUDE_CONFIG_DIR or ~/.claude) and exposes the per-family provider mapping plus resolve_request_model().
  • build_command — resolves the selected id through the family mapping before passing --model. When no mapping is configured it keeps the previous behavior (including the [1m] context suffix), so native Anthropic users are unaffected.
  • models() — when a mapping is configured, surfaces the configured provider models instead of the Claude ids they are mapped from.
  • Title runs--setting-sources "" dropped the user settings file entirely, so a third-party provider's env (base URL / token / model) was lost and titles failed with "Not logged in". user keeps that env while still skipping project/local settings.

Design notes

  • The picker row id is the provider model value (deepseek-v4-flash[1M]); Claude Code strips the trailing [...] context suffix, so the request and the displayed model stay identical.
  • Provider rows are deduped by display name (ANTHROPIC_DEFAULT_*_MODEL_NAME): cc-switch points all four families at one model, which would otherwise render as four identical rows. Distinct provider models (e.g. opus → A, haiku → B) remain separate rows.
  • Curated Claude options (context window / fast mode) are dropped on provider rows; the family's reasoning ladder is kept.

Validation

  • cargo check --locked -p zeron-harness --lib --tests — clean for the changed files
  • cargo test --locked -p zeron-harness --lib claude:: — 32 passed
  • cargo test --locked -p zeron-harness --test claude — 10 passed
  • cargo test --locked -p zeron-ui --lib -- --test-threads=1 — 778 passed, 0 failed
  • Mock-endpoint check: --model claude-opus-5 reached the provider as claude-opus-5 before, and as the configured provider model after; --setting-sources "" produced no request, user produced one.
  • rustfmt --check on the changed files; git diff --check — clean

Note: zeron-harness --lib has a pre-existing, environment-dependent failure (shell_env::unix::tests::falls_back_when_interactive_attempt_hangs) that also fails on unmodified main.


中文说明

概述

cc-switch(以及各类 Anthropic 兼容网关)是通过改写 ~/.claude/settings.jsonenv 块来切换 Claude Code 的供应商的:

{
  "env": {
    "ANTHROPIC_BASE_URL": "https://api.deepseek.com/anthropic",
    "ANTHROPIC_MODEL": "deepseek-v4-flash",
    "ANTHROPIC_DEFAULT_OPUS_MODEL": "deepseek-v4-flash[1M]",
    "ANTHROPIC_DEFAULT_SONNET_MODEL": "deepseek-v4-flash[1M]",
    "ANTHROPIC_DEFAULT_HAIKU_MODEL": "deepseek-v4-flash"
  }
}

Claude Code 的 ANTHROPIC_DEFAULT_*_MODEL 映射只对 family 别名生效fable/opus/sonnet/haiku)。通过 --model 传完整模型 id(claude-opus-5)时,会被原样发给 provider —— 这一点已用一个本地假 Anthropic endpoint 实测确认。而 Zeron 的 catalog 恰好全是完整 id,于是映射被绕过:模型选择器一直显示 Claude 名字,请求里带的也是 Claude 模型名,校验严格的 provider 会直接报 model_not_found(见 #305)。

引用 #305

改动

  • 新增 crates/harness/src/claude/settings.rs —— 读取用户级 Claude 配置的 env$CLAUDE_CONFIG_DIR~/.claude),提供 per-family 的 provider 映射以及 resolve_request_model()
  • build_command —— 传 --model 之前先按 family 映射解析选中的 id。没有配置映射时保持原行为(含 [1m] 上下文后缀),因此原生 Anthropic 用户不受影响。
  • models() —— 配置了映射时,直接展示 provider 模型,而不是它们所映射自的 Claude id。
  • 标题生成 —— --setting-sources "" 会把用户配置文件整个禁用,第三方 provider 的 env(base URL / token / model)随之丢失,标题运行会以 “Not logged in” 失败。改为 user 后既保留该 env,又仍然跳过 project/local 配置。

设计说明

  • picker 行的 id 用 provider 模型值(deepseek-v4-flash[1M]);Claude Code 会剥掉末尾的 [...] 上下文后缀,因此请求与显示的模型保持一致
  • provider 行按显示名称去重ANTHROPIC_DEFAULT_*_MODEL_NAME):cc-switch 通常把四个 family 都指向同一个模型,不去重会渲染成四行一模一样的条目。若确实配了不同的 provider 模型(如 opus → A、haiku → B),仍会分别成行。
  • provider 行会去掉 Claude 专有的选项(上下文窗口 / fast mode),保留该 family 的推理等级梯度。

验证

  • cargo check --locked -p zeron-harness --lib --tests —— 改动文件无 warning
  • cargo test --locked -p zeron-harness --lib claude:: —— 32 passed
  • cargo test --locked -p zeron-harness --test claude —— 10 passed
  • cargo test --locked -p zeron-ui --lib -- --test-threads=1 —— 778 passed, 0 failed
  • 假 endpoint 实测:改动前 --model claude-opus-5 原样到达 provider,改动后到达的是配置的 provider 模型;--setting-sources "" 不产生请求,user 会产生请求。
  • 改动文件 rustfmt --checkgit diff --check —— 通过

说明:zeron-harness --lib 有一个既有的、依赖环境的失败用例(shell_env::unix::tests::falls_back_when_interactive_attempt_hangs),在未改动的 main 上同样失败。


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

cc-switch (and Anthropic-compatible gateways) switch Claude Code's provider
by rewriting the env block of ~/.claude/settings.json:
ANTHROPIC_BASE_URL/AUTH_TOKEN plus ANTHROPIC_DEFAULT_{FABLE,OPUS,SONNET,
HAIKU}_MODEL. Claude Code applies that family mapping only to FAMILY ALIASES
(opus, sonnet, ...); a full model id passed via --model (claude-opus-5) goes
to the provider verbatim. Zeron's curated catalog is exactly those full ids,
so the mapping was bypassed: requests carried the Claude model name and some
providers rejected them with model_not_found (issue zeronsh#305).

Read the user-level settings and resolve the selected id through the family
mapping before passing --model. When a mapping is configured, models() now
surfaces the provider models (deduped by display name) so the picker names
what actually runs; native Anthropic settings are untouched.

Also stop disabling the user settings file for title runs: --setting-sources
"" dropped the provider env, so titles failed with "Not logged in" for
third-party providers. `user` keeps them while still skipping project/local.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant