dsh-subscriptions 0.2.0 → 0.2.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 +212 -180
- package/lib/auth/claude-code-creds.js +31 -9
- package/lib/auth/device-flow.js +16 -9
- package/lib/auth/oauth-flow.js +13 -10
- package/lib/auth/rpc.js +74 -23
- package/lib/auth/store.js +80 -31
- package/lib/client/CursorCard.d.ts +1 -1
- package/lib/client/CursorCard.js +50 -16
- package/lib/client/ExternalUsageCards.d.ts +1 -1
- package/lib/client/ExternalUsageCards.js +37 -16
- package/lib/client/ImageGallery.d.ts +2 -2
- package/lib/client/ImageGallery.js +92 -33
- package/lib/client/ImageGenerateToolview.js +50 -14
- package/lib/client/OllamaCard.d.ts +1 -1
- package/lib/client/OllamaCard.js +26 -8
- package/lib/client/OllamaSettingsManager.js +113 -30
- package/lib/client/ProviderAccountManager.d.ts +1 -1
- package/lib/client/ProviderAccountManager.js +164 -49
- package/lib/client/ProviderModelEditor.js +107 -26
- package/lib/client/SpeedSelect.d.ts +62 -14
- package/lib/client/SpeedSelect.js +257 -46
- package/lib/client/SubscriptionUsageBadge.d.ts +4 -10
- package/lib/client/SubscriptionUsageBadge.js +300 -112
- package/lib/client/SubscriptionsSection.js +258 -85
- package/lib/client/UsageBadgeDisplaySetting.js +35 -12
- package/lib/client/UsageMeter.d.ts +1 -1
- package/lib/client/UsageMeter.js +62 -24
- package/lib/client/VideoGenerateToolview.js +69 -17
- package/lib/client/account-preferences.d.ts +8 -0
- package/lib/client/account-preferences.js +20 -5
- package/lib/client/host-contract.d.ts +11 -2
- package/lib/client/host-contract.js +20 -3
- package/lib/client/index.d.ts +5 -5
- package/lib/client/index.js +34 -12
- package/lib/client/locales.d.ts +10 -0
- package/lib/client/locales.js +61 -51
- package/lib/client/ollama-draft.js +21 -12
- package/lib/client/reset-action.js +6 -4
- package/lib/client/reset-credits-view.d.ts +1 -1
- package/lib/client/reset-credits-view.js +66 -17
- package/lib/client/subscription-card-styles.js +94 -24
- package/lib/client/usage-color-preferences.js +4 -2
- package/lib/client/usage-pace.js +30 -12
- package/lib/client.js +287 -47
- package/lib/client.js.map +1 -1
- package/lib/http.js +1 -1
- package/lib/index.d.ts +3 -3
- package/lib/index.js +118 -64
- package/lib/model-defaults.js +4 -2
- package/lib/provider-settings.d.ts +12 -0
- package/lib/provider-settings.js +58 -16
- package/lib/providers/account-preferences.js +51 -22
- package/lib/providers/accounts.js +15 -9
- package/lib/providers/antigravity-oauth-client.js +2 -2
- package/lib/providers/antigravity.js +141 -68
- package/lib/providers/catalog-store.js +120 -41
- package/lib/providers/claude-cli-version.js +3 -3
- package/lib/providers/claude.d.ts +9 -2
- package/lib/providers/claude.js +153 -90
- package/lib/providers/codex-client-version.js +3 -3
- package/lib/providers/codex-search.js +40 -13
- package/lib/providers/codex.js +196 -112
- package/lib/providers/common.js +71 -39
- package/lib/providers/copilot.js +143 -82
- package/lib/providers/cursor-adapter.js +42 -18
- package/lib/providers/cursor-auth.d.ts +1 -1
- package/lib/providers/cursor-auth.js +69 -24
- package/lib/providers/cursor-model-settings.js +14 -5
- package/lib/providers/cursor-usage.js +27 -13
- package/lib/providers/external-usage-controller.js +14 -5
- package/lib/providers/external-usage.js +59 -23
- package/lib/providers/grok.js +152 -89
- package/lib/providers/image-pool.js +29 -11
- package/lib/providers/minimax-usage.js +31 -11
- package/lib/providers/npm-cli-version.js +10 -3
- package/lib/providers/ollama-settings.js +24 -8
- package/lib/providers/ollama-web.js +29 -11
- package/lib/providers/ollama.js +129 -66
- package/lib/providers/pool-family.js +11 -3
- package/lib/providers/pool-health.js +28 -8
- package/lib/providers/pool-usage.js +16 -9
- package/lib/providers/pool.d.ts +8 -0
- package/lib/providers/pool.js +81 -42
- package/lib/providers/rate-limit.js +8 -2
- package/lib/providers/replay.js +40 -17
- package/lib/providers/reset-redemption.js +18 -6
- package/lib/tools/image-generate.js +123 -65
- package/lib/tools/registration.js +5 -2
- package/lib/tools/video-generate.js +51 -31
- package/lib/tools/x-search.js +50 -22
- package/lib/translate/anthropic.js +85 -37
- package/lib/translate/antigravity-schema.js +71 -28
- package/lib/translate/antigravity-thinking.js +22 -9
- package/lib/translate/antigravity.js +141 -50
- package/lib/translate/chat-completions.js +67 -23
- package/lib/translate/resolved.js +55 -21
- package/lib/translate/responses.js +69 -29
- package/lib/translate/sse.js +4 -1
- package/package.json +26 -4
package/lib/providers/claude.js
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* the Anthropic Messages API with the Claude Code identity headers.
|
|
5
5
|
*/
|
|
6
6
|
import { execFileSync } from 'node:child_process';
|
|
7
|
-
import { EMPTY_RESPONSE_CODE, errorChain, LlmAdapter, LlmError, ReasoningEffortId } from '@deepseek-ai/dsh-llm';
|
|
8
|
-
import { hostSupportsImageOffload, IMAGE_OFFLOAD_REQUIRED, requiredImageOffloadCount, resolveImages } from '../translate/resolved.js';
|
|
7
|
+
import { EMPTY_RESPONSE_CODE, errorChain, LlmAdapter, LlmError, ReasoningEffortId, } from '@deepseek-ai/dsh-llm';
|
|
8
|
+
import { hostSupportsImageOffload, IMAGE_OFFLOAD_REQUIRED, requiredImageOffloadCount, resolveImages, } from '../translate/resolved.js';
|
|
9
9
|
import { markMessageCache, streamAnthropic, toAnthropicMessages, toAnthropicSystem, toAnthropicTools, } from '../translate/anthropic.js';
|
|
10
10
|
import { httpLlmError, idleWatchdog, mapFetchFailure, mergeReasoning, ModelCatalogCache, discoverAcrossAccounts, discoverOrRetryAuth, isDiscoveryAborted, isMissingOrInvalidCredential, parseProviderJson, oauthEndpointError, OAuthEndpointError, } from './common.js';
|
|
11
|
-
import { AccountTokenManager, DISCOVERY_TIMEOUT_MS, unionAccountCatalogs } from './accounts.js';
|
|
11
|
+
import { AccountTokenManager, DISCOVERY_TIMEOUT_MS, unionAccountCatalogs, } from './accounts.js';
|
|
12
12
|
import { hostFetch } from '../http.js';
|
|
13
13
|
import { compareVersions } from './npm-cli-version.js';
|
|
14
14
|
import { DEFAULT_RATE_LIMIT_WAIT, DEFAULT_RETRY, earliestReset, jsonBody, resetFromFields, resetInstantFromHeader, subscriptionRetryPolicy, } from './rate-limit.js';
|
|
@@ -29,7 +29,12 @@ export const CLAUDE_PREEMPT_MS = 5 * 60_000;
|
|
|
29
29
|
* Body fields Anthropic uses to name a reset instant, read when the unified
|
|
30
30
|
* headers are absent.
|
|
31
31
|
*/
|
|
32
|
-
const CLAUDE_RESET_FIELDS = [
|
|
32
|
+
const CLAUDE_RESET_FIELDS = [
|
|
33
|
+
'resets_at',
|
|
34
|
+
'resetsAt',
|
|
35
|
+
'reset_at',
|
|
36
|
+
'retry_after',
|
|
37
|
+
];
|
|
33
38
|
/**
|
|
34
39
|
* Reads the reset instant of the Anthropic window that rejected a request.
|
|
35
40
|
*
|
|
@@ -153,15 +158,24 @@ async function fetchClaudeProfile(accessToken) {
|
|
|
153
158
|
});
|
|
154
159
|
if (!response.ok)
|
|
155
160
|
return {};
|
|
156
|
-
const profile = await response.json();
|
|
161
|
+
const profile = (await response.json());
|
|
157
162
|
const account = typeof profile.account === 'object' && profile.account !== null
|
|
158
163
|
? profile.account
|
|
159
164
|
: {};
|
|
160
|
-
const email = profile.emailAddress ??
|
|
161
|
-
|
|
165
|
+
const email = profile.emailAddress ??
|
|
166
|
+
profile.email ??
|
|
167
|
+
account.email_address ??
|
|
168
|
+
account.email;
|
|
169
|
+
const subscription = profile.subscriptionType ??
|
|
170
|
+
profile.subscription_type ??
|
|
171
|
+
account.subscription_type;
|
|
162
172
|
return {
|
|
163
|
-
...typeof email === 'string' && email.length > 0
|
|
164
|
-
|
|
173
|
+
...(typeof email === 'string' && email.length > 0
|
|
174
|
+
? { emailAddress: email }
|
|
175
|
+
: {}),
|
|
176
|
+
...(typeof subscription === 'string' && subscription.length > 0
|
|
177
|
+
? { subscriptionType: subscription }
|
|
178
|
+
: {}),
|
|
165
179
|
};
|
|
166
180
|
}
|
|
167
181
|
catch {
|
|
@@ -171,7 +185,8 @@ async function fetchClaudeProfile(accessToken) {
|
|
|
171
185
|
}
|
|
172
186
|
/** Build a session from a token response. */
|
|
173
187
|
async function claudeSession(tokens, fallbackRefreshToken, withProfile) {
|
|
174
|
-
if (typeof tokens.access_token !== 'string' ||
|
|
188
|
+
if (typeof tokens.access_token !== 'string' ||
|
|
189
|
+
tokens.access_token.length === 0) {
|
|
175
190
|
throw new Error('claude token endpoint returned no access token');
|
|
176
191
|
}
|
|
177
192
|
const refreshToken = tokens.refresh_token ?? fallbackRefreshToken;
|
|
@@ -180,7 +195,9 @@ async function claudeSession(tokens, fallbackRefreshToken, withProfile) {
|
|
|
180
195
|
if (typeof tokens.expires_in !== 'number' || tokens.expires_in <= 0) {
|
|
181
196
|
throw new Error('claude token endpoint returned no usable expiry');
|
|
182
197
|
}
|
|
183
|
-
const profile = withProfile
|
|
198
|
+
const profile = withProfile
|
|
199
|
+
? await fetchClaudeProfile(tokens.access_token)
|
|
200
|
+
: {};
|
|
184
201
|
return {
|
|
185
202
|
accessToken: tokens.access_token,
|
|
186
203
|
refreshToken,
|
|
@@ -212,7 +229,7 @@ export async function exchangeClaudeCode(code, verifier, redirectUri, state) {
|
|
|
212
229
|
});
|
|
213
230
|
if (!response.ok)
|
|
214
231
|
throw await oauthEndpointError(response, 'claude');
|
|
215
|
-
return claudeSession(await parseProviderJson(response, 'claude token exchange'), undefined, true);
|
|
232
|
+
return claudeSession((await parseProviderJson(response, 'claude token exchange')), undefined, true);
|
|
216
233
|
}
|
|
217
234
|
/**
|
|
218
235
|
* Refresh a claude session (JSON grant echoing the issued scope).
|
|
@@ -232,11 +249,15 @@ export async function refreshClaude(session) {
|
|
|
232
249
|
});
|
|
233
250
|
if (!response.ok)
|
|
234
251
|
throw await oauthEndpointError(response, 'claude');
|
|
235
|
-
const next = await claudeSession(await parseProviderJson(response, 'claude token refresh'), session.refreshToken, false);
|
|
252
|
+
const next = await claudeSession((await parseProviderJson(response, 'claude token refresh')), session.refreshToken, false);
|
|
236
253
|
return {
|
|
237
254
|
...next,
|
|
238
|
-
...session.emailAddress === undefined
|
|
239
|
-
|
|
255
|
+
...(session.emailAddress === undefined
|
|
256
|
+
? {}
|
|
257
|
+
: { emailAddress: session.emailAddress }),
|
|
258
|
+
...(session.subscriptionType === undefined
|
|
259
|
+
? {}
|
|
260
|
+
: { subscriptionType: session.subscriptionType }),
|
|
240
261
|
};
|
|
241
262
|
}
|
|
242
263
|
/**
|
|
@@ -245,8 +266,8 @@ export async function refreshClaude(session) {
|
|
|
245
266
|
* @returns true when re-login is the only fix.
|
|
246
267
|
*/
|
|
247
268
|
export function isClaudePermanentRefreshError(error) {
|
|
248
|
-
return error instanceof OAuthEndpointError
|
|
249
|
-
|
|
269
|
+
return (error instanceof OAuthEndpointError &&
|
|
270
|
+
(error.oauthCode === 'invalid_grant' || error.oauthCode === 'invalid_token'));
|
|
250
271
|
}
|
|
251
272
|
export const CLAUDE_USAGE_URL = 'https://api.anthropic.com/api/oauth/usage';
|
|
252
273
|
/** ISO-8601 or numeric epoch (seconds below 1e10 in magnitude, otherwise ms). */
|
|
@@ -261,7 +282,9 @@ function claudeResetsAt(value) {
|
|
|
261
282
|
else {
|
|
262
283
|
return undefined;
|
|
263
284
|
}
|
|
264
|
-
return Number.isFinite(parsed) && Number.isFinite(new Date(parsed).getTime())
|
|
285
|
+
return Number.isFinite(parsed) && Number.isFinite(new Date(parsed).getTime())
|
|
286
|
+
? parsed
|
|
287
|
+
: undefined;
|
|
265
288
|
}
|
|
266
289
|
/**
|
|
267
290
|
* Read the observed cedar_ember shape from ItsJazii/pane, not an official
|
|
@@ -285,20 +308,28 @@ function claudeResetCredits(value, now) {
|
|
|
285
308
|
if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
|
|
286
309
|
continue;
|
|
287
310
|
const grant = raw;
|
|
288
|
-
const { id, resets_total: total, resets_left: left, usable_now: usable, paused } = grant;
|
|
311
|
+
const { id, resets_total: total, resets_left: left, usable_now: usable, paused, } = grant;
|
|
289
312
|
const expiresAt = claudeResetsAt(grant.ends_at);
|
|
290
|
-
if (typeof id !== 'string' ||
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
||
|
|
294
|
-
|
|
295
|
-
|
|
313
|
+
if (typeof id !== 'string' ||
|
|
314
|
+
id.trim().length === 0 ||
|
|
315
|
+
typeof total !== 'number' ||
|
|
316
|
+
!Number.isSafeInteger(total) ||
|
|
317
|
+
total < 1 ||
|
|
318
|
+
typeof left !== 'number' ||
|
|
319
|
+
!Number.isSafeInteger(left) ||
|
|
320
|
+
expiresAt === undefined ||
|
|
321
|
+
(usable !== undefined && typeof usable !== 'boolean') ||
|
|
322
|
+
(paused !== undefined && typeof paused !== 'boolean'))
|
|
296
323
|
continue;
|
|
297
324
|
const resetsLeft = Math.max(0, Math.min(left, total));
|
|
298
325
|
if (resetsLeft === 0)
|
|
299
326
|
continue;
|
|
300
|
-
const claimable = !selected &&
|
|
301
|
-
|
|
327
|
+
const claimable = !selected &&
|
|
328
|
+
id === ember.next_grant_id &&
|
|
329
|
+
usable === true &&
|
|
330
|
+
paused !== true &&
|
|
331
|
+
!cooling &&
|
|
332
|
+
expiresAt > now;
|
|
302
333
|
if (claimable)
|
|
303
334
|
selected = true;
|
|
304
335
|
credits.push({
|
|
@@ -306,33 +337,37 @@ function claudeResetCredits(value, now) {
|
|
|
306
337
|
expiresAt,
|
|
307
338
|
resetsTotal: total,
|
|
308
339
|
resetsLeft,
|
|
309
|
-
...usable === undefined ? {} : { usableNow: usable },
|
|
310
|
-
...paused === undefined ? {} : { paused },
|
|
340
|
+
...(usable === undefined ? {} : { usableNow: usable }),
|
|
341
|
+
...(paused === undefined ? {} : { paused }),
|
|
311
342
|
claimable,
|
|
312
|
-
...cooldownUntil === undefined ? {} : { cooldownUntil },
|
|
343
|
+
...(cooldownUntil === undefined ? {} : { cooldownUntil }),
|
|
313
344
|
});
|
|
314
345
|
}
|
|
315
346
|
return credits.length > 0 ? credits : undefined;
|
|
316
347
|
}
|
|
317
348
|
/** Claude session and weekly buckets reset as whole five-hour/seven-day windows. */
|
|
318
349
|
function claudeWindowTiming(kind) {
|
|
319
|
-
return kind === 'session'
|
|
320
|
-
|
|
350
|
+
return kind === 'session'
|
|
351
|
+
? { windowDurationMs: 5 * 60 * 60_000, fixedWindow: true }
|
|
352
|
+
: kind === 'weekly'
|
|
353
|
+
? { windowDurationMs: 7 * 24 * 60 * 60_000, fixedWindow: true }
|
|
354
|
+
: {};
|
|
321
355
|
}
|
|
322
356
|
/** Map one legacy `{utilization, resets_at}` bucket; undefined when null or unusable. */
|
|
323
357
|
function claudeLegacyWindow(value, kind, scope) {
|
|
324
358
|
if (typeof value !== 'object' || value === null)
|
|
325
359
|
return undefined;
|
|
326
360
|
const bucket = value;
|
|
327
|
-
if (typeof bucket.utilization !== 'number' ||
|
|
361
|
+
if (typeof bucket.utilization !== 'number' ||
|
|
362
|
+
!Number.isFinite(bucket.utilization))
|
|
328
363
|
return undefined;
|
|
329
364
|
const resetsAt = claudeResetsAt(bucket.resets_at);
|
|
330
365
|
return {
|
|
331
366
|
kind,
|
|
332
|
-
...scope === undefined ? {} : { scope },
|
|
367
|
+
...(scope === undefined ? {} : { scope }),
|
|
333
368
|
...claudeWindowTiming(kind),
|
|
334
369
|
usedPercent: bucket.utilization,
|
|
335
|
-
...resetsAt === undefined ? {} : { resetsAt },
|
|
370
|
+
...(resetsAt === undefined ? {} : { resetsAt }),
|
|
336
371
|
};
|
|
337
372
|
}
|
|
338
373
|
/** Map the modern `limits` array; empty when absent or carrying nothing usable. */
|
|
@@ -348,15 +383,17 @@ function claudeLimitsWindows(value) {
|
|
|
348
383
|
continue;
|
|
349
384
|
const kind = entry.kind === 'session'
|
|
350
385
|
? 'session'
|
|
351
|
-
: entry.kind === 'weekly_all' || entry.kind === 'weekly_scoped'
|
|
386
|
+
: entry.kind === 'weekly_all' || entry.kind === 'weekly_scoped'
|
|
387
|
+
? 'weekly'
|
|
388
|
+
: 'other';
|
|
352
389
|
const scope = entry.scope?.model?.display_name;
|
|
353
390
|
const resetsAt = claudeResetsAt(entry.resets_at);
|
|
354
391
|
windows.push({
|
|
355
392
|
kind,
|
|
356
|
-
...typeof scope === 'string' && scope.length > 0 ? { scope } : {},
|
|
393
|
+
...(typeof scope === 'string' && scope.length > 0 ? { scope } : {}),
|
|
357
394
|
...claudeWindowTiming(kind),
|
|
358
395
|
usedPercent: entry.percent,
|
|
359
|
-
...resetsAt === undefined ? {} : { resetsAt },
|
|
396
|
+
...(resetsAt === undefined ? {} : { resetsAt }),
|
|
360
397
|
});
|
|
361
398
|
}
|
|
362
399
|
return windows;
|
|
@@ -380,14 +417,14 @@ export async function fetchClaudeUsage(session, fetchFn = hostFetch, signal, cli
|
|
|
380
417
|
const userAgent = claudeCliUserAgent(await cliVersion());
|
|
381
418
|
const init = {
|
|
382
419
|
headers: {
|
|
383
|
-
|
|
420
|
+
authorization: `Bearer ${session.accessToken}`,
|
|
384
421
|
'anthropic-beta': 'oauth-2025-04-20',
|
|
385
422
|
// Unrecognized clients are aggressively rate-limited on this endpoint,
|
|
386
423
|
// so it presents as the CLI like every other subscription request.
|
|
387
424
|
'user-agent': userAgent,
|
|
388
|
-
|
|
425
|
+
accept: 'application/json',
|
|
389
426
|
},
|
|
390
|
-
...signal === undefined ? {} : { signal },
|
|
427
|
+
...(signal === undefined ? {} : { signal }),
|
|
391
428
|
};
|
|
392
429
|
let response = await fetchFn(`${CLAUDE_USAGE_URL}?cedar_ember=1`, init);
|
|
393
430
|
let resetCreditsError;
|
|
@@ -398,11 +435,13 @@ export async function fetchClaudeUsage(session, fetchFn = hostFetch, signal, cli
|
|
|
398
435
|
}
|
|
399
436
|
if (!response.ok)
|
|
400
437
|
throw await oauthEndpointError(response, 'claude usage');
|
|
401
|
-
const payload = await parseProviderJson(response, 'claude usage');
|
|
402
|
-
const resetCredits = resetCreditsError === undefined
|
|
438
|
+
const payload = (await parseProviderJson(response, 'claude usage'));
|
|
439
|
+
const resetCredits = resetCreditsError === undefined
|
|
440
|
+
? claudeResetCredits(payload.cedar_ember, Date.now())
|
|
441
|
+
: undefined;
|
|
403
442
|
const resets = {
|
|
404
|
-
...resetCredits === undefined ? {} : { resetCredits },
|
|
405
|
-
...resetCreditsError === undefined ? {} : { resetCreditsError },
|
|
443
|
+
...(resetCredits === undefined ? {} : { resetCredits }),
|
|
444
|
+
...(resetCreditsError === undefined ? {} : { resetCreditsError }),
|
|
406
445
|
};
|
|
407
446
|
const modern = claudeLimitsWindows(payload.limits);
|
|
408
447
|
if (modern.length > 0)
|
|
@@ -434,14 +473,17 @@ function claudeReasoning(capabilities) {
|
|
|
434
473
|
const effort = capabilities?.effort;
|
|
435
474
|
if (effort?.supported !== true)
|
|
436
475
|
return undefined;
|
|
437
|
-
const efforts = CLAUDE_EFFORT_LEVELS
|
|
438
|
-
|
|
439
|
-
|
|
476
|
+
const efforts = CLAUDE_EFFORT_LEVELS.filter((level) => effort[level]?.supported === true).map((level) => ({
|
|
477
|
+
id: ReasoningEffortId(level),
|
|
478
|
+
name: level[0].toUpperCase() + level.slice(1),
|
|
479
|
+
}));
|
|
440
480
|
return efforts.length > 0 ? { efforts } : undefined;
|
|
441
481
|
}
|
|
442
482
|
/** A token limit the endpoint disclosed, or undefined when absent or malformed. */
|
|
443
483
|
function positiveTokenCount(value) {
|
|
444
|
-
return typeof value === 'number' && Number.isSafeInteger(value) && value > 0
|
|
484
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value > 0
|
|
485
|
+
? value
|
|
486
|
+
: undefined;
|
|
445
487
|
}
|
|
446
488
|
/**
|
|
447
489
|
* Fetch the live model catalog from the subscription endpoint. `signal`
|
|
@@ -452,17 +494,17 @@ export async function fetchClaudeModels(session, fetchFn = hostFetch, signal, cl
|
|
|
452
494
|
const userAgent = claudeCliUserAgent(await cliVersion());
|
|
453
495
|
const response = await fetchFn(CLAUDE_MODELS_URL, {
|
|
454
496
|
headers: {
|
|
455
|
-
|
|
497
|
+
authorization: `Bearer ${session.accessToken}`,
|
|
456
498
|
'anthropic-version': '2023-06-01',
|
|
457
499
|
'user-agent': userAgent,
|
|
458
500
|
'anthropic-dangerous-direct-browser-access': 'true',
|
|
459
|
-
|
|
501
|
+
accept: 'application/json',
|
|
460
502
|
},
|
|
461
|
-
...signal === undefined ? {} : { signal },
|
|
503
|
+
...(signal === undefined ? {} : { signal }),
|
|
462
504
|
});
|
|
463
505
|
if (!response.ok)
|
|
464
506
|
throw await httpLlmError(response, 'claude models API');
|
|
465
|
-
const payload = await parseProviderJson(response, 'claude models API');
|
|
507
|
+
const payload = (await parseProviderJson(response, 'claude models API'));
|
|
466
508
|
if (!Array.isArray(payload.data)) {
|
|
467
509
|
throw new Error('claude models API returned an invalid catalog');
|
|
468
510
|
}
|
|
@@ -478,10 +520,10 @@ export async function fetchClaudeModels(session, fetchFn = hostFetch, signal, cl
|
|
|
478
520
|
return {
|
|
479
521
|
id: m.id,
|
|
480
522
|
name: m.display_name ?? m.id,
|
|
481
|
-
...contextWindow === undefined ? {} : { contextWindow },
|
|
482
|
-
...maxOutputTokens === undefined ? {} : { maxOutputTokens },
|
|
483
|
-
...thinkingType === undefined ? {} : { thinkingType },
|
|
484
|
-
...reasoning === undefined ? {} : { reasoning },
|
|
523
|
+
...(contextWindow === undefined ? {} : { contextWindow }),
|
|
524
|
+
...(maxOutputTokens === undefined ? {} : { maxOutputTokens }),
|
|
525
|
+
...(thinkingType === undefined ? {} : { thinkingType }),
|
|
526
|
+
...(reasoning === undefined ? {} : { reasoning }),
|
|
485
527
|
};
|
|
486
528
|
});
|
|
487
529
|
if (models.length === 0) {
|
|
@@ -496,7 +538,9 @@ export async function fetchClaudeModels(session, fetchFn = hostFetch, signal, cl
|
|
|
496
538
|
function claudeMaxTokens(configured, disc) {
|
|
497
539
|
const outputLimit = disc?.maxOutputTokens;
|
|
498
540
|
const preferred = configured?.maxTokens ?? outputLimit ?? CLAUDE_DEFAULT_MAX_TOKENS;
|
|
499
|
-
return outputLimit === undefined
|
|
541
|
+
return outputLimit === undefined
|
|
542
|
+
? preferred
|
|
543
|
+
: Math.min(preferred, outputLimit);
|
|
500
544
|
}
|
|
501
545
|
/**
|
|
502
546
|
* Anthropic rejects any image edge over 2000px once a request carries many
|
|
@@ -530,8 +574,9 @@ const CLAUDE_MODALITIES = ['text', 'image'];
|
|
|
530
574
|
* @param effort - the reasoning effort, when the model advertises efforts.
|
|
531
575
|
* @param cacheTtl - lifetime of every cache breakpoint; defaults to five minutes.
|
|
532
576
|
* Requests that carry a `purpose` (compaction, session titles) always use five
|
|
533
|
-
* minutes to preserve their existing auxiliary-request behavior
|
|
534
|
-
*
|
|
577
|
+
* minutes to preserve their existing auxiliary-request behavior — the override
|
|
578
|
+
* changes the TTL for ordinary turns only, never the auxiliary floor. A
|
|
579
|
+
* five-minute mark can still read an entry a one-hour mark wrote.
|
|
535
580
|
* @returns the JSON body to POST.
|
|
536
581
|
*/
|
|
537
582
|
export function claudeRequestBody(options, messages, maxTokens, thinking, effort, cacheTtl) {
|
|
@@ -546,13 +591,15 @@ export function claudeRequestBody(options, messages, maxTokens, thinking, effort
|
|
|
546
591
|
max_tokens: maxTokens,
|
|
547
592
|
system: toAnthropicSystem(options.system, messages, ttl),
|
|
548
593
|
messages: anthropicMessages,
|
|
549
|
-
...options.tools !== undefined && options.tools.length > 0
|
|
594
|
+
...(options.tools !== undefined && options.tools.length > 0
|
|
550
595
|
? { tools: toAnthropicTools(options.tools) }
|
|
551
|
-
: {},
|
|
552
|
-
...thinking === undefined ? {} : { thinking },
|
|
553
|
-
...effort === undefined ? {} : { output_config: { effort } },
|
|
596
|
+
: {}),
|
|
597
|
+
...(thinking === undefined ? {} : { thinking }),
|
|
598
|
+
...(effort === undefined ? {} : { output_config: { effort } }),
|
|
554
599
|
stream: true,
|
|
555
|
-
...options.sessionId !== undefined
|
|
600
|
+
...(options.sessionId !== undefined
|
|
601
|
+
? { metadata: { user_id: String(options.sessionId) } }
|
|
602
|
+
: {}),
|
|
556
603
|
};
|
|
557
604
|
}
|
|
558
605
|
/** Claude wire adapter: one instance serves the `claude` provider route. */
|
|
@@ -577,7 +624,9 @@ export class ClaudeAdapter extends LlmAdapter {
|
|
|
577
624
|
this.accountCatalogs.clear();
|
|
578
625
|
else
|
|
579
626
|
this.accountCatalogs.delete(account);
|
|
580
|
-
if (account === undefined ||
|
|
627
|
+
if (account === undefined ||
|
|
628
|
+
this.catalogOwner === account ||
|
|
629
|
+
this.catalogOwner === undefined) {
|
|
581
630
|
this.catalogOwner = undefined;
|
|
582
631
|
this.catalog.invalidate();
|
|
583
632
|
}
|
|
@@ -606,16 +655,16 @@ export class ClaudeAdapter extends LlmAdapter {
|
|
|
606
655
|
// A named account is looked up alone: its window must not be borrowed
|
|
607
656
|
// from another account's catalog.
|
|
608
657
|
const accounts = account === undefined
|
|
609
|
-
? (await this.options.tokens.list()).map(entry => entry.key)
|
|
658
|
+
? (await this.options.tokens.list()).map((entry) => entry.key)
|
|
610
659
|
: [account];
|
|
611
660
|
return discoverAcrossAccounts(accounts, async (account) => {
|
|
612
661
|
const catalog = await this.catalogFor(account);
|
|
613
662
|
const models = await catalog.resolve(() => this.fetchCatalog(account));
|
|
614
|
-
return models?.find(entry => entry.id === model);
|
|
663
|
+
return models?.find((entry) => entry.id === model);
|
|
615
664
|
});
|
|
616
665
|
}
|
|
617
666
|
staticModels(provider) {
|
|
618
|
-
return this.options.models.map(model => ({
|
|
667
|
+
return this.options.models.map((model) => ({
|
|
619
668
|
provider,
|
|
620
669
|
id: model.id,
|
|
621
670
|
name: model.name ?? model.id,
|
|
@@ -634,27 +683,30 @@ export class ClaudeAdapter extends LlmAdapter {
|
|
|
634
683
|
if (pool === undefined)
|
|
635
684
|
return own;
|
|
636
685
|
const extra = await pool.modelsForProvider(provider);
|
|
637
|
-
const seen = new Set(own.map(model => model.id));
|
|
686
|
+
const seen = new Set(own.map((model) => model.id));
|
|
638
687
|
// Account pools reuse the catalog row; only configured tiers are extra.
|
|
639
|
-
return [...own, ...extra.filter(model => !seen.has(model.id))];
|
|
688
|
+
return [...own, ...extra.filter((model) => !seen.has(model.id))];
|
|
640
689
|
}
|
|
641
690
|
/** The provider's own catalog: union of every account, or one account when named. */
|
|
642
691
|
async listOwnModels(provider, account, signal) {
|
|
643
692
|
if (account === undefined) {
|
|
644
|
-
const accounts = (await this.options.tokens.list()).map(entry => entry.key);
|
|
693
|
+
const accounts = (await this.options.tokens.list()).map((entry) => entry.key);
|
|
645
694
|
if (accounts.length === 0)
|
|
646
695
|
return [];
|
|
647
|
-
return unionAccountCatalogs(accounts, (key, accountSignal) => this.listOwnModels(provider, key, accountSignal), {
|
|
696
|
+
return unionAccountCatalogs(accounts, (key, accountSignal) => this.listOwnModels(provider, key, accountSignal), {
|
|
697
|
+
timeoutMs: DISCOVERY_TIMEOUT_MS,
|
|
698
|
+
...(signal === undefined ? {} : { signal }),
|
|
699
|
+
});
|
|
648
700
|
}
|
|
649
|
-
if (!await this.options.tokens.hasSession(account)) {
|
|
701
|
+
if (!(await this.options.tokens.hasSession(account))) {
|
|
650
702
|
return [];
|
|
651
703
|
}
|
|
652
704
|
if (!this.options.discovery)
|
|
653
705
|
return this.staticModels(provider);
|
|
654
706
|
const catalog = await this.catalogFor(account);
|
|
655
707
|
try {
|
|
656
|
-
const models = await discoverOrRetryAuth(force => this.options.tokens.session(account, force), catalog, () => catalog.get(() => this.fetchCatalog(account, signal)));
|
|
657
|
-
return models.map(model => ({
|
|
708
|
+
const models = await discoverOrRetryAuth((force) => this.options.tokens.session(account, force), catalog, () => catalog.get(() => this.fetchCatalog(account, signal)));
|
|
709
|
+
return models.map((model) => ({
|
|
658
710
|
provider,
|
|
659
711
|
id: model.id,
|
|
660
712
|
name: model.name,
|
|
@@ -672,7 +724,8 @@ export class ClaudeAdapter extends LlmAdapter {
|
|
|
672
724
|
}
|
|
673
725
|
async resolveModel(provider, model) {
|
|
674
726
|
const pool = this.options.pool?.();
|
|
675
|
-
if (pool !== undefined &&
|
|
727
|
+
if (pool !== undefined &&
|
|
728
|
+
(await pool.owns(provider, model))) {
|
|
676
729
|
return pool.resolveModel(provider, model);
|
|
677
730
|
}
|
|
678
731
|
return this.resolveOwnModel(provider, model);
|
|
@@ -680,7 +733,7 @@ export class ClaudeAdapter extends LlmAdapter {
|
|
|
680
733
|
/** Capability resolution of the provider's own models (the pool resolves members here). */
|
|
681
734
|
async resolveOwnModel(provider, model, account) {
|
|
682
735
|
const disc = await this.discovered(model, account);
|
|
683
|
-
const configured = this.options.models.find(entry => entry.id === model);
|
|
736
|
+
const configured = this.options.models.find((entry) => entry.id === model);
|
|
684
737
|
const limits = this.contextLimitsFor(disc, configured);
|
|
685
738
|
const reasoning = mergeReasoning(this.options.defaultEffortOf?.(model), disc?.reasoning);
|
|
686
739
|
return {
|
|
@@ -688,7 +741,9 @@ export class ClaudeAdapter extends LlmAdapter {
|
|
|
688
741
|
id: model,
|
|
689
742
|
name: disc?.name ?? configured?.name ?? model,
|
|
690
743
|
inputModalities: configured?.inputModalities ?? CLAUDE_MODALITIES,
|
|
691
|
-
context: {
|
|
744
|
+
context: {
|
|
745
|
+
contextWindow: Math.min(this.options.contextWindowOf?.(model) ?? limits.default, limits.max),
|
|
746
|
+
},
|
|
692
747
|
defaultMaxTokens: claudeMaxTokens(configured, disc),
|
|
693
748
|
...(reasoning === undefined ? {} : { reasoning }),
|
|
694
749
|
};
|
|
@@ -700,11 +755,12 @@ export class ClaudeAdapter extends LlmAdapter {
|
|
|
700
755
|
}
|
|
701
756
|
/** Account-specific bounds the Settings editor shows next to a model's override input. */
|
|
702
757
|
async contextLimits(model, account) {
|
|
703
|
-
return this.contextLimitsFor(await this.discovered(model, account), this.options.models.find(entry => entry.id === model));
|
|
758
|
+
return this.contextLimitsFor(await this.discovered(model, account), this.options.models.find((entry) => entry.id === model));
|
|
704
759
|
}
|
|
705
760
|
async *stream(options) {
|
|
706
761
|
const pool = this.options.pool?.();
|
|
707
|
-
if (pool !== undefined &&
|
|
762
|
+
if (pool !== undefined &&
|
|
763
|
+
(await pool.owns(options.provider, options.model))) {
|
|
708
764
|
yield* pool.stream(options);
|
|
709
765
|
return;
|
|
710
766
|
}
|
|
@@ -726,13 +782,17 @@ export class ClaudeAdapter extends LlmAdapter {
|
|
|
726
782
|
if (!response.ok) {
|
|
727
783
|
throw await httpLlmError(response, 'claude API', {
|
|
728
784
|
rateLimitReset: claudeRateLimitReset,
|
|
729
|
-
...this.options.onWarn === undefined
|
|
785
|
+
...(this.options.onWarn === undefined
|
|
786
|
+
? {}
|
|
787
|
+
: { onWarn: this.options.onWarn }),
|
|
730
788
|
});
|
|
731
789
|
}
|
|
732
790
|
if (response.body === null) {
|
|
733
791
|
throw new LlmError('claude API returned no response body', EMPTY_RESPONSE_CODE);
|
|
734
792
|
}
|
|
735
|
-
yield* streamAnthropic(response.body, () => {
|
|
793
|
+
yield* streamAnthropic(response.body, () => {
|
|
794
|
+
watchdog.pulse();
|
|
795
|
+
});
|
|
736
796
|
}
|
|
737
797
|
catch (error) {
|
|
738
798
|
throw mapFetchFailure('claude API', error, watchdog, options.signal);
|
|
@@ -765,29 +825,32 @@ export class ClaudeAdapter extends LlmAdapter {
|
|
|
765
825
|
const offloadImages = requiredImageOffloadCount(messages, CLAUDE_REQUEST_IMAGE_BUDGET);
|
|
766
826
|
if (offloadImages > 0) {
|
|
767
827
|
// The supported 0.1.7-rc.2 floor reads `offloadImages`; older option types lack it.
|
|
768
|
-
throw new LlmError(`claude request images exceed the ${CLAUDE_REQUEST_IMAGE_BUDGET}-byte base64 budget; `
|
|
769
|
-
|
|
828
|
+
throw new LlmError(`claude request images exceed the ${CLAUDE_REQUEST_IMAGE_BUDGET}-byte base64 budget; ` +
|
|
829
|
+
`${offloadImages} more oldest image(s) must be offloaded`, IMAGE_OFFLOAD_REQUIRED, { offloadImages });
|
|
770
830
|
}
|
|
771
831
|
}
|
|
772
832
|
const disc = await this.discovered(options.model);
|
|
773
|
-
const maxTokens = options.maxTokens
|
|
774
|
-
|
|
833
|
+
const maxTokens = options.maxTokens ??
|
|
834
|
+
claudeMaxTokens(this.options.models.find((entry) => entry.id === options.model), disc);
|
|
775
835
|
const thinking = this.thinkingParam(disc?.thinkingType, maxTokens);
|
|
776
836
|
const effort = options.reasoningEffort !== undefined && disc?.reasoning !== undefined
|
|
777
837
|
? String(options.reasoningEffort)
|
|
778
838
|
: undefined;
|
|
779
|
-
|
|
839
|
+
// Read the Settings override per request: a saved change must apply from
|
|
840
|
+
// the next turn, and a cached value here would go stale.
|
|
841
|
+
const cacheTtl = this.options.promptCacheTtlOverride?.() ?? this.options.promptCacheTtl;
|
|
842
|
+
const body = claudeRequestBody(options, messages, maxTokens, thinking, effort, cacheTtl);
|
|
780
843
|
const cliVersion = await (this.options.resolveCliVersion ?? localClaudeCliVersion)();
|
|
781
844
|
return hostFetch(CLAUDE_API_URL, {
|
|
782
845
|
method: 'POST',
|
|
783
846
|
headers: {
|
|
784
|
-
|
|
847
|
+
authorization: `Bearer ${session.accessToken}`,
|
|
785
848
|
'anthropic-version': '2023-06-01',
|
|
786
849
|
'anthropic-beta': CLAUDE_BETA_FLAGS,
|
|
787
850
|
'user-agent': claudeCliUserAgent(cliVersion),
|
|
788
851
|
'x-app': 'cli',
|
|
789
852
|
'anthropic-dangerous-direct-browser-access': 'true',
|
|
790
|
-
|
|
853
|
+
accept: 'text/event-stream',
|
|
791
854
|
'content-type': 'application/json',
|
|
792
855
|
},
|
|
793
856
|
body: JSON.stringify(body),
|
|
@@ -9,9 +9,9 @@ export class CodexClientVersionCache extends NpmCliVersionCache {
|
|
|
9
9
|
url: CODEX_VERSION_URL,
|
|
10
10
|
label: 'Codex',
|
|
11
11
|
floor: () => ({ version: CODEX_CLIENT_VERSION, source: 'fallback' }),
|
|
12
|
-
...fetchFn === undefined ? {} : { fetchFn },
|
|
13
|
-
...now === undefined ? {} : { now },
|
|
14
|
-
...timeoutMs === undefined ? {} : { timeoutMs },
|
|
12
|
+
...(fetchFn === undefined ? {} : { fetchFn }),
|
|
13
|
+
...(now === undefined ? {} : { now }),
|
|
14
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
15
15
|
});
|
|
16
16
|
}
|
|
17
17
|
}
|