@demicodes/provider-claude-code 0.1.0 → 0.2.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 +20 -0
- package/dist/index.d.mts +88 -3
- package/dist/index.mjs +482 -29
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -5,8 +5,28 @@ 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
|
+
|
|
10
30
|
> Diagnostics: the transport writes a raw request/response wire log (including
|
|
11
31
|
> prompts) to `$TMPDIR/demi-claude-wire` by default. Disable with
|
|
12
32
|
> `DEMI_CLAUDE_WIRE_LOG=0`. See [SECURITY](../../SECURITY.md).
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ModelPolicy, Provider, ProviderModelList } 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,60 @@ 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
|
+
interface ClaudeCodeOAuthAccess {
|
|
15
|
+
accessToken: string;
|
|
16
|
+
subscriptionType?: string | null;
|
|
17
|
+
rateLimitTier?: string | null;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolve Claude Code consumer OAuth access for quota APIs.
|
|
21
|
+
* Order: options env CLAUDE_CODE_OAUTH_TOKEN, then macOS Keychain "Claude Code-credentials".
|
|
22
|
+
* Prefer injecting {@link ClaudeCodeAuthStore} when multi-credential is enabled.
|
|
23
|
+
*/
|
|
24
|
+
declare function resolveClaudeCodeOAuthAccess(): Promise<ClaudeCodeOAuthAccess | null>;
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/auth.d.ts
|
|
27
|
+
interface ClaudeCodeAuthStore {
|
|
28
|
+
status(): Promise<ProviderAuthState>;
|
|
29
|
+
resolveAccess(options?: {
|
|
30
|
+
forceRefresh?: boolean;
|
|
31
|
+
}): Promise<ClaudeCodeOAuthAccess>;
|
|
32
|
+
}
|
|
33
|
+
interface FileClaudeCodeAuthStoreOptions {
|
|
34
|
+
/** Optional path to oauth.json (pool entry). */
|
|
35
|
+
oauthFile?: string;
|
|
36
|
+
/** Prefer this token over env/keychain when set (tests / static). */
|
|
37
|
+
accessToken?: string | null;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolves Claude OAuth: explicit token → oauth file → CLAUDE_CODE_OAUTH_TOKEN → keychain.
|
|
41
|
+
*/
|
|
42
|
+
declare class FileClaudeCodeAuthStore implements ClaudeCodeAuthStore {
|
|
43
|
+
private readonly oauthFile;
|
|
44
|
+
private readonly accessToken;
|
|
45
|
+
constructor(options?: FileClaudeCodeAuthStoreOptions);
|
|
46
|
+
status(): Promise<ProviderAuthState>;
|
|
47
|
+
resolveAccess(): Promise<ClaudeCodeOAuthAccess>;
|
|
48
|
+
}
|
|
49
|
+
declare class StaticClaudeCodeAuthStore implements ClaudeCodeAuthStore {
|
|
50
|
+
private readonly access;
|
|
51
|
+
constructor(access: ClaudeCodeOAuthAccess);
|
|
52
|
+
status(): Promise<ProviderAuthState>;
|
|
53
|
+
resolveAccess(): Promise<ClaudeCodeOAuthAccess>;
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
13
56
|
//#region src/provider.d.ts
|
|
14
57
|
interface ClaudeCodeProviderOptions {
|
|
15
58
|
id?: string;
|
|
16
59
|
displayName?: string;
|
|
17
60
|
claudePath?: string;
|
|
18
61
|
models?: ModelPolicy;
|
|
62
|
+
/** Demi state root for credential pool (`$DEMI_HOME` / `~/.demi`). */
|
|
63
|
+
stateDir?: string;
|
|
64
|
+
/** When true (default), attach multi-credential pool + global switch. */
|
|
65
|
+
credentials?: boolean;
|
|
66
|
+
authStore?: ClaudeCodeAuthStore;
|
|
19
67
|
}
|
|
20
68
|
declare function createClaudeCodeProvider(options?: ClaudeCodeProviderOptions): Provider;
|
|
21
69
|
//#endregion
|
|
@@ -27,4 +75,41 @@ declare function createClaudeCodeProvider(options?: ClaudeCodeProviderOptions):
|
|
|
27
75
|
*/
|
|
28
76
|
declare function resolveWireLogDir(): string | null;
|
|
29
77
|
//#endregion
|
|
30
|
-
|
|
78
|
+
//#region src/quota.d.ts
|
|
79
|
+
interface ClaudeCodeQuotaOptions {
|
|
80
|
+
providerId?: string;
|
|
81
|
+
/** Override token resolution (tests / custom stores). */
|
|
82
|
+
resolveAccess?: () => Promise<ClaudeCodeOAuthAccess | null>;
|
|
83
|
+
fetch?: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
84
|
+
usageUrl?: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Active probe: GET /api/oauth/usage (Claude.ai consumer plan windows).
|
|
88
|
+
* Observation: anthropic-ratelimit-unified-* response headers.
|
|
89
|
+
*/
|
|
90
|
+
declare function createClaudeCodeQuota(options?: ClaudeCodeQuotaOptions): ProviderQuota;
|
|
91
|
+
/** Claude CLI stream-json / status envelopes that embed `rate_limits`. */
|
|
92
|
+
declare function observeClaudeStreamBody(body: unknown): ProviderQuotaProbeResult | null;
|
|
93
|
+
declare function mapClaudeUsagePayload(payload: unknown, access?: ClaudeCodeOAuthAccess | null): ProviderQuotaProbeResult;
|
|
94
|
+
/** Map anthropic-ratelimit-unified-* headers into a coarse snapshot. */
|
|
95
|
+
declare function observeClaudeRateLimitHeaders(headers: Headers | undefined): ProviderQuotaProbeResult | null;
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/credentials.d.ts
|
|
98
|
+
declare class PoolAwareClaudeCodeAuthStore implements ClaudeCodeAuthStore {
|
|
99
|
+
private readonly pool;
|
|
100
|
+
constructor(pool: FileCredentialPool);
|
|
101
|
+
status(): Promise<import("@demicodes/provider").ProviderAuthState>;
|
|
102
|
+
resolveAccess(): Promise<ClaudeCodeOAuthAccess>;
|
|
103
|
+
private currentStore;
|
|
104
|
+
}
|
|
105
|
+
declare function openClaudeCodeCredentialPool(options?: {
|
|
106
|
+
stateDir?: string;
|
|
107
|
+
}): FileCredentialPool;
|
|
108
|
+
declare function createClaudeCodeCredentials(pool: FileCredentialPool, authStore: ClaudeCodeAuthStore, options?: {
|
|
109
|
+
loginCommand?: string;
|
|
110
|
+
loginArgs?: string[];
|
|
111
|
+
quota?: ProviderQuota | null;
|
|
112
|
+
onActiveChange?: () => void;
|
|
113
|
+
}): ProviderCredentials;
|
|
114
|
+
//#endregion
|
|
115
|
+
export { type ClaudeCodeAuthStore, type ClaudeCodeModelCatalogOptions, type ClaudeCodeOAuthAccess, type ClaudeCodeProviderOptions, type ClaudeCodeQuotaOptions, FileClaudeCodeAuthStore, PoolAwareClaudeCodeAuthStore, StaticClaudeCodeAuthStore, createClaudeCodeCredentials, createClaudeCodeProvider, createClaudeCodeQuota, listClaudeCodeModels, mapClaudeUsagePayload, observeClaudeRateLimitHeaders, observeClaudeStreamBody, openClaudeCodeCredentialPool, resolveClaudeCodeOAuthAccess, resolveWireLogDir };
|
package/dist/index.mjs
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { errorMessage, isRecord, nonEmptyString, numberOrNull, stringOrNull } from "@demicodes/utils";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { applyModelPolicy, defineProvider } from "@demicodes/provider";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { applyModelPolicy, clampUsedPercent, createProviderQuota, defineProvider, numberHeader, severityFromUsedPercent, toolResultContentToText, unixSecondsToIso } from "@demicodes/provider";
|
|
4
|
+
import { execFile, spawn } from "node:child_process";
|
|
5
|
+
import { readFile } from "node:fs/promises";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
import process$1 from "node:process";
|
|
8
|
+
import { FileCredentialPool, credentialIdFromIdentity, runVendorLoginCommand } from "@demicodes/provider/credentials-pool";
|
|
4
9
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
5
|
-
import { spawn } from "node:child_process";
|
|
6
10
|
import { appendFileSync, mkdirSync, statSync } from "node:fs";
|
|
7
11
|
import { createInterface } from "node:readline";
|
|
8
12
|
import { tmpdir } from "node:os";
|
|
9
13
|
import { join } from "node:path";
|
|
10
|
-
import process$1 from "node:process";
|
|
11
14
|
//#region src/models.ts
|
|
12
15
|
const DEFAULT_MODELS_DEV_URL = "https://models.dev/api.json";
|
|
13
16
|
const DEFAULT_MINIMUM_MODEL_VERSION = "4.6";
|
|
@@ -94,7 +97,8 @@ const CLAUDE_FAMILY_RANK = {
|
|
|
94
97
|
haiku: 2
|
|
95
98
|
};
|
|
96
99
|
function claudeFamilyRank(id) {
|
|
97
|
-
|
|
100
|
+
const family = id.slice(7).split("-")[0] ?? "";
|
|
101
|
+
return CLAUDE_FAMILY_RANK[family] ?? 3;
|
|
98
102
|
}
|
|
99
103
|
/** Canonical catalog order: flagship family first (Opus > Sonnet > Haiku > others), newest version first. */
|
|
100
104
|
function compareClaudeModels(a, b) {
|
|
@@ -196,6 +200,235 @@ function reasoningEfforts(value) {
|
|
|
196
200
|
return efforts.length > 0 ? efforts : [];
|
|
197
201
|
}
|
|
198
202
|
//#endregion
|
|
203
|
+
//#region src/auth.ts
|
|
204
|
+
const execFileAsync = promisify(execFile);
|
|
205
|
+
/**
|
|
206
|
+
* Resolves Claude OAuth: explicit token → oauth file → CLAUDE_CODE_OAUTH_TOKEN → keychain.
|
|
207
|
+
*/
|
|
208
|
+
var FileClaudeCodeAuthStore = class {
|
|
209
|
+
oauthFile;
|
|
210
|
+
accessToken;
|
|
211
|
+
constructor(options = {}) {
|
|
212
|
+
this.oauthFile = options.oauthFile ?? null;
|
|
213
|
+
this.accessToken = nonEmptyString(options.accessToken) ?? null;
|
|
214
|
+
}
|
|
215
|
+
async status() {
|
|
216
|
+
try {
|
|
217
|
+
return {
|
|
218
|
+
status: "authenticated",
|
|
219
|
+
accountLabel: nonEmptyString((await this.resolveAccess()).subscriptionType) ?? "Claude Code"
|
|
220
|
+
};
|
|
221
|
+
} catch (error) {
|
|
222
|
+
if (error instanceof ClaudeCodeAuthError && error.code === "auth_missing") return {
|
|
223
|
+
status: "unauthenticated",
|
|
224
|
+
message: error.message
|
|
225
|
+
};
|
|
226
|
+
return {
|
|
227
|
+
status: "error",
|
|
228
|
+
message: error instanceof Error ? error.message : String(error)
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
async resolveAccess() {
|
|
233
|
+
if (this.accessToken) return { accessToken: this.accessToken };
|
|
234
|
+
if (this.oauthFile) try {
|
|
235
|
+
const raw = JSON.parse(await readFile(this.oauthFile, "utf8"));
|
|
236
|
+
if (!isRecord(raw)) throw new ClaudeCodeAuthError("auth_invalid", `Invalid OAuth file: ${this.oauthFile}`);
|
|
237
|
+
const accessToken = nonEmptyString(raw.accessToken) ?? nonEmptyString(raw.access_token);
|
|
238
|
+
if (!accessToken) throw new ClaudeCodeAuthError("auth_missing", `No accessToken in ${this.oauthFile}`);
|
|
239
|
+
return {
|
|
240
|
+
accessToken,
|
|
241
|
+
subscriptionType: nonEmptyString(raw.subscriptionType) ?? null,
|
|
242
|
+
rateLimitTier: nonEmptyString(raw.rateLimitTier) ?? null
|
|
243
|
+
};
|
|
244
|
+
} catch (error) {
|
|
245
|
+
if (error instanceof ClaudeCodeAuthError) throw error;
|
|
246
|
+
throw new ClaudeCodeAuthError("auth_missing", `Failed to read Claude OAuth file ${this.oauthFile}: ${error instanceof Error ? error.message : String(error)}`);
|
|
247
|
+
}
|
|
248
|
+
const fromEnv = nonEmptyString(process$1.env.CLAUDE_CODE_OAUTH_TOKEN);
|
|
249
|
+
if (fromEnv) return { accessToken: fromEnv };
|
|
250
|
+
if (process$1.platform === "darwin") try {
|
|
251
|
+
const { stdout } = await execFileAsync("security", [
|
|
252
|
+
"find-generic-password",
|
|
253
|
+
"-s",
|
|
254
|
+
"Claude Code-credentials",
|
|
255
|
+
"-w"
|
|
256
|
+
], {
|
|
257
|
+
encoding: "utf8",
|
|
258
|
+
timeout: 5e3
|
|
259
|
+
});
|
|
260
|
+
const parsed = JSON.parse(stdout.trim());
|
|
261
|
+
if (!isRecord(parsed)) throw new ClaudeCodeAuthError("auth_missing", "Claude Code keychain item is not a JSON object");
|
|
262
|
+
const oauth = isRecord(parsed.claudeAiOauth) ? parsed.claudeAiOauth : null;
|
|
263
|
+
if (!oauth) throw new ClaudeCodeAuthError("auth_missing", "Claude Code keychain missing claudeAiOauth");
|
|
264
|
+
const accessToken = nonEmptyString(oauth.accessToken);
|
|
265
|
+
if (!accessToken) throw new ClaudeCodeAuthError("auth_missing", "Claude Code keychain missing accessToken");
|
|
266
|
+
return {
|
|
267
|
+
accessToken,
|
|
268
|
+
subscriptionType: nonEmptyString(oauth.subscriptionType) ?? null,
|
|
269
|
+
rateLimitTier: nonEmptyString(oauth.rateLimitTier) ?? null
|
|
270
|
+
};
|
|
271
|
+
} catch (error) {
|
|
272
|
+
if (error instanceof ClaudeCodeAuthError) throw error;
|
|
273
|
+
}
|
|
274
|
+
throw new ClaudeCodeAuthError("auth_missing", "Claude Code OAuth access token not found (set CLAUDE_CODE_OAUTH_TOKEN or log in with Claude Code)");
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
var StaticClaudeCodeAuthStore = class {
|
|
278
|
+
access;
|
|
279
|
+
constructor(access) {
|
|
280
|
+
this.access = access;
|
|
281
|
+
}
|
|
282
|
+
async status() {
|
|
283
|
+
return {
|
|
284
|
+
status: "authenticated",
|
|
285
|
+
accountLabel: nonEmptyString(this.access.subscriptionType) ?? "Claude Code"
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
async resolveAccess() {
|
|
289
|
+
return this.access;
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
var ClaudeCodeAuthError = class extends Error {
|
|
293
|
+
code;
|
|
294
|
+
constructor(code, message) {
|
|
295
|
+
super(message);
|
|
296
|
+
this.code = code;
|
|
297
|
+
this.name = "ClaudeCodeAuthError";
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
//#endregion
|
|
301
|
+
//#region src/credentials.ts
|
|
302
|
+
var PoolAwareClaudeCodeAuthStore = class {
|
|
303
|
+
pool;
|
|
304
|
+
constructor(pool) {
|
|
305
|
+
this.pool = pool;
|
|
306
|
+
}
|
|
307
|
+
async status() {
|
|
308
|
+
return this.currentStore().then((s) => s.status());
|
|
309
|
+
}
|
|
310
|
+
async resolveAccess() {
|
|
311
|
+
return this.currentStore().then((s) => s.resolveAccess());
|
|
312
|
+
}
|
|
313
|
+
async currentStore() {
|
|
314
|
+
await this.pool.ensureActivePointer();
|
|
315
|
+
const activeId = await this.pool.getActiveId();
|
|
316
|
+
if (activeId) return new FileClaudeCodeAuthStore({ oauthFile: this.pool.secretPath(activeId) });
|
|
317
|
+
return new FileClaudeCodeAuthStore();
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
function openClaudeCodeCredentialPool(options = {}) {
|
|
321
|
+
return new FileCredentialPool({
|
|
322
|
+
stateDir: options.stateDir,
|
|
323
|
+
providerKey: "claude-code",
|
|
324
|
+
secretFileName: "oauth.json"
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
function createClaudeCodeCredentials(pool, authStore, options = {}) {
|
|
328
|
+
const loginCommand = options.loginCommand ?? "claude";
|
|
329
|
+
const loginArgs = options.loginArgs ?? ["auth", "login"];
|
|
330
|
+
const capability = () => ({
|
|
331
|
+
mode: "supported",
|
|
332
|
+
canBeginLogin: true,
|
|
333
|
+
canImportDefault: true,
|
|
334
|
+
canAdd: true,
|
|
335
|
+
multi: true
|
|
336
|
+
});
|
|
337
|
+
const getActive = async () => {
|
|
338
|
+
await pool.ensureActivePointer();
|
|
339
|
+
return {
|
|
340
|
+
credentialId: await pool.getActiveId(),
|
|
341
|
+
status: await authStore.status()
|
|
342
|
+
};
|
|
343
|
+
};
|
|
344
|
+
const setActive = async (credentialId) => {
|
|
345
|
+
await pool.setActiveId(credentialId);
|
|
346
|
+
options.quota?.clearLatest?.();
|
|
347
|
+
options.onActiveChange?.();
|
|
348
|
+
return getActive();
|
|
349
|
+
};
|
|
350
|
+
const importAccess = async (access, source) => {
|
|
351
|
+
const token = nonEmptyString(access.accessToken);
|
|
352
|
+
if (!token) throw new ClaudeCodeAuthError("auth_missing", "No Claude access token to import");
|
|
353
|
+
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)}`;
|
|
354
|
+
const label = nonEmptyString(access.subscriptionType) ?? `claude-${identityKey.slice(-8)}`;
|
|
355
|
+
const id = (await pool.findByIdentityKey(identityKey))?.id ?? credentialIdFromIdentity(identityKey, label);
|
|
356
|
+
const meta = {
|
|
357
|
+
id,
|
|
358
|
+
label,
|
|
359
|
+
detail: nonEmptyString(access.rateLimitTier) ?? null,
|
|
360
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
361
|
+
source,
|
|
362
|
+
identityKey
|
|
363
|
+
};
|
|
364
|
+
const secret = {
|
|
365
|
+
accessToken: token,
|
|
366
|
+
subscriptionType: access.subscriptionType ?? null,
|
|
367
|
+
rateLimitTier: access.rateLimitTier ?? null
|
|
368
|
+
};
|
|
369
|
+
await pool.writeEntry(meta, `${JSON.stringify(secret, null, 2)}\n`);
|
|
370
|
+
if (!await pool.getActiveId()) await pool.setActiveId(id);
|
|
371
|
+
options.quota?.clearLatest?.();
|
|
372
|
+
options.onActiveChange?.();
|
|
373
|
+
return {
|
|
374
|
+
id: meta.id,
|
|
375
|
+
label: meta.label,
|
|
376
|
+
detail: meta.detail,
|
|
377
|
+
updatedAt: meta.updatedAt
|
|
378
|
+
};
|
|
379
|
+
};
|
|
380
|
+
return {
|
|
381
|
+
capability,
|
|
382
|
+
list: () => pool.list(),
|
|
383
|
+
getActive,
|
|
384
|
+
setActive,
|
|
385
|
+
beginLogin: async (loginOptions) => {
|
|
386
|
+
const result = await runVendorLoginCommand(loginCommand, loginArgs, { signal: loginOptions?.signal });
|
|
387
|
+
if (result.status === "completed") return { status: "completed" };
|
|
388
|
+
if (result.status === "cancelled") return { status: "cancelled" };
|
|
389
|
+
if (result.status === "unavailable") return {
|
|
390
|
+
status: "unavailable",
|
|
391
|
+
message: result.message ?? "Login unavailable"
|
|
392
|
+
};
|
|
393
|
+
return {
|
|
394
|
+
status: "failed",
|
|
395
|
+
message: result.message ?? "Login failed"
|
|
396
|
+
};
|
|
397
|
+
},
|
|
398
|
+
importDefault: async () => {
|
|
399
|
+
const vendor = new FileClaudeCodeAuthStore();
|
|
400
|
+
let access;
|
|
401
|
+
try {
|
|
402
|
+
access = await vendor.resolveAccess();
|
|
403
|
+
} catch {
|
|
404
|
+
throw new ClaudeCodeAuthError("auth_missing", "No Claude Code OAuth to import. Run claude auth login or beginLogin first.");
|
|
405
|
+
}
|
|
406
|
+
return importAccess(access, "vendor:default");
|
|
407
|
+
},
|
|
408
|
+
add: async (input) => {
|
|
409
|
+
if (typeof input.accessToken === "string") return importAccess({
|
|
410
|
+
accessToken: input.accessToken,
|
|
411
|
+
subscriptionType: typeof input.subscriptionType === "string" ? input.subscriptionType : null,
|
|
412
|
+
rateLimitTier: typeof input.rateLimitTier === "string" ? input.rateLimitTier : null
|
|
413
|
+
}, "add:accessToken");
|
|
414
|
+
if (isRecord(input.oauth) && typeof input.oauth.accessToken === "string") {
|
|
415
|
+
const oauth = input.oauth;
|
|
416
|
+
return importAccess({
|
|
417
|
+
accessToken: oauth.accessToken,
|
|
418
|
+
subscriptionType: typeof oauth.subscriptionType === "string" ? oauth.subscriptionType : null,
|
|
419
|
+
rateLimitTier: typeof oauth.rateLimitTier === "string" ? oauth.rateLimitTier : null
|
|
420
|
+
}, "add:oauth");
|
|
421
|
+
}
|
|
422
|
+
throw new Error("Claude credentials.add expects accessToken or oauth.accessToken");
|
|
423
|
+
},
|
|
424
|
+
remove: async (credentialId) => {
|
|
425
|
+
await pool.remove(credentialId);
|
|
426
|
+
options.quota?.clearLatest?.();
|
|
427
|
+
options.onActiveChange?.();
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
//#endregion
|
|
199
432
|
//#region src/jsonl.ts
|
|
200
433
|
/**
|
|
201
434
|
* Builds the input messages used to prime a *fresh* Claude CLI process with prior
|
|
@@ -273,7 +506,7 @@ function renderToolUseText(toolName, input) {
|
|
|
273
506
|
return `[Earlier in this conversation I called the tool ${toolName} with input: ${safeJson(input)}.`;
|
|
274
507
|
}
|
|
275
508
|
function renderToolResultText(toolName, item) {
|
|
276
|
-
const body =
|
|
509
|
+
const body = toolResultContentToText(item.output);
|
|
277
510
|
const suffix = toolName ? ` from ${toolName}` : "";
|
|
278
511
|
return item.isError ? `It returned an error${suffix}: ${body}]` : `It returned${suffix}: ${body}]`;
|
|
279
512
|
}
|
|
@@ -338,6 +571,10 @@ function userContentToClaude(content) {
|
|
|
338
571
|
type: "image",
|
|
339
572
|
source: imageSourceToClaude(block.source)
|
|
340
573
|
};
|
|
574
|
+
if (block.type === "video") return {
|
|
575
|
+
type: "text",
|
|
576
|
+
text: "[video]"
|
|
577
|
+
};
|
|
341
578
|
if (block.type === "document") return documentSourceToClaude(block.source);
|
|
342
579
|
return {
|
|
343
580
|
type: "text",
|
|
@@ -370,11 +607,30 @@ function assistantItemToClaudeContent(item) {
|
|
|
370
607
|
};
|
|
371
608
|
}
|
|
372
609
|
function toolResultToClaudeContent(item) {
|
|
610
|
+
const hasImage = item.output.some((block) => block.type !== "text");
|
|
373
611
|
return {
|
|
374
612
|
type: "tool_result",
|
|
375
613
|
tool_use_id: item.toolUseId,
|
|
376
614
|
is_error: item.isError,
|
|
377
|
-
content:
|
|
615
|
+
content: hasImage ? item.output.map(toolResultBlockToClaude) : toolResultContentToText(item.output)
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
function toolResultBlockToClaude(block) {
|
|
619
|
+
if (block.type === "text") return {
|
|
620
|
+
type: "text",
|
|
621
|
+
text: block.text
|
|
622
|
+
};
|
|
623
|
+
if (block.type === "video") return {
|
|
624
|
+
type: "text",
|
|
625
|
+
text: `[video:${block.source.mediaType}]`
|
|
626
|
+
};
|
|
627
|
+
return {
|
|
628
|
+
type: "image",
|
|
629
|
+
source: {
|
|
630
|
+
type: "base64",
|
|
631
|
+
media_type: block.source.mediaType,
|
|
632
|
+
data: block.source.data
|
|
633
|
+
}
|
|
378
634
|
};
|
|
379
635
|
}
|
|
380
636
|
function imageSourceToClaude(source) {
|
|
@@ -399,9 +655,6 @@ function documentSourceToClaude(source) {
|
|
|
399
655
|
title: source.fileName
|
|
400
656
|
};
|
|
401
657
|
}
|
|
402
|
-
function toolResultToText(output) {
|
|
403
|
-
return output.map((block) => block.type === "text" ? block.text : `[image:${block.source.mediaType}]`).join("\n");
|
|
404
|
-
}
|
|
405
658
|
function bytesToBase64(data) {
|
|
406
659
|
return Buffer$1.from(data.buffer, data.byteOffset, data.byteLength).toString("base64");
|
|
407
660
|
}
|
|
@@ -604,6 +857,157 @@ function stripMcpToolPrefix(name) {
|
|
|
604
857
|
return /^mcp__[^_]+__(.+)$/.exec(name)?.[1] ?? name;
|
|
605
858
|
}
|
|
606
859
|
//#endregion
|
|
860
|
+
//#region src/oauth.ts
|
|
861
|
+
/**
|
|
862
|
+
* Resolve Claude Code consumer OAuth access for quota APIs.
|
|
863
|
+
* Order: options env CLAUDE_CODE_OAUTH_TOKEN, then macOS Keychain "Claude Code-credentials".
|
|
864
|
+
* Prefer injecting {@link ClaudeCodeAuthStore} when multi-credential is enabled.
|
|
865
|
+
*/
|
|
866
|
+
async function resolveClaudeCodeOAuthAccess() {
|
|
867
|
+
try {
|
|
868
|
+
return await new FileClaudeCodeAuthStore().resolveAccess();
|
|
869
|
+
} catch {
|
|
870
|
+
return null;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
//#endregion
|
|
874
|
+
//#region src/quota.ts
|
|
875
|
+
const DEFAULT_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
876
|
+
const DEFAULT_OAUTH_BETA = "oauth-2025-04-20";
|
|
877
|
+
/**
|
|
878
|
+
* Active probe: GET /api/oauth/usage (Claude.ai consumer plan windows).
|
|
879
|
+
* Observation: anthropic-ratelimit-unified-* response headers.
|
|
880
|
+
*/
|
|
881
|
+
function createClaudeCodeQuota(options = {}) {
|
|
882
|
+
const providerId = options.providerId ?? "claude-code";
|
|
883
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
884
|
+
const usageUrl = options.usageUrl ?? DEFAULT_USAGE_URL;
|
|
885
|
+
const resolveAccess = options.resolveAccess ?? resolveClaudeCodeOAuthAccess;
|
|
886
|
+
return createProviderQuota({
|
|
887
|
+
providerId,
|
|
888
|
+
canProbe: true,
|
|
889
|
+
canObserve: true,
|
|
890
|
+
probeCost: "free",
|
|
891
|
+
staleAfterMs: 6e4,
|
|
892
|
+
probe: async ({ signal } = {}) => {
|
|
893
|
+
const access = await resolveAccess();
|
|
894
|
+
if (!access?.accessToken) throw new Error("Claude Code OAuth access token not found (set CLAUDE_CODE_OAUTH_TOKEN or log in with Claude Code)");
|
|
895
|
+
const headers = new Headers({
|
|
896
|
+
authorization: `Bearer ${access.accessToken}`,
|
|
897
|
+
"anthropic-beta": DEFAULT_OAUTH_BETA,
|
|
898
|
+
accept: "application/json",
|
|
899
|
+
"user-agent": "demi-provider-claude-code"
|
|
900
|
+
});
|
|
901
|
+
const response = await fetchImpl(usageUrl, {
|
|
902
|
+
method: "GET",
|
|
903
|
+
headers,
|
|
904
|
+
signal
|
|
905
|
+
});
|
|
906
|
+
if (!response.ok) {
|
|
907
|
+
const body = await response.text().catch(() => "");
|
|
908
|
+
throw new Error(`Claude usage request failed (${response.status}): ${body.slice(0, 200)}`);
|
|
909
|
+
}
|
|
910
|
+
return mapClaudeUsagePayload(await response.json(), access);
|
|
911
|
+
},
|
|
912
|
+
observe: ({ headers, body }) => {
|
|
913
|
+
if (headers) {
|
|
914
|
+
const fromHeaders = observeClaudeRateLimitHeaders(headers);
|
|
915
|
+
if (fromHeaders) return fromHeaders;
|
|
916
|
+
}
|
|
917
|
+
if (body !== void 0) return observeClaudeStreamBody(body);
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
/** Claude CLI stream-json / status envelopes that embed `rate_limits`. */
|
|
923
|
+
function observeClaudeStreamBody(body) {
|
|
924
|
+
if (!isRecord(body)) return null;
|
|
925
|
+
const rateLimits = isRecord(body.rate_limits) ? body.rate_limits : isRecord(body.message) && isRecord(body.message.rate_limits) ? body.message.rate_limits : null;
|
|
926
|
+
if (!rateLimits) return null;
|
|
927
|
+
const partial = mapClaudeUsagePayload(rateLimits);
|
|
928
|
+
return partial.windows.length > 0 ? {
|
|
929
|
+
windows: partial.windows,
|
|
930
|
+
raw: rateLimits
|
|
931
|
+
} : null;
|
|
932
|
+
}
|
|
933
|
+
function mapClaudeUsagePayload(payload, access) {
|
|
934
|
+
const record = isRecord(payload) ? payload : {};
|
|
935
|
+
const windows = [];
|
|
936
|
+
pushWindow(windows, "five_hour", "5h session", record.five_hour);
|
|
937
|
+
pushWindow(windows, "seven_day", "7d all models", record.seven_day);
|
|
938
|
+
pushWindow(windows, "seven_day_sonnet", "7d Sonnet", record.seven_day_sonnet);
|
|
939
|
+
pushWindow(windows, "seven_day_opus", "7d Opus", record.seven_day_opus);
|
|
940
|
+
if (Array.isArray(record.limits)) for (const item of record.limits) {
|
|
941
|
+
if (!isRecord(item)) continue;
|
|
942
|
+
const kind = typeof item.kind === "string" ? item.kind : null;
|
|
943
|
+
if (!kind) continue;
|
|
944
|
+
if (kind === "session" || kind === "weekly_all") continue;
|
|
945
|
+
const percent = clampUsedPercent(typeof item.percent === "number" ? item.percent : null);
|
|
946
|
+
const scopeLabel = isRecord(item.scope) && isRecord(item.scope.model) && typeof item.scope.model.display_name === "string" ? item.scope.model.display_name : void 0;
|
|
947
|
+
windows.push({
|
|
948
|
+
id: `limit:${kind}${scopeLabel ? `:${scopeLabel}` : ""}`,
|
|
949
|
+
label: scopeLabel ? `${kind} (${scopeLabel})` : kind,
|
|
950
|
+
usedPercent: percent,
|
|
951
|
+
unit: "percent",
|
|
952
|
+
resetsAt: unixSecondsToIso(item.resets_at) ?? stringOrNull(item.resets_at),
|
|
953
|
+
severity: item.severity === "critical" || item.severity === "warning" || item.severity === "normal" ? item.severity : severityFromUsedPercent(percent),
|
|
954
|
+
scope: scopeLabel ? {
|
|
955
|
+
kind: "model",
|
|
956
|
+
label: scopeLabel
|
|
957
|
+
} : { kind }
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
const planId = access?.subscriptionType ?? null;
|
|
961
|
+
return {
|
|
962
|
+
plan: planId ? {
|
|
963
|
+
id: planId,
|
|
964
|
+
label: planId,
|
|
965
|
+
raw: access?.rateLimitTier ?? planId
|
|
966
|
+
} : null,
|
|
967
|
+
accountLabel: null,
|
|
968
|
+
windows,
|
|
969
|
+
raw: payload
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
/** Map anthropic-ratelimit-unified-* headers into a coarse snapshot. */
|
|
973
|
+
function observeClaudeRateLimitHeaders(headers) {
|
|
974
|
+
if (!headers) return null;
|
|
975
|
+
const status = headers.get("anthropic-ratelimit-unified-status");
|
|
976
|
+
const reset = headers.get("anthropic-ratelimit-unified-reset");
|
|
977
|
+
const claim = headers.get("anthropic-ratelimit-unified-representative-claim");
|
|
978
|
+
const overageUtil = headers.get("anthropic-ratelimit-unified-overage-period-channel-utilization");
|
|
979
|
+
if (!status && !reset && !claim && !overageUtil) return null;
|
|
980
|
+
const usedPercent = clampUsedPercent(numberHeader(headers, "anthropic-ratelimit-unified-overage-period-channel-utilization"));
|
|
981
|
+
return {
|
|
982
|
+
windows: [{
|
|
983
|
+
id: "unified",
|
|
984
|
+
label: claim ?? "Unified rate limit",
|
|
985
|
+
usedPercent,
|
|
986
|
+
unit: "percent",
|
|
987
|
+
resetsAt: unixSecondsToIso(reset),
|
|
988
|
+
severity: status === "rejected" || status === "allowed_warning" ? status === "rejected" ? "critical" : "warning" : severityFromUsedPercent(usedPercent)
|
|
989
|
+
}],
|
|
990
|
+
raw: {
|
|
991
|
+
status,
|
|
992
|
+
reset,
|
|
993
|
+
claim,
|
|
994
|
+
overageUtil
|
|
995
|
+
}
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
function pushWindow(windows, id, label, value) {
|
|
999
|
+
if (!isRecord(value)) return;
|
|
1000
|
+
const usedPercent = clampUsedPercent(typeof value.utilization === "number" ? value.utilization : typeof value.used_percentage === "number" ? value.used_percentage : null);
|
|
1001
|
+
windows.push({
|
|
1002
|
+
id,
|
|
1003
|
+
label,
|
|
1004
|
+
usedPercent,
|
|
1005
|
+
unit: "percent",
|
|
1006
|
+
resetsAt: unixSecondsToIso(value.resets_at) ?? stringOrNull(value.resets_at),
|
|
1007
|
+
severity: severityFromUsedPercent(usedPercent)
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
//#endregion
|
|
607
1011
|
//#region src/cli.ts
|
|
608
1012
|
function buildClaudeArgs(params) {
|
|
609
1013
|
const args = [
|
|
@@ -630,13 +1034,15 @@ function buildClaudeArgs(params) {
|
|
|
630
1034
|
if (params.thinkingEffort) args.push("--effort", params.thinkingEffort);
|
|
631
1035
|
return args;
|
|
632
1036
|
}
|
|
633
|
-
function buildClaudeEnv(base = process.env) {
|
|
1037
|
+
function buildClaudeEnv(base = process.env, options = {}) {
|
|
634
1038
|
const env = {
|
|
635
1039
|
...base,
|
|
636
1040
|
DISABLE_AUTO_COMPACT: "1",
|
|
637
1041
|
MAX_MCP_OUTPUT_TOKENS: "1000000"
|
|
638
1042
|
};
|
|
639
1043
|
delete env.CLAUDECODE;
|
|
1044
|
+
const token = options.oauthAccessToken?.trim();
|
|
1045
|
+
if (token) env.CLAUDE_CODE_OAUTH_TOKEN = token;
|
|
640
1046
|
return env;
|
|
641
1047
|
}
|
|
642
1048
|
//#endregion
|
|
@@ -683,13 +1089,19 @@ function resolveSpawnCwd(cwd) {
|
|
|
683
1089
|
try {
|
|
684
1090
|
if (statSync(cwd).isDirectory()) return cwd;
|
|
685
1091
|
} catch {}
|
|
686
|
-
return process.cwd();
|
|
1092
|
+
return process$1.cwd();
|
|
687
1093
|
}
|
|
688
1094
|
var ClaudeCliTransportFactory = class {
|
|
689
1095
|
claudePath;
|
|
1096
|
+
resolveOAuthAccessToken;
|
|
690
1097
|
constructor(options = {}) {
|
|
691
|
-
if (typeof options === "string")
|
|
692
|
-
|
|
1098
|
+
if (typeof options === "string") {
|
|
1099
|
+
this.claudePath = options;
|
|
1100
|
+
this.resolveOAuthAccessToken = null;
|
|
1101
|
+
} else {
|
|
1102
|
+
this.claudePath = options.claudePath ?? "claude";
|
|
1103
|
+
this.resolveOAuthAccessToken = options.resolveOAuthAccessToken ?? null;
|
|
1104
|
+
}
|
|
693
1105
|
}
|
|
694
1106
|
async start(request) {
|
|
695
1107
|
const args = buildClaudeArgsForRequest(request);
|
|
@@ -701,9 +1113,10 @@ var ClaudeCliTransportFactory = class {
|
|
|
701
1113
|
cwd: request.cwd,
|
|
702
1114
|
args
|
|
703
1115
|
});
|
|
1116
|
+
const oauthAccessToken = this.resolveOAuthAccessToken ? await this.resolveOAuthAccessToken() : null;
|
|
704
1117
|
return new ChildProcessClaudeTransport(spawn(this.claudePath, args, {
|
|
705
1118
|
cwd: resolveSpawnCwd(request.cwd),
|
|
706
|
-
env: buildClaudeEnv(),
|
|
1119
|
+
env: buildClaudeEnv(process$1.env, { oauthAccessToken }),
|
|
707
1120
|
stdio: [
|
|
708
1121
|
"pipe",
|
|
709
1122
|
"pipe",
|
|
@@ -791,9 +1204,27 @@ function thinkingEffort(thinking) {
|
|
|
791
1204
|
//#region src/provider.ts
|
|
792
1205
|
var ClaudeCodeProvider = class {
|
|
793
1206
|
transportFactory;
|
|
1207
|
+
quota;
|
|
1208
|
+
getActiveCredentialId;
|
|
794
1209
|
active = null;
|
|
795
1210
|
constructor(options = {}) {
|
|
796
|
-
this.transportFactory = options.transportFactory ?? new ClaudeCliTransportFactory({
|
|
1211
|
+
this.transportFactory = options.transportFactory ?? new ClaudeCliTransportFactory({
|
|
1212
|
+
claudePath: options.claudePath,
|
|
1213
|
+
resolveOAuthAccessToken: options.authStore ? async () => {
|
|
1214
|
+
try {
|
|
1215
|
+
return (await options.authStore.resolveAccess()).accessToken;
|
|
1216
|
+
} catch {
|
|
1217
|
+
return null;
|
|
1218
|
+
}
|
|
1219
|
+
} : void 0
|
|
1220
|
+
});
|
|
1221
|
+
this.quota = options.quota ?? null;
|
|
1222
|
+
this.getActiveCredentialId = options.getActiveCredentialId ?? null;
|
|
1223
|
+
}
|
|
1224
|
+
observeQuotaFromMessage(message) {
|
|
1225
|
+
try {
|
|
1226
|
+
this.quota?.observeResponse?.({ body: message });
|
|
1227
|
+
} catch {}
|
|
797
1228
|
}
|
|
798
1229
|
async *run(request) {
|
|
799
1230
|
let active = null;
|
|
@@ -837,6 +1268,7 @@ var ClaudeCodeProvider = class {
|
|
|
837
1268
|
return;
|
|
838
1269
|
}
|
|
839
1270
|
const raw = next.value;
|
|
1271
|
+
this.observeQuotaFromMessage(raw);
|
|
840
1272
|
const mapped = mapClaudeStdoutMessage(raw, {
|
|
841
1273
|
ignoreAssistantContent: active.hasStreamed && isMessageType(raw, "assistant"),
|
|
842
1274
|
ignoreAssistantToolUse: active.sdkMcpEnabled
|
|
@@ -894,17 +1326,18 @@ var ClaudeCodeProvider = class {
|
|
|
894
1326
|
* the session changed, or the transcript was rewritten underneath us (compaction).
|
|
895
1327
|
*/
|
|
896
1328
|
async ensureActiveForRequest(request) {
|
|
1329
|
+
const credentialId = this.getActiveCredentialId ? await this.getActiveCredentialId() : null;
|
|
897
1330
|
const existing = this.active;
|
|
898
|
-
if (existing && existing.sessionId === request.sessionId && existing.modelId === request.modelId && existing.thinkingSig === thinkingSignature(request)) {
|
|
1331
|
+
if (existing && existing.sessionId === request.sessionId && existing.modelId === request.modelId && existing.thinkingSig === thinkingSignature(request) && existing.credentialId === credentialId) {
|
|
899
1332
|
if (existing.pendingControlRequest !== null || existing.pendingToolUseIds.length > 0 || !itemsDiverged(existing, request.items)) {
|
|
900
1333
|
await this.sendContinuation(existing, request);
|
|
901
1334
|
return existing;
|
|
902
1335
|
}
|
|
903
1336
|
}
|
|
904
1337
|
if (existing) await this.disposeActive(existing);
|
|
905
|
-
return this.coldStart(request);
|
|
1338
|
+
return this.coldStart(request, credentialId);
|
|
906
1339
|
}
|
|
907
|
-
async coldStart(request) {
|
|
1340
|
+
async coldStart(request, credentialId) {
|
|
908
1341
|
const transport = await this.transportFactory.start(request);
|
|
909
1342
|
const active = {
|
|
910
1343
|
transport,
|
|
@@ -917,6 +1350,7 @@ var ClaudeCodeProvider = class {
|
|
|
917
1350
|
sessionId: request.sessionId,
|
|
918
1351
|
modelId: request.modelId,
|
|
919
1352
|
thinkingSig: thinkingSignature(request),
|
|
1353
|
+
credentialId,
|
|
920
1354
|
sentUserMessageCount: 0,
|
|
921
1355
|
firstUserSig: null
|
|
922
1356
|
};
|
|
@@ -1095,14 +1529,32 @@ var ClaudeCodeProvider = class {
|
|
|
1095
1529
|
function createClaudeCodeProvider(options = {}) {
|
|
1096
1530
|
const id = options.id ?? "claude-code";
|
|
1097
1531
|
const displayName = options.displayName ?? "Claude Code";
|
|
1098
|
-
const
|
|
1532
|
+
const enableCredentials = options.credentials ?? options.authStore === void 0;
|
|
1533
|
+
const pool = !options.authStore && enableCredentials ? openClaudeCodeCredentialPool({ stateDir: options.stateDir }) : null;
|
|
1534
|
+
const authStore = options.authStore ?? (pool ? new PoolAwareClaudeCodeAuthStore(pool) : new FileClaudeCodeAuthStore());
|
|
1535
|
+
const quota = createClaudeCodeQuota({
|
|
1536
|
+
providerId: id,
|
|
1537
|
+
resolveAccess: async () => {
|
|
1538
|
+
try {
|
|
1539
|
+
return await authStore.resolveAccess();
|
|
1540
|
+
} catch {
|
|
1541
|
+
return null;
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
});
|
|
1545
|
+
const credentialsApi = pool ? createClaudeCodeCredentials(pool, authStore, { quota }) : void 0;
|
|
1546
|
+
const runtimeOptions = {
|
|
1547
|
+
claudePath: options.claudePath,
|
|
1548
|
+
quota,
|
|
1549
|
+
authStore,
|
|
1550
|
+
getActiveCredentialId: pool ? () => pool.getActiveId() : void 0
|
|
1551
|
+
};
|
|
1099
1552
|
return defineProvider({
|
|
1100
1553
|
id,
|
|
1101
1554
|
displayName,
|
|
1102
|
-
auth: { status: () => (
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
}) },
|
|
1555
|
+
auth: { status: () => authStore.status() },
|
|
1556
|
+
quota,
|
|
1557
|
+
...credentialsApi ? { credentials: credentialsApi } : {},
|
|
1106
1558
|
state: () => ({
|
|
1107
1559
|
status: "unknown",
|
|
1108
1560
|
message: "Runtime is checked when a Claude Code request runs"
|
|
@@ -1120,18 +1572,19 @@ function isControlResponseFor(value, requestId) {
|
|
|
1120
1572
|
if (!isRecord(value) || value.type !== "control_response" || !isRecord(value.response)) return false;
|
|
1121
1573
|
return value.response.request_id === requestId && value.response.subtype === "success";
|
|
1122
1574
|
}
|
|
1123
|
-
function toolResultContentToText(output) {
|
|
1124
|
-
return output.map((block) => block.type === "text" ? block.text : `[image:${block.source.mediaType}]`).join("\n");
|
|
1125
|
-
}
|
|
1126
1575
|
function toolResultContentToMcp(output) {
|
|
1127
1576
|
return output.map((block) => {
|
|
1128
1577
|
if (block.type === "text") return {
|
|
1129
1578
|
type: "text",
|
|
1130
1579
|
text: block.text
|
|
1131
1580
|
};
|
|
1581
|
+
if (block.type === "video") return {
|
|
1582
|
+
type: "text",
|
|
1583
|
+
text: `[video:${block.source.mediaType}]`
|
|
1584
|
+
};
|
|
1132
1585
|
return {
|
|
1133
1586
|
type: "image",
|
|
1134
|
-
data:
|
|
1587
|
+
data: block.source.data,
|
|
1135
1588
|
mimeType: block.source.mediaType
|
|
1136
1589
|
};
|
|
1137
1590
|
});
|
|
@@ -1163,4 +1616,4 @@ function itemsDiverged(active, items) {
|
|
|
1163
1616
|
return false;
|
|
1164
1617
|
}
|
|
1165
1618
|
//#endregion
|
|
1166
|
-
export { createClaudeCodeProvider, listClaudeCodeModels, resolveWireLogDir };
|
|
1619
|
+
export { FileClaudeCodeAuthStore, PoolAwareClaudeCodeAuthStore, StaticClaudeCodeAuthStore, createClaudeCodeCredentials, createClaudeCodeProvider, createClaudeCodeQuota, listClaudeCodeModels, mapClaudeUsagePayload, observeClaudeRateLimitHeaders, observeClaudeStreamBody, openClaudeCodeCredentialPool, 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.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@demicodes/core": "
|
|
16
|
-
"@demicodes/provider": "
|
|
17
|
-
"@demicodes/utils": "
|
|
15
|
+
"@demicodes/core": "0.2.0",
|
|
16
|
+
"@demicodes/provider": "0.2.0",
|
|
17
|
+
"@demicodes/utils": "0.2.0"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"@demicodes/agent": "
|
|
21
|
-
"@demicodes/shell": "
|
|
20
|
+
"@demicodes/agent": "0.2.0",
|
|
21
|
+
"@demicodes/shell": "0.2.0"
|
|
22
22
|
},
|
|
23
23
|
"license": "Apache-2.0",
|
|
24
24
|
"main": "./dist/index.mjs",
|