dsh-plugin-subscriptions 0.4.0 → 0.4.2
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 +14 -6
- package/README.zh.md +14 -6
- 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/client/SubscriptionsSection.js +6 -1
- package/lib/client.js +5 -1
- package/lib/client.js.map +1 -1
- package/lib/index.js +498 -77
- package/lib/providers/catalog-store.js +4 -0
- package/lib/providers/claude.d.ts +35 -3
- package/lib/providers/claude.js +181 -27
- package/lib/providers/common.d.ts +2 -0
- package/lib/tools/image-generate.d.ts +55 -19
- package/lib/tools/image-generate.js +130 -31
- package/package.json +6 -8
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, createUserMessage, errorChain, isContextWindowExceededError, isQuotaExceededError } from "@deepseek-ai/dsh-llm";
|
|
2
|
+
import { CONTEXT_WINDOW_EXCEEDED_CODE, CallId, EMPTY_RESPONSE_CODE, LlmAdapter, LlmError, QUOTA_EXCEEDED_CODE, ReasoningEffortId, attributionHeaders, createUserMessage, 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. */
|
|
@@ -827,13 +1011,16 @@ function sanitizeModel(value) {
|
|
|
827
1011
|
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
1012
|
const reasoning = raw.reasoning === void 0 ? void 0 : sanitizeReasoning(raw.reasoning);
|
|
829
1013
|
if (raw.reasoning !== void 0 && reasoning === void 0) return void 0;
|
|
1014
|
+
const thinkingType = raw.thinkingType;
|
|
1015
|
+
if (thinkingType !== void 0 && thinkingType !== "enabled" && thinkingType !== "adaptive") return void 0;
|
|
830
1016
|
return {
|
|
831
1017
|
id: raw.id,
|
|
832
1018
|
name: raw.name,
|
|
833
1019
|
...raw.description === void 0 ? {} : { description: raw.description },
|
|
834
1020
|
...raw.contextWindow === void 0 ? {} : { contextWindow: raw.contextWindow },
|
|
835
1021
|
...raw.priority === void 0 ? {} : { priority: raw.priority },
|
|
836
|
-
...reasoning === void 0 ? {} : { reasoning }
|
|
1022
|
+
...reasoning === void 0 ? {} : { reasoning },
|
|
1023
|
+
...thinkingType === void 0 ? {} : { thinkingType }
|
|
837
1024
|
};
|
|
838
1025
|
}
|
|
839
1026
|
/**
|
|
@@ -2143,12 +2330,11 @@ async function* streamAnthropic(stream, onActivity) {
|
|
|
2143
2330
|
//#endregion
|
|
2144
2331
|
//#region src/providers/claude.ts
|
|
2145
2332
|
const CLAUDE_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
2146
|
-
const
|
|
2147
|
-
const CLAUDE_TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
|
|
2333
|
+
const CLAUDE_TOKEN_URL = "https://claude.ai/v1/oauth/token";
|
|
2148
2334
|
const CLAUDE_API_URL = "https://api.anthropic.com/v1/messages?beta=true";
|
|
2149
2335
|
const CLAUDE_PROFILE_URL = "https://api.anthropic.com/api/oauth/profile";
|
|
2336
|
+
const CLAUDE_MODELS_URL = "https://api.anthropic.com/v1/models?beta=true";
|
|
2150
2337
|
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
2338
|
const CLAUDE_CONTEXT_WINDOW = 2e5;
|
|
2153
2339
|
const CLAUDE_DEFAULT_MAX_TOKENS = 32e3;
|
|
2154
2340
|
/** Refresh when the access token has less than this much life left. */
|
|
@@ -2158,28 +2344,28 @@ const CLAUDE_PREEMPT_MS = 5 * 6e4;
|
|
|
2158
2344
|
* so these headers impersonate the CLI; the harness attribution user-agent
|
|
2159
2345
|
* cannot be sent here (one user-agent slot, and the CLI's wins).
|
|
2160
2346
|
*/
|
|
2161
|
-
const
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
const
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
}
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2347
|
+
const CLAUDE_CLI_FALLBACK_VERSION = "2.1.234";
|
|
2348
|
+
function detectClaudeVersion() {
|
|
2349
|
+
try {
|
|
2350
|
+
const match = execFileSync("claude", ["--version"], {
|
|
2351
|
+
timeout: 3e3,
|
|
2352
|
+
encoding: "utf8"
|
|
2353
|
+
}).match(/^(\d+\.\d+\.\d+)/);
|
|
2354
|
+
if (match) return match[1];
|
|
2355
|
+
} catch {}
|
|
2356
|
+
return CLAUDE_CLI_FALLBACK_VERSION;
|
|
2357
|
+
}
|
|
2358
|
+
const CLAUDE_CLI_USER_AGENT = `claude-cli/${detectClaudeVersion()} (external, cli)`;
|
|
2359
|
+
const CLAUDE_BETA_FALLBACK = [
|
|
2360
|
+
"claude-code-20250219",
|
|
2361
|
+
"oauth-2025-04-20",
|
|
2362
|
+
"interleaved-thinking-2025-05-14",
|
|
2363
|
+
"context-management-2025-06-27",
|
|
2364
|
+
"effort-2025-11-24",
|
|
2365
|
+
"compact-2026-01-12",
|
|
2366
|
+
"files-api-2025-04-14"
|
|
2367
|
+
].join(",");
|
|
2368
|
+
const CLAUDE_BETA_FLAGS = CLAUDE_BETA_FALLBACK;
|
|
2183
2369
|
/** Best-effort account profile; login must not fail when this does. */
|
|
2184
2370
|
async function fetchClaudeProfile(accessToken) {
|
|
2185
2371
|
try {
|
|
@@ -2347,22 +2533,79 @@ async function fetchClaudeUsage(session, fetchFn = fetch, signal) {
|
|
|
2347
2533
|
windows
|
|
2348
2534
|
};
|
|
2349
2535
|
}
|
|
2536
|
+
function claudeThinkingType(capabilities) {
|
|
2537
|
+
const types = capabilities?.thinking?.types;
|
|
2538
|
+
if (types?.enabled?.supported === true) return "enabled";
|
|
2539
|
+
if (types?.adaptive?.supported === true) return "adaptive";
|
|
2540
|
+
}
|
|
2541
|
+
/** Effort levels in display order; a model exposes only the ones it advertises as supported. */
|
|
2542
|
+
const CLAUDE_EFFORT_LEVELS = [
|
|
2543
|
+
"low",
|
|
2544
|
+
"medium",
|
|
2545
|
+
"high",
|
|
2546
|
+
"xhigh",
|
|
2547
|
+
"max"
|
|
2548
|
+
];
|
|
2549
|
+
function claudeReasoning(capabilities) {
|
|
2550
|
+
const effort = capabilities?.effort;
|
|
2551
|
+
if (effort?.supported !== true) return void 0;
|
|
2552
|
+
const efforts = CLAUDE_EFFORT_LEVELS.filter((level) => effort[level]?.supported === true).map((level) => ({
|
|
2553
|
+
id: ReasoningEffortId(level),
|
|
2554
|
+
name: level[0].toUpperCase() + level.slice(1)
|
|
2555
|
+
}));
|
|
2556
|
+
return efforts.length > 0 ? { efforts } : void 0;
|
|
2557
|
+
}
|
|
2558
|
+
/** Fetch the live model catalog from the subscription endpoint. */
|
|
2559
|
+
async function fetchClaudeModels(session, fetchFn = fetch) {
|
|
2560
|
+
const response = await fetchFn(CLAUDE_MODELS_URL, { headers: {
|
|
2561
|
+
"authorization": `Bearer ${session.accessToken}`,
|
|
2562
|
+
"anthropic-version": "2023-06-01",
|
|
2563
|
+
"user-agent": CLAUDE_CLI_USER_AGENT,
|
|
2564
|
+
"anthropic-dangerous-direct-browser-access": "true",
|
|
2565
|
+
"accept": "application/json"
|
|
2566
|
+
} });
|
|
2567
|
+
if (!response.ok) throw await httpLlmError(response, "claude models API");
|
|
2568
|
+
const payload = await response.json();
|
|
2569
|
+
if (!Array.isArray(payload.data)) throw new Error("claude models API returned an invalid catalog");
|
|
2570
|
+
const models = payload.data.filter((m) => typeof m.id === "string").map((m) => {
|
|
2571
|
+
const thinkingType = claudeThinkingType(m.capabilities);
|
|
2572
|
+
const reasoning = claudeReasoning(m.capabilities);
|
|
2573
|
+
return {
|
|
2574
|
+
id: m.id,
|
|
2575
|
+
name: m.display_name ?? m.id,
|
|
2576
|
+
...thinkingType === void 0 ? {} : { thinkingType },
|
|
2577
|
+
...reasoning === void 0 ? {} : { reasoning }
|
|
2578
|
+
};
|
|
2579
|
+
});
|
|
2580
|
+
if (models.length === 0) throw new Error("claude models API returned an empty catalog");
|
|
2581
|
+
return models;
|
|
2582
|
+
}
|
|
2583
|
+
/**
|
|
2584
|
+
* Claude Code's own SDK retry shape: exponential backoff starting at 1s,
|
|
2585
|
+
* doubling per attempt, capped at 60s, plus jitter. `maxRetries` is the
|
|
2586
|
+
* count of retries after the first attempt (Claude Code defaults to 10).
|
|
2587
|
+
*/
|
|
2588
|
+
const CLAUDE_RETRY_INITIAL_DELAY_MS = 1e3;
|
|
2589
|
+
const CLAUDE_RETRY_MAX_DELAY_MS = 6e4;
|
|
2590
|
+
const CLAUDE_RETRY_JITTER_RATIO = .2;
|
|
2350
2591
|
/** The Claude 4.5 family accepts image input. */
|
|
2351
2592
|
const CLAUDE_MODALITIES = ["text", "image"];
|
|
2352
2593
|
/** Claude wire adapter: one instance serves the `claude` provider route. */
|
|
2353
2594
|
var ClaudeAdapter = class extends LlmAdapter {
|
|
2595
|
+
catalog;
|
|
2354
2596
|
constructor(options) {
|
|
2355
2597
|
super();
|
|
2356
2598
|
this.options = options;
|
|
2599
|
+
this.catalog = new ModelCatalogCache(options.catalogStore);
|
|
2357
2600
|
}
|
|
2358
|
-
|
|
2359
|
-
return
|
|
2360
|
-
id: provider,
|
|
2361
|
-
name: "Claude (Subscription)"
|
|
2362
|
-
};
|
|
2601
|
+
async fetchCatalog() {
|
|
2602
|
+
return fetchClaudeModels(await this.options.tokens.session(), this.options.fetchFn);
|
|
2363
2603
|
}
|
|
2364
|
-
async
|
|
2365
|
-
if (!
|
|
2604
|
+
async discovered(model) {
|
|
2605
|
+
if (!this.options.discovery) return void 0;
|
|
2606
|
+
return (await this.catalog.resolve(() => this.fetchCatalog()))?.find((entry) => entry.id === model);
|
|
2607
|
+
}
|
|
2608
|
+
staticModels(provider) {
|
|
2366
2609
|
return this.options.models.map((model) => ({
|
|
2367
2610
|
provider,
|
|
2368
2611
|
id: model.id,
|
|
@@ -2370,16 +2613,54 @@ var ClaudeAdapter = class extends LlmAdapter {
|
|
|
2370
2613
|
inputModalities: model.inputModalities ?? CLAUDE_MODALITIES
|
|
2371
2614
|
}));
|
|
2372
2615
|
}
|
|
2373
|
-
|
|
2616
|
+
providerInfo(provider) {
|
|
2617
|
+
return {
|
|
2618
|
+
id: provider,
|
|
2619
|
+
name: "Claude (Subscription)"
|
|
2620
|
+
};
|
|
2621
|
+
}
|
|
2622
|
+
providerRetryPolicy(provider) {
|
|
2623
|
+
if (this.options.maxRetries === void 0) return void 0;
|
|
2624
|
+
return resolveRetryPolicy({
|
|
2625
|
+
mode: "normal",
|
|
2626
|
+
maxRetries: this.options.maxRetries,
|
|
2627
|
+
backoff: {
|
|
2628
|
+
initialDelayMs: CLAUDE_RETRY_INITIAL_DELAY_MS,
|
|
2629
|
+
maxDelayMs: CLAUDE_RETRY_MAX_DELAY_MS,
|
|
2630
|
+
jitterRatio: CLAUDE_RETRY_JITTER_RATIO
|
|
2631
|
+
}
|
|
2632
|
+
}, `claude: provider "${provider}" retryPolicy`);
|
|
2633
|
+
}
|
|
2634
|
+
async listModels(provider) {
|
|
2635
|
+
if (await this.options.tokens.peek() === void 0) return [];
|
|
2636
|
+
if (!this.options.discovery) return this.staticModels(provider);
|
|
2637
|
+
try {
|
|
2638
|
+
return (await this.catalog.get(() => this.fetchCatalog())).map((model) => ({
|
|
2639
|
+
provider,
|
|
2640
|
+
id: model.id,
|
|
2641
|
+
name: model.name,
|
|
2642
|
+
inputModalities: CLAUDE_MODALITIES
|
|
2643
|
+
}));
|
|
2644
|
+
} catch (error) {
|
|
2645
|
+
if (error instanceof LlmError && (error.code === "MISSING_CREDENTIAL" || error.code === "INVALID_CREDENTIAL")) return [];
|
|
2646
|
+
if (error instanceof LlmError && error.code === "AUTH") this.catalog.invalidate();
|
|
2647
|
+
this.options.onWarn?.(`claude model discovery failed; using the built-in catalog (${errorChain(error)})`);
|
|
2648
|
+
return this.staticModels(provider);
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
async resolveModel(provider, model) {
|
|
2652
|
+
const disc = await this.discovered(model);
|
|
2374
2653
|
const configured = this.options.models.find((entry) => entry.id === model);
|
|
2375
|
-
|
|
2654
|
+
const reasoning = disc?.reasoning;
|
|
2655
|
+
return {
|
|
2376
2656
|
provider,
|
|
2377
2657
|
id: model,
|
|
2378
|
-
name: configured?.name ?? model,
|
|
2658
|
+
name: disc?.name ?? configured?.name ?? model,
|
|
2379
2659
|
inputModalities: configured?.inputModalities ?? CLAUDE_MODALITIES,
|
|
2380
|
-
context: { contextWindow: configured?.contextWindow ?? CLAUDE_CONTEXT_WINDOW },
|
|
2381
|
-
defaultMaxTokens: configured?.maxTokens ?? CLAUDE_DEFAULT_MAX_TOKENS
|
|
2382
|
-
|
|
2660
|
+
context: { contextWindow: disc?.contextWindow ?? configured?.contextWindow ?? CLAUDE_CONTEXT_WINDOW },
|
|
2661
|
+
defaultMaxTokens: configured?.maxTokens ?? CLAUDE_DEFAULT_MAX_TOKENS,
|
|
2662
|
+
...reasoning === void 0 ? {} : { reasoning }
|
|
2663
|
+
};
|
|
2383
2664
|
}
|
|
2384
2665
|
async *stream(options) {
|
|
2385
2666
|
const watchdog = idleWatchdog(options.signal, this.options.streamIdleTimeoutMs);
|
|
@@ -2401,14 +2682,42 @@ var ClaudeAdapter = class extends LlmAdapter {
|
|
|
2401
2682
|
watchdog.stop();
|
|
2402
2683
|
}
|
|
2403
2684
|
}
|
|
2685
|
+
/**
|
|
2686
|
+
* `display: 'summarized'` is set explicitly on both shapes: `adaptive`-type
|
|
2687
|
+
* models default to `display: 'omitted'`, which returns thinking blocks with
|
|
2688
|
+
* an empty `thinking` field — without this override the "Think" panel would
|
|
2689
|
+
* always render empty even though real reasoning (and billed thinking_tokens)
|
|
2690
|
+
* ran.
|
|
2691
|
+
*/
|
|
2692
|
+
thinkingParam(thinkingType, maxTokens) {
|
|
2693
|
+
if (thinkingType === "adaptive") return {
|
|
2694
|
+
type: "adaptive",
|
|
2695
|
+
display: "summarized"
|
|
2696
|
+
};
|
|
2697
|
+
if (thinkingType === "enabled") {
|
|
2698
|
+
const budget = Math.min(Math.max(1024, Math.floor(maxTokens * .5)), maxTokens - 100);
|
|
2699
|
+
if (budget < 1024) return void 0;
|
|
2700
|
+
return {
|
|
2701
|
+
type: "enabled",
|
|
2702
|
+
budget_tokens: budget,
|
|
2703
|
+
display: "summarized"
|
|
2704
|
+
};
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2404
2707
|
async request(options, session, signal) {
|
|
2405
2708
|
const messages = await resolveImages(options.messages, this.options.resolveAttachments?.(), signal);
|
|
2709
|
+
const maxTokens = options.maxTokens ?? this.options.models.find((entry) => entry.id === options.model)?.maxTokens ?? CLAUDE_DEFAULT_MAX_TOKENS;
|
|
2710
|
+
const disc = await this.discovered(options.model);
|
|
2711
|
+
const thinking = this.thinkingParam(disc?.thinkingType, maxTokens);
|
|
2712
|
+
const effort = options.reasoningEffort !== void 0 && disc?.reasoning !== void 0 ? { output_config: { effort: String(options.reasoningEffort) } } : {};
|
|
2406
2713
|
const body = {
|
|
2407
2714
|
model: options.model,
|
|
2408
|
-
max_tokens:
|
|
2715
|
+
max_tokens: maxTokens,
|
|
2409
2716
|
system: toAnthropicSystem(options.system, messages),
|
|
2410
2717
|
messages: toAnthropicMessages(messages),
|
|
2411
2718
|
...options.tools !== void 0 && options.tools.length > 0 ? { tools: toAnthropicTools(options.tools) } : {},
|
|
2719
|
+
...thinking === void 0 ? {} : { thinking },
|
|
2720
|
+
...effort,
|
|
2412
2721
|
stream: true,
|
|
2413
2722
|
...options.sessionId !== void 0 ? { metadata: { user_id: String(options.sessionId) } } : {}
|
|
2414
2723
|
};
|
|
@@ -3078,13 +3387,17 @@ function createXSearchTool(options) {
|
|
|
3078
3387
|
|
|
3079
3388
|
//#endregion
|
|
3080
3389
|
//#region src/tools/image-generate.ts
|
|
3081
|
-
/** Endpoint the generation request is posted to. */
|
|
3390
|
+
/** Endpoint the codex generation request is posted to. */
|
|
3082
3391
|
const IMAGE_GENERATE_URL = "https://chatgpt.com/backend-api/codex/images/generations";
|
|
3083
3392
|
/** The image model the codex subscription endpoint serves. */
|
|
3084
3393
|
const IMAGE_GENERATE_MODEL = "gpt-image-2";
|
|
3394
|
+
/** Endpoint the grok generation request is posted to. */
|
|
3395
|
+
const GROK_IMAGE_GENERATE_URL = "https://api.x.ai/v1/images/generations";
|
|
3396
|
+
/** The image model the grok subscription endpoint serves. */
|
|
3397
|
+
const GROK_IMAGE_GENERATE_MODEL = "grok-imagine-image-2.0";
|
|
3085
3398
|
/**
|
|
3086
|
-
* Assemble the request body from tool arguments (hand-checks the
|
|
3087
|
-
* prompt the schema DSL cannot express).
|
|
3399
|
+
* Assemble the codex request body from tool arguments (hand-checks the
|
|
3400
|
+
* non-empty prompt the schema DSL cannot express).
|
|
3088
3401
|
*/
|
|
3089
3402
|
function buildImageGenerateBody(args) {
|
|
3090
3403
|
const prompt = args.prompt.trim();
|
|
@@ -3096,6 +3409,30 @@ function buildImageGenerateBody(args) {
|
|
|
3096
3409
|
...args.quality === void 0 ? {} : { quality: args.quality }
|
|
3097
3410
|
};
|
|
3098
3411
|
}
|
|
3412
|
+
/** The codex `size` values mapped onto grok aspect ratios. */
|
|
3413
|
+
const GROK_ASPECT_RATIOS = {
|
|
3414
|
+
"1024x1024": "1:1",
|
|
3415
|
+
"1024x1536": "2:3",
|
|
3416
|
+
"1536x1024": "3:2",
|
|
3417
|
+
"auto": "auto"
|
|
3418
|
+
};
|
|
3419
|
+
/**
|
|
3420
|
+
* Assemble the grok request body from the same tool arguments: `size` maps
|
|
3421
|
+
* onto the nearest `aspect_ratio`, and `quality` folds into grok's low/medium
|
|
3422
|
+
* pair (`high` → `medium`, `auto` → provider default).
|
|
3423
|
+
*/
|
|
3424
|
+
function buildGrokImageGenerateBody(args) {
|
|
3425
|
+
const prompt = args.prompt.trim();
|
|
3426
|
+
if (prompt.length === 0) throw new Error("image_generate: prompt must be a non-empty string");
|
|
3427
|
+
const quality = args.quality === "low" ? "low" : args.quality === "medium" || args.quality === "high" ? "medium" : void 0;
|
|
3428
|
+
return {
|
|
3429
|
+
prompt,
|
|
3430
|
+
model: GROK_IMAGE_GENERATE_MODEL,
|
|
3431
|
+
response_format: "b64_json",
|
|
3432
|
+
...args.size === void 0 ? {} : { aspect_ratio: GROK_ASPECT_RATIOS[args.size] },
|
|
3433
|
+
...quality === void 0 ? {} : { quality }
|
|
3434
|
+
};
|
|
3435
|
+
}
|
|
3099
3436
|
/**
|
|
3100
3437
|
* Parse the generations response into decodable images. Throws when the
|
|
3101
3438
|
* payload carries no usable `b64_json` entries.
|
|
@@ -3116,13 +3453,29 @@ function parseImageGenerateResponse(payload) {
|
|
|
3116
3453
|
if (images.length === 0) throw new Error("image_generate: the response carried no image data");
|
|
3117
3454
|
return images;
|
|
3118
3455
|
}
|
|
3119
|
-
/** Directory the generated
|
|
3456
|
+
/** Directory the generated image files are written to. */
|
|
3120
3457
|
function imagesDirectory() {
|
|
3121
3458
|
return dshHomePath("plugins", "subscriptions", "images");
|
|
3122
3459
|
}
|
|
3460
|
+
/**
|
|
3461
|
+
* Sniff a generated image's media type from its magic bytes (codex serves
|
|
3462
|
+
* PNG; grok's format is undocumented, so trust the bytes). Unrecognized data
|
|
3463
|
+
* defaults to PNG, matching the historical behavior.
|
|
3464
|
+
*/
|
|
3465
|
+
function sniffImageMediaType(data) {
|
|
3466
|
+
if (data.length >= 3 && data[0] === 255 && data[1] === 216 && data[2] === 255) return "image/jpeg";
|
|
3467
|
+
if (data.length >= 12 && data.toString("latin1", 0, 4) === "RIFF" && data.toString("latin1", 8, 12) === "WEBP") return "image/webp";
|
|
3468
|
+
return "image/png";
|
|
3469
|
+
}
|
|
3470
|
+
/** File extension for one sniffed media type. */
|
|
3471
|
+
const MEDIA_TYPE_EXTENSIONS = {
|
|
3472
|
+
"image/png": "png",
|
|
3473
|
+
"image/jpeg": "jpg",
|
|
3474
|
+
"image/webp": "webp"
|
|
3475
|
+
};
|
|
3123
3476
|
/** Timestamped, collision-safe file name for one generated image. */
|
|
3124
|
-
function imageFileName(index) {
|
|
3125
|
-
return `image-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${Math.random().toString(36).slice(2, 8)}-${index}
|
|
3477
|
+
function imageFileName(index, mediaType) {
|
|
3478
|
+
return `image-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${Math.random().toString(36).slice(2, 8)}-${index}.${MEDIA_TYPE_EXTENSIONS[mediaType]}`;
|
|
3126
3479
|
}
|
|
3127
3480
|
/** Bound a call-card title's prompt. */
|
|
3128
3481
|
function truncate$1(text, max = 60) {
|
|
@@ -3180,7 +3533,7 @@ function imageGenerateText(value) {
|
|
|
3180
3533
|
function createImageGenerateTool(options) {
|
|
3181
3534
|
return defineTool({
|
|
3182
3535
|
name: "image_generate",
|
|
3183
|
-
description: "Generate an image with the ChatGPT subscription (gpt-image-2) and save it as
|
|
3536
|
+
description: "Generate an image with the ChatGPT subscription (gpt-image-2) or the Grok subscription (grok-imagine-image-2.0) and save it as an image file. The `provider` parameter picks the preferred provider (default gpt); when the preferred one is logged out the other serves as fallback. Returns the saved file paths; on image-capable models the image itself is attached.",
|
|
3184
3537
|
parameters: {
|
|
3185
3538
|
prompt: {
|
|
3186
3539
|
type: "string",
|
|
@@ -3206,6 +3559,11 @@ function createImageGenerateTool(options) {
|
|
|
3206
3559
|
"auto"
|
|
3207
3560
|
],
|
|
3208
3561
|
description: "Rendering quality; omit for the provider default."
|
|
3562
|
+
},
|
|
3563
|
+
provider: {
|
|
3564
|
+
type: "string",
|
|
3565
|
+
enum: ["gpt", "grok"],
|
|
3566
|
+
description: "Preferred provider (default gpt); the other one serves as fallback when the preferred is logged out."
|
|
3209
3567
|
}
|
|
3210
3568
|
},
|
|
3211
3569
|
output: {
|
|
@@ -3268,29 +3626,57 @@ function createImageGenerateTool(options) {
|
|
|
3268
3626
|
content: result.content.filter((block) => block.type === "text")
|
|
3269
3627
|
}),
|
|
3270
3628
|
async execute(args, exec) {
|
|
3271
|
-
const
|
|
3272
|
-
const
|
|
3273
|
-
const
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3629
|
+
const fetchFn = options.fetchFn ?? fetch;
|
|
3630
|
+
const preferGrok = args.provider === "grok";
|
|
3631
|
+
const codexReady = options.codexTokens !== void 0 && await options.codexTokens.hasSession();
|
|
3632
|
+
const grokReady = options.grokTokens !== void 0 && await options.grokTokens.hasSession();
|
|
3633
|
+
const useGrok = preferGrok ? grokReady : grokReady && !codexReady;
|
|
3634
|
+
const useCodex = !useGrok && codexReady;
|
|
3635
|
+
let response;
|
|
3636
|
+
if (useCodex && options.codexTokens !== void 0) {
|
|
3637
|
+
const session = await options.codexTokens.session();
|
|
3638
|
+
response = await fetchFn(IMAGE_GENERATE_URL, {
|
|
3639
|
+
method: "POST",
|
|
3640
|
+
headers: {
|
|
3641
|
+
"authorization": `Bearer ${session.accessToken}`,
|
|
3642
|
+
"chatgpt-account-id": session.accountId,
|
|
3643
|
+
"originator": "codex_cli_rs",
|
|
3644
|
+
"content-type": "application/json",
|
|
3645
|
+
"accept": "application/json"
|
|
3646
|
+
},
|
|
3647
|
+
body: JSON.stringify(buildImageGenerateBody(args)),
|
|
3648
|
+
signal: exec.signal
|
|
3649
|
+
});
|
|
3650
|
+
} else if (useGrok && options.grokTokens !== void 0) {
|
|
3651
|
+
const session = await options.grokTokens.session();
|
|
3652
|
+
response = await fetchFn(GROK_IMAGE_GENERATE_URL, {
|
|
3653
|
+
method: "POST",
|
|
3654
|
+
headers: {
|
|
3655
|
+
"authorization": `Bearer ${session.accessToken}`,
|
|
3656
|
+
"content-type": "application/json",
|
|
3657
|
+
"accept": "application/json"
|
|
3658
|
+
},
|
|
3659
|
+
body: JSON.stringify(buildGrokImageGenerateBody(args)),
|
|
3660
|
+
signal: exec.signal
|
|
3661
|
+
});
|
|
3662
|
+
} else {
|
|
3663
|
+
const manager = preferGrok ? options.grokTokens ?? options.codexTokens : options.codexTokens ?? options.grokTokens;
|
|
3664
|
+
if (manager === void 0) throw new Error("image_generate: no image provider is configured");
|
|
3665
|
+
await manager.session();
|
|
3666
|
+
throw new Error("image_generate: no image provider is logged in");
|
|
3667
|
+
}
|
|
3285
3668
|
if (!response.ok) throw await httpLlmError(response, "image_generate");
|
|
3286
3669
|
const images = parseImageGenerateResponse(await response.json());
|
|
3287
3670
|
const directory = options.imagesDir ?? imagesDirectory();
|
|
3288
3671
|
await mkdir(directory, { recursive: true });
|
|
3289
3672
|
const paths = [];
|
|
3673
|
+
const mediaTypes = [];
|
|
3290
3674
|
for (const [index, image] of images.entries()) {
|
|
3291
|
-
const
|
|
3675
|
+
const mediaType = sniffImageMediaType(image.data);
|
|
3676
|
+
const path = join(directory, imageFileName(index, mediaType));
|
|
3292
3677
|
await writeFile(path, image.data);
|
|
3293
3678
|
paths.push(path);
|
|
3679
|
+
mediaTypes.push(mediaType);
|
|
3294
3680
|
}
|
|
3295
3681
|
const attachments = options.resolveAttachments?.();
|
|
3296
3682
|
const imageCapable = attachments !== void 0 && await routeDeclaresImageInput(options.resolveLlm, exec);
|
|
@@ -3298,7 +3684,7 @@ function createImageGenerateTool(options) {
|
|
|
3298
3684
|
if (attachments !== void 0 && imageCapable) for (const [index, image] of images.entries()) {
|
|
3299
3685
|
const ref = await attachments.saveImage({
|
|
3300
3686
|
data: image.data,
|
|
3301
|
-
mediaType:
|
|
3687
|
+
mediaType: mediaTypes[index],
|
|
3302
3688
|
name: basename(paths[index])
|
|
3303
3689
|
});
|
|
3304
3690
|
refs.push({
|
|
@@ -3620,17 +4006,27 @@ const DEFAULT_MODELS = {
|
|
|
3620
4006
|
],
|
|
3621
4007
|
claude: [
|
|
3622
4008
|
{
|
|
3623
|
-
id: "claude-opus-
|
|
3624
|
-
name: "Claude Opus
|
|
3625
|
-
maxTokens:
|
|
4009
|
+
id: "claude-opus-5",
|
|
4010
|
+
name: "Claude Opus 5",
|
|
4011
|
+
maxTokens: 128e3,
|
|
4012
|
+
contextWindow: 1e6
|
|
3626
4013
|
},
|
|
3627
4014
|
{
|
|
3628
|
-
id: "claude-sonnet-
|
|
3629
|
-
name: "Claude Sonnet
|
|
4015
|
+
id: "claude-sonnet-5",
|
|
4016
|
+
name: "Claude Sonnet 5",
|
|
4017
|
+
maxTokens: 128e3,
|
|
4018
|
+
contextWindow: 1e6
|
|
3630
4019
|
},
|
|
3631
4020
|
{
|
|
3632
|
-
id: "claude-
|
|
3633
|
-
name: "Claude
|
|
4021
|
+
id: "claude-fable-5",
|
|
4022
|
+
name: "Claude Fable 5",
|
|
4023
|
+
maxTokens: 128e3,
|
|
4024
|
+
contextWindow: 1e6
|
|
4025
|
+
},
|
|
4026
|
+
{
|
|
4027
|
+
id: "claude-haiku-4-5-20251001",
|
|
4028
|
+
name: "Claude Haiku 4.5",
|
|
4029
|
+
maxTokens: 64e3
|
|
3634
4030
|
}
|
|
3635
4031
|
],
|
|
3636
4032
|
grok: [
|
|
@@ -3719,7 +4115,17 @@ var SubscriptionsAuthController = class {
|
|
|
3719
4115
|
};
|
|
3720
4116
|
}
|
|
3721
4117
|
async login(provider) {
|
|
3722
|
-
|
|
4118
|
+
if (provider === "claude") {
|
|
4119
|
+
const session = readClaudeCodeCredentials();
|
|
4120
|
+
if (session) {
|
|
4121
|
+
await this.persist("claude", session);
|
|
4122
|
+
this.lastError.delete("claude");
|
|
4123
|
+
this.onAuthChanged("claude");
|
|
4124
|
+
return { authorizeUrl: "" };
|
|
4125
|
+
}
|
|
4126
|
+
throw new Error("Claude Code credentials not found. Run \"claude\" first to log in.");
|
|
4127
|
+
}
|
|
4128
|
+
const spec = provider === "grok" ? await grokFlow() : codexFlow;
|
|
3723
4129
|
const attempt = await this.flows.start(provider, spec);
|
|
3724
4130
|
this.complete(provider, attempt);
|
|
3725
4131
|
return { authorizeUrl: attempt.authorizeUrl };
|
|
@@ -3783,6 +4189,7 @@ function apply(ctx, config) {
|
|
|
3783
4189
|
handles.get(provider)?.replace([provider]);
|
|
3784
4190
|
};
|
|
3785
4191
|
let codexTokens;
|
|
4192
|
+
let claudeTokens;
|
|
3786
4193
|
let grokTokens;
|
|
3787
4194
|
const usageFetchers = {};
|
|
3788
4195
|
for (const provider of providers) switch (provider) {
|
|
@@ -3819,18 +4226,23 @@ function apply(ctx, config) {
|
|
|
3819
4226
|
load: () => getSession("claude"),
|
|
3820
4227
|
save: (session) => saveSession("claude", session),
|
|
3821
4228
|
remove: () => deleteSession("claude"),
|
|
3822
|
-
refresh: refreshClaude,
|
|
4229
|
+
refresh: (session) => refreshClaudeSynced(session, refreshClaude),
|
|
3823
4230
|
isPermanent: isClaudePermanentRefreshError,
|
|
3824
4231
|
onRemoved: () => {
|
|
3825
4232
|
authChanged("claude");
|
|
3826
4233
|
}
|
|
3827
4234
|
});
|
|
4235
|
+
claudeTokens = tokens;
|
|
3828
4236
|
usageFetchers.claude = async (signal) => fetchClaudeUsage(await tokens.session(), fetch, signal);
|
|
3829
4237
|
handles.set("claude", ctx.llm.registerAdapter(["claude"], new ClaudeAdapter({
|
|
3830
4238
|
models: catalog.claude,
|
|
3831
4239
|
streamIdleTimeoutMs,
|
|
3832
4240
|
tokens,
|
|
3833
|
-
|
|
4241
|
+
discovery: !overridden.has("claude"),
|
|
4242
|
+
onWarn,
|
|
4243
|
+
maxRetries: 10,
|
|
4244
|
+
resolveAttachments,
|
|
4245
|
+
catalogStore: catalogStore("claude")
|
|
3834
4246
|
})));
|
|
3835
4247
|
break;
|
|
3836
4248
|
}
|
|
@@ -3862,13 +4274,22 @@ function apply(ctx, config) {
|
|
|
3862
4274
|
}
|
|
3863
4275
|
}
|
|
3864
4276
|
registerAuthRpc(ctx, new SubscriptionsAuthController(flows, authChanged, resolveAttachments, usageFetchers));
|
|
4277
|
+
if (claudeTokens !== void 0) {
|
|
4278
|
+
const syncTimer = setInterval(() => {
|
|
4279
|
+
claudeTokens?.session().catch(() => {});
|
|
4280
|
+
}, 5 * 6e4);
|
|
4281
|
+
ctx.effect(() => () => {
|
|
4282
|
+
clearInterval(syncTimer);
|
|
4283
|
+
}, "dsh-plugin-subscriptions: claude background sync timer");
|
|
4284
|
+
}
|
|
3865
4285
|
ctx.inject(["tools"], (toolsCtx) => {
|
|
3866
4286
|
if (grokTokens !== void 0) {
|
|
3867
4287
|
toolsCtx.tools.register(createXSearchTool({ tokens: grokTokens }));
|
|
3868
4288
|
toolsCtx.tools.register(createVideoGenerateTool({ tokens: grokTokens }));
|
|
3869
4289
|
}
|
|
3870
|
-
if (codexTokens !== void 0) toolsCtx.tools.register(createImageGenerateTool({
|
|
3871
|
-
|
|
4290
|
+
if (codexTokens !== void 0 || grokTokens !== void 0) toolsCtx.tools.register(createImageGenerateTool({
|
|
4291
|
+
...codexTokens === void 0 ? {} : { codexTokens },
|
|
4292
|
+
...grokTokens === void 0 ? {} : { grokTokens },
|
|
3872
4293
|
resolveAttachments,
|
|
3873
4294
|
resolveLlm: () => ctx.get("llm")
|
|
3874
4295
|
}));
|