Bundle
dsh-claude-bridge
Use a Claude Pro or Max subscription as a DeepSeek Harness model provider, by driving the Claude Code CLI through the Agent SDK.
- Source
- shaneconner
- License
- MIT
- Updated
- Updated 5 days ago
Readme
# dsh-claude-bridge
Use a Claude Pro or Max subscription as a [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) model provider, by driving the Claude Code CLI through the Agent SDK.
## Install
```sh
dsh plugin --profile web add dsh-claude-bridge
```
Restart `dsh`. It registers one provider route, `claude-bridge`, and does not disable anything: it sits beside whatever else serves models and is chosen per agent by provider id.
You need to be signed in to Claude Code already. This bridge does not authenticate: it runs a CLI
that reads the credential `claude` login wrote, at `~/.claude/.credentials.json` (or
`$CLAUDE_CONFIG_DIR/.credentials.json`). Sign in with `claude` in a terminal, then `/login` there.
The CLI it runs is the one bundled with the Agent SDK, not whatever `claude` is on your PATH; PATH is
not consulted. Only the credential file is shared. Set `pathToClaudeCodeExecutable` to run a
different binary.
Settings are not read off disk. The child loads no user, project or local `settings.json`, so your own
hooks, plugins and skills stay out of it, and a stray `env` block cannot move the route off your plan.
## Why not just sign in to Anthropic
The harness can already do that, and it works. [`dsh-provider-login`](https://www.npmjs.com/package/dsh-provider-login) reaches the OAuth flow labelled "Anthropic (Claude Pro/Max)" that the harness registers.
The difference is billing, not capability. Per Pi's documentation of the same underlying library, third-party harness usage through that route **draws from extra usage and is billed per token, not against Claude plan limits**. Driving the CLI through the Agent SDK is the mechanism that lands usage on the plan instead.
That ground is not perfectly settled: Anthropic [announced and then unannounced](https://support.claude.com/en/articles/15036540-use-the-claude-agent-sdk-with-your-claude-plan) a change to how Agent SDK usage is billed. Know that before you depend on it.
This approach is not original. It is the same one [`pi-claude-bridge`](https://github.com/elidickinson/pi-claude-bridge) takes for the Pi agent, by Eli Dickinson, which is where the design here comes from.
## What works, and what does not
**Works.** Streaming text and reasoning; **tool calls**, including multi-round conversations; token usage with cache reads and writes; finish reasons; model listing and resolution; reasoning effort; and the one-million-token window as an opt-in.
Tools are the interesting part, because the two sides disagree about who blocks. The harness treats a tool round as two calls: `stream()` ends with a `tool-calls` finish, the harness runs the tools, and it calls `stream()` again with the results appended. The CLI treats the same round as one call that never ended: it invokes a tool and its query sits blocked inside that handler.
So the query is not closed when the first `stream()` returns. It is parked, with the handler still awaiting, and resumed when the results arrive. The harness's tools reach the CLI over an in-process MCP server, with their JSON Schemas served verbatim rather than round-tripped through Zod, which loses everything below the top level. Calls are paired to results by the tool-use id the CLI stamps on each request, because call order is not promised to match the order the blocks were emitted.
A result can also arrive before the CLI has got round to asking for it, so delivery and waiting are decoupled: whichever comes second finds the other. Assuming an order there deadlocks the pair.
What a parked round is waiting for is the set of calls the adapter reported to the harness, not the set the child has dispatched. Those differ on every parallel round, because the child dispatches its calls one at a time while the harness answers all of them in one message. Gating on the dispatched set drops the answers to the rest, and nothing carries them again.
**A named limitation on history.** The CLI owns its own session history, and the faithful way to carry a conversation into it is to write its session file and resume. This build states the turns inline in the prompt instead. The model sees the whole conversation, but the CLI's prefix cache sees a new one each turn: correct, and expensive, in that order. Within a single tool round the query stays open, so that round does reuse the cache.
**Cancelling.** The harness cancels by abandoning the stream, so that is where the child is ended: the abort reaches it directly rather than only through a control request it has to be well enough to answer, and an interrupt that is not acknowledged within five seconds stops being waited on. Otherwise a cancelled turn keeps running to completion, on your plan.
**Concurrency.** Sessions are held per harness session id, but a call that offers no tools never takes one: with no MCP server it cannot park, so it runs detached and cannot evict a round parked under the same id. The shipped session-title provider issues exactly that call, concurrently, under the live turn's own id.
## Configuration
```yaml
- id: claude-bridge
name: 'dsh-claude-bridge'
config:
# Ask for the 1M window. Off by default: outside a Max plan it draws extra
# usage, which is the exact thing this bridge exists to avoid.
longContext: false
# Run a different claude binary. PATH is not consulted; left unset, the SDK
# runs the one bundled in its own dependency.
pathToClaudeCodeExecutable: /home/you/.local/bin/claude
# Working directory for the CLI child. Defaults to the harness process's.
cwd: /home/you/project
```
Some decisions are taken for you and are not options. The CLI child runs with `ENABLE_CLAUDEAI_MCP_SERVERS=0` and `DISABLE_AUTO_COMPACT=1`, its preset is told to exclude `CLAUDE.md` and `.claude/rules`, and it loads no settings files at all. Each hands a decision back to the harness that the CLI would otherwise make for itself: the harness owns tool discovery, compaction, and context files.
The variables that would move the child onto a different account, `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_BASE_URL`, `CLAUDE_CODE_USE_BEDROCK` and `CLAUDE_CODE_USE_VERTEX`, are removed from its environment rather than forwarded. The harness materializes `<cwd>/.env` and `$DSH_HOME/.env` into its own environment before any plugin loads, so one of these left in a `.env` for another tool would otherwise reach the child and bill per token.
## Development
```sh
npm run check # typecheck
npm test # the gate suite
npm run build # emit lib/
```
The suite covers the orderings the two sides can arrive in, which is where every real defect here has been. The adapter tests replace the CLI but not its tool calls: a genuine MCP client drives the server the adapter builds, through the same handler and the same tool-use id pairing the CLI uses.
## License
MIT
Install
dsh plugin --profile web add github:shaneconner/dsh-claude-bridge
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-claude-bridge from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.