codex-copilot-dx 0.5.5 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -1
- package/bin/cli.mjs +73 -91
- package/package.json +1 -1
- package/src/adapter.mjs +56 -14
- package/src/auth-profile.mjs +205 -0
- package/src/cli-auth.mjs +493 -0
- package/src/cli-models.mjs +189 -0
- package/src/cli-options.mjs +94 -9
- package/src/cli-status.mjs +29 -3
- package/src/copilot-client.mjs +226 -0
- package/src/copilot-token-session.mjs +362 -0
- package/src/copilot.mjs +45 -387
- package/src/doctor.mjs +169 -23
- package/src/model-cache.mjs +22 -6
- package/src/observability.mjs +53 -3
- package/src/profile-model-runtime.mjs +261 -0
- package/src/profile-runtime.mjs +80 -0
- package/src/responses-handler.mjs +7 -6
package/src/copilot.mjs
CHANGED
|
@@ -1,26 +1,7 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
1
|
import { randomUUID } from "node:crypto";
|
|
4
2
|
import { status } from "./status.mjs";
|
|
5
3
|
import { debugLog } from "./log.mjs";
|
|
6
|
-
import {
|
|
7
|
-
fetchGithubIdentity,
|
|
8
|
-
githubReauthMessage,
|
|
9
|
-
githubTokenPath,
|
|
10
|
-
importDiscoveredGithubToken,
|
|
11
|
-
readGithubTokenMetadata,
|
|
12
|
-
writeGithubTokenMetadata,
|
|
13
|
-
} from "./auth.mjs";
|
|
14
|
-
import {
|
|
15
|
-
githubIdentitiesEqual,
|
|
16
|
-
githubIdentityLabel,
|
|
17
|
-
githubTokenFingerprint,
|
|
18
|
-
normalizeGithubIdentity,
|
|
19
|
-
} from "./github-identity.mjs";
|
|
20
|
-
import { prepareResponsesPayload, summarizeReqBody } from "./image-optimization.mjs";
|
|
21
|
-
import { enforceResponsesPayloadByteBudget } from "./responses-byte-budget.mjs";
|
|
22
|
-
import { withChatStreamUsage } from "./stream-contract.mjs";
|
|
23
|
-
import { markUpstreamStarted } from "./stream-performance.mjs";
|
|
4
|
+
import { createCopilotClientRuntime } from "./copilot-client.mjs";
|
|
24
5
|
|
|
25
6
|
export {
|
|
26
7
|
optimizeImageDataUrl,
|
|
@@ -33,35 +14,6 @@ export {
|
|
|
33
14
|
} from "./image-optimization.mjs";
|
|
34
15
|
|
|
35
16
|
export const DEFAULT_API_BASE = "https://api.githubcopilot.com";
|
|
36
|
-
let apiBase = DEFAULT_API_BASE;
|
|
37
|
-
|
|
38
|
-
// Cache of model id -> supported_endpoints, populated from listModels(). Used to
|
|
39
|
-
// route requests to /responses vs /chat/completions based on real model metadata.
|
|
40
|
-
let modelEndpointCache = new Map();
|
|
41
|
-
|
|
42
|
-
export function cacheModelEndpoints(models) {
|
|
43
|
-
const data = Array.isArray(models) ? models : models?.data;
|
|
44
|
-
if (!Array.isArray(data)) return false;
|
|
45
|
-
const next = new Map();
|
|
46
|
-
for (const model of data) {
|
|
47
|
-
const id = String(model?.id || "").trim();
|
|
48
|
-
if (id && Array.isArray(model?.supported_endpoints)) {
|
|
49
|
-
next.set(id, [...model.supported_endpoints]);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
if (!next.size) return false;
|
|
53
|
-
modelEndpointCache = next;
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Returns the cached supported_endpoints for a model id, or null if unknown.
|
|
58
|
-
export function getCachedModelEndpoints(modelId) {
|
|
59
|
-
return modelEndpointCache.get(String(modelId || "").trim()) || null;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function resetModelEndpointCacheForTests() {
|
|
63
|
-
modelEndpointCache.clear();
|
|
64
|
-
}
|
|
65
17
|
const DEFAULT_UPSTREAM_RETRIES = 2;
|
|
66
18
|
const MAX_UPSTREAM_RETRIES = 5;
|
|
67
19
|
const DEFAULT_UPSTREAM_RETRY_DELAY_MS = 300;
|
|
@@ -85,14 +37,9 @@ export function parseApiBase(data) {
|
|
|
85
37
|
: DEFAULT_API_BASE;
|
|
86
38
|
}
|
|
87
39
|
|
|
88
|
-
export function getApiBase() {
|
|
89
|
-
return apiBase;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
40
|
export function responsesEndpointPath() {
|
|
93
41
|
return "/responses";
|
|
94
42
|
}
|
|
95
|
-
const GITHUB_API = "https://api.github.com";
|
|
96
43
|
const RETRYABLE_UPSTREAM_ERROR_CODES = new Set([
|
|
97
44
|
"UND_ERR_CONNECT_TIMEOUT",
|
|
98
45
|
"UND_ERR_HEADERS_TIMEOUT",
|
|
@@ -292,359 +239,70 @@ export async function refreshVSCodeVersion() {
|
|
|
292
239
|
return cachedVersion;
|
|
293
240
|
}
|
|
294
241
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
242
|
+
// Stateful Copilot data lives inside per-profile clients. These module-level
|
|
243
|
+
// wrappers preserve every existing export as the default Codex compatibility
|
|
244
|
+
// layer while allowing the adapter to use isolated Codex and Claude clients.
|
|
245
|
+
export function createCopilotClient(options = {}) {
|
|
246
|
+
return createCopilotClientRuntime({
|
|
247
|
+
...options,
|
|
248
|
+
defaultApiBase: DEFAULT_API_BASE,
|
|
249
|
+
parseApiBase,
|
|
250
|
+
fetchCopilotUpstream,
|
|
251
|
+
buildHeaders,
|
|
252
|
+
getVSCodeVersion,
|
|
253
|
+
computeInitiator,
|
|
254
|
+
computeVision,
|
|
255
|
+
responsesEndpointPath,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
303
258
|
|
|
304
|
-
export
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
return
|
|
308
|
-
token_cached: Boolean(copilotToken),
|
|
309
|
-
token_expires_in_ms: copilotToken ? Math.max(0, copilotTokenExpiry - now) : null,
|
|
310
|
-
token_refresh_in_flight: Boolean(copilotTokenRefresh),
|
|
311
|
-
token_refresh_backoff_ms: Math.max(0, copilotTokenRefreshRetryAt - now),
|
|
312
|
-
account_bound: Boolean(copilotTokenGithubIdentity),
|
|
313
|
-
upstream_host: upstreamHost,
|
|
314
|
-
model_endpoint_cache_entries: modelEndpointCache.size,
|
|
315
|
-
model_list_flights: modelListFlights.size,
|
|
316
|
-
};
|
|
259
|
+
export const defaultCopilotClient = createCopilotClient({ profile: "codex" });
|
|
260
|
+
|
|
261
|
+
export function cacheModelEndpoints(models) {
|
|
262
|
+
return defaultCopilotClient.cacheModelEndpoints(models);
|
|
317
263
|
}
|
|
318
264
|
|
|
319
|
-
function
|
|
320
|
-
|
|
321
|
-
if (!fs.existsSync(GITHUB_TOKEN_PATH)) {
|
|
322
|
-
throw new Error("GitHub token not found. Run ccdx again to log in.");
|
|
323
|
-
}
|
|
324
|
-
const token = fs.readFileSync(GITHUB_TOKEN_PATH, "utf-8").trim();
|
|
325
|
-
if (!token) throw new Error(githubReauthMessage("GitHub token file is empty."));
|
|
326
|
-
return token;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function requestCopilotToken(ghToken, { fetchImpl = fetch, signal, retryOptions } = {}) {
|
|
330
|
-
return fetchCopilotUpstream(`${GITHUB_API}/copilot_internal/v2/token`, {
|
|
331
|
-
headers: { Authorization: `token ${ghToken}`, Accept: "application/json" },
|
|
332
|
-
signal,
|
|
333
|
-
}, { fetchImpl, ...retryOptions });
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
function cacheCopilotTokenData(data, {
|
|
337
|
-
sourceKey,
|
|
338
|
-
githubToken,
|
|
339
|
-
githubIdentity,
|
|
340
|
-
allowAccountSwitch = false,
|
|
341
|
-
} = {}) {
|
|
342
|
-
if (!data.token) throw new Error("Copilot token response missing token field");
|
|
343
|
-
const fingerprint = githubTokenFingerprint(githubToken);
|
|
344
|
-
const identity = normalizeGithubIdentity(githubIdentity);
|
|
345
|
-
const tokenChanged = Boolean(copilotTokenGithubFingerprint && fingerprint !== copilotTokenGithubFingerprint);
|
|
346
|
-
if (!allowAccountSwitch && copilotTokenGithubIdentity && identity
|
|
347
|
-
&& !githubIdentitiesEqual(copilotTokenGithubIdentity, identity)) {
|
|
348
|
-
throw new Error(`Refusing to switch GitHub Copilot account from ${githubIdentityLabel(copilotTokenGithubIdentity)} to ${githubIdentityLabel(identity)} while the adapter is running. Restart ccdx to switch accounts intentionally.`);
|
|
349
|
-
}
|
|
350
|
-
if (!allowAccountSwitch && copilotTokenGithubIdentity && tokenChanged && !identity) {
|
|
351
|
-
const error = new Error("The saved GitHub token changed, but its account could not be verified. Keeping the running adapter bound to the existing GitHub account.");
|
|
352
|
-
error.transient = true;
|
|
353
|
-
throw error;
|
|
354
|
-
}
|
|
355
|
-
copilotToken = data.token;
|
|
356
|
-
apiBase = parseApiBase(data);
|
|
357
|
-
copilotTokenExpiry = typeof data.expires_at === "number"
|
|
358
|
-
? data.expires_at * 1000
|
|
359
|
-
: Date.now() + 25 * 60 * 1000; // fallback if expires_at absent: refresh in ~25min
|
|
360
|
-
copilotTokenSourceKey = sourceKey || copilotTokenSourceKey;
|
|
361
|
-
copilotTokenGithubFingerprint = fingerprint;
|
|
362
|
-
if (identity) copilotTokenGithubIdentity = identity;
|
|
363
|
-
copilotTokenRefreshRetryAt = 0;
|
|
364
|
-
console.log(status("ok", "Copilot token refreshed"));
|
|
365
|
-
return copilotToken;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
async function githubIdentityForToken(token, { home, fetchImpl, signal }) {
|
|
369
|
-
const cached = readGithubTokenMetadata(home, token);
|
|
370
|
-
if (cached) return cached;
|
|
371
|
-
const identity = await fetchGithubIdentity(token, { fetchImpl, signal });
|
|
372
|
-
return identity.ok ? identity : null;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
function tokenRefreshError(message, { statusCode, transient = false } = {}) {
|
|
376
|
-
const error = new Error(message);
|
|
377
|
-
if (statusCode) error.statusCode = statusCode;
|
|
378
|
-
if (transient) error.transient = true;
|
|
379
|
-
return error;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
async function refreshCopilotToken({
|
|
383
|
-
signal,
|
|
384
|
-
home = os.homedir(),
|
|
385
|
-
env = process.env,
|
|
386
|
-
fetchImpl = fetch,
|
|
387
|
-
tokenRetryOptions,
|
|
388
|
-
} = {}) {
|
|
389
|
-
const ghToken = getGithubToken({ home });
|
|
390
|
-
const sourceKey = githubTokenPath(home);
|
|
391
|
-
let resp;
|
|
392
|
-
try {
|
|
393
|
-
resp = await requestCopilotToken(ghToken, {
|
|
394
|
-
fetchImpl,
|
|
395
|
-
signal,
|
|
396
|
-
retryOptions: tokenRetryOptions,
|
|
397
|
-
});
|
|
398
|
-
} catch (e) {
|
|
399
|
-
if (!isAbortError(e, signal)) e.transient = true;
|
|
400
|
-
throw e;
|
|
401
|
-
}
|
|
402
|
-
if (!resp.ok) {
|
|
403
|
-
if (resp.status === 401 || resp.status === 403) {
|
|
404
|
-
const imported = await importDiscoveredGithubToken({
|
|
405
|
-
home,
|
|
406
|
-
env,
|
|
407
|
-
fetchImpl,
|
|
408
|
-
signal,
|
|
409
|
-
excludeTokens: [ghToken],
|
|
410
|
-
validateSavedToken: true,
|
|
411
|
-
});
|
|
412
|
-
if (imported?.validation?.copilotTokenData?.token) {
|
|
413
|
-
const explicitSource = imported.source?.type === "env" || imported.source?.type === "token-file";
|
|
414
|
-
return cacheCopilotTokenData(imported.validation.copilotTokenData, {
|
|
415
|
-
sourceKey,
|
|
416
|
-
githubToken: imported.token,
|
|
417
|
-
githubIdentity: imported.validation,
|
|
418
|
-
allowAccountSwitch: explicitSource,
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
throw new Error(githubReauthMessage(`Failed to get Copilot token: ${resp.status}. The saved GitHub token may be expired, revoked, or missing Copilot access.`));
|
|
422
|
-
}
|
|
423
|
-
throw tokenRefreshError(`Failed to get Copilot token: ${resp.status}`, {
|
|
424
|
-
statusCode: resp.status,
|
|
425
|
-
transient: resp.status === 408 || resp.status === 425 || resp.status === 429 || resp.status >= 500,
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
const data = await resp.json();
|
|
429
|
-
const identity = await githubIdentityForToken(ghToken, { home, fetchImpl, signal });
|
|
430
|
-
const token = cacheCopilotTokenData(data, {
|
|
431
|
-
sourceKey,
|
|
432
|
-
githubToken: ghToken,
|
|
433
|
-
githubIdentity: identity,
|
|
434
|
-
});
|
|
435
|
-
if (identity) writeGithubTokenMetadata(identity, home, ghToken);
|
|
436
|
-
return token;
|
|
265
|
+
export function getCachedModelEndpoints(modelId) {
|
|
266
|
+
return defaultCopilotClient.getCachedModelEndpoints(modelId);
|
|
437
267
|
}
|
|
438
268
|
|
|
439
|
-
function
|
|
440
|
-
|
|
441
|
-
return Boolean(copilotToken && sameSource && now < copilotTokenExpiry);
|
|
269
|
+
export function resetModelEndpointCacheForTests() {
|
|
270
|
+
return defaultCopilotClient.resetModelEndpointCacheForTests();
|
|
442
271
|
}
|
|
443
272
|
|
|
444
|
-
export function
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
const now = Date.now();
|
|
448
|
-
const allowCurrentProcessToken = options.home === undefined;
|
|
449
|
-
if (canUseCachedToken(sourceKey, now, allowCurrentProcessToken) && now < copilotTokenExpiry - 60000) {
|
|
450
|
-
return Promise.resolve(copilotToken);
|
|
451
|
-
}
|
|
452
|
-
if (canUseCachedToken(sourceKey, now, allowCurrentProcessToken) && now < copilotTokenRefreshRetryAt) {
|
|
453
|
-
return Promise.resolve(copilotToken);
|
|
454
|
-
}
|
|
455
|
-
if (options.signal?.aborted) return Promise.reject(abortError(options.signal));
|
|
273
|
+
export function getApiBase() {
|
|
274
|
+
return defaultCopilotClient.getApiBase();
|
|
275
|
+
}
|
|
456
276
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
flight = null;
|
|
461
|
-
}
|
|
277
|
+
export function copilotRuntimeStatus(now = Date.now()) {
|
|
278
|
+
return defaultCopilotClient.runtimeStatus(now);
|
|
279
|
+
}
|
|
462
280
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
const tokenRetryOptions = options.tokenRetryOptions
|
|
466
|
-
?? (canUseCachedToken(sourceKey, now, allowCurrentProcessToken) ? { retries: 0 } : undefined);
|
|
467
|
-
flight = { key: sourceKey, controller, waiters: 0, settled: false, promise: null };
|
|
468
|
-
flight.promise = refreshCopilotToken({
|
|
469
|
-
...options,
|
|
470
|
-
home,
|
|
471
|
-
signal: controller.signal,
|
|
472
|
-
tokenRetryOptions,
|
|
473
|
-
})
|
|
474
|
-
.catch((error) => {
|
|
475
|
-
const fallbackNow = Date.now();
|
|
476
|
-
if (error?.transient && canUseCachedToken(sourceKey, fallbackNow)) {
|
|
477
|
-
copilotTokenRefreshRetryAt = Math.min(copilotTokenExpiry, fallbackNow + 5000);
|
|
478
|
-
console.warn(status("warn", `Copilot token refresh failed temporarily; using the existing token until ${new Date(copilotTokenExpiry).toISOString()} (${error.message})`));
|
|
479
|
-
return copilotToken;
|
|
480
|
-
}
|
|
481
|
-
throw error;
|
|
482
|
-
})
|
|
483
|
-
.finally(() => {
|
|
484
|
-
flight.settled = true;
|
|
485
|
-
if (copilotTokenRefresh === flight) copilotTokenRefresh = null;
|
|
486
|
-
});
|
|
487
|
-
copilotTokenRefresh = flight;
|
|
488
|
-
}
|
|
489
|
-
return waitForSingleflight(flight, options.signal);
|
|
281
|
+
export function getCopilotToken(options = {}) {
|
|
282
|
+
return defaultCopilotClient.getCopilotToken(options);
|
|
490
283
|
}
|
|
491
284
|
|
|
492
285
|
export function resetCopilotTokenForTests() {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
copilotTokenExpiry = 0;
|
|
496
|
-
copilotTokenRefresh = null;
|
|
497
|
-
copilotTokenSourceKey = null;
|
|
498
|
-
copilotTokenGithubFingerprint = "";
|
|
499
|
-
copilotTokenGithubIdentity = null;
|
|
500
|
-
copilotTokenRefreshRetryAt = 0;
|
|
501
|
-
apiBase = DEFAULT_API_BASE;
|
|
502
|
-
resetModelListSingleflightForTests();
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
// chatReq is already converted by the adapter. The caller parses the raw Response.
|
|
506
|
-
export async function chatCompletions(chatReq, {
|
|
507
|
-
signal,
|
|
508
|
-
fetchImpl,
|
|
509
|
-
retryOptions,
|
|
510
|
-
bodyText,
|
|
511
|
-
} = {}) {
|
|
512
|
-
const token = await getCopilotToken({ signal });
|
|
513
|
-
const upstreamReq = chatReq.stream === true ? withChatStreamUsage(chatReq) : chatReq;
|
|
514
|
-
const messages = upstreamReq.messages || [];
|
|
515
|
-
const serializedBody = typeof bodyText === "string" && upstreamReq === chatReq
|
|
516
|
-
? bodyText
|
|
517
|
-
: JSON.stringify(upstreamReq);
|
|
518
|
-
const headers = buildHeaders({
|
|
519
|
-
token,
|
|
520
|
-
version: getVSCodeVersion(),
|
|
521
|
-
initiator: computeInitiator(messages),
|
|
522
|
-
vision: computeVision(messages),
|
|
523
|
-
});
|
|
524
|
-
headers["Content-Length"] = String(Buffer.byteLength(serializedBody));
|
|
525
|
-
if (upstreamReq.stream === true) markUpstreamStarted();
|
|
526
|
-
return fetchCopilotUpstream(`${getApiBase()}/chat/completions`, {
|
|
527
|
-
method: "POST",
|
|
528
|
-
headers,
|
|
529
|
-
body: serializedBody,
|
|
530
|
-
signal,
|
|
531
|
-
}, { fetchImpl, ...retryOptions });
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
async function fetchModels({ signal, fetchImpl, retryOptions }) {
|
|
535
|
-
const token = await getCopilotToken({ signal });
|
|
536
|
-
const headers = buildHeaders({
|
|
537
|
-
token, version: getVSCodeVersion(), initiator: "user", vision: false,
|
|
538
|
-
});
|
|
539
|
-
const resp = await fetchCopilotUpstream(`${getApiBase()}/models`, { headers, signal }, { fetchImpl, ...retryOptions });
|
|
540
|
-
const body = await resp.text();
|
|
541
|
-
if (resp.ok) {
|
|
542
|
-
try { cacheModelEndpoints(JSON.parse(body)); } catch {}
|
|
543
|
-
}
|
|
544
|
-
return { status: resp.status, body };
|
|
286
|
+
defaultCopilotClient.resetTokenForTests();
|
|
287
|
+
defaultCopilotClient.resetModelListSingleflightForTests();
|
|
545
288
|
}
|
|
546
289
|
|
|
547
|
-
function
|
|
548
|
-
|
|
549
|
-
flight.waiters += 1;
|
|
550
|
-
|
|
551
|
-
return new Promise((resolve, reject) => {
|
|
552
|
-
let finished = false;
|
|
553
|
-
const finish = (handler, value) => {
|
|
554
|
-
if (finished) return;
|
|
555
|
-
finished = true;
|
|
556
|
-
signal?.removeEventListener("abort", onAbort);
|
|
557
|
-
flight.waiters -= 1;
|
|
558
|
-
if (!flight.settled && flight.waiters === 0) flight.controller.abort();
|
|
559
|
-
handler(value);
|
|
560
|
-
};
|
|
561
|
-
const onAbort = () => finish(reject, abortError(signal));
|
|
562
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
563
|
-
if (signal?.aborted) onAbort();
|
|
564
|
-
flight.promise.then(
|
|
565
|
-
(value) => finish(resolve, value),
|
|
566
|
-
(error) => finish(reject, error),
|
|
567
|
-
);
|
|
568
|
-
});
|
|
290
|
+
export function chatCompletions(chatReq, options = {}) {
|
|
291
|
+
return defaultCopilotClient.chatCompletions(chatReq, options);
|
|
569
292
|
}
|
|
570
293
|
|
|
571
|
-
export function listModels(
|
|
572
|
-
|
|
573
|
-
let flight = modelListFlights.get(fetchImpl);
|
|
574
|
-
if (flight?.controller.signal.aborted) {
|
|
575
|
-
modelListFlights.delete(fetchImpl);
|
|
576
|
-
flight = null;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
if (!flight) {
|
|
580
|
-
const controller = new AbortController();
|
|
581
|
-
flight = { controller, waiters: 0, settled: false, promise: null };
|
|
582
|
-
flight.promise = fetchModels({ signal: controller.signal, fetchImpl, retryOptions })
|
|
583
|
-
.finally(() => {
|
|
584
|
-
flight.settled = true;
|
|
585
|
-
if (modelListFlights.get(fetchImpl) === flight) modelListFlights.delete(fetchImpl);
|
|
586
|
-
});
|
|
587
|
-
modelListFlights.set(fetchImpl, flight);
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
return waitForSingleflight(flight, signal);
|
|
294
|
+
export function listModels(options = {}) {
|
|
295
|
+
return defaultCopilotClient.listModels(options);
|
|
591
296
|
}
|
|
592
297
|
|
|
593
298
|
export function resetModelListSingleflightForTests() {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
// Responses-only models go directly to Copilot's /responses endpoint.
|
|
599
|
-
export async function responses(reqBody, {
|
|
600
|
-
signal,
|
|
601
|
-
fetchImpl,
|
|
602
|
-
retryOptions,
|
|
603
|
-
currentInputStart = 0,
|
|
604
|
-
payloadPrepared = false,
|
|
605
|
-
payloadOptions = {},
|
|
606
|
-
} = {}) {
|
|
607
|
-
const token = await getCopilotToken({ signal });
|
|
608
|
-
const preparedPayload = await prepareResponsesPayload(reqBody, {
|
|
609
|
-
...payloadOptions,
|
|
610
|
-
currentInputStart,
|
|
611
|
-
profiles: payloadPrepared ? [] : payloadOptions.profiles,
|
|
612
|
-
skipInitialOptimization: payloadPrepared || payloadOptions.skipInitialOptimization,
|
|
613
|
-
signal,
|
|
614
|
-
});
|
|
615
|
-
const finalizedPayload = enforceResponsesPayloadByteBudget(reqBody, preparedPayload);
|
|
616
|
-
const { bodyText, bodyBytes, stage, adapted } = finalizedPayload;
|
|
617
|
-
const summary = finalizedPayload.stage === preparedPayload.stage
|
|
618
|
-
? preparedPayload.summary
|
|
619
|
-
: summarizeReqBody(reqBody);
|
|
620
|
-
console.log(status("info", `responses payload bytes=${bodyBytes} input_items=${summary.items} images=${summary.images}`));
|
|
621
|
-
if (adapted) debugLog(`responses payload adapted stage=${stage} bytes=${bodyBytes}/${preparedPayload.targetBytes}`);
|
|
622
|
-
const headers = buildHeaders({
|
|
623
|
-
token,
|
|
624
|
-
version: getVSCodeVersion(),
|
|
625
|
-
initiator: "user",
|
|
626
|
-
vision: false,
|
|
627
|
-
});
|
|
628
|
-
headers["Content-Type"] = "application/json; charset=utf-8";
|
|
629
|
-
headers["Content-Length"] = String(bodyBytes);
|
|
630
|
-
headers["Accept"] = reqBody.stream ? "text/event-stream" : "application/json";
|
|
299
|
+
return defaultCopilotClient.resetModelListSingleflightForTests();
|
|
300
|
+
}
|
|
631
301
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
return await fetchCopilotUpstream(`${getApiBase()}${responsesEndpointPath()}`, {
|
|
635
|
-
method: "POST",
|
|
636
|
-
headers,
|
|
637
|
-
body: bodyText,
|
|
638
|
-
signal,
|
|
639
|
-
}, { fetchImpl, ...retryOptions });
|
|
640
|
-
} catch (e) {
|
|
641
|
-
const cause = e?.cause;
|
|
642
|
-
const causeText = cause ? ` (${[cause.code, cause.message].filter(Boolean).join(": ")})` : "";
|
|
643
|
-
throw new Error(`Copilot responses fetch failed: ${e.message}${causeText}`);
|
|
644
|
-
}
|
|
302
|
+
export function responses(reqBody, options = {}) {
|
|
303
|
+
return defaultCopilotClient.responses(reqBody, options);
|
|
645
304
|
}
|
|
646
305
|
|
|
647
|
-
export
|
|
648
|
-
|
|
649
|
-
return responses(reqBody, options);
|
|
306
|
+
export function responsesCompact(reqBody, options = {}) {
|
|
307
|
+
return defaultCopilotClient.responsesCompact(reqBody, options);
|
|
650
308
|
}
|