@nick3/copilot-api 1.3.3 → 1.3.8
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 +34 -24
- package/dist/account-DhQb2A6q.js +17 -0
- package/dist/account-DhQb2A6q.js.map +1 -0
- package/dist/{accounts-manager-H7YGTVk8.js → accounts-manager-BGBtDChT.js} +25 -573
- package/dist/accounts-manager-BGBtDChT.js.map +1 -0
- package/dist/accounts-registry-c7rs5Ed9.js +180 -0
- package/dist/accounts-registry-c7rs5Ed9.js.map +1 -0
- package/dist/admin/assets/index-BB9SaCFS.js +57 -0
- package/dist/admin/assets/{index-D-pIr-q0.css → index-CvffOmW7.css} +1 -1
- package/dist/admin/index.html +2 -2
- package/dist/auth-BvIHvhP9.js +241 -0
- package/dist/auth-BvIHvhP9.js.map +1 -0
- package/dist/check-usage-CiTcYDij.js +82 -0
- package/dist/check-usage-CiTcYDij.js.map +1 -0
- package/dist/debug-hQJWwXtC.js +82 -0
- package/dist/debug-hQJWwXtC.js.map +1 -0
- package/dist/get-copilot-token-CUT8hpgX.js +13 -0
- package/dist/get-copilot-token-CUT8hpgX.js.map +1 -0
- package/dist/main.js +25 -735
- package/dist/main.js.map +1 -1
- package/dist/paths-DoT4SZ8f.js +49 -0
- package/dist/paths-DoT4SZ8f.js.map +1 -0
- package/dist/poll-access-token-Dnd_xK36.js +52 -0
- package/dist/poll-access-token-Dnd_xK36.js.map +1 -0
- package/dist/{server-BZhJgGbw.js → server-COWbIpDR.js} +277 -23
- package/dist/server-COWbIpDR.js.map +1 -0
- package/dist/start-BbULwJ9B.js +306 -0
- package/dist/start-BbULwJ9B.js.map +1 -0
- package/dist/utils-BaoXuYkx.js +338 -0
- package/dist/utils-BaoXuYkx.js.map +1 -0
- package/package.json +1 -1
- package/dist/accounts-manager-H7YGTVk8.js.map +0 -1
- package/dist/admin/assets/index-C9gbhsHu.js +0 -56
- package/dist/server-BZhJgGbw.js.map +0 -1
|
@@ -1,562 +1,12 @@
|
|
|
1
|
+
import { PATHS } from "./paths-DoT4SZ8f.js";
|
|
2
|
+
import { addAccountToRegistry, hasLegacyToken, hasRegistry, listAccountsFromRegistry, loadAccountToken, readLegacyToken, saveAccountToken } from "./accounts-registry-c7rs5Ed9.js";
|
|
3
|
+
import { HTTPError, getCopilotUsage, getGitHubUser, getModels } from "./utils-BaoXuYkx.js";
|
|
4
|
+
import { getCopilotToken } from "./get-copilot-token-CUT8hpgX.js";
|
|
1
5
|
import consola from "consola";
|
|
2
|
-
import fs from "node:fs
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
import os, { networkInterfaces } from "node:os";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
7
|
-
import fs$1 from "node:fs";
|
|
6
|
+
import fs from "node:fs";
|
|
8
7
|
|
|
9
|
-
//#region src/lib/paths.ts
|
|
10
|
-
const AUTH_APP = process.env.COPILOT_API_OAUTH_APP?.trim() || "";
|
|
11
|
-
const ENTERPRISE_PREFIX = process.env.COPILOT_API_ENTERPRISE_URL ? "ent_" : "";
|
|
12
|
-
const DEFAULT_DIR = path.join(os.homedir(), ".local", "share", "copilot-api");
|
|
13
|
-
const APP_DIR = process.env.COPILOT_API_HOME || DEFAULT_DIR;
|
|
14
|
-
const GITHUB_TOKEN_PATH = path.join(APP_DIR, AUTH_APP, ENTERPRISE_PREFIX + "github_token");
|
|
15
|
-
const CONFIG_PATH = path.join(APP_DIR, "config.json");
|
|
16
|
-
const MODELS_PATH = path.join(APP_DIR, "models.json");
|
|
17
|
-
const TOKENS_DIR = path.join(APP_DIR, "tokens");
|
|
18
|
-
const ACCOUNTS_REGISTRY_PATH = path.join(APP_DIR, "accounts-registry.json");
|
|
19
|
-
const PATHS = {
|
|
20
|
-
APP_DIR,
|
|
21
|
-
GITHUB_TOKEN_PATH,
|
|
22
|
-
CONFIG_PATH,
|
|
23
|
-
MODELS_PATH,
|
|
24
|
-
TOKENS_DIR,
|
|
25
|
-
ACCOUNTS_REGISTRY_PATH
|
|
26
|
-
};
|
|
27
|
-
/**
|
|
28
|
-
* Get the token file path for a specific account.
|
|
29
|
-
* @param id - The account ID (GitHub login)
|
|
30
|
-
* @returns The absolute path to the account's token file
|
|
31
|
-
*/
|
|
32
|
-
function accountTokenPath(id) {
|
|
33
|
-
return path.join(TOKENS_DIR, `github_${id}`);
|
|
34
|
-
}
|
|
35
|
-
async function ensurePaths() {
|
|
36
|
-
await fs.mkdir(PATHS.APP_DIR, { recursive: true });
|
|
37
|
-
await fs.mkdir(path.join(PATHS.APP_DIR, AUTH_APP), { recursive: true });
|
|
38
|
-
await fs.mkdir(PATHS.TOKENS_DIR, { recursive: true });
|
|
39
|
-
await ensureFile(PATHS.GITHUB_TOKEN_PATH);
|
|
40
|
-
await ensureFile(PATHS.CONFIG_PATH);
|
|
41
|
-
}
|
|
42
|
-
async function ensureFile(filePath) {
|
|
43
|
-
try {
|
|
44
|
-
await fs.access(filePath, fs.constants.W_OK);
|
|
45
|
-
} catch {
|
|
46
|
-
await fs.writeFile(filePath, "");
|
|
47
|
-
await fs.chmod(filePath, 384);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
//#endregion
|
|
52
|
-
//#region src/lib/accounts-registry.ts
|
|
53
|
-
/**
|
|
54
|
-
* Validate account ID (GitHub login).
|
|
55
|
-
* Rules:
|
|
56
|
-
* - Only alphanumeric characters or single hyphens
|
|
57
|
-
* - 1-39 chars
|
|
58
|
-
* - Cannot begin or end with a hyphen
|
|
59
|
-
* - No consecutive hyphens
|
|
60
|
-
*/
|
|
61
|
-
function validateAccountId(id) {
|
|
62
|
-
if (id.length === 0 || id.length > 39) return false;
|
|
63
|
-
if (!/^[a-z0-9-]+$/i.test(id)) return false;
|
|
64
|
-
if (id.startsWith("-") || id.endsWith("-")) return false;
|
|
65
|
-
if (id.includes("--")) return false;
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
const accountMetaSchema = z.object({
|
|
69
|
-
id: z.string().refine(validateAccountId, { message: "Invalid account id. Expected a GitHub login (1-39 chars, alphanumeric or single hyphens, no leading/trailing hyphen, no consecutive hyphens)." }),
|
|
70
|
-
accountType: z.enum([
|
|
71
|
-
"individual",
|
|
72
|
-
"business",
|
|
73
|
-
"enterprise"
|
|
74
|
-
]),
|
|
75
|
-
addedAt: z.number()
|
|
76
|
-
});
|
|
77
|
-
const accountRegistrySchema = z.object({
|
|
78
|
-
version: z.literal(1),
|
|
79
|
-
accounts: z.array(accountMetaSchema)
|
|
80
|
-
});
|
|
81
|
-
/**
|
|
82
|
-
* Create an empty registry with the current schema version.
|
|
83
|
-
*/
|
|
84
|
-
function createEmptyRegistry() {
|
|
85
|
-
return {
|
|
86
|
-
version: 1,
|
|
87
|
-
accounts: []
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Load the accounts registry from disk.
|
|
92
|
-
* Returns an empty registry if the file doesn't exist.
|
|
93
|
-
*/
|
|
94
|
-
async function loadRegistry() {
|
|
95
|
-
try {
|
|
96
|
-
const content = await fs.readFile(PATHS.ACCOUNTS_REGISTRY_PATH, "utf8");
|
|
97
|
-
if (!content.trim()) return createEmptyRegistry();
|
|
98
|
-
let parsed;
|
|
99
|
-
try {
|
|
100
|
-
parsed = JSON.parse(content);
|
|
101
|
-
} catch (error) {
|
|
102
|
-
throw new Error(`Invalid accounts registry JSON at ${PATHS.ACCOUNTS_REGISTRY_PATH}: ${error instanceof Error ? error.message : String(error)}`);
|
|
103
|
-
}
|
|
104
|
-
const result = accountRegistrySchema.safeParse(parsed);
|
|
105
|
-
if (!result.success) {
|
|
106
|
-
const issues = result.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ");
|
|
107
|
-
throw new Error(`Invalid accounts registry at ${PATHS.ACCOUNTS_REGISTRY_PATH}: ${issues}`);
|
|
108
|
-
}
|
|
109
|
-
const registry = result.data;
|
|
110
|
-
const seen = /* @__PURE__ */ new Set();
|
|
111
|
-
for (const account of registry.accounts) {
|
|
112
|
-
if (seen.has(account.id)) throw new Error(`Invalid accounts registry at ${PATHS.ACCOUNTS_REGISTRY_PATH}: duplicate account id "${account.id}"`);
|
|
113
|
-
seen.add(account.id);
|
|
114
|
-
}
|
|
115
|
-
return registry;
|
|
116
|
-
} catch (error) {
|
|
117
|
-
if (error.code === "ENOENT") return createEmptyRegistry();
|
|
118
|
-
throw error;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Save the accounts registry to disk with secure permissions.
|
|
123
|
-
*/
|
|
124
|
-
async function saveRegistry(registry) {
|
|
125
|
-
const content = JSON.stringify(registry, null, 2);
|
|
126
|
-
await fs.writeFile(PATHS.ACCOUNTS_REGISTRY_PATH, content, { mode: 384 });
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Add an account to the registry.
|
|
130
|
-
* The account is appended to the end of the list (lowest priority).
|
|
131
|
-
*/
|
|
132
|
-
async function addAccountToRegistry(meta) {
|
|
133
|
-
if (!validateAccountId(meta.id)) throw new Error(`Invalid account ID: ${meta.id}`);
|
|
134
|
-
const registry = await loadRegistry();
|
|
135
|
-
if (registry.accounts.some((a) => a.id === meta.id)) throw new Error(`Account already exists: ${meta.id}`);
|
|
136
|
-
registry.accounts.push(meta);
|
|
137
|
-
await saveRegistry(registry);
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Remove an account from the registry by ID or index (1-based).
|
|
141
|
-
* Returns the removed account metadata.
|
|
142
|
-
*/
|
|
143
|
-
async function removeAccountFromRegistry(idOrIndex) {
|
|
144
|
-
const registry = await loadRegistry();
|
|
145
|
-
let index;
|
|
146
|
-
if (typeof idOrIndex === "number") {
|
|
147
|
-
index = idOrIndex - 1;
|
|
148
|
-
if (index < 0 || index >= registry.accounts.length) throw new Error(`Invalid account index: ${idOrIndex}`);
|
|
149
|
-
} else {
|
|
150
|
-
index = registry.accounts.findIndex((a) => a.id === idOrIndex);
|
|
151
|
-
if (index === -1) throw new Error(`Account not found: ${idOrIndex}`);
|
|
152
|
-
}
|
|
153
|
-
const [removed] = registry.accounts.splice(index, 1);
|
|
154
|
-
await saveRegistry(registry);
|
|
155
|
-
return removed;
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* List all accounts from the registry.
|
|
159
|
-
*/
|
|
160
|
-
async function listAccountsFromRegistry() {
|
|
161
|
-
return (await loadRegistry()).accounts;
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Load the GitHub token for a specific account.
|
|
165
|
-
* Returns null if the token file doesn't exist.
|
|
166
|
-
*/
|
|
167
|
-
async function loadAccountToken(id) {
|
|
168
|
-
if (!validateAccountId(id)) throw new Error(`Invalid account ID: ${id}`);
|
|
169
|
-
try {
|
|
170
|
-
const tokenPath = accountTokenPath(id);
|
|
171
|
-
return (await fs.readFile(tokenPath, "utf8")).trim() || null;
|
|
172
|
-
} catch (error) {
|
|
173
|
-
if (error.code === "ENOENT") return null;
|
|
174
|
-
throw error;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Save the GitHub token for a specific account with secure permissions.
|
|
179
|
-
*/
|
|
180
|
-
async function saveAccountToken(id, token) {
|
|
181
|
-
if (!validateAccountId(id)) throw new Error(`Invalid account ID: ${id}`);
|
|
182
|
-
const tokenPath = accountTokenPath(id);
|
|
183
|
-
await fs.writeFile(tokenPath, token, { mode: 384 });
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
|
-
* Remove the GitHub token file for a specific account.
|
|
187
|
-
*/
|
|
188
|
-
async function removeAccountToken(id) {
|
|
189
|
-
if (!validateAccountId(id)) throw new Error(`Invalid account ID: ${id}`);
|
|
190
|
-
const tokenPath = accountTokenPath(id);
|
|
191
|
-
try {
|
|
192
|
-
await fs.unlink(tokenPath);
|
|
193
|
-
} catch (error) {
|
|
194
|
-
if (error.code !== "ENOENT") throw error;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* Check if the legacy github_token file exists.
|
|
199
|
-
*/
|
|
200
|
-
async function hasLegacyToken() {
|
|
201
|
-
try {
|
|
202
|
-
return (await fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8")).trim().length > 0;
|
|
203
|
-
} catch {
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Read the legacy github_token file.
|
|
209
|
-
* Returns null if the file doesn't exist or is empty.
|
|
210
|
-
*/
|
|
211
|
-
async function readLegacyToken() {
|
|
212
|
-
try {
|
|
213
|
-
return (await fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8")).trim() || null;
|
|
214
|
-
} catch {
|
|
215
|
-
return null;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* Check if the registry file exists and has accounts.
|
|
220
|
-
*/
|
|
221
|
-
async function hasRegistry() {
|
|
222
|
-
return (await loadRegistry()).accounts.length > 0;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
//#endregion
|
|
226
|
-
//#region src/lib/state.ts
|
|
227
|
-
const state = {
|
|
228
|
-
accountType: "individual",
|
|
229
|
-
manualApprove: false,
|
|
230
|
-
rateLimitWait: false,
|
|
231
|
-
showToken: false,
|
|
232
|
-
verbose: false
|
|
233
|
-
};
|
|
234
|
-
/**
|
|
235
|
-
* Create an AccountContext from the current global state.
|
|
236
|
-
* This is a compatibility layer for transitioning to multi-account support.
|
|
237
|
-
* @throws Error if githubToken is not set in state
|
|
238
|
-
*/
|
|
239
|
-
function accountFromState() {
|
|
240
|
-
if (!state.githubToken) throw new Error("GitHub token not set in state");
|
|
241
|
-
return {
|
|
242
|
-
githubToken: state.githubToken,
|
|
243
|
-
copilotToken: state.copilotToken,
|
|
244
|
-
accountType: state.accountType,
|
|
245
|
-
vsCodeVersion: state.vsCodeVersion
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
//#endregion
|
|
250
|
-
//#region src/lib/api-config.ts
|
|
251
|
-
const isOpencodeOauthApp = () => {
|
|
252
|
-
return process.env.COPILOT_API_OAUTH_APP?.trim() === "opencode";
|
|
253
|
-
};
|
|
254
|
-
const normalizeDomain = (input) => {
|
|
255
|
-
return input.trim().replace(/^https?:\/\//u, "").replace(/\/+$/u, "");
|
|
256
|
-
};
|
|
257
|
-
const getEnterpriseDomain = () => {
|
|
258
|
-
const raw = (process.env.COPILOT_API_ENTERPRISE_URL ?? "").trim();
|
|
259
|
-
if (!raw) return null;
|
|
260
|
-
return normalizeDomain(raw) || null;
|
|
261
|
-
};
|
|
262
|
-
const getGitHubBaseUrl = () => {
|
|
263
|
-
const resolvedDomain = getEnterpriseDomain();
|
|
264
|
-
return resolvedDomain ? `https://${resolvedDomain}` : GITHUB_BASE_URL;
|
|
265
|
-
};
|
|
266
|
-
const getGitHubApiBaseUrl = () => {
|
|
267
|
-
const resolvedDomain = getEnterpriseDomain();
|
|
268
|
-
return resolvedDomain ? `https://${resolvedDomain}/api/v3` : GITHUB_API_BASE_URL;
|
|
269
|
-
};
|
|
270
|
-
const getOpencodeOauthHeaders = () => {
|
|
271
|
-
return {
|
|
272
|
-
Accept: "application/json",
|
|
273
|
-
"Content-Type": "application/json",
|
|
274
|
-
"User-Agent": "opencode/1.2.16 ai-sdk/provider-utils/3.0.21 runtime/bun/1.3.10, opencode/1.2.16"
|
|
275
|
-
};
|
|
276
|
-
};
|
|
277
|
-
const getOauthUrls = () => {
|
|
278
|
-
const githubBaseUrl = getGitHubBaseUrl();
|
|
279
|
-
return {
|
|
280
|
-
deviceCodeUrl: `${githubBaseUrl}/login/device/code`,
|
|
281
|
-
accessTokenUrl: `${githubBaseUrl}/login/oauth/access_token`
|
|
282
|
-
};
|
|
283
|
-
};
|
|
284
|
-
const getOauthAppConfig = () => {
|
|
285
|
-
if (isOpencodeOauthApp()) return {
|
|
286
|
-
clientId: OPENCODE_GITHUB_CLIENT_ID,
|
|
287
|
-
headers: getOpencodeOauthHeaders(),
|
|
288
|
-
scope: GITHUB_APP_SCOPES
|
|
289
|
-
};
|
|
290
|
-
return {
|
|
291
|
-
clientId: GITHUB_CLIENT_ID,
|
|
292
|
-
headers: standardHeaders(),
|
|
293
|
-
scope: GITHUB_APP_SCOPES
|
|
294
|
-
};
|
|
295
|
-
};
|
|
296
|
-
const prepareInteractionHeaders = (sessionId, isSubagent, headers) => {
|
|
297
|
-
const sendInteractionHeaders = !isOpencodeOauthApp();
|
|
298
|
-
if (isSubagent) {
|
|
299
|
-
headers["x-initiator"] = "agent";
|
|
300
|
-
if (sendInteractionHeaders) headers["x-interaction-type"] = "conversation-subagent";
|
|
301
|
-
}
|
|
302
|
-
if (sessionId && sendInteractionHeaders) headers["x-interaction-id"] = sessionId;
|
|
303
|
-
};
|
|
304
|
-
const standardHeaders = () => ({
|
|
305
|
-
"content-type": "application/json",
|
|
306
|
-
accept: "application/json"
|
|
307
|
-
});
|
|
308
|
-
const COPILOT_VERSION = "0.38.2";
|
|
309
|
-
const EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`;
|
|
310
|
-
const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`;
|
|
311
|
-
const API_VERSION = "2025-10-01";
|
|
312
|
-
const copilotBaseUrl = (account) => {
|
|
313
|
-
const enterpriseDomain = getEnterpriseDomain();
|
|
314
|
-
if (enterpriseDomain) return `https://copilot-api.${enterpriseDomain}`;
|
|
315
|
-
return account.accountType === "individual" ? "https://api.githubcopilot.com" : `https://api.${account.accountType}.githubcopilot.com`;
|
|
316
|
-
};
|
|
317
|
-
const copilotHeaders = (account, vision = false, requestId) => {
|
|
318
|
-
if (isOpencodeOauthApp()) {
|
|
319
|
-
const headers$1 = {
|
|
320
|
-
Authorization: `Bearer ${account.copilotToken ?? account.githubToken}`,
|
|
321
|
-
...getOpencodeOauthHeaders(),
|
|
322
|
-
"Openai-Intent": "conversation-edits"
|
|
323
|
-
};
|
|
324
|
-
if (vision) headers$1["Copilot-Vision-Request"] = "true";
|
|
325
|
-
return headers$1;
|
|
326
|
-
}
|
|
327
|
-
const resolvedRequestId = requestId ?? randomUUID();
|
|
328
|
-
const headers = {
|
|
329
|
-
Authorization: `Bearer ${account.copilotToken}`,
|
|
330
|
-
"content-type": standardHeaders()["content-type"],
|
|
331
|
-
"copilot-integration-id": "vscode-chat",
|
|
332
|
-
"editor-version": `vscode/${account.vsCodeVersion}`,
|
|
333
|
-
"editor-plugin-version": EDITOR_PLUGIN_VERSION,
|
|
334
|
-
"user-agent": USER_AGENT,
|
|
335
|
-
"openai-intent": "conversation-agent",
|
|
336
|
-
"x-github-api-version": API_VERSION,
|
|
337
|
-
"x-request-id": resolvedRequestId,
|
|
338
|
-
"x-vscode-user-agent-library-version": "electron-fetch",
|
|
339
|
-
"x-agent-task-id": resolvedRequestId,
|
|
340
|
-
"x-interaction-type": "conversation-agent"
|
|
341
|
-
};
|
|
342
|
-
if (vision) headers["copilot-vision-request"] = "true";
|
|
343
|
-
if (state.macMachineId) headers["vscode-machineid"] = state.macMachineId;
|
|
344
|
-
if (state.vsCodeSessionId) headers["vscode-sessionid"] = state.vsCodeSessionId;
|
|
345
|
-
return headers;
|
|
346
|
-
};
|
|
347
|
-
const GITHUB_API_BASE_URL = "https://api.github.com";
|
|
348
|
-
const githubHeaders = (account) => ({
|
|
349
|
-
...standardHeaders(),
|
|
350
|
-
authorization: `token ${account.githubToken}`,
|
|
351
|
-
"editor-version": `vscode/${account.vsCodeVersion}`,
|
|
352
|
-
"editor-plugin-version": EDITOR_PLUGIN_VERSION,
|
|
353
|
-
"user-agent": USER_AGENT,
|
|
354
|
-
"x-github-api-version": API_VERSION,
|
|
355
|
-
"x-vscode-user-agent-library-version": "electron-fetch"
|
|
356
|
-
});
|
|
357
|
-
const GITHUB_BASE_URL = "https://github.com";
|
|
358
|
-
const GITHUB_CLIENT_ID = "Iv1.b507a08c87ecfe98";
|
|
359
|
-
const GITHUB_APP_SCOPES = ["read:user"].join(" ");
|
|
360
|
-
const OPENCODE_GITHUB_CLIENT_ID = "Ov23li8tweQw6odWQebz";
|
|
361
|
-
|
|
362
|
-
//#endregion
|
|
363
|
-
//#region src/lib/error.ts
|
|
364
|
-
var HTTPError = class extends Error {
|
|
365
|
-
response;
|
|
366
|
-
constructor(message, response) {
|
|
367
|
-
super(message);
|
|
368
|
-
this.response = response;
|
|
369
|
-
}
|
|
370
|
-
};
|
|
371
|
-
async function forwardError(c, error) {
|
|
372
|
-
consola.error("Error occurred:", error);
|
|
373
|
-
if (error instanceof HTTPError) {
|
|
374
|
-
const errorText = await error.response.text();
|
|
375
|
-
let errorJson;
|
|
376
|
-
try {
|
|
377
|
-
errorJson = JSON.parse(errorText);
|
|
378
|
-
} catch {
|
|
379
|
-
errorJson = errorText;
|
|
380
|
-
}
|
|
381
|
-
consola.error("HTTP error:", errorJson);
|
|
382
|
-
return c.json({ error: {
|
|
383
|
-
message: errorText,
|
|
384
|
-
type: "error"
|
|
385
|
-
} }, error.response.status);
|
|
386
|
-
}
|
|
387
|
-
return c.json({ error: {
|
|
388
|
-
message: error.message,
|
|
389
|
-
type: "error"
|
|
390
|
-
} }, 500);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
//#endregion
|
|
394
|
-
//#region src/services/github/get-copilot-usage.ts
|
|
395
|
-
const getCopilotUsage = async (account) => {
|
|
396
|
-
const ctx = account ?? accountFromState();
|
|
397
|
-
const response = await fetch(`${getGitHubApiBaseUrl()}/copilot_internal/user`, { headers: githubHeaders(ctx) });
|
|
398
|
-
if (!response.ok) throw new HTTPError("Failed to get Copilot usage", response);
|
|
399
|
-
return await response.json();
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
//#endregion
|
|
403
|
-
//#region src/services/github/get-user.ts
|
|
404
|
-
async function getGitHubUser(account) {
|
|
405
|
-
const token = account?.githubToken ?? state.githubToken;
|
|
406
|
-
if (!token) throw new Error("GitHub token not set");
|
|
407
|
-
const response = await fetch(`${getGitHubApiBaseUrl()}/user`, { headers: {
|
|
408
|
-
authorization: `token ${token}`,
|
|
409
|
-
...standardHeaders()
|
|
410
|
-
} });
|
|
411
|
-
if (!response.ok) throw new HTTPError("Failed to get GitHub user", response);
|
|
412
|
-
return await response.json();
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
//#endregion
|
|
416
|
-
//#region src/services/copilot/get-models.ts
|
|
417
|
-
const getModels = async (account) => {
|
|
418
|
-
const ctx = account ?? accountFromState();
|
|
419
|
-
const response = await fetch(`${copilotBaseUrl(ctx)}/models`, { headers: copilotHeaders(ctx) });
|
|
420
|
-
if (!response.ok) throw new HTTPError("Failed to get models", response);
|
|
421
|
-
const models = await response.json();
|
|
422
|
-
try {
|
|
423
|
-
await fs.mkdir(PATHS.APP_DIR, { recursive: true });
|
|
424
|
-
await fs.writeFile(PATHS.MODELS_PATH, `${JSON.stringify(models, null, 2)}\n`, {
|
|
425
|
-
encoding: "utf8",
|
|
426
|
-
mode: 384
|
|
427
|
-
});
|
|
428
|
-
} catch {}
|
|
429
|
-
return models;
|
|
430
|
-
};
|
|
431
|
-
|
|
432
|
-
//#endregion
|
|
433
|
-
//#region src/services/get-vscode-version.ts
|
|
434
|
-
const FALLBACK = "1.110.1";
|
|
435
|
-
async function getVSCodeVersion() {
|
|
436
|
-
await Promise.resolve();
|
|
437
|
-
return FALLBACK;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
//#endregion
|
|
441
|
-
//#region src/lib/utils.ts
|
|
442
|
-
const sleep = (ms) => new Promise((resolve) => {
|
|
443
|
-
setTimeout(resolve, ms);
|
|
444
|
-
});
|
|
445
|
-
const isNullish = (value) => value === null || value === void 0;
|
|
446
|
-
const cacheVSCodeVersion = async () => {
|
|
447
|
-
const response = await getVSCodeVersion();
|
|
448
|
-
state.vsCodeVersion = response;
|
|
449
|
-
consola.info(`Using VSCode version: ${response}`);
|
|
450
|
-
};
|
|
451
|
-
const invalidMacAddresses = new Set([
|
|
452
|
-
"00:00:00:00:00:00",
|
|
453
|
-
"ff:ff:ff:ff:ff:ff",
|
|
454
|
-
"ac:de:48:00:11:22"
|
|
455
|
-
]);
|
|
456
|
-
function validateMacAddress(candidate) {
|
|
457
|
-
const tempCandidate = candidate.replaceAll("-", ":").toLowerCase();
|
|
458
|
-
return !invalidMacAddresses.has(tempCandidate);
|
|
459
|
-
}
|
|
460
|
-
function getMac() {
|
|
461
|
-
const ifaces = networkInterfaces();
|
|
462
|
-
for (const name in ifaces) {
|
|
463
|
-
const networkInterface = ifaces[name];
|
|
464
|
-
if (networkInterface) {
|
|
465
|
-
for (const { mac } of networkInterface) if (validateMacAddress(mac)) return mac;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
return null;
|
|
469
|
-
}
|
|
470
|
-
const cacheMacMachineId = () => {
|
|
471
|
-
const macAddress = getMac() ?? randomUUID();
|
|
472
|
-
state.macMachineId = createHash("sha256").update(macAddress, "utf8").digest("hex");
|
|
473
|
-
consola.debug(`Using machine ID: ${state.macMachineId}`);
|
|
474
|
-
};
|
|
475
|
-
const SESSION_REFRESH_BASE_MS = 3600 * 1e3;
|
|
476
|
-
const SESSION_REFRESH_JITTER_MS = 1200 * 1e3;
|
|
477
|
-
let vsCodeSessionRefreshTimer = null;
|
|
478
|
-
const generateSessionId = () => {
|
|
479
|
-
state.vsCodeSessionId = randomUUID() + Date.now().toString();
|
|
480
|
-
consola.debug(`Generated VSCode session ID: ${state.vsCodeSessionId}`);
|
|
481
|
-
};
|
|
482
|
-
const stopVsCodeSessionRefreshLoop = () => {
|
|
483
|
-
if (vsCodeSessionRefreshTimer) {
|
|
484
|
-
clearTimeout(vsCodeSessionRefreshTimer);
|
|
485
|
-
vsCodeSessionRefreshTimer = null;
|
|
486
|
-
}
|
|
487
|
-
};
|
|
488
|
-
const scheduleSessionIdRefresh = () => {
|
|
489
|
-
const randomDelay = Math.floor(Math.random() * SESSION_REFRESH_JITTER_MS);
|
|
490
|
-
const delay = SESSION_REFRESH_BASE_MS + randomDelay;
|
|
491
|
-
consola.debug(`Scheduling next VSCode session ID refresh in ${Math.round(delay / 1e3)} seconds`);
|
|
492
|
-
stopVsCodeSessionRefreshLoop();
|
|
493
|
-
vsCodeSessionRefreshTimer = setTimeout(() => {
|
|
494
|
-
try {
|
|
495
|
-
generateSessionId();
|
|
496
|
-
} catch (error) {
|
|
497
|
-
consola.error("Failed to refresh session ID, rescheduling...", error);
|
|
498
|
-
} finally {
|
|
499
|
-
scheduleSessionIdRefresh();
|
|
500
|
-
}
|
|
501
|
-
}, delay);
|
|
502
|
-
};
|
|
503
|
-
const cacheVsCodeSessionId = () => {
|
|
504
|
-
stopVsCodeSessionRefreshLoop();
|
|
505
|
-
generateSessionId();
|
|
506
|
-
scheduleSessionIdRefresh();
|
|
507
|
-
};
|
|
508
|
-
const findLastUserContent = (messages) => {
|
|
509
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
510
|
-
const msg = messages[i];
|
|
511
|
-
if (msg.role === "user" && msg.content) {
|
|
512
|
-
if (typeof msg.content === "string") return msg.content;
|
|
513
|
-
else if (Array.isArray(msg.content)) {
|
|
514
|
-
const array = msg.content.filter((n) => n.type !== "tool_result").map((n) => ({
|
|
515
|
-
...n,
|
|
516
|
-
cache_control: void 0
|
|
517
|
-
}));
|
|
518
|
-
if (array.length > 0) return JSON.stringify(array);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
return null;
|
|
523
|
-
};
|
|
524
|
-
const generateRequestIdFromPayload = (payload, sessionId) => {
|
|
525
|
-
const messages = payload.messages;
|
|
526
|
-
if (messages) {
|
|
527
|
-
const lastUserContent = typeof messages === "string" ? messages : findLastUserContent(messages);
|
|
528
|
-
if (lastUserContent) return getUUID((sessionId ?? "") + (state.macMachineId ?? "") + lastUserContent);
|
|
529
|
-
}
|
|
530
|
-
return randomUUID();
|
|
531
|
-
};
|
|
532
|
-
const getRootSessionId = (anthropicPayload, c) => {
|
|
533
|
-
let sessionId;
|
|
534
|
-
if (anthropicPayload.metadata?.user_id) {
|
|
535
|
-
const sessionMatch = (/* @__PURE__ */ new RegExp(/_session_(.+)$/)).exec(anthropicPayload.metadata.user_id);
|
|
536
|
-
sessionId = sessionMatch ? sessionMatch[1] : void 0;
|
|
537
|
-
} else sessionId = c.req.header("x-session-id");
|
|
538
|
-
if (sessionId) return getUUID(sessionId);
|
|
539
|
-
return sessionId;
|
|
540
|
-
};
|
|
541
|
-
const getUUID = (content) => {
|
|
542
|
-
const uuidBytes = createHash("sha256").update(content).digest().subarray(0, 16);
|
|
543
|
-
uuidBytes[6] = uuidBytes[6] & 15 | 64;
|
|
544
|
-
uuidBytes[8] = uuidBytes[8] & 63 | 128;
|
|
545
|
-
const uuidHex = uuidBytes.toString("hex");
|
|
546
|
-
return `${uuidHex.slice(0, 8)}-${uuidHex.slice(8, 12)}-${uuidHex.slice(12, 16)}-${uuidHex.slice(16, 20)}-${uuidHex.slice(20)}`;
|
|
547
|
-
};
|
|
548
|
-
|
|
549
|
-
//#endregion
|
|
550
|
-
//#region src/services/github/get-copilot-token.ts
|
|
551
|
-
const getCopilotToken = async (account) => {
|
|
552
|
-
const ctx = account ?? accountFromState();
|
|
553
|
-
const response = await fetch(`${getGitHubApiBaseUrl()}/copilot_internal/v2/token`, { headers: githubHeaders(ctx) });
|
|
554
|
-
if (!response.ok) throw new HTTPError("Failed to get Copilot token", response);
|
|
555
|
-
return await response.json();
|
|
556
|
-
};
|
|
557
|
-
|
|
558
|
-
//#endregion
|
|
559
8
|
//#region src/lib/config.ts
|
|
9
|
+
const PROVIDER_TYPE_ANTHROPIC = "anthropic";
|
|
560
10
|
const gpt5ExplorationPrompt = `## Exploration and reading files
|
|
561
11
|
- **Think first.** Before any tool call, decide ALL files/resources you will need.
|
|
562
12
|
- **Batch everything.** If you need multiple files (even from different places), read them together.
|
|
@@ -588,6 +38,7 @@ const defaultConfig = {
|
|
|
588
38
|
extraPrompts: {
|
|
589
39
|
"gpt-5-mini": gpt5ExplorationPrompt,
|
|
590
40
|
"gpt-5.3-codex": gpt5CommentaryPrompt,
|
|
41
|
+
"gpt-5.4-mini": gpt5CommentaryPrompt,
|
|
591
42
|
"gpt-5.4": gpt5CommentaryPrompt
|
|
592
43
|
},
|
|
593
44
|
smallModel: "gpt-5-mini",
|
|
@@ -596,6 +47,7 @@ const defaultConfig = {
|
|
|
596
47
|
modelReasoningEfforts: {
|
|
597
48
|
"gpt-5-mini": "low",
|
|
598
49
|
"gpt-5.3-codex": "xhigh",
|
|
50
|
+
"gpt-5.4-mini": "xhigh",
|
|
599
51
|
"gpt-5.4": "xhigh"
|
|
600
52
|
},
|
|
601
53
|
allowOriginalModelNamesForAliases: false,
|
|
@@ -621,23 +73,23 @@ function normalizeModelRefreshIntervalHours(value) {
|
|
|
621
73
|
}
|
|
622
74
|
function ensureConfigFile() {
|
|
623
75
|
try {
|
|
624
|
-
fs
|
|
76
|
+
fs.accessSync(PATHS.CONFIG_PATH, fs.constants.R_OK);
|
|
625
77
|
return;
|
|
626
78
|
} catch {}
|
|
627
79
|
try {
|
|
628
|
-
fs
|
|
629
|
-
fs
|
|
80
|
+
fs.mkdirSync(PATHS.APP_DIR, { recursive: true });
|
|
81
|
+
fs.writeFileSync(PATHS.CONFIG_PATH, `${JSON.stringify(defaultConfig, null, 2)}\n`, "utf8");
|
|
630
82
|
try {
|
|
631
|
-
fs
|
|
83
|
+
fs.chmodSync(PATHS.CONFIG_PATH, 384);
|
|
632
84
|
} catch {}
|
|
633
85
|
} catch {}
|
|
634
86
|
}
|
|
635
87
|
function readConfigFromDisk() {
|
|
636
88
|
ensureConfigFile();
|
|
637
89
|
try {
|
|
638
|
-
const raw = fs
|
|
90
|
+
const raw = fs.readFileSync(PATHS.CONFIG_PATH, "utf8");
|
|
639
91
|
if (!raw.trim()) {
|
|
640
|
-
fs
|
|
92
|
+
fs.writeFileSync(PATHS.CONFIG_PATH, `${JSON.stringify(defaultConfig, null, 2)}\n`, "utf8");
|
|
641
93
|
return defaultConfig;
|
|
642
94
|
}
|
|
643
95
|
return JSON.parse(raw);
|
|
@@ -737,7 +189,7 @@ function mergeConfigWithDefaults() {
|
|
|
737
189
|
mergeDefaultModelRefreshInterval
|
|
738
190
|
]);
|
|
739
191
|
if (changed) try {
|
|
740
|
-
fs
|
|
192
|
+
fs.writeFileSync(PATHS.CONFIG_PATH, `${JSON.stringify(mergedConfig, null, 2)}\n`, "utf8");
|
|
741
193
|
} catch (writeError) {
|
|
742
194
|
consola.warn("Failed to write merged config defaults", writeError);
|
|
743
195
|
}
|
|
@@ -867,6 +319,9 @@ function getModelRefreshIntervalMs() {
|
|
|
867
319
|
function isMessageStartInputTokensFallbackEnabled() {
|
|
868
320
|
return getConfig().messageStartInputTokensFallback ?? false;
|
|
869
321
|
}
|
|
322
|
+
function shouldCompactUseSmallModel() {
|
|
323
|
+
return getConfig().compactUseSmallModel ?? true;
|
|
324
|
+
}
|
|
870
325
|
function getResponsesApiContextManagementModels() {
|
|
871
326
|
return getConfig().responsesApiContextManagementModels ?? defaultConfig.responsesApiContextManagementModels ?? [];
|
|
872
327
|
}
|
|
@@ -883,9 +338,6 @@ function getReasoningEffortForModel(model) {
|
|
|
883
338
|
function isForceAgentEnabled() {
|
|
884
339
|
return getConfig().forceAgent ?? false;
|
|
885
340
|
}
|
|
886
|
-
function shouldCompactUseSmallModel() {
|
|
887
|
-
return getConfig().compactUseSmallModel ?? true;
|
|
888
|
-
}
|
|
889
341
|
function normalizeProviderBaseUrl(url) {
|
|
890
342
|
return url.trim().replace(/\/+$/u, "");
|
|
891
343
|
}
|
|
@@ -895,8 +347,7 @@ function getProviderConfig(name) {
|
|
|
895
347
|
const provider = getConfig().providers?.[providerName];
|
|
896
348
|
if (!provider) return null;
|
|
897
349
|
if (provider.enabled === false) return null;
|
|
898
|
-
|
|
899
|
-
if (type !== "anthropic") {
|
|
350
|
+
if ((provider.type ?? PROVIDER_TYPE_ANTHROPIC) !== PROVIDER_TYPE_ANTHROPIC) {
|
|
900
351
|
consola.warn(`Provider ${providerName} is ignored because only anthropic type is supported`);
|
|
901
352
|
return null;
|
|
902
353
|
}
|
|
@@ -908,10 +359,11 @@ function getProviderConfig(name) {
|
|
|
908
359
|
}
|
|
909
360
|
return {
|
|
910
361
|
name: providerName,
|
|
911
|
-
type,
|
|
362
|
+
type: PROVIDER_TYPE_ANTHROPIC,
|
|
912
363
|
baseUrl,
|
|
913
364
|
apiKey,
|
|
914
|
-
models: provider.models
|
|
365
|
+
models: provider.models,
|
|
366
|
+
adjustInputTokens: provider.adjustInputTokens
|
|
915
367
|
};
|
|
916
368
|
}
|
|
917
369
|
function isMessagesApiEnabled() {
|
|
@@ -1557,7 +1009,7 @@ var AccountsManager = class {
|
|
|
1557
1009
|
startRegistryWatcher() {
|
|
1558
1010
|
this.stopRegistryWatcher();
|
|
1559
1011
|
try {
|
|
1560
|
-
this.registryWatcher = fs
|
|
1012
|
+
this.registryWatcher = fs.watch(PATHS.ACCOUNTS_REGISTRY_PATH, (eventType) => {
|
|
1561
1013
|
if (eventType === "change") this.scheduleReload();
|
|
1562
1014
|
});
|
|
1563
1015
|
this.registryWatcherRestartDelayMs = WATCHER_RESTART_INITIAL_DELAY_MS;
|
|
@@ -1723,5 +1175,5 @@ var AccountsManager = class {
|
|
|
1723
1175
|
const accountsManager = new AccountsManager();
|
|
1724
1176
|
|
|
1725
1177
|
//#endregion
|
|
1726
|
-
export {
|
|
1727
|
-
//# sourceMappingURL=accounts-manager-
|
|
1178
|
+
export { PROVIDER_TYPE_ANTHROPIC, accountsManager, getAliasTargetSet, getConfig, getExtraPromptForModel, getModelAliases, getModelAliasesInfo, getModelRefreshIntervalMs, getProviderConfig, getReasoningEffortForModel, getSmallModel, isForceAgentEnabled, isFreeModelLoadBalancingEnabled, isMessageStartInputTokensFallbackEnabled, isMessagesApiEnabled, isResponsesApiContextManagementModel, mergeConfigWithDefaults, shouldCompactUseSmallModel };
|
|
1179
|
+
//# sourceMappingURL=accounts-manager-BGBtDChT.js.map
|