@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/lib/index.js ADDED
@@ -0,0 +1,1735 @@
1
+ // ../dsh-plugin-claude-subscription/src/adapter.ts
2
+ import {
3
+ LlmAdapter
4
+ } from "@deepseek-ai/dsh-llm";
5
+
6
+ // ../dsh-plugin-claude-subscription/src/errors.ts
7
+ import {
8
+ CONTEXT_WINDOW_EXCEEDED_CODE,
9
+ EMPTY_RESPONSE_CODE,
10
+ isContextWindowExceededError,
11
+ isQuotaExceededError,
12
+ LlmError,
13
+ QUOTA_EXCEEDED_CODE
14
+ } from "@deepseek-ai/dsh-llm";
15
+ var LOGIN_MESSAGE = "Not logged in to Claude. Run `claude login` in a terminal, then try again.";
16
+ var API_KEY_REJECTED_CODE = "API_KEY_SOURCE_REJECTED";
17
+ var ACCOUNT_ERRORS = /* @__PURE__ */ new Set([
18
+ "oauth_org_not_allowed",
19
+ "account_on_hold",
20
+ "verification_required"
21
+ ]);
22
+ var CONTEXT_TERMINAL_REASONS = /* @__PURE__ */ new Set(["blocking_limit", "prompt_too_long"]);
23
+ var USAGE_LIMIT_WORDING = /hit your|usage limit|usage credits|credits required/i;
24
+ var TIMEOUT_WORDING = /timed? ?out|timeout/i;
25
+ var TRANSPORT_WORDING = /connection|network|socket|fetch failed|econn|enotfound|eai_again/i;
26
+ function loginRequiredError() {
27
+ return new LlmError(LOGIN_MESSAGE, "MISSING_CREDENTIAL");
28
+ }
29
+ function refusalError(explanation) {
30
+ const base = "The model declined to answer this request.";
31
+ return new LlmError(explanation ? `${base} ${explanation}` : base, "REFUSAL");
32
+ }
33
+ function emptyResponseError() {
34
+ return new LlmError("Claude returned no output.", EMPTY_RESPONSE_CODE);
35
+ }
36
+ function unsupportedOptionError(option) {
37
+ return new LlmError(
38
+ `The Claude subscription route does not support GenerateOptions.${option}.`,
39
+ "UNSUPPORTED_OPTION"
40
+ );
41
+ }
42
+ function apiKeyBlockedError(source) {
43
+ return new LlmError(
44
+ `Claude Code would use an API key from "${source}" instead of your claude login. No request was sent.`,
45
+ API_KEY_REJECTED_CODE
46
+ );
47
+ }
48
+ function apiKeyDuringStepError(source) {
49
+ return new LlmError(
50
+ `Claude Code reported an API key from "${source}" instead of your claude login, so the call was stopped. One request may already have gone out and been billed to the API.`,
51
+ API_KEY_REJECTED_CODE
52
+ );
53
+ }
54
+ function notSubscriptionLoginError(provider) {
55
+ return new LlmError(
56
+ `Claude Code would use the provider "${provider}" instead of your claude login. No request was sent.`,
57
+ "NOT_SUBSCRIPTION_LOGIN"
58
+ );
59
+ }
60
+ function loginCheckTimeoutError() {
61
+ return new LlmError("Claude Code did not report its login state in time.", "TIMEOUT");
62
+ }
63
+ function loginRefreshTimeoutError() {
64
+ return new LlmError("Claude Code did not renew the login in time.", "TIMEOUT");
65
+ }
66
+ function pluginDisposedError() {
67
+ return new LlmError("The Claude subscription plugin has stopped.", "PLUGIN_DISPOSED");
68
+ }
69
+ function isExpiredLogin(facts) {
70
+ if (facts.assistantError !== "authentication_failed") return false;
71
+ return facts.status === 401 || /session expired/i.test(facts.text);
72
+ }
73
+ function mapStepFailure(facts) {
74
+ const { assistantError: kind, text } = facts;
75
+ const options = errorOptions(facts);
76
+ if (kind === "authentication_failed") return loginRequiredError();
77
+ if (kind !== void 0 && ACCOUNT_ERRORS.has(kind)) {
78
+ return new LlmError(text, "CLAUDE_ACCOUNT", options);
79
+ }
80
+ if (isContextFailure(facts)) return new LlmError(text, CONTEXT_WINDOW_EXCEEDED_CODE, options);
81
+ const rateLimited = kind === "rate_limit" || facts.status === 429;
82
+ if (kind === "billing_error" || rateLimited && isUsageLimit(facts)) {
83
+ return new LlmError(usageLimitMessage(facts), QUOTA_EXCEEDED_CODE, options);
84
+ }
85
+ if (rateLimited) return new LlmError(text, "RATE_LIMIT", options);
86
+ if (kind === "model_not_found") {
87
+ return new LlmError(`${text} Your plan may not include this model.`, "UNKNOWN_MODEL", options);
88
+ }
89
+ if (kind === "server_error" || kind === "overloaded") {
90
+ return new LlmError(text, "SERVER", options);
91
+ }
92
+ return new LlmError(text, codeFromStatusOrWording(facts), options);
93
+ }
94
+ function mapThrownError(error) {
95
+ if (error instanceof LlmError) return error;
96
+ const message = error instanceof Error ? error.message : String(error);
97
+ return new LlmError(message || "Claude Code stopped without a reason.", "CLAUDE_CODE_PROCESS", {
98
+ cause: error
99
+ });
100
+ }
101
+ function codeFromStatusOrWording({ assistantError: kind, status, text }) {
102
+ if (status === 408) return "TIMEOUT";
103
+ if (status === 429) return "RATE_LIMIT";
104
+ if (status !== null && status >= 500) return "SERVER";
105
+ if (status !== null && status >= 400) return "INVALID_REQUEST";
106
+ if (kind === "invalid_request") return "INVALID_REQUEST";
107
+ if (TIMEOUT_WORDING.test(text)) return "TIMEOUT";
108
+ if (TRANSPORT_WORDING.test(text)) return "TRANSPORT";
109
+ return "CLAUDE_CODE_ERROR";
110
+ }
111
+ function isContextFailure({ terminalReason, text }) {
112
+ if (terminalReason !== null && CONTEXT_TERMINAL_REASONS.has(terminalReason)) return true;
113
+ return isContextWindowExceededError(text) || /prompt is too long/i.test(text);
114
+ }
115
+ function isUsageLimit({ rateLimit, text }) {
116
+ return rateLimit?.status === "rejected" || USAGE_LIMIT_WORDING.test(text) || isQuotaExceededError(text);
117
+ }
118
+ function usageLimitMessage({ rateLimit, text }) {
119
+ const base = `Claude usage limit reached: ${text}`;
120
+ if (rateLimit?.resetsAt === void 0) return base;
121
+ return `${base} It resets at ${new Date(rateLimit.resetsAt * 1e3).toISOString()}.`;
122
+ }
123
+ function errorOptions({ status }) {
124
+ return status !== null && status >= 100 && status <= 599 ? { status } : {};
125
+ }
126
+
127
+ // ../dsh-plugin-claude-subscription/src/login-check.ts
128
+ import { randomUUID as randomUUID2 } from "node:crypto";
129
+ import {
130
+ query
131
+ } from "@anthropic-ai/claude-agent-sdk";
132
+
133
+ // ../dsh-plugin-claude-subscription/src/child-env.ts
134
+ var FORWARDED_NAMES = [
135
+ "PATH",
136
+ "HOME",
137
+ "USER",
138
+ "LANG",
139
+ "LC_ALL",
140
+ // Where `claude login` stored the login, when it is not the default dir.
141
+ "CLAUDE_CONFIG_DIR",
142
+ // The CLI reads these directly. Without them it cannot reach the API behind a proxy.
143
+ "HTTPS_PROXY",
144
+ "https_proxy",
145
+ "HTTP_PROXY",
146
+ "http_proxy",
147
+ "ALL_PROXY",
148
+ "all_proxy",
149
+ "NO_PROXY",
150
+ "no_proxy",
151
+ // The root certificate of a proxy that re-signs TLS traffic.
152
+ "NODE_EXTRA_CA_CERTS"
153
+ ];
154
+ var FIXED_VALUES = {
155
+ ENABLE_CLAUDEAI_MCP_SERVERS: "0",
156
+ DISABLE_AUTO_COMPACT: "1",
157
+ CLAUDE_CODE_DISABLE_ATTACHMENTS: "1",
158
+ CLAUDE_CODE_DISABLE_AUTO_MEMORY: "1",
159
+ CLAUDE_CODE_DISABLE_ADVISOR_TOOL: "1",
160
+ CLAUDE_CODE_DISABLE_CLAUDE_MDS: "1",
161
+ CLAUDE_AGENT_SDK_DISABLE_BUILTIN_AGENTS: "1",
162
+ CLAUDE_AGENT_SDK_MCP_NO_PREFIX: "1",
163
+ CLAUDE_CODE_DISABLE_REFUSAL_RETRY: "1",
164
+ CLAUDE_CODE_DISABLE_REFUSAL_FALLBACK: "1",
165
+ // A non-streaming fallback sends no stream events, so a tool call would never reach the
166
+ // message_delta that stops the step, and the never-settling handler would hang it.
167
+ CLAUDE_CODE_DISABLE_NONSTREAMING_FALLBACK: "1"
168
+ };
169
+ function buildChildEnv(processEnv, options) {
170
+ const env = {};
171
+ for (const name2 of FORWARDED_NAMES) {
172
+ const value = processEnv[name2];
173
+ if (value !== void 0) env[name2] = value;
174
+ }
175
+ return {
176
+ ...env,
177
+ ...FIXED_VALUES,
178
+ TMPDIR: options.tempDir,
179
+ CLAUDE_CODE_MAX_RETRIES: String(options.maxRetries),
180
+ ...options.maxOutputTokens === void 0 ? {} : { CLAUDE_CODE_MAX_OUTPUT_TOKENS: String(options.maxOutputTokens) }
181
+ };
182
+ }
183
+
184
+ // ../dsh-plugin-claude-subscription/src/private-temp-dir.ts
185
+ import { lstatSync, mkdirSync, mkdtempSync, readdirSync, readlinkSync, rmSync } from "node:fs";
186
+ import { homedir, hostname } from "node:os";
187
+ import { isAbsolute, join } from "node:path";
188
+ var ROOT_NAME = "dsh-plugin-claude-subscription";
189
+ var PROCESS_DIR_NAME = /^(\d+)-(.+)-(\d+)-[A-Za-z0-9]{6}$/;
190
+ var NO_PID_NAMESPACE = "0";
191
+ var FOREIGN_NAMESPACE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
192
+ function defaultTempRoot(env = process.env, home = homedir()) {
193
+ const cacheHome = env.XDG_CACHE_HOME;
194
+ return join(cacheHome && isAbsolute(cacheHome) ? cacheHome : join(home, ".cache"), ROOT_NAME);
195
+ }
196
+ function createPrivateTempDir(options = {}) {
197
+ const root = options.root ?? defaultTempRoot();
198
+ const pid = options.pid ?? process.pid;
199
+ const host = options.hostname ?? hostname();
200
+ const namespace = options.pidNamespace ?? readPidNamespace();
201
+ const removeDir = options.removeDir ?? removeDirTree;
202
+ mkdirSync(root, { recursive: true, mode: 448 });
203
+ assertPrivateRoot(root);
204
+ removeLeftovers(root, {
205
+ host,
206
+ namespace,
207
+ isAlive: options.isProcessAlive ?? isProcessAlive,
208
+ now: options.now ?? Date.now
209
+ });
210
+ const path = mkdtempSync(join(root, `${pid}-${host}-${namespace}-`));
211
+ const remove = () => {
212
+ try {
213
+ removeDir(path);
214
+ } catch (error) {
215
+ options.onRemoveFailed?.(error);
216
+ }
217
+ };
218
+ process.once("exit", remove);
219
+ return {
220
+ path,
221
+ redirectSdkTempDir: () => redirectTmpdir(path),
222
+ dispose() {
223
+ process.removeListener("exit", remove);
224
+ remove();
225
+ }
226
+ };
227
+ }
228
+ function removeDirTree(path) {
229
+ rmSync(path, { recursive: true, force: true });
230
+ }
231
+ function readPidNamespace() {
232
+ try {
233
+ return /\[(\d+)\]/.exec(readlinkSync("/proc/self/ns/pid"))?.[1] ?? NO_PID_NAMESPACE;
234
+ } catch {
235
+ return NO_PID_NAMESPACE;
236
+ }
237
+ }
238
+ function assertPrivateRoot(root) {
239
+ const stat = lstatSync(root);
240
+ const ownedByUser = process.getuid === void 0 || stat.uid === process.getuid();
241
+ if (!stat.isDirectory() || !ownedByUser || (stat.mode & 63) !== 0) {
242
+ throw new Error(`The temp root ${root} is not private. It must be a mode 700 dir you own.`);
243
+ }
244
+ }
245
+ function removeLeftovers(root, here) {
246
+ for (const name2 of readdirSync(root)) {
247
+ const match = PROCESS_DIR_NAME.exec(name2);
248
+ if (match === null || match[2] !== here.host) continue;
249
+ const path = join(root, name2);
250
+ const leftover = match[3] === here.namespace ? !here.isAlive(Number(match[1])) : here.now() - lstatSync(path).mtimeMs > FOREIGN_NAMESPACE_MAX_AGE_MS;
251
+ if (leftover) removeDirTree(path);
252
+ }
253
+ }
254
+ function isProcessAlive(pid) {
255
+ try {
256
+ process.kill(pid, 0);
257
+ return true;
258
+ } catch (error) {
259
+ return error.code !== "ESRCH";
260
+ }
261
+ }
262
+ var hostTmpdir;
263
+ var redirectsInFlight = 0;
264
+ function redirectTmpdir(path) {
265
+ if (redirectsInFlight === 0) hostTmpdir = process.env.TMPDIR;
266
+ redirectsInFlight += 1;
267
+ process.env.TMPDIR = path;
268
+ setImmediate(() => {
269
+ redirectsInFlight -= 1;
270
+ if (redirectsInFlight > 0) return;
271
+ if (hostTmpdir === void 0) delete process.env.TMPDIR;
272
+ else process.env.TMPDIR = hostTmpdir;
273
+ });
274
+ }
275
+
276
+ // ../dsh-plugin-claude-subscription/src/query-lifecycle.ts
277
+ var DRAIN_DEADLINE_MS = 5e3;
278
+ var CHILD_EXIT_DEADLINE_MS = 5e3;
279
+ var CHILD_EXIT_POLL_MS = 50;
280
+ var QueryHandle = class {
281
+ constructor(query4, abortController, tracker) {
282
+ this.query = query4;
283
+ this.abortController = abortController;
284
+ this.tracker = tracker;
285
+ this.iterator = query4[Symbol.asyncIterator]();
286
+ tracker.add(this);
287
+ }
288
+ query;
289
+ abortController;
290
+ tracker;
291
+ iterator;
292
+ pending;
293
+ teardown;
294
+ stopped = false;
295
+ /** True once `hardStop` has ended this query from outside the step. */
296
+ get hardStopped() {
297
+ return this.stopped;
298
+ }
299
+ next() {
300
+ const pending = this.iterator.next();
301
+ this.pending = pending;
302
+ return pending;
303
+ }
304
+ /** Ends the query in the background. Later calls keep the first mode. */
305
+ settle(mode) {
306
+ if (this.teardown !== void 0) return;
307
+ if (mode === "interrupt") {
308
+ this.query.interrupt().catch(() => {
309
+ });
310
+ }
311
+ this.teardown = this.drainThenClose();
312
+ }
313
+ /**
314
+ * Aborts and closes the query at once. The child can stay alive for about two
315
+ * more seconds, and a request it has already started is not called back.
316
+ */
317
+ hardStop() {
318
+ this.stopped = true;
319
+ this.abortController.abort();
320
+ this.query.close();
321
+ this.settle("drain");
322
+ return this.teardown;
323
+ }
324
+ async drainThenClose() {
325
+ let timer;
326
+ const deadline = new Promise((resolve) => {
327
+ timer = setTimeout(resolve, DRAIN_DEADLINE_MS);
328
+ timer.unref?.();
329
+ });
330
+ await Promise.race([this.drain(), deadline]);
331
+ clearTimeout(timer);
332
+ this.query.close();
333
+ this.tracker.remove(this);
334
+ }
335
+ async drain() {
336
+ try {
337
+ await this.pending;
338
+ while (!(await this.iterator.next()).done) {
339
+ }
340
+ } catch {
341
+ }
342
+ }
343
+ };
344
+ var QueryTracker = class {
345
+ handles = /* @__PURE__ */ new Set();
346
+ children = /* @__PURE__ */ new Set();
347
+ closingController = new AbortController();
348
+ isAlive;
349
+ kill;
350
+ childExitDeadlineMs;
351
+ constructor(options = {}) {
352
+ this.isAlive = options.isAlive ?? isProcessAlive;
353
+ this.kill = options.kill ?? ((pid) => process.kill(pid, "SIGKILL"));
354
+ this.childExitDeadlineMs = options.childExitDeadlineMs ?? CHILD_EXIT_DEADLINE_MS;
355
+ }
356
+ /** Fires when disposal starts. */
357
+ get closing() {
358
+ return this.closingController.signal;
359
+ }
360
+ /**
361
+ * Query options that report each child's PID. SDK 0.3.318 calls this hook, but its types do not
362
+ * list it. The end of a query does not say when the child is gone: a busy child gets SIGTERM
363
+ * after 2 seconds, the SDK removes its temp dir at that moment, and the child then writes its
364
+ * transcript there while it exits, which makes the dir again.
365
+ */
366
+ childOptions() {
367
+ const onProcessSpawned = (pid) => {
368
+ this.forgetExitedChildren();
369
+ this.children.add(pid);
370
+ };
371
+ return { onProcessSpawned };
372
+ }
373
+ /** Call this right before `query()`: a child started after disposal would outlive the plugin. */
374
+ assertOpen() {
375
+ if (this.closing.aborted) throw pluginDisposedError();
376
+ }
377
+ add(handle) {
378
+ this.handles.add(handle);
379
+ }
380
+ remove(handle) {
381
+ this.handles.delete(handle);
382
+ }
383
+ /** Ends every query, then waits for the children to exit. One still alive at the deadline is killed. */
384
+ async dispose() {
385
+ this.closingController.abort();
386
+ await Promise.all([...this.handles].map((handle) => handle.hardStop()));
387
+ const deadline = performance.now() + this.childExitDeadlineMs;
388
+ this.forgetExitedChildren();
389
+ while (this.children.size > 0 && performance.now() < deadline) {
390
+ await new Promise((resolve) => setTimeout(resolve, CHILD_EXIT_POLL_MS));
391
+ this.forgetExitedChildren();
392
+ }
393
+ for (const pid of this.children) {
394
+ try {
395
+ this.kill(pid);
396
+ } catch {
397
+ }
398
+ }
399
+ }
400
+ forgetExitedChildren() {
401
+ for (const pid of this.children) {
402
+ if (!this.isAlive(pid)) this.children.delete(pid);
403
+ }
404
+ }
405
+ };
406
+
407
+ // ../dsh-plugin-claude-subscription/src/tool-server.ts
408
+ var SERVER_NAME = "dsh";
409
+ var JSON_RPC_METHOD_NOT_FOUND = -32601;
410
+ function createToolServer(tools) {
411
+ const listedTools = tools.map((tool) => ({
412
+ name: tool.name,
413
+ description: tool.description,
414
+ inputSchema: tool.parameters,
415
+ _meta: { "anthropic/alwaysLoad": true }
416
+ }));
417
+ function reply(request) {
418
+ switch (request.method) {
419
+ case "initialize":
420
+ return {
421
+ result: {
422
+ protocolVersion: request.params?.protocolVersion,
423
+ capabilities: { tools: {} },
424
+ serverInfo: { name: SERVER_NAME, version: "1.0.0" }
425
+ }
426
+ };
427
+ case "tools/list":
428
+ return { result: { tools: listedTools } };
429
+ case "ping":
430
+ return { result: {} };
431
+ case "tools/call":
432
+ return void 0;
433
+ default:
434
+ return {
435
+ error: { code: JSON_RPC_METHOD_NOT_FOUND, message: `Unknown method ${request.method}` }
436
+ };
437
+ }
438
+ }
439
+ const instance = {
440
+ async connect(transport) {
441
+ transport.onmessage = (request) => {
442
+ if (request.id === void 0) return;
443
+ const answer = reply(request);
444
+ if (answer === void 0) return;
445
+ transport.send({ jsonrpc: "2.0", id: request.id, ...answer }).catch(() => {
446
+ });
447
+ };
448
+ await transport.start?.();
449
+ },
450
+ async close() {
451
+ }
452
+ };
453
+ return {
454
+ type: "sdk",
455
+ name: SERVER_NAME,
456
+ instance
457
+ };
458
+ }
459
+
460
+ // ../dsh-plugin-claude-subscription/src/query-options.ts
461
+ var REFRESH_PROMPT = "Reply with the word ok.";
462
+ var REFRESH_MODEL = "claude-haiku-5-5";
463
+ var LOGIN_CHECK_MODEL = "claude-haiku-5-5";
464
+ function buildStepOptions(input) {
465
+ return {
466
+ ...isolationOptions(input.cwd, input.env),
467
+ model: input.model,
468
+ abortController: input.abortController,
469
+ systemPrompt: { type: "custom", prompt: input.systemPrompt, snapshot: false },
470
+ includePartialMessages: true,
471
+ resume: input.sessionId,
472
+ sessionStore: {
473
+ load: transcriptLoader(input),
474
+ append: async (_key, entries) => {
475
+ input.onAppend(entries);
476
+ }
477
+ },
478
+ sessionStoreFlush: "eager",
479
+ thinking: { type: "adaptive", display: "summarized" },
480
+ ...input.effort === void 0 ? {} : { effort: input.effort },
481
+ ...input.tools.length === 0 ? {} : {
482
+ mcpServers: { [SERVER_NAME]: createToolServer(input.tools) },
483
+ allowedTools: [`mcp__${SERVER_NAME}`]
484
+ }
485
+ };
486
+ }
487
+ function buildRefreshOptions(input) {
488
+ return {
489
+ ...isolationOptions(input.cwd, input.env),
490
+ model: REFRESH_MODEL,
491
+ systemPrompt: { type: "custom", prompt: "", snapshot: false },
492
+ persistSession: false,
493
+ maxTurns: 1,
494
+ thinking: { type: "disabled" }
495
+ };
496
+ }
497
+ function buildLoginCheckOptions(input) {
498
+ return {
499
+ ...isolationOptions(input.cwd, input.env),
500
+ model: LOGIN_CHECK_MODEL,
501
+ abortController: input.abortController,
502
+ resume: input.sessionId,
503
+ sessionStore: { load: transcriptLoader(input), append: async () => {
504
+ } }
505
+ };
506
+ }
507
+ function isolationOptions(cwd, env) {
508
+ return { cwd, env, tools: [], settingSources: [], strictMcpConfig: true, skills: [] };
509
+ }
510
+ function transcriptLoader(input) {
511
+ return async (key) => {
512
+ if (key.subpath !== void 0 || input.abortController.signal.aborted) return null;
513
+ input.onLoad();
514
+ return [...input.entries];
515
+ };
516
+ }
517
+ function attachmentsToReplay(appended) {
518
+ return appended.filter((entry) => {
519
+ if (entry.type !== "attachment") return false;
520
+ const attachment = entry.attachment;
521
+ return entry.rendered !== void 0 || attachment?.type === "credential_org";
522
+ });
523
+ }
524
+
525
+ // ../dsh-plugin-claude-subscription/src/shared-run.ts
526
+ var ABORTED = /* @__PURE__ */ Symbol("aborted");
527
+ var SharedRun = class {
528
+ constructor(options) {
529
+ this.options = options;
530
+ }
531
+ options;
532
+ active;
533
+ join(signal) {
534
+ if (signal?.aborted) return Promise.resolve(ABORTED);
535
+ const active = this.active ?? this.begin();
536
+ active.waiters += 1;
537
+ return new Promise((resolve, reject) => {
538
+ const leave = () => {
539
+ signal?.removeEventListener("abort", onAbort);
540
+ active.waiters -= 1;
541
+ };
542
+ const onAbort = () => {
543
+ leave();
544
+ if (active.waiters === 0) this.stopActive(active);
545
+ resolve(ABORTED);
546
+ };
547
+ signal?.addEventListener("abort", onAbort, { once: true });
548
+ active.promise.then(
549
+ (value) => {
550
+ leave();
551
+ resolve(value);
552
+ },
553
+ (error) => {
554
+ leave();
555
+ reject(error);
556
+ }
557
+ );
558
+ });
559
+ }
560
+ begin() {
561
+ const stop = new AbortController();
562
+ let timer;
563
+ const deadline = new Promise((resolve) => {
564
+ timer = setTimeout(() => resolve(this.options.onDeadline()), this.options.deadlineMs);
565
+ timer.unref?.();
566
+ });
567
+ const active = {
568
+ stop,
569
+ waiters: 0,
570
+ promise: Promise.race([this.options.run(stop.signal), deadline]).finally(() => {
571
+ clearTimeout(timer);
572
+ this.stopActive(active);
573
+ })
574
+ };
575
+ this.active = active;
576
+ return active;
577
+ }
578
+ stopActive(active) {
579
+ if (this.active === active) this.active = void 0;
580
+ active.stop.abort();
581
+ }
582
+ };
583
+
584
+ // ../dsh-plugin-claude-subscription/src/transcript.ts
585
+ import { randomUUID } from "node:crypto";
586
+ import {
587
+ LlmError as LlmError2
588
+ } from "@deepseek-ai/dsh-llm";
589
+
590
+ // ../dsh-plugin-claude-subscription/src/replay-state.ts
591
+ var REPLAY_KIND = "claude-subscription";
592
+ var REPLAY_VERSION = 1;
593
+ var REPLAY_BLOCK_TYPES = /* @__PURE__ */ new Set(["thinking", "redacted_thinking", "text", "tool_use", "dropped"]);
594
+ function createReplayEnvelope(input) {
595
+ const envelope = {
596
+ response: {
597
+ kind: REPLAY_KIND,
598
+ version: REPLAY_VERSION,
599
+ model: input.model,
600
+ messageId: input.messageId,
601
+ stopReason: input.stopReason,
602
+ attachments: input.attachments,
603
+ ...input.healedErrors.length > 0 ? { healedErrors: input.healedErrors } : {}
604
+ },
605
+ blocks: input.blocks
606
+ };
607
+ return envelope;
608
+ }
609
+ function readReplayEnvelope(value) {
610
+ if (!isRecord(value) || !isRecord(value.response) || !Array.isArray(value.blocks)) {
611
+ return void 0;
612
+ }
613
+ const { kind, version, model, attachments } = value.response;
614
+ if (kind !== REPLAY_KIND || version !== REPLAY_VERSION) return void 0;
615
+ if (typeof model !== "string" || !Array.isArray(attachments)) return void 0;
616
+ if (!value.blocks.every(isReplayBlock)) return void 0;
617
+ return value;
618
+ }
619
+ function isReplayBlock(block) {
620
+ return isRecord(block) && typeof block.type === "string" && REPLAY_BLOCK_TYPES.has(block.type);
621
+ }
622
+ function isRecord(value) {
623
+ return typeof value === "object" && value !== null;
624
+ }
625
+
626
+ // ../dsh-plugin-claude-subscription/src/transcript.ts
627
+ var SEAL_TEXT = "<local-command-stdout></local-command-stdout>";
628
+ function splitSystemPrompt(options) {
629
+ const parts = [options.system];
630
+ let end = 0;
631
+ for (const message of options.messages) {
632
+ if (message.role !== "system") break;
633
+ parts.push(systemText(message.content));
634
+ end += 1;
635
+ }
636
+ return {
637
+ systemPrompt: parts.filter(Boolean).join("\n\n"),
638
+ messages: options.messages.slice(end)
639
+ };
640
+ }
641
+ function buildTranscript(messages, options) {
642
+ const rows = [];
643
+ let index = 0;
644
+ while (index < messages.length) {
645
+ const message = messages[index];
646
+ index += 1;
647
+ if (message.role !== "assistant") {
648
+ appendUserSide(rows, message, options);
649
+ continue;
650
+ }
651
+ const toolCalls = appendAssistant(rows, message, options);
652
+ if (toolCalls.length === 0) continue;
653
+ const turn = collectToolTurn(messages, index, toolCalls);
654
+ appendToolResults(rows, toolCalls, turn.results, options);
655
+ for (const held of turn.held) appendUserSide(rows, held, options);
656
+ index = turn.nextIndex;
657
+ }
658
+ const last = rows.at(-1);
659
+ if (last === void 0 || !("isMeta" in last)) {
660
+ throw new LlmError2(
661
+ "A Claude request must end with a user message or tool results.",
662
+ "INVALID_REQUEST"
663
+ );
664
+ }
665
+ return withIdentity(rows, options);
666
+ }
667
+ function withIdentity(rows, options) {
668
+ const newUuid = options.newUuid ?? randomUUID;
669
+ const timestamp = (options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString()))();
670
+ let parentUuid = null;
671
+ return rows.map((row) => {
672
+ const replayed = row;
673
+ const uuid = replayed.uuid ?? newUuid();
674
+ const entry = {
675
+ ...row,
676
+ uuid,
677
+ parentUuid,
678
+ timestamp: replayed.timestamp ?? timestamp
679
+ };
680
+ parentUuid = uuid;
681
+ return entry;
682
+ });
683
+ }
684
+ function appendUserSide(rows, message, options) {
685
+ switch (message.role) {
686
+ case "user": {
687
+ const content = contentBlocks(message.content, options);
688
+ if (content.length > 0) appendSealedUser(rows, content);
689
+ return;
690
+ }
691
+ case "tool":
692
+ appendSealedUser(rows, [toToolResult(message, options)]);
693
+ return;
694
+ case "system":
695
+ return;
696
+ case "developer":
697
+ throw new LlmError2(
698
+ "The Claude subscription route cannot read developer messages.",
699
+ "UNSUPPORTED_CONTENT"
700
+ );
701
+ }
702
+ }
703
+ function appendSealedUser(rows, content) {
704
+ rows.push({ type: "user", message: { role: "user", content } });
705
+ rows.push({
706
+ type: "user",
707
+ isMeta: true,
708
+ message: { role: "user", content: [{ type: "text", text: SEAL_TEXT }] }
709
+ });
710
+ }
711
+ function appendAssistant(rows, message, options) {
712
+ const replay = replayOf(message, options);
713
+ if (replay !== void 0) rows.push(...replay.attachments);
714
+ const content = replay?.blocks ?? convertAssistantBlocks(message.content);
715
+ if (content.length > 0) {
716
+ rows.push({
717
+ type: "assistant",
718
+ // The CLI drops signed thinking from an entry with no message.model.
719
+ message: { role: "assistant", model: replay?.model ?? options.model, content }
720
+ });
721
+ }
722
+ return content.filter((block) => block.type === "tool_use");
723
+ }
724
+ function replayOf(message, options) {
725
+ const state = message.source.replayState;
726
+ if (state === void 0) return void 0;
727
+ const envelope = readReplayEnvelope(state);
728
+ if (envelope === void 0) {
729
+ options.onReplayDegraded?.("the replay state has an unknown shape");
730
+ return void 0;
731
+ }
732
+ if (envelope.blocks.length !== message.content.length) {
733
+ options.onReplayDegraded?.("the replay state does not line up with the message blocks");
734
+ return void 0;
735
+ }
736
+ return {
737
+ model: envelope.response.model,
738
+ attachments: envelope.response.attachments,
739
+ blocks: envelope.blocks.filter(
740
+ (block) => block.type !== "dropped"
741
+ )
742
+ };
743
+ }
744
+ function convertAssistantBlocks(blocks) {
745
+ const converted = [];
746
+ for (const block of blocks) {
747
+ switch (block.type) {
748
+ case "text":
749
+ if (block.text.trim().length > 0) converted.push({ type: "text", text: block.text });
750
+ break;
751
+ case "reasoning":
752
+ break;
753
+ case "tool-call":
754
+ converted.push({
755
+ type: "tool_use",
756
+ id: normalizeToolCallId(block.id),
757
+ name: block.name,
758
+ input: parseToolInput(block.arguments)
759
+ });
760
+ break;
761
+ default:
762
+ throw unsupportedBlock(block.type);
763
+ }
764
+ }
765
+ return converted;
766
+ }
767
+ function parseToolInput(raw) {
768
+ try {
769
+ const parsed = JSON.parse(raw);
770
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
771
+ return parsed;
772
+ }
773
+ } catch {
774
+ return {};
775
+ }
776
+ return {};
777
+ }
778
+ function normalizeToolCallId(id) {
779
+ return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
780
+ }
781
+ function collectToolTurn(messages, start, toolCalls) {
782
+ const ids = new Set(toolCalls.map((call) => call.id));
783
+ const results = /* @__PURE__ */ new Map();
784
+ const held = [];
785
+ let index = start;
786
+ while (index < messages.length) {
787
+ const message = messages[index];
788
+ if (message.role === "assistant") break;
789
+ const id = message.role === "tool" ? normalizeToolCallId(message.toolCallId) : void 0;
790
+ if (message.role === "tool" && id !== void 0 && ids.has(id)) results.set(id, message);
791
+ else held.push(message);
792
+ index += 1;
793
+ }
794
+ return { results, held, nextIndex: index };
795
+ }
796
+ function appendToolResults(rows, toolCalls, results, options) {
797
+ const content = [];
798
+ const movedImages = [];
799
+ for (const call of toolCalls) {
800
+ const result = results.get(call.id);
801
+ if (result === void 0) {
802
+ content.push({
803
+ type: "tool_result",
804
+ tool_use_id: call.id,
805
+ is_error: true,
806
+ content: [{ type: "text", text: "No result provided" }]
807
+ });
808
+ continue;
809
+ }
810
+ content.push(toToolResult(result, options));
811
+ movedImages.push(...movedErrorImages(result, call.name, options));
812
+ }
813
+ appendSealedUser(rows, [...content, ...movedImages]);
814
+ }
815
+ function toToolResult(message, options) {
816
+ const toolUseId = normalizeToolCallId(message.toolCallId);
817
+ const blocks = message.isError ? contentBlocks(withoutErrorImages(message.content), options) : contentBlocks(message.content, options);
818
+ return {
819
+ type: "tool_result",
820
+ tool_use_id: toolUseId,
821
+ content: blocks.length > 0 ? blocks : [{ type: "text", text: "(no output)" }],
822
+ ...message.isError ? { is_error: true } : {}
823
+ };
824
+ }
825
+ function withoutErrorImages(blocks) {
826
+ return blocks.map(
827
+ (block) => block.type === "image" ? { type: "text", text: "(image attached after the tool results)" } : block
828
+ );
829
+ }
830
+ function movedErrorImages(message, toolName, options) {
831
+ if (!message.isError) return [];
832
+ const images = message.content.filter((block) => block.type === "image");
833
+ if (images.length === 0) return [];
834
+ return [
835
+ {
836
+ type: "text",
837
+ text: `Images from the error result of tool call ${message.toolCallId} (${toolName}):`
838
+ },
839
+ ...images.flatMap((block) => options.renderImage(block.attachment))
840
+ ];
841
+ }
842
+ function contentBlocks(blocks, options) {
843
+ const content = [];
844
+ for (const block of blocks) {
845
+ switch (block.type) {
846
+ case "text":
847
+ if (block.text.trim().length > 0) content.push({ type: "text", text: block.text });
848
+ break;
849
+ case "image":
850
+ content.push(...options.renderImage(block.attachment));
851
+ break;
852
+ default:
853
+ throw unsupportedBlock(block.type);
854
+ }
855
+ }
856
+ return content;
857
+ }
858
+ function systemText(blocks) {
859
+ return blocks.map((block) => {
860
+ if (block.type !== "text") throw unsupportedBlock(block.type);
861
+ return block.text;
862
+ }).join("");
863
+ }
864
+ function unsupportedBlock(type) {
865
+ return new LlmError2(
866
+ `The Claude subscription route cannot send a "${type}" block here.`,
867
+ "UNSUPPORTED_CONTENT"
868
+ );
869
+ }
870
+
871
+ // ../dsh-plugin-claude-subscription/src/login-check.ts
872
+ var LOGIN_CHECK_DEADLINE_MS = 3e4;
873
+ var LOGIN_CHECK_MAX_RETRIES = 0;
874
+ function classifyAccount(account) {
875
+ if (account.apiKeySource !== void 0 && account.apiKeySource !== "none") {
876
+ return apiKeyBlockedError(account.apiKeySource);
877
+ }
878
+ if (account.apiProvider !== "firstParty") {
879
+ return notSubscriptionLoginError(account.apiProvider ?? "unknown");
880
+ }
881
+ if (account.subscriptionType === void 0) return loginRequiredError();
882
+ return void 0;
883
+ }
884
+ function idlePrompt() {
885
+ let release = () => {
886
+ };
887
+ const released = new Promise((resolve) => {
888
+ release = resolve;
889
+ });
890
+ return {
891
+ prompt: {
892
+ [Symbol.asyncIterator]: () => ({
893
+ next: async () => {
894
+ await released;
895
+ return { done: true, value: void 0 };
896
+ }
897
+ })
898
+ },
899
+ release
900
+ };
901
+ }
902
+ var LoginCheck = class {
903
+ constructor(services) {
904
+ this.services = services;
905
+ this.shared = new SharedRun({
906
+ deadlineMs: LOGIN_CHECK_DEADLINE_MS,
907
+ onDeadline: loginCheckTimeoutError,
908
+ run: (stop) => this.run(stop)
909
+ });
910
+ }
911
+ services;
912
+ passed = false;
913
+ shared;
914
+ async check(signal) {
915
+ if (this.passed) return void 0;
916
+ const result = await this.shared.join(signal);
917
+ if (result === void 0) this.passed = true;
918
+ return result;
919
+ }
920
+ async run(stop) {
921
+ const { tracker, cwd, processEnv, tempDir } = this.services;
922
+ tracker.assertOpen();
923
+ const abortController = new AbortController();
924
+ const idle = idlePrompt();
925
+ const running = query({
926
+ prompt: idle.prompt,
927
+ options: {
928
+ ...buildLoginCheckOptions({
929
+ sessionId: randomUUID2(),
930
+ entries: loginCheckEntries(),
931
+ cwd: cwd(),
932
+ env: buildChildEnv(processEnv, {
933
+ tempDir: tempDir.path,
934
+ maxRetries: LOGIN_CHECK_MAX_RETRIES
935
+ }),
936
+ abortController,
937
+ onLoad: tempDir.redirectSdkTempDir
938
+ }),
939
+ ...tracker.childOptions()
940
+ }
941
+ });
942
+ const handle = new QueryHandle(running, abortController, tracker);
943
+ try {
944
+ const answer = await raceAbort(
945
+ running.initializationResult(),
946
+ AbortSignal.any([stop, tracker.closing])
947
+ );
948
+ tracker.assertOpen();
949
+ if (answer === ABORTED) return loginCheckTimeoutError();
950
+ return classifyAccount(answer.account);
951
+ } catch (error) {
952
+ tracker.assertOpen();
953
+ return mapThrownError(error);
954
+ } finally {
955
+ void handle.hardStop();
956
+ idle.release();
957
+ }
958
+ }
959
+ };
960
+ function loginCheckEntries() {
961
+ return buildTranscript([{ role: "user", content: [{ type: "text", text: "Login check." }] }], {
962
+ model: LOGIN_CHECK_MODEL,
963
+ renderImage: () => []
964
+ });
965
+ }
966
+ function raceAbort(work, signal) {
967
+ return new Promise((resolve, reject) => {
968
+ const onAbort = () => resolve(ABORTED);
969
+ if (signal.aborted) onAbort();
970
+ else signal.addEventListener("abort", onAbort, { once: true });
971
+ work.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
972
+ });
973
+ }
974
+
975
+ // ../dsh-plugin-claude-subscription/src/login-refresh.ts
976
+ import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
977
+ var REFRESH_MAX_RETRIES = 2;
978
+ var REFRESH_DEADLINE_MS = 6e4;
979
+ var LoginRefresher = class {
980
+ constructor(services) {
981
+ this.services = services;
982
+ this.shared = new SharedRun({
983
+ deadlineMs: REFRESH_DEADLINE_MS,
984
+ onDeadline: loginRefreshTimeoutError,
985
+ run: (stop) => this.run(stop)
986
+ });
987
+ }
988
+ services;
989
+ shared;
990
+ refresh(signal) {
991
+ return this.shared.join(signal);
992
+ }
993
+ async run(stop) {
994
+ this.services.tracker.assertOpen();
995
+ const abortController = new AbortController();
996
+ const handle = new QueryHandle(
997
+ query2({
998
+ prompt: REFRESH_PROMPT,
999
+ options: {
1000
+ ...buildRefreshOptions({
1001
+ cwd: this.services.cwd(),
1002
+ env: buildChildEnv(this.services.processEnv, {
1003
+ tempDir: this.services.tempDir.path,
1004
+ maxRetries: REFRESH_MAX_RETRIES
1005
+ })
1006
+ }),
1007
+ abortController,
1008
+ ...this.services.tracker.childOptions()
1009
+ }
1010
+ }),
1011
+ abortController,
1012
+ this.services.tracker
1013
+ );
1014
+ const onStop = () => void handle.hardStop();
1015
+ stop.addEventListener("abort", onStop, { once: true });
1016
+ try {
1017
+ while (true) {
1018
+ const next = await handle.next();
1019
+ if (next.done) return void 0;
1020
+ const message = next.value;
1021
+ if (message.type === "system" && message.subtype === "init") {
1022
+ if (message.apiKeySource !== "none") {
1023
+ void handle.hardStop();
1024
+ return apiKeyDuringStepError(message.apiKeySource);
1025
+ }
1026
+ }
1027
+ if (message.type === "assistant" && message.error === "authentication_failed") {
1028
+ return loginRequiredError();
1029
+ }
1030
+ if (message.type === "result") return void 0;
1031
+ }
1032
+ } catch {
1033
+ return void 0;
1034
+ } finally {
1035
+ stop.removeEventListener("abort", onStop);
1036
+ handle.settle("drain");
1037
+ }
1038
+ }
1039
+ };
1040
+
1041
+ // ../dsh-plugin-claude-subscription/src/models.ts
1042
+ import {
1043
+ LlmError as LlmError3,
1044
+ ReasoningEffortId
1045
+ } from "@deepseek-ai/dsh-llm";
1046
+ var PROVIDER_ID = "claude-subscription";
1047
+ var EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
1048
+ var CLAUDE_MODELS = [
1049
+ { id: "claude-fable-5-1", name: "Claude Fable 5.1", contextWindow: 1e6 },
1050
+ { id: "claude-opus-5-5", name: "Claude Opus 5.5", contextWindow: 1e6 },
1051
+ { id: "claude-sonnet-5-5", name: "Claude Sonnet 5.5", contextWindow: 1e6 },
1052
+ { id: "claude-haiku-5-5", name: "Claude Haiku 5.5", contextWindow: 1e6 }
1053
+ ];
1054
+ var EFFORTS = [
1055
+ { id: ReasoningEffortId("low"), name: "Low" },
1056
+ { id: ReasoningEffortId("medium"), name: "Medium" },
1057
+ { id: ReasoningEffortId("high"), name: "High" },
1058
+ { id: ReasoningEffortId("xhigh"), name: "Extra high" },
1059
+ { id: ReasoningEffortId("max"), name: "Max" }
1060
+ ];
1061
+ function findClaudeModel(id) {
1062
+ return CLAUDE_MODELS.find((model) => model.id === id);
1063
+ }
1064
+ function requireClaudeModel(id) {
1065
+ const model = findClaudeModel(id);
1066
+ if (model === void 0) {
1067
+ throw new LlmError3(`The ${PROVIDER_ID} route has no model named "${id}".`, "UNKNOWN_MODEL");
1068
+ }
1069
+ return model;
1070
+ }
1071
+ function listModelInfo() {
1072
+ return CLAUDE_MODELS.map((model) => ({
1073
+ provider: PROVIDER_ID,
1074
+ id: model.id,
1075
+ name: model.name,
1076
+ inputModalities: ["text", "image"]
1077
+ }));
1078
+ }
1079
+ function resolveModelInfo(id) {
1080
+ const model = requireClaudeModel(id);
1081
+ return {
1082
+ provider: PROVIDER_ID,
1083
+ id: model.id,
1084
+ name: model.name,
1085
+ inputModalities: ["text", "image"],
1086
+ context: { contextWindow: model.contextWindow },
1087
+ reasoning: { efforts: EFFORTS }
1088
+ };
1089
+ }
1090
+ function parseEffort(effort) {
1091
+ if (effort === void 0) return void 0;
1092
+ const level = EFFORT_LEVELS.find((candidate) => candidate === String(effort));
1093
+ if (level === void 0) {
1094
+ throw new LlmError3(
1095
+ `The ${PROVIDER_ID} route has no effort named "${String(effort)}".`,
1096
+ "UNSUPPORTED_REASONING_EFFORT"
1097
+ );
1098
+ }
1099
+ return level;
1100
+ }
1101
+
1102
+ // ../dsh-plugin-claude-subscription/src/request-images.ts
1103
+ import { requestImageDimensions } from "@deepseek-ai/dsh-attachment";
1104
+ import {
1105
+ contentHasImage,
1106
+ IMAGE_OFFLOAD_REQUIRED_CODE,
1107
+ LlmError as LlmError4,
1108
+ offloadedImageText,
1109
+ projectOffloadedImages,
1110
+ requestImageHandleText,
1111
+ requiredImageOffload,
1112
+ resolveImageAttachmentAccess
1113
+ } from "@deepseek-ai/dsh-llm";
1114
+ var MAX_REQUEST_IMAGE_PIXELS = 2048 * 2048;
1115
+ var MAX_REQUEST_IMAGE_BYTES = 1024 * 1024;
1116
+ var MAX_REQUEST_BASE64_BYTES = 20 * 1024 * 1024;
1117
+ function imageRequestTarget(image) {
1118
+ return {
1119
+ ...requestImageDimensions(image.width, image.height, MAX_REQUEST_IMAGE_PIXELS),
1120
+ maxBytes: MAX_REQUEST_IMAGE_BYTES
1121
+ };
1122
+ }
1123
+ async function prepareRequestImages(messages, services, signal) {
1124
+ if (!messages.some((message) => contentHasImage(message.content))) {
1125
+ return { messages, renderImage: renderWithoutService };
1126
+ }
1127
+ const attachments = services.attachments();
1128
+ if (attachments === void 0) {
1129
+ throw new LlmError4(
1130
+ "Sending an image to Claude needs the DSH attachment service.",
1131
+ "UNSUPPORTED_CONTENT"
1132
+ );
1133
+ }
1134
+ const versions = await readRetainedImages(messages, attachments, signal);
1135
+ assertRequestFits(messages, versions);
1136
+ const access = (ref) => resolveImageAttachmentAccess(attachments, services.mapHostPath, ref);
1137
+ return {
1138
+ messages: projectOffloadedImages(messages, (ref) => offloadedImageText(ref, access(ref))),
1139
+ renderImage(ref) {
1140
+ const version = versions.get(ref.attachmentId);
1141
+ return [
1142
+ { type: "text", text: requestImageHandleText(ref, version, access(ref)) },
1143
+ {
1144
+ type: "image",
1145
+ source: {
1146
+ type: "base64",
1147
+ media_type: version.mediaType,
1148
+ data: Buffer.from(version.data).toString("base64")
1149
+ }
1150
+ }
1151
+ ];
1152
+ }
1153
+ };
1154
+ }
1155
+ async function readRetainedImages(messages, attachments, signal) {
1156
+ const refs = /* @__PURE__ */ new Map();
1157
+ for (const message of messages) {
1158
+ for (const block of message.content) {
1159
+ if (block.type === "image" && block.offloaded !== true) {
1160
+ refs.set(block.attachment.attachmentId, block.attachment);
1161
+ }
1162
+ }
1163
+ }
1164
+ const versions = /* @__PURE__ */ new Map();
1165
+ await Promise.all(
1166
+ [...refs.values()].map(async (ref) => {
1167
+ versions.set(
1168
+ ref.attachmentId,
1169
+ await attachments.readImageRequest(ref, imageRequestTarget(ref), signal)
1170
+ );
1171
+ })
1172
+ );
1173
+ return versions;
1174
+ }
1175
+ function assertRequestFits(messages, versions) {
1176
+ const offloadImages = requiredImageOffload(
1177
+ messages,
1178
+ { representation: "base64", maxBytes: MAX_REQUEST_BASE64_BYTES },
1179
+ (block) => versions.get(block.attachment.attachmentId).bytes
1180
+ );
1181
+ if (offloadImages > 0) {
1182
+ throw new LlmError4(
1183
+ `Request images exceed ${MAX_REQUEST_BASE64_BYTES} base64 bytes. ${offloadImages} more of the oldest must be offloaded.`,
1184
+ IMAGE_OFFLOAD_REQUIRED_CODE,
1185
+ { offloadImages }
1186
+ );
1187
+ }
1188
+ }
1189
+ function renderWithoutService() {
1190
+ throw new Error("renderImage was called for a history with no images");
1191
+ }
1192
+
1193
+ // ../dsh-plugin-claude-subscription/src/step.ts
1194
+ import { randomUUID as randomUUID3 } from "node:crypto";
1195
+ import {
1196
+ query as query3
1197
+ } from "@anthropic-ai/claude-agent-sdk";
1198
+ import { LlmError as LlmError6 } from "@deepseek-ai/dsh-llm";
1199
+
1200
+ // ../dsh-plugin-claude-subscription/src/stream-mapper.ts
1201
+ import {
1202
+ CONTEXT_WINDOW_EXCEEDED_CODE as CONTEXT_WINDOW_EXCEEDED_CODE2,
1203
+ LlmError as LlmError5,
1204
+ ToolCallId
1205
+ } from "@deepseek-ai/dsh-llm";
1206
+ var REDACTED_THINKING_TEXT = "[Reasoning redacted]";
1207
+ function needsInterrupt(stopReason) {
1208
+ return stopReason !== "end_turn" && stopReason !== "stop_sequence";
1209
+ }
1210
+ function createStreamMapper() {
1211
+ const blocks = /* @__PURE__ */ new Map();
1212
+ let messageId = null;
1213
+ let stopReason = null;
1214
+ let stopExplanation;
1215
+ let usage;
1216
+ function mergeUsage(next) {
1217
+ if (next !== void 0) usage = { ...usage, ...next };
1218
+ }
1219
+ function onBlockStart(index, start) {
1220
+ const block = openBlock(blocks.size, start);
1221
+ blocks.set(index, block);
1222
+ const chunks = [
1223
+ {
1224
+ type: "block-start",
1225
+ index: block.dshIndex,
1226
+ blockType: block.kind === "text" ? "text" : block.kind === "tool" ? "tool-call" : "reasoning"
1227
+ }
1228
+ ];
1229
+ if (block.kind === "text" && block.text.length > 0) {
1230
+ chunks.push({ type: "text-delta", index: block.dshIndex, text: block.text });
1231
+ }
1232
+ return chunks;
1233
+ }
1234
+ function onBlockDelta(index, delta) {
1235
+ const block = blocks.get(index);
1236
+ if (block === void 0) return [];
1237
+ switch (delta.type) {
1238
+ case "text_delta":
1239
+ if (block.kind !== "text" || delta.text === void 0) return [];
1240
+ block.text += delta.text;
1241
+ return [{ type: "text-delta", index: block.dshIndex, text: delta.text }];
1242
+ case "thinking_delta":
1243
+ if (block.kind !== "thinking" || !delta.thinking) return [];
1244
+ block.thinking += delta.thinking;
1245
+ return [{ type: "reasoning-delta", index: block.dshIndex, text: delta.thinking }];
1246
+ case "signature_delta":
1247
+ if (block.kind === "thinking" && delta.signature !== void 0) {
1248
+ block.signature = delta.signature;
1249
+ }
1250
+ return [];
1251
+ case "input_json_delta":
1252
+ if (block.kind !== "tool" || delta.partial_json === void 0) return [];
1253
+ block.json += delta.partial_json;
1254
+ return [
1255
+ {
1256
+ type: "tool-call-delta",
1257
+ index: block.dshIndex,
1258
+ id: ToolCallId(block.id),
1259
+ name: block.name,
1260
+ argumentsDelta: delta.partial_json
1261
+ }
1262
+ ];
1263
+ default:
1264
+ return [];
1265
+ }
1266
+ }
1267
+ function onBlockStop(index) {
1268
+ const block = blocks.get(index);
1269
+ return block === void 0 ? [] : [closeBlock(block)];
1270
+ }
1271
+ return {
1272
+ onEvent(event) {
1273
+ switch (event.type) {
1274
+ case "message_start":
1275
+ if (messageId !== null) {
1276
+ throw new LlmError5("Claude Code restarted a response mid-stream.", "TRANSPORT");
1277
+ }
1278
+ messageId = event.message.id;
1279
+ mergeUsage(event.message.usage);
1280
+ return [];
1281
+ case "content_block_start":
1282
+ return onBlockStart(event.index, event.content_block);
1283
+ case "content_block_delta":
1284
+ return onBlockDelta(event.index, event.delta);
1285
+ case "content_block_stop":
1286
+ return onBlockStop(event.index);
1287
+ case "message_delta":
1288
+ stopReason = event.delta.stop_reason;
1289
+ stopExplanation = event.delta.stop_details?.explanation;
1290
+ mergeUsage(event.usage);
1291
+ return [];
1292
+ default:
1293
+ return [];
1294
+ }
1295
+ },
1296
+ get stopReason() {
1297
+ return stopReason;
1298
+ },
1299
+ usageChunks() {
1300
+ return usage === void 0 ? [] : [{ type: "usage", usage: toTokenUsage(usage) }];
1301
+ },
1302
+ finish(context) {
1303
+ if (stopReason === null) throw new Error("finish() needs a stop reason");
1304
+ const ordered = [...blocks.values()].sort((a, b) => a.dshIndex - b.dshIndex);
1305
+ const closing = ordered.flatMap(
1306
+ (block) => (
1307
+ // A tool call cut off by the output limit stays open: the assembler drops it.
1308
+ block.closed || block.kind === "tool" ? [] : [closeBlock(block)]
1309
+ )
1310
+ );
1311
+ const reason = finishReason(stopReason, stopExplanation, ordered);
1312
+ const succeeded = reason.kind !== "error";
1313
+ return [
1314
+ ...closing,
1315
+ ...this.usageChunks(),
1316
+ {
1317
+ type: "finish",
1318
+ reason,
1319
+ ...succeeded ? {
1320
+ replayState: createReplayEnvelope({
1321
+ model: context.model,
1322
+ messageId,
1323
+ stopReason,
1324
+ attachments: context.attachments,
1325
+ healedErrors: context.healedErrors,
1326
+ blocks: ordered.map(toReplayBlock)
1327
+ })
1328
+ } : {}
1329
+ }
1330
+ ];
1331
+ }
1332
+ };
1333
+ }
1334
+ function openBlock(dshIndex, start) {
1335
+ const base = { dshIndex, closed: false };
1336
+ switch (start.type) {
1337
+ case "text":
1338
+ return { ...base, kind: "text", text: start.text ?? "" };
1339
+ case "thinking":
1340
+ return {
1341
+ ...base,
1342
+ kind: "thinking",
1343
+ thinking: start.thinking ?? "",
1344
+ signature: start.signature ?? ""
1345
+ };
1346
+ case "redacted_thinking":
1347
+ return { ...base, kind: "redacted", data: start.data ?? "" };
1348
+ case "tool_use":
1349
+ return { ...base, kind: "tool", id: start.id ?? "", name: start.name ?? "", json: "" };
1350
+ default:
1351
+ throw new LlmError5(
1352
+ `Claude Code sent a content block of type "${start.type}", which this plugin cannot read.`,
1353
+ "INVALID_RESPONSE"
1354
+ );
1355
+ }
1356
+ }
1357
+ function closeBlock(block) {
1358
+ block.closed = true;
1359
+ const index = block.dshIndex;
1360
+ switch (block.kind) {
1361
+ case "text":
1362
+ return { type: "block-end", index, block: { type: "text", text: block.text } };
1363
+ case "thinking":
1364
+ return { type: "block-end", index, block: { type: "reasoning", text: block.thinking } };
1365
+ case "redacted":
1366
+ return {
1367
+ type: "block-end",
1368
+ index,
1369
+ block: { type: "reasoning", text: REDACTED_THINKING_TEXT }
1370
+ };
1371
+ case "tool": {
1372
+ const json = block.json.length > 0 ? block.json : "{}";
1373
+ block.input = parseToolInput2(json);
1374
+ return {
1375
+ type: "block-end",
1376
+ index,
1377
+ block: { type: "tool-call", id: ToolCallId(block.id), name: block.name, arguments: json }
1378
+ };
1379
+ }
1380
+ }
1381
+ }
1382
+ function parseToolInput2(json) {
1383
+ let parsed;
1384
+ try {
1385
+ parsed = JSON.parse(json);
1386
+ } catch (cause) {
1387
+ throw new LlmError5("Claude sent tool arguments that are not valid JSON.", "INVALID_RESPONSE", {
1388
+ cause
1389
+ });
1390
+ }
1391
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1392
+ throw new LlmError5("Claude sent tool arguments that are not an object.", "INVALID_RESPONSE");
1393
+ }
1394
+ return parsed;
1395
+ }
1396
+ function finishReason(stopReason, explanation, blocks) {
1397
+ const hasToolCall = blocks.some((block) => block.kind === "tool" && block.closed);
1398
+ const hasText = blocks.some((block) => block.kind === "text" && block.text.length > 0);
1399
+ switch (stopReason) {
1400
+ case "end_turn":
1401
+ case "stop_sequence":
1402
+ case "pause_turn":
1403
+ return hasText || hasToolCall ? { kind: "stop" } : failed(emptyResponseError().failure);
1404
+ case "tool_use":
1405
+ return hasToolCall ? { kind: "tool-calls" } : failed(emptyResponseError().failure);
1406
+ case "max_tokens":
1407
+ return { kind: "max-tokens" };
1408
+ case "refusal":
1409
+ return failed(refusalError(explanation).failure);
1410
+ case "model_context_window_exceeded":
1411
+ return failed({
1412
+ message: "Claude reached the end of its context window.",
1413
+ code: CONTEXT_WINDOW_EXCEEDED_CODE2
1414
+ });
1415
+ default:
1416
+ return failed({
1417
+ message: `Claude stopped with an unknown reason: ${stopReason}.`,
1418
+ code: "UNKNOWN_STOP_REASON"
1419
+ });
1420
+ }
1421
+ }
1422
+ function failed(failure) {
1423
+ return { kind: "error", failure };
1424
+ }
1425
+ function toReplayBlock(block) {
1426
+ switch (block.kind) {
1427
+ case "text":
1428
+ return block.text.length > 0 ? { type: "text", text: block.text } : { type: "dropped" };
1429
+ case "thinking":
1430
+ return block.signature.length > 0 ? { type: "thinking", thinking: block.thinking, signature: block.signature } : { type: "dropped" };
1431
+ case "redacted":
1432
+ return { type: "redacted_thinking", data: block.data };
1433
+ case "tool":
1434
+ return block.input === void 0 ? { type: "dropped" } : { type: "tool_use", id: block.id, name: block.name, input: block.input };
1435
+ }
1436
+ }
1437
+ function toTokenUsage(usage) {
1438
+ const inputTokens = usage.input_tokens ?? 0;
1439
+ const outputTokens = usage.output_tokens ?? 0;
1440
+ const cacheReadTokens = usage.cache_read_input_tokens ?? 0;
1441
+ const cacheWriteTokens = usage.cache_creation_input_tokens ?? 0;
1442
+ const reasoningTokens = usage.output_tokens_details?.thinking_tokens ?? 0;
1443
+ return {
1444
+ inputTokens,
1445
+ outputTokens,
1446
+ totalTokens: inputTokens + outputTokens + cacheReadTokens + cacheWriteTokens,
1447
+ ...cacheReadTokens > 0 ? { cacheReadTokens } : {},
1448
+ ...cacheWriteTokens > 0 ? { cacheWriteTokens } : {},
1449
+ ...reasoningTokens > 0 ? { reasoningTokens } : {}
1450
+ };
1451
+ }
1452
+
1453
+ // ../dsh-plugin-claude-subscription/src/step.ts
1454
+ var STEP_MAX_RETRIES = 0;
1455
+ async function* runStep(request, services) {
1456
+ const appended = [];
1457
+ const abortController = new AbortController();
1458
+ const handle = new QueryHandle(
1459
+ startQuery(request, services, abortController, (entries) => appended.push(...entries)),
1460
+ abortController,
1461
+ services.tracker
1462
+ );
1463
+ const mapper = createStreamMapper();
1464
+ const frames = new FrameFacts();
1465
+ const aborted = abortedPromise(request.signal);
1466
+ let emittedOutput = false;
1467
+ const fail = (error, expiredLogin = false) => {
1468
+ handle.settle("drain");
1469
+ return { kind: "failed", error, expiredLogin, emittedOutput };
1470
+ };
1471
+ try {
1472
+ while (true) {
1473
+ let next;
1474
+ try {
1475
+ next = await Promise.race([handle.next(), aborted.promise]);
1476
+ } catch (error) {
1477
+ if (!request.signal?.aborted && !handle.hardStopped) return fail(mapThrownError(error));
1478
+ next = ABORTED;
1479
+ }
1480
+ if (next === ABORTED || next.done && handle.hardStopped) {
1481
+ handle.settle("interrupt");
1482
+ yield* abortedChunks(mapper);
1483
+ return { kind: "completed" };
1484
+ }
1485
+ if (next.done) {
1486
+ return fail(new LlmError6("Claude Code ended the stream with no result.", "STREAM_CLOSED"));
1487
+ }
1488
+ const message = next.value;
1489
+ if (message.type === "system" && message.subtype === "init") {
1490
+ if (message.apiKeySource !== "none") {
1491
+ void handle.hardStop();
1492
+ return fail(apiKeyDuringStepError(message.apiKeySource));
1493
+ }
1494
+ } else if (message.type === "result") {
1495
+ if (!message.is_error) return fail(emptyResponseError());
1496
+ const failure = frames.failureFrom(message);
1497
+ return fail(mapStepFailure(failure), isExpiredLogin(failure));
1498
+ } else if (message.type === "stream_event") {
1499
+ if (message.parent_tool_use_id !== null) continue;
1500
+ let chunks;
1501
+ try {
1502
+ chunks = mapper.onEvent(message.event);
1503
+ } catch (error) {
1504
+ handle.settle("interrupt");
1505
+ return fail(mapThrownError(error));
1506
+ }
1507
+ for (const chunk of chunks) {
1508
+ emittedOutput = true;
1509
+ yield chunk;
1510
+ }
1511
+ if (mapper.stopReason !== null) {
1512
+ handle.settle(needsInterrupt(mapper.stopReason) ? "interrupt" : "drain");
1513
+ yield* finishChunks(mapper, request.model, appended, frames.assistantErrors, services);
1514
+ return { kind: "completed" };
1515
+ }
1516
+ } else {
1517
+ frames.observe(message);
1518
+ }
1519
+ }
1520
+ } finally {
1521
+ aborted.dispose();
1522
+ handle.settle("interrupt");
1523
+ }
1524
+ }
1525
+ function* abortedChunks(mapper) {
1526
+ if (mapper !== void 0) yield* mapper.usageChunks();
1527
+ yield {
1528
+ type: "finish",
1529
+ reason: { kind: "aborted", failure: { message: "The request was aborted.", code: "ABORTED" } }
1530
+ };
1531
+ }
1532
+ function startQuery(request, services, abortController, onAppend) {
1533
+ return query3({
1534
+ // Empty on purpose: the whole history is in the transcript, and an empty prompt adds no text.
1535
+ prompt: "",
1536
+ options: {
1537
+ ...buildStepOptions({
1538
+ model: request.model,
1539
+ systemPrompt: request.systemPrompt,
1540
+ tools: request.tools,
1541
+ sessionId: randomUUID3(),
1542
+ entries: request.entries,
1543
+ cwd: services.cwd(),
1544
+ env: buildChildEnv(services.processEnv, {
1545
+ tempDir: services.tempDir.path,
1546
+ maxRetries: STEP_MAX_RETRIES,
1547
+ ...request.maxOutputTokens === void 0 ? {} : { maxOutputTokens: request.maxOutputTokens }
1548
+ }),
1549
+ abortController,
1550
+ ...request.effort === void 0 ? {} : { effort: request.effort },
1551
+ onAppend,
1552
+ onLoad: services.tempDir.redirectSdkTempDir
1553
+ }),
1554
+ ...services.tracker.childOptions()
1555
+ }
1556
+ });
1557
+ }
1558
+ var FrameFacts = class {
1559
+ /** An error that ends the step, or, when the step still succeeds, a problem the CLI healed. */
1560
+ assistantErrors = [];
1561
+ rateLimit;
1562
+ observe(message) {
1563
+ if (message.type === "rate_limit_event") {
1564
+ this.rateLimit = message.rate_limit_info;
1565
+ } else if (message.type === "assistant" && message.error !== void 0 && message.parent_tool_use_id === null) {
1566
+ this.assistantErrors.push({
1567
+ kind: message.error,
1568
+ text: message.message.content.map((block) => block.type === "text" ? block.text : "").join("")
1569
+ });
1570
+ }
1571
+ }
1572
+ failureFrom(result) {
1573
+ const text = "result" in result ? result.result : result.errors.join("; ");
1574
+ const status = "api_error_status" in result ? result.api_error_status : null;
1575
+ return {
1576
+ ...this.assistantErrors.length > 0 ? { assistantError: this.assistantErrors.at(-1)?.kind } : {},
1577
+ status: status ?? null,
1578
+ terminalReason: result.terminal_reason ?? null,
1579
+ text: text || result.subtype,
1580
+ ...this.rateLimit === void 0 ? {} : { rateLimit: this.rateLimit }
1581
+ };
1582
+ }
1583
+ };
1584
+ function* finishChunks(mapper, model, appended, healedErrors, services) {
1585
+ const chunks = mapper.finish({
1586
+ model,
1587
+ attachments: attachmentsToReplay(appended),
1588
+ healedErrors
1589
+ });
1590
+ const finish = chunks.at(-1);
1591
+ if (finish?.type === "finish" && finish.reason.kind !== "error") {
1592
+ for (const healed of healedErrors) {
1593
+ services.logger.warn(
1594
+ `claude-subscription: Claude Code recovered from a problem on ${model}: ${healed.text}`
1595
+ );
1596
+ }
1597
+ }
1598
+ yield* chunks;
1599
+ }
1600
+ function abortedPromise(signal) {
1601
+ if (signal === void 0) return { promise: new Promise(() => {
1602
+ }), dispose() {
1603
+ } };
1604
+ let onAbort = () => {
1605
+ };
1606
+ const promise = new Promise((resolve) => {
1607
+ onAbort = () => resolve(ABORTED);
1608
+ if (signal.aborted) onAbort();
1609
+ else signal.addEventListener("abort", onAbort, { once: true });
1610
+ });
1611
+ return { promise, dispose: () => signal.removeEventListener("abort", onAbort) };
1612
+ }
1613
+
1614
+ // ../dsh-plugin-claude-subscription/src/adapter.ts
1615
+ var ClaudeSubscriptionAdapter = class extends LlmAdapter {
1616
+ constructor(services) {
1617
+ super();
1618
+ this.services = services;
1619
+ this.stepServices = { ...services, tracker: this.tracker };
1620
+ this.loginCheck = new LoginCheck(this.stepServices);
1621
+ this.refresher = new LoginRefresher(this.stepServices);
1622
+ }
1623
+ services;
1624
+ tracker = new QueryTracker();
1625
+ stepServices;
1626
+ loginCheck;
1627
+ refresher;
1628
+ providerInfo(provider) {
1629
+ return { id: provider, name: "Claude (subscription)" };
1630
+ }
1631
+ imageRequestTarget(_provider, _model, image) {
1632
+ return imageRequestTarget(image);
1633
+ }
1634
+ async listModels(_provider) {
1635
+ return listModelInfo();
1636
+ }
1637
+ async resolveModel(_provider, model) {
1638
+ return resolveModelInfo(model);
1639
+ }
1640
+ async *stream(options) {
1641
+ const { signal } = options;
1642
+ this.tracker.assertOpen();
1643
+ if (signal?.aborted) {
1644
+ yield* abortedChunks();
1645
+ return;
1646
+ }
1647
+ const request = await this.buildStepRequest(options);
1648
+ this.tracker.assertOpen();
1649
+ const loginProblem = await this.loginCheck.check(signal);
1650
+ if (loginProblem === ABORTED) {
1651
+ yield* abortedChunks();
1652
+ return;
1653
+ }
1654
+ if (loginProblem !== void 0) throw loginProblem;
1655
+ let refreshed = false;
1656
+ while (true) {
1657
+ this.tracker.assertOpen();
1658
+ if (signal?.aborted) {
1659
+ yield* abortedChunks();
1660
+ return;
1661
+ }
1662
+ const outcome = yield* runStep(request, this.stepServices);
1663
+ if (outcome.kind === "completed") return;
1664
+ if (!outcome.expiredLogin) throw outcome.error;
1665
+ if (refreshed || outcome.emittedOutput) throw loginRequiredError();
1666
+ refreshed = true;
1667
+ const refreshProblem = await this.refresher.refresh(signal);
1668
+ if (refreshProblem === ABORTED) {
1669
+ yield* abortedChunks();
1670
+ return;
1671
+ }
1672
+ if (refreshProblem !== void 0) throw refreshProblem;
1673
+ }
1674
+ }
1675
+ /** Ends every query still running. No step can start after this. */
1676
+ dispose() {
1677
+ return this.tracker.dispose();
1678
+ }
1679
+ async buildStepRequest(options) {
1680
+ if (options.temperature !== void 0) throw unsupportedOptionError("temperature");
1681
+ if (options.stop !== void 0) throw unsupportedOptionError("stop");
1682
+ const model = requireClaudeModel(options.model);
1683
+ const effort = parseEffort(options.reasoningEffort);
1684
+ const { systemPrompt, messages } = splitSystemPrompt(options);
1685
+ const images = await prepareRequestImages(messages, this.services, options.signal);
1686
+ const entries = buildTranscript(images.messages, {
1687
+ model: model.id,
1688
+ renderImage: images.renderImage,
1689
+ onReplayDegraded: (reason) => {
1690
+ this.services.logger.warn(
1691
+ `claude-subscription: sending an assistant message without its thinking: ${reason}`
1692
+ );
1693
+ }
1694
+ });
1695
+ return {
1696
+ model: model.id,
1697
+ systemPrompt,
1698
+ entries,
1699
+ tools: options.tools ?? [],
1700
+ ...effort === void 0 ? {} : { effort },
1701
+ ...options.maxTokens === void 0 ? {} : { maxOutputTokens: options.maxTokens },
1702
+ ...options.signal === void 0 ? {} : { signal: options.signal }
1703
+ };
1704
+ }
1705
+ };
1706
+
1707
+ // ../dsh-plugin-claude-subscription/src/index.ts
1708
+ var name = "claude-subscription";
1709
+ var inject = ["llm"];
1710
+ function apply(ctx) {
1711
+ const tempDir = createPrivateTempDir({
1712
+ onRemoveFailed: (error) => ctx.logger.warn(`claude-subscription: could not remove the private temp dir: ${error}`)
1713
+ });
1714
+ const adapter = new ClaudeSubscriptionAdapter({
1715
+ processEnv: process.env,
1716
+ cwd: () => process.cwd(),
1717
+ tempDir,
1718
+ logger: { warn: (message) => ctx.logger.warn(message) },
1719
+ attachments: () => ctx.get("attachments"),
1720
+ mapHostPath: (hostPath) => ctx.get("fs")?.processPathFromHostPath(hostPath)
1721
+ });
1722
+ ctx.llm.registerAdapter([PROVIDER_ID], adapter);
1723
+ ctx.effect(
1724
+ () => async () => {
1725
+ await adapter.dispose();
1726
+ tempDir.dispose();
1727
+ },
1728
+ "claude-subscription: running queries and temp dir"
1729
+ );
1730
+ }
1731
+ export {
1732
+ apply,
1733
+ inject,
1734
+ name
1735
+ };