@dianshuv/copilot-api 0.6.1 → 0.6.3
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 +2 -1
- package/dist/main.mjs +453 -181
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,7 +66,8 @@ copilot-api start
|
|
|
66
66
|
| `--no-auto-truncate` | Disable auto-truncate when exceeding token limits | false |
|
|
67
67
|
| `--compress-tool-results` | Compress old tool results before truncating | false |
|
|
68
68
|
| `--redirect-anthropic` | Force Anthropic through OpenAI translation | false |
|
|
69
|
-
| `--
|
|
69
|
+
| `--strip-server-tools` | Strip server-side tools from Anthropic requests | false |
|
|
70
|
+
| `--context-editing` | Context editing mode: off, clear-thinking, clear-tooluse, clear-both | off |
|
|
70
71
|
| `--timezone-offset` | Timezone offset in hours from UTC for log timestamps (e.g., +8, -5, 0) | +8 |
|
|
71
72
|
| `--posthog-key` | PostHog API key for token usage analytics (opt-in) | none |
|
|
72
73
|
|
package/dist/main.mjs
CHANGED
|
@@ -4,14 +4,14 @@ import consola from "consola";
|
|
|
4
4
|
import fs from "node:fs/promises";
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import path, { dirname, join } from "node:path";
|
|
7
|
+
import { getProxyForUrl } from "proxy-from-env";
|
|
8
|
+
import { Agent, ProxyAgent, setGlobalDispatcher } from "undici";
|
|
7
9
|
import { createHash, randomUUID } from "node:crypto";
|
|
8
10
|
import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
9
11
|
import clipboard from "clipboardy";
|
|
10
12
|
import { serve } from "srvx";
|
|
11
13
|
import invariant from "tiny-invariant";
|
|
12
14
|
import { PostHog } from "posthog-node";
|
|
13
|
-
import { getProxyForUrl } from "proxy-from-env";
|
|
14
|
-
import { Agent, ProxyAgent, setGlobalDispatcher } from "undici";
|
|
15
15
|
import { execSync } from "node:child_process";
|
|
16
16
|
import process$1 from "node:process";
|
|
17
17
|
import pc from "picocolors";
|
|
@@ -32,12 +32,90 @@ async function ensurePaths() {
|
|
|
32
32
|
await ensureFile(PATHS.GITHUB_TOKEN_PATH);
|
|
33
33
|
}
|
|
34
34
|
async function ensureFile(filePath) {
|
|
35
|
+
const isWindows = process.platform === "win32";
|
|
35
36
|
try {
|
|
36
37
|
await fs.access(filePath, fs.constants.W_OK);
|
|
37
|
-
if (
|
|
38
|
+
if (!isWindows) {
|
|
39
|
+
if (((await fs.stat(filePath)).mode & 511) !== 384) await fs.chmod(filePath, 384);
|
|
40
|
+
}
|
|
38
41
|
} catch {
|
|
39
42
|
await fs.writeFile(filePath, "");
|
|
40
|
-
await fs.chmod(filePath, 384);
|
|
43
|
+
if (!isWindows) await fs.chmod(filePath, 384);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/lib/proxy.ts
|
|
49
|
+
/**
|
|
50
|
+
* Custom dispatcher that routes requests through proxies based on environment variables.
|
|
51
|
+
* Extends Agent to properly inherit the Dispatcher interface.
|
|
52
|
+
*/
|
|
53
|
+
var ProxyDispatcher = class extends Agent {
|
|
54
|
+
proxies = /* @__PURE__ */ new Map();
|
|
55
|
+
dispatch(options, handler) {
|
|
56
|
+
try {
|
|
57
|
+
const origin = this.getOriginUrl(options.origin);
|
|
58
|
+
const proxyUrl = this.getProxyUrl(origin);
|
|
59
|
+
if (!proxyUrl) {
|
|
60
|
+
consola.debug(`HTTP proxy bypass: ${origin.hostname}`);
|
|
61
|
+
return super.dispatch(options, handler);
|
|
62
|
+
}
|
|
63
|
+
const agent = this.getOrCreateProxyAgent(proxyUrl);
|
|
64
|
+
consola.debug(`HTTP proxy route: ${origin.hostname} via ${this.formatProxyLabel(proxyUrl)}`);
|
|
65
|
+
return agent.dispatch(options, handler);
|
|
66
|
+
} catch {
|
|
67
|
+
return super.dispatch(options, handler);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
getOriginUrl(origin) {
|
|
71
|
+
return typeof origin === "string" ? new URL(origin) : origin;
|
|
72
|
+
}
|
|
73
|
+
getProxyUrl(origin) {
|
|
74
|
+
const raw = getProxyForUrl(origin.toString());
|
|
75
|
+
return raw && raw.length > 0 ? raw : void 0;
|
|
76
|
+
}
|
|
77
|
+
getOrCreateProxyAgent(proxyUrl) {
|
|
78
|
+
let agent = this.proxies.get(proxyUrl);
|
|
79
|
+
if (!agent) {
|
|
80
|
+
agent = new ProxyAgent(proxyUrl);
|
|
81
|
+
this.proxies.set(proxyUrl, agent);
|
|
82
|
+
}
|
|
83
|
+
return agent;
|
|
84
|
+
}
|
|
85
|
+
formatProxyLabel(proxyUrl) {
|
|
86
|
+
try {
|
|
87
|
+
const u = new URL(proxyUrl);
|
|
88
|
+
return `${u.protocol}//${u.host}`;
|
|
89
|
+
} catch {
|
|
90
|
+
return proxyUrl;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async close() {
|
|
94
|
+
await super.close();
|
|
95
|
+
await Promise.all([...this.proxies.values()].map((p) => p.close()));
|
|
96
|
+
this.proxies.clear();
|
|
97
|
+
}
|
|
98
|
+
destroy(errOrCallback, callback) {
|
|
99
|
+
for (const agent of this.proxies.values()) if (typeof errOrCallback === "function") agent.destroy(errOrCallback);
|
|
100
|
+
else if (callback) agent.destroy(errOrCallback ?? null, callback);
|
|
101
|
+
else agent.destroy(errOrCallback ?? null).catch(() => {});
|
|
102
|
+
this.proxies.clear();
|
|
103
|
+
if (typeof errOrCallback === "function") {
|
|
104
|
+
super.destroy(errOrCallback);
|
|
105
|
+
return;
|
|
106
|
+
} else if (callback) {
|
|
107
|
+
super.destroy(errOrCallback ?? null, callback);
|
|
108
|
+
return;
|
|
109
|
+
} else return super.destroy(errOrCallback ?? null);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
function initProxyFromEnv() {
|
|
113
|
+
if (typeof Bun !== "undefined") return;
|
|
114
|
+
try {
|
|
115
|
+
setGlobalDispatcher(new ProxyDispatcher());
|
|
116
|
+
consola.debug("HTTP proxy configured from environment (per-URL)");
|
|
117
|
+
} catch (err) {
|
|
118
|
+
consola.debug("Proxy setup skipped:", err);
|
|
41
119
|
}
|
|
42
120
|
}
|
|
43
121
|
|
|
@@ -51,7 +129,10 @@ const state = {
|
|
|
51
129
|
autoTruncate: true,
|
|
52
130
|
compressToolResults: false,
|
|
53
131
|
redirectAnthropic: false,
|
|
54
|
-
|
|
132
|
+
stripServerTools: false,
|
|
133
|
+
contextEditingMode: "off",
|
|
134
|
+
normalizeResponsesCallIds: true,
|
|
135
|
+
historyMinEntries: 50,
|
|
55
136
|
staleRequestMaxAge: 600,
|
|
56
137
|
timezoneOffset: 8,
|
|
57
138
|
shutdownGracefulWait: 60,
|
|
@@ -482,6 +563,7 @@ async function runAuth(options) {
|
|
|
482
563
|
consola.info("Verbose logging enabled");
|
|
483
564
|
}
|
|
484
565
|
state.showToken = options.showToken;
|
|
566
|
+
initProxyFromEnv();
|
|
485
567
|
await ensurePaths();
|
|
486
568
|
await setupGitHubToken({ force: true });
|
|
487
569
|
consola.success("GitHub token written to", PATHS.GITHUB_TOKEN_PATH);
|
|
@@ -528,6 +610,7 @@ const checkUsage = defineCommand({
|
|
|
528
610
|
description: "Show current GitHub Copilot usage/quota information"
|
|
529
611
|
},
|
|
530
612
|
async run() {
|
|
613
|
+
initProxyFromEnv();
|
|
531
614
|
await ensurePaths();
|
|
532
615
|
await setupGitHubToken();
|
|
533
616
|
try {
|
|
@@ -585,6 +668,7 @@ async function checkTokenExists() {
|
|
|
585
668
|
}
|
|
586
669
|
async function getAccountInfo() {
|
|
587
670
|
try {
|
|
671
|
+
initProxyFromEnv();
|
|
588
672
|
await ensurePaths();
|
|
589
673
|
await setupGitHubToken();
|
|
590
674
|
if (!state.githubToken) return null;
|
|
@@ -673,6 +757,7 @@ const debugModels = defineCommand({
|
|
|
673
757
|
},
|
|
674
758
|
async run({ args }) {
|
|
675
759
|
state.accountType = args["account-type"];
|
|
760
|
+
initProxyFromEnv();
|
|
676
761
|
await ensurePaths();
|
|
677
762
|
if (args["github-token"]) {
|
|
678
763
|
state.githubToken = args["github-token"];
|
|
@@ -1036,7 +1121,7 @@ const patchClaude = defineCommand({
|
|
|
1036
1121
|
|
|
1037
1122
|
//#endregion
|
|
1038
1123
|
//#region package.json
|
|
1039
|
-
var version = "0.6.
|
|
1124
|
+
var version = "0.6.3";
|
|
1040
1125
|
|
|
1041
1126
|
//#endregion
|
|
1042
1127
|
//#region src/lib/adaptive-rate-limiter.ts
|
|
@@ -1908,6 +1993,26 @@ function getTokenStats() {
|
|
|
1908
1993
|
timeline
|
|
1909
1994
|
};
|
|
1910
1995
|
}
|
|
1996
|
+
function getHistoryEntryCount() {
|
|
1997
|
+
return historyState.entries.length;
|
|
1998
|
+
}
|
|
1999
|
+
function getHistoryMaxEntries() {
|
|
2000
|
+
return historyState.maxEntries;
|
|
2001
|
+
}
|
|
2002
|
+
function setHistoryMaxEntries(max) {
|
|
2003
|
+
historyState.maxEntries = max;
|
|
2004
|
+
}
|
|
2005
|
+
/**
|
|
2006
|
+
* Evict the oldest `count` entries from the history store.
|
|
2007
|
+
* Returns the actual number of entries evicted.
|
|
2008
|
+
*/
|
|
2009
|
+
function evictOldestEntries(count) {
|
|
2010
|
+
if (count <= 0) return 0;
|
|
2011
|
+
const actual = Math.min(count, historyState.entries.length);
|
|
2012
|
+
const removed = historyState.entries.splice(0, actual);
|
|
2013
|
+
for (const entry of removed) if (!historyState.entries.some((e) => e.sessionId === entry.sessionId)) historyState.sessions.delete(entry.sessionId);
|
|
2014
|
+
return actual;
|
|
2015
|
+
}
|
|
1911
2016
|
function exportHistory(format = "json") {
|
|
1912
2017
|
if (format === "json") return JSON.stringify({
|
|
1913
2018
|
sessions: Array.from(historyState.sessions.values()),
|
|
@@ -1948,6 +2053,91 @@ function exportHistory(format = "json") {
|
|
|
1948
2053
|
return [headers.join(","), ...rows.map((r) => r.join(","))].join("\n");
|
|
1949
2054
|
}
|
|
1950
2055
|
|
|
2056
|
+
//#endregion
|
|
2057
|
+
//#region src/lib/history-memory-pressure.ts
|
|
2058
|
+
/**
|
|
2059
|
+
* Memory pressure monitor — proactively evicts old history entries
|
|
2060
|
+
* when heap usage approaches the V8 heap limit, preventing OOM crashes.
|
|
2061
|
+
*
|
|
2062
|
+
* Graduated response:
|
|
2063
|
+
* 75–80% Warning: log only, no eviction
|
|
2064
|
+
* 80–90% High: evict entries, reduce maxEntries by 25%
|
|
2065
|
+
* 90%+ Critical: aggressive eviction, reduce maxEntries by 50%
|
|
2066
|
+
*/
|
|
2067
|
+
const CHECK_INTERVAL_MS = 3e4;
|
|
2068
|
+
const WARN_THRESHOLD = .75;
|
|
2069
|
+
const EVICT_THRESHOLD = .8;
|
|
2070
|
+
const CRITICAL_THRESHOLD = .9;
|
|
2071
|
+
const WARN_LOG_COOLDOWN_MS = 3e5;
|
|
2072
|
+
let resolvedHeapLimit = null;
|
|
2073
|
+
let timer = null;
|
|
2074
|
+
let lastWarningTime = 0;
|
|
2075
|
+
let totalEvictedCount = 0;
|
|
2076
|
+
async function resolveHeapLimit() {
|
|
2077
|
+
if (resolvedHeapLimit !== null) return resolvedHeapLimit;
|
|
2078
|
+
let limit;
|
|
2079
|
+
try {
|
|
2080
|
+
limit = (await import("node:v8")).getHeapStatistics().heap_size_limit;
|
|
2081
|
+
} catch {
|
|
2082
|
+
limit = 512 * 1024 * 1024;
|
|
2083
|
+
}
|
|
2084
|
+
resolvedHeapLimit = limit;
|
|
2085
|
+
return limit;
|
|
2086
|
+
}
|
|
2087
|
+
function formatMB(bytes) {
|
|
2088
|
+
return `${Math.round(bytes / 1024 / 1024)}MB`;
|
|
2089
|
+
}
|
|
2090
|
+
function formatPct(ratio) {
|
|
2091
|
+
return `${Math.round(ratio * 100)}%`;
|
|
2092
|
+
}
|
|
2093
|
+
async function checkMemoryPressure() {
|
|
2094
|
+
const heapLimit = await resolveHeapLimit();
|
|
2095
|
+
const { heapUsed } = process.memoryUsage();
|
|
2096
|
+
const ratio = heapUsed / heapLimit;
|
|
2097
|
+
if (ratio < WARN_THRESHOLD) return;
|
|
2098
|
+
const currentEntries = getHistoryEntryCount();
|
|
2099
|
+
if (currentEntries <= state.historyMinEntries) {
|
|
2100
|
+
if (ratio >= EVICT_THRESHOLD && Date.now() - lastWarningTime > WARN_LOG_COOLDOWN_MS) {
|
|
2101
|
+
lastWarningTime = Date.now();
|
|
2102
|
+
consola.warn(`[memory] Heap ${formatMB(heapUsed)}/${formatMB(heapLimit)} (${formatPct(ratio)}) — only ${currentEntries} history entries remain. Consider increasing --max-old-space-size`);
|
|
2103
|
+
}
|
|
2104
|
+
return;
|
|
2105
|
+
}
|
|
2106
|
+
if (ratio < EVICT_THRESHOLD) {
|
|
2107
|
+
if (Date.now() - lastWarningTime > WARN_LOG_COOLDOWN_MS) {
|
|
2108
|
+
lastWarningTime = Date.now();
|
|
2109
|
+
consola.warn(`[memory] Heap ${formatMB(heapUsed)}/${formatMB(heapLimit)} (${formatPct(ratio)}) — approaching limit, ${currentEntries} history entries in memory`);
|
|
2110
|
+
}
|
|
2111
|
+
return;
|
|
2112
|
+
}
|
|
2113
|
+
lastWarningTime = Date.now();
|
|
2114
|
+
const currentMax = getHistoryMaxEntries();
|
|
2115
|
+
const newMaxEntries = ratio >= CRITICAL_THRESHOLD ? Math.max(state.historyMinEntries, Math.floor(currentMax * .5)) : Math.max(state.historyMinEntries, Math.floor(currentMax * .75));
|
|
2116
|
+
const evictCount = Math.max(0, currentEntries - newMaxEntries);
|
|
2117
|
+
if (evictCount <= 0) return;
|
|
2118
|
+
const evicted = evictOldestEntries(evictCount);
|
|
2119
|
+
totalEvictedCount += evicted;
|
|
2120
|
+
if (newMaxEntries < currentMax) setHistoryMaxEntries(newMaxEntries);
|
|
2121
|
+
const afterHeapUsed = process.memoryUsage().heapUsed;
|
|
2122
|
+
consola.warn(`[memory] Evicted ${evicted} history entries due to memory pressure (heap: ${formatMB(heapUsed)} → ${formatMB(afterHeapUsed)}/${formatMB(heapLimit)}, entries: ${currentEntries} → ${currentEntries - evicted}, max: ${newMaxEntries})`);
|
|
2123
|
+
globalThis.gc?.();
|
|
2124
|
+
}
|
|
2125
|
+
function startMemoryPressureMonitor() {
|
|
2126
|
+
if (timer) return;
|
|
2127
|
+
timer = setInterval(() => {
|
|
2128
|
+
checkMemoryPressure().catch((error) => {
|
|
2129
|
+
consola.error("[memory] Error in memory pressure check:", error);
|
|
2130
|
+
});
|
|
2131
|
+
}, CHECK_INTERVAL_MS);
|
|
2132
|
+
if ("unref" in timer) timer.unref();
|
|
2133
|
+
}
|
|
2134
|
+
function stopMemoryPressureMonitor() {
|
|
2135
|
+
if (timer) {
|
|
2136
|
+
clearInterval(timer);
|
|
2137
|
+
timer = null;
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
|
|
1951
2141
|
//#endregion
|
|
1952
2142
|
//#region src/lib/posthog.ts
|
|
1953
2143
|
let client = null;
|
|
@@ -1997,81 +2187,6 @@ async function shutdownPostHog() {
|
|
|
1997
2187
|
}
|
|
1998
2188
|
}
|
|
1999
2189
|
|
|
2000
|
-
//#endregion
|
|
2001
|
-
//#region src/lib/proxy.ts
|
|
2002
|
-
/**
|
|
2003
|
-
* Custom dispatcher that routes requests through proxies based on environment variables.
|
|
2004
|
-
* Extends Agent to properly inherit the Dispatcher interface.
|
|
2005
|
-
*/
|
|
2006
|
-
var ProxyDispatcher = class extends Agent {
|
|
2007
|
-
proxies = /* @__PURE__ */ new Map();
|
|
2008
|
-
dispatch(options, handler) {
|
|
2009
|
-
try {
|
|
2010
|
-
const origin = this.getOriginUrl(options.origin);
|
|
2011
|
-
const proxyUrl = this.getProxyUrl(origin);
|
|
2012
|
-
if (!proxyUrl) {
|
|
2013
|
-
consola.debug(`HTTP proxy bypass: ${origin.hostname}`);
|
|
2014
|
-
return super.dispatch(options, handler);
|
|
2015
|
-
}
|
|
2016
|
-
const agent = this.getOrCreateProxyAgent(proxyUrl);
|
|
2017
|
-
consola.debug(`HTTP proxy route: ${origin.hostname} via ${this.formatProxyLabel(proxyUrl)}`);
|
|
2018
|
-
return agent.dispatch(options, handler);
|
|
2019
|
-
} catch {
|
|
2020
|
-
return super.dispatch(options, handler);
|
|
2021
|
-
}
|
|
2022
|
-
}
|
|
2023
|
-
getOriginUrl(origin) {
|
|
2024
|
-
return typeof origin === "string" ? new URL(origin) : origin;
|
|
2025
|
-
}
|
|
2026
|
-
getProxyUrl(origin) {
|
|
2027
|
-
const raw = getProxyForUrl(origin.toString());
|
|
2028
|
-
return raw && raw.length > 0 ? raw : void 0;
|
|
2029
|
-
}
|
|
2030
|
-
getOrCreateProxyAgent(proxyUrl) {
|
|
2031
|
-
let agent = this.proxies.get(proxyUrl);
|
|
2032
|
-
if (!agent) {
|
|
2033
|
-
agent = new ProxyAgent(proxyUrl);
|
|
2034
|
-
this.proxies.set(proxyUrl, agent);
|
|
2035
|
-
}
|
|
2036
|
-
return agent;
|
|
2037
|
-
}
|
|
2038
|
-
formatProxyLabel(proxyUrl) {
|
|
2039
|
-
try {
|
|
2040
|
-
const u = new URL(proxyUrl);
|
|
2041
|
-
return `${u.protocol}//${u.host}`;
|
|
2042
|
-
} catch {
|
|
2043
|
-
return proxyUrl;
|
|
2044
|
-
}
|
|
2045
|
-
}
|
|
2046
|
-
async close() {
|
|
2047
|
-
await super.close();
|
|
2048
|
-
await Promise.all([...this.proxies.values()].map((p) => p.close()));
|
|
2049
|
-
this.proxies.clear();
|
|
2050
|
-
}
|
|
2051
|
-
destroy(errOrCallback, callback) {
|
|
2052
|
-
for (const agent of this.proxies.values()) if (typeof errOrCallback === "function") agent.destroy(errOrCallback);
|
|
2053
|
-
else if (callback) agent.destroy(errOrCallback ?? null, callback);
|
|
2054
|
-
else agent.destroy(errOrCallback ?? null).catch(() => {});
|
|
2055
|
-
this.proxies.clear();
|
|
2056
|
-
if (typeof errOrCallback === "function") {
|
|
2057
|
-
super.destroy(errOrCallback);
|
|
2058
|
-
return;
|
|
2059
|
-
} else if (callback) {
|
|
2060
|
-
super.destroy(errOrCallback ?? null, callback);
|
|
2061
|
-
return;
|
|
2062
|
-
} else return super.destroy(errOrCallback ?? null);
|
|
2063
|
-
}
|
|
2064
|
-
};
|
|
2065
|
-
function initProxyFromEnv() {
|
|
2066
|
-
if (typeof Bun !== "undefined") return;
|
|
2067
|
-
try {
|
|
2068
|
-
setGlobalDispatcher(new ProxyDispatcher());
|
|
2069
|
-
consola.debug("HTTP proxy configured from environment (per-URL)");
|
|
2070
|
-
} catch (err) {
|
|
2071
|
-
consola.debug("Proxy setup skipped:", err);
|
|
2072
|
-
}
|
|
2073
|
-
}
|
|
2074
|
-
|
|
2075
2190
|
//#endregion
|
|
2076
2191
|
//#region src/lib/shell.ts
|
|
2077
2192
|
function getShell() {
|
|
@@ -2184,6 +2299,7 @@ async function gracefulShutdown(signal, deps) {
|
|
|
2184
2299
|
try {
|
|
2185
2300
|
deps?.contextManager?.stopReaper();
|
|
2186
2301
|
} catch {}
|
|
2302
|
+
stopMemoryPressureMonitor();
|
|
2187
2303
|
stopRefresh();
|
|
2188
2304
|
const wsClients = getWsCount();
|
|
2189
2305
|
if (wsClients > 0) {
|
|
@@ -4483,6 +4599,30 @@ function handleNonStreamResponse(c, response, model, ctx, payload) {
|
|
|
4483
4599
|
return c.json(geminiResponse);
|
|
4484
4600
|
}
|
|
4485
4601
|
|
|
4602
|
+
//#endregion
|
|
4603
|
+
//#region src/routes/gemini/model-alias.ts
|
|
4604
|
+
/**
|
|
4605
|
+
* Maps Gemini model names that aren't available on GitHub Copilot
|
|
4606
|
+
* to equivalent models that are.
|
|
4607
|
+
*
|
|
4608
|
+
* The Gemini CLI's routing classifier requests gemini-2.5-flash-lite
|
|
4609
|
+
* and gemini-2.5-flash, which Copilot doesn't serve. We map them to
|
|
4610
|
+
* the closest available flash model.
|
|
4611
|
+
*
|
|
4612
|
+
* Aliases are only applied when the requested model is absent from
|
|
4613
|
+
* the Copilot model list, so if Copilot adds support for these models
|
|
4614
|
+
* natively, requests will go through unchanged.
|
|
4615
|
+
*/
|
|
4616
|
+
const GEMINI_MODEL_ALIASES = {
|
|
4617
|
+
"gemini-2.5-flash-lite": "gemini-3-flash-preview",
|
|
4618
|
+
"gemini-2.5-flash": "gemini-3-flash-preview"
|
|
4619
|
+
};
|
|
4620
|
+
function resolveGeminiModelAlias(model) {
|
|
4621
|
+
if (!(model in GEMINI_MODEL_ALIASES)) return model;
|
|
4622
|
+
if (state.models?.data.some((m) => m.id === model)) return model;
|
|
4623
|
+
return GEMINI_MODEL_ALIASES[model];
|
|
4624
|
+
}
|
|
4625
|
+
|
|
4486
4626
|
//#endregion
|
|
4487
4627
|
//#region src/routes/gemini/route.ts
|
|
4488
4628
|
const geminiRoutes = new Hono();
|
|
@@ -4490,7 +4630,7 @@ geminiRoutes.post("/:modelAction", async (c) => {
|
|
|
4490
4630
|
const modelAction = c.req.param("modelAction");
|
|
4491
4631
|
const colonIndex = modelAction.lastIndexOf(":");
|
|
4492
4632
|
if (colonIndex === -1) return geminiError(c, 400, "INVALID_ARGUMENT", "Missing action in URL");
|
|
4493
|
-
const model = modelAction.slice(0, Math.max(0, colonIndex));
|
|
4633
|
+
const model = resolveGeminiModelAlias(modelAction.slice(0, Math.max(0, colonIndex)));
|
|
4494
4634
|
const action = modelAction.slice(Math.max(0, colonIndex + 1));
|
|
4495
4635
|
switch (action) {
|
|
4496
4636
|
case "generateContent": return handleGeminiGenerate(c, model, false);
|
|
@@ -6526,6 +6666,156 @@ async function checkNeedsCompactionAnthropic(payload, model, config = {}) {
|
|
|
6526
6666
|
};
|
|
6527
6667
|
}
|
|
6528
6668
|
|
|
6669
|
+
//#endregion
|
|
6670
|
+
//#region src/lib/anthropic/features.ts
|
|
6671
|
+
function normalizeForMatching(modelId) {
|
|
6672
|
+
return modelId.toLowerCase().replaceAll(/[-_.]/g, "").replace(/\d{8}$/, "");
|
|
6673
|
+
}
|
|
6674
|
+
function modelSupportsContextEditing(modelId) {
|
|
6675
|
+
const n = normalizeForMatching(modelId);
|
|
6676
|
+
return n.includes("claude") && (n.includes("haiku45") || n.includes("sonnet4") || n.includes("sonnet45") || n.includes("sonnet46") || n.includes("opus4") || n.includes("opus41") || n.includes("opus45") || n.includes("opus46"));
|
|
6677
|
+
}
|
|
6678
|
+
function modelSupportsToolSearch(modelId) {
|
|
6679
|
+
const n = normalizeForMatching(modelId);
|
|
6680
|
+
return n.includes("claude") && (n.includes("opus45") || n.includes("opus46") || n.includes("sonnet45") || n.includes("sonnet46"));
|
|
6681
|
+
}
|
|
6682
|
+
function isContextEditingEnabled(modelId) {
|
|
6683
|
+
return modelSupportsContextEditing(modelId) && state.contextEditingMode !== "off";
|
|
6684
|
+
}
|
|
6685
|
+
function modelHasAdaptiveThinking(resolvedModel) {
|
|
6686
|
+
return resolvedModel?.capabilities?.supports?.adaptive_thinking === true;
|
|
6687
|
+
}
|
|
6688
|
+
function buildAnthropicBetaHeaders(modelId, resolvedModel) {
|
|
6689
|
+
const headers = {};
|
|
6690
|
+
const betaFeatures = [];
|
|
6691
|
+
if (!modelHasAdaptiveThinking(resolvedModel)) betaFeatures.push("interleaved-thinking-2025-05-14");
|
|
6692
|
+
if (isContextEditingEnabled(modelId)) betaFeatures.push("context-management-2025-06-27");
|
|
6693
|
+
if (modelSupportsToolSearch(modelId)) betaFeatures.push("advanced-tool-use-2025-11-20");
|
|
6694
|
+
if (betaFeatures.length > 0) headers["anthropic-beta"] = betaFeatures.join(",");
|
|
6695
|
+
return headers;
|
|
6696
|
+
}
|
|
6697
|
+
const THINKING_KEEP_TURNS = 2;
|
|
6698
|
+
const TOOL_USE_TRIGGER_TYPE = "input_tokens";
|
|
6699
|
+
const TOOL_USE_TRIGGER_VALUE = 8e4;
|
|
6700
|
+
const TOOL_USE_KEEP_COUNT = 10;
|
|
6701
|
+
function buildContextManagement(mode, hasThinking) {
|
|
6702
|
+
if (mode === "off") return void 0;
|
|
6703
|
+
const edits = [];
|
|
6704
|
+
if ((mode === "clear-thinking" || mode === "clear-both") && hasThinking) edits.push({
|
|
6705
|
+
type: "clear_thinking_20251015",
|
|
6706
|
+
keep: {
|
|
6707
|
+
type: "thinking_turns",
|
|
6708
|
+
value: Math.max(1, THINKING_KEEP_TURNS)
|
|
6709
|
+
}
|
|
6710
|
+
});
|
|
6711
|
+
if (mode === "clear-tooluse" || mode === "clear-both") edits.push({
|
|
6712
|
+
type: "clear_tool_uses_20250919",
|
|
6713
|
+
trigger: {
|
|
6714
|
+
type: TOOL_USE_TRIGGER_TYPE,
|
|
6715
|
+
value: TOOL_USE_TRIGGER_VALUE
|
|
6716
|
+
},
|
|
6717
|
+
keep: {
|
|
6718
|
+
type: "tool_uses",
|
|
6719
|
+
value: TOOL_USE_KEEP_COUNT
|
|
6720
|
+
}
|
|
6721
|
+
});
|
|
6722
|
+
return edits.length > 0 ? { edits } : void 0;
|
|
6723
|
+
}
|
|
6724
|
+
|
|
6725
|
+
//#endregion
|
|
6726
|
+
//#region src/lib/anthropic/server-tool-filter.ts
|
|
6727
|
+
const SERVER_TOOL_TYPE_PREFIXES = [
|
|
6728
|
+
"web_search_",
|
|
6729
|
+
"web_fetch_",
|
|
6730
|
+
"code_execution_",
|
|
6731
|
+
"text_editor_",
|
|
6732
|
+
"computer_",
|
|
6733
|
+
"bash_"
|
|
6734
|
+
];
|
|
6735
|
+
/** Check if a block type is a server-side tool result (ends with _tool_result, but not plain tool_result) */
|
|
6736
|
+
function isServerToolResultType(type) {
|
|
6737
|
+
return type !== "tool_result" && type.endsWith("_tool_result");
|
|
6738
|
+
}
|
|
6739
|
+
/** Check if a content block is a server-side tool block */
|
|
6740
|
+
function isServerToolBlock(block) {
|
|
6741
|
+
if (block.type === "server_tool_use") return true;
|
|
6742
|
+
return isServerToolResultType(block.type);
|
|
6743
|
+
}
|
|
6744
|
+
/** Check if a tool's type field matches a known server tool prefix */
|
|
6745
|
+
function isServerToolType(type) {
|
|
6746
|
+
if (!type) return false;
|
|
6747
|
+
return SERVER_TOOL_TYPE_PREFIXES.some((prefix) => type.startsWith(prefix));
|
|
6748
|
+
}
|
|
6749
|
+
/** Log a single server tool block */
|
|
6750
|
+
function logServerToolBlock(block) {
|
|
6751
|
+
if (block.type === "server_tool_use") {
|
|
6752
|
+
consola.debug(`[ServerTool] server_tool_use: ${block.name}`);
|
|
6753
|
+
return;
|
|
6754
|
+
}
|
|
6755
|
+
if (!isServerToolResultType(block.type)) return;
|
|
6756
|
+
consola.debug(`[ServerTool] ${block.type}`);
|
|
6757
|
+
}
|
|
6758
|
+
/** Log all server tool blocks from a non-streaming response */
|
|
6759
|
+
function logServerToolBlocks(content) {
|
|
6760
|
+
for (const block of content) logServerToolBlock(block);
|
|
6761
|
+
}
|
|
6762
|
+
/**
|
|
6763
|
+
* Creates a filter for server tool blocks in SSE streams.
|
|
6764
|
+
* Handles index remapping so block indices remain dense/sequential after filtering.
|
|
6765
|
+
* Always active — matching vscode-copilot-chat behavior.
|
|
6766
|
+
*/
|
|
6767
|
+
function createServerToolBlockFilter() {
|
|
6768
|
+
const filteredIndices = /* @__PURE__ */ new Set();
|
|
6769
|
+
const clientIndexMap = /* @__PURE__ */ new Map();
|
|
6770
|
+
let nextClientIndex = 0;
|
|
6771
|
+
function getClientIndex(apiIndex) {
|
|
6772
|
+
let idx = clientIndexMap.get(apiIndex);
|
|
6773
|
+
if (idx === void 0) {
|
|
6774
|
+
idx = nextClientIndex++;
|
|
6775
|
+
clientIndexMap.set(apiIndex, idx);
|
|
6776
|
+
}
|
|
6777
|
+
return idx;
|
|
6778
|
+
}
|
|
6779
|
+
return { rewriteEvent(parsed, rawData) {
|
|
6780
|
+
if (!parsed) return rawData;
|
|
6781
|
+
if (parsed.type === "content_block_start") {
|
|
6782
|
+
const block = parsed.content_block;
|
|
6783
|
+
if (isServerToolBlock(block)) {
|
|
6784
|
+
filteredIndices.add(parsed.index);
|
|
6785
|
+
return null;
|
|
6786
|
+
}
|
|
6787
|
+
if (filteredIndices.size === 0) {
|
|
6788
|
+
getClientIndex(parsed.index);
|
|
6789
|
+
return rawData;
|
|
6790
|
+
}
|
|
6791
|
+
const clientIndex = getClientIndex(parsed.index);
|
|
6792
|
+
if (clientIndex === parsed.index) return rawData;
|
|
6793
|
+
const obj = JSON.parse(rawData);
|
|
6794
|
+
obj.index = clientIndex;
|
|
6795
|
+
return JSON.stringify(obj);
|
|
6796
|
+
}
|
|
6797
|
+
if (parsed.type === "content_block_delta" || parsed.type === "content_block_stop") {
|
|
6798
|
+
if (filteredIndices.has(parsed.index)) return null;
|
|
6799
|
+
if (filteredIndices.size === 0) return rawData;
|
|
6800
|
+
const clientIndex = getClientIndex(parsed.index);
|
|
6801
|
+
if (clientIndex === parsed.index) return rawData;
|
|
6802
|
+
const obj = JSON.parse(rawData);
|
|
6803
|
+
obj.index = clientIndex;
|
|
6804
|
+
return JSON.stringify(obj);
|
|
6805
|
+
}
|
|
6806
|
+
return rawData;
|
|
6807
|
+
} };
|
|
6808
|
+
}
|
|
6809
|
+
/** Filter server tool blocks from a non-streaming response */
|
|
6810
|
+
function filterServerToolBlocksFromResponse(response) {
|
|
6811
|
+
const filtered = response.content.filter((block) => !isServerToolBlock(block));
|
|
6812
|
+
if (filtered.length === response.content.length) return response;
|
|
6813
|
+
return {
|
|
6814
|
+
...response,
|
|
6815
|
+
content: filtered
|
|
6816
|
+
};
|
|
6817
|
+
}
|
|
6818
|
+
|
|
6529
6819
|
//#endregion
|
|
6530
6820
|
//#region src/services/copilot/create-anthropic-messages.ts
|
|
6531
6821
|
/**
|
|
@@ -6550,14 +6840,15 @@ const COPILOT_SUPPORTED_FIELDS = new Set([
|
|
|
6550
6840
|
"tools",
|
|
6551
6841
|
"tool_choice",
|
|
6552
6842
|
"thinking",
|
|
6553
|
-
"service_tier"
|
|
6843
|
+
"service_tier",
|
|
6844
|
+
"context_management"
|
|
6554
6845
|
]);
|
|
6555
6846
|
/**
|
|
6556
6847
|
* Filter payload to only include fields supported by Copilot's Anthropic API.
|
|
6557
6848
|
* This prevents errors like "Extra inputs are not permitted" for unsupported
|
|
6558
6849
|
* fields like `output_config`.
|
|
6559
6850
|
*
|
|
6560
|
-
*
|
|
6851
|
+
* Optionally strips server-side tools when state.stripServerTools is enabled.
|
|
6561
6852
|
*/
|
|
6562
6853
|
function filterPayloadForCopilot(payload) {
|
|
6563
6854
|
const filtered = {};
|
|
@@ -6565,7 +6856,7 @@ function filterPayloadForCopilot(payload) {
|
|
|
6565
6856
|
for (const [key, value] of Object.entries(payload)) if (COPILOT_SUPPORTED_FIELDS.has(key)) filtered[key] = value;
|
|
6566
6857
|
else unsupportedFields.push(key);
|
|
6567
6858
|
if (unsupportedFields.length > 0) consola.debug(`[DirectAnthropic] Filtered unsupported fields: ${unsupportedFields.join(", ")}`);
|
|
6568
|
-
if (filtered.tools) filtered.tools =
|
|
6859
|
+
if (filtered.tools) filtered.tools = stripServerToolsFromPayload(filtered.tools);
|
|
6569
6860
|
return filtered;
|
|
6570
6861
|
}
|
|
6571
6862
|
/**
|
|
@@ -6596,6 +6887,7 @@ async function createAnthropicMessages(payload, options) {
|
|
|
6596
6887
|
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
6597
6888
|
let filteredPayload = filterPayloadForCopilot(payload);
|
|
6598
6889
|
filteredPayload = adjustMaxTokensForThinking(filteredPayload);
|
|
6890
|
+
const resolvedModel = state.models?.data.find((m) => m.id === filteredPayload.model);
|
|
6599
6891
|
const enableVision = filteredPayload.messages.some((msg) => {
|
|
6600
6892
|
if (typeof msg.content === "string") return false;
|
|
6601
6893
|
return msg.content.some((block) => block.type === "image");
|
|
@@ -6609,6 +6901,16 @@ async function createAnthropicMessages(payload, options) {
|
|
|
6609
6901
|
"X-Initiator": options?.initiator ?? (isAgentCall ? "agent" : "user"),
|
|
6610
6902
|
"anthropic-version": "2023-06-01"
|
|
6611
6903
|
};
|
|
6904
|
+
const betaHeaders = buildAnthropicBetaHeaders(filteredPayload.model, resolvedModel);
|
|
6905
|
+
Object.assign(headers, betaHeaders);
|
|
6906
|
+
if (isContextEditingEnabled(filteredPayload.model)) {
|
|
6907
|
+
const hasThinking = filteredPayload.thinking?.type === "enabled";
|
|
6908
|
+
const cm = buildContextManagement(state.contextEditingMode, hasThinking);
|
|
6909
|
+
if (cm) {
|
|
6910
|
+
filteredPayload.context_management = cm;
|
|
6911
|
+
consola.debug("[DirectAnthropic] Added context_management:", JSON.stringify(cm));
|
|
6912
|
+
}
|
|
6913
|
+
}
|
|
6612
6914
|
consola.debug("Sending direct Anthropic request to Copilot /v1/messages");
|
|
6613
6915
|
const response = await fetch(`${copilotBaseUrl(state)}/v1/messages`, {
|
|
6614
6916
|
method: "POST",
|
|
@@ -6632,96 +6934,21 @@ async function createAnthropicMessages(payload, options) {
|
|
|
6632
6934
|
if (payload.stream) return events(response);
|
|
6633
6935
|
return await response.json();
|
|
6634
6936
|
}
|
|
6635
|
-
const SERVER_TOOL_CONFIGS = {
|
|
6636
|
-
web_search: {
|
|
6637
|
-
description: "Search the web for current information. Returns web search results that can help answer questions about recent events, current data, or information that may have changed since your knowledge cutoff.",
|
|
6638
|
-
input_schema: {
|
|
6639
|
-
type: "object",
|
|
6640
|
-
properties: { query: {
|
|
6641
|
-
type: "string",
|
|
6642
|
-
description: "The search query"
|
|
6643
|
-
} },
|
|
6644
|
-
required: ["query"]
|
|
6645
|
-
}
|
|
6646
|
-
},
|
|
6647
|
-
web_fetch: {
|
|
6648
|
-
description: "Fetch content from a URL. NOTE: This is a client-side tool - the client must fetch the URL and return the content.",
|
|
6649
|
-
input_schema: {
|
|
6650
|
-
type: "object",
|
|
6651
|
-
properties: { url: {
|
|
6652
|
-
type: "string",
|
|
6653
|
-
description: "The URL to fetch"
|
|
6654
|
-
} },
|
|
6655
|
-
required: ["url"]
|
|
6656
|
-
}
|
|
6657
|
-
},
|
|
6658
|
-
code_execution: {
|
|
6659
|
-
description: "Execute code in a sandbox. NOTE: This is a client-side tool - the client must execute the code.",
|
|
6660
|
-
input_schema: {
|
|
6661
|
-
type: "object",
|
|
6662
|
-
properties: {
|
|
6663
|
-
code: {
|
|
6664
|
-
type: "string",
|
|
6665
|
-
description: "The code to execute"
|
|
6666
|
-
},
|
|
6667
|
-
language: {
|
|
6668
|
-
type: "string",
|
|
6669
|
-
description: "The programming language"
|
|
6670
|
-
}
|
|
6671
|
-
},
|
|
6672
|
-
required: ["code"]
|
|
6673
|
-
}
|
|
6674
|
-
},
|
|
6675
|
-
computer: {
|
|
6676
|
-
description: "Control computer desktop. NOTE: This is a client-side tool - the client must handle computer control.",
|
|
6677
|
-
input_schema: {
|
|
6678
|
-
type: "object",
|
|
6679
|
-
properties: { action: {
|
|
6680
|
-
type: "string",
|
|
6681
|
-
description: "The action to perform"
|
|
6682
|
-
} },
|
|
6683
|
-
required: ["action"]
|
|
6684
|
-
}
|
|
6685
|
-
}
|
|
6686
|
-
};
|
|
6687
|
-
/**
|
|
6688
|
-
* Check if a tool is a server-side tool that needs conversion.
|
|
6689
|
-
*/
|
|
6690
|
-
function getServerToolPrefix(tool) {
|
|
6691
|
-
if (tool.type) {
|
|
6692
|
-
for (const prefix of Object.keys(SERVER_TOOL_CONFIGS)) if (tool.type.startsWith(prefix)) return prefix;
|
|
6693
|
-
}
|
|
6694
|
-
return null;
|
|
6695
|
-
}
|
|
6696
6937
|
/**
|
|
6697
|
-
*
|
|
6698
|
-
*
|
|
6699
|
-
*
|
|
6700
|
-
* Note: Server-side tools are only converted if state.rewriteAnthropicTools is enabled.
|
|
6938
|
+
* Strip server-side tools from the tools array when configured.
|
|
6939
|
+
* When state.stripServerTools is enabled, server tools are removed from the request.
|
|
6940
|
+
* When disabled (default), server tools are passed through unchanged.
|
|
6701
6941
|
*/
|
|
6702
|
-
function
|
|
6703
|
-
if (!tools) return;
|
|
6942
|
+
function stripServerToolsFromPayload(tools) {
|
|
6943
|
+
if (!tools) return void 0;
|
|
6944
|
+
if (!state.stripServerTools) return tools;
|
|
6704
6945
|
const result = [];
|
|
6705
6946
|
for (const tool of tools) {
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
result.push(tool);
|
|
6712
|
-
continue;
|
|
6713
|
-
}
|
|
6714
|
-
if (config.remove) {
|
|
6715
|
-
consola.warn(`[DirectAnthropic] Removing unsupported server tool: ${tool.name}. Reason: ${config.removalReason}`);
|
|
6716
|
-
continue;
|
|
6717
|
-
}
|
|
6718
|
-
consola.debug(`[DirectAnthropic] Converting server tool to custom: ${tool.name} (type: ${tool.type})`);
|
|
6719
|
-
result.push({
|
|
6720
|
-
name: tool.name,
|
|
6721
|
-
description: config.description,
|
|
6722
|
-
input_schema: config.input_schema
|
|
6723
|
-
});
|
|
6724
|
-
} else result.push(tool);
|
|
6947
|
+
if (isServerToolType(tool.type)) {
|
|
6948
|
+
consola.warn(`[DirectAnthropic] Stripping server tool: ${tool.name} (type: ${tool.type})`);
|
|
6949
|
+
continue;
|
|
6950
|
+
}
|
|
6951
|
+
result.push(tool);
|
|
6725
6952
|
}
|
|
6726
6953
|
return result.length > 0 ? result : void 0;
|
|
6727
6954
|
}
|
|
@@ -6816,9 +7043,6 @@ function createAnthropicStreamAccumulator() {
|
|
|
6816
7043
|
currentToolCall: null
|
|
6817
7044
|
};
|
|
6818
7045
|
}
|
|
6819
|
-
function isServerToolResultType(type) {
|
|
6820
|
-
return type !== "tool_result" && type.endsWith("_tool_result");
|
|
6821
|
-
}
|
|
6822
7046
|
function processAnthropicEvent(event, acc) {
|
|
6823
7047
|
switch (event.type) {
|
|
6824
7048
|
case "content_block_delta":
|
|
@@ -7467,6 +7691,8 @@ function handleDirectAnthropicNonStreamingResponse(c, response, ctx, truncateRes
|
|
|
7467
7691
|
});
|
|
7468
7692
|
let finalResponse = response;
|
|
7469
7693
|
if (state.verbose && truncateResult?.wasCompacted) finalResponse = prependMarkerToAnthropicResponse$1(response, createTruncationMarker$1(truncateResult));
|
|
7694
|
+
logServerToolBlocks(finalResponse.content);
|
|
7695
|
+
finalResponse = filterServerToolBlocksFromResponse(finalResponse);
|
|
7470
7696
|
return c.json(finalResponse);
|
|
7471
7697
|
}
|
|
7472
7698
|
/**
|
|
@@ -7498,6 +7724,7 @@ async function handleDirectAnthropicStreamingResponse(opts) {
|
|
|
7498
7724
|
const { stream, response, anthropicPayload, ctx } = opts;
|
|
7499
7725
|
const acc = createAnthropicStreamAccumulator();
|
|
7500
7726
|
const checkRepetition = createStreamRepetitionChecker(`anthropic:${anthropicPayload.model}`);
|
|
7727
|
+
const serverToolFilter = createServerToolBlockFilter();
|
|
7501
7728
|
try {
|
|
7502
7729
|
for await (const rawEvent of response) {
|
|
7503
7730
|
consola.debug("Direct Anthropic raw stream event:", JSON.stringify(rawEvent));
|
|
@@ -7511,10 +7738,13 @@ async function handleDirectAnthropicStreamingResponse(opts) {
|
|
|
7511
7738
|
continue;
|
|
7512
7739
|
}
|
|
7513
7740
|
processAnthropicEvent(event, acc);
|
|
7741
|
+
if (event.type === "content_block_start") logServerToolBlock(event.content_block);
|
|
7514
7742
|
if (event.type === "content_block_delta" && event.delta.type === "text_delta") checkRepetition(event.delta.text);
|
|
7743
|
+
const forwardData = serverToolFilter.rewriteEvent(event, rawEvent.data);
|
|
7744
|
+
if (forwardData === null) continue;
|
|
7515
7745
|
await stream.writeSSE({
|
|
7516
7746
|
event: rawEvent.event || event.type,
|
|
7517
|
-
data:
|
|
7747
|
+
data: forwardData
|
|
7518
7748
|
});
|
|
7519
7749
|
}
|
|
7520
7750
|
recordAnthropicStreamingResponse(acc, anthropicPayload.model, ctx);
|
|
@@ -8032,6 +8262,38 @@ const handleItemId = (parsed, tracker) => {
|
|
|
8032
8262
|
|
|
8033
8263
|
//#endregion
|
|
8034
8264
|
//#region src/routes/responses/utils.ts
|
|
8265
|
+
const CALL_PREFIX = "call_";
|
|
8266
|
+
const FC_PREFIX = "fc_";
|
|
8267
|
+
/**
|
|
8268
|
+
* Normalize function call IDs in Responses API input.
|
|
8269
|
+
* Converts Chat Completions format `call_xxx` IDs to Responses format `fc_xxx` IDs
|
|
8270
|
+
* on function_call and function_call_output items.
|
|
8271
|
+
*/
|
|
8272
|
+
function normalizeCallIds(payload) {
|
|
8273
|
+
if (typeof payload.input === "string") return payload;
|
|
8274
|
+
let count = 0;
|
|
8275
|
+
const input = payload.input.map((item) => {
|
|
8276
|
+
if ("call_id" in item && typeof item.call_id === "string") {
|
|
8277
|
+
const callId = item.call_id;
|
|
8278
|
+
if (callId.startsWith(CALL_PREFIX)) {
|
|
8279
|
+
count++;
|
|
8280
|
+
return {
|
|
8281
|
+
...item,
|
|
8282
|
+
call_id: FC_PREFIX + callId.slice(5)
|
|
8283
|
+
};
|
|
8284
|
+
}
|
|
8285
|
+
}
|
|
8286
|
+
return item;
|
|
8287
|
+
});
|
|
8288
|
+
if (count > 0) {
|
|
8289
|
+
consola.debug(`[Responses] Normalized ${count} call IDs (call_ → fc_)`);
|
|
8290
|
+
return {
|
|
8291
|
+
...payload,
|
|
8292
|
+
input
|
|
8293
|
+
};
|
|
8294
|
+
}
|
|
8295
|
+
return payload;
|
|
8296
|
+
}
|
|
8035
8297
|
const getResponsesRequestOptions = (payload) => {
|
|
8036
8298
|
return {
|
|
8037
8299
|
vision: hasVisionInput(payload),
|
|
@@ -8182,7 +8444,8 @@ const TERMINAL_EVENTS = new Set([
|
|
|
8182
8444
|
"error"
|
|
8183
8445
|
]);
|
|
8184
8446
|
const handleResponses = async (c) => {
|
|
8185
|
-
|
|
8447
|
+
let payload = await c.req.json();
|
|
8448
|
+
if (state.normalizeResponsesCallIds) payload = normalizeCallIds(payload);
|
|
8186
8449
|
consola.debug("Responses request payload:", JSON.stringify(payload));
|
|
8187
8450
|
const trackingId = c.get("trackingId");
|
|
8188
8451
|
const startTime = (trackingId ? requestTracker.getRequest(trackingId) : void 0)?.startTime ?? Date.now();
|
|
@@ -8461,7 +8724,8 @@ async function runServer(options) {
|
|
|
8461
8724
|
state.autoTruncate = options.autoTruncate;
|
|
8462
8725
|
state.compressToolResults = options.compressToolResults;
|
|
8463
8726
|
state.redirectAnthropic = options.redirectAnthropic;
|
|
8464
|
-
state.
|
|
8727
|
+
state.stripServerTools = options.stripServerTools;
|
|
8728
|
+
state.contextEditingMode = options.contextEditing;
|
|
8465
8729
|
state.timezoneOffset = options.timezoneOffset;
|
|
8466
8730
|
if (options.rateLimit) initAdaptiveRateLimiter({
|
|
8467
8731
|
baseRetryIntervalSeconds: options.retryInterval,
|
|
@@ -8473,11 +8737,13 @@ async function runServer(options) {
|
|
|
8473
8737
|
if (!options.autoTruncate) consola.info("Auto-truncate disabled");
|
|
8474
8738
|
if (options.compressToolResults) consola.info("Tool result compression enabled");
|
|
8475
8739
|
if (options.redirectAnthropic) consola.info("Anthropic API redirect enabled (using OpenAI translation)");
|
|
8476
|
-
if (
|
|
8740
|
+
if (options.stripServerTools) consola.info("Server-side tools will be stripped from requests");
|
|
8741
|
+
if (options.contextEditing !== "off") consola.info(`Context editing mode: ${options.contextEditing}`);
|
|
8477
8742
|
initHistory(options.history, options.historyLimit);
|
|
8478
8743
|
if (options.history) {
|
|
8479
8744
|
const limitText = options.historyLimit === 0 ? "unlimited" : `max ${options.historyLimit}`;
|
|
8480
8745
|
consola.info(`History recording enabled (${limitText} entries)`);
|
|
8746
|
+
startMemoryPressureMonitor();
|
|
8481
8747
|
}
|
|
8482
8748
|
if (options.posthogKey) {
|
|
8483
8749
|
initPostHog(options.posthogKey);
|
|
@@ -8648,10 +8914,15 @@ const start = defineCommand({
|
|
|
8648
8914
|
default: false,
|
|
8649
8915
|
description: "Redirect Anthropic models through OpenAI translation (instead of direct API)"
|
|
8650
8916
|
},
|
|
8651
|
-
"
|
|
8917
|
+
"strip-server-tools": {
|
|
8652
8918
|
type: "boolean",
|
|
8653
8919
|
default: false,
|
|
8654
|
-
description: "
|
|
8920
|
+
description: "Strip Anthropic server-side tools (web_search, etc.) from requests"
|
|
8921
|
+
},
|
|
8922
|
+
"context-editing": {
|
|
8923
|
+
type: "string",
|
|
8924
|
+
default: "off",
|
|
8925
|
+
description: "Context editing mode: off, clear-thinking, clear-tooluse, clear-both"
|
|
8655
8926
|
},
|
|
8656
8927
|
"timezone-offset": {
|
|
8657
8928
|
type: "string",
|
|
@@ -8684,7 +8955,8 @@ const start = defineCommand({
|
|
|
8684
8955
|
autoTruncate: !args["no-auto-truncate"],
|
|
8685
8956
|
compressToolResults: args["compress-tool-results"],
|
|
8686
8957
|
redirectAnthropic: args["redirect-anthropic"],
|
|
8687
|
-
|
|
8958
|
+
stripServerTools: args["strip-server-tools"],
|
|
8959
|
+
contextEditing: args["context-editing"],
|
|
8688
8960
|
timezoneOffset: parseTimezoneOffset(args["timezone-offset"]),
|
|
8689
8961
|
posthogKey: args["posthog-key"]
|
|
8690
8962
|
});
|