@ashsec/copilot-api 0.7.4 → 0.7.5
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 +246 -113
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -96,11 +96,16 @@ const GITHUB_APP_SCOPES = ["read:user"].join(" ");
|
|
|
96
96
|
//#region src/lib/error.ts
|
|
97
97
|
var HTTPError = class extends Error {
|
|
98
98
|
response;
|
|
99
|
-
|
|
99
|
+
requestPayload;
|
|
100
|
+
constructor(message, response, requestPayload) {
|
|
100
101
|
super(message);
|
|
101
102
|
this.response = response;
|
|
103
|
+
this.requestPayload = requestPayload;
|
|
102
104
|
}
|
|
103
105
|
};
|
|
106
|
+
function isContentFilterError(obj) {
|
|
107
|
+
return typeof obj === "object" && obj !== null && "error" in obj && typeof obj.error === "object" && obj.error?.code === "content_filter";
|
|
108
|
+
}
|
|
104
109
|
async function forwardError(c, error) {
|
|
105
110
|
consola.error("Error occurred:", error);
|
|
106
111
|
if (error instanceof HTTPError) {
|
|
@@ -112,6 +117,15 @@ async function forwardError(c, error) {
|
|
|
112
117
|
errorJson = errorText;
|
|
113
118
|
}
|
|
114
119
|
consola.error("HTTP error:", errorJson);
|
|
120
|
+
if (isContentFilterError(errorJson)) {
|
|
121
|
+
consola.box("CONTENT FILTER TRIGGERED");
|
|
122
|
+
consola.error("Full error response:");
|
|
123
|
+
console.log(JSON.stringify(errorJson, null, 2));
|
|
124
|
+
if (error.requestPayload) {
|
|
125
|
+
consola.error("Request payload that triggered the filter:");
|
|
126
|
+
console.log(JSON.stringify(error.requestPayload, null, 2));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
115
129
|
return c.json({ error: {
|
|
116
130
|
message: errorText,
|
|
117
131
|
type: "error"
|
|
@@ -209,8 +223,11 @@ function getAzureDeploymentName(modelId) {
|
|
|
209
223
|
|
|
210
224
|
//#endregion
|
|
211
225
|
//#region src/lib/retry-fetch.ts
|
|
212
|
-
const
|
|
213
|
-
|
|
226
|
+
const RETRY_DELAYS_MS = [
|
|
227
|
+
100,
|
|
228
|
+
200,
|
|
229
|
+
300
|
|
230
|
+
];
|
|
214
231
|
/**
|
|
215
232
|
* Check if an error is retryable (transient network error)
|
|
216
233
|
*/
|
|
@@ -224,6 +241,7 @@ function isRetryableError(error) {
|
|
|
224
241
|
"connection reset",
|
|
225
242
|
"econnreset",
|
|
226
243
|
"socket hang up",
|
|
244
|
+
"socket connection was closed unexpectedly",
|
|
227
245
|
"etimedout",
|
|
228
246
|
"econnrefused",
|
|
229
247
|
"network error",
|
|
@@ -238,13 +256,14 @@ function isRetryableStatus(status) {
|
|
|
238
256
|
return status === 408 || status === 429 || status >= 500 && status <= 599;
|
|
239
257
|
}
|
|
240
258
|
/**
|
|
241
|
-
* Fetch with automatic retry on transient failures
|
|
259
|
+
* Fetch with automatic fast retry on transient failures
|
|
260
|
+
* Retries with delays: 100ms, 200ms, 300ms (max ~600ms total wait)
|
|
242
261
|
*/
|
|
243
|
-
async function fetchWithRetry(input, init
|
|
244
|
-
const
|
|
262
|
+
async function fetchWithRetry(input, init) {
|
|
263
|
+
const maxAttempts = RETRY_DELAYS_MS.length + 1;
|
|
245
264
|
let lastError;
|
|
246
265
|
let lastResponse;
|
|
247
|
-
for (let attempt = 0; attempt
|
|
266
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) try {
|
|
248
267
|
const headers = new Headers(init?.headers);
|
|
249
268
|
headers.set("Connection", "close");
|
|
250
269
|
const response = await fetch(input, {
|
|
@@ -252,19 +271,19 @@ async function fetchWithRetry(input, init, options = {}) {
|
|
|
252
271
|
headers,
|
|
253
272
|
keepalive: false
|
|
254
273
|
});
|
|
255
|
-
if (isRetryableStatus(response.status) && attempt <
|
|
274
|
+
if (isRetryableStatus(response.status) && attempt < maxAttempts - 1) {
|
|
256
275
|
lastResponse = response;
|
|
257
|
-
const delayMs =
|
|
258
|
-
consola.warn(`HTTP ${response.status} (attempt ${attempt + 1}/${
|
|
276
|
+
const delayMs = RETRY_DELAYS_MS[attempt];
|
|
277
|
+
consola.warn(`HTTP ${response.status} (attempt ${attempt + 1}/${maxAttempts}), retrying in ${delayMs}ms`);
|
|
259
278
|
await sleep(delayMs);
|
|
260
279
|
continue;
|
|
261
280
|
}
|
|
262
281
|
return response;
|
|
263
282
|
} catch (error) {
|
|
264
283
|
lastError = error;
|
|
265
|
-
if (!isRetryableError(error) || attempt ===
|
|
266
|
-
const delayMs =
|
|
267
|
-
consola.warn(`Fetch failed (attempt ${attempt + 1}/${
|
|
284
|
+
if (!isRetryableError(error) || attempt === maxAttempts - 1) throw error;
|
|
285
|
+
const delayMs = RETRY_DELAYS_MS[attempt];
|
|
286
|
+
consola.warn(`Fetch failed (attempt ${attempt + 1}/${maxAttempts}), retrying in ${delayMs}ms:`, lastError.message);
|
|
268
287
|
await sleep(delayMs);
|
|
269
288
|
}
|
|
270
289
|
if (lastResponse) return lastResponse;
|
|
@@ -292,7 +311,7 @@ async function createAzureOpenAIChatCompletions(config$1, payload) {
|
|
|
292
311
|
});
|
|
293
312
|
if (!response.ok) {
|
|
294
313
|
consola.error("Failed to create Azure OpenAI chat completions:", response);
|
|
295
|
-
throw new HTTPError("Failed to create Azure OpenAI chat completions", response);
|
|
314
|
+
throw new HTTPError("Failed to create Azure OpenAI chat completions", response, payload);
|
|
296
315
|
}
|
|
297
316
|
if (payload.stream) return events(response);
|
|
298
317
|
return await response.json();
|
|
@@ -578,7 +597,8 @@ const checkUsage = defineCommand({
|
|
|
578
597
|
//#region src/lib/auto-replace.ts
|
|
579
598
|
const SYSTEM_REPLACEMENTS = [{
|
|
580
599
|
id: "system-anthropic-billing",
|
|
581
|
-
|
|
600
|
+
name: "Remove Anthropic billing header",
|
|
601
|
+
pattern: "x-anthropic-billing-header:[^\\n]*\\n?",
|
|
582
602
|
replacement: "",
|
|
583
603
|
isRegex: true,
|
|
584
604
|
enabled: true,
|
|
@@ -592,7 +612,7 @@ let isLoaded = false;
|
|
|
592
612
|
async function loadReplacements() {
|
|
593
613
|
try {
|
|
594
614
|
const data = await fs.readFile(PATHS.REPLACEMENTS_CONFIG_PATH);
|
|
595
|
-
userReplacements = JSON.parse(data).filter((r) => !r.isSystem);
|
|
615
|
+
userReplacements = JSON.parse(data.toString()).filter((r) => !r.isSystem);
|
|
596
616
|
isLoaded = true;
|
|
597
617
|
consola.debug(`Loaded ${userReplacements.length} user replacement rules`);
|
|
598
618
|
} catch {
|
|
@@ -635,10 +655,11 @@ async function getUserReplacements() {
|
|
|
635
655
|
/**
|
|
636
656
|
* Add a new user replacement rule
|
|
637
657
|
*/
|
|
638
|
-
async function addReplacement(pattern, replacement, isRegex = false) {
|
|
658
|
+
async function addReplacement(pattern, replacement, isRegex = false, name) {
|
|
639
659
|
await ensureLoaded();
|
|
640
660
|
const rule = {
|
|
641
661
|
id: `user-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
662
|
+
name,
|
|
642
663
|
pattern,
|
|
643
664
|
replacement,
|
|
644
665
|
isRegex,
|
|
@@ -667,6 +688,26 @@ async function removeReplacement(id) {
|
|
|
667
688
|
return true;
|
|
668
689
|
}
|
|
669
690
|
/**
|
|
691
|
+
* Update an existing user replacement rule
|
|
692
|
+
*/
|
|
693
|
+
async function updateReplacement(id, updates) {
|
|
694
|
+
await ensureLoaded();
|
|
695
|
+
const rule = userReplacements.find((r) => r.id === id);
|
|
696
|
+
if (!rule) return null;
|
|
697
|
+
if (rule.isSystem) {
|
|
698
|
+
consola.warn("Cannot update system replacement rule");
|
|
699
|
+
return null;
|
|
700
|
+
}
|
|
701
|
+
if (updates.name !== void 0) rule.name = updates.name;
|
|
702
|
+
if (updates.pattern !== void 0) rule.pattern = updates.pattern;
|
|
703
|
+
if (updates.replacement !== void 0) rule.replacement = updates.replacement;
|
|
704
|
+
if (updates.isRegex !== void 0) rule.isRegex = updates.isRegex;
|
|
705
|
+
if (updates.enabled !== void 0) rule.enabled = updates.enabled;
|
|
706
|
+
await saveReplacements();
|
|
707
|
+
consola.info(`Updated replacement rule: ${rule.name || rule.id}`);
|
|
708
|
+
return rule;
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
670
711
|
* Toggle a replacement rule on/off
|
|
671
712
|
*/
|
|
672
713
|
async function toggleReplacement(id) {
|
|
@@ -693,18 +734,32 @@ async function clearUserReplacements() {
|
|
|
693
734
|
consola.info("Cleared all user replacement rules");
|
|
694
735
|
}
|
|
695
736
|
/**
|
|
696
|
-
* Apply a single replacement rule to text
|
|
737
|
+
* Apply a single replacement rule to text and return info about whether it matched
|
|
697
738
|
*/
|
|
698
739
|
function applyRule(text, rule) {
|
|
699
|
-
if (!rule.enabled) return
|
|
740
|
+
if (!rule.enabled) return {
|
|
741
|
+
result: text,
|
|
742
|
+
matched: false
|
|
743
|
+
};
|
|
700
744
|
if (rule.isRegex) try {
|
|
701
745
|
const regex = new RegExp(rule.pattern, "g");
|
|
702
|
-
|
|
746
|
+
const result$1 = text.replace(regex, rule.replacement);
|
|
747
|
+
return {
|
|
748
|
+
result: result$1,
|
|
749
|
+
matched: result$1 !== text
|
|
750
|
+
};
|
|
703
751
|
} catch {
|
|
704
752
|
consola.warn(`Invalid regex pattern in rule ${rule.id}: ${rule.pattern}`);
|
|
705
|
-
return
|
|
753
|
+
return {
|
|
754
|
+
result: text,
|
|
755
|
+
matched: false
|
|
756
|
+
};
|
|
706
757
|
}
|
|
707
|
-
|
|
758
|
+
const result = text.split(rule.pattern).join(rule.replacement);
|
|
759
|
+
return {
|
|
760
|
+
result,
|
|
761
|
+
matched: result !== text
|
|
762
|
+
};
|
|
708
763
|
}
|
|
709
764
|
/**
|
|
710
765
|
* Apply all replacement rules to text
|
|
@@ -712,11 +767,15 @@ function applyRule(text, rule) {
|
|
|
712
767
|
async function applyReplacements(text) {
|
|
713
768
|
let result = text;
|
|
714
769
|
const allRules = await getAllReplacements();
|
|
770
|
+
const appliedRules = [];
|
|
715
771
|
for (const rule of allRules) {
|
|
716
|
-
const
|
|
717
|
-
|
|
718
|
-
|
|
772
|
+
const { result: newResult, matched } = applyRule(result, rule);
|
|
773
|
+
if (matched) {
|
|
774
|
+
result = newResult;
|
|
775
|
+
appliedRules.push(rule.name || rule.id);
|
|
776
|
+
}
|
|
719
777
|
}
|
|
778
|
+
if (appliedRules.length > 0) consola.info(`Replacements applied: ${appliedRules.join(", ")}`);
|
|
720
779
|
return result;
|
|
721
780
|
}
|
|
722
781
|
/**
|
|
@@ -753,8 +812,9 @@ function formatRule(rule, index) {
|
|
|
753
812
|
const status = rule.enabled ? "✓" : "✗";
|
|
754
813
|
const type = rule.isRegex ? "regex" : "string";
|
|
755
814
|
const system = rule.isSystem ? " [system]" : "";
|
|
815
|
+
const name = rule.name ? ` "${rule.name}"` : "";
|
|
756
816
|
const replacement = rule.replacement || "(empty)";
|
|
757
|
-
return `${index + 1}. [${status}] (${type})${system} "${rule.pattern}" → "${replacement}"`;
|
|
817
|
+
return `${index + 1}. [${status}] (${type})${system}${name} "${rule.pattern}" → "${replacement}"`;
|
|
758
818
|
}
|
|
759
819
|
async function listReplacements() {
|
|
760
820
|
const all = await getAllReplacements();
|
|
@@ -767,6 +827,14 @@ async function listReplacements() {
|
|
|
767
827
|
console.log();
|
|
768
828
|
}
|
|
769
829
|
async function addNewReplacement() {
|
|
830
|
+
const name = await consola.prompt("Name (optional, short description):", {
|
|
831
|
+
type: "text",
|
|
832
|
+
default: ""
|
|
833
|
+
});
|
|
834
|
+
if (typeof name === "symbol") {
|
|
835
|
+
consola.info("Cancelled.");
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
770
838
|
const matchType = await consola.prompt("Match type:", {
|
|
771
839
|
type: "select",
|
|
772
840
|
options: [{
|
|
@@ -800,8 +868,87 @@ async function addNewReplacement() {
|
|
|
800
868
|
consola.info("Cancelled.");
|
|
801
869
|
return;
|
|
802
870
|
}
|
|
803
|
-
const rule = await addReplacement(pattern, replacement, matchType === "regex");
|
|
804
|
-
consola.success(`Added rule: ${rule.id}`);
|
|
871
|
+
const rule = await addReplacement(pattern, replacement, matchType === "regex", name || void 0);
|
|
872
|
+
consola.success(`Added rule: ${rule.name || rule.id}`);
|
|
873
|
+
}
|
|
874
|
+
async function editExistingReplacement() {
|
|
875
|
+
const userRules = await getUserReplacements();
|
|
876
|
+
if (userRules.length === 0) {
|
|
877
|
+
consola.info("No user rules to edit.");
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
const options = userRules.map((rule$1, i) => ({
|
|
881
|
+
label: formatRule(rule$1, i),
|
|
882
|
+
value: rule$1.id
|
|
883
|
+
}));
|
|
884
|
+
const selected = await consola.prompt("Select rule to edit:", {
|
|
885
|
+
type: "select",
|
|
886
|
+
options
|
|
887
|
+
});
|
|
888
|
+
if (typeof selected === "symbol") {
|
|
889
|
+
consola.info("Cancelled.");
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
const rule = userRules.find((r) => r.id === selected);
|
|
893
|
+
if (!rule) {
|
|
894
|
+
consola.error("Rule not found.");
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
consola.info(`\nEditing rule: ${rule.name || rule.id}`);
|
|
898
|
+
consola.info("Press Enter to keep current value.\n");
|
|
899
|
+
const name = await consola.prompt("Name:", {
|
|
900
|
+
type: "text",
|
|
901
|
+
default: rule.name || ""
|
|
902
|
+
});
|
|
903
|
+
if (typeof name === "symbol") {
|
|
904
|
+
consola.info("Cancelled.");
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
const matchType = await consola.prompt("Match type:", {
|
|
908
|
+
type: "select",
|
|
909
|
+
options: [{
|
|
910
|
+
label: "String (exact match)",
|
|
911
|
+
value: "string"
|
|
912
|
+
}, {
|
|
913
|
+
label: "Regex (regular expression)",
|
|
914
|
+
value: "regex"
|
|
915
|
+
}],
|
|
916
|
+
initial: rule.isRegex ? "regex" : "string"
|
|
917
|
+
});
|
|
918
|
+
if (typeof matchType === "symbol") {
|
|
919
|
+
consola.info("Cancelled.");
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
const pattern = await consola.prompt("Pattern to match:", {
|
|
923
|
+
type: "text",
|
|
924
|
+
default: rule.pattern
|
|
925
|
+
});
|
|
926
|
+
if (typeof pattern === "symbol" || !pattern) {
|
|
927
|
+
consola.info("Cancelled.");
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
if (matchType === "regex") try {
|
|
931
|
+
new RegExp(pattern);
|
|
932
|
+
} catch {
|
|
933
|
+
consola.error(`Invalid regex pattern: ${pattern}`);
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
const replacement = await consola.prompt("Replacement text:", {
|
|
937
|
+
type: "text",
|
|
938
|
+
default: rule.replacement
|
|
939
|
+
});
|
|
940
|
+
if (typeof replacement === "symbol") {
|
|
941
|
+
consola.info("Cancelled.");
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
const updated = await updateReplacement(selected, {
|
|
945
|
+
name: name || void 0,
|
|
946
|
+
pattern,
|
|
947
|
+
replacement,
|
|
948
|
+
isRegex: matchType === "regex"
|
|
949
|
+
});
|
|
950
|
+
if (updated) consola.success(`Updated rule: ${updated.name || updated.id}`);
|
|
951
|
+
else consola.error("Failed to update rule.");
|
|
805
952
|
}
|
|
806
953
|
async function removeExistingReplacement() {
|
|
807
954
|
const userRules = await getUserReplacements();
|
|
@@ -884,6 +1031,10 @@ async function mainMenu() {
|
|
|
884
1031
|
label: "➕ Add new rule",
|
|
885
1032
|
value: "add"
|
|
886
1033
|
},
|
|
1034
|
+
{
|
|
1035
|
+
label: "✏️ Edit rule",
|
|
1036
|
+
value: "edit"
|
|
1037
|
+
},
|
|
887
1038
|
{
|
|
888
1039
|
label: "➖ Remove rule",
|
|
889
1040
|
value: "remove"
|
|
@@ -914,6 +1065,9 @@ async function mainMenu() {
|
|
|
914
1065
|
case "add":
|
|
915
1066
|
await addNewReplacement();
|
|
916
1067
|
break;
|
|
1068
|
+
case "edit":
|
|
1069
|
+
await editExistingReplacement();
|
|
1070
|
+
break;
|
|
917
1071
|
case "remove":
|
|
918
1072
|
await removeExistingReplacement();
|
|
919
1073
|
break;
|
|
@@ -1463,7 +1617,7 @@ async function handleCompletion$1(c) {
|
|
|
1463
1617
|
});
|
|
1464
1618
|
if (state.manualApprove) await awaitApproval();
|
|
1465
1619
|
const response$1 = await createAzureOpenAIChatCompletions(state.azureOpenAIConfig, payload);
|
|
1466
|
-
if (isNonStreaming
|
|
1620
|
+
if (isNonStreaming(response$1)) {
|
|
1467
1621
|
consola.debug("Non-streaming response:", JSON.stringify(response$1));
|
|
1468
1622
|
if (response$1.usage) setRequestContext(c, {
|
|
1469
1623
|
inputTokens: response$1.usage.prompt_tokens,
|
|
@@ -1494,7 +1648,7 @@ async function handleCompletion$1(c) {
|
|
|
1494
1648
|
try {
|
|
1495
1649
|
if (selectedModel) {
|
|
1496
1650
|
const tokenCount = await getTokenCount(payload, selectedModel);
|
|
1497
|
-
setRequestContext(c, { inputTokens: tokenCount });
|
|
1651
|
+
setRequestContext(c, { inputTokens: tokenCount.input });
|
|
1498
1652
|
}
|
|
1499
1653
|
} catch (error) {
|
|
1500
1654
|
consola.warn("Failed to calculate token count:", error);
|
|
@@ -1508,7 +1662,7 @@ async function handleCompletion$1(c) {
|
|
|
1508
1662
|
consola.debug("Set max_tokens to:", JSON.stringify(payload.max_tokens));
|
|
1509
1663
|
}
|
|
1510
1664
|
const response = await createChatCompletions(payload);
|
|
1511
|
-
if (isNonStreaming
|
|
1665
|
+
if (isNonStreaming(response)) {
|
|
1512
1666
|
consola.debug("Non-streaming response:", JSON.stringify(response));
|
|
1513
1667
|
if (response.usage) setRequestContext(c, {
|
|
1514
1668
|
inputTokens: response.usage.prompt_tokens,
|
|
@@ -1531,7 +1685,7 @@ async function handleCompletion$1(c) {
|
|
|
1531
1685
|
}
|
|
1532
1686
|
});
|
|
1533
1687
|
}
|
|
1534
|
-
const isNonStreaming
|
|
1688
|
+
const isNonStreaming = (response) => Object.hasOwn(response, "choices");
|
|
1535
1689
|
|
|
1536
1690
|
//#endregion
|
|
1537
1691
|
//#region src/routes/chat-completions/route.ts
|
|
@@ -1821,12 +1975,7 @@ function translateChunkToAnthropicEvents(chunk, state$1) {
|
|
|
1821
1975
|
content: [],
|
|
1822
1976
|
model: chunk.model,
|
|
1823
1977
|
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
|
-
}
|
|
1978
|
+
stop_sequence: null
|
|
1830
1979
|
}
|
|
1831
1980
|
});
|
|
1832
1981
|
state$1.messageStartSent = true;
|
|
@@ -1908,6 +2057,9 @@ function translateChunkToAnthropicEvents(chunk, state$1) {
|
|
|
1908
2057
|
});
|
|
1909
2058
|
state$1.contentBlockOpen = false;
|
|
1910
2059
|
}
|
|
2060
|
+
const inputTokens = chunk.usage?.prompt_tokens ?? 0;
|
|
2061
|
+
const outputTokens = chunk.usage?.completion_tokens ?? 0;
|
|
2062
|
+
const cachedTokens = chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0;
|
|
1911
2063
|
events$1.push({
|
|
1912
2064
|
type: "message_delta",
|
|
1913
2065
|
delta: {
|
|
@@ -1915,9 +2067,10 @@ function translateChunkToAnthropicEvents(chunk, state$1) {
|
|
|
1915
2067
|
stop_sequence: null
|
|
1916
2068
|
},
|
|
1917
2069
|
usage: {
|
|
1918
|
-
input_tokens:
|
|
1919
|
-
output_tokens:
|
|
1920
|
-
|
|
2070
|
+
input_tokens: inputTokens,
|
|
2071
|
+
output_tokens: outputTokens,
|
|
2072
|
+
cache_creation_input_tokens: 0,
|
|
2073
|
+
cache_read_input_tokens: cachedTokens
|
|
1921
2074
|
}
|
|
1922
2075
|
}, { type: "message_stop" });
|
|
1923
2076
|
}
|
|
@@ -1934,95 +2087,68 @@ async function handleCompletion(c) {
|
|
|
1934
2087
|
const openAIPayload = await applyReplacementsToPayload(translatedPayload);
|
|
1935
2088
|
consola.debug("Translated OpenAI request payload:", JSON.stringify(openAIPayload));
|
|
1936
2089
|
if (state.manualApprove) await awaitApproval();
|
|
1937
|
-
|
|
2090
|
+
const isAzureModel = isAzureOpenAIModel(openAIPayload.model);
|
|
2091
|
+
if (isAzureModel) {
|
|
1938
2092
|
if (!state.azureOpenAIConfig) return c.json({ error: "Azure OpenAI not configured" }, 500);
|
|
1939
2093
|
setRequestContext(c, {
|
|
1940
2094
|
provider: "Azure OpenAI",
|
|
1941
2095
|
model: openAIPayload.model
|
|
1942
2096
|
});
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
consola.debug("Streaming response from Azure OpenAI");
|
|
2097
|
+
} else setRequestContext(c, {
|
|
2098
|
+
provider: "Copilot",
|
|
2099
|
+
model: openAIPayload.model
|
|
2100
|
+
});
|
|
2101
|
+
if (anthropicPayload.stream) {
|
|
2102
|
+
const streamPayload = {
|
|
2103
|
+
...openAIPayload,
|
|
2104
|
+
stream: true,
|
|
2105
|
+
stream_options: { include_usage: true }
|
|
2106
|
+
};
|
|
2107
|
+
const eventStream = isAzureModel ? await createAzureOpenAIChatCompletions(state.azureOpenAIConfig, streamPayload) : await createChatCompletions(streamPayload);
|
|
1955
2108
|
return streamSSE(c, async (stream) => {
|
|
1956
2109
|
const streamState = {
|
|
1957
2110
|
messageStartSent: false,
|
|
1958
|
-
contentBlockIndex: 0,
|
|
1959
2111
|
contentBlockOpen: false,
|
|
2112
|
+
contentBlockIndex: 0,
|
|
1960
2113
|
toolCalls: {}
|
|
1961
2114
|
};
|
|
1962
|
-
for await (const
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
2115
|
+
for await (const event of eventStream) {
|
|
2116
|
+
if (!event.data || event.data === "[DONE]") continue;
|
|
2117
|
+
try {
|
|
2118
|
+
const chunk = JSON.parse(event.data);
|
|
2119
|
+
consola.debug("OpenAI chunk:", JSON.stringify(chunk));
|
|
2120
|
+
const anthropicEvents = translateChunkToAnthropicEvents(chunk, streamState);
|
|
2121
|
+
for (const anthropicEvent of anthropicEvents) {
|
|
2122
|
+
consola.debug("Anthropic event:", JSON.stringify(anthropicEvent));
|
|
2123
|
+
await stream.writeSSE({
|
|
2124
|
+
event: anthropicEvent.type,
|
|
2125
|
+
data: JSON.stringify(anthropicEvent)
|
|
2126
|
+
});
|
|
2127
|
+
}
|
|
2128
|
+
if (chunk.usage) setRequestContext(c, {
|
|
2129
|
+
inputTokens: chunk.usage.prompt_tokens,
|
|
2130
|
+
outputTokens: chunk.usage.completion_tokens
|
|
1977
2131
|
});
|
|
2132
|
+
} catch (error) {
|
|
2133
|
+
consola.error("Failed to parse chunk:", error, event.data);
|
|
1978
2134
|
}
|
|
1979
2135
|
}
|
|
1980
2136
|
});
|
|
1981
2137
|
}
|
|
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
|
-
}
|
|
2138
|
+
const nonStreamPayload = {
|
|
2139
|
+
...openAIPayload,
|
|
2140
|
+
stream: false
|
|
2141
|
+
};
|
|
2142
|
+
const response = isAzureModel ? await createAzureOpenAIChatCompletions(state.azureOpenAIConfig, nonStreamPayload) : await createChatCompletions(nonStreamPayload);
|
|
2143
|
+
consola.debug("Response from upstream:", JSON.stringify(response).slice(-400));
|
|
2144
|
+
if (response.usage) setRequestContext(c, {
|
|
2145
|
+
inputTokens: response.usage.prompt_tokens,
|
|
2146
|
+
outputTokens: response.usage.completion_tokens
|
|
2023
2147
|
});
|
|
2148
|
+
const anthropicResponse = translateToAnthropic(response);
|
|
2149
|
+
consola.debug("Translated Anthropic response:", JSON.stringify(anthropicResponse));
|
|
2150
|
+
return c.json(anthropicResponse);
|
|
2024
2151
|
}
|
|
2025
|
-
const isNonStreaming = (response) => Object.hasOwn(response, "choices");
|
|
2026
2152
|
|
|
2027
2153
|
//#endregion
|
|
2028
2154
|
//#region src/routes/messages/route.ts
|
|
@@ -2089,7 +2215,7 @@ replacementsRoute.get("/", async (c) => {
|
|
|
2089
2215
|
replacementsRoute.post("/", async (c) => {
|
|
2090
2216
|
const body = await c.req.json();
|
|
2091
2217
|
if (!body.pattern) return c.json({ error: "Pattern is required" }, 400);
|
|
2092
|
-
const rule = await addReplacement(body.pattern, body.replacement ?? "", body.isRegex ?? false);
|
|
2218
|
+
const rule = await addReplacement(body.pattern, body.replacement ?? "", body.isRegex ?? false, body.name);
|
|
2093
2219
|
return c.json(rule, 201);
|
|
2094
2220
|
});
|
|
2095
2221
|
replacementsRoute.delete("/:id", async (c) => {
|
|
@@ -2097,6 +2223,13 @@ replacementsRoute.delete("/:id", async (c) => {
|
|
|
2097
2223
|
if (!await removeReplacement(id)) return c.json({ error: "Replacement not found or is a system rule" }, 404);
|
|
2098
2224
|
return c.json({ success: true });
|
|
2099
2225
|
});
|
|
2226
|
+
replacementsRoute.patch("/:id", async (c) => {
|
|
2227
|
+
const id = c.req.param("id");
|
|
2228
|
+
const body = await c.req.json();
|
|
2229
|
+
const rule = await updateReplacement(id, body);
|
|
2230
|
+
if (!rule) return c.json({ error: "Replacement not found or is a system rule" }, 404);
|
|
2231
|
+
return c.json(rule);
|
|
2232
|
+
});
|
|
2100
2233
|
replacementsRoute.patch("/:id/toggle", async (c) => {
|
|
2101
2234
|
const id = c.req.param("id");
|
|
2102
2235
|
const rule = await toggleReplacement(id);
|