codex-copilot-dx 0.4.42 → 0.4.44

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/src/copilot.mjs CHANGED
@@ -17,7 +17,10 @@ import {
17
17
  githubTokenFingerprint,
18
18
  normalizeGithubIdentity,
19
19
  } from "./github-identity.mjs";
20
- import { prepareResponsesPayload } from "./image-optimization.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";
21
24
 
22
25
  export {
23
26
  optimizeImageDataUrl,
@@ -316,7 +319,7 @@ export function copilotRuntimeStatus(now = Date.now()) {
316
319
  function getGithubToken({ home = os.homedir() } = {}) {
317
320
  const GITHUB_TOKEN_PATH = githubTokenPath(home);
318
321
  if (!fs.existsSync(GITHUB_TOKEN_PATH)) {
319
- throw new Error("GitHub token not found. Run codex-copilot-dx again to log in.");
322
+ throw new Error("GitHub token not found. Run ccdx again to log in.");
320
323
  }
321
324
  const token = fs.readFileSync(GITHUB_TOKEN_PATH, "utf-8").trim();
322
325
  if (!token) throw new Error(githubReauthMessage("GitHub token file is empty."));
@@ -342,7 +345,7 @@ function cacheCopilotTokenData(data, {
342
345
  const tokenChanged = Boolean(copilotTokenGithubFingerprint && fingerprint !== copilotTokenGithubFingerprint);
343
346
  if (!allowAccountSwitch && copilotTokenGithubIdentity && identity
344
347
  && !githubIdentitiesEqual(copilotTokenGithubIdentity, identity)) {
345
- throw new Error(`Refusing to switch GitHub Copilot account from ${githubIdentityLabel(copilotTokenGithubIdentity)} to ${githubIdentityLabel(identity)} while the adapter is running. Restart codex-copilot-dx to switch accounts intentionally.`);
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.`);
346
349
  }
347
350
  if (!allowAccountSwitch && copilotTokenGithubIdentity && tokenChanged && !identity) {
348
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.");
@@ -500,19 +503,30 @@ export function resetCopilotTokenForTests() {
500
503
  }
501
504
 
502
505
  // chatReq is already converted by the adapter. The caller parses the raw Response.
503
- export async function chatCompletions(chatReq, { signal, fetchImpl, retryOptions } = {}) {
506
+ export async function chatCompletions(chatReq, {
507
+ signal,
508
+ fetchImpl,
509
+ retryOptions,
510
+ bodyText,
511
+ } = {}) {
504
512
  const token = await getCopilotToken({ signal });
505
- const messages = chatReq.messages || [];
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);
506
518
  const headers = buildHeaders({
507
519
  token,
508
520
  version: getVSCodeVersion(),
509
521
  initiator: computeInitiator(messages),
510
522
  vision: computeVision(messages),
511
523
  });
524
+ headers["Content-Length"] = String(Buffer.byteLength(serializedBody));
525
+ if (upstreamReq.stream === true) markUpstreamStarted();
512
526
  return fetchCopilotUpstream(`${getApiBase()}/chat/completions`, {
513
527
  method: "POST",
514
528
  headers,
515
- body: JSON.stringify(chatReq),
529
+ body: serializedBody,
516
530
  signal,
517
531
  }, { fetchImpl, ...retryOptions });
518
532
  }
@@ -582,10 +596,29 @@ export function resetModelListSingleflightForTests() {
582
596
  }
583
597
 
584
598
  // Responses-only models go directly to Copilot's /responses endpoint.
585
- export async function responses(reqBody, { signal, fetchImpl, retryOptions } = {}) {
599
+ export async function responses(reqBody, {
600
+ signal,
601
+ fetchImpl,
602
+ retryOptions,
603
+ currentInputStart = 0,
604
+ payloadPrepared = false,
605
+ payloadOptions = {},
606
+ } = {}) {
586
607
  const token = await getCopilotToken({ signal });
587
- const { bodyText, bodyBytes, summary } = await prepareResponsesPayload(reqBody, { 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);
588
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}`);
589
622
  const headers = buildHeaders({
590
623
  token,
591
624
  version: getVSCodeVersion(),
@@ -597,6 +630,7 @@ export async function responses(reqBody, { signal, fetchImpl, retryOptions } = {
597
630
  headers["Accept"] = reqBody.stream ? "text/event-stream" : "application/json";
598
631
 
599
632
  try {
633
+ if (reqBody.stream === true) markUpstreamStarted();
600
634
  return await fetchCopilotUpstream(`${getApiBase()}${responsesEndpointPath()}`, {
601
635
  method: "POST",
602
636
  headers,
package/src/doctor.mjs CHANGED
@@ -124,6 +124,15 @@ function parseResponseObject(text) {
124
124
  return response;
125
125
  }
126
126
 
127
+ function parseCompactionResponse(text) {
128
+ const response = parseResponseObject(text);
129
+ if (response.object !== "response.compaction"
130
+ || !response.output.some((item) => item?.type === "compaction")) {
131
+ throw new Error("response body is missing valid compaction state");
132
+ }
133
+ return response;
134
+ }
135
+
127
136
  export async function inspectAdapterCompatibility({
128
137
  host = "127.0.0.1",
129
138
  port = 2026,
@@ -184,7 +193,7 @@ export async function inspectAdapterCompatibility({
184
193
  stream: false,
185
194
  input: "Compact this short context.",
186
195
  }, timeoutMs);
187
- parseResponseObject(text);
196
+ parseCompactionResponse(text);
188
197
  });
189
198
 
190
199
  if (!claudeModel) {
@@ -446,7 +455,7 @@ export async function collectDoctorChecks({
446
455
  export async function runDoctor(options = {}) {
447
456
  const log = options.log || console.log;
448
457
  const flags = [options.online ? "--online" : "", options.compat ? "--compat" : ""].filter(Boolean);
449
- log(`codex-copilot-dx doctor${flags.length ? ` ${flags.join(" ")}` : ""}`);
458
+ log(`ccdx doctor${flags.length ? ` ${flags.join(" ")}` : ""}`);
450
459
  const checks = await collectDoctorChecks(options);
451
460
  for (const check of checks) log(status(check.kind, check.message));
452
461
  return checks;
@@ -2,6 +2,7 @@ import { promisify } from "node:util";
2
2
  import * as zlib from "node:zlib";
3
3
  import { loadRuntimeConfig, parsePositiveInteger } from "./runtime-config.mjs";
4
4
  import { status } from "./status.mjs";
5
+ import { safeUpstreamResponseHeaders } from "./upstream-headers.mjs";
5
6
 
6
7
  const COMPRESSED_BODY_WEIGHT_MULTIPLIER = 4;
7
8
  const gunzipAsync = promisify(zlib.gunzip);
@@ -291,12 +292,14 @@ export function sendJsonError(res, err, fallbackStatus = 400) {
291
292
  return;
292
293
  }
293
294
  res.writeHead(err?.statusCode || fallbackStatus, { "Content-Type": "application/json" });
294
- res.end(JSON.stringify({ error: err?.message || "Request failed" }));
295
+ res.end(JSON.stringify(err?.jsonBody || { error: err?.message || "Request failed" }));
295
296
  }
296
297
 
297
298
  export function sendUpstreamError(res, response, text) {
298
299
  if (!res.headersSent) {
299
- res.writeHead(response.status || 502, { "Content-Type": response.headers?.get("content-type") || "application/json" });
300
+ res.writeHead(response.status || 502, safeUpstreamResponseHeaders(response.headers, {
301
+ defaultContentType: "application/json",
302
+ }));
300
303
  }
301
304
  res.end(text || JSON.stringify({ error: "Upstream request failed" }));
302
305
  }