@demicodes/provider-claude-code 0.10.3 → 0.12.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/README.md +30 -0
- package/dist/index.d.mts +117 -15
- package/dist/index.mjs +621 -94
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -5,8 +5,38 @@ 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)
|
|
8
11
|
```
|
|
9
12
|
|
|
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
|
+
|
|
10
40
|
> Diagnostics: the transport writes a raw request/response wire log (including
|
|
11
41
|
> prompts) to `$TMPDIR/demi-claude-wire` by default. Disable with
|
|
12
42
|
> `DEMI_CLAUDE_WIRE_LOG=0`. See [SECURITY](../../SECURITY.md).
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ModelPolicy, Provider, ProviderModelList, ProviderQuota, ProviderQuotaProbeResult } from "@demicodes/provider";
|
|
2
|
-
|
|
1
|
+
import { ModelPolicy, Provider, ProviderAuthState, ProviderCredentials, ProviderModelList, ProviderQuota, ProviderQuotaProbeResult } from "@demicodes/provider";
|
|
2
|
+
import { FileCredentialPool } from "@demicodes/provider/credentials-pool";
|
|
3
3
|
//#region src/models.d.ts
|
|
4
4
|
interface ClaudeCodeModelCatalogOptions {
|
|
5
5
|
fetch?: ModelCatalogFetch;
|
|
@@ -10,12 +10,74 @@ 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
|
|
13
70
|
//#region src/provider.d.ts
|
|
14
71
|
interface ClaudeCodeProviderOptions {
|
|
15
72
|
id?: string;
|
|
16
73
|
displayName?: string;
|
|
17
74
|
claudePath?: string;
|
|
18
75
|
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;
|
|
19
81
|
}
|
|
20
82
|
declare function createClaudeCodeProvider(options?: ClaudeCodeProviderOptions): Provider;
|
|
21
83
|
//#endregion
|
|
@@ -27,18 +89,6 @@ declare function createClaudeCodeProvider(options?: ClaudeCodeProviderOptions):
|
|
|
27
89
|
*/
|
|
28
90
|
declare function resolveWireLogDir(): string | null;
|
|
29
91
|
//#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
|
|
42
92
|
//#region src/quota.d.ts
|
|
43
93
|
interface ClaudeCodeQuotaOptions {
|
|
44
94
|
providerId?: string;
|
|
@@ -58,4 +108,56 @@ declare function mapClaudeUsagePayload(payload: unknown, access?: ClaudeCodeOAut
|
|
|
58
108
|
/** Map anthropic-ratelimit-unified-* headers into a coarse snapshot. */
|
|
59
109
|
declare function observeClaudeRateLimitHeaders(headers: Headers | undefined): ProviderQuotaProbeResult | null;
|
|
60
110
|
//#endregion
|
|
61
|
-
|
|
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 };
|
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
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";
|
|
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";
|
|
5
4
|
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";
|
|
8
10
|
import { appendFileSync, mkdirSync, statSync } from "node:fs";
|
|
9
11
|
import { createInterface } from "node:readline";
|
|
10
12
|
import { tmpdir } from "node:os";
|
|
@@ -12,7 +14,7 @@ import { join } from "node:path";
|
|
|
12
14
|
//#region src/models.ts
|
|
13
15
|
const DEFAULT_MODELS_DEV_URL = "https://models.dev/api.json";
|
|
14
16
|
const DEFAULT_MINIMUM_MODEL_VERSION = "4.6";
|
|
15
|
-
const MODELS_DEV_CACHE_TTL_MS =
|
|
17
|
+
const MODELS_DEV_CACHE_TTL_MS = 864e5;
|
|
16
18
|
let memoryCache = null;
|
|
17
19
|
async function listClaudeCodeModels(options = {}) {
|
|
18
20
|
const fetchImpl = options.fetch ?? fetch;
|
|
@@ -95,7 +97,8 @@ const CLAUDE_FAMILY_RANK = {
|
|
|
95
97
|
haiku: 2
|
|
96
98
|
};
|
|
97
99
|
function claudeFamilyRank(id) {
|
|
98
|
-
|
|
100
|
+
const family = id.slice(7).split("-")[0] ?? "";
|
|
101
|
+
return CLAUDE_FAMILY_RANK[family] ?? 3;
|
|
99
102
|
}
|
|
100
103
|
/** Canonical catalog order: flagship family first (Opus > Sonnet > Haiku > others), newest version first. */
|
|
101
104
|
function compareClaudeModels(a, b) {
|
|
@@ -197,6 +200,378 @@ function reasoningEfforts(value) {
|
|
|
197
200
|
return efforts.length > 0 ? efforts : [];
|
|
198
201
|
}
|
|
199
202
|
//#endregion
|
|
203
|
+
//#region src/auth.ts
|
|
204
|
+
const execFileAsync = promisify(execFile);
|
|
205
|
+
const OAUTH_EXPIRY_SKEW_MS = 3e5;
|
|
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
|
+
const access = await this.resolveAccess();
|
|
221
|
+
return {
|
|
222
|
+
status: "authenticated",
|
|
223
|
+
accountLabel: nonEmptyString(access.subscriptionType) ?? "Claude Code"
|
|
224
|
+
};
|
|
225
|
+
} catch (error) {
|
|
226
|
+
if (error instanceof ClaudeCodeAuthError && error.code === "auth_missing") return {
|
|
227
|
+
status: "unauthenticated",
|
|
228
|
+
message: error.message
|
|
229
|
+
};
|
|
230
|
+
return {
|
|
231
|
+
status: "error",
|
|
232
|
+
message: error instanceof Error ? error.message : String(error)
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
async resolveAccess() {
|
|
237
|
+
if (this.accessToken) return {
|
|
238
|
+
accessToken: this.accessToken,
|
|
239
|
+
source: "static"
|
|
240
|
+
};
|
|
241
|
+
if (this.oauthFile) try {
|
|
242
|
+
let raw = JSON.parse(await readFile(this.oauthFile, "utf8"));
|
|
243
|
+
if (!isRecord(raw)) throw new ClaudeCodeAuthError("auth_invalid", `Invalid OAuth file: ${this.oauthFile}`);
|
|
244
|
+
const expiresAt = nonEmptyString(raw.expiresAt);
|
|
245
|
+
if (expiresAt !== void 0 && Date.parse(expiresAt) - Date.now() < OAUTH_EXPIRY_SKEW_MS && nonEmptyString(raw.refreshToken) && this.refresh) {
|
|
246
|
+
raw = await this.refresh(raw);
|
|
247
|
+
await writeFile(this.oauthFile, `${JSON.stringify(raw, null, 2)}\n`);
|
|
248
|
+
}
|
|
249
|
+
if (!isRecord(raw)) throw new ClaudeCodeAuthError("auth_invalid", `Invalid OAuth file: ${this.oauthFile}`);
|
|
250
|
+
const accessToken = nonEmptyString(raw.accessToken) ?? nonEmptyString(raw.access_token);
|
|
251
|
+
if (!accessToken) throw new ClaudeCodeAuthError("auth_missing", `No accessToken in ${this.oauthFile}`);
|
|
252
|
+
return {
|
|
253
|
+
accessToken,
|
|
254
|
+
source: "file",
|
|
255
|
+
subscriptionType: nonEmptyString(raw.subscriptionType) ?? null,
|
|
256
|
+
rateLimitTier: nonEmptyString(raw.rateLimitTier) ?? null
|
|
257
|
+
};
|
|
258
|
+
} catch (error) {
|
|
259
|
+
if (error instanceof ClaudeCodeAuthError) throw error;
|
|
260
|
+
throw new ClaudeCodeAuthError("auth_missing", `Failed to read Claude OAuth file ${this.oauthFile}: ${error instanceof Error ? error.message : String(error)}`);
|
|
261
|
+
}
|
|
262
|
+
const fromEnv = nonEmptyString(process$1.env.CLAUDE_CODE_OAUTH_TOKEN);
|
|
263
|
+
if (fromEnv) return {
|
|
264
|
+
accessToken: fromEnv,
|
|
265
|
+
source: "env"
|
|
266
|
+
};
|
|
267
|
+
if (process$1.platform === "darwin") try {
|
|
268
|
+
const { stdout } = await execFileAsync("security", [
|
|
269
|
+
"find-generic-password",
|
|
270
|
+
"-s",
|
|
271
|
+
"Claude Code-credentials",
|
|
272
|
+
"-w"
|
|
273
|
+
], {
|
|
274
|
+
encoding: "utf8",
|
|
275
|
+
timeout: 5e3
|
|
276
|
+
});
|
|
277
|
+
const parsed = JSON.parse(stdout.trim());
|
|
278
|
+
if (!isRecord(parsed)) throw new ClaudeCodeAuthError("auth_missing", "Claude Code keychain item is not a JSON object");
|
|
279
|
+
const oauth = isRecord(parsed.claudeAiOauth) ? parsed.claudeAiOauth : null;
|
|
280
|
+
if (!oauth) throw new ClaudeCodeAuthError("auth_missing", "Claude Code keychain missing claudeAiOauth");
|
|
281
|
+
const accessToken = nonEmptyString(oauth.accessToken);
|
|
282
|
+
if (!accessToken) throw new ClaudeCodeAuthError("auth_missing", "Claude Code keychain missing accessToken");
|
|
283
|
+
return {
|
|
284
|
+
accessToken,
|
|
285
|
+
source: "keychain",
|
|
286
|
+
subscriptionType: nonEmptyString(oauth.subscriptionType) ?? null,
|
|
287
|
+
rateLimitTier: nonEmptyString(oauth.rateLimitTier) ?? null
|
|
288
|
+
};
|
|
289
|
+
} catch (error) {
|
|
290
|
+
if (error instanceof ClaudeCodeAuthError) throw error;
|
|
291
|
+
}
|
|
292
|
+
throw new ClaudeCodeAuthError("auth_missing", "Claude Code OAuth access token not found (set CLAUDE_CODE_OAUTH_TOKEN or log in with Claude Code)");
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
var StaticClaudeCodeAuthStore = class {
|
|
296
|
+
access;
|
|
297
|
+
constructor(access) {
|
|
298
|
+
this.access = access;
|
|
299
|
+
}
|
|
300
|
+
async status() {
|
|
301
|
+
return {
|
|
302
|
+
status: "authenticated",
|
|
303
|
+
accountLabel: nonEmptyString(this.access.subscriptionType) ?? "Claude Code"
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
async resolveAccess() {
|
|
307
|
+
return this.access;
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
var ClaudeCodeAuthError = class extends Error {
|
|
311
|
+
code;
|
|
312
|
+
constructor(code, message) {
|
|
313
|
+
super(message);
|
|
314
|
+
this.code = code;
|
|
315
|
+
this.name = "ClaudeCodeAuthError";
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
//#endregion
|
|
319
|
+
//#region src/login.ts
|
|
320
|
+
const CLAUDE_AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
|
|
321
|
+
const CLAUDE_CONSOLE_BASE = "https://console.anthropic.com";
|
|
322
|
+
const CLAUDE_CODE_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
323
|
+
const CLAUDE_LOGIN_SCOPE = "org:create_api_key user:profile user:inference";
|
|
324
|
+
function tokenEndpoint(consoleBase) {
|
|
325
|
+
return `${consoleBase.replace(/\/+$/, "")}/v1/oauth/token`;
|
|
326
|
+
}
|
|
327
|
+
async function requestTokens(fetchImpl, consoleBase, body, signal) {
|
|
328
|
+
const response = await fetchImpl(tokenEndpoint(consoleBase), {
|
|
329
|
+
method: "POST",
|
|
330
|
+
headers: { "content-type": "application/json" },
|
|
331
|
+
body: JSON.stringify(body),
|
|
332
|
+
signal
|
|
333
|
+
});
|
|
334
|
+
if (!response.ok) throw new ClaudeCodeAuthError("auth_invalid", `Claude OAuth token request failed with HTTP ${response.status}`);
|
|
335
|
+
const parsed = await response.json().catch(() => null);
|
|
336
|
+
if (!isRecord(parsed)) throw new ClaudeCodeAuthError("auth_invalid", "Claude OAuth token response is not a JSON object");
|
|
337
|
+
const accessToken = nonEmptyString(parsed.access_token);
|
|
338
|
+
if (!accessToken) throw new ClaudeCodeAuthError("auth_invalid", "Claude OAuth token response is missing access_token");
|
|
339
|
+
const expiresIn = Number(parsed.expires_in);
|
|
340
|
+
const account = isRecord(parsed.account) ? parsed.account : {};
|
|
341
|
+
return {
|
|
342
|
+
accessToken,
|
|
343
|
+
refreshToken: nonEmptyString(parsed.refresh_token) ?? null,
|
|
344
|
+
expiresAt: Number.isFinite(expiresIn) && expiresIn > 0 ? new Date(Date.now() + expiresIn * 1e3).toISOString() : null,
|
|
345
|
+
scopes: typeof parsed.scope === "string" ? parsed.scope.split(" ").filter(Boolean) : null,
|
|
346
|
+
subscriptionType: nonEmptyString(parsed.subscription_type) ?? nonEmptyString(account.subscription_type) ?? null,
|
|
347
|
+
...nonEmptyString(account.email_address) ? { emailAddress: nonEmptyString(account.email_address) } : {}
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
/** Runs the copy-back OAuth flow and returns a refreshable pool secret. */
|
|
351
|
+
async function runClaudeCodeLogin(options) {
|
|
352
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
353
|
+
const consoleBase = options.consoleBase ?? CLAUDE_CONSOLE_BASE;
|
|
354
|
+
const verifier = randomBytes(32).toString("base64url");
|
|
355
|
+
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
356
|
+
const state = randomBytes(32).toString("base64url");
|
|
357
|
+
const redirectUri = `${consoleBase.replace(/\/+$/, "")}/oauth/code/callback`;
|
|
358
|
+
const params = new URLSearchParams({
|
|
359
|
+
code: "true",
|
|
360
|
+
client_id: CLAUDE_CODE_CLIENT_ID,
|
|
361
|
+
response_type: "code",
|
|
362
|
+
redirect_uri: redirectUri,
|
|
363
|
+
scope: CLAUDE_LOGIN_SCOPE,
|
|
364
|
+
code_challenge: challenge,
|
|
365
|
+
code_challenge_method: "S256",
|
|
366
|
+
state
|
|
367
|
+
});
|
|
368
|
+
options.onPending?.({
|
|
369
|
+
verificationUrl: `${CLAUDE_AUTHORIZE_URL}?${params.toString()}`,
|
|
370
|
+
requiresCodeInput: true
|
|
371
|
+
});
|
|
372
|
+
const pasted = (await options.promptForCode()).trim();
|
|
373
|
+
if (!pasted) throw new ClaudeCodeAuthError("auth_invalid", "Empty authorization code");
|
|
374
|
+
const [code, returnedState] = pasted.split("#");
|
|
375
|
+
if (!nonEmptyString(code)) throw new ClaudeCodeAuthError("auth_invalid", "Authorization code is missing the code part");
|
|
376
|
+
if (returnedState && returnedState !== state) throw new ClaudeCodeAuthError("auth_invalid", "Authorization code state mismatch — copy the full string from the callback page");
|
|
377
|
+
return requestTokens(fetchImpl, consoleBase, {
|
|
378
|
+
grant_type: "authorization_code",
|
|
379
|
+
code,
|
|
380
|
+
state: returnedState ?? state,
|
|
381
|
+
client_id: CLAUDE_CODE_CLIENT_ID,
|
|
382
|
+
redirect_uri: redirectUri,
|
|
383
|
+
code_verifier: verifier
|
|
384
|
+
}, options.signal);
|
|
385
|
+
}
|
|
386
|
+
/** Refreshes a pool secret in place; returns the renewed secret. */
|
|
387
|
+
async function refreshClaudeCodeSecret(secret, options = {}) {
|
|
388
|
+
const refreshToken = nonEmptyString(secret.refreshToken);
|
|
389
|
+
if (!refreshToken) throw new ClaudeCodeAuthError("auth_missing", "Claude OAuth secret has no refreshToken to renew with");
|
|
390
|
+
const renewed = await requestTokens(options.fetch ?? fetch, options.consoleBase ?? CLAUDE_CONSOLE_BASE, {
|
|
391
|
+
grant_type: "refresh_token",
|
|
392
|
+
refresh_token: refreshToken,
|
|
393
|
+
client_id: CLAUDE_CODE_CLIENT_ID
|
|
394
|
+
}, options.signal);
|
|
395
|
+
return {
|
|
396
|
+
...secret,
|
|
397
|
+
accessToken: renewed.accessToken,
|
|
398
|
+
refreshToken: renewed.refreshToken ?? refreshToken,
|
|
399
|
+
expiresAt: renewed.expiresAt ?? secret.expiresAt ?? null,
|
|
400
|
+
scopes: renewed.scopes ?? secret.scopes ?? null,
|
|
401
|
+
subscriptionType: renewed.subscriptionType ?? secret.subscriptionType ?? null,
|
|
402
|
+
...nonEmptyString(renewed.emailAddress) ? { emailAddress: renewed.emailAddress } : {}
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
//#endregion
|
|
406
|
+
//#region src/credentials.ts
|
|
407
|
+
var PoolAwareClaudeCodeAuthStore = class {
|
|
408
|
+
pool;
|
|
409
|
+
constructor(pool) {
|
|
410
|
+
this.pool = pool;
|
|
411
|
+
}
|
|
412
|
+
async status() {
|
|
413
|
+
return this.currentStore().then((s) => s.status());
|
|
414
|
+
}
|
|
415
|
+
async resolveAccess() {
|
|
416
|
+
return this.currentStore().then((s) => s.resolveAccess());
|
|
417
|
+
}
|
|
418
|
+
async currentStore() {
|
|
419
|
+
await this.pool.ensureActivePointer();
|
|
420
|
+
const activeId = await this.pool.getActiveId();
|
|
421
|
+
if (activeId) return new FileClaudeCodeAuthStore({
|
|
422
|
+
oauthFile: this.pool.secretPath(activeId),
|
|
423
|
+
refresh: (secret) => refreshClaudeCodeSecret(secret)
|
|
424
|
+
});
|
|
425
|
+
return new FileClaudeCodeAuthStore();
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
function openClaudeCodeCredentialPool(options = {}) {
|
|
429
|
+
return new FileCredentialPool({
|
|
430
|
+
stateDir: options.stateDir,
|
|
431
|
+
providerKey: "claude-code",
|
|
432
|
+
secretFileName: "oauth.json"
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
function createClaudeCodeCredentials(pool, authStore, options = {}) {
|
|
436
|
+
const capability = () => ({
|
|
437
|
+
mode: "supported",
|
|
438
|
+
canBeginLogin: true,
|
|
439
|
+
canImportDefault: true,
|
|
440
|
+
canAdd: true,
|
|
441
|
+
multi: true
|
|
442
|
+
});
|
|
443
|
+
const getActive = async () => {
|
|
444
|
+
await pool.ensureActivePointer();
|
|
445
|
+
return {
|
|
446
|
+
credentialId: await pool.getActiveId(),
|
|
447
|
+
status: await authStore.status()
|
|
448
|
+
};
|
|
449
|
+
};
|
|
450
|
+
const setActive = async (credentialId) => {
|
|
451
|
+
await pool.setActiveId(credentialId);
|
|
452
|
+
options.quota?.clearLatest?.();
|
|
453
|
+
options.onActiveChange?.();
|
|
454
|
+
return getActive();
|
|
455
|
+
};
|
|
456
|
+
const importAccess = async (access, source) => {
|
|
457
|
+
const token = nonEmptyString(access.accessToken);
|
|
458
|
+
if (!token) throw new ClaudeCodeAuthError("auth_missing", "No Claude access token to import");
|
|
459
|
+
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)}`;
|
|
460
|
+
const label = nonEmptyString(access.subscriptionType) ?? `claude-${identityKey.slice(-8)}`;
|
|
461
|
+
const id = (await pool.findByIdentityKey(identityKey))?.id ?? credentialIdFromIdentity(identityKey, label);
|
|
462
|
+
const meta = {
|
|
463
|
+
id,
|
|
464
|
+
label,
|
|
465
|
+
detail: nonEmptyString(access.rateLimitTier) ?? null,
|
|
466
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
467
|
+
source,
|
|
468
|
+
identityKey
|
|
469
|
+
};
|
|
470
|
+
const secret = {
|
|
471
|
+
accessToken: token,
|
|
472
|
+
subscriptionType: access.subscriptionType ?? null,
|
|
473
|
+
rateLimitTier: access.rateLimitTier ?? null
|
|
474
|
+
};
|
|
475
|
+
await pool.writeEntry(meta, `${JSON.stringify(secret, null, 2)}\n`);
|
|
476
|
+
if (!await pool.getActiveId()) await pool.setActiveId(id);
|
|
477
|
+
options.quota?.clearLatest?.();
|
|
478
|
+
options.onActiveChange?.();
|
|
479
|
+
return {
|
|
480
|
+
id: meta.id,
|
|
481
|
+
label: meta.label,
|
|
482
|
+
detail: meta.detail,
|
|
483
|
+
updatedAt: meta.updatedAt
|
|
484
|
+
};
|
|
485
|
+
};
|
|
486
|
+
const importSecret = async (secret, source) => {
|
|
487
|
+
const email = nonEmptyString(secret.emailAddress);
|
|
488
|
+
const identityKey = email ? `email:${email}` : `token:${createHash("sha256").update(secret.accessToken).digest("hex").slice(0, 16)}`;
|
|
489
|
+
const label = email ?? nonEmptyString(secret.subscriptionType) ?? `claude-${identityKey.slice(-8)}`;
|
|
490
|
+
const id = (await pool.findByIdentityKey(identityKey))?.id ?? credentialIdFromIdentity(identityKey, label);
|
|
491
|
+
const meta = {
|
|
492
|
+
id,
|
|
493
|
+
label,
|
|
494
|
+
detail: nonEmptyString(secret.subscriptionType) ?? nonEmptyString(secret.rateLimitTier) ?? null,
|
|
495
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
496
|
+
source,
|
|
497
|
+
identityKey
|
|
498
|
+
};
|
|
499
|
+
await pool.writeEntry(meta, `${JSON.stringify(secret, null, 2)}\n`);
|
|
500
|
+
if (!await pool.getActiveId()) await pool.setActiveId(id);
|
|
501
|
+
options.quota?.clearLatest?.();
|
|
502
|
+
options.onActiveChange?.();
|
|
503
|
+
return {
|
|
504
|
+
id: meta.id,
|
|
505
|
+
label: meta.label,
|
|
506
|
+
detail: meta.detail,
|
|
507
|
+
updatedAt: meta.updatedAt
|
|
508
|
+
};
|
|
509
|
+
};
|
|
510
|
+
return {
|
|
511
|
+
capability,
|
|
512
|
+
list: () => pool.list(),
|
|
513
|
+
getActive,
|
|
514
|
+
setActive,
|
|
515
|
+
beginLogin: async (loginOptions) => {
|
|
516
|
+
if (!loginOptions?.promptForCode) return {
|
|
517
|
+
status: "unavailable",
|
|
518
|
+
message: "Claude login requires promptForCode to collect the pasted authorization code"
|
|
519
|
+
};
|
|
520
|
+
try {
|
|
521
|
+
const secret = await runClaudeCodeLogin({
|
|
522
|
+
signal: loginOptions.signal,
|
|
523
|
+
onPending: loginOptions.onPending,
|
|
524
|
+
promptForCode: loginOptions.promptForCode,
|
|
525
|
+
fetch: options.loginFetch
|
|
526
|
+
});
|
|
527
|
+
return {
|
|
528
|
+
status: "completed",
|
|
529
|
+
credentialId: (await importSecret(secret, "login:oauth")).id
|
|
530
|
+
};
|
|
531
|
+
} catch (error) {
|
|
532
|
+
if (loginOptions.signal?.aborted) return { status: "cancelled" };
|
|
533
|
+
return {
|
|
534
|
+
status: "failed",
|
|
535
|
+
message: errorMessage(error)
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
importDefault: async () => {
|
|
540
|
+
const vendor = new FileClaudeCodeAuthStore();
|
|
541
|
+
let access;
|
|
542
|
+
try {
|
|
543
|
+
access = await vendor.resolveAccess();
|
|
544
|
+
} catch {
|
|
545
|
+
throw new ClaudeCodeAuthError("auth_missing", "No Claude Code OAuth to import. Run claude auth login or beginLogin first.");
|
|
546
|
+
}
|
|
547
|
+
return importAccess(access, "vendor:default");
|
|
548
|
+
},
|
|
549
|
+
add: async (input) => {
|
|
550
|
+
if (typeof input.accessToken === "string") return importAccess({
|
|
551
|
+
accessToken: input.accessToken,
|
|
552
|
+
source: "static",
|
|
553
|
+
subscriptionType: typeof input.subscriptionType === "string" ? input.subscriptionType : null,
|
|
554
|
+
rateLimitTier: typeof input.rateLimitTier === "string" ? input.rateLimitTier : null
|
|
555
|
+
}, "add:accessToken");
|
|
556
|
+
if (isRecord(input.oauth) && typeof input.oauth.accessToken === "string") {
|
|
557
|
+
const oauth = input.oauth;
|
|
558
|
+
return importAccess({
|
|
559
|
+
accessToken: oauth.accessToken,
|
|
560
|
+
source: "static",
|
|
561
|
+
subscriptionType: typeof oauth.subscriptionType === "string" ? oauth.subscriptionType : null,
|
|
562
|
+
rateLimitTier: typeof oauth.rateLimitTier === "string" ? oauth.rateLimitTier : null
|
|
563
|
+
}, "add:oauth");
|
|
564
|
+
}
|
|
565
|
+
throw new Error("Claude credentials.add expects accessToken or oauth.accessToken");
|
|
566
|
+
},
|
|
567
|
+
remove: async (credentialId) => {
|
|
568
|
+
await pool.remove(credentialId);
|
|
569
|
+
options.quota?.clearLatest?.();
|
|
570
|
+
options.onActiveChange?.();
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
//#endregion
|
|
200
575
|
//#region src/jsonl.ts
|
|
201
576
|
/**
|
|
202
577
|
* Builds the input messages used to prime a *fresh* Claude CLI process with prior
|
|
@@ -260,12 +635,10 @@ function coldStartInputMessages(items) {
|
|
|
260
635
|
text: renderToolUseText(item.toolName, item.input)
|
|
261
636
|
}]);
|
|
262
637
|
break;
|
|
263
|
-
case "tool_result":
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}]);
|
|
268
|
-
break;
|
|
638
|
+
case "tool_result": append("assistant", [{
|
|
639
|
+
type: "text",
|
|
640
|
+
text: renderToolResultText(toolNames.get(item.toolUseId), item)
|
|
641
|
+
}]);
|
|
269
642
|
}
|
|
270
643
|
flush();
|
|
271
644
|
return messages;
|
|
@@ -274,7 +647,7 @@ function renderToolUseText(toolName, input) {
|
|
|
274
647
|
return `[Earlier in this conversation I called the tool ${toolName} with input: ${safeJson(input)}.`;
|
|
275
648
|
}
|
|
276
649
|
function renderToolResultText(toolName, item) {
|
|
277
|
-
const body =
|
|
650
|
+
const body = toolResultContentToText(item.output);
|
|
278
651
|
const suffix = toolName ? ` from ${toolName}` : "";
|
|
279
652
|
return item.isError ? `It returned an error${suffix}: ${body}]` : `It returned${suffix}: ${body}]`;
|
|
280
653
|
}
|
|
@@ -339,6 +712,10 @@ function userContentToClaude(content) {
|
|
|
339
712
|
type: "image",
|
|
340
713
|
source: imageSourceToClaude(block.source)
|
|
341
714
|
};
|
|
715
|
+
if (block.type === "video") return {
|
|
716
|
+
type: "text",
|
|
717
|
+
text: "[video]"
|
|
718
|
+
};
|
|
342
719
|
if (block.type === "document") return documentSourceToClaude(block.source);
|
|
343
720
|
return {
|
|
344
721
|
type: "text",
|
|
@@ -371,11 +748,30 @@ function assistantItemToClaudeContent(item) {
|
|
|
371
748
|
};
|
|
372
749
|
}
|
|
373
750
|
function toolResultToClaudeContent(item) {
|
|
751
|
+
const hasImage = item.output.some((block) => block.type !== "text");
|
|
374
752
|
return {
|
|
375
753
|
type: "tool_result",
|
|
376
754
|
tool_use_id: item.toolUseId,
|
|
377
755
|
is_error: item.isError,
|
|
378
|
-
content:
|
|
756
|
+
content: hasImage ? item.output.map(toolResultBlockToClaude) : toolResultContentToText(item.output)
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
function toolResultBlockToClaude(block) {
|
|
760
|
+
if (block.type === "text") return {
|
|
761
|
+
type: "text",
|
|
762
|
+
text: block.text
|
|
763
|
+
};
|
|
764
|
+
if (block.type === "video") return {
|
|
765
|
+
type: "text",
|
|
766
|
+
text: `[video:${block.source.mediaType}]`
|
|
767
|
+
};
|
|
768
|
+
return {
|
|
769
|
+
type: "image",
|
|
770
|
+
source: {
|
|
771
|
+
type: "base64",
|
|
772
|
+
media_type: block.source.mediaType,
|
|
773
|
+
data: block.source.data
|
|
774
|
+
}
|
|
379
775
|
};
|
|
380
776
|
}
|
|
381
777
|
function imageSourceToClaude(source) {
|
|
@@ -400,9 +796,6 @@ function documentSourceToClaude(source) {
|
|
|
400
796
|
title: source.fileName
|
|
401
797
|
};
|
|
402
798
|
}
|
|
403
|
-
function toolResultToText(output) {
|
|
404
|
-
return output.map((block) => block.type === "text" ? block.text : `[image:${block.source.mediaType}]`).join("\n");
|
|
405
|
-
}
|
|
406
799
|
function bytesToBase64(data) {
|
|
407
800
|
return Buffer$1.from(data.buffer, data.byteOffset, data.byteLength).toString("base64");
|
|
408
801
|
}
|
|
@@ -411,6 +804,28 @@ function toolNameToClaude(name) {
|
|
|
411
804
|
return `mcp__main__${name}`;
|
|
412
805
|
}
|
|
413
806
|
//#endregion
|
|
807
|
+
//#region src/oauth.ts
|
|
808
|
+
/**
|
|
809
|
+
* The token to inject into a spawned CLI as CLAUDE_CODE_OAUTH_TOKEN, or null
|
|
810
|
+
* to let the CLI authenticate itself. Keychain-sourced tokens are never
|
|
811
|
+
* injected — see {@link ClaudeCodeOAuthSource}.
|
|
812
|
+
*/
|
|
813
|
+
function injectableCliToken(access) {
|
|
814
|
+
return access.source === "keychain" ? null : access.accessToken;
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Resolve Claude Code consumer OAuth access for quota APIs.
|
|
818
|
+
* Order: options env CLAUDE_CODE_OAUTH_TOKEN, then macOS Keychain "Claude Code-credentials".
|
|
819
|
+
* Prefer injecting {@link ClaudeCodeAuthStore} when multi-credential is enabled.
|
|
820
|
+
*/
|
|
821
|
+
async function resolveClaudeCodeOAuthAccess() {
|
|
822
|
+
try {
|
|
823
|
+
return await new FileClaudeCodeAuthStore().resolveAccess();
|
|
824
|
+
} catch {
|
|
825
|
+
return null;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
//#endregion
|
|
414
829
|
//#region src/output.ts
|
|
415
830
|
function mapClaudeStdoutMessage(message, options = {}) {
|
|
416
831
|
const events = [];
|
|
@@ -418,7 +833,7 @@ function mapClaudeStdoutMessage(message, options = {}) {
|
|
|
418
833
|
events,
|
|
419
834
|
terminal: false
|
|
420
835
|
};
|
|
421
|
-
if (message.type === "assistant" && isRecord(message.message)
|
|
836
|
+
if (message.type === "assistant" && isRecord(message.message)) events.push(...mapContentArray(message.message.content, options));
|
|
422
837
|
if (message.type === "stream_event" && isRecord(message.event)) events.push(...mapStreamEvent(message.event));
|
|
423
838
|
if (message.type === "control_request") {
|
|
424
839
|
const request = parseControlRequest(message);
|
|
@@ -439,7 +854,7 @@ function mapClaudeStdoutMessage(message, options = {}) {
|
|
|
439
854
|
}
|
|
440
855
|
events.push({
|
|
441
856
|
type: "response",
|
|
442
|
-
usage:
|
|
857
|
+
usage: mapResultUsage(message.usage)
|
|
443
858
|
});
|
|
444
859
|
return {
|
|
445
860
|
events,
|
|
@@ -476,11 +891,11 @@ function mapContentArray(content, options) {
|
|
|
476
891
|
const events = [];
|
|
477
892
|
for (const block of content) {
|
|
478
893
|
if (!isRecord(block)) continue;
|
|
479
|
-
if (block.type === "text") events.push({
|
|
894
|
+
if (block.type === "text" && !options.ignoreAssistantContent) events.push({
|
|
480
895
|
type: "text_delta",
|
|
481
896
|
text: String(block.text ?? "")
|
|
482
897
|
});
|
|
483
|
-
else if (block.type === "thinking") {
|
|
898
|
+
else if (block.type === "thinking" && !options.ignoreAssistantContent) {
|
|
484
899
|
events.push({ type: "thinking_start" });
|
|
485
900
|
events.push({
|
|
486
901
|
type: "thinking_delta",
|
|
@@ -490,7 +905,7 @@ function mapContentArray(content, options) {
|
|
|
490
905
|
type: "thinking_signature",
|
|
491
906
|
signature: block.signature
|
|
492
907
|
});
|
|
493
|
-
} else if (block.type === "redacted_thinking") events.push({
|
|
908
|
+
} else if (block.type === "redacted_thinking" && !options.ignoreAssistantContent) events.push({
|
|
494
909
|
type: "redacted_thinking",
|
|
495
910
|
data: String(block.data ?? "")
|
|
496
911
|
});
|
|
@@ -554,6 +969,7 @@ function parseControlRequest(message) {
|
|
|
554
969
|
outerRequestId: message.request_id,
|
|
555
970
|
serverName: request.server_name,
|
|
556
971
|
id,
|
|
972
|
+
toolUseId: sdkMcpToolUseId(inner.params),
|
|
557
973
|
method,
|
|
558
974
|
params: inner.params
|
|
559
975
|
};
|
|
@@ -568,6 +984,26 @@ function parseControlRequest(message) {
|
|
|
568
984
|
params: message.params
|
|
569
985
|
};
|
|
570
986
|
}
|
|
987
|
+
function sdkMcpToolUseId(params) {
|
|
988
|
+
if (!isRecord(params) || !isRecord(params._meta)) return void 0;
|
|
989
|
+
const value = params._meta["claudecode/toolUseId"];
|
|
990
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Usage for the response event from a `result` message. The top-level
|
|
994
|
+
* `result.usage` sums every API call the CLI made within the turn (initial call
|
|
995
|
+
* plus one per tool result), so its input/cache counts can exceed the context
|
|
996
|
+
* window itself and must not be reported as request usage. `usage.iterations`
|
|
997
|
+
* lists the per-call usage; the last entry is the final request — the one the
|
|
998
|
+
* response contract requires.
|
|
999
|
+
*/
|
|
1000
|
+
function mapResultUsage(usage) {
|
|
1001
|
+
if (isRecord(usage) && Array.isArray(usage.iterations)) {
|
|
1002
|
+
const last = usage.iterations[usage.iterations.length - 1];
|
|
1003
|
+
if (isRecord(last)) return mapUsage(last);
|
|
1004
|
+
}
|
|
1005
|
+
return mapUsage(usage);
|
|
1006
|
+
}
|
|
571
1007
|
function mapUsage(usage) {
|
|
572
1008
|
if (!isRecord(usage)) return {
|
|
573
1009
|
inputTokens: 0,
|
|
@@ -605,42 +1041,6 @@ function stripMcpToolPrefix(name) {
|
|
|
605
1041
|
return /^mcp__[^_]+__(.+)$/.exec(name)?.[1] ?? name;
|
|
606
1042
|
}
|
|
607
1043
|
//#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
|
|
644
1044
|
//#region src/quota.ts
|
|
645
1045
|
const DEFAULT_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
646
1046
|
const DEFAULT_OAUTH_BETA = "oauth-2025-04-20";
|
|
@@ -662,14 +1062,15 @@ function createClaudeCodeQuota(options = {}) {
|
|
|
662
1062
|
probe: async ({ signal } = {}) => {
|
|
663
1063
|
const access = await resolveAccess();
|
|
664
1064
|
if (!access?.accessToken) throw new Error("Claude Code OAuth access token not found (set CLAUDE_CODE_OAUTH_TOKEN or log in with Claude Code)");
|
|
1065
|
+
const headers = new Headers({
|
|
1066
|
+
authorization: `Bearer ${access.accessToken}`,
|
|
1067
|
+
"anthropic-beta": DEFAULT_OAUTH_BETA,
|
|
1068
|
+
accept: "application/json",
|
|
1069
|
+
"user-agent": "demi-provider-claude-code"
|
|
1070
|
+
});
|
|
665
1071
|
const response = await fetchImpl(usageUrl, {
|
|
666
1072
|
method: "GET",
|
|
667
|
-
headers
|
|
668
|
-
authorization: `Bearer ${access.accessToken}`,
|
|
669
|
-
"anthropic-beta": DEFAULT_OAUTH_BETA,
|
|
670
|
-
accept: "application/json",
|
|
671
|
-
"user-agent": "demi-provider-claude-code"
|
|
672
|
-
}),
|
|
1073
|
+
headers,
|
|
673
1074
|
signal
|
|
674
1075
|
});
|
|
675
1076
|
if (!response.ok) {
|
|
@@ -746,7 +1147,7 @@ function observeClaudeRateLimitHeaders(headers) {
|
|
|
746
1147
|
const claim = headers.get("anthropic-ratelimit-unified-representative-claim");
|
|
747
1148
|
const overageUtil = headers.get("anthropic-ratelimit-unified-overage-period-channel-utilization");
|
|
748
1149
|
if (!status && !reset && !claim && !overageUtil) return null;
|
|
749
|
-
const usedPercent = clampUsedPercent(
|
|
1150
|
+
const usedPercent = clampUsedPercent(numberHeader(headers, "anthropic-ratelimit-unified-overage-period-channel-utilization"));
|
|
750
1151
|
return {
|
|
751
1152
|
windows: [{
|
|
752
1153
|
id: "unified",
|
|
@@ -803,13 +1204,15 @@ function buildClaudeArgs(params) {
|
|
|
803
1204
|
if (params.thinkingEffort) args.push("--effort", params.thinkingEffort);
|
|
804
1205
|
return args;
|
|
805
1206
|
}
|
|
806
|
-
function buildClaudeEnv(base = process.env) {
|
|
1207
|
+
function buildClaudeEnv(base = process.env, options = {}) {
|
|
807
1208
|
const env = {
|
|
808
1209
|
...base,
|
|
809
1210
|
DISABLE_AUTO_COMPACT: "1",
|
|
810
1211
|
MAX_MCP_OUTPUT_TOKENS: "1000000"
|
|
811
1212
|
};
|
|
812
1213
|
delete env.CLAUDECODE;
|
|
1214
|
+
const token = options.oauthAccessToken?.trim();
|
|
1215
|
+
if (token) env.CLAUDE_CODE_OAUTH_TOKEN = token;
|
|
813
1216
|
return env;
|
|
814
1217
|
}
|
|
815
1218
|
//#endregion
|
|
@@ -835,7 +1238,8 @@ function createClaudeWireLog(sessionId) {
|
|
|
835
1238
|
} catch {
|
|
836
1239
|
return NULL_WIRE_LOG;
|
|
837
1240
|
}
|
|
838
|
-
const
|
|
1241
|
+
const safeSession = sessionId.replace(/[^a-zA-Z0-9_-]/g, "_") || "session";
|
|
1242
|
+
const path = join(dir, `claude-${safeSession}.jsonl`);
|
|
839
1243
|
return {
|
|
840
1244
|
path,
|
|
841
1245
|
record(direction, data) {
|
|
@@ -856,13 +1260,19 @@ function resolveSpawnCwd(cwd) {
|
|
|
856
1260
|
try {
|
|
857
1261
|
if (statSync(cwd).isDirectory()) return cwd;
|
|
858
1262
|
} catch {}
|
|
859
|
-
return process.cwd();
|
|
1263
|
+
return process$1.cwd();
|
|
860
1264
|
}
|
|
861
1265
|
var ClaudeCliTransportFactory = class {
|
|
862
1266
|
claudePath;
|
|
1267
|
+
resolveOAuthAccessToken;
|
|
863
1268
|
constructor(options = {}) {
|
|
864
|
-
if (typeof options === "string")
|
|
865
|
-
|
|
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
|
+
}
|
|
866
1276
|
}
|
|
867
1277
|
async start(request) {
|
|
868
1278
|
const args = buildClaudeArgsForRequest(request);
|
|
@@ -874,9 +1284,10 @@ var ClaudeCliTransportFactory = class {
|
|
|
874
1284
|
cwd: request.cwd,
|
|
875
1285
|
args
|
|
876
1286
|
});
|
|
1287
|
+
const oauthAccessToken = this.resolveOAuthAccessToken ? await this.resolveOAuthAccessToken() : null;
|
|
877
1288
|
return new ChildProcessClaudeTransport(spawn(this.claudePath, args, {
|
|
878
1289
|
cwd: resolveSpawnCwd(request.cwd),
|
|
879
|
-
env: buildClaudeEnv(),
|
|
1290
|
+
env: buildClaudeEnv(process$1.env, { oauthAccessToken }),
|
|
880
1291
|
stdio: [
|
|
881
1292
|
"pipe",
|
|
882
1293
|
"pipe",
|
|
@@ -962,13 +1373,32 @@ function thinkingEffort(thinking) {
|
|
|
962
1373
|
}
|
|
963
1374
|
//#endregion
|
|
964
1375
|
//#region src/provider.ts
|
|
965
|
-
var ClaudeCodeProvider = class {
|
|
1376
|
+
var ClaudeCodeProvider = class ClaudeCodeProvider {
|
|
1377
|
+
cloneOptions;
|
|
966
1378
|
transportFactory;
|
|
967
1379
|
quota;
|
|
1380
|
+
getActiveCredentialId;
|
|
968
1381
|
active = null;
|
|
969
1382
|
constructor(options = {}) {
|
|
970
|
-
this.
|
|
1383
|
+
this.cloneOptions = options;
|
|
1384
|
+
this.transportFactory = options.transportFactory ?? new ClaudeCliTransportFactory({
|
|
1385
|
+
claudePath: options.claudePath,
|
|
1386
|
+
resolveOAuthAccessToken: options.authStore ? async () => {
|
|
1387
|
+
try {
|
|
1388
|
+
return injectableCliToken(await options.authStore.resolveAccess());
|
|
1389
|
+
} catch {
|
|
1390
|
+
return null;
|
|
1391
|
+
}
|
|
1392
|
+
} : void 0
|
|
1393
|
+
});
|
|
971
1394
|
this.quota = options.quota ?? null;
|
|
1395
|
+
this.getActiveCredentialId = options.getActiveCredentialId ?? null;
|
|
1396
|
+
}
|
|
1397
|
+
clone() {
|
|
1398
|
+
return new ClaudeCodeProvider({
|
|
1399
|
+
...this.cloneOptions,
|
|
1400
|
+
transportFactory: this.transportFactory
|
|
1401
|
+
});
|
|
972
1402
|
}
|
|
973
1403
|
observeQuotaFromMessage(message) {
|
|
974
1404
|
try {
|
|
@@ -1018,21 +1448,50 @@ var ClaudeCodeProvider = class {
|
|
|
1018
1448
|
}
|
|
1019
1449
|
const raw = next.value;
|
|
1020
1450
|
this.observeQuotaFromMessage(raw);
|
|
1021
|
-
const mapped = mapClaudeStdoutMessage(raw, {
|
|
1022
|
-
ignoreAssistantContent: active.hasStreamed && isMessageType(raw, "assistant"),
|
|
1023
|
-
ignoreAssistantToolUse: active.sdkMcpEnabled
|
|
1024
|
-
});
|
|
1451
|
+
const mapped = mapClaudeStdoutMessage(raw, { ignoreAssistantContent: active.hasStreamed && isMessageType(raw, "assistant") });
|
|
1025
1452
|
if (isMessageType(raw, "stream_event")) active.hasStreamed = true;
|
|
1026
1453
|
if (mapped.controlRequest) {
|
|
1027
|
-
|
|
1454
|
+
const handled = await this.handleControlRequest(active, mapped.controlRequest, request);
|
|
1455
|
+
if (handled === "tool-call") {
|
|
1028
1456
|
const event = active.pendingControlRequest ? controlRequestToToolCall(active.pendingControlRequest) : null;
|
|
1029
1457
|
keepActiveForContinuation = true;
|
|
1030
1458
|
if (event) yield event;
|
|
1031
1459
|
return;
|
|
1032
1460
|
}
|
|
1461
|
+
if (handled === "sdk-tool-call") {
|
|
1462
|
+
const pending = active.pendingSdkControlRequests.get(mapped.controlRequest.toolUseId ?? "");
|
|
1463
|
+
const event = pending ? controlRequestToToolCall(pending) : null;
|
|
1464
|
+
if (event?.type === "tool_call_requested") {
|
|
1465
|
+
active.pendingSdkToolCalls = [event];
|
|
1466
|
+
keepActiveForContinuation = true;
|
|
1467
|
+
yield event;
|
|
1468
|
+
}
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1033
1471
|
continue;
|
|
1034
1472
|
}
|
|
1035
1473
|
const toolUseIds = mapped.events.filter(isToolCallRequested).map((event) => event.toolUseId);
|
|
1474
|
+
if (active.sdkMcpEnabled) {
|
|
1475
|
+
for (const event of mapped.events) {
|
|
1476
|
+
if (event.type === "tool_call_requested") {
|
|
1477
|
+
active.collectingSdkToolCalls.set(event.toolUseId, event);
|
|
1478
|
+
continue;
|
|
1479
|
+
}
|
|
1480
|
+
yield event;
|
|
1481
|
+
}
|
|
1482
|
+
if (isStreamMessageStop(raw) && active.collectingSdkToolCalls.size > 0) {
|
|
1483
|
+
active.pendingSdkToolCalls = [...active.collectingSdkToolCalls.values()];
|
|
1484
|
+
active.collectingSdkToolCalls.clear();
|
|
1485
|
+
keepActiveForContinuation = true;
|
|
1486
|
+
for (const event of active.pendingSdkToolCalls) yield event;
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
if (mapped.terminal) {
|
|
1490
|
+
keepActiveForContinuation = true;
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
continue;
|
|
1494
|
+
}
|
|
1036
1495
|
if (toolUseIds.length > 0) active.pendingToolUseIds = toolUseIds;
|
|
1037
1496
|
for (const event of mapped.events) {
|
|
1038
1497
|
if (event.type === "tool_call_requested") keepActiveForContinuation = true;
|
|
@@ -1075,22 +1534,26 @@ var ClaudeCodeProvider = class {
|
|
|
1075
1534
|
* the session changed, or the transcript was rewritten underneath us (compaction).
|
|
1076
1535
|
*/
|
|
1077
1536
|
async ensureActiveForRequest(request) {
|
|
1537
|
+
const credentialId = this.getActiveCredentialId ? await this.getActiveCredentialId() : null;
|
|
1078
1538
|
const existing = this.active;
|
|
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)) {
|
|
1539
|
+
if (existing && existing.sessionId === request.sessionId && existing.modelId === request.modelId && existing.thinkingSig === thinkingSignature(request) && existing.credentialId === credentialId) {
|
|
1540
|
+
if (existing.pendingControlRequest !== null || existing.pendingSdkToolCalls.length > 0 || existing.pendingToolUseIds.length > 0 || !itemsDiverged(existing, request.items)) {
|
|
1081
1541
|
await this.sendContinuation(existing, request);
|
|
1082
1542
|
return existing;
|
|
1083
1543
|
}
|
|
1084
1544
|
}
|
|
1085
1545
|
if (existing) await this.disposeActive(existing);
|
|
1086
|
-
return this.coldStart(request);
|
|
1546
|
+
return this.coldStart(request, credentialId);
|
|
1087
1547
|
}
|
|
1088
|
-
async coldStart(request) {
|
|
1548
|
+
async coldStart(request, credentialId) {
|
|
1089
1549
|
const transport = await this.transportFactory.start(request);
|
|
1090
1550
|
const active = {
|
|
1091
1551
|
transport,
|
|
1092
1552
|
iterator: transport.messages()[Symbol.asyncIterator](),
|
|
1093
1553
|
pendingControlRequest: null,
|
|
1554
|
+
pendingSdkControlRequests: /* @__PURE__ */ new Map(),
|
|
1555
|
+
pendingSdkToolCalls: [],
|
|
1556
|
+
collectingSdkToolCalls: /* @__PURE__ */ new Map(),
|
|
1094
1557
|
pendingToolUseIds: [],
|
|
1095
1558
|
bufferedMessages: [],
|
|
1096
1559
|
sdkMcpEnabled: request.tools.length > 0,
|
|
@@ -1098,6 +1561,7 @@ var ClaudeCodeProvider = class {
|
|
|
1098
1561
|
sessionId: request.sessionId,
|
|
1099
1562
|
modelId: request.modelId,
|
|
1100
1563
|
thinkingSig: thinkingSignature(request),
|
|
1564
|
+
credentialId,
|
|
1101
1565
|
sentUserMessageCount: 0,
|
|
1102
1566
|
firstUserSig: null
|
|
1103
1567
|
};
|
|
@@ -1114,6 +1578,10 @@ var ClaudeCodeProvider = class {
|
|
|
1114
1578
|
await this.writeToolResults(request, active.pendingControlRequest);
|
|
1115
1579
|
active.pendingControlRequest = null;
|
|
1116
1580
|
}
|
|
1581
|
+
if (active.pendingSdkToolCalls.length > 0) {
|
|
1582
|
+
await this.writeSdkMcpToolResults(active, request);
|
|
1583
|
+
active.pendingSdkToolCalls = [];
|
|
1584
|
+
}
|
|
1117
1585
|
if (active.pendingToolUseIds.length > 0) {
|
|
1118
1586
|
await this.writeToolResultMessages(request, active.pendingToolUseIds);
|
|
1119
1587
|
active.pendingToolUseIds = [];
|
|
@@ -1167,6 +1635,15 @@ var ClaudeCodeProvider = class {
|
|
|
1167
1635
|
await this.writeControlError(active, request, "Invalid tools/call request");
|
|
1168
1636
|
return "handled";
|
|
1169
1637
|
}
|
|
1638
|
+
if (request.protocol === "sdk-mcp") {
|
|
1639
|
+
request.toolUseId ??= `mcp-control-${randomUUID()}`;
|
|
1640
|
+
const normalized = {
|
|
1641
|
+
...request,
|
|
1642
|
+
toolUseId: request.toolUseId
|
|
1643
|
+
};
|
|
1644
|
+
active.pendingSdkControlRequests.set(normalized.toolUseId, normalized);
|
|
1645
|
+
return active.hasStreamed ? "handled" : "sdk-tool-call";
|
|
1646
|
+
}
|
|
1170
1647
|
active.pendingControlRequest = {
|
|
1171
1648
|
...request,
|
|
1172
1649
|
toolUseId: `mcp-control-${randomUUID()}`
|
|
@@ -1176,6 +1653,38 @@ var ClaudeCodeProvider = class {
|
|
|
1176
1653
|
await this.writeControlError(active, request, `Unsupported method: ${request.method}`);
|
|
1177
1654
|
return "handled";
|
|
1178
1655
|
}
|
|
1656
|
+
async writeSdkMcpToolResults(active, request) {
|
|
1657
|
+
const results = new Map(request.items.filter((item) => item.type === "tool_result").map((item) => [item.toolUseId, item]));
|
|
1658
|
+
const expected = active.pendingSdkToolCalls.map((event) => event.toolUseId);
|
|
1659
|
+
const missing = expected.filter((toolUseId) => !results.has(toolUseId));
|
|
1660
|
+
if (missing.length > 0) throw new Error(`Claude Code provider missing tool_result for SDK MCP tool_use ${missing.join(", ")}`);
|
|
1661
|
+
const remaining = new Set(expected);
|
|
1662
|
+
while (remaining.size > 0) {
|
|
1663
|
+
let responded = false;
|
|
1664
|
+
for (const toolUseId of remaining) {
|
|
1665
|
+
const controlRequest = active.pendingSdkControlRequests.get(toolUseId);
|
|
1666
|
+
if (!controlRequest) continue;
|
|
1667
|
+
await this.writeToolResults(request, controlRequest);
|
|
1668
|
+
active.pendingSdkControlRequests.delete(toolUseId);
|
|
1669
|
+
remaining.delete(toolUseId);
|
|
1670
|
+
responded = true;
|
|
1671
|
+
}
|
|
1672
|
+
if (remaining.size === 0) return;
|
|
1673
|
+
if (responded) continue;
|
|
1674
|
+
const next = await abortable(active.iterator.next(), request.cancel);
|
|
1675
|
+
if (next.done) throw new Error(`Claude Code exited before requesting SDK MCP tool result for ${[...remaining].join(", ")}`);
|
|
1676
|
+
this.observeQuotaFromMessage(next.value);
|
|
1677
|
+
const mapped = mapClaudeStdoutMessage(next.value, {
|
|
1678
|
+
ignoreAssistantContent: true,
|
|
1679
|
+
ignoreAssistantToolUse: true
|
|
1680
|
+
});
|
|
1681
|
+
if (mapped.controlRequest) {
|
|
1682
|
+
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");
|
|
1683
|
+
continue;
|
|
1684
|
+
}
|
|
1685
|
+
active.bufferedMessages.push(next.value);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1179
1688
|
async writeToolResults(request, controlRequest) {
|
|
1180
1689
|
if (!this.active) throw new Error("No active Claude transport");
|
|
1181
1690
|
const expectedToolUseId = controlRequest.toolUseId ?? String(controlRequest.id);
|
|
@@ -1276,25 +1785,39 @@ var ClaudeCodeProvider = class {
|
|
|
1276
1785
|
function createClaudeCodeProvider(options = {}) {
|
|
1277
1786
|
const id = options.id ?? "claude-code";
|
|
1278
1787
|
const displayName = options.displayName ?? "Claude Code";
|
|
1279
|
-
const
|
|
1788
|
+
const enableCredentials = options.credentials ?? options.authStore === void 0;
|
|
1789
|
+
const pool = !options.authStore && enableCredentials ? openClaudeCodeCredentialPool({ stateDir: options.stateDir }) : null;
|
|
1790
|
+
const authStore = options.authStore ?? (pool ? new PoolAwareClaudeCodeAuthStore(pool) : new FileClaudeCodeAuthStore());
|
|
1791
|
+
const quota = createClaudeCodeQuota({
|
|
1792
|
+
providerId: id,
|
|
1793
|
+
resolveAccess: async () => {
|
|
1794
|
+
try {
|
|
1795
|
+
return await authStore.resolveAccess();
|
|
1796
|
+
} catch {
|
|
1797
|
+
return null;
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
});
|
|
1801
|
+
const credentialsApi = pool ? createClaudeCodeCredentials(pool, authStore, { quota }) : void 0;
|
|
1280
1802
|
const runtimeOptions = {
|
|
1281
1803
|
claudePath: options.claudePath,
|
|
1282
|
-
quota
|
|
1804
|
+
quota,
|
|
1805
|
+
authStore,
|
|
1806
|
+
getActiveCredentialId: pool ? () => pool.getActiveId() : void 0
|
|
1283
1807
|
};
|
|
1284
1808
|
return defineProvider({
|
|
1285
1809
|
id,
|
|
1286
1810
|
displayName,
|
|
1287
|
-
auth: { status: () => (
|
|
1288
|
-
status: "unknown",
|
|
1289
|
-
message: "Auth is checked when a Claude Code request runs"
|
|
1290
|
-
}) },
|
|
1811
|
+
auth: { status: () => authStore.status() },
|
|
1291
1812
|
quota,
|
|
1813
|
+
...credentialsApi ? { credentials: credentialsApi } : {},
|
|
1292
1814
|
state: () => ({
|
|
1293
1815
|
status: "unknown",
|
|
1294
1816
|
message: "Runtime is checked when a Claude Code request runs"
|
|
1295
1817
|
}),
|
|
1296
1818
|
listModels: async () => {
|
|
1297
|
-
|
|
1819
|
+
const catalog = await listClaudeCodeModels();
|
|
1820
|
+
return applyModelPolicy(catalog, id, options.models);
|
|
1298
1821
|
},
|
|
1299
1822
|
createRuntime: () => new ClaudeCodeProvider(runtimeOptions)
|
|
1300
1823
|
});
|
|
@@ -1306,18 +1829,19 @@ function isControlResponseFor(value, requestId) {
|
|
|
1306
1829
|
if (!isRecord(value) || value.type !== "control_response" || !isRecord(value.response)) return false;
|
|
1307
1830
|
return value.response.request_id === requestId && value.response.subtype === "success";
|
|
1308
1831
|
}
|
|
1309
|
-
function toolResultContentToText(output) {
|
|
1310
|
-
return output.map((block) => block.type === "text" ? block.text : `[image:${block.source.mediaType}]`).join("\n");
|
|
1311
|
-
}
|
|
1312
1832
|
function toolResultContentToMcp(output) {
|
|
1313
1833
|
return output.map((block) => {
|
|
1314
1834
|
if (block.type === "text") return {
|
|
1315
1835
|
type: "text",
|
|
1316
1836
|
text: block.text
|
|
1317
1837
|
};
|
|
1838
|
+
if (block.type === "video") return {
|
|
1839
|
+
type: "text",
|
|
1840
|
+
text: `[video:${block.source.mediaType}]`
|
|
1841
|
+
};
|
|
1318
1842
|
return {
|
|
1319
1843
|
type: "image",
|
|
1320
|
-
data:
|
|
1844
|
+
data: block.source.data,
|
|
1321
1845
|
mimeType: block.source.mediaType
|
|
1322
1846
|
};
|
|
1323
1847
|
});
|
|
@@ -1325,6 +1849,9 @@ function toolResultContentToMcp(output) {
|
|
|
1325
1849
|
function isMessageType(value, type) {
|
|
1326
1850
|
return isRecord(value) && value.type === type;
|
|
1327
1851
|
}
|
|
1852
|
+
function isStreamMessageStop(value) {
|
|
1853
|
+
return isRecord(value) && value.type === "stream_event" && isRecord(value.event) && value.event.type === "message_stop";
|
|
1854
|
+
}
|
|
1328
1855
|
function thinkingSignature(request) {
|
|
1329
1856
|
return JSON.stringify(request.thinking ?? null);
|
|
1330
1857
|
}
|
|
@@ -1349,4 +1876,4 @@ function itemsDiverged(active, items) {
|
|
|
1349
1876
|
return false;
|
|
1350
1877
|
}
|
|
1351
1878
|
//#endregion
|
|
1352
|
-
export { createClaudeCodeProvider, createClaudeCodeQuota, listClaudeCodeModels, mapClaudeUsagePayload, observeClaudeRateLimitHeaders, observeClaudeStreamBody, resolveClaudeCodeOAuthAccess, resolveWireLogDir };
|
|
1879
|
+
export { FileClaudeCodeAuthStore, PoolAwareClaudeCodeAuthStore, StaticClaudeCodeAuthStore, createClaudeCodeCredentials, createClaudeCodeProvider, createClaudeCodeQuota, listClaudeCodeModels, mapClaudeUsagePayload, observeClaudeRateLimitHeaders, observeClaudeStreamBody, openClaudeCodeCredentialPool, refreshClaudeCodeSecret, resolveClaudeCodeOAuthAccess, resolveWireLogDir, runClaudeCodeLogin };
|
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.
|
|
4
|
+
"version": "0.12.0",
|
|
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.
|
|
15
|
-
"@demicodes/provider": "^0.
|
|
16
|
-
"@demicodes/utils": "^0.
|
|
14
|
+
"@demicodes/core": "^0.12.0",
|
|
15
|
+
"@demicodes/provider": "^0.12.0",
|
|
16
|
+
"@demicodes/utils": "^0.12.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@demicodes/agent": "^0.
|
|
20
|
-
"@demicodes/shell": "^0.
|
|
19
|
+
"@demicodes/agent": "^0.12.0",
|
|
20
|
+
"@demicodes/shell": "^0.12.0"
|
|
21
21
|
},
|
|
22
22
|
"license": "Apache-2.0",
|
|
23
23
|
"main": "./dist/index.mjs",
|