@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/README.md +11 -0
- package/dist/main.js +617 -387
- package/dist/main.js.map +1 -1
- package/package.json +3 -6
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
|
-
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
state.copilotToken =
|
|
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:",
|
|
316
|
-
}
|
|
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
|
|
788
|
-
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
1037
|
-
else
|
|
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 =
|
|
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 =
|
|
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 = `${
|
|
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:
|
|
1476
|
-
safetyMarginPercent:
|
|
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 =
|
|
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
|
|
1577
|
-
const
|
|
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
|
-
|
|
1590
|
-
|
|
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
|
-
|
|
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/
|
|
1722
|
-
|
|
1723
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
2144
|
+
if (isNonStreaming(response)) return handleNonStreamingResponse$1(c, response, ctx);
|
|
1759
2145
|
consola.debug("Streaming response");
|
|
1760
|
-
updateTrackerStatus
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
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
|
|
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
|
-
|
|
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);
|