@1outres/dsh-plugin-claude-subscription 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -0
- package/README.md +121 -178
- package/lib/index.js +3574 -1536
- package/lib/inert-mcp.js +33 -0
- package/package.json +9 -12
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# @1outres/dsh-plugin-claude-subscription
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
Every call goes through
|
|
5
|
-
[`@anthropic-ai/claude-agent-sdk`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk).
|
|
6
|
-
DSH keeps its own loop, tools, sessions and UI.
|
|
3
|
+
Use your own Claude subscription as an LLM provider in DeepSeek Harness (DSH).
|
|
7
4
|
|
|
8
|
-
This plugin is
|
|
9
|
-
|
|
5
|
+
This plugin is unofficial. It is not affiliated with or endorsed by Anthropic.
|
|
6
|
+
|
|
7
|
+
DSH keeps its agent loop, tools, approvals and compaction. For each model call,
|
|
8
|
+
the plugin runs the official `claude` CLI once, in `-p` stream-json mode, behind a
|
|
9
|
+
small relay on the loopback address. The relay lets exactly one request reach
|
|
10
|
+
Anthropic and refuses every other request from that process. The CLI's own tools
|
|
11
|
+
are off, so the model can only call the tools DSH offers, and DSH runs them.
|
|
10
12
|
|
|
11
13
|
Install just this plugin:
|
|
12
14
|
|
|
@@ -18,187 +20,128 @@ Or install it with every plugin in the repository through the
|
|
|
18
20
|
[`@1outres/dsh-plugins`](../dsh-plugins) bundle. Enable one or the other: both
|
|
19
21
|
insert the same `claude-subscription` row.
|
|
20
22
|
|
|
21
|
-
## What it adds
|
|
22
|
-
|
|
23
|
-
A provider route `claude-subscription` with a fixed model list:
|
|
24
|
-
|
|
25
|
-
| Model | Context window |
|
|
26
|
-
| ------------------- | -------------- |
|
|
27
|
-
| `claude-fable-5-1` | 1,000,000 |
|
|
28
|
-
| `claude-opus-5-5` | 1,000,000 |
|
|
29
|
-
| `claude-sonnet-5-5` | 1,000,000 |
|
|
30
|
-
| `claude-haiku-5-5` | 1,000,000 |
|
|
31
|
-
|
|
32
|
-
The context windows are the `modelUsage[model].contextWindow` values of a real call. A model
|
|
33
|
-
your plan does not allow fails at call time. Efforts are `low`, `medium`, `high`, `xhigh` and
|
|
34
|
-
`max`. Thinking is adaptive and summarized on all four models. Images work in user messages and
|
|
35
|
-
in tool results.
|
|
36
|
-
|
|
37
|
-
The plugin has no settings and no browser half. Its row id is `claude-subscription`.
|
|
38
|
-
|
|
39
23
|
## Requirements
|
|
40
24
|
|
|
41
|
-
-
|
|
42
|
-
|
|
43
|
-
-
|
|
44
|
-
this is why the `@1outres/dsh-plugins` bundle install gets large too.
|
|
45
|
-
- Tested on Linux x64 only.
|
|
46
|
-
|
|
47
|
-
## How a call works
|
|
48
|
-
|
|
49
|
-
Each DSH step starts a new `query()`. The whole history goes in as a synthesized transcript
|
|
50
|
-
through `sessionStore.load` and `resume`, with an empty prompt. DSH runs every tool: the plugin
|
|
51
|
-
declares tools to the CLI, reads tool calls from the stream, and stops the child at
|
|
52
|
-
`message_delta`. A tool handler never runs a tool and never returns a result.
|
|
53
|
-
|
|
54
|
-
### The pinned SDK
|
|
55
|
-
|
|
56
|
-
`@anthropic-ai/claude-agent-sdk` is pinned to exactly `0.3.318`. The transcript format, the seal
|
|
57
|
-
entry, `CLAUDE_AGENT_SDK_MCP_NO_PREFIX`, `alwaysLoad`, the alpha `sessionStore`, the
|
|
58
|
-
`onProcessSpawned` hook and the temp dir redirect are all undocumented or unstable behavior,
|
|
59
|
-
verified on this version only. Run the
|
|
60
|
-
contract test below before you move the pin. The SDK's own peers (`zod`,
|
|
61
|
-
`@modelcontextprotocol/sdk`, `@anthropic-ai/sdk`) are not declared: the plugin needs none of them
|
|
62
|
-
at runtime, and an install without them only prints a peer warning.
|
|
63
|
-
|
|
64
|
-
### What the CLI always adds
|
|
65
|
-
|
|
66
|
-
No option or variable removes these from what the model reads:
|
|
67
|
-
|
|
68
|
-
- the Claude Code identity prefix before your system prompt
|
|
69
|
-
- an environment section (working directory, platform, and so on)
|
|
70
|
-
- the current date
|
|
71
|
-
- a reminder that names your account email
|
|
72
|
-
|
|
73
|
-
### What ends up in DSH session data
|
|
74
|
-
|
|
75
|
-
`replayState` stores what the CLI emitted, so that the cache keeps working. That includes the
|
|
76
|
-
working directory, your account email and your organization UUID. They stay in your DSH session
|
|
77
|
-
logs. Treat those logs as personal data if you share them.
|
|
78
|
-
|
|
79
|
-
The working directory is the one the DSH process started in.
|
|
80
|
-
|
|
81
|
-
### Isolation
|
|
82
|
-
|
|
83
|
-
- `env` is a strict allow-list. `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`,
|
|
84
|
-
`CLAUDE_CODE_OAUTH_TOKEN`, cloud-provider variables and `DSH_*` never reach the child.
|
|
85
|
-
- `settingSources: []`, built-in tools off and auto-compaction off keep `~/.claude` out of it.
|
|
86
|
-
|
|
87
|
-
### The billing guard
|
|
88
|
-
|
|
89
|
-
Before the first step, the plugin starts the CLI with a prompt that yields nothing, reads
|
|
90
|
-
`initializationResult()`, and closes it. This sends no model request, which the contract test
|
|
91
|
-
checks in the CLI's debug log. A pass is remembered until the plugin stops. A failure is not.
|
|
25
|
+
- DSH 0.2.1-rc.5 or newer in the 0.2 line. Windows is not
|
|
26
|
+
supported.
|
|
27
|
+
- The official Claude Code CLI, installed:
|
|
92
28
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
key, and that the old values such as `oauth` are no longer sent. A Console login reports
|
|
97
|
-
`/login managed key`, so it fails with `API_KEY_SOURCE_REJECTED`.
|
|
98
|
-
- With no subscription and no API key, the call fails with `MISSING_CREDENTIAL`.
|
|
29
|
+
```sh
|
|
30
|
+
npm install -g @anthropic-ai/claude-code
|
|
31
|
+
```
|
|
99
32
|
|
|
100
|
-
|
|
101
|
-
line only: `init` arrives just before the first request, and the child lives for about 2 seconds
|
|
102
|
-
after it is stopped. So if the login changes to an API key while the plugin runs, one request may
|
|
103
|
-
go out and be billed to the API. The error says so.
|
|
33
|
+
- A login, made as the same user that DSH runs as:
|
|
104
34
|
|
|
105
|
-
|
|
35
|
+
```sh
|
|
36
|
+
claude auth login
|
|
37
|
+
```
|
|
106
38
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
`~/.cache/...` when `XDG_CACHE_HOME` is not set. The root and the dir are mode 700, and the plugin
|
|
111
|
-
refuses a root that is a symlink, is not yours, or is open to others.
|
|
39
|
+
The login belongs to the CLI. The plugin never opens or copies credential files,
|
|
40
|
+
has no login flow of its own, and asks for no API key. When the CLI is logged out,
|
|
41
|
+
a call fails with a message that tells you to run `claude auth login`.
|
|
112
42
|
|
|
113
|
-
|
|
43
|
+
The plugin refuses to run when the environment of DSH sends the CLI to another
|
|
44
|
+
backend. It fails with `CONFLICTING_ENVIRONMENT` and names the variables, never
|
|
45
|
+
their values. These are `ANTHROPIC_BASE_URL`, `CLAUDE_CODE_USE_BEDROCK`,
|
|
46
|
+
`CLAUDE_CODE_USE_VERTEX` and `CLAUDE_CODE_USE_FOUNDRY`.
|
|
114
47
|
|
|
115
|
-
|
|
116
|
-
that dir stays until a later start finds the PID free.
|
|
117
|
-
- A PID from another namespace cannot be checked here, for example from `bwrap --unshare-pid` with
|
|
118
|
-
a shared `~/.cache`. Such a dir goes only once it is older than 7 days. If its process is still
|
|
119
|
-
idle by then, the SDK makes the dir again at the next step.
|
|
120
|
-
- A dir from another host stays, because its process cannot be checked.
|
|
48
|
+
## Settings
|
|
121
49
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
warning. A step started after that fails with `PLUGIN_DISPOSED`.
|
|
50
|
+
The plugin has no settings page. Set these fields in your profile's
|
|
51
|
+
`cordis.patch.yml`:
|
|
125
52
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
plugin stops.
|
|
131
|
-
|
|
132
|
-
SDK 0.3.318 picks that location from the parent's `os.tmpdir()`, not from `options.env.TMPDIR`. So
|
|
133
|
-
`sessionStore.load` sets `process.env.TMPDIR` in the DSH process for one moment and restores it at
|
|
134
|
-
the next check phase of the event loop. The contract test checks that the copy lands in the
|
|
135
|
-
private dir.
|
|
136
|
-
|
|
137
|
-
## Options and errors
|
|
138
|
-
|
|
139
|
-
`maxTokens` sets `CLAUDE_CODE_MAX_OUTPUT_TOKENS`. `temperature` and `stop` have no equivalent, so
|
|
140
|
-
they raise `UNSUPPORTED_OPTION`. This means DSH's experimental auto-review, which sends
|
|
141
|
-
`temperature: 0`, cannot use this route.
|
|
142
|
-
|
|
143
|
-
| Failure | Code | DSH retries |
|
|
144
|
-
| -------------------------------------------- | ---------------------------------------------- | ----------- |
|
|
145
|
-
| Not logged in, or a failed login refresh | `MISSING_CREDENTIAL` | no |
|
|
146
|
-
| Usage limit, with the reset time when known | `QUOTA` | no |
|
|
147
|
-
| Prompt too long | `CONTEXT_WINDOW_EXCEEDED` | compacts |
|
|
148
|
-
| Rate limit, server, connection or timeout | `RATE_LIMIT`, `SERVER`, `TRANSPORT`, `TIMEOUT` | yes |
|
|
149
|
-
| No output | `EMPTY_RESPONSE` | yes |
|
|
150
|
-
| The model declined | `REFUSAL` | no |
|
|
151
|
-
| Model not in your plan | `UNKNOWN_MODEL` | no |
|
|
152
|
-
| Account needs action (on hold, verification) | `CLAUDE_ACCOUNT` | no |
|
|
153
|
-
| Bad request | `INVALID_REQUEST` | no |
|
|
154
|
-
| An API key would be used | `API_KEY_SOURCE_REJECTED` | no |
|
|
155
|
-
| A cloud provider would be used | `NOT_SUBSCRIPTION_LOGIN` | no |
|
|
156
|
-
| The child process failed | `CLAUDE_CODE_PROCESS` | no |
|
|
157
|
-
| The plugin has stopped | `PLUGIN_DISPOSED` | no |
|
|
158
|
-
|
|
159
|
-
An abort gives the `aborted` finish. The login check and the login refresh each have a deadline
|
|
160
|
-
(30 and 60 seconds), and passing it gives `TIMEOUT`.
|
|
161
|
-
|
|
162
|
-
`CLAUDE_CODE_MAX_RETRIES` is `0`, so DSH owns the retry policy. The CLI's default of 10 retries
|
|
163
|
-
would multiply with DSH's 5, up to 66 requests with long silent waits. The CLI's recovery from a
|
|
164
|
-
bad image or a rejected thinking block still works with `0`, as checked with real calls.
|
|
165
|
-
|
|
166
|
-
### Problems the CLI recovers from by itself
|
|
167
|
-
|
|
168
|
-
The CLI can drop a bad image and still end in success. DSH's stream has no warning chunk, so the
|
|
169
|
-
plugin logs these at `warn` and records them in `replayState.response.healedErrors`. They do not
|
|
170
|
-
show in the Web UI.
|
|
171
|
-
|
|
172
|
-
A rejected thinking signature is also recovered, but the CLI reports it only in its debug log.
|
|
173
|
-
The plugin cannot see it.
|
|
174
|
-
|
|
175
|
-
### Expired login: not verified against a real expiry
|
|
176
|
-
|
|
177
|
-
The SDK removes the refresh token from the copy a step uses, so a step cannot renew the login. On
|
|
178
|
-
`authentication_failed` with status 401, or the CLI's "session expired" wording, the plugin runs
|
|
179
|
-
one non-resume `query()` with `persistSession: false`, then retries the step once. If either
|
|
180
|
-
fails, it raises the login error.
|
|
181
|
-
|
|
182
|
-
A real expiry cannot be reproduced on demand. This path is unit tested, and the contract test
|
|
183
|
-
only checks that the non-resume call starts and succeeds. It has never run against a token that
|
|
184
|
-
really expired.
|
|
185
|
-
|
|
186
|
-
## Tests
|
|
187
|
-
|
|
188
|
-
`pnpm test` at the repository root runs the unit tests, with the SDK's `query` mocked. Some of them
|
|
189
|
-
replay `test/fixtures/*.jsonl`, frames recorded from real calls on SDK 0.3.318 with only a whitelist
|
|
190
|
-
of fields kept. They hold no ids, account data or paths, and must stay that way because this repo is
|
|
191
|
-
public.
|
|
192
|
-
|
|
193
|
-
The contract test makes real calls on your subscription, and it guards SDK upgrades. It is not
|
|
194
|
-
part of `pnpm test`.
|
|
195
|
-
|
|
196
|
-
```sh
|
|
197
|
-
pnpm --filter @1outres/dsh-plugin-claude-subscription test:contract
|
|
53
|
+
```yaml
|
|
54
|
+
- id: claude-subscription
|
|
55
|
+
config:
|
|
56
|
+
claudeConfigDir: ~/.claude-work
|
|
198
57
|
```
|
|
199
58
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
59
|
+
DSH reads the edit when it recomposes the profile. Without its HMR plugin, restart
|
|
60
|
+
DSH instead.
|
|
61
|
+
|
|
62
|
+
| Field | Default | Meaning |
|
|
63
|
+
| ------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
64
|
+
| `command` | `claude` | Name found through `PATH`, or an absolute path. |
|
|
65
|
+
| `claudeConfigDir` | empty | Config directory of a separate Claude login. Empty uses `CLAUDE_CONFIG_DIR` from the environment, then the CLI's own default. |
|
|
66
|
+
| `claudeCodeTelemetry` | on | Lets the CLI send its telemetry and fetch feature flags. The flags unlock models in its picker, so turning this off can shrink the model list. |
|
|
67
|
+
| `readIdleTimeoutSeconds` | `1800` | Seconds of silence from the CLI before a call fails with `TIMEOUT`. From 1 to 2147483. |
|
|
68
|
+
|
|
69
|
+
Whatever the settings say, the CLI is started with `DISABLE_AUTOUPDATER`,
|
|
70
|
+
`DISABLE_FEEDBACK_COMMAND` and `CLAUDE_CODE_DISABLE_TERMINAL_TITLE`.
|
|
71
|
+
|
|
72
|
+
The relay reads `https_proxy`, `http_proxy` and `NO_PROXY` from the environment of
|
|
73
|
+
DSH. Only an `http://` proxy that supports `CONNECT` works. Any other proxy URL
|
|
74
|
+
fails the call, and the plugin does not connect directly instead. Set
|
|
75
|
+
`CLAUDE_CODE_GZIP_REQUEST_BODIES=0` to send request bodies uncompressed.
|
|
76
|
+
|
|
77
|
+
## Models
|
|
78
|
+
|
|
79
|
+
The model list comes from the CLI's answer about your account. Pinned routes the
|
|
80
|
+
answer leaves out are added after it, except models Anthropic grants per
|
|
81
|
+
organization. If the CLI cannot answer, for example when it is logged out, no
|
|
82
|
+
model is listed, and the message appears on the provider's row under
|
|
83
|
+
**Settings → Models**. The next look at the list asks the CLI again.
|
|
84
|
+
|
|
85
|
+
A pinned 1M model is sent with `[1m]`. An id the plugin does not know is sent as it
|
|
86
|
+
is and reports a 200K window. An unknown id that ends in `[1m]` reports no window.
|
|
87
|
+
|
|
88
|
+
## What the model sees
|
|
89
|
+
|
|
90
|
+
The CLI adds its own text to every request. Besides the DSH system prompt and
|
|
91
|
+
history, the request holds:
|
|
92
|
+
|
|
93
|
+
- A first system block with the CLI's version and the caller name, for example
|
|
94
|
+
`x-anthropic-billing-header: cc_version=2.1.317.173; cc_entrypoint=deepseek-harness;`.
|
|
95
|
+
- A second system block: `You are a Claude agent, built on Anthropic's Claude Agent SDK.`
|
|
96
|
+
- A `# Environment` block, sent as a `system` message after the first user turn.
|
|
97
|
+
It lists the working directory, whether it is a git repository, the platform, the
|
|
98
|
+
shell and the OS version. It adds a note on downloaded files, the model's name
|
|
99
|
+
and id, its knowledge cutoff and today's date.
|
|
100
|
+
|
|
101
|
+
The working directory in that block is a private directory the plugin keeps for the
|
|
102
|
+
CLI, not your workspace. The DSH system prompt names the real one.
|
|
103
|
+
|
|
104
|
+
A user turn that ends in text starting with `/` reaches the model with one leading
|
|
105
|
+
space. Without the space the CLI reads the text as a slash command, even with
|
|
106
|
+
`--disable-slash-commands`, and the call fails.
|
|
107
|
+
|
|
108
|
+
Text in this section was read from a request by Claude Code 2.1.317. Another CLI
|
|
109
|
+
version can differ.
|
|
110
|
+
|
|
111
|
+
## Known limits
|
|
112
|
+
|
|
113
|
+
- Each model call starts a new `claude` process, so a call has more start-up time
|
|
114
|
+
than a direct API call.
|
|
115
|
+
- The plugin relies on how the CLI behaves today: its replay of history, its
|
|
116
|
+
extra-body setting, and its stream-json output. A CLI update can change them.
|
|
117
|
+
- Calls count against your subscription limits. Your account decides whether a model
|
|
118
|
+
uses usage credits. Some rows in the model list say so, and the plugin does not
|
|
119
|
+
block them.
|
|
120
|
+
- DSH shows token counts for a call, but the plugin reports no cost.
|
|
121
|
+
- A reply cut off by the output limit runs no tool call, and DSH sees `max-tokens`.
|
|
122
|
+
- A refusal ends the call with `CONTENT_FILTER` and the reason the service gave. A
|
|
123
|
+
tool call in a refused reply never runs.
|
|
124
|
+
- A reply that ends normally with no text and no tool call, even one with thinking,
|
|
125
|
+
fails with `EMPTY_RESPONSE`, which DSH retries. A reply cut off by the output limit
|
|
126
|
+
reports `max-tokens` instead.
|
|
127
|
+
- A request whose retained images pass 20 MiB as base64 fails with `IMAGE_OFFLOAD_REQUIRED`.
|
|
128
|
+
DSH then offloads the oldest images and retries. A dropped image keeps a text note that
|
|
129
|
+
names its read-only path when DSH has one.
|
|
130
|
+
- `temperature` is checked and then dropped. It never reaches the model.
|
|
131
|
+
- DSH removes variables with `TOKEN` in their names before it starts a child process,
|
|
132
|
+
so `CLAUDE_CODE_OAUTH_TOKEN` in the environment of DSH does not reach the CLI. Log in
|
|
133
|
+
with `claude auth login` instead.
|
|
134
|
+
- The relay trusts Node's bundled certificate list and `NODE_EXTRA_CA_CERTS`. A
|
|
135
|
+
TLS-inspecting proxy whose certificate is only in `SSL_CERT_FILE` fails the call.
|
|
136
|
+
- Every call declares the full active tool list, so adding or removing a tool during a
|
|
137
|
+
conversation rewrites the prompt cache from that point.
|
|
138
|
+
- Signed thinking is replayed only for a message that was not edited, was made by the
|
|
139
|
+
same model that the request names, and holds text or a tool call besides its thinking.
|
|
140
|
+
Any other message, such as an edited one or one from before a model switch, is sent
|
|
141
|
+
again as plain text and tool calls.
|
|
142
|
+
|
|
143
|
+
## Attribution
|
|
144
|
+
|
|
145
|
+
Ported from the `claude-subscription-directsdk` plugin of
|
|
146
|
+
[NousResearch/hermes-official-plugins](https://github.com/NousResearch/hermes-official-plugins),
|
|
147
|
+
at commit `83f0a89`, MIT licensed. See `LICENSE`.
|