@demicodes/provider-claude-code 0.3.2 → 0.4.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/dist/index.d.mts +51 -3
- package/dist/index.mjs +184 -34
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -11,8 +11,17 @@ type ModelCatalogFetch = (input: string | URL | Request, init?: RequestInit) =>
|
|
|
11
11
|
declare function listClaudeCodeModels(options?: ClaudeCodeModelCatalogOptions): Promise<ProviderModelList>;
|
|
12
12
|
//#endregion
|
|
13
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';
|
|
14
22
|
interface ClaudeCodeOAuthAccess {
|
|
15
23
|
accessToken: string;
|
|
24
|
+
source: ClaudeCodeOAuthSource;
|
|
16
25
|
subscriptionType?: string | null;
|
|
17
26
|
rateLimitTier?: string | null;
|
|
18
27
|
}
|
|
@@ -30,11 +39,15 @@ interface ClaudeCodeAuthStore {
|
|
|
30
39
|
forceRefresh?: boolean;
|
|
31
40
|
}): Promise<ClaudeCodeOAuthAccess>;
|
|
32
41
|
}
|
|
42
|
+
/** Renews a pool oauth secret (wired to `refreshClaudeCodeSecret`; injectable in tests). */
|
|
43
|
+
type ClaudeCodeSecretRefresh = (secret: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
33
44
|
interface FileClaudeCodeAuthStoreOptions {
|
|
34
45
|
/** Optional path to oauth.json (pool entry). */
|
|
35
46
|
oauthFile?: string;
|
|
36
47
|
/** Prefer this token over env/keychain when set (tests / static). */
|
|
37
48
|
accessToken?: string | null;
|
|
49
|
+
/** Renews the oauth file when its access token nears expiry. */
|
|
50
|
+
refresh?: ClaudeCodeSecretRefresh;
|
|
38
51
|
}
|
|
39
52
|
/**
|
|
40
53
|
* Resolves Claude OAuth: explicit token → oauth file → CLAUDE_CODE_OAUTH_TOKEN → keychain.
|
|
@@ -42,6 +55,7 @@ interface FileClaudeCodeAuthStoreOptions {
|
|
|
42
55
|
declare class FileClaudeCodeAuthStore implements ClaudeCodeAuthStore {
|
|
43
56
|
private readonly oauthFile;
|
|
44
57
|
private readonly accessToken;
|
|
58
|
+
private readonly refresh;
|
|
45
59
|
constructor(options?: FileClaudeCodeAuthStoreOptions);
|
|
46
60
|
status(): Promise<ProviderAuthState>;
|
|
47
61
|
resolveAccess(): Promise<ClaudeCodeOAuthAccess>;
|
|
@@ -106,10 +120,44 @@ declare function openClaudeCodeCredentialPool(options?: {
|
|
|
106
120
|
stateDir?: string;
|
|
107
121
|
}): FileCredentialPool;
|
|
108
122
|
declare function createClaudeCodeCredentials(pool: FileCredentialPool, authStore: ClaudeCodeAuthStore, options?: {
|
|
109
|
-
loginCommand?: string;
|
|
110
|
-
loginArgs?: string[];
|
|
111
123
|
quota?: ProviderQuota | null;
|
|
112
124
|
onActiveChange?: () => void;
|
|
125
|
+
/** Injectable fetch for the OAuth login flow (tests). */
|
|
126
|
+
loginFetch?: typeof fetch;
|
|
113
127
|
}): ProviderCredentials;
|
|
114
128
|
//#endregion
|
|
115
|
-
|
|
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,11 +1,11 @@
|
|
|
1
1
|
import { errorMessage, isRecord, nonEmptyString, numberOrNull, stringOrNull } from "@demicodes/utils";
|
|
2
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
3
3
|
import { applyModelPolicy, clampUsedPercent, createProviderQuota, defineProvider, numberHeader, severityFromUsedPercent, toolResultContentToText, unixSecondsToIso } from "@demicodes/provider";
|
|
4
4
|
import { execFile, spawn } from "node:child_process";
|
|
5
|
-
import { readFile } from "node:fs/promises";
|
|
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
|
|
8
|
+
import { FileCredentialPool, credentialIdFromIdentity } from "@demicodes/provider/credentials-pool";
|
|
9
9
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
10
10
|
import { appendFileSync, mkdirSync, statSync } from "node:fs";
|
|
11
11
|
import { createInterface } from "node:readline";
|
|
@@ -202,15 +202,18 @@ function reasoningEfforts(value) {
|
|
|
202
202
|
//#endregion
|
|
203
203
|
//#region src/auth.ts
|
|
204
204
|
const execFileAsync = promisify(execFile);
|
|
205
|
+
const OAUTH_EXPIRY_SKEW_MS = 300 * 1e3;
|
|
205
206
|
/**
|
|
206
207
|
* Resolves Claude OAuth: explicit token → oauth file → CLAUDE_CODE_OAUTH_TOKEN → keychain.
|
|
207
208
|
*/
|
|
208
209
|
var FileClaudeCodeAuthStore = class {
|
|
209
210
|
oauthFile;
|
|
210
211
|
accessToken;
|
|
212
|
+
refresh;
|
|
211
213
|
constructor(options = {}) {
|
|
212
214
|
this.oauthFile = options.oauthFile ?? null;
|
|
213
215
|
this.accessToken = nonEmptyString(options.accessToken) ?? null;
|
|
216
|
+
this.refresh = options.refresh ?? null;
|
|
214
217
|
}
|
|
215
218
|
async status() {
|
|
216
219
|
try {
|
|
@@ -230,14 +233,24 @@ var FileClaudeCodeAuthStore = class {
|
|
|
230
233
|
}
|
|
231
234
|
}
|
|
232
235
|
async resolveAccess() {
|
|
233
|
-
if (this.accessToken) return {
|
|
236
|
+
if (this.accessToken) return {
|
|
237
|
+
accessToken: this.accessToken,
|
|
238
|
+
source: "static"
|
|
239
|
+
};
|
|
234
240
|
if (this.oauthFile) try {
|
|
235
|
-
|
|
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
|
+
}
|
|
236
248
|
if (!isRecord(raw)) throw new ClaudeCodeAuthError("auth_invalid", `Invalid OAuth file: ${this.oauthFile}`);
|
|
237
249
|
const accessToken = nonEmptyString(raw.accessToken) ?? nonEmptyString(raw.access_token);
|
|
238
250
|
if (!accessToken) throw new ClaudeCodeAuthError("auth_missing", `No accessToken in ${this.oauthFile}`);
|
|
239
251
|
return {
|
|
240
252
|
accessToken,
|
|
253
|
+
source: "file",
|
|
241
254
|
subscriptionType: nonEmptyString(raw.subscriptionType) ?? null,
|
|
242
255
|
rateLimitTier: nonEmptyString(raw.rateLimitTier) ?? null
|
|
243
256
|
};
|
|
@@ -246,7 +259,10 @@ var FileClaudeCodeAuthStore = class {
|
|
|
246
259
|
throw new ClaudeCodeAuthError("auth_missing", `Failed to read Claude OAuth file ${this.oauthFile}: ${error instanceof Error ? error.message : String(error)}`);
|
|
247
260
|
}
|
|
248
261
|
const fromEnv = nonEmptyString(process$1.env.CLAUDE_CODE_OAUTH_TOKEN);
|
|
249
|
-
if (fromEnv) return {
|
|
262
|
+
if (fromEnv) return {
|
|
263
|
+
accessToken: fromEnv,
|
|
264
|
+
source: "env"
|
|
265
|
+
};
|
|
250
266
|
if (process$1.platform === "darwin") try {
|
|
251
267
|
const { stdout } = await execFileAsync("security", [
|
|
252
268
|
"find-generic-password",
|
|
@@ -265,6 +281,7 @@ var FileClaudeCodeAuthStore = class {
|
|
|
265
281
|
if (!accessToken) throw new ClaudeCodeAuthError("auth_missing", "Claude Code keychain missing accessToken");
|
|
266
282
|
return {
|
|
267
283
|
accessToken,
|
|
284
|
+
source: "keychain",
|
|
268
285
|
subscriptionType: nonEmptyString(oauth.subscriptionType) ?? null,
|
|
269
286
|
rateLimitTier: nonEmptyString(oauth.rateLimitTier) ?? null
|
|
270
287
|
};
|
|
@@ -298,6 +315,93 @@ var ClaudeCodeAuthError = class extends Error {
|
|
|
298
315
|
}
|
|
299
316
|
};
|
|
300
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
|
|
301
405
|
//#region src/credentials.ts
|
|
302
406
|
var PoolAwareClaudeCodeAuthStore = class {
|
|
303
407
|
pool;
|
|
@@ -313,7 +417,10 @@ var PoolAwareClaudeCodeAuthStore = class {
|
|
|
313
417
|
async currentStore() {
|
|
314
418
|
await this.pool.ensureActivePointer();
|
|
315
419
|
const activeId = await this.pool.getActiveId();
|
|
316
|
-
if (activeId) return new FileClaudeCodeAuthStore({
|
|
420
|
+
if (activeId) return new FileClaudeCodeAuthStore({
|
|
421
|
+
oauthFile: this.pool.secretPath(activeId),
|
|
422
|
+
refresh: (secret) => refreshClaudeCodeSecret(secret)
|
|
423
|
+
});
|
|
317
424
|
return new FileClaudeCodeAuthStore();
|
|
318
425
|
}
|
|
319
426
|
};
|
|
@@ -325,8 +432,6 @@ function openClaudeCodeCredentialPool(options = {}) {
|
|
|
325
432
|
});
|
|
326
433
|
}
|
|
327
434
|
function createClaudeCodeCredentials(pool, authStore, options = {}) {
|
|
328
|
-
const loginCommand = options.loginCommand ?? "claude";
|
|
329
|
-
const loginArgs = options.loginArgs ?? ["auth", "login"];
|
|
330
435
|
const capability = () => ({
|
|
331
436
|
mode: "supported",
|
|
332
437
|
canBeginLogin: true,
|
|
@@ -377,23 +482,58 @@ function createClaudeCodeCredentials(pool, authStore, options = {}) {
|
|
|
377
482
|
updatedAt: meta.updatedAt
|
|
378
483
|
};
|
|
379
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
|
+
};
|
|
380
509
|
return {
|
|
381
510
|
capability,
|
|
382
511
|
list: () => pool.list(),
|
|
383
512
|
getActive,
|
|
384
513
|
setActive,
|
|
385
514
|
beginLogin: async (loginOptions) => {
|
|
386
|
-
|
|
387
|
-
if (result.status === "completed") return { status: "completed" };
|
|
388
|
-
if (result.status === "cancelled") return { status: "cancelled" };
|
|
389
|
-
if (result.status === "unavailable") return {
|
|
515
|
+
if (!loginOptions?.promptForCode) return {
|
|
390
516
|
status: "unavailable",
|
|
391
|
-
message:
|
|
392
|
-
};
|
|
393
|
-
return {
|
|
394
|
-
status: "failed",
|
|
395
|
-
message: result.message ?? "Login failed"
|
|
517
|
+
message: "Claude login requires promptForCode to collect the pasted authorization code"
|
|
396
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
|
+
}
|
|
397
537
|
},
|
|
398
538
|
importDefault: async () => {
|
|
399
539
|
const vendor = new FileClaudeCodeAuthStore();
|
|
@@ -408,6 +548,7 @@ function createClaudeCodeCredentials(pool, authStore, options = {}) {
|
|
|
408
548
|
add: async (input) => {
|
|
409
549
|
if (typeof input.accessToken === "string") return importAccess({
|
|
410
550
|
accessToken: input.accessToken,
|
|
551
|
+
source: "static",
|
|
411
552
|
subscriptionType: typeof input.subscriptionType === "string" ? input.subscriptionType : null,
|
|
412
553
|
rateLimitTier: typeof input.rateLimitTier === "string" ? input.rateLimitTier : null
|
|
413
554
|
}, "add:accessToken");
|
|
@@ -415,6 +556,7 @@ function createClaudeCodeCredentials(pool, authStore, options = {}) {
|
|
|
415
556
|
const oauth = input.oauth;
|
|
416
557
|
return importAccess({
|
|
417
558
|
accessToken: oauth.accessToken,
|
|
559
|
+
source: "static",
|
|
418
560
|
subscriptionType: typeof oauth.subscriptionType === "string" ? oauth.subscriptionType : null,
|
|
419
561
|
rateLimitTier: typeof oauth.rateLimitTier === "string" ? oauth.rateLimitTier : null
|
|
420
562
|
}, "add:oauth");
|
|
@@ -663,6 +805,28 @@ function toolNameToClaude(name) {
|
|
|
663
805
|
return `mcp__main__${name}`;
|
|
664
806
|
}
|
|
665
807
|
//#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
|
|
666
830
|
//#region src/output.ts
|
|
667
831
|
function mapClaudeStdoutMessage(message, options = {}) {
|
|
668
832
|
const events = [];
|
|
@@ -872,20 +1036,6 @@ function stripMcpToolPrefix(name) {
|
|
|
872
1036
|
return /^mcp__[^_]+__(.+)$/.exec(name)?.[1] ?? name;
|
|
873
1037
|
}
|
|
874
1038
|
//#endregion
|
|
875
|
-
//#region src/oauth.ts
|
|
876
|
-
/**
|
|
877
|
-
* Resolve Claude Code consumer OAuth access for quota APIs.
|
|
878
|
-
* Order: options env CLAUDE_CODE_OAUTH_TOKEN, then macOS Keychain "Claude Code-credentials".
|
|
879
|
-
* Prefer injecting {@link ClaudeCodeAuthStore} when multi-credential is enabled.
|
|
880
|
-
*/
|
|
881
|
-
async function resolveClaudeCodeOAuthAccess() {
|
|
882
|
-
try {
|
|
883
|
-
return await new FileClaudeCodeAuthStore().resolveAccess();
|
|
884
|
-
} catch {
|
|
885
|
-
return null;
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
//#endregion
|
|
889
1039
|
//#region src/quota.ts
|
|
890
1040
|
const DEFAULT_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
891
1041
|
const DEFAULT_OAUTH_BETA = "oauth-2025-04-20";
|
|
@@ -1227,7 +1377,7 @@ var ClaudeCodeProvider = class {
|
|
|
1227
1377
|
claudePath: options.claudePath,
|
|
1228
1378
|
resolveOAuthAccessToken: options.authStore ? async () => {
|
|
1229
1379
|
try {
|
|
1230
|
-
return (await options.authStore.resolveAccess())
|
|
1380
|
+
return injectableCliToken(await options.authStore.resolveAccess());
|
|
1231
1381
|
} catch {
|
|
1232
1382
|
return null;
|
|
1233
1383
|
}
|
|
@@ -1631,4 +1781,4 @@ function itemsDiverged(active, items) {
|
|
|
1631
1781
|
return false;
|
|
1632
1782
|
}
|
|
1633
1783
|
//#endregion
|
|
1634
|
-
export { FileClaudeCodeAuthStore, PoolAwareClaudeCodeAuthStore, StaticClaudeCodeAuthStore, createClaudeCodeCredentials, createClaudeCodeProvider, createClaudeCodeQuota, listClaudeCodeModels, mapClaudeUsagePayload, observeClaudeRateLimitHeaders, observeClaudeStreamBody, openClaudeCodeCredentialPool, resolveClaudeCodeOAuthAccess, resolveWireLogDir };
|
|
1784
|
+
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.4.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@demicodes/core": "^0.3.2",
|
|
15
|
-
"@demicodes/provider": "^0.
|
|
15
|
+
"@demicodes/provider": "^0.4.0",
|
|
16
16
|
"@demicodes/utils": "^0.3.2"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@demicodes/agent": "^0.3.
|
|
19
|
+
"@demicodes/agent": "^0.3.3",
|
|
20
20
|
"@demicodes/shell": "^0.3.2"
|
|
21
21
|
},
|
|
22
22
|
"license": "Apache-2.0",
|