dsh-plugin-subscriptions 0.4.1 → 0.5.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 +9 -5
- package/README.zh.md +9 -5
- package/lib/auth/claude-code-creds.d.ts +23 -0
- package/lib/auth/claude-code-creds.js +167 -0
- package/lib/auth/pkce.js +1 -1
- package/lib/auth/rpc.d.ts +18 -1
- package/lib/auth/rpc.js +23 -3
- package/lib/client/ImageGallery.d.ts +54 -0
- package/lib/client/ImageGallery.js +112 -0
- package/lib/client/ImageGenerateToolview.d.ts +1 -1
- package/lib/client/ImageGenerateToolview.js +2 -2
- package/lib/client/SpeedSelect.d.ts +48 -0
- package/lib/client/SpeedSelect.js +173 -0
- package/lib/client/SubscriptionsSection.d.ts +9 -0
- package/lib/client/SubscriptionsSection.js +8 -2
- package/lib/client/index.d.ts +1 -0
- package/lib/client/index.js +42 -0
- package/lib/client/locales.d.ts +14 -0
- package/lib/client/locales.js +14 -0
- package/lib/client.js +590 -74
- package/lib/client.js.map +1 -1
- package/lib/index.js +504 -90
- package/lib/providers/catalog-store.js +8 -0
- package/lib/providers/claude.d.ts +35 -3
- package/lib/providers/claude.js +193 -29
- package/lib/providers/codex.d.ts +27 -0
- package/lib/providers/codex.js +66 -20
- package/lib/providers/common.d.ts +4 -0
- package/lib/tools/image-generate.js +3 -10
- package/package.json +3 -1
package/lib/index.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
|
-
import { CONTEXT_WINDOW_EXCEEDED_CODE, CallId, EMPTY_RESPONSE_CODE, LlmAdapter, LlmError, QUOTA_EXCEEDED_CODE, ReasoningEffortId, attributionHeaders,
|
|
2
|
+
import { CONTEXT_WINDOW_EXCEEDED_CODE, CallId, EMPTY_RESPONSE_CODE, LlmAdapter, LlmError, QUOTA_EXCEEDED_CODE, ReasoningEffortId, attributionHeaders, errorChain, isContextWindowExceededError, isQuotaExceededError, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
3
3
|
import { createServer } from "node:http";
|
|
4
4
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
5
5
|
import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
6
6
|
import { basename, dirname, join } from "node:path";
|
|
7
|
+
import { execFileSync } from "node:child_process";
|
|
8
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { homedir } from "node:os";
|
|
7
10
|
import { AttachmentId } from "@deepseek-ai/dsh-attachment";
|
|
8
11
|
import { dshHomePath } from "@deepseek-ai/dsh-home-paths";
|
|
9
12
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
@@ -33,7 +36,7 @@ function createPkce() {
|
|
|
33
36
|
* @param bytes - entropy length.
|
|
34
37
|
* @returns base64url-encoded random bytes.
|
|
35
38
|
*/
|
|
36
|
-
function randomToken(bytes =
|
|
39
|
+
function randomToken(bytes = 32) {
|
|
37
40
|
return base64url(randomBytes(bytes));
|
|
38
41
|
}
|
|
39
42
|
/**
|
|
@@ -247,6 +250,187 @@ var OAuthFlowManager = class {
|
|
|
247
250
|
}
|
|
248
251
|
};
|
|
249
252
|
|
|
253
|
+
//#endregion
|
|
254
|
+
//#region src/auth/claude-code-creds.ts
|
|
255
|
+
const PRIMARY_SERVICE = "Claude Code-credentials";
|
|
256
|
+
const DEFAULT_SCOPES = "user:profile user:inference user:sessions:claude_code user:mcp_servers";
|
|
257
|
+
function toSession(data) {
|
|
258
|
+
if (typeof data.accessToken !== "string" || typeof data.refreshToken !== "string" || typeof data.expiresAt !== "number") return;
|
|
259
|
+
const scopes = Array.isArray(data.scopes) ? data.scopes.join(" ") : typeof data.scopes === "string" ? data.scopes : DEFAULT_SCOPES;
|
|
260
|
+
return {
|
|
261
|
+
accessToken: data.accessToken,
|
|
262
|
+
refreshToken: data.refreshToken,
|
|
263
|
+
expiresAt: Math.trunc(data.expiresAt),
|
|
264
|
+
scopes,
|
|
265
|
+
...typeof data.emailAddress === "string" ? { emailAddress: data.emailAddress } : {},
|
|
266
|
+
...typeof data.subscriptionType === "string" ? { subscriptionType: data.subscriptionType } : {}
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function parseBlob(raw) {
|
|
270
|
+
let parsed;
|
|
271
|
+
try {
|
|
272
|
+
parsed = JSON.parse(raw);
|
|
273
|
+
} catch {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
return toSession(parsed.claudeAiOauth ?? parsed);
|
|
277
|
+
}
|
|
278
|
+
function credentialsFilePath() {
|
|
279
|
+
return join(process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude"), ".credentials.json");
|
|
280
|
+
}
|
|
281
|
+
function readKeychainRaw() {
|
|
282
|
+
try {
|
|
283
|
+
return execFileSync("/usr/bin/security", [
|
|
284
|
+
"find-generic-password",
|
|
285
|
+
"-s",
|
|
286
|
+
PRIMARY_SERVICE,
|
|
287
|
+
"-w"
|
|
288
|
+
], {
|
|
289
|
+
timeout: 3e3,
|
|
290
|
+
encoding: "utf8",
|
|
291
|
+
stdio: [
|
|
292
|
+
"pipe",
|
|
293
|
+
"pipe",
|
|
294
|
+
"pipe"
|
|
295
|
+
]
|
|
296
|
+
}).trim();
|
|
297
|
+
} catch {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function readFileRaw() {
|
|
302
|
+
try {
|
|
303
|
+
return readFileSync(credentialsFilePath(), "utf8");
|
|
304
|
+
} catch {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/** Read the current Claude Code session from its source of truth: macOS Keychain, falling back to the credentials file. */
|
|
309
|
+
function readClaudeCodeCredentials() {
|
|
310
|
+
if (process.platform === "darwin") {
|
|
311
|
+
const raw$1 = readKeychainRaw();
|
|
312
|
+
const session = raw$1 !== void 0 ? parseBlob(raw$1) : void 0;
|
|
313
|
+
if (session) return session;
|
|
314
|
+
}
|
|
315
|
+
const raw = readFileRaw();
|
|
316
|
+
return raw !== void 0 ? parseBlob(raw) : void 0;
|
|
317
|
+
}
|
|
318
|
+
function blobMatches(raw, expectedAccessToken) {
|
|
319
|
+
return parseBlob(raw)?.accessToken === expectedAccessToken;
|
|
320
|
+
}
|
|
321
|
+
function getKeychainAccountName() {
|
|
322
|
+
try {
|
|
323
|
+
const output = execFileSync("/usr/bin/security", [
|
|
324
|
+
"find-generic-password",
|
|
325
|
+
"-s",
|
|
326
|
+
PRIMARY_SERVICE
|
|
327
|
+
], {
|
|
328
|
+
timeout: 2e3,
|
|
329
|
+
encoding: "utf8",
|
|
330
|
+
stdio: [
|
|
331
|
+
"pipe",
|
|
332
|
+
"pipe",
|
|
333
|
+
"pipe"
|
|
334
|
+
]
|
|
335
|
+
});
|
|
336
|
+
return /"acct"<blob>="([^"]*)"/.exec(output)?.[1];
|
|
337
|
+
} catch {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
/** Merge fresh tokens into an existing raw blob, preserving unrelated fields. */
|
|
342
|
+
function mergeIntoBlob(existingRaw, next) {
|
|
343
|
+
let parsed;
|
|
344
|
+
try {
|
|
345
|
+
parsed = JSON.parse(existingRaw);
|
|
346
|
+
} catch {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
const target = parsed.claudeAiOauth ?? parsed;
|
|
350
|
+
target.accessToken = next.accessToken;
|
|
351
|
+
target.refreshToken = next.refreshToken;
|
|
352
|
+
target.expiresAt = next.expiresAt;
|
|
353
|
+
return JSON.stringify(parsed);
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Write a refreshed session back to Claude Code's own credential store, so
|
|
357
|
+
* the `claude` CLI and any other consumer of the same account see the token
|
|
358
|
+
* we just rotated. A stale-blob mismatch (something else rotated it first)
|
|
359
|
+
* is a no-op — the caller already has that other rotation via readClaudeCodeCredentials.
|
|
360
|
+
* @param next - the freshly refreshed session to persist.
|
|
361
|
+
* @param expectedPriorAccessToken - the access token this refresh started from.
|
|
362
|
+
* @returns whether the write-back succeeded.
|
|
363
|
+
*/
|
|
364
|
+
function writeBackClaudeCodeCredentials(next, expectedPriorAccessToken) {
|
|
365
|
+
if (process.platform === "darwin") {
|
|
366
|
+
const raw$1 = readKeychainRaw();
|
|
367
|
+
if (raw$1 === void 0 || !blobMatches(raw$1, expectedPriorAccessToken)) return false;
|
|
368
|
+
const updated$1 = mergeIntoBlob(raw$1, next);
|
|
369
|
+
if (updated$1 === void 0) return false;
|
|
370
|
+
const account = getKeychainAccountName() ?? PRIMARY_SERVICE;
|
|
371
|
+
try {
|
|
372
|
+
execFileSync("/usr/bin/security", [
|
|
373
|
+
"add-generic-password",
|
|
374
|
+
"-s",
|
|
375
|
+
PRIMARY_SERVICE,
|
|
376
|
+
"-a",
|
|
377
|
+
account,
|
|
378
|
+
"-w",
|
|
379
|
+
updated$1,
|
|
380
|
+
"-U"
|
|
381
|
+
], {
|
|
382
|
+
timeout: 2e3,
|
|
383
|
+
stdio: "ignore"
|
|
384
|
+
});
|
|
385
|
+
return true;
|
|
386
|
+
} catch {
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const path = credentialsFilePath();
|
|
391
|
+
let raw;
|
|
392
|
+
try {
|
|
393
|
+
raw = readFileSync(path, "utf8");
|
|
394
|
+
} catch {
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
if (!blobMatches(raw, expectedPriorAccessToken)) return false;
|
|
398
|
+
const updated = mergeIntoBlob(raw, next);
|
|
399
|
+
if (updated === void 0) return false;
|
|
400
|
+
try {
|
|
401
|
+
const dir = dirname(path);
|
|
402
|
+
if (!existsSync(dir)) mkdirSync(dir, {
|
|
403
|
+
recursive: true,
|
|
404
|
+
mode: 448
|
|
405
|
+
});
|
|
406
|
+
writeFileSync(path, updated, {
|
|
407
|
+
encoding: "utf8",
|
|
408
|
+
mode: 384
|
|
409
|
+
});
|
|
410
|
+
chmodSync(path, 384);
|
|
411
|
+
return true;
|
|
412
|
+
} catch {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Refresh a Claude session, first checking whether Claude Code's own store
|
|
418
|
+
* already holds a fresher token (rotated by the `claude` CLI or another
|
|
419
|
+
* consumer) before hitting the OAuth endpoint ourselves — and writing our own
|
|
420
|
+
* refresh back to that store so every consumer of the account stays synced.
|
|
421
|
+
* @param session - the session TokenManager wants refreshed.
|
|
422
|
+
* @param doRefresh - the actual OAuth refresh-token grant (network call).
|
|
423
|
+
* @returns the freshest available session.
|
|
424
|
+
*/
|
|
425
|
+
async function refreshClaudeSynced(session, doRefresh) {
|
|
426
|
+
const fromSource = readClaudeCodeCredentials();
|
|
427
|
+
const base = fromSource !== void 0 && fromSource.accessToken !== session.accessToken ? fromSource : session;
|
|
428
|
+
if (base.expiresAt > Date.now() + 6e4) return base;
|
|
429
|
+
const next = await doRefresh(base);
|
|
430
|
+
writeBackClaudeCodeCredentials(next, base.accessToken);
|
|
431
|
+
return next;
|
|
432
|
+
}
|
|
433
|
+
|
|
250
434
|
//#endregion
|
|
251
435
|
//#region src/auth/store.ts
|
|
252
436
|
/** Every provider route, in display order. */
|
|
@@ -411,6 +595,12 @@ function readString(payload, field) {
|
|
|
411
595
|
if (typeof value !== "string" || value.length === 0) throw new BadRequest(`payload.${field} must be a non-empty string`);
|
|
412
596
|
return value;
|
|
413
597
|
}
|
|
598
|
+
/** Validate the `setSpeed` endpoint's tier. */
|
|
599
|
+
function readSpeedTier(payload) {
|
|
600
|
+
const tier = payload.tier;
|
|
601
|
+
if (tier !== "standard" && tier !== "fast") throw new BadRequest("payload.tier must be \"standard\" or \"fast\"");
|
|
602
|
+
return tier;
|
|
603
|
+
}
|
|
414
604
|
/** Validate the `image` endpoint's payload into a full attachment reference. */
|
|
415
605
|
function readImageRef(payload) {
|
|
416
606
|
if (typeof payload !== "object" || payload === null) throw new BadRequest("payload must be an object");
|
|
@@ -449,7 +639,12 @@ function readVideoName(payload) {
|
|
|
449
639
|
if (typeof name$1 !== "string" || !VIDEO_NAME_PATTERN.test(name$1)) throw new BadRequest("payload.name must be a bare .mp4 file name");
|
|
450
640
|
return name$1;
|
|
451
641
|
}
|
|
452
|
-
|
|
642
|
+
/** Validate the session id both speed endpoints carry. */
|
|
643
|
+
function readSessionId(payload) {
|
|
644
|
+
if (typeof payload !== "object" || payload === null) throw new BadRequest("payload must be an object");
|
|
645
|
+
return readString(payload, "sessionId");
|
|
646
|
+
}
|
|
647
|
+
async function dispatch(controller, speed, endpoint, payload, signal) {
|
|
453
648
|
switch (endpoint) {
|
|
454
649
|
case "status": {
|
|
455
650
|
const entries = await Promise.all(PROVIDER_IDS.map(async (provider) => [provider, await controller.status(provider)]));
|
|
@@ -470,6 +665,10 @@ async function dispatch(controller, endpoint, payload, signal) {
|
|
|
470
665
|
case "usage": return ok(await controller.usage(readProvider(payload), signal));
|
|
471
666
|
case "image": return ok(await controller.readImage(readImageRef(payload), signal));
|
|
472
667
|
case "video": return ok(await controller.readVideo(readVideoName(payload), signal));
|
|
668
|
+
case "speed": return ok(await speed.speed(readSessionId(payload)));
|
|
669
|
+
case "setSpeed":
|
|
670
|
+
await speed.setSpeed(readSessionId(payload), readSpeedTier(payload));
|
|
671
|
+
return ok({ ok: true });
|
|
473
672
|
default: throw new BadRequest(`unknown /subscriptions-auth endpoint "${endpoint}"`);
|
|
474
673
|
}
|
|
475
674
|
}
|
|
@@ -477,13 +676,14 @@ async function dispatch(controller, endpoint, payload, signal) {
|
|
|
477
676
|
* Register the `/subscriptions-auth` RPC channel when a host connection exists.
|
|
478
677
|
* @param ctx - the plugin context (headless profiles have no `connection`).
|
|
479
678
|
* @param controller - the auth operations backing the endpoints.
|
|
679
|
+
* @param speed - the per-session speed-tier state backing the Speed toggle.
|
|
480
680
|
*/
|
|
481
|
-
function registerAuthRpc(ctx, controller) {
|
|
681
|
+
function registerAuthRpc(ctx, controller, speed) {
|
|
482
682
|
ctx.inject(["connection"], (ctx$1) => {
|
|
483
683
|
const connection = ctx$1.get("connection");
|
|
484
684
|
ctx$1.effect(() => connection.rpc.handle(SUBSCRIPTIONS_AUTH_CHANNEL, async (endpoint, payload, signal) => {
|
|
485
685
|
try {
|
|
486
|
-
return await dispatch(controller, endpoint, payload, signal);
|
|
686
|
+
return await dispatch(controller, speed, endpoint, payload, signal);
|
|
487
687
|
} catch (error) {
|
|
488
688
|
return failure(error);
|
|
489
689
|
}
|
|
@@ -827,13 +1027,19 @@ function sanitizeModel(value) {
|
|
|
827
1027
|
if (typeof raw.id !== "string" || raw.id.length === 0 || typeof raw.name !== "string" || raw.name.length === 0 || raw.description !== void 0 && typeof raw.description !== "string" || raw.contextWindow !== void 0 && (typeof raw.contextWindow !== "number" || !Number.isInteger(raw.contextWindow) || raw.contextWindow <= 0) || raw.priority !== void 0 && (typeof raw.priority !== "number" || !Number.isFinite(raw.priority))) return void 0;
|
|
828
1028
|
const reasoning = raw.reasoning === void 0 ? void 0 : sanitizeReasoning(raw.reasoning);
|
|
829
1029
|
if (raw.reasoning !== void 0 && reasoning === void 0) return void 0;
|
|
1030
|
+
const thinkingType = raw.thinkingType;
|
|
1031
|
+
if (thinkingType !== void 0 && thinkingType !== "enabled" && thinkingType !== "adaptive") return void 0;
|
|
1032
|
+
const fastTier = raw.fastTier;
|
|
1033
|
+
if (fastTier !== void 0 && typeof fastTier !== "boolean") return void 0;
|
|
830
1034
|
return {
|
|
831
1035
|
id: raw.id,
|
|
832
1036
|
name: raw.name,
|
|
833
1037
|
...raw.description === void 0 ? {} : { description: raw.description },
|
|
834
1038
|
...raw.contextWindow === void 0 ? {} : { contextWindow: raw.contextWindow },
|
|
835
1039
|
...raw.priority === void 0 ? {} : { priority: raw.priority },
|
|
836
|
-
...reasoning === void 0 ? {} : { reasoning }
|
|
1040
|
+
...reasoning === void 0 ? {} : { reasoning },
|
|
1041
|
+
...thinkingType === void 0 ? {} : { thinkingType },
|
|
1042
|
+
...fastTier === void 0 ? {} : { fastTier }
|
|
837
1043
|
};
|
|
838
1044
|
}
|
|
839
1045
|
/**
|
|
@@ -1397,6 +1603,14 @@ const CODEX_EFFORTS = [
|
|
|
1397
1603
|
const CODEX_DEFAULT_EFFORT = ReasoningEffortId("high");
|
|
1398
1604
|
/** Every gpt-5.x codex model accepts image input. */
|
|
1399
1605
|
const CODEX_MODALITIES = ["text", "image"];
|
|
1606
|
+
/**
|
|
1607
|
+
* Fast tier (the codex CLI's "fast mode"): the Responses `service_tier` wire
|
|
1608
|
+
* value for priority processing, mirroring codex-rs
|
|
1609
|
+
* `ServiceTier::Fast.request_value()`. The legacy catalog spelling is the
|
|
1610
|
+
* `additional_speed_tiers` entry "fast".
|
|
1611
|
+
*/
|
|
1612
|
+
const CODEX_FAST_SERVICE_TIER = "priority";
|
|
1613
|
+
const CODEX_FAST_SPEED_TIER = "fast";
|
|
1400
1614
|
/** Static codex flow facts for the OAuth flow engine. */
|
|
1401
1615
|
const codexFlow = {
|
|
1402
1616
|
callbackPath: CODEX_CALLBACK_PATH,
|
|
@@ -1608,6 +1822,14 @@ function effortName(effort) {
|
|
|
1608
1822
|
return effort === "xhigh" ? "Extra High" : effort.charAt(0).toUpperCase() + effort.slice(1);
|
|
1609
1823
|
}
|
|
1610
1824
|
/**
|
|
1825
|
+
* Whether a catalog entry advertises the fast tier. Mirrors codex-rs
|
|
1826
|
+
* `ModelPreset::supports_fast_mode`: a `service_tiers` id matching the fast
|
|
1827
|
+
* wire value, or the legacy `additional_speed_tiers` "fast" entry.
|
|
1828
|
+
*/
|
|
1829
|
+
function supportsFastTier(entry) {
|
|
1830
|
+
return (entry.service_tiers ?? []).some((tier) => tier.id === CODEX_FAST_SERVICE_TIER) || (entry.additional_speed_tiers ?? []).includes(CODEX_FAST_SPEED_TIER);
|
|
1831
|
+
}
|
|
1832
|
+
/**
|
|
1611
1833
|
* Fetch the live codex model catalog with the session's auth headers.
|
|
1612
1834
|
* @param session - the stored session (used as-is; never refreshed here).
|
|
1613
1835
|
* @param fetchFn - fetch implementation (injectable for tests).
|
|
@@ -1634,7 +1856,7 @@ async function fetchCodexModels(session, fetchFn = fetch) {
|
|
|
1634
1856
|
...level.description === void 0 ? {} : { description: level.description }
|
|
1635
1857
|
}));
|
|
1636
1858
|
const defaultEffort = typeof entry.default_reasoning_level === "string" && entry.default_reasoning_level.length > 0 && efforts.some((effort) => effort.id === ReasoningEffortId(entry.default_reasoning_level)) ? ReasoningEffortId(entry.default_reasoning_level) : void 0;
|
|
1637
|
-
|
|
1859
|
+
const model = {
|
|
1638
1860
|
id: entry.slug,
|
|
1639
1861
|
name: typeof entry.display_name === "string" && entry.display_name.length > 0 ? entry.display_name : entry.slug,
|
|
1640
1862
|
...typeof entry.description === "string" && entry.description.length > 0 ? { description: entry.description } : {},
|
|
@@ -1643,13 +1865,40 @@ async function fetchCodexModels(session, fetchFn = fetch) {
|
|
|
1643
1865
|
...efforts.length > 0 ? { reasoning: {
|
|
1644
1866
|
efforts,
|
|
1645
1867
|
...defaultEffort === void 0 ? {} : { defaultEffort }
|
|
1646
|
-
} } : {}
|
|
1647
|
-
|
|
1868
|
+
} } : {},
|
|
1869
|
+
...supportsFastTier(entry) ? { fastTier: true } : {}
|
|
1870
|
+
};
|
|
1871
|
+
discovered.push(model);
|
|
1648
1872
|
}
|
|
1649
1873
|
discovered.sort((a, b) => (a.priority ?? Number.MAX_SAFE_INTEGER) - (b.priority ?? Number.MAX_SAFE_INTEGER));
|
|
1650
1874
|
if (discovered.length === 0) throw new Error(`codex models endpoint returned an empty catalog (client_version ${CODEX_CLIENT_VERSION})`);
|
|
1651
1875
|
return discovered;
|
|
1652
1876
|
}
|
|
1877
|
+
/**
|
|
1878
|
+
* The Responses request body for one generation. A fast-tier request (the
|
|
1879
|
+
* composer Speed toggle, the codex CLI's fast mode) carries
|
|
1880
|
+
* `service_tier: priority`; the tier field is omitted entirely otherwise,
|
|
1881
|
+
* matching the CLI (it never sends an explicit standard tier).
|
|
1882
|
+
*/
|
|
1883
|
+
function codexRequestBody(options, resolved, fast) {
|
|
1884
|
+
return {
|
|
1885
|
+
model: options.model,
|
|
1886
|
+
instructions: resolved.instructions ?? DEFAULT_CODEX_INSTRUCTIONS,
|
|
1887
|
+
input: resolved.input,
|
|
1888
|
+
...options.tools !== void 0 && options.tools.length > 0 ? { tools: toResponsesTools(options.tools) } : {},
|
|
1889
|
+
tool_choice: "auto",
|
|
1890
|
+
parallel_tool_calls: true,
|
|
1891
|
+
...options.reasoningEffort !== void 0 ? { reasoning: {
|
|
1892
|
+
effort: String(options.reasoningEffort),
|
|
1893
|
+
summary: "auto"
|
|
1894
|
+
} } : {},
|
|
1895
|
+
store: false,
|
|
1896
|
+
stream: true,
|
|
1897
|
+
include: ["reasoning.encrypted_content"],
|
|
1898
|
+
...options.sessionId !== void 0 ? { prompt_cache_key: String(options.sessionId) } : {},
|
|
1899
|
+
...fast ? { service_tier: CODEX_FAST_SERVICE_TIER } : {}
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1653
1902
|
/** Codex wire adapter: one instance serves the `codex` provider route. */
|
|
1654
1903
|
var CodexAdapter = class extends LlmAdapter {
|
|
1655
1904
|
catalog;
|
|
@@ -1705,6 +1954,16 @@ var CodexAdapter = class extends LlmAdapter {
|
|
|
1705
1954
|
if (!this.options.discovery) return void 0;
|
|
1706
1955
|
return (await this.catalog.resolve(() => this.fetchCatalog()))?.find((entry) => entry.id === model);
|
|
1707
1956
|
}
|
|
1957
|
+
/** Whether the discovered catalog advertises a fast tier for this model. */
|
|
1958
|
+
async supportsFastTier(model) {
|
|
1959
|
+
return (await this.discovered(model))?.fastTier === true;
|
|
1960
|
+
}
|
|
1961
|
+
/** Ids of every discovered model with a fast tier (the Speed toggle's visibility list). */
|
|
1962
|
+
async fastCapableModels() {
|
|
1963
|
+
if (!this.options.discovery) return [];
|
|
1964
|
+
if (await this.options.tokens.peek() === void 0) return [];
|
|
1965
|
+
return (await this.catalog.resolve(() => this.fetchCatalog()) ?? []).filter((model) => model.fastTier === true).map((model) => model.id);
|
|
1966
|
+
}
|
|
1708
1967
|
async resolveModel(provider, model) {
|
|
1709
1968
|
const discovered = await this.discovered(model);
|
|
1710
1969
|
const configured = this.options.models.find((entry) => entry.id === model);
|
|
@@ -1743,23 +2002,9 @@ var CodexAdapter = class extends LlmAdapter {
|
|
|
1743
2002
|
}
|
|
1744
2003
|
}
|
|
1745
2004
|
async request(options, session, signal) {
|
|
1746
|
-
const
|
|
1747
|
-
const
|
|
1748
|
-
|
|
1749
|
-
instructions: instructions ?? DEFAULT_CODEX_INSTRUCTIONS,
|
|
1750
|
-
input,
|
|
1751
|
-
...options.tools !== void 0 && options.tools.length > 0 ? { tools: toResponsesTools(options.tools) } : {},
|
|
1752
|
-
tool_choice: "auto",
|
|
1753
|
-
parallel_tool_calls: true,
|
|
1754
|
-
...options.reasoningEffort !== void 0 ? { reasoning: {
|
|
1755
|
-
effort: String(options.reasoningEffort),
|
|
1756
|
-
summary: "auto"
|
|
1757
|
-
} } : {},
|
|
1758
|
-
store: false,
|
|
1759
|
-
stream: true,
|
|
1760
|
-
include: ["reasoning.encrypted_content"],
|
|
1761
|
-
...options.sessionId !== void 0 ? { prompt_cache_key: String(options.sessionId) } : {}
|
|
1762
|
-
};
|
|
2005
|
+
const messages = await resolveImages(options.messages, this.options.resolveAttachments?.(), signal);
|
|
2006
|
+
const fast = this.options.speedFor !== void 0 && await this.options.speedFor(options.sessionId, options.model);
|
|
2007
|
+
const body = codexRequestBody(options, toResponsesInput(messages, options.system), fast);
|
|
1763
2008
|
return fetch(CODEX_API_URL, {
|
|
1764
2009
|
method: "POST",
|
|
1765
2010
|
headers: {
|
|
@@ -2143,12 +2388,11 @@ async function* streamAnthropic(stream, onActivity) {
|
|
|
2143
2388
|
//#endregion
|
|
2144
2389
|
//#region src/providers/claude.ts
|
|
2145
2390
|
const CLAUDE_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
2146
|
-
const
|
|
2147
|
-
const CLAUDE_TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
|
|
2391
|
+
const CLAUDE_TOKEN_URL = "https://claude.ai/v1/oauth/token";
|
|
2148
2392
|
const CLAUDE_API_URL = "https://api.anthropic.com/v1/messages?beta=true";
|
|
2149
2393
|
const CLAUDE_PROFILE_URL = "https://api.anthropic.com/api/oauth/profile";
|
|
2394
|
+
const CLAUDE_MODELS_URL = "https://api.anthropic.com/v1/models?beta=true";
|
|
2150
2395
|
const CLAUDE_SCOPE = "org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";
|
|
2151
|
-
const CLAUDE_CALLBACK_PATH = "/callback";
|
|
2152
2396
|
const CLAUDE_CONTEXT_WINDOW = 2e5;
|
|
2153
2397
|
const CLAUDE_DEFAULT_MAX_TOKENS = 32e3;
|
|
2154
2398
|
/** Refresh when the access token has less than this much life left. */
|
|
@@ -2158,28 +2402,32 @@ const CLAUDE_PREEMPT_MS = 5 * 6e4;
|
|
|
2158
2402
|
* so these headers impersonate the CLI; the harness attribution user-agent
|
|
2159
2403
|
* cannot be sent here (one user-agent slot, and the CLI's wins).
|
|
2160
2404
|
*/
|
|
2161
|
-
const
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
const
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
}
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2405
|
+
const CLAUDE_CLI_FALLBACK_VERSION = "2.1.234";
|
|
2406
|
+
function detectClaudeVersion() {
|
|
2407
|
+
try {
|
|
2408
|
+
const match = execFileSync("claude", ["--version"], {
|
|
2409
|
+
timeout: 3e3,
|
|
2410
|
+
encoding: "utf8"
|
|
2411
|
+
}).match(/^(\d+\.\d+\.\d+)/);
|
|
2412
|
+
if (match) return match[1];
|
|
2413
|
+
} catch {}
|
|
2414
|
+
return CLAUDE_CLI_FALLBACK_VERSION;
|
|
2415
|
+
}
|
|
2416
|
+
let claudeCliUserAgent;
|
|
2417
|
+
function getClaudeCliUserAgent() {
|
|
2418
|
+
if (claudeCliUserAgent === void 0) claudeCliUserAgent = `claude-cli/${detectClaudeVersion()} (external, cli)`;
|
|
2419
|
+
return claudeCliUserAgent;
|
|
2420
|
+
}
|
|
2421
|
+
const CLAUDE_BETA_FALLBACK = [
|
|
2422
|
+
"claude-code-20250219",
|
|
2423
|
+
"oauth-2025-04-20",
|
|
2424
|
+
"interleaved-thinking-2025-05-14",
|
|
2425
|
+
"context-management-2025-06-27",
|
|
2426
|
+
"effort-2025-11-24",
|
|
2427
|
+
"compact-2026-01-12",
|
|
2428
|
+
"files-api-2025-04-14"
|
|
2429
|
+
].join(",");
|
|
2430
|
+
const CLAUDE_BETA_FLAGS = CLAUDE_BETA_FALLBACK;
|
|
2183
2431
|
/** Best-effort account profile; login must not fail when this does. */
|
|
2184
2432
|
async function fetchClaudeProfile(accessToken) {
|
|
2185
2433
|
try {
|
|
@@ -2322,7 +2570,7 @@ async function fetchClaudeUsage(session, fetchFn = fetch, signal) {
|
|
|
2322
2570
|
headers: {
|
|
2323
2571
|
"authorization": `Bearer ${session.accessToken}`,
|
|
2324
2572
|
"anthropic-beta": "oauth-2025-04-20",
|
|
2325
|
-
"user-agent":
|
|
2573
|
+
"user-agent": getClaudeCliUserAgent(),
|
|
2326
2574
|
"accept": "application/json"
|
|
2327
2575
|
},
|
|
2328
2576
|
...signal === void 0 ? {} : { signal }
|
|
@@ -2347,22 +2595,79 @@ async function fetchClaudeUsage(session, fetchFn = fetch, signal) {
|
|
|
2347
2595
|
windows
|
|
2348
2596
|
};
|
|
2349
2597
|
}
|
|
2598
|
+
function claudeThinkingType(capabilities) {
|
|
2599
|
+
const types = capabilities?.thinking?.types;
|
|
2600
|
+
if (types?.enabled?.supported === true) return "enabled";
|
|
2601
|
+
if (types?.adaptive?.supported === true) return "adaptive";
|
|
2602
|
+
}
|
|
2603
|
+
/** Effort levels in display order; a model exposes only the ones it advertises as supported. */
|
|
2604
|
+
const CLAUDE_EFFORT_LEVELS = [
|
|
2605
|
+
"low",
|
|
2606
|
+
"medium",
|
|
2607
|
+
"high",
|
|
2608
|
+
"xhigh",
|
|
2609
|
+
"max"
|
|
2610
|
+
];
|
|
2611
|
+
function claudeReasoning(capabilities) {
|
|
2612
|
+
const effort = capabilities?.effort;
|
|
2613
|
+
if (effort?.supported !== true) return void 0;
|
|
2614
|
+
const efforts = CLAUDE_EFFORT_LEVELS.filter((level) => effort[level]?.supported === true).map((level) => ({
|
|
2615
|
+
id: ReasoningEffortId(level),
|
|
2616
|
+
name: level[0].toUpperCase() + level.slice(1)
|
|
2617
|
+
}));
|
|
2618
|
+
return efforts.length > 0 ? { efforts } : void 0;
|
|
2619
|
+
}
|
|
2620
|
+
/** Fetch the live model catalog from the subscription endpoint. */
|
|
2621
|
+
async function fetchClaudeModels(session, fetchFn = fetch) {
|
|
2622
|
+
const response = await fetchFn(CLAUDE_MODELS_URL, { headers: {
|
|
2623
|
+
"authorization": `Bearer ${session.accessToken}`,
|
|
2624
|
+
"anthropic-version": "2023-06-01",
|
|
2625
|
+
"user-agent": getClaudeCliUserAgent(),
|
|
2626
|
+
"anthropic-dangerous-direct-browser-access": "true",
|
|
2627
|
+
"accept": "application/json"
|
|
2628
|
+
} });
|
|
2629
|
+
if (!response.ok) throw await httpLlmError(response, "claude models API");
|
|
2630
|
+
const payload = await response.json();
|
|
2631
|
+
if (!Array.isArray(payload.data)) throw new Error("claude models API returned an invalid catalog");
|
|
2632
|
+
const models = payload.data.filter((m) => typeof m.id === "string").map((m) => {
|
|
2633
|
+
const thinkingType = claudeThinkingType(m.capabilities);
|
|
2634
|
+
const reasoning = claudeReasoning(m.capabilities);
|
|
2635
|
+
return {
|
|
2636
|
+
id: m.id,
|
|
2637
|
+
name: m.display_name ?? m.id,
|
|
2638
|
+
...thinkingType === void 0 ? {} : { thinkingType },
|
|
2639
|
+
...reasoning === void 0 ? {} : { reasoning }
|
|
2640
|
+
};
|
|
2641
|
+
});
|
|
2642
|
+
if (models.length === 0) throw new Error("claude models API returned an empty catalog");
|
|
2643
|
+
return models;
|
|
2644
|
+
}
|
|
2645
|
+
/**
|
|
2646
|
+
* Claude Code's own SDK retry shape: exponential backoff starting at 1s,
|
|
2647
|
+
* doubling per attempt, capped at 60s, plus jitter. `maxRetries` is the
|
|
2648
|
+
* count of retries after the first attempt (Claude Code defaults to 10).
|
|
2649
|
+
*/
|
|
2650
|
+
const CLAUDE_RETRY_INITIAL_DELAY_MS = 1e3;
|
|
2651
|
+
const CLAUDE_RETRY_MAX_DELAY_MS = 6e4;
|
|
2652
|
+
const CLAUDE_RETRY_JITTER_RATIO = .2;
|
|
2350
2653
|
/** The Claude 4.5 family accepts image input. */
|
|
2351
2654
|
const CLAUDE_MODALITIES = ["text", "image"];
|
|
2352
2655
|
/** Claude wire adapter: one instance serves the `claude` provider route. */
|
|
2353
2656
|
var ClaudeAdapter = class extends LlmAdapter {
|
|
2657
|
+
catalog;
|
|
2354
2658
|
constructor(options) {
|
|
2355
2659
|
super();
|
|
2356
2660
|
this.options = options;
|
|
2661
|
+
this.catalog = new ModelCatalogCache(options.catalogStore);
|
|
2357
2662
|
}
|
|
2358
|
-
|
|
2359
|
-
return
|
|
2360
|
-
id: provider,
|
|
2361
|
-
name: "Claude (Subscription)"
|
|
2362
|
-
};
|
|
2663
|
+
async fetchCatalog() {
|
|
2664
|
+
return fetchClaudeModels(await this.options.tokens.session(), this.options.fetchFn);
|
|
2363
2665
|
}
|
|
2364
|
-
async
|
|
2365
|
-
if (!
|
|
2666
|
+
async discovered(model) {
|
|
2667
|
+
if (!this.options.discovery) return void 0;
|
|
2668
|
+
return (await this.catalog.resolve(() => this.fetchCatalog()))?.find((entry) => entry.id === model);
|
|
2669
|
+
}
|
|
2670
|
+
staticModels(provider) {
|
|
2366
2671
|
return this.options.models.map((model) => ({
|
|
2367
2672
|
provider,
|
|
2368
2673
|
id: model.id,
|
|
@@ -2370,16 +2675,54 @@ var ClaudeAdapter = class extends LlmAdapter {
|
|
|
2370
2675
|
inputModalities: model.inputModalities ?? CLAUDE_MODALITIES
|
|
2371
2676
|
}));
|
|
2372
2677
|
}
|
|
2373
|
-
|
|
2678
|
+
providerInfo(provider) {
|
|
2679
|
+
return {
|
|
2680
|
+
id: provider,
|
|
2681
|
+
name: "Claude (Subscription)"
|
|
2682
|
+
};
|
|
2683
|
+
}
|
|
2684
|
+
providerRetryPolicy(provider) {
|
|
2685
|
+
if (this.options.maxRetries === void 0) return void 0;
|
|
2686
|
+
return resolveRetryPolicy({
|
|
2687
|
+
mode: "normal",
|
|
2688
|
+
maxRetries: this.options.maxRetries,
|
|
2689
|
+
backoff: {
|
|
2690
|
+
initialDelayMs: CLAUDE_RETRY_INITIAL_DELAY_MS,
|
|
2691
|
+
maxDelayMs: CLAUDE_RETRY_MAX_DELAY_MS,
|
|
2692
|
+
jitterRatio: CLAUDE_RETRY_JITTER_RATIO
|
|
2693
|
+
}
|
|
2694
|
+
}, `claude: provider "${provider}" retryPolicy`);
|
|
2695
|
+
}
|
|
2696
|
+
async listModels(provider) {
|
|
2697
|
+
if (await this.options.tokens.peek() === void 0) return [];
|
|
2698
|
+
if (!this.options.discovery) return this.staticModels(provider);
|
|
2699
|
+
try {
|
|
2700
|
+
return (await this.catalog.get(() => this.fetchCatalog())).map((model) => ({
|
|
2701
|
+
provider,
|
|
2702
|
+
id: model.id,
|
|
2703
|
+
name: model.name,
|
|
2704
|
+
inputModalities: CLAUDE_MODALITIES
|
|
2705
|
+
}));
|
|
2706
|
+
} catch (error) {
|
|
2707
|
+
if (error instanceof LlmError && (error.code === "MISSING_CREDENTIAL" || error.code === "INVALID_CREDENTIAL")) return [];
|
|
2708
|
+
if (error instanceof LlmError && error.code === "AUTH") this.catalog.invalidate();
|
|
2709
|
+
this.options.onWarn?.(`claude model discovery failed; using the built-in catalog (${errorChain(error)})`);
|
|
2710
|
+
return this.staticModels(provider);
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
async resolveModel(provider, model) {
|
|
2714
|
+
const disc = await this.discovered(model);
|
|
2374
2715
|
const configured = this.options.models.find((entry) => entry.id === model);
|
|
2375
|
-
|
|
2716
|
+
const reasoning = disc?.reasoning;
|
|
2717
|
+
return {
|
|
2376
2718
|
provider,
|
|
2377
2719
|
id: model,
|
|
2378
|
-
name: configured?.name ?? model,
|
|
2720
|
+
name: disc?.name ?? configured?.name ?? model,
|
|
2379
2721
|
inputModalities: configured?.inputModalities ?? CLAUDE_MODALITIES,
|
|
2380
|
-
context: { contextWindow: configured?.contextWindow ?? CLAUDE_CONTEXT_WINDOW },
|
|
2381
|
-
defaultMaxTokens: configured?.maxTokens ?? CLAUDE_DEFAULT_MAX_TOKENS
|
|
2382
|
-
|
|
2722
|
+
context: { contextWindow: disc?.contextWindow ?? configured?.contextWindow ?? CLAUDE_CONTEXT_WINDOW },
|
|
2723
|
+
defaultMaxTokens: configured?.maxTokens ?? CLAUDE_DEFAULT_MAX_TOKENS,
|
|
2724
|
+
...reasoning === void 0 ? {} : { reasoning }
|
|
2725
|
+
};
|
|
2383
2726
|
}
|
|
2384
2727
|
async *stream(options) {
|
|
2385
2728
|
const watchdog = idleWatchdog(options.signal, this.options.streamIdleTimeoutMs);
|
|
@@ -2401,14 +2744,42 @@ var ClaudeAdapter = class extends LlmAdapter {
|
|
|
2401
2744
|
watchdog.stop();
|
|
2402
2745
|
}
|
|
2403
2746
|
}
|
|
2747
|
+
/**
|
|
2748
|
+
* `display: 'summarized'` is set explicitly on both shapes: `adaptive`-type
|
|
2749
|
+
* models default to `display: 'omitted'`, which returns thinking blocks with
|
|
2750
|
+
* an empty `thinking` field — without this override the "Think" panel would
|
|
2751
|
+
* always render empty even though real reasoning (and billed thinking_tokens)
|
|
2752
|
+
* ran.
|
|
2753
|
+
*/
|
|
2754
|
+
thinkingParam(thinkingType, maxTokens) {
|
|
2755
|
+
if (thinkingType === "adaptive") return {
|
|
2756
|
+
type: "adaptive",
|
|
2757
|
+
display: "summarized"
|
|
2758
|
+
};
|
|
2759
|
+
if (thinkingType === "enabled") {
|
|
2760
|
+
const budget = Math.min(Math.max(1024, Math.floor(maxTokens * .5)), maxTokens - 100);
|
|
2761
|
+
if (budget < 1024) return void 0;
|
|
2762
|
+
return {
|
|
2763
|
+
type: "enabled",
|
|
2764
|
+
budget_tokens: budget,
|
|
2765
|
+
display: "summarized"
|
|
2766
|
+
};
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2404
2769
|
async request(options, session, signal) {
|
|
2405
2770
|
const messages = await resolveImages(options.messages, this.options.resolveAttachments?.(), signal);
|
|
2771
|
+
const maxTokens = options.maxTokens ?? this.options.models.find((entry) => entry.id === options.model)?.maxTokens ?? CLAUDE_DEFAULT_MAX_TOKENS;
|
|
2772
|
+
const disc = await this.discovered(options.model);
|
|
2773
|
+
const thinking = this.thinkingParam(disc?.thinkingType, maxTokens);
|
|
2774
|
+
const effort = options.reasoningEffort !== void 0 && disc?.reasoning !== void 0 ? { output_config: { effort: String(options.reasoningEffort) } } : {};
|
|
2406
2775
|
const body = {
|
|
2407
2776
|
model: options.model,
|
|
2408
|
-
max_tokens:
|
|
2777
|
+
max_tokens: maxTokens,
|
|
2409
2778
|
system: toAnthropicSystem(options.system, messages),
|
|
2410
2779
|
messages: toAnthropicMessages(messages),
|
|
2411
2780
|
...options.tools !== void 0 && options.tools.length > 0 ? { tools: toAnthropicTools(options.tools) } : {},
|
|
2781
|
+
...thinking === void 0 ? {} : { thinking },
|
|
2782
|
+
...effort,
|
|
2412
2783
|
stream: true,
|
|
2413
2784
|
...options.sessionId !== void 0 ? { metadata: { user_id: String(options.sessionId) } } : {}
|
|
2414
2785
|
};
|
|
@@ -2418,7 +2789,7 @@ var ClaudeAdapter = class extends LlmAdapter {
|
|
|
2418
2789
|
"authorization": `Bearer ${session.accessToken}`,
|
|
2419
2790
|
"anthropic-version": "2023-06-01",
|
|
2420
2791
|
"anthropic-beta": CLAUDE_BETA_FLAGS,
|
|
2421
|
-
"user-agent":
|
|
2792
|
+
"user-agent": getClaudeCliUserAgent(),
|
|
2422
2793
|
"x-app": "cli",
|
|
2423
2794
|
"anthropic-dangerous-direct-browser-access": "true",
|
|
2424
2795
|
"accept": "text/event-stream",
|
|
@@ -3388,19 +3759,11 @@ function createImageGenerateTool(options) {
|
|
|
3388
3759
|
});
|
|
3389
3760
|
}
|
|
3390
3761
|
const revisedPrompt = images.find((image) => image.revisedPrompt !== void 0)?.revisedPrompt;
|
|
3391
|
-
|
|
3762
|
+
return {
|
|
3392
3763
|
paths,
|
|
3393
3764
|
...refs.length > 0 ? { images: refs } : {},
|
|
3394
3765
|
...revisedPrompt === void 0 ? {} : { revisedPrompt }
|
|
3395
3766
|
};
|
|
3396
|
-
if (exec.parent !== void 0 && refs.length > 0) exec.deferContext(createUserMessage({
|
|
3397
|
-
content: imageGenerateContent(value),
|
|
3398
|
-
source: {
|
|
3399
|
-
kind: "plugin",
|
|
3400
|
-
plugin: "dsh-plugin-subscriptions"
|
|
3401
|
-
}
|
|
3402
|
-
}));
|
|
3403
|
-
return value;
|
|
3404
3767
|
}
|
|
3405
3768
|
});
|
|
3406
3769
|
}
|
|
@@ -3697,17 +4060,27 @@ const DEFAULT_MODELS = {
|
|
|
3697
4060
|
],
|
|
3698
4061
|
claude: [
|
|
3699
4062
|
{
|
|
3700
|
-
id: "claude-opus-
|
|
3701
|
-
name: "Claude Opus
|
|
3702
|
-
maxTokens:
|
|
4063
|
+
id: "claude-opus-5",
|
|
4064
|
+
name: "Claude Opus 5",
|
|
4065
|
+
maxTokens: 128e3,
|
|
4066
|
+
contextWindow: 1e6
|
|
4067
|
+
},
|
|
4068
|
+
{
|
|
4069
|
+
id: "claude-sonnet-5",
|
|
4070
|
+
name: "Claude Sonnet 5",
|
|
4071
|
+
maxTokens: 128e3,
|
|
4072
|
+
contextWindow: 1e6
|
|
3703
4073
|
},
|
|
3704
4074
|
{
|
|
3705
|
-
id: "claude-
|
|
3706
|
-
name: "Claude
|
|
4075
|
+
id: "claude-fable-5",
|
|
4076
|
+
name: "Claude Fable 5",
|
|
4077
|
+
maxTokens: 128e3,
|
|
4078
|
+
contextWindow: 1e6
|
|
3707
4079
|
},
|
|
3708
4080
|
{
|
|
3709
|
-
id: "claude-haiku-4-5",
|
|
3710
|
-
name: "Claude Haiku 4.5"
|
|
4081
|
+
id: "claude-haiku-4-5-20251001",
|
|
4082
|
+
name: "Claude Haiku 4.5",
|
|
4083
|
+
maxTokens: 64e3
|
|
3711
4084
|
}
|
|
3712
4085
|
],
|
|
3713
4086
|
grok: [
|
|
@@ -3796,7 +4169,17 @@ var SubscriptionsAuthController = class {
|
|
|
3796
4169
|
};
|
|
3797
4170
|
}
|
|
3798
4171
|
async login(provider) {
|
|
3799
|
-
|
|
4172
|
+
if (provider === "claude") {
|
|
4173
|
+
const session = readClaudeCodeCredentials();
|
|
4174
|
+
if (session) {
|
|
4175
|
+
await this.persist("claude", session);
|
|
4176
|
+
this.lastError.delete("claude");
|
|
4177
|
+
this.onAuthChanged("claude");
|
|
4178
|
+
return { authorizeUrl: "" };
|
|
4179
|
+
}
|
|
4180
|
+
throw new Error("Claude Code credentials not found. Run \"claude\" first to log in.");
|
|
4181
|
+
}
|
|
4182
|
+
const spec = provider === "grok" ? await grokFlow() : codexFlow;
|
|
3800
4183
|
const attempt = await this.flows.start(provider, spec);
|
|
3801
4184
|
this.complete(provider, attempt);
|
|
3802
4185
|
return { authorizeUrl: attempt.authorizeUrl };
|
|
@@ -3860,8 +4243,11 @@ function apply(ctx, config) {
|
|
|
3860
4243
|
handles.get(provider)?.replace([provider]);
|
|
3861
4244
|
};
|
|
3862
4245
|
let codexTokens;
|
|
4246
|
+
let claudeTokens;
|
|
3863
4247
|
let grokTokens;
|
|
3864
4248
|
const usageFetchers = {};
|
|
4249
|
+
const speedBySession = /* @__PURE__ */ new Map();
|
|
4250
|
+
let codexAdapter;
|
|
3865
4251
|
for (const provider of providers) switch (provider) {
|
|
3866
4252
|
case "codex": {
|
|
3867
4253
|
const tokens = new TokenManager({
|
|
@@ -3878,15 +4264,19 @@ function apply(ctx, config) {
|
|
|
3878
4264
|
});
|
|
3879
4265
|
codexTokens = tokens;
|
|
3880
4266
|
usageFetchers.codex = async (signal) => fetchCodexUsage(await tokens.session(), fetch, signal);
|
|
3881
|
-
|
|
4267
|
+
let adapter;
|
|
4268
|
+
adapter = new CodexAdapter({
|
|
3882
4269
|
models: catalog.codex,
|
|
3883
4270
|
streamIdleTimeoutMs,
|
|
3884
4271
|
tokens,
|
|
3885
4272
|
discovery: !overridden.has("codex"),
|
|
3886
4273
|
onWarn,
|
|
3887
4274
|
resolveAttachments,
|
|
3888
|
-
catalogStore: catalogStore("codex")
|
|
3889
|
-
|
|
4275
|
+
catalogStore: catalogStore("codex"),
|
|
4276
|
+
speedFor: (sessionId, model) => sessionId !== void 0 && speedBySession.get(sessionId) === "fast" && adapter.supportsFastTier(model)
|
|
4277
|
+
});
|
|
4278
|
+
codexAdapter = adapter;
|
|
4279
|
+
handles.set("codex", ctx.llm.registerAdapter(["codex"], adapter));
|
|
3890
4280
|
break;
|
|
3891
4281
|
}
|
|
3892
4282
|
case "claude": {
|
|
@@ -3896,18 +4286,23 @@ function apply(ctx, config) {
|
|
|
3896
4286
|
load: () => getSession("claude"),
|
|
3897
4287
|
save: (session) => saveSession("claude", session),
|
|
3898
4288
|
remove: () => deleteSession("claude"),
|
|
3899
|
-
refresh: refreshClaude,
|
|
4289
|
+
refresh: (session) => refreshClaudeSynced(session, refreshClaude),
|
|
3900
4290
|
isPermanent: isClaudePermanentRefreshError,
|
|
3901
4291
|
onRemoved: () => {
|
|
3902
4292
|
authChanged("claude");
|
|
3903
4293
|
}
|
|
3904
4294
|
});
|
|
4295
|
+
claudeTokens = tokens;
|
|
3905
4296
|
usageFetchers.claude = async (signal) => fetchClaudeUsage(await tokens.session(), fetch, signal);
|
|
3906
4297
|
handles.set("claude", ctx.llm.registerAdapter(["claude"], new ClaudeAdapter({
|
|
3907
4298
|
models: catalog.claude,
|
|
3908
4299
|
streamIdleTimeoutMs,
|
|
3909
4300
|
tokens,
|
|
3910
|
-
|
|
4301
|
+
discovery: !overridden.has("claude"),
|
|
4302
|
+
onWarn,
|
|
4303
|
+
maxRetries: 10,
|
|
4304
|
+
resolveAttachments,
|
|
4305
|
+
catalogStore: catalogStore("claude")
|
|
3911
4306
|
})));
|
|
3912
4307
|
break;
|
|
3913
4308
|
}
|
|
@@ -3938,7 +4333,26 @@ function apply(ctx, config) {
|
|
|
3938
4333
|
break;
|
|
3939
4334
|
}
|
|
3940
4335
|
}
|
|
3941
|
-
registerAuthRpc(ctx, new SubscriptionsAuthController(flows, authChanged, resolveAttachments, usageFetchers)
|
|
4336
|
+
registerAuthRpc(ctx, new SubscriptionsAuthController(flows, authChanged, resolveAttachments, usageFetchers), {
|
|
4337
|
+
async speed(sessionId) {
|
|
4338
|
+
return {
|
|
4339
|
+
tier: speedBySession.get(sessionId) ?? "standard",
|
|
4340
|
+
fastModels: await codexAdapter?.fastCapableModels() ?? []
|
|
4341
|
+
};
|
|
4342
|
+
},
|
|
4343
|
+
async setSpeed(sessionId, tier) {
|
|
4344
|
+
if (tier === "standard") speedBySession.delete(sessionId);
|
|
4345
|
+
else speedBySession.set(sessionId, tier);
|
|
4346
|
+
}
|
|
4347
|
+
});
|
|
4348
|
+
if (claudeTokens !== void 0) {
|
|
4349
|
+
const syncTimer = setInterval(() => {
|
|
4350
|
+
claudeTokens?.session().catch(() => {});
|
|
4351
|
+
}, 5 * 6e4);
|
|
4352
|
+
ctx.effect(() => () => {
|
|
4353
|
+
clearInterval(syncTimer);
|
|
4354
|
+
}, "dsh-plugin-subscriptions: claude background sync timer");
|
|
4355
|
+
}
|
|
3942
4356
|
ctx.inject(["tools"], (toolsCtx) => {
|
|
3943
4357
|
if (grokTokens !== void 0) {
|
|
3944
4358
|
toolsCtx.tools.register(createXSearchTool({ tokens: grokTokens }));
|