@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/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/auth-store.ts
|
|
2
|
-
import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
+
import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
3
3
|
import { dirname, join } from "path";
|
|
4
4
|
function authStorePath(env = process.env) {
|
|
5
5
|
if (env.HOOPILOT_AUTH_FILE) {
|
|
@@ -31,25 +31,36 @@ function readStoredCopilotAuth(path = authStorePath()) {
|
|
|
31
31
|
}
|
|
32
32
|
function writeStoredCopilotAuth(auth, path = authStorePath()) {
|
|
33
33
|
mkdirSync(dirname(path), { recursive: true });
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
);
|
|
34
|
+
const data = `${JSON.stringify(
|
|
35
|
+
{
|
|
36
|
+
...auth,
|
|
37
|
+
createdAt: auth.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
38
|
+
},
|
|
39
|
+
null,
|
|
40
|
+
2
|
|
41
|
+
)}
|
|
42
|
+
`;
|
|
43
|
+
const tmpPath = `${path}.${process.pid}.tmp`;
|
|
44
|
+
writeFileSync(tmpPath, data, { mode: 384 });
|
|
45
|
+
renameSync(tmpPath, path);
|
|
47
46
|
try {
|
|
48
47
|
chmodSync(path, 384);
|
|
49
48
|
} catch {
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
|
|
52
|
+
// src/util.ts
|
|
53
|
+
function trimTrailingSlash(value) {
|
|
54
|
+
return value.replace(/\/+$/, "");
|
|
55
|
+
}
|
|
56
|
+
async function truncatedResponseText(response, max = 500) {
|
|
57
|
+
const text = await response.text();
|
|
58
|
+
return text.slice(0, max);
|
|
59
|
+
}
|
|
60
|
+
function asRecord(value) {
|
|
61
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
62
|
+
}
|
|
63
|
+
|
|
53
64
|
// src/auth.ts
|
|
54
65
|
var DEFAULT_COPILOT_API_BASE_URL = "https://api.githubcopilot.com";
|
|
55
66
|
var REFRESH_SKEW_MS = 6e4;
|
|
@@ -92,11 +103,19 @@ var CopilotAuth = class {
|
|
|
92
103
|
return access;
|
|
93
104
|
}
|
|
94
105
|
};
|
|
95
|
-
function trimTrailingSlash(value) {
|
|
96
|
-
return value.replace(/\/+$/, "");
|
|
97
|
-
}
|
|
98
106
|
|
|
99
107
|
// src/copilot.ts
|
|
108
|
+
function applyCopilotHeaders(headers, token) {
|
|
109
|
+
headers.set("accept", headers.get("accept") ?? "application/json");
|
|
110
|
+
headers.set("authorization", `Bearer ${token}`);
|
|
111
|
+
headers.set("copilot-integration-id", "vscode-chat");
|
|
112
|
+
headers.set("editor-plugin-version", "hoopilot/0.1.0");
|
|
113
|
+
headers.set("editor-version", "Hoopilot/0.1.0");
|
|
114
|
+
headers.set("openai-intent", "conversation-panel");
|
|
115
|
+
headers.set("user-agent", "hoopilot/0.1.0");
|
|
116
|
+
headers.set("x-github-api-version", "2026-06-01");
|
|
117
|
+
return headers;
|
|
118
|
+
}
|
|
100
119
|
var CopilotClient = class {
|
|
101
120
|
#auth;
|
|
102
121
|
#fetch;
|
|
@@ -135,15 +154,7 @@ var CopilotClient = class {
|
|
|
135
154
|
}
|
|
136
155
|
async fetchCopilot(path, init) {
|
|
137
156
|
const access = await this.#auth.getAccess();
|
|
138
|
-
const headers = new Headers(init.headers);
|
|
139
|
-
headers.set("accept", headers.get("accept") ?? "application/json");
|
|
140
|
-
headers.set("authorization", `Bearer ${access.token}`);
|
|
141
|
-
headers.set("copilot-integration-id", "vscode-chat");
|
|
142
|
-
headers.set("editor-plugin-version", "hoopilot/0.1.0");
|
|
143
|
-
headers.set("editor-version", "Hoopilot/0.1.0");
|
|
144
|
-
headers.set("openai-intent", "conversation-panel");
|
|
145
|
-
headers.set("user-agent", "hoopilot/0.1.0");
|
|
146
|
-
headers.set("x-github-api-version", "2026-06-01");
|
|
157
|
+
const headers = applyCopilotHeaders(new Headers(init.headers), access.token);
|
|
147
158
|
return this.#fetch(`${access.apiBaseUrl}${path}`, {
|
|
148
159
|
...init,
|
|
149
160
|
headers
|
|
@@ -157,6 +168,7 @@ var DEFAULT_GITHUB_COPILOT_CLIENT_ID = "Ov23li8tweQw6odWQebz";
|
|
|
157
168
|
var DEFAULT_GITHUB_DOMAIN = "github.com";
|
|
158
169
|
var DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
159
170
|
var POLLING_SAFETY_MARGIN_MS = 3e3;
|
|
171
|
+
var REQUEST_TIMEOUT_MS = 15e3;
|
|
160
172
|
async function githubCopilotDeviceLogin(options = {}) {
|
|
161
173
|
const env = options.env ?? process.env;
|
|
162
174
|
const fetcher = options.fetch ?? fetch;
|
|
@@ -191,16 +203,20 @@ async function requestDeviceCode(fetcher, domain, clientId) {
|
|
|
191
203
|
scope: "read:user"
|
|
192
204
|
}),
|
|
193
205
|
headers: oauthHeaders(),
|
|
194
|
-
method: "POST"
|
|
206
|
+
method: "POST",
|
|
207
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
195
208
|
});
|
|
196
209
|
if (!response.ok) {
|
|
197
210
|
throw new Error(
|
|
198
|
-
`GitHub device authorization failed with ${response.status}: ${await
|
|
211
|
+
`GitHub device authorization failed with ${response.status}: ${await truncatedResponseText(
|
|
199
212
|
response
|
|
200
213
|
)}`
|
|
201
214
|
);
|
|
202
215
|
}
|
|
203
|
-
return
|
|
216
|
+
return parseJsonResponse(
|
|
217
|
+
response,
|
|
218
|
+
"GitHub device authorization response was not valid JSON"
|
|
219
|
+
);
|
|
204
220
|
}
|
|
205
221
|
async function pollForAccessToken(fetcher, sleeper, domain, clientId, device) {
|
|
206
222
|
let intervalMs = device.interval * 1e3 + POLLING_SAFETY_MARGIN_MS;
|
|
@@ -214,16 +230,20 @@ async function pollForAccessToken(fetcher, sleeper, domain, clientId, device) {
|
|
|
214
230
|
grant_type: DEVICE_GRANT_TYPE
|
|
215
231
|
}),
|
|
216
232
|
headers: oauthHeaders(),
|
|
217
|
-
method: "POST"
|
|
233
|
+
method: "POST",
|
|
234
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
218
235
|
});
|
|
219
236
|
if (!response.ok) {
|
|
220
237
|
throw new Error(
|
|
221
|
-
`GitHub device token exchange failed with ${response.status}: ${await
|
|
238
|
+
`GitHub device token exchange failed with ${response.status}: ${await truncatedResponseText(
|
|
222
239
|
response
|
|
223
240
|
)}`
|
|
224
241
|
);
|
|
225
242
|
}
|
|
226
|
-
const data = await
|
|
243
|
+
const data = await parseJsonResponse(
|
|
244
|
+
response,
|
|
245
|
+
"GitHub device token response was not valid JSON"
|
|
246
|
+
);
|
|
227
247
|
if (data.access_token) {
|
|
228
248
|
return data.access_token;
|
|
229
249
|
}
|
|
@@ -259,9 +279,13 @@ function normalizeDomain(value) {
|
|
|
259
279
|
function positiveSeconds(value, fallback) {
|
|
260
280
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
261
281
|
}
|
|
262
|
-
async function
|
|
282
|
+
async function parseJsonResponse(response, context) {
|
|
263
283
|
const text = await response.text();
|
|
264
|
-
|
|
284
|
+
try {
|
|
285
|
+
return JSON.parse(text);
|
|
286
|
+
} catch {
|
|
287
|
+
throw new Error(`${context}: ${text.slice(0, 500)}`);
|
|
288
|
+
}
|
|
265
289
|
}
|
|
266
290
|
|
|
267
291
|
// src/logger.ts
|
|
@@ -364,6 +388,16 @@ function shouldCreateLogger(options) {
|
|
|
364
388
|
options.logger || options.logFormat || options.logLevel || options.env?.HOOPILOT_LOG_FORMAT || options.env?.HOOPILOT_LOG_LEVEL
|
|
365
389
|
);
|
|
366
390
|
}
|
|
391
|
+
function errorDetails(error) {
|
|
392
|
+
if (error instanceof Error) {
|
|
393
|
+
return {
|
|
394
|
+
message: error.message,
|
|
395
|
+
name: error.name,
|
|
396
|
+
stack: error.stack
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
return { message: String(error) };
|
|
400
|
+
}
|
|
367
401
|
function isLogFormat(value) {
|
|
368
402
|
return LOG_FORMATS.includes(value);
|
|
369
403
|
}
|
|
@@ -541,17 +575,18 @@ function responsesStreamFromChatStream(chatStream, options) {
|
|
|
541
575
|
const lines = buffer.split(/\r?\n/);
|
|
542
576
|
buffer = lines.pop() ?? "";
|
|
543
577
|
for (const line of lines) {
|
|
544
|
-
processChatSseLine(line, enqueue, tools, (delta) => {
|
|
578
|
+
processChatSseLine(messageId, line, enqueue, tools, (delta) => {
|
|
545
579
|
text += delta;
|
|
546
580
|
});
|
|
547
581
|
}
|
|
548
582
|
}
|
|
549
583
|
if (buffer) {
|
|
550
|
-
processChatSseLine(buffer, enqueue, tools, (delta) => {
|
|
584
|
+
processChatSseLine(messageId, buffer, enqueue, tools, (delta) => {
|
|
551
585
|
text += delta;
|
|
552
586
|
});
|
|
553
587
|
}
|
|
554
|
-
const
|
|
588
|
+
const toolItems = [...tools.values()].map(functionCallItem);
|
|
589
|
+
const output = [messageOutputItem(text, messageId), ...toolItems];
|
|
555
590
|
enqueue("response.output_text.done", {
|
|
556
591
|
content_index: 0,
|
|
557
592
|
item_id: messageId,
|
|
@@ -575,8 +610,7 @@ function responsesStreamFromChatStream(chatStream, options) {
|
|
|
575
610
|
output_index: 0,
|
|
576
611
|
type: "response.output_item.done"
|
|
577
612
|
});
|
|
578
|
-
|
|
579
|
-
const item = functionCallItem(tool);
|
|
613
|
+
toolItems.forEach((item, index) => {
|
|
580
614
|
const outputIndex = index + 1;
|
|
581
615
|
enqueue("response.output_item.added", {
|
|
582
616
|
item,
|
|
@@ -584,7 +618,7 @@ function responsesStreamFromChatStream(chatStream, options) {
|
|
|
584
618
|
type: "response.output_item.added"
|
|
585
619
|
});
|
|
586
620
|
enqueue("response.function_call_arguments.done", {
|
|
587
|
-
arguments:
|
|
621
|
+
arguments: item.arguments,
|
|
588
622
|
item_id: item.id,
|
|
589
623
|
output_index: outputIndex,
|
|
590
624
|
type: "response.function_call_arguments.done"
|
|
@@ -602,6 +636,8 @@ function responsesStreamFromChatStream(chatStream, options) {
|
|
|
602
636
|
enqueue("done", "[DONE]");
|
|
603
637
|
controller.close();
|
|
604
638
|
} catch (error) {
|
|
639
|
+
await reader.cancel(error).catch(() => {
|
|
640
|
+
});
|
|
605
641
|
controller.error(error);
|
|
606
642
|
} finally {
|
|
607
643
|
reader.releaseLock();
|
|
@@ -808,7 +844,7 @@ function firstChoice(completion) {
|
|
|
808
844
|
const choices = Array.isArray(completion.choices) ? completion.choices : [];
|
|
809
845
|
return asRecord(choices[0]);
|
|
810
846
|
}
|
|
811
|
-
function processChatSseLine(line, enqueue, tools, appendText) {
|
|
847
|
+
function processChatSseLine(messageId, line, enqueue, tools, appendText) {
|
|
812
848
|
const trimmed = line.trim();
|
|
813
849
|
if (!trimmed.startsWith("data:")) {
|
|
814
850
|
return;
|
|
@@ -829,7 +865,7 @@ function processChatSseLine(line, enqueue, tools, appendText) {
|
|
|
829
865
|
enqueue("response.output_text.delta", {
|
|
830
866
|
content_index: 0,
|
|
831
867
|
delta: content,
|
|
832
|
-
item_id:
|
|
868
|
+
item_id: messageId,
|
|
833
869
|
output_index: 0,
|
|
834
870
|
type: "response.output_text.delta"
|
|
835
871
|
});
|
|
@@ -851,9 +887,6 @@ function processChatSseLine(line, enqueue, tools, appendText) {
|
|
|
851
887
|
tools.set(index, existing);
|
|
852
888
|
}
|
|
853
889
|
}
|
|
854
|
-
function streamOutputItems(messageId, text, tools) {
|
|
855
|
-
return [messageOutputItem(text, messageId), ...tools.map((tool) => functionCallItem(tool))];
|
|
856
|
-
}
|
|
857
890
|
function baseStreamResponse(id, model, createdAt, status, output) {
|
|
858
891
|
return {
|
|
859
892
|
created_at: createdAt,
|
|
@@ -893,9 +926,6 @@ function parseJson(data) {
|
|
|
893
926
|
function removeUndefined(record) {
|
|
894
927
|
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== void 0));
|
|
895
928
|
}
|
|
896
|
-
function asRecord(value) {
|
|
897
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
898
|
-
}
|
|
899
929
|
function randomId() {
|
|
900
930
|
return crypto.randomUUID().replaceAll("-", "");
|
|
901
931
|
}
|
|
@@ -1085,6 +1115,9 @@ async function handleCompletions(client, request, logger) {
|
|
|
1085
1115
|
return proxyError(upstream, logger);
|
|
1086
1116
|
}
|
|
1087
1117
|
logUpstreamSuccess(logger, "/chat/completions", upstream.status);
|
|
1118
|
+
if (isStreamingResponse(upstream)) {
|
|
1119
|
+
return proxyResponse(upstream);
|
|
1120
|
+
}
|
|
1088
1121
|
return jsonResponse(chatCompletionToCompletion(await upstream.json()));
|
|
1089
1122
|
}
|
|
1090
1123
|
async function handleResponses(client, request, logger) {
|
|
@@ -1127,8 +1160,7 @@ function proxyResponse(upstream) {
|
|
|
1127
1160
|
}
|
|
1128
1161
|
async function readJson(request) {
|
|
1129
1162
|
try {
|
|
1130
|
-
|
|
1131
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1163
|
+
return asRecord(await request.json());
|
|
1132
1164
|
} catch {
|
|
1133
1165
|
throw new Error(INVALID_JSON_MESSAGE);
|
|
1134
1166
|
}
|
|
@@ -1299,16 +1331,6 @@ function logUpstreamSuccess(logger, upstreamPath, status) {
|
|
|
1299
1331
|
"copilot upstream request completed"
|
|
1300
1332
|
);
|
|
1301
1333
|
}
|
|
1302
|
-
function errorDetails(error) {
|
|
1303
|
-
if (error instanceof Error) {
|
|
1304
|
-
return {
|
|
1305
|
-
message: error.message,
|
|
1306
|
-
name: error.name,
|
|
1307
|
-
stack: error.stack
|
|
1308
|
-
};
|
|
1309
|
-
}
|
|
1310
|
-
return { message: String(error) };
|
|
1311
|
-
}
|
|
1312
1334
|
export {
|
|
1313
1335
|
CopilotAuth,
|
|
1314
1336
|
CopilotAuthError,
|