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.
@@ -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 POST_PATHS = new Set([
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, { "Content-Type": "application/json", ...headers });
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 null;
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 handlePost(req, res, pathname, token, abort) {
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 supportsStream = pathname === "/chat/completions" || pathname === "/responses";
442
- const streaming = supportsStream && body?.stream === true;
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 modelType = modelRouter.classify(body?.model);
456
- if (modelType === "unsupported_claude" || (modelType === "claude" && pathname !== "/chat/completions")) {
457
- throw localError(400, "model_not_supported", "The requested Claude model is not available for this endpoint");
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, supportsStream ? body : null, res, abort, streamIdleTimeoutMs);
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" ? "GET" : POST_PATHS.has(upstreamPath) ? "POST" : null;
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 handlePost(req, res, upstreamPath, token, abort);
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 {