@hsupu/copilot-api 0.7.3 → 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";
@@ -12,37 +13,12 @@ import { getProxyForUrl } from "proxy-from-env";
12
13
  import { Agent, ProxyAgent, setGlobalDispatcher } from "undici";
13
14
  import { execSync } from "node:child_process";
14
15
  import process$1 from "node:process";
16
+ import pc from "picocolors";
15
17
  import { Hono } from "hono";
16
18
  import { cors } from "hono/cors";
17
19
  import { streamSSE } from "hono/streaming";
18
20
  import { events } from "fetch-event-stream";
19
21
 
20
- //#region rolldown:runtime
21
- var __create = Object.create;
22
- var __defProp = Object.defineProperty;
23
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
24
- var __getOwnPropNames = Object.getOwnPropertyNames;
25
- var __getProtoOf = Object.getPrototypeOf;
26
- var __hasOwnProp = Object.prototype.hasOwnProperty;
27
- var __commonJS = (cb, mod) => function() {
28
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
29
- };
30
- var __copyProps = (to, from, except, desc) => {
31
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
32
- key = keys[i];
33
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
34
- get: ((k) => from[k]).bind(null, key),
35
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
36
- });
37
- }
38
- return to;
39
- };
40
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
41
- value: mod,
42
- enumerable: true
43
- }) : target, mod));
44
-
45
- //#endregion
46
22
  //#region src/lib/paths.ts
47
23
  const APP_DIR = path.join(os.homedir(), ".local", "share", "copilot-api");
48
24
  const GITHUB_TOKEN_PATH = path.join(APP_DIR, "github_token");
@@ -151,9 +127,24 @@ function formatTokenLimitError(current, limit) {
151
127
  }
152
128
  };
153
129
  }
154
- async function forwardError(c, error) {
130
+ /** Format Anthropic-compatible error for request too large (413) */
131
+ function formatRequestTooLargeError() {
132
+ return {
133
+ type: "error",
134
+ error: {
135
+ type: "invalid_request_error",
136
+ message: "Request body too large. The HTTP request exceeds the server's size limit. Try reducing the conversation history or removing large content like images."
137
+ }
138
+ };
139
+ }
140
+ function forwardError(c, error) {
155
141
  consola.error("Error occurred:", error);
156
142
  if (error instanceof HTTPError) {
143
+ if (error.status === 413) {
144
+ const formattedError = formatRequestTooLargeError();
145
+ consola.debug("Returning formatted 413 error:", formattedError);
146
+ return c.json(formattedError, 413);
147
+ }
157
148
  let errorJson;
158
149
  try {
159
150
  errorJson = JSON.parse(error.responseText);
@@ -300,6 +291,24 @@ async function pollAccessToken(deviceCode) {
300
291
  //#region src/lib/token.ts
301
292
  const readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8");
302
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
+ }
303
312
  const setupCopilotToken = async () => {
304
313
  const { token, refresh_in } = await getCopilotToken();
305
314
  state.copilotToken = token;
@@ -308,14 +317,12 @@ const setupCopilotToken = async () => {
308
317
  const refreshInterval = (refresh_in - 60) * 1e3;
309
318
  setInterval(async () => {
310
319
  consola.debug("Refreshing Copilot token");
311
- try {
312
- const { token: token$1 } = await getCopilotToken();
313
- state.copilotToken = token$1;
320
+ const newToken = await refreshCopilotTokenWithRetry();
321
+ if (newToken) {
322
+ state.copilotToken = newToken;
314
323
  consola.debug("Copilot token refreshed");
315
- if (state.showToken) consola.info("Refreshed Copilot token:", token$1);
316
- } catch (error) {
317
- consola.error("Failed to refresh Copilot token (will retry on next interval):", error);
318
- }
324
+ if (state.showToken) consola.info("Refreshed Copilot token:", newToken);
325
+ } else consola.error("Failed to refresh Copilot token after retries, using existing token");
319
326
  }, refreshInterval);
320
327
  };
321
328
  async function setupGitHubToken(options) {
@@ -530,6 +537,167 @@ const logout = defineCommand({
530
537
  }
531
538
  });
532
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
+
533
701
  //#endregion
534
702
  //#region src/lib/history.ts
535
703
  function generateId$1() {
@@ -781,44 +949,74 @@ function exportHistory(format = "json") {
781
949
 
782
950
  //#endregion
783
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
+ };
784
1015
  function initProxyFromEnv() {
785
1016
  if (typeof Bun !== "undefined") return;
786
1017
  try {
787
- const direct = new Agent();
788
- const proxies = /* @__PURE__ */ new Map();
789
- setGlobalDispatcher({
790
- dispatch(options, handler) {
791
- try {
792
- const origin = typeof options.origin === "string" ? new URL(options.origin) : options.origin;
793
- const raw = getProxyForUrl(origin.toString());
794
- const proxyUrl = raw && raw.length > 0 ? raw : void 0;
795
- if (!proxyUrl) {
796
- consola.debug(`HTTP proxy bypass: ${origin.hostname}`);
797
- return direct.dispatch(options, handler);
798
- }
799
- let agent = proxies.get(proxyUrl);
800
- if (!agent) {
801
- agent = new ProxyAgent(proxyUrl);
802
- proxies.set(proxyUrl, agent);
803
- }
804
- let label = proxyUrl;
805
- try {
806
- const u = new URL(proxyUrl);
807
- label = `${u.protocol}//${u.host}`;
808
- } catch {}
809
- consola.debug(`HTTP proxy route: ${origin.hostname} via ${label}`);
810
- return agent.dispatch(options, handler);
811
- } catch {
812
- return direct.dispatch(options, handler);
813
- }
814
- },
815
- close() {
816
- return direct.close();
817
- },
818
- destroy() {
819
- return direct.destroy();
820
- }
821
- });
1018
+ const dispatcher = new ProxyDispatcher();
1019
+ setGlobalDispatcher(dispatcher);
822
1020
  consola.debug("HTTP proxy configured from environment (per-URL)");
823
1021
  } catch (err) {
824
1022
  consola.debug("Proxy setup skipped:", err);
@@ -828,7 +1026,7 @@ function initProxyFromEnv() {
828
1026
  //#endregion
829
1027
  //#region src/lib/shell.ts
830
1028
  function getShell() {
831
- const { platform, ppid, env: env$1 } = process$1;
1029
+ const { platform, ppid, env } = process$1;
832
1030
  if (platform === "win32") {
833
1031
  try {
834
1032
  const command = `wmic process get ParentProcessId,Name | findstr "${ppid}"`;
@@ -838,7 +1036,7 @@ function getShell() {
838
1036
  }
839
1037
  return "cmd";
840
1038
  } else {
841
- const shellPath = env$1.SHELL;
1039
+ const shellPath = env.SHELL;
842
1040
  if (shellPath) {
843
1041
  if (shellPath.endsWith("zsh")) return "zsh";
844
1042
  if (shellPath.endsWith("fish")) return "fish";
@@ -878,78 +1076,8 @@ function generateEnvScript(envVars, commandToRun = "") {
878
1076
  return commandBlock || commandToRun;
879
1077
  }
880
1078
 
881
- //#endregion
882
- //#region node_modules/picocolors/picocolors.js
883
- var require_picocolors = /* @__PURE__ */ __commonJS({ "node_modules/picocolors/picocolors.js": ((exports, module) => {
884
- let p = process || {}, argv = p.argv || [], env = p.env || {};
885
- let isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
886
- let formatter = (open, close, replace = open) => (input) => {
887
- let string = "" + input, index = string.indexOf(close, open.length);
888
- return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
889
- };
890
- let replaceClose = (string, close, replace, index) => {
891
- let result = "", cursor = 0;
892
- do {
893
- result += string.substring(cursor, index) + replace;
894
- cursor = index + close.length;
895
- index = string.indexOf(close, cursor);
896
- } while (~index);
897
- return result + string.substring(cursor);
898
- };
899
- let createColors = (enabled = isColorSupported) => {
900
- let f = enabled ? formatter : () => String;
901
- return {
902
- isColorSupported: enabled,
903
- reset: f("\x1B[0m", "\x1B[0m"),
904
- bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
905
- dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
906
- italic: f("\x1B[3m", "\x1B[23m"),
907
- underline: f("\x1B[4m", "\x1B[24m"),
908
- inverse: f("\x1B[7m", "\x1B[27m"),
909
- hidden: f("\x1B[8m", "\x1B[28m"),
910
- strikethrough: f("\x1B[9m", "\x1B[29m"),
911
- black: f("\x1B[30m", "\x1B[39m"),
912
- red: f("\x1B[31m", "\x1B[39m"),
913
- green: f("\x1B[32m", "\x1B[39m"),
914
- yellow: f("\x1B[33m", "\x1B[39m"),
915
- blue: f("\x1B[34m", "\x1B[39m"),
916
- magenta: f("\x1B[35m", "\x1B[39m"),
917
- cyan: f("\x1B[36m", "\x1B[39m"),
918
- white: f("\x1B[37m", "\x1B[39m"),
919
- gray: f("\x1B[90m", "\x1B[39m"),
920
- bgBlack: f("\x1B[40m", "\x1B[49m"),
921
- bgRed: f("\x1B[41m", "\x1B[49m"),
922
- bgGreen: f("\x1B[42m", "\x1B[49m"),
923
- bgYellow: f("\x1B[43m", "\x1B[49m"),
924
- bgBlue: f("\x1B[44m", "\x1B[49m"),
925
- bgMagenta: f("\x1B[45m", "\x1B[49m"),
926
- bgCyan: f("\x1B[46m", "\x1B[49m"),
927
- bgWhite: f("\x1B[47m", "\x1B[49m"),
928
- blackBright: f("\x1B[90m", "\x1B[39m"),
929
- redBright: f("\x1B[91m", "\x1B[39m"),
930
- greenBright: f("\x1B[92m", "\x1B[39m"),
931
- yellowBright: f("\x1B[93m", "\x1B[39m"),
932
- blueBright: f("\x1B[94m", "\x1B[39m"),
933
- magentaBright: f("\x1B[95m", "\x1B[39m"),
934
- cyanBright: f("\x1B[96m", "\x1B[39m"),
935
- whiteBright: f("\x1B[97m", "\x1B[39m"),
936
- bgBlackBright: f("\x1B[100m", "\x1B[49m"),
937
- bgRedBright: f("\x1B[101m", "\x1B[49m"),
938
- bgGreenBright: f("\x1B[102m", "\x1B[49m"),
939
- bgYellowBright: f("\x1B[103m", "\x1B[49m"),
940
- bgBlueBright: f("\x1B[104m", "\x1B[49m"),
941
- bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
942
- bgCyanBright: f("\x1B[106m", "\x1B[49m"),
943
- bgWhiteBright: f("\x1B[107m", "\x1B[49m")
944
- };
945
- };
946
- module.exports = createColors();
947
- module.exports.createColors = createColors;
948
- }) });
949
-
950
1079
  //#endregion
951
1080
  //#region src/lib/tui/console-renderer.ts
952
- var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
953
1081
  const CLEAR_LINE = "\x1B[2K\r";
954
1082
  function formatTime(date = /* @__PURE__ */ new Date()) {
955
1083
  const h = String(date.getHours()).padStart(2, "0");
@@ -973,24 +1101,56 @@ function formatTokens(input, output) {
973
1101
  /**
974
1102
  * Console renderer that shows request lifecycle with apt-get style footer
975
1103
  *
976
- * Log format:
977
- * - Start: [....] METHOD /path model-name
978
- * - Streaming: [<-->] METHOD /path model-name streaming...
979
- * - 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
980
1109
  *
981
1110
  * Features:
982
1111
  * - /history API requests are displayed in gray (dim)
983
1112
  * - Sticky footer shows active request count, updated in-place on the last line
984
1113
  * - Footer disappears when all requests complete
1114
+ * - Intercepts consola output to properly handle footer
985
1115
  */
986
1116
  var ConsoleRenderer = class {
987
1117
  activeRequests = /* @__PURE__ */ new Map();
988
1118
  showActive;
989
1119
  footerVisible = false;
990
1120
  isTTY;
1121
+ originalReporters = [];
991
1122
  constructor(options) {
992
1123
  this.showActive = options?.showActive ?? true;
993
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
+ }
994
1154
  }
995
1155
  /**
996
1156
  * Get footer text based on active request count
@@ -999,7 +1159,7 @@ var ConsoleRenderer = class {
999
1159
  const activeCount = this.activeRequests.size;
1000
1160
  if (activeCount === 0) return "";
1001
1161
  const plural = activeCount === 1 ? "" : "s";
1002
- return import_picocolors.default.dim(`[....] ${activeCount} request${plural} in progress...`);
1162
+ return pc.dim(`[....] ${activeCount} request${plural} in progress...`);
1003
1163
  }
1004
1164
  /**
1005
1165
  * Render footer in-place on current line (no newline)
@@ -1033,8 +1193,8 @@ var ConsoleRenderer = class {
1033
1193
  */
1034
1194
  printLog(message, isGray = false) {
1035
1195
  this.clearFooterForLog();
1036
- if (isGray) consola.log(import_picocolors.default.dim(message));
1037
- else consola.log(message);
1196
+ if (isGray) process.stdout.write(pc.dim(message) + "\n");
1197
+ else process.stdout.write(message + "\n");
1038
1198
  this.renderFooter();
1039
1199
  }
1040
1200
  onRequestStart(request) {
@@ -1043,7 +1203,7 @@ var ConsoleRenderer = class {
1043
1203
  const time = formatTime();
1044
1204
  const modelInfo = request.model ? ` ${request.model}` : "";
1045
1205
  const queueInfo = request.queuePosition !== void 0 && request.queuePosition > 0 ? ` [q#${request.queuePosition}]` : "";
1046
- const message = `${time} [....] ${request.method} ${request.path}${modelInfo}${queueInfo}`;
1206
+ const message = `[....] ${time} ${request.method} ${request.path}${modelInfo}${queueInfo}`;
1047
1207
  this.printLog(message, request.isHistoryAccess);
1048
1208
  }
1049
1209
  }
@@ -1054,7 +1214,7 @@ var ConsoleRenderer = class {
1054
1214
  if (this.showActive && update.status === "streaming") {
1055
1215
  const time = formatTime();
1056
1216
  const modelInfo = request.model ? ` ${request.model}` : "";
1057
- const message = `${time} [<-->] ${request.method} ${request.path}${modelInfo} streaming...`;
1217
+ const message = `[<-->] ${time} ${request.method} ${request.path}${modelInfo} streaming...`;
1058
1218
  this.printLog(message, request.isHistoryAccess);
1059
1219
  }
1060
1220
  }
@@ -1068,7 +1228,7 @@ var ConsoleRenderer = class {
1068
1228
  const isError = request.status === "error" || status >= 400;
1069
1229
  const prefix = isError ? "[FAIL]" : "[ OK ]";
1070
1230
  const tokensPart = tokens ? ` ${tokens}` : "";
1071
- 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}`;
1072
1232
  if (isError) {
1073
1233
  const errorInfo = request.error ? `: ${request.error}` : "";
1074
1234
  content += errorInfo;
@@ -1081,6 +1241,7 @@ var ConsoleRenderer = class {
1081
1241
  this.footerVisible = false;
1082
1242
  }
1083
1243
  this.activeRequests.clear();
1244
+ if (this.originalReporters.length > 0) consola.setReporters(this.originalReporters);
1084
1245
  }
1085
1246
  };
1086
1247
 
@@ -1472,14 +1633,14 @@ const getTokenCount = async (payload, model) => {
1472
1633
  //#endregion
1473
1634
  //#region src/lib/auto-compact.ts
1474
1635
  const DEFAULT_CONFIG = {
1475
- targetTokens: 1e5,
1476
- safetyMarginPercent: 10
1636
+ targetTokens: 12e4,
1637
+ safetyMarginPercent: 2
1477
1638
  };
1478
1639
  /**
1479
1640
  * Check if payload needs compaction based on model limits.
1480
1641
  * Uses a safety margin to account for token counting differences.
1481
1642
  */
1482
- async function checkNeedsCompaction(payload, model, safetyMarginPercent = 10) {
1643
+ async function checkNeedsCompaction(payload, model, safetyMarginPercent = 2) {
1483
1644
  const currentTokens = (await getTokenCount(payload, model)).input;
1484
1645
  const rawLimit = model.capabilities?.limits?.max_prompt_tokens ?? 128e3;
1485
1646
  const limit = Math.floor(rawLimit * (1 - safetyMarginPercent / 100));
@@ -1522,6 +1683,13 @@ function extractSystemMessages(messages) {
1522
1683
  };
1523
1684
  }
1524
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
+ /**
1525
1693
  * Find messages to keep from the end to stay under target tokens.
1526
1694
  * Returns the starting index of messages to preserve.
1527
1695
  */
@@ -1536,6 +1704,41 @@ function findPreserveIndex(messages, targetTokens, systemTokens) {
1536
1704
  return 0;
1537
1705
  }
1538
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
+ /**
1539
1742
  * Calculate estimated tokens for system messages.
1540
1743
  */
1541
1744
  function estimateSystemTokens(systemMessages) {
@@ -1553,6 +1756,7 @@ function createTruncationMarker(removedCount) {
1553
1756
  /**
1554
1757
  * Perform auto-compaction on a payload that exceeds token limits.
1555
1758
  * This uses simple truncation - no LLM calls required.
1759
+ * Uses iterative approach with decreasing target tokens until under limit.
1556
1760
  */
1557
1761
  async function autoCompact(payload, model, config = {}) {
1558
1762
  const cfg = {
@@ -1573,8 +1777,49 @@ async function autoCompact(payload, model, config = {}) {
1573
1777
  const { systemMessages, remainingMessages } = extractSystemMessages(payload.messages);
1574
1778
  const systemTokens = estimateSystemTokens(systemMessages);
1575
1779
  consola.debug(`Auto-compact: ${systemMessages.length} system messages (~${systemTokens} tokens)`);
1576
- const effectiveTarget = Math.min(cfg.targetTokens, limit);
1577
- 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);
1578
1823
  if (preserveIndex === 0) {
1579
1824
  consola.warn("Auto-compact: Cannot truncate further without losing all conversation history");
1580
1825
  return {
@@ -1586,8 +1831,21 @@ async function autoCompact(payload, model, config = {}) {
1586
1831
  };
1587
1832
  }
1588
1833
  const removedMessages = remainingMessages.slice(0, preserveIndex);
1589
- const preservedMessages = remainingMessages.slice(preserveIndex);
1590
- 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}`);
1591
1849
  const truncationMarker = createTruncationMarker(removedMessages.length);
1592
1850
  const newPayload = {
1593
1851
  ...payload,
@@ -1598,25 +1856,6 @@ async function autoCompact(payload, model, config = {}) {
1598
1856
  ]
1599
1857
  };
1600
1858
  const newTokenCount = await getTokenCount(newPayload, model);
1601
- consola.info(`Auto-compact: Reduced from ${originalTokens} to ${newTokenCount.input} tokens`);
1602
- if (newTokenCount.input > limit) {
1603
- consola.warn(`Auto-compact: Still over limit (${newTokenCount.input} > ${limit}), trying more aggressive truncation`);
1604
- const aggressiveTarget = Math.floor(effectiveTarget * .7);
1605
- if (aggressiveTarget < 2e4) {
1606
- consola.error("Auto-compact: Cannot reduce further, target too low");
1607
- return {
1608
- payload: newPayload,
1609
- wasCompacted: true,
1610
- originalTokens,
1611
- compactedTokens: newTokenCount.input,
1612
- removedMessageCount: removedMessages.length
1613
- };
1614
- }
1615
- return autoCompact(payload, model, {
1616
- ...cfg,
1617
- targetTokens: aggressiveTarget
1618
- });
1619
- }
1620
1859
  return {
1621
1860
  payload: newPayload,
1622
1861
  wasCompacted: true,
@@ -1643,11 +1882,12 @@ var RequestQueue = class {
1643
1882
  lastRequestTime = 0;
1644
1883
  async enqueue(execute, rateLimitSeconds) {
1645
1884
  return new Promise((resolve, reject) => {
1646
- this.queue.push({
1885
+ const request = {
1647
1886
  execute,
1648
1887
  resolve,
1649
1888
  reject
1650
- });
1889
+ };
1890
+ this.queue.push(request);
1651
1891
  if (this.queue.length > 1) {
1652
1892
  const position = this.queue.length;
1653
1893
  const waitTime = Math.ceil((position - 1) * rateLimitSeconds);
@@ -1718,16 +1958,149 @@ const createChatCompletions = async (payload) => {
1718
1958
  };
1719
1959
 
1720
1960
  //#endregion
1721
- //#region src/routes/chat-completions/handler.ts
1722
- function getModelMaxOutputTokens(model) {
1723
- 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 });
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");
1724
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
1725
2098
  async function handleCompletion$1(c) {
1726
2099
  const originalPayload = await c.req.json();
1727
2100
  consola.debug("Request payload:", JSON.stringify(originalPayload).slice(-400));
1728
2101
  const trackingId = c.get("trackingId");
1729
2102
  const startTime = (trackingId ? requestTracker.getRequest(trackingId) : void 0)?.startTime ?? Date.now();
1730
- updateTrackerModel$1(trackingId, originalPayload.model);
2103
+ updateTrackerModel(trackingId, originalPayload.model);
1731
2104
  const ctx = {
1732
2105
  historyId: recordRequest("openai", {
1733
2106
  model: originalPayload.model,
@@ -1745,19 +2118,32 @@ async function handleCompletion$1(c) {
1745
2118
  };
1746
2119
  const selectedModel = state.models?.data.find((model) => model.id === originalPayload.model);
1747
2120
  await logTokenCount(originalPayload, selectedModel);
1748
- const { finalPayload, compactResult } = await buildFinalPayload$1(originalPayload, selectedModel);
2121
+ const { finalPayload, compactResult } = await buildFinalPayload(originalPayload, selectedModel);
1749
2122
  if (compactResult) ctx.compactResult = compactResult;
1750
2123
  const payload = isNullish(finalPayload.max_tokens) ? {
1751
2124
  ...finalPayload,
1752
- max_tokens: getModelMaxOutputTokens(selectedModel)
2125
+ max_tokens: selectedModel?.capabilities?.limits?.max_output_tokens
1753
2126
  } : finalPayload;
1754
2127
  if (isNullish(originalPayload.max_tokens)) consola.debug("Set max_tokens to:", JSON.stringify(payload.max_tokens));
1755
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;
1756
2142
  try {
1757
2143
  const response = await executeWithRateLimit(state, () => createChatCompletions(payload));
1758
- if (isNonStreaming$1(response)) return handleNonStreamingResponse$1(c, response, ctx);
2144
+ if (isNonStreaming(response)) return handleNonStreamingResponse$1(c, response, ctx);
1759
2145
  consola.debug("Streaming response");
1760
- updateTrackerStatus$1(trackingId, "streaming");
2146
+ updateTrackerStatus(trackingId, "streaming");
1761
2147
  return streamSSE(c, async (stream) => {
1762
2148
  await handleStreamingResponse$1({
1763
2149
  stream,
@@ -1767,39 +2153,11 @@ async function handleCompletion$1(c) {
1767
2153
  });
1768
2154
  });
1769
2155
  } catch (error) {
1770
- recordErrorResponse$1(ctx, payload.model, error);
2156
+ if (error instanceof HTTPError && error.status === 413) await logPayloadSizeInfo(payload, selectedModel);
2157
+ recordErrorResponse(ctx, payload.model, error);
1771
2158
  throw error;
1772
2159
  }
1773
2160
  }
1774
- async function buildFinalPayload$1(payload, model) {
1775
- if (!state.autoCompact || !model) {
1776
- if (state.autoCompact && !model) consola.warn(`Auto-compact: Model '${payload.model}' not found in cached models, skipping`);
1777
- return {
1778
- finalPayload: payload,
1779
- compactResult: null
1780
- };
1781
- }
1782
- try {
1783
- const check = await checkNeedsCompaction(payload, model);
1784
- consola.debug(`Auto-compact check: ${check.currentTokens} tokens, limit ${check.limit}, needed: ${check.needed}`);
1785
- if (!check.needed) return {
1786
- finalPayload: payload,
1787
- compactResult: null
1788
- };
1789
- consola.info(`Auto-compact triggered: ${check.currentTokens} tokens > ${check.limit} limit`);
1790
- const compactResult = await autoCompact(payload, model);
1791
- return {
1792
- finalPayload: compactResult.payload,
1793
- compactResult
1794
- };
1795
- } catch (error) {
1796
- consola.warn("Auto-compact failed, proceeding with original payload:", error);
1797
- return {
1798
- finalPayload: payload,
1799
- compactResult: null
1800
- };
1801
- }
1802
- }
1803
2161
  async function logTokenCount(payload, selectedModel) {
1804
2162
  try {
1805
2163
  if (selectedModel) {
@@ -1810,27 +2168,6 @@ async function logTokenCount(payload, selectedModel) {
1810
2168
  consola.debug("Failed to calculate token count:", error);
1811
2169
  }
1812
2170
  }
1813
- function updateTrackerModel$1(trackingId, model) {
1814
- if (!trackingId) return;
1815
- const request = requestTracker.getRequest(trackingId);
1816
- if (request) request.model = model;
1817
- }
1818
- function updateTrackerStatus$1(trackingId, status) {
1819
- if (!trackingId) return;
1820
- requestTracker.updateRequest(trackingId, { status });
1821
- }
1822
- function recordErrorResponse$1(ctx, model, error) {
1823
- recordResponse(ctx.historyId, {
1824
- success: false,
1825
- model,
1826
- usage: {
1827
- input_tokens: 0,
1828
- output_tokens: 0
1829
- },
1830
- error: error instanceof Error ? error.message : "Unknown error",
1831
- content: null
1832
- }, Date.now() - ctx.startTime);
1833
- }
1834
2171
  function handleNonStreamingResponse$1(c, originalResponse, ctx) {
1835
2172
  consola.debug("Non-streaming response:", JSON.stringify(originalResponse));
1836
2173
  let response = originalResponse;
@@ -1928,7 +2265,7 @@ async function handleStreamingResponse$1(opts) {
1928
2265
  acc.content += marker;
1929
2266
  }
1930
2267
  recordStreamSuccess(acc, payload.model, ctx);
1931
- completeTracking$1(ctx.trackingId, acc.inputTokens, acc.outputTokens);
2268
+ completeTracking(ctx.trackingId, acc.inputTokens, acc.outputTokens);
1932
2269
  } catch (error) {
1933
2270
  recordStreamError({
1934
2271
  acc,
@@ -1936,7 +2273,7 @@ async function handleStreamingResponse$1(opts) {
1936
2273
  ctx,
1937
2274
  error
1938
2275
  });
1939
- failTracking$1(ctx.trackingId, error);
2276
+ failTracking(ctx.trackingId, error);
1940
2277
  throw error;
1941
2278
  }
1942
2279
  }
@@ -1944,42 +2281,31 @@ function parseStreamChunk(chunk, acc) {
1944
2281
  if (!chunk.data || chunk.data === "[DONE]") return;
1945
2282
  try {
1946
2283
  const parsed = JSON.parse(chunk.data);
1947
- accumulateModel(parsed, acc);
1948
- accumulateUsage(parsed, acc);
1949
- accumulateChoice(parsed.choices[0], acc);
1950
- } catch {}
1951
- }
1952
- function accumulateModel(parsed, acc) {
1953
- if (parsed.model && !acc.model) acc.model = parsed.model;
1954
- }
1955
- function accumulateUsage(parsed, acc) {
1956
- if (parsed.usage) {
1957
- acc.inputTokens = parsed.usage.prompt_tokens;
1958
- acc.outputTokens = parsed.usage.completion_tokens;
1959
- }
1960
- }
1961
- function accumulateChoice(choice, acc) {
1962
- if (!choice) return;
1963
- if (choice.delta.content) acc.content += choice.delta.content;
1964
- if (choice.delta.tool_calls) accumulateToolCalls(choice.delta.tool_calls, acc);
1965
- if (choice.finish_reason) acc.finishReason = choice.finish_reason;
1966
- }
1967
- function accumulateToolCalls(toolCalls, acc) {
1968
- if (!toolCalls) return;
1969
- for (const tc of toolCalls) {
1970
- const idx = tc.index;
1971
- if (!acc.toolCallMap.has(idx)) acc.toolCallMap.set(idx, {
1972
- id: tc.id ?? "",
1973
- name: tc.function?.name ?? "",
1974
- arguments: ""
1975
- });
1976
- const item = acc.toolCallMap.get(idx);
1977
- if (item) {
1978
- if (tc.id) item.id = tc.id;
1979
- if (tc.function?.name) item.name = tc.function.name;
1980
- 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;
1981
2288
  }
1982
- }
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 {}
1983
2309
  }
1984
2310
  function recordStreamSuccess(acc, fallbackModel, ctx) {
1985
2311
  for (const tc of acc.toolCallMap.values()) if (tc.id && tc.name) acc.toolCalls.push(tc);
@@ -2011,35 +2337,6 @@ function recordStreamSuccess(acc, fallbackModel, ctx) {
2011
2337
  })) : void 0
2012
2338
  }, Date.now() - ctx.startTime);
2013
2339
  }
2014
- function recordStreamError(opts) {
2015
- const { acc, fallbackModel, ctx, error } = opts;
2016
- recordResponse(ctx.historyId, {
2017
- success: false,
2018
- model: acc.model || fallbackModel,
2019
- usage: {
2020
- input_tokens: 0,
2021
- output_tokens: 0
2022
- },
2023
- error: error instanceof Error ? error.message : "Stream error",
2024
- content: null
2025
- }, Date.now() - ctx.startTime);
2026
- }
2027
- function completeTracking$1(trackingId, inputTokens, outputTokens) {
2028
- if (!trackingId) return;
2029
- requestTracker.updateRequest(trackingId, {
2030
- inputTokens,
2031
- outputTokens
2032
- });
2033
- requestTracker.completeRequest(trackingId, 200, {
2034
- inputTokens,
2035
- outputTokens
2036
- });
2037
- }
2038
- function failTracking$1(trackingId, error) {
2039
- if (!trackingId) return;
2040
- requestTracker.failRequest(trackingId, error instanceof Error ? error.message : "Stream error");
2041
- }
2042
- const isNonStreaming$1 = (response) => Object.hasOwn(response, "choices");
2043
2340
  function convertOpenAIMessages(messages) {
2044
2341
  return messages.map((msg) => {
2045
2342
  const result = {
@@ -3240,6 +3537,15 @@ function mapOpenAIStopReasonToAnthropic(finishReason) {
3240
3537
  //#endregion
3241
3538
  //#region src/routes/messages/non-stream-translation.ts
3242
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
+ */
3243
3549
  function fixMessageSequence(messages) {
3244
3550
  const fixedMessages = [];
3245
3551
  for (let i = 0; i < messages.length; i++) {
@@ -3398,7 +3704,7 @@ function getTruncatedToolName(originalName, toolNameMapping) {
3398
3704
  for (let i = 0; i < originalName.length; i++) {
3399
3705
  const char = originalName.codePointAt(i) ?? 0;
3400
3706
  hash = (hash << 5) - hash + char;
3401
- hash = hash & hash;
3707
+ hash = Math.trunc(hash);
3402
3708
  }
3403
3709
  const hashSuffix = Math.abs(hash).toString(36).slice(0, 8);
3404
3710
  const truncatedName = originalName.slice(0, OPENAI_TOOL_NAME_LIMIT - 9) + "_" + hashSuffix;
@@ -3735,60 +4041,11 @@ async function handleCompletion(c) {
3735
4041
  });
3736
4042
  });
3737
4043
  } catch (error) {
4044
+ if (error instanceof HTTPError && error.status === 413) await logPayloadSizeInfo(openAIPayload, selectedModel);
3738
4045
  recordErrorResponse(ctx, anthropicPayload.model, error);
3739
4046
  throw error;
3740
4047
  }
3741
4048
  }
3742
- function updateTrackerModel(trackingId, model) {
3743
- if (!trackingId) return;
3744
- const request = requestTracker.getRequest(trackingId);
3745
- if (request) request.model = model;
3746
- }
3747
- async function buildFinalPayload(payload, model) {
3748
- if (!state.autoCompact || !model) {
3749
- if (state.autoCompact && !model) consola.warn(`Auto-compact: Model '${payload.model}' not found in cached models, skipping`);
3750
- return {
3751
- finalPayload: payload,
3752
- compactResult: null
3753
- };
3754
- }
3755
- try {
3756
- const check = await checkNeedsCompaction(payload, model);
3757
- consola.debug(`Auto-compact check: ${check.currentTokens} tokens, limit ${check.limit}, needed: ${check.needed}`);
3758
- if (!check.needed) return {
3759
- finalPayload: payload,
3760
- compactResult: null
3761
- };
3762
- consola.info(`Auto-compact triggered: ${check.currentTokens} tokens > ${check.limit} limit`);
3763
- const compactResult = await autoCompact(payload, model);
3764
- return {
3765
- finalPayload: compactResult.payload,
3766
- compactResult
3767
- };
3768
- } catch (error) {
3769
- consola.warn("Auto-compact failed, proceeding with original payload:", error);
3770
- return {
3771
- finalPayload: payload,
3772
- compactResult: null
3773
- };
3774
- }
3775
- }
3776
- function updateTrackerStatus(trackingId, status) {
3777
- if (!trackingId) return;
3778
- requestTracker.updateRequest(trackingId, { status });
3779
- }
3780
- function recordErrorResponse(ctx, model, error) {
3781
- recordResponse(ctx.historyId, {
3782
- success: false,
3783
- model,
3784
- usage: {
3785
- input_tokens: 0,
3786
- output_tokens: 0
3787
- },
3788
- error: error instanceof Error ? error.message : "Unknown error",
3789
- content: null
3790
- }, Date.now() - ctx.startTime);
3791
- }
3792
4049
  function handleNonStreamingResponse(opts) {
3793
4050
  const { c, response, toolNameMapping, ctx } = opts;
3794
4051
  consola.debug("Non-streaming response from Copilot:", JSON.stringify(response).slice(-400));
@@ -3882,7 +4139,7 @@ async function handleStreamingResponse(opts) {
3882
4139
  completeTracking(ctx.trackingId, acc.inputTokens, acc.outputTokens);
3883
4140
  } catch (error) {
3884
4141
  consola.error("Stream error:", error);
3885
- recordStreamingError({
4142
+ recordStreamError({
3886
4143
  acc,
3887
4144
  fallbackModel: anthropicPayload.model,
3888
4145
  ctx,
@@ -4022,34 +4279,6 @@ function recordStreamingResponse(acc, fallbackModel, ctx) {
4022
4279
  toolCalls: acc.toolCalls.length > 0 ? acc.toolCalls : void 0
4023
4280
  }, Date.now() - ctx.startTime);
4024
4281
  }
4025
- function recordStreamingError(opts) {
4026
- const { acc, fallbackModel, ctx, error } = opts;
4027
- recordResponse(ctx.historyId, {
4028
- success: false,
4029
- model: acc.model || fallbackModel,
4030
- usage: {
4031
- input_tokens: 0,
4032
- output_tokens: 0
4033
- },
4034
- error: error instanceof Error ? error.message : "Stream error",
4035
- content: null
4036
- }, Date.now() - ctx.startTime);
4037
- }
4038
- function completeTracking(trackingId, inputTokens, outputTokens) {
4039
- if (!trackingId) return;
4040
- requestTracker.updateRequest(trackingId, {
4041
- inputTokens,
4042
- outputTokens
4043
- });
4044
- requestTracker.completeRequest(trackingId, 200, {
4045
- inputTokens,
4046
- outputTokens
4047
- });
4048
- }
4049
- function failTracking(trackingId, error) {
4050
- if (!trackingId) return;
4051
- requestTracker.failRequest(trackingId, error instanceof Error ? error.message : "Stream error");
4052
- }
4053
4282
  function convertAnthropicMessages(messages) {
4054
4283
  return messages.map((msg) => {
4055
4284
  if (typeof msg.content === "string") return {
@@ -4097,7 +4326,6 @@ function extractToolCallsFromContent(content) {
4097
4326
  });
4098
4327
  return tools.length > 0 ? tools : void 0;
4099
4328
  }
4100
- const isNonStreaming = (response) => Object.hasOwn(response, "choices");
4101
4329
 
4102
4330
  //#endregion
4103
4331
  //#region src/routes/messages/route.ts
@@ -4389,6 +4617,7 @@ const start = defineCommand({
4389
4617
 
4390
4618
  //#endregion
4391
4619
  //#region src/main.ts
4620
+ consola.options.formatOptions.date = false;
4392
4621
  const main = defineCommand({
4393
4622
  meta: {
4394
4623
  name: "copilot-api",
@@ -4399,7 +4628,8 @@ const main = defineCommand({
4399
4628
  logout,
4400
4629
  start,
4401
4630
  "check-usage": checkUsage,
4402
- debug
4631
+ debug,
4632
+ "patch-claude": patchClaude
4403
4633
  }
4404
4634
  });
4405
4635
  await runMain(main);