@ashsec/copilot-api 0.7.0 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +667 -37
- package/dist/main.js.map +1 -1
- package/package.json +68 -68
package/dist/main.js
CHANGED
|
@@ -15,17 +15,18 @@ import { execSync } from "node:child_process";
|
|
|
15
15
|
import process$1 from "node:process";
|
|
16
16
|
import { Hono } from "hono";
|
|
17
17
|
import { cors } from "hono/cors";
|
|
18
|
-
import { logger } from "hono/logger";
|
|
19
18
|
import { streamSSE } from "hono/streaming";
|
|
20
19
|
|
|
21
20
|
//#region src/lib/paths.ts
|
|
22
21
|
const APP_DIR = path.join(os.homedir(), ".local", "share", "copilot-api");
|
|
23
22
|
const GITHUB_TOKEN_PATH = path.join(APP_DIR, "github_token");
|
|
24
23
|
const AZURE_OPENAI_CONFIG_PATH = path.join(APP_DIR, "azure_openai_config");
|
|
24
|
+
const REPLACEMENTS_CONFIG_PATH = path.join(APP_DIR, "replacements.json");
|
|
25
25
|
const PATHS = {
|
|
26
26
|
APP_DIR,
|
|
27
27
|
GITHUB_TOKEN_PATH,
|
|
28
|
-
AZURE_OPENAI_CONFIG_PATH
|
|
28
|
+
AZURE_OPENAI_CONFIG_PATH,
|
|
29
|
+
REPLACEMENTS_CONFIG_PATH
|
|
29
30
|
};
|
|
30
31
|
async function ensurePaths() {
|
|
31
32
|
await fs.mkdir(PATHS.APP_DIR, { recursive: true });
|
|
@@ -164,15 +165,15 @@ async function loadAzureOpenAIConfig() {
|
|
|
164
165
|
const content = await fs.readFile(PATHS.AZURE_OPENAI_CONFIG_PATH, "utf8");
|
|
165
166
|
if (!content.trim()) return null;
|
|
166
167
|
const decoded = Buffer.from(content.trim(), "base64").toString("utf8");
|
|
167
|
-
const config = JSON.parse(decoded);
|
|
168
|
-
if (!config.endpoint || !config.apiKey) return null;
|
|
169
|
-
return config;
|
|
168
|
+
const config$1 = JSON.parse(decoded);
|
|
169
|
+
if (!config$1.endpoint || !config$1.apiKey) return null;
|
|
170
|
+
return config$1;
|
|
170
171
|
} catch {
|
|
171
172
|
return null;
|
|
172
173
|
}
|
|
173
174
|
}
|
|
174
|
-
async function saveAzureOpenAIConfig(config) {
|
|
175
|
-
const encoded = Buffer.from(JSON.stringify(config)).toString("base64");
|
|
175
|
+
async function saveAzureOpenAIConfig(config$1) {
|
|
176
|
+
const encoded = Buffer.from(JSON.stringify(config$1)).toString("base64");
|
|
176
177
|
await fs.writeFile(PATHS.AZURE_OPENAI_CONFIG_PATH, encoded, "utf8");
|
|
177
178
|
await fs.chmod(PATHS.AZURE_OPENAI_CONFIG_PATH, 384);
|
|
178
179
|
consola.success("Azure OpenAI configuration saved");
|
|
@@ -192,12 +193,12 @@ async function promptAzureOpenAISetup() {
|
|
|
192
193
|
consola.warn("No API key provided, skipping Azure OpenAI setup");
|
|
193
194
|
return null;
|
|
194
195
|
}
|
|
195
|
-
const config = {
|
|
196
|
+
const config$1 = {
|
|
196
197
|
endpoint: endpoint.trim().replace(/\/$/, ""),
|
|
197
198
|
apiKey: apiKey.trim()
|
|
198
199
|
};
|
|
199
|
-
await saveAzureOpenAIConfig(config);
|
|
200
|
-
return config;
|
|
200
|
+
await saveAzureOpenAIConfig(config$1);
|
|
201
|
+
return config$1;
|
|
201
202
|
}
|
|
202
203
|
function isAzureOpenAIModel(modelId) {
|
|
203
204
|
return modelId.startsWith(AZURE_OPENAI_MODEL_PREFIX);
|
|
@@ -206,10 +207,74 @@ function getAzureDeploymentName(modelId) {
|
|
|
206
207
|
return modelId.slice(13);
|
|
207
208
|
}
|
|
208
209
|
|
|
210
|
+
//#endregion
|
|
211
|
+
//#region src/lib/retry-fetch.ts
|
|
212
|
+
const DEFAULT_MAX_RETRIES = 3;
|
|
213
|
+
const DEFAULT_BASE_DELAY_MS = 1e3;
|
|
214
|
+
/**
|
|
215
|
+
* Check if an error is retryable (transient network error)
|
|
216
|
+
*/
|
|
217
|
+
function isRetryableError(error) {
|
|
218
|
+
if (!(error instanceof Error)) return false;
|
|
219
|
+
const message = error.message.toLowerCase();
|
|
220
|
+
const causeMessage = error.cause instanceof Error ? error.cause.message.toLowerCase() : "";
|
|
221
|
+
return [
|
|
222
|
+
"fetch failed",
|
|
223
|
+
"other side closed",
|
|
224
|
+
"connection reset",
|
|
225
|
+
"econnreset",
|
|
226
|
+
"socket hang up",
|
|
227
|
+
"etimedout",
|
|
228
|
+
"econnrefused",
|
|
229
|
+
"network error",
|
|
230
|
+
"aborted",
|
|
231
|
+
"timeout"
|
|
232
|
+
].some((pattern) => message.includes(pattern) || causeMessage.includes(pattern));
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Check if an HTTP response status is retryable
|
|
236
|
+
*/
|
|
237
|
+
function isRetryableStatus(status) {
|
|
238
|
+
return status === 408 || status === 429 || status >= 500 && status <= 599;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Fetch with automatic retry on transient failures
|
|
242
|
+
*/
|
|
243
|
+
async function fetchWithRetry(input, init, options = {}) {
|
|
244
|
+
const { maxRetries = DEFAULT_MAX_RETRIES, baseDelayMs = DEFAULT_BASE_DELAY_MS } = options;
|
|
245
|
+
let lastError;
|
|
246
|
+
let lastResponse;
|
|
247
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) try {
|
|
248
|
+
const headers = new Headers(init?.headers);
|
|
249
|
+
headers.set("Connection", "close");
|
|
250
|
+
const response = await fetch(input, {
|
|
251
|
+
...init,
|
|
252
|
+
headers,
|
|
253
|
+
keepalive: false
|
|
254
|
+
});
|
|
255
|
+
if (isRetryableStatus(response.status) && attempt < maxRetries) {
|
|
256
|
+
lastResponse = response;
|
|
257
|
+
const delayMs = baseDelayMs * 2 ** attempt;
|
|
258
|
+
consola.warn(`HTTP ${response.status} (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${delayMs}ms`);
|
|
259
|
+
await sleep(delayMs);
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
return response;
|
|
263
|
+
} catch (error) {
|
|
264
|
+
lastError = error;
|
|
265
|
+
if (!isRetryableError(error) || attempt === maxRetries) throw error;
|
|
266
|
+
const delayMs = baseDelayMs * 2 ** attempt;
|
|
267
|
+
consola.warn(`Fetch failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${delayMs}ms:`, lastError.message);
|
|
268
|
+
await sleep(delayMs);
|
|
269
|
+
}
|
|
270
|
+
if (lastResponse) return lastResponse;
|
|
271
|
+
throw lastError;
|
|
272
|
+
}
|
|
273
|
+
|
|
209
274
|
//#endregion
|
|
210
275
|
//#region src/services/azure-openai/create-chat-completions.ts
|
|
211
276
|
const AZURE_API_VERSION = "2024-10-21";
|
|
212
|
-
async function createAzureOpenAIChatCompletions(config, payload) {
|
|
277
|
+
async function createAzureOpenAIChatCompletions(config$1, payload) {
|
|
213
278
|
const deploymentName = getAzureDeploymentName(payload.model);
|
|
214
279
|
const { max_tokens,...restPayload } = payload;
|
|
215
280
|
const azurePayload = {
|
|
@@ -217,10 +282,10 @@ async function createAzureOpenAIChatCompletions(config, payload) {
|
|
|
217
282
|
model: deploymentName,
|
|
218
283
|
...max_tokens != null && { max_completion_tokens: max_tokens }
|
|
219
284
|
};
|
|
220
|
-
const response = await
|
|
285
|
+
const response = await fetchWithRetry(`${config$1.endpoint}/openai/deployments/${deploymentName}/chat/completions?api-version=${AZURE_API_VERSION}`, {
|
|
221
286
|
method: "POST",
|
|
222
287
|
headers: {
|
|
223
|
-
"api-key": config.apiKey,
|
|
288
|
+
"api-key": config$1.apiKey,
|
|
224
289
|
"Content-Type": "application/json"
|
|
225
290
|
},
|
|
226
291
|
body: JSON.stringify(azurePayload)
|
|
@@ -236,10 +301,10 @@ async function createAzureOpenAIChatCompletions(config, payload) {
|
|
|
236
301
|
//#endregion
|
|
237
302
|
//#region src/services/azure-openai/get-models.ts
|
|
238
303
|
const AZURE_DEPLOYMENTS_API_VERSION = "2022-12-01";
|
|
239
|
-
async function getAzureOpenAIDeployments(config) {
|
|
304
|
+
async function getAzureOpenAIDeployments(config$1) {
|
|
240
305
|
try {
|
|
241
|
-
const response = await
|
|
242
|
-
"api-key": config.apiKey,
|
|
306
|
+
const response = await fetchWithRetry(`${config$1.endpoint}/openai/deployments?api-version=${AZURE_DEPLOYMENTS_API_VERSION}`, { headers: {
|
|
307
|
+
"api-key": config$1.apiKey,
|
|
243
308
|
"Content-Type": "application/json"
|
|
244
309
|
} });
|
|
245
310
|
if (!response.ok) {
|
|
@@ -265,8 +330,20 @@ async function getAzureOpenAIDeployments(config) {
|
|
|
265
330
|
//#endregion
|
|
266
331
|
//#region src/services/copilot/get-models.ts
|
|
267
332
|
const getModels = async () => {
|
|
268
|
-
const
|
|
269
|
-
|
|
333
|
+
const url = `${copilotBaseUrl(state)}/models`;
|
|
334
|
+
const response = await fetchWithRetry(url, { headers: copilotHeaders(state) });
|
|
335
|
+
if (!response.ok) {
|
|
336
|
+
const errorBody = await response.text();
|
|
337
|
+
let errorDetails;
|
|
338
|
+
try {
|
|
339
|
+
const parsed = JSON.parse(errorBody);
|
|
340
|
+
errorDetails = JSON.stringify(parsed, null, 2);
|
|
341
|
+
} catch {
|
|
342
|
+
errorDetails = errorBody || "(empty response)";
|
|
343
|
+
}
|
|
344
|
+
consola.error(`Failed to get models from ${url}\nStatus: ${response.status} ${response.statusText}\nResponse: ${errorDetails}`);
|
|
345
|
+
throw new HTTPError(`Failed to get models: ${response.status} ${response.statusText}`, response);
|
|
346
|
+
}
|
|
270
347
|
return await response.json();
|
|
271
348
|
};
|
|
272
349
|
|
|
@@ -297,7 +374,16 @@ const sleep = (ms) => new Promise((resolve) => {
|
|
|
297
374
|
});
|
|
298
375
|
const isNullish = (value) => value === null || value === void 0;
|
|
299
376
|
async function cacheModels() {
|
|
300
|
-
|
|
377
|
+
try {
|
|
378
|
+
state.models = await getModels();
|
|
379
|
+
} catch (error) {
|
|
380
|
+
consola.error("Failed to fetch and cache models. This could be due to:");
|
|
381
|
+
consola.error(" - Invalid or expired Copilot token");
|
|
382
|
+
consola.error(" - Network connectivity issues");
|
|
383
|
+
consola.error(" - GitHub Copilot service unavailable");
|
|
384
|
+
consola.error(" - Account type mismatch (try --account-type=individual or --account-type=business)");
|
|
385
|
+
throw error;
|
|
386
|
+
}
|
|
301
387
|
}
|
|
302
388
|
const cacheVSCodeVersion = async () => {
|
|
303
389
|
const response = await getVSCodeVersion();
|
|
@@ -305,16 +391,16 @@ const cacheVSCodeVersion = async () => {
|
|
|
305
391
|
consola.info(`Using VSCode version: ${response}`);
|
|
306
392
|
};
|
|
307
393
|
async function setupAzureOpenAI() {
|
|
308
|
-
let config = await loadAzureOpenAIConfig();
|
|
309
|
-
if (!config) config = await promptAzureOpenAISetup();
|
|
310
|
-
if (!config) {
|
|
394
|
+
let config$1 = await loadAzureOpenAIConfig();
|
|
395
|
+
if (!config$1) config$1 = await promptAzureOpenAISetup();
|
|
396
|
+
if (!config$1) {
|
|
311
397
|
consola.info("Azure OpenAI not configured");
|
|
312
398
|
return;
|
|
313
399
|
}
|
|
314
|
-
state.azureOpenAIConfig = config;
|
|
400
|
+
state.azureOpenAIConfig = config$1;
|
|
315
401
|
consola.info("Azure OpenAI configuration loaded");
|
|
316
402
|
try {
|
|
317
|
-
const deployments = await getAzureOpenAIDeployments(config);
|
|
403
|
+
const deployments = await getAzureOpenAIDeployments(config$1);
|
|
318
404
|
state.azureOpenAIDeployments = deployments;
|
|
319
405
|
if (deployments.length > 0) consola.info(`Loaded ${deployments.length} Azure OpenAI deployment(s):\n${deployments.map((d) => `- ${d.id} (${d.model})`).join("\n")}`);
|
|
320
406
|
else consola.warn("No Azure OpenAI deployments found");
|
|
@@ -488,6 +574,377 @@ const checkUsage = defineCommand({
|
|
|
488
574
|
}
|
|
489
575
|
});
|
|
490
576
|
|
|
577
|
+
//#endregion
|
|
578
|
+
//#region src/lib/auto-replace.ts
|
|
579
|
+
const SYSTEM_REPLACEMENTS = [{
|
|
580
|
+
id: "system-anthropic-billing",
|
|
581
|
+
pattern: "x-anthropic-billing-header:[^\n]*\n?",
|
|
582
|
+
replacement: "",
|
|
583
|
+
isRegex: true,
|
|
584
|
+
enabled: true,
|
|
585
|
+
isSystem: true
|
|
586
|
+
}];
|
|
587
|
+
let userReplacements = [];
|
|
588
|
+
let isLoaded = false;
|
|
589
|
+
/**
|
|
590
|
+
* Load user replacements from disk
|
|
591
|
+
*/
|
|
592
|
+
async function loadReplacements() {
|
|
593
|
+
try {
|
|
594
|
+
const data = await fs.readFile(PATHS.REPLACEMENTS_CONFIG_PATH);
|
|
595
|
+
userReplacements = JSON.parse(data).filter((r) => !r.isSystem);
|
|
596
|
+
isLoaded = true;
|
|
597
|
+
consola.debug(`Loaded ${userReplacements.length} user replacement rules`);
|
|
598
|
+
} catch {
|
|
599
|
+
userReplacements = [];
|
|
600
|
+
isLoaded = true;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Save user replacements to disk
|
|
605
|
+
*/
|
|
606
|
+
async function saveReplacements() {
|
|
607
|
+
try {
|
|
608
|
+
await fs.writeFile(PATHS.REPLACEMENTS_CONFIG_PATH, JSON.stringify(userReplacements, null, 2), "utf8");
|
|
609
|
+
consola.debug(`Saved ${userReplacements.length} user replacement rules`);
|
|
610
|
+
} catch (error) {
|
|
611
|
+
consola.error("Failed to save replacement rules:", error);
|
|
612
|
+
throw error;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Ensure replacements are loaded before accessing
|
|
617
|
+
*/
|
|
618
|
+
async function ensureLoaded() {
|
|
619
|
+
if (!isLoaded) await loadReplacements();
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Get all replacement rules (system + user)
|
|
623
|
+
*/
|
|
624
|
+
async function getAllReplacements() {
|
|
625
|
+
await ensureLoaded();
|
|
626
|
+
return [...SYSTEM_REPLACEMENTS, ...userReplacements];
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Get only user-configurable replacements
|
|
630
|
+
*/
|
|
631
|
+
async function getUserReplacements() {
|
|
632
|
+
await ensureLoaded();
|
|
633
|
+
return userReplacements;
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Add a new user replacement rule
|
|
637
|
+
*/
|
|
638
|
+
async function addReplacement(pattern, replacement, isRegex = false) {
|
|
639
|
+
await ensureLoaded();
|
|
640
|
+
const rule = {
|
|
641
|
+
id: `user-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
642
|
+
pattern,
|
|
643
|
+
replacement,
|
|
644
|
+
isRegex,
|
|
645
|
+
enabled: true,
|
|
646
|
+
isSystem: false
|
|
647
|
+
};
|
|
648
|
+
userReplacements.push(rule);
|
|
649
|
+
await saveReplacements();
|
|
650
|
+
consola.info(`Added replacement rule: "${pattern}" -> "${replacement}"`);
|
|
651
|
+
return rule;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Remove a user replacement rule by ID
|
|
655
|
+
*/
|
|
656
|
+
async function removeReplacement(id) {
|
|
657
|
+
await ensureLoaded();
|
|
658
|
+
const rule = userReplacements.find((r) => r.id === id);
|
|
659
|
+
if (!rule) return false;
|
|
660
|
+
if (rule.isSystem) {
|
|
661
|
+
consola.warn("Cannot remove system replacement rule");
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
userReplacements = userReplacements.filter((r) => r.id !== id);
|
|
665
|
+
await saveReplacements();
|
|
666
|
+
consola.info(`Removed replacement rule: ${id}`);
|
|
667
|
+
return true;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Toggle a replacement rule on/off
|
|
671
|
+
*/
|
|
672
|
+
async function toggleReplacement(id) {
|
|
673
|
+
await ensureLoaded();
|
|
674
|
+
const userRule = userReplacements.find((r) => r.id === id);
|
|
675
|
+
if (userRule) {
|
|
676
|
+
userRule.enabled = !userRule.enabled;
|
|
677
|
+
await saveReplacements();
|
|
678
|
+
consola.info(`Toggled replacement rule ${id}: ${userRule.enabled ? "enabled" : "disabled"}`);
|
|
679
|
+
return userRule;
|
|
680
|
+
}
|
|
681
|
+
if (SYSTEM_REPLACEMENTS.find((r) => r.id === id)) {
|
|
682
|
+
consola.warn("Cannot toggle system replacement rule");
|
|
683
|
+
return null;
|
|
684
|
+
}
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Clear all user replacements
|
|
689
|
+
*/
|
|
690
|
+
async function clearUserReplacements() {
|
|
691
|
+
userReplacements = [];
|
|
692
|
+
await saveReplacements();
|
|
693
|
+
consola.info("Cleared all user replacement rules");
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Apply a single replacement rule to text
|
|
697
|
+
*/
|
|
698
|
+
function applyRule(text, rule) {
|
|
699
|
+
if (!rule.enabled) return text;
|
|
700
|
+
if (rule.isRegex) try {
|
|
701
|
+
const regex = new RegExp(rule.pattern, "g");
|
|
702
|
+
return text.replace(regex, rule.replacement);
|
|
703
|
+
} catch {
|
|
704
|
+
consola.warn(`Invalid regex pattern in rule ${rule.id}: ${rule.pattern}`);
|
|
705
|
+
return text;
|
|
706
|
+
}
|
|
707
|
+
return text.split(rule.pattern).join(rule.replacement);
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Apply all replacement rules to text
|
|
711
|
+
*/
|
|
712
|
+
async function applyReplacements(text) {
|
|
713
|
+
let result = text;
|
|
714
|
+
const allRules = await getAllReplacements();
|
|
715
|
+
for (const rule of allRules) {
|
|
716
|
+
const before = result;
|
|
717
|
+
result = applyRule(result, rule);
|
|
718
|
+
if (before !== result) consola.debug(`Applied replacement rule: ${rule.id}`);
|
|
719
|
+
}
|
|
720
|
+
return result;
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Apply replacements to a chat completions payload
|
|
724
|
+
* This modifies message content in place
|
|
725
|
+
*/
|
|
726
|
+
async function applyReplacementsToPayload(payload) {
|
|
727
|
+
const processedMessages = await Promise.all(payload.messages.map(async (message) => {
|
|
728
|
+
if (typeof message.content === "string") return {
|
|
729
|
+
...message,
|
|
730
|
+
content: await applyReplacements(message.content)
|
|
731
|
+
};
|
|
732
|
+
if (Array.isArray(message.content)) return {
|
|
733
|
+
...message,
|
|
734
|
+
content: await Promise.all(message.content.map(async (part) => {
|
|
735
|
+
if (typeof part === "object" && part.type === "text" && part.text) return {
|
|
736
|
+
...part,
|
|
737
|
+
text: await applyReplacements(part.text)
|
|
738
|
+
};
|
|
739
|
+
return part;
|
|
740
|
+
}))
|
|
741
|
+
};
|
|
742
|
+
return message;
|
|
743
|
+
}));
|
|
744
|
+
return {
|
|
745
|
+
...payload,
|
|
746
|
+
messages: processedMessages
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
//#endregion
|
|
751
|
+
//#region src/config.ts
|
|
752
|
+
function formatRule(rule, index) {
|
|
753
|
+
const status = rule.enabled ? "✓" : "✗";
|
|
754
|
+
const type = rule.isRegex ? "regex" : "string";
|
|
755
|
+
const system = rule.isSystem ? " [system]" : "";
|
|
756
|
+
const replacement = rule.replacement || "(empty)";
|
|
757
|
+
return `${index + 1}. [${status}] (${type})${system} "${rule.pattern}" → "${replacement}"`;
|
|
758
|
+
}
|
|
759
|
+
async function listReplacements() {
|
|
760
|
+
const all = await getAllReplacements();
|
|
761
|
+
if (all.length === 0) {
|
|
762
|
+
consola.info("No replacement rules configured.");
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
consola.info("\n📋 Replacement Rules:\n");
|
|
766
|
+
for (const [i, element] of all.entries()) console.log(formatRule(element, i));
|
|
767
|
+
console.log();
|
|
768
|
+
}
|
|
769
|
+
async function addNewReplacement() {
|
|
770
|
+
const matchType = await consola.prompt("Match type:", {
|
|
771
|
+
type: "select",
|
|
772
|
+
options: [{
|
|
773
|
+
label: "String (exact match)",
|
|
774
|
+
value: "string"
|
|
775
|
+
}, {
|
|
776
|
+
label: "Regex (regular expression)",
|
|
777
|
+
value: "regex"
|
|
778
|
+
}]
|
|
779
|
+
});
|
|
780
|
+
if (typeof matchType === "symbol") {
|
|
781
|
+
consola.info("Cancelled.");
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
const pattern = await consola.prompt("Pattern to match:", { type: "text" });
|
|
785
|
+
if (typeof pattern === "symbol" || !pattern) {
|
|
786
|
+
consola.info("Cancelled.");
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
if (matchType === "regex") try {
|
|
790
|
+
new RegExp(pattern);
|
|
791
|
+
} catch {
|
|
792
|
+
consola.error(`Invalid regex pattern: ${pattern}`);
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
const replacement = await consola.prompt("Replacement text (leave empty to delete matches):", {
|
|
796
|
+
type: "text",
|
|
797
|
+
default: ""
|
|
798
|
+
});
|
|
799
|
+
if (typeof replacement === "symbol") {
|
|
800
|
+
consola.info("Cancelled.");
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
const rule = await addReplacement(pattern, replacement, matchType === "regex");
|
|
804
|
+
consola.success(`Added rule: ${rule.id}`);
|
|
805
|
+
}
|
|
806
|
+
async function removeExistingReplacement() {
|
|
807
|
+
const userRules = await getUserReplacements();
|
|
808
|
+
if (userRules.length === 0) {
|
|
809
|
+
consola.info("No user rules to remove.");
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
const options = userRules.map((rule, i) => ({
|
|
813
|
+
label: formatRule(rule, i),
|
|
814
|
+
value: rule.id
|
|
815
|
+
}));
|
|
816
|
+
const selected = await consola.prompt("Select rule to remove:", {
|
|
817
|
+
type: "select",
|
|
818
|
+
options
|
|
819
|
+
});
|
|
820
|
+
if (typeof selected === "symbol") {
|
|
821
|
+
consola.info("Cancelled.");
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
if (await removeReplacement(selected)) consola.success("Rule removed.");
|
|
825
|
+
else consola.error("Failed to remove rule.");
|
|
826
|
+
}
|
|
827
|
+
async function toggleExistingReplacement() {
|
|
828
|
+
const userRules = await getUserReplacements();
|
|
829
|
+
if (userRules.length === 0) {
|
|
830
|
+
consola.info("No user rules to toggle.");
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
const options = userRules.map((rule$1, i) => ({
|
|
834
|
+
label: formatRule(rule$1, i),
|
|
835
|
+
value: rule$1.id
|
|
836
|
+
}));
|
|
837
|
+
const selected = await consola.prompt("Select rule to toggle:", {
|
|
838
|
+
type: "select",
|
|
839
|
+
options
|
|
840
|
+
});
|
|
841
|
+
if (typeof selected === "symbol") {
|
|
842
|
+
consola.info("Cancelled.");
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
const rule = await toggleReplacement(selected);
|
|
846
|
+
if (rule) consola.success(`Rule ${rule.enabled ? "enabled" : "disabled"}.`);
|
|
847
|
+
else consola.error("Failed to toggle rule.");
|
|
848
|
+
}
|
|
849
|
+
async function testReplacements() {
|
|
850
|
+
const testText = await consola.prompt("Enter text to test replacements:", { type: "text" });
|
|
851
|
+
if (typeof testText === "symbol" || !testText) {
|
|
852
|
+
consola.info("Cancelled.");
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
const result = await applyReplacements(testText);
|
|
856
|
+
consola.info("\n📝 Original:");
|
|
857
|
+
console.log(testText);
|
|
858
|
+
consola.info("\n✨ After replacements:");
|
|
859
|
+
console.log(result);
|
|
860
|
+
console.log();
|
|
861
|
+
}
|
|
862
|
+
async function clearAllReplacements() {
|
|
863
|
+
if (await consola.prompt("Are you sure you want to clear all user replacements?", {
|
|
864
|
+
type: "confirm",
|
|
865
|
+
initial: false
|
|
866
|
+
})) {
|
|
867
|
+
await clearUserReplacements();
|
|
868
|
+
consola.success("All user replacements cleared.");
|
|
869
|
+
} else consola.info("Cancelled.");
|
|
870
|
+
}
|
|
871
|
+
async function mainMenu() {
|
|
872
|
+
consola.info(`\n🔧 Copilot API - Replacement Configuration`);
|
|
873
|
+
consola.info(`Config file: ${PATHS.REPLACEMENTS_CONFIG_PATH}\n`);
|
|
874
|
+
let running = true;
|
|
875
|
+
while (running) {
|
|
876
|
+
const action = await consola.prompt("What would you like to do?", {
|
|
877
|
+
type: "select",
|
|
878
|
+
options: [
|
|
879
|
+
{
|
|
880
|
+
label: "📋 List all rules",
|
|
881
|
+
value: "list"
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
label: "➕ Add new rule",
|
|
885
|
+
value: "add"
|
|
886
|
+
},
|
|
887
|
+
{
|
|
888
|
+
label: "➖ Remove rule",
|
|
889
|
+
value: "remove"
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
label: "🔄 Toggle rule on/off",
|
|
893
|
+
value: "toggle"
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
label: "🧪 Test replacements",
|
|
897
|
+
value: "test"
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
label: "🗑️ Clear all user rules",
|
|
901
|
+
value: "clear"
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
label: "🚪 Exit",
|
|
905
|
+
value: "exit"
|
|
906
|
+
}
|
|
907
|
+
]
|
|
908
|
+
});
|
|
909
|
+
if (typeof action === "symbol") break;
|
|
910
|
+
switch (action) {
|
|
911
|
+
case "list":
|
|
912
|
+
await listReplacements();
|
|
913
|
+
break;
|
|
914
|
+
case "add":
|
|
915
|
+
await addNewReplacement();
|
|
916
|
+
break;
|
|
917
|
+
case "remove":
|
|
918
|
+
await removeExistingReplacement();
|
|
919
|
+
break;
|
|
920
|
+
case "toggle":
|
|
921
|
+
await toggleExistingReplacement();
|
|
922
|
+
break;
|
|
923
|
+
case "test":
|
|
924
|
+
await testReplacements();
|
|
925
|
+
break;
|
|
926
|
+
case "clear":
|
|
927
|
+
await clearAllReplacements();
|
|
928
|
+
break;
|
|
929
|
+
case "exit":
|
|
930
|
+
running = false;
|
|
931
|
+
break;
|
|
932
|
+
default: break;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
consola.info("Goodbye! 👋");
|
|
936
|
+
}
|
|
937
|
+
const config = defineCommand({
|
|
938
|
+
meta: {
|
|
939
|
+
name: "config",
|
|
940
|
+
description: "Configure replacement rules interactively"
|
|
941
|
+
},
|
|
942
|
+
run: async () => {
|
|
943
|
+
await ensurePaths();
|
|
944
|
+
await mainMenu();
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
|
|
491
948
|
//#endregion
|
|
492
949
|
//#region src/debug.ts
|
|
493
950
|
async function getPackageVersion() {
|
|
@@ -661,6 +1118,84 @@ function generateEnvScript(envVars, commandToRun = "") {
|
|
|
661
1118
|
return commandBlock || commandToRun;
|
|
662
1119
|
}
|
|
663
1120
|
|
|
1121
|
+
//#endregion
|
|
1122
|
+
//#region src/lib/request-logger.ts
|
|
1123
|
+
const REQUEST_CONTEXT_KEY = "requestContext";
|
|
1124
|
+
const colors = {
|
|
1125
|
+
reset: "\x1B[0m",
|
|
1126
|
+
dim: "\x1B[2m",
|
|
1127
|
+
bold: "\x1B[1m",
|
|
1128
|
+
cyan: "\x1B[36m",
|
|
1129
|
+
green: "\x1B[32m",
|
|
1130
|
+
yellow: "\x1B[33m",
|
|
1131
|
+
red: "\x1B[31m",
|
|
1132
|
+
magenta: "\x1B[35m",
|
|
1133
|
+
blue: "\x1B[34m",
|
|
1134
|
+
white: "\x1B[37m",
|
|
1135
|
+
gray: "\x1B[90m"
|
|
1136
|
+
};
|
|
1137
|
+
/**
|
|
1138
|
+
* Get the current time formatted as HH:MM:SS
|
|
1139
|
+
*/
|
|
1140
|
+
function getTimeString() {
|
|
1141
|
+
return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", {
|
|
1142
|
+
hour12: false,
|
|
1143
|
+
hour: "2-digit",
|
|
1144
|
+
minute: "2-digit",
|
|
1145
|
+
second: "2-digit"
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Get status color based on HTTP status code
|
|
1150
|
+
*/
|
|
1151
|
+
function getStatusColor(status) {
|
|
1152
|
+
if (status >= 500) return colors.red;
|
|
1153
|
+
if (status >= 400) return colors.yellow;
|
|
1154
|
+
if (status >= 300) return colors.cyan;
|
|
1155
|
+
return colors.green;
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Set request context for logging
|
|
1159
|
+
*/
|
|
1160
|
+
function setRequestContext(c, ctx) {
|
|
1161
|
+
const existing = c.get(REQUEST_CONTEXT_KEY);
|
|
1162
|
+
if (existing) c.set(REQUEST_CONTEXT_KEY, {
|
|
1163
|
+
...existing,
|
|
1164
|
+
...ctx
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Custom request logger middleware
|
|
1169
|
+
*/
|
|
1170
|
+
async function requestLogger(c, next) {
|
|
1171
|
+
const startTime = Date.now();
|
|
1172
|
+
const method = c.req.method;
|
|
1173
|
+
const path$1 = c.req.path + (c.req.raw.url.includes("?") ? "?" + c.req.raw.url.split("?")[1] : "");
|
|
1174
|
+
c.set(REQUEST_CONTEXT_KEY, { startTime });
|
|
1175
|
+
await next();
|
|
1176
|
+
const ctx = c.get(REQUEST_CONTEXT_KEY);
|
|
1177
|
+
const duration = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
1178
|
+
const status = c.res.status;
|
|
1179
|
+
const statusColor = getStatusColor(status);
|
|
1180
|
+
const lines = [];
|
|
1181
|
+
lines.push(`${colors.dim}${"─".repeat(60)}${colors.reset}`);
|
|
1182
|
+
const statusBadge = `${statusColor}${status}${colors.reset}`;
|
|
1183
|
+
const durationStr = `${colors.cyan}${duration}s${colors.reset}`;
|
|
1184
|
+
lines.push(`${colors.bold}${method}${colors.reset} ${path$1} ${statusBadge} ${durationStr}`);
|
|
1185
|
+
if (ctx?.provider && ctx?.model) {
|
|
1186
|
+
const providerColor = ctx.provider === "Azure OpenAI" ? colors.blue : colors.magenta;
|
|
1187
|
+
lines.push(` ${colors.gray}Provider:${colors.reset} ${providerColor}${ctx.provider}${colors.reset} ${colors.gray}->${colors.reset} ${colors.white}${ctx.model}${colors.reset}`);
|
|
1188
|
+
}
|
|
1189
|
+
if (ctx?.inputTokens !== void 0 || ctx?.outputTokens !== void 0) {
|
|
1190
|
+
const tokenParts = [];
|
|
1191
|
+
if (ctx.inputTokens !== void 0) tokenParts.push(`${colors.gray}Input:${colors.reset} ${colors.yellow}${ctx.inputTokens.toLocaleString()}${colors.reset}`);
|
|
1192
|
+
if (ctx.outputTokens !== void 0) tokenParts.push(`${colors.gray}Output:${colors.reset} ${colors.green}${ctx.outputTokens.toLocaleString()}${colors.reset}`);
|
|
1193
|
+
lines.push(` ${tokenParts.join(" ")}`);
|
|
1194
|
+
}
|
|
1195
|
+
lines.push(` ${colors.dim}${getTimeString()}${colors.reset}`);
|
|
1196
|
+
console.log(lines.join("\n"));
|
|
1197
|
+
}
|
|
1198
|
+
|
|
664
1199
|
//#endregion
|
|
665
1200
|
//#region src/lib/approval.ts
|
|
666
1201
|
const awaitApproval = async () => {
|
|
@@ -900,7 +1435,7 @@ const createChatCompletions = async (payload) => {
|
|
|
900
1435
|
...copilotHeaders(state, enableVision),
|
|
901
1436
|
"X-Initiator": isAgentCall ? "agent" : "user"
|
|
902
1437
|
};
|
|
903
|
-
const response = await
|
|
1438
|
+
const response = await fetchWithRetry(`${copilotBaseUrl(state)}/chat/completions`, {
|
|
904
1439
|
method: "POST",
|
|
905
1440
|
headers,
|
|
906
1441
|
body: JSON.stringify(payload)
|
|
@@ -917,32 +1452,50 @@ const createChatCompletions = async (payload) => {
|
|
|
917
1452
|
//#region src/routes/chat-completions/handler.ts
|
|
918
1453
|
async function handleCompletion$1(c) {
|
|
919
1454
|
await checkRateLimit(state);
|
|
920
|
-
|
|
1455
|
+
const rawPayload = await c.req.json();
|
|
1456
|
+
let payload = await applyReplacementsToPayload(rawPayload);
|
|
921
1457
|
consola.debug("Request payload:", JSON.stringify(payload).slice(-400));
|
|
922
1458
|
if (isAzureOpenAIModel(payload.model)) {
|
|
923
1459
|
if (!state.azureOpenAIConfig) return c.json({ error: "Azure OpenAI not configured" }, 500);
|
|
924
|
-
|
|
1460
|
+
setRequestContext(c, {
|
|
1461
|
+
provider: "Azure OpenAI",
|
|
1462
|
+
model: payload.model
|
|
1463
|
+
});
|
|
925
1464
|
if (state.manualApprove) await awaitApproval();
|
|
926
1465
|
const response$1 = await createAzureOpenAIChatCompletions(state.azureOpenAIConfig, payload);
|
|
927
1466
|
if (isNonStreaming$1(response$1)) {
|
|
928
1467
|
consola.debug("Non-streaming response:", JSON.stringify(response$1));
|
|
1468
|
+
if (response$1.usage) setRequestContext(c, {
|
|
1469
|
+
inputTokens: response$1.usage.prompt_tokens,
|
|
1470
|
+
outputTokens: response$1.usage.completion_tokens
|
|
1471
|
+
});
|
|
929
1472
|
return c.json(response$1);
|
|
930
1473
|
}
|
|
931
1474
|
consola.debug("Streaming response");
|
|
932
1475
|
return streamSSE(c, async (stream) => {
|
|
933
1476
|
for await (const chunk of response$1) {
|
|
934
1477
|
consola.debug("Streaming chunk:", JSON.stringify(chunk));
|
|
1478
|
+
if (chunk.data && chunk.data !== "[DONE]") {
|
|
1479
|
+
const parsed = JSON.parse(chunk.data);
|
|
1480
|
+
if (parsed.usage) setRequestContext(c, {
|
|
1481
|
+
inputTokens: parsed.usage.prompt_tokens,
|
|
1482
|
+
outputTokens: parsed.usage.completion_tokens
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
935
1485
|
await stream.writeSSE(chunk);
|
|
936
1486
|
}
|
|
937
1487
|
});
|
|
938
1488
|
}
|
|
939
|
-
|
|
1489
|
+
setRequestContext(c, {
|
|
1490
|
+
provider: "Copilot",
|
|
1491
|
+
model: payload.model
|
|
1492
|
+
});
|
|
940
1493
|
const selectedModel = state.models?.data.find((model) => model.id === payload.model);
|
|
941
1494
|
try {
|
|
942
1495
|
if (selectedModel) {
|
|
943
1496
|
const tokenCount = await getTokenCount(payload, selectedModel);
|
|
944
|
-
|
|
945
|
-
}
|
|
1497
|
+
setRequestContext(c, { inputTokens: tokenCount });
|
|
1498
|
+
}
|
|
946
1499
|
} catch (error) {
|
|
947
1500
|
consola.warn("Failed to calculate token count:", error);
|
|
948
1501
|
}
|
|
@@ -957,12 +1510,23 @@ async function handleCompletion$1(c) {
|
|
|
957
1510
|
const response = await createChatCompletions(payload);
|
|
958
1511
|
if (isNonStreaming$1(response)) {
|
|
959
1512
|
consola.debug("Non-streaming response:", JSON.stringify(response));
|
|
1513
|
+
if (response.usage) setRequestContext(c, {
|
|
1514
|
+
inputTokens: response.usage.prompt_tokens,
|
|
1515
|
+
outputTokens: response.usage.completion_tokens
|
|
1516
|
+
});
|
|
960
1517
|
return c.json(response);
|
|
961
1518
|
}
|
|
962
1519
|
consola.debug("Streaming response");
|
|
963
1520
|
return streamSSE(c, async (stream) => {
|
|
964
1521
|
for await (const chunk of response) {
|
|
965
1522
|
consola.debug("Streaming chunk:", JSON.stringify(chunk));
|
|
1523
|
+
if (chunk.data && chunk.data !== "[DONE]") {
|
|
1524
|
+
const parsed = JSON.parse(chunk.data);
|
|
1525
|
+
if (parsed.usage) setRequestContext(c, {
|
|
1526
|
+
inputTokens: parsed.usage.prompt_tokens,
|
|
1527
|
+
outputTokens: parsed.usage.completion_tokens
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
966
1530
|
await stream.writeSSE(chunk);
|
|
967
1531
|
}
|
|
968
1532
|
});
|
|
@@ -984,7 +1548,7 @@ completionRoutes.post("/", async (c) => {
|
|
|
984
1548
|
//#region src/services/copilot/create-embeddings.ts
|
|
985
1549
|
const createEmbeddings = async (payload) => {
|
|
986
1550
|
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
987
|
-
const response = await
|
|
1551
|
+
const response = await fetchWithRetry(`${copilotBaseUrl(state)}/embeddings`, {
|
|
988
1552
|
method: "POST",
|
|
989
1553
|
headers: copilotHeaders(state),
|
|
990
1554
|
body: JSON.stringify(payload)
|
|
@@ -1366,15 +1930,23 @@ async function handleCompletion(c) {
|
|
|
1366
1930
|
await checkRateLimit(state);
|
|
1367
1931
|
const anthropicPayload = await c.req.json();
|
|
1368
1932
|
consola.debug("Anthropic request payload:", JSON.stringify(anthropicPayload));
|
|
1369
|
-
const
|
|
1933
|
+
const translatedPayload = translateToOpenAI(anthropicPayload);
|
|
1934
|
+
const openAIPayload = await applyReplacementsToPayload(translatedPayload);
|
|
1370
1935
|
consola.debug("Translated OpenAI request payload:", JSON.stringify(openAIPayload));
|
|
1371
1936
|
if (state.manualApprove) await awaitApproval();
|
|
1372
1937
|
if (isAzureOpenAIModel(openAIPayload.model)) {
|
|
1373
1938
|
if (!state.azureOpenAIConfig) return c.json({ error: "Azure OpenAI not configured" }, 500);
|
|
1374
|
-
|
|
1939
|
+
setRequestContext(c, {
|
|
1940
|
+
provider: "Azure OpenAI",
|
|
1941
|
+
model: openAIPayload.model
|
|
1942
|
+
});
|
|
1375
1943
|
const response$1 = await createAzureOpenAIChatCompletions(state.azureOpenAIConfig, openAIPayload);
|
|
1376
1944
|
if (isNonStreaming(response$1)) {
|
|
1377
1945
|
consola.debug("Non-streaming response from Azure OpenAI:", JSON.stringify(response$1).slice(-400));
|
|
1946
|
+
if (response$1.usage) setRequestContext(c, {
|
|
1947
|
+
inputTokens: response$1.usage.prompt_tokens,
|
|
1948
|
+
outputTokens: response$1.usage.completion_tokens
|
|
1949
|
+
});
|
|
1378
1950
|
const anthropicResponse = translateToAnthropic(response$1);
|
|
1379
1951
|
consola.debug("Translated Anthropic response:", JSON.stringify(anthropicResponse));
|
|
1380
1952
|
return c.json(anthropicResponse);
|
|
@@ -1392,6 +1964,10 @@ async function handleCompletion(c) {
|
|
|
1392
1964
|
if (rawEvent.data === "[DONE]") break;
|
|
1393
1965
|
if (!rawEvent.data) continue;
|
|
1394
1966
|
const chunk = JSON.parse(rawEvent.data);
|
|
1967
|
+
if (chunk.usage) setRequestContext(c, {
|
|
1968
|
+
inputTokens: chunk.usage.prompt_tokens,
|
|
1969
|
+
outputTokens: chunk.usage.completion_tokens
|
|
1970
|
+
});
|
|
1395
1971
|
const events$1 = translateChunkToAnthropicEvents(chunk, streamState);
|
|
1396
1972
|
for (const event of events$1) {
|
|
1397
1973
|
consola.debug("Translated Anthropic event:", JSON.stringify(event));
|
|
@@ -1403,10 +1979,17 @@ async function handleCompletion(c) {
|
|
|
1403
1979
|
}
|
|
1404
1980
|
});
|
|
1405
1981
|
}
|
|
1406
|
-
|
|
1982
|
+
setRequestContext(c, {
|
|
1983
|
+
provider: "Copilot",
|
|
1984
|
+
model: openAIPayload.model
|
|
1985
|
+
});
|
|
1407
1986
|
const response = await createChatCompletions(openAIPayload);
|
|
1408
1987
|
if (isNonStreaming(response)) {
|
|
1409
1988
|
consola.debug("Non-streaming response from Copilot:", JSON.stringify(response).slice(-400));
|
|
1989
|
+
if (response.usage) setRequestContext(c, {
|
|
1990
|
+
inputTokens: response.usage.prompt_tokens,
|
|
1991
|
+
outputTokens: response.usage.completion_tokens
|
|
1992
|
+
});
|
|
1410
1993
|
const anthropicResponse = translateToAnthropic(response);
|
|
1411
1994
|
consola.debug("Translated Anthropic response:", JSON.stringify(anthropicResponse));
|
|
1412
1995
|
return c.json(anthropicResponse);
|
|
@@ -1424,6 +2007,10 @@ async function handleCompletion(c) {
|
|
|
1424
2007
|
if (rawEvent.data === "[DONE]") break;
|
|
1425
2008
|
if (!rawEvent.data) continue;
|
|
1426
2009
|
const chunk = JSON.parse(rawEvent.data);
|
|
2010
|
+
if (chunk.usage) setRequestContext(c, {
|
|
2011
|
+
inputTokens: chunk.usage.prompt_tokens,
|
|
2012
|
+
outputTokens: chunk.usage.completion_tokens
|
|
2013
|
+
});
|
|
1427
2014
|
const events$1 = translateChunkToAnthropicEvents(chunk, streamState);
|
|
1428
2015
|
for (const event of events$1) {
|
|
1429
2016
|
consola.debug("Translated Anthropic event:", JSON.stringify(event));
|
|
@@ -1490,6 +2077,37 @@ modelRoutes.get("/", async (c) => {
|
|
|
1490
2077
|
}
|
|
1491
2078
|
});
|
|
1492
2079
|
|
|
2080
|
+
//#endregion
|
|
2081
|
+
//#region src/routes/replacements/route.ts
|
|
2082
|
+
const replacementsRoute = new Hono();
|
|
2083
|
+
replacementsRoute.get("/", async (c) => {
|
|
2084
|
+
return c.json({
|
|
2085
|
+
all: await getAllReplacements(),
|
|
2086
|
+
user: await getUserReplacements()
|
|
2087
|
+
});
|
|
2088
|
+
});
|
|
2089
|
+
replacementsRoute.post("/", async (c) => {
|
|
2090
|
+
const body = await c.req.json();
|
|
2091
|
+
if (!body.pattern) return c.json({ error: "Pattern is required" }, 400);
|
|
2092
|
+
const rule = await addReplacement(body.pattern, body.replacement ?? "", body.isRegex ?? false);
|
|
2093
|
+
return c.json(rule, 201);
|
|
2094
|
+
});
|
|
2095
|
+
replacementsRoute.delete("/:id", async (c) => {
|
|
2096
|
+
const id = c.req.param("id");
|
|
2097
|
+
if (!await removeReplacement(id)) return c.json({ error: "Replacement not found or is a system rule" }, 404);
|
|
2098
|
+
return c.json({ success: true });
|
|
2099
|
+
});
|
|
2100
|
+
replacementsRoute.patch("/:id/toggle", async (c) => {
|
|
2101
|
+
const id = c.req.param("id");
|
|
2102
|
+
const rule = await toggleReplacement(id);
|
|
2103
|
+
if (!rule) return c.json({ error: "Replacement not found or is a system rule" }, 404);
|
|
2104
|
+
return c.json(rule);
|
|
2105
|
+
});
|
|
2106
|
+
replacementsRoute.delete("/", async (c) => {
|
|
2107
|
+
await clearUserReplacements();
|
|
2108
|
+
return c.json({ success: true });
|
|
2109
|
+
});
|
|
2110
|
+
|
|
1493
2111
|
//#endregion
|
|
1494
2112
|
//#region src/routes/token/route.ts
|
|
1495
2113
|
const tokenRoute = new Hono();
|
|
@@ -1521,7 +2139,7 @@ usageRoute.get("/", async (c) => {
|
|
|
1521
2139
|
//#endregion
|
|
1522
2140
|
//#region src/server.ts
|
|
1523
2141
|
const server = new Hono();
|
|
1524
|
-
server.use(
|
|
2142
|
+
server.use(requestLogger);
|
|
1525
2143
|
server.use(cors());
|
|
1526
2144
|
server.get("/", (c) => c.text("Server running"));
|
|
1527
2145
|
server.route("/chat/completions", completionRoutes);
|
|
@@ -1529,6 +2147,7 @@ server.route("/models", modelRoutes);
|
|
|
1529
2147
|
server.route("/embeddings", embeddingRoutes);
|
|
1530
2148
|
server.route("/usage", usageRoute);
|
|
1531
2149
|
server.route("/token", tokenRoute);
|
|
2150
|
+
server.route("/replacements", replacementsRoute);
|
|
1532
2151
|
server.route("/v1/chat/completions", completionRoutes);
|
|
1533
2152
|
server.route("/v1/models", modelRoutes);
|
|
1534
2153
|
server.route("/v1/embeddings", embeddingRoutes);
|
|
@@ -1537,6 +2156,10 @@ server.route("/v1/messages", messageRoutes);
|
|
|
1537
2156
|
//#endregion
|
|
1538
2157
|
//#region src/start.ts
|
|
1539
2158
|
async function runServer(options) {
|
|
2159
|
+
if (options.insecure) {
|
|
2160
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
2161
|
+
consola.warn("SSL certificate verification disabled (insecure mode)");
|
|
2162
|
+
}
|
|
1540
2163
|
if (options.proxyEnv) initProxyFromEnv();
|
|
1541
2164
|
if (options.verbose) {
|
|
1542
2165
|
consola.level = 5;
|
|
@@ -1656,6 +2279,11 @@ const start = defineCommand({
|
|
|
1656
2279
|
type: "boolean",
|
|
1657
2280
|
default: false,
|
|
1658
2281
|
description: "Initialize proxy from environment variables"
|
|
2282
|
+
},
|
|
2283
|
+
insecure: {
|
|
2284
|
+
type: "boolean",
|
|
2285
|
+
default: false,
|
|
2286
|
+
description: "Disable SSL certificate verification (for corporate proxies with self-signed certs)"
|
|
1659
2287
|
}
|
|
1660
2288
|
},
|
|
1661
2289
|
run({ args }) {
|
|
@@ -1671,7 +2299,8 @@ const start = defineCommand({
|
|
|
1671
2299
|
githubToken: args["github-token"],
|
|
1672
2300
|
claudeCode: args["claude-code"],
|
|
1673
2301
|
showToken: args["show-token"],
|
|
1674
|
-
proxyEnv: args["proxy-env"]
|
|
2302
|
+
proxyEnv: args["proxy-env"],
|
|
2303
|
+
insecure: args.insecure
|
|
1675
2304
|
});
|
|
1676
2305
|
}
|
|
1677
2306
|
});
|
|
@@ -1687,7 +2316,8 @@ const main = defineCommand({
|
|
|
1687
2316
|
auth,
|
|
1688
2317
|
start,
|
|
1689
2318
|
"check-usage": checkUsage,
|
|
1690
|
-
debug
|
|
2319
|
+
debug,
|
|
2320
|
+
config
|
|
1691
2321
|
}
|
|
1692
2322
|
});
|
|
1693
2323
|
await runMain(main);
|