dsh-subscriptions 0.1.5 → 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 -154
- 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.d.ts +73 -2
- package/lib/auth/rpc.js +124 -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 +13 -0
- package/lib/client/OllamaCard.js +94 -0
- package/lib/client/OllamaSettingsManager.d.ts +24 -0
- package/lib/client/OllamaSettingsManager.js +329 -0
- package/lib/client/ProviderAccountManager.d.ts +1 -1
- package/lib/client/ProviderAccountManager.js +164 -49
- package/lib/client/ProviderModelEditor.js +109 -26
- package/lib/client/SpeedSelect.d.ts +62 -14
- package/lib/client/SpeedSelect.js +257 -46
- package/lib/client/SubscriptionUsageBadge.d.ts +5 -11
- package/lib/client/SubscriptionUsageBadge.js +301 -112
- package/lib/client/SubscriptionsSection.d.ts +2 -0
- package/lib/client/SubscriptionsSection.js +259 -85
- package/lib/client/UsageBadgeDisplaySetting.js +35 -12
- package/lib/client/UsageMeter.d.ts +1 -1
- package/lib/client/UsageMeter.js +69 -27
- 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 +78 -0
- package/lib/client/locales.js +121 -34
- package/lib/client/ollama-draft.d.ts +229 -0
- package/lib/client/ollama-draft.js +255 -0
- 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.d.ts +7 -0
- package/lib/client/usage-pace.js +43 -10
- package/lib/client.js +1647 -266
- package/lib/client.js.map +1 -1
- package/lib/http.js +1 -1
- package/lib/index.d.ts +28 -3
- package/lib/index.js +9329 -8217
- package/lib/model-defaults.js +4 -2
- package/lib/provider-settings.d.ts +14 -1
- package/lib/provider-settings.js +64 -20
- 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 +23 -3
- package/lib/providers/claude.js +167 -92
- 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.d.ts +14 -0
- package/lib/providers/common.js +118 -38
- 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.d.ts +9 -2
- package/lib/providers/external-usage-controller.js +30 -7
- package/lib/providers/external-usage.d.ts +8 -0
- package/lib/providers/external-usage.js +140 -20
- 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.d.ts +90 -0
- package/lib/providers/ollama-settings.js +213 -0
- package/lib/providers/ollama-web.d.ts +46 -0
- package/lib/providers/ollama-web.js +231 -0
- package/lib/providers/ollama.d.ts +107 -0
- package/lib/providers/ollama.js +608 -0
- 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/grok.js
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
* auth.x.ai with the Grok CLI client id, and streaming against the xAI
|
|
4
4
|
* Responses-style endpoint.
|
|
5
5
|
*/
|
|
6
|
-
import { attributionHeaders, EMPTY_RESPONSE_CODE, errorChain, LlmAdapter, LlmError, ReasoningEffortId } from '@deepseek-ai/dsh-llm';
|
|
6
|
+
import { attributionHeaders, EMPTY_RESPONSE_CODE, errorChain, LlmAdapter, LlmError, ReasoningEffortId, } from '@deepseek-ai/dsh-llm';
|
|
7
7
|
import { decodeJwtPayload } from '../auth/jwt.js';
|
|
8
8
|
import { resolveImages } from '../translate/resolved.js';
|
|
9
|
-
import { streamResponses, toResponsesInput, toResponsesTools } from '../translate/responses.js';
|
|
9
|
+
import { streamResponses, toResponsesInput, toResponsesTools, } from '../translate/responses.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 { DEFAULT_RATE_LIMIT_WAIT, DEFAULT_RETRY, jsonBody, resetFromFields, subscriptionRetryPolicy, } from './rate-limit.js';
|
|
14
14
|
export const GROK_CLIENT_ID = 'b1a00492-073a-47ea-816f-4c329264a828';
|
|
@@ -21,7 +21,12 @@ const GROK_DEFAULT_MAX_TOKENS = 32_000;
|
|
|
21
21
|
/** Refresh when the access token has less than this much life left. */
|
|
22
22
|
export const GROK_PREEMPT_MS = 2 * 60_000;
|
|
23
23
|
/** Body fields xAI uses to name a delay or reset. */
|
|
24
|
-
const GROK_RESET_FIELDS = [
|
|
24
|
+
const GROK_RESET_FIELDS = [
|
|
25
|
+
'retry_after',
|
|
26
|
+
'retry_after_seconds',
|
|
27
|
+
'resets_at',
|
|
28
|
+
'reset_at',
|
|
29
|
+
];
|
|
25
30
|
/**
|
|
26
31
|
* Reads the reset instant of the xAI window that rejected a request.
|
|
27
32
|
*
|
|
@@ -47,8 +52,8 @@ function assertXaiEndpoint(url, field) {
|
|
|
47
52
|
catch {
|
|
48
53
|
throw new Error(`grok OIDC discovery returned an invalid ${field}`);
|
|
49
54
|
}
|
|
50
|
-
if (parsed.protocol !== 'https:'
|
|
51
|
-
|
|
55
|
+
if (parsed.protocol !== 'https:' ||
|
|
56
|
+
(parsed.hostname !== 'x.ai' && !parsed.hostname.endsWith('.x.ai'))) {
|
|
52
57
|
throw new Error(`grok OIDC discovery returned a non-x.ai ${field}`);
|
|
53
58
|
}
|
|
54
59
|
return url;
|
|
@@ -64,8 +69,9 @@ export async function grokDiscovery() {
|
|
|
64
69
|
const response = await hostFetch(GROK_DISCOVERY_URL);
|
|
65
70
|
if (!response.ok)
|
|
66
71
|
throw await oauthEndpointError(response, 'grok OIDC discovery');
|
|
67
|
-
const document = await parseProviderJson(response, 'grok OIDC discovery');
|
|
68
|
-
if (typeof document.authorization_endpoint !== 'string' ||
|
|
72
|
+
const document = (await parseProviderJson(response, 'grok OIDC discovery'));
|
|
73
|
+
if (typeof document.authorization_endpoint !== 'string' ||
|
|
74
|
+
typeof document.token_endpoint !== 'string') {
|
|
69
75
|
throw new Error('grok OIDC discovery document is missing endpoints');
|
|
70
76
|
}
|
|
71
77
|
discoveryCache = {
|
|
@@ -141,12 +147,16 @@ export function grokTierName(accessToken) {
|
|
|
141
147
|
/** Pick a display account from an id token's claims. */
|
|
142
148
|
function grokAccount(idToken) {
|
|
143
149
|
const payload = idToken === undefined ? undefined : decodeJwtPayload(idToken);
|
|
144
|
-
const claim = payload?.email ??
|
|
150
|
+
const claim = payload?.email ??
|
|
151
|
+
payload?.preferred_username ??
|
|
152
|
+
payload?.name ??
|
|
153
|
+
payload?.sub;
|
|
145
154
|
return typeof claim === 'string' && claim.length > 0 ? claim : undefined;
|
|
146
155
|
}
|
|
147
156
|
/** Build a session from a token response. */
|
|
148
157
|
function grokSession(tokens, tokenEndpoint, fallbackRefreshToken) {
|
|
149
|
-
if (typeof tokens.access_token !== 'string' ||
|
|
158
|
+
if (typeof tokens.access_token !== 'string' ||
|
|
159
|
+
tokens.access_token.length === 0) {
|
|
150
160
|
throw new Error('grok token endpoint returned no access token');
|
|
151
161
|
}
|
|
152
162
|
const refreshToken = tokens.refresh_token ?? fallbackRefreshToken;
|
|
@@ -161,8 +171,8 @@ function grokSession(tokens, tokenEndpoint, fallbackRefreshToken) {
|
|
|
161
171
|
refreshToken,
|
|
162
172
|
expiresAt: Date.now() + tokens.expires_in * 1000,
|
|
163
173
|
tokenEndpoint,
|
|
164
|
-
...typeof tokens.scope === 'string' ? { scopes: tokens.scope } : {},
|
|
165
|
-
...account === undefined ? {} : { account },
|
|
174
|
+
...(typeof tokens.scope === 'string' ? { scopes: tokens.scope } : {}),
|
|
175
|
+
...(account === undefined ? {} : { account }),
|
|
166
176
|
};
|
|
167
177
|
}
|
|
168
178
|
/**
|
|
@@ -191,12 +201,12 @@ export async function exchangeGrokCode(code, verifier, redirectUri, challenge) {
|
|
|
191
201
|
}).toString(),
|
|
192
202
|
});
|
|
193
203
|
if (response.status === 403) {
|
|
194
|
-
throw new OAuthEndpointError('grok token endpoint refused the exchange (HTTP 403): your X plan does not include '
|
|
195
|
-
|
|
204
|
+
throw new OAuthEndpointError('grok token endpoint refused the exchange (HTTP 403): your X plan does not include ' +
|
|
205
|
+
'the API OAuth entitlement; an X Premium or xAI subscription with API access is required', 403);
|
|
196
206
|
}
|
|
197
207
|
if (!response.ok)
|
|
198
208
|
throw await oauthEndpointError(response, 'grok');
|
|
199
|
-
return grokSession(await parseProviderJson(response, 'grok token exchange'), discovery.tokenEndpoint);
|
|
209
|
+
return grokSession((await parseProviderJson(response, 'grok token exchange')), discovery.tokenEndpoint);
|
|
200
210
|
}
|
|
201
211
|
/**
|
|
202
212
|
* Refresh a grok session (form-encoded grant).
|
|
@@ -215,11 +225,13 @@ export async function refreshGrok(session) {
|
|
|
215
225
|
});
|
|
216
226
|
if (!response.ok)
|
|
217
227
|
throw await oauthEndpointError(response, 'grok');
|
|
218
|
-
const next = grokSession(await parseProviderJson(response, 'grok token refresh'), session.tokenEndpoint, session.refreshToken);
|
|
228
|
+
const next = grokSession((await parseProviderJson(response, 'grok token refresh')), session.tokenEndpoint, session.refreshToken);
|
|
219
229
|
return {
|
|
220
230
|
...next,
|
|
221
|
-
...session.account === undefined ? {} : { account: session.account },
|
|
222
|
-
...next.scopes === undefined && session.scopes !== undefined
|
|
231
|
+
...(session.account === undefined ? {} : { account: session.account }),
|
|
232
|
+
...(next.scopes === undefined && session.scopes !== undefined
|
|
233
|
+
? { scopes: session.scopes }
|
|
234
|
+
: {}),
|
|
223
235
|
};
|
|
224
236
|
}
|
|
225
237
|
/**
|
|
@@ -228,7 +240,7 @@ export async function refreshGrok(session) {
|
|
|
228
240
|
* @returns true when re-login is the only fix.
|
|
229
241
|
*/
|
|
230
242
|
export function isGrokPermanentRefreshError(error) {
|
|
231
|
-
return error instanceof OAuthEndpointError && error.oauthCode === 'invalid_grant';
|
|
243
|
+
return (error instanceof OAuthEndpointError && error.oauthCode === 'invalid_grant');
|
|
232
244
|
}
|
|
233
245
|
/**
|
|
234
246
|
* The Grok Build CLI chat proxy's billing endpoint (the source of the CLI's
|
|
@@ -256,50 +268,61 @@ function grokResetsAt(value) {
|
|
|
256
268
|
export async function fetchGrokUsage(session, fetchFn = hostFetch, signal) {
|
|
257
269
|
const response = await fetchFn(GROK_BILLING_URL, {
|
|
258
270
|
headers: {
|
|
259
|
-
|
|
271
|
+
authorization: `Bearer ${session.accessToken}`,
|
|
260
272
|
// The proxy only honors bearer tokens presented as the Grok CLI.
|
|
261
273
|
'x-xai-token-auth': 'xai-grok-cli',
|
|
262
|
-
|
|
274
|
+
accept: 'application/json',
|
|
263
275
|
...attributionHeaders(),
|
|
264
276
|
},
|
|
265
|
-
...signal === undefined ? {} : { signal },
|
|
277
|
+
...(signal === undefined ? {} : { signal }),
|
|
266
278
|
});
|
|
267
279
|
if (!response.ok)
|
|
268
280
|
throw await oauthEndpointError(response, 'grok billing');
|
|
269
|
-
const payload = await parseProviderJson(response, 'grok billing');
|
|
270
|
-
const config = typeof payload.config === 'object' && payload.config !== null
|
|
281
|
+
const payload = (await parseProviderJson(response, 'grok billing'));
|
|
282
|
+
const config = typeof payload.config === 'object' && payload.config !== null
|
|
283
|
+
? payload.config
|
|
284
|
+
: {};
|
|
271
285
|
const windows = [];
|
|
272
|
-
if (config.currentPeriod ||
|
|
286
|
+
if (config.currentPeriod ||
|
|
287
|
+
(typeof config.creditUsagePercent === 'number' &&
|
|
288
|
+
Number.isFinite(config.creditUsagePercent))) {
|
|
273
289
|
const kind = config.currentPeriod?.type === 'USAGE_PERIOD_TYPE_WEEKLY'
|
|
274
290
|
? 'weekly'
|
|
275
291
|
: 'other';
|
|
276
292
|
const resetsAt = grokResetsAt(config.currentPeriod?.end);
|
|
277
|
-
const usedPercent = typeof config.creditUsagePercent === 'number' &&
|
|
293
|
+
const usedPercent = typeof config.creditUsagePercent === 'number' &&
|
|
294
|
+
Number.isFinite(config.creditUsagePercent)
|
|
278
295
|
? config.creditUsagePercent
|
|
279
296
|
: 0;
|
|
280
297
|
const startsAt = grokResetsAt(config.currentPeriod?.start);
|
|
281
|
-
windows.push({
|
|
282
|
-
|
|
298
|
+
windows.push({
|
|
299
|
+
kind,
|
|
300
|
+
usedPercent,
|
|
301
|
+
...(resetsAt === undefined ? {} : { resetsAt }),
|
|
302
|
+
...(startsAt === undefined ? {} : { startsAt }),
|
|
303
|
+
});
|
|
283
304
|
}
|
|
284
|
-
else if (typeof config.monthlyLimit?.val === 'number' &&
|
|
305
|
+
else if (typeof config.monthlyLimit?.val === 'number' &&
|
|
306
|
+
config.monthlyLimit.val > 0) {
|
|
285
307
|
const used = typeof config.used?.val === 'number' ? config.used.val : 0;
|
|
286
308
|
const resetsAt = grokResetsAt(config.billingPeriodEnd);
|
|
287
309
|
windows.push({
|
|
288
310
|
kind: 'other',
|
|
289
311
|
usedPercent: (used / config.monthlyLimit.val) * 100,
|
|
290
|
-
...resetsAt === undefined ? {} : { resetsAt },
|
|
312
|
+
...(resetsAt === undefined ? {} : { resetsAt }),
|
|
291
313
|
});
|
|
292
314
|
}
|
|
293
315
|
// The upstream billing response rarely carries `subscriptionTier` (the CLI
|
|
294
316
|
// enriches it locally from its settings cache), so the access token's
|
|
295
317
|
// `tier` claim is the working fallback.
|
|
296
|
-
const plan = typeof payload.subscriptionTier === 'string' &&
|
|
318
|
+
const plan = typeof payload.subscriptionTier === 'string' &&
|
|
319
|
+
payload.subscriptionTier.length > 0
|
|
297
320
|
? payload.subscriptionTier
|
|
298
321
|
: grokTierName(session.accessToken);
|
|
299
322
|
return {
|
|
300
323
|
supported: true,
|
|
301
324
|
windows,
|
|
302
|
-
...plan === undefined ? {} : { plan },
|
|
325
|
+
...(plan === undefined ? {} : { plan }),
|
|
303
326
|
};
|
|
304
327
|
}
|
|
305
328
|
export const GROK_MODELS_URL = 'https://api.x.ai/v1/models';
|
|
@@ -323,24 +346,26 @@ function grokCliReasoning(entry) {
|
|
|
323
346
|
if (entry.supports_reasoning_effort !== true)
|
|
324
347
|
return undefined;
|
|
325
348
|
const efforts = (entry.reasoning_efforts ?? [])
|
|
326
|
-
.filter(level => typeof level.value === 'string' && level.value.length > 0)
|
|
327
|
-
.map(level => ({
|
|
349
|
+
.filter((level) => typeof level.value === 'string' && level.value.length > 0)
|
|
350
|
+
.map((level) => ({
|
|
328
351
|
id: ReasoningEffortId(level.value),
|
|
329
|
-
name: typeof level.label === 'string' && level.label.length > 0
|
|
330
|
-
|
|
352
|
+
name: typeof level.label === 'string' && level.label.length > 0
|
|
353
|
+
? level.label
|
|
354
|
+
: level.value,
|
|
355
|
+
...(typeof level.description === 'string' && level.description.length > 0
|
|
331
356
|
? { description: level.description }
|
|
332
|
-
: {},
|
|
357
|
+
: {}),
|
|
333
358
|
}));
|
|
334
359
|
if (efforts.length === 0)
|
|
335
360
|
return undefined;
|
|
336
361
|
// The per-entry `default` flags are unreliable (the live catalog marks
|
|
337
362
|
// several levels default at once), so the top-level `reasoning_effort`
|
|
338
363
|
// field is the trusted default.
|
|
339
|
-
const defaultEffort = typeof entry.reasoning_effort === 'string'
|
|
340
|
-
|
|
364
|
+
const defaultEffort = typeof entry.reasoning_effort === 'string' &&
|
|
365
|
+
efforts.some((effort) => effort.id === ReasoningEffortId(entry.reasoning_effort))
|
|
341
366
|
? ReasoningEffortId(entry.reasoning_effort)
|
|
342
367
|
: undefined;
|
|
343
|
-
return { efforts, ...defaultEffort === undefined ? {} : { defaultEffort } };
|
|
368
|
+
return { efforts, ...(defaultEffort === undefined ? {} : { defaultEffort }) };
|
|
344
369
|
}
|
|
345
370
|
/**
|
|
346
371
|
* Fetch the CLI catalog and index its per-model metadata by model id.
|
|
@@ -352,17 +377,17 @@ function grokCliReasoning(entry) {
|
|
|
352
377
|
export async function fetchGrokCliCatalog(session, fetchFn = hostFetch, signal) {
|
|
353
378
|
const response = await fetchFn(GROK_CLI_MODELS_URL, {
|
|
354
379
|
headers: {
|
|
355
|
-
|
|
380
|
+
authorization: `Bearer ${session.accessToken}`,
|
|
356
381
|
// The proxy only honors bearer tokens presented as the Grok CLI.
|
|
357
382
|
'x-xai-token-auth': 'xai-grok-cli',
|
|
358
|
-
|
|
383
|
+
accept: 'application/json',
|
|
359
384
|
...attributionHeaders(),
|
|
360
385
|
},
|
|
361
|
-
...signal === undefined ? {} : { signal },
|
|
386
|
+
...(signal === undefined ? {} : { signal }),
|
|
362
387
|
});
|
|
363
388
|
if (!response.ok)
|
|
364
389
|
throw await oauthEndpointError(response, 'grok CLI catalog');
|
|
365
|
-
const payload = await parseProviderJson(response, 'grok CLI catalog');
|
|
390
|
+
const payload = (await parseProviderJson(response, 'grok CLI catalog'));
|
|
366
391
|
if (!Array.isArray(payload.data))
|
|
367
392
|
throw new Error('grok CLI catalog returned no data array');
|
|
368
393
|
const catalog = new Map();
|
|
@@ -371,14 +396,16 @@ export async function fetchGrokCliCatalog(session, fetchFn = hostFetch, signal)
|
|
|
371
396
|
continue;
|
|
372
397
|
const reasoning = grokCliReasoning(entry);
|
|
373
398
|
catalog.set(entry.id, {
|
|
374
|
-
...typeof entry.name === 'string' && entry.name.length > 0
|
|
375
|
-
|
|
399
|
+
...(typeof entry.name === 'string' && entry.name.length > 0
|
|
400
|
+
? { name: entry.name }
|
|
401
|
+
: {}),
|
|
402
|
+
...(typeof entry.description === 'string' && entry.description.length > 0
|
|
376
403
|
? { description: entry.description }
|
|
377
|
-
: {},
|
|
378
|
-
...typeof entry.context_window === 'number' && entry.context_window > 0
|
|
404
|
+
: {}),
|
|
405
|
+
...(typeof entry.context_window === 'number' && entry.context_window > 0
|
|
379
406
|
? { contextWindow: entry.context_window }
|
|
380
|
-
: {},
|
|
381
|
-
...reasoning === undefined ? {} : { reasoning },
|
|
407
|
+
: {}),
|
|
408
|
+
...(reasoning === undefined ? {} : { reasoning }),
|
|
382
409
|
});
|
|
383
410
|
}
|
|
384
411
|
return catalog;
|
|
@@ -402,8 +429,12 @@ function grokPriorMeta(prior) {
|
|
|
402
429
|
return {};
|
|
403
430
|
return {
|
|
404
431
|
...(prior.name.length > 0 ? { name: prior.name } : {}),
|
|
405
|
-
...(prior.description === undefined
|
|
406
|
-
|
|
432
|
+
...(prior.description === undefined
|
|
433
|
+
? {}
|
|
434
|
+
: { description: prior.description }),
|
|
435
|
+
...(prior.contextWindow === undefined
|
|
436
|
+
? {}
|
|
437
|
+
: { contextWindow: prior.contextWindow }),
|
|
407
438
|
...(prior.reasoning === undefined ? {} : { reasoning: prior.reasoning }),
|
|
408
439
|
};
|
|
409
440
|
}
|
|
@@ -426,15 +457,15 @@ function grokPriorMeta(prior) {
|
|
|
426
457
|
export async function fetchGrokModels(session, fetchFn = hostFetch, onWarn, previous, signal) {
|
|
427
458
|
const previousById = previous === undefined || previous.length === 0
|
|
428
459
|
? undefined
|
|
429
|
-
: new Map(previous.map(model => [model.id, model]));
|
|
460
|
+
: new Map(previous.map((model) => [model.id, model]));
|
|
430
461
|
const [response, cliCatalog] = await Promise.all([
|
|
431
462
|
fetchFn(GROK_MODELS_URL, {
|
|
432
463
|
headers: {
|
|
433
|
-
|
|
434
|
-
|
|
464
|
+
authorization: `Bearer ${session.accessToken}`,
|
|
465
|
+
accept: 'application/json',
|
|
435
466
|
...attributionHeaders(),
|
|
436
467
|
},
|
|
437
|
-
...signal === undefined ? {} : { signal },
|
|
468
|
+
...(signal === undefined ? {} : { signal }),
|
|
438
469
|
}),
|
|
439
470
|
fetchGrokCliCatalog(session, fetchFn, signal).catch((error) => {
|
|
440
471
|
if (isDiscoveryAborted(error, signal))
|
|
@@ -447,13 +478,15 @@ export async function fetchGrokModels(session, fetchFn = hostFetch, onWarn, prev
|
|
|
447
478
|
]);
|
|
448
479
|
if (!response.ok)
|
|
449
480
|
throw await oauthEndpointError(response, 'grok models');
|
|
450
|
-
const payload = await parseProviderJson(response, 'grok models');
|
|
481
|
+
const payload = (await parseProviderJson(response, 'grok models'));
|
|
451
482
|
if (!Array.isArray(payload.data))
|
|
452
483
|
throw new Error('grok models endpoint returned no data array');
|
|
453
484
|
const seen = new Set();
|
|
454
485
|
const discovered = [];
|
|
455
486
|
for (const entry of payload.data) {
|
|
456
|
-
if (typeof entry.id !== 'string' ||
|
|
487
|
+
if (typeof entry.id !== 'string' ||
|
|
488
|
+
entry.id.length === 0 ||
|
|
489
|
+
seen.has(entry.id))
|
|
457
490
|
continue;
|
|
458
491
|
if (!isChatModel(entry.id))
|
|
459
492
|
continue;
|
|
@@ -482,23 +515,33 @@ export async function fetchGrokModels(session, fetchFn = hostFetch, onWarn, prev
|
|
|
482
515
|
export function grokRequestBody(options, resolved) {
|
|
483
516
|
return {
|
|
484
517
|
model: options.model,
|
|
485
|
-
...resolved.instructions === undefined
|
|
518
|
+
...(resolved.instructions === undefined
|
|
519
|
+
? {}
|
|
520
|
+
: { instructions: resolved.instructions }),
|
|
486
521
|
input: resolved.input,
|
|
487
|
-
...options.tools !== undefined && options.tools.length > 0
|
|
488
|
-
? {
|
|
489
|
-
|
|
490
|
-
|
|
522
|
+
...(options.tools !== undefined && options.tools.length > 0
|
|
523
|
+
? {
|
|
524
|
+
tools: toResponsesTools(options.tools),
|
|
525
|
+
tool_choice: 'auto',
|
|
526
|
+
parallel_tool_calls: true,
|
|
527
|
+
}
|
|
528
|
+
: {}),
|
|
529
|
+
...(options.maxTokens !== undefined
|
|
530
|
+
? { max_output_tokens: options.maxTokens }
|
|
531
|
+
: {}),
|
|
491
532
|
// The harness only passes an effort the resolved model advertised (the
|
|
492
533
|
// CLI catalog's), so this never reaches a model that rejects it.
|
|
493
|
-
...options.reasoningEffort !== undefined
|
|
534
|
+
...(options.reasoningEffort !== undefined
|
|
494
535
|
? { reasoning: { effort: String(options.reasoningEffort) } }
|
|
495
|
-
: {},
|
|
536
|
+
: {}),
|
|
496
537
|
// Cache-affinity hint (mirrors codex): xAI caches prompts per server,
|
|
497
538
|
// and `prompt_cache_key` is the Responses-API signal that routes repeat
|
|
498
539
|
// requests back to the cache-holding shard — without it every turn's
|
|
499
540
|
// cache hit is shard-routing luck. The session id is the stable key
|
|
500
541
|
// xAI's own docs recommend.
|
|
501
|
-
...options.sessionId !== undefined
|
|
542
|
+
...(options.sessionId !== undefined
|
|
543
|
+
? { prompt_cache_key: String(options.sessionId) }
|
|
544
|
+
: {}),
|
|
502
545
|
store: false,
|
|
503
546
|
stream: true,
|
|
504
547
|
};
|
|
@@ -518,7 +561,8 @@ export class GrokAdapter extends LlmAdapter {
|
|
|
518
561
|
}
|
|
519
562
|
/** Discovery fetcher: resolves the session through the refresh-aware path. */
|
|
520
563
|
async fetchCatalog(account, signal) {
|
|
521
|
-
const lastKnown = account === undefined ||
|
|
564
|
+
const lastKnown = account === undefined ||
|
|
565
|
+
account === (await this.options.tokens.defaultAccount())
|
|
522
566
|
? this.catalog.lastKnown()
|
|
523
567
|
: this.accountCatalogs.get(account)?.lastKnown();
|
|
524
568
|
return fetchGrokModels(await this.options.tokens.session(account), this.options.fetchFn, this.options.onWarn, lastKnown, signal);
|
|
@@ -529,7 +573,9 @@ export class GrokAdapter extends LlmAdapter {
|
|
|
529
573
|
this.accountCatalogs.clear();
|
|
530
574
|
else
|
|
531
575
|
this.accountCatalogs.delete(account);
|
|
532
|
-
if (account === undefined ||
|
|
576
|
+
if (account === undefined ||
|
|
577
|
+
this.catalogOwner === account ||
|
|
578
|
+
this.catalogOwner === undefined) {
|
|
533
579
|
this.catalogOwner = undefined;
|
|
534
580
|
this.catalog.invalidate();
|
|
535
581
|
}
|
|
@@ -553,11 +599,13 @@ export class GrokAdapter extends LlmAdapter {
|
|
|
553
599
|
return cache;
|
|
554
600
|
}
|
|
555
601
|
listed(provider, discovered) {
|
|
556
|
-
return discovered.map(model => ({
|
|
602
|
+
return discovered.map((model) => ({
|
|
557
603
|
provider,
|
|
558
604
|
id: model.id,
|
|
559
605
|
name: model.name,
|
|
560
|
-
...model.description === undefined
|
|
606
|
+
...(model.description === undefined
|
|
607
|
+
? {}
|
|
608
|
+
: { description: model.description }),
|
|
561
609
|
inputModalities: grokModalities(model.id),
|
|
562
610
|
}));
|
|
563
611
|
}
|
|
@@ -568,7 +616,7 @@ export class GrokAdapter extends LlmAdapter {
|
|
|
568
616
|
return subscriptionRetryPolicy(DEFAULT_RETRY, this.options.rateLimit ?? DEFAULT_RATE_LIMIT_WAIT, `grok: provider "${provider}" retryPolicy`);
|
|
569
617
|
}
|
|
570
618
|
staticModels(provider) {
|
|
571
|
-
return this.options.models.map(model => ({
|
|
619
|
+
return this.options.models.map((model) => ({
|
|
572
620
|
provider,
|
|
573
621
|
id: model.id,
|
|
574
622
|
name: model.name ?? model.id,
|
|
@@ -581,19 +629,22 @@ export class GrokAdapter extends LlmAdapter {
|
|
|
581
629
|
if (pool === undefined)
|
|
582
630
|
return own;
|
|
583
631
|
const extra = await pool.modelsForProvider(provider);
|
|
584
|
-
const seen = new Set(own.map(model => model.id));
|
|
632
|
+
const seen = new Set(own.map((model) => model.id));
|
|
585
633
|
// Account pools reuse the catalog row; only configured tiers are extra.
|
|
586
|
-
return [...own, ...extra.filter(model => !seen.has(model.id))];
|
|
634
|
+
return [...own, ...extra.filter((model) => !seen.has(model.id))];
|
|
587
635
|
}
|
|
588
636
|
/** The provider's own catalog: union of every account, or one account when named. */
|
|
589
637
|
async listOwnModels(provider, account, signal) {
|
|
590
638
|
if (account === undefined) {
|
|
591
|
-
const accounts = (await this.options.tokens.list()).map(entry => entry.key);
|
|
639
|
+
const accounts = (await this.options.tokens.list()).map((entry) => entry.key);
|
|
592
640
|
if (accounts.length === 0)
|
|
593
641
|
return [];
|
|
594
|
-
return unionAccountCatalogs(accounts, (key, accountSignal) => this.listOwnModels(provider, key, accountSignal), {
|
|
642
|
+
return unionAccountCatalogs(accounts, (key, accountSignal) => this.listOwnModels(provider, key, accountSignal), {
|
|
643
|
+
timeoutMs: DISCOVERY_TIMEOUT_MS,
|
|
644
|
+
...(signal === undefined ? {} : { signal }),
|
|
645
|
+
});
|
|
595
646
|
}
|
|
596
|
-
if (!await this.options.tokens.hasSession(account)) {
|
|
647
|
+
if (!(await this.options.tokens.hasSession(account))) {
|
|
597
648
|
return [];
|
|
598
649
|
}
|
|
599
650
|
if (!this.options.discovery)
|
|
@@ -603,7 +654,7 @@ export class GrokAdapter extends LlmAdapter {
|
|
|
603
654
|
// The fetcher runs only on a cache miss, and resolves the session
|
|
604
655
|
// through the refresh-aware path so an expired access token renews here
|
|
605
656
|
// instead of failing discovery into the static fallback.
|
|
606
|
-
return this.listed(provider, await discoverOrRetryAuth(force => this.options.tokens.session(account, force), catalog, () => catalog.get(() => this.fetchCatalog(account, signal))));
|
|
657
|
+
return this.listed(provider, await discoverOrRetryAuth((force) => this.options.tokens.session(account, force), catalog, () => catalog.get(() => this.fetchCatalog(account, signal))));
|
|
607
658
|
}
|
|
608
659
|
catch (error) {
|
|
609
660
|
if (isDiscoveryAborted(error, signal))
|
|
@@ -627,16 +678,17 @@ export class GrokAdapter extends LlmAdapter {
|
|
|
627
678
|
async discovered(model) {
|
|
628
679
|
if (!this.options.discovery)
|
|
629
680
|
return undefined;
|
|
630
|
-
const accounts = (await this.options.tokens.list()).map(entry => entry.key);
|
|
681
|
+
const accounts = (await this.options.tokens.list()).map((entry) => entry.key);
|
|
631
682
|
return discoverAcrossAccounts(accounts, async (account) => {
|
|
632
683
|
const catalog = await this.catalogFor(account);
|
|
633
684
|
const models = await catalog.resolve(() => this.fetchCatalog(account));
|
|
634
|
-
return models?.find(entry => entry.id === model);
|
|
685
|
+
return models?.find((entry) => entry.id === model);
|
|
635
686
|
});
|
|
636
687
|
}
|
|
637
688
|
async resolveModel(provider, model) {
|
|
638
689
|
const pool = this.options.pool?.();
|
|
639
|
-
if (pool !== undefined &&
|
|
690
|
+
if (pool !== undefined &&
|
|
691
|
+
(await pool.owns(provider, model))) {
|
|
640
692
|
return pool.resolveModel(provider, model);
|
|
641
693
|
}
|
|
642
694
|
return this.resolveOwnModel(provider, model);
|
|
@@ -644,7 +696,7 @@ export class GrokAdapter extends LlmAdapter {
|
|
|
644
696
|
/** Capability resolution of the provider's own models (the pool resolves members here). */
|
|
645
697
|
async resolveOwnModel(provider, model) {
|
|
646
698
|
const discovered = await this.discovered(model);
|
|
647
|
-
const configured = this.options.models.find(entry => entry.id === model);
|
|
699
|
+
const configured = this.options.models.find((entry) => entry.id === model);
|
|
648
700
|
// Efforts come from the discovered CLI catalog; models it does not
|
|
649
701
|
// cover expose none, so the harness rejects explicit efforts before
|
|
650
702
|
// provider I/O instead of the API 400ing. A configured default effort
|
|
@@ -655,16 +707,23 @@ export class GrokAdapter extends LlmAdapter {
|
|
|
655
707
|
provider,
|
|
656
708
|
id: model,
|
|
657
709
|
name: discovered?.name ?? configured?.name ?? model,
|
|
658
|
-
...discovered?.description === undefined
|
|
710
|
+
...(discovered?.description === undefined
|
|
711
|
+
? {}
|
|
712
|
+
: { description: discovered.description }),
|
|
659
713
|
inputModalities: configured?.inputModalities ?? grokModalities(model),
|
|
660
|
-
context: {
|
|
714
|
+
context: {
|
|
715
|
+
contextWindow: discovered?.contextWindow ??
|
|
716
|
+
configured?.contextWindow ??
|
|
717
|
+
GROK_CONTEXT_WINDOW,
|
|
718
|
+
},
|
|
661
719
|
defaultMaxTokens: configured?.maxTokens ?? GROK_DEFAULT_MAX_TOKENS,
|
|
662
|
-
...reasoning === undefined ? {} : { reasoning },
|
|
720
|
+
...(reasoning === undefined ? {} : { reasoning }),
|
|
663
721
|
};
|
|
664
722
|
}
|
|
665
723
|
async *stream(options) {
|
|
666
724
|
const pool = this.options.pool?.();
|
|
667
|
-
if (pool !== undefined &&
|
|
725
|
+
if (pool !== undefined &&
|
|
726
|
+
(await pool.owns(options.provider, options.model))) {
|
|
668
727
|
yield* pool.stream(options);
|
|
669
728
|
return;
|
|
670
729
|
}
|
|
@@ -687,13 +746,17 @@ export class GrokAdapter extends LlmAdapter {
|
|
|
687
746
|
if (!response.ok) {
|
|
688
747
|
throw await httpLlmError(response, 'grok API', {
|
|
689
748
|
rateLimitReset: grokRateLimitReset,
|
|
690
|
-
...this.options.onWarn === undefined
|
|
749
|
+
...(this.options.onWarn === undefined
|
|
750
|
+
? {}
|
|
751
|
+
: { onWarn: this.options.onWarn }),
|
|
691
752
|
});
|
|
692
753
|
}
|
|
693
754
|
if (response.body === null) {
|
|
694
755
|
throw new LlmError('grok API returned no response body', EMPTY_RESPONSE_CODE);
|
|
695
756
|
}
|
|
696
|
-
yield* streamResponses(response.body, () => {
|
|
757
|
+
yield* streamResponses(response.body, () => {
|
|
758
|
+
watchdog.pulse();
|
|
759
|
+
});
|
|
697
760
|
}
|
|
698
761
|
catch (error) {
|
|
699
762
|
throw mapFetchFailure('grok API', error, watchdog, options.signal);
|
|
@@ -708,8 +771,8 @@ export class GrokAdapter extends LlmAdapter {
|
|
|
708
771
|
return hostFetch(GROK_API_URL, {
|
|
709
772
|
method: 'POST',
|
|
710
773
|
headers: {
|
|
711
|
-
|
|
712
|
-
|
|
774
|
+
authorization: `Bearer ${session.accessToken}`,
|
|
775
|
+
accept: 'text/event-stream',
|
|
713
776
|
'content-type': 'application/json',
|
|
714
777
|
...attributionHeaders(),
|
|
715
778
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Account scheduling shared by image generation and editing, independent of chat catalogs/quota. */
|
|
2
2
|
import { LlmError } from '@deepseek-ai/dsh-llm';
|
|
3
3
|
import { httpLlmError } from './common.js';
|
|
4
|
-
import { AUTH_COOLDOWN_MS, DEFAULT_QUOTA_COOLDOWN_MS, TRANSIENT_COOLDOWN_MS, PoolHealthRegistry, memberKey } from './pool-health.js';
|
|
4
|
+
import { AUTH_COOLDOWN_MS, DEFAULT_QUOTA_COOLDOWN_MS, TRANSIENT_COOLDOWN_MS, PoolHealthRegistry, memberKey, } from './pool-health.js';
|
|
5
5
|
export class ImageAccountPool {
|
|
6
6
|
options;
|
|
7
7
|
// Deliberately separate from chat health: image quotas/entitlements need not match chat.
|
|
@@ -24,10 +24,13 @@ export class ImageAccountPool {
|
|
|
24
24
|
throw new LlmError('image_generate: no image account is logged in', 'MISSING_CREDENTIAL');
|
|
25
25
|
}
|
|
26
26
|
const pooling = this.options.enabled !== false;
|
|
27
|
-
const members = (pooling ? accounts : accounts.slice(0, 1)).map(entry => entry.key);
|
|
28
|
-
const sticky = pooling && owner !== undefined
|
|
27
|
+
const members = (pooling ? accounts : accounts.slice(0, 1)).map((entry) => entry.key);
|
|
28
|
+
const sticky = pooling && owner !== undefined
|
|
29
|
+
? this.sticky.get(owner)?.get(provider)
|
|
30
|
+
: undefined;
|
|
29
31
|
const ordered = sticky !== undefined && members.includes(sticky)
|
|
30
|
-
? [sticky, ...members.filter(key => key !== sticky)]
|
|
32
|
+
? [sticky, ...members.filter((key) => key !== sticky)]
|
|
33
|
+
: members;
|
|
31
34
|
let lastError;
|
|
32
35
|
for (const account of ordered) {
|
|
33
36
|
signal.throwIfAborted();
|
|
@@ -60,30 +63,45 @@ export class ImageAccountPool {
|
|
|
60
63
|
}
|
|
61
64
|
// Images can be produced despite timeouts/server failures. Switch only on
|
|
62
65
|
// explicit auth/quota/entitlement rejection, never ambiguous transport/5xx.
|
|
63
|
-
failure = await httpLlmError(response, 'image_generate', {
|
|
66
|
+
failure = await httpLlmError(response, 'image_generate', {
|
|
67
|
+
rateLimitReset,
|
|
68
|
+
});
|
|
64
69
|
if (![401, 402, 403, 404, 429].includes(response.status))
|
|
65
70
|
throw failure;
|
|
66
71
|
}
|
|
67
72
|
catch (error) {
|
|
68
73
|
signal.throwIfAborted();
|
|
69
|
-
if (!(error instanceof LlmError) ||
|
|
74
|
+
if (!(error instanceof LlmError) ||
|
|
75
|
+
![
|
|
76
|
+
'AUTH',
|
|
77
|
+
'INVALID_CREDENTIAL',
|
|
78
|
+
'MISSING_CREDENTIAL',
|
|
79
|
+
'RATE_LIMIT',
|
|
80
|
+
'HTTP_402',
|
|
81
|
+
'HTTP_404',
|
|
82
|
+
].includes(error.code))
|
|
70
83
|
throw error;
|
|
71
84
|
failure = error;
|
|
72
85
|
}
|
|
73
86
|
if (!pooling)
|
|
74
87
|
throw failure;
|
|
75
|
-
const delay = failure.failure.providerRetryAfterMs ??
|
|
76
|
-
|
|
77
|
-
|
|
88
|
+
const delay = failure.failure.providerRetryAfterMs ??
|
|
89
|
+
(failure.code === 'RATE_LIMIT'
|
|
90
|
+
? DEFAULT_QUOTA_COOLDOWN_MS
|
|
91
|
+
: ['AUTH', 'INVALID_CREDENTIAL', 'MISSING_CREDENTIAL'].includes(failure.code)
|
|
92
|
+
? AUTH_COOLDOWN_MS
|
|
93
|
+
: TRANSIENT_COOLDOWN_MS);
|
|
78
94
|
this.health.markUnavailable(key, delay, failure.code);
|
|
79
95
|
this.options.onWarn?.(`image pool ${provider}: account ${members.indexOf(account) + 1}/${members.length} rejected (${failure.code}); checking remaining accounts`);
|
|
80
96
|
lastError = failure;
|
|
81
97
|
}
|
|
82
98
|
if (lastError !== undefined)
|
|
83
99
|
throw lastError;
|
|
84
|
-
const recovery = this.health.earliestRecovery(new Set(members.map(account => memberKey(provider, account, 'images'))));
|
|
100
|
+
const recovery = this.health.earliestRecovery(new Set(members.map((account) => memberKey(provider, account, 'images'))));
|
|
85
101
|
throw new LlmError(`image_generate: all ${provider} image accounts are cooling down`, 'RATE_LIMIT', {
|
|
86
|
-
...recovery === undefined
|
|
102
|
+
...(recovery === undefined
|
|
103
|
+
? {}
|
|
104
|
+
: { providerRetryAfterMs: Math.max(0, recovery - Date.now()) }),
|
|
87
105
|
});
|
|
88
106
|
}
|
|
89
107
|
}
|