@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 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
- constructor(message, response) {
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 DEFAULT_MAX_RETRIES = 3;
213
- const DEFAULT_BASE_DELAY_MS = 1e3;
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, options = {}) {
244
- const { maxRetries = DEFAULT_MAX_RETRIES, baseDelayMs = DEFAULT_BASE_DELAY_MS } = options;
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 <= maxRetries; attempt++) try {
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 < maxRetries) {
274
+ if (isRetryableStatus(response.status) && attempt < maxAttempts - 1) {
256
275
  lastResponse = response;
257
- const delayMs = baseDelayMs * 2 ** attempt;
258
- consola.warn(`HTTP ${response.status} (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${delayMs}ms`);
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 === 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);
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
- pattern: "x-anthropic-billing-header:[^\n]*\n?",
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 text;
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
- return text.replace(regex, rule.replacement);
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 text;
753
+ return {
754
+ result: text,
755
+ matched: false
756
+ };
706
757
  }
707
- return text.split(rule.pattern).join(rule.replacement);
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 before = result;
717
- result = applyRule(result, rule);
718
- if (before !== result) consola.debug(`Applied replacement rule: ${rule.id}`);
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$1(response$1)) {
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$1(response)) {
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$1 = (response) => Object.hasOwn(response, "choices");
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: (chunk.usage?.prompt_tokens ?? 0) - (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0),
1919
- output_tokens: chunk.usage?.completion_tokens ?? 0,
1920
- ...chunk.usage?.prompt_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: chunk.usage.prompt_tokens_details.cached_tokens }
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
- if (isAzureOpenAIModel(openAIPayload.model)) {
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
- const response$1 = await createAzureOpenAIChatCompletions(state.azureOpenAIConfig, openAIPayload);
1944
- if (isNonStreaming(response$1)) {
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
- });
1950
- const anthropicResponse = translateToAnthropic(response$1);
1951
- consola.debug("Translated Anthropic response:", JSON.stringify(anthropicResponse));
1952
- return c.json(anthropicResponse);
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 rawEvent of response$1) {
1963
- consola.debug("Azure OpenAI raw stream event:", JSON.stringify(rawEvent));
1964
- if (rawEvent.data === "[DONE]") break;
1965
- if (!rawEvent.data) continue;
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
- });
1971
- const events$1 = translateChunkToAnthropicEvents(chunk, streamState);
1972
- for (const event of events$1) {
1973
- consola.debug("Translated Anthropic event:", JSON.stringify(event));
1974
- await stream.writeSSE({
1975
- event: event.type,
1976
- data: JSON.stringify(event)
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
- setRequestContext(c, {
1983
- provider: "Copilot",
1984
- model: openAIPayload.model
1985
- });
1986
- const response = await createChatCompletions(openAIPayload);
1987
- if (isNonStreaming(response)) {
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
- });
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);