@1outres/dsh-plugin-claude-subscription 0.0.0-stage → 0.3.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 +21 -0
- package/README.md +203 -2
- package/cordis.patch.yml +6 -0
- package/lib/index.js +1735 -0
- package/package.json +55 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 1outres
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,204 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @1outres/dsh-plugin-claude-subscription
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Makes Claude models selectable in DeepSeek Harness through your own `claude login`.
|
|
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.
|
|
7
|
+
|
|
8
|
+
This plugin is meant for your own subscription and your own personal use. Check that your use
|
|
9
|
+
fits Anthropic's terms for your plan.
|
|
10
|
+
|
|
11
|
+
Install just this plugin:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
dsh plugin add @1outres/dsh-plugin-claude-subscription
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or install it with every plugin in the repository through the
|
|
18
|
+
[`@1outres/dsh-plugins`](../dsh-plugins) bundle. Enable one or the other: both
|
|
19
|
+
insert the same `claude-subscription` row.
|
|
20
|
+
|
|
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
|
+
## Requirements
|
|
40
|
+
|
|
41
|
+
- Run `claude login` once, in the same user account that runs DSH. The plugin never reads,
|
|
42
|
+
stores or logs credentials. The SDK's child process uses the stored login.
|
|
43
|
+
- About 330 MB of disk on Linux x64. The SDK ships the Claude Code binary (323 MB unpacked), and
|
|
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.
|
|
92
|
+
|
|
93
|
+
- `subscriptionType` must be present and `apiProvider` must be `firstParty`. The CLI sets
|
|
94
|
+
`subscriptionType` only for an OAuth login with the inference scope, whatever the plan is called.
|
|
95
|
+
- `apiKeySource` must be absent or `none`. The SDK types say every other current value is an API
|
|
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`.
|
|
99
|
+
|
|
100
|
+
Each step and each login refresh still checks `apiKeySource` at `system/init`. That is a second
|
|
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.
|
|
104
|
+
|
|
105
|
+
### The private temp dir
|
|
106
|
+
|
|
107
|
+
While a step runs, the SDK keeps a copy of your access token in a `claude-resume-*` dir. A killed
|
|
108
|
+
parent leaves that dir behind. So the plugin uses
|
|
109
|
+
`$XDG_CACHE_HOME/dsh-plugin-claude-subscription/<pid>-<host>-<pid namespace>-<random>/`, or
|
|
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.
|
|
112
|
+
|
|
113
|
+
At startup it removes leftovers on this host without reading the files in them:
|
|
114
|
+
|
|
115
|
+
- A dir from the same PID namespace goes when its process is dead. A reused PID looks alive, so
|
|
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.
|
|
121
|
+
|
|
122
|
+
When the plugin stops, it stops every child and waits up to 5 seconds for each one to exit. It
|
|
123
|
+
kills a child still alive at that point, and then removes the dir. If the removal fails, it logs a
|
|
124
|
+
warning. A step started after that fails with `PLUGIN_DISPOSED`.
|
|
125
|
+
|
|
126
|
+
The wait uses the child's PID, not the SDK's own cleanup. A busy child gets `SIGTERM` after 2
|
|
127
|
+
seconds, the SDK removes `claude-resume-*` at that moment, and the child then writes its transcript
|
|
128
|
+
there while it exits. So when a query that was stopped mid-stream ends this way, a
|
|
129
|
+
`claude-resume-*` dir with that transcript and no token copy can stay in the private dir until the
|
|
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
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
It proves with a nonce that an earlier turn is seen, runs a tool round trip, and shows cache reads
|
|
201
|
+
past the system prompt. It also checks that the login check sends no request, the errors for an
|
|
202
|
+
invalid model and for no login, thinking replay, the output cap, images, the private temp dir,
|
|
203
|
+
abort, dispose with a running child, and the non-resume call. It uses `claude-haiku-5-5`. Set
|
|
204
|
+
`CLAUDE_SUBSCRIPTION_CONTRACT_MODEL` to try another model.
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Installs this plugin on its own (dsh plugin add @1outres/dsh-plugin-claude-subscription).
|
|
2
|
+
# The @1outres/dsh-plugins bundle inserts the same "claude-subscription" row, so select
|
|
3
|
+
# one bundle or the other, never both.
|
|
4
|
+
- insert:
|
|
5
|
+
- id: claude-subscription
|
|
6
|
+
name: "@1outres/dsh-plugin-claude-subscription"
|