xiaodcs-copilot-api-edge 2.3.9-edge.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +888 -0
- package/README.zh-CN.md +940 -0
- package/dist/auth-BiVetBYt.js +446 -0
- package/dist/auth-Bu4MXadr.js +2 -0
- package/dist/config-SytZjLq8.js +544 -0
- package/dist/debug-BFadhEB4.js +90 -0
- package/dist/electron-fetch-BRX-ug5E.js +20 -0
- package/dist/fast-path-BoMnZCVC.js +9 -0
- package/dist/main.js +49 -0
- package/dist/mcp-fpSlKZxK.js +14 -0
- package/dist/mcp-server-BeNu_Edl.js +25 -0
- package/dist/mcp-server-DQ4r-fAy.js +2 -0
- package/dist/models-YMUf33c-.js +88 -0
- package/dist/server-CFQmvoAJ.js +11710 -0
- package/dist/start-FFVCi8su.js +528 -0
- package/dist/tls-Aq1Dd8E2.js +14 -0
- package/dist/token-D9svRIYW.js +1950 -0
- package/dist/tool-search-Ds1vbmGG.js +114 -0
- package/package.json +96 -0
- package/pages/index.html +2257 -0
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
import consola from "consola";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import fs$1 from "node:fs/promises";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
//#region src/lib/atomic-file.ts
|
|
8
|
+
const TEMP_FILE_RANDOM_BYTES = 8;
|
|
9
|
+
const fsyncDirectory = (directory) => {
|
|
10
|
+
if (process.platform === "win32") return;
|
|
11
|
+
let directoryFd;
|
|
12
|
+
try {
|
|
13
|
+
directoryFd = fs.openSync(directory, "r");
|
|
14
|
+
fs.fsyncSync(directoryFd);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.warn(`Failed to fsync directory: ${directory}`, error);
|
|
17
|
+
} finally {
|
|
18
|
+
if (directoryFd !== void 0) try {
|
|
19
|
+
fs.closeSync(directoryFd);
|
|
20
|
+
} catch {}
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
function writeFileAtomically(filePath, content) {
|
|
24
|
+
const directory = path.dirname(filePath);
|
|
25
|
+
const tempPath = path.join(directory, `.${path.basename(filePath)}.${process.pid}.${randomBytes(TEMP_FILE_RANDOM_BYTES).toString("hex")}.tmp`);
|
|
26
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
27
|
+
let fileDescriptor;
|
|
28
|
+
let tempFileCreated = false;
|
|
29
|
+
try {
|
|
30
|
+
fileDescriptor = fs.openSync(tempPath, "wx", 384);
|
|
31
|
+
tempFileCreated = true;
|
|
32
|
+
fs.writeFileSync(fileDescriptor, content, "utf8");
|
|
33
|
+
fs.fsyncSync(fileDescriptor);
|
|
34
|
+
fs.closeSync(fileDescriptor);
|
|
35
|
+
fileDescriptor = void 0;
|
|
36
|
+
fs.renameSync(tempPath, filePath);
|
|
37
|
+
tempFileCreated = false;
|
|
38
|
+
fsyncDirectory(directory);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (fileDescriptor !== void 0) try {
|
|
41
|
+
fs.closeSync(fileDescriptor);
|
|
42
|
+
} catch {}
|
|
43
|
+
if (tempFileCreated) try {
|
|
44
|
+
fs.rmSync(tempPath, { force: true });
|
|
45
|
+
} catch {}
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/lib/paths.ts
|
|
51
|
+
const AUTH_APP = process.env.COPILOT_API_OAUTH_APP?.trim() || "";
|
|
52
|
+
const ENTERPRISE_PREFIX = process.env.COPILOT_API_ENTERPRISE_URL ? "ent_" : "";
|
|
53
|
+
const DEFAULT_DIR = path.join(os.homedir(), ".local", "share", "copilot-api");
|
|
54
|
+
const APP_DIR = process.env.COPILOT_API_HOME || DEFAULT_DIR;
|
|
55
|
+
const PATHS = {
|
|
56
|
+
APP_DIR,
|
|
57
|
+
GITHUB_TOKEN_PATH: path.join(APP_DIR, AUTH_APP, ENTERPRISE_PREFIX + "github_token"),
|
|
58
|
+
CODEX_CREDENTIAL_PATH: path.join(APP_DIR, "codex_credentials.json"),
|
|
59
|
+
CONFIG_PATH: path.join(APP_DIR, "config.json")
|
|
60
|
+
};
|
|
61
|
+
async function ensurePaths() {
|
|
62
|
+
await fs$1.mkdir(path.join(PATHS.APP_DIR, AUTH_APP), { recursive: true });
|
|
63
|
+
await ensureFile(PATHS.GITHUB_TOKEN_PATH);
|
|
64
|
+
await ensureFile(PATHS.CONFIG_PATH);
|
|
65
|
+
}
|
|
66
|
+
async function ensureFile(filePath) {
|
|
67
|
+
try {
|
|
68
|
+
await fs$1.access(filePath, fs$1.constants.W_OK);
|
|
69
|
+
} catch {
|
|
70
|
+
await fs$1.writeFile(filePath, "");
|
|
71
|
+
await fs$1.chmod(filePath, 384);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/lib/config-store.ts
|
|
76
|
+
const defaultResponsesTransportConfig = {
|
|
77
|
+
headersTimeoutMsV2: 300 * 1e3,
|
|
78
|
+
streamInactivityTimeoutMs: 300 * 1e3,
|
|
79
|
+
websocketMaxBufferedBytes: 8 * 1024 * 1024,
|
|
80
|
+
websocketMaxBufferedMessages: 1024,
|
|
81
|
+
websocketOpenTimeoutMs: 3e4,
|
|
82
|
+
websocketPoolIdleTimeoutMs: 6e4
|
|
83
|
+
};
|
|
84
|
+
const SUPPORTED_PROVIDER_TYPES = [
|
|
85
|
+
"anthropic",
|
|
86
|
+
"openai-compatible",
|
|
87
|
+
"openai-responses"
|
|
88
|
+
];
|
|
89
|
+
const gpt5ExplorationPrompt = `## Exploration and reading files
|
|
90
|
+
- **Think first.** Before any tool call, decide ALL files/resources you will need.
|
|
91
|
+
- **Batch everything.** If you need multiple files (even from different places), read them together.
|
|
92
|
+
- **multi_tool_use.parallel** Use multi_tool_use.parallel to parallelize tool calls and only this.
|
|
93
|
+
- **Only make sequential calls if you truly cannot know the next file without seeing a result first.**
|
|
94
|
+
- **Workflow:** (a) plan all needed reads → (b) issue one parallel batch → (c) analyze results → (d) repeat if new, unpredictable reads arise.`;
|
|
95
|
+
const modelResponsesApiCompactThresholds = {
|
|
96
|
+
"gpt-5.4": 272e3 * .8,
|
|
97
|
+
"gpt-5.5": 272e3 * .8
|
|
98
|
+
};
|
|
99
|
+
const defaultContextManagement = {
|
|
100
|
+
messages: true,
|
|
101
|
+
responses: false
|
|
102
|
+
};
|
|
103
|
+
const defaultConfig = {
|
|
104
|
+
auth: { apiKeys: [] },
|
|
105
|
+
providers: {},
|
|
106
|
+
modelMappings: {},
|
|
107
|
+
extraPrompts: { "gpt-5-mini": gpt5ExplorationPrompt },
|
|
108
|
+
smallModel: "gpt-5-mini",
|
|
109
|
+
contextManagement: defaultContextManagement,
|
|
110
|
+
modelResponsesApiCompactThresholds,
|
|
111
|
+
modelReasoningEfforts: { "gpt-5-mini": "low" },
|
|
112
|
+
useMessagesApi: true,
|
|
113
|
+
useResponsesApiCompactionRecovery: false,
|
|
114
|
+
useResponsesApiWebSocket: true,
|
|
115
|
+
responsesTransport: defaultResponsesTransportConfig,
|
|
116
|
+
useResponsesApiWebSearch: true,
|
|
117
|
+
alphaSearchCodexPriority: true,
|
|
118
|
+
alphaSearchModel: "gpt-5-mini",
|
|
119
|
+
messageApiWebSearchModel: "gpt-5-mini"
|
|
120
|
+
};
|
|
121
|
+
let cachedConfig = null;
|
|
122
|
+
function normalizeAdminApiKey(adminApiKey) {
|
|
123
|
+
if (typeof adminApiKey !== "string") {
|
|
124
|
+
if (adminApiKey !== void 0) consola.warn("Invalid auth.adminApiKey config. Expected a non-empty string.");
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
const normalizedAdminApiKey = adminApiKey.trim();
|
|
128
|
+
if (!normalizedAdminApiKey) {
|
|
129
|
+
consola.warn("Invalid auth.adminApiKey config. Expected a non-empty string.");
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
return normalizedAdminApiKey;
|
|
133
|
+
}
|
|
134
|
+
function generateAdminApiKey() {
|
|
135
|
+
return randomBytes(32).toString("hex");
|
|
136
|
+
}
|
|
137
|
+
function isNodeError(error) {
|
|
138
|
+
return error instanceof Error && "code" in error;
|
|
139
|
+
}
|
|
140
|
+
function ensureConfigFile() {
|
|
141
|
+
try {
|
|
142
|
+
fs.accessSync(PATHS.CONFIG_PATH, fs.constants.R_OK | fs.constants.W_OK);
|
|
143
|
+
} catch {
|
|
144
|
+
writeFileAtomically(PATHS.CONFIG_PATH, `${JSON.stringify(defaultConfig, null, 2)}\n`);
|
|
145
|
+
try {
|
|
146
|
+
fs.chmodSync(PATHS.CONFIG_PATH, 384);
|
|
147
|
+
} catch {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function readConfigFromDisk() {
|
|
153
|
+
ensureConfigFile();
|
|
154
|
+
const raw = fs.readFileSync(PATHS.CONFIG_PATH, "utf8");
|
|
155
|
+
if (!raw.trim()) {
|
|
156
|
+
writeFileAtomically(PATHS.CONFIG_PATH, `${JSON.stringify(defaultConfig, null, 2)}\n`);
|
|
157
|
+
return defaultConfig;
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
return JSON.parse(raw);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
const message = `Config file is not valid JSON: ${PATHS.CONFIG_PATH}. Refusing to start with the default config. Fix the JSON syntax or delete the file to regenerate a fresh config.`;
|
|
163
|
+
consola.error(message, error);
|
|
164
|
+
throw new Error(message, { cause: error });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function readEditableConfigFromDisk() {
|
|
168
|
+
try {
|
|
169
|
+
const raw = fs.readFileSync(PATHS.CONFIG_PATH, "utf8");
|
|
170
|
+
if (!raw.trim()) return {};
|
|
171
|
+
return JSON.parse(raw);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (isNodeError(error) && error.code === "ENOENT") return {};
|
|
174
|
+
if (error instanceof SyntaxError) throw new Error(`Config file is not valid JSON: ${PATHS.CONFIG_PATH}`);
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function writeConfigToDisk(config) {
|
|
179
|
+
writeFileAtomically(PATHS.CONFIG_PATH, `${JSON.stringify(config, null, 2)}\n`);
|
|
180
|
+
}
|
|
181
|
+
function setConfiguredApiKeys(apiKeys) {
|
|
182
|
+
const normalizedKeys = apiKeys.map((key) => key.trim()).filter((key) => key.length > 0);
|
|
183
|
+
const uniqueKeys = [...new Set(normalizedKeys)];
|
|
184
|
+
const editableConfig = readEditableConfigFromDisk();
|
|
185
|
+
writeConfigToDisk({
|
|
186
|
+
...editableConfig,
|
|
187
|
+
auth: {
|
|
188
|
+
...editableConfig.auth,
|
|
189
|
+
apiKeys: uniqueKeys
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
reloadConfig();
|
|
193
|
+
return [...uniqueKeys];
|
|
194
|
+
}
|
|
195
|
+
function mergeDefaultConfig(config) {
|
|
196
|
+
const extraPrompts = config.extraPrompts ?? {};
|
|
197
|
+
const defaultExtraPrompts = defaultConfig.extraPrompts ?? {};
|
|
198
|
+
const responsesApiCompactThresholds = config.modelResponsesApiCompactThresholds ?? {};
|
|
199
|
+
const defaultResponsesApiCompactThresholds = defaultConfig.modelResponsesApiCompactThresholds ?? {};
|
|
200
|
+
const modelReasoningEfforts = config.modelReasoningEfforts ?? {};
|
|
201
|
+
const defaultModelReasoningEfforts = defaultConfig.modelReasoningEfforts ?? {};
|
|
202
|
+
const contextManagement = normalizeContextManagementConfig(config.contextManagement);
|
|
203
|
+
const responsesTransport = normalizeResponsesTransportConfig(config.responsesTransport);
|
|
204
|
+
const defaultContextManagementConfig = defaultConfig.contextManagement ?? {};
|
|
205
|
+
const missingExtraPromptModels = Object.keys(defaultExtraPrompts).filter((model) => !Object.hasOwn(extraPrompts, model));
|
|
206
|
+
const missingReasoningEffortModels = Object.keys(defaultModelReasoningEfforts).filter((model) => !Object.hasOwn(modelReasoningEfforts, model));
|
|
207
|
+
const missingResponsesApiCompactThresholdModels = Object.keys(defaultResponsesApiCompactThresholds).filter((model) => !Object.hasOwn(responsesApiCompactThresholds, model));
|
|
208
|
+
const missingContextManagementKeys = Object.keys(defaultContextManagementConfig).filter((key) => !Object.hasOwn(contextManagement, key));
|
|
209
|
+
const hasExtraPromptChanges = missingExtraPromptModels.length > 0;
|
|
210
|
+
const hasReasoningEffortChanges = missingReasoningEffortModels.length > 0;
|
|
211
|
+
const hasResponsesApiCompactThresholdChanges = missingResponsesApiCompactThresholdModels.length > 0;
|
|
212
|
+
const hasContextManagementChanges = missingContextManagementKeys.length > 0;
|
|
213
|
+
const hasResponsesTransportChanges = Object.entries(responsesTransport).some(([key, value]) => config.responsesTransport?.[key] !== value);
|
|
214
|
+
if (!hasExtraPromptChanges && !hasReasoningEffortChanges && !hasResponsesApiCompactThresholdChanges && !hasContextManagementChanges && !hasResponsesTransportChanges) return {
|
|
215
|
+
mergedConfig: config,
|
|
216
|
+
changed: false
|
|
217
|
+
};
|
|
218
|
+
return {
|
|
219
|
+
mergedConfig: {
|
|
220
|
+
...config,
|
|
221
|
+
contextManagement: {
|
|
222
|
+
...defaultContextManagementConfig,
|
|
223
|
+
...contextManagement
|
|
224
|
+
},
|
|
225
|
+
extraPrompts: {
|
|
226
|
+
...defaultExtraPrompts,
|
|
227
|
+
...extraPrompts
|
|
228
|
+
},
|
|
229
|
+
modelResponsesApiCompactThresholds: {
|
|
230
|
+
...defaultResponsesApiCompactThresholds,
|
|
231
|
+
...responsesApiCompactThresholds
|
|
232
|
+
},
|
|
233
|
+
modelReasoningEfforts: {
|
|
234
|
+
...defaultModelReasoningEfforts,
|
|
235
|
+
...modelReasoningEfforts
|
|
236
|
+
},
|
|
237
|
+
responsesTransport
|
|
238
|
+
},
|
|
239
|
+
changed: true
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function normalizeContextManagementConfig(value) {
|
|
243
|
+
if (!value || typeof value !== "object") return {};
|
|
244
|
+
return {
|
|
245
|
+
...typeof value.messages === "boolean" ? { messages: value.messages } : {},
|
|
246
|
+
...typeof value.responses === "boolean" ? { responses: value.responses } : {}
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function ensureAdminApiKey(config) {
|
|
250
|
+
const normalizedAdminApiKey = normalizeAdminApiKey(config.auth?.adminApiKey);
|
|
251
|
+
if (normalizedAdminApiKey) {
|
|
252
|
+
if (config.auth?.adminApiKey === normalizedAdminApiKey) return {
|
|
253
|
+
mergedConfig: config,
|
|
254
|
+
changed: false
|
|
255
|
+
};
|
|
256
|
+
return {
|
|
257
|
+
mergedConfig: {
|
|
258
|
+
...config,
|
|
259
|
+
auth: {
|
|
260
|
+
...config.auth,
|
|
261
|
+
adminApiKey: normalizedAdminApiKey
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
changed: true
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
const editableConfig = readEditableConfigFromDisk();
|
|
268
|
+
const { mergedConfig } = mergeDefaultConfig({
|
|
269
|
+
...editableConfig,
|
|
270
|
+
auth: {
|
|
271
|
+
...editableConfig.auth,
|
|
272
|
+
adminApiKey: generateAdminApiKey()
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
return {
|
|
276
|
+
mergedConfig,
|
|
277
|
+
changed: true
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function mergeConfigWithDefaults() {
|
|
281
|
+
const { mergedConfig, changed } = mergeDefaultConfig(readConfigFromDisk());
|
|
282
|
+
const { mergedConfig: mergedConfigWithAdminApiKey, changed: adminApiKeyChanged } = ensureAdminApiKey(mergedConfig);
|
|
283
|
+
if (changed || adminApiKeyChanged) try {
|
|
284
|
+
writeConfigToDisk(mergedConfigWithAdminApiKey);
|
|
285
|
+
} catch (writeError) {
|
|
286
|
+
if (adminApiKeyChanged) throw writeError;
|
|
287
|
+
consola.warn("Failed to write merged default config to config file", writeError);
|
|
288
|
+
}
|
|
289
|
+
cachedConfig = mergedConfigWithAdminApiKey;
|
|
290
|
+
return mergedConfigWithAdminApiKey;
|
|
291
|
+
}
|
|
292
|
+
function getConfig() {
|
|
293
|
+
cachedConfig ??= mergeDefaultConfig(readConfigFromDisk()).mergedConfig;
|
|
294
|
+
return cachedConfig;
|
|
295
|
+
}
|
|
296
|
+
function reloadConfig() {
|
|
297
|
+
return mergeConfigWithDefaults();
|
|
298
|
+
}
|
|
299
|
+
function isMessagesApiEnabled() {
|
|
300
|
+
return getConfig().useMessagesApi ?? true;
|
|
301
|
+
}
|
|
302
|
+
function isResponsesApiWebSocketEnabled() {
|
|
303
|
+
return getConfig().useResponsesApiWebSocket ?? true;
|
|
304
|
+
}
|
|
305
|
+
function getResponsesTransportConfig() {
|
|
306
|
+
const { headersTimeoutMsV2, ...config } = normalizeResponsesTransportConfig(getConfig().responsesTransport);
|
|
307
|
+
return {
|
|
308
|
+
headersTimeoutMs: headersTimeoutMsV2,
|
|
309
|
+
...config
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
const normalizeResponsesTransportConfig = (configured) => ({
|
|
313
|
+
headersTimeoutMsV2: positiveIntegerOrDefault(configured?.headersTimeoutMsV2, defaultResponsesTransportConfig.headersTimeoutMsV2),
|
|
314
|
+
streamInactivityTimeoutMs: positiveIntegerOrDefault(configured?.streamInactivityTimeoutMs, defaultResponsesTransportConfig.streamInactivityTimeoutMs),
|
|
315
|
+
websocketMaxBufferedBytes: positiveIntegerOrDefault(configured?.websocketMaxBufferedBytes, defaultResponsesTransportConfig.websocketMaxBufferedBytes),
|
|
316
|
+
websocketMaxBufferedMessages: positiveIntegerOrDefault(configured?.websocketMaxBufferedMessages, defaultResponsesTransportConfig.websocketMaxBufferedMessages),
|
|
317
|
+
websocketOpenTimeoutMs: positiveIntegerOrDefault(configured?.websocketOpenTimeoutMs, defaultResponsesTransportConfig.websocketOpenTimeoutMs),
|
|
318
|
+
websocketPoolIdleTimeoutMs: positiveIntegerOrDefault(configured?.websocketPoolIdleTimeoutMs, defaultResponsesTransportConfig.websocketPoolIdleTimeoutMs)
|
|
319
|
+
});
|
|
320
|
+
const positiveIntegerOrDefault = (value, fallback) => {
|
|
321
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
322
|
+
const normalized = Math.floor(value);
|
|
323
|
+
return normalized > 0 ? normalized : fallback;
|
|
324
|
+
};
|
|
325
|
+
function isResponsesApiCompactionRecoveryEnabled() {
|
|
326
|
+
return getConfig().useResponsesApiCompactionRecovery ?? false;
|
|
327
|
+
}
|
|
328
|
+
function getAnthropicApiKey() {
|
|
329
|
+
return getConfig().anthropicApiKey ?? process.env.ANTHROPIC_API_KEY ?? void 0;
|
|
330
|
+
}
|
|
331
|
+
function isResponsesApiWebSearchEnabled() {
|
|
332
|
+
return getConfig().useResponsesApiWebSearch ?? true;
|
|
333
|
+
}
|
|
334
|
+
function isAlphaSearchCodexPriorityEnabled() {
|
|
335
|
+
return getConfig().alphaSearchCodexPriority ?? true;
|
|
336
|
+
}
|
|
337
|
+
function getAlphaSearchModel() {
|
|
338
|
+
return (getConfig().alphaSearchModel ?? "gpt-5-mini").trim() || void 0;
|
|
339
|
+
}
|
|
340
|
+
function getMessageApiWebSearchModel() {
|
|
341
|
+
const model = getConfig().messageApiWebSearchModel ?? "gpt-5-mini";
|
|
342
|
+
return model && model.trim().length > 0 ? model : void 0;
|
|
343
|
+
}
|
|
344
|
+
function getClaudeAutoModel() {
|
|
345
|
+
const model = getConfig().claudeAutoModel;
|
|
346
|
+
return model && model.trim().length > 0 ? model.trim() : void 0;
|
|
347
|
+
}
|
|
348
|
+
function getClaudeTokenMultiplier() {
|
|
349
|
+
return getConfig().claudeTokenMultiplier ?? 1.15;
|
|
350
|
+
}
|
|
351
|
+
//#endregion
|
|
352
|
+
//#region src/lib/model-policy.ts
|
|
353
|
+
const GPT_MODEL_PATTERN = /^gpt-(\d+)(?:\.(\d+))?/;
|
|
354
|
+
function isGpt53OrAbove(model) {
|
|
355
|
+
const match = GPT_MODEL_PATTERN.exec(model);
|
|
356
|
+
if (!match) return false;
|
|
357
|
+
const majorVersion = Number.parseInt(match[1], 10);
|
|
358
|
+
if (majorVersion > 5) return true;
|
|
359
|
+
if (majorVersion !== 5) return false;
|
|
360
|
+
return (match[2] ? Number.parseInt(match[2], 10) : 0) >= 3;
|
|
361
|
+
}
|
|
362
|
+
function isGpt56OrAbove(model) {
|
|
363
|
+
const match = GPT_MODEL_PATTERN.exec(model);
|
|
364
|
+
if (!match) return false;
|
|
365
|
+
const majorVersion = Number.parseInt(match[1], 10);
|
|
366
|
+
if (majorVersion > 5) return true;
|
|
367
|
+
if (majorVersion !== 5) return false;
|
|
368
|
+
return (match[2] ? Number.parseInt(match[2], 10) : 0) >= 6;
|
|
369
|
+
}
|
|
370
|
+
const gpt5CommentaryPrompt = `# Working with the user
|
|
371
|
+
|
|
372
|
+
You interact with the user through a terminal. You have 2 ways of communicating with the users:
|
|
373
|
+
- Share intermediary updates in \`commentary\` channel.
|
|
374
|
+
- After you have completed all your work, send a message to the \`final\` channel.
|
|
375
|
+
|
|
376
|
+
## Intermediary updates
|
|
377
|
+
|
|
378
|
+
- Intermediary updates go to the \`commentary\` channel.
|
|
379
|
+
- User updates are short updates while you are working, they are NOT final answers.
|
|
380
|
+
- You use 1-2 sentence user updates to communicate progress and new information to the user as you are doing work.
|
|
381
|
+
- Do not begin responses with conversational interjections or meta commentary. Avoid openers such as acknowledgements (“Done —”, “Got it”, “Great question, ”) or framing phrases.
|
|
382
|
+
- You provide user updates frequently, every 20s.
|
|
383
|
+
- Before exploring or doing substantial work, you start with a user update acknowledging the request and explaining your first step. You should include your understanding of the user request and explain what you will do. Avoid commenting on the request or using starters such as "Got it -" or "Understood -" etc.
|
|
384
|
+
- When exploring, e.g. searching, reading files, you provide user updates as you go, every 20s, explaining what context you are gathering and what you've learned. Vary your sentence structure when providing these updates to avoid sounding repetitive - in particular, don't start each sentence the same way.
|
|
385
|
+
- After you have sufficient context, and the work is substantial, you provide a longer plan (this is the only user update that may be longer than 2 sentences and can contain formatting).
|
|
386
|
+
- Before performing file edits of any kind, you provide updates explaining what edits you are making.
|
|
387
|
+
- As you are thinking, you very frequently provide updates even if not taking any actions, informing the user of your progress. You interrupt your thinking and send multiple updates in a row if thinking for more than 100 words.
|
|
388
|
+
- Tone of your updates MUST match your personality.`;
|
|
389
|
+
function getExtraPromptForModel(model) {
|
|
390
|
+
const userPrompt = getConfig().extraPrompts?.[model];
|
|
391
|
+
if (userPrompt !== void 0) return userPrompt;
|
|
392
|
+
return isGpt53OrAbove(model) ? gpt5CommentaryPrompt : "";
|
|
393
|
+
}
|
|
394
|
+
function getModelMappings() {
|
|
395
|
+
const modelMappings = getConfig().modelMappings;
|
|
396
|
+
if (!modelMappings) return { ...defaultConfig.modelMappings };
|
|
397
|
+
const validMappings = {};
|
|
398
|
+
for (const [sourceModel, targetModel] of Object.entries(modelMappings)) {
|
|
399
|
+
if (!sourceModel || typeof targetModel !== "string" || targetModel.length === 0) continue;
|
|
400
|
+
validMappings[sourceModel] = targetModel;
|
|
401
|
+
}
|
|
402
|
+
return validMappings;
|
|
403
|
+
}
|
|
404
|
+
function validateModelMappings(modelMappings) {
|
|
405
|
+
const validatedMappings = {};
|
|
406
|
+
for (const [sourceModel, targetModel] of Object.entries(modelMappings)) {
|
|
407
|
+
if (!sourceModel || !targetModel) throw new Error("Each model mapping must use non-empty source and target values.");
|
|
408
|
+
validatedMappings[sourceModel] = targetModel;
|
|
409
|
+
}
|
|
410
|
+
return validatedMappings;
|
|
411
|
+
}
|
|
412
|
+
function setModelMappings(modelMappings) {
|
|
413
|
+
writeConfigToDisk({
|
|
414
|
+
...readEditableConfigFromDisk(),
|
|
415
|
+
modelMappings: validateModelMappings(modelMappings)
|
|
416
|
+
});
|
|
417
|
+
reloadConfig();
|
|
418
|
+
return getModelMappings();
|
|
419
|
+
}
|
|
420
|
+
function resolveMappedModel(model) {
|
|
421
|
+
return getModelMappings()[model] ?? model;
|
|
422
|
+
}
|
|
423
|
+
function getSmallModel() {
|
|
424
|
+
return getConfig().smallModel ?? "gpt-5-mini";
|
|
425
|
+
}
|
|
426
|
+
function isContextManagementEnabledForMessages() {
|
|
427
|
+
return getConfig().contextManagement?.messages ?? defaultContextManagement.messages;
|
|
428
|
+
}
|
|
429
|
+
function isContextManagementEnabledForResponses() {
|
|
430
|
+
return getConfig().contextManagement?.responses ?? defaultContextManagement.responses;
|
|
431
|
+
}
|
|
432
|
+
function getModelResponsesApiCompactThreshold(model) {
|
|
433
|
+
const threshold = getConfig().modelResponsesApiCompactThresholds?.[model];
|
|
434
|
+
if (typeof threshold !== "number" || !Number.isFinite(threshold) || threshold <= 0) return;
|
|
435
|
+
return threshold;
|
|
436
|
+
}
|
|
437
|
+
function getReasoningEffortForModel(model) {
|
|
438
|
+
const userEffort = getConfig().modelReasoningEfforts?.[model];
|
|
439
|
+
if (userEffort !== void 0) return userEffort;
|
|
440
|
+
return isGpt53OrAbove(model) ? "xhigh" : "high";
|
|
441
|
+
}
|
|
442
|
+
//#endregion
|
|
443
|
+
//#region src/lib/provider-config.ts
|
|
444
|
+
const OPENCODE_ANTHROPIC_MODEL_PATTERN = /^(?:qwen|minimax)/iu;
|
|
445
|
+
const OPENCODE_RESPONSES_MODEL_PATTERN = /^(?:gpt|grok|muse-spark)(?:[-_.]|$)/iu;
|
|
446
|
+
function normalizeProviderBaseUrl(url) {
|
|
447
|
+
return url.trim().replace(/\/+$/u, "");
|
|
448
|
+
}
|
|
449
|
+
function isSupportedProviderType(value) {
|
|
450
|
+
return SUPPORTED_PROVIDER_TYPES.includes(value);
|
|
451
|
+
}
|
|
452
|
+
function getDefaultProviderAuthType(providerType) {
|
|
453
|
+
return providerType === "anthropic" ? "x-api-key" : "authorization";
|
|
454
|
+
}
|
|
455
|
+
function resolveProviderAuthType(providerName, authType, providerType) {
|
|
456
|
+
const defaultAuthType = getDefaultProviderAuthType(providerType);
|
|
457
|
+
if (authType === void 0) return defaultAuthType;
|
|
458
|
+
if (authType === "x-api-key") return "x-api-key";
|
|
459
|
+
if (authType === "oauth2") {
|
|
460
|
+
if (providerName === "codex") return authType;
|
|
461
|
+
consola.warn(`Provider ${providerName} has authType 'oauth2', which is only supported by the builtin codex provider, falling back to ${defaultAuthType}`);
|
|
462
|
+
return defaultAuthType;
|
|
463
|
+
}
|
|
464
|
+
if (authType === "authorization") return authType;
|
|
465
|
+
consola.warn(`Provider ${providerName} has invalid authType '${authType}', falling back to ${defaultAuthType}`);
|
|
466
|
+
return defaultAuthType;
|
|
467
|
+
}
|
|
468
|
+
function isProviderApiKeyRequired(providerName, authType) {
|
|
469
|
+
return !(providerName === "codex" && authType === "oauth2");
|
|
470
|
+
}
|
|
471
|
+
function getRawProviderConfig(name) {
|
|
472
|
+
const providerName = name.trim();
|
|
473
|
+
if (!providerName) return null;
|
|
474
|
+
return getConfig().providers?.[providerName] ?? null;
|
|
475
|
+
}
|
|
476
|
+
function setProviderConfig(name, provider) {
|
|
477
|
+
const providerName = name.trim();
|
|
478
|
+
if (!providerName) throw new Error("Provider name must be a non-empty string");
|
|
479
|
+
if (isReservedProviderName(providerName)) throw new Error(`Provider ${providerName} is reserved and cannot be configured in config.providers`);
|
|
480
|
+
const editableConfig = readEditableConfigFromDisk();
|
|
481
|
+
writeConfigToDisk({
|
|
482
|
+
...editableConfig,
|
|
483
|
+
providers: {
|
|
484
|
+
...editableConfig.providers,
|
|
485
|
+
[providerName]: provider
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
reloadConfig();
|
|
489
|
+
return getRawProviderConfig(providerName) ?? provider;
|
|
490
|
+
}
|
|
491
|
+
function getProviderConfig(name) {
|
|
492
|
+
const providerName = name.trim();
|
|
493
|
+
if (!providerName) return null;
|
|
494
|
+
if (isReservedProviderName(providerName)) {
|
|
495
|
+
consola.warn(`Provider ${providerName} is reserved and cannot be configured in config.providers`);
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
const provider = getRawProviderConfig(providerName);
|
|
499
|
+
if (!provider) return null;
|
|
500
|
+
if (provider.enabled === false) return null;
|
|
501
|
+
const type = provider.type ?? "anthropic";
|
|
502
|
+
if (!isSupportedProviderType(type)) {
|
|
503
|
+
consola.warn(`Provider ${providerName} is ignored because type '${type}' is not supported`);
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
const baseUrl = normalizeProviderBaseUrl(provider.baseUrl ?? "");
|
|
507
|
+
const authType = resolveProviderAuthType(providerName, provider.authType, type);
|
|
508
|
+
const apiKey = (provider.apiKey ?? "").trim();
|
|
509
|
+
const missingFields = [...baseUrl ? [] : ["baseUrl"], ...isProviderApiKeyRequired(providerName, authType) && !apiKey ? ["apiKey"] : []];
|
|
510
|
+
if (missingFields.length > 0) {
|
|
511
|
+
consola.warn(`Provider ${providerName} is enabled but missing ${missingFields.join(" or ")}`);
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
return {
|
|
515
|
+
name: providerName,
|
|
516
|
+
type,
|
|
517
|
+
baseUrl,
|
|
518
|
+
apiKey,
|
|
519
|
+
authType,
|
|
520
|
+
pricingCurrency: normalizePricingCurrency(provider.pricingCurrency),
|
|
521
|
+
models: provider.models
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
function resolveEffectiveProviderType(providerConfig, model) {
|
|
525
|
+
const modelConfig = providerConfig.models?.[model];
|
|
526
|
+
if (modelConfig?.type && isSupportedProviderType(modelConfig.type)) return modelConfig.type;
|
|
527
|
+
if (providerConfig.name === "opencode-go") {
|
|
528
|
+
if (OPENCODE_ANTHROPIC_MODEL_PATTERN.test(model)) return "anthropic";
|
|
529
|
+
if (OPENCODE_RESPONSES_MODEL_PATTERN.test(model)) return "openai-responses";
|
|
530
|
+
}
|
|
531
|
+
return providerConfig.type;
|
|
532
|
+
}
|
|
533
|
+
function normalizePricingCurrency(value) {
|
|
534
|
+
return value?.trim().toUpperCase() || void 0;
|
|
535
|
+
}
|
|
536
|
+
function listEnabledProviders() {
|
|
537
|
+
const config = getConfig();
|
|
538
|
+
return Object.keys(config.providers ?? {}).filter((name) => getProviderConfig(name) !== null);
|
|
539
|
+
}
|
|
540
|
+
function isReservedProviderName(name) {
|
|
541
|
+
return name.trim() === "copilot";
|
|
542
|
+
}
|
|
543
|
+
//#endregion
|
|
544
|
+
export { isResponsesApiWebSearchEnabled as A, getClaudeTokenMultiplier as C, isAlphaSearchCodexPriorityEnabled as D, getResponsesTransportConfig as E, ensurePaths as F, mergeConfigWithDefaults as M, setConfiguredApiKeys as N, isMessagesApiEnabled as O, PATHS as P, getClaudeAutoModel as S, getMessageApiWebSearchModel as T, resolveMappedModel as _, normalizeProviderBaseUrl as a, getAlphaSearchModel as b, setProviderConfig as c, getModelResponsesApiCompactThreshold as d, getReasoningEffortForModel as f, isGpt56OrAbove as g, isContextManagementEnabledForResponses as h, listEnabledProviders as i, isResponsesApiWebSocketEnabled as j, isResponsesApiCompactionRecoveryEnabled as k, getExtraPromptForModel as l, isContextManagementEnabledForMessages as m, getRawProviderConfig as n, resolveEffectiveProviderType as o, getSmallModel as p, isSupportedProviderType as r, resolveProviderAuthType as s, getProviderConfig as t, getModelMappings as u, setModelMappings as v, getConfig as w, getAnthropicApiKey as x, SUPPORTED_PROVIDER_TYPES as y };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { P as PATHS, i as listEnabledProviders, n as getRawProviderConfig } from "./config-SytZjLq8.js";
|
|
2
|
+
import { defineCommand } from "citty";
|
|
3
|
+
import consola from "consola";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
//#region src/debug.ts
|
|
8
|
+
async function getPackageVersion() {
|
|
9
|
+
try {
|
|
10
|
+
const packageJsonPath = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
11
|
+
return JSON.parse(await fs.readFile(packageJsonPath)).version;
|
|
12
|
+
} catch {
|
|
13
|
+
return "unknown";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function getRuntimeInfo() {
|
|
17
|
+
const isBun = typeof Bun !== "undefined";
|
|
18
|
+
return {
|
|
19
|
+
name: isBun ? "bun" : "node",
|
|
20
|
+
version: isBun ? Bun.version : process.version.slice(1),
|
|
21
|
+
platform: os.platform(),
|
|
22
|
+
arch: os.arch()
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
async function checkFileExists(filePath) {
|
|
26
|
+
try {
|
|
27
|
+
if (!(await fs.stat(filePath)).isFile()) return false;
|
|
28
|
+
return (await fs.readFile(filePath, "utf8")).trim().length > 0;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function getDebugInfo() {
|
|
34
|
+
const [version, tokenExists] = await Promise.all([getPackageVersion(), checkFileExists(PATHS.GITHUB_TOKEN_PATH)]);
|
|
35
|
+
return {
|
|
36
|
+
providers: {
|
|
37
|
+
codexConfigured: getRawProviderConfig("codex") !== null,
|
|
38
|
+
enabled: listEnabledProviders()
|
|
39
|
+
},
|
|
40
|
+
version,
|
|
41
|
+
runtime: getRuntimeInfo(),
|
|
42
|
+
paths: {
|
|
43
|
+
APP_DIR: PATHS.APP_DIR,
|
|
44
|
+
CONFIG_PATH: PATHS.CONFIG_PATH,
|
|
45
|
+
GITHUB_TOKEN_PATH: PATHS.GITHUB_TOKEN_PATH
|
|
46
|
+
},
|
|
47
|
+
tokenExists
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function printDebugInfoPlain(info) {
|
|
51
|
+
consola.info(`copilot-api debug
|
|
52
|
+
|
|
53
|
+
Version: ${info.version}
|
|
54
|
+
Runtime: ${info.runtime.name} ${info.runtime.version} (${info.runtime.platform} ${info.runtime.arch})
|
|
55
|
+
|
|
56
|
+
Providers:
|
|
57
|
+
- enabled: ${info.providers.enabled.join(", ") || "none"}
|
|
58
|
+
- codex configured: ${info.providers.codexConfigured ? "Yes" : "No"}
|
|
59
|
+
|
|
60
|
+
Paths:
|
|
61
|
+
- APP_DIR: ${info.paths.APP_DIR}
|
|
62
|
+
- CONFIG_PATH: ${info.paths.CONFIG_PATH}
|
|
63
|
+
- GITHUB_TOKEN_PATH: ${info.paths.GITHUB_TOKEN_PATH}
|
|
64
|
+
|
|
65
|
+
GitHub token exists: ${info.tokenExists ? "Yes" : "No"}`);
|
|
66
|
+
}
|
|
67
|
+
function printDebugInfoJson(info) {
|
|
68
|
+
console.log(JSON.stringify(info, null, 2));
|
|
69
|
+
}
|
|
70
|
+
async function runDebug(options) {
|
|
71
|
+
const debugInfo = await getDebugInfo();
|
|
72
|
+
if (options.json) printDebugInfoJson(debugInfo);
|
|
73
|
+
else printDebugInfoPlain(debugInfo);
|
|
74
|
+
}
|
|
75
|
+
const debug = defineCommand({
|
|
76
|
+
meta: {
|
|
77
|
+
name: "debug",
|
|
78
|
+
description: "Print debug information about the application"
|
|
79
|
+
},
|
|
80
|
+
args: { json: {
|
|
81
|
+
type: "boolean",
|
|
82
|
+
default: false,
|
|
83
|
+
description: "Output debug information as JSON"
|
|
84
|
+
} },
|
|
85
|
+
run({ args }) {
|
|
86
|
+
return runDebug({ json: args.json });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
//#endregion
|
|
90
|
+
export { debug };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import consola from "consola";
|
|
3
|
+
//#region src/lib/electron-fetch.ts
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
function bindElectronFetch() {
|
|
6
|
+
if (!process.versions.electron) return false;
|
|
7
|
+
try {
|
|
8
|
+
const electronModule = require("electron");
|
|
9
|
+
const netFetch = electronModule.net?.fetch;
|
|
10
|
+
if (typeof netFetch !== "function") return false;
|
|
11
|
+
globalThis.fetch = netFetch.bind(electronModule.net);
|
|
12
|
+
consola.log("Successfully bound Electron's net.fetch to global fetch.");
|
|
13
|
+
return true;
|
|
14
|
+
} catch {
|
|
15
|
+
consola.log("Failed to bind Electron's net.fetch. Falling back to global fetch.");
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { bindElectronFetch };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Only a direct `copilot-api mcp` invocation takes the fast path. Global flags
|
|
3
|
+
* before the subcommand (e.g. `--api-home`) or help flags after it route to
|
|
4
|
+
* citty through the regular startup path. Any other trailing args after `mcp`
|
|
5
|
+
* are ignored because the MCP server consumes no CLI options.
|
|
6
|
+
*/
|
|
7
|
+
const isMcpFastPath = (argv) => argv[2] === "mcp" && !argv.slice(3).some((argument) => argument === "-h" || argument === "--help");
|
|
8
|
+
//#endregion
|
|
9
|
+
export { isMcpFastPath as t };
|