@openhoo/hoopilot 0.6.0 → 0.6.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/dist/codexx.js CHANGED
@@ -156,7 +156,7 @@ function errorMessage(error) {
156
156
  }
157
157
  if (import.meta.main) {
158
158
  main().catch((error) => {
159
- console.error(error instanceof Error ? error.message : String(error));
159
+ console.error(errorMessage(error));
160
160
  process.exit(1);
161
161
  });
162
162
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/codexx.ts"],"sourcesContent":["#!/usr/bin/env bun\n\nimport { spawn } from \"node:child_process\";\nimport { constants as osConstants } from \"node:os\";\nimport type { FetchLike } from \"./types\";\n\nconst DEFAULT_BASE_URL = \"http://127.0.0.1:4141/v1\";\nconst DEFAULT_API_KEY = \"local-key\";\nconst DEFAULT_CODEX_BIN = \"codex\";\nconst DEFAULT_MODEL = \"gpt-5.5\";\nconst DEFAULT_REASONING_EFFORT = \"xhigh\";\nconst PROXY_ENV_KEYS = [\n \"ALL_PROXY\",\n \"HTTPS_PROXY\",\n \"HTTP_PROXY\",\n \"NO_PROXY\",\n \"all_proxy\",\n \"https_proxy\",\n \"http_proxy\",\n \"no_proxy\",\n];\n\nexport interface CodexxInvocation {\n args: string[];\n baseUrl: string;\n command: string;\n env: NodeJS.ProcessEnv;\n model: string;\n}\n\nexport function buildCodexxInvocation(\n argv: string[],\n env: NodeJS.ProcessEnv = process.env,\n): CodexxInvocation {\n const baseUrl = env.CODEXX_BASE_URL ?? DEFAULT_BASE_URL;\n const apiKey =\n env.CODEXX_API_KEY ?? env.HOOPILOT_API_KEY ?? env.OPENAI_API_KEY ?? DEFAULT_API_KEY;\n const command = env.CODEXX_CODEX_BIN ?? DEFAULT_CODEX_BIN;\n const model = env.CODEXX_MODEL ?? DEFAULT_MODEL;\n const reasoningEffort = env.CODEXX_MODEL_REASONING_EFFORT ?? DEFAULT_REASONING_EFFORT;\n const providerConfig = [\n '{ name = \"Hoopilot\"',\n `base_url = ${JSON.stringify(baseUrl)}`,\n 'env_key = \"OPENAI_API_KEY\"',\n 'wire_api = \"responses\"',\n \"supports_websockets = false }\",\n ].join(\", \");\n\n return {\n args: [\n \"--disable\",\n \"network_proxy\",\n \"-c\",\n 'model_provider=\"hoopilot\"',\n \"-c\",\n `model_providers.hoopilot=${providerConfig}`,\n \"-m\",\n model,\n \"-c\",\n `model_reasoning_effort=${JSON.stringify(reasoningEffort)}`,\n ...argv,\n ],\n baseUrl,\n command,\n env: withoutProxyEnv({\n ...env,\n OPENAI_API_KEY: apiKey,\n }),\n model,\n };\n}\n\nfunction withoutProxyEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n const next = { ...env };\n for (const key of PROXY_ENV_KEYS) {\n delete next[key];\n }\n return next;\n}\n\nexport async function main(argv = Bun.argv.slice(2), env = process.env): Promise<void> {\n if (argv.length === 1 && (argv[0] === \"--help\" || argv[0] === \"-h\")) {\n console.log(helpText());\n return;\n }\n\n const invocation = buildCodexxInvocation(argv, env);\n if (env.CODEXX_SKIP_MODEL_PREFLIGHT !== \"1\") {\n await verifyCodexxModel(invocation);\n }\n const child = spawn(invocation.command, invocation.args, {\n env: invocation.env,\n shell: process.platform === \"win32\",\n stdio: \"inherit\",\n });\n\n const exitCode = await new Promise<number>((resolve, reject) => {\n child.once(\"error\", reject);\n child.once(\"exit\", (code, signal) => {\n if (typeof code === \"number\") {\n resolve(code);\n return;\n }\n resolve(signal ? 128 + signalNumber(signal) : 1);\n });\n });\n\n process.exitCode = exitCode;\n}\n\nexport async function verifyCodexxModel(\n invocation: Pick<CodexxInvocation, \"baseUrl\" | \"env\" | \"model\">,\n fetcher: FetchLike = fetch,\n): Promise<void> {\n const modelsUrl = `${invocation.baseUrl.replace(/\\/+$/, \"\")}/models`;\n let response: Response;\n try {\n response = await fetcher(modelsUrl, {\n headers: {\n accept: \"application/json\",\n authorization: `Bearer ${invocation.env.OPENAI_API_KEY ?? DEFAULT_API_KEY}`,\n },\n method: \"GET\",\n });\n } catch (error) {\n throw new Error(\n `Could not reach Hoopilot at ${modelsUrl}. Start Hoopilot first, or set CODEXX_SKIP_MODEL_PREFLIGHT=1 to skip this check. ${errorMessage(error)}`,\n );\n }\n\n if (!response.ok) {\n throw new Error(\n `Could not verify model ${JSON.stringify(invocation.model)} because ${modelsUrl} returned ${response.status}: ${await shortResponseText(response)}`,\n );\n }\n\n const models = modelIds(await response.json().catch(() => undefined));\n if (models.length > 0 && !models.includes(invocation.model)) {\n throw new Error(\n `The logged-in Copilot account does not advertise model ${JSON.stringify(invocation.model)} at ${modelsUrl}. Available models: ${models.join(\", \")}. After upgrading Hoopilot, rerun \"hoopilot login\" to refresh the Copilot OAuth token, or set CODEXX_MODEL to one of the advertised model IDs.`,\n );\n }\n}\n\nfunction helpText(): string {\n return `codexx\n\nRun Codex against an already-running local Hoopilot server.\n\nUsage:\n codexx [codex options] [prompt]\n\nEnvironment:\n CODEXX_BASE_URL OpenAI-compatible base URL. Default: ${DEFAULT_BASE_URL}\n CODEXX_API_KEY API key sent to the local Hoopilot server.\n HOOPILOT_API_KEY Used as the API key when CODEXX_API_KEY is unset.\n OPENAI_API_KEY Used as the API key when both CODEXX_API_KEY and HOOPILOT_API_KEY are unset.\n CODEXX_CODEX_BIN Codex executable to run. Default: ${DEFAULT_CODEX_BIN}\n CODEXX_MODEL Codex model to use. Default: ${DEFAULT_MODEL}\n CODEXX_MODEL_REASONING_EFFORT\n Codex reasoning effort. Default: ${DEFAULT_REASONING_EFFORT}\n CODEXX_SKIP_MODEL_PREFLIGHT\n Set to 1 to skip checking /v1/models before starting Codex.\n\ncodexx does not start Hoopilot and does not change your shell environment. It selects a temporary Hoopilot model provider with Responses WebSockets disabled, uses ${DEFAULT_MODEL} with ${DEFAULT_REASONING_EFFORT} reasoning by default, disables Codex's network_proxy feature, and removes proxy variables only from the spawned Codex process.`;\n}\n\nfunction signalNumber(signal: NodeJS.Signals): number {\n return osConstants.signals[signal] ?? 1;\n}\n\nfunction modelIds(value: unknown): string[] {\n const record = value && typeof value === \"object\" && !Array.isArray(value) ? value : {};\n const data = \"data\" in record && Array.isArray(record.data) ? record.data : [];\n return data\n .map((entry) =>\n entry && typeof entry === \"object\" && \"id\" in entry && typeof entry.id === \"string\"\n ? entry.id\n : undefined,\n )\n .filter((id): id is string => typeof id === \"string\" && id.length > 0);\n}\n\nasync function shortResponseText(response: Response): Promise<string> {\n const text = await response.text();\n return text.slice(0, 500);\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nif (import.meta.main) {\n main().catch((error: unknown) => {\n console.error(error instanceof Error ? error.message : String(error));\n process.exit(1);\n });\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/codexx.ts"],"sourcesContent":["#!/usr/bin/env bun\n\nimport { spawn } from \"node:child_process\";\nimport { constants as osConstants } from \"node:os\";\nimport type { FetchLike } from \"./types\";\n\nconst DEFAULT_BASE_URL = \"http://127.0.0.1:4141/v1\";\nconst DEFAULT_API_KEY = \"local-key\";\nconst DEFAULT_CODEX_BIN = \"codex\";\nconst DEFAULT_MODEL = \"gpt-5.5\";\nconst DEFAULT_REASONING_EFFORT = \"xhigh\";\nconst PROXY_ENV_KEYS = [\n \"ALL_PROXY\",\n \"HTTPS_PROXY\",\n \"HTTP_PROXY\",\n \"NO_PROXY\",\n \"all_proxy\",\n \"https_proxy\",\n \"http_proxy\",\n \"no_proxy\",\n];\n\nexport interface CodexxInvocation {\n args: string[];\n baseUrl: string;\n command: string;\n env: NodeJS.ProcessEnv;\n model: string;\n}\n\nexport function buildCodexxInvocation(\n argv: string[],\n env: NodeJS.ProcessEnv = process.env,\n): CodexxInvocation {\n const baseUrl = env.CODEXX_BASE_URL ?? DEFAULT_BASE_URL;\n const apiKey =\n env.CODEXX_API_KEY ?? env.HOOPILOT_API_KEY ?? env.OPENAI_API_KEY ?? DEFAULT_API_KEY;\n const command = env.CODEXX_CODEX_BIN ?? DEFAULT_CODEX_BIN;\n const model = env.CODEXX_MODEL ?? DEFAULT_MODEL;\n const reasoningEffort = env.CODEXX_MODEL_REASONING_EFFORT ?? DEFAULT_REASONING_EFFORT;\n const providerConfig = [\n '{ name = \"Hoopilot\"',\n `base_url = ${JSON.stringify(baseUrl)}`,\n 'env_key = \"OPENAI_API_KEY\"',\n 'wire_api = \"responses\"',\n \"supports_websockets = false }\",\n ].join(\", \");\n\n return {\n args: [\n \"--disable\",\n \"network_proxy\",\n \"-c\",\n 'model_provider=\"hoopilot\"',\n \"-c\",\n `model_providers.hoopilot=${providerConfig}`,\n \"-m\",\n model,\n \"-c\",\n `model_reasoning_effort=${JSON.stringify(reasoningEffort)}`,\n ...argv,\n ],\n baseUrl,\n command,\n env: withoutProxyEnv({\n ...env,\n OPENAI_API_KEY: apiKey,\n }),\n model,\n };\n}\n\nfunction withoutProxyEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n const next = { ...env };\n for (const key of PROXY_ENV_KEYS) {\n delete next[key];\n }\n return next;\n}\n\nexport async function main(argv = Bun.argv.slice(2), env = process.env): Promise<void> {\n if (argv.length === 1 && (argv[0] === \"--help\" || argv[0] === \"-h\")) {\n console.log(helpText());\n return;\n }\n\n const invocation = buildCodexxInvocation(argv, env);\n if (env.CODEXX_SKIP_MODEL_PREFLIGHT !== \"1\") {\n await verifyCodexxModel(invocation);\n }\n const child = spawn(invocation.command, invocation.args, {\n env: invocation.env,\n shell: process.platform === \"win32\",\n stdio: \"inherit\",\n });\n\n const exitCode = await new Promise<number>((resolve, reject) => {\n child.once(\"error\", reject);\n child.once(\"exit\", (code, signal) => {\n if (typeof code === \"number\") {\n resolve(code);\n return;\n }\n resolve(signal ? 128 + signalNumber(signal) : 1);\n });\n });\n\n process.exitCode = exitCode;\n}\n\nexport async function verifyCodexxModel(\n invocation: Pick<CodexxInvocation, \"baseUrl\" | \"env\" | \"model\">,\n fetcher: FetchLike = fetch,\n): Promise<void> {\n const modelsUrl = `${invocation.baseUrl.replace(/\\/+$/, \"\")}/models`;\n let response: Response;\n try {\n response = await fetcher(modelsUrl, {\n headers: {\n accept: \"application/json\",\n authorization: `Bearer ${invocation.env.OPENAI_API_KEY ?? DEFAULT_API_KEY}`,\n },\n method: \"GET\",\n });\n } catch (error) {\n throw new Error(\n `Could not reach Hoopilot at ${modelsUrl}. Start Hoopilot first, or set CODEXX_SKIP_MODEL_PREFLIGHT=1 to skip this check. ${errorMessage(error)}`,\n );\n }\n\n if (!response.ok) {\n throw new Error(\n `Could not verify model ${JSON.stringify(invocation.model)} because ${modelsUrl} returned ${response.status}: ${await shortResponseText(response)}`,\n );\n }\n\n const models = modelIds(await response.json().catch(() => undefined));\n if (models.length > 0 && !models.includes(invocation.model)) {\n throw new Error(\n `The logged-in Copilot account does not advertise model ${JSON.stringify(invocation.model)} at ${modelsUrl}. Available models: ${models.join(\", \")}. After upgrading Hoopilot, rerun \"hoopilot login\" to refresh the Copilot OAuth token, or set CODEXX_MODEL to one of the advertised model IDs.`,\n );\n }\n}\n\nfunction helpText(): string {\n return `codexx\n\nRun Codex against an already-running local Hoopilot server.\n\nUsage:\n codexx [codex options] [prompt]\n\nEnvironment:\n CODEXX_BASE_URL OpenAI-compatible base URL. Default: ${DEFAULT_BASE_URL}\n CODEXX_API_KEY API key sent to the local Hoopilot server.\n HOOPILOT_API_KEY Used as the API key when CODEXX_API_KEY is unset.\n OPENAI_API_KEY Used as the API key when both CODEXX_API_KEY and HOOPILOT_API_KEY are unset.\n CODEXX_CODEX_BIN Codex executable to run. Default: ${DEFAULT_CODEX_BIN}\n CODEXX_MODEL Codex model to use. Default: ${DEFAULT_MODEL}\n CODEXX_MODEL_REASONING_EFFORT\n Codex reasoning effort. Default: ${DEFAULT_REASONING_EFFORT}\n CODEXX_SKIP_MODEL_PREFLIGHT\n Set to 1 to skip checking /v1/models before starting Codex.\n\ncodexx does not start Hoopilot and does not change your shell environment. It selects a temporary Hoopilot model provider with Responses WebSockets disabled, uses ${DEFAULT_MODEL} with ${DEFAULT_REASONING_EFFORT} reasoning by default, disables Codex's network_proxy feature, and removes proxy variables only from the spawned Codex process.`;\n}\n\nfunction signalNumber(signal: NodeJS.Signals): number {\n return osConstants.signals[signal] ?? 1;\n}\n\nfunction modelIds(value: unknown): string[] {\n const record = value && typeof value === \"object\" && !Array.isArray(value) ? value : {};\n const data = \"data\" in record && Array.isArray(record.data) ? record.data : [];\n return data\n .map((entry) =>\n entry && typeof entry === \"object\" && \"id\" in entry && typeof entry.id === \"string\"\n ? entry.id\n : undefined,\n )\n .filter((id): id is string => typeof id === \"string\" && id.length > 0);\n}\n\nasync function shortResponseText(response: Response): Promise<string> {\n const text = await response.text();\n return text.slice(0, 500);\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nif (import.meta.main) {\n main().catch((error: unknown) => {\n console.error(errorMessage(error));\n process.exit(1);\n });\n}\n"],"mappings":"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","names":[]}
package/dist/index.cjs CHANGED
@@ -91,25 +91,36 @@ function readStoredCopilotAuth(path = authStorePath()) {
91
91
  }
92
92
  function writeStoredCopilotAuth(auth, path = authStorePath()) {
93
93
  (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path), { recursive: true });
94
- (0, import_node_fs.writeFileSync)(
95
- path,
96
- `${JSON.stringify(
97
- {
98
- ...auth,
99
- createdAt: auth.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
100
- },
101
- null,
102
- 2
103
- )}
104
- `,
105
- { mode: 384 }
106
- );
94
+ const data = `${JSON.stringify(
95
+ {
96
+ ...auth,
97
+ createdAt: auth.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
98
+ },
99
+ null,
100
+ 2
101
+ )}
102
+ `;
103
+ const tmpPath = `${path}.${process.pid}.tmp`;
104
+ (0, import_node_fs.writeFileSync)(tmpPath, data, { mode: 384 });
105
+ (0, import_node_fs.renameSync)(tmpPath, path);
107
106
  try {
108
107
  (0, import_node_fs.chmodSync)(path, 384);
109
108
  } catch {
110
109
  }
111
110
  }
112
111
 
112
+ // src/util.ts
113
+ function trimTrailingSlash(value) {
114
+ return value.replace(/\/+$/, "");
115
+ }
116
+ async function truncatedResponseText(response, max = 500) {
117
+ const text = await response.text();
118
+ return text.slice(0, max);
119
+ }
120
+ function asRecord(value) {
121
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
122
+ }
123
+
113
124
  // src/auth.ts
114
125
  var DEFAULT_COPILOT_API_BASE_URL = "https://api.githubcopilot.com";
115
126
  var REFRESH_SKEW_MS = 6e4;
@@ -152,11 +163,19 @@ var CopilotAuth = class {
152
163
  return access;
153
164
  }
154
165
  };
155
- function trimTrailingSlash(value) {
156
- return value.replace(/\/+$/, "");
157
- }
158
166
 
159
167
  // src/copilot.ts
168
+ function applyCopilotHeaders(headers, token) {
169
+ headers.set("accept", headers.get("accept") ?? "application/json");
170
+ headers.set("authorization", `Bearer ${token}`);
171
+ headers.set("copilot-integration-id", "vscode-chat");
172
+ headers.set("editor-plugin-version", "hoopilot/0.1.0");
173
+ headers.set("editor-version", "Hoopilot/0.1.0");
174
+ headers.set("openai-intent", "conversation-panel");
175
+ headers.set("user-agent", "hoopilot/0.1.0");
176
+ headers.set("x-github-api-version", "2026-06-01");
177
+ return headers;
178
+ }
160
179
  var CopilotClient = class {
161
180
  #auth;
162
181
  #fetch;
@@ -195,15 +214,7 @@ var CopilotClient = class {
195
214
  }
196
215
  async fetchCopilot(path, init) {
197
216
  const access = await this.#auth.getAccess();
198
- const headers = new Headers(init.headers);
199
- headers.set("accept", headers.get("accept") ?? "application/json");
200
- headers.set("authorization", `Bearer ${access.token}`);
201
- headers.set("copilot-integration-id", "vscode-chat");
202
- headers.set("editor-plugin-version", "hoopilot/0.1.0");
203
- headers.set("editor-version", "Hoopilot/0.1.0");
204
- headers.set("openai-intent", "conversation-panel");
205
- headers.set("user-agent", "hoopilot/0.1.0");
206
- headers.set("x-github-api-version", "2026-06-01");
217
+ const headers = applyCopilotHeaders(new Headers(init.headers), access.token);
207
218
  return this.#fetch(`${access.apiBaseUrl}${path}`, {
208
219
  ...init,
209
220
  headers
@@ -217,6 +228,7 @@ var DEFAULT_GITHUB_COPILOT_CLIENT_ID = "Ov23li8tweQw6odWQebz";
217
228
  var DEFAULT_GITHUB_DOMAIN = "github.com";
218
229
  var DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
219
230
  var POLLING_SAFETY_MARGIN_MS = 3e3;
231
+ var REQUEST_TIMEOUT_MS = 15e3;
220
232
  async function githubCopilotDeviceLogin(options = {}) {
221
233
  const env = options.env ?? process.env;
222
234
  const fetcher = options.fetch ?? fetch;
@@ -251,16 +263,20 @@ async function requestDeviceCode(fetcher, domain, clientId) {
251
263
  scope: "read:user"
252
264
  }),
253
265
  headers: oauthHeaders(),
254
- method: "POST"
266
+ method: "POST",
267
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
255
268
  });
256
269
  if (!response.ok) {
257
270
  throw new Error(
258
- `GitHub device authorization failed with ${response.status}: ${await safeResponseText(
271
+ `GitHub device authorization failed with ${response.status}: ${await truncatedResponseText(
259
272
  response
260
273
  )}`
261
274
  );
262
275
  }
263
- return await response.json();
276
+ return parseJsonResponse(
277
+ response,
278
+ "GitHub device authorization response was not valid JSON"
279
+ );
264
280
  }
265
281
  async function pollForAccessToken(fetcher, sleeper, domain, clientId, device) {
266
282
  let intervalMs = device.interval * 1e3 + POLLING_SAFETY_MARGIN_MS;
@@ -274,16 +290,20 @@ async function pollForAccessToken(fetcher, sleeper, domain, clientId, device) {
274
290
  grant_type: DEVICE_GRANT_TYPE
275
291
  }),
276
292
  headers: oauthHeaders(),
277
- method: "POST"
293
+ method: "POST",
294
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
278
295
  });
279
296
  if (!response.ok) {
280
297
  throw new Error(
281
- `GitHub device token exchange failed with ${response.status}: ${await safeResponseText(
298
+ `GitHub device token exchange failed with ${response.status}: ${await truncatedResponseText(
282
299
  response
283
300
  )}`
284
301
  );
285
302
  }
286
- const data = await response.json();
303
+ const data = await parseJsonResponse(
304
+ response,
305
+ "GitHub device token response was not valid JSON"
306
+ );
287
307
  if (data.access_token) {
288
308
  return data.access_token;
289
309
  }
@@ -319,9 +339,13 @@ function normalizeDomain(value) {
319
339
  function positiveSeconds(value, fallback) {
320
340
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
321
341
  }
322
- async function safeResponseText(response) {
342
+ async function parseJsonResponse(response, context) {
323
343
  const text = await response.text();
324
- return text.slice(0, 500);
344
+ try {
345
+ return JSON.parse(text);
346
+ } catch {
347
+ throw new Error(`${context}: ${text.slice(0, 500)}`);
348
+ }
325
349
  }
326
350
 
327
351
  // src/logger.ts
@@ -424,6 +448,16 @@ function shouldCreateLogger(options) {
424
448
  options.logger || options.logFormat || options.logLevel || options.env?.HOOPILOT_LOG_FORMAT || options.env?.HOOPILOT_LOG_LEVEL
425
449
  );
426
450
  }
451
+ function errorDetails(error) {
452
+ if (error instanceof Error) {
453
+ return {
454
+ message: error.message,
455
+ name: error.name,
456
+ stack: error.stack
457
+ };
458
+ }
459
+ return { message: String(error) };
460
+ }
427
461
  function isLogFormat(value) {
428
462
  return LOG_FORMATS.includes(value);
429
463
  }
@@ -601,17 +635,18 @@ function responsesStreamFromChatStream(chatStream, options) {
601
635
  const lines = buffer.split(/\r?\n/);
602
636
  buffer = lines.pop() ?? "";
603
637
  for (const line of lines) {
604
- processChatSseLine(line, enqueue, tools, (delta) => {
638
+ processChatSseLine(messageId, line, enqueue, tools, (delta) => {
605
639
  text += delta;
606
640
  });
607
641
  }
608
642
  }
609
643
  if (buffer) {
610
- processChatSseLine(buffer, enqueue, tools, (delta) => {
644
+ processChatSseLine(messageId, buffer, enqueue, tools, (delta) => {
611
645
  text += delta;
612
646
  });
613
647
  }
614
- const output = streamOutputItems(messageId, text, [...tools.values()]);
648
+ const toolItems = [...tools.values()].map(functionCallItem);
649
+ const output = [messageOutputItem(text, messageId), ...toolItems];
615
650
  enqueue("response.output_text.done", {
616
651
  content_index: 0,
617
652
  item_id: messageId,
@@ -635,8 +670,7 @@ function responsesStreamFromChatStream(chatStream, options) {
635
670
  output_index: 0,
636
671
  type: "response.output_item.done"
637
672
  });
638
- tools.forEach((tool, index) => {
639
- const item = functionCallItem(tool);
673
+ toolItems.forEach((item, index) => {
640
674
  const outputIndex = index + 1;
641
675
  enqueue("response.output_item.added", {
642
676
  item,
@@ -644,7 +678,7 @@ function responsesStreamFromChatStream(chatStream, options) {
644
678
  type: "response.output_item.added"
645
679
  });
646
680
  enqueue("response.function_call_arguments.done", {
647
- arguments: tool.arguments,
681
+ arguments: item.arguments,
648
682
  item_id: item.id,
649
683
  output_index: outputIndex,
650
684
  type: "response.function_call_arguments.done"
@@ -662,6 +696,8 @@ function responsesStreamFromChatStream(chatStream, options) {
662
696
  enqueue("done", "[DONE]");
663
697
  controller.close();
664
698
  } catch (error) {
699
+ await reader.cancel(error).catch(() => {
700
+ });
665
701
  controller.error(error);
666
702
  } finally {
667
703
  reader.releaseLock();
@@ -868,7 +904,7 @@ function firstChoice(completion) {
868
904
  const choices = Array.isArray(completion.choices) ? completion.choices : [];
869
905
  return asRecord(choices[0]);
870
906
  }
871
- function processChatSseLine(line, enqueue, tools, appendText) {
907
+ function processChatSseLine(messageId, line, enqueue, tools, appendText) {
872
908
  const trimmed = line.trim();
873
909
  if (!trimmed.startsWith("data:")) {
874
910
  return;
@@ -889,7 +925,7 @@ function processChatSseLine(line, enqueue, tools, appendText) {
889
925
  enqueue("response.output_text.delta", {
890
926
  content_index: 0,
891
927
  delta: content,
892
- item_id: "",
928
+ item_id: messageId,
893
929
  output_index: 0,
894
930
  type: "response.output_text.delta"
895
931
  });
@@ -911,9 +947,6 @@ function processChatSseLine(line, enqueue, tools, appendText) {
911
947
  tools.set(index, existing);
912
948
  }
913
949
  }
914
- function streamOutputItems(messageId, text, tools) {
915
- return [messageOutputItem(text, messageId), ...tools.map((tool) => functionCallItem(tool))];
916
- }
917
950
  function baseStreamResponse(id, model, createdAt, status, output) {
918
951
  return {
919
952
  created_at: createdAt,
@@ -953,9 +986,6 @@ function parseJson(data) {
953
986
  function removeUndefined(record) {
954
987
  return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== void 0));
955
988
  }
956
- function asRecord(value) {
957
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
958
- }
959
989
  function randomId() {
960
990
  return crypto.randomUUID().replaceAll("-", "");
961
991
  }
@@ -1145,6 +1175,9 @@ async function handleCompletions(client, request, logger) {
1145
1175
  return proxyError(upstream, logger);
1146
1176
  }
1147
1177
  logUpstreamSuccess(logger, "/chat/completions", upstream.status);
1178
+ if (isStreamingResponse(upstream)) {
1179
+ return proxyResponse(upstream);
1180
+ }
1148
1181
  return jsonResponse(chatCompletionToCompletion(await upstream.json()));
1149
1182
  }
1150
1183
  async function handleResponses(client, request, logger) {
@@ -1187,8 +1220,7 @@ function proxyResponse(upstream) {
1187
1220
  }
1188
1221
  async function readJson(request) {
1189
1222
  try {
1190
- const value = await request.json();
1191
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
1223
+ return asRecord(await request.json());
1192
1224
  } catch {
1193
1225
  throw new Error(INVALID_JSON_MESSAGE);
1194
1226
  }
@@ -1359,16 +1391,6 @@ function logUpstreamSuccess(logger, upstreamPath, status) {
1359
1391
  "copilot upstream request completed"
1360
1392
  );
1361
1393
  }
1362
- function errorDetails(error) {
1363
- if (error instanceof Error) {
1364
- return {
1365
- message: error.message,
1366
- name: error.name,
1367
- stack: error.stack
1368
- };
1369
- }
1370
- return { message: String(error) };
1371
- }
1372
1394
  // Annotate the CommonJS export names for ESM import in node:
1373
1395
  0 && (module.exports = {
1374
1396
  CopilotAuth,