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.
- package/README.md +12 -3
- package/bin/cli.mjs +17 -3
- package/package.json +1 -1
- package/scripts/benchmark.mjs +58 -1
- package/src/adapter.mjs +1 -0
- package/src/cli-auth.mjs +101 -14
- package/src/cli-models.mjs +74 -12
- package/src/cli-options.mjs +43 -16
- package/src/cli-table.mjs +114 -0
- package/src/copilot-client.mjs +3 -1
- package/src/doctor.mjs +9 -8
- package/src/http-transport.mjs +114 -13
- package/src/image-optimization.mjs +33 -11
- package/src/legacy-command-warning.mjs +3 -2
- package/src/models.mjs +14 -10
- package/src/observability.mjs +18 -1
- package/src/pm-studio-setup.mjs +28 -4
- package/src/pm-studio-status.mjs +177 -25
- package/src/profile-routing.mjs +7 -16
- package/src/profile-runtime.mjs +12 -3
- package/src/response-history.mjs +12 -2
- package/src/responses-byte-budget.mjs +53 -26
- package/src/responses-chat-payload.mjs +35 -16
- package/src/responses-content.mjs +36 -4
- package/src/responses-handler.mjs +47 -27
- package/src/responses-image-limit.mjs +16 -2
- package/src/responses-image-pressure.mjs +10 -4
- package/src/responses-request.mjs +28 -10
- package/src/usage.mjs +104 -9
|
@@ -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
|
+
}
|
package/src/copilot-client.mjs
CHANGED
|
@@ -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)
|
|
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(
|
|
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({
|
package/src/http-transport.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
|
87
|
-
|
|
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 = () => ({
|
|
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
|
-
|
|
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().
|
|
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
|
-
|
|
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"],
|
|
535
|
+
decoded = await decodeRequestBuffer(buffer, req.headers?.["content-encoding"], tentativeDecodeLimit);
|
|
453
536
|
} catch (e) {
|
|
454
|
-
if (e?.statusCode)
|
|
455
|
-
|
|
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
|
-
|
|
556
|
+
if (supportsResizableReservation) decodedReservation.resize(decoded.length);
|
|
557
|
+
else decodedReservation = await admission?.reserveDecodedBody?.(decoded.length, { signal });
|
|
460
558
|
}
|
|
461
|
-
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
-
//
|
|
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
|
|
138
|
+
export function isClaudeCopilotCatalogEntry(model) {
|
|
144
139
|
const id = String(model?.id || "").trim();
|
|
145
|
-
const vendor = String(model?.vendor || "").toLowerCase();
|
|
146
|
-
return id
|
|
147
|
-
|
|
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
|
-
&&
|
|
152
|
+
&& (!policy || policy === "enabled")
|
|
153
|
+
&& modelSupportsChatCompletions(model);
|
|
150
154
|
}
|
|
151
155
|
|
|
152
156
|
export function gptModelIdsFromCopilotModels(models) {
|
package/src/observability.mjs
CHANGED
|
@@ -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: {
|
|
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: {
|