dsh-subscriptions 0.2.0 → 0.2.3
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 +220 -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.d.ts +8 -4
- package/lib/auth/rpc.js +89 -25
- 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 +137 -34
- package/lib/client/SpeedSelect.d.ts +62 -14
- package/lib/client/SpeedSelect.js +261 -49
- package/lib/client/SubscriptionUsageBadge.d.ts +4 -10
- package/lib/client/SubscriptionUsageBadge.js +300 -112
- package/lib/client/SubscriptionsSection.d.ts +2 -0
- package/lib/client/SubscriptionsSection.js +265 -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 +22 -0
- package/lib/client/locales.js +73 -51
- package/lib/client/model-filter.d.ts +37 -0
- package/lib/client/model-filter.js +45 -0
- 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/usage-window-filter.d.ts +7 -0
- package/lib/client/usage-window-filter.js +12 -0
- package/lib/client.js +480 -136
- package/lib/client.js.map +1 -1
- package/lib/http.js +1 -1
- package/lib/index.d.ts +3 -3
- package/lib/index.js +167 -76
- package/lib/model-defaults.js +4 -2
- package/lib/provider-settings.d.ts +17 -0
- package/lib/provider-settings.js +79 -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 +151 -52
- 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 +27 -4
package/lib/providers/copilot.js
CHANGED
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
* fresh exchange, so the standard preempt/401-retry machinery applies
|
|
14
14
|
* unchanged.
|
|
15
15
|
*/
|
|
16
|
-
import { EMPTY_RESPONSE_CODE, errorChain, LlmAdapter, LlmError, ReasoningEffortId } from '@deepseek-ai/dsh-llm';
|
|
16
|
+
import { EMPTY_RESPONSE_CODE, errorChain, LlmAdapter, LlmError, ReasoningEffortId, } from '@deepseek-ai/dsh-llm';
|
|
17
17
|
import { resolveImages } from '../translate/resolved.js';
|
|
18
18
|
import { streamChatCompletions, toChatMessages, toChatTools, } from '../translate/chat-completions.js';
|
|
19
|
-
import { streamResponses, toResponsesInput, toResponsesTools } from '../translate/responses.js';
|
|
19
|
+
import { streamResponses, toResponsesInput, toResponsesTools, } from '../translate/responses.js';
|
|
20
20
|
import { httpLlmError, idleWatchdog, mapFetchFailure, mergeReasoning, ModelCatalogCache, discoverAcrossAccounts, discoverOrRetryAuth, isDiscoveryAborted, isMissingOrInvalidCredential, parseProviderJson, oauthEndpointError, OAuthEndpointError, } from './common.js';
|
|
21
|
-
import { AccountTokenManager, DISCOVERY_TIMEOUT_MS, unionAccountCatalogs } from './accounts.js';
|
|
21
|
+
import { AccountTokenManager, DISCOVERY_TIMEOUT_MS, unionAccountCatalogs, } from './accounts.js';
|
|
22
22
|
import { hostFetch } from '../http.js';
|
|
23
23
|
import { DEFAULT_RATE_LIMIT_WAIT, DEFAULT_RETRY, subscriptionRetryPolicy, } from './rate-limit.js';
|
|
24
24
|
/**
|
|
@@ -61,18 +61,21 @@ let vscodeVersionInflight;
|
|
|
61
61
|
* @returns a `major.minor.patch` version string.
|
|
62
62
|
*/
|
|
63
63
|
export async function latestVsCodeVersion(fetchFn = hostFetch, forceRefresh = false) {
|
|
64
|
-
if (!forceRefresh &&
|
|
65
|
-
|
|
64
|
+
if (!forceRefresh &&
|
|
65
|
+
vscodeVersionCache !== undefined &&
|
|
66
|
+
Date.now() - vscodeVersionCache.at < VSCODE_VERSION_TTL_MS) {
|
|
66
67
|
return vscodeVersionCache.version;
|
|
67
68
|
}
|
|
68
69
|
vscodeVersionInflight ??= (async () => {
|
|
69
70
|
try {
|
|
70
|
-
const response = await fetchFn(VSCODE_RELEASES_URL, {
|
|
71
|
+
const response = await fetchFn(VSCODE_RELEASES_URL, {
|
|
72
|
+
headers: { accept: 'application/json' },
|
|
73
|
+
});
|
|
71
74
|
if (!response.ok)
|
|
72
75
|
throw new Error(`HTTP ${String(response.status)}`);
|
|
73
76
|
const releases = await response.json();
|
|
74
77
|
const version = Array.isArray(releases)
|
|
75
|
-
? releases.find(entry => typeof entry === 'string' && /^\d+\.\d+\.\d+$/.test(entry))
|
|
78
|
+
? releases.find((entry) => typeof entry === 'string' && /^\d+\.\d+\.\d+$/.test(entry))
|
|
76
79
|
: undefined;
|
|
77
80
|
if (version === undefined)
|
|
78
81
|
throw new Error('no version string in the feed');
|
|
@@ -84,7 +87,9 @@ export async function latestVsCodeVersion(fetchFn = hostFetch, forceRefresh = fa
|
|
|
84
87
|
// cache, else the pinned fallback.
|
|
85
88
|
return vscodeVersionCache?.version ?? FALLBACK_VSCODE_VERSION;
|
|
86
89
|
}
|
|
87
|
-
})().finally(() => {
|
|
90
|
+
})().finally(() => {
|
|
91
|
+
vscodeVersionInflight = undefined;
|
|
92
|
+
});
|
|
88
93
|
return vscodeVersionInflight;
|
|
89
94
|
}
|
|
90
95
|
/**
|
|
@@ -123,7 +128,7 @@ export function copilotHeaders(hasVision = false, vscodeVersion = FALLBACK_VSCOD
|
|
|
123
128
|
'copilot-integration-id': 'vscode-chat',
|
|
124
129
|
'openai-intent': 'conversation-edits',
|
|
125
130
|
'x-github-api-version': '2026-06-01',
|
|
126
|
-
...hasVision ? { 'copilot-vision-request': 'true' } : {},
|
|
131
|
+
...(hasVision ? { 'copilot-vision-request': 'true' } : {}),
|
|
127
132
|
};
|
|
128
133
|
}
|
|
129
134
|
/**
|
|
@@ -137,14 +142,14 @@ export function copilotHeaders(hasVision = false, vscodeVersion = FALLBACK_VSCOD
|
|
|
137
142
|
export async function exchangeCopilotToken(githubToken, fetchFn = hostFetch) {
|
|
138
143
|
const response = await fetchFn(COPILOT_TOKEN_URL, {
|
|
139
144
|
headers: {
|
|
140
|
-
|
|
141
|
-
|
|
145
|
+
authorization: `Bearer ${githubToken}`,
|
|
146
|
+
accept: 'application/json',
|
|
142
147
|
...copilotHeaders(false, await latestVsCodeVersion(fetchFn)),
|
|
143
148
|
},
|
|
144
149
|
});
|
|
145
150
|
if (!response.ok)
|
|
146
151
|
throw await oauthEndpointError(response, 'copilot');
|
|
147
|
-
const wire = await parseProviderJson(response, 'copilot token exchange');
|
|
152
|
+
const wire = (await parseProviderJson(response, 'copilot token exchange'));
|
|
148
153
|
if (typeof wire.token !== 'string' || wire.token.length === 0) {
|
|
149
154
|
throw new Error('copilot token endpoint returned no token');
|
|
150
155
|
}
|
|
@@ -170,14 +175,14 @@ export async function completeCopilotLogin(githubToken, fetchFn = hostFetch) {
|
|
|
170
175
|
try {
|
|
171
176
|
const response = await fetchFn(GITHUB_USER_URL, {
|
|
172
177
|
headers: {
|
|
173
|
-
|
|
174
|
-
|
|
178
|
+
authorization: `Bearer ${githubToken}`,
|
|
179
|
+
accept: 'application/json',
|
|
175
180
|
// api.github.com only demands a user agent; no editor disguise needed.
|
|
176
181
|
'user-agent': 'GitHubCopilotChat/0.35.0',
|
|
177
182
|
},
|
|
178
183
|
});
|
|
179
184
|
if (response.ok) {
|
|
180
|
-
const profile = await response.json();
|
|
185
|
+
const profile = (await response.json());
|
|
181
186
|
if (typeof profile.login === 'string' && profile.login.length > 0)
|
|
182
187
|
account = profile.login;
|
|
183
188
|
}
|
|
@@ -189,7 +194,7 @@ export async function completeCopilotLogin(githubToken, fetchFn = hostFetch) {
|
|
|
189
194
|
accessToken: pair.accessToken,
|
|
190
195
|
refreshToken: githubToken,
|
|
191
196
|
expiresAt: pair.expiresAt,
|
|
192
|
-
...account === undefined ? {} : { account },
|
|
197
|
+
...(account === undefined ? {} : { account }),
|
|
193
198
|
};
|
|
194
199
|
}
|
|
195
200
|
/**
|
|
@@ -205,7 +210,7 @@ export async function refreshCopilot(session, fetchFn = hostFetch) {
|
|
|
205
210
|
accessToken: pair.accessToken,
|
|
206
211
|
refreshToken: session.refreshToken,
|
|
207
212
|
expiresAt: pair.expiresAt,
|
|
208
|
-
...session.account === undefined ? {} : { account: session.account },
|
|
213
|
+
...(session.account === undefined ? {} : { account: session.account }),
|
|
209
214
|
};
|
|
210
215
|
}
|
|
211
216
|
/**
|
|
@@ -214,11 +219,14 @@ export async function refreshCopilot(session, fetchFn = hostFetch) {
|
|
|
214
219
|
* @returns true when re-login is the only fix (GitHub token revoked or the subscription lost).
|
|
215
220
|
*/
|
|
216
221
|
export function isCopilotPermanentRefreshError(error) {
|
|
217
|
-
return error instanceof OAuthEndpointError &&
|
|
222
|
+
return (error instanceof OAuthEndpointError &&
|
|
223
|
+
(error.status === 401 || error.status === 403));
|
|
218
224
|
}
|
|
219
225
|
/** Display name for one Copilot wire reasoning-effort value. */
|
|
220
226
|
function copilotEffortName(effort) {
|
|
221
|
-
return effort === 'xhigh'
|
|
227
|
+
return effort === 'xhigh'
|
|
228
|
+
? 'Extra High'
|
|
229
|
+
: effort.charAt(0).toUpperCase() + effort.slice(1);
|
|
222
230
|
}
|
|
223
231
|
/**
|
|
224
232
|
* Map a catalog entry's `supports.reasoning_effort` array into selectable
|
|
@@ -236,7 +244,10 @@ function copilotReasoning(entry) {
|
|
|
236
244
|
if (typeof value !== 'string' || value.length === 0 || seen.has(value))
|
|
237
245
|
continue;
|
|
238
246
|
seen.add(value);
|
|
239
|
-
efforts.push({
|
|
247
|
+
efforts.push({
|
|
248
|
+
id: ReasoningEffortId(value),
|
|
249
|
+
name: copilotEffortName(value),
|
|
250
|
+
});
|
|
240
251
|
}
|
|
241
252
|
return efforts.length > 0 ? { efforts } : undefined;
|
|
242
253
|
}
|
|
@@ -260,23 +271,26 @@ function copilotReasoning(entry) {
|
|
|
260
271
|
export async function fetchCopilotModels(session, fetchFn = hostFetch, signal) {
|
|
261
272
|
const response = await fetchFn(COPILOT_MODELS_URL, {
|
|
262
273
|
headers: {
|
|
263
|
-
|
|
264
|
-
|
|
274
|
+
authorization: `Bearer ${session.accessToken}`,
|
|
275
|
+
accept: 'application/json',
|
|
265
276
|
...copilotHeaders(false, await latestVsCodeVersion(fetchFn)),
|
|
266
277
|
},
|
|
267
|
-
...signal === undefined ? {} : { signal },
|
|
278
|
+
...(signal === undefined ? {} : { signal }),
|
|
268
279
|
});
|
|
269
280
|
if (!response.ok)
|
|
270
281
|
throw await oauthEndpointError(response, 'copilot models');
|
|
271
|
-
const payload = await parseProviderJson(response, 'copilot models');
|
|
282
|
+
const payload = (await parseProviderJson(response, 'copilot models'));
|
|
272
283
|
if (!Array.isArray(payload.data))
|
|
273
284
|
throw new Error('copilot models endpoint returned no data array');
|
|
274
285
|
const seen = new Set();
|
|
275
286
|
const discovered = [];
|
|
276
287
|
for (const entry of payload.data) {
|
|
277
|
-
if (typeof entry.id !== 'string' ||
|
|
288
|
+
if (typeof entry.id !== 'string' ||
|
|
289
|
+
entry.id.length === 0 ||
|
|
290
|
+
seen.has(entry.id))
|
|
278
291
|
continue;
|
|
279
|
-
if (entry.model_picker_enabled !== true ||
|
|
292
|
+
if (entry.model_picker_enabled !== true ||
|
|
293
|
+
entry.policy?.state === 'disabled')
|
|
280
294
|
continue;
|
|
281
295
|
let wire;
|
|
282
296
|
let responsesSupported = false;
|
|
@@ -293,15 +307,19 @@ export async function fetchCopilotModels(session, fetchFn = hostFetch, signal) {
|
|
|
293
307
|
const reasoning = copilotReasoning(entry);
|
|
294
308
|
discovered.push({
|
|
295
309
|
id: entry.id,
|
|
296
|
-
name: typeof entry.name === 'string' && entry.name.length > 0
|
|
297
|
-
|
|
298
|
-
|
|
310
|
+
name: typeof entry.name === 'string' && entry.name.length > 0
|
|
311
|
+
? entry.name
|
|
312
|
+
: entry.id,
|
|
313
|
+
...(typeof entry.capabilities?.limits?.max_context_window_tokens ===
|
|
314
|
+
'number' && entry.capabilities.limits.max_context_window_tokens > 0
|
|
299
315
|
? { contextWindow: entry.capabilities.limits.max_context_window_tokens }
|
|
300
|
-
: {},
|
|
301
|
-
inputModalities: entry.capabilities?.supports?.vision === true
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
...
|
|
316
|
+
: {}),
|
|
317
|
+
inputModalities: entry.capabilities?.supports?.vision === true
|
|
318
|
+
? ['text', 'image']
|
|
319
|
+
: ['text'],
|
|
320
|
+
...(reasoning === undefined ? {} : { reasoning }),
|
|
321
|
+
...(wire === undefined ? {} : { copilotWire: wire }),
|
|
322
|
+
...(responsesSupported ? { copilotResponses: true } : {}),
|
|
305
323
|
});
|
|
306
324
|
}
|
|
307
325
|
// An empty catalog from a 200 response is treated as a discovery failure so
|
|
@@ -343,7 +361,8 @@ export function copilotRequestWire(entry, options) {
|
|
|
343
361
|
return wire;
|
|
344
362
|
if (options.tools === undefined || options.tools.length === 0)
|
|
345
363
|
return wire;
|
|
346
|
-
if (options.reasoningEffort === undefined ||
|
|
364
|
+
if (options.reasoningEffort === undefined ||
|
|
365
|
+
options.reasoningEffort === 'none')
|
|
347
366
|
return wire;
|
|
348
367
|
return 'responses';
|
|
349
368
|
}
|
|
@@ -360,16 +379,18 @@ export function copilotChatRequestBody(options, messages) {
|
|
|
360
379
|
return {
|
|
361
380
|
model: options.model,
|
|
362
381
|
messages,
|
|
363
|
-
...options.tools !== undefined && options.tools.length > 0
|
|
382
|
+
...(options.tools !== undefined && options.tools.length > 0
|
|
364
383
|
? { tools: toChatTools(options.tools), tool_choice: 'auto' }
|
|
365
|
-
: {},
|
|
366
|
-
...options.maxTokens !== undefined
|
|
384
|
+
: {}),
|
|
385
|
+
...(options.maxTokens !== undefined
|
|
386
|
+
? { max_completion_tokens: options.maxTokens }
|
|
387
|
+
: {}),
|
|
367
388
|
// The harness only passes an effort the resolved model advertised (the
|
|
368
389
|
// catalog's reasoning_effort array), and copilotRequestWire keeps the
|
|
369
390
|
// tools+effort combination off this wire for models that reject it.
|
|
370
|
-
...options.reasoningEffort !== undefined
|
|
391
|
+
...(options.reasoningEffort !== undefined
|
|
371
392
|
? { reasoning_effort: String(options.reasoningEffort) }
|
|
372
|
-
: {},
|
|
393
|
+
: {}),
|
|
373
394
|
// The upstream is stream-only; usage arrives on the terminal chunk.
|
|
374
395
|
stream: true,
|
|
375
396
|
stream_options: { include_usage: true },
|
|
@@ -385,17 +406,24 @@ export function copilotChatRequestBody(options, messages) {
|
|
|
385
406
|
export function copilotResponsesRequestBody(options, resolved) {
|
|
386
407
|
return {
|
|
387
408
|
model: options.model,
|
|
388
|
-
...resolved.instructions !== undefined
|
|
409
|
+
...(resolved.instructions !== undefined
|
|
410
|
+
? { instructions: resolved.instructions }
|
|
411
|
+
: {}),
|
|
389
412
|
input: resolved.input,
|
|
390
|
-
...options.tools !== undefined && options.tools.length > 0
|
|
391
|
-
? {
|
|
392
|
-
|
|
393
|
-
|
|
413
|
+
...(options.tools !== undefined && options.tools.length > 0
|
|
414
|
+
? {
|
|
415
|
+
tools: toResponsesTools(options.tools, { strict: false }),
|
|
416
|
+
tool_choice: 'auto',
|
|
417
|
+
}
|
|
418
|
+
: {}),
|
|
419
|
+
...(options.maxTokens !== undefined
|
|
420
|
+
? { max_output_tokens: options.maxTokens }
|
|
421
|
+
: {}),
|
|
394
422
|
// The Responses wire spells the effort nested; only advertised efforts
|
|
395
423
|
// ever reach this branch (see copilotChatRequestBody).
|
|
396
|
-
...options.reasoningEffort !== undefined
|
|
424
|
+
...(options.reasoningEffort !== undefined
|
|
397
425
|
? { reasoning: { effort: String(options.reasoningEffort) } }
|
|
398
|
-
: {},
|
|
426
|
+
: {}),
|
|
399
427
|
// [2026-08-23]-[a reasoning model continuing a tool chain must replay its
|
|
400
428
|
// encrypted reasoning on the next request, and the blobs only arrive when
|
|
401
429
|
// asked for; for non-reasoning models the include is a no-op]
|
|
@@ -413,15 +441,18 @@ export function copilotResponsesRequestBody(options, resolved) {
|
|
|
413
441
|
* invalid input item.
|
|
414
442
|
*/
|
|
415
443
|
function completedReasoningItem(item) {
|
|
416
|
-
if (typeof item.encrypted_content !== 'string' ||
|
|
444
|
+
if (typeof item.encrypted_content !== 'string' ||
|
|
445
|
+
item.encrypted_content.length === 0)
|
|
417
446
|
return undefined;
|
|
418
447
|
if (typeof item.id !== 'string' || item.id.length === 0)
|
|
419
448
|
return undefined;
|
|
420
449
|
return {
|
|
421
450
|
type: 'reasoning',
|
|
422
451
|
id: item.id,
|
|
423
|
-
...Array.isArray(item.summary) ? { summary: item.summary } : {},
|
|
424
|
-
...typeof item.status === 'string' && item.status.length > 0
|
|
452
|
+
...(Array.isArray(item.summary) ? { summary: item.summary } : {}),
|
|
453
|
+
...(typeof item.status === 'string' && item.status.length > 0
|
|
454
|
+
? { status: item.status }
|
|
455
|
+
: {}),
|
|
425
456
|
encrypted_content: item.encrypted_content,
|
|
426
457
|
};
|
|
427
458
|
}
|
|
@@ -484,7 +515,9 @@ export class CopilotResponsesItemNormalizer {
|
|
|
484
515
|
: `copilot-item-${String(this.adds)}`;
|
|
485
516
|
this.lastKey = key;
|
|
486
517
|
const item = event.item;
|
|
487
|
-
if (item?.type === 'function_call' &&
|
|
518
|
+
if (item?.type === 'function_call' &&
|
|
519
|
+
typeof item.call_id === 'string' &&
|
|
520
|
+
item.call_id.length > 0) {
|
|
488
521
|
this.capturedCallIds.push(item.call_id);
|
|
489
522
|
}
|
|
490
523
|
return item === undefined
|
|
@@ -507,7 +540,8 @@ export class CopilotResponsesItemNormalizer {
|
|
|
507
540
|
if (event.type === 'response.completed') {
|
|
508
541
|
// Both sides present is the only replayable response; clear either way —
|
|
509
542
|
// one SSE stream may carry multiple responses.
|
|
510
|
-
if (this.capturedCallIds.length > 0 &&
|
|
543
|
+
if (this.capturedCallIds.length > 0 &&
|
|
544
|
+
this.capturedReasoning.length > 0) {
|
|
511
545
|
this.onCaptured?.(this.capturedCallIds, this.capturedReasoning);
|
|
512
546
|
}
|
|
513
547
|
this.capturedCallIds = [];
|
|
@@ -558,7 +592,9 @@ export class CopilotAdapter extends LlmAdapter {
|
|
|
558
592
|
this.accountCatalogs.clear();
|
|
559
593
|
else
|
|
560
594
|
this.accountCatalogs.delete(account);
|
|
561
|
-
if (account === undefined ||
|
|
595
|
+
if (account === undefined ||
|
|
596
|
+
this.catalogOwner === account ||
|
|
597
|
+
this.catalogOwner === undefined) {
|
|
562
598
|
this.catalogOwner = undefined;
|
|
563
599
|
this.catalog.invalidate();
|
|
564
600
|
}
|
|
@@ -588,7 +624,7 @@ export class CopilotAdapter extends LlmAdapter {
|
|
|
588
624
|
return subscriptionRetryPolicy(DEFAULT_RETRY, this.options.rateLimit ?? DEFAULT_RATE_LIMIT_WAIT, `copilot: provider "${provider}" retryPolicy`);
|
|
589
625
|
}
|
|
590
626
|
staticModels(provider) {
|
|
591
|
-
return this.options.models.map(model => ({
|
|
627
|
+
return this.options.models.map((model) => ({
|
|
592
628
|
provider,
|
|
593
629
|
id: model.id,
|
|
594
630
|
name: model.name ?? model.id,
|
|
@@ -601,19 +637,22 @@ export class CopilotAdapter extends LlmAdapter {
|
|
|
601
637
|
if (pool === undefined)
|
|
602
638
|
return own;
|
|
603
639
|
const extra = await pool.modelsForProvider(provider);
|
|
604
|
-
const seen = new Set(own.map(model => model.id));
|
|
640
|
+
const seen = new Set(own.map((model) => model.id));
|
|
605
641
|
// Account pools reuse the catalog row; only configured tiers are extra.
|
|
606
|
-
return [...own, ...extra.filter(model => !seen.has(model.id))];
|
|
642
|
+
return [...own, ...extra.filter((model) => !seen.has(model.id))];
|
|
607
643
|
}
|
|
608
644
|
/** The provider's own catalog: union of every account, or one account when named. */
|
|
609
645
|
async listOwnModels(provider, account, signal) {
|
|
610
646
|
if (account === undefined) {
|
|
611
|
-
const accounts = (await this.options.tokens.list()).map(entry => entry.key);
|
|
647
|
+
const accounts = (await this.options.tokens.list()).map((entry) => entry.key);
|
|
612
648
|
if (accounts.length === 0)
|
|
613
649
|
return [];
|
|
614
|
-
return unionAccountCatalogs(accounts, (key, accountSignal) => this.listOwnModels(provider, key, accountSignal), {
|
|
650
|
+
return unionAccountCatalogs(accounts, (key, accountSignal) => this.listOwnModels(provider, key, accountSignal), {
|
|
651
|
+
timeoutMs: DISCOVERY_TIMEOUT_MS,
|
|
652
|
+
...(signal === undefined ? {} : { signal }),
|
|
653
|
+
});
|
|
615
654
|
}
|
|
616
|
-
if (!await this.options.tokens.hasSession(account)) {
|
|
655
|
+
if (!(await this.options.tokens.hasSession(account))) {
|
|
617
656
|
return [];
|
|
618
657
|
}
|
|
619
658
|
if (!this.options.discovery)
|
|
@@ -623,13 +662,17 @@ export class CopilotAdapter extends LlmAdapter {
|
|
|
623
662
|
// The fetcher runs only on a cache miss, and resolves the session
|
|
624
663
|
// through the refresh-aware path so an expired access token renews here
|
|
625
664
|
// instead of failing discovery into the static fallback.
|
|
626
|
-
const discovered = await discoverOrRetryAuth(force => this.options.tokens.session(account, force), catalog, () => catalog.get(() => this.fetchCatalog(account, signal)));
|
|
627
|
-
return discovered.map(model => ({
|
|
665
|
+
const discovered = await discoverOrRetryAuth((force) => this.options.tokens.session(account, force), catalog, () => catalog.get(() => this.fetchCatalog(account, signal)));
|
|
666
|
+
return discovered.map((model) => ({
|
|
628
667
|
provider,
|
|
629
668
|
id: model.id,
|
|
630
669
|
name: model.name,
|
|
631
|
-
...model.description === undefined
|
|
632
|
-
|
|
670
|
+
...(model.description === undefined
|
|
671
|
+
? {}
|
|
672
|
+
: { description: model.description }),
|
|
673
|
+
...(model.inputModalities === undefined
|
|
674
|
+
? {}
|
|
675
|
+
: { inputModalities: model.inputModalities }),
|
|
633
676
|
}));
|
|
634
677
|
}
|
|
635
678
|
catch (error) {
|
|
@@ -652,11 +695,11 @@ export class CopilotAdapter extends LlmAdapter {
|
|
|
652
695
|
async discovered(model) {
|
|
653
696
|
if (!this.options.discovery)
|
|
654
697
|
return undefined;
|
|
655
|
-
const accounts = (await this.options.tokens.list()).map(entry => entry.key);
|
|
698
|
+
const accounts = (await this.options.tokens.list()).map((entry) => entry.key);
|
|
656
699
|
return discoverAcrossAccounts(accounts, async (account) => {
|
|
657
700
|
const catalog = await this.catalogFor(account);
|
|
658
701
|
const models = await catalog.resolve(() => this.fetchCatalog(account));
|
|
659
|
-
return models?.find(entry => entry.id === model);
|
|
702
|
+
return models?.find((entry) => entry.id === model);
|
|
660
703
|
});
|
|
661
704
|
}
|
|
662
705
|
/**
|
|
@@ -667,10 +710,14 @@ export class CopilotAdapter extends LlmAdapter {
|
|
|
667
710
|
* `models[].wire` now routes the request even without discovery]
|
|
668
711
|
*/
|
|
669
712
|
configuredWireEntry(model) {
|
|
670
|
-
const configured = this.options.models.find(entry => entry.id === model);
|
|
713
|
+
const configured = this.options.models.find((entry) => entry.id === model);
|
|
671
714
|
return configured?.wire === undefined
|
|
672
715
|
? undefined
|
|
673
|
-
: {
|
|
716
|
+
: {
|
|
717
|
+
id: configured.id,
|
|
718
|
+
name: configured.name ?? configured.id,
|
|
719
|
+
copilotWire: configured.wire,
|
|
720
|
+
};
|
|
674
721
|
}
|
|
675
722
|
/**
|
|
676
723
|
* The replay scope isolating one ACCOUNT × CONVERSATION × MODEL. The
|
|
@@ -779,7 +826,8 @@ export class CopilotAdapter extends LlmAdapter {
|
|
|
779
826
|
}
|
|
780
827
|
async resolveModel(provider, model) {
|
|
781
828
|
const pool = this.options.pool?.();
|
|
782
|
-
if (pool !== undefined &&
|
|
829
|
+
if (pool !== undefined &&
|
|
830
|
+
(await pool.owns(provider, model))) {
|
|
783
831
|
return pool.resolveModel(provider, model);
|
|
784
832
|
}
|
|
785
833
|
return this.resolveOwnModel(provider, model);
|
|
@@ -787,7 +835,7 @@ export class CopilotAdapter extends LlmAdapter {
|
|
|
787
835
|
/** Capability resolution of the provider's own models (the pool resolves members here). */
|
|
788
836
|
async resolveOwnModel(provider, model) {
|
|
789
837
|
const discovered = await this.discovered(model);
|
|
790
|
-
const configured = this.options.models.find(entry => entry.id === model);
|
|
838
|
+
const configured = this.options.models.find((entry) => entry.id === model);
|
|
791
839
|
// Efforts come from the discovered catalog's reasoning_effort array; a
|
|
792
840
|
// model that did not advertise one exposes none, so the harness rejects
|
|
793
841
|
// an explicit effort before provider I/O instead of the API 400ing
|
|
@@ -799,16 +847,24 @@ export class CopilotAdapter extends LlmAdapter {
|
|
|
799
847
|
provider,
|
|
800
848
|
id: model,
|
|
801
849
|
name: discovered?.name ?? configured?.name ?? model,
|
|
802
|
-
...discovered?.description === undefined
|
|
803
|
-
|
|
804
|
-
|
|
850
|
+
...(discovered?.description === undefined
|
|
851
|
+
? {}
|
|
852
|
+
: { description: discovered.description }),
|
|
853
|
+
inputModalities: discovered?.inputModalities ??
|
|
854
|
+
configured?.inputModalities ?? ['text'],
|
|
855
|
+
context: {
|
|
856
|
+
contextWindow: discovered?.contextWindow ??
|
|
857
|
+
configured?.contextWindow ??
|
|
858
|
+
COPILOT_CONTEXT_WINDOW,
|
|
859
|
+
},
|
|
805
860
|
defaultMaxTokens: configured?.maxTokens ?? COPILOT_DEFAULT_MAX_TOKENS,
|
|
806
|
-
...reasoning === undefined ? {} : { reasoning },
|
|
861
|
+
...(reasoning === undefined ? {} : { reasoning }),
|
|
807
862
|
};
|
|
808
863
|
}
|
|
809
864
|
async *stream(options) {
|
|
810
865
|
const pool = this.options.pool?.();
|
|
811
|
-
if (pool !== undefined &&
|
|
866
|
+
if (pool !== undefined &&
|
|
867
|
+
(await pool.owns(options.provider, options.model))) {
|
|
812
868
|
yield* pool.stream(options);
|
|
813
869
|
return;
|
|
814
870
|
}
|
|
@@ -826,7 +882,8 @@ export class CopilotAdapter extends LlmAdapter {
|
|
|
826
882
|
// dual-protocol models reroute there once the request combines function
|
|
827
883
|
// tools with a reasoning effort (gpt-5.4 400s on the chat wire then).
|
|
828
884
|
// A configured `wire` outranks the catalog (see configuredWireEntry).
|
|
829
|
-
const wire = copilotRequestWire(this.configuredWireEntry(options.model) ??
|
|
885
|
+
const wire = copilotRequestWire(this.configuredWireEntry(options.model) ??
|
|
886
|
+
(await this.discovered(options.model)), options);
|
|
830
887
|
let session = await this.options.tokens.session(account);
|
|
831
888
|
// Replay scope: account identity × conversation × model (see
|
|
832
889
|
// replayScope); a Copilot-token refresh preserves the GitHub token, so
|
|
@@ -847,19 +904,23 @@ export class CopilotAdapter extends LlmAdapter {
|
|
|
847
904
|
// Copilot has no provider-specific reset reader yet. The shared
|
|
848
905
|
// mapper still honors its generic retry-after header and warns with
|
|
849
906
|
// rate-limit-shaped headers/body when GitHub sends another signal.
|
|
850
|
-
...this.options.onWarn === undefined
|
|
907
|
+
...(this.options.onWarn === undefined
|
|
908
|
+
? {}
|
|
909
|
+
: { onWarn: this.options.onWarn }),
|
|
851
910
|
});
|
|
852
911
|
}
|
|
853
912
|
if (response.body === null) {
|
|
854
913
|
throw new LlmError('copilot API returned no response body', EMPTY_RESPONSE_CODE);
|
|
855
914
|
}
|
|
856
|
-
const pulse = () => {
|
|
915
|
+
const pulse = () => {
|
|
916
|
+
watchdog.pulse();
|
|
917
|
+
};
|
|
857
918
|
if (wire === 'responses') {
|
|
858
919
|
// The normalizer doubles as the capture point for completed reasoning.
|
|
859
920
|
const normalizer = new CopilotResponsesItemNormalizer((callIds, items) => {
|
|
860
921
|
this.captureReasoning(scope, callIds, items);
|
|
861
922
|
});
|
|
862
|
-
yield* streamResponses(response.body, pulse, event => normalizer.push(event));
|
|
923
|
+
yield* streamResponses(response.body, pulse, (event) => normalizer.push(event));
|
|
863
924
|
}
|
|
864
925
|
else {
|
|
865
926
|
yield* streamChatCompletions(response.body, pulse);
|
|
@@ -874,17 +935,17 @@ export class CopilotAdapter extends LlmAdapter {
|
|
|
874
935
|
}
|
|
875
936
|
async request(options, session, signal, wire, replayScopeKey) {
|
|
876
937
|
const messages = await resolveImages(options.messages, this.options.resolveAttachments?.(), signal);
|
|
877
|
-
const hasVision = messages.some(message => message.content.some(block => block.type === 'image'));
|
|
938
|
+
const hasVision = messages.some((message) => message.content.some((block) => block.type === 'image'));
|
|
878
939
|
const body = wire === 'responses'
|
|
879
940
|
? copilotResponsesRequestBody(options, toResponsesInput(messages, options.system,
|
|
880
941
|
// Captured completed reasoning replays ahead of its tool call.
|
|
881
|
-
callId => this.replayFor(replayScopeKey, callId)))
|
|
942
|
+
(callId) => this.replayFor(replayScopeKey, callId)))
|
|
882
943
|
: copilotChatRequestBody(options, toChatMessages(messages, options.system));
|
|
883
944
|
return hostFetch(wire === 'responses' ? COPILOT_RESPONSES_URL : COPILOT_API_URL, {
|
|
884
945
|
method: 'POST',
|
|
885
946
|
headers: {
|
|
886
|
-
|
|
887
|
-
|
|
947
|
+
authorization: `Bearer ${session.accessToken}`,
|
|
948
|
+
accept: 'text/event-stream',
|
|
888
949
|
'content-type': 'application/json',
|
|
889
950
|
...copilotHeaders(hasVision, await latestVsCodeVersion(this.options.fetchFn ?? hostFetch)),
|
|
890
951
|
},
|
|
@@ -4,16 +4,18 @@ import { CursorAdapter } from 'dsh-subscriptions/cursor-transport';
|
|
|
4
4
|
* blocks. DSH 0.1.7 carries them as first-class role=tool messages instead.
|
|
5
5
|
*/
|
|
6
6
|
export function projectCursorMessages(messages) {
|
|
7
|
-
return messages.map(message => message.role === 'tool'
|
|
7
|
+
return messages.map((message) => message.role === 'tool'
|
|
8
8
|
? {
|
|
9
9
|
...message,
|
|
10
10
|
role: 'user',
|
|
11
|
-
content: [
|
|
11
|
+
content: [
|
|
12
|
+
{
|
|
12
13
|
type: 'tool-result',
|
|
13
14
|
toolCallId: message.toolCallId,
|
|
14
15
|
isError: message.isError,
|
|
15
16
|
content: message.content,
|
|
16
|
-
}
|
|
17
|
+
},
|
|
18
|
+
],
|
|
17
19
|
}
|
|
18
20
|
: message);
|
|
19
21
|
}
|
|
@@ -23,7 +25,11 @@ export function projectCursorMessages(messages) {
|
|
|
23
25
|
* carry a `classifyCursorError` code or `CURSOR_PROTOCOL`, never these, so the
|
|
24
26
|
* code is a sound discriminator for them.
|
|
25
27
|
*/
|
|
26
|
-
const LOCAL_VENDOR_CODES = new Set([
|
|
28
|
+
const LOCAL_VENDOR_CODES = new Set([
|
|
29
|
+
'UNSUPPORTED_OPTION',
|
|
30
|
+
'UNSUPPORTED_CONTENT',
|
|
31
|
+
'TOOL_LIMIT',
|
|
32
|
+
]);
|
|
27
33
|
/**
|
|
28
34
|
* Plain-error messages the vendor throws as fixed literals (`vendor/cursor/
|
|
29
35
|
* index.js`: `waitForResponse`, the request write, the tool-continuation write,
|
|
@@ -70,19 +76,30 @@ const LOCAL_VENDOR_PATTERNS = [
|
|
|
70
76
|
* protocol errors) are not recognised and stay replaced.
|
|
71
77
|
*/
|
|
72
78
|
const LOCAL_SYSTEM_ERRNO = new Set([
|
|
73
|
-
'ECONNREFUSED',
|
|
74
|
-
'
|
|
79
|
+
'ECONNREFUSED',
|
|
80
|
+
'ECONNRESET',
|
|
81
|
+
'ECONNABORTED',
|
|
82
|
+
'ENOTFOUND',
|
|
83
|
+
'EAI_AGAIN',
|
|
84
|
+
'ETIMEDOUT',
|
|
85
|
+
'ENETUNREACH',
|
|
86
|
+
'ENETDOWN',
|
|
87
|
+
'EHOSTUNREACH',
|
|
88
|
+
'EHOSTDOWN',
|
|
89
|
+
'EPIPE',
|
|
75
90
|
]);
|
|
76
91
|
function errorCode(value) {
|
|
77
|
-
return typeof value === 'object' && value !== null
|
|
92
|
+
return typeof value === 'object' && value !== null
|
|
93
|
+
? value.code
|
|
94
|
+
: undefined;
|
|
78
95
|
}
|
|
79
96
|
function isLocalSystemError(error) {
|
|
80
97
|
if (!(error instanceof Error))
|
|
81
98
|
return false;
|
|
82
99
|
const own = errorCode(error);
|
|
83
100
|
const cause = errorCode(error.cause);
|
|
84
|
-
return (typeof own === 'string' && LOCAL_SYSTEM_ERRNO.has(own))
|
|
85
|
-
|
|
101
|
+
return ((typeof own === 'string' && LOCAL_SYSTEM_ERRNO.has(own)) ||
|
|
102
|
+
(typeof cause === 'string' && LOCAL_SYSTEM_ERRNO.has(cause)));
|
|
86
103
|
}
|
|
87
104
|
function messageOf(error) {
|
|
88
105
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -123,7 +140,8 @@ class LocalFailureMessages {
|
|
|
123
140
|
wrapAttachments(resolve) {
|
|
124
141
|
return () => {
|
|
125
142
|
const store = resolve();
|
|
126
|
-
const readImage = store
|
|
143
|
+
const readImage = store
|
|
144
|
+
?.readImage;
|
|
127
145
|
if (typeof readImage !== 'function')
|
|
128
146
|
return store;
|
|
129
147
|
return Object.create(store, {
|
|
@@ -184,7 +202,8 @@ class LocalFailureMessages {
|
|
|
184
202
|
}
|
|
185
203
|
}
|
|
186
204
|
function isVendorLocalMessage(message) {
|
|
187
|
-
return LOCAL_VENDOR_MESSAGES.has(message) ||
|
|
205
|
+
return (LOCAL_VENDOR_MESSAGES.has(message) ||
|
|
206
|
+
LOCAL_VENDOR_PATTERNS.some((pattern) => pattern.test(message)));
|
|
188
207
|
}
|
|
189
208
|
/**
|
|
190
209
|
* Cursor adapter for the DSH host.
|
|
@@ -230,7 +249,9 @@ export class CursorCompatAdapter extends CursorAdapter {
|
|
|
230
249
|
auth: localFailures.wrap(options.auth),
|
|
231
250
|
...(options.resolveAttachments === undefined
|
|
232
251
|
? {}
|
|
233
|
-
: {
|
|
252
|
+
: {
|
|
253
|
+
resolveAttachments: localFailures.wrapAttachments(options.resolveAttachments),
|
|
254
|
+
}),
|
|
234
255
|
});
|
|
235
256
|
this.localFailures = localFailures;
|
|
236
257
|
this.visibleModels = options.visibleModels;
|
|
@@ -238,12 +259,14 @@ export class CursorCompatAdapter extends CursorAdapter {
|
|
|
238
259
|
// `AgentRun` factory) in its constructor but does not type it as a member.
|
|
239
260
|
const vendor = this;
|
|
240
261
|
const createRun = vendor.createAgentRun;
|
|
241
|
-
vendor.createAgentRun = access => localFailures.observeRun(createRun(access));
|
|
262
|
+
vendor.createAgentRun = (access) => localFailures.observeRun(createRun(access));
|
|
242
263
|
}
|
|
243
264
|
async listModels(provider) {
|
|
244
265
|
const models = await super.listModels(provider);
|
|
245
266
|
const visible = this.visibleModels?.();
|
|
246
|
-
return visible === undefined
|
|
267
|
+
return visible === undefined
|
|
268
|
+
? models
|
|
269
|
+
: models.filter((model) => visible.includes(model.id));
|
|
247
270
|
}
|
|
248
271
|
async *stream(options) {
|
|
249
272
|
for await (const chunk of super.stream({
|
|
@@ -252,10 +275,11 @@ export class CursorCompatAdapter extends CursorAdapter {
|
|
|
252
275
|
model: options.model === 'composer-2' ? 'composer-2.5' : options.model,
|
|
253
276
|
messages: projectCursorMessages(options.messages),
|
|
254
277
|
})) {
|
|
255
|
-
if (chunk.type === 'finish' &&
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
278
|
+
if (chunk.type === 'finish' &&
|
|
279
|
+
chunk.reason.kind === 'error' &&
|
|
280
|
+
!LOCAL_VENDOR_CODES.has(chunk.reason.failure.code) &&
|
|
281
|
+
!isVendorLocalMessage(chunk.reason.failure.message) &&
|
|
282
|
+
!this.localFailures.has(chunk.reason.failure.message)) {
|
|
259
283
|
// The vendor uses raw headers, trailers and end-stream errors for protocol
|
|
260
284
|
// checks and classification before this boundary. Keep its code and retry
|
|
261
285
|
// decisions; do not reclassify the replacement text. "Safe" here means no
|
|
@@ -29,7 +29,7 @@ export declare class CursorAuth {
|
|
|
29
29
|
constructor(credentials: CursorCredentialService, http?: typeof fetch, now?: () => number);
|
|
30
30
|
private read;
|
|
31
31
|
status(): Promise<CursorAuthStatus>;
|
|
32
|
-
accessToken({ signal }?: {
|
|
32
|
+
accessToken({ signal, }?: {
|
|
33
33
|
signal?: AbortSignal;
|
|
34
34
|
}): Promise<string>;
|
|
35
35
|
private refresh;
|