codex-copilot-dx 0.6.21 → 0.7.1
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 +142 -323
- package/bin/cli.mjs +26 -130
- package/package.json +2 -8
- package/scripts/benchmark.mjs +0 -114
- package/src/adapter.mjs +22 -107
- package/src/auth-profile.mjs +18 -130
- package/src/cli-animation.mjs +108 -0
- package/src/cli-auth.mjs +44 -398
- package/src/cli-models.mjs +21 -49
- package/src/cli-options.mjs +53 -75
- package/src/cli-status.mjs +6 -23
- package/src/config.mjs +2 -8
- package/src/copilot.mjs +2 -2
- package/src/doctor.mjs +44 -291
- package/src/model-cache.mjs +4 -11
- package/src/models.mjs +0 -184
- package/src/observability.mjs +1 -38
- package/src/profile-model-runtime.mjs +61 -189
- package/src/profile-routing.mjs +2 -102
- package/src/profile-runtime.mjs +15 -68
- package/src/responses-handler.mjs +1 -1
- package/src/responses-proxy.mjs +1 -1
- package/src/running-adapter.mjs +2 -4
- package/src/stream-errors.mjs +3 -6
- package/src/stream-performance.mjs +1 -1
- package/src/terminal-activity.mjs +40 -34
- package/src/terminal-animation.mjs +249 -0
- package/src/usage.mjs +0 -31
- package/src/user-settings.mjs +50 -0
- package/src/anthropic-handler.mjs +0 -195
- package/src/anthropic.mjs +0 -387
- package/src/claude-config.mjs +0 -57
- package/src/claude-desktop-config.mjs +0 -317
- package/src/cli-error.mjs +0 -50
- package/src/pm-studio-asar.mjs +0 -1244
- package/src/pm-studio-bundle-content.mjs +0 -165
- package/src/pm-studio-relay.mjs +0 -563
- package/src/pm-studio-setup-lock.mjs +0 -336
- package/src/pm-studio-setup.mjs +0 -1581
- package/src/pm-studio-status.mjs +0 -369
- package/src/token-counter-service.mjs +0 -211
- package/src/token-counter-worker.mjs +0 -16
package/src/pm-studio-status.mjs
DELETED
|
@@ -1,369 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
AUTH_PROFILE_CLAUDE,
|
|
6
|
-
readAuthProfileCredentials,
|
|
7
|
-
} from "./auth-profile.mjs";
|
|
8
|
-
import { PM_STUDIO_RECIPES } from "./pm-studio-asar.mjs";
|
|
9
|
-
import {
|
|
10
|
-
assertPatchedBinaryRecord,
|
|
11
|
-
createPmStudioStatusOperationOverrides,
|
|
12
|
-
createPmStudioSetupOperations,
|
|
13
|
-
DEFAULT_PM_STUDIO_APP_PATH,
|
|
14
|
-
inspectPmStudioApp,
|
|
15
|
-
PM_STUDIO_CLAUDE_AUTH_COMMAND,
|
|
16
|
-
pmStudioBackupRoot,
|
|
17
|
-
pmStudioPatchManifestPath,
|
|
18
|
-
resolvePmStudioCompatibleRecipe,
|
|
19
|
-
} from "./pm-studio-setup.mjs";
|
|
20
|
-
import { readAdapterStatus } from "./cli-status.mjs";
|
|
21
|
-
import {
|
|
22
|
-
PM_STUDIO_RELAY_MARKER_HEADER,
|
|
23
|
-
PM_STUDIO_RELAY_MARKER_VALUE,
|
|
24
|
-
PM_STUDIO_RELAY_PREFIX,
|
|
25
|
-
} from "./pm-studio-relay.mjs";
|
|
26
|
-
import { checkRunningAdapter } from "./running-adapter.mjs";
|
|
27
|
-
import { status } from "./status.mjs";
|
|
28
|
-
import {
|
|
29
|
-
cliOutputFormat,
|
|
30
|
-
cliOutputWidth,
|
|
31
|
-
formatResponsiveCliTable,
|
|
32
|
-
terminalCell,
|
|
33
|
-
} from "./cli-table.mjs";
|
|
34
|
-
|
|
35
|
-
const PM_RELAY_HOST = "127.0.0.1";
|
|
36
|
-
const PM_RELAY_PORT = 2026;
|
|
37
|
-
const PM_RELAY_PROBE_TIMEOUT_MS = 500;
|
|
38
|
-
const PM_RELAY_CAPABILITY = "pm_studio_split_origin_v1";
|
|
39
|
-
const PM_RELAY_ROUTE_NAMES = Object.freeze([
|
|
40
|
-
"pm_models",
|
|
41
|
-
"pm_chat_completions",
|
|
42
|
-
]);
|
|
43
|
-
|
|
44
|
-
function safeMessage(error) {
|
|
45
|
-
return String(error?.message || "inspection failed").replace(/\s+/g, " ").trim();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export async function probePmStudioRelay({
|
|
49
|
-
baseUrl,
|
|
50
|
-
fetchImpl = fetch,
|
|
51
|
-
timeoutMs = PM_RELAY_PROBE_TIMEOUT_MS,
|
|
52
|
-
} = {}) {
|
|
53
|
-
const controller = new AbortController();
|
|
54
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
55
|
-
try {
|
|
56
|
-
const response = await fetchImpl(new URL(`${PM_STUDIO_RELAY_PREFIX}/models`, baseUrl).href, {
|
|
57
|
-
method: "GET",
|
|
58
|
-
headers: { Accept: "application/json" },
|
|
59
|
-
redirect: "error",
|
|
60
|
-
signal: controller.signal,
|
|
61
|
-
});
|
|
62
|
-
const result = {
|
|
63
|
-
status: response.status,
|
|
64
|
-
marker: response.headers.get(PM_STUDIO_RELAY_MARKER_HEADER) || "",
|
|
65
|
-
};
|
|
66
|
-
await response.body?.cancel().catch(() => {});
|
|
67
|
-
return result;
|
|
68
|
-
} finally {
|
|
69
|
-
clearTimeout(timer);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function inspectClaudeProfile(home, readCredentials) {
|
|
74
|
-
try {
|
|
75
|
-
const credentials = readCredentials(AUTH_PROFILE_CLAUDE, { home });
|
|
76
|
-
return {
|
|
77
|
-
configured: credentials.configured === true,
|
|
78
|
-
valid: credentials.valid === true,
|
|
79
|
-
reason: credentials.reason || "",
|
|
80
|
-
login: credentials.identity?.login || "",
|
|
81
|
-
};
|
|
82
|
-
} catch (error) {
|
|
83
|
-
return { configured: true, valid: false, reason: safeMessage(error), login: "" };
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function inspectAdapterRuntime(adapter, runtimeStatus) {
|
|
88
|
-
const data = runtimeStatus?.data;
|
|
89
|
-
const health = adapter?.data;
|
|
90
|
-
const issues = [];
|
|
91
|
-
if (!data || typeof data !== "object") {
|
|
92
|
-
return { ok: false, baseUrl: runtimeStatus?.baseUrl || adapter?.baseUrl, data: null, issues: ["runtime status payload is missing"] };
|
|
93
|
-
}
|
|
94
|
-
if (data.version !== health?.version
|
|
95
|
-
|| data.protocol_version !== health?.protocol_version
|
|
96
|
-
|| data.instance_id !== health?.instance_id
|
|
97
|
-
|| data.pid !== health?.pid) {
|
|
98
|
-
issues.push("health and runtime status do not describe the same adapter instance");
|
|
99
|
-
}
|
|
100
|
-
const capabilities = new Set(Array.isArray(data.capabilities) ? data.capabilities : []);
|
|
101
|
-
if (!capabilities.has(PM_RELAY_CAPABILITY)) issues.push("PM Studio split-origin relay capability is missing");
|
|
102
|
-
if (data.profiles?.claude?.mode !== "isolated") issues.push("Claude profile mode is not isolated");
|
|
103
|
-
if (data.profiles?.claude?.profile_current !== true) {
|
|
104
|
-
issues.push("running Claude credentials do not match the current isolated profile");
|
|
105
|
-
}
|
|
106
|
-
if (data.routing?.responses !== "codex") issues.push("/v1/responses is not routed to Codex");
|
|
107
|
-
if (data.routing?.messages !== "claude") issues.push("/v1/messages is not routed to Claude");
|
|
108
|
-
const routes = data.requests?.by_route;
|
|
109
|
-
for (const routeName of PM_RELAY_ROUTE_NAMES) {
|
|
110
|
-
if (!routes || typeof routes !== "object" || !Object.hasOwn(routes, routeName)) {
|
|
111
|
-
issues.push(`runtime status does not expose ${routeName}`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return {
|
|
115
|
-
...runtimeStatus,
|
|
116
|
-
ok: issues.length === 0,
|
|
117
|
-
issues,
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function inspectRelayProbe(runtime, probe) {
|
|
122
|
-
const relayProbe = {
|
|
123
|
-
status: probe?.status,
|
|
124
|
-
marker: String(probe?.marker || ""),
|
|
125
|
-
};
|
|
126
|
-
const issues = [...runtime.issues];
|
|
127
|
-
if (relayProbe.status !== 401) {
|
|
128
|
-
issues.push(`PM Studio relay probe returned HTTP ${relayProbe.status ?? "unknown"}; expected 401`);
|
|
129
|
-
}
|
|
130
|
-
if (relayProbe.marker !== PM_STUDIO_RELAY_MARKER_VALUE) {
|
|
131
|
-
issues.push("PM Studio relay compatibility marker is missing or incompatible");
|
|
132
|
-
}
|
|
133
|
-
return { ...runtime, relayProbe, ok: issues.length === 0, issues };
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export async function inspectPmStudioStatus({
|
|
137
|
-
appPath = DEFAULT_PM_STUDIO_APP_PATH,
|
|
138
|
-
home = os.homedir(),
|
|
139
|
-
backupRoot = pmStudioBackupRoot(home),
|
|
140
|
-
recipes = PM_STUDIO_RECIPES,
|
|
141
|
-
operationOverrides = {},
|
|
142
|
-
existsSync = fs.existsSync,
|
|
143
|
-
inspectApp = inspectPmStudioApp,
|
|
144
|
-
verifyPatchRecord = assertPatchedBinaryRecord,
|
|
145
|
-
resolveCompatibleRecipe = resolvePmStudioCompatibleRecipe,
|
|
146
|
-
readClaudeCredentials = readAuthProfileCredentials,
|
|
147
|
-
checkRunningAdapterFn = checkRunningAdapter,
|
|
148
|
-
readAdapterStatusFn = readAdapterStatus,
|
|
149
|
-
probePmStudioRelayFn = probePmStudioRelay,
|
|
150
|
-
} = {}) {
|
|
151
|
-
const operations = createPmStudioSetupOperations({
|
|
152
|
-
...createPmStudioStatusOperationOverrides(),
|
|
153
|
-
...operationOverrides,
|
|
154
|
-
});
|
|
155
|
-
let app;
|
|
156
|
-
if (!existsSync(appPath)) {
|
|
157
|
-
app = { state: "not_installed", metadata: null, issues: [] };
|
|
158
|
-
} else {
|
|
159
|
-
try {
|
|
160
|
-
const metadata = operations.readBundleMetadata({
|
|
161
|
-
appPath,
|
|
162
|
-
infoPlistPath: path.join(appPath, "Contents/Info.plist"),
|
|
163
|
-
processRunner: operations.processRunner,
|
|
164
|
-
});
|
|
165
|
-
const normalized = {
|
|
166
|
-
version: String(metadata.version),
|
|
167
|
-
build: String(metadata.build),
|
|
168
|
-
bundleIdentifier: String(metadata.bundleIdentifier),
|
|
169
|
-
};
|
|
170
|
-
let recipe = null;
|
|
171
|
-
let compatibilityError = null;
|
|
172
|
-
try {
|
|
173
|
-
recipe = resolveCompatibleRecipe({
|
|
174
|
-
appPath,
|
|
175
|
-
home,
|
|
176
|
-
backupRoot,
|
|
177
|
-
metadata: normalized,
|
|
178
|
-
recipes,
|
|
179
|
-
operations,
|
|
180
|
-
inspectApp,
|
|
181
|
-
});
|
|
182
|
-
} catch (error) {
|
|
183
|
-
compatibilityError = error;
|
|
184
|
-
}
|
|
185
|
-
if (recipe) {
|
|
186
|
-
app = { ...inspectApp({ appPath, recipe, operations }), recipe: recipe.id };
|
|
187
|
-
if (["patched", "predecessor"].includes(app.state)) {
|
|
188
|
-
const manifestPath = pmStudioPatchManifestPath({ home, backupRoot, recipe });
|
|
189
|
-
try {
|
|
190
|
-
verifyPatchRecord({
|
|
191
|
-
inspection: app,
|
|
192
|
-
manifestPath,
|
|
193
|
-
recipe,
|
|
194
|
-
recordState: app.state,
|
|
195
|
-
});
|
|
196
|
-
app.patchRecord = { valid: true, manifestPath, reason: "" };
|
|
197
|
-
} catch (error) {
|
|
198
|
-
const reason = safeMessage(error);
|
|
199
|
-
app.patchRecipeMatched = true;
|
|
200
|
-
app.patchRecord = { valid: false, manifestPath, reason };
|
|
201
|
-
app.state = "drift";
|
|
202
|
-
app.issues = [...(Array.isArray(app.issues) ? app.issues : []), reason];
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
} else {
|
|
206
|
-
app = {
|
|
207
|
-
state: "unsupported",
|
|
208
|
-
metadata: normalized,
|
|
209
|
-
issues: compatibilityError ? [safeMessage(compatibilityError)] : [],
|
|
210
|
-
recipe: "",
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
} catch (error) {
|
|
214
|
-
app = { state: "error", metadata: null, issues: [safeMessage(error)], recipe: "" };
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const claude = inspectClaudeProfile(home, readClaudeCredentials);
|
|
219
|
-
let adapter;
|
|
220
|
-
try {
|
|
221
|
-
adapter = await checkRunningAdapterFn({ host: PM_RELAY_HOST, port: PM_RELAY_PORT });
|
|
222
|
-
} catch (error) {
|
|
223
|
-
adapter = { ok: false, error };
|
|
224
|
-
}
|
|
225
|
-
let runtime = { ok: false, baseUrl: adapter?.baseUrl, data: null, issues: ["adapter health check failed"] };
|
|
226
|
-
if (adapter?.ok === true) {
|
|
227
|
-
try {
|
|
228
|
-
runtime = inspectAdapterRuntime(adapter, await readAdapterStatusFn({
|
|
229
|
-
host: PM_RELAY_HOST,
|
|
230
|
-
port: PM_RELAY_PORT,
|
|
231
|
-
}));
|
|
232
|
-
} catch (error) {
|
|
233
|
-
runtime = { ok: false, baseUrl: adapter.baseUrl, data: null, issues: [safeMessage(error)] };
|
|
234
|
-
}
|
|
235
|
-
if (runtime.data) {
|
|
236
|
-
try {
|
|
237
|
-
runtime = inspectRelayProbe(runtime, await probePmStudioRelayFn({ baseUrl: adapter.baseUrl }));
|
|
238
|
-
} catch (error) {
|
|
239
|
-
runtime = {
|
|
240
|
-
...runtime,
|
|
241
|
-
ok: false,
|
|
242
|
-
relayProbe: null,
|
|
243
|
-
issues: [...runtime.issues, `PM Studio relay probe failed: ${safeMessage(error)}`],
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
const operational = app.state === "patched"
|
|
249
|
-
&& app.patchRecord?.valid === true
|
|
250
|
-
&& claude.valid
|
|
251
|
-
&& adapter?.ok === true
|
|
252
|
-
&& runtime.ok === true;
|
|
253
|
-
return { ok: operational, appPath, app, claude, adapter, runtime };
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function appStatusItem(result, commandName) {
|
|
257
|
-
const { app } = result;
|
|
258
|
-
const version = app.metadata ? `${app.metadata.version} build ${app.metadata.build}` : "";
|
|
259
|
-
if (app.state === "patched" && app.patchRecord?.valid === true) {
|
|
260
|
-
return { kind: "ok", component: "App patch", detail: `${version}; patched and verified`, message: `PM Studio ${version} is patched and verified against its installed patch record` };
|
|
261
|
-
}
|
|
262
|
-
if (app.state === "patched") {
|
|
263
|
-
const reason = app.patchRecord?.reason || "installed patch record is missing";
|
|
264
|
-
return { kind: "err", component: "App patch", detail: `${version}; patch record not verified`, message: `PM Studio ${version} matches the patch recipe, but its installed patch record is not verified: ${reason}` };
|
|
265
|
-
}
|
|
266
|
-
if (app.state === "predecessor" && app.patchRecord?.valid === true) {
|
|
267
|
-
return { kind: "warn", component: "App patch", detail: `${version}; verified predecessor split patch`, message: `PM Studio ${version} has a verified predecessor split patch; run ${commandName} pms setup to migrate` };
|
|
268
|
-
}
|
|
269
|
-
if (app.state === "legacy") {
|
|
270
|
-
return { kind: "err", component: "App patch", detail: `${version}; legacy global-origin patch`, message: `PM Studio ${version} has a legacy global-origin patch; run ${commandName} pms setup to migrate` };
|
|
271
|
-
}
|
|
272
|
-
if (app.state === "clean") return { kind: "warn", component: "App patch", detail: `${version}; compatible structure, not patched`, message: `PM Studio ${version} has a compatible local patch structure but is not patched; run ${commandName} pms setup` };
|
|
273
|
-
if (app.state === "unsupported") return { kind: "err", component: "App patch", detail: `${version}; structure incompatible`, message: `PM Studio ${version} does not expose one uniquely compatible patch structure; no files will be changed` };
|
|
274
|
-
if (app.state === "drift" && app.patchRecipeMatched) {
|
|
275
|
-
return { kind: "err", component: "App patch", detail: `${version}; patch record drift`, message: `PM Studio ${version} matches the patch recipe, but its installed patch record is not verified: ${app.issues.join("; ")}` };
|
|
276
|
-
}
|
|
277
|
-
if (app.state === "drift") return { kind: "err", component: "App patch", detail: `${version}; integrity drift`, message: `PM Studio ${version} does not match the clean or patched recipe: ${app.issues.join("; ")}` };
|
|
278
|
-
if (app.state === "not_installed") return { kind: "warn", component: "App patch", detail: "not installed", message: `PM Studio is not installed at ${result.appPath}` };
|
|
279
|
-
return { kind: "err", component: "App patch", detail: "inspection failed", message: `PM Studio could not be inspected: ${app.issues.join("; ")}` };
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function pmStudioStatusItems(result, commandName) {
|
|
283
|
-
const items = [appStatusItem(result, commandName)];
|
|
284
|
-
if (result.claude.valid) {
|
|
285
|
-
const account = result.claude.login ? ` for ${result.claude.login}` : "";
|
|
286
|
-
const detail = result.claude.login ? `${result.claude.login}; isolated profile valid` : "isolated profile valid";
|
|
287
|
-
items.push({ kind: "ok", component: "Claude profile", detail, message: `Isolated Claude profile is valid${account}` });
|
|
288
|
-
} else {
|
|
289
|
-
const reason = result.claude.configured && result.claude.reason ? ` (${result.claude.reason})` : "";
|
|
290
|
-
items.push({ kind: "err", component: "Claude profile", detail: "not ready", message: `Isolated Claude profile is not ready${reason}; run ${PM_STUDIO_CLAUDE_AUTH_COMMAND}` });
|
|
291
|
-
}
|
|
292
|
-
if (result.adapter?.ok && result.runtime?.ok) {
|
|
293
|
-
items.push({ kind: "ok", component: "PM relay", detail: `verified at ${result.adapter.baseUrl}`, message: `PM model discovery and isolated Claude routing are verified at ${result.adapter.baseUrl}` });
|
|
294
|
-
} else if (result.adapter?.ok) {
|
|
295
|
-
const reason = result.runtime?.issues?.join("; ") || "runtime status is unavailable";
|
|
296
|
-
items.push({ kind: "err", component: "PM relay", detail: "running but routing not ready", message: `The adapter is running at ${result.adapter.baseUrl}, but PM relay routing is not ready: ${reason}; stop it and run ${commandName} start` });
|
|
297
|
-
} else if (result.adapter?.incompatible) {
|
|
298
|
-
items.push({ kind: "err", component: "PM relay", detail: `incompatible at ${result.adapter.baseUrl}`, message: `The adapter at ${result.adapter.baseUrl} is incompatible; stop it and run ${commandName} start` });
|
|
299
|
-
} else {
|
|
300
|
-
items.push({ kind: "warn", component: "PM relay", detail: `not running at ${PM_RELAY_HOST}:${PM_RELAY_PORT}`, message: `PM relay is not running at http://${PM_RELAY_HOST}:${PM_RELAY_PORT}; run ${commandName} start` });
|
|
301
|
-
}
|
|
302
|
-
if (result.runtime?.ok) {
|
|
303
|
-
items.push({ kind: "info", component: "Routing", detail: "GPT -> native official; models/Claude -> local", message: "Routing: PM GPT uses its native official GitHub Copilot path; model discovery and eligible Claude chat use the local CCDX relay" });
|
|
304
|
-
} else {
|
|
305
|
-
items.push({ kind: "info", component: "Routing", detail: "expected: GPT -> native official; models/Claude -> local", message: "Expected routing: PM GPT uses its native official GitHub Copilot path; model discovery and eligible Claude chat use the local CCDX relay" });
|
|
306
|
-
}
|
|
307
|
-
return items;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
function formatPmStudioStatusPlain(result, commandName, { sanitize = false } = {}) {
|
|
311
|
-
const lines = [
|
|
312
|
-
`${commandName} pms status`,
|
|
313
|
-
...pmStudioStatusItems(result, commandName).map((item) => status(item.kind, item.message)),
|
|
314
|
-
];
|
|
315
|
-
return (sanitize ? lines.map((line) => terminalCell(line, { fallback: "" })) : lines).join("\n");
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function tableState(kind) {
|
|
319
|
-
return `[${String(kind).toUpperCase()}]`;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function formatPmStudioStatusTable(result, { commandName, output }) {
|
|
323
|
-
const items = pmStudioStatusItems(result, commandName);
|
|
324
|
-
const rows = items.map((item) => ({
|
|
325
|
-
component: item.component,
|
|
326
|
-
state: tableState(item.kind),
|
|
327
|
-
detail: item.detail,
|
|
328
|
-
}));
|
|
329
|
-
const table = formatResponsiveCliTable({
|
|
330
|
-
columns: [
|
|
331
|
-
{ key: "component", label: "COMPONENT" },
|
|
332
|
-
{ key: "state", label: "STATE" },
|
|
333
|
-
{ key: "detail", label: "DETAIL" },
|
|
334
|
-
],
|
|
335
|
-
compactColumns: [
|
|
336
|
-
{ key: "component", label: "COMPONENT" },
|
|
337
|
-
{ key: "state", label: "STATE" },
|
|
338
|
-
],
|
|
339
|
-
rows,
|
|
340
|
-
width: cliOutputWidth(output),
|
|
341
|
-
});
|
|
342
|
-
const lines = [`${commandName} pms status`, table.output];
|
|
343
|
-
const details = table.compact ? items : items.filter((item) => item.kind === "warn" || item.kind === "err");
|
|
344
|
-
if (details.length) {
|
|
345
|
-
lines.push("", "Details:", ...details.map((item) => terminalCell(status(item.kind, item.message), { fallback: "" })));
|
|
346
|
-
}
|
|
347
|
-
return { output: lines.join("\n"), overflow: table.overflow };
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
export function formatPmStudioStatus(result, {
|
|
351
|
-
commandName = "ccdx",
|
|
352
|
-
format = "plain",
|
|
353
|
-
output = process.stdout,
|
|
354
|
-
} = {}) {
|
|
355
|
-
if (cliOutputFormat(format, output) === "plain") {
|
|
356
|
-
return formatPmStudioStatusPlain(result, commandName);
|
|
357
|
-
}
|
|
358
|
-
const table = formatPmStudioStatusTable(result, { commandName, output });
|
|
359
|
-
return format === "auto" && table.overflow
|
|
360
|
-
? formatPmStudioStatusPlain(result, commandName, { sanitize: true })
|
|
361
|
-
: table.output;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
export async function runPmStudioStatus(options = {}) {
|
|
365
|
-
const result = await inspectPmStudioStatus(options);
|
|
366
|
-
const output = formatPmStudioStatus(result, options);
|
|
367
|
-
(options.log || console.log)(output);
|
|
368
|
-
return { ...result, output };
|
|
369
|
-
}
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
import { Worker } from "node:worker_threads";
|
|
2
|
-
import { anthropicTokenText } from "./anthropic.mjs";
|
|
3
|
-
import { debugLog } from "./log.mjs";
|
|
4
|
-
|
|
5
|
-
export const DEFAULT_TOKEN_COUNTER_TIMEOUT_MS = 120 * 1000;
|
|
6
|
-
export const DEFAULT_TOKEN_COUNTER_MAX_QUEUED = 16;
|
|
7
|
-
|
|
8
|
-
function serviceError(message, statusCode) {
|
|
9
|
-
return Object.assign(new Error(message), { statusCode });
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const DIAGNOSTIC_PHASES = new Set(["create", "worker", "error", "exit", "postMessage"]);
|
|
13
|
-
const DIAGNOSTIC_ERROR_NAMES = new Set(["Error", "TypeError", "RangeError", "SyntaxError", "WorkerExit"]);
|
|
14
|
-
|
|
15
|
-
export function tokenCounterWorkerDiagnostic(phase, error) {
|
|
16
|
-
const safePhase = DIAGNOSTIC_PHASES.has(phase) ? phase : "unknown";
|
|
17
|
-
let rawName;
|
|
18
|
-
let rawCode;
|
|
19
|
-
try { rawName = error?.name; } catch {}
|
|
20
|
-
try { rawCode = error?.code; } catch {}
|
|
21
|
-
const safeName = DIAGNOSTIC_ERROR_NAMES.has(rawName) ? rawName : "Error";
|
|
22
|
-
if (typeof rawCode === "number") rawCode = String(rawCode);
|
|
23
|
-
const safeCode = typeof rawCode === "string" && /^(?:\d+|[A-Z][A-Z0-9_]{0,63})$/.test(rawCode)
|
|
24
|
-
? rawCode
|
|
25
|
-
: "unknown";
|
|
26
|
-
return `Token counter worker failure: phase=${safePhase} name=${safeName} code=${safeCode}`;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function positiveInteger(value, fallback) {
|
|
30
|
-
return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function abortError(signal) {
|
|
34
|
-
if (signal?.reason instanceof Error) return signal.reason;
|
|
35
|
-
const error = new Error("The operation was aborted");
|
|
36
|
-
error.name = "AbortError";
|
|
37
|
-
return error;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function tokenCounterWorkerExecArgv(execArgv = process.execArgv) {
|
|
41
|
-
const filtered = [];
|
|
42
|
-
for (let index = 0; index < execArgv.length; index += 1) {
|
|
43
|
-
const value = execArgv[index];
|
|
44
|
-
if (value === "--input-type") {
|
|
45
|
-
index += 1;
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
if (value.startsWith("--input-type=")) continue;
|
|
49
|
-
filtered.push(value);
|
|
50
|
-
}
|
|
51
|
-
return filtered;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function createTokenCounterWorker() {
|
|
55
|
-
const execArgv = tokenCounterWorkerExecArgv();
|
|
56
|
-
const options = execArgv.length === process.execArgv.length ? undefined : { execArgv };
|
|
57
|
-
return new Worker(new URL("./token-counter-worker.mjs", import.meta.url), options);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function createTokenCounterService({
|
|
61
|
-
timeoutMs = DEFAULT_TOKEN_COUNTER_TIMEOUT_MS,
|
|
62
|
-
maxQueued = DEFAULT_TOKEN_COUNTER_MAX_QUEUED,
|
|
63
|
-
workerFactory = createTokenCounterWorker,
|
|
64
|
-
} = {}) {
|
|
65
|
-
timeoutMs = positiveInteger(timeoutMs, DEFAULT_TOKEN_COUNTER_TIMEOUT_MS);
|
|
66
|
-
maxQueued = positiveInteger(maxQueued, DEFAULT_TOKEN_COUNTER_MAX_QUEUED);
|
|
67
|
-
const queue = [];
|
|
68
|
-
let worker = null;
|
|
69
|
-
let active = null;
|
|
70
|
-
let nextId = 1;
|
|
71
|
-
let closed = false;
|
|
72
|
-
|
|
73
|
-
const finish = (job, callback, value) => {
|
|
74
|
-
if (job.finished) return;
|
|
75
|
-
job.finished = true;
|
|
76
|
-
if (job.timer) clearTimeout(job.timer);
|
|
77
|
-
job.signal?.removeEventListener("abort", job.onAbort);
|
|
78
|
-
callback(value);
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const retireWorker = () => {
|
|
82
|
-
const retired = worker;
|
|
83
|
-
worker = null;
|
|
84
|
-
if (!retired) return;
|
|
85
|
-
retired.removeAllListeners();
|
|
86
|
-
retired.on("error", () => {});
|
|
87
|
-
Promise.resolve(retired.terminate()).catch(() => {});
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const pump = () => {
|
|
91
|
-
if (closed || active || queue.length === 0) return;
|
|
92
|
-
if (!worker) {
|
|
93
|
-
try {
|
|
94
|
-
worker = workerFactory();
|
|
95
|
-
} catch (error) {
|
|
96
|
-
debugLog(tokenCounterWorkerDiagnostic("create", error));
|
|
97
|
-
const job = queue.shift();
|
|
98
|
-
if (job) finish(job, job.reject, serviceError("Token counter worker failed", 500));
|
|
99
|
-
queueMicrotask(pump);
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
worker.on("message", (message) => {
|
|
103
|
-
if (!active || message?.id !== active.id) return;
|
|
104
|
-
const job = active;
|
|
105
|
-
active = null;
|
|
106
|
-
if (message.error) {
|
|
107
|
-
debugLog(tokenCounterWorkerDiagnostic("worker", message.error));
|
|
108
|
-
retireWorker();
|
|
109
|
-
finish(job, job.reject, serviceError("Token counter worker failed", 500));
|
|
110
|
-
} else {
|
|
111
|
-
finish(job, job.resolve, message.result);
|
|
112
|
-
}
|
|
113
|
-
pump();
|
|
114
|
-
});
|
|
115
|
-
worker.on("error", (error) => {
|
|
116
|
-
debugLog(tokenCounterWorkerDiagnostic("error", error));
|
|
117
|
-
const job = active;
|
|
118
|
-
active = null;
|
|
119
|
-
retireWorker();
|
|
120
|
-
if (job) finish(job, job.reject, serviceError("Token counter worker failed", 500));
|
|
121
|
-
pump();
|
|
122
|
-
});
|
|
123
|
-
worker.on("exit", (code) => {
|
|
124
|
-
if (active) debugLog(tokenCounterWorkerDiagnostic("exit", { name: "WorkerExit", code }));
|
|
125
|
-
const job = active;
|
|
126
|
-
active = null;
|
|
127
|
-
worker = null;
|
|
128
|
-
if (job) finish(job, job.reject, serviceError("Token counter worker failed", 500));
|
|
129
|
-
pump();
|
|
130
|
-
});
|
|
131
|
-
worker.unref?.();
|
|
132
|
-
}
|
|
133
|
-
const job = queue.shift();
|
|
134
|
-
if (!job || job.finished) return pump();
|
|
135
|
-
active = job;
|
|
136
|
-
try {
|
|
137
|
-
worker.postMessage({ id: job.id, text: job.text });
|
|
138
|
-
job.text = null;
|
|
139
|
-
} catch (error) {
|
|
140
|
-
debugLog(tokenCounterWorkerDiagnostic("postMessage", error));
|
|
141
|
-
active = null;
|
|
142
|
-
retireWorker();
|
|
143
|
-
finish(job, job.reject, serviceError("Token counter worker failed", 500));
|
|
144
|
-
pump();
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
const cancel = (job, error) => {
|
|
149
|
-
if (job.finished) return;
|
|
150
|
-
const queuedIndex = queue.indexOf(job);
|
|
151
|
-
if (queuedIndex >= 0) queue.splice(queuedIndex, 1);
|
|
152
|
-
if (active === job) {
|
|
153
|
-
active = null;
|
|
154
|
-
retireWorker();
|
|
155
|
-
}
|
|
156
|
-
finish(job, job.reject, error);
|
|
157
|
-
pump();
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
count(body, { signal, deadlineMs = timeoutMs } = {}) {
|
|
162
|
-
if (closed) return Promise.reject(serviceError("Token counter service is closed", 503));
|
|
163
|
-
if (signal?.aborted) return Promise.reject(abortError(signal));
|
|
164
|
-
let text;
|
|
165
|
-
try {
|
|
166
|
-
text = anthropicTokenText(body);
|
|
167
|
-
} catch {
|
|
168
|
-
return Promise.reject(serviceError("Invalid count_tokens request", 400));
|
|
169
|
-
}
|
|
170
|
-
if (active && queue.length >= maxQueued) {
|
|
171
|
-
return Promise.reject(serviceError(`Token counter queue is full (${maxQueued} waiting)`, 503));
|
|
172
|
-
}
|
|
173
|
-
return new Promise((resolve, reject) => {
|
|
174
|
-
deadlineMs = positiveInteger(deadlineMs, timeoutMs);
|
|
175
|
-
const job = {
|
|
176
|
-
id: nextId++,
|
|
177
|
-
text,
|
|
178
|
-
signal,
|
|
179
|
-
resolve,
|
|
180
|
-
reject,
|
|
181
|
-
finished: false,
|
|
182
|
-
timer: null,
|
|
183
|
-
onAbort: null,
|
|
184
|
-
};
|
|
185
|
-
job.onAbort = () => cancel(job, abortError(signal));
|
|
186
|
-
signal?.addEventListener("abort", job.onAbort, { once: true });
|
|
187
|
-
job.timer = setTimeout(() => {
|
|
188
|
-
cancel(job, serviceError(`Token counting timed out after ${deadlineMs}ms`, 504));
|
|
189
|
-
}, deadlineMs);
|
|
190
|
-
queue.push(job);
|
|
191
|
-
if (signal?.aborted) job.onAbort();
|
|
192
|
-
else pump();
|
|
193
|
-
});
|
|
194
|
-
},
|
|
195
|
-
close() {
|
|
196
|
-
if (closed) return;
|
|
197
|
-
closed = true;
|
|
198
|
-
const error = serviceError("Token counter service is closed", 503);
|
|
199
|
-
if (active) {
|
|
200
|
-
const job = active;
|
|
201
|
-
active = null;
|
|
202
|
-
finish(job, job.reject, error);
|
|
203
|
-
}
|
|
204
|
-
for (const job of queue.splice(0)) finish(job, job.reject, error);
|
|
205
|
-
retireWorker();
|
|
206
|
-
},
|
|
207
|
-
stats() {
|
|
208
|
-
return { active: active ? 1 : 0, queued: queue.length, worker: worker ? 1 : 0 };
|
|
209
|
-
},
|
|
210
|
-
};
|
|
211
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
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 (error) {
|
|
8
|
-
parentPort.postMessage({
|
|
9
|
-
id,
|
|
10
|
-
error: {
|
|
11
|
-
name: typeof error?.name === "string" ? error.name : "Error",
|
|
12
|
-
code: typeof error?.code === "string" ? error.code : "unknown",
|
|
13
|
-
},
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
});
|