@demicodes/provider-claude-code 0.10.2 → 0.10.3

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/README.md CHANGED
@@ -5,38 +5,8 @@ and `listClaudeCodeModels()`.
5
5
 
6
6
  ```ts
7
7
  import { createClaudeCodeProvider, listClaudeCodeModels } from '@demicodes/provider-claude-code'
8
-
9
- const provider = createClaudeCodeProvider()
10
- // provider.auth, provider.quota, provider.credentials (multi-account pool by default)
11
8
  ```
12
9
 
13
- ## Auth and credentials
14
-
15
- - Default material: `CLAUDE_CODE_OAUTH_TOKEN` or macOS Keychain (`Claude Code-credentials`).
16
- - Multi-credential pool under `$DEMI_HOME/credentials/claude-code/`; active token is
17
- injected into the CLI child as `CLAUDE_CODE_OAUTH_TOKEN`.
18
- - Lifecycle: `beginLogin` → `claude auth login` → `importDefault` / `add` → `setActive`.
19
- - Changing active credential forces a cold restart of a long-lived CLI process.
20
-
21
- See [docs/provider-global-credentials.md](../../docs/provider-global-credentials.md).
22
-
23
- ## Quota
24
-
25
- - **probe** (cost: `free`): `GET /api/oauth/usage` with the active OAuth token.
26
- - **observe**: stream-json `rate_limits` bodies (and unified rate-limit headers when present).
27
-
28
- See [docs/provider-quota.md](../../docs/provider-quota.md).
29
-
30
- ## Tool-call batches
31
-
32
- Claude Code's SDK-MCP control channel can hold later `tools/call` callbacks until
33
- the preceding callback receives a result, even when the model emitted several
34
- `tool_use` blocks in one response. The provider preserves the model's original
35
- batch and tool-use IDs for the host scheduler, then matches the completed
36
- results to SDK-MCP callbacks as the CLI releases them.
37
-
38
- See [docs/tool-call-concurrency.md](../../docs/tool-call-concurrency.md).
39
-
40
10
  > Diagnostics: the transport writes a raw request/response wire log (including
41
11
  > prompts) to `$TMPDIR/demi-claude-wire` by default. Disable with
42
12
  > `DEMI_CLAUDE_WIRE_LOG=0`. See [SECURITY](../../SECURITY.md).
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { ModelPolicy, Provider, ProviderAuthState, ProviderCredentials, ProviderModelList, ProviderQuota, ProviderQuotaProbeResult } from "@demicodes/provider";
2
- import { FileCredentialPool } from "@demicodes/provider/credentials-pool";
1
+ import { ModelPolicy, Provider, ProviderModelList, ProviderQuota, ProviderQuotaProbeResult } from "@demicodes/provider";
2
+
3
3
  //#region src/models.d.ts
4
4
  interface ClaudeCodeModelCatalogOptions {
5
5
  fetch?: ModelCatalogFetch;
@@ -10,74 +10,12 @@ interface ClaudeCodeModelCatalogOptions {
10
10
  type ModelCatalogFetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
11
11
  declare function listClaudeCodeModels(options?: ClaudeCodeModelCatalogOptions): Promise<ProviderModelList>;
12
12
  //#endregion
13
- //#region src/oauth.d.ts
14
- /**
15
- * Where a resolved token came from. Callers that inject the token into a
16
- * spawned CLI must skip `keychain`: that token is the CLI's own short-lived
17
- * credential, and injecting it via CLAUDE_CODE_OAUTH_TOKEN disables the CLI's
18
- * refresh flow — the run starts 401ing as soon as the token expires. Owned
19
- * sources (static/file/env) are the caller's responsibility and inject as-is.
20
- */
21
- type ClaudeCodeOAuthSource = 'static' | 'file' | 'env' | 'keychain';
22
- interface ClaudeCodeOAuthAccess {
23
- accessToken: string;
24
- source: ClaudeCodeOAuthSource;
25
- subscriptionType?: string | null;
26
- rateLimitTier?: string | null;
27
- }
28
- /**
29
- * Resolve Claude Code consumer OAuth access for quota APIs.
30
- * Order: options env CLAUDE_CODE_OAUTH_TOKEN, then macOS Keychain "Claude Code-credentials".
31
- * Prefer injecting {@link ClaudeCodeAuthStore} when multi-credential is enabled.
32
- */
33
- declare function resolveClaudeCodeOAuthAccess(): Promise<ClaudeCodeOAuthAccess | null>;
34
- //#endregion
35
- //#region src/auth.d.ts
36
- interface ClaudeCodeAuthStore {
37
- status(): Promise<ProviderAuthState>;
38
- resolveAccess(options?: {
39
- forceRefresh?: boolean;
40
- }): Promise<ClaudeCodeOAuthAccess>;
41
- }
42
- /** Renews a pool oauth secret (wired to `refreshClaudeCodeSecret`; injectable in tests). */
43
- type ClaudeCodeSecretRefresh = (secret: Record<string, unknown>) => Promise<Record<string, unknown>>;
44
- interface FileClaudeCodeAuthStoreOptions {
45
- /** Optional path to oauth.json (pool entry). */
46
- oauthFile?: string;
47
- /** Prefer this token over env/keychain when set (tests / static). */
48
- accessToken?: string | null;
49
- /** Renews the oauth file when its access token nears expiry. */
50
- refresh?: ClaudeCodeSecretRefresh;
51
- }
52
- /**
53
- * Resolves Claude OAuth: explicit token → oauth file → CLAUDE_CODE_OAUTH_TOKEN → keychain.
54
- */
55
- declare class FileClaudeCodeAuthStore implements ClaudeCodeAuthStore {
56
- private readonly oauthFile;
57
- private readonly accessToken;
58
- private readonly refresh;
59
- constructor(options?: FileClaudeCodeAuthStoreOptions);
60
- status(): Promise<ProviderAuthState>;
61
- resolveAccess(): Promise<ClaudeCodeOAuthAccess>;
62
- }
63
- declare class StaticClaudeCodeAuthStore implements ClaudeCodeAuthStore {
64
- private readonly access;
65
- constructor(access: ClaudeCodeOAuthAccess);
66
- status(): Promise<ProviderAuthState>;
67
- resolveAccess(): Promise<ClaudeCodeOAuthAccess>;
68
- }
69
- //#endregion
70
13
  //#region src/provider.d.ts
71
14
  interface ClaudeCodeProviderOptions {
72
15
  id?: string;
73
16
  displayName?: string;
74
17
  claudePath?: string;
75
18
  models?: ModelPolicy;
76
- /** Demi state root for credential pool (`$DEMI_HOME` / `~/.demi`). */
77
- stateDir?: string;
78
- /** When true (default), attach multi-credential pool + global switch. */
79
- credentials?: boolean;
80
- authStore?: ClaudeCodeAuthStore;
81
19
  }
82
20
  declare function createClaudeCodeProvider(options?: ClaudeCodeProviderOptions): Provider;
83
21
  //#endregion
@@ -89,6 +27,18 @@ declare function createClaudeCodeProvider(options?: ClaudeCodeProviderOptions):
89
27
  */
90
28
  declare function resolveWireLogDir(): string | null;
91
29
  //#endregion
30
+ //#region src/oauth.d.ts
31
+ interface ClaudeCodeOAuthAccess {
32
+ accessToken: string;
33
+ subscriptionType?: string | null;
34
+ rateLimitTier?: string | null;
35
+ }
36
+ /**
37
+ * Resolve Claude Code consumer OAuth access for quota APIs.
38
+ * Order: options env CLAUDE_CODE_OAUTH_TOKEN, then macOS Keychain "Claude Code-credentials".
39
+ */
40
+ declare function resolveClaudeCodeOAuthAccess(): Promise<ClaudeCodeOAuthAccess | null>;
41
+ //#endregion
92
42
  //#region src/quota.d.ts
93
43
  interface ClaudeCodeQuotaOptions {
94
44
  providerId?: string;
@@ -108,56 +58,4 @@ declare function mapClaudeUsagePayload(payload: unknown, access?: ClaudeCodeOAut
108
58
  /** Map anthropic-ratelimit-unified-* headers into a coarse snapshot. */
109
59
  declare function observeClaudeRateLimitHeaders(headers: Headers | undefined): ProviderQuotaProbeResult | null;
110
60
  //#endregion
111
- //#region src/credentials.d.ts
112
- declare class PoolAwareClaudeCodeAuthStore implements ClaudeCodeAuthStore {
113
- private readonly pool;
114
- constructor(pool: FileCredentialPool);
115
- status(): Promise<import("@demicodes/provider").ProviderAuthState>;
116
- resolveAccess(): Promise<ClaudeCodeOAuthAccess>;
117
- private currentStore;
118
- }
119
- declare function openClaudeCodeCredentialPool(options?: {
120
- stateDir?: string;
121
- }): FileCredentialPool;
122
- declare function createClaudeCodeCredentials(pool: FileCredentialPool, authStore: ClaudeCodeAuthStore, options?: {
123
- quota?: ProviderQuota | null;
124
- onActiveChange?: () => void;
125
- /** Injectable fetch for the OAuth login flow (tests). */
126
- loginFetch?: typeof fetch;
127
- }): ProviderCredentials;
128
- //#endregion
129
- //#region src/login.d.ts
130
- /** Pool-entry oauth.json shape (demi-owned; refreshable when refreshToken present). */
131
- interface ClaudeCodeOAuthSecret {
132
- accessToken: string;
133
- refreshToken?: string | null;
134
- /** ISO-8601 access token expiry. */
135
- expiresAt?: string | null;
136
- scopes?: string[] | null;
137
- subscriptionType?: string | null;
138
- rateLimitTier?: string | null;
139
- emailAddress?: string | null;
140
- [key: string]: unknown;
141
- }
142
- interface ClaudeCodeLoginOptions {
143
- signal?: AbortSignal;
144
- /** Fires once with the authorize URL; the flow then waits on promptForCode. */
145
- onPending?: (pending: {
146
- verificationUrl: string;
147
- requiresCodeInput: true;
148
- }) => void;
149
- /** Collects the "code#state" string the vendor page shows after approval. */
150
- promptForCode: () => Promise<string>;
151
- fetch?: typeof fetch;
152
- consoleBase?: string;
153
- }
154
- /** Runs the copy-back OAuth flow and returns a refreshable pool secret. */
155
- declare function runClaudeCodeLogin(options: ClaudeCodeLoginOptions): Promise<ClaudeCodeOAuthSecret>;
156
- /** Refreshes a pool secret in place; returns the renewed secret. */
157
- declare function refreshClaudeCodeSecret(secret: ClaudeCodeOAuthSecret, options?: {
158
- fetch?: typeof fetch;
159
- consoleBase?: string;
160
- signal?: AbortSignal;
161
- }): Promise<ClaudeCodeOAuthSecret>;
162
- //#endregion
163
- export { type ClaudeCodeAuthStore, type ClaudeCodeLoginOptions, type ClaudeCodeModelCatalogOptions, type ClaudeCodeOAuthAccess, type ClaudeCodeOAuthSecret, type ClaudeCodeProviderOptions, type ClaudeCodeQuotaOptions, FileClaudeCodeAuthStore, PoolAwareClaudeCodeAuthStore, StaticClaudeCodeAuthStore, createClaudeCodeCredentials, createClaudeCodeProvider, createClaudeCodeQuota, listClaudeCodeModels, mapClaudeUsagePayload, observeClaudeRateLimitHeaders, observeClaudeStreamBody, openClaudeCodeCredentialPool, refreshClaudeCodeSecret, resolveClaudeCodeOAuthAccess, resolveWireLogDir, runClaudeCodeLogin };
61
+ export { type ClaudeCodeModelCatalogOptions, type ClaudeCodeOAuthAccess, type ClaudeCodeProviderOptions, type ClaudeCodeQuotaOptions, createClaudeCodeProvider, createClaudeCodeQuota, listClaudeCodeModels, mapClaudeUsagePayload, observeClaudeRateLimitHeaders, observeClaudeStreamBody, resolveClaudeCodeOAuthAccess, resolveWireLogDir };
package/dist/index.mjs CHANGED
@@ -1,12 +1,10 @@
1
- import { abortable, errorMessage, isRecord, nonEmptyString, numberOrNull, stringOrNull } from "@demicodes/utils";
2
- import { createHash, randomBytes, randomUUID } from "node:crypto";
3
- import { applyModelPolicy, clampUsedPercent, createProviderQuota, defineProvider, numberHeader, severityFromUsedPercent, toolResultContentToText, unixSecondsToIso } from "@demicodes/provider";
1
+ import { errorMessage, isRecord, nonEmptyString, numberOrNull, stringOrNull } from "@demicodes/utils";
2
+ import { randomUUID } from "node:crypto";
3
+ import { applyModelPolicy, clampUsedPercent, createProviderQuota, defineProvider, severityFromUsedPercent, unixSecondsToIso } from "@demicodes/provider";
4
+ import { Buffer as Buffer$1 } from "node:buffer";
4
5
  import { execFile, spawn } from "node:child_process";
5
- import { readFile, writeFile } from "node:fs/promises";
6
6
  import { promisify } from "node:util";
7
7
  import process$1 from "node:process";
8
- import { FileCredentialPool, credentialIdFromIdentity } from "@demicodes/provider/credentials-pool";
9
- import { Buffer as Buffer$1 } from "node:buffer";
10
8
  import { appendFileSync, mkdirSync, statSync } from "node:fs";
11
9
  import { createInterface } from "node:readline";
12
10
  import { tmpdir } from "node:os";
@@ -97,8 +95,7 @@ const CLAUDE_FAMILY_RANK = {
97
95
  haiku: 2
98
96
  };
99
97
  function claudeFamilyRank(id) {
100
- const family = id.slice(7).split("-")[0] ?? "";
101
- return CLAUDE_FAMILY_RANK[family] ?? 3;
98
+ return CLAUDE_FAMILY_RANK[id.slice(7).split("-")[0] ?? ""] ?? 3;
102
99
  }
103
100
  /** Canonical catalog order: flagship family first (Opus > Sonnet > Haiku > others), newest version first. */
104
101
  function compareClaudeModels(a, b) {
@@ -200,377 +197,6 @@ function reasoningEfforts(value) {
200
197
  return efforts.length > 0 ? efforts : [];
201
198
  }
202
199
  //#endregion
203
- //#region src/auth.ts
204
- const execFileAsync = promisify(execFile);
205
- const OAUTH_EXPIRY_SKEW_MS = 300 * 1e3;
206
- /**
207
- * Resolves Claude OAuth: explicit token → oauth file → CLAUDE_CODE_OAUTH_TOKEN → keychain.
208
- */
209
- var FileClaudeCodeAuthStore = class {
210
- oauthFile;
211
- accessToken;
212
- refresh;
213
- constructor(options = {}) {
214
- this.oauthFile = options.oauthFile ?? null;
215
- this.accessToken = nonEmptyString(options.accessToken) ?? null;
216
- this.refresh = options.refresh ?? null;
217
- }
218
- async status() {
219
- try {
220
- return {
221
- status: "authenticated",
222
- accountLabel: nonEmptyString((await this.resolveAccess()).subscriptionType) ?? "Claude Code"
223
- };
224
- } catch (error) {
225
- if (error instanceof ClaudeCodeAuthError && error.code === "auth_missing") return {
226
- status: "unauthenticated",
227
- message: error.message
228
- };
229
- return {
230
- status: "error",
231
- message: error instanceof Error ? error.message : String(error)
232
- };
233
- }
234
- }
235
- async resolveAccess() {
236
- if (this.accessToken) return {
237
- accessToken: this.accessToken,
238
- source: "static"
239
- };
240
- if (this.oauthFile) try {
241
- let raw = JSON.parse(await readFile(this.oauthFile, "utf8"));
242
- if (!isRecord(raw)) throw new ClaudeCodeAuthError("auth_invalid", `Invalid OAuth file: ${this.oauthFile}`);
243
- const expiresAt = nonEmptyString(raw.expiresAt);
244
- if (expiresAt !== void 0 && Date.parse(expiresAt) - Date.now() < OAUTH_EXPIRY_SKEW_MS && nonEmptyString(raw.refreshToken) && this.refresh) {
245
- raw = await this.refresh(raw);
246
- await writeFile(this.oauthFile, `${JSON.stringify(raw, null, 2)}\n`);
247
- }
248
- if (!isRecord(raw)) throw new ClaudeCodeAuthError("auth_invalid", `Invalid OAuth file: ${this.oauthFile}`);
249
- const accessToken = nonEmptyString(raw.accessToken) ?? nonEmptyString(raw.access_token);
250
- if (!accessToken) throw new ClaudeCodeAuthError("auth_missing", `No accessToken in ${this.oauthFile}`);
251
- return {
252
- accessToken,
253
- source: "file",
254
- subscriptionType: nonEmptyString(raw.subscriptionType) ?? null,
255
- rateLimitTier: nonEmptyString(raw.rateLimitTier) ?? null
256
- };
257
- } catch (error) {
258
- if (error instanceof ClaudeCodeAuthError) throw error;
259
- throw new ClaudeCodeAuthError("auth_missing", `Failed to read Claude OAuth file ${this.oauthFile}: ${error instanceof Error ? error.message : String(error)}`);
260
- }
261
- const fromEnv = nonEmptyString(process$1.env.CLAUDE_CODE_OAUTH_TOKEN);
262
- if (fromEnv) return {
263
- accessToken: fromEnv,
264
- source: "env"
265
- };
266
- if (process$1.platform === "darwin") try {
267
- const { stdout } = await execFileAsync("security", [
268
- "find-generic-password",
269
- "-s",
270
- "Claude Code-credentials",
271
- "-w"
272
- ], {
273
- encoding: "utf8",
274
- timeout: 5e3
275
- });
276
- const parsed = JSON.parse(stdout.trim());
277
- if (!isRecord(parsed)) throw new ClaudeCodeAuthError("auth_missing", "Claude Code keychain item is not a JSON object");
278
- const oauth = isRecord(parsed.claudeAiOauth) ? parsed.claudeAiOauth : null;
279
- if (!oauth) throw new ClaudeCodeAuthError("auth_missing", "Claude Code keychain missing claudeAiOauth");
280
- const accessToken = nonEmptyString(oauth.accessToken);
281
- if (!accessToken) throw new ClaudeCodeAuthError("auth_missing", "Claude Code keychain missing accessToken");
282
- return {
283
- accessToken,
284
- source: "keychain",
285
- subscriptionType: nonEmptyString(oauth.subscriptionType) ?? null,
286
- rateLimitTier: nonEmptyString(oauth.rateLimitTier) ?? null
287
- };
288
- } catch (error) {
289
- if (error instanceof ClaudeCodeAuthError) throw error;
290
- }
291
- throw new ClaudeCodeAuthError("auth_missing", "Claude Code OAuth access token not found (set CLAUDE_CODE_OAUTH_TOKEN or log in with Claude Code)");
292
- }
293
- };
294
- var StaticClaudeCodeAuthStore = class {
295
- access;
296
- constructor(access) {
297
- this.access = access;
298
- }
299
- async status() {
300
- return {
301
- status: "authenticated",
302
- accountLabel: nonEmptyString(this.access.subscriptionType) ?? "Claude Code"
303
- };
304
- }
305
- async resolveAccess() {
306
- return this.access;
307
- }
308
- };
309
- var ClaudeCodeAuthError = class extends Error {
310
- code;
311
- constructor(code, message) {
312
- super(message);
313
- this.code = code;
314
- this.name = "ClaudeCodeAuthError";
315
- }
316
- };
317
- //#endregion
318
- //#region src/login.ts
319
- const CLAUDE_AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
320
- const CLAUDE_CONSOLE_BASE = "https://console.anthropic.com";
321
- const CLAUDE_CODE_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
322
- const CLAUDE_LOGIN_SCOPE = "org:create_api_key user:profile user:inference";
323
- function tokenEndpoint(consoleBase) {
324
- return `${consoleBase.replace(/\/+$/, "")}/v1/oauth/token`;
325
- }
326
- async function requestTokens(fetchImpl, consoleBase, body, signal) {
327
- const response = await fetchImpl(tokenEndpoint(consoleBase), {
328
- method: "POST",
329
- headers: { "content-type": "application/json" },
330
- body: JSON.stringify(body),
331
- signal
332
- });
333
- if (!response.ok) throw new ClaudeCodeAuthError("auth_invalid", `Claude OAuth token request failed with HTTP ${response.status}`);
334
- const parsed = await response.json().catch(() => null);
335
- if (!isRecord(parsed)) throw new ClaudeCodeAuthError("auth_invalid", "Claude OAuth token response is not a JSON object");
336
- const accessToken = nonEmptyString(parsed.access_token);
337
- if (!accessToken) throw new ClaudeCodeAuthError("auth_invalid", "Claude OAuth token response is missing access_token");
338
- const expiresIn = Number(parsed.expires_in);
339
- const account = isRecord(parsed.account) ? parsed.account : {};
340
- return {
341
- accessToken,
342
- refreshToken: nonEmptyString(parsed.refresh_token) ?? null,
343
- expiresAt: Number.isFinite(expiresIn) && expiresIn > 0 ? new Date(Date.now() + expiresIn * 1e3).toISOString() : null,
344
- scopes: typeof parsed.scope === "string" ? parsed.scope.split(" ").filter(Boolean) : null,
345
- subscriptionType: nonEmptyString(parsed.subscription_type) ?? nonEmptyString(account.subscription_type) ?? null,
346
- ...nonEmptyString(account.email_address) ? { emailAddress: nonEmptyString(account.email_address) } : {}
347
- };
348
- }
349
- /** Runs the copy-back OAuth flow and returns a refreshable pool secret. */
350
- async function runClaudeCodeLogin(options) {
351
- const fetchImpl = options.fetch ?? fetch;
352
- const consoleBase = options.consoleBase ?? CLAUDE_CONSOLE_BASE;
353
- const verifier = randomBytes(32).toString("base64url");
354
- const challenge = createHash("sha256").update(verifier).digest("base64url");
355
- const state = randomBytes(32).toString("base64url");
356
- const redirectUri = `${consoleBase.replace(/\/+$/, "")}/oauth/code/callback`;
357
- const params = new URLSearchParams({
358
- code: "true",
359
- client_id: CLAUDE_CODE_CLIENT_ID,
360
- response_type: "code",
361
- redirect_uri: redirectUri,
362
- scope: CLAUDE_LOGIN_SCOPE,
363
- code_challenge: challenge,
364
- code_challenge_method: "S256",
365
- state
366
- });
367
- options.onPending?.({
368
- verificationUrl: `${CLAUDE_AUTHORIZE_URL}?${params.toString()}`,
369
- requiresCodeInput: true
370
- });
371
- const pasted = (await options.promptForCode()).trim();
372
- if (!pasted) throw new ClaudeCodeAuthError("auth_invalid", "Empty authorization code");
373
- const [code, returnedState] = pasted.split("#");
374
- if (!nonEmptyString(code)) throw new ClaudeCodeAuthError("auth_invalid", "Authorization code is missing the code part");
375
- if (returnedState && returnedState !== state) throw new ClaudeCodeAuthError("auth_invalid", "Authorization code state mismatch — copy the full string from the callback page");
376
- return requestTokens(fetchImpl, consoleBase, {
377
- grant_type: "authorization_code",
378
- code,
379
- state: returnedState ?? state,
380
- client_id: CLAUDE_CODE_CLIENT_ID,
381
- redirect_uri: redirectUri,
382
- code_verifier: verifier
383
- }, options.signal);
384
- }
385
- /** Refreshes a pool secret in place; returns the renewed secret. */
386
- async function refreshClaudeCodeSecret(secret, options = {}) {
387
- const refreshToken = nonEmptyString(secret.refreshToken);
388
- if (!refreshToken) throw new ClaudeCodeAuthError("auth_missing", "Claude OAuth secret has no refreshToken to renew with");
389
- const renewed = await requestTokens(options.fetch ?? fetch, options.consoleBase ?? CLAUDE_CONSOLE_BASE, {
390
- grant_type: "refresh_token",
391
- refresh_token: refreshToken,
392
- client_id: CLAUDE_CODE_CLIENT_ID
393
- }, options.signal);
394
- return {
395
- ...secret,
396
- accessToken: renewed.accessToken,
397
- refreshToken: renewed.refreshToken ?? refreshToken,
398
- expiresAt: renewed.expiresAt ?? secret.expiresAt ?? null,
399
- scopes: renewed.scopes ?? secret.scopes ?? null,
400
- subscriptionType: renewed.subscriptionType ?? secret.subscriptionType ?? null,
401
- ...nonEmptyString(renewed.emailAddress) ? { emailAddress: renewed.emailAddress } : {}
402
- };
403
- }
404
- //#endregion
405
- //#region src/credentials.ts
406
- var PoolAwareClaudeCodeAuthStore = class {
407
- pool;
408
- constructor(pool) {
409
- this.pool = pool;
410
- }
411
- async status() {
412
- return this.currentStore().then((s) => s.status());
413
- }
414
- async resolveAccess() {
415
- return this.currentStore().then((s) => s.resolveAccess());
416
- }
417
- async currentStore() {
418
- await this.pool.ensureActivePointer();
419
- const activeId = await this.pool.getActiveId();
420
- if (activeId) return new FileClaudeCodeAuthStore({
421
- oauthFile: this.pool.secretPath(activeId),
422
- refresh: (secret) => refreshClaudeCodeSecret(secret)
423
- });
424
- return new FileClaudeCodeAuthStore();
425
- }
426
- };
427
- function openClaudeCodeCredentialPool(options = {}) {
428
- return new FileCredentialPool({
429
- stateDir: options.stateDir,
430
- providerKey: "claude-code",
431
- secretFileName: "oauth.json"
432
- });
433
- }
434
- function createClaudeCodeCredentials(pool, authStore, options = {}) {
435
- const capability = () => ({
436
- mode: "supported",
437
- canBeginLogin: true,
438
- canImportDefault: true,
439
- canAdd: true,
440
- multi: true
441
- });
442
- const getActive = async () => {
443
- await pool.ensureActivePointer();
444
- return {
445
- credentialId: await pool.getActiveId(),
446
- status: await authStore.status()
447
- };
448
- };
449
- const setActive = async (credentialId) => {
450
- await pool.setActiveId(credentialId);
451
- options.quota?.clearLatest?.();
452
- options.onActiveChange?.();
453
- return getActive();
454
- };
455
- const importAccess = async (access, source) => {
456
- const token = nonEmptyString(access.accessToken);
457
- if (!token) throw new ClaudeCodeAuthError("auth_missing", "No Claude access token to import");
458
- const identityKey = nonEmptyString(access.subscriptionType) != null ? `token:${createHash("sha256").update(token).digest("hex").slice(0, 16)}:${access.subscriptionType}` : `token:${createHash("sha256").update(token).digest("hex").slice(0, 16)}`;
459
- const label = nonEmptyString(access.subscriptionType) ?? `claude-${identityKey.slice(-8)}`;
460
- const id = (await pool.findByIdentityKey(identityKey))?.id ?? credentialIdFromIdentity(identityKey, label);
461
- const meta = {
462
- id,
463
- label,
464
- detail: nonEmptyString(access.rateLimitTier) ?? null,
465
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
466
- source,
467
- identityKey
468
- };
469
- const secret = {
470
- accessToken: token,
471
- subscriptionType: access.subscriptionType ?? null,
472
- rateLimitTier: access.rateLimitTier ?? null
473
- };
474
- await pool.writeEntry(meta, `${JSON.stringify(secret, null, 2)}\n`);
475
- if (!await pool.getActiveId()) await pool.setActiveId(id);
476
- options.quota?.clearLatest?.();
477
- options.onActiveChange?.();
478
- return {
479
- id: meta.id,
480
- label: meta.label,
481
- detail: meta.detail,
482
- updatedAt: meta.updatedAt
483
- };
484
- };
485
- const importSecret = async (secret, source) => {
486
- const email = nonEmptyString(secret.emailAddress);
487
- const identityKey = email ? `email:${email}` : `token:${createHash("sha256").update(secret.accessToken).digest("hex").slice(0, 16)}`;
488
- const label = email ?? nonEmptyString(secret.subscriptionType) ?? `claude-${identityKey.slice(-8)}`;
489
- const id = (await pool.findByIdentityKey(identityKey))?.id ?? credentialIdFromIdentity(identityKey, label);
490
- const meta = {
491
- id,
492
- label,
493
- detail: nonEmptyString(secret.subscriptionType) ?? nonEmptyString(secret.rateLimitTier) ?? null,
494
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
495
- source,
496
- identityKey
497
- };
498
- await pool.writeEntry(meta, `${JSON.stringify(secret, null, 2)}\n`);
499
- if (!await pool.getActiveId()) await pool.setActiveId(id);
500
- options.quota?.clearLatest?.();
501
- options.onActiveChange?.();
502
- return {
503
- id: meta.id,
504
- label: meta.label,
505
- detail: meta.detail,
506
- updatedAt: meta.updatedAt
507
- };
508
- };
509
- return {
510
- capability,
511
- list: () => pool.list(),
512
- getActive,
513
- setActive,
514
- beginLogin: async (loginOptions) => {
515
- if (!loginOptions?.promptForCode) return {
516
- status: "unavailable",
517
- message: "Claude login requires promptForCode to collect the pasted authorization code"
518
- };
519
- try {
520
- const secret = await runClaudeCodeLogin({
521
- signal: loginOptions.signal,
522
- onPending: loginOptions.onPending,
523
- promptForCode: loginOptions.promptForCode,
524
- fetch: options.loginFetch
525
- });
526
- return {
527
- status: "completed",
528
- credentialId: (await importSecret(secret, "login:oauth")).id
529
- };
530
- } catch (error) {
531
- if (loginOptions.signal?.aborted) return { status: "cancelled" };
532
- return {
533
- status: "failed",
534
- message: errorMessage(error)
535
- };
536
- }
537
- },
538
- importDefault: async () => {
539
- const vendor = new FileClaudeCodeAuthStore();
540
- let access;
541
- try {
542
- access = await vendor.resolveAccess();
543
- } catch {
544
- throw new ClaudeCodeAuthError("auth_missing", "No Claude Code OAuth to import. Run claude auth login or beginLogin first.");
545
- }
546
- return importAccess(access, "vendor:default");
547
- },
548
- add: async (input) => {
549
- if (typeof input.accessToken === "string") return importAccess({
550
- accessToken: input.accessToken,
551
- source: "static",
552
- subscriptionType: typeof input.subscriptionType === "string" ? input.subscriptionType : null,
553
- rateLimitTier: typeof input.rateLimitTier === "string" ? input.rateLimitTier : null
554
- }, "add:accessToken");
555
- if (isRecord(input.oauth) && typeof input.oauth.accessToken === "string") {
556
- const oauth = input.oauth;
557
- return importAccess({
558
- accessToken: oauth.accessToken,
559
- source: "static",
560
- subscriptionType: typeof oauth.subscriptionType === "string" ? oauth.subscriptionType : null,
561
- rateLimitTier: typeof oauth.rateLimitTier === "string" ? oauth.rateLimitTier : null
562
- }, "add:oauth");
563
- }
564
- throw new Error("Claude credentials.add expects accessToken or oauth.accessToken");
565
- },
566
- remove: async (credentialId) => {
567
- await pool.remove(credentialId);
568
- options.quota?.clearLatest?.();
569
- options.onActiveChange?.();
570
- }
571
- };
572
- }
573
- //#endregion
574
200
  //#region src/jsonl.ts
575
201
  /**
576
202
  * Builds the input messages used to prime a *fresh* Claude CLI process with prior
@@ -648,7 +274,7 @@ function renderToolUseText(toolName, input) {
648
274
  return `[Earlier in this conversation I called the tool ${toolName} with input: ${safeJson(input)}.`;
649
275
  }
650
276
  function renderToolResultText(toolName, item) {
651
- const body = toolResultContentToText(item.output);
277
+ const body = toolResultToText(item.output);
652
278
  const suffix = toolName ? ` from ${toolName}` : "";
653
279
  return item.isError ? `It returned an error${suffix}: ${body}]` : `It returned${suffix}: ${body}]`;
654
280
  }
@@ -713,10 +339,6 @@ function userContentToClaude(content) {
713
339
  type: "image",
714
340
  source: imageSourceToClaude(block.source)
715
341
  };
716
- if (block.type === "video") return {
717
- type: "text",
718
- text: "[video]"
719
- };
720
342
  if (block.type === "document") return documentSourceToClaude(block.source);
721
343
  return {
722
344
  type: "text",
@@ -749,30 +371,11 @@ function assistantItemToClaudeContent(item) {
749
371
  };
750
372
  }
751
373
  function toolResultToClaudeContent(item) {
752
- const hasImage = item.output.some((block) => block.type !== "text");
753
374
  return {
754
375
  type: "tool_result",
755
376
  tool_use_id: item.toolUseId,
756
377
  is_error: item.isError,
757
- content: hasImage ? item.output.map(toolResultBlockToClaude) : toolResultContentToText(item.output)
758
- };
759
- }
760
- function toolResultBlockToClaude(block) {
761
- if (block.type === "text") return {
762
- type: "text",
763
- text: block.text
764
- };
765
- if (block.type === "video") return {
766
- type: "text",
767
- text: `[video:${block.source.mediaType}]`
768
- };
769
- return {
770
- type: "image",
771
- source: {
772
- type: "base64",
773
- media_type: block.source.mediaType,
774
- data: block.source.data
775
- }
378
+ content: toolResultToText(item.output)
776
379
  };
777
380
  }
778
381
  function imageSourceToClaude(source) {
@@ -797,6 +400,9 @@ function documentSourceToClaude(source) {
797
400
  title: source.fileName
798
401
  };
799
402
  }
403
+ function toolResultToText(output) {
404
+ return output.map((block) => block.type === "text" ? block.text : `[image:${block.source.mediaType}]`).join("\n");
405
+ }
800
406
  function bytesToBase64(data) {
801
407
  return Buffer$1.from(data.buffer, data.byteOffset, data.byteLength).toString("base64");
802
408
  }
@@ -805,28 +411,6 @@ function toolNameToClaude(name) {
805
411
  return `mcp__main__${name}`;
806
412
  }
807
413
  //#endregion
808
- //#region src/oauth.ts
809
- /**
810
- * The token to inject into a spawned CLI as CLAUDE_CODE_OAUTH_TOKEN, or null
811
- * to let the CLI authenticate itself. Keychain-sourced tokens are never
812
- * injected — see {@link ClaudeCodeOAuthSource}.
813
- */
814
- function injectableCliToken(access) {
815
- return access.source === "keychain" ? null : access.accessToken;
816
- }
817
- /**
818
- * Resolve Claude Code consumer OAuth access for quota APIs.
819
- * Order: options env CLAUDE_CODE_OAUTH_TOKEN, then macOS Keychain "Claude Code-credentials".
820
- * Prefer injecting {@link ClaudeCodeAuthStore} when multi-credential is enabled.
821
- */
822
- async function resolveClaudeCodeOAuthAccess() {
823
- try {
824
- return await new FileClaudeCodeAuthStore().resolveAccess();
825
- } catch {
826
- return null;
827
- }
828
- }
829
- //#endregion
830
414
  //#region src/output.ts
831
415
  function mapClaudeStdoutMessage(message, options = {}) {
832
416
  const events = [];
@@ -834,7 +418,7 @@ function mapClaudeStdoutMessage(message, options = {}) {
834
418
  events,
835
419
  terminal: false
836
420
  };
837
- if (message.type === "assistant" && isRecord(message.message)) events.push(...mapContentArray(message.message.content, options));
421
+ if (message.type === "assistant" && isRecord(message.message) && !options.ignoreAssistantContent) events.push(...mapContentArray(message.message.content, options));
838
422
  if (message.type === "stream_event" && isRecord(message.event)) events.push(...mapStreamEvent(message.event));
839
423
  if (message.type === "control_request") {
840
424
  const request = parseControlRequest(message);
@@ -855,7 +439,7 @@ function mapClaudeStdoutMessage(message, options = {}) {
855
439
  }
856
440
  events.push({
857
441
  type: "response",
858
- usage: mapResultUsage(message.usage)
442
+ usage: mapUsage(message.usage)
859
443
  });
860
444
  return {
861
445
  events,
@@ -892,11 +476,11 @@ function mapContentArray(content, options) {
892
476
  const events = [];
893
477
  for (const block of content) {
894
478
  if (!isRecord(block)) continue;
895
- if (block.type === "text" && !options.ignoreAssistantContent) events.push({
479
+ if (block.type === "text") events.push({
896
480
  type: "text_delta",
897
481
  text: String(block.text ?? "")
898
482
  });
899
- else if (block.type === "thinking" && !options.ignoreAssistantContent) {
483
+ else if (block.type === "thinking") {
900
484
  events.push({ type: "thinking_start" });
901
485
  events.push({
902
486
  type: "thinking_delta",
@@ -906,7 +490,7 @@ function mapContentArray(content, options) {
906
490
  type: "thinking_signature",
907
491
  signature: block.signature
908
492
  });
909
- } else if (block.type === "redacted_thinking" && !options.ignoreAssistantContent) events.push({
493
+ } else if (block.type === "redacted_thinking") events.push({
910
494
  type: "redacted_thinking",
911
495
  data: String(block.data ?? "")
912
496
  });
@@ -970,7 +554,6 @@ function parseControlRequest(message) {
970
554
  outerRequestId: message.request_id,
971
555
  serverName: request.server_name,
972
556
  id,
973
- toolUseId: sdkMcpToolUseId(inner.params),
974
557
  method,
975
558
  params: inner.params
976
559
  };
@@ -985,26 +568,6 @@ function parseControlRequest(message) {
985
568
  params: message.params
986
569
  };
987
570
  }
988
- function sdkMcpToolUseId(params) {
989
- if (!isRecord(params) || !isRecord(params._meta)) return void 0;
990
- const value = params._meta["claudecode/toolUseId"];
991
- return typeof value === "string" && value.length > 0 ? value : void 0;
992
- }
993
- /**
994
- * Usage for the response event from a `result` message. The top-level
995
- * `result.usage` sums every API call the CLI made within the turn (initial call
996
- * plus one per tool result), so its input/cache counts can exceed the context
997
- * window itself and must not be reported as request usage. `usage.iterations`
998
- * lists the per-call usage; the last entry is the final request — the one the
999
- * response contract requires.
1000
- */
1001
- function mapResultUsage(usage) {
1002
- if (isRecord(usage) && Array.isArray(usage.iterations)) {
1003
- const last = usage.iterations[usage.iterations.length - 1];
1004
- if (isRecord(last)) return mapUsage(last);
1005
- }
1006
- return mapUsage(usage);
1007
- }
1008
571
  function mapUsage(usage) {
1009
572
  if (!isRecord(usage)) return {
1010
573
  inputTokens: 0,
@@ -1042,6 +605,42 @@ function stripMcpToolPrefix(name) {
1042
605
  return /^mcp__[^_]+__(.+)$/.exec(name)?.[1] ?? name;
1043
606
  }
1044
607
  //#endregion
608
+ //#region src/oauth.ts
609
+ const execFileAsync = promisify(execFile);
610
+ /**
611
+ * Resolve Claude Code consumer OAuth access for quota APIs.
612
+ * Order: options env CLAUDE_CODE_OAUTH_TOKEN, then macOS Keychain "Claude Code-credentials".
613
+ */
614
+ async function resolveClaudeCodeOAuthAccess() {
615
+ const fromEnv = nonEmptyString(process$1.env.CLAUDE_CODE_OAUTH_TOKEN);
616
+ if (fromEnv) return { accessToken: fromEnv };
617
+ if (process$1.platform === "darwin") try {
618
+ const { stdout } = await execFileAsync("security", [
619
+ "find-generic-password",
620
+ "-s",
621
+ "Claude Code-credentials",
622
+ "-w"
623
+ ], {
624
+ encoding: "utf8",
625
+ timeout: 5e3
626
+ });
627
+ const parsed = JSON.parse(stdout.trim());
628
+ if (!isRecord(parsed)) return null;
629
+ const oauth = isRecord(parsed.claudeAiOauth) ? parsed.claudeAiOauth : null;
630
+ if (!oauth) return null;
631
+ const accessToken = nonEmptyString(oauth.accessToken);
632
+ if (!accessToken) return null;
633
+ return {
634
+ accessToken,
635
+ subscriptionType: nonEmptyString(oauth.subscriptionType) ?? null,
636
+ rateLimitTier: nonEmptyString(oauth.rateLimitTier) ?? null
637
+ };
638
+ } catch {
639
+ return null;
640
+ }
641
+ return null;
642
+ }
643
+ //#endregion
1045
644
  //#region src/quota.ts
1046
645
  const DEFAULT_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
1047
646
  const DEFAULT_OAUTH_BETA = "oauth-2025-04-20";
@@ -1063,15 +662,14 @@ function createClaudeCodeQuota(options = {}) {
1063
662
  probe: async ({ signal } = {}) => {
1064
663
  const access = await resolveAccess();
1065
664
  if (!access?.accessToken) throw new Error("Claude Code OAuth access token not found (set CLAUDE_CODE_OAUTH_TOKEN or log in with Claude Code)");
1066
- const headers = new Headers({
1067
- authorization: `Bearer ${access.accessToken}`,
1068
- "anthropic-beta": DEFAULT_OAUTH_BETA,
1069
- accept: "application/json",
1070
- "user-agent": "demi-provider-claude-code"
1071
- });
1072
665
  const response = await fetchImpl(usageUrl, {
1073
666
  method: "GET",
1074
- headers,
667
+ headers: new Headers({
668
+ authorization: `Bearer ${access.accessToken}`,
669
+ "anthropic-beta": DEFAULT_OAUTH_BETA,
670
+ accept: "application/json",
671
+ "user-agent": "demi-provider-claude-code"
672
+ }),
1075
673
  signal
1076
674
  });
1077
675
  if (!response.ok) {
@@ -1148,7 +746,7 @@ function observeClaudeRateLimitHeaders(headers) {
1148
746
  const claim = headers.get("anthropic-ratelimit-unified-representative-claim");
1149
747
  const overageUtil = headers.get("anthropic-ratelimit-unified-overage-period-channel-utilization");
1150
748
  if (!status && !reset && !claim && !overageUtil) return null;
1151
- const usedPercent = clampUsedPercent(numberHeader(headers, "anthropic-ratelimit-unified-overage-period-channel-utilization"));
749
+ const usedPercent = clampUsedPercent(overageUtil != null ? Number(overageUtil) : null);
1152
750
  return {
1153
751
  windows: [{
1154
752
  id: "unified",
@@ -1205,15 +803,13 @@ function buildClaudeArgs(params) {
1205
803
  if (params.thinkingEffort) args.push("--effort", params.thinkingEffort);
1206
804
  return args;
1207
805
  }
1208
- function buildClaudeEnv(base = process.env, options = {}) {
806
+ function buildClaudeEnv(base = process.env) {
1209
807
  const env = {
1210
808
  ...base,
1211
809
  DISABLE_AUTO_COMPACT: "1",
1212
810
  MAX_MCP_OUTPUT_TOKENS: "1000000"
1213
811
  };
1214
812
  delete env.CLAUDECODE;
1215
- const token = options.oauthAccessToken?.trim();
1216
- if (token) env.CLAUDE_CODE_OAUTH_TOKEN = token;
1217
813
  return env;
1218
814
  }
1219
815
  //#endregion
@@ -1260,19 +856,13 @@ function resolveSpawnCwd(cwd) {
1260
856
  try {
1261
857
  if (statSync(cwd).isDirectory()) return cwd;
1262
858
  } catch {}
1263
- return process$1.cwd();
859
+ return process.cwd();
1264
860
  }
1265
861
  var ClaudeCliTransportFactory = class {
1266
862
  claudePath;
1267
- resolveOAuthAccessToken;
1268
863
  constructor(options = {}) {
1269
- if (typeof options === "string") {
1270
- this.claudePath = options;
1271
- this.resolveOAuthAccessToken = null;
1272
- } else {
1273
- this.claudePath = options.claudePath ?? "claude";
1274
- this.resolveOAuthAccessToken = options.resolveOAuthAccessToken ?? null;
1275
- }
864
+ if (typeof options === "string") this.claudePath = options;
865
+ else this.claudePath = options.claudePath ?? "claude";
1276
866
  }
1277
867
  async start(request) {
1278
868
  const args = buildClaudeArgsForRequest(request);
@@ -1284,10 +874,9 @@ var ClaudeCliTransportFactory = class {
1284
874
  cwd: request.cwd,
1285
875
  args
1286
876
  });
1287
- const oauthAccessToken = this.resolveOAuthAccessToken ? await this.resolveOAuthAccessToken() : null;
1288
877
  return new ChildProcessClaudeTransport(spawn(this.claudePath, args, {
1289
878
  cwd: resolveSpawnCwd(request.cwd),
1290
- env: buildClaudeEnv(process$1.env, { oauthAccessToken }),
879
+ env: buildClaudeEnv(),
1291
880
  stdio: [
1292
881
  "pipe",
1293
882
  "pipe",
@@ -1376,21 +965,10 @@ function thinkingEffort(thinking) {
1376
965
  var ClaudeCodeProvider = class {
1377
966
  transportFactory;
1378
967
  quota;
1379
- getActiveCredentialId;
1380
968
  active = null;
1381
969
  constructor(options = {}) {
1382
- this.transportFactory = options.transportFactory ?? new ClaudeCliTransportFactory({
1383
- claudePath: options.claudePath,
1384
- resolveOAuthAccessToken: options.authStore ? async () => {
1385
- try {
1386
- return injectableCliToken(await options.authStore.resolveAccess());
1387
- } catch {
1388
- return null;
1389
- }
1390
- } : void 0
1391
- });
970
+ this.transportFactory = options.transportFactory ?? new ClaudeCliTransportFactory({ claudePath: options.claudePath });
1392
971
  this.quota = options.quota ?? null;
1393
- this.getActiveCredentialId = options.getActiveCredentialId ?? null;
1394
972
  }
1395
973
  observeQuotaFromMessage(message) {
1396
974
  try {
@@ -1440,50 +1018,21 @@ var ClaudeCodeProvider = class {
1440
1018
  }
1441
1019
  const raw = next.value;
1442
1020
  this.observeQuotaFromMessage(raw);
1443
- const mapped = mapClaudeStdoutMessage(raw, { ignoreAssistantContent: active.hasStreamed && isMessageType(raw, "assistant") });
1021
+ const mapped = mapClaudeStdoutMessage(raw, {
1022
+ ignoreAssistantContent: active.hasStreamed && isMessageType(raw, "assistant"),
1023
+ ignoreAssistantToolUse: active.sdkMcpEnabled
1024
+ });
1444
1025
  if (isMessageType(raw, "stream_event")) active.hasStreamed = true;
1445
1026
  if (mapped.controlRequest) {
1446
- const handled = await this.handleControlRequest(active, mapped.controlRequest, request);
1447
- if (handled === "tool-call") {
1027
+ if (await this.handleControlRequest(active, mapped.controlRequest, request) === "tool-call") {
1448
1028
  const event = active.pendingControlRequest ? controlRequestToToolCall(active.pendingControlRequest) : null;
1449
1029
  keepActiveForContinuation = true;
1450
1030
  if (event) yield event;
1451
1031
  return;
1452
1032
  }
1453
- if (handled === "sdk-tool-call") {
1454
- const pending = active.pendingSdkControlRequests.get(mapped.controlRequest.toolUseId ?? "");
1455
- const event = pending ? controlRequestToToolCall(pending) : null;
1456
- if (event?.type === "tool_call_requested") {
1457
- active.pendingSdkToolCalls = [event];
1458
- keepActiveForContinuation = true;
1459
- yield event;
1460
- }
1461
- return;
1462
- }
1463
1033
  continue;
1464
1034
  }
1465
1035
  const toolUseIds = mapped.events.filter(isToolCallRequested).map((event) => event.toolUseId);
1466
- if (active.sdkMcpEnabled) {
1467
- for (const event of mapped.events) {
1468
- if (event.type === "tool_call_requested") {
1469
- active.collectingSdkToolCalls.set(event.toolUseId, event);
1470
- continue;
1471
- }
1472
- yield event;
1473
- }
1474
- if (isStreamMessageStop(raw) && active.collectingSdkToolCalls.size > 0) {
1475
- active.pendingSdkToolCalls = [...active.collectingSdkToolCalls.values()];
1476
- active.collectingSdkToolCalls.clear();
1477
- keepActiveForContinuation = true;
1478
- for (const event of active.pendingSdkToolCalls) yield event;
1479
- return;
1480
- }
1481
- if (mapped.terminal) {
1482
- keepActiveForContinuation = true;
1483
- return;
1484
- }
1485
- continue;
1486
- }
1487
1036
  if (toolUseIds.length > 0) active.pendingToolUseIds = toolUseIds;
1488
1037
  for (const event of mapped.events) {
1489
1038
  if (event.type === "tool_call_requested") keepActiveForContinuation = true;
@@ -1526,26 +1075,22 @@ var ClaudeCodeProvider = class {
1526
1075
  * the session changed, or the transcript was rewritten underneath us (compaction).
1527
1076
  */
1528
1077
  async ensureActiveForRequest(request) {
1529
- const credentialId = this.getActiveCredentialId ? await this.getActiveCredentialId() : null;
1530
1078
  const existing = this.active;
1531
- if (existing && existing.sessionId === request.sessionId && existing.modelId === request.modelId && existing.thinkingSig === thinkingSignature(request) && existing.credentialId === credentialId) {
1532
- if (existing.pendingControlRequest !== null || existing.pendingSdkToolCalls.length > 0 || existing.pendingToolUseIds.length > 0 || !itemsDiverged(existing, request.items)) {
1079
+ if (existing && existing.sessionId === request.sessionId && existing.modelId === request.modelId && existing.thinkingSig === thinkingSignature(request)) {
1080
+ if (existing.pendingControlRequest !== null || existing.pendingToolUseIds.length > 0 || !itemsDiverged(existing, request.items)) {
1533
1081
  await this.sendContinuation(existing, request);
1534
1082
  return existing;
1535
1083
  }
1536
1084
  }
1537
1085
  if (existing) await this.disposeActive(existing);
1538
- return this.coldStart(request, credentialId);
1086
+ return this.coldStart(request);
1539
1087
  }
1540
- async coldStart(request, credentialId) {
1088
+ async coldStart(request) {
1541
1089
  const transport = await this.transportFactory.start(request);
1542
1090
  const active = {
1543
1091
  transport,
1544
1092
  iterator: transport.messages()[Symbol.asyncIterator](),
1545
1093
  pendingControlRequest: null,
1546
- pendingSdkControlRequests: /* @__PURE__ */ new Map(),
1547
- pendingSdkToolCalls: [],
1548
- collectingSdkToolCalls: /* @__PURE__ */ new Map(),
1549
1094
  pendingToolUseIds: [],
1550
1095
  bufferedMessages: [],
1551
1096
  sdkMcpEnabled: request.tools.length > 0,
@@ -1553,7 +1098,6 @@ var ClaudeCodeProvider = class {
1553
1098
  sessionId: request.sessionId,
1554
1099
  modelId: request.modelId,
1555
1100
  thinkingSig: thinkingSignature(request),
1556
- credentialId,
1557
1101
  sentUserMessageCount: 0,
1558
1102
  firstUserSig: null
1559
1103
  };
@@ -1570,10 +1114,6 @@ var ClaudeCodeProvider = class {
1570
1114
  await this.writeToolResults(request, active.pendingControlRequest);
1571
1115
  active.pendingControlRequest = null;
1572
1116
  }
1573
- if (active.pendingSdkToolCalls.length > 0) {
1574
- await this.writeSdkMcpToolResults(active, request);
1575
- active.pendingSdkToolCalls = [];
1576
- }
1577
1117
  if (active.pendingToolUseIds.length > 0) {
1578
1118
  await this.writeToolResultMessages(request, active.pendingToolUseIds);
1579
1119
  active.pendingToolUseIds = [];
@@ -1627,15 +1167,6 @@ var ClaudeCodeProvider = class {
1627
1167
  await this.writeControlError(active, request, "Invalid tools/call request");
1628
1168
  return "handled";
1629
1169
  }
1630
- if (request.protocol === "sdk-mcp") {
1631
- request.toolUseId ??= `mcp-control-${randomUUID()}`;
1632
- const normalized = {
1633
- ...request,
1634
- toolUseId: request.toolUseId
1635
- };
1636
- active.pendingSdkControlRequests.set(normalized.toolUseId, normalized);
1637
- return active.hasStreamed ? "handled" : "sdk-tool-call";
1638
- }
1639
1170
  active.pendingControlRequest = {
1640
1171
  ...request,
1641
1172
  toolUseId: `mcp-control-${randomUUID()}`
@@ -1645,38 +1176,6 @@ var ClaudeCodeProvider = class {
1645
1176
  await this.writeControlError(active, request, `Unsupported method: ${request.method}`);
1646
1177
  return "handled";
1647
1178
  }
1648
- async writeSdkMcpToolResults(active, request) {
1649
- const results = new Map(request.items.filter((item) => item.type === "tool_result").map((item) => [item.toolUseId, item]));
1650
- const expected = active.pendingSdkToolCalls.map((event) => event.toolUseId);
1651
- const missing = expected.filter((toolUseId) => !results.has(toolUseId));
1652
- if (missing.length > 0) throw new Error(`Claude Code provider missing tool_result for SDK MCP tool_use ${missing.join(", ")}`);
1653
- const remaining = new Set(expected);
1654
- while (remaining.size > 0) {
1655
- let responded = false;
1656
- for (const toolUseId of remaining) {
1657
- const controlRequest = active.pendingSdkControlRequests.get(toolUseId);
1658
- if (!controlRequest) continue;
1659
- await this.writeToolResults(request, controlRequest);
1660
- active.pendingSdkControlRequests.delete(toolUseId);
1661
- remaining.delete(toolUseId);
1662
- responded = true;
1663
- }
1664
- if (remaining.size === 0) return;
1665
- if (responded) continue;
1666
- const next = await abortable(active.iterator.next(), request.cancel);
1667
- if (next.done) throw new Error(`Claude Code exited before requesting SDK MCP tool result for ${[...remaining].join(", ")}`);
1668
- this.observeQuotaFromMessage(next.value);
1669
- const mapped = mapClaudeStdoutMessage(next.value, {
1670
- ignoreAssistantContent: true,
1671
- ignoreAssistantToolUse: true
1672
- });
1673
- if (mapped.controlRequest) {
1674
- if (await this.handleControlRequest(active, mapped.controlRequest, request) === "tool-call") throw new Error("Claude Code emitted a legacy tool call while awaiting SDK MCP results");
1675
- continue;
1676
- }
1677
- active.bufferedMessages.push(next.value);
1678
- }
1679
- }
1680
1179
  async writeToolResults(request, controlRequest) {
1681
1180
  if (!this.active) throw new Error("No active Claude transport");
1682
1181
  const expectedToolUseId = controlRequest.toolUseId ?? String(controlRequest.id);
@@ -1777,32 +1276,19 @@ var ClaudeCodeProvider = class {
1777
1276
  function createClaudeCodeProvider(options = {}) {
1778
1277
  const id = options.id ?? "claude-code";
1779
1278
  const displayName = options.displayName ?? "Claude Code";
1780
- const enableCredentials = options.credentials ?? options.authStore === void 0;
1781
- const pool = !options.authStore && enableCredentials ? openClaudeCodeCredentialPool({ stateDir: options.stateDir }) : null;
1782
- const authStore = options.authStore ?? (pool ? new PoolAwareClaudeCodeAuthStore(pool) : new FileClaudeCodeAuthStore());
1783
- const quota = createClaudeCodeQuota({
1784
- providerId: id,
1785
- resolveAccess: async () => {
1786
- try {
1787
- return await authStore.resolveAccess();
1788
- } catch {
1789
- return null;
1790
- }
1791
- }
1792
- });
1793
- const credentialsApi = pool ? createClaudeCodeCredentials(pool, authStore, { quota }) : void 0;
1279
+ const quota = createClaudeCodeQuota({ providerId: id });
1794
1280
  const runtimeOptions = {
1795
1281
  claudePath: options.claudePath,
1796
- quota,
1797
- authStore,
1798
- getActiveCredentialId: pool ? () => pool.getActiveId() : void 0
1282
+ quota
1799
1283
  };
1800
1284
  return defineProvider({
1801
1285
  id,
1802
1286
  displayName,
1803
- auth: { status: () => authStore.status() },
1287
+ auth: { status: () => ({
1288
+ status: "unknown",
1289
+ message: "Auth is checked when a Claude Code request runs"
1290
+ }) },
1804
1291
  quota,
1805
- ...credentialsApi ? { credentials: credentialsApi } : {},
1806
1292
  state: () => ({
1807
1293
  status: "unknown",
1808
1294
  message: "Runtime is checked when a Claude Code request runs"
@@ -1820,19 +1306,18 @@ function isControlResponseFor(value, requestId) {
1820
1306
  if (!isRecord(value) || value.type !== "control_response" || !isRecord(value.response)) return false;
1821
1307
  return value.response.request_id === requestId && value.response.subtype === "success";
1822
1308
  }
1309
+ function toolResultContentToText(output) {
1310
+ return output.map((block) => block.type === "text" ? block.text : `[image:${block.source.mediaType}]`).join("\n");
1311
+ }
1823
1312
  function toolResultContentToMcp(output) {
1824
1313
  return output.map((block) => {
1825
1314
  if (block.type === "text") return {
1826
1315
  type: "text",
1827
1316
  text: block.text
1828
1317
  };
1829
- if (block.type === "video") return {
1830
- type: "text",
1831
- text: `[video:${block.source.mediaType}]`
1832
- };
1833
1318
  return {
1834
1319
  type: "image",
1835
- data: block.source.data,
1320
+ data: Buffer.from(block.source.data).toString("base64"),
1836
1321
  mimeType: block.source.mediaType
1837
1322
  };
1838
1323
  });
@@ -1840,9 +1325,6 @@ function toolResultContentToMcp(output) {
1840
1325
  function isMessageType(value, type) {
1841
1326
  return isRecord(value) && value.type === type;
1842
1327
  }
1843
- function isStreamMessageStop(value) {
1844
- return isRecord(value) && value.type === "stream_event" && isRecord(value.event) && value.event.type === "message_stop";
1845
- }
1846
1328
  function thinkingSignature(request) {
1847
1329
  return JSON.stringify(request.thinking ?? null);
1848
1330
  }
@@ -1867,4 +1349,4 @@ function itemsDiverged(active, items) {
1867
1349
  return false;
1868
1350
  }
1869
1351
  //#endregion
1870
- export { FileClaudeCodeAuthStore, PoolAwareClaudeCodeAuthStore, StaticClaudeCodeAuthStore, createClaudeCodeCredentials, createClaudeCodeProvider, createClaudeCodeQuota, listClaudeCodeModels, mapClaudeUsagePayload, observeClaudeRateLimitHeaders, observeClaudeStreamBody, openClaudeCodeCredentialPool, refreshClaudeCodeSecret, resolveClaudeCodeOAuthAccess, resolveWireLogDir, runClaudeCodeLogin };
1352
+ export { createClaudeCodeProvider, createClaudeCodeQuota, listClaudeCodeModels, mapClaudeUsagePayload, observeClaudeRateLimitHeaders, observeClaudeStreamBody, resolveClaudeCodeOAuthAccess, resolveWireLogDir };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@demicodes/provider-claude-code",
3
3
  "description": "Claude Code provider adapter for Demi.",
4
- "version": "0.10.2",
4
+ "version": "0.10.3",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "exports": {
@@ -11,13 +11,13 @@
11
11
  }
12
12
  },
13
13
  "dependencies": {
14
- "@demicodes/core": "^0.10.2",
15
- "@demicodes/provider": "^0.10.2",
16
- "@demicodes/utils": "^0.10.2"
14
+ "@demicodes/core": "^0.10.3",
15
+ "@demicodes/provider": "^0.10.3",
16
+ "@demicodes/utils": "^0.10.3"
17
17
  },
18
18
  "devDependencies": {
19
- "@demicodes/agent": "^0.10.2",
20
- "@demicodes/shell": "^0.10.2"
19
+ "@demicodes/agent": "^0.10.3",
20
+ "@demicodes/shell": "^0.10.3"
21
21
  },
22
22
  "license": "Apache-2.0",
23
23
  "main": "./dist/index.mjs",