cc-switch bridge
Every Anthropic-compatible provider saved in the local cc-switch app becomes one provider route in DeepSeek Harness, so its models appear in the model picker and switch like built-in models.
What it reads
~/.cc-switch/cc-switch.db (read-only), table providers, rows with app_type claude /
claude-desktop. From each row: ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN / ANTHROPIC_API_KEY,
the model ids from meta.stackModels and the ANTHROPIC_*MODEL env keys (model[1M] labels keep the
id and give the context window), plus the row name as the group title. Rows are re-read every 60s and
on every model-picker open, so edits made in cc-switch show up without restarting Harness.
Rows whose meta.apiFormat is anything but anthropic, or without a base URL / credential / model,
are skipped and logged. Codex rows are imported through the built-in pi-ai adapter instead (see
Settings page); the other tool groups store their own shapes, see "Why some groups are not imported".
What it registers
One ctx.llm.registerAdapter() route per provider (ccswitch-<first 8 chars of the cc-switch id>),
speaking Anthropic Messages (/v1/messages, SSE or plain JSON) and translating the reply into
Harness stream chunks: text, reasoning/thinking, tool calls, usage, finish reasons. Tool
call/result pairing is repaired before sending, because one unanswered tool call makes the upstream
reject the whole session.
Thinking levels
The levels are not the bridge's own ladder: cc-switch already declares them per model, and the Harness menu mirrors that declaration, so both apps offer the same choices.
codexrows carrymodelCatalog.models[].reasoningLevels(+defaultReasoningLevel), which is what cc-switch shows for a Codex provider.pirows carrythinkingLevelMap, where anullvalue means the level is not offered.- The row with the same provider name wins; the same model id under another provider only fills in what that row does not say.
none in cc-switch is off here. Hosts whose endpoint cannot replay a thinking block are excluded in
replaysWithoutThinking() in lib/thinking.js, and the plugin config thinking: false drops every
effort menu.
Settings page
Settings → cc-switch-bridge lists the tool groups your cc-switch actually has configured (Claude Code, Claude Code Desktop, Codex, …) and imports the ones you tick. Changing the selection re-syncs immediately — no restart — and the model picker follows.
| Group | How it is imported |
|---|---|
| Claude Code / Claude Code Desktop / any Anthropic-format group | the bridge's own adapter, one route per provider |
| Codex | handed to the built-in dsh-llm-pi-ai adapter: one provider per cc-switch Codex row, api taken from the row's wire_api (openai-responses / openai-completions), base URL and model catalog straight from the row |
| pi / openclaw / opencode | each stores its own app's config structure and the bridge has no parser for it yet, so they are listed as not importable |
| gemini | this machine's row is an empty config, so there is nothing to import |
The Codex handoff needs no manual setup: turning it on writes a codex entry into the dsh-llm-pi-ai
plugin config in the desktop profile patch (settingsNs: codex) and stores each row's key in the
credential store under CCSWITCH_CODEX_<row>. Turning it off again removes both the routes and the
secrets the bridge wrote.
The page itself is a browser half (client.js) served by this package and talks to the host over
/dsh-cc-switch/state (GET snapshot, POST {apps, order} for the selection and its order). The lower
half of the page lists what is actually live, as "bridged" and "handed to pi-ai".
Why some groups are not imported
Not a model problem and not a protocol problem: cc-switch stores one config structure per tool, and the
bridge only parses two of them — claude / claude-desktop (env.ANTHROPIC_*) and codex (TOML).
| Group | What cc-switch actually stores | Status |
|---|---|---|
pi |
baseUrl / api / models[] / thinkingLevelMap — pi-ai's own provider shape |
fields map almost 1:1, not implemented yet |
openclaw |
baseUrl / api / models[], also pi-ai-shaped |
not implemented yet |
opencode |
AI SDK {npm, options, models} (@ai-sdk/openai-compatible / @ai-sdk/anthropic) |
needs a small npm → api map, not implemented yet |
gemini |
{"env":{},"config":{}} (empty on this machine) |
nothing to import |
The first three would ride the same pi-ai handoff once parsed; no Harness change needed.
Status file
~/.dsh/dsh-cc-switch-bridge.status.json — written after each successful sync: which routes and models
the host process actually holds. First place to look when a provider is missing from the picker.
Ordering
The bridged and pi-ai rows can be dragged to reorder them; the saved order (order in the state
file) is the order the routes are registered in, so it is what the model picker shows. The
DeepSeek account/official groups stay pinned first because the picker does that itself.
After editing this plugin
The profile installs it as a link, but the running host keeps the old module loaded: toggle the plugin off and on in Settings → Plugins (or restart the app).
Checks
node tests/smoke.mjs — SSE decoding, error mapping, tool-call repair, max_tokens clamping, model
labels, ordering, and a read of the real cc-switch database.
node tests/client-render.mjs — renders the settings page against a stub React, which catches the
hook-order regression that blanks the page inside the app.
No comments yet. Be the first to write one.