codex-copilot-dx 0.6.15 → 0.6.19
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 +28 -15
- package/bin/cli.mjs +7 -1
- package/package.json +1 -1
- package/src/adapter.mjs +47 -10
- package/src/anthropic-handler.mjs +25 -6
- package/src/anthropic.mjs +19 -7
- package/src/atomic-file.mjs +76 -2
- package/src/auth-profile.mjs +11 -34
- package/src/auth.mjs +265 -52
- package/src/cli-auth.mjs +124 -94
- package/src/cli-error.mjs +44 -0
- package/src/cli-options.mjs +8 -5
- package/src/copilot-client.mjs +5 -1
- package/src/copilot-responses-compat.mjs +9 -2
- package/src/copilot.mjs +2 -1
- package/src/http-transport.mjs +138 -0
- package/src/lock.mjs +199 -17
- package/src/models.mjs +17 -0
- package/src/pm-studio-asar.mjs +399 -19
- package/src/pm-studio-relay.mjs +26 -5
- package/src/pm-studio-setup.mjs +351 -16
- package/src/pm-studio-status.mjs +5 -1
- package/src/response-history.mjs +122 -13
- package/src/responses-bridge.mjs +22 -4
- package/src/responses-handler.mjs +123 -36
- package/src/responses-image-pressure.mjs +30 -0
- package/src/responses-proxy.mjs +13 -1
- package/src/responses-request.mjs +17 -5
- package/src/runtime-config.mjs +10 -0
- package/src/token-counter-service.mjs +188 -0
- package/src/token-counter-worker.mjs +10 -0
- package/src/usage.mjs +25 -7
package/src/runtime-config.mjs
CHANGED
|
@@ -7,6 +7,8 @@ export const RUNTIME_DEFAULTS = Object.freeze({
|
|
|
7
7
|
maxBodyBytes: 64 * 1024 * 1024,
|
|
8
8
|
maxDecodedBodyBytes: 128 * 1024 * 1024,
|
|
9
9
|
maxInflightBodyBytes: 32 * 1024 * 1024,
|
|
10
|
+
maxUpstreamChatResponseBytes: 64 * 1024 * 1024,
|
|
11
|
+
maxUpstreamResponsesResponseBytes: 256 * 1024 * 1024,
|
|
10
12
|
maxQueuedRequests: 16,
|
|
11
13
|
requestQueueTimeoutMs: 120 * 1000,
|
|
12
14
|
responseHistoryMaxBytes: 64 * 1024 * 1024,
|
|
@@ -30,6 +32,14 @@ export function loadRuntimeConfig(env = process.env) {
|
|
|
30
32
|
maxBodyBytes: parsePositiveInteger(env.CCDX_MAX_BODY_BYTES, RUNTIME_DEFAULTS.maxBodyBytes),
|
|
31
33
|
maxDecodedBodyBytes: parsePositiveInteger(env.CCDX_MAX_DECODED_BODY_BYTES, RUNTIME_DEFAULTS.maxDecodedBodyBytes),
|
|
32
34
|
maxInflightBodyBytes: parsePositiveInteger(env.CCDX_MAX_INFLIGHT_BODY_BYTES, RUNTIME_DEFAULTS.maxInflightBodyBytes),
|
|
35
|
+
maxUpstreamChatResponseBytes: parsePositiveInteger(
|
|
36
|
+
env.CCDX_MAX_UPSTREAM_CHAT_RESPONSE_BYTES,
|
|
37
|
+
RUNTIME_DEFAULTS.maxUpstreamChatResponseBytes,
|
|
38
|
+
),
|
|
39
|
+
maxUpstreamResponsesResponseBytes: parsePositiveInteger(
|
|
40
|
+
env.CCDX_MAX_UPSTREAM_RESPONSES_RESPONSE_BYTES,
|
|
41
|
+
RUNTIME_DEFAULTS.maxUpstreamResponsesResponseBytes,
|
|
42
|
+
),
|
|
33
43
|
maxQueuedRequests: parsePositiveInteger(env.CCDX_MAX_QUEUED_REQUESTS, RUNTIME_DEFAULTS.maxQueuedRequests),
|
|
34
44
|
requestQueueTimeoutMs: parsePositiveInteger(env.CCDX_REQUEST_QUEUE_TIMEOUT_MS, RUNTIME_DEFAULTS.requestQueueTimeoutMs),
|
|
35
45
|
responseHistoryMaxBytes: parsePositiveInteger(env.CCDX_RESPONSE_HISTORY_MAX_BYTES, RUNTIME_DEFAULTS.responseHistoryMaxBytes),
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { Worker } from "node:worker_threads";
|
|
2
|
+
import { anthropicTokenText } from "./anthropic.mjs";
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_TOKEN_COUNTER_TIMEOUT_MS = 120 * 1000;
|
|
5
|
+
export const DEFAULT_TOKEN_COUNTER_MAX_QUEUED = 16;
|
|
6
|
+
|
|
7
|
+
function serviceError(message, statusCode) {
|
|
8
|
+
return Object.assign(new Error(message), { statusCode });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function positiveInteger(value, fallback) {
|
|
12
|
+
return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function abortError(signal) {
|
|
16
|
+
if (signal?.reason instanceof Error) return signal.reason;
|
|
17
|
+
const error = new Error("The operation was aborted");
|
|
18
|
+
error.name = "AbortError";
|
|
19
|
+
return error;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function tokenCounterWorkerExecArgv(execArgv = process.execArgv) {
|
|
23
|
+
const filtered = [];
|
|
24
|
+
for (let index = 0; index < execArgv.length; index += 1) {
|
|
25
|
+
const value = execArgv[index];
|
|
26
|
+
if (value === "--input-type") {
|
|
27
|
+
index += 1;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (value.startsWith("--input-type=")) continue;
|
|
31
|
+
filtered.push(value);
|
|
32
|
+
}
|
|
33
|
+
return filtered;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function createTokenCounterWorker() {
|
|
37
|
+
const execArgv = tokenCounterWorkerExecArgv();
|
|
38
|
+
const options = execArgv.length === process.execArgv.length ? undefined : { execArgv };
|
|
39
|
+
return new Worker(new URL("./token-counter-worker.mjs", import.meta.url), options);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createTokenCounterService({
|
|
43
|
+
timeoutMs = DEFAULT_TOKEN_COUNTER_TIMEOUT_MS,
|
|
44
|
+
maxQueued = DEFAULT_TOKEN_COUNTER_MAX_QUEUED,
|
|
45
|
+
workerFactory = createTokenCounterWorker,
|
|
46
|
+
} = {}) {
|
|
47
|
+
timeoutMs = positiveInteger(timeoutMs, DEFAULT_TOKEN_COUNTER_TIMEOUT_MS);
|
|
48
|
+
maxQueued = positiveInteger(maxQueued, DEFAULT_TOKEN_COUNTER_MAX_QUEUED);
|
|
49
|
+
const queue = [];
|
|
50
|
+
let worker = null;
|
|
51
|
+
let active = null;
|
|
52
|
+
let nextId = 1;
|
|
53
|
+
let closed = false;
|
|
54
|
+
|
|
55
|
+
const finish = (job, callback, value) => {
|
|
56
|
+
if (job.finished) return;
|
|
57
|
+
job.finished = true;
|
|
58
|
+
if (job.timer) clearTimeout(job.timer);
|
|
59
|
+
job.signal?.removeEventListener("abort", job.onAbort);
|
|
60
|
+
callback(value);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const retireWorker = () => {
|
|
64
|
+
const retired = worker;
|
|
65
|
+
worker = null;
|
|
66
|
+
if (!retired) return;
|
|
67
|
+
retired.removeAllListeners();
|
|
68
|
+
retired.on("error", () => {});
|
|
69
|
+
Promise.resolve(retired.terminate()).catch(() => {});
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const pump = () => {
|
|
73
|
+
if (closed || active || queue.length === 0) return;
|
|
74
|
+
if (!worker) {
|
|
75
|
+
try {
|
|
76
|
+
worker = workerFactory();
|
|
77
|
+
} catch {
|
|
78
|
+
const job = queue.shift();
|
|
79
|
+
if (job) finish(job, job.reject, serviceError("Token counter worker failed", 500));
|
|
80
|
+
queueMicrotask(pump);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
worker.on("message", (message) => {
|
|
84
|
+
if (!active || message?.id !== active.id) return;
|
|
85
|
+
const job = active;
|
|
86
|
+
active = null;
|
|
87
|
+
if (message.error) {
|
|
88
|
+
retireWorker();
|
|
89
|
+
finish(job, job.reject, serviceError("Token counter worker failed", 500));
|
|
90
|
+
} else {
|
|
91
|
+
finish(job, job.resolve, message.result);
|
|
92
|
+
}
|
|
93
|
+
pump();
|
|
94
|
+
});
|
|
95
|
+
worker.on("error", () => {
|
|
96
|
+
const job = active;
|
|
97
|
+
active = null;
|
|
98
|
+
retireWorker();
|
|
99
|
+
if (job) finish(job, job.reject, serviceError("Token counter worker failed", 500));
|
|
100
|
+
pump();
|
|
101
|
+
});
|
|
102
|
+
worker.on("exit", () => {
|
|
103
|
+
const job = active;
|
|
104
|
+
active = null;
|
|
105
|
+
worker = null;
|
|
106
|
+
if (job) finish(job, job.reject, serviceError("Token counter worker failed", 500));
|
|
107
|
+
pump();
|
|
108
|
+
});
|
|
109
|
+
worker.unref?.();
|
|
110
|
+
}
|
|
111
|
+
const job = queue.shift();
|
|
112
|
+
if (!job || job.finished) return pump();
|
|
113
|
+
active = job;
|
|
114
|
+
try {
|
|
115
|
+
worker.postMessage({ id: job.id, text: job.text });
|
|
116
|
+
job.text = null;
|
|
117
|
+
} catch {
|
|
118
|
+
active = null;
|
|
119
|
+
retireWorker();
|
|
120
|
+
finish(job, job.reject, serviceError("Token counter worker failed", 500));
|
|
121
|
+
pump();
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const cancel = (job, error) => {
|
|
126
|
+
if (job.finished) return;
|
|
127
|
+
const queuedIndex = queue.indexOf(job);
|
|
128
|
+
if (queuedIndex >= 0) queue.splice(queuedIndex, 1);
|
|
129
|
+
if (active === job) {
|
|
130
|
+
active = null;
|
|
131
|
+
retireWorker();
|
|
132
|
+
}
|
|
133
|
+
finish(job, job.reject, error);
|
|
134
|
+
pump();
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
count(body, { signal, deadlineMs = timeoutMs } = {}) {
|
|
139
|
+
if (closed) return Promise.reject(serviceError("Token counter service is closed", 503));
|
|
140
|
+
if (signal?.aborted) return Promise.reject(abortError(signal));
|
|
141
|
+
let text;
|
|
142
|
+
try {
|
|
143
|
+
text = anthropicTokenText(body);
|
|
144
|
+
} catch {
|
|
145
|
+
return Promise.reject(serviceError("Invalid count_tokens request", 400));
|
|
146
|
+
}
|
|
147
|
+
if (active && queue.length >= maxQueued) {
|
|
148
|
+
return Promise.reject(serviceError(`Token counter queue is full (${maxQueued} waiting)`, 503));
|
|
149
|
+
}
|
|
150
|
+
return new Promise((resolve, reject) => {
|
|
151
|
+
deadlineMs = positiveInteger(deadlineMs, timeoutMs);
|
|
152
|
+
const job = {
|
|
153
|
+
id: nextId++,
|
|
154
|
+
text,
|
|
155
|
+
signal,
|
|
156
|
+
resolve,
|
|
157
|
+
reject,
|
|
158
|
+
finished: false,
|
|
159
|
+
timer: null,
|
|
160
|
+
onAbort: null,
|
|
161
|
+
};
|
|
162
|
+
job.onAbort = () => cancel(job, abortError(signal));
|
|
163
|
+
signal?.addEventListener("abort", job.onAbort, { once: true });
|
|
164
|
+
job.timer = setTimeout(() => {
|
|
165
|
+
cancel(job, serviceError(`Token counting timed out after ${deadlineMs}ms`, 504));
|
|
166
|
+
}, deadlineMs);
|
|
167
|
+
queue.push(job);
|
|
168
|
+
if (signal?.aborted) job.onAbort();
|
|
169
|
+
else pump();
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
close() {
|
|
173
|
+
if (closed) return;
|
|
174
|
+
closed = true;
|
|
175
|
+
const error = serviceError("Token counter service is closed", 503);
|
|
176
|
+
if (active) {
|
|
177
|
+
const job = active;
|
|
178
|
+
active = null;
|
|
179
|
+
finish(job, job.reject, error);
|
|
180
|
+
}
|
|
181
|
+
for (const job of queue.splice(0)) finish(job, job.reject, error);
|
|
182
|
+
retireWorker();
|
|
183
|
+
},
|
|
184
|
+
stats() {
|
|
185
|
+
return { active: active ? 1 : 0, queued: queue.length, worker: worker ? 1 : 0 };
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { parentPort } from "node:worker_threads";
|
|
2
|
+
import { countTokenText } from "./anthropic.mjs";
|
|
3
|
+
|
|
4
|
+
parentPort.on("message", async ({ id, text }) => {
|
|
5
|
+
try {
|
|
6
|
+
parentPort.postMessage({ id, result: await countTokenText(text) });
|
|
7
|
+
} catch {
|
|
8
|
+
parentPort.postMessage({ id, error: true });
|
|
9
|
+
}
|
|
10
|
+
});
|
package/src/usage.mjs
CHANGED
|
@@ -150,7 +150,7 @@ export async function flushUsageWrites() {
|
|
|
150
150
|
await writeQueue;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
async function* iterateUsageRecords(filePath) {
|
|
153
|
+
async function* iterateUsageRecords(filePath, { warn = console.error } = {}) {
|
|
154
154
|
let input;
|
|
155
155
|
try {
|
|
156
156
|
input = fs.createReadStream(filePath, { encoding: "utf8" });
|
|
@@ -165,14 +165,31 @@ async function* iterateUsageRecords(filePath) {
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
const lines = readline.createInterface({ input, crlfDelay: Infinity });
|
|
168
|
+
let invalidRecords = 0;
|
|
168
169
|
for await (const line of lines) {
|
|
169
|
-
if (line)
|
|
170
|
+
if (!line) continue;
|
|
171
|
+
try {
|
|
172
|
+
const record = JSON.parse(line);
|
|
173
|
+
if (!record || typeof record !== "object" || Array.isArray(record)) {
|
|
174
|
+
invalidRecords += 1;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
yield record;
|
|
178
|
+
} catch {
|
|
179
|
+
invalidRecords += 1;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (invalidRecords > 0) {
|
|
183
|
+
try {
|
|
184
|
+
const safePath = terminalCell(JSON.stringify(filePath));
|
|
185
|
+
warn(`codex-copilot-dx usage log ignored ${invalidRecords} invalid record${invalidRecords === 1 ? "" : "s"} in ${safePath}`);
|
|
186
|
+
} catch {}
|
|
170
187
|
}
|
|
171
188
|
}
|
|
172
189
|
|
|
173
|
-
export async function readUsageRecords(filePath = usageLogPath()) {
|
|
190
|
+
export async function readUsageRecords(filePath = usageLogPath(), { warn = console.error } = {}) {
|
|
174
191
|
const records = [];
|
|
175
|
-
for await (const record of iterateUsageRecords(filePath)) records.push(record);
|
|
192
|
+
for await (const record of iterateUsageRecords(filePath, { warn })) records.push(record);
|
|
176
193
|
return records;
|
|
177
194
|
}
|
|
178
195
|
|
|
@@ -200,10 +217,10 @@ export function summarizeUsage(records) {
|
|
|
200
217
|
return summary;
|
|
201
218
|
}
|
|
202
219
|
|
|
203
|
-
export async function summarizeUsageLogs(filePath = usageLogPath()) {
|
|
220
|
+
export async function summarizeUsageLogs(filePath = usageLogPath(), { warn = console.error } = {}) {
|
|
204
221
|
const summary = { requests: 0, totals: {}, byModel: {} };
|
|
205
222
|
for (const candidate of [rotatedFilePath(filePath), filePath]) {
|
|
206
|
-
for await (const record of iterateUsageRecords(candidate)) {
|
|
223
|
+
for await (const record of iterateUsageRecords(candidate, { warn })) {
|
|
207
224
|
const one = summarizeUsage([record]);
|
|
208
225
|
summary.requests += one.requests;
|
|
209
226
|
addUsageTotals(summary.totals, one.totals);
|
|
@@ -324,7 +341,8 @@ export async function printUsageSummary({
|
|
|
324
341
|
format = "auto",
|
|
325
342
|
output = process.stdout,
|
|
326
343
|
log = console.log,
|
|
344
|
+
warn = console.error,
|
|
327
345
|
} = {}) {
|
|
328
|
-
const summary = await summarizeUsageLogs(filePath);
|
|
346
|
+
const summary = await summarizeUsageLogs(filePath, { warn });
|
|
329
347
|
log(formatUsageSummary(summary, { filePath, format, output }));
|
|
330
348
|
}
|