@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/README.md +3 -16
- package/dist/cli.js +102 -121
- package/dist/cli.js.map +1 -1
- package/dist/codexx.js +1 -1
- package/dist/codexx.js.map +1 -1
- package/dist/index.cjs +81 -59
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +82 -60
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/codexx.js
CHANGED
package/dist/codexx.js.map
CHANGED
|
@@ -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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
271
|
+
`GitHub device authorization failed with ${response.status}: ${await truncatedResponseText(
|
|
259
272
|
response
|
|
260
273
|
)}`
|
|
261
274
|
);
|
|
262
275
|
}
|
|
263
|
-
return
|
|
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
|
|
298
|
+
`GitHub device token exchange failed with ${response.status}: ${await truncatedResponseText(
|
|
282
299
|
response
|
|
283
300
|
)}`
|
|
284
301
|
);
|
|
285
302
|
}
|
|
286
|
-
const data = await
|
|
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
|
|
342
|
+
async function parseJsonResponse(response, context) {
|
|
323
343
|
const text = await response.text();
|
|
324
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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,
|