@ashsec/copilot-api 0.7.4 → 0.7.6
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 +315 -118
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -48,7 +48,8 @@ const state = {
|
|
|
48
48
|
accountType: "individual",
|
|
49
49
|
manualApprove: false,
|
|
50
50
|
rateLimitWait: false,
|
|
51
|
-
showToken: false
|
|
51
|
+
showToken: false,
|
|
52
|
+
debug: false
|
|
52
53
|
};
|
|
53
54
|
|
|
54
55
|
//#endregion
|
|
@@ -96,11 +97,16 @@ const GITHUB_APP_SCOPES = ["read:user"].join(" ");
|
|
|
96
97
|
//#region src/lib/error.ts
|
|
97
98
|
var HTTPError = class extends Error {
|
|
98
99
|
response;
|
|
99
|
-
|
|
100
|
+
requestPayload;
|
|
101
|
+
constructor(message, response, requestPayload) {
|
|
100
102
|
super(message);
|
|
101
103
|
this.response = response;
|
|
104
|
+
this.requestPayload = requestPayload;
|
|
102
105
|
}
|
|
103
106
|
};
|
|
107
|
+
function isContentFilterError(obj) {
|
|
108
|
+
return typeof obj === "object" && obj !== null && "error" in obj && typeof obj.error === "object" && obj.error?.code === "content_filter";
|
|
109
|
+
}
|
|
104
110
|
async function forwardError(c, error) {
|
|
105
111
|
consola.error("Error occurred:", error);
|
|
106
112
|
if (error instanceof HTTPError) {
|
|
@@ -112,6 +118,15 @@ async function forwardError(c, error) {
|
|
|
112
118
|
errorJson = errorText;
|
|
113
119
|
}
|
|
114
120
|
consola.error("HTTP error:", errorJson);
|
|
121
|
+
if (isContentFilterError(errorJson)) {
|
|
122
|
+
consola.box("CONTENT FILTER TRIGGERED");
|
|
123
|
+
consola.error("Full error response:");
|
|
124
|
+
console.log(JSON.stringify(errorJson, null, 2));
|
|
125
|
+
if (error.requestPayload) {
|
|
126
|
+
consola.error("Request payload that triggered the filter:");
|
|
127
|
+
console.log(JSON.stringify(error.requestPayload, null, 2));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
115
130
|
return c.json({ error: {
|
|
116
131
|
message: errorText,
|
|
117
132
|
type: "error"
|
|
@@ -209,8 +224,11 @@ function getAzureDeploymentName(modelId) {
|
|
|
209
224
|
|
|
210
225
|
//#endregion
|
|
211
226
|
//#region src/lib/retry-fetch.ts
|
|
212
|
-
const
|
|
213
|
-
|
|
227
|
+
const RETRY_DELAYS_MS = [
|
|
228
|
+
100,
|
|
229
|
+
200,
|
|
230
|
+
300
|
|
231
|
+
];
|
|
214
232
|
/**
|
|
215
233
|
* Check if an error is retryable (transient network error)
|
|
216
234
|
*/
|
|
@@ -224,6 +242,7 @@ function isRetryableError(error) {
|
|
|
224
242
|
"connection reset",
|
|
225
243
|
"econnreset",
|
|
226
244
|
"socket hang up",
|
|
245
|
+
"socket connection was closed unexpectedly",
|
|
227
246
|
"etimedout",
|
|
228
247
|
"econnrefused",
|
|
229
248
|
"network error",
|
|
@@ -238,13 +257,14 @@ function isRetryableStatus(status) {
|
|
|
238
257
|
return status === 408 || status === 429 || status >= 500 && status <= 599;
|
|
239
258
|
}
|
|
240
259
|
/**
|
|
241
|
-
* Fetch with automatic retry on transient failures
|
|
260
|
+
* Fetch with automatic fast retry on transient failures
|
|
261
|
+
* Retries with delays: 100ms, 200ms, 300ms (max ~600ms total wait)
|
|
242
262
|
*/
|
|
243
|
-
async function fetchWithRetry(input, init
|
|
244
|
-
const
|
|
263
|
+
async function fetchWithRetry(input, init) {
|
|
264
|
+
const maxAttempts = RETRY_DELAYS_MS.length + 1;
|
|
245
265
|
let lastError;
|
|
246
266
|
let lastResponse;
|
|
247
|
-
for (let attempt = 0; attempt
|
|
267
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) try {
|
|
248
268
|
const headers = new Headers(init?.headers);
|
|
249
269
|
headers.set("Connection", "close");
|
|
250
270
|
const response = await fetch(input, {
|
|
@@ -252,19 +272,19 @@ async function fetchWithRetry(input, init, options = {}) {
|
|
|
252
272
|
headers,
|
|
253
273
|
keepalive: false
|
|
254
274
|
});
|
|
255
|
-
if (isRetryableStatus(response.status) && attempt <
|
|
275
|
+
if (isRetryableStatus(response.status) && attempt < maxAttempts - 1) {
|
|
256
276
|
lastResponse = response;
|
|
257
|
-
const delayMs =
|
|
258
|
-
consola.warn(`HTTP ${response.status} (attempt ${attempt + 1}/${
|
|
277
|
+
const delayMs = RETRY_DELAYS_MS[attempt];
|
|
278
|
+
consola.warn(`HTTP ${response.status} (attempt ${attempt + 1}/${maxAttempts}), retrying in ${delayMs}ms`);
|
|
259
279
|
await sleep(delayMs);
|
|
260
280
|
continue;
|
|
261
281
|
}
|
|
262
282
|
return response;
|
|
263
283
|
} catch (error) {
|
|
264
284
|
lastError = error;
|
|
265
|
-
if (!isRetryableError(error) || attempt ===
|
|
266
|
-
const delayMs =
|
|
267
|
-
consola.warn(`Fetch failed (attempt ${attempt + 1}/${
|
|
285
|
+
if (!isRetryableError(error) || attempt === maxAttempts - 1) throw error;
|
|
286
|
+
const delayMs = RETRY_DELAYS_MS[attempt];
|
|
287
|
+
consola.warn(`Fetch failed (attempt ${attempt + 1}/${maxAttempts}), retrying in ${delayMs}ms:`, lastError.message);
|
|
268
288
|
await sleep(delayMs);
|
|
269
289
|
}
|
|
270
290
|
if (lastResponse) return lastResponse;
|
|
@@ -292,7 +312,7 @@ async function createAzureOpenAIChatCompletions(config$1, payload) {
|
|
|
292
312
|
});
|
|
293
313
|
if (!response.ok) {
|
|
294
314
|
consola.error("Failed to create Azure OpenAI chat completions:", response);
|
|
295
|
-
throw new HTTPError("Failed to create Azure OpenAI chat completions", response);
|
|
315
|
+
throw new HTTPError("Failed to create Azure OpenAI chat completions", response, payload);
|
|
296
316
|
}
|
|
297
317
|
if (payload.stream) return events(response);
|
|
298
318
|
return await response.json();
|
|
@@ -578,7 +598,8 @@ const checkUsage = defineCommand({
|
|
|
578
598
|
//#region src/lib/auto-replace.ts
|
|
579
599
|
const SYSTEM_REPLACEMENTS = [{
|
|
580
600
|
id: "system-anthropic-billing",
|
|
581
|
-
|
|
601
|
+
name: "Remove Anthropic billing header",
|
|
602
|
+
pattern: "x-anthropic-billing-header:[^\\n]*\\n?",
|
|
582
603
|
replacement: "",
|
|
583
604
|
isRegex: true,
|
|
584
605
|
enabled: true,
|
|
@@ -592,7 +613,7 @@ let isLoaded = false;
|
|
|
592
613
|
async function loadReplacements() {
|
|
593
614
|
try {
|
|
594
615
|
const data = await fs.readFile(PATHS.REPLACEMENTS_CONFIG_PATH);
|
|
595
|
-
userReplacements = JSON.parse(data).filter((r) => !r.isSystem);
|
|
616
|
+
userReplacements = JSON.parse(data.toString()).filter((r) => !r.isSystem);
|
|
596
617
|
isLoaded = true;
|
|
597
618
|
consola.debug(`Loaded ${userReplacements.length} user replacement rules`);
|
|
598
619
|
} catch {
|
|
@@ -635,10 +656,11 @@ async function getUserReplacements() {
|
|
|
635
656
|
/**
|
|
636
657
|
* Add a new user replacement rule
|
|
637
658
|
*/
|
|
638
|
-
async function addReplacement(pattern, replacement, isRegex = false) {
|
|
659
|
+
async function addReplacement(pattern, replacement, isRegex = false, name) {
|
|
639
660
|
await ensureLoaded();
|
|
640
661
|
const rule = {
|
|
641
662
|
id: `user-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
663
|
+
name,
|
|
642
664
|
pattern,
|
|
643
665
|
replacement,
|
|
644
666
|
isRegex,
|
|
@@ -667,6 +689,26 @@ async function removeReplacement(id) {
|
|
|
667
689
|
return true;
|
|
668
690
|
}
|
|
669
691
|
/**
|
|
692
|
+
* Update an existing user replacement rule
|
|
693
|
+
*/
|
|
694
|
+
async function updateReplacement(id, updates) {
|
|
695
|
+
await ensureLoaded();
|
|
696
|
+
const rule = userReplacements.find((r) => r.id === id);
|
|
697
|
+
if (!rule) return null;
|
|
698
|
+
if (rule.isSystem) {
|
|
699
|
+
consola.warn("Cannot update system replacement rule");
|
|
700
|
+
return null;
|
|
701
|
+
}
|
|
702
|
+
if (updates.name !== void 0) rule.name = updates.name;
|
|
703
|
+
if (updates.pattern !== void 0) rule.pattern = updates.pattern;
|
|
704
|
+
if (updates.replacement !== void 0) rule.replacement = updates.replacement;
|
|
705
|
+
if (updates.isRegex !== void 0) rule.isRegex = updates.isRegex;
|
|
706
|
+
if (updates.enabled !== void 0) rule.enabled = updates.enabled;
|
|
707
|
+
await saveReplacements();
|
|
708
|
+
consola.info(`Updated replacement rule: ${rule.name || rule.id}`);
|
|
709
|
+
return rule;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
670
712
|
* Toggle a replacement rule on/off
|
|
671
713
|
*/
|
|
672
714
|
async function toggleReplacement(id) {
|
|
@@ -693,18 +735,32 @@ async function clearUserReplacements() {
|
|
|
693
735
|
consola.info("Cleared all user replacement rules");
|
|
694
736
|
}
|
|
695
737
|
/**
|
|
696
|
-
* Apply a single replacement rule to text
|
|
738
|
+
* Apply a single replacement rule to text and return info about whether it matched
|
|
697
739
|
*/
|
|
698
740
|
function applyRule(text, rule) {
|
|
699
|
-
if (!rule.enabled) return
|
|
741
|
+
if (!rule.enabled) return {
|
|
742
|
+
result: text,
|
|
743
|
+
matched: false
|
|
744
|
+
};
|
|
700
745
|
if (rule.isRegex) try {
|
|
701
746
|
const regex = new RegExp(rule.pattern, "g");
|
|
702
|
-
|
|
747
|
+
const result$1 = text.replace(regex, rule.replacement);
|
|
748
|
+
return {
|
|
749
|
+
result: result$1,
|
|
750
|
+
matched: result$1 !== text
|
|
751
|
+
};
|
|
703
752
|
} catch {
|
|
704
753
|
consola.warn(`Invalid regex pattern in rule ${rule.id}: ${rule.pattern}`);
|
|
705
|
-
return
|
|
754
|
+
return {
|
|
755
|
+
result: text,
|
|
756
|
+
matched: false
|
|
757
|
+
};
|
|
706
758
|
}
|
|
707
|
-
|
|
759
|
+
const result = text.split(rule.pattern).join(rule.replacement);
|
|
760
|
+
return {
|
|
761
|
+
result,
|
|
762
|
+
matched: result !== text
|
|
763
|
+
};
|
|
708
764
|
}
|
|
709
765
|
/**
|
|
710
766
|
* Apply all replacement rules to text
|
|
@@ -712,11 +768,15 @@ function applyRule(text, rule) {
|
|
|
712
768
|
async function applyReplacements(text) {
|
|
713
769
|
let result = text;
|
|
714
770
|
const allRules = await getAllReplacements();
|
|
771
|
+
const appliedRules = [];
|
|
715
772
|
for (const rule of allRules) {
|
|
716
|
-
const
|
|
717
|
-
|
|
718
|
-
|
|
773
|
+
const { result: newResult, matched } = applyRule(result, rule);
|
|
774
|
+
if (matched) {
|
|
775
|
+
result = newResult;
|
|
776
|
+
appliedRules.push(rule.name || rule.id);
|
|
777
|
+
}
|
|
719
778
|
}
|
|
779
|
+
if (appliedRules.length > 0) consola.info(`Replacements applied: ${appliedRules.join(", ")}`);
|
|
720
780
|
return result;
|
|
721
781
|
}
|
|
722
782
|
/**
|
|
@@ -753,8 +813,9 @@ function formatRule(rule, index) {
|
|
|
753
813
|
const status = rule.enabled ? "✓" : "✗";
|
|
754
814
|
const type = rule.isRegex ? "regex" : "string";
|
|
755
815
|
const system = rule.isSystem ? " [system]" : "";
|
|
816
|
+
const name = rule.name ? ` "${rule.name}"` : "";
|
|
756
817
|
const replacement = rule.replacement || "(empty)";
|
|
757
|
-
return `${index + 1}. [${status}] (${type})${system} "${rule.pattern}" → "${replacement}"`;
|
|
818
|
+
return `${index + 1}. [${status}] (${type})${system}${name} "${rule.pattern}" → "${replacement}"`;
|
|
758
819
|
}
|
|
759
820
|
async function listReplacements() {
|
|
760
821
|
const all = await getAllReplacements();
|
|
@@ -767,6 +828,14 @@ async function listReplacements() {
|
|
|
767
828
|
console.log();
|
|
768
829
|
}
|
|
769
830
|
async function addNewReplacement() {
|
|
831
|
+
const name = await consola.prompt("Name (optional, short description):", {
|
|
832
|
+
type: "text",
|
|
833
|
+
default: ""
|
|
834
|
+
});
|
|
835
|
+
if (typeof name === "symbol") {
|
|
836
|
+
consola.info("Cancelled.");
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
770
839
|
const matchType = await consola.prompt("Match type:", {
|
|
771
840
|
type: "select",
|
|
772
841
|
options: [{
|
|
@@ -800,8 +869,87 @@ async function addNewReplacement() {
|
|
|
800
869
|
consola.info("Cancelled.");
|
|
801
870
|
return;
|
|
802
871
|
}
|
|
803
|
-
const rule = await addReplacement(pattern, replacement, matchType === "regex");
|
|
804
|
-
consola.success(`Added rule: ${rule.id}`);
|
|
872
|
+
const rule = await addReplacement(pattern, replacement, matchType === "regex", name || void 0);
|
|
873
|
+
consola.success(`Added rule: ${rule.name || rule.id}`);
|
|
874
|
+
}
|
|
875
|
+
async function editExistingReplacement() {
|
|
876
|
+
const userRules = await getUserReplacements();
|
|
877
|
+
if (userRules.length === 0) {
|
|
878
|
+
consola.info("No user rules to edit.");
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
const options = userRules.map((rule$1, i) => ({
|
|
882
|
+
label: formatRule(rule$1, i),
|
|
883
|
+
value: rule$1.id
|
|
884
|
+
}));
|
|
885
|
+
const selected = await consola.prompt("Select rule to edit:", {
|
|
886
|
+
type: "select",
|
|
887
|
+
options
|
|
888
|
+
});
|
|
889
|
+
if (typeof selected === "symbol") {
|
|
890
|
+
consola.info("Cancelled.");
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
const rule = userRules.find((r) => r.id === selected);
|
|
894
|
+
if (!rule) {
|
|
895
|
+
consola.error("Rule not found.");
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
consola.info(`\nEditing rule: ${rule.name || rule.id}`);
|
|
899
|
+
consola.info("Press Enter to keep current value.\n");
|
|
900
|
+
const name = await consola.prompt("Name:", {
|
|
901
|
+
type: "text",
|
|
902
|
+
default: rule.name || ""
|
|
903
|
+
});
|
|
904
|
+
if (typeof name === "symbol") {
|
|
905
|
+
consola.info("Cancelled.");
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
const matchType = await consola.prompt("Match type:", {
|
|
909
|
+
type: "select",
|
|
910
|
+
options: [{
|
|
911
|
+
label: "String (exact match)",
|
|
912
|
+
value: "string"
|
|
913
|
+
}, {
|
|
914
|
+
label: "Regex (regular expression)",
|
|
915
|
+
value: "regex"
|
|
916
|
+
}],
|
|
917
|
+
initial: rule.isRegex ? "regex" : "string"
|
|
918
|
+
});
|
|
919
|
+
if (typeof matchType === "symbol") {
|
|
920
|
+
consola.info("Cancelled.");
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
const pattern = await consola.prompt("Pattern to match:", {
|
|
924
|
+
type: "text",
|
|
925
|
+
default: rule.pattern
|
|
926
|
+
});
|
|
927
|
+
if (typeof pattern === "symbol" || !pattern) {
|
|
928
|
+
consola.info("Cancelled.");
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
if (matchType === "regex") try {
|
|
932
|
+
new RegExp(pattern);
|
|
933
|
+
} catch {
|
|
934
|
+
consola.error(`Invalid regex pattern: ${pattern}`);
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
const replacement = await consola.prompt("Replacement text:", {
|
|
938
|
+
type: "text",
|
|
939
|
+
default: rule.replacement
|
|
940
|
+
});
|
|
941
|
+
if (typeof replacement === "symbol") {
|
|
942
|
+
consola.info("Cancelled.");
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
const updated = await updateReplacement(selected, {
|
|
946
|
+
name: name || void 0,
|
|
947
|
+
pattern,
|
|
948
|
+
replacement,
|
|
949
|
+
isRegex: matchType === "regex"
|
|
950
|
+
});
|
|
951
|
+
if (updated) consola.success(`Updated rule: ${updated.name || updated.id}`);
|
|
952
|
+
else consola.error("Failed to update rule.");
|
|
805
953
|
}
|
|
806
954
|
async function removeExistingReplacement() {
|
|
807
955
|
const userRules = await getUserReplacements();
|
|
@@ -884,6 +1032,10 @@ async function mainMenu() {
|
|
|
884
1032
|
label: "➕ Add new rule",
|
|
885
1033
|
value: "add"
|
|
886
1034
|
},
|
|
1035
|
+
{
|
|
1036
|
+
label: "✏️ Edit rule",
|
|
1037
|
+
value: "edit"
|
|
1038
|
+
},
|
|
887
1039
|
{
|
|
888
1040
|
label: "➖ Remove rule",
|
|
889
1041
|
value: "remove"
|
|
@@ -914,6 +1066,9 @@ async function mainMenu() {
|
|
|
914
1066
|
case "add":
|
|
915
1067
|
await addNewReplacement();
|
|
916
1068
|
break;
|
|
1069
|
+
case "edit":
|
|
1070
|
+
await editExistingReplacement();
|
|
1071
|
+
break;
|
|
917
1072
|
case "remove":
|
|
918
1073
|
await removeExistingReplacement();
|
|
919
1074
|
break;
|
|
@@ -1155,6 +1310,39 @@ function getStatusColor(status) {
|
|
|
1155
1310
|
return colors.green;
|
|
1156
1311
|
}
|
|
1157
1312
|
/**
|
|
1313
|
+
* Log raw HTTP request details (for debug mode)
|
|
1314
|
+
*/
|
|
1315
|
+
async function logRawRequest(c) {
|
|
1316
|
+
const method = c.req.method;
|
|
1317
|
+
const url = c.req.url;
|
|
1318
|
+
const headers = Object.fromEntries(c.req.raw.headers.entries());
|
|
1319
|
+
const lines = [];
|
|
1320
|
+
lines.push(`${colors.magenta}${colors.bold}[DEBUG] Incoming Request${colors.reset}`);
|
|
1321
|
+
lines.push(`${colors.cyan}${method}${colors.reset} ${url}`);
|
|
1322
|
+
lines.push(`${colors.dim}Headers:${colors.reset}`);
|
|
1323
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
1324
|
+
const displayValue = key.toLowerCase().includes("authorization") ? `${value.slice(0, 20)}...` : value;
|
|
1325
|
+
lines.push(` ${colors.gray}${key}:${colors.reset} ${displayValue}`);
|
|
1326
|
+
}
|
|
1327
|
+
if (method !== "GET" && method !== "HEAD") try {
|
|
1328
|
+
const body = await c.req.raw.clone().text();
|
|
1329
|
+
if (body) try {
|
|
1330
|
+
const parsed = JSON.parse(body);
|
|
1331
|
+
const sanitized = {};
|
|
1332
|
+
for (const [key, value] of Object.entries(parsed)) if (key === "messages" || key === "prompt") sanitized[key] = `[${Array.isArray(value) ? value.length : 1} items omitted]`;
|
|
1333
|
+
else sanitized[key] = value;
|
|
1334
|
+
lines.push(`${colors.dim}Body (sanitized):${colors.reset}`);
|
|
1335
|
+
lines.push(` ${JSON.stringify(sanitized, null, 2).split("\n").join("\n ")}`);
|
|
1336
|
+
} catch {
|
|
1337
|
+
lines.push(`${colors.dim}Body:${colors.reset} [${body.length} bytes]`);
|
|
1338
|
+
}
|
|
1339
|
+
} catch {
|
|
1340
|
+
lines.push(`${colors.dim}Body:${colors.reset} [unable to read]`);
|
|
1341
|
+
}
|
|
1342
|
+
lines.push(`${colors.dim}${"─".repeat(60)}${colors.reset}`);
|
|
1343
|
+
console.log(lines.join("\n"));
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1158
1346
|
* Set request context for logging
|
|
1159
1347
|
*/
|
|
1160
1348
|
function setRequestContext(c, ctx) {
|
|
@@ -1168,6 +1356,7 @@ function setRequestContext(c, ctx) {
|
|
|
1168
1356
|
* Custom request logger middleware
|
|
1169
1357
|
*/
|
|
1170
1358
|
async function requestLogger(c, next) {
|
|
1359
|
+
if (state.debug) await logRawRequest(c);
|
|
1171
1360
|
const startTime = Date.now();
|
|
1172
1361
|
const method = c.req.method;
|
|
1173
1362
|
const path$1 = c.req.path + (c.req.raw.url.includes("?") ? "?" + c.req.raw.url.split("?")[1] : "");
|
|
@@ -1202,6 +1391,18 @@ const awaitApproval = async () => {
|
|
|
1202
1391
|
if (!await consola.prompt(`Accept incoming request?`, { type: "confirm" })) throw new HTTPError("Request rejected", Response.json({ message: "Request rejected" }, { status: 403 }));
|
|
1203
1392
|
};
|
|
1204
1393
|
|
|
1394
|
+
//#endregion
|
|
1395
|
+
//#region src/lib/model-resolver.ts
|
|
1396
|
+
/**
|
|
1397
|
+
* Normalize a model name by converting dashes to dots between numbers.
|
|
1398
|
+
* e.g., "claude-opus-4-5" -> "claude-opus-4.5"
|
|
1399
|
+
* "gpt-4-1" -> "gpt-4.1"
|
|
1400
|
+
* "gpt-5-1-codex" -> "gpt-5.1-codex"
|
|
1401
|
+
*/
|
|
1402
|
+
function normalizeModelName(model) {
|
|
1403
|
+
return model.replace(/(\d)-(\d)/g, (_, p1, p2) => `${p1}.${p2}`);
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1205
1406
|
//#endregion
|
|
1206
1407
|
//#region src/lib/rate-limit.ts
|
|
1207
1408
|
async function checkRateLimit(state$1) {
|
|
@@ -1454,6 +1655,10 @@ async function handleCompletion$1(c) {
|
|
|
1454
1655
|
await checkRateLimit(state);
|
|
1455
1656
|
const rawPayload = await c.req.json();
|
|
1456
1657
|
let payload = await applyReplacementsToPayload(rawPayload);
|
|
1658
|
+
payload = {
|
|
1659
|
+
...payload,
|
|
1660
|
+
model: normalizeModelName(payload.model)
|
|
1661
|
+
};
|
|
1457
1662
|
consola.debug("Request payload:", JSON.stringify(payload).slice(-400));
|
|
1458
1663
|
if (isAzureOpenAIModel(payload.model)) {
|
|
1459
1664
|
if (!state.azureOpenAIConfig) return c.json({ error: "Azure OpenAI not configured" }, 500);
|
|
@@ -1463,7 +1668,7 @@ async function handleCompletion$1(c) {
|
|
|
1463
1668
|
});
|
|
1464
1669
|
if (state.manualApprove) await awaitApproval();
|
|
1465
1670
|
const response$1 = await createAzureOpenAIChatCompletions(state.azureOpenAIConfig, payload);
|
|
1466
|
-
if (isNonStreaming
|
|
1671
|
+
if (isNonStreaming(response$1)) {
|
|
1467
1672
|
consola.debug("Non-streaming response:", JSON.stringify(response$1));
|
|
1468
1673
|
if (response$1.usage) setRequestContext(c, {
|
|
1469
1674
|
inputTokens: response$1.usage.prompt_tokens,
|
|
@@ -1494,7 +1699,7 @@ async function handleCompletion$1(c) {
|
|
|
1494
1699
|
try {
|
|
1495
1700
|
if (selectedModel) {
|
|
1496
1701
|
const tokenCount = await getTokenCount(payload, selectedModel);
|
|
1497
|
-
setRequestContext(c, { inputTokens: tokenCount });
|
|
1702
|
+
setRequestContext(c, { inputTokens: tokenCount.input });
|
|
1498
1703
|
}
|
|
1499
1704
|
} catch (error) {
|
|
1500
1705
|
consola.warn("Failed to calculate token count:", error);
|
|
@@ -1508,7 +1713,7 @@ async function handleCompletion$1(c) {
|
|
|
1508
1713
|
consola.debug("Set max_tokens to:", JSON.stringify(payload.max_tokens));
|
|
1509
1714
|
}
|
|
1510
1715
|
const response = await createChatCompletions(payload);
|
|
1511
|
-
if (isNonStreaming
|
|
1716
|
+
if (isNonStreaming(response)) {
|
|
1512
1717
|
consola.debug("Non-streaming response:", JSON.stringify(response));
|
|
1513
1718
|
if (response.usage) setRequestContext(c, {
|
|
1514
1719
|
inputTokens: response.usage.prompt_tokens,
|
|
@@ -1531,7 +1736,7 @@ async function handleCompletion$1(c) {
|
|
|
1531
1736
|
}
|
|
1532
1737
|
});
|
|
1533
1738
|
}
|
|
1534
|
-
const isNonStreaming
|
|
1739
|
+
const isNonStreaming = (response) => Object.hasOwn(response, "choices");
|
|
1535
1740
|
|
|
1536
1741
|
//#endregion
|
|
1537
1742
|
//#region src/routes/chat-completions/route.ts
|
|
@@ -1599,8 +1804,8 @@ function translateToOpenAI(payload) {
|
|
|
1599
1804
|
};
|
|
1600
1805
|
}
|
|
1601
1806
|
function translateModelName(model) {
|
|
1602
|
-
if (model.
|
|
1603
|
-
else if (model.
|
|
1807
|
+
if (model.match(/^claude-sonnet-4-\d{8}/)) return "claude-sonnet-4";
|
|
1808
|
+
else if (model.match(/^claude-opus-4-\d{8}/)) return "claude-opus-4";
|
|
1604
1809
|
return model;
|
|
1605
1810
|
}
|
|
1606
1811
|
function translateAnthropicMessagesToOpenAI(anthropicMessages, system) {
|
|
@@ -1821,12 +2026,7 @@ function translateChunkToAnthropicEvents(chunk, state$1) {
|
|
|
1821
2026
|
content: [],
|
|
1822
2027
|
model: chunk.model,
|
|
1823
2028
|
stop_reason: null,
|
|
1824
|
-
stop_sequence: null
|
|
1825
|
-
usage: {
|
|
1826
|
-
input_tokens: (chunk.usage?.prompt_tokens ?? 0) - (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0),
|
|
1827
|
-
output_tokens: 0,
|
|
1828
|
-
...chunk.usage?.prompt_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: chunk.usage.prompt_tokens_details.cached_tokens }
|
|
1829
|
-
}
|
|
2029
|
+
stop_sequence: null
|
|
1830
2030
|
}
|
|
1831
2031
|
});
|
|
1832
2032
|
state$1.messageStartSent = true;
|
|
@@ -1908,6 +2108,9 @@ function translateChunkToAnthropicEvents(chunk, state$1) {
|
|
|
1908
2108
|
});
|
|
1909
2109
|
state$1.contentBlockOpen = false;
|
|
1910
2110
|
}
|
|
2111
|
+
const inputTokens = chunk.usage?.prompt_tokens ?? 0;
|
|
2112
|
+
const outputTokens = chunk.usage?.completion_tokens ?? 0;
|
|
2113
|
+
const cachedTokens = chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0;
|
|
1911
2114
|
events$1.push({
|
|
1912
2115
|
type: "message_delta",
|
|
1913
2116
|
delta: {
|
|
@@ -1915,9 +2118,10 @@ function translateChunkToAnthropicEvents(chunk, state$1) {
|
|
|
1915
2118
|
stop_sequence: null
|
|
1916
2119
|
},
|
|
1917
2120
|
usage: {
|
|
1918
|
-
input_tokens:
|
|
1919
|
-
output_tokens:
|
|
1920
|
-
|
|
2121
|
+
input_tokens: inputTokens,
|
|
2122
|
+
output_tokens: outputTokens,
|
|
2123
|
+
cache_creation_input_tokens: 0,
|
|
2124
|
+
cache_read_input_tokens: cachedTokens
|
|
1921
2125
|
}
|
|
1922
2126
|
}, { type: "message_stop" });
|
|
1923
2127
|
}
|
|
@@ -1931,98 +2135,75 @@ async function handleCompletion(c) {
|
|
|
1931
2135
|
const anthropicPayload = await c.req.json();
|
|
1932
2136
|
consola.debug("Anthropic request payload:", JSON.stringify(anthropicPayload));
|
|
1933
2137
|
const translatedPayload = translateToOpenAI(anthropicPayload);
|
|
1934
|
-
|
|
2138
|
+
let openAIPayload = await applyReplacementsToPayload(translatedPayload);
|
|
2139
|
+
openAIPayload = {
|
|
2140
|
+
...openAIPayload,
|
|
2141
|
+
model: normalizeModelName(openAIPayload.model)
|
|
2142
|
+
};
|
|
1935
2143
|
consola.debug("Translated OpenAI request payload:", JSON.stringify(openAIPayload));
|
|
1936
2144
|
if (state.manualApprove) await awaitApproval();
|
|
1937
|
-
|
|
2145
|
+
const isAzureModel = isAzureOpenAIModel(openAIPayload.model);
|
|
2146
|
+
if (isAzureModel) {
|
|
1938
2147
|
if (!state.azureOpenAIConfig) return c.json({ error: "Azure OpenAI not configured" }, 500);
|
|
1939
2148
|
setRequestContext(c, {
|
|
1940
2149
|
provider: "Azure OpenAI",
|
|
1941
2150
|
model: openAIPayload.model
|
|
1942
2151
|
});
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
consola.debug("Streaming response from Azure OpenAI");
|
|
2152
|
+
} else setRequestContext(c, {
|
|
2153
|
+
provider: "Copilot",
|
|
2154
|
+
model: openAIPayload.model
|
|
2155
|
+
});
|
|
2156
|
+
if (anthropicPayload.stream) {
|
|
2157
|
+
const streamPayload = {
|
|
2158
|
+
...openAIPayload,
|
|
2159
|
+
stream: true,
|
|
2160
|
+
stream_options: { include_usage: true }
|
|
2161
|
+
};
|
|
2162
|
+
const eventStream = isAzureModel ? await createAzureOpenAIChatCompletions(state.azureOpenAIConfig, streamPayload) : await createChatCompletions(streamPayload);
|
|
1955
2163
|
return streamSSE(c, async (stream) => {
|
|
1956
2164
|
const streamState = {
|
|
1957
2165
|
messageStartSent: false,
|
|
1958
|
-
contentBlockIndex: 0,
|
|
1959
2166
|
contentBlockOpen: false,
|
|
2167
|
+
contentBlockIndex: 0,
|
|
1960
2168
|
toolCalls: {}
|
|
1961
2169
|
};
|
|
1962
|
-
for await (const
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
2170
|
+
for await (const event of eventStream) {
|
|
2171
|
+
if (!event.data || event.data === "[DONE]") continue;
|
|
2172
|
+
try {
|
|
2173
|
+
const chunk = JSON.parse(event.data);
|
|
2174
|
+
consola.debug("OpenAI chunk:", JSON.stringify(chunk));
|
|
2175
|
+
const anthropicEvents = translateChunkToAnthropicEvents(chunk, streamState);
|
|
2176
|
+
for (const anthropicEvent of anthropicEvents) {
|
|
2177
|
+
consola.debug("Anthropic event:", JSON.stringify(anthropicEvent));
|
|
2178
|
+
await stream.writeSSE({
|
|
2179
|
+
event: anthropicEvent.type,
|
|
2180
|
+
data: JSON.stringify(anthropicEvent)
|
|
2181
|
+
});
|
|
2182
|
+
}
|
|
2183
|
+
if (chunk.usage) setRequestContext(c, {
|
|
2184
|
+
inputTokens: chunk.usage.prompt_tokens,
|
|
2185
|
+
outputTokens: chunk.usage.completion_tokens
|
|
1977
2186
|
});
|
|
2187
|
+
} catch (error) {
|
|
2188
|
+
consola.error("Failed to parse chunk:", error, event.data);
|
|
1978
2189
|
}
|
|
1979
2190
|
}
|
|
1980
2191
|
});
|
|
1981
2192
|
}
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
}
|
|
1986
|
-
const response = await createChatCompletions(
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
outputTokens: response.usage.completion_tokens
|
|
1992
|
-
});
|
|
1993
|
-
const anthropicResponse = translateToAnthropic(response);
|
|
1994
|
-
consola.debug("Translated Anthropic response:", JSON.stringify(anthropicResponse));
|
|
1995
|
-
return c.json(anthropicResponse);
|
|
1996
|
-
}
|
|
1997
|
-
consola.debug("Streaming response from Copilot");
|
|
1998
|
-
return streamSSE(c, async (stream) => {
|
|
1999
|
-
const streamState = {
|
|
2000
|
-
messageStartSent: false,
|
|
2001
|
-
contentBlockIndex: 0,
|
|
2002
|
-
contentBlockOpen: false,
|
|
2003
|
-
toolCalls: {}
|
|
2004
|
-
};
|
|
2005
|
-
for await (const rawEvent of response) {
|
|
2006
|
-
consola.debug("Copilot raw stream event:", JSON.stringify(rawEvent));
|
|
2007
|
-
if (rawEvent.data === "[DONE]") break;
|
|
2008
|
-
if (!rawEvent.data) continue;
|
|
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
|
-
});
|
|
2014
|
-
const events$1 = translateChunkToAnthropicEvents(chunk, streamState);
|
|
2015
|
-
for (const event of events$1) {
|
|
2016
|
-
consola.debug("Translated Anthropic event:", JSON.stringify(event));
|
|
2017
|
-
await stream.writeSSE({
|
|
2018
|
-
event: event.type,
|
|
2019
|
-
data: JSON.stringify(event)
|
|
2020
|
-
});
|
|
2021
|
-
}
|
|
2022
|
-
}
|
|
2193
|
+
const nonStreamPayload = {
|
|
2194
|
+
...openAIPayload,
|
|
2195
|
+
stream: false
|
|
2196
|
+
};
|
|
2197
|
+
const response = isAzureModel ? await createAzureOpenAIChatCompletions(state.azureOpenAIConfig, nonStreamPayload) : await createChatCompletions(nonStreamPayload);
|
|
2198
|
+
consola.debug("Response from upstream:", JSON.stringify(response).slice(-400));
|
|
2199
|
+
if (response.usage) setRequestContext(c, {
|
|
2200
|
+
inputTokens: response.usage.prompt_tokens,
|
|
2201
|
+
outputTokens: response.usage.completion_tokens
|
|
2023
2202
|
});
|
|
2203
|
+
const anthropicResponse = translateToAnthropic(response);
|
|
2204
|
+
consola.debug("Translated Anthropic response:", JSON.stringify(anthropicResponse));
|
|
2205
|
+
return c.json(anthropicResponse);
|
|
2024
2206
|
}
|
|
2025
|
-
const isNonStreaming = (response) => Object.hasOwn(response, "choices");
|
|
2026
2207
|
|
|
2027
2208
|
//#endregion
|
|
2028
2209
|
//#region src/routes/messages/route.ts
|
|
@@ -2089,7 +2270,7 @@ replacementsRoute.get("/", async (c) => {
|
|
|
2089
2270
|
replacementsRoute.post("/", async (c) => {
|
|
2090
2271
|
const body = await c.req.json();
|
|
2091
2272
|
if (!body.pattern) return c.json({ error: "Pattern is required" }, 400);
|
|
2092
|
-
const rule = await addReplacement(body.pattern, body.replacement ?? "", body.isRegex ?? false);
|
|
2273
|
+
const rule = await addReplacement(body.pattern, body.replacement ?? "", body.isRegex ?? false, body.name);
|
|
2093
2274
|
return c.json(rule, 201);
|
|
2094
2275
|
});
|
|
2095
2276
|
replacementsRoute.delete("/:id", async (c) => {
|
|
@@ -2097,6 +2278,13 @@ replacementsRoute.delete("/:id", async (c) => {
|
|
|
2097
2278
|
if (!await removeReplacement(id)) return c.json({ error: "Replacement not found or is a system rule" }, 404);
|
|
2098
2279
|
return c.json({ success: true });
|
|
2099
2280
|
});
|
|
2281
|
+
replacementsRoute.patch("/:id", async (c) => {
|
|
2282
|
+
const id = c.req.param("id");
|
|
2283
|
+
const body = await c.req.json();
|
|
2284
|
+
const rule = await updateReplacement(id, body);
|
|
2285
|
+
if (!rule) return c.json({ error: "Replacement not found or is a system rule" }, 404);
|
|
2286
|
+
return c.json(rule);
|
|
2287
|
+
});
|
|
2100
2288
|
replacementsRoute.patch("/:id/toggle", async (c) => {
|
|
2101
2289
|
const id = c.req.param("id");
|
|
2102
2290
|
const rule = await toggleReplacement(id);
|
|
@@ -2171,6 +2359,8 @@ async function runServer(options) {
|
|
|
2171
2359
|
state.rateLimitSeconds = options.rateLimit;
|
|
2172
2360
|
state.rateLimitWait = options.rateLimitWait;
|
|
2173
2361
|
state.showToken = options.showToken;
|
|
2362
|
+
state.debug = options.debug;
|
|
2363
|
+
if (options.debug) consola.info("Debug mode enabled - raw HTTP requests will be logged");
|
|
2174
2364
|
await ensurePaths();
|
|
2175
2365
|
await cacheVSCodeVersion();
|
|
2176
2366
|
if (options.githubToken) {
|
|
@@ -2284,6 +2474,12 @@ const start = defineCommand({
|
|
|
2284
2474
|
type: "boolean",
|
|
2285
2475
|
default: false,
|
|
2286
2476
|
description: "Disable SSL certificate verification (for corporate proxies with self-signed certs)"
|
|
2477
|
+
},
|
|
2478
|
+
debug: {
|
|
2479
|
+
alias: "d",
|
|
2480
|
+
type: "boolean",
|
|
2481
|
+
default: false,
|
|
2482
|
+
description: "Log raw HTTP requests received by the server (headers, method, path)"
|
|
2287
2483
|
}
|
|
2288
2484
|
},
|
|
2289
2485
|
run({ args }) {
|
|
@@ -2300,7 +2496,8 @@ const start = defineCommand({
|
|
|
2300
2496
|
claudeCode: args["claude-code"],
|
|
2301
2497
|
showToken: args["show-token"],
|
|
2302
2498
|
proxyEnv: args["proxy-env"],
|
|
2303
|
-
insecure: args.insecure
|
|
2499
|
+
insecure: args.insecure,
|
|
2500
|
+
debug: args.debug
|
|
2304
2501
|
});
|
|
2305
2502
|
}
|
|
2306
2503
|
});
|