@minniexcode/codex-switch 0.1.5 → 0.2.0

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.
@@ -163,13 +163,13 @@ async function probeCopilotBridgeRuntime(provider, persistedState, runtimeDir) {
163
163
  /**
164
164
  * Starts or reuses a Copilot bridge worker, then verifies its health before returning.
165
165
  */
166
- async function ensureCopilotBridge(providerName, provider, runtimeDir, runtimesDir) {
167
- return startOrReuseCopilotBridge(providerName, provider, runtimeDir, runtimesDir);
166
+ async function ensureCopilotBridge(providerName, provider, runtimeDir, runtimesDir, toolHomeDir) {
167
+ return startOrReuseCopilotBridge(providerName, provider, runtimeDir, runtimesDir, toolHomeDir);
168
168
  }
169
169
  /**
170
170
  * Starts or reuses a Copilot bridge worker and reports the chosen port.
171
171
  */
172
- async function startOrReuseCopilotBridge(providerName, provider, runtimeDir, runtimesDir) {
172
+ async function startOrReuseCopilotBridge(providerName, provider, runtimeDir, runtimesDir, toolHomeDir) {
173
173
  if (!(0, providers_1.isCopilotBridgeProvider)(provider)) {
174
174
  throw (0, errors_1.cliError)("RUNTIME_PROVIDER_INVALID", "Provider is not backed by a Copilot bridge runtime.", {
175
175
  provider: providerName,
@@ -221,7 +221,7 @@ async function startOrReuseCopilotBridge(providerName, provider, runtimeDir, run
221
221
  }
222
222
  const selectedPort = await selectBridgePort(runtime.bridgeHost, runtime.bridgePort);
223
223
  const selectedBaseUrl = `http://${runtime.bridgeHost}:${selectedPort}${runtime.bridgePath}`;
224
- const workerPath = path.join(__dirname, "copilot-bridge-worker.js");
224
+ const workerPath = path.join(__dirname, "copilot-http-bridge-worker.js");
225
225
  ensureBridgeLogFile(logPath);
226
226
  appendBridgeLifecycleLog(logPath, `worker start provider=${providerName} host=${runtime.bridgeHost} port=${String(selectedPort)} replaced=${String(replaced)}`);
227
227
  let child;
@@ -239,6 +239,7 @@ async function startOrReuseCopilotBridge(providerName, provider, runtimeDir, run
239
239
  CODEX_SWITCH_RUNTIME_DIR: runtimeDir ?? "",
240
240
  CODEX_SWITCH_RUNTIMES_DIR: runtimesDir ?? "",
241
241
  CODEX_SWITCH_BRIDGE_LOG_PATH: logPath,
242
+ CODEX_SWITCH_TOOL_HOME_DIR: toolHomeDir ?? "",
242
243
  },
243
244
  });
244
245
  }
@@ -349,23 +350,27 @@ function createCopilotBridgeRequestHandler(context) {
349
350
  connection: "keep-alive",
350
351
  });
351
352
  const heartbeat = startSseHeartbeat(response);
352
- const payload = await context.executeChatCompletion(body, {
353
- timeoutMs,
354
- onTextDelta: (delta) => {
355
- response.write(`data: ${JSON.stringify({
356
- choices: [
357
- {
358
- index: 0,
359
- delta: { content: delta },
360
- finish_reason: null,
361
- },
362
- ],
363
- })}\n\n`);
364
- },
365
- });
366
- clearInterval(heartbeat);
367
- response.write("data: [DONE]\n\n");
368
- response.end();
353
+ try {
354
+ const payload = await context.executeChatCompletion(body, {
355
+ timeoutMs,
356
+ onTextDelta: (delta) => {
357
+ response.write(`data: ${JSON.stringify({
358
+ choices: [
359
+ {
360
+ index: 0,
361
+ delta: { content: delta },
362
+ finish_reason: null,
363
+ },
364
+ ],
365
+ })}\n\n`);
366
+ },
367
+ });
368
+ response.write("data: [DONE]\n\n");
369
+ response.end();
370
+ }
371
+ finally {
372
+ clearInterval(heartbeat);
373
+ }
369
374
  return;
370
375
  }
371
376
  const payload = await context.executeChatCompletion(body, { timeoutMs });
@@ -389,31 +394,36 @@ function createCopilotBridgeRequestHandler(context) {
389
394
  const responseId = `resp_${Date.now()}`;
390
395
  const messageId = buildResponsesMessageId(responseId);
391
396
  writeResponsesStreamStart(response, responseId, normalized.model, messageId);
397
+ writeResponsesReasoningPartAdded(response, responseId);
392
398
  const heartbeat = startSseHeartbeat(response);
393
399
  let text = "";
394
- const payload = await context.executeChatCompletion(chatPayload, {
395
- timeoutMs: normalized.timeoutMs,
396
- onTextDelta: (delta) => {
397
- text += delta;
398
- writeResponsesTextDelta(response, messageId, delta);
399
- },
400
- onTextDone: (doneText) => {
401
- if (text.length === 0) {
402
- text = doneText;
403
- writeResponsesTextDelta(response, messageId, doneText);
404
- }
405
- },
406
- onRuntimeEvent: (event) => {
407
- writeResponsesRuntimeEvent(response, responseId, event);
408
- },
409
- });
410
- clearInterval(heartbeat);
411
- const outputText = text || getChatCompletionText(payload);
412
- if (text.length === 0 && outputText.length > 0) {
413
- writeResponsesTextDelta(response, messageId, outputText);
400
+ try {
401
+ const payload = await context.executeChatCompletion(chatPayload, {
402
+ timeoutMs: normalized.timeoutMs,
403
+ onTextDelta: (delta) => {
404
+ text += delta;
405
+ writeResponsesTextDelta(response, messageId, delta);
406
+ },
407
+ onTextDone: (doneText) => {
408
+ if (text.length === 0) {
409
+ text = doneText;
410
+ writeResponsesTextDelta(response, messageId, doneText);
411
+ }
412
+ },
413
+ onRuntimeEvent: (event) => {
414
+ writeResponsesRuntimeEvent(response, responseId, event);
415
+ },
416
+ });
417
+ const outputText = text || getChatCompletionText(payload);
418
+ if (text.length === 0 && outputText.length > 0) {
419
+ writeResponsesTextDelta(response, messageId, outputText);
420
+ }
421
+ writeResponsesStreamDone(response, responseId, normalized.model, messageId, outputText);
422
+ response.end();
423
+ }
424
+ finally {
425
+ clearInterval(heartbeat);
414
426
  }
415
- writeResponsesStreamDone(response, responseId, normalized.model, messageId, outputText);
416
- response.end();
417
427
  return;
418
428
  }
419
429
  const payload = await context.executeChatCompletion(chatPayload, {
@@ -432,9 +442,15 @@ function createCopilotBridgeRequestHandler(context) {
432
442
  response.end(JSON.stringify({ error: { message: "Not found" } }));
433
443
  }
434
444
  catch (error) {
435
- const statusCode = mapBridgeErrorStatus(error);
436
- response.writeHead(statusCode, { "content-type": "application/json" });
437
- response.end(JSON.stringify({ error: { message: error instanceof Error ? error.message : String(error), code: isCliError(error) ? error.code : "BRIDGE_RUNTIME_FAILURE" } }));
445
+ if (!response.headersSent) {
446
+ const statusCode = mapBridgeErrorStatus(error);
447
+ response.writeHead(statusCode, { "content-type": "application/json" });
448
+ response.end(JSON.stringify({ error: { message: error instanceof Error ? error.message : String(error), code: isCliError(error) ? error.code : "BRIDGE_RUNTIME_FAILURE" } }));
449
+ }
450
+ else if (!response.writableEnded) {
451
+ response.write(`data: ${JSON.stringify({ error: { message: error instanceof Error ? error.message : String(error), code: isCliError(error) ? error.code : "BRIDGE_RUNTIME_FAILURE" } })}\n\n`);
452
+ response.end();
453
+ }
438
454
  }
439
455
  };
440
456
  }
@@ -631,7 +647,7 @@ function writeResponsesStream(response, payload) {
631
647
  });
632
648
  writeSseEvent(response, "response.output_item.added", {
633
649
  type: "response.output_item.added",
634
- output_index: 0,
650
+ output_index: 1,
635
651
  item: {
636
652
  id: messageId,
637
653
  type: "message",
@@ -643,7 +659,7 @@ function writeResponsesStream(response, payload) {
643
659
  writeSseEvent(response, "response.content_part.added", {
644
660
  type: "response.content_part.added",
645
661
  item_id: messageId,
646
- output_index: 0,
662
+ output_index: 1,
647
663
  content_index: 0,
648
664
  part: {
649
665
  type: "output_text",
@@ -654,21 +670,21 @@ function writeResponsesStream(response, payload) {
654
670
  writeSseEvent(response, "response.output_text.delta", {
655
671
  type: "response.output_text.delta",
656
672
  item_id: messageId,
657
- output_index: 0,
673
+ output_index: 1,
658
674
  content_index: 0,
659
675
  delta: outputText,
660
676
  });
661
677
  writeSseEvent(response, "response.output_text.done", {
662
678
  type: "response.output_text.done",
663
679
  item_id: messageId,
664
- output_index: 0,
680
+ output_index: 1,
665
681
  content_index: 0,
666
682
  text: outputText,
667
683
  });
668
684
  writeSseEvent(response, "response.content_part.done", {
669
685
  type: "response.content_part.done",
670
686
  item_id: messageId,
671
- output_index: 0,
687
+ output_index: 1,
672
688
  content_index: 0,
673
689
  part: {
674
690
  type: "output_text",
@@ -678,7 +694,7 @@ function writeResponsesStream(response, payload) {
678
694
  });
679
695
  writeSseEvent(response, "response.output_item.done", {
680
696
  type: "response.output_item.done",
681
- output_index: 0,
697
+ output_index: 1,
682
698
  item: completedMessage,
683
699
  });
684
700
  writeSseEvent(response, "response.completed", {
@@ -710,7 +726,7 @@ function writeResponsesStreamStart(response, responseId, model, messageId) {
710
726
  });
711
727
  writeSseEvent(response, "response.output_item.added", {
712
728
  type: "response.output_item.added",
713
- output_index: 0,
729
+ output_index: 1,
714
730
  item: {
715
731
  id: messageId,
716
732
  type: "message",
@@ -722,7 +738,7 @@ function writeResponsesStreamStart(response, responseId, model, messageId) {
722
738
  writeSseEvent(response, "response.content_part.added", {
723
739
  type: "response.content_part.added",
724
740
  item_id: messageId,
725
- output_index: 0,
741
+ output_index: 1,
726
742
  content_index: 0,
727
743
  part: {
728
744
  type: "output_text",
@@ -738,7 +754,7 @@ function writeResponsesTextDelta(response, messageId, delta) {
738
754
  writeSseEvent(response, "response.output_text.delta", {
739
755
  type: "response.output_text.delta",
740
756
  item_id: messageId,
741
- output_index: 0,
757
+ output_index: 1,
742
758
  content_index: 0,
743
759
  delta,
744
760
  });
@@ -760,14 +776,14 @@ function writeResponsesStreamDone(response, responseId, model, messageId, output
760
776
  writeSseEvent(response, "response.output_text.done", {
761
777
  type: "response.output_text.done",
762
778
  item_id: messageId,
763
- output_index: 0,
779
+ output_index: 1,
764
780
  content_index: 0,
765
781
  text: outputText,
766
782
  });
767
783
  writeSseEvent(response, "response.content_part.done", {
768
784
  type: "response.content_part.done",
769
785
  item_id: messageId,
770
- output_index: 0,
786
+ output_index: 1,
771
787
  content_index: 0,
772
788
  part: {
773
789
  type: "output_text",
@@ -777,7 +793,7 @@ function writeResponsesStreamDone(response, responseId, model, messageId, output
777
793
  });
778
794
  writeSseEvent(response, "response.output_item.done", {
779
795
  type: "response.output_item.done",
780
- output_index: 0,
796
+ output_index: 1,
781
797
  item: completedMessage,
782
798
  });
783
799
  writeSseEvent(response, "response.completed", {
@@ -797,23 +813,40 @@ function writeResponsesRuntimeEvent(response, responseId, event) {
797
813
  if (event.type === "assistant.message_delta") {
798
814
  return;
799
815
  }
800
- if (event.type === "assistant.reasoning_delta") {
801
- writeResponsesReasoningDelta(response, responseId, formatRuntimeEventText(event));
802
- return;
803
- }
804
816
  if (event.type === "session.unknown") {
805
- process.stderr.write(`[${new Date().toISOString()}] bridge runtime event ignored type=${event.sdkType} summary=${truncateBridgeText(event.summary, 240)}\n`);
817
+ process.stderr.write(`[${new Date().toISOString()}] bridge runtime event ignored type=${event.sdkType ?? "unknown"} summary=${truncateBridgeText(event.summary ?? "", 240)}\n`);
806
818
  return;
807
819
  }
808
820
  const text = formatRuntimeEventText(event);
809
821
  if (text.length === 0) {
810
822
  return;
811
823
  }
824
+ writeResponsesReasoningDelta(response, responseId, text);
812
825
  writeResponsesCommentaryItem(response, responseId, text);
813
826
  process.stderr.write(`[${new Date().toISOString()}] bridge runtime event type=${event.type} summary=${truncateBridgeText(text, 240)}\n`);
814
827
  }
815
828
  function writeResponsesReasoningDelta(response, responseId, text) {
816
829
  const reasoningId = `${responseId}_rs_0`;
830
+ writeSseEvent(response, "response.reasoning_summary_text.delta", {
831
+ type: "response.reasoning_summary_text.delta",
832
+ item_id: reasoningId,
833
+ output_index: 0,
834
+ summary_index: 0,
835
+ delta: text,
836
+ });
837
+ }
838
+ function writeResponsesReasoningPartAdded(response, responseId) {
839
+ const reasoningId = `${responseId}_rs_0`;
840
+ writeSseEvent(response, "response.output_item.added", {
841
+ type: "response.output_item.added",
842
+ output_index: 0,
843
+ item: {
844
+ id: reasoningId,
845
+ type: "reasoning",
846
+ status: "in_progress",
847
+ summary: [],
848
+ },
849
+ });
817
850
  writeSseEvent(response, "response.reasoning_summary_part.added", {
818
851
  type: "response.reasoning_summary_part.added",
819
852
  item_id: reasoningId,
@@ -824,19 +857,12 @@ function writeResponsesReasoningDelta(response, responseId, text) {
824
857
  text: "",
825
858
  },
826
859
  });
827
- writeSseEvent(response, "response.reasoning_summary_text.delta", {
828
- type: "response.reasoning_summary_text.delta",
829
- item_id: reasoningId,
830
- output_index: 0,
831
- summary_index: 0,
832
- delta: text,
833
- });
834
860
  }
835
861
  function writeResponsesCommentaryItem(response, responseId, text) {
836
862
  const itemId = `${responseId}_commentary_${Date.now()}_${Math.floor(Math.random() * 100000)}`;
837
863
  writeSseEvent(response, "response.output_item.done", {
838
864
  type: "response.output_item.done",
839
- output_index: 0,
865
+ output_index: 1,
840
866
  item: {
841
867
  id: itemId,
842
868
  type: "message",
@@ -856,9 +882,9 @@ function writeResponsesCommentaryItem(response, responseId, text) {
856
882
  function formatRuntimeEventText(event) {
857
883
  switch (event.type) {
858
884
  case "assistant.intent":
859
- return truncateBridgeText(event.text, 600);
885
+ return truncateBridgeText(event.text ?? "", 600);
860
886
  case "assistant.reasoning_delta":
861
- return truncateBridgeText(event.text, 600);
887
+ return truncateBridgeText(event.text ?? "", 600);
862
888
  case "tool.execution_start":
863
889
  return truncateBridgeText(`Copilot started ${formatToolName(event.name)}${formatRequestId(event.requestId)}${formatSummarySuffix(event.summary)}`, 600);
864
890
  case "tool.execution_progress":
@@ -878,10 +904,12 @@ function formatRuntimeEventText(event) {
878
904
  case "session.error":
879
905
  return truncateBridgeText(`Copilot session error${formatSummarySuffix(event.summary)}`, 600);
880
906
  case "session.idle":
881
- return truncateBridgeText(event.summary, 600);
907
+ return truncateBridgeText(event.summary ?? "", 600);
882
908
  case "assistant.message_delta":
883
909
  case "session.unknown":
884
910
  return "";
911
+ default:
912
+ return "";
885
913
  }
886
914
  }
887
915
  function formatToolName(name) {
@@ -891,7 +919,10 @@ function formatRequestId(requestId) {
891
919
  return requestId ? ` (${requestId})` : "";
892
920
  }
893
921
  function formatSummarySuffix(summary) {
894
- return summary.length > 0 ? `: ${summary}` : "";
922
+ if (!summary || summary.length === 0) {
923
+ return "";
924
+ }
925
+ return `: ${summary}`;
895
926
  }
896
927
  function truncateBridgeText(value, maxLength) {
897
928
  if (value.length <= maxLength) {
@@ -901,7 +932,9 @@ function truncateBridgeText(value, maxLength) {
901
932
  }
902
933
  function startSseHeartbeat(response) {
903
934
  return setInterval(() => {
904
- response.write(": keep-alive\n\n");
935
+ if (!response.writableEnded) {
936
+ response.write(": keep-alive\n\n");
937
+ }
905
938
  }, 15000);
906
939
  }
907
940
  function getChatCompletionText(payload) {
@@ -0,0 +1,228 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const http = __importStar(require("node:http"));
37
+ const https = __importStar(require("node:https"));
38
+ const crypto = __importStar(require("node:crypto"));
39
+ const copilot_token_1 = require("./copilot-token");
40
+ let tokenManager = null;
41
+ function logWorkerEvent(message) {
42
+ process.stderr.write(`[${new Date().toISOString()}] ${message}\n`);
43
+ }
44
+ async function main() {
45
+ const provider = process.env.CODEX_SWITCH_BRIDGE_PROVIDER ?? "copilot";
46
+ const host = process.env.CODEX_SWITCH_BRIDGE_HOST ?? "127.0.0.1";
47
+ const port = Number(process.env.CODEX_SWITCH_BRIDGE_PORT ?? "41415");
48
+ const localApiKey = process.env.CODEX_SWITCH_BRIDGE_API_KEY ?? "";
49
+ const toolHomeDir = process.env.CODEX_SWITCH_TOOL_HOME_DIR || undefined;
50
+ const staticCopilotToken = process.env.CODEX_SWITCH_BRIDGE_COPILOT_TOKEN || undefined;
51
+ logWorkerEvent(`worker startup provider=${provider} host=${host} port=${String(port)}`);
52
+ if (staticCopilotToken) {
53
+ tokenManager = (0, copilot_token_1.createStaticTokenManager)(staticCopilotToken);
54
+ logWorkerEvent("copilot token acquired (static), api base: https://api.githubcopilot.com");
55
+ }
56
+ else {
57
+ const githubPat = process.env.CODEX_SWITCH_GITHUB_TOKEN || (0, copilot_token_1.readGithubToken)(toolHomeDir);
58
+ if (!githubPat) {
59
+ throw new Error("No GitHub token found. Run `codexs login copilot` first.");
60
+ }
61
+ tokenManager = (0, copilot_token_1.createTokenManager)(githubPat);
62
+ await tokenManager.getToken();
63
+ logWorkerEvent(`copilot token acquired, api base: ${tokenManager.getApiBaseUrl()}`);
64
+ }
65
+ const server = http.createServer(async (req, res) => {
66
+ try {
67
+ await handleRequest(req, res, localApiKey);
68
+ }
69
+ catch (error) {
70
+ if (!res.headersSent) {
71
+ res.writeHead(500, { "content-type": "application/json" });
72
+ res.end(JSON.stringify({ error: { message: error instanceof Error ? error.message : String(error) } }));
73
+ }
74
+ }
75
+ });
76
+ const stopWorker = () => {
77
+ logWorkerEvent(`worker shutdown provider=${provider}`);
78
+ tokenManager?.stop();
79
+ server.close();
80
+ process.exit(0);
81
+ };
82
+ process.once("SIGINT", stopWorker);
83
+ process.once("SIGTERM", stopWorker);
84
+ await new Promise((resolve, reject) => {
85
+ server.once("error", reject);
86
+ server.listen(port, host, () => {
87
+ server.off("error", reject);
88
+ resolve();
89
+ });
90
+ });
91
+ logWorkerEvent(`worker ready provider=${provider} host=${host} port=${String(port)}`);
92
+ }
93
+ async function handleRequest(req, res, localApiKey) {
94
+ const method = req.method ?? "GET";
95
+ const url = req.url ?? "/";
96
+ if (method === "GET" && url === "/healthz") {
97
+ res.writeHead(200, { "content-type": "application/json" });
98
+ res.end(JSON.stringify({ ok: true }));
99
+ return;
100
+ }
101
+ if (!isAuthorized(req, localApiKey)) {
102
+ res.writeHead(401, { "content-type": "application/json" });
103
+ res.end(JSON.stringify({ error: { message: "Unauthorized" } }));
104
+ return;
105
+ }
106
+ if (method === "GET" && url === "/v1/models") {
107
+ await proxyGet("/models", res);
108
+ return;
109
+ }
110
+ if (method === "POST" && url === "/v1/chat/completions") {
111
+ await proxyPost("/chat/completions", req, res);
112
+ return;
113
+ }
114
+ if (method === "POST" && url === "/v1/responses") {
115
+ await proxyPost("/responses", req, res);
116
+ return;
117
+ }
118
+ res.writeHead(404, { "content-type": "application/json" });
119
+ res.end(JSON.stringify({ error: { message: "Not found" } }));
120
+ }
121
+ async function proxyGet(upstreamPath, res) {
122
+ const copilotToken = await tokenManager.getToken();
123
+ const apiBase = tokenManager.getApiBaseUrl();
124
+ const targetUrl = new URL(upstreamPath, apiBase);
125
+ const headers = (0, copilot_token_1.getCopilotRequestHeaders)(copilotToken);
126
+ const upstreamRes = await httpsRequest({
127
+ method: "GET",
128
+ url: targetUrl,
129
+ headers,
130
+ });
131
+ res.writeHead(upstreamRes.statusCode, filterResponseHeaders(upstreamRes.headers));
132
+ upstreamRes.pipe(res);
133
+ }
134
+ async function proxyPost(upstreamPath, req, res) {
135
+ const body = await readRequestBody(req);
136
+ const copilotToken = await tokenManager.getToken();
137
+ const apiBase = tokenManager.getApiBaseUrl();
138
+ const targetUrl = new URL(upstreamPath, apiBase);
139
+ const requestId = crypto.randomUUID();
140
+ const headers = (0, copilot_token_1.getCopilotRequestHeaders)(copilotToken, requestId);
141
+ // Preserve content-length for the body
142
+ headers["content-length"] = Buffer.byteLength(body).toString();
143
+ const upstreamRes = await httpsRequest({
144
+ method: "POST",
145
+ url: targetUrl,
146
+ headers,
147
+ body,
148
+ });
149
+ // If upstream returned 401, try refreshing token once and retry
150
+ if (upstreamRes.statusCode === 401) {
151
+ upstreamRes.resume();
152
+ logWorkerEvent("upstream 401, invalidating token and retrying");
153
+ tokenManager.invalidate();
154
+ const freshToken = await tokenManager.getToken();
155
+ const retryHeaders = (0, copilot_token_1.getCopilotRequestHeaders)(freshToken, crypto.randomUUID());
156
+ retryHeaders["content-length"] = Buffer.byteLength(body).toString();
157
+ const retryRes = await httpsRequest({
158
+ method: "POST",
159
+ url: targetUrl,
160
+ headers: retryHeaders,
161
+ body,
162
+ });
163
+ res.writeHead(retryRes.statusCode, filterResponseHeaders(retryRes.headers));
164
+ retryRes.pipe(res);
165
+ return;
166
+ }
167
+ res.writeHead(upstreamRes.statusCode, filterResponseHeaders(upstreamRes.headers));
168
+ upstreamRes.pipe(res);
169
+ }
170
+ function httpsRequest(args) {
171
+ return new Promise((resolve, reject) => {
172
+ const req = https.request({
173
+ hostname: args.url.hostname,
174
+ port: args.url.port || 443,
175
+ path: args.url.pathname + args.url.search,
176
+ method: args.method,
177
+ headers: args.headers,
178
+ }, (res) => {
179
+ resolve(res);
180
+ });
181
+ req.on("error", reject);
182
+ if (args.body) {
183
+ req.write(args.body);
184
+ }
185
+ req.end();
186
+ });
187
+ }
188
+ function readRequestBody(req) {
189
+ return new Promise((resolve, reject) => {
190
+ const chunks = [];
191
+ req.on("data", (chunk) => chunks.push(chunk));
192
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
193
+ req.on("error", reject);
194
+ });
195
+ }
196
+ function filterResponseHeaders(headers) {
197
+ const filtered = {};
198
+ const passthrough = ["content-type", "transfer-encoding", "x-request-id"];
199
+ for (const key of passthrough) {
200
+ if (headers[key]) {
201
+ filtered[key] = headers[key];
202
+ }
203
+ }
204
+ // Always allow cache-control for SSE
205
+ if (headers["cache-control"]) {
206
+ filtered["cache-control"] = headers["cache-control"];
207
+ }
208
+ return filtered;
209
+ }
210
+ function isAuthorized(req, expectedApiKey) {
211
+ const authorization = req.headers.authorization;
212
+ if (!authorization || !authorization.startsWith("Bearer ")) {
213
+ return false;
214
+ }
215
+ return authorization.slice("Bearer ".length) === expectedApiKey;
216
+ }
217
+ if (require.main === module) {
218
+ process.on("uncaughtException", (error) => {
219
+ logWorkerEvent(`worker uncaught exception: ${error.message}`);
220
+ });
221
+ process.on("unhandledRejection", (reason) => {
222
+ logWorkerEvent(`worker unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
223
+ });
224
+ void main().catch((error) => {
225
+ logWorkerEvent(`worker startup failure: ${error instanceof Error ? error.message : String(error)}`);
226
+ process.exit(1);
227
+ });
228
+ }