@hsupu/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
@@ -3,8 +3,9 @@ import { defineCommand, runMain } from "citty";
3
3
  import consola from "consola";
4
4
  import fs from "node:fs/promises";
5
5
  import os from "node:os";
6
- import path from "node:path";
6
+ import path, { join } from "node:path";
7
7
  import { randomUUID } from "node:crypto";
8
+ import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
8
9
  import clipboard from "clipboardy";
9
10
  import { serve } from "srvx";
10
11
  import invariant from "tiny-invariant";
@@ -136,7 +137,7 @@ function formatRequestTooLargeError() {
136
137
  }
137
138
  };
138
139
  }
139
- async function forwardError(c, error) {
140
+ function forwardError(c, error) {
140
141
  consola.error("Error occurred:", error);
141
142
  if (error instanceof HTTPError) {
142
143
  if (error.status === 413) {
@@ -290,6 +291,24 @@ async function pollAccessToken(deviceCode) {
290
291
  //#region src/lib/token.ts
291
292
  const readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8");
292
293
  const writeGithubToken = (token) => fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token);
294
+ /**
295
+ * Refresh the Copilot token with exponential backoff retry.
296
+ * Returns the new token on success, or null if all retries fail.
297
+ */
298
+ async function refreshCopilotTokenWithRetry(maxRetries = 3) {
299
+ let lastError = null;
300
+ for (let attempt = 0; attempt < maxRetries; attempt++) try {
301
+ const { token } = await getCopilotToken();
302
+ return token;
303
+ } catch (error) {
304
+ lastError = error;
305
+ const delay = Math.min(1e3 * 2 ** attempt, 3e4);
306
+ consola.warn(`Token refresh attempt ${attempt + 1}/${maxRetries} failed, retrying in ${delay}ms`);
307
+ await new Promise((resolve) => setTimeout(resolve, delay));
308
+ }
309
+ consola.error("All token refresh attempts failed:", lastError);
310
+ return null;
311
+ }
293
312
  const setupCopilotToken = async () => {
294
313
  const { token, refresh_in } = await getCopilotToken();
295
314
  state.copilotToken = token;
@@ -298,14 +317,12 @@ const setupCopilotToken = async () => {
298
317
  const refreshInterval = (refresh_in - 60) * 1e3;
299
318
  setInterval(async () => {
300
319
  consola.debug("Refreshing Copilot token");
301
- try {
302
- const { token: token$1 } = await getCopilotToken();
303
- state.copilotToken = token$1;
320
+ const newToken = await refreshCopilotTokenWithRetry();
321
+ if (newToken) {
322
+ state.copilotToken = newToken;
304
323
  consola.debug("Copilot token refreshed");
305
- if (state.showToken) consola.info("Refreshed Copilot token:", token$1);
306
- } catch (error) {
307
- consola.error("Failed to refresh Copilot token (will retry on next interval):", error);
308
- }
324
+ if (state.showToken) consola.info("Refreshed Copilot token:", newToken);
325
+ } else consola.error("Failed to refresh Copilot token after retries, using existing token");
309
326
  }, refreshInterval);
310
327
  };
311
328
  async function setupGitHubToken(options) {
@@ -520,6 +537,167 @@ const logout = defineCommand({
520
537
  }
521
538
  });
522
539
 
540
+ //#endregion
541
+ //#region src/patch-claude.ts
542
+ const ORIGINAL_PATTERN = /function HR\(A\)\{if\(A\.includes\("\[1m\]"\)\)return 1e6;return 200000\}/;
543
+ const PATCHED_PATTERN = /function HR\(A\)\{if\(A\.includes\("\[1m\]"\)\)return 1e6;return \d+\}/;
544
+ /**
545
+ * Search volta tools directory for Claude Code
546
+ */
547
+ function findInVoltaTools(voltaHome) {
548
+ const paths = [];
549
+ const toolsDir = join(voltaHome, "tools", "image", "node");
550
+ if (!existsSync(toolsDir)) return paths;
551
+ try {
552
+ for (const version of readdirSync(toolsDir)) {
553
+ const claudePath = join(toolsDir, version, "lib", "node_modules", "@anthropic-ai", "claude-code", "cli.js");
554
+ if (existsSync(claudePath)) paths.push(claudePath);
555
+ }
556
+ } catch {}
557
+ return paths;
558
+ }
559
+ /**
560
+ * Find Claude Code CLI path by checking common locations
561
+ */
562
+ function findClaudeCodePath() {
563
+ const possiblePaths = [];
564
+ const home = process.env.HOME || "";
565
+ const voltaHome = process.env.VOLTA_HOME || join(home, ".volta");
566
+ if (existsSync(voltaHome)) possiblePaths.push(...findInVoltaTools(voltaHome));
567
+ const npmPrefix = process.env.npm_config_prefix;
568
+ if (npmPrefix) possiblePaths.push(join(npmPrefix, "lib", "node_modules", "@anthropic-ai", "claude-code", "cli.js"));
569
+ const globalPaths = [
570
+ join(home, ".npm-global", "lib", "node_modules"),
571
+ "/usr/local/lib/node_modules",
572
+ "/usr/lib/node_modules"
573
+ ];
574
+ for (const base of globalPaths) possiblePaths.push(join(base, "@anthropic-ai", "claude-code", "cli.js"));
575
+ const bunGlobal = join(home, ".bun", "install", "global");
576
+ if (existsSync(bunGlobal)) possiblePaths.push(join(bunGlobal, "node_modules", "@anthropic-ai", "claude-code", "cli.js"));
577
+ return possiblePaths.find((p) => existsSync(p)) ?? null;
578
+ }
579
+ /**
580
+ * Get current context limit from Claude Code
581
+ */
582
+ function getCurrentLimit(content) {
583
+ const match = content.match(PATCHED_PATTERN);
584
+ if (!match) return null;
585
+ const limitMatch = match[0].match(/return (\d+)\}$/);
586
+ return limitMatch ? Number.parseInt(limitMatch[1], 10) : null;
587
+ }
588
+ /**
589
+ * Patch Claude Code to use a different context limit
590
+ */
591
+ function patchClaudeCode(cliPath, newLimit) {
592
+ const content = readFileSync(cliPath, "utf8");
593
+ if (getCurrentLimit(content) === newLimit) {
594
+ consola.info(`Already patched with limit ${newLimit}`);
595
+ return true;
596
+ }
597
+ const replacement = `function HR(A){if(A.includes("[1m]"))return 1e6;return ${newLimit}}`;
598
+ let newContent;
599
+ if (ORIGINAL_PATTERN.test(content)) newContent = content.replace(ORIGINAL_PATTERN, replacement);
600
+ else if (PATCHED_PATTERN.test(content)) newContent = content.replace(PATCHED_PATTERN, replacement);
601
+ else return false;
602
+ writeFileSync(cliPath, newContent);
603
+ return true;
604
+ }
605
+ /**
606
+ * Restore Claude Code to original 200k limit
607
+ */
608
+ function restoreClaudeCode(cliPath) {
609
+ const content = readFileSync(cliPath, "utf8");
610
+ if (getCurrentLimit(content) === 2e5) {
611
+ consola.info("Already at original 200000 limit");
612
+ return true;
613
+ }
614
+ if (!PATCHED_PATTERN.test(content)) return false;
615
+ const newContent = content.replace(PATCHED_PATTERN, "function HR(A){if(A.includes(\"[1m]\"))return 1e6;return 200000}");
616
+ writeFileSync(cliPath, newContent);
617
+ return true;
618
+ }
619
+ function showStatus(currentLimit) {
620
+ if (currentLimit === null) {
621
+ consola.warn("Could not detect current limit - CLI may have been updated");
622
+ consola.info("Look for the HR function pattern in cli.js");
623
+ } else if (currentLimit === 2e5) consola.info("Status: Original (200k context window)");
624
+ else consola.info(`Status: Patched (${currentLimit} context window)`);
625
+ }
626
+ const patchClaude = defineCommand({
627
+ meta: {
628
+ name: "patch-claude",
629
+ description: "Patch Claude Code's context window limit to match Copilot's limits"
630
+ },
631
+ args: {
632
+ limit: {
633
+ alias: "l",
634
+ type: "string",
635
+ default: "128000",
636
+ description: "Context window limit in tokens (default: 128000 for Copilot)"
637
+ },
638
+ restore: {
639
+ alias: "r",
640
+ type: "boolean",
641
+ default: false,
642
+ description: "Restore original 200k limit"
643
+ },
644
+ path: {
645
+ alias: "p",
646
+ type: "string",
647
+ description: "Path to Claude Code cli.js (auto-detected if not specified)"
648
+ },
649
+ status: {
650
+ alias: "s",
651
+ type: "boolean",
652
+ default: false,
653
+ description: "Show current patch status without modifying"
654
+ }
655
+ },
656
+ run({ args }) {
657
+ const cliPath = args.path || findClaudeCodePath();
658
+ if (!cliPath) {
659
+ consola.error("Could not find Claude Code installation");
660
+ consola.info("Searched in: volta, npm global, bun global");
661
+ consola.info("Use --path to specify the path to cli.js manually");
662
+ process.exit(1);
663
+ }
664
+ if (!existsSync(cliPath)) {
665
+ consola.error(`File not found: ${cliPath}`);
666
+ process.exit(1);
667
+ }
668
+ consola.info(`Claude Code path: ${cliPath}`);
669
+ const content = readFileSync(cliPath, "utf8");
670
+ const currentLimit = getCurrentLimit(content);
671
+ if (args.status) {
672
+ showStatus(currentLimit);
673
+ return;
674
+ }
675
+ if (args.restore) {
676
+ if (restoreClaudeCode(cliPath)) consola.success("Restored to original 200k limit");
677
+ else {
678
+ consola.error("Failed to restore - pattern not found");
679
+ consola.info("Claude Code may have been updated to a new version");
680
+ process.exit(1);
681
+ }
682
+ return;
683
+ }
684
+ const limit = Number.parseInt(args.limit, 10);
685
+ if (Number.isNaN(limit) || limit < 1e3) {
686
+ consola.error("Invalid limit value. Must be a number >= 1000");
687
+ process.exit(1);
688
+ }
689
+ if (patchClaudeCode(cliPath, limit)) {
690
+ consola.success(`Patched context window: 200000 → ${limit}`);
691
+ consola.info("Note: You may need to re-run this after Claude Code updates");
692
+ } else {
693
+ consola.error("Failed to patch - pattern not found");
694
+ consola.info("Claude Code may have been updated to a new version");
695
+ consola.info("Check the cli.js for the HR function pattern");
696
+ process.exit(1);
697
+ }
698
+ }
699
+ });
700
+
523
701
  //#endregion
524
702
  //#region src/lib/history.ts
525
703
  function generateId$1() {
@@ -771,44 +949,74 @@ function exportHistory(format = "json") {
771
949
 
772
950
  //#endregion
773
951
  //#region src/lib/proxy.ts
952
+ /**
953
+ * Custom dispatcher that routes requests through proxies based on environment variables.
954
+ * Extends Agent to properly inherit the Dispatcher interface.
955
+ */
956
+ var ProxyDispatcher = class extends Agent {
957
+ proxies = /* @__PURE__ */ new Map();
958
+ dispatch(options, handler) {
959
+ try {
960
+ const origin = this.getOriginUrl(options.origin);
961
+ const proxyUrl = this.getProxyUrl(origin);
962
+ if (!proxyUrl) {
963
+ consola.debug(`HTTP proxy bypass: ${origin.hostname}`);
964
+ return super.dispatch(options, handler);
965
+ }
966
+ const agent = this.getOrCreateProxyAgent(proxyUrl);
967
+ consola.debug(`HTTP proxy route: ${origin.hostname} via ${this.formatProxyLabel(proxyUrl)}`);
968
+ return agent.dispatch(options, handler);
969
+ } catch {
970
+ return super.dispatch(options, handler);
971
+ }
972
+ }
973
+ getOriginUrl(origin) {
974
+ return typeof origin === "string" ? new URL(origin) : origin;
975
+ }
976
+ getProxyUrl(origin) {
977
+ const raw = getProxyForUrl(origin.toString());
978
+ return raw && raw.length > 0 ? raw : void 0;
979
+ }
980
+ getOrCreateProxyAgent(proxyUrl) {
981
+ let agent = this.proxies.get(proxyUrl);
982
+ if (!agent) {
983
+ agent = new ProxyAgent(proxyUrl);
984
+ this.proxies.set(proxyUrl, agent);
985
+ }
986
+ return agent;
987
+ }
988
+ formatProxyLabel(proxyUrl) {
989
+ try {
990
+ const u = new URL(proxyUrl);
991
+ return `${u.protocol}//${u.host}`;
992
+ } catch {
993
+ return proxyUrl;
994
+ }
995
+ }
996
+ async close() {
997
+ await super.close();
998
+ await Promise.all([...this.proxies.values()].map((p) => p.close()));
999
+ this.proxies.clear();
1000
+ }
1001
+ destroy(errOrCallback, callback) {
1002
+ for (const agent of this.proxies.values()) if (typeof errOrCallback === "function") agent.destroy(errOrCallback);
1003
+ else if (callback) agent.destroy(errOrCallback ?? null, callback);
1004
+ else agent.destroy(errOrCallback ?? null).catch(() => {});
1005
+ this.proxies.clear();
1006
+ if (typeof errOrCallback === "function") {
1007
+ super.destroy(errOrCallback);
1008
+ return;
1009
+ } else if (callback) {
1010
+ super.destroy(errOrCallback ?? null, callback);
1011
+ return;
1012
+ } else return super.destroy(errOrCallback ?? null);
1013
+ }
1014
+ };
774
1015
  function initProxyFromEnv() {
775
1016
  if (typeof Bun !== "undefined") return;
776
1017
  try {
777
- const direct = new Agent();
778
- const proxies = /* @__PURE__ */ new Map();
779
- setGlobalDispatcher({
780
- dispatch(options, handler) {
781
- try {
782
- const origin = typeof options.origin === "string" ? new URL(options.origin) : options.origin;
783
- const raw = getProxyForUrl(origin.toString());
784
- const proxyUrl = raw && raw.length > 0 ? raw : void 0;
785
- if (!proxyUrl) {
786
- consola.debug(`HTTP proxy bypass: ${origin.hostname}`);
787
- return direct.dispatch(options, handler);
788
- }
789
- let agent = proxies.get(proxyUrl);
790
- if (!agent) {
791
- agent = new ProxyAgent(proxyUrl);
792
- proxies.set(proxyUrl, agent);
793
- }
794
- let label = proxyUrl;
795
- try {
796
- const u = new URL(proxyUrl);
797
- label = `${u.protocol}//${u.host}`;
798
- } catch {}
799
- consola.debug(`HTTP proxy route: ${origin.hostname} via ${label}`);
800
- return agent.dispatch(options, handler);
801
- } catch {
802
- return direct.dispatch(options, handler);
803
- }
804
- },
805
- close() {
806
- return direct.close();
807
- },
808
- destroy() {
809
- return direct.destroy();
810
- }
811
- });
1018
+ const dispatcher = new ProxyDispatcher();
1019
+ setGlobalDispatcher(dispatcher);
812
1020
  consola.debug("HTTP proxy configured from environment (per-URL)");
813
1021
  } catch (err) {
814
1022
  consola.debug("Proxy setup skipped:", err);
@@ -893,24 +1101,56 @@ function formatTokens(input, output) {
893
1101
  /**
894
1102
  * Console renderer that shows request lifecycle with apt-get style footer
895
1103
  *
896
- * Log format:
897
- * - Start: [....] METHOD /path model-name
898
- * - Streaming: [<-->] METHOD /path model-name streaming...
899
- * - Complete: [ OK ] METHOD /path 200 1.2s 1.5K/500 model-name
1104
+ * Log format (status prefix first, then timestamp):
1105
+ * - Start: [....] HH:MM:SS METHOD /path model-name
1106
+ * - Streaming: [<-->] HH:MM:SS METHOD /path model-name streaming...
1107
+ * - Complete: [ OK ] HH:MM:SS METHOD /path 200 1.2s 1.5K/500 model-name
1108
+ * - Error: [FAIL] HH:MM:SS METHOD /path 500 1.2s model-name: error message
900
1109
  *
901
1110
  * Features:
902
1111
  * - /history API requests are displayed in gray (dim)
903
1112
  * - Sticky footer shows active request count, updated in-place on the last line
904
1113
  * - Footer disappears when all requests complete
1114
+ * - Intercepts consola output to properly handle footer
905
1115
  */
906
1116
  var ConsoleRenderer = class {
907
1117
  activeRequests = /* @__PURE__ */ new Map();
908
1118
  showActive;
909
1119
  footerVisible = false;
910
1120
  isTTY;
1121
+ originalReporters = [];
911
1122
  constructor(options) {
912
1123
  this.showActive = options?.showActive ?? true;
913
1124
  this.isTTY = process.stdout.isTTY;
1125
+ this.installConsolaReporter();
1126
+ }
1127
+ /**
1128
+ * Install a custom consola reporter that coordinates with footer
1129
+ */
1130
+ installConsolaReporter() {
1131
+ this.originalReporters = [...consola.options.reporters];
1132
+ consola.setReporters([{ log: (logObj) => {
1133
+ this.clearFooterForLog();
1134
+ const message = logObj.args.map((arg) => typeof arg === "string" ? arg : JSON.stringify(arg)).join(" ");
1135
+ const prefix = this.getLogPrefix(logObj.type);
1136
+ if (prefix) process.stdout.write(`${prefix} ${message}\n`);
1137
+ else process.stdout.write(`${message}\n`);
1138
+ this.renderFooter();
1139
+ } }]);
1140
+ }
1141
+ /**
1142
+ * Get log prefix based on log type
1143
+ */
1144
+ getLogPrefix(type) {
1145
+ switch (type) {
1146
+ case "error":
1147
+ case "fatal": return pc.red("✖");
1148
+ case "warn": return pc.yellow("⚠");
1149
+ case "info": return pc.cyan("ℹ");
1150
+ case "success": return pc.green("✔");
1151
+ case "debug": return pc.gray("●");
1152
+ default: return "";
1153
+ }
914
1154
  }
915
1155
  /**
916
1156
  * Get footer text based on active request count
@@ -953,8 +1193,8 @@ var ConsoleRenderer = class {
953
1193
  */
954
1194
  printLog(message, isGray = false) {
955
1195
  this.clearFooterForLog();
956
- if (isGray) consola.log(pc.dim(message));
957
- else consola.log(message);
1196
+ if (isGray) process.stdout.write(pc.dim(message) + "\n");
1197
+ else process.stdout.write(message + "\n");
958
1198
  this.renderFooter();
959
1199
  }
960
1200
  onRequestStart(request) {
@@ -963,7 +1203,7 @@ var ConsoleRenderer = class {
963
1203
  const time = formatTime();
964
1204
  const modelInfo = request.model ? ` ${request.model}` : "";
965
1205
  const queueInfo = request.queuePosition !== void 0 && request.queuePosition > 0 ? ` [q#${request.queuePosition}]` : "";
966
- const message = `${time} [....] ${request.method} ${request.path}${modelInfo}${queueInfo}`;
1206
+ const message = `[....] ${time} ${request.method} ${request.path}${modelInfo}${queueInfo}`;
967
1207
  this.printLog(message, request.isHistoryAccess);
968
1208
  }
969
1209
  }
@@ -974,7 +1214,7 @@ var ConsoleRenderer = class {
974
1214
  if (this.showActive && update.status === "streaming") {
975
1215
  const time = formatTime();
976
1216
  const modelInfo = request.model ? ` ${request.model}` : "";
977
- const message = `${time} [<-->] ${request.method} ${request.path}${modelInfo} streaming...`;
1217
+ const message = `[<-->] ${time} ${request.method} ${request.path}${modelInfo} streaming...`;
978
1218
  this.printLog(message, request.isHistoryAccess);
979
1219
  }
980
1220
  }
@@ -988,7 +1228,7 @@ var ConsoleRenderer = class {
988
1228
  const isError = request.status === "error" || status >= 400;
989
1229
  const prefix = isError ? "[FAIL]" : "[ OK ]";
990
1230
  const tokensPart = tokens ? ` ${tokens}` : "";
991
- let content = `${time} ${prefix} ${request.method} ${request.path} ${status} ${duration}${tokensPart}${modelInfo}`;
1231
+ let content = `${prefix} ${time} ${request.method} ${request.path} ${status} ${duration}${tokensPart}${modelInfo}`;
992
1232
  if (isError) {
993
1233
  const errorInfo = request.error ? `: ${request.error}` : "";
994
1234
  content += errorInfo;
@@ -1001,6 +1241,7 @@ var ConsoleRenderer = class {
1001
1241
  this.footerVisible = false;
1002
1242
  }
1003
1243
  this.activeRequests.clear();
1244
+ if (this.originalReporters.length > 0) consola.setReporters(this.originalReporters);
1004
1245
  }
1005
1246
  };
1006
1247
 
@@ -1392,14 +1633,14 @@ const getTokenCount = async (payload, model) => {
1392
1633
  //#endregion
1393
1634
  //#region src/lib/auto-compact.ts
1394
1635
  const DEFAULT_CONFIG = {
1395
- targetTokens: 1e5,
1396
- safetyMarginPercent: 10
1636
+ targetTokens: 12e4,
1637
+ safetyMarginPercent: 2
1397
1638
  };
1398
1639
  /**
1399
1640
  * Check if payload needs compaction based on model limits.
1400
1641
  * Uses a safety margin to account for token counting differences.
1401
1642
  */
1402
- async function checkNeedsCompaction(payload, model, safetyMarginPercent = 10) {
1643
+ async function checkNeedsCompaction(payload, model, safetyMarginPercent = 2) {
1403
1644
  const currentTokens = (await getTokenCount(payload, model)).input;
1404
1645
  const rawLimit = model.capabilities?.limits?.max_prompt_tokens ?? 128e3;
1405
1646
  const limit = Math.floor(rawLimit * (1 - safetyMarginPercent / 100));
@@ -1442,6 +1683,13 @@ function extractSystemMessages(messages) {
1442
1683
  };
1443
1684
  }
1444
1685
  /**
1686
+ * Extract tool_use ids from assistant messages with tool_calls.
1687
+ */
1688
+ function getToolUseIds(message) {
1689
+ if (message.role === "assistant" && message.tool_calls) return message.tool_calls.map((tc) => tc.id);
1690
+ return [];
1691
+ }
1692
+ /**
1445
1693
  * Find messages to keep from the end to stay under target tokens.
1446
1694
  * Returns the starting index of messages to preserve.
1447
1695
  */
@@ -1456,6 +1704,41 @@ function findPreserveIndex(messages, targetTokens, systemTokens) {
1456
1704
  return 0;
1457
1705
  }
1458
1706
  /**
1707
+ * Filter out orphaned tool_result messages that don't have a matching tool_use
1708
+ * in the preserved message list. This prevents API errors when truncation
1709
+ * separates tool_use/tool_result pairs.
1710
+ */
1711
+ function filterOrphanedToolResults(messages) {
1712
+ const availableToolUseIds = /* @__PURE__ */ new Set();
1713
+ for (const msg of messages) for (const id of getToolUseIds(msg)) availableToolUseIds.add(id);
1714
+ const filteredMessages = [];
1715
+ let removedCount = 0;
1716
+ for (const msg of messages) {
1717
+ if (msg.role === "tool" && msg.tool_call_id && !availableToolUseIds.has(msg.tool_call_id)) {
1718
+ removedCount++;
1719
+ continue;
1720
+ }
1721
+ filteredMessages.push(msg);
1722
+ }
1723
+ if (removedCount > 0) consola.info(`Auto-compact: Removed ${removedCount} orphaned tool_result message(s) without matching tool_use`);
1724
+ return filteredMessages;
1725
+ }
1726
+ /**
1727
+ * Ensure the message list starts with a user message.
1728
+ * If it starts with assistant or tool messages, skip them until we find a user message.
1729
+ * This is required because OpenAI API expects conversations to start with user messages
1730
+ * (after system messages).
1731
+ */
1732
+ function ensureStartsWithUser(messages) {
1733
+ let startIndex = 0;
1734
+ while (startIndex < messages.length) {
1735
+ if (messages[startIndex].role === "user") break;
1736
+ startIndex++;
1737
+ }
1738
+ if (startIndex > 0) consola.info(`Auto-compact: Skipped ${startIndex} leading non-user message(s) to ensure valid sequence`);
1739
+ return messages.slice(startIndex);
1740
+ }
1741
+ /**
1459
1742
  * Calculate estimated tokens for system messages.
1460
1743
  */
1461
1744
  function estimateSystemTokens(systemMessages) {
@@ -1473,6 +1756,7 @@ function createTruncationMarker(removedCount) {
1473
1756
  /**
1474
1757
  * Perform auto-compaction on a payload that exceeds token limits.
1475
1758
  * This uses simple truncation - no LLM calls required.
1759
+ * Uses iterative approach with decreasing target tokens until under limit.
1476
1760
  */
1477
1761
  async function autoCompact(payload, model, config = {}) {
1478
1762
  const cfg = {
@@ -1493,8 +1777,49 @@ async function autoCompact(payload, model, config = {}) {
1493
1777
  const { systemMessages, remainingMessages } = extractSystemMessages(payload.messages);
1494
1778
  const systemTokens = estimateSystemTokens(systemMessages);
1495
1779
  consola.debug(`Auto-compact: ${systemMessages.length} system messages (~${systemTokens} tokens)`);
1496
- const effectiveTarget = Math.min(cfg.targetTokens, limit);
1497
- const preserveIndex = findPreserveIndex(remainingMessages, effectiveTarget, systemTokens);
1780
+ const MAX_ITERATIONS = 5;
1781
+ const MIN_TARGET = 2e4;
1782
+ let currentTarget = Math.min(cfg.targetTokens, limit);
1783
+ let lastResult = null;
1784
+ for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
1785
+ const result = await tryCompactWithTarget({
1786
+ payload,
1787
+ model,
1788
+ systemMessages,
1789
+ remainingMessages,
1790
+ systemTokens,
1791
+ targetTokens: currentTarget,
1792
+ limit,
1793
+ originalTokens
1794
+ });
1795
+ if (!result.wasCompacted) return result;
1796
+ lastResult = result;
1797
+ if (result.compactedTokens <= limit) {
1798
+ consola.info(`Auto-compact: ${originalTokens} → ${result.compactedTokens} tokens (removed ${result.removedMessageCount} messages)`);
1799
+ return result;
1800
+ }
1801
+ consola.warn(`Auto-compact: Still over limit (${result.compactedTokens} > ${limit}), trying more aggressive truncation`);
1802
+ currentTarget = Math.floor(currentTarget * .7);
1803
+ if (currentTarget < MIN_TARGET) {
1804
+ consola.error("Auto-compact: Cannot reduce further, target too low");
1805
+ return result;
1806
+ }
1807
+ }
1808
+ consola.error(`Auto-compact: Exhausted ${MAX_ITERATIONS} iterations, returning best effort`);
1809
+ return lastResult ?? {
1810
+ payload,
1811
+ wasCompacted: false,
1812
+ originalTokens,
1813
+ compactedTokens: originalTokens,
1814
+ removedMessageCount: 0
1815
+ };
1816
+ }
1817
+ /**
1818
+ * Helper to attempt compaction with a specific target token count.
1819
+ */
1820
+ async function tryCompactWithTarget(opts) {
1821
+ const { payload, model, systemMessages, remainingMessages, systemTokens, targetTokens, originalTokens } = opts;
1822
+ const preserveIndex = findPreserveIndex(remainingMessages, targetTokens, systemTokens);
1498
1823
  if (preserveIndex === 0) {
1499
1824
  consola.warn("Auto-compact: Cannot truncate further without losing all conversation history");
1500
1825
  return {
@@ -1506,8 +1831,21 @@ async function autoCompact(payload, model, config = {}) {
1506
1831
  };
1507
1832
  }
1508
1833
  const removedMessages = remainingMessages.slice(0, preserveIndex);
1509
- const preservedMessages = remainingMessages.slice(preserveIndex);
1510
- consola.info(`Auto-compact: Removing ${removedMessages.length} messages, keeping ${preservedMessages.length}`);
1834
+ let preservedMessages = remainingMessages.slice(preserveIndex);
1835
+ preservedMessages = filterOrphanedToolResults(preservedMessages);
1836
+ preservedMessages = ensureStartsWithUser(preservedMessages);
1837
+ preservedMessages = filterOrphanedToolResults(preservedMessages);
1838
+ if (preservedMessages.length === 0) {
1839
+ consola.warn("Auto-compact: All messages were filtered out after cleanup, cannot compact");
1840
+ return {
1841
+ payload,
1842
+ wasCompacted: false,
1843
+ originalTokens,
1844
+ compactedTokens: originalTokens,
1845
+ removedMessageCount: 0
1846
+ };
1847
+ }
1848
+ consola.debug(`Auto-compact: Removing ${removedMessages.length} messages, keeping ${preservedMessages.length}`);
1511
1849
  const truncationMarker = createTruncationMarker(removedMessages.length);
1512
1850
  const newPayload = {
1513
1851
  ...payload,
@@ -1518,25 +1856,6 @@ async function autoCompact(payload, model, config = {}) {
1518
1856
  ]
1519
1857
  };
1520
1858
  const newTokenCount = await getTokenCount(newPayload, model);
1521
- consola.info(`Auto-compact: Reduced from ${originalTokens} to ${newTokenCount.input} tokens`);
1522
- if (newTokenCount.input > limit) {
1523
- consola.warn(`Auto-compact: Still over limit (${newTokenCount.input} > ${limit}), trying more aggressive truncation`);
1524
- const aggressiveTarget = Math.floor(effectiveTarget * .7);
1525
- if (aggressiveTarget < 2e4) {
1526
- consola.error("Auto-compact: Cannot reduce further, target too low");
1527
- return {
1528
- payload: newPayload,
1529
- wasCompacted: true,
1530
- originalTokens,
1531
- compactedTokens: newTokenCount.input,
1532
- removedMessageCount: removedMessages.length
1533
- };
1534
- }
1535
- return autoCompact(payload, model, {
1536
- ...cfg,
1537
- targetTokens: aggressiveTarget
1538
- });
1539
- }
1540
1859
  return {
1541
1860
  payload: newPayload,
1542
1861
  wasCompacted: true,
@@ -1563,11 +1882,12 @@ var RequestQueue = class {
1563
1882
  lastRequestTime = 0;
1564
1883
  async enqueue(execute, rateLimitSeconds) {
1565
1884
  return new Promise((resolve, reject) => {
1566
- this.queue.push({
1885
+ const request = {
1567
1886
  execute,
1568
1887
  resolve,
1569
1888
  reject
1570
- });
1889
+ };
1890
+ this.queue.push(request);
1571
1891
  if (this.queue.length > 1) {
1572
1892
  const position = this.queue.length;
1573
1893
  const waitTime = Math.ceil((position - 1) * rateLimitSeconds);
@@ -1638,16 +1958,149 @@ const createChatCompletions = async (payload) => {
1638
1958
  };
1639
1959
 
1640
1960
  //#endregion
1641
- //#region src/routes/chat-completions/handler.ts
1642
- function getModelMaxOutputTokens(model) {
1643
- return model?.capabilities?.limits?.max_output_tokens;
1961
+ //#region src/routes/shared.ts
1962
+ /** Helper to update tracker model */
1963
+ function updateTrackerModel(trackingId, model) {
1964
+ if (!trackingId) return;
1965
+ const request = requestTracker.getRequest(trackingId);
1966
+ if (request) request.model = model;
1967
+ }
1968
+ /** Helper to update tracker status */
1969
+ function updateTrackerStatus(trackingId, status) {
1970
+ if (!trackingId) return;
1971
+ requestTracker.updateRequest(trackingId, { status });
1644
1972
  }
1973
+ /** Record error response to history */
1974
+ function recordErrorResponse(ctx, model, error) {
1975
+ recordResponse(ctx.historyId, {
1976
+ success: false,
1977
+ model,
1978
+ usage: {
1979
+ input_tokens: 0,
1980
+ output_tokens: 0
1981
+ },
1982
+ error: error instanceof Error ? error.message : "Unknown error",
1983
+ content: null
1984
+ }, Date.now() - ctx.startTime);
1985
+ }
1986
+ /** Complete TUI tracking */
1987
+ function completeTracking(trackingId, inputTokens, outputTokens) {
1988
+ if (!trackingId) return;
1989
+ requestTracker.updateRequest(trackingId, {
1990
+ inputTokens,
1991
+ outputTokens
1992
+ });
1993
+ requestTracker.completeRequest(trackingId, 200, {
1994
+ inputTokens,
1995
+ outputTokens
1996
+ });
1997
+ }
1998
+ /** Fail TUI tracking */
1999
+ function failTracking(trackingId, error) {
2000
+ if (!trackingId) return;
2001
+ requestTracker.failRequest(trackingId, error instanceof Error ? error.message : "Stream error");
2002
+ }
2003
+ /** Record streaming error to history (works with any accumulator type) */
2004
+ function recordStreamError(opts) {
2005
+ const { acc, fallbackModel, ctx, error } = opts;
2006
+ recordResponse(ctx.historyId, {
2007
+ success: false,
2008
+ model: acc.model || fallbackModel,
2009
+ usage: {
2010
+ input_tokens: 0,
2011
+ output_tokens: 0
2012
+ },
2013
+ error: error instanceof Error ? error.message : "Stream error",
2014
+ content: null
2015
+ }, Date.now() - ctx.startTime);
2016
+ }
2017
+ /** Type guard for non-streaming responses */
2018
+ function isNonStreaming(response) {
2019
+ return Object.hasOwn(response, "choices");
2020
+ }
2021
+ /** Build final payload with auto-compact if needed */
2022
+ async function buildFinalPayload(payload, model) {
2023
+ if (!state.autoCompact || !model) {
2024
+ if (state.autoCompact && !model) consola.warn(`Auto-compact: Model '${payload.model}' not found in cached models, skipping`);
2025
+ return {
2026
+ finalPayload: payload,
2027
+ compactResult: null
2028
+ };
2029
+ }
2030
+ try {
2031
+ const check = await checkNeedsCompaction(payload, model);
2032
+ consola.debug(`Auto-compact check: ${check.currentTokens} tokens, limit ${check.limit}, needed: ${check.needed}`);
2033
+ if (!check.needed) return {
2034
+ finalPayload: payload,
2035
+ compactResult: null
2036
+ };
2037
+ consola.info(`Auto-compact triggered: ${check.currentTokens} tokens > ${check.limit} limit`);
2038
+ const compactResult = await autoCompact(payload, model);
2039
+ return {
2040
+ finalPayload: compactResult.payload,
2041
+ compactResult
2042
+ };
2043
+ } catch (error) {
2044
+ consola.warn("Auto-compact failed, proceeding with original payload:", error instanceof Error ? error.message : error);
2045
+ return {
2046
+ finalPayload: payload,
2047
+ compactResult: null
2048
+ };
2049
+ }
2050
+ }
2051
+ /**
2052
+ * Log helpful debugging information when a 413 error occurs.
2053
+ */
2054
+ async function logPayloadSizeInfo(payload, model) {
2055
+ const messageCount = payload.messages.length;
2056
+ const bodySize = JSON.stringify(payload).length;
2057
+ const bodySizeKB = Math.round(bodySize / 1024);
2058
+ let imageCount = 0;
2059
+ let largeMessages = 0;
2060
+ let totalImageSize = 0;
2061
+ for (const msg of payload.messages) {
2062
+ if (Array.isArray(msg.content)) {
2063
+ for (const part of msg.content) if (part.type === "image_url") {
2064
+ imageCount++;
2065
+ if (part.image_url.url.startsWith("data:")) totalImageSize += part.image_url.url.length;
2066
+ }
2067
+ }
2068
+ if ((typeof msg.content === "string" ? msg.content.length : JSON.stringify(msg.content).length) > 5e4) largeMessages++;
2069
+ }
2070
+ consola.info("");
2071
+ consola.info("╭─────────────────────────────────────────────────────────╮");
2072
+ consola.info("│ 413 Request Entity Too Large │");
2073
+ consola.info("╰─────────────────────────────────────────────────────────╯");
2074
+ consola.info("");
2075
+ consola.info(` Request body size: ${bodySizeKB} KB (${bodySize.toLocaleString()} bytes)`);
2076
+ consola.info(` Message count: ${messageCount}`);
2077
+ if (model) try {
2078
+ const tokenCount = await getTokenCount(payload, model);
2079
+ const limit = model.capabilities?.limits?.max_prompt_tokens ?? 128e3;
2080
+ consola.info(` Estimated tokens: ${tokenCount.input.toLocaleString()} / ${limit.toLocaleString()}`);
2081
+ } catch {}
2082
+ if (imageCount > 0) {
2083
+ const imageSizeKB = Math.round(totalImageSize / 1024);
2084
+ consola.info(` Images: ${imageCount} (${imageSizeKB} KB base64 data)`);
2085
+ }
2086
+ if (largeMessages > 0) consola.info(` Large messages (>50KB): ${largeMessages}`);
2087
+ consola.info("");
2088
+ consola.info(" Suggestions:");
2089
+ if (!state.autoCompact) consola.info(" • Enable --auto-compact to automatically truncate history");
2090
+ if (imageCount > 0) consola.info(" • Remove or resize large images in the conversation");
2091
+ consola.info(" • Start a new conversation with /clear or /reset");
2092
+ consola.info(" • Reduce conversation history by deleting old messages");
2093
+ consola.info("");
2094
+ }
2095
+
2096
+ //#endregion
2097
+ //#region src/routes/chat-completions/handler.ts
1645
2098
  async function handleCompletion$1(c) {
1646
2099
  const originalPayload = await c.req.json();
1647
2100
  consola.debug("Request payload:", JSON.stringify(originalPayload).slice(-400));
1648
2101
  const trackingId = c.get("trackingId");
1649
2102
  const startTime = (trackingId ? requestTracker.getRequest(trackingId) : void 0)?.startTime ?? Date.now();
1650
- updateTrackerModel$1(trackingId, originalPayload.model);
2103
+ updateTrackerModel(trackingId, originalPayload.model);
1651
2104
  const ctx = {
1652
2105
  historyId: recordRequest("openai", {
1653
2106
  model: originalPayload.model,
@@ -1665,19 +2118,32 @@ async function handleCompletion$1(c) {
1665
2118
  };
1666
2119
  const selectedModel = state.models?.data.find((model) => model.id === originalPayload.model);
1667
2120
  await logTokenCount(originalPayload, selectedModel);
1668
- const { finalPayload, compactResult } = await buildFinalPayload$1(originalPayload, selectedModel);
2121
+ const { finalPayload, compactResult } = await buildFinalPayload(originalPayload, selectedModel);
1669
2122
  if (compactResult) ctx.compactResult = compactResult;
1670
2123
  const payload = isNullish(finalPayload.max_tokens) ? {
1671
2124
  ...finalPayload,
1672
- max_tokens: getModelMaxOutputTokens(selectedModel)
2125
+ max_tokens: selectedModel?.capabilities?.limits?.max_output_tokens
1673
2126
  } : finalPayload;
1674
2127
  if (isNullish(originalPayload.max_tokens)) consola.debug("Set max_tokens to:", JSON.stringify(payload.max_tokens));
1675
2128
  if (state.manualApprove) await awaitApproval();
2129
+ return executeRequest({
2130
+ c,
2131
+ payload,
2132
+ selectedModel,
2133
+ ctx,
2134
+ trackingId
2135
+ });
2136
+ }
2137
+ /**
2138
+ * Execute the API call with enhanced error handling for 413 errors.
2139
+ */
2140
+ async function executeRequest(opts) {
2141
+ const { c, payload, selectedModel, ctx, trackingId } = opts;
1676
2142
  try {
1677
2143
  const response = await executeWithRateLimit(state, () => createChatCompletions(payload));
1678
- if (isNonStreaming$1(response)) return handleNonStreamingResponse$1(c, response, ctx);
2144
+ if (isNonStreaming(response)) return handleNonStreamingResponse$1(c, response, ctx);
1679
2145
  consola.debug("Streaming response");
1680
- updateTrackerStatus$1(trackingId, "streaming");
2146
+ updateTrackerStatus(trackingId, "streaming");
1681
2147
  return streamSSE(c, async (stream) => {
1682
2148
  await handleStreamingResponse$1({
1683
2149
  stream,
@@ -1687,39 +2153,11 @@ async function handleCompletion$1(c) {
1687
2153
  });
1688
2154
  });
1689
2155
  } catch (error) {
1690
- recordErrorResponse$1(ctx, payload.model, error);
2156
+ if (error instanceof HTTPError && error.status === 413) await logPayloadSizeInfo(payload, selectedModel);
2157
+ recordErrorResponse(ctx, payload.model, error);
1691
2158
  throw error;
1692
2159
  }
1693
2160
  }
1694
- async function buildFinalPayload$1(payload, model) {
1695
- if (!state.autoCompact || !model) {
1696
- if (state.autoCompact && !model) consola.warn(`Auto-compact: Model '${payload.model}' not found in cached models, skipping`);
1697
- return {
1698
- finalPayload: payload,
1699
- compactResult: null
1700
- };
1701
- }
1702
- try {
1703
- const check = await checkNeedsCompaction(payload, model);
1704
- consola.debug(`Auto-compact check: ${check.currentTokens} tokens, limit ${check.limit}, needed: ${check.needed}`);
1705
- if (!check.needed) return {
1706
- finalPayload: payload,
1707
- compactResult: null
1708
- };
1709
- consola.info(`Auto-compact triggered: ${check.currentTokens} tokens > ${check.limit} limit`);
1710
- const compactResult = await autoCompact(payload, model);
1711
- return {
1712
- finalPayload: compactResult.payload,
1713
- compactResult
1714
- };
1715
- } catch (error) {
1716
- consola.warn("Auto-compact failed, proceeding with original payload:", error);
1717
- return {
1718
- finalPayload: payload,
1719
- compactResult: null
1720
- };
1721
- }
1722
- }
1723
2161
  async function logTokenCount(payload, selectedModel) {
1724
2162
  try {
1725
2163
  if (selectedModel) {
@@ -1730,27 +2168,6 @@ async function logTokenCount(payload, selectedModel) {
1730
2168
  consola.debug("Failed to calculate token count:", error);
1731
2169
  }
1732
2170
  }
1733
- function updateTrackerModel$1(trackingId, model) {
1734
- if (!trackingId) return;
1735
- const request = requestTracker.getRequest(trackingId);
1736
- if (request) request.model = model;
1737
- }
1738
- function updateTrackerStatus$1(trackingId, status) {
1739
- if (!trackingId) return;
1740
- requestTracker.updateRequest(trackingId, { status });
1741
- }
1742
- function recordErrorResponse$1(ctx, model, error) {
1743
- recordResponse(ctx.historyId, {
1744
- success: false,
1745
- model,
1746
- usage: {
1747
- input_tokens: 0,
1748
- output_tokens: 0
1749
- },
1750
- error: error instanceof Error ? error.message : "Unknown error",
1751
- content: null
1752
- }, Date.now() - ctx.startTime);
1753
- }
1754
2171
  function handleNonStreamingResponse$1(c, originalResponse, ctx) {
1755
2172
  consola.debug("Non-streaming response:", JSON.stringify(originalResponse));
1756
2173
  let response = originalResponse;
@@ -1848,7 +2265,7 @@ async function handleStreamingResponse$1(opts) {
1848
2265
  acc.content += marker;
1849
2266
  }
1850
2267
  recordStreamSuccess(acc, payload.model, ctx);
1851
- completeTracking$1(ctx.trackingId, acc.inputTokens, acc.outputTokens);
2268
+ completeTracking(ctx.trackingId, acc.inputTokens, acc.outputTokens);
1852
2269
  } catch (error) {
1853
2270
  recordStreamError({
1854
2271
  acc,
@@ -1856,7 +2273,7 @@ async function handleStreamingResponse$1(opts) {
1856
2273
  ctx,
1857
2274
  error
1858
2275
  });
1859
- failTracking$1(ctx.trackingId, error);
2276
+ failTracking(ctx.trackingId, error);
1860
2277
  throw error;
1861
2278
  }
1862
2279
  }
@@ -1864,42 +2281,31 @@ function parseStreamChunk(chunk, acc) {
1864
2281
  if (!chunk.data || chunk.data === "[DONE]") return;
1865
2282
  try {
1866
2283
  const parsed = JSON.parse(chunk.data);
1867
- accumulateModel(parsed, acc);
1868
- accumulateUsage(parsed, acc);
1869
- accumulateChoice(parsed.choices[0], acc);
1870
- } catch {}
1871
- }
1872
- function accumulateModel(parsed, acc) {
1873
- if (parsed.model && !acc.model) acc.model = parsed.model;
1874
- }
1875
- function accumulateUsage(parsed, acc) {
1876
- if (parsed.usage) {
1877
- acc.inputTokens = parsed.usage.prompt_tokens;
1878
- acc.outputTokens = parsed.usage.completion_tokens;
1879
- }
1880
- }
1881
- function accumulateChoice(choice, acc) {
1882
- if (!choice) return;
1883
- if (choice.delta.content) acc.content += choice.delta.content;
1884
- if (choice.delta.tool_calls) accumulateToolCalls(choice.delta.tool_calls, acc);
1885
- if (choice.finish_reason) acc.finishReason = choice.finish_reason;
1886
- }
1887
- function accumulateToolCalls(toolCalls, acc) {
1888
- if (!toolCalls) return;
1889
- for (const tc of toolCalls) {
1890
- const idx = tc.index;
1891
- if (!acc.toolCallMap.has(idx)) acc.toolCallMap.set(idx, {
1892
- id: tc.id ?? "",
1893
- name: tc.function?.name ?? "",
1894
- arguments: ""
1895
- });
1896
- const item = acc.toolCallMap.get(idx);
1897
- if (item) {
1898
- if (tc.id) item.id = tc.id;
1899
- if (tc.function?.name) item.name = tc.function.name;
1900
- if (tc.function?.arguments) item.arguments += tc.function.arguments;
2284
+ if (parsed.model && !acc.model) acc.model = parsed.model;
2285
+ if (parsed.usage) {
2286
+ acc.inputTokens = parsed.usage.prompt_tokens;
2287
+ acc.outputTokens = parsed.usage.completion_tokens;
1901
2288
  }
1902
- }
2289
+ const choice = parsed.choices[0];
2290
+ if (choice) {
2291
+ if (choice.delta.content) acc.content += choice.delta.content;
2292
+ if (choice.delta.tool_calls) for (const tc of choice.delta.tool_calls) {
2293
+ const idx = tc.index;
2294
+ if (!acc.toolCallMap.has(idx)) acc.toolCallMap.set(idx, {
2295
+ id: tc.id ?? "",
2296
+ name: tc.function?.name ?? "",
2297
+ arguments: ""
2298
+ });
2299
+ const item = acc.toolCallMap.get(idx);
2300
+ if (item) {
2301
+ if (tc.id) item.id = tc.id;
2302
+ if (tc.function?.name) item.name = tc.function.name;
2303
+ if (tc.function?.arguments) item.arguments += tc.function.arguments;
2304
+ }
2305
+ }
2306
+ if (choice.finish_reason) acc.finishReason = choice.finish_reason;
2307
+ }
2308
+ } catch {}
1903
2309
  }
1904
2310
  function recordStreamSuccess(acc, fallbackModel, ctx) {
1905
2311
  for (const tc of acc.toolCallMap.values()) if (tc.id && tc.name) acc.toolCalls.push(tc);
@@ -1931,35 +2337,6 @@ function recordStreamSuccess(acc, fallbackModel, ctx) {
1931
2337
  })) : void 0
1932
2338
  }, Date.now() - ctx.startTime);
1933
2339
  }
1934
- function recordStreamError(opts) {
1935
- const { acc, fallbackModel, ctx, error } = opts;
1936
- recordResponse(ctx.historyId, {
1937
- success: false,
1938
- model: acc.model || fallbackModel,
1939
- usage: {
1940
- input_tokens: 0,
1941
- output_tokens: 0
1942
- },
1943
- error: error instanceof Error ? error.message : "Stream error",
1944
- content: null
1945
- }, Date.now() - ctx.startTime);
1946
- }
1947
- function completeTracking$1(trackingId, inputTokens, outputTokens) {
1948
- if (!trackingId) return;
1949
- requestTracker.updateRequest(trackingId, {
1950
- inputTokens,
1951
- outputTokens
1952
- });
1953
- requestTracker.completeRequest(trackingId, 200, {
1954
- inputTokens,
1955
- outputTokens
1956
- });
1957
- }
1958
- function failTracking$1(trackingId, error) {
1959
- if (!trackingId) return;
1960
- requestTracker.failRequest(trackingId, error instanceof Error ? error.message : "Stream error");
1961
- }
1962
- const isNonStreaming$1 = (response) => Object.hasOwn(response, "choices");
1963
2340
  function convertOpenAIMessages(messages) {
1964
2341
  return messages.map((msg) => {
1965
2342
  const result = {
@@ -3160,6 +3537,15 @@ function mapOpenAIStopReasonToAnthropic(finishReason) {
3160
3537
  //#endregion
3161
3538
  //#region src/routes/messages/non-stream-translation.ts
3162
3539
  const OPENAI_TOOL_NAME_LIMIT = 64;
3540
+ /**
3541
+ * Ensure all tool_use blocks have corresponding tool_result responses.
3542
+ * This handles edge cases where conversation history may be incomplete:
3543
+ * - Session interruptions where tool execution was cut off
3544
+ * - Previous request failures
3545
+ * - Client sending truncated history
3546
+ *
3547
+ * Adding placeholder responses prevents API errors and maintains protocol compliance.
3548
+ */
3163
3549
  function fixMessageSequence(messages) {
3164
3550
  const fixedMessages = [];
3165
3551
  for (let i = 0; i < messages.length; i++) {
@@ -3318,7 +3704,7 @@ function getTruncatedToolName(originalName, toolNameMapping) {
3318
3704
  for (let i = 0; i < originalName.length; i++) {
3319
3705
  const char = originalName.codePointAt(i) ?? 0;
3320
3706
  hash = (hash << 5) - hash + char;
3321
- hash = hash & hash;
3707
+ hash = Math.trunc(hash);
3322
3708
  }
3323
3709
  const hashSuffix = Math.abs(hash).toString(36).slice(0, 8);
3324
3710
  const truncatedName = originalName.slice(0, OPENAI_TOOL_NAME_LIMIT - 9) + "_" + hashSuffix;
@@ -3655,60 +4041,11 @@ async function handleCompletion(c) {
3655
4041
  });
3656
4042
  });
3657
4043
  } catch (error) {
4044
+ if (error instanceof HTTPError && error.status === 413) await logPayloadSizeInfo(openAIPayload, selectedModel);
3658
4045
  recordErrorResponse(ctx, anthropicPayload.model, error);
3659
4046
  throw error;
3660
4047
  }
3661
4048
  }
3662
- function updateTrackerModel(trackingId, model) {
3663
- if (!trackingId) return;
3664
- const request = requestTracker.getRequest(trackingId);
3665
- if (request) request.model = model;
3666
- }
3667
- async function buildFinalPayload(payload, model) {
3668
- if (!state.autoCompact || !model) {
3669
- if (state.autoCompact && !model) consola.warn(`Auto-compact: Model '${payload.model}' not found in cached models, skipping`);
3670
- return {
3671
- finalPayload: payload,
3672
- compactResult: null
3673
- };
3674
- }
3675
- try {
3676
- const check = await checkNeedsCompaction(payload, model);
3677
- consola.debug(`Auto-compact check: ${check.currentTokens} tokens, limit ${check.limit}, needed: ${check.needed}`);
3678
- if (!check.needed) return {
3679
- finalPayload: payload,
3680
- compactResult: null
3681
- };
3682
- consola.info(`Auto-compact triggered: ${check.currentTokens} tokens > ${check.limit} limit`);
3683
- const compactResult = await autoCompact(payload, model);
3684
- return {
3685
- finalPayload: compactResult.payload,
3686
- compactResult
3687
- };
3688
- } catch (error) {
3689
- consola.warn("Auto-compact failed, proceeding with original payload:", error);
3690
- return {
3691
- finalPayload: payload,
3692
- compactResult: null
3693
- };
3694
- }
3695
- }
3696
- function updateTrackerStatus(trackingId, status) {
3697
- if (!trackingId) return;
3698
- requestTracker.updateRequest(trackingId, { status });
3699
- }
3700
- function recordErrorResponse(ctx, model, error) {
3701
- recordResponse(ctx.historyId, {
3702
- success: false,
3703
- model,
3704
- usage: {
3705
- input_tokens: 0,
3706
- output_tokens: 0
3707
- },
3708
- error: error instanceof Error ? error.message : "Unknown error",
3709
- content: null
3710
- }, Date.now() - ctx.startTime);
3711
- }
3712
4049
  function handleNonStreamingResponse(opts) {
3713
4050
  const { c, response, toolNameMapping, ctx } = opts;
3714
4051
  consola.debug("Non-streaming response from Copilot:", JSON.stringify(response).slice(-400));
@@ -3802,7 +4139,7 @@ async function handleStreamingResponse(opts) {
3802
4139
  completeTracking(ctx.trackingId, acc.inputTokens, acc.outputTokens);
3803
4140
  } catch (error) {
3804
4141
  consola.error("Stream error:", error);
3805
- recordStreamingError({
4142
+ recordStreamError({
3806
4143
  acc,
3807
4144
  fallbackModel: anthropicPayload.model,
3808
4145
  ctx,
@@ -3942,34 +4279,6 @@ function recordStreamingResponse(acc, fallbackModel, ctx) {
3942
4279
  toolCalls: acc.toolCalls.length > 0 ? acc.toolCalls : void 0
3943
4280
  }, Date.now() - ctx.startTime);
3944
4281
  }
3945
- function recordStreamingError(opts) {
3946
- const { acc, fallbackModel, ctx, error } = opts;
3947
- recordResponse(ctx.historyId, {
3948
- success: false,
3949
- model: acc.model || fallbackModel,
3950
- usage: {
3951
- input_tokens: 0,
3952
- output_tokens: 0
3953
- },
3954
- error: error instanceof Error ? error.message : "Stream error",
3955
- content: null
3956
- }, Date.now() - ctx.startTime);
3957
- }
3958
- function completeTracking(trackingId, inputTokens, outputTokens) {
3959
- if (!trackingId) return;
3960
- requestTracker.updateRequest(trackingId, {
3961
- inputTokens,
3962
- outputTokens
3963
- });
3964
- requestTracker.completeRequest(trackingId, 200, {
3965
- inputTokens,
3966
- outputTokens
3967
- });
3968
- }
3969
- function failTracking(trackingId, error) {
3970
- if (!trackingId) return;
3971
- requestTracker.failRequest(trackingId, error instanceof Error ? error.message : "Stream error");
3972
- }
3973
4282
  function convertAnthropicMessages(messages) {
3974
4283
  return messages.map((msg) => {
3975
4284
  if (typeof msg.content === "string") return {
@@ -4017,7 +4326,6 @@ function extractToolCallsFromContent(content) {
4017
4326
  });
4018
4327
  return tools.length > 0 ? tools : void 0;
4019
4328
  }
4020
- const isNonStreaming = (response) => Object.hasOwn(response, "choices");
4021
4329
 
4022
4330
  //#endregion
4023
4331
  //#region src/routes/messages/route.ts
@@ -4320,7 +4628,8 @@ const main = defineCommand({
4320
4628
  logout,
4321
4629
  start,
4322
4630
  "check-usage": checkUsage,
4323
- debug
4631
+ debug,
4632
+ "patch-claude": patchClaude
4324
4633
  }
4325
4634
  });
4326
4635
  await runMain(main);