codex-copilot-dx 0.6.5 → 0.6.7

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.
@@ -0,0 +1,114 @@
1
+ import { stripVTControlCharacters } from "node:util";
2
+
3
+ const DEFAULT_TERMINAL_COLUMNS = 120;
4
+ const COMBINING_MARK = /\p{Mark}/u;
5
+ const EMOJI = /\p{Extended_Pictographic}|\p{Regional_Indicator}/u;
6
+ const SIMPLE_WIDTH = /^[\u0020-\u007e\u2014]*$/;
7
+ let graphemeSegmenter;
8
+
9
+ function wideCodePoint(codePoint) {
10
+ return codePoint >= 0x1100 && (
11
+ codePoint <= 0x115f
12
+ || codePoint === 0x2329
13
+ || codePoint === 0x232a
14
+ || (codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f)
15
+ || (codePoint >= 0xac00 && codePoint <= 0xd7a3)
16
+ || (codePoint >= 0xf900 && codePoint <= 0xfaff)
17
+ || (codePoint >= 0xfe10 && codePoint <= 0xfe19)
18
+ || (codePoint >= 0xfe30 && codePoint <= 0xfe6f)
19
+ || (codePoint >= 0xff00 && codePoint <= 0xff60)
20
+ || (codePoint >= 0xffe0 && codePoint <= 0xffe6)
21
+ || (codePoint >= 0x20000 && codePoint <= 0x3fffd)
22
+ );
23
+ }
24
+
25
+ function graphemeWidth(segment) {
26
+ if (EMOJI.test(segment)) return 2;
27
+ let width = 0;
28
+ for (const character of segment) {
29
+ const codePoint = character.codePointAt(0);
30
+ if (codePoint === 0x200d || (codePoint >= 0xfe00 && codePoint <= 0xfe0f) || COMBINING_MARK.test(character)) continue;
31
+ width += wideCodePoint(codePoint) ? 2 : 1;
32
+ }
33
+ return width;
34
+ }
35
+
36
+ function displayWidth(value) {
37
+ if (SIMPLE_WIDTH.test(value)) return value.length;
38
+ graphemeSegmenter ||= new Intl.Segmenter("en", { granularity: "grapheme" });
39
+ let width = 0;
40
+ for (const { segment } of graphemeSegmenter.segment(value)) width += graphemeWidth(segment);
41
+ return width;
42
+ }
43
+
44
+ function padding(length) {
45
+ return length > 0 ? " ".repeat(length) : "";
46
+ }
47
+
48
+ function padded(value, width, align) {
49
+ const spaces = padding(width - displayWidth(value));
50
+ return align === "right" ? `${spaces}${value}` : `${value}${spaces}`;
51
+ }
52
+
53
+ export function terminalCell(value, { fallback = "—" } = {}) {
54
+ if (value === undefined || value === null) return fallback;
55
+ const text = stripVTControlCharacters(String(value))
56
+ .replace(/[\u0000-\u001f\u007f-\u009f]/g, " ")
57
+ .replace(/[\u061c\u200b\u200e\u200f\u202a-\u202e\u2060\u2066-\u2069\ufeff]/g, "")
58
+ .replace(/\s+/g, " ")
59
+ .trim();
60
+ return text || fallback;
61
+ }
62
+
63
+ export function cliOutputFormat(format = "auto", output = process.stdout) {
64
+ if (format === "table" || format === "plain") return format;
65
+ return output?.isTTY === true ? "table" : "plain";
66
+ }
67
+
68
+ export function cliOutputWidth(output = process.stdout) {
69
+ const columns = Number(output?.columns);
70
+ return Number.isSafeInteger(columns) && columns > 0 ? columns : DEFAULT_TERMINAL_COLUMNS;
71
+ }
72
+
73
+ export function formatCliTable({ columns, rows, gap = 2 }) {
74
+ const normalizedColumns = columns.map((column) => ({
75
+ ...column,
76
+ label: terminalCell(column.label, { fallback: "" }),
77
+ }));
78
+ const normalizedRows = rows.map((row) => Object.fromEntries(normalizedColumns.map((column) => [
79
+ column.key,
80
+ terminalCell(row[column.key]),
81
+ ])));
82
+ const widths = normalizedColumns.map((column) => Math.max(
83
+ displayWidth(column.label),
84
+ ...normalizedRows.map((row) => displayWidth(row[column.key])),
85
+ ));
86
+ const line = (values) => normalizedColumns
87
+ .map((column, index) => padded(values[column.key], widths[index], column.align))
88
+ .join(" ".repeat(gap))
89
+ .trimEnd();
90
+ const header = Object.fromEntries(normalizedColumns.map((column) => [column.key, column.label]));
91
+ const separator = Object.fromEntries(normalizedColumns.map((column, index) => [
92
+ column.key,
93
+ "-".repeat(widths[index]),
94
+ ]));
95
+ return [line(header), line(separator), ...normalizedRows.map(line)].join("\n");
96
+ }
97
+
98
+ export function cliTableWidth(table) {
99
+ return Math.max(0, ...String(table).split("\n").map(displayWidth));
100
+ }
101
+
102
+ export function formatResponsiveCliTable({ columns, compactColumns, rows, width = DEFAULT_TERMINAL_COLUMNS, gap = 2 }) {
103
+ const full = formatCliTable({ columns, rows, gap });
104
+ const fullWidth = cliTableWidth(full);
105
+ if (!compactColumns?.length || fullWidth <= width) {
106
+ return { output: full, compact: false, overflow: fullWidth > width };
107
+ }
108
+ const compact = formatCliTable({ columns: compactColumns, rows, gap });
109
+ return {
110
+ output: compact,
111
+ compact: true,
112
+ overflow: cliTableWidth(compact) > width,
113
+ };
114
+ }
@@ -150,6 +150,7 @@ export function createCopilotClientRuntime({
150
150
  }
151
151
 
152
152
  async function responses(reqBody, {
153
+ assertActive,
153
154
  signal,
154
155
  fetchImpl,
155
156
  retryOptions,
@@ -160,13 +161,14 @@ export function createCopilotClientRuntime({
160
161
  } = {}) {
161
162
  const token = await tokenSession.getToken({ signal });
162
163
  const preparedPayload = await prepareResponsesPayload(reqBody, {
164
+ assertActive,
163
165
  ...payloadOptions,
164
166
  currentInputStart,
165
167
  profiles: payloadPrepared ? [] : payloadOptions.profiles,
166
168
  skipInitialOptimization: payloadPrepared || payloadOptions.skipInitialOptimization,
167
169
  signal,
168
170
  });
169
- const finalizedPayload = enforceResponsesPayloadByteBudget(reqBody, preparedPayload);
171
+ const finalizedPayload = enforceResponsesPayloadByteBudget(reqBody, preparedPayload, { assertActive });
170
172
  const { bodyText, bodyBytes, stage, adapted } = finalizedPayload;
171
173
  const summary = finalizedPayload.stage === preparedPayload.stage
172
174
  ? preparedPayload.summary
package/src/doctor.mjs CHANGED
@@ -12,7 +12,7 @@ import { claudeDesktopPaths } from "./claude-desktop-config.mjs";
12
12
  import { status } from "./status.mjs";
13
13
  import { buildHeaders, DEFAULT_API_BASE, FALLBACK_VSCODE_VERSION } from "./copilot.mjs";
14
14
  import { adapterBaseUrl, checkRunningAdapter } from "./running-adapter.mjs";
15
- import { CODEX_AUTO_REVIEW_MODEL } from "./models.mjs";
15
+ import { CODEX_AUTO_REVIEW_MODEL, isClaudeCopilotModel } from "./models.mjs";
16
16
  import { loadModelCache } from "./model-cache.mjs";
17
17
 
18
18
  function localGatewayBaseUrl(host, port) {
@@ -66,7 +66,7 @@ function modelEndpoints(model) {
66
66
 
67
67
  export function selectCompatibilityModels(models, { claudeModels = models } = {}) {
68
68
  const data = copilotModelData(models).filter((model) => model?.model_picker_enabled !== false);
69
- const claudeData = copilotModelData(claudeModels).filter((model) => model?.model_picker_enabled !== false);
69
+ const claudeData = copilotModelData(claudeModels);
70
70
  const responsesCandidates = data.filter((model) => {
71
71
  const id = String(model?.id || "");
72
72
  const endpoints = modelEndpoints(model);
@@ -74,12 +74,7 @@ export function selectCompatibilityModels(models, { claudeModels = models } = {}
74
74
  });
75
75
  const responsesOnly = [...responsesCandidates].reverse()
76
76
  .find((model) => !modelEndpoints(model).includes("/chat/completions"));
77
- const claude = claudeData.find((model) => {
78
- const id = String(model?.id || "");
79
- const vendor = String(model?.vendor || "").toLowerCase();
80
- return (id.startsWith("claude-") || vendor === "anthropic")
81
- && modelEndpoints(model).includes("/chat/completions");
82
- });
77
+ const claude = claudeData.find(isClaudeCopilotModel);
83
78
  return {
84
79
  responsesModel: String((responsesOnly || responsesCandidates[0])?.id || ""),
85
80
  claudeModel: String(claude?.id || ""),
@@ -588,6 +583,12 @@ export async function collectDoctorChecks({
588
583
  }
589
584
  if (!pm.adapter?.ok) {
590
585
  checks.push({ kind: "warn", message: "PM Studio relay is not currently available", fix: "ccdx start" });
586
+ } else if (!pm.runtime?.ok) {
587
+ checks.push({
588
+ kind: "warn",
589
+ message: "PM Studio relay is running, but its isolated routing is not ready",
590
+ fix: "stop the running adapter, then run ccdx start",
591
+ });
591
592
  }
592
593
  } else if (pm.app.state === "clean") {
593
594
  checks.push({
@@ -47,29 +47,56 @@ function admissionAbortError(signal) {
47
47
  return error;
48
48
  }
49
49
 
50
- function createSupplementalAdmission({ maxWeight, maxQueued, waitTimeoutMs, label }) {
50
+ function createSupplementalAdmission({ maxWeight, maxQueued, waitTimeoutMs, label, blockBehindExclusive = false }) {
51
51
  const queue = [];
52
52
  let activeWeight = 0;
53
+ let activeRequestedWeight = 0;
53
54
  let activeRequests = 0;
54
55
 
55
56
  const activate = (entry) => {
56
57
  entry.cleanup();
57
58
  activeWeight += entry.weight;
59
+ activeRequestedWeight += entry.requestedWeight;
58
60
  activeRequests += 1;
59
61
  let released = false;
60
- entry.resolve(() => {
62
+ const release = () => {
61
63
  if (released) return;
62
64
  released = true;
63
65
  activeWeight = Math.max(0, activeWeight - entry.weight);
66
+ activeRequestedWeight = Math.max(0, activeRequestedWeight - entry.requestedWeight);
64
67
  activeRequests = Math.max(0, activeRequests - 1);
65
68
  drain();
69
+ };
70
+ release.resize = (requestedWeight) => {
71
+ if (released) return 0;
72
+ const parsedWeight = Number.isFinite(requestedWeight) && requestedWeight > 0
73
+ ? Math.ceil(requestedWeight)
74
+ : maxWeight;
75
+ const requested = Math.max(1, parsedWeight);
76
+ const weight = Math.min(requested, maxWeight);
77
+ if (weight > entry.weight) {
78
+ throw new RangeError(`${label} admission weight cannot grow`);
79
+ }
80
+ activeWeight = Math.max(0, activeWeight - (entry.weight - weight));
81
+ activeRequestedWeight = Math.max(0, activeRequestedWeight + requested - entry.requestedWeight);
82
+ entry.weight = weight;
83
+ entry.requestedWeight = requested;
84
+ drain();
85
+ return weight;
86
+ };
87
+ Object.defineProperties(release, {
88
+ weight: { get: () => released ? 0 : entry.weight },
89
+ requestedWeight: { get: () => released ? 0 : entry.requestedWeight },
90
+ maxWeight: { value: maxWeight },
66
91
  });
92
+ entry.resolve(release);
67
93
  };
68
94
 
69
95
  const drain = () => {
70
96
  for (let index = 0; index < queue.length;) {
71
97
  const entry = queue[index];
72
98
  if (activeWeight + entry.weight > maxWeight) {
99
+ if (blockBehindExclusive && entry.weight === maxWeight) break;
73
100
  index += 1;
74
101
  continue;
75
102
  }
@@ -83,8 +110,12 @@ function createSupplementalAdmission({ maxWeight, maxQueued, waitTimeoutMs, labe
83
110
  const parsedWeight = Number.isFinite(requestedWeight) && requestedWeight > 0
84
111
  ? Math.ceil(requestedWeight)
85
112
  : maxWeight;
86
- const weight = Math.max(1, Math.min(parsedWeight, maxWeight));
87
- if (queue.length >= maxQueued && activeWeight + weight > maxWeight) {
113
+ const requested = Math.max(1, parsedWeight);
114
+ const weight = Math.min(requested, maxWeight);
115
+ const blockedByExclusive = blockBehindExclusive
116
+ && queue.some((entry) => entry.weight === maxWeight);
117
+ const mustWait = blockedByExclusive || activeWeight + weight > maxWeight;
118
+ if (queue.length >= maxQueued && mustWait) {
88
119
  return Promise.reject(httpError(`${label} queue is full (${maxQueued} waiting)`, 503));
89
120
  }
90
121
 
@@ -92,6 +123,7 @@ function createSupplementalAdmission({ maxWeight, maxQueued, waitTimeoutMs, labe
92
123
  let timer;
93
124
  const entry = {
94
125
  weight,
126
+ requestedWeight: requested,
95
127
  resolve,
96
128
  cleanup: () => {
97
129
  if (timer) clearTimeout(timer);
@@ -120,7 +152,13 @@ function createSupplementalAdmission({ maxWeight, maxQueued, waitTimeoutMs, labe
120
152
  });
121
153
  };
122
154
 
123
- acquire.stats = () => ({ activeWeight, activeRequests, queued: queue.length, maxWeight });
155
+ acquire.stats = () => ({
156
+ activeWeight,
157
+ activeRequestedWeight,
158
+ activeRequests,
159
+ queued: queue.length,
160
+ maxWeight,
161
+ });
124
162
  return acquire;
125
163
  }
126
164
 
@@ -143,6 +181,7 @@ export function createRequestAdmission({
143
181
  maxQueued: queueLimit,
144
182
  waitTimeoutMs: timeoutMs,
145
183
  label: "Decoded request body",
184
+ blockBehindExclusive: true,
146
185
  });
147
186
  const acquireResponseHistory = createSupplementalAdmission({
148
187
  maxWeight: byteLimit,
@@ -194,10 +233,30 @@ export function createRequestAdmission({
194
233
  releaseSupplemental();
195
234
  throw admissionAbortError(options?.signal);
196
235
  }
197
- supplementalReleases.add(releaseSupplemental);
236
+ let supplementalReleased = false;
237
+ const releaseReservation = () => {
238
+ if (supplementalReleased) return;
239
+ supplementalReleased = true;
240
+ supplementalReleases.delete(releaseReservation);
241
+ releaseSupplemental();
242
+ };
243
+ releaseReservation.resize = (nextWeight) => {
244
+ if (supplementalReleased) return 0;
245
+ return releaseSupplemental.resize(nextWeight);
246
+ };
247
+ Object.defineProperties(releaseReservation, {
248
+ weight: { get: () => supplementalReleased ? 0 : releaseSupplemental.weight },
249
+ requestedWeight: {
250
+ get: () => supplementalReleased ? 0 : releaseSupplemental.requestedWeight,
251
+ },
252
+ maxWeight: { value: releaseSupplemental.maxWeight },
253
+ });
254
+ supplementalReleases.add(releaseReservation);
255
+ return releaseReservation;
198
256
  };
199
257
  release.acquireDecompression = (options) => acquireDecompression(1, options);
200
258
  release.reserveDecodedBody = (bytes, options) => reserve(acquireDecodedBody, bytes, options);
259
+ release.reserveDecodedBody.supportsResize = true;
201
260
  release.reserveResponseHistory = (bytes, options) => reserve(acquireResponseHistory, bytes, options);
202
261
  entry.resolve(release);
203
262
  };
@@ -285,7 +344,8 @@ export function createRequestAdmission({
285
344
  waitMsMax: counters.waitMsMax,
286
345
  decompressionsActive: acquireDecompression.stats().activeRequests,
287
346
  decompressionsQueued: acquireDecompression.stats().queued,
288
- decodedBodyBytes: acquireDecodedBody.stats().activeWeight,
347
+ decodedBodyBytes: acquireDecodedBody.stats().activeRequestedWeight,
348
+ decodedBodyAdmissionBytes: acquireDecodedBody.stats().activeWeight,
289
349
  decodedBodiesActive: acquireDecodedBody.stats().activeRequests,
290
350
  decodedBodiesQueued: acquireDecodedBody.stats().queued,
291
351
  responseHistoryBytes: acquireResponseHistory.stats().activeWeight,
@@ -445,21 +505,62 @@ export async function readJsonBody(req, {
445
505
  return parseRequestJson(await readIdentityText(req, maxBodyBytes, maxDecodedBodyBytes, signal));
446
506
  }
447
507
  const buffer = await readRequestBuffer(req, maxBodyBytes, signal);
448
- const releaseDecompression = await admission?.acquireDecompression?.({ signal }) || (() => {});
508
+ let releaseDecompression = () => {};
509
+ let decodedReservation;
510
+ let retainDecodedReservation = false;
449
511
  try {
450
512
  let decoded;
513
+ const supportsResizableReservation = admission?.reserveDecodedBody?.supportsResize === true;
514
+ // Most compressed bodies fit the existing 4x admission estimate. High-ratio bodies retry
515
+ // only after obtaining the pool's exclusive reservation, so a full decoded body never waits
516
+ // outside the decoded-body budget.
517
+ const estimatedDecodedBytes = Math.max(
518
+ 1,
519
+ Math.min(buffer.length * COMPRESSED_BODY_WEIGHT_MULTIPLIER, maxDecodedBodyBytes),
520
+ );
521
+ if (supportsResizableReservation) {
522
+ decodedReservation = await admission.reserveDecodedBody(estimatedDecodedBytes, { signal });
523
+ }
524
+ releaseDecompression = await admission?.acquireDecompression?.({ signal }) || (() => {});
525
+ const reservedBytes = decodedReservation?.requestedWeight;
526
+ const reservedAdmissionBytes = decodedReservation?.weight;
527
+ const decodedPoolBytes = decodedReservation?.maxWeight;
528
+ const tentativeDecodeLimit = Number.isFinite(reservedBytes)
529
+ && Number.isFinite(reservedAdmissionBytes)
530
+ && Number.isFinite(decodedPoolBytes)
531
+ && reservedAdmissionBytes < decodedPoolBytes
532
+ ? Math.min(reservedBytes, maxDecodedBodyBytes)
533
+ : maxDecodedBodyBytes;
451
534
  try {
452
- decoded = await decodeRequestBuffer(buffer, req.headers?.["content-encoding"], maxDecodedBodyBytes);
535
+ decoded = await decodeRequestBuffer(buffer, req.headers?.["content-encoding"], tentativeDecodeLimit);
453
536
  } catch (e) {
454
- if (e?.statusCode) throw e;
455
- throw httpError(`Invalid compressed request body: ${e.message}`, 400);
537
+ if (e?.statusCode === 413 && tentativeDecodeLimit < maxDecodedBodyBytes && supportsResizableReservation) {
538
+ releaseDecompression();
539
+ releaseDecompression = () => {};
540
+ decodedReservation();
541
+ decodedReservation = await admission.reserveDecodedBody(maxDecodedBodyBytes, { signal });
542
+ releaseDecompression = await admission.acquireDecompression?.({ signal }) || (() => {});
543
+ try {
544
+ decoded = await decodeRequestBuffer(buffer, req.headers?.["content-encoding"], maxDecodedBodyBytes);
545
+ } catch (retryError) {
546
+ if (retryError?.statusCode) throw retryError;
547
+ throw httpError(`Invalid compressed request body: ${retryError.message}`, 400);
548
+ }
549
+ } else {
550
+ if (e?.statusCode) throw e;
551
+ throw httpError(`Invalid compressed request body: ${e.message}`, 400);
552
+ }
456
553
  }
457
554
  if (signal?.aborted) throw admissionAbortError(signal);
458
555
  if (decoded.length > 0) {
459
- await admission?.reserveDecodedBody?.(decoded.length, { signal });
556
+ if (supportsResizableReservation) decodedReservation.resize(decoded.length);
557
+ else decodedReservation = await admission?.reserveDecodedBody?.(decoded.length, { signal });
460
558
  }
461
- return parseRequestJson(decoded.toString("utf8"));
559
+ const parsed = parseRequestJson(decoded.toString("utf8"));
560
+ retainDecodedReservation = true;
561
+ return parsed;
462
562
  } finally {
563
+ if (!retainDecodedReservation) decodedReservation?.();
463
564
  releaseDecompression();
464
565
  }
465
566
  }
@@ -273,9 +273,11 @@ export async function runWithConcurrency(taskFns, concurrency) {
273
273
  }));
274
274
  }
275
275
 
276
- function visitImageParts(parts, references, commit = null) {
276
+ function visitImageParts(parts, references, commit = null, assertActive) {
277
277
  if (!Array.isArray(parts)) return;
278
- for (const part of parts) {
278
+ for (let index = 0; index < parts.length; index += 1) {
279
+ if ((index & 63) === 0) assertActive?.();
280
+ const part = parts[index];
279
281
  const image = readResponsesImagePart(part);
280
282
  if (!image?.identity) continue;
281
283
  references.push({
@@ -286,22 +288,24 @@ function visitImageParts(parts, references, commit = null) {
286
288
  }
287
289
  }
288
290
 
289
- function collectImageReferences(reqBody) {
291
+ function collectImageReferences(reqBody, assertActive) {
290
292
  const references = [];
291
293
  const dirtyCommits = new Set();
292
294
  if (!Array.isArray(reqBody?.input)) {
293
295
  return { references, markDirty() {}, commitDirty() {} };
294
296
  }
295
297
 
296
- for (const item of reqBody.input) {
298
+ for (let index = 0; index < reqBody.input.length; index += 1) {
299
+ if ((index & 63) === 0) assertActive?.();
300
+ const item = reqBody.input[index];
297
301
  if (!item) continue;
298
- visitImageParts([item], references);
302
+ visitImageParts([item], references, null, assertActive);
299
303
  if (item.type === "message" && Array.isArray(item.content)) {
300
- visitImageParts(item.content, references);
304
+ visitImageParts(item.content, references, null, assertActive);
301
305
  }
302
306
  const toolOutput = readResponsesToolOutputParts(item);
303
307
  if (toolOutput) {
304
- visitImageParts(toolOutput.parts, references, toolOutput.commit);
308
+ visitImageParts(toolOutput.parts, references, toolOutput.commit, assertActive);
305
309
  }
306
310
  }
307
311
 
@@ -320,6 +324,7 @@ function collectImageReferences(reqBody) {
320
324
  }
321
325
 
322
326
  async function optimizeImageReferences(collection, {
327
+ assertActive,
323
328
  concurrency,
324
329
  model,
325
330
  optimizeImage,
@@ -334,7 +339,9 @@ async function optimizeImageReferences(collection, {
334
339
  return optimizedByDataUrl.get(identity);
335
340
  };
336
341
  const tasks = collection.references.map((reference) => async () => {
342
+ assertActive?.();
337
343
  if (reference.set(await optimizeOnce(reference.get()))) collection.markDirty([reference]);
344
+ assertActive?.();
338
345
  });
339
346
  if (tasks.length) await runWithConcurrency(tasks, concurrency);
340
347
  collection.commitDirty();
@@ -346,9 +353,11 @@ function imageInputBytes(dataUrl) {
346
353
  return match ? Buffer.byteLength(match[1], "base64") : 0;
347
354
  }
348
355
 
349
- function uniqueOriginalImages(references) {
356
+ function uniqueOriginalImages(references, assertActive) {
350
357
  const unique = new Map();
351
- for (const reference of references) {
358
+ for (let index = 0; index < references.length; index += 1) {
359
+ if ((index & 63) === 0) assertActive?.();
360
+ const reference = references[index];
352
361
  const dataUrl = reference.get();
353
362
  const identity = canonicalInlineImageIdentity(dataUrl);
354
363
  if (!identity) continue;
@@ -422,6 +431,7 @@ export async function optimizeImagesInBody(reqBody, {
422
431
  }
423
432
 
424
433
  export async function prepareResponsesPayload(reqBody, {
434
+ assertActive,
425
435
  currentInputStart = 0,
426
436
  maxBytes = MAX_UPSTREAM_BODY_BYTES,
427
437
  optimizeImage = optimizeImageDataUrl,
@@ -429,18 +439,22 @@ export async function prepareResponsesPayload(reqBody, {
429
439
  skipInitialOptimization = false,
430
440
  signal,
431
441
  } = {}) {
442
+ assertActive?.();
432
443
  const shouldOptimizeImages = !IMG_OPT_DISABLED && !skipInitialOptimization;
433
- const collection = shouldOptimizeImages ? collectImageReferences(reqBody) : null;
434
- const originalImages = collection ? uniqueOriginalImages(collection.references) : [];
444
+ const collection = shouldOptimizeImages ? collectImageReferences(reqBody, assertActive) : null;
445
+ const originalImages = collection ? uniqueOriginalImages(collection.references, assertActive) : [];
435
446
  if (collection) {
436
447
  await optimizeImageReferences(collection, {
448
+ assertActive,
437
449
  concurrency: IMG_CONCURRENCY,
438
450
  model: reqBody.model,
439
451
  optimizeImage: (dataUrl, options) => optimizeImage(dataUrl, { ...options, quality: IMG_QUALITY }),
440
452
  signal,
441
453
  });
442
454
  }
455
+ assertActive?.();
443
456
  let bodyText = JSON.stringify(reqBody);
457
+ assertActive?.();
444
458
  let bodyBytes = Buffer.byteLength(bodyText);
445
459
  const summary = summarizeReqBody(reqBody);
446
460
  const targetBytes = positiveInt(maxBytes, MAX_UPSTREAM_BODY_BYTES);
@@ -450,11 +464,13 @@ export async function prepareResponsesPayload(reqBody, {
450
464
  if (collection && originalImages.length > 0 && bodyBytes > targetBytes) {
451
465
  const batchSize = Math.max(1, IMG_CONCURRENCY);
452
466
  for (const profile of Array.isArray(profiles) ? profiles : []) {
467
+ assertActive?.();
453
468
  const beforeBytes = bodyBytes;
454
469
  let processed = 0;
455
470
  stage = `q${positiveInt(profile.quality, IMG_QUALITY, 100)}`;
456
471
  const orderedImages = orderImagesByCurrentWireBytes(originalImages);
457
472
  for (let index = 0; index < orderedImages.length && bodyBytes > targetBytes; index += batchSize) {
473
+ assertActive?.();
458
474
  const batch = orderedImages.slice(index, index + batchSize);
459
475
  const candidates = await Promise.all(batch.map((image) => optimizeImage(image.dataUrl, {
460
476
  ...profile,
@@ -462,6 +478,7 @@ export async function prepareResponsesPayload(reqBody, {
462
478
  model: reqBody.model,
463
479
  signal,
464
480
  })));
481
+ assertActive?.();
465
482
  for (let offset = 0; offset < batch.length && bodyBytes > targetBytes; offset += 1) {
466
483
  processed += 1;
467
484
  const applied = applyImageCandidate(collection, batch[offset], candidates[offset], bodyBytes);
@@ -469,19 +486,24 @@ export async function prepareResponsesPayload(reqBody, {
469
486
  adapted ||= applied.changed;
470
487
  if (bodyBytes <= targetBytes) {
471
488
  collection.commitDirty();
489
+ assertActive?.();
472
490
  bodyText = JSON.stringify(reqBody);
491
+ assertActive?.();
473
492
  bodyBytes = Buffer.byteLength(bodyText);
474
493
  }
475
494
  }
476
495
  }
477
496
  collection.commitDirty();
497
+ assertActive?.();
478
498
  bodyText = JSON.stringify(reqBody);
499
+ assertActive?.();
479
500
  bodyBytes = Buffer.byteLength(bodyText);
480
501
  console.warn(status("warn", `responses payload ${beforeBytes}b exceeds ${targetBytes}b; image profile max_dim=${profile.maxDim} quality=${profile.quality} processed=${processed}/${orderedImages.length} -> ${bodyBytes}b`));
481
502
  if (bodyBytes <= targetBytes) break;
482
503
  }
483
504
  }
484
505
 
506
+ assertActive?.();
485
507
  return {
486
508
  bodyText,
487
509
  bodyBytes,
@@ -30,8 +30,9 @@ export function shouldShowLegacyCommandWarning({
30
30
  }
31
31
  try {
32
32
  atomicWriteFileSync(filePath, `${now}\n`, { mode: 0o600 });
33
+ return true;
33
34
  } catch {
34
- // The warning remains best effort when the cache directory is read-only.
35
+ // Stay silent when the throttle cannot persist, otherwise every invocation would warn.
36
+ return false;
35
37
  }
36
- return true;
37
38
  }
package/src/models.mjs CHANGED
@@ -95,11 +95,6 @@ function numericLimit(value, fallback) {
95
95
  return Number.isFinite(value) && value > 0 ? value : fallback;
96
96
  }
97
97
 
98
- function hasMessagesEndpoint(model) {
99
- const endpoints = Array.isArray(model?.supported_endpoints) ? model.supported_endpoints : [];
100
- return endpoints.includes("/v1/messages") || endpoints.includes("/chat/completions");
101
- }
102
-
103
98
  function hasOpenAIEndpoint(model) {
104
99
  const endpoints = Array.isArray(model?.supported_endpoints) ? model.supported_endpoints : [];
105
100
  return endpoints.includes("/responses") || endpoints.includes("/v1/responses") || endpoints.includes("/chat/completions");
@@ -140,13 +135,22 @@ function uniqueIds(models, predicate) {
140
135
  return ids;
141
136
  }
142
137
 
143
- function isClaudeCopilotModel(model) {
138
+ export function isClaudeCopilotCatalogEntry(model) {
144
139
  const id = String(model?.id || "").trim();
145
- const vendor = String(model?.vendor || "").toLowerCase();
146
- return id
147
- && (id.startsWith("claude-") || vendor === "anthropic")
140
+ const vendor = String(model?.vendor || model?.owned_by || "").trim().toLowerCase();
141
+ return Boolean(id) && (id.toLowerCase().startsWith("claude-") || vendor === "anthropic");
142
+ }
143
+
144
+ export function isClaudeCopilotModel(model) {
145
+ const policy = String(model?.policy?.state || "").trim().toLowerCase();
146
+ const declaredOwners = [model?.vendor, model?.owned_by]
147
+ .map((value) => String(value || "").trim().toLowerCase())
148
+ .filter(Boolean);
149
+ return isClaudeCopilotCatalogEntry(model)
150
+ && declaredOwners.every((owner) => owner === "anthropic")
148
151
  && model?.model_picker_enabled !== false
149
- && hasMessagesEndpoint(model);
152
+ && (!policy || policy === "enabled")
153
+ && modelSupportsChatCompletions(model);
150
154
  }
151
155
 
152
156
  export function gptModelIdsFromCopilotModels(models) {
@@ -150,6 +150,15 @@ function safeClientRuntimeStatus(client) {
150
150
  };
151
151
  }
152
152
 
153
+ function safeProfileCurrent(checkProfileCurrent) {
154
+ if (typeof checkProfileCurrent !== "function") return false;
155
+ try {
156
+ return checkProfileCurrent() === true;
157
+ } catch {
158
+ return false;
159
+ }
160
+ }
161
+
153
162
  export function runtimeStatusPayload({
154
163
  metrics,
155
164
  streamPerformance,
@@ -161,6 +170,7 @@ export function runtimeStatusPayload({
161
170
  codexModelRegistry,
162
171
  claudeModelRegistry,
163
172
  claudeMode = "inherited",
173
+ isClaudeProfileCurrent,
164
174
  } = {}) {
165
175
  const memory = process.memoryUsage();
166
176
  const resolvedClaudeMode = claudeMode === "isolated" ? "isolated" : "inherited";
@@ -172,6 +182,8 @@ export function runtimeStatusPayload({
172
182
  const claudeModels = resolvedClaudeMode === "isolated"
173
183
  ? modelRegistryStatus(claudeModelRegistry)
174
184
  : codexModels;
185
+ const claudeProfileCurrent = resolvedClaudeMode === "isolated"
186
+ && safeProfileCurrent(isClaudeProfileCurrent);
175
187
  return {
176
188
  ...adapterHealthPayload(),
177
189
  uptime_ms: Math.round(process.uptime() * 1000),
@@ -192,7 +204,12 @@ export function runtimeStatusPayload({
192
204
  models: codexModels,
193
205
  profiles: {
194
206
  codex: { mode: "legacy", client: codexRuntime, models: codexModels },
195
- claude: { mode: resolvedClaudeMode, client: claudeRuntime, models: claudeModels },
207
+ claude: {
208
+ mode: resolvedClaudeMode,
209
+ profile_current: claudeProfileCurrent,
210
+ client: claudeRuntime,
211
+ models: claudeModels,
212
+ },
196
213
  },
197
214
  routing: profileRouting({ claudeMode: resolvedClaudeMode }),
198
215
  limits: {