codex-copilot-dx 0.6.7 → 0.6.9
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 +47 -21
- package/package.json +1 -1
- package/src/cli-status.mjs +2 -2
- package/src/config.mjs +18 -0
- package/src/observability.mjs +0 -4
- package/src/pm-studio-asar.mjs +193 -50
- package/src/pm-studio-relay.mjs +40 -30
- package/src/pm-studio-setup.mjs +398 -54
- package/src/pm-studio-status.mjs +67 -7
- package/src/responses-request.mjs +16 -2
- package/src/running-adapter.mjs +3 -1
package/src/pm-studio-relay.mjs
CHANGED
|
@@ -16,6 +16,8 @@ import { requireUpstreamEventStream } from "./stream-contract.mjs";
|
|
|
16
16
|
import { safeUpstreamResponseHeaders } from "./upstream-headers.mjs";
|
|
17
17
|
|
|
18
18
|
export const PM_STUDIO_RELAY_PREFIX = "/pm-ccdx";
|
|
19
|
+
export const PM_STUDIO_RELAY_MARKER_HEADER = "X-CCDX-PM-Relay";
|
|
20
|
+
export const PM_STUDIO_RELAY_MARKER_VALUE = "split-origin-v1";
|
|
19
21
|
const COPILOT_ORIGIN = "https://api.githubcopilot.com";
|
|
20
22
|
const DEFAULT_VALIDATION_TTL_MS = 5 * 60 * 1000;
|
|
21
23
|
const DEFAULT_VALIDATION_CACHE_SIZE = 128;
|
|
@@ -32,11 +34,7 @@ const HOP_BY_HOP_HEADERS = new Set([
|
|
|
32
34
|
"transfer-encoding",
|
|
33
35
|
"upgrade",
|
|
34
36
|
]);
|
|
35
|
-
const
|
|
36
|
-
"/chat/completions",
|
|
37
|
-
"/responses",
|
|
38
|
-
"/embeddings",
|
|
39
|
-
]);
|
|
37
|
+
const CHAT_COMPLETIONS_PATH = "/chat/completions";
|
|
40
38
|
|
|
41
39
|
function localError(statusCode, code, message) {
|
|
42
40
|
const error = httpError(message, statusCode);
|
|
@@ -46,7 +44,11 @@ function localError(statusCode, code, message) {
|
|
|
46
44
|
|
|
47
45
|
function sendLocalError(res, statusCode, code, message, headers = {}) {
|
|
48
46
|
if (res.destroyed || res.writableEnded) return;
|
|
49
|
-
res.writeHead(statusCode, {
|
|
47
|
+
res.writeHead(statusCode, {
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
[PM_STUDIO_RELAY_MARKER_HEADER]: PM_STUDIO_RELAY_MARKER_VALUE,
|
|
50
|
+
...headers,
|
|
51
|
+
});
|
|
50
52
|
res.end(JSON.stringify({ error: { message, type: "invalid_request_error", code } }));
|
|
51
53
|
}
|
|
52
54
|
|
|
@@ -160,6 +162,7 @@ async function responseSnapshot(response) {
|
|
|
160
162
|
function responseHeaders(headers, body, options) {
|
|
161
163
|
return {
|
|
162
164
|
...safeUpstreamResponseHeaders(headers, options),
|
|
165
|
+
[PM_STUDIO_RELAY_MARKER_HEADER]: PM_STUDIO_RELAY_MARKER_VALUE,
|
|
163
166
|
"Content-Length": String(body.length),
|
|
164
167
|
};
|
|
165
168
|
}
|
|
@@ -179,8 +182,6 @@ function rejectUpstreamRedirect(response) {
|
|
|
179
182
|
|
|
180
183
|
function mergeModelCatalog(snapshot, modelRouter) {
|
|
181
184
|
if (!snapshot.ok) return null;
|
|
182
|
-
const allowed = modelRouter.allowedModels();
|
|
183
|
-
if (!allowed.length) return null;
|
|
184
185
|
let enterprise;
|
|
185
186
|
try {
|
|
186
187
|
enterprise = JSON.parse(snapshot.body.toString("utf8"));
|
|
@@ -189,6 +190,8 @@ function mergeModelCatalog(snapshot, modelRouter) {
|
|
|
189
190
|
}
|
|
190
191
|
const data = catalogData(enterprise);
|
|
191
192
|
if (!data) return null;
|
|
193
|
+
const allowed = modelRouter.allowedModels();
|
|
194
|
+
if (!allowed.length) return snapshot.body;
|
|
192
195
|
const seen = new Set(data.map((model) => String(model?.id || "").trim()).filter(Boolean));
|
|
193
196
|
const additions = [];
|
|
194
197
|
for (const model of allowed) {
|
|
@@ -197,7 +200,7 @@ function mergeModelCatalog(snapshot, modelRouter) {
|
|
|
197
200
|
seen.add(id);
|
|
198
201
|
additions.push(model);
|
|
199
202
|
}
|
|
200
|
-
if (!additions.length) return
|
|
203
|
+
if (!additions.length) return snapshot.body;
|
|
201
204
|
return Buffer.from(JSON.stringify(Array.isArray(enterprise)
|
|
202
205
|
? [...enterprise, ...additions]
|
|
203
206
|
: { ...enterprise, data: [...data, ...additions] }));
|
|
@@ -215,6 +218,7 @@ async function relayUpstreamResponse(response, reqBody, res, abort, streamIdleTi
|
|
|
215
218
|
abort.setTimeout(streamIdleTimeoutMs, "stream_idle_timeout");
|
|
216
219
|
res.writeHead(response.status, {
|
|
217
220
|
...safeUpstreamResponseHeaders(response.headers, { contentType: "text/event-stream" }),
|
|
221
|
+
[PM_STUDIO_RELAY_MARKER_HEADER]: PM_STUDIO_RELAY_MARKER_VALUE,
|
|
218
222
|
"Cache-Control": "no-cache",
|
|
219
223
|
});
|
|
220
224
|
const reader = response.body.getReader();
|
|
@@ -424,6 +428,9 @@ export function createPmStudioRelayHandler(options = {}) {
|
|
|
424
428
|
}
|
|
425
429
|
const merged = mergeModelCatalog(snapshot, modelRouter);
|
|
426
430
|
if (!merged) {
|
|
431
|
+
throw localError(502, "invalid_upstream", "PM Studio upstream model catalog has an invalid JSON shape");
|
|
432
|
+
}
|
|
433
|
+
if (merged === snapshot.body) {
|
|
427
434
|
sendSnapshot(res, snapshot);
|
|
428
435
|
return;
|
|
429
436
|
}
|
|
@@ -431,15 +438,26 @@ export function createPmStudioRelayHandler(options = {}) {
|
|
|
431
438
|
res.end(merged);
|
|
432
439
|
}
|
|
433
440
|
|
|
434
|
-
async function
|
|
441
|
+
async function handleChatCompletions(req, res, token, abort) {
|
|
435
442
|
let releaseRequest = releaseOnce();
|
|
436
443
|
try {
|
|
437
444
|
const admission = await acquireRequest(req, { signal: abort.signal });
|
|
438
445
|
releaseRequest = releaseOnce(admission);
|
|
439
446
|
abort.setTimeout(requestBodyTimeoutMs, "request_body_timeout");
|
|
440
447
|
const body = await readJsonBody(req, { admission, signal: abort.signal });
|
|
441
|
-
const
|
|
442
|
-
|
|
448
|
+
const modelType = modelRouter.classify(body?.model);
|
|
449
|
+
if (modelType === "unsupported_claude") {
|
|
450
|
+
throw localError(400, "model_not_supported", "The requested Claude model is not enabled for PM Studio");
|
|
451
|
+
}
|
|
452
|
+
if (modelType !== "claude") {
|
|
453
|
+
throw localError(
|
|
454
|
+
400,
|
|
455
|
+
"native_route_required",
|
|
456
|
+
"Non-Claude PM Studio requests must use the native GitHub Copilot origin",
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const streaming = body?.stream === true;
|
|
443
461
|
const requestTimeoutMs = streaming ? streamHandshakeTimeoutMs : upstreamTimeoutMs;
|
|
444
462
|
const timeoutReason = streaming ? "stream_handshake_timeout" : "upstream_timeout";
|
|
445
463
|
abort.setTimeout(requestTimeoutMs, timeoutReason);
|
|
@@ -452,23 +470,12 @@ export function createPmStudioRelayHandler(options = {}) {
|
|
|
452
470
|
}
|
|
453
471
|
abort.setTimeout(requestTimeoutMs, timeoutReason);
|
|
454
472
|
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
let upstream;
|
|
461
|
-
if (modelType === "claude") {
|
|
462
|
-
upstream = await options.claudeClient.chatCompletions(body, {
|
|
463
|
-
signal: abort.signal,
|
|
464
|
-
fetchImpl: fetchClaudeUpstream,
|
|
465
|
-
});
|
|
466
|
-
} else {
|
|
467
|
-
const bodyText = JSON.stringify(body);
|
|
468
|
-
upstream = await fetchEnterprise(pathname, "POST", req.headers, token, bodyText, abort.signal);
|
|
469
|
-
}
|
|
473
|
+
const upstream = await options.claudeClient.chatCompletions(body, {
|
|
474
|
+
signal: abort.signal,
|
|
475
|
+
fetchImpl: fetchClaudeUpstream,
|
|
476
|
+
});
|
|
470
477
|
releaseRequest();
|
|
471
|
-
await relayUpstreamResponse(upstream,
|
|
478
|
+
await relayUpstreamResponse(upstream, body, res, abort, streamIdleTimeoutMs);
|
|
472
479
|
} finally {
|
|
473
480
|
releaseRequest();
|
|
474
481
|
}
|
|
@@ -494,7 +501,9 @@ export function createPmStudioRelayHandler(options = {}) {
|
|
|
494
501
|
}
|
|
495
502
|
|
|
496
503
|
const upstreamPath = pathname.slice(PM_STUDIO_RELAY_PREFIX.length);
|
|
497
|
-
const allowedMethod = upstreamPath === "/models"
|
|
504
|
+
const allowedMethod = upstreamPath === "/models"
|
|
505
|
+
? "GET"
|
|
506
|
+
: upstreamPath === CHAT_COMPLETIONS_PATH ? "POST" : null;
|
|
498
507
|
if (!allowedMethod) {
|
|
499
508
|
sendLocalError(res, 404, "route_not_found", "PM Studio relay route not found");
|
|
500
509
|
return true;
|
|
@@ -512,13 +521,14 @@ export function createPmStudioRelayHandler(options = {}) {
|
|
|
512
521
|
abort.setTimeout(upstreamTimeoutMs, "upstream_timeout");
|
|
513
522
|
await handleModels(req, res, token, abort);
|
|
514
523
|
} else {
|
|
515
|
-
await
|
|
524
|
+
await handleChatCompletions(req, res, token, abort);
|
|
516
525
|
}
|
|
517
526
|
} catch (error) {
|
|
518
527
|
const safe = safeFailure(error, abort);
|
|
519
528
|
if (res.headersSent) {
|
|
520
529
|
if (!res.destroyed) res.destroy();
|
|
521
530
|
} else {
|
|
531
|
+
res.setHeader(PM_STUDIO_RELAY_MARKER_HEADER, PM_STUDIO_RELAY_MARKER_VALUE);
|
|
522
532
|
sendJsonError(res, safe, safe.statusCode || 502);
|
|
523
533
|
}
|
|
524
534
|
} finally {
|