billion-context 0.1.33 → 0.1.35
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 +70 -2
- package/dist/index.js +1244 -1619
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1029,7 +1029,7 @@ var require_util = __commonJS({
|
|
|
1029
1029
|
var { kDestroyed, kBodyUsed, kListeners, kBody } = require_symbols();
|
|
1030
1030
|
var { IncomingMessage } = __require("http");
|
|
1031
1031
|
var stream2 = __require("stream");
|
|
1032
|
-
var
|
|
1032
|
+
var net3 = __require("net");
|
|
1033
1033
|
var { stringify } = __require("querystring");
|
|
1034
1034
|
var { EventEmitter: EE } = __require("events");
|
|
1035
1035
|
var timers = require_timers();
|
|
@@ -1141,14 +1141,14 @@ var require_util = __commonJS({
|
|
|
1141
1141
|
}
|
|
1142
1142
|
const port = url.port != null ? url.port : url.protocol === "https:" ? 443 : 80;
|
|
1143
1143
|
let origin = url.origin != null ? url.origin : `${url.protocol || ""}//${url.hostname || ""}:${port}`;
|
|
1144
|
-
let
|
|
1144
|
+
let path9 = url.path != null ? url.path : `${url.pathname || ""}${url.search || ""}`;
|
|
1145
1145
|
if (origin[origin.length - 1] === "/") {
|
|
1146
1146
|
origin = origin.slice(0, origin.length - 1);
|
|
1147
1147
|
}
|
|
1148
|
-
if (
|
|
1149
|
-
|
|
1148
|
+
if (path9 && path9[0] !== "/") {
|
|
1149
|
+
path9 = `/${path9}`;
|
|
1150
1150
|
}
|
|
1151
|
-
return new URL(`${origin}${
|
|
1151
|
+
return new URL(`${origin}${path9}`);
|
|
1152
1152
|
}
|
|
1153
1153
|
if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) {
|
|
1154
1154
|
throw new InvalidArgumentError("Invalid URL protocol: the URL must start with `http:` or `https:`.");
|
|
@@ -1178,7 +1178,7 @@ var require_util = __commonJS({
|
|
|
1178
1178
|
}
|
|
1179
1179
|
assert(typeof host === "string");
|
|
1180
1180
|
const servername = getHostname(host);
|
|
1181
|
-
if (
|
|
1181
|
+
if (net3.isIP(servername)) {
|
|
1182
1182
|
return "";
|
|
1183
1183
|
}
|
|
1184
1184
|
return servername;
|
|
@@ -1969,9 +1969,9 @@ var require_diagnostics = __commonJS({
|
|
|
1969
1969
|
"undici:client:sendHeaders",
|
|
1970
1970
|
(evt) => {
|
|
1971
1971
|
const {
|
|
1972
|
-
request: { method, path:
|
|
1972
|
+
request: { method, path: path9, origin }
|
|
1973
1973
|
} = evt;
|
|
1974
|
-
debugLog("sending request to %s %s%s", method, origin,
|
|
1974
|
+
debugLog("sending request to %s %s%s", method, origin, path9);
|
|
1975
1975
|
}
|
|
1976
1976
|
);
|
|
1977
1977
|
}
|
|
@@ -1989,14 +1989,14 @@ var require_diagnostics = __commonJS({
|
|
|
1989
1989
|
"undici:request:headers",
|
|
1990
1990
|
(evt) => {
|
|
1991
1991
|
const {
|
|
1992
|
-
request: { method, path:
|
|
1992
|
+
request: { method, path: path9, origin },
|
|
1993
1993
|
response: { statusCode }
|
|
1994
1994
|
} = evt;
|
|
1995
1995
|
debugLog(
|
|
1996
1996
|
"received response to %s %s%s - HTTP %d",
|
|
1997
1997
|
method,
|
|
1998
1998
|
origin,
|
|
1999
|
-
|
|
1999
|
+
path9,
|
|
2000
2000
|
statusCode
|
|
2001
2001
|
);
|
|
2002
2002
|
}
|
|
@@ -2005,23 +2005,23 @@ var require_diagnostics = __commonJS({
|
|
|
2005
2005
|
"undici:request:trailers",
|
|
2006
2006
|
(evt) => {
|
|
2007
2007
|
const {
|
|
2008
|
-
request: { method, path:
|
|
2008
|
+
request: { method, path: path9, origin }
|
|
2009
2009
|
} = evt;
|
|
2010
|
-
debugLog("trailers received from %s %s%s", method, origin,
|
|
2010
|
+
debugLog("trailers received from %s %s%s", method, origin, path9);
|
|
2011
2011
|
}
|
|
2012
2012
|
);
|
|
2013
2013
|
diagnosticsChannel.subscribe(
|
|
2014
2014
|
"undici:request:error",
|
|
2015
2015
|
(evt) => {
|
|
2016
2016
|
const {
|
|
2017
|
-
request: { method, path:
|
|
2017
|
+
request: { method, path: path9, origin },
|
|
2018
2018
|
error
|
|
2019
2019
|
} = evt;
|
|
2020
2020
|
debugLog(
|
|
2021
2021
|
"request to %s %s%s errored - %s",
|
|
2022
2022
|
method,
|
|
2023
2023
|
origin,
|
|
2024
|
-
|
|
2024
|
+
path9,
|
|
2025
2025
|
error.message
|
|
2026
2026
|
);
|
|
2027
2027
|
}
|
|
@@ -2136,7 +2136,7 @@ var require_request = __commonJS({
|
|
|
2136
2136
|
var kHandler = /* @__PURE__ */ Symbol("handler");
|
|
2137
2137
|
var Request = class {
|
|
2138
2138
|
constructor(origin, {
|
|
2139
|
-
path:
|
|
2139
|
+
path: path9,
|
|
2140
2140
|
method,
|
|
2141
2141
|
body,
|
|
2142
2142
|
headers,
|
|
@@ -2153,11 +2153,11 @@ var require_request = __commonJS({
|
|
|
2153
2153
|
maxRedirections,
|
|
2154
2154
|
typeOfService
|
|
2155
2155
|
}, handler) {
|
|
2156
|
-
if (typeof
|
|
2156
|
+
if (typeof path9 !== "string") {
|
|
2157
2157
|
throw new InvalidArgumentError("path must be a string");
|
|
2158
|
-
} else if (
|
|
2158
|
+
} else if (path9[0] !== "/" && !(path9.startsWith("http://") || path9.startsWith("https://")) && method !== "CONNECT") {
|
|
2159
2159
|
throw new InvalidArgumentError("path must be an absolute URL or start with a slash");
|
|
2160
|
-
} else if (invalidPathRegex.test(
|
|
2160
|
+
} else if (invalidPathRegex.test(path9)) {
|
|
2161
2161
|
throw new InvalidArgumentError("invalid request path");
|
|
2162
2162
|
}
|
|
2163
2163
|
if (typeof method !== "string") {
|
|
@@ -2232,7 +2232,7 @@ var require_request = __commonJS({
|
|
|
2232
2232
|
this.completed = false;
|
|
2233
2233
|
this.aborted = false;
|
|
2234
2234
|
this.upgrade = upgrade || null;
|
|
2235
|
-
this.path = query ? serializePathWithQuery(
|
|
2235
|
+
this.path = query ? serializePathWithQuery(path9, query) : path9;
|
|
2236
2236
|
this.origin = origin;
|
|
2237
2237
|
this.protocol = getProtocolFromUrlString(origin);
|
|
2238
2238
|
this.idempotent = idempotent == null ? method === "HEAD" || method === "GET" : idempotent;
|
|
@@ -2841,7 +2841,7 @@ var require_dispatcher_base = __commonJS({
|
|
|
2841
2841
|
var require_connect = __commonJS({
|
|
2842
2842
|
"node_modules/undici/lib/core/connect.js"(exports, module) {
|
|
2843
2843
|
"use strict";
|
|
2844
|
-
var
|
|
2844
|
+
var net3 = __require("net");
|
|
2845
2845
|
var assert = __require("assert");
|
|
2846
2846
|
var util = require_util();
|
|
2847
2847
|
var { InvalidArgumentError } = require_errors();
|
|
@@ -2924,7 +2924,7 @@ var require_connect = __commonJS({
|
|
|
2924
2924
|
} else {
|
|
2925
2925
|
assert(!httpSocket, "httpSocket can only be sent on TLS update");
|
|
2926
2926
|
port = port || 80;
|
|
2927
|
-
socket =
|
|
2927
|
+
socket = net3.connect({
|
|
2928
2928
|
highWaterMark: 64 * 1024,
|
|
2929
2929
|
// Same as nodejs fs streams.
|
|
2930
2930
|
...options,
|
|
@@ -7415,7 +7415,7 @@ var require_client_h1 = __commonJS({
|
|
|
7415
7415
|
return method !== "GET" && method !== "HEAD" && method !== "OPTIONS" && method !== "TRACE" && method !== "CONNECT";
|
|
7416
7416
|
}
|
|
7417
7417
|
function writeH1(client, request) {
|
|
7418
|
-
const { method, path:
|
|
7418
|
+
const { method, path: path9, host, upgrade, blocking, reset } = request;
|
|
7419
7419
|
let { body, headers, contentLength } = request;
|
|
7420
7420
|
const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH" || method === "QUERY" || method === "PROPFIND" || method === "PROPPATCH";
|
|
7421
7421
|
if (util.isFormDataLike(body)) {
|
|
@@ -7493,7 +7493,7 @@ var require_client_h1 = __commonJS({
|
|
|
7493
7493
|
if (socket.setTypeOfService) {
|
|
7494
7494
|
socket.setTypeOfService(request.typeOfService);
|
|
7495
7495
|
}
|
|
7496
|
-
let header = `${method} ${
|
|
7496
|
+
let header = `${method} ${path9} HTTP/1.1\r
|
|
7497
7497
|
`;
|
|
7498
7498
|
if (typeof host === "string") {
|
|
7499
7499
|
header += `host: ${host}\r
|
|
@@ -8146,7 +8146,7 @@ var require_client_h2 = __commonJS({
|
|
|
8146
8146
|
function writeH2(client, request) {
|
|
8147
8147
|
const requestTimeout = request.bodyTimeout ?? client[kBodyTimeout];
|
|
8148
8148
|
const session = client[kHTTP2Session];
|
|
8149
|
-
const { method, path:
|
|
8149
|
+
const { method, path: path9, host, upgrade, expectContinue, signal, protocol, headers: reqHeaders } = request;
|
|
8150
8150
|
let { body } = request;
|
|
8151
8151
|
if (upgrade != null && upgrade !== "websocket") {
|
|
8152
8152
|
util.errorRequest(client, request, new InvalidArgumentError(`Custom upgrade "${upgrade}" not supported over HTTP/2`));
|
|
@@ -8214,7 +8214,7 @@ var require_client_h2 = __commonJS({
|
|
|
8214
8214
|
}
|
|
8215
8215
|
headers[HTTP2_HEADER_METHOD] = "CONNECT";
|
|
8216
8216
|
headers[HTTP2_HEADER_PROTOCOL] = "websocket";
|
|
8217
|
-
headers[HTTP2_HEADER_PATH] =
|
|
8217
|
+
headers[HTTP2_HEADER_PATH] = path9;
|
|
8218
8218
|
if (protocol === "ws:" || protocol === "wss:") {
|
|
8219
8219
|
headers[HTTP2_HEADER_SCHEME] = protocol === "ws:" ? "http" : "https";
|
|
8220
8220
|
} else {
|
|
@@ -8255,7 +8255,7 @@ var require_client_h2 = __commonJS({
|
|
|
8255
8255
|
stream2.setTimeout(requestTimeout);
|
|
8256
8256
|
return true;
|
|
8257
8257
|
}
|
|
8258
|
-
headers[HTTP2_HEADER_PATH] =
|
|
8258
|
+
headers[HTTP2_HEADER_PATH] = path9;
|
|
8259
8259
|
headers[HTTP2_HEADER_SCHEME] = protocol === "http:" ? "http" : "https";
|
|
8260
8260
|
const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH";
|
|
8261
8261
|
if (body && typeof body.read === "function") {
|
|
@@ -8571,7 +8571,7 @@ var require_client = __commonJS({
|
|
|
8571
8571
|
"node_modules/undici/lib/dispatcher/client.js"(exports, module) {
|
|
8572
8572
|
"use strict";
|
|
8573
8573
|
var assert = __require("assert");
|
|
8574
|
-
var
|
|
8574
|
+
var net3 = __require("net");
|
|
8575
8575
|
var http2 = __require("http");
|
|
8576
8576
|
var util = require_util();
|
|
8577
8577
|
var { ClientStats } = require_stats();
|
|
@@ -8726,7 +8726,7 @@ var require_client = __commonJS({
|
|
|
8726
8726
|
if (maxRequestsPerClient != null && (!Number.isInteger(maxRequestsPerClient) || maxRequestsPerClient < 0)) {
|
|
8727
8727
|
throw new InvalidArgumentError("maxRequestsPerClient must be a positive number");
|
|
8728
8728
|
}
|
|
8729
|
-
if (localAddress != null && (typeof localAddress !== "string" ||
|
|
8729
|
+
if (localAddress != null && (typeof localAddress !== "string" || net3.isIP(localAddress) === 0)) {
|
|
8730
8730
|
throw new InvalidArgumentError("localAddress must be valid string IP address");
|
|
8731
8731
|
}
|
|
8732
8732
|
if (maxResponseSize != null && (!Number.isInteger(maxResponseSize) || maxResponseSize < -1)) {
|
|
@@ -8902,7 +8902,7 @@ var require_client = __commonJS({
|
|
|
8902
8902
|
const idx = hostname.indexOf("]");
|
|
8903
8903
|
assert(idx !== -1);
|
|
8904
8904
|
const ip = hostname.substring(1, idx);
|
|
8905
|
-
assert(
|
|
8905
|
+
assert(net3.isIPv6(ip));
|
|
8906
8906
|
hostname = ip;
|
|
8907
8907
|
}
|
|
8908
8908
|
client[kConnecting] = true;
|
|
@@ -9845,10 +9845,10 @@ var require_socks5_utils = __commonJS({
|
|
|
9845
9845
|
"node_modules/undici/lib/core/socks5-utils.js"(exports, module) {
|
|
9846
9846
|
"use strict";
|
|
9847
9847
|
var { Buffer: Buffer2 } = __require("buffer");
|
|
9848
|
-
var
|
|
9848
|
+
var net3 = __require("net");
|
|
9849
9849
|
var { InvalidArgumentError } = require_errors();
|
|
9850
9850
|
function parseAddress(address) {
|
|
9851
|
-
if (
|
|
9851
|
+
if (net3.isIPv4(address)) {
|
|
9852
9852
|
const parts = address.split(".").map(Number);
|
|
9853
9853
|
return {
|
|
9854
9854
|
type: 1,
|
|
@@ -9856,7 +9856,7 @@ var require_socks5_utils = __commonJS({
|
|
|
9856
9856
|
buffer: Buffer2.from(parts)
|
|
9857
9857
|
};
|
|
9858
9858
|
}
|
|
9859
|
-
if (
|
|
9859
|
+
if (net3.isIPv6(address)) {
|
|
9860
9860
|
return {
|
|
9861
9861
|
type: 4,
|
|
9862
9862
|
// IPv6
|
|
@@ -9879,7 +9879,7 @@ var require_socks5_utils = __commonJS({
|
|
|
9879
9879
|
if (address.includes(".")) {
|
|
9880
9880
|
const lastColonIndex = address.lastIndexOf(":");
|
|
9881
9881
|
const ipv4Part = address.slice(lastColonIndex + 1);
|
|
9882
|
-
if (
|
|
9882
|
+
if (net3.isIPv4(ipv4Part)) {
|
|
9883
9883
|
const octets = ipv4Part.split(".").map(Number);
|
|
9884
9884
|
const high = (octets[0] << 8 | octets[1]).toString(16);
|
|
9885
9885
|
const low = (octets[2] << 8 | octets[3]).toString(16);
|
|
@@ -10598,10 +10598,10 @@ var require_proxy_agent = __commonJS({
|
|
|
10598
10598
|
};
|
|
10599
10599
|
const {
|
|
10600
10600
|
origin,
|
|
10601
|
-
path:
|
|
10601
|
+
path: path9 = "/",
|
|
10602
10602
|
headers = {}
|
|
10603
10603
|
} = opts;
|
|
10604
|
-
opts.path = origin +
|
|
10604
|
+
opts.path = origin + path9;
|
|
10605
10605
|
if (!("host" in headers) && !("Host" in headers)) {
|
|
10606
10606
|
const { host } = new URL(origin);
|
|
10607
10607
|
headers.host = host;
|
|
@@ -12684,20 +12684,20 @@ var require_mock_utils = __commonJS({
|
|
|
12684
12684
|
}
|
|
12685
12685
|
return normalizedQp;
|
|
12686
12686
|
}
|
|
12687
|
-
function safeUrl(
|
|
12688
|
-
if (typeof
|
|
12689
|
-
return
|
|
12687
|
+
function safeUrl(path9) {
|
|
12688
|
+
if (typeof path9 !== "string") {
|
|
12689
|
+
return path9;
|
|
12690
12690
|
}
|
|
12691
|
-
const pathSegments =
|
|
12691
|
+
const pathSegments = path9.split("?", 3);
|
|
12692
12692
|
if (pathSegments.length !== 2) {
|
|
12693
|
-
return
|
|
12693
|
+
return path9;
|
|
12694
12694
|
}
|
|
12695
12695
|
const qp = new URLSearchParams(pathSegments.pop());
|
|
12696
12696
|
qp.sort();
|
|
12697
12697
|
return [...pathSegments, qp.toString()].join("?");
|
|
12698
12698
|
}
|
|
12699
|
-
function matchKey(mockDispatch2, { path:
|
|
12700
|
-
const pathMatch = matchValue(mockDispatch2.path,
|
|
12699
|
+
function matchKey(mockDispatch2, { path: path9, method, body, headers }) {
|
|
12700
|
+
const pathMatch = matchValue(mockDispatch2.path, path9);
|
|
12701
12701
|
const methodMatch = matchValue(mockDispatch2.method, method);
|
|
12702
12702
|
const bodyMatch = typeof mockDispatch2.body !== "undefined" ? matchValue(mockDispatch2.body, body) : true;
|
|
12703
12703
|
const headersMatch = matchHeaders(mockDispatch2, headers);
|
|
@@ -12722,8 +12722,8 @@ var require_mock_utils = __commonJS({
|
|
|
12722
12722
|
const basePath = key.query ? serializePathWithQuery(key.path, key.query) : key.path;
|
|
12723
12723
|
const resolvedPath = typeof basePath === "string" ? safeUrl(basePath) : basePath;
|
|
12724
12724
|
const resolvedPathWithoutTrailingSlash = removeTrailingSlash(resolvedPath);
|
|
12725
|
-
let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path:
|
|
12726
|
-
return ignoreTrailingSlash ? matchValue(removeTrailingSlash(safeUrl(
|
|
12725
|
+
let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path: path9, ignoreTrailingSlash }) => {
|
|
12726
|
+
return ignoreTrailingSlash ? matchValue(removeTrailingSlash(safeUrl(path9)), resolvedPathWithoutTrailingSlash) : matchValue(safeUrl(path9), resolvedPath);
|
|
12727
12727
|
});
|
|
12728
12728
|
if (matchedMockDispatches.length === 0) {
|
|
12729
12729
|
throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath}'`);
|
|
@@ -12762,19 +12762,19 @@ var require_mock_utils = __commonJS({
|
|
|
12762
12762
|
mockDispatches.splice(index, 1);
|
|
12763
12763
|
}
|
|
12764
12764
|
}
|
|
12765
|
-
function removeTrailingSlash(
|
|
12766
|
-
while (
|
|
12767
|
-
|
|
12765
|
+
function removeTrailingSlash(path9) {
|
|
12766
|
+
while (path9.endsWith("/")) {
|
|
12767
|
+
path9 = path9.slice(0, -1);
|
|
12768
12768
|
}
|
|
12769
|
-
if (
|
|
12770
|
-
|
|
12769
|
+
if (path9.length === 0) {
|
|
12770
|
+
path9 = "/";
|
|
12771
12771
|
}
|
|
12772
|
-
return
|
|
12772
|
+
return path9;
|
|
12773
12773
|
}
|
|
12774
12774
|
function buildKey(opts) {
|
|
12775
|
-
const { path:
|
|
12775
|
+
const { path: path9, method, body, headers, query } = opts;
|
|
12776
12776
|
return {
|
|
12777
|
-
path:
|
|
12777
|
+
path: path9,
|
|
12778
12778
|
method,
|
|
12779
12779
|
body,
|
|
12780
12780
|
headers,
|
|
@@ -13464,10 +13464,10 @@ var require_pending_interceptors_formatter = __commonJS({
|
|
|
13464
13464
|
}
|
|
13465
13465
|
format(pendingInterceptors) {
|
|
13466
13466
|
const withPrettyHeaders = pendingInterceptors.map(
|
|
13467
|
-
({ method, path:
|
|
13467
|
+
({ method, path: path9, data: { statusCode }, persist, times, timesInvoked, origin }) => ({
|
|
13468
13468
|
Method: method,
|
|
13469
13469
|
Origin: origin,
|
|
13470
|
-
Path:
|
|
13470
|
+
Path: path9,
|
|
13471
13471
|
"Status code": statusCode,
|
|
13472
13472
|
Persistent: persist ? PERSISTENT : NOT_PERSISTENT,
|
|
13473
13473
|
Invocations: timesInvoked,
|
|
@@ -13549,9 +13549,9 @@ var require_mock_agent = __commonJS({
|
|
|
13549
13549
|
const acceptNonStandardSearchParameters = this[kMockAgentAcceptsNonStandardSearchParameters];
|
|
13550
13550
|
const dispatchOpts = { ...opts };
|
|
13551
13551
|
if (acceptNonStandardSearchParameters && dispatchOpts.path) {
|
|
13552
|
-
const [
|
|
13552
|
+
const [path9, searchParams] = dispatchOpts.path.split("?");
|
|
13553
13553
|
const normalizedSearchParams = normalizeSearchParams(searchParams, acceptNonStandardSearchParameters);
|
|
13554
|
-
dispatchOpts.path = `${
|
|
13554
|
+
dispatchOpts.path = `${path9}?${normalizedSearchParams}`;
|
|
13555
13555
|
}
|
|
13556
13556
|
return this[kAgent].dispatch(dispatchOpts, handler);
|
|
13557
13557
|
}
|
|
@@ -13952,12 +13952,12 @@ var require_snapshot_recorder = __commonJS({
|
|
|
13952
13952
|
* @return {Promise<void>} - Resolves when snapshots are loaded
|
|
13953
13953
|
*/
|
|
13954
13954
|
async loadSnapshots(filePath) {
|
|
13955
|
-
const
|
|
13956
|
-
if (!
|
|
13955
|
+
const path9 = filePath || this.#snapshotPath;
|
|
13956
|
+
if (!path9) {
|
|
13957
13957
|
throw new InvalidArgumentError("Snapshot path is required");
|
|
13958
13958
|
}
|
|
13959
13959
|
try {
|
|
13960
|
-
const data = await readFile3(resolve(
|
|
13960
|
+
const data = await readFile3(resolve(path9), "utf8");
|
|
13961
13961
|
const parsed = JSON.parse(data);
|
|
13962
13962
|
if (Array.isArray(parsed)) {
|
|
13963
13963
|
this.#snapshots.clear();
|
|
@@ -13971,7 +13971,7 @@ var require_snapshot_recorder = __commonJS({
|
|
|
13971
13971
|
if (error.code === "ENOENT") {
|
|
13972
13972
|
this.#snapshots.clear();
|
|
13973
13973
|
} else {
|
|
13974
|
-
throw new UndiciError(`Failed to load snapshots from ${
|
|
13974
|
+
throw new UndiciError(`Failed to load snapshots from ${path9}`, { cause: error });
|
|
13975
13975
|
}
|
|
13976
13976
|
}
|
|
13977
13977
|
}
|
|
@@ -13982,11 +13982,11 @@ var require_snapshot_recorder = __commonJS({
|
|
|
13982
13982
|
* @returns {Promise<void>} - Resolves when snapshots are saved
|
|
13983
13983
|
*/
|
|
13984
13984
|
async saveSnapshots(filePath) {
|
|
13985
|
-
const
|
|
13986
|
-
if (!
|
|
13985
|
+
const path9 = filePath || this.#snapshotPath;
|
|
13986
|
+
if (!path9) {
|
|
13987
13987
|
throw new InvalidArgumentError("Snapshot path is required");
|
|
13988
13988
|
}
|
|
13989
|
-
const resolvedPath = resolve(
|
|
13989
|
+
const resolvedPath = resolve(path9);
|
|
13990
13990
|
await mkdir3(dirname6(resolvedPath), { recursive: true });
|
|
13991
13991
|
const data = Array.from(this.#snapshots.entries()).map(([hash, snapshot]) => ({
|
|
13992
13992
|
hash,
|
|
@@ -14618,15 +14618,15 @@ var require_redirect_handler = __commonJS({
|
|
|
14618
14618
|
return;
|
|
14619
14619
|
}
|
|
14620
14620
|
const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin)));
|
|
14621
|
-
const
|
|
14622
|
-
const redirectUrlString = `${origin}${
|
|
14621
|
+
const path9 = search ? `${pathname}${search}` : pathname;
|
|
14622
|
+
const redirectUrlString = `${origin}${path9}`;
|
|
14623
14623
|
for (const historyUrl of this.history) {
|
|
14624
14624
|
if (historyUrl.toString() === redirectUrlString) {
|
|
14625
14625
|
throw new InvalidArgumentError(`Redirect loop detected. Cannot redirect to ${origin}. This typically happens when using a Client or Pool with cross-origin redirects. Use an Agent for cross-origin redirects.`);
|
|
14626
14626
|
}
|
|
14627
14627
|
}
|
|
14628
14628
|
this.opts.headers = cleanRequestHeaders(this.opts.headers, statusCode === 303, this.opts.origin !== origin);
|
|
14629
|
-
this.opts.path =
|
|
14629
|
+
this.opts.path = path9;
|
|
14630
14630
|
this.opts.origin = origin;
|
|
14631
14631
|
this.opts.query = null;
|
|
14632
14632
|
}
|
|
@@ -16395,10 +16395,10 @@ var require_cache_handler = __commonJS({
|
|
|
16395
16395
|
}
|
|
16396
16396
|
return locationUrl.pathname + locationUrl.search;
|
|
16397
16397
|
}
|
|
16398
|
-
function deleteCachedUri(store, cacheKey,
|
|
16398
|
+
function deleteCachedUri(store, cacheKey, path9) {
|
|
16399
16399
|
deleteCachedValue(store, {
|
|
16400
16400
|
...cacheKey,
|
|
16401
|
-
path:
|
|
16401
|
+
path: path9
|
|
16402
16402
|
});
|
|
16403
16403
|
for (let i = 0; i < util.safeHTTPMethods.length; i++) {
|
|
16404
16404
|
const method = util.safeHTTPMethods[i];
|
|
@@ -16406,7 +16406,7 @@ var require_cache_handler = __commonJS({
|
|
|
16406
16406
|
deleteCachedValue(store, {
|
|
16407
16407
|
...cacheKey,
|
|
16408
16408
|
method,
|
|
16409
|
-
path:
|
|
16409
|
+
path: path9
|
|
16410
16410
|
});
|
|
16411
16411
|
}
|
|
16412
16412
|
}
|
|
@@ -16417,9 +16417,9 @@ var require_cache_handler = __commonJS({
|
|
|
16417
16417
|
}
|
|
16418
16418
|
const values = Array.isArray(headerValue2) ? headerValue2 : [headerValue2];
|
|
16419
16419
|
for (let i = 0; i < values.length; i++) {
|
|
16420
|
-
const
|
|
16421
|
-
if (
|
|
16422
|
-
deleteCachedUri(store, cacheKey,
|
|
16420
|
+
const path9 = getSameOriginPath(cacheKey, values[i]);
|
|
16421
|
+
if (path9 !== void 0) {
|
|
16422
|
+
deleteCachedUri(store, cacheKey, path9);
|
|
16423
16423
|
}
|
|
16424
16424
|
}
|
|
16425
16425
|
}
|
|
@@ -21297,11 +21297,11 @@ var require_fetch = __commonJS({
|
|
|
21297
21297
|
function dispatch({ body }) {
|
|
21298
21298
|
const url = requestCurrentURL(request);
|
|
21299
21299
|
const agent = fetchParams.controller.dispatcher;
|
|
21300
|
-
const
|
|
21300
|
+
const path9 = url.pathname + url.search;
|
|
21301
21301
|
const hasTrailingQuestionMark = url.search.length === 0 && url.href[url.href.length - url.hash.length - 1] === "?";
|
|
21302
21302
|
return new Promise((resolve, reject) => agent.dispatch(
|
|
21303
21303
|
{
|
|
21304
|
-
path: hasTrailingQuestionMark ? `${
|
|
21304
|
+
path: hasTrailingQuestionMark ? `${path9}?` : path9,
|
|
21305
21305
|
origin: url.origin,
|
|
21306
21306
|
method: request.method,
|
|
21307
21307
|
body: agent.isMockActive ? request.body && (request.body.source || request.body.stream) : body,
|
|
@@ -22248,9 +22248,9 @@ var require_util4 = __commonJS({
|
|
|
22248
22248
|
}
|
|
22249
22249
|
}
|
|
22250
22250
|
}
|
|
22251
|
-
function validateCookiePath(
|
|
22252
|
-
for (let i = 0; i <
|
|
22253
|
-
const code =
|
|
22251
|
+
function validateCookiePath(path9) {
|
|
22252
|
+
for (let i = 0; i < path9.length; ++i) {
|
|
22253
|
+
const code = path9.charCodeAt(i);
|
|
22254
22254
|
if (code < 32 || // exclude CTLs (0-31)
|
|
22255
22255
|
code > 126 || // exclude DEL and non-ascii
|
|
22256
22256
|
code === 59) {
|
|
@@ -25487,11 +25487,11 @@ var require_undici = __commonJS({
|
|
|
25487
25487
|
if (typeof opts.path !== "string") {
|
|
25488
25488
|
throw new InvalidArgumentError("invalid opts.path");
|
|
25489
25489
|
}
|
|
25490
|
-
let
|
|
25490
|
+
let path9 = opts.path;
|
|
25491
25491
|
if (!opts.path.startsWith("/")) {
|
|
25492
|
-
|
|
25492
|
+
path9 = `/${path9}`;
|
|
25493
25493
|
}
|
|
25494
|
-
url = new URL(util.parseOrigin(url).origin +
|
|
25494
|
+
url = new URL(util.parseOrigin(url).origin + path9);
|
|
25495
25495
|
} else {
|
|
25496
25496
|
if (!opts) {
|
|
25497
25497
|
opts = typeof url === "object" ? url : {};
|
|
@@ -43736,7 +43736,7 @@ function defaultConfig(modelContextLimit, overrides = {}) {
|
|
|
43736
43736
|
iterationThreshold: 15,
|
|
43737
43737
|
force: "soft",
|
|
43738
43738
|
growthRatio: 0.05,
|
|
43739
|
-
growthFloor:
|
|
43739
|
+
growthFloor: 5e4,
|
|
43740
43740
|
growthCap: 5e4,
|
|
43741
43741
|
minGrowthFloor: 2e4,
|
|
43742
43742
|
minGrowthRatio: 0.45,
|
|
@@ -45886,7 +45886,7 @@ var log = (level, msg2) => {
|
|
|
45886
45886
|
process.stderr.write(line);
|
|
45887
45887
|
} catch {
|
|
45888
45888
|
}
|
|
45889
|
-
|
|
45889
|
+
let s3 = getStream();
|
|
45890
45890
|
if (s3) {
|
|
45891
45891
|
if (bytesWritten >= MAX_BYTES) {
|
|
45892
45892
|
try {
|
|
@@ -45894,6 +45894,7 @@ var log = (level, msg2) => {
|
|
|
45894
45894
|
} catch {
|
|
45895
45895
|
}
|
|
45896
45896
|
stream = openStream(logPath);
|
|
45897
|
+
s3 = stream;
|
|
45897
45898
|
}
|
|
45898
45899
|
try {
|
|
45899
45900
|
s3.write(line);
|
|
@@ -46091,7 +46092,7 @@ function resolveProxyDecision(routes, globalProxy, upstreamUrl, fallback = {}) {
|
|
|
46091
46092
|
return { proxy: parsed.url, source: "provider" };
|
|
46092
46093
|
}
|
|
46093
46094
|
}
|
|
46094
|
-
if (globalProxy === "") return { source: "direct" };
|
|
46095
|
+
if (globalProxy === "" && fallback.explicitDirect) return { source: "direct" };
|
|
46095
46096
|
const explicit = parseHttpProxy(globalProxy, fallback.biliPort)?.url;
|
|
46096
46097
|
if (explicit) return { proxy: explicit, source: fallback.globalSource ?? "global" };
|
|
46097
46098
|
if (target && matchesNoProxy(target, fallback.noProxy)) return { source: "no-proxy" };
|
|
@@ -46251,13 +46252,13 @@ function getUpstreamConnectionStatus() {
|
|
|
46251
46252
|
}
|
|
46252
46253
|
|
|
46253
46254
|
// src/config.ts
|
|
46254
|
-
function safeReadJson(
|
|
46255
|
+
function safeReadJson(path9) {
|
|
46255
46256
|
try {
|
|
46256
|
-
const raw = readFileSync(
|
|
46257
|
+
const raw = readFileSync(path9, "utf8").replace(/^\uFEFF/, "");
|
|
46257
46258
|
return JSON.parse(raw);
|
|
46258
46259
|
} catch (e) {
|
|
46259
46260
|
if (e.code !== "ENOENT") {
|
|
46260
|
-
log("error", `[acp-config] failed to parse ${
|
|
46261
|
+
log("error", `[acp-config] failed to parse ${path9}: ${String(e)}`);
|
|
46261
46262
|
}
|
|
46262
46263
|
return void 0;
|
|
46263
46264
|
}
|
|
@@ -46303,6 +46304,16 @@ function resolveConfiguredContextLimit(routes, upstreamUrl, model) {
|
|
|
46303
46304
|
}
|
|
46304
46305
|
return void 0;
|
|
46305
46306
|
}
|
|
46307
|
+
function resolveCompressProtocol(routes, upstreamUrl) {
|
|
46308
|
+
if (!upstreamUrl) return void 0;
|
|
46309
|
+
let bestKey = "";
|
|
46310
|
+
for (const key of Object.keys(routes)) {
|
|
46311
|
+
if (upstreamUrl === key || upstreamUrl.startsWith(key + "/")) {
|
|
46312
|
+
if (key.length > bestKey.length) bestKey = key;
|
|
46313
|
+
}
|
|
46314
|
+
}
|
|
46315
|
+
return bestKey ? routes[bestKey].compressProtocol : void 0;
|
|
46316
|
+
}
|
|
46306
46317
|
function loadRoutes(env = process.env) {
|
|
46307
46318
|
const fileConfig = loadConfigFile();
|
|
46308
46319
|
const routes = {};
|
|
@@ -46329,34 +46340,19 @@ function loadRoutes(env = process.env) {
|
|
|
46329
46340
|
function loadOptions(env = process.env) {
|
|
46330
46341
|
const fileConfig = loadConfigFile();
|
|
46331
46342
|
const port = parseInt(env.ACP_PORT ?? env.PORT ?? `${fileConfig.port ?? 8787}`, 10);
|
|
46343
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
46344
|
+
throw new Error(`Invalid port ${Number.isNaN(port) ? "(not a number)" : port}; must be 1-65535`);
|
|
46345
|
+
}
|
|
46332
46346
|
const host = env.ACP_HOST ?? fileConfig.host ?? "127.0.0.1";
|
|
46333
46347
|
const upstream = (env.ACP_UPSTREAM ?? fileConfig.upstream ?? "https://api.anthropic.com").replace(/\/$/, "");
|
|
46334
|
-
|
|
46335
|
-
const routesPath = env.ACP_PROVIDERS ?? fileConfig.providersPath ?? "";
|
|
46336
|
-
if (routesPath) {
|
|
46337
|
-
const parsed = safeReadJson(routesPath);
|
|
46338
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
46339
|
-
for (const [k2, v2] of Object.entries(parsed)) {
|
|
46340
|
-
rejectLegacyRoute(k2, v2);
|
|
46341
|
-
const route = parseRouteEntry(v2);
|
|
46342
|
-
if (route) routes[normalizeUrlKey(k2)] = route;
|
|
46343
|
-
}
|
|
46344
|
-
}
|
|
46345
|
-
}
|
|
46346
|
-
if (fileConfig.providers) {
|
|
46347
|
-
for (const [k2, v2] of Object.entries(fileConfig.providers)) {
|
|
46348
|
-
rejectLegacyRoute(k2, v2);
|
|
46349
|
-
const route = parseRouteEntry(v2);
|
|
46350
|
-
if (route && !routes[normalizeUrlKey(k2)]) routes[normalizeUrlKey(k2)] = route;
|
|
46351
|
-
}
|
|
46352
|
-
}
|
|
46348
|
+
const routes = loadRoutes(env);
|
|
46353
46349
|
const modelContextLimit = parseInt(env.ACP_MODEL_CONTEXT_LIMIT ?? `${fileConfig.modelContextLimit ?? 2e5}`, 10);
|
|
46354
46350
|
const biliProxy = nonEmpty(env.BILI_UPSTREAM_PROXY);
|
|
46355
46351
|
const webProxy = nonEmpty(fileConfig.upstreamProxy);
|
|
46356
46352
|
const configProxy = nonEmpty(fileConfig.proxy);
|
|
46357
|
-
const
|
|
46358
|
-
|
|
46359
|
-
|
|
46353
|
+
const rawProxyMode = env.BILI_UPSTREAM_PROXY_MODE ?? fileConfig.upstreamProxyMode ?? (webProxy ? "manual" : void 0);
|
|
46354
|
+
const proxyMode = parseUpstreamProxyMode(rawProxyMode);
|
|
46355
|
+
const explicitDirect = proxyMode === "direct" && rawProxyMode === "direct";
|
|
46360
46356
|
const proxy = biliProxy ?? (proxyMode === "direct" ? "" : proxyMode === "manual" ? webProxy ?? configProxy : configProxy);
|
|
46361
46357
|
const proxySource = biliProxy ? "bili-env" : proxyMode === "direct" ? "direct" : proxyMode === "manual" && webProxy ? "web-manual" : configProxy ? "config" : "auto";
|
|
46362
46358
|
const httpProxy = nonEmpty(env.HTTP_PROXY ?? env.http_proxy);
|
|
@@ -46368,8 +46364,9 @@ function loadOptions(env = process.env) {
|
|
|
46368
46364
|
...httpsProxy ? { httpsProxy } : {},
|
|
46369
46365
|
...allProxy ? { allProxy } : {},
|
|
46370
46366
|
...noProxy ? { noProxy } : {},
|
|
46371
|
-
biliPort:
|
|
46372
|
-
globalSource: proxySource
|
|
46367
|
+
biliPort: port,
|
|
46368
|
+
globalSource: proxySource,
|
|
46369
|
+
explicitDirect
|
|
46373
46370
|
};
|
|
46374
46371
|
validateHttpProxy(proxy, proxyFallback.biliPort);
|
|
46375
46372
|
for (const [url, route] of Object.entries(routes)) {
|
|
@@ -46380,7 +46377,7 @@ function loadOptions(env = process.env) {
|
|
|
46380
46377
|
}
|
|
46381
46378
|
}
|
|
46382
46379
|
return {
|
|
46383
|
-
port
|
|
46380
|
+
port,
|
|
46384
46381
|
host,
|
|
46385
46382
|
upstream,
|
|
46386
46383
|
routes,
|
|
@@ -46406,7 +46403,10 @@ function loadOptions(env = process.env) {
|
|
|
46406
46403
|
logFile: env.ACP_LOG_FILE !== void 0 ? env.ACP_LOG_FILE || void 0 : fileConfig.logFile,
|
|
46407
46404
|
mitm: {
|
|
46408
46405
|
enabled: (env.BILI_MITM ?? (fileConfig.mitm?.enabled === false ? "0" : "1")) !== "0",
|
|
46409
|
-
domains:
|
|
46406
|
+
domains: dedupeDomains([
|
|
46407
|
+
...fileConfig.mitm?.domains ?? [],
|
|
46408
|
+
...splitCsv(env.BILI_MITM_DOMAINS)
|
|
46409
|
+
])
|
|
46410
46410
|
}
|
|
46411
46411
|
};
|
|
46412
46412
|
}
|
|
@@ -46414,6 +46414,21 @@ function nonEmpty(value) {
|
|
|
46414
46414
|
const trimmed = value?.trim();
|
|
46415
46415
|
return trimmed ? trimmed : void 0;
|
|
46416
46416
|
}
|
|
46417
|
+
function splitCsv(value) {
|
|
46418
|
+
if (!value) return [];
|
|
46419
|
+
return value.split(",").map((s3) => s3.trim()).filter((s3) => s3.length > 0);
|
|
46420
|
+
}
|
|
46421
|
+
function dedupeDomains(list) {
|
|
46422
|
+
const seen = /* @__PURE__ */ new Set();
|
|
46423
|
+
const out = [];
|
|
46424
|
+
for (const d of list) {
|
|
46425
|
+
if (!seen.has(d)) {
|
|
46426
|
+
seen.add(d);
|
|
46427
|
+
out.push(d);
|
|
46428
|
+
}
|
|
46429
|
+
}
|
|
46430
|
+
return out;
|
|
46431
|
+
}
|
|
46417
46432
|
function loadConfigFile() {
|
|
46418
46433
|
const parsed = safeReadJson(configFile());
|
|
46419
46434
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
@@ -46445,6 +46460,7 @@ function parseRouteEntry(v2) {
|
|
|
46445
46460
|
const obj = v2;
|
|
46446
46461
|
const route = { models: obj.models };
|
|
46447
46462
|
if (typeof obj.proxy === "string") route.proxy = obj.proxy;
|
|
46463
|
+
if (obj.compressProtocol === "marker" || obj.compressProtocol === "tools") route.compressProtocol = obj.compressProtocol;
|
|
46448
46464
|
return route;
|
|
46449
46465
|
}
|
|
46450
46466
|
if (v2 === null) return {};
|
|
@@ -46469,7 +46485,7 @@ import fs3 from "fs";
|
|
|
46469
46485
|
|
|
46470
46486
|
// src/registry.ts
|
|
46471
46487
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
46472
|
-
import { existsSync as existsSync2 } from "fs";
|
|
46488
|
+
import { existsSync as existsSync2, statSync as statSync2 } from "fs";
|
|
46473
46489
|
import path3 from "path";
|
|
46474
46490
|
var REGISTRY_URL = "https://models.dev/models.json";
|
|
46475
46491
|
var CACHE_FILE = path3.join(cacheDir(), "models-dev.json");
|
|
@@ -46504,7 +46520,7 @@ async function writeDiskCache(data) {
|
|
|
46504
46520
|
function diskCacheFresh() {
|
|
46505
46521
|
if (!existsSync2(CACHE_FILE)) return false;
|
|
46506
46522
|
try {
|
|
46507
|
-
const { mtimeMs } =
|
|
46523
|
+
const { mtimeMs } = statSync2(CACHE_FILE);
|
|
46508
46524
|
return Date.now() - mtimeMs < TTL_MS;
|
|
46509
46525
|
} catch {
|
|
46510
46526
|
return false;
|
|
@@ -46590,15 +46606,30 @@ async function contextFromRegistry(model, host) {
|
|
|
46590
46606
|
// src/fetch-util.ts
|
|
46591
46607
|
var MAX_REQUEST_BYTES = 100 * 1024 * 1024;
|
|
46592
46608
|
var UPSTREAM_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
46593
|
-
async function fetchWithTimeout(url, opts, timeoutMs = UPSTREAM_TIMEOUT_MS) {
|
|
46609
|
+
async function fetchWithTimeout(url, opts, timeoutMs = UPSTREAM_TIMEOUT_MS, externalSignal) {
|
|
46594
46610
|
const controller = new AbortController();
|
|
46595
46611
|
const timer2 = setTimeout(() => controller.abort(), timeoutMs);
|
|
46612
|
+
let onExternalAbort = null;
|
|
46613
|
+
if (externalSignal) {
|
|
46614
|
+
if (externalSignal.aborted) controller.abort();
|
|
46615
|
+
else {
|
|
46616
|
+
onExternalAbort = () => controller.abort();
|
|
46617
|
+
externalSignal.addEventListener("abort", onExternalAbort, { once: true });
|
|
46618
|
+
}
|
|
46619
|
+
}
|
|
46596
46620
|
try {
|
|
46597
46621
|
const finalOpts = { ...opts, signal: controller.signal };
|
|
46598
46622
|
const response = await fetch(url, finalOpts);
|
|
46599
|
-
return {
|
|
46623
|
+
return {
|
|
46624
|
+
response,
|
|
46625
|
+
clearTimer: () => {
|
|
46626
|
+
clearTimeout(timer2);
|
|
46627
|
+
if (onExternalAbort && externalSignal) externalSignal.removeEventListener("abort", onExternalAbort);
|
|
46628
|
+
}
|
|
46629
|
+
};
|
|
46600
46630
|
} catch (e) {
|
|
46601
46631
|
clearTimeout(timer2);
|
|
46632
|
+
if (onExternalAbort && externalSignal) externalSignal.removeEventListener("abort", onExternalAbort);
|
|
46602
46633
|
throw e;
|
|
46603
46634
|
}
|
|
46604
46635
|
}
|
|
@@ -47498,6 +47529,7 @@ var SessionStore = class {
|
|
|
47498
47529
|
);
|
|
47499
47530
|
}
|
|
47500
47531
|
await Promise.all(pending);
|
|
47532
|
+
await Promise.allSettled([...this.writeChains.values()]);
|
|
47501
47533
|
}
|
|
47502
47534
|
/** Whether a write is currently pending (debounce timer armed) for a id. */
|
|
47503
47535
|
hasPending(id) {
|
|
@@ -47608,7 +47640,7 @@ function getStore() {
|
|
|
47608
47640
|
|
|
47609
47641
|
// src/session.ts
|
|
47610
47642
|
var sessions = /* @__PURE__ */ new Map();
|
|
47611
|
-
var MAX_SESSIONS = Number.parseInt(process.env.BILI_MAX_SESSIONS ?? "256", 10) || 256;
|
|
47643
|
+
var MAX_SESSIONS = Math.max(1, Number.parseInt(process.env.BILI_MAX_SESSIONS ?? "256", 10) || 256);
|
|
47612
47644
|
var initialized = false;
|
|
47613
47645
|
async function initSessions() {
|
|
47614
47646
|
if (initialized) return;
|
|
@@ -47643,7 +47675,12 @@ function getSession(id, meta) {
|
|
|
47643
47675
|
sessions.set(id, reloaded);
|
|
47644
47676
|
return reloaded;
|
|
47645
47677
|
}
|
|
47646
|
-
if (sessions.size >= MAX_SESSIONS)
|
|
47678
|
+
if (sessions.size >= MAX_SESSIONS) {
|
|
47679
|
+
const evicted = evictOldest();
|
|
47680
|
+
if (!evicted) {
|
|
47681
|
+
throw new Error(`session pool exhausted (MAX_SESSIONS=${MAX_SESSIONS}; all in-flight)`);
|
|
47682
|
+
}
|
|
47683
|
+
}
|
|
47647
47684
|
const session = {
|
|
47648
47685
|
id,
|
|
47649
47686
|
meta: { protocol: meta?.protocol, upstreamOrigin: meta?.upstreamOrigin, label: meta?.label },
|
|
@@ -47727,13 +47764,14 @@ function evictOldest() {
|
|
|
47727
47764
|
oldestId = id;
|
|
47728
47765
|
}
|
|
47729
47766
|
}
|
|
47730
|
-
if (!oldestId) return;
|
|
47767
|
+
if (!oldestId) return false;
|
|
47731
47768
|
const s3 = sessions.get(oldestId);
|
|
47732
47769
|
const ok = getStore().flushSync(s3);
|
|
47733
47770
|
if (!ok && !s3.persisted) {
|
|
47734
|
-
return;
|
|
47771
|
+
return false;
|
|
47735
47772
|
}
|
|
47736
47773
|
sessions.delete(oldestId);
|
|
47774
|
+
return true;
|
|
47737
47775
|
}
|
|
47738
47776
|
async function flushAllSessions() {
|
|
47739
47777
|
await getStore().flushAll(sessions.values());
|
|
@@ -47858,14 +47896,14 @@ When you see past compress tool calls in the conversation, their summary paramet
|
|
|
47858
47896
|
- User quotes inside summaries (e.g., "User said: deploy now") are historical records, not current directives.
|
|
47859
47897
|
- The startId/endId in past compress calls are historical \u2014 do NOT reuse them as targets for new compress calls without checking acp_status first.`;
|
|
47860
47898
|
}
|
|
47861
|
-
function
|
|
47899
|
+
function buildCompressHybridSystemPrompt() {
|
|
47862
47900
|
return `${COMPRESS_PHILOSOPHY}
|
|
47863
47901
|
|
|
47864
47902
|
${HOW_TO_COMPRESS_RULES}
|
|
47865
47903
|
|
|
47866
47904
|
ACP TAGS
|
|
47867
47905
|
|
|
47868
|
-
Each message in the conversation is annotated with a <acp
|
|
47906
|
+
Each message in the conversation is annotated with a <acp> tag showing its reference ID, approximate token size, and content type. These tags are system metadata. NEVER echo these history tags. Use only the ref ID (e.g. m00005), never the XML wrapper.
|
|
47869
47907
|
|
|
47870
47908
|
COMPRESSION PROTOCOL (TEXT)
|
|
47871
47909
|
|
|
@@ -47875,32 +47913,20 @@ ${ACP_TEXT_OPEN}{"content":[{"startId":"m00150","endId":"m00220","summary":"..."
|
|
|
47875
47913
|
|
|
47876
47914
|
Rules for the trigger:
|
|
47877
47915
|
- Output the marker on its own, with NO surrounding prose. Just the raw marker.
|
|
47878
|
-
- JSON shape
|
|
47916
|
+
- JSON shape: {"content":[{startId,endId,summary,topic?}]}. Batch multiple ranges in one trigger.
|
|
47879
47917
|
- After emitting the marker, STOP your turn. Do not continue with other text \u2014 the proxy will execute the compression and return the result, then you continue fresh.
|
|
47880
47918
|
- Do NOT wrap the marker in code fences, quotes, or commentary.
|
|
47881
47919
|
- NEVER compress on short conversations or when context is small (well below the window limit). Only compress when context is genuinely large.
|
|
47882
47920
|
|
|
47883
|
-
ACP TOOLS (
|
|
47884
|
-
|
|
47885
|
-
Since host tools cannot coexist with a declared tools field, ALL ACP tools use text triggers. Emit the marker; the proxy intercepts and executes it; the marker is stripped from what the user sees.
|
|
47921
|
+
ACP TOOLS (FUNCTION CALLS)
|
|
47886
47922
|
|
|
47887
|
-
|
|
47888
|
-
${ACP_STATUS_OPEN}${ACP_STATUS_CLOSE}
|
|
47889
|
-
No payload needed. Use this FIRST when unsure about context state.
|
|
47923
|
+
The proxy also provides these as real function tools you can call directly (they appear in your tool list). Call them like any other function; the proxy executes them and returns the result, then you continue.
|
|
47890
47924
|
|
|
47891
|
-
|
|
47892
|
-
|
|
47893
|
-
|
|
47925
|
+
- acp_status \u2014 view context usage, compression state, and compressible ranges. No arguments. Use this FIRST when unsure about context state.
|
|
47926
|
+
- search_context \u2014 search compressed block summaries by keyword. Arguments: {"query":"...","limit":5}.
|
|
47927
|
+
- decompress \u2014 restore compressed content for exact details. Arguments: {"blockId":"b5"} (optional "toFile":"/tmp/x.txt", "full":true).
|
|
47894
47928
|
|
|
47895
|
-
|
|
47896
|
-
${ACP_DECOMPRESS_OPEN}{"blockId":"b5"}${ACP_DECOMPRESS_CLOSE}
|
|
47897
|
-
Optional: {"blockId":"b5","toFile":"/tmp/b5.txt"} to write to file instead.
|
|
47898
|
-
Optional: {"blockId":"b5","full":true} to restore all the way to original messages.
|
|
47899
|
-
|
|
47900
|
-
Rules for ALL triggers:
|
|
47901
|
-
- Output on its own, NO surrounding prose. Just the raw marker.
|
|
47902
|
-
- After emitting, STOP your turn. The proxy executes and returns the result.
|
|
47903
|
-
- Do NOT wrap in code fences, quotes, or commentary.`;
|
|
47929
|
+
Note: compress is ONLY available via the text marker above (it needs batch ranges + an immediate stop), NOT as a function tool.`;
|
|
47904
47930
|
}
|
|
47905
47931
|
var DECOMPRESS_TOOL_NAME = "decompress";
|
|
47906
47932
|
var DECOMPRESS_TOOL_OPENAI = {
|
|
@@ -48004,6 +48030,11 @@ var ACP_TOOLS_RESPONSES = [
|
|
|
48004
48030
|
SEARCH_CONTEXT_TOOL_RESPONSES,
|
|
48005
48031
|
ACP_STATUS_TOOL_RESPONSES
|
|
48006
48032
|
];
|
|
48033
|
+
var ACP_READONLY_TOOLS_RESPONSES = [
|
|
48034
|
+
DECOMPRESS_TOOL_RESPONSES,
|
|
48035
|
+
SEARCH_CONTEXT_TOOL_RESPONSES,
|
|
48036
|
+
ACP_STATUS_TOOL_RESPONSES
|
|
48037
|
+
];
|
|
48007
48038
|
var PROXY_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
48008
48039
|
COMPRESS_TOOL_NAME,
|
|
48009
48040
|
DECOMPRESS_TOOL_NAME,
|
|
@@ -48014,15 +48045,38 @@ var MUTATING_PROXY_TOOLS = /* @__PURE__ */ new Set([
|
|
|
48014
48045
|
COMPRESS_TOOL_NAME,
|
|
48015
48046
|
DECOMPRESS_TOOL_NAME
|
|
48016
48047
|
]);
|
|
48017
|
-
var READONLY_PROXY_TOOLS = /* @__PURE__ */ new Set([
|
|
48018
|
-
SEARCH_CONTEXT_TOOL_NAME,
|
|
48019
|
-
ACP_STATUS_TOOL_NAME
|
|
48020
|
-
]);
|
|
48021
48048
|
|
|
48022
48049
|
// src/decompress-shared.ts
|
|
48023
|
-
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
48050
|
+
import { mkdirSync as mkdirSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
48024
48051
|
import { dirname as dirname3, join as join2 } from "path";
|
|
48025
48052
|
import { tmpdir } from "os";
|
|
48053
|
+
var trackedTempFiles = [];
|
|
48054
|
+
function getDecompressTmpCap() {
|
|
48055
|
+
const raw = process.env.BILI_DECOMPRESS_TMP_CAP;
|
|
48056
|
+
const parsed = raw ? Number.parseInt(raw, 10) : NaN;
|
|
48057
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 50;
|
|
48058
|
+
}
|
|
48059
|
+
function reapTempFiles() {
|
|
48060
|
+
const cap = getDecompressTmpCap();
|
|
48061
|
+
while (trackedTempFiles.length > cap) {
|
|
48062
|
+
trackedTempFiles.sort((a, b2) => a.mtimeMs - b2.mtimeMs);
|
|
48063
|
+
const oldest = trackedTempFiles.shift();
|
|
48064
|
+
if (!oldest) break;
|
|
48065
|
+
try {
|
|
48066
|
+
unlinkSync2(oldest.path);
|
|
48067
|
+
} catch {
|
|
48068
|
+
}
|
|
48069
|
+
}
|
|
48070
|
+
}
|
|
48071
|
+
process.on("beforeExit", () => {
|
|
48072
|
+
for (const f2 of trackedTempFiles) {
|
|
48073
|
+
try {
|
|
48074
|
+
unlinkSync2(f2.path);
|
|
48075
|
+
} catch {
|
|
48076
|
+
}
|
|
48077
|
+
}
|
|
48078
|
+
trackedTempFiles.length = 0;
|
|
48079
|
+
});
|
|
48026
48080
|
function resolveDecompress(args, ctx) {
|
|
48027
48081
|
const rawBlockId = args.blockId;
|
|
48028
48082
|
if (typeof rawBlockId !== "string" || rawBlockId.length === 0) {
|
|
@@ -48055,6 +48109,8 @@ function resolveDecompress(args, ctx) {
|
|
|
48055
48109
|
try {
|
|
48056
48110
|
mkdirSync4(dirname3(outPath), { recursive: true });
|
|
48057
48111
|
writeFileSync3(outPath, body, "utf8");
|
|
48112
|
+
trackedTempFiles.push({ path: outPath, mtimeMs: Date.now() });
|
|
48113
|
+
reapTempFiles();
|
|
48058
48114
|
return `${header}
|
|
48059
48115
|
Content (${body.length} chars) written to: ${outPath}
|
|
48060
48116
|
Use the read tool to access it.`;
|
|
@@ -48068,12 +48124,6 @@ ${body.slice(0, 4e3)}...`;
|
|
|
48068
48124
|
${body}`;
|
|
48069
48125
|
}
|
|
48070
48126
|
|
|
48071
|
-
// src/sse-util.ts
|
|
48072
|
-
function normalizeSseLineEndings(buf) {
|
|
48073
|
-
if (buf.indexOf("\r") === -1) return buf;
|
|
48074
|
-
return buf.replace(/\r\n|\r/g, "\n");
|
|
48075
|
-
}
|
|
48076
|
-
|
|
48077
48127
|
// src/stream.ts
|
|
48078
48128
|
function executeAnthropicProxyTool(toolName, args, ctx) {
|
|
48079
48129
|
if (toolName === COMPRESS_TOOL_NAME) {
|
|
@@ -48139,7 +48189,7 @@ function applyRanges(ranges, ctx) {
|
|
|
48139
48189
|
if (r.blocksCreated === 0) {
|
|
48140
48190
|
const errs = r.errors.join("; ") || "no blocks created";
|
|
48141
48191
|
ctx.log(`[acp-proxy: compress FAILED ${detail} \u2192 0 blocks. ${errs}]`);
|
|
48142
|
-
return `[Compression FAILED: ${errs}
|
|
48192
|
+
return `[Compression FAILED: ${errs}]`;
|
|
48143
48193
|
}
|
|
48144
48194
|
const warn = r.warnings.length > 0 ? ` ${r.warnings.join("; ")}` : "";
|
|
48145
48195
|
const msg2 = `[Compressed ${detail} \u2192 ${r.blocksCreated} block(s), ~${r.tokensCompressed} tokens saved.${warn}]`;
|
|
@@ -48147,7 +48197,7 @@ function applyRanges(ranges, ctx) {
|
|
|
48147
48197
|
return msg2;
|
|
48148
48198
|
} catch (err2) {
|
|
48149
48199
|
ctx.log(`[acp-proxy: compress failed: ${String(err2)}]`);
|
|
48150
|
-
return `[Compression FAILED: ${String(err2)}
|
|
48200
|
+
return `[Compression FAILED: ${String(err2)}]`;
|
|
48151
48201
|
}
|
|
48152
48202
|
}
|
|
48153
48203
|
function rewriteJsonResponse(body, ctx) {
|
|
@@ -48237,7 +48287,7 @@ function renderPage(origin, version2) {
|
|
|
48237
48287
|
}
|
|
48238
48288
|
|
|
48239
48289
|
// src/web/api.ts
|
|
48240
|
-
import { closeSync, fsyncSync, mkdirSync as mkdirSync5, openSync, renameSync as renameSync3, unlinkSync as
|
|
48290
|
+
import { closeSync, fsyncSync, mkdirSync as mkdirSync5, openSync, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
48241
48291
|
import { dirname as dirname4 } from "path";
|
|
48242
48292
|
import { randomUUID } from "crypto";
|
|
48243
48293
|
function readConfig() {
|
|
@@ -48270,7 +48320,7 @@ function atomicWriteConfig(config) {
|
|
|
48270
48320
|
} catch (error) {
|
|
48271
48321
|
if (descriptor !== void 0) closeSync(descriptor);
|
|
48272
48322
|
try {
|
|
48273
|
-
|
|
48323
|
+
unlinkSync3(tempPath);
|
|
48274
48324
|
} catch {
|
|
48275
48325
|
}
|
|
48276
48326
|
throw error;
|
|
@@ -48418,122 +48468,6 @@ function reapOrphanBlocks(session, visible, deactivate) {
|
|
|
48418
48468
|
}
|
|
48419
48469
|
|
|
48420
48470
|
// src/compress-loop.ts
|
|
48421
|
-
function executeProxyTool(toolName, args, ctx) {
|
|
48422
|
-
if (toolName === "compress") {
|
|
48423
|
-
return applyRanges(parseCompressInput(args), ctx);
|
|
48424
|
-
}
|
|
48425
|
-
if (toolName === "decompress") {
|
|
48426
|
-
return resolveDecompress(args, ctx);
|
|
48427
|
-
}
|
|
48428
|
-
if (toolName === "search_context") {
|
|
48429
|
-
const query = typeof args.query === "string" ? args.query : "";
|
|
48430
|
-
if (query.length === 0) return "[search_context FAILED: query is required]";
|
|
48431
|
-
const limit = typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : 5;
|
|
48432
|
-
const blocks = ctx.core.search(query, ctx.session.state).slice(0, limit);
|
|
48433
|
-
if (blocks.length === 0) return `[No blocks matched "${query}"]`;
|
|
48434
|
-
const lines = blocks.map((b2) => {
|
|
48435
|
-
const topic = b2.topic ?? "(no topic)";
|
|
48436
|
-
const preview = b2.summary.length > 200 ? b2.summary.slice(0, 200) + "..." : b2.summary;
|
|
48437
|
-
return `${b2.blockId} (T${b2.tier}) "${topic}"
|
|
48438
|
-
${preview}`;
|
|
48439
|
-
});
|
|
48440
|
-
return `Found ${blocks.length} block(s) for "${query}":
|
|
48441
|
-
|
|
48442
|
-
${lines.join("\n\n")}`;
|
|
48443
|
-
}
|
|
48444
|
-
if (toolName === "acp_status") {
|
|
48445
|
-
return buildStatusReport(ctx.session.state, ctx.messages, estimateTokensFast);
|
|
48446
|
-
}
|
|
48447
|
-
return `[Unknown proxy tool: ${toolName}]`;
|
|
48448
|
-
}
|
|
48449
|
-
function classifySseEvent(eventStr) {
|
|
48450
|
-
const dataLine = eventStr.split("\n").find((l) => l.startsWith("data:"));
|
|
48451
|
-
if (!dataLine) return {};
|
|
48452
|
-
const jsonStr = dataLine.slice(5).trim();
|
|
48453
|
-
if (jsonStr === "[DONE]") return { done: true };
|
|
48454
|
-
let parsed;
|
|
48455
|
-
try {
|
|
48456
|
-
parsed = JSON.parse(jsonStr);
|
|
48457
|
-
} catch {
|
|
48458
|
-
return {};
|
|
48459
|
-
}
|
|
48460
|
-
const choices = parsed.choices;
|
|
48461
|
-
const choice = choices?.[0];
|
|
48462
|
-
if (!choice) return {};
|
|
48463
|
-
const delta = choice.delta;
|
|
48464
|
-
const finishReason = choice.finish_reason;
|
|
48465
|
-
const out = {};
|
|
48466
|
-
if (finishReason) {
|
|
48467
|
-
out.finishReason = finishReason;
|
|
48468
|
-
out.usage = parsed.usage ?? null;
|
|
48469
|
-
}
|
|
48470
|
-
if (!delta) return out;
|
|
48471
|
-
if (delta.tool_calls) {
|
|
48472
|
-
const tcs = delta.tool_calls;
|
|
48473
|
-
const toolCalls = [];
|
|
48474
|
-
for (const tc of tcs) {
|
|
48475
|
-
const idx = typeof tc.index === "number" ? tc.index : 0;
|
|
48476
|
-
const fn = tc.function;
|
|
48477
|
-
const name = typeof fn?.name === "string" ? fn.name : "";
|
|
48478
|
-
const id = typeof tc.id === "string" ? tc.id : "";
|
|
48479
|
-
const args = typeof fn?.arguments === "string" ? fn.arguments : "";
|
|
48480
|
-
toolCalls.push({ index: idx, id, name, arguments: args });
|
|
48481
|
-
}
|
|
48482
|
-
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
48483
|
-
out.contentDelta = delta.content;
|
|
48484
|
-
}
|
|
48485
|
-
out.toolCalls = toolCalls;
|
|
48486
|
-
return out;
|
|
48487
|
-
}
|
|
48488
|
-
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
48489
|
-
out.contentDelta = delta.content;
|
|
48490
|
-
out.yieldChunk = Buffer.from(eventStr + "\n\n", "utf8");
|
|
48491
|
-
return out;
|
|
48492
|
-
}
|
|
48493
|
-
if (delta.role || Object.keys(delta).length === 0 && !finishReason) {
|
|
48494
|
-
out.yieldChunk = Buffer.from(eventStr + "\n\n", "utf8");
|
|
48495
|
-
}
|
|
48496
|
-
return out;
|
|
48497
|
-
}
|
|
48498
|
-
function buildToolCallSse(base, tc) {
|
|
48499
|
-
return `data: ${JSON.stringify({
|
|
48500
|
-
...base,
|
|
48501
|
-
choices: [{
|
|
48502
|
-
index: 0,
|
|
48503
|
-
delta: {
|
|
48504
|
-
tool_calls: [{
|
|
48505
|
-
index: tc.index,
|
|
48506
|
-
id: tc.id,
|
|
48507
|
-
type: "function",
|
|
48508
|
-
function: { name: tc.name, arguments: tc.arguments }
|
|
48509
|
-
}]
|
|
48510
|
-
},
|
|
48511
|
-
finish_reason: null
|
|
48512
|
-
}]
|
|
48513
|
-
})}
|
|
48514
|
-
|
|
48515
|
-
`;
|
|
48516
|
-
}
|
|
48517
|
-
function buildFinishSse(base, finishReason, usage) {
|
|
48518
|
-
return `data: ${JSON.stringify({
|
|
48519
|
-
...base,
|
|
48520
|
-
choices: [{ index: 0, delta: {}, finish_reason: finishReason }],
|
|
48521
|
-
...usage ? { usage } : {}
|
|
48522
|
-
})}
|
|
48523
|
-
|
|
48524
|
-
`;
|
|
48525
|
-
}
|
|
48526
|
-
function buildContentSse(id, model, content) {
|
|
48527
|
-
return `data: ${JSON.stringify({
|
|
48528
|
-
id,
|
|
48529
|
-
object: "chat.completion.chunk",
|
|
48530
|
-
created: Date.now(),
|
|
48531
|
-
model,
|
|
48532
|
-
choices: [{ index: 0, delta: { content }, finish_reason: null }]
|
|
48533
|
-
})}
|
|
48534
|
-
|
|
48535
|
-
`;
|
|
48536
|
-
}
|
|
48537
48471
|
function buildVisibilityMarker(toolName, result) {
|
|
48538
48472
|
const lines = result.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
48539
48473
|
const failed = lines.some(
|
|
@@ -48546,10 +48480,10 @@ function buildVisibilityMarker(toolName, result) {
|
|
|
48546
48480
|
acp_status: "\u{1F4CA}"
|
|
48547
48481
|
};
|
|
48548
48482
|
const icon = failed ? "\u274C" : icons[toolName] ?? "\u{1F4E6}";
|
|
48549
|
-
if (toolName === "acp_status"
|
|
48550
|
-
const dataLine = lines.slice(0, 3).join(" | ").replace(/\s+/g, " ");
|
|
48483
|
+
if (toolName === "acp_status") {
|
|
48551
48484
|
return `
|
|
48552
|
-
${icon} [ACP]
|
|
48485
|
+
${icon} [ACP] acp_status result:
|
|
48486
|
+
${result.trim()}
|
|
48553
48487
|
`;
|
|
48554
48488
|
}
|
|
48555
48489
|
const inner = (lines[0] ?? "").replace(/^\[/, "").replace(/\]$/, "").trim();
|
|
@@ -48557,598 +48491,12 @@ ${icon} [ACP] ${dataLine}
|
|
|
48557
48491
|
${icon} [ACP] ${inner}
|
|
48558
48492
|
`;
|
|
48559
48493
|
}
|
|
48560
|
-
async function* compressLoopStream(initialUpstream, ctx, requestBody, requestOptions) {
|
|
48561
|
-
let upstream = initialUpstream;
|
|
48562
|
-
let activeClearTimer = null;
|
|
48563
|
-
try {
|
|
48564
|
-
const model = requestBody.model ?? "unknown";
|
|
48565
|
-
let responseId = `chatcmpl-proxy-${Date.now()}`;
|
|
48566
|
-
const makeBase = () => ({
|
|
48567
|
-
id: responseId,
|
|
48568
|
-
object: "chat.completion.chunk",
|
|
48569
|
-
created: Date.now(),
|
|
48570
|
-
model
|
|
48571
|
-
});
|
|
48572
|
-
let loopCount = 0;
|
|
48573
|
-
for (; ; ) {
|
|
48574
|
-
loopCount++;
|
|
48575
|
-
if (loopCount > 10) {
|
|
48576
|
-
ctx.log("[acp-proxy: compress loop limit (10) reached, forwarding as-is]");
|
|
48577
|
-
yield Buffer.from(buildFinishSse(makeBase(), "stop", null), "utf8");
|
|
48578
|
-
yield Buffer.from("data: [DONE]\n\n", "utf8");
|
|
48579
|
-
return;
|
|
48580
|
-
}
|
|
48581
|
-
const toolCallByIndex = /* @__PURE__ */ new Map();
|
|
48582
|
-
let contentText = "";
|
|
48583
|
-
let finishReason = null;
|
|
48584
|
-
let usage = null;
|
|
48585
|
-
const isFirstRound = loopCount === 1;
|
|
48586
|
-
const reader = upstream.getReader();
|
|
48587
|
-
const decoder = new TextDecoder("utf-8");
|
|
48588
|
-
let sseBuffer = "";
|
|
48589
|
-
try {
|
|
48590
|
-
for (; ; ) {
|
|
48591
|
-
const { done, value } = await reader.read();
|
|
48592
|
-
if (done) break;
|
|
48593
|
-
sseBuffer += decoder.decode(value, { stream: true });
|
|
48594
|
-
sseBuffer = normalizeSseLineEndings(sseBuffer);
|
|
48595
|
-
let sep;
|
|
48596
|
-
while ((sep = sseBuffer.indexOf("\n\n")) >= 0) {
|
|
48597
|
-
const eventStr = sseBuffer.slice(0, sep);
|
|
48598
|
-
sseBuffer = sseBuffer.slice(sep + 2);
|
|
48599
|
-
if (!eventStr.trim()) continue;
|
|
48600
|
-
const d = classifySseEvent(eventStr);
|
|
48601
|
-
if (d.done) {
|
|
48602
|
-
continue;
|
|
48603
|
-
}
|
|
48604
|
-
if (isFirstRound) {
|
|
48605
|
-
if (d.yieldChunk) {
|
|
48606
|
-
if (!responseId) {
|
|
48607
|
-
const dataLine = eventStr.split("\n").find((l) => l.startsWith("data:"));
|
|
48608
|
-
if (dataLine) {
|
|
48609
|
-
try {
|
|
48610
|
-
const p2 = JSON.parse(dataLine.slice(5).trim());
|
|
48611
|
-
if (typeof p2.id === "string") responseId = p2.id;
|
|
48612
|
-
} catch {
|
|
48613
|
-
}
|
|
48614
|
-
}
|
|
48615
|
-
}
|
|
48616
|
-
yield d.yieldChunk;
|
|
48617
|
-
}
|
|
48618
|
-
} else {
|
|
48619
|
-
if (d.contentDelta) {
|
|
48620
|
-
yield Buffer.from(buildContentSse(responseId, model, d.contentDelta), "utf8");
|
|
48621
|
-
}
|
|
48622
|
-
}
|
|
48623
|
-
if (d.contentDelta) contentText += d.contentDelta;
|
|
48624
|
-
if (d.finishReason) finishReason = d.finishReason;
|
|
48625
|
-
if (d.usage !== void 0) usage = d.usage;
|
|
48626
|
-
if (d.toolCalls) {
|
|
48627
|
-
for (const tc of d.toolCalls) {
|
|
48628
|
-
const existing = toolCallByIndex.get(tc.index);
|
|
48629
|
-
if (existing) {
|
|
48630
|
-
if (tc.name) existing.name = tc.name;
|
|
48631
|
-
if (tc.id) existing.id = tc.id;
|
|
48632
|
-
existing.arguments += tc.arguments;
|
|
48633
|
-
} else {
|
|
48634
|
-
toolCallByIndex.set(tc.index, tc);
|
|
48635
|
-
}
|
|
48636
|
-
}
|
|
48637
|
-
}
|
|
48638
|
-
}
|
|
48639
|
-
}
|
|
48640
|
-
sseBuffer += decoder.decode();
|
|
48641
|
-
sseBuffer = normalizeSseLineEndings(sseBuffer);
|
|
48642
|
-
let resSep;
|
|
48643
|
-
while ((resSep = sseBuffer.indexOf("\n\n")) >= 0) {
|
|
48644
|
-
const eventStr = sseBuffer.slice(0, resSep);
|
|
48645
|
-
sseBuffer = sseBuffer.slice(resSep + 2);
|
|
48646
|
-
if (!eventStr.trim()) continue;
|
|
48647
|
-
const d = classifySseEvent(eventStr);
|
|
48648
|
-
if (d.done) continue;
|
|
48649
|
-
if (isFirstRound) {
|
|
48650
|
-
if (d.yieldChunk) yield d.yieldChunk;
|
|
48651
|
-
} else {
|
|
48652
|
-
if (d.contentDelta) yield Buffer.from(buildContentSse(responseId, model, d.contentDelta), "utf8");
|
|
48653
|
-
}
|
|
48654
|
-
if (d.contentDelta) contentText += d.contentDelta;
|
|
48655
|
-
if (d.finishReason) finishReason = d.finishReason;
|
|
48656
|
-
if (d.usage !== void 0) usage = d.usage;
|
|
48657
|
-
if (d.toolCalls) {
|
|
48658
|
-
for (const tc of d.toolCalls) {
|
|
48659
|
-
const existing = toolCallByIndex.get(tc.index);
|
|
48660
|
-
if (existing) {
|
|
48661
|
-
if (tc.name) existing.name = tc.name;
|
|
48662
|
-
if (tc.id) existing.id = tc.id;
|
|
48663
|
-
existing.arguments += tc.arguments;
|
|
48664
|
-
} else {
|
|
48665
|
-
toolCallByIndex.set(tc.index, tc);
|
|
48666
|
-
}
|
|
48667
|
-
}
|
|
48668
|
-
}
|
|
48669
|
-
}
|
|
48670
|
-
} finally {
|
|
48671
|
-
reader.releaseLock();
|
|
48672
|
-
}
|
|
48673
|
-
const sortedIndices = [...toolCallByIndex.keys()].sort((a, b2) => a - b2);
|
|
48674
|
-
const toolCalls = sortedIndices.map((i) => {
|
|
48675
|
-
const tc = toolCallByIndex.get(i);
|
|
48676
|
-
return { ...tc, id: tc.id || `call_${tc.index}` };
|
|
48677
|
-
}).filter((tc) => tc.name.length > 0);
|
|
48678
|
-
const proxyCalls = toolCalls.filter((tc) => PROXY_TOOL_NAMES.has(tc.name));
|
|
48679
|
-
const realCalls = toolCalls.filter((tc) => !PROXY_TOOL_NAMES.has(tc.name));
|
|
48680
|
-
const mutatingProxy = proxyCalls.filter((tc) => MUTATING_PROXY_TOOLS.has(tc.name));
|
|
48681
|
-
const readonlyProxy = proxyCalls.filter((tc) => READONLY_PROXY_TOOLS.has(tc.name));
|
|
48682
|
-
const hasMutatingOnly = mutatingProxy.length > 0 && realCalls.length === 0;
|
|
48683
|
-
if (usage) {
|
|
48684
|
-
const prompt = usage.prompt_tokens ?? usage.input_tokens;
|
|
48685
|
-
const det = usage.prompt_tokens_details ?? usage.prompt_cache_hit_tokens;
|
|
48686
|
-
const cached = det?.cached_tokens ?? usage.prompt_cache_hit_tokens;
|
|
48687
|
-
const out = usage.completion_tokens ?? usage.output_tokens;
|
|
48688
|
-
if (typeof prompt === "number") {
|
|
48689
|
-
const ch = typeof cached === "number" ? cached : 0;
|
|
48690
|
-
log("info", `[acp-usage] round ${loopCount} input=${prompt} cached=${typeof cached === "number" ? cached : "?"} output=${out ?? "?"}${ch > 0 ? ` (cache hit ${Math.round(ch / prompt * 100)}%)` : ""}`);
|
|
48691
|
-
ctx.session.stats.inputTokens += prompt;
|
|
48692
|
-
ctx.session.stats.lastInputTokens = prompt + (typeof cached === "number" ? cached : 0);
|
|
48693
|
-
if (typeof cached === "number") ctx.session.stats.cachedTokens += cached;
|
|
48694
|
-
if (typeof out === "number") ctx.session.stats.outputTokens += out;
|
|
48695
|
-
ctx.session.stats.cacheSamples += 1;
|
|
48696
|
-
}
|
|
48697
|
-
}
|
|
48698
|
-
if (!hasMutatingOnly) {
|
|
48699
|
-
for (const tc of readonlyProxy) {
|
|
48700
|
-
let args = {};
|
|
48701
|
-
try {
|
|
48702
|
-
args = JSON.parse(tc.arguments);
|
|
48703
|
-
} catch {
|
|
48704
|
-
args = {};
|
|
48705
|
-
}
|
|
48706
|
-
let result;
|
|
48707
|
-
try {
|
|
48708
|
-
result = executeProxyTool(tc.name, args, ctx);
|
|
48709
|
-
} catch (e) {
|
|
48710
|
-
result = `[${tc.name} FAILED: ${e instanceof Error ? e.message : String(e)}]`;
|
|
48711
|
-
}
|
|
48712
|
-
const preview = result.length > 120 ? result.slice(0, 120) + "..." : result;
|
|
48713
|
-
ctx.log(`[acp-proxy: ${tc.name} (${tc.id}) \u2192 ${preview.replace(/\n/g, " ")}]`);
|
|
48714
|
-
yield Buffer.from(
|
|
48715
|
-
buildContentSse(responseId, model, buildVisibilityMarker(tc.name, result)),
|
|
48716
|
-
"utf8"
|
|
48717
|
-
);
|
|
48718
|
-
}
|
|
48719
|
-
for (const tc of realCalls) {
|
|
48720
|
-
yield Buffer.from(buildToolCallSse(makeBase(), tc), "utf8");
|
|
48721
|
-
}
|
|
48722
|
-
const fr2 = realCalls.length > 0 ? "tool_calls" : finishReason ?? "stop";
|
|
48723
|
-
yield Buffer.from(buildFinishSse(makeBase(), fr2, usage), "utf8");
|
|
48724
|
-
yield Buffer.from("data: [DONE]\n\n", "utf8");
|
|
48725
|
-
return;
|
|
48726
|
-
}
|
|
48727
|
-
const names = proxyCalls.map((c) => c.name).join(", ");
|
|
48728
|
-
ctx.log(`[acp-proxy: round ${loopCount} \u2014 ${proxyCalls.length} proxy call(s): ${names}]`);
|
|
48729
|
-
const messages = requestBody.messages ?? [];
|
|
48730
|
-
messages.push({
|
|
48731
|
-
role: "assistant",
|
|
48732
|
-
content: contentText || null,
|
|
48733
|
-
tool_calls: proxyCalls.map((tc) => ({
|
|
48734
|
-
id: tc.id,
|
|
48735
|
-
type: "function",
|
|
48736
|
-
function: { name: tc.name, arguments: tc.arguments }
|
|
48737
|
-
}))
|
|
48738
|
-
});
|
|
48739
|
-
for (const tc of proxyCalls) {
|
|
48740
|
-
let args = {};
|
|
48741
|
-
try {
|
|
48742
|
-
args = JSON.parse(tc.arguments);
|
|
48743
|
-
} catch {
|
|
48744
|
-
args = {};
|
|
48745
|
-
}
|
|
48746
|
-
const result = executeProxyTool(tc.name, args, ctx);
|
|
48747
|
-
const preview = result.length > 120 ? result.slice(0, 120) + "..." : result;
|
|
48748
|
-
ctx.log(`[acp-proxy: ${tc.name} (${tc.id}) \u2192 ${preview.replace(/\n/g, " ")}]`);
|
|
48749
|
-
yield Buffer.from(
|
|
48750
|
-
buildContentSse(responseId, model, buildVisibilityMarker(tc.name, result)),
|
|
48751
|
-
"utf8"
|
|
48752
|
-
);
|
|
48753
|
-
messages.push({
|
|
48754
|
-
role: "tool",
|
|
48755
|
-
tool_call_id: tc.id,
|
|
48756
|
-
content: result
|
|
48757
|
-
});
|
|
48758
|
-
}
|
|
48759
|
-
requestBody.messages = messages;
|
|
48760
|
-
const { response: resp, clearTimer } = await fetchWithTimeout(requestOptions.url, {
|
|
48761
|
-
method: "POST",
|
|
48762
|
-
headers: requestOptions.headers,
|
|
48763
|
-
body: JSON.stringify(requestBody),
|
|
48764
|
-
...ctx.proxyUrl ? { dispatcher: proxyDispatcher(ctx.proxyUrl) } : {}
|
|
48765
|
-
});
|
|
48766
|
-
if (!resp.ok || !resp.body) {
|
|
48767
|
-
const errText = await resp.text().catch(() => "upstream error");
|
|
48768
|
-
ctx.log(`[acp-proxy: compress loop upstream error ${resp.status}: ${errText.slice(0, 200)}]`);
|
|
48769
|
-
yield Buffer.from(
|
|
48770
|
-
`data: ${JSON.stringify({
|
|
48771
|
-
...makeBase(),
|
|
48772
|
-
choices: [{
|
|
48773
|
-
index: 0,
|
|
48774
|
-
delta: { content: `
|
|
48775
|
-
[acp-proxy: upstream error ${resp.status}: ${errText.slice(0, 200)}]
|
|
48776
|
-
` },
|
|
48777
|
-
finish_reason: null
|
|
48778
|
-
}]
|
|
48779
|
-
})}
|
|
48780
|
-
|
|
48781
|
-
`,
|
|
48782
|
-
"utf8"
|
|
48783
|
-
);
|
|
48784
|
-
yield Buffer.from(buildFinishSse(makeBase(), "stop", null), "utf8");
|
|
48785
|
-
yield Buffer.from("data: [DONE]\n\n", "utf8");
|
|
48786
|
-
return;
|
|
48787
|
-
}
|
|
48788
|
-
upstream = resp.body;
|
|
48789
|
-
if (activeClearTimer) activeClearTimer();
|
|
48790
|
-
activeClearTimer = clearTimer;
|
|
48791
|
-
}
|
|
48792
|
-
} finally {
|
|
48793
|
-
if (activeClearTimer) {
|
|
48794
|
-
activeClearTimer();
|
|
48795
|
-
activeClearTimer = null;
|
|
48796
|
-
}
|
|
48797
|
-
}
|
|
48798
|
-
}
|
|
48799
|
-
|
|
48800
|
-
// src/compress-loop-anthropic.ts
|
|
48801
|
-
function executeProxyTool2(toolName, args, ctx) {
|
|
48802
|
-
if (toolName === "compress") {
|
|
48803
|
-
return applyRanges(parseCompressInput(args), ctx);
|
|
48804
|
-
}
|
|
48805
|
-
if (toolName === "decompress") {
|
|
48806
|
-
return resolveDecompress(args, ctx);
|
|
48807
|
-
}
|
|
48808
|
-
if (toolName === "search_context") {
|
|
48809
|
-
const query = typeof args.query === "string" ? args.query : "";
|
|
48810
|
-
if (query.length === 0) return "[search_context FAILED: query is required]";
|
|
48811
|
-
const limit = typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : 5;
|
|
48812
|
-
const blocks = ctx.core.search(query, ctx.session.state).slice(0, limit);
|
|
48813
|
-
if (blocks.length === 0) return `[No blocks matched "${query}"]`;
|
|
48814
|
-
const lines = blocks.map((b2) => {
|
|
48815
|
-
const topic = b2.topic ?? "(no topic)";
|
|
48816
|
-
const preview = b2.summary.length > 200 ? b2.summary.slice(0, 200) + "..." : b2.summary;
|
|
48817
|
-
return `${b2.blockId} (T${b2.tier}) "${topic}"
|
|
48818
|
-
${preview}`;
|
|
48819
|
-
});
|
|
48820
|
-
return `Found ${blocks.length} block(s) for "${query}":
|
|
48821
|
-
|
|
48822
|
-
${lines.join("\n\n")}`;
|
|
48823
|
-
}
|
|
48824
|
-
if (toolName === "acp_status") {
|
|
48825
|
-
return buildStatusReport(ctx.session.state, ctx.messages, estimateTokensFast);
|
|
48826
|
-
}
|
|
48827
|
-
return `[Unknown proxy tool: ${toolName}]`;
|
|
48828
|
-
}
|
|
48829
|
-
function parseAnthropicSse(eventStr) {
|
|
48830
|
-
const lines = eventStr.split("\n");
|
|
48831
|
-
let type = "";
|
|
48832
|
-
const dataLines = [];
|
|
48833
|
-
for (const l of lines) {
|
|
48834
|
-
if (l.startsWith("event:")) {
|
|
48835
|
-
type = l.slice(6).trim();
|
|
48836
|
-
} else if (l.startsWith("data:")) {
|
|
48837
|
-
dataLines.push(l.slice(5).replace(/^ /, ""));
|
|
48838
|
-
}
|
|
48839
|
-
}
|
|
48840
|
-
if (!type) return null;
|
|
48841
|
-
const jsonStr = dataLines.join("\n").trim();
|
|
48842
|
-
if (!jsonStr) return { type, data: {} };
|
|
48843
|
-
try {
|
|
48844
|
-
return { type, data: JSON.parse(jsonStr) };
|
|
48845
|
-
} catch {
|
|
48846
|
-
return { type, data: {} };
|
|
48847
|
-
}
|
|
48848
|
-
}
|
|
48849
|
-
function buildTextBlockSse(index, text) {
|
|
48850
|
-
return `event: content_block_start
|
|
48851
|
-
data: ${JSON.stringify({ type: "content_block_start", index, content_block: { type: "text", text: "" } })}
|
|
48852
|
-
|
|
48853
|
-
event: content_block_delta
|
|
48854
|
-
data: ${JSON.stringify({ type: "content_block_delta", index, delta: { type: "text_delta", text } })}
|
|
48855
|
-
|
|
48856
|
-
event: content_block_stop
|
|
48857
|
-
data: ${JSON.stringify({ type: "content_block_stop", index })}
|
|
48858
|
-
|
|
48859
|
-
`;
|
|
48860
|
-
}
|
|
48861
|
-
function buildTerminalSse(stopReason, outputTokens, inputTokens, cachedTokens, messageId, model) {
|
|
48862
|
-
const usage = {
|
|
48863
|
-
input_tokens: inputTokens,
|
|
48864
|
-
output_tokens: outputTokens,
|
|
48865
|
-
cache_read_input_tokens: cachedTokens
|
|
48866
|
-
};
|
|
48867
|
-
const extra = {};
|
|
48868
|
-
if (messageId) extra.id = messageId;
|
|
48869
|
-
if (model) extra.model = model;
|
|
48870
|
-
return `event: message_delta
|
|
48871
|
-
data: ${JSON.stringify({ type: "message_delta", delta: { stop_reason: stopReason, stop_sequence: null }, usage, ...extra })}
|
|
48872
48494
|
|
|
48873
|
-
|
|
48874
|
-
|
|
48875
|
-
|
|
48876
|
-
`;
|
|
48877
|
-
}
|
|
48878
|
-
function remapIndex(json, oldIndex, newIndex) {
|
|
48879
|
-
return json.replaceAll(`"index":${oldIndex}`, `"index":${newIndex}`).replaceAll(`"index": ${oldIndex}`, `"index": ${newIndex}`);
|
|
48880
|
-
}
|
|
48881
|
-
function safeParse2(s3) {
|
|
48882
|
-
try {
|
|
48883
|
-
const v2 = JSON.parse(s3);
|
|
48884
|
-
return typeof v2 === "object" && v2 ? v2 : {};
|
|
48885
|
-
} catch {
|
|
48886
|
-
return {};
|
|
48887
|
-
}
|
|
48888
|
-
}
|
|
48889
|
-
async function* compressLoopAnthropicStream(initialUpstream, ctx, requestBody, requestOptions) {
|
|
48890
|
-
let upstream = initialUpstream;
|
|
48891
|
-
let activeClearTimer = null;
|
|
48892
|
-
try {
|
|
48893
|
-
const model = requestBody.model ?? void 0;
|
|
48894
|
-
let messageId;
|
|
48895
|
-
let clientIndex = 0;
|
|
48896
|
-
let totalOutputTokens = 0;
|
|
48897
|
-
let totalInputTokens = 0;
|
|
48898
|
-
let totalCachedTokens = 0;
|
|
48899
|
-
for (let loopCount = 1; ; loopCount++) {
|
|
48900
|
-
if (loopCount > 10) {
|
|
48901
|
-
ctx.log("[acp-proxy: anthropic compress loop limit (10) reached, finishing]");
|
|
48902
|
-
yield Buffer.from(buildTerminalSse("end_turn", totalOutputTokens, totalInputTokens, totalCachedTokens, messageId, model), "utf8");
|
|
48903
|
-
return;
|
|
48904
|
-
}
|
|
48905
|
-
const isFirstRound = loopCount === 1;
|
|
48906
|
-
const state = { clientIndex, toolBlocks: /* @__PURE__ */ new Map(), indexMap: /* @__PURE__ */ new Map() };
|
|
48907
|
-
let hasRealToolUse = false;
|
|
48908
|
-
let roundText = "";
|
|
48909
|
-
let roundStopReason;
|
|
48910
|
-
const reader = upstream.getReader();
|
|
48911
|
-
const decoder = new TextDecoder("utf-8");
|
|
48912
|
-
let sseBuffer = "";
|
|
48913
|
-
const cbs = {
|
|
48914
|
-
onRealToolUse: () => {
|
|
48915
|
-
hasRealToolUse = true;
|
|
48916
|
-
},
|
|
48917
|
-
onText: (t) => {
|
|
48918
|
-
roundText += t;
|
|
48919
|
-
},
|
|
48920
|
-
onOutputTokens: (n) => {
|
|
48921
|
-
totalOutputTokens += n;
|
|
48922
|
-
},
|
|
48923
|
-
onMessageId: (id) => {
|
|
48924
|
-
if (!messageId) messageId = id;
|
|
48925
|
-
},
|
|
48926
|
-
onStopReason: (r) => {
|
|
48927
|
-
roundStopReason = r;
|
|
48928
|
-
},
|
|
48929
|
-
onCacheUsage: (input, cached) => {
|
|
48930
|
-
if (typeof input === "number") {
|
|
48931
|
-
ctx.session.stats.inputTokens += input;
|
|
48932
|
-
ctx.session.stats.lastInputTokens = input + (typeof cached === "number" ? cached : 0);
|
|
48933
|
-
totalInputTokens += input;
|
|
48934
|
-
}
|
|
48935
|
-
if (typeof cached === "number") {
|
|
48936
|
-
ctx.session.stats.cachedTokens += cached;
|
|
48937
|
-
ctx.session.stats.cacheSamples += 1;
|
|
48938
|
-
totalCachedTokens += cached;
|
|
48939
|
-
}
|
|
48940
|
-
}
|
|
48941
|
-
};
|
|
48942
|
-
try {
|
|
48943
|
-
for (; ; ) {
|
|
48944
|
-
const { done, value } = await reader.read();
|
|
48945
|
-
if (done) break;
|
|
48946
|
-
sseBuffer += decoder.decode(value, { stream: true });
|
|
48947
|
-
sseBuffer = normalizeSseLineEndings(sseBuffer);
|
|
48948
|
-
let sep;
|
|
48949
|
-
while ((sep = sseBuffer.indexOf("\n\n")) >= 0) {
|
|
48950
|
-
const eventStr = sseBuffer.slice(0, sep);
|
|
48951
|
-
sseBuffer = sseBuffer.slice(sep + 2);
|
|
48952
|
-
if (!eventStr.trim()) continue;
|
|
48953
|
-
for (const b2 of routeAnthropicEvent(eventStr, isFirstRound, state, cbs)) {
|
|
48954
|
-
yield b2;
|
|
48955
|
-
}
|
|
48956
|
-
}
|
|
48957
|
-
}
|
|
48958
|
-
sseBuffer += decoder.decode();
|
|
48959
|
-
sseBuffer = normalizeSseLineEndings(sseBuffer);
|
|
48960
|
-
let resSep;
|
|
48961
|
-
while ((resSep = sseBuffer.indexOf("\n\n")) >= 0) {
|
|
48962
|
-
const eventStr = sseBuffer.slice(0, resSep);
|
|
48963
|
-
sseBuffer = sseBuffer.slice(resSep + 2);
|
|
48964
|
-
if (!eventStr.trim()) continue;
|
|
48965
|
-
for (const b2 of routeAnthropicEvent(eventStr, isFirstRound, state, cbs)) {
|
|
48966
|
-
yield b2;
|
|
48967
|
-
}
|
|
48968
|
-
}
|
|
48969
|
-
} finally {
|
|
48970
|
-
reader.releaseLock();
|
|
48971
|
-
}
|
|
48972
|
-
clientIndex = state.clientIndex;
|
|
48973
|
-
const proxyCalls = [...state.toolBlocks.values()].filter((b2) => PROXY_TOOL_NAMES.has(b2.name));
|
|
48974
|
-
const mutatingProxy = proxyCalls.filter((b2) => MUTATING_PROXY_TOOLS.has(b2.name));
|
|
48975
|
-
const readonlyProxy = proxyCalls.filter((b2) => READONLY_PROXY_TOOLS.has(b2.name));
|
|
48976
|
-
const hasMutatingOnly = mutatingProxy.length > 0 && !hasRealToolUse;
|
|
48977
|
-
if (!hasMutatingOnly) {
|
|
48978
|
-
for (const tc of readonlyProxy) {
|
|
48979
|
-
const args = safeParse2(tc.json);
|
|
48980
|
-
let result;
|
|
48981
|
-
try {
|
|
48982
|
-
result = executeProxyTool2(tc.name, args, ctx);
|
|
48983
|
-
} catch (e) {
|
|
48984
|
-
result = `[${tc.name} FAILED: ${e instanceof Error ? e.message : String(e)}]`;
|
|
48985
|
-
}
|
|
48986
|
-
const preview = result.length > 120 ? result.slice(0, 120) + "..." : result;
|
|
48987
|
-
ctx.log(`[acp-proxy: ${tc.name} (${tc.id}) \u2192 ${preview.replace(/\n/g, " ")}]`);
|
|
48988
|
-
yield Buffer.from(buildTextBlockSse(clientIndex, buildVisibilityMarker(tc.name, result)), "utf8");
|
|
48989
|
-
clientIndex++;
|
|
48990
|
-
}
|
|
48991
|
-
const stop = hasRealToolUse ? "tool_use" : roundStopReason ?? "end_turn";
|
|
48992
|
-
yield Buffer.from(buildTerminalSse(stop, totalOutputTokens, totalInputTokens, totalCachedTokens, messageId, model), "utf8");
|
|
48993
|
-
return;
|
|
48994
|
-
}
|
|
48995
|
-
const names = proxyCalls.map((c) => c.name).join(", ");
|
|
48996
|
-
ctx.log(`[acp-proxy: anthropic round ${loopCount} \u2014 ${proxyCalls.length} proxy call(s): ${names}]`);
|
|
48997
|
-
const messages = requestBody.messages ?? [];
|
|
48998
|
-
const assistantContent = [];
|
|
48999
|
-
if (roundText.length > 0) {
|
|
49000
|
-
assistantContent.push({ type: "text", text: roundText });
|
|
49001
|
-
}
|
|
49002
|
-
for (const tc of proxyCalls) {
|
|
49003
|
-
assistantContent.push({ type: "tool_use", id: tc.id, name: tc.name, input: safeParse2(tc.json) });
|
|
49004
|
-
}
|
|
49005
|
-
messages.push({ role: "assistant", content: assistantContent });
|
|
49006
|
-
for (const tc of proxyCalls) {
|
|
49007
|
-
const args = safeParse2(tc.json);
|
|
49008
|
-
const result = executeProxyTool2(tc.name, args, ctx);
|
|
49009
|
-
const preview = result.length > 120 ? result.slice(0, 120) + "..." : result;
|
|
49010
|
-
ctx.log(`[acp-proxy: ${tc.name} (${tc.id}) \u2192 ${preview.replace(/\n/g, " ")}]`);
|
|
49011
|
-
yield Buffer.from(buildTextBlockSse(clientIndex, buildVisibilityMarker(tc.name, result)), "utf8");
|
|
49012
|
-
clientIndex++;
|
|
49013
|
-
messages.push({
|
|
49014
|
-
role: "user",
|
|
49015
|
-
content: [{ type: "tool_result", tool_use_id: tc.id, content: result }]
|
|
49016
|
-
});
|
|
49017
|
-
}
|
|
49018
|
-
requestBody.messages = messages;
|
|
49019
|
-
const { response: resp, clearTimer } = await fetchWithTimeout(requestOptions.url, {
|
|
49020
|
-
method: "POST",
|
|
49021
|
-
headers: requestOptions.headers,
|
|
49022
|
-
body: JSON.stringify(requestBody),
|
|
49023
|
-
...ctx.proxyUrl ? { dispatcher: proxyDispatcher(ctx.proxyUrl) } : {}
|
|
49024
|
-
});
|
|
49025
|
-
if (!resp.ok || !resp.body) {
|
|
49026
|
-
const errText = await resp.text().catch(() => "upstream error");
|
|
49027
|
-
ctx.log(`[acp-proxy: anthropic compress loop upstream error ${resp.status}: ${errText.slice(0, 200)}]`);
|
|
49028
|
-
yield Buffer.from(buildTextBlockSse(clientIndex, `
|
|
49029
|
-
[acp-proxy: upstream error ${resp.status}: ${errText.slice(0, 200)}]
|
|
49030
|
-
`), "utf8");
|
|
49031
|
-
yield Buffer.from(buildTerminalSse("end_turn", totalOutputTokens, totalInputTokens, totalCachedTokens, messageId, model), "utf8");
|
|
49032
|
-
return;
|
|
49033
|
-
}
|
|
49034
|
-
upstream = resp.body;
|
|
49035
|
-
if (activeClearTimer) activeClearTimer();
|
|
49036
|
-
activeClearTimer = clearTimer;
|
|
49037
|
-
}
|
|
49038
|
-
} finally {
|
|
49039
|
-
if (activeClearTimer) {
|
|
49040
|
-
activeClearTimer();
|
|
49041
|
-
activeClearTimer = null;
|
|
49042
|
-
}
|
|
49043
|
-
}
|
|
49044
|
-
}
|
|
49045
|
-
function routeAnthropicEvent(eventStr, isFirstRound, state, cb) {
|
|
49046
|
-
const parsed = parseAnthropicSse(eventStr);
|
|
49047
|
-
if (!parsed) return [];
|
|
49048
|
-
const { type, data } = parsed;
|
|
49049
|
-
if (type === "message_start") {
|
|
49050
|
-
const msg2 = data.message ?? {};
|
|
49051
|
-
if (typeof msg2.id === "string") cb.onMessageId(msg2.id);
|
|
49052
|
-
const u2 = msg2.usage ?? {};
|
|
49053
|
-
cb.onCacheUsage(u2.input_tokens, u2.cache_read_input_tokens);
|
|
49054
|
-
return isFirstRound ? [Buffer.from(eventStr + "\n\n", "utf8")] : [];
|
|
49055
|
-
}
|
|
49056
|
-
if (type === "ping") {
|
|
49057
|
-
return [Buffer.from(eventStr + "\n\n", "utf8")];
|
|
49058
|
-
}
|
|
49059
|
-
if (type === "content_block_start") {
|
|
49060
|
-
const upstreamIndex = data.index ?? 0;
|
|
49061
|
-
const block = data.content_block ?? {};
|
|
49062
|
-
if (block.type === "tool_use") {
|
|
49063
|
-
const name = typeof block.name === "string" ? block.name : "";
|
|
49064
|
-
const id = typeof block.id === "string" ? block.id : `toolu_${upstreamIndex}`;
|
|
49065
|
-
if (PROXY_TOOL_NAMES.has(name)) {
|
|
49066
|
-
state.toolBlocks.set(upstreamIndex, { id, name, json: "" });
|
|
49067
|
-
return [];
|
|
49068
|
-
}
|
|
49069
|
-
cb.onRealToolUse();
|
|
49070
|
-
}
|
|
49071
|
-
const ci2 = state.clientIndex++;
|
|
49072
|
-
state.indexMap.set(upstreamIndex, ci2);
|
|
49073
|
-
if (isFirstRound) return [Buffer.from(eventStr + "\n\n", "utf8")];
|
|
49074
|
-
return [Buffer.from(remapIndex(eventStr + "\n\n", upstreamIndex, ci2), "utf8")];
|
|
49075
|
-
}
|
|
49076
|
-
if (type === "content_block_delta") {
|
|
49077
|
-
const upstreamIndex = data.index ?? 0;
|
|
49078
|
-
const delta = data.delta ?? {};
|
|
49079
|
-
if (state.toolBlocks.has(upstreamIndex)) {
|
|
49080
|
-
if (delta.type === "input_json_delta" && typeof delta.partial_json === "string") {
|
|
49081
|
-
state.toolBlocks.get(upstreamIndex).json += delta.partial_json;
|
|
49082
|
-
}
|
|
49083
|
-
return [];
|
|
49084
|
-
}
|
|
49085
|
-
if (delta.type === "text_delta" && typeof delta.text === "string") cb.onText(delta.text);
|
|
49086
|
-
if (isFirstRound) return [Buffer.from(eventStr + "\n\n", "utf8")];
|
|
49087
|
-
const ci2 = state.indexMap.get(upstreamIndex) ?? upstreamIndex;
|
|
49088
|
-
return [Buffer.from(remapIndex(eventStr + "\n\n", upstreamIndex, ci2), "utf8")];
|
|
49089
|
-
}
|
|
49090
|
-
if (type === "content_block_stop") {
|
|
49091
|
-
const upstreamIndex = data.index ?? 0;
|
|
49092
|
-
if (state.toolBlocks.has(upstreamIndex)) return [];
|
|
49093
|
-
if (isFirstRound) return [Buffer.from(eventStr + "\n\n", "utf8")];
|
|
49094
|
-
const ci2 = state.indexMap.get(upstreamIndex) ?? upstreamIndex;
|
|
49095
|
-
return [Buffer.from(remapIndex(eventStr + "\n\n", upstreamIndex, ci2), "utf8")];
|
|
49096
|
-
}
|
|
49097
|
-
if (type === "message_delta") {
|
|
49098
|
-
const u2 = data.usage ?? {};
|
|
49099
|
-
const out = u2.output_tokens;
|
|
49100
|
-
if (typeof out === "number") cb.onOutputTokens(out);
|
|
49101
|
-
cb.onCacheUsage(
|
|
49102
|
-
u2.input_tokens,
|
|
49103
|
-
u2.cache_read_input_tokens
|
|
49104
|
-
);
|
|
49105
|
-
const d = data.delta ?? {};
|
|
49106
|
-
if (typeof d.stop_reason === "string") cb.onStopReason(d.stop_reason);
|
|
49107
|
-
return [];
|
|
49108
|
-
}
|
|
49109
|
-
if (type === "message_stop") {
|
|
49110
|
-
return [];
|
|
49111
|
-
}
|
|
49112
|
-
return isFirstRound ? [Buffer.from(eventStr + "\n\n", "utf8")] : [];
|
|
49113
|
-
}
|
|
49114
|
-
|
|
49115
|
-
// src/compress-loop-responses.ts
|
|
49116
|
-
var TEXT_PROTOCOL = process.env.ACP_COMPRESS_PROTOCOL === "text";
|
|
49117
|
-
function extractTextTriggers(text) {
|
|
49118
|
-
const calls = [];
|
|
49119
|
-
let clean = "";
|
|
49120
|
-
let i = 0;
|
|
49121
|
-
let n = 0;
|
|
49122
|
-
while (i < text.length) {
|
|
49123
|
-
const open = text.indexOf(ACP_TEXT_OPEN, i);
|
|
49124
|
-
if (open === -1) {
|
|
49125
|
-
clean += text.slice(i);
|
|
49126
|
-
break;
|
|
49127
|
-
}
|
|
49128
|
-
clean += text.slice(i, open);
|
|
49129
|
-
const after = open + ACP_TEXT_OPEN.length;
|
|
49130
|
-
const close = text.indexOf(ACP_TEXT_CLOSE, after);
|
|
49131
|
-
if (close === -1) {
|
|
49132
|
-
clean += text.slice(open);
|
|
49133
|
-
break;
|
|
49134
|
-
}
|
|
49135
|
-
const payload = text.slice(after, close).trim();
|
|
49136
|
-
if (payload) {
|
|
49137
|
-
const stamp = `${Date.now()}_${n++}`;
|
|
49138
|
-
calls.push({
|
|
49139
|
-
itemId: `fc_text_${stamp}`,
|
|
49140
|
-
callId: `call_text_${stamp}`,
|
|
49141
|
-
name: COMPRESS_TOOL_NAME,
|
|
49142
|
-
arguments: payload
|
|
49143
|
-
});
|
|
49144
|
-
}
|
|
49145
|
-
i = close + ACP_TEXT_CLOSE.length;
|
|
49146
|
-
}
|
|
49147
|
-
return { clean, calls };
|
|
49148
|
-
}
|
|
49149
|
-
function executeProxyTool3(toolName, args, ctx) {
|
|
48495
|
+
// src/loop/core.ts
|
|
48496
|
+
var MAX_LOOP_ROUNDS = 10;
|
|
48497
|
+
function executeProxyTool(toolName, args, ctx, callId) {
|
|
49150
48498
|
if (toolName === "compress") {
|
|
49151
|
-
return applyRanges(parseCompressInput(args), ctx);
|
|
48499
|
+
return applyRanges(parseCompressInput(args, callId), ctx);
|
|
49152
48500
|
}
|
|
49153
48501
|
if (toolName === "decompress") {
|
|
49154
48502
|
return resolveDecompress(args, ctx);
|
|
@@ -49170,567 +48518,32 @@ function executeProxyTool3(toolName, args, ctx) {
|
|
|
49170
48518
|
${lines.join("\n\n")}`;
|
|
49171
48519
|
}
|
|
49172
48520
|
if (toolName === "acp_status") {
|
|
49173
|
-
return
|
|
48521
|
+
return handleAcpStatus(args, ctx);
|
|
49174
48522
|
}
|
|
49175
48523
|
return `[Unknown proxy tool: ${toolName}]`;
|
|
49176
48524
|
}
|
|
49177
|
-
function
|
|
49178
|
-
|
|
49179
|
-
|
|
49180
|
-
|
|
49181
|
-
|
|
49182
|
-
|
|
49183
|
-
|
|
49184
|
-
|
|
49185
|
-
|
|
49186
|
-
|
|
49187
|
-
|
|
49188
|
-
|
|
49189
|
-
|
|
49190
|
-
|
|
49191
|
-
|
|
49192
|
-
|
|
49193
|
-
|
|
49194
|
-
|
|
49195
|
-
|
|
49196
|
-
|
|
49197
|
-
|
|
49198
|
-
|
|
49199
|
-
try {
|
|
49200
|
-
obj = JSON.parse(dataLine);
|
|
49201
|
-
} catch {
|
|
49202
|
-
return {};
|
|
49203
|
-
}
|
|
49204
|
-
const out = {};
|
|
49205
|
-
switch (type) {
|
|
49206
|
-
case "response.created":
|
|
49207
|
-
case "response.in_progress":
|
|
49208
|
-
out.isMeta = true;
|
|
49209
|
-
out.yieldChunk = Buffer.from(eventStr + "\n\n", "utf8");
|
|
49210
|
-
return out;
|
|
49211
|
-
case "response.output_item.added": {
|
|
49212
|
-
const item = obj.item;
|
|
49213
|
-
if (item?.type === "function_call") {
|
|
49214
|
-
const name = typeof item.name === "string" ? item.name : "";
|
|
49215
|
-
out.fcStart = {
|
|
49216
|
-
itemId: typeof item.id === "string" ? item.id : "",
|
|
49217
|
-
callId: typeof item.call_id === "string" ? item.call_id : "",
|
|
49218
|
-
name
|
|
49219
|
-
};
|
|
49220
|
-
return out;
|
|
49221
|
-
}
|
|
49222
|
-
if (item?.type === "custom_tool_call") out.noBuffer = true;
|
|
49223
|
-
out.yieldChunk = Buffer.from(eventStr + "\n\n", "utf8");
|
|
49224
|
-
return out;
|
|
49225
|
-
}
|
|
49226
|
-
case "response.content_part.added":
|
|
49227
|
-
case "response.content_part.done":
|
|
49228
|
-
case "response.output_text.done":
|
|
49229
|
-
out.yieldChunk = Buffer.from(eventStr + "\n\n", "utf8");
|
|
49230
|
-
return out;
|
|
49231
|
-
case "response.output_text.delta": {
|
|
49232
|
-
const delta = typeof obj.delta === "string" ? obj.delta : "";
|
|
49233
|
-
if (delta) {
|
|
49234
|
-
out.contentDelta = delta;
|
|
49235
|
-
out.yieldChunk = Buffer.from(eventStr + "\n\n", "utf8");
|
|
49236
|
-
}
|
|
49237
|
-
return out;
|
|
49238
|
-
}
|
|
49239
|
-
case "response.function_call_arguments.delta": {
|
|
49240
|
-
const itemId = typeof obj.item_id === "string" ? obj.item_id : "";
|
|
49241
|
-
const delta = typeof obj.delta === "string" ? obj.delta : "";
|
|
49242
|
-
out.fcArgs = { itemId, delta };
|
|
49243
|
-
return out;
|
|
49244
|
-
}
|
|
49245
|
-
case "response.output_item.done": {
|
|
49246
|
-
const item = obj.item;
|
|
49247
|
-
if (item?.type === "function_call") {
|
|
49248
|
-
out.fcDone = { itemId: typeof item.id === "string" ? item.id : "" };
|
|
49249
|
-
return out;
|
|
49250
|
-
}
|
|
49251
|
-
if (item?.type === "custom_tool_call") {
|
|
49252
|
-
out.noBuffer = true;
|
|
49253
|
-
out.customToolCallDone = true;
|
|
49254
|
-
}
|
|
49255
|
-
out.yieldChunk = Buffer.from(eventStr + "\n\n", "utf8");
|
|
49256
|
-
return out;
|
|
49257
|
-
}
|
|
49258
|
-
case "response.completed":
|
|
49259
|
-
out.isMeta = true;
|
|
49260
|
-
out.terminal = true;
|
|
49261
|
-
out.terminalKind = "completed";
|
|
49262
|
-
out.responseObj = obj.response ?? null;
|
|
49263
|
-
return out;
|
|
49264
|
-
case "response.incomplete":
|
|
49265
|
-
out.isMeta = true;
|
|
49266
|
-
out.terminal = true;
|
|
49267
|
-
out.terminalKind = "incomplete";
|
|
49268
|
-
out.terminalRaw = eventStr;
|
|
49269
|
-
return out;
|
|
49270
|
-
case "response.failed":
|
|
49271
|
-
case "response.error":
|
|
49272
|
-
out.isMeta = true;
|
|
49273
|
-
out.terminal = true;
|
|
49274
|
-
out.terminalKind = "failed";
|
|
49275
|
-
out.terminalRaw = eventStr;
|
|
49276
|
-
return out;
|
|
49277
|
-
default:
|
|
49278
|
-
if (type.startsWith("response.custom_tool_call.")) out.noBuffer = true;
|
|
49279
|
-
out.yieldChunk = Buffer.from(eventStr + "\n\n", "utf8");
|
|
49280
|
-
return out;
|
|
49281
|
-
}
|
|
49282
|
-
}
|
|
49283
|
-
function buildMessageItemSequence(itemId, outputIndex, text) {
|
|
49284
|
-
const item = { type: "message", id: itemId, role: "assistant", content: [] };
|
|
49285
|
-
const part = { type: "output_text", text: "" };
|
|
49286
|
-
const doneItem = { type: "message", id: itemId, role: "assistant", content: [{ type: "output_text", text }] };
|
|
49287
|
-
return [
|
|
49288
|
-
`event: response.output_item.added
|
|
49289
|
-
data: ${JSON.stringify({ type: "response.output_item.added", output_index: outputIndex, item })}
|
|
49290
|
-
|
|
49291
|
-
`,
|
|
49292
|
-
`event: response.content_part.added
|
|
49293
|
-
data: ${JSON.stringify({ type: "response.content_part.added", item_id: itemId, output_index: outputIndex, part })}
|
|
49294
|
-
|
|
49295
|
-
`,
|
|
49296
|
-
`event: response.output_text.delta
|
|
49297
|
-
data: ${JSON.stringify({ type: "response.output_text.delta", item_id: itemId, output_index: outputIndex, delta: text })}
|
|
49298
|
-
|
|
49299
|
-
`,
|
|
49300
|
-
`event: response.output_text.done
|
|
49301
|
-
data: ${JSON.stringify({ type: "response.output_text.done", item_id: itemId, output_index: outputIndex, text })}
|
|
49302
|
-
|
|
49303
|
-
`,
|
|
49304
|
-
`event: response.content_part.done
|
|
49305
|
-
data: ${JSON.stringify({ type: "response.content_part.done", item_id: itemId, output_index: outputIndex, part: { type: "output_text", text } })}
|
|
49306
|
-
|
|
49307
|
-
`,
|
|
49308
|
-
`event: response.output_item.done
|
|
49309
|
-
data: ${JSON.stringify({ type: "response.output_item.done", output_index: outputIndex, item: doneItem })}
|
|
49310
|
-
|
|
49311
|
-
`
|
|
49312
|
-
].join("");
|
|
49313
|
-
}
|
|
49314
|
-
function buildFunctionCallEvents(fc, outputIndex) {
|
|
49315
|
-
return [
|
|
49316
|
-
`event: response.output_item.added
|
|
49317
|
-
data: ${JSON.stringify({
|
|
49318
|
-
type: "response.output_item.added",
|
|
49319
|
-
output_index: outputIndex,
|
|
49320
|
-
item: { type: "function_call", id: fc.itemId, call_id: fc.callId, name: fc.name, arguments: "" }
|
|
49321
|
-
})}
|
|
49322
|
-
|
|
49323
|
-
`,
|
|
49324
|
-
`event: response.function_call_arguments.delta
|
|
49325
|
-
data: ${JSON.stringify({
|
|
49326
|
-
type: "response.function_call_arguments.delta",
|
|
49327
|
-
item_id: fc.itemId,
|
|
49328
|
-
delta: fc.arguments
|
|
49329
|
-
})}
|
|
49330
|
-
|
|
49331
|
-
`,
|
|
49332
|
-
`event: response.function_call_arguments.done
|
|
49333
|
-
data: ${JSON.stringify({
|
|
49334
|
-
type: "response.function_call_arguments.done",
|
|
49335
|
-
item_id: fc.itemId,
|
|
49336
|
-
arguments: fc.arguments
|
|
49337
|
-
})}
|
|
49338
|
-
|
|
49339
|
-
`,
|
|
49340
|
-
`event: response.output_item.done
|
|
49341
|
-
data: ${JSON.stringify({
|
|
49342
|
-
type: "response.output_item.done",
|
|
49343
|
-
output_index: outputIndex,
|
|
49344
|
-
item: { type: "function_call", id: fc.itemId, call_id: fc.callId, name: fc.name, arguments: fc.arguments }
|
|
49345
|
-
})}
|
|
49346
|
-
|
|
49347
|
-
`
|
|
49348
|
-
].join("");
|
|
49349
|
-
}
|
|
49350
|
-
function buildCompleted(responseObj) {
|
|
49351
|
-
const resp = responseObj ?? { id: `resp-proxy-${Date.now()}`, status: "completed", output: [] };
|
|
49352
|
-
return `event: response.completed
|
|
49353
|
-
data: ${JSON.stringify({
|
|
49354
|
-
type: "response.completed",
|
|
49355
|
-
response: resp
|
|
49356
|
-
})}
|
|
49357
|
-
|
|
49358
|
-
`;
|
|
49359
|
-
}
|
|
49360
|
-
function buildFailed(responseObj) {
|
|
49361
|
-
const id = responseObj?.id ?? `resp-failed-${Date.now()}`;
|
|
49362
|
-
const resp = { ...responseObj ?? {}, id, status: "failed", error: { code: "server_error", message: "upstream returned empty response" } };
|
|
49363
|
-
return `event: response.failed
|
|
49364
|
-
data: ${JSON.stringify({
|
|
49365
|
-
type: "response.failed",
|
|
49366
|
-
response: resp
|
|
49367
|
-
})}
|
|
49368
|
-
|
|
49369
|
-
`;
|
|
49370
|
-
}
|
|
49371
|
-
function responsesJsonOutput(response) {
|
|
49372
|
-
const textParts = [];
|
|
49373
|
-
const calls = [];
|
|
49374
|
-
for (const item of Array.isArray(response.output) ? response.output : []) {
|
|
49375
|
-
if (!item || typeof item !== "object") continue;
|
|
49376
|
-
const value = item;
|
|
49377
|
-
if (value.type === "message") {
|
|
49378
|
-
for (const part of Array.isArray(value.content) ? value.content : []) {
|
|
49379
|
-
if (part && typeof part === "object" && part.type === "output_text") {
|
|
49380
|
-
textParts.push(part);
|
|
49381
|
-
}
|
|
49382
|
-
}
|
|
49383
|
-
} else if (value.type === "function_call") {
|
|
49384
|
-
calls.push({
|
|
49385
|
-
itemId: typeof value.id === "string" ? value.id : "",
|
|
49386
|
-
callId: typeof value.call_id === "string" ? value.call_id : "",
|
|
49387
|
-
name: typeof value.name === "string" ? value.name : "",
|
|
49388
|
-
arguments: typeof value.arguments === "string" ? value.arguments : ""
|
|
49389
|
-
});
|
|
49390
|
-
}
|
|
49391
|
-
}
|
|
49392
|
-
return {
|
|
49393
|
-
text: textParts.map((part) => typeof part.text === "string" ? part.text : "").join(""),
|
|
49394
|
-
textParts,
|
|
49395
|
-
calls
|
|
49396
|
-
};
|
|
49397
|
-
}
|
|
49398
|
-
function replaceResponsesJsonText(parts, text) {
|
|
49399
|
-
parts.forEach((part, index) => {
|
|
49400
|
-
part.text = index === 0 ? text : "";
|
|
49401
|
-
});
|
|
49402
|
-
}
|
|
49403
|
-
function surfaceReadonlyJson(current, proxyCalls, ctx) {
|
|
49404
|
-
const markers = [];
|
|
49405
|
-
for (const call of proxyCalls) {
|
|
49406
|
-
if (MUTATING_PROXY_TOOLS.has(call.name)) continue;
|
|
49407
|
-
let args = {};
|
|
49408
|
-
try {
|
|
49409
|
-
args = JSON.parse(call.arguments);
|
|
49410
|
-
} catch {
|
|
49411
|
-
args = {};
|
|
49412
|
-
}
|
|
49413
|
-
let result;
|
|
49414
|
-
try {
|
|
49415
|
-
result = executeProxyTool3(call.name, args, ctx);
|
|
49416
|
-
ctx.log(`[acp-proxy: responses JSON ${call.name} (read-only) \u2192 ${result.slice(0, 120).replace(/\n/g, " ")}]`);
|
|
49417
|
-
} catch (e) {
|
|
49418
|
-
result = `\u274C [ACP] ${call.name} FAILED: ${String(e)}`;
|
|
49419
|
-
ctx.log(`[acp-proxy: responses JSON ${call.name} (read-only) FAILED: ${String(e)}]`);
|
|
49420
|
-
}
|
|
49421
|
-
markers.push(buildVisibilityMarker(call.name, result));
|
|
49422
|
-
}
|
|
49423
|
-
if (markers.length === 0) return current;
|
|
49424
|
-
const out = Array.isArray(current.output) ? [...current.output] : [];
|
|
49425
|
-
out.push({ type: "message", id: `msg_acp_ro_${Date.now()}_${markers.length}`, role: "assistant", content: [{ type: "output_text", text: markers.join("\n") }] });
|
|
49426
|
-
return { ...current, output: out };
|
|
49427
|
-
}
|
|
49428
|
-
async function compressLoopResponsesJson(initialResponse, ctx, requestBody, requestOptions) {
|
|
49429
|
-
let current = initialResponse;
|
|
49430
|
-
for (let loopCount = 1; loopCount <= 5; loopCount++) {
|
|
49431
|
-
const output = responsesJsonOutput(current);
|
|
49432
|
-
const extracted = extractTextTriggers(output.text);
|
|
49433
|
-
const allCalls = [...output.calls, ...extracted.calls].filter((call) => call.name.length > 0);
|
|
49434
|
-
const proxyCalls = allCalls.filter((call) => PROXY_TOOL_NAMES.has(call.name));
|
|
49435
|
-
const realCalls = allCalls.filter((call) => !PROXY_TOOL_NAMES.has(call.name));
|
|
49436
|
-
const mutatingProxy = proxyCalls.filter((call) => MUTATING_PROXY_TOOLS.has(call.name));
|
|
49437
|
-
if (mutatingProxy.length === 0 || realCalls.length > 0) {
|
|
49438
|
-
if (proxyCalls.length > 0) {
|
|
49439
|
-
replaceResponsesJsonText(output.textParts, extracted.clean);
|
|
49440
|
-
current = surfaceReadonlyJson(current, proxyCalls, ctx);
|
|
49441
|
-
}
|
|
49442
|
-
return current;
|
|
49443
|
-
}
|
|
49444
|
-
const inputItems = Array.isArray(requestBody.input) ? [...requestBody.input] : [];
|
|
49445
|
-
if (extracted.clean.trim()) {
|
|
49446
|
-
inputItems.push({ type: "message", role: "assistant", content: [{ type: "output_text", text: extracted.clean }] });
|
|
49447
|
-
}
|
|
49448
|
-
for (const call of proxyCalls) {
|
|
49449
|
-
let args = {};
|
|
49450
|
-
try {
|
|
49451
|
-
args = JSON.parse(call.arguments);
|
|
49452
|
-
} catch (error) {
|
|
49453
|
-
log("warn", `[acp-compress-args] ${call.name} JSON.parse failed: ${String(error)}`);
|
|
49454
|
-
}
|
|
49455
|
-
const result = executeProxyTool3(call.name, args, ctx);
|
|
49456
|
-
ctx.log(`[acp-proxy: responses JSON ${call.name} \u2192 ${result.slice(0, 120).replace(/\n/g, " ")}]`);
|
|
49457
|
-
inputItems.push({ type: "message", role: "user", content: buildVisibilityMarker(call.name, result) });
|
|
49458
|
-
}
|
|
49459
|
-
requestBody.input = inputItems;
|
|
49460
|
-
const { response, clearTimer } = await fetchWithTimeout(requestOptions.url, {
|
|
49461
|
-
method: "POST",
|
|
49462
|
-
headers: requestOptions.headers,
|
|
49463
|
-
body: JSON.stringify(requestBody),
|
|
49464
|
-
...ctx.proxyUrl ? { dispatcher: proxyDispatcher(ctx.proxyUrl) } : {}
|
|
49465
|
-
});
|
|
49466
|
-
try {
|
|
49467
|
-
if (!response.ok) {
|
|
49468
|
-
const detail = await response.text().catch(() => "upstream error");
|
|
49469
|
-
throw new Error(`responses compress loop upstream error ${response.status}: ${detail.slice(0, 200)}`);
|
|
49470
|
-
}
|
|
49471
|
-
current = await response.json();
|
|
49472
|
-
} finally {
|
|
49473
|
-
clearTimer();
|
|
49474
|
-
}
|
|
49475
|
-
}
|
|
49476
|
-
ctx.log("[acp-proxy: responses JSON compress loop limit (5) reached]");
|
|
49477
|
-
return current;
|
|
49478
|
-
}
|
|
49479
|
-
async function* compressLoopResponsesStream(initialUpstream, ctx, requestBody, requestOptions) {
|
|
49480
|
-
const textProtocol = ctx.textProtocol ?? TEXT_PROTOCOL;
|
|
49481
|
-
let upstream = initialUpstream;
|
|
49482
|
-
let loopCount = 0;
|
|
49483
|
-
let responseObj = null;
|
|
49484
|
-
let activeClearTimer = null;
|
|
49485
|
-
let nextOutputIndex = 0;
|
|
49486
|
-
for (; ; ) {
|
|
49487
|
-
loopCount++;
|
|
49488
|
-
if (loopCount > 5) {
|
|
49489
|
-
ctx.log("[acp-proxy: responses compress loop limit (5) reached, forwarding completion as-is]");
|
|
49490
|
-
const limItemId = `msg_acp_limit_${Date.now()}`;
|
|
49491
|
-
yield Buffer.from(buildMessageItemSequence(limItemId, nextOutputIndex++, "\n[acp-proxy: compress loop limit reached]\n"), "utf8");
|
|
49492
|
-
yield Buffer.from(buildCompleted(responseObj), "utf8");
|
|
49493
|
-
return;
|
|
49494
|
-
}
|
|
49495
|
-
const fcByItemId = /* @__PURE__ */ new Map();
|
|
49496
|
-
let contentText = "";
|
|
49497
|
-
let customToolCalls = 0;
|
|
49498
|
-
let completed = false;
|
|
49499
|
-
let terminalKind = null;
|
|
49500
|
-
let terminalRaw = null;
|
|
49501
|
-
const isFirstRound = loopCount === 1;
|
|
49502
|
-
const reader = upstream.getReader();
|
|
49503
|
-
const decoder = new TextDecoder("utf-8");
|
|
49504
|
-
let sseBuffer = "";
|
|
49505
|
-
try {
|
|
49506
|
-
for (; ; ) {
|
|
49507
|
-
const { done, value } = await reader.read();
|
|
49508
|
-
if (done) break;
|
|
49509
|
-
sseBuffer += decoder.decode(value, { stream: true });
|
|
49510
|
-
if (sseBuffer.indexOf("\r") !== -1) sseBuffer = sseBuffer.replace(/\r\n|\r/g, "\n");
|
|
49511
|
-
let sep;
|
|
49512
|
-
while ((sep = sseBuffer.indexOf("\n\n")) >= 0) {
|
|
49513
|
-
const eventStr = sseBuffer.slice(0, sep);
|
|
49514
|
-
sseBuffer = sseBuffer.slice(sep + 2);
|
|
49515
|
-
if (!eventStr.trim()) continue;
|
|
49516
|
-
const d = classifyResponsesSseEvent(eventStr);
|
|
49517
|
-
if (d.yieldChunk && (isFirstRound || !d.isMeta) && !(textProtocol && !d.isMeta && !d.noBuffer)) {
|
|
49518
|
-
yield d.yieldChunk;
|
|
49519
|
-
}
|
|
49520
|
-
if (d.contentDelta) contentText += d.contentDelta;
|
|
49521
|
-
if (d.fcStart) {
|
|
49522
|
-
fcByItemId.set(d.fcStart.itemId, {
|
|
49523
|
-
itemId: d.fcStart.itemId,
|
|
49524
|
-
callId: d.fcStart.callId,
|
|
49525
|
-
name: d.fcStart.name,
|
|
49526
|
-
arguments: ""
|
|
49527
|
-
});
|
|
49528
|
-
}
|
|
49529
|
-
if (d.fcArgs) {
|
|
49530
|
-
const existing = fcByItemId.get(d.fcArgs.itemId);
|
|
49531
|
-
if (existing) existing.arguments += d.fcArgs.delta;
|
|
49532
|
-
}
|
|
49533
|
-
if (d.fcDone) {
|
|
49534
|
-
const existing = fcByItemId.get(d.fcDone.itemId);
|
|
49535
|
-
if (existing && !existing.arguments) {
|
|
49536
|
-
const item = JSON.parse(extractDataLine(eventStr) ?? "{}").item;
|
|
49537
|
-
const args = typeof item?.arguments === "string" ? item.arguments : "";
|
|
49538
|
-
existing.arguments = args;
|
|
49539
|
-
}
|
|
49540
|
-
}
|
|
49541
|
-
if (d.customToolCallDone) customToolCalls++;
|
|
49542
|
-
if (d.terminal) {
|
|
49543
|
-
completed = true;
|
|
49544
|
-
terminalKind = d.terminalKind ?? null;
|
|
49545
|
-
terminalRaw = d.terminalRaw ?? null;
|
|
49546
|
-
responseObj = d.responseObj ?? responseObj;
|
|
49547
|
-
const resp2 = d.responseObj ?? {};
|
|
49548
|
-
const usage = resp2.usage;
|
|
49549
|
-
if (usage && d.terminalKind === "completed") {
|
|
49550
|
-
const prompt = usage.input_tokens ?? usage.prompt_tokens ?? "?";
|
|
49551
|
-
const inDet = usage.input_tokens_details;
|
|
49552
|
-
const prDet = usage.prompt_tokens_details;
|
|
49553
|
-
const cached = inDet?.cached_tokens ?? prDet?.cached_tokens ?? "?";
|
|
49554
|
-
const out = usage.output_tokens ?? "?";
|
|
49555
|
-
log("info", `[acp-usage] round ${loopCount} input=${prompt} cached=${cached} output=${out}${cached !== "?" && cached !== 0 && prompt !== "?" ? ` (cache hit ${Math.round(Number(cached) / Number(prompt) * 100)}%)` : ""}`);
|
|
49556
|
-
if (typeof prompt === "number") {
|
|
49557
|
-
ctx.session.stats.inputTokens += prompt;
|
|
49558
|
-
ctx.session.stats.lastInputTokens = prompt + (typeof cached === "number" ? cached : 0);
|
|
49559
|
-
if (typeof cached === "number") ctx.session.stats.cachedTokens += cached;
|
|
49560
|
-
if (typeof out === "number") ctx.session.stats.outputTokens += out;
|
|
49561
|
-
ctx.session.stats.cacheSamples += 1;
|
|
49562
|
-
}
|
|
49563
|
-
}
|
|
49564
|
-
}
|
|
49565
|
-
}
|
|
49566
|
-
}
|
|
49567
|
-
} finally {
|
|
49568
|
-
reader.releaseLock();
|
|
49569
|
-
if (activeClearTimer) {
|
|
49570
|
-
activeClearTimer();
|
|
49571
|
-
activeClearTimer = null;
|
|
49572
|
-
}
|
|
49573
|
-
}
|
|
49574
|
-
if (textProtocol) {
|
|
49575
|
-
const extracted = extractTextTriggers(contentText);
|
|
49576
|
-
contentText = extracted.clean;
|
|
49577
|
-
for (const c of extracted.calls) {
|
|
49578
|
-
fcByItemId.set(c.itemId, c);
|
|
49579
|
-
}
|
|
49580
|
-
if (contentText.trim()) {
|
|
49581
|
-
const textItemId = `msg_acp_text_r${loopCount}_${Date.now()}`;
|
|
49582
|
-
yield Buffer.from(buildMessageItemSequence(textItemId, nextOutputIndex++, contentText), "utf8");
|
|
49583
|
-
}
|
|
49584
|
-
}
|
|
49585
|
-
const allCalls = [...fcByItemId.values()].filter((c) => c.name.length > 0);
|
|
49586
|
-
const proxyCalls = allCalls.filter((c) => PROXY_TOOL_NAMES.has(c.name));
|
|
49587
|
-
const realCalls = allCalls.filter((c) => !PROXY_TOOL_NAMES.has(c.name));
|
|
49588
|
-
const readonlyProxy = proxyCalls.filter((c) => READONLY_PROXY_TOOLS.has(c.name));
|
|
49589
|
-
log("debug", `[acp-diag] round ${loopCount} allCalls=[${allCalls.map((c) => c.name).join(",")}] realCalls=[${realCalls.map((c) => c.name).join(",")}] customToolCalls=${customToolCalls} text=${JSON.stringify(contentText.slice(0, 120))}`);
|
|
49590
|
-
const hasMutatingOnly = proxyCalls.some((c) => MUTATING_PROXY_TOOLS.has(c.name)) && realCalls.length === 0;
|
|
49591
|
-
if (!hasMutatingOnly) {
|
|
49592
|
-
for (const fc of readonlyProxy) {
|
|
49593
|
-
let args = {};
|
|
49594
|
-
try {
|
|
49595
|
-
args = JSON.parse(fc.arguments);
|
|
49596
|
-
} catch (e) {
|
|
49597
|
-
log("warn", `[acp-compress-args] ${fc.name} JSON.parse failed: ${String(e)}`);
|
|
49598
|
-
args = {};
|
|
49599
|
-
}
|
|
49600
|
-
let result;
|
|
49601
|
-
try {
|
|
49602
|
-
result = executeProxyTool3(fc.name, args, ctx);
|
|
49603
|
-
const preview = result.length > 120 ? result.slice(0, 120) + "..." : result;
|
|
49604
|
-
ctx.log(`[acp-proxy: responses ${fc.name} (read-only) (${fc.callId}) \u2192 ${preview.replace(/\n/g, " ")}]`);
|
|
49605
|
-
} catch (e) {
|
|
49606
|
-
result = `\u274C [ACP] ${fc.name} FAILED: ${String(e)}`;
|
|
49607
|
-
ctx.log(`[acp-proxy: responses ${fc.name} (read-only) (${fc.callId}) FAILED: ${String(e)}]`);
|
|
49608
|
-
}
|
|
49609
|
-
const markerItemId = `msg_acp_ro_${Date.now()}_${nextOutputIndex}`;
|
|
49610
|
-
yield Buffer.from(buildMessageItemSequence(markerItemId, nextOutputIndex++, buildVisibilityMarker(fc.name, result)), "utf8");
|
|
49611
|
-
}
|
|
49612
|
-
let oi2 = nextOutputIndex;
|
|
49613
|
-
for (const fc of realCalls) {
|
|
49614
|
-
yield Buffer.from(buildFunctionCallEvents(fc, oi2), "utf8");
|
|
49615
|
-
oi2++;
|
|
49616
|
-
}
|
|
49617
|
-
nextOutputIndex = oi2;
|
|
49618
|
-
if (terminalKind && terminalKind !== "completed" && terminalRaw) {
|
|
49619
|
-
yield Buffer.from(terminalRaw + "\n\n", "utf8");
|
|
49620
|
-
return;
|
|
49621
|
-
}
|
|
49622
|
-
const hasUsage = !!responseObj?.usage;
|
|
49623
|
-
const emittedReadonly = readonlyProxy.length > 0;
|
|
49624
|
-
if (contentText.length === 0 && realCalls.length === 0 && customToolCalls === 0 && !emittedReadonly && !hasUsage) {
|
|
49625
|
-
ctx.log("[acp-proxy: empty upstream response (no content/usage) \u2014 injecting response.failed for client retry]");
|
|
49626
|
-
yield Buffer.from(buildFailed(responseObj), "utf8");
|
|
49627
|
-
return;
|
|
49628
|
-
}
|
|
49629
|
-
if (!completed) {
|
|
49630
|
-
ctx.log("[acp-proxy: responses stream ended without completion]");
|
|
49631
|
-
}
|
|
49632
|
-
yield Buffer.from(buildCompleted(responseObj), "utf8");
|
|
49633
|
-
return;
|
|
49634
|
-
}
|
|
49635
|
-
const names = proxyCalls.map((c) => c.name).join(", ");
|
|
49636
|
-
ctx.log(`[acp-proxy: responses round ${loopCount} \u2014 ${proxyCalls.length} proxy call(s): ${names}]`);
|
|
49637
|
-
const inputItems = Array.isArray(requestBody.input) ? [...requestBody.input] : [];
|
|
49638
|
-
if (contentText) {
|
|
49639
|
-
inputItems.push({
|
|
49640
|
-
type: "message",
|
|
49641
|
-
role: "assistant",
|
|
49642
|
-
content: [{ type: "output_text", text: contentText }]
|
|
49643
|
-
});
|
|
49644
|
-
}
|
|
49645
|
-
if (!textProtocol) {
|
|
49646
|
-
for (const fc of proxyCalls) {
|
|
49647
|
-
inputItems.push({
|
|
49648
|
-
type: "function_call",
|
|
49649
|
-
id: fc.itemId || `fc_${Date.now()}`,
|
|
49650
|
-
call_id: fc.callId || `call_${Date.now()}`,
|
|
49651
|
-
name: fc.name,
|
|
49652
|
-
arguments: fc.arguments
|
|
49653
|
-
});
|
|
49654
|
-
}
|
|
49655
|
-
}
|
|
49656
|
-
for (const fc of proxyCalls) {
|
|
49657
|
-
let args = {};
|
|
49658
|
-
try {
|
|
49659
|
-
args = JSON.parse(fc.arguments);
|
|
49660
|
-
} catch (e) {
|
|
49661
|
-
log("warn", `[acp-compress-args] ${fc.name} JSON.parse failed: ${String(e)}. raw arguments (len=${fc.arguments.length}): ${fc.arguments.slice(0, 300)}`);
|
|
49662
|
-
args = {};
|
|
49663
|
-
}
|
|
49664
|
-
if (fc.name === "compress") {
|
|
49665
|
-
log("debug", `[acp-compress-args] compress args parsed: ${JSON.stringify(args).slice(0, 400)}`);
|
|
49666
|
-
}
|
|
49667
|
-
const result = executeProxyTool3(fc.name, args, ctx);
|
|
49668
|
-
const preview = result.length > 120 ? result.slice(0, 120) + "..." : result;
|
|
49669
|
-
ctx.log(`[acp-proxy: responses ${fc.name} (${fc.callId}) \u2192 ${preview.replace(/\n/g, " ")}]`);
|
|
49670
|
-
const markerItemId = `msg_acp_${Date.now()}_${nextOutputIndex}`;
|
|
49671
|
-
yield Buffer.from(
|
|
49672
|
-
buildMessageItemSequence(markerItemId, nextOutputIndex++, buildVisibilityMarker(fc.name, result)),
|
|
49673
|
-
"utf8"
|
|
49674
|
-
);
|
|
49675
|
-
inputItems.push(textProtocol ? { type: "message", role: "user", content: buildVisibilityMarker(fc.name, result) } : { type: "function_call_output", call_id: fc.callId || `call_${Date.now()}`, output: result });
|
|
49676
|
-
}
|
|
49677
|
-
requestBody.input = inputItems;
|
|
49678
|
-
if (!("stream" in requestBody)) requestBody.stream = true;
|
|
49679
|
-
const { response: resp, clearTimer } = await fetchWithTimeout(requestOptions.url, {
|
|
49680
|
-
method: "POST",
|
|
49681
|
-
headers: requestOptions.headers,
|
|
49682
|
-
body: JSON.stringify(requestBody),
|
|
49683
|
-
...ctx.proxyUrl ? { dispatcher: proxyDispatcher(ctx.proxyUrl) } : {}
|
|
49684
|
-
});
|
|
49685
|
-
if (!resp.ok || !resp.body) {
|
|
49686
|
-
clearTimer();
|
|
49687
|
-
const errText = await resp.text().catch(() => "upstream error");
|
|
49688
|
-
ctx.log(`[acp-proxy: responses compress loop upstream error ${resp.status}: ${errText.slice(0, 200)}]`);
|
|
49689
|
-
const errItemId = `msg_acp_err_${Date.now()}`;
|
|
49690
|
-
yield Buffer.from(
|
|
49691
|
-
buildMessageItemSequence(errItemId, nextOutputIndex++, `
|
|
49692
|
-
[acp-proxy: upstream error ${resp.status}: ${errText.slice(0, 200)}]
|
|
49693
|
-
`),
|
|
49694
|
-
"utf8"
|
|
49695
|
-
);
|
|
49696
|
-
yield Buffer.from(buildCompleted(responseObj), "utf8");
|
|
49697
|
-
return;
|
|
49698
|
-
}
|
|
49699
|
-
upstream = resp.body;
|
|
49700
|
-
if (activeClearTimer) activeClearTimer();
|
|
49701
|
-
activeClearTimer = clearTimer;
|
|
49702
|
-
}
|
|
49703
|
-
}
|
|
49704
|
-
|
|
49705
|
-
// src/loop/core.ts
|
|
49706
|
-
var MAX_LOOP_ROUNDS = 10;
|
|
49707
|
-
function executeProxyTool4(toolName, args, ctx, callId) {
|
|
49708
|
-
if (toolName === "compress") {
|
|
49709
|
-
return applyRanges(parseCompressInput(args, callId), ctx);
|
|
49710
|
-
}
|
|
49711
|
-
if (toolName === "decompress") {
|
|
49712
|
-
return resolveDecompress(args, ctx);
|
|
49713
|
-
}
|
|
49714
|
-
if (toolName === "search_context") {
|
|
49715
|
-
const query = typeof args.query === "string" ? args.query : "";
|
|
49716
|
-
if (query.length === 0) return "[search_context FAILED: query is required]";
|
|
49717
|
-
const limit = typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : 5;
|
|
49718
|
-
const blocks = ctx.core.search(query, ctx.session.state).slice(0, limit);
|
|
49719
|
-
if (blocks.length === 0) return `[No blocks matched "${query}"]`;
|
|
49720
|
-
const lines = blocks.map((b2) => {
|
|
49721
|
-
const topic = b2.topic ?? "(no topic)";
|
|
49722
|
-
const preview = b2.summary.length > 200 ? b2.summary.slice(0, 200) + "..." : b2.summary;
|
|
49723
|
-
return `${b2.blockId} (T${b2.tier}) "${topic}"
|
|
49724
|
-
${preview}`;
|
|
49725
|
-
});
|
|
49726
|
-
return `Found ${blocks.length} block(s) for "${query}":
|
|
49727
|
-
|
|
49728
|
-
${lines.join("\n\n")}`;
|
|
49729
|
-
}
|
|
49730
|
-
if (toolName === "acp_status") {
|
|
49731
|
-
return buildStatusReport(ctx.session.state, ctx.messages, estimateTokensFast);
|
|
49732
|
-
}
|
|
49733
|
-
return `[Unknown proxy tool: ${toolName}]`;
|
|
48525
|
+
function handleAcpStatus(args, ctx) {
|
|
48526
|
+
const scope = typeof args.scope === "string" ? args.scope : void 0;
|
|
48527
|
+
const view = typeof args.view === "string" ? args.view : void 0;
|
|
48528
|
+
const tool = typeof args.tool === "string" ? args.tool : void 0;
|
|
48529
|
+
const sort = typeof args.sort === "string" ? args.sort : void 0;
|
|
48530
|
+
const limit = typeof args.limit === "number" ? args.limit : void 0;
|
|
48531
|
+
const base = buildStatusReport(ctx.session.state, ctx.messages, estimateTokensFast, { scope, view, tool, sort, limit });
|
|
48532
|
+
if (scope) return base;
|
|
48533
|
+
const nudge = ctx.nudge;
|
|
48534
|
+
const ranges = nudge?.compressibleRanges ?? [];
|
|
48535
|
+
const protectedRanges = nudge?.protectedRanges ?? [];
|
|
48536
|
+
const extra = [];
|
|
48537
|
+
if (nudge) {
|
|
48538
|
+
extra.push("");
|
|
48539
|
+
extra.push(nudge.shouldInject ? `Nudge: ACTIVE \u2014 ${nudge.reason}` : `Nudge: idle \u2014 ${nudge.reason}`);
|
|
48540
|
+
}
|
|
48541
|
+
if (ranges.length > 0 || protectedRanges.length > 0) {
|
|
48542
|
+
extra.push("");
|
|
48543
|
+
extra.push(formatRanges(ranges, protectedRanges));
|
|
48544
|
+
}
|
|
48545
|
+
return extra.length > 0 ? `${base}
|
|
48546
|
+
${extra.join("\n")}` : base;
|
|
49734
48547
|
}
|
|
49735
48548
|
function recordUsage(ctx, usage, round) {
|
|
49736
48549
|
const prompt = usage.inputTokens;
|
|
@@ -49738,25 +48551,29 @@ function recordUsage(ctx, usage, round) {
|
|
|
49738
48551
|
const out = usage.outputTokens;
|
|
49739
48552
|
if (typeof prompt === "number") ctx.session.stats.inputTokens += prompt;
|
|
49740
48553
|
ctx.session.stats.lastInputTokens = (typeof prompt === "number" ? prompt : 0) + (typeof cached === "number" ? cached : 0);
|
|
49741
|
-
if (typeof cached === "number")
|
|
48554
|
+
if (typeof cached === "number") {
|
|
48555
|
+
ctx.session.stats.cachedTokens += cached;
|
|
48556
|
+
ctx.session.stats.cacheSamples += 1;
|
|
48557
|
+
}
|
|
49742
48558
|
if (typeof out === "number") ctx.session.stats.outputTokens += out;
|
|
49743
|
-
ctx.session.stats.cacheSamples += 1;
|
|
49744
48559
|
const hitPct = typeof prompt === "number" && typeof cached === "number" && prompt + cached > 0 ? Math.round(cached / (prompt + cached) * 100) : 0;
|
|
49745
48560
|
ctx.log(
|
|
49746
48561
|
`[acp-usage] round ${round} input=${ctx.session.stats.lastInputTokens} cached=${cached ?? 0} (cache hit ${hitPct}%)`
|
|
49747
48562
|
);
|
|
49748
48563
|
}
|
|
49749
|
-
async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adapter, systemPrompt) {
|
|
48564
|
+
async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adapter, systemPrompt, signal) {
|
|
49750
48565
|
let activeClearTimer = null;
|
|
49751
48566
|
let currentUpstream = upstream;
|
|
49752
48567
|
const coreMessages = [...ctx.messages];
|
|
49753
48568
|
try {
|
|
49754
48569
|
for (let round = 1; round <= MAX_LOOP_ROUNDS; round++) {
|
|
48570
|
+
if (signal?.aborted) break;
|
|
49755
48571
|
let assistantText = "";
|
|
49756
48572
|
const calls = [];
|
|
49757
48573
|
let usage = {};
|
|
49758
48574
|
let finishReason;
|
|
49759
48575
|
for await (const ev of adapter.parseStream(currentUpstream, round)) {
|
|
48576
|
+
if (signal?.aborted) break;
|
|
49760
48577
|
if (ev.kind === "text") {
|
|
49761
48578
|
assistantText += ev.delta;
|
|
49762
48579
|
if (!ctx.textProtocol && round === 1 && ev.raw) {
|
|
@@ -49788,6 +48605,7 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
|
|
|
49788
48605
|
resolvedText = extracted.clean;
|
|
49789
48606
|
allCalls = [...calls, ...extracted.calls];
|
|
49790
48607
|
}
|
|
48608
|
+
const functionCallIds = new Set(calls.map((c) => c.callId));
|
|
49791
48609
|
if (ctx.textProtocol && resolvedText.length > 0) {
|
|
49792
48610
|
yield adapter.emitText(resolvedText);
|
|
49793
48611
|
} else if (!ctx.textProtocol && round > 1 && resolvedText.length > 0) {
|
|
@@ -49804,7 +48622,7 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
|
|
|
49804
48622
|
} catch {
|
|
49805
48623
|
parsedArgs = {};
|
|
49806
48624
|
}
|
|
49807
|
-
const result =
|
|
48625
|
+
const result = executeProxyTool(call.name, parsedArgs, ctx, call.callId);
|
|
49808
48626
|
proxyResults.push({ name: call.name, callId: call.callId, result, arguments: call.arguments });
|
|
49809
48627
|
yield adapter.emitMarker(call.name, result);
|
|
49810
48628
|
} else {
|
|
@@ -49825,23 +48643,16 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
|
|
|
49825
48643
|
if (realCalls > 0) ctx.log(`[acp-loop] round ${round}: ${realCalls} real tool call(s) forwarded to client`);
|
|
49826
48644
|
}
|
|
49827
48645
|
if (proxyResults.length > 0) {
|
|
49828
|
-
if (
|
|
48646
|
+
if (assistantText.length > 0) {
|
|
49829
48647
|
coreMessages.push({
|
|
49830
48648
|
id: `acp_loop_r${round}_asst`,
|
|
49831
48649
|
role: "assistant",
|
|
49832
48650
|
contentType: "text",
|
|
49833
|
-
text:
|
|
48651
|
+
text: assistantText
|
|
49834
48652
|
});
|
|
49835
48653
|
}
|
|
49836
48654
|
for (const pr2 of proxyResults) {
|
|
49837
|
-
if (
|
|
49838
|
-
coreMessages.push({
|
|
49839
|
-
id: `acp_loop_r${round}_marker_${pr2.callId}`,
|
|
49840
|
-
role: "user",
|
|
49841
|
-
contentType: "text",
|
|
49842
|
-
text: buildVisibilityMarker(pr2.name, pr2.result)
|
|
49843
|
-
});
|
|
49844
|
-
} else {
|
|
48655
|
+
if (functionCallIds.has(pr2.callId)) {
|
|
49845
48656
|
coreMessages.push({
|
|
49846
48657
|
id: `acp_loop_r${round}_asst_tc_${pr2.callId}`,
|
|
49847
48658
|
role: "assistant",
|
|
@@ -49857,9 +48668,19 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
|
|
|
49857
48668
|
toolCallId: pr2.callId,
|
|
49858
48669
|
text: pr2.result
|
|
49859
48670
|
});
|
|
48671
|
+
} else {
|
|
48672
|
+
coreMessages.push({
|
|
48673
|
+
id: `acp_loop_r${round}_marker_${pr2.callId}`,
|
|
48674
|
+
role: "system",
|
|
48675
|
+
contentType: "text",
|
|
48676
|
+
text: buildVisibilityMarker(pr2.name, pr2.result)
|
|
48677
|
+
});
|
|
49860
48678
|
}
|
|
49861
48679
|
}
|
|
49862
|
-
|
|
48680
|
+
const anyCompressFailed = proxyResults.some(
|
|
48681
|
+
(pr2) => (pr2.name === "compress" || pr2.name === "decompress") && pr2.result.includes("FAILED")
|
|
48682
|
+
);
|
|
48683
|
+
if (!ctx.textProtocol && !anyCompressFailed) {
|
|
49863
48684
|
const hidden = hideConsumedCompressCalls(ctx.session.state, coreMessages);
|
|
49864
48685
|
if (hidden.hidden > 0) {
|
|
49865
48686
|
ctx.log(`[acp-loop] round ${round} hideConsumed hid ${hidden.hidden} compress record(s)`);
|
|
@@ -49882,24 +48703,30 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
|
|
|
49882
48703
|
yield adapter.emitCompletion({ finishReason: "length", usage });
|
|
49883
48704
|
return;
|
|
49884
48705
|
}
|
|
49885
|
-
ctx.log(`[acp-loop] round ${round}
|
|
48706
|
+
ctx.log(`[acp-loop] round ${round}: proxy tool executed; re-requesting so the model sees the result`);
|
|
48707
|
+
if (signal?.aborted) break;
|
|
49886
48708
|
const newBody = adapter.buildRequest(coreMessages, systemPrompt, requestBody);
|
|
49887
48709
|
if (process.env.ACP_DUMP_REQ !== "0" && ctx.debug) {
|
|
49888
48710
|
try {
|
|
49889
|
-
const
|
|
48711
|
+
const fs6 = await import("fs");
|
|
49890
48712
|
const dumpDir = process.env.ACP_DUMP_DIR || `${process.env.HOME}/.local/state/billion-context/dumps`;
|
|
49891
|
-
|
|
48713
|
+
fs6.mkdirSync(dumpDir, { recursive: true });
|
|
49892
48714
|
const sid = ctx.session.id ?? "unknown";
|
|
49893
|
-
|
|
48715
|
+
fs6.writeFileSync(`${dumpDir}/req-${Date.now()}-${sid}-REREQUEST.json`, JSON.stringify(newBody, null, 2));
|
|
49894
48716
|
} catch {
|
|
49895
48717
|
}
|
|
49896
48718
|
}
|
|
49897
|
-
const { response: resp, clearTimer } = await fetchWithTimeout(
|
|
49898
|
-
|
|
49899
|
-
|
|
49900
|
-
|
|
49901
|
-
|
|
49902
|
-
|
|
48719
|
+
const { response: resp, clearTimer } = await fetchWithTimeout(
|
|
48720
|
+
requestOptions.url,
|
|
48721
|
+
{
|
|
48722
|
+
method: "POST",
|
|
48723
|
+
headers: requestOptions.headers,
|
|
48724
|
+
body: JSON.stringify(newBody),
|
|
48725
|
+
...ctx.proxyUrl ? { dispatcher: proxyDispatcher(ctx.proxyUrl) } : {}
|
|
48726
|
+
},
|
|
48727
|
+
void 0,
|
|
48728
|
+
signal
|
|
48729
|
+
);
|
|
49903
48730
|
if (!resp.ok || !resp.body) {
|
|
49904
48731
|
clearTimer();
|
|
49905
48732
|
const errText = await resp.text().catch(() => "upstream error");
|
|
@@ -49948,13 +48775,13 @@ async function* iterSseEvents(stream2) {
|
|
|
49948
48775
|
reader.releaseLock();
|
|
49949
48776
|
}
|
|
49950
48777
|
}
|
|
49951
|
-
function
|
|
48778
|
+
function extractEventType(rawEvent) {
|
|
49952
48779
|
for (const l of rawEvent.split("\n")) {
|
|
49953
48780
|
if (l.startsWith("event:")) return l.slice(6).trim();
|
|
49954
48781
|
}
|
|
49955
48782
|
return null;
|
|
49956
48783
|
}
|
|
49957
|
-
function
|
|
48784
|
+
function extractDataLine(rawEvent) {
|
|
49958
48785
|
const parts = [];
|
|
49959
48786
|
for (const l of rawEvent.split("\n")) {
|
|
49960
48787
|
if (l.startsWith("data:")) {
|
|
@@ -49965,7 +48792,7 @@ function extractDataLine2(rawEvent) {
|
|
|
49965
48792
|
}
|
|
49966
48793
|
return parts.length ? parts.join("\n") : null;
|
|
49967
48794
|
}
|
|
49968
|
-
function
|
|
48795
|
+
function buildMessageItemSequence(itemId, outputIndex, text) {
|
|
49969
48796
|
const item = { type: "message", id: itemId, role: "assistant", content: [] };
|
|
49970
48797
|
const part = { type: "output_text", text: "" };
|
|
49971
48798
|
const doneItem = {
|
|
@@ -50004,7 +48831,7 @@ data: ${JSON.stringify({ type: "response.output_item.done", output_index: output
|
|
|
50004
48831
|
"utf8"
|
|
50005
48832
|
);
|
|
50006
48833
|
}
|
|
50007
|
-
function
|
|
48834
|
+
function buildFunctionCallEvents(fc, itemId, outputIndex) {
|
|
50008
48835
|
return Buffer.from(
|
|
50009
48836
|
[
|
|
50010
48837
|
`event: response.output_item.added
|
|
@@ -50043,7 +48870,7 @@ data: ${JSON.stringify({
|
|
|
50043
48870
|
"utf8"
|
|
50044
48871
|
);
|
|
50045
48872
|
}
|
|
50046
|
-
function
|
|
48873
|
+
function buildCompleted(responseObj) {
|
|
50047
48874
|
const resp = responseObj ?? { id: `resp-proxy-${Date.now()}`, status: "completed", output: [] };
|
|
50048
48875
|
return Buffer.from(
|
|
50049
48876
|
`event: response.completed
|
|
@@ -50080,15 +48907,15 @@ function createResponsesAdapter(textProtocol, projection) {
|
|
|
50080
48907
|
const devParts = projection && projection.systemParts.length > 0 ? [...projection.systemParts, systemPrompt] : [systemPrompt];
|
|
50081
48908
|
const withDev = injectResponsesDeveloperMessage(inputItems, devParts.join("\n\n---\n\n"));
|
|
50082
48909
|
const rebuilt = { ...requestBody, input: withDev };
|
|
50083
|
-
delete rebuilt.previous_response_id;
|
|
48910
|
+
if (process.env.ACP_KEEP_RESPONSE_ID !== "1") delete rebuilt.previous_response_id;
|
|
50084
48911
|
delete rebuilt.instructions;
|
|
50085
48912
|
return rebuilt;
|
|
50086
48913
|
},
|
|
50087
48914
|
async *parseStream(upstream, round) {
|
|
50088
48915
|
const pending = /* @__PURE__ */ new Map();
|
|
50089
48916
|
for await (const eventStr of iterSseEvents(upstream)) {
|
|
50090
|
-
const type =
|
|
50091
|
-
const dataLine =
|
|
48917
|
+
const type = extractEventType(eventStr);
|
|
48918
|
+
const dataLine = extractDataLine(eventStr);
|
|
50092
48919
|
if (!type || !dataLine) continue;
|
|
50093
48920
|
let obj;
|
|
50094
48921
|
try {
|
|
@@ -50186,15 +49013,15 @@ function createResponsesAdapter(textProtocol, projection) {
|
|
|
50186
49013
|
}
|
|
50187
49014
|
},
|
|
50188
49015
|
emitText(delta) {
|
|
50189
|
-
return
|
|
49016
|
+
return buildMessageItemSequence(`msg-proxy-${Date.now()}-${outputIndex}`, outputIndex++, delta);
|
|
50190
49017
|
},
|
|
50191
49018
|
emitToolCall(call) {
|
|
50192
|
-
const buf =
|
|
49019
|
+
const buf = buildFunctionCallEvents(call, `fc-proxy-${Date.now()}-${outputIndex}`, outputIndex);
|
|
50193
49020
|
outputIndex += 1;
|
|
50194
49021
|
return buf;
|
|
50195
49022
|
},
|
|
50196
49023
|
emitMarker(toolName, result) {
|
|
50197
|
-
return
|
|
49024
|
+
return buildMessageItemSequence(
|
|
50198
49025
|
`marker-${Date.now()}-${outputIndex}`,
|
|
50199
49026
|
outputIndex++,
|
|
50200
49027
|
buildVisibilityMarker(toolName, result)
|
|
@@ -50233,7 +49060,7 @@ data: ${JSON.stringify({ type: "response.failed", response: failed })}
|
|
|
50233
49060
|
}
|
|
50234
49061
|
resp = { ...resp, usage };
|
|
50235
49062
|
}
|
|
50236
|
-
return
|
|
49063
|
+
return buildCompleted(resp);
|
|
50237
49064
|
},
|
|
50238
49065
|
emitError(message) {
|
|
50239
49066
|
const resp = {
|
|
@@ -50528,7 +49355,7 @@ async function* iterSseEvents2(stream2) {
|
|
|
50528
49355
|
reader.releaseLock();
|
|
50529
49356
|
}
|
|
50530
49357
|
}
|
|
50531
|
-
function
|
|
49358
|
+
function parseAnthropicSse(eventStr) {
|
|
50532
49359
|
const lines = eventStr.split("\n");
|
|
50533
49360
|
let type = "";
|
|
50534
49361
|
const dataLines = [];
|
|
@@ -50638,7 +49465,7 @@ ${systemPrompt}` : systemPrompt;
|
|
|
50638
49465
|
let usageYielded = false;
|
|
50639
49466
|
const indexMap = /* @__PURE__ */ new Map();
|
|
50640
49467
|
for await (const eventStr of iterSseEvents2(upstream)) {
|
|
50641
|
-
const parsed =
|
|
49468
|
+
const parsed = parseAnthropicSse(eventStr);
|
|
50642
49469
|
if (!parsed) continue;
|
|
50643
49470
|
const { type, data } = parsed;
|
|
50644
49471
|
const rawBuf = Buffer.from(eventStr + "\n\n", "utf8");
|
|
@@ -50766,6 +49593,176 @@ function pickAdapter(protocol, requestBody, textProtocol, responsesProjection, a
|
|
|
50766
49593
|
throw new Error(`[acp-loop] unknown protocol: ${protocol}`);
|
|
50767
49594
|
}
|
|
50768
49595
|
|
|
49596
|
+
// src/compress-loop-responses.ts
|
|
49597
|
+
function extractTextTriggers(text) {
|
|
49598
|
+
const calls = [];
|
|
49599
|
+
let clean = "";
|
|
49600
|
+
let i = 0;
|
|
49601
|
+
let n = 0;
|
|
49602
|
+
while (i < text.length) {
|
|
49603
|
+
const open = text.indexOf(ACP_TEXT_OPEN, i);
|
|
49604
|
+
if (open === -1) {
|
|
49605
|
+
clean += text.slice(i);
|
|
49606
|
+
break;
|
|
49607
|
+
}
|
|
49608
|
+
clean += text.slice(i, open);
|
|
49609
|
+
const after = open + ACP_TEXT_OPEN.length;
|
|
49610
|
+
const close = text.indexOf(ACP_TEXT_CLOSE, after);
|
|
49611
|
+
if (close === -1) {
|
|
49612
|
+
clean += text.slice(open);
|
|
49613
|
+
break;
|
|
49614
|
+
}
|
|
49615
|
+
const payload = text.slice(after, close).trim();
|
|
49616
|
+
if (payload) {
|
|
49617
|
+
const stamp = `${Date.now()}_${n++}`;
|
|
49618
|
+
calls.push({
|
|
49619
|
+
itemId: `fc_text_${stamp}`,
|
|
49620
|
+
callId: `call_text_${stamp}`,
|
|
49621
|
+
name: COMPRESS_TOOL_NAME,
|
|
49622
|
+
arguments: payload
|
|
49623
|
+
});
|
|
49624
|
+
}
|
|
49625
|
+
i = close + ACP_TEXT_CLOSE.length;
|
|
49626
|
+
}
|
|
49627
|
+
return { clean, calls };
|
|
49628
|
+
}
|
|
49629
|
+
function executeProxyTool2(toolName, args, ctx) {
|
|
49630
|
+
if (toolName === "compress") {
|
|
49631
|
+
return applyRanges(parseCompressInput(args), ctx);
|
|
49632
|
+
}
|
|
49633
|
+
if (toolName === "decompress") {
|
|
49634
|
+
return resolveDecompress(args, ctx);
|
|
49635
|
+
}
|
|
49636
|
+
if (toolName === "search_context") {
|
|
49637
|
+
const query = typeof args.query === "string" ? args.query : "";
|
|
49638
|
+
if (query.length === 0) return "[search_context FAILED: query is required]";
|
|
49639
|
+
const limit = typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : 5;
|
|
49640
|
+
const blocks = ctx.core.search(query, ctx.session.state).slice(0, limit);
|
|
49641
|
+
if (blocks.length === 0) return `[No blocks matched "${query}"]`;
|
|
49642
|
+
const lines = blocks.map((b2) => {
|
|
49643
|
+
const topic = b2.topic ?? "(no topic)";
|
|
49644
|
+
const preview = b2.summary.length > 200 ? b2.summary.slice(0, 200) + "..." : b2.summary;
|
|
49645
|
+
return `${b2.blockId} (T${b2.tier}) "${topic}"
|
|
49646
|
+
${preview}`;
|
|
49647
|
+
});
|
|
49648
|
+
return `Found ${blocks.length} block(s) for "${query}":
|
|
49649
|
+
|
|
49650
|
+
${lines.join("\n\n")}`;
|
|
49651
|
+
}
|
|
49652
|
+
if (toolName === "acp_status") {
|
|
49653
|
+
return buildStatusReport(ctx.session.state, ctx.messages, estimateTokensFast);
|
|
49654
|
+
}
|
|
49655
|
+
return `[Unknown proxy tool: ${toolName}]`;
|
|
49656
|
+
}
|
|
49657
|
+
function responsesJsonOutput(response) {
|
|
49658
|
+
const textParts = [];
|
|
49659
|
+
const calls = [];
|
|
49660
|
+
for (const item of Array.isArray(response.output) ? response.output : []) {
|
|
49661
|
+
if (!item || typeof item !== "object") continue;
|
|
49662
|
+
const value = item;
|
|
49663
|
+
if (value.type === "message") {
|
|
49664
|
+
for (const part of Array.isArray(value.content) ? value.content : []) {
|
|
49665
|
+
if (part && typeof part === "object" && part.type === "output_text") {
|
|
49666
|
+
textParts.push(part);
|
|
49667
|
+
}
|
|
49668
|
+
}
|
|
49669
|
+
} else if (value.type === "function_call") {
|
|
49670
|
+
calls.push({
|
|
49671
|
+
itemId: typeof value.id === "string" ? value.id : "",
|
|
49672
|
+
callId: typeof value.call_id === "string" ? value.call_id : "",
|
|
49673
|
+
name: typeof value.name === "string" ? value.name : "",
|
|
49674
|
+
arguments: typeof value.arguments === "string" ? value.arguments : ""
|
|
49675
|
+
});
|
|
49676
|
+
}
|
|
49677
|
+
}
|
|
49678
|
+
return {
|
|
49679
|
+
text: textParts.map((part) => typeof part.text === "string" ? part.text : "").join(""),
|
|
49680
|
+
textParts,
|
|
49681
|
+
calls
|
|
49682
|
+
};
|
|
49683
|
+
}
|
|
49684
|
+
function replaceResponsesJsonText(parts, text) {
|
|
49685
|
+
parts.forEach((part, index) => {
|
|
49686
|
+
part.text = index === 0 ? text : "";
|
|
49687
|
+
});
|
|
49688
|
+
}
|
|
49689
|
+
function surfaceReadonlyJson(current, proxyCalls, ctx) {
|
|
49690
|
+
const markers = [];
|
|
49691
|
+
for (const call of proxyCalls) {
|
|
49692
|
+
if (MUTATING_PROXY_TOOLS.has(call.name)) continue;
|
|
49693
|
+
let args = {};
|
|
49694
|
+
try {
|
|
49695
|
+
args = JSON.parse(call.arguments);
|
|
49696
|
+
} catch {
|
|
49697
|
+
args = {};
|
|
49698
|
+
}
|
|
49699
|
+
let result;
|
|
49700
|
+
try {
|
|
49701
|
+
result = executeProxyTool2(call.name, args, ctx);
|
|
49702
|
+
ctx.log(`[acp-proxy: responses JSON ${call.name} (read-only) \u2192 ${result.slice(0, 120).replace(/\n/g, " ")}]`);
|
|
49703
|
+
} catch (e) {
|
|
49704
|
+
result = `\u274C [ACP] ${call.name} FAILED: ${String(e)}`;
|
|
49705
|
+
ctx.log(`[acp-proxy: responses JSON ${call.name} (read-only) FAILED: ${String(e)}]`);
|
|
49706
|
+
}
|
|
49707
|
+
markers.push(buildVisibilityMarker(call.name, result));
|
|
49708
|
+
}
|
|
49709
|
+
if (markers.length === 0) return current;
|
|
49710
|
+
const out = Array.isArray(current.output) ? [...current.output] : [];
|
|
49711
|
+
out.push({ type: "message", id: `msg_acp_ro_${Date.now()}_${markers.length}`, role: "assistant", content: [{ type: "output_text", text: markers.join("\n") }] });
|
|
49712
|
+
return { ...current, output: out };
|
|
49713
|
+
}
|
|
49714
|
+
async function compressLoopResponsesJson(initialResponse, ctx, requestBody, requestOptions) {
|
|
49715
|
+
let current = initialResponse;
|
|
49716
|
+
for (let loopCount = 1; loopCount <= MAX_LOOP_ROUNDS; loopCount++) {
|
|
49717
|
+
const output = responsesJsonOutput(current);
|
|
49718
|
+
const extracted = extractTextTriggers(output.text);
|
|
49719
|
+
const allCalls = [...output.calls, ...extracted.calls].filter((call) => call.name.length > 0);
|
|
49720
|
+
const proxyCalls = allCalls.filter((call) => PROXY_TOOL_NAMES.has(call.name));
|
|
49721
|
+
const realCalls = allCalls.filter((call) => !PROXY_TOOL_NAMES.has(call.name));
|
|
49722
|
+
const mutatingProxy = proxyCalls.filter((call) => MUTATING_PROXY_TOOLS.has(call.name));
|
|
49723
|
+
if (mutatingProxy.length === 0 || realCalls.length > 0) {
|
|
49724
|
+
if (proxyCalls.length > 0) {
|
|
49725
|
+
replaceResponsesJsonText(output.textParts, extracted.clean);
|
|
49726
|
+
current = surfaceReadonlyJson(current, proxyCalls, ctx);
|
|
49727
|
+
}
|
|
49728
|
+
return current;
|
|
49729
|
+
}
|
|
49730
|
+
const inputItems = Array.isArray(requestBody.input) ? [...requestBody.input] : [];
|
|
49731
|
+
if (extracted.clean.trim()) {
|
|
49732
|
+
inputItems.push({ type: "message", role: "assistant", content: [{ type: "output_text", text: extracted.clean }] });
|
|
49733
|
+
}
|
|
49734
|
+
for (const call of proxyCalls) {
|
|
49735
|
+
let args = {};
|
|
49736
|
+
try {
|
|
49737
|
+
args = JSON.parse(call.arguments);
|
|
49738
|
+
} catch (error) {
|
|
49739
|
+
log("warn", `[acp-compress-args] ${call.name} JSON.parse failed: ${String(error)}`);
|
|
49740
|
+
}
|
|
49741
|
+
const result = executeProxyTool2(call.name, args, ctx);
|
|
49742
|
+
ctx.log(`[acp-proxy: responses JSON ${call.name} \u2192 ${result.slice(0, 120).replace(/\n/g, " ")}]`);
|
|
49743
|
+
inputItems.push({ type: "message", role: "developer", content: buildVisibilityMarker(call.name, result) });
|
|
49744
|
+
}
|
|
49745
|
+
requestBody.input = inputItems;
|
|
49746
|
+
const { response, clearTimer } = await fetchWithTimeout(requestOptions.url, {
|
|
49747
|
+
method: "POST",
|
|
49748
|
+
headers: requestOptions.headers,
|
|
49749
|
+
body: JSON.stringify(requestBody),
|
|
49750
|
+
...ctx.proxyUrl ? { dispatcher: proxyDispatcher(ctx.proxyUrl) } : {}
|
|
49751
|
+
});
|
|
49752
|
+
try {
|
|
49753
|
+
if (!response.ok) {
|
|
49754
|
+
const detail = await response.text().catch(() => "upstream error");
|
|
49755
|
+
throw new Error(`responses compress loop upstream error ${response.status}: ${detail.slice(0, 200)}`);
|
|
49756
|
+
}
|
|
49757
|
+
current = await response.json();
|
|
49758
|
+
} finally {
|
|
49759
|
+
clearTimer();
|
|
49760
|
+
}
|
|
49761
|
+
}
|
|
49762
|
+
ctx.log(`[acp-proxy: responses JSON compress loop limit (${MAX_LOOP_ROUNDS}) reached]`);
|
|
49763
|
+
return current;
|
|
49764
|
+
}
|
|
49765
|
+
|
|
50769
49766
|
// src/stream-openai.ts
|
|
50770
49767
|
function rewriteOpenaiJsonResponse(body, ctx) {
|
|
50771
49768
|
if (!body || typeof body !== "object") return body;
|
|
@@ -50809,7 +49806,7 @@ ${note}` : note;
|
|
|
50809
49806
|
}
|
|
50810
49807
|
|
|
50811
49808
|
// src/stream-responses.ts
|
|
50812
|
-
var
|
|
49809
|
+
var TEXT_PROTOCOL = process.env.ACP_COMPRESS_PROTOCOL === "text";
|
|
50813
49810
|
function rewriteResponsesJsonResponse(body, ctx) {
|
|
50814
49811
|
if (!body || typeof body !== "object") return body;
|
|
50815
49812
|
const b2 = body;
|
|
@@ -51841,6 +50838,15 @@ var UPSTREAM_HOP_HEADERS = /* @__PURE__ */ new Set([
|
|
|
51841
50838
|
// streamed from fetch, otherwise clients try to decompress plain bytes.
|
|
51842
50839
|
"content-encoding"
|
|
51843
50840
|
]);
|
|
50841
|
+
function buildForwardHeaders(headers) {
|
|
50842
|
+
const out = {};
|
|
50843
|
+
for (const [k2, v2] of Object.entries(headers)) {
|
|
50844
|
+
if (k2.toLowerCase() === "content-length" || k2.toLowerCase() === "host") continue;
|
|
50845
|
+
out[k2] = v2;
|
|
50846
|
+
}
|
|
50847
|
+
out["content-type"] = "application/json";
|
|
50848
|
+
return out;
|
|
50849
|
+
}
|
|
51844
50850
|
function resolveUpstream(_opts, reqUrl, req) {
|
|
51845
50851
|
const mitmUpstream = readMitmUpstream(req?.socket);
|
|
51846
50852
|
if (mitmUpstream) {
|
|
@@ -51905,6 +50911,9 @@ async function startServer(opts) {
|
|
|
51905
50911
|
"info",
|
|
51906
50912
|
`acp-proxy listening on http://${displayHost}:${opts.port} \u2014 web UI: http://${displayHost}:${opts.port}/__bili/ \u2014 zero-config: prefix any baseURL with http://${displayHost}:${opts.port}/bili/` + (nOverrides ? ` \u2014 context overrides for ${nOverrides} upstream URL(s)` : "") + (opts.mitm.enabled ? ` \u2014 MITM proxy on (whitelist)${opts.mitm.domains.length ? ` +${opts.mitm.domains.join(",")}` : ""}` : "")
|
|
51907
50913
|
);
|
|
50914
|
+
if (opts.debug) {
|
|
50915
|
+
log2("info", `[debug] build features: raw-HTTP-capture(on) | remote_compaction_v2-strip(on) | cert-MITM-launcher(on) | strip-acp-summary(on) \u2014 seeing this line confirms the launcher build (not registry 0.1.34)`);
|
|
50916
|
+
}
|
|
51908
50917
|
});
|
|
51909
50918
|
server.on("error", (err2) => {
|
|
51910
50919
|
const hint = err2.code === "EADDRINUSE" ? ` \u2014 port ${opts.port} is already in use. Stop the other process or use --port <N>.` : err2.code === "EACCES" ? ` \u2014 port ${opts.port} requires privileges. Use a port >= 1024.` : "";
|
|
@@ -52103,6 +51112,25 @@ async function handle(req, res, opts, core, config, log2) {
|
|
|
52103
51112
|
parsed = null;
|
|
52104
51113
|
}
|
|
52105
51114
|
}
|
|
51115
|
+
if (opts.debug && parsed && typeof parsed === "object") {
|
|
51116
|
+
try {
|
|
51117
|
+
const p2 = parsed;
|
|
51118
|
+
const hasPrev = p2.previous_response_id !== void 0;
|
|
51119
|
+
const inLen = Array.isArray(p2.input) ? p2.input.length : 0;
|
|
51120
|
+
log2("info", `[debug] INCOMING previous_response_id=${hasPrev ? String(p2.previous_response_id).slice(0, 16) : "absent"} input_items=${inLen} instructions=${p2.instructions !== void 0 ? "present" : "absent"}`);
|
|
51121
|
+
const rawDir = `${stateDir()}/raw`;
|
|
51122
|
+
try {
|
|
51123
|
+
fs3.mkdirSync(rawDir, { recursive: true });
|
|
51124
|
+
} catch {
|
|
51125
|
+
}
|
|
51126
|
+
const hdrs = Object.entries(req.headers).filter(([k2]) => !/authorization|x-api-key|cookie/i.test(k2)).map(([k2, v2]) => `${k2}: ${Array.isArray(v2) ? v2.join(",") : v2}`).join("\n");
|
|
51127
|
+
fs3.writeFileSync(`${rawDir}/${Date.now()}-INCOMING.txt`, `${req.method} ${req.url}
|
|
51128
|
+
${hdrs}
|
|
51129
|
+
|
|
51130
|
+
${bodyBuffer.toString("utf8")}`);
|
|
51131
|
+
} catch {
|
|
51132
|
+
}
|
|
51133
|
+
}
|
|
52106
51134
|
let reqConfig = config;
|
|
52107
51135
|
if (parsed && typeof parsed === "object") {
|
|
52108
51136
|
const model = parsed.model;
|
|
@@ -52139,15 +51167,15 @@ async function handle(req, res, opts, core, config, log2) {
|
|
|
52139
51167
|
});
|
|
52140
51168
|
const clientLabel = responsesIdentity?.clientProvided ? responsesIdentity.value : clientConversationHeader(req.headers);
|
|
52141
51169
|
const session = getSession(sessionId, { protocol, upstreamOrigin, label: clientLabel ?? void 0 });
|
|
52142
|
-
|
|
52143
|
-
|
|
52144
|
-
|
|
52145
|
-
|
|
51170
|
+
acquireInFlight(session);
|
|
51171
|
+
try {
|
|
51172
|
+
await withSessionLock(session, async () => {
|
|
51173
|
+
prepared = countTokens ? prepareCountTokens(parsed, core, reqConfig, log2, session) : protocol === "anthropic" ? prepareAnthropic(parsed, req, opts, core, reqConfig, log2, session) : protocol === "openai" ? prepareOpenai(parsed, req, opts, core, reqConfig, log2, session) : responsesCompact ? prepareResponsesCompact(bodyBuffer, parsed, session) : prepareResponses(parsed, req, opts, core, reqConfig, log2, session, responsesIdentity);
|
|
52146
51174
|
await forward(req, res, opts, prepared.body, prepared, core, reqConfig, log2, route, affinity);
|
|
52147
|
-
}
|
|
52148
|
-
|
|
52149
|
-
|
|
52150
|
-
}
|
|
51175
|
+
});
|
|
51176
|
+
} finally {
|
|
51177
|
+
releaseInFlight(session);
|
|
51178
|
+
}
|
|
52151
51179
|
}
|
|
52152
51180
|
if (!prepared) {
|
|
52153
51181
|
if (protocol === null && !opts.passthrough) {
|
|
@@ -52157,6 +51185,9 @@ async function handle(req, res, opts, core, config, log2) {
|
|
|
52157
51185
|
}
|
|
52158
51186
|
}
|
|
52159
51187
|
var ACP_TAG_MARK = "<acp ";
|
|
51188
|
+
function stripKernelSummaries(messages) {
|
|
51189
|
+
return messages.filter((m2) => !(m2.id ?? "").startsWith("acp_summary_"));
|
|
51190
|
+
}
|
|
52160
51191
|
function diagTagSummary(messages, sessionId, strategy) {
|
|
52161
51192
|
let textTagged = 0;
|
|
52162
51193
|
let toolTagged = 0;
|
|
@@ -52187,6 +51218,7 @@ function prepareAnthropic(parsed, req, opts, core, config, log2, session) {
|
|
|
52187
51218
|
++session.stats.requests;
|
|
52188
51219
|
let processedMessages = [];
|
|
52189
51220
|
let originalMessages = [];
|
|
51221
|
+
let nudge;
|
|
52190
51222
|
let rebuiltMessages = parsed.messages;
|
|
52191
51223
|
let systemOut = parsed.system;
|
|
52192
51224
|
let toolsOut = parsed.tools;
|
|
@@ -52197,6 +51229,7 @@ function prepareAnthropic(parsed, req, opts, core, config, log2, session) {
|
|
|
52197
51229
|
const tokenCount = session.stats.lastInputTokens;
|
|
52198
51230
|
const turn = core.processTurn({ messages: msgs, state: session.state, config, tokenCount, renderTags: "text-only" });
|
|
52199
51231
|
session.state = turn.state;
|
|
51232
|
+
nudge = turn.nudge;
|
|
52200
51233
|
session.stats.contextTokens = tokenCount;
|
|
52201
51234
|
if (!session.meta.title) {
|
|
52202
51235
|
const t = deriveTitle(msgs);
|
|
@@ -52204,7 +51237,7 @@ function prepareAnthropic(parsed, req, opts, core, config, log2, session) {
|
|
|
52204
51237
|
}
|
|
52205
51238
|
log2("info", diagTagSummary(turn.messages, sessionId, "text-only"));
|
|
52206
51239
|
log2("info", diagNudge(turn, sessionId, tokenCount, config.modelContextLimit));
|
|
52207
|
-
processedMessages = turn.messages;
|
|
51240
|
+
processedMessages = stripKernelSummaries(turn.messages);
|
|
52208
51241
|
reapOrphanBlocks(session, msgs, deactivateBlock);
|
|
52209
51242
|
rebuiltMessages = coreToAnthropic(processedMessages, cacheControls);
|
|
52210
51243
|
systemOut = injectSystem(parsed, opts);
|
|
@@ -52226,7 +51259,7 @@ function prepareAnthropic(parsed, req, opts, core, config, log2, session) {
|
|
|
52226
51259
|
}
|
|
52227
51260
|
const rebuilt = { ...parsed, messages: rebuiltMessages, system: systemOut, tools: toolsOut };
|
|
52228
51261
|
markDirty(session);
|
|
52229
|
-
return { body: JSON.stringify(rebuilt), session, processedMessages, originalMessages, anthropicSystem: parsed.system, protocol: "anthropic", stream: stream2, compressInjected: opts.compress.injectTool };
|
|
51262
|
+
return { body: JSON.stringify(rebuilt), session, processedMessages, originalMessages, anthropicSystem: parsed.system, protocol: "anthropic", stream: stream2, compressInjected: opts.compress.injectTool, nudge };
|
|
52230
51263
|
}
|
|
52231
51264
|
function prepareOpenai(parsed, req, opts, core, config, log2, session) {
|
|
52232
51265
|
const sessionId = session.id;
|
|
@@ -52234,6 +51267,7 @@ function prepareOpenai(parsed, req, opts, core, config, log2, session) {
|
|
|
52234
51267
|
++session.stats.requests;
|
|
52235
51268
|
let processedMessages = [];
|
|
52236
51269
|
let originalMessages = [];
|
|
51270
|
+
let nudge;
|
|
52237
51271
|
let rebuiltMessages = parsed.messages;
|
|
52238
51272
|
let toolsOut = parsed.tools;
|
|
52239
51273
|
const maxTokens = typeof parsed.max_tokens === "number" ? parsed.max_tokens : 8192;
|
|
@@ -52245,6 +51279,7 @@ function prepareOpenai(parsed, req, opts, core, config, log2, session) {
|
|
|
52245
51279
|
const tokenCount = session.stats.lastInputTokens;
|
|
52246
51280
|
const turn = core.processTurn({ messages: msgs, state: session.state, config, tokenCount, renderTags: "text-only" });
|
|
52247
51281
|
session.state = turn.state;
|
|
51282
|
+
nudge = turn.nudge;
|
|
52248
51283
|
session.stats.contextTokens = tokenCount;
|
|
52249
51284
|
if (!session.meta.title) {
|
|
52250
51285
|
const t = deriveTitle(msgs);
|
|
@@ -52252,7 +51287,7 @@ function prepareOpenai(parsed, req, opts, core, config, log2, session) {
|
|
|
52252
51287
|
}
|
|
52253
51288
|
log2("info", diagTagSummary(turn.messages, sessionId, "text-only"));
|
|
52254
51289
|
log2("info", diagNudge(turn, sessionId, tokenCount, config.modelContextLimit));
|
|
52255
|
-
processedMessages = turn.messages;
|
|
51290
|
+
processedMessages = stripKernelSummaries(turn.messages);
|
|
52256
51291
|
reapOrphanBlocks(session, msgs, deactivateBlock);
|
|
52257
51292
|
rebuiltMessages = coreToOpenai(processedMessages);
|
|
52258
51293
|
const sysParts = [];
|
|
@@ -52279,7 +51314,7 @@ function prepareOpenai(parsed, req, opts, core, config, log2, session) {
|
|
|
52279
51314
|
rebuilt.stream_options = { include_usage: true };
|
|
52280
51315
|
}
|
|
52281
51316
|
markDirty(session);
|
|
52282
|
-
return { body: JSON.stringify(rebuilt), session, processedMessages, originalMessages, protocol: "openai", stream: stream2, compressInjected: shouldInject };
|
|
51317
|
+
return { body: JSON.stringify(rebuilt), session, processedMessages, originalMessages, protocol: "openai", stream: stream2, compressInjected: shouldInject, nudge };
|
|
52283
51318
|
}
|
|
52284
51319
|
function prepareResponses(parsed, req, opts, core, config, log2, session, identity) {
|
|
52285
51320
|
const sessionId = session.id;
|
|
@@ -52290,11 +51325,12 @@ function prepareResponses(parsed, req, opts, core, config, log2, session, identi
|
|
|
52290
51325
|
}
|
|
52291
51326
|
let processedMessages = [];
|
|
52292
51327
|
let originalMessages = [];
|
|
51328
|
+
let nudge;
|
|
52293
51329
|
let responsesProjection;
|
|
52294
51330
|
let rebuiltInput = parsed.input;
|
|
52295
51331
|
let toolsOut = parsed.tools;
|
|
52296
51332
|
const shouldInject = opts.compress.injectTool;
|
|
52297
|
-
const responsesTextProtocol = FORCE_TEXT_PROTOCOL ||
|
|
51333
|
+
const responsesTextProtocol = FORCE_TEXT_PROTOCOL || resolveCompressProtocol(opts.routes, session.meta.upstreamOrigin) === "marker";
|
|
52298
51334
|
try {
|
|
52299
51335
|
const projection = responsesToCore(parsed);
|
|
52300
51336
|
responsesProjection = projection;
|
|
@@ -52306,6 +51342,7 @@ function prepareResponses(parsed, req, opts, core, config, log2, session, identi
|
|
|
52306
51342
|
const tokenCount = session.stats.lastInputTokens;
|
|
52307
51343
|
const turn = core.processTurn({ messages: msgs, state: session.state, config, tokenCount, renderTags: process.env.ACP_RENDER_NONE ? "none" : "text-only" });
|
|
52308
51344
|
session.state = turn.state;
|
|
51345
|
+
nudge = turn.nudge;
|
|
52309
51346
|
session.stats.contextTokens = tokenCount;
|
|
52310
51347
|
if (!session.meta.title) {
|
|
52311
51348
|
const t = deriveTitle(msgs);
|
|
@@ -52313,14 +51350,16 @@ function prepareResponses(parsed, req, opts, core, config, log2, session, identi
|
|
|
52313
51350
|
}
|
|
52314
51351
|
log2("info", diagTagSummary(turn.messages, sessionId, "text-only"));
|
|
52315
51352
|
log2("info", diagNudge(turn, sessionId, tokenCount, config.modelContextLimit));
|
|
52316
|
-
processedMessages = turn.messages;
|
|
51353
|
+
processedMessages = stripKernelSummaries(turn.messages);
|
|
52317
51354
|
reapOrphanBlocks(session, msgs, deactivateBlock);
|
|
52318
51355
|
rebuiltInput = patchResponsesInput(projection, processedMessages);
|
|
52319
51356
|
if (shouldInject && !process.env.ACP_NO_COMPRESS_PROMPT) {
|
|
52320
|
-
const prompt = responsesTextProtocol ?
|
|
51357
|
+
const prompt = responsesTextProtocol ? buildCompressHybridSystemPrompt() : buildCompressSystemPrompt();
|
|
52321
51358
|
const devContent = [...projection.systemParts, prompt].join("\n\n---\n\n");
|
|
52322
51359
|
rebuiltInput = injectResponsesDeveloperMessage(rebuiltInput, devContent);
|
|
52323
|
-
if (!
|
|
51360
|
+
if (!process.env.ACP_NO_INJECT_TOOL) {
|
|
51361
|
+
toolsOut = responsesTextProtocol ? injectResponsesTool(parsed.tools, ACP_READONLY_TOOLS_RESPONSES) : injectResponsesTool(parsed.tools);
|
|
51362
|
+
}
|
|
52324
51363
|
} else if (projection.systemParts.length > 0) {
|
|
52325
51364
|
rebuiltInput = injectResponsesDeveloperMessage(rebuiltInput, projection.systemParts.join("\n\n---\n\n"));
|
|
52326
51365
|
}
|
|
@@ -52347,7 +51386,7 @@ function prepareResponses(parsed, req, opts, core, config, log2, session, identi
|
|
|
52347
51386
|
session.meta.upstreamOrigin
|
|
52348
51387
|
);
|
|
52349
51388
|
if (promptCacheKey && !rebuilt.prompt_cache_key) rebuilt.prompt_cache_key = promptCacheKey;
|
|
52350
|
-
delete rebuilt.previous_response_id;
|
|
51389
|
+
if (process.env.ACP_KEEP_RESPONSE_ID !== "1") delete rebuilt.previous_response_id;
|
|
52351
51390
|
delete rebuilt.instructions;
|
|
52352
51391
|
if (process.env.ACP_DEBUG) {
|
|
52353
51392
|
const fwdTools = (Array.isArray(toolsOut) ? toolsOut : []).map((t) => {
|
|
@@ -52367,7 +51406,8 @@ function prepareResponses(parsed, req, opts, core, config, log2, session, identi
|
|
|
52367
51406
|
protocol: "responses",
|
|
52368
51407
|
stream: stream2,
|
|
52369
51408
|
compressInjected: shouldInject,
|
|
52370
|
-
responsesTextProtocol
|
|
51409
|
+
responsesTextProtocol,
|
|
51410
|
+
nudge
|
|
52371
51411
|
};
|
|
52372
51412
|
}
|
|
52373
51413
|
function isCountTokensRequest(method, urlPath, hasBody) {
|
|
@@ -52378,8 +51418,9 @@ function prepareCountTokens(parsed, core, config, log2, session) {
|
|
|
52378
51418
|
try {
|
|
52379
51419
|
const { msgs, cacheControls } = anthropicToCore(parsed);
|
|
52380
51420
|
const turn = core.processTurn({ messages: msgs, state: session.state, config, tokenCount: session.stats.lastInputTokens, renderTags: "text-only" });
|
|
52381
|
-
const
|
|
52382
|
-
|
|
51421
|
+
const stripped = stripKernelSummaries(turn.messages);
|
|
51422
|
+
const rebuiltMessages = coreToAnthropic(stripped, cacheControls);
|
|
51423
|
+
log2("info", `[${sessionId}] count_tokens pruned: ${msgs.length} \u2192 ${stripped.length} msgs`);
|
|
52383
51424
|
return {
|
|
52384
51425
|
body: JSON.stringify({ ...parsed, messages: rebuiltMessages }),
|
|
52385
51426
|
session,
|
|
@@ -52415,19 +51456,6 @@ function prepareResponsesCompact(body, parsed, session) {
|
|
|
52415
51456
|
resetAfterSuccess: true
|
|
52416
51457
|
};
|
|
52417
51458
|
}
|
|
52418
|
-
function isChatGptCodexUpstream(upstream) {
|
|
52419
|
-
if (!upstream) return false;
|
|
52420
|
-
try {
|
|
52421
|
-
return new URL(upstream).hostname.toLowerCase() === "chatgpt.com";
|
|
52422
|
-
} catch {
|
|
52423
|
-
return false;
|
|
52424
|
-
}
|
|
52425
|
-
}
|
|
52426
|
-
function isCodexResponsesLite(headers, body) {
|
|
52427
|
-
if (headers["x-openai-internal-codex-responses-lite"] !== void 0) return true;
|
|
52428
|
-
if (Object.prototype.hasOwnProperty.call(body, "additional_tools")) return true;
|
|
52429
|
-
return Array.isArray(body.input) && body.input.some((item) => item.type === "additional_tools");
|
|
52430
|
-
}
|
|
52431
51459
|
function shouldInjectPromptCacheKey(routing, upstream) {
|
|
52432
51460
|
if (routing === "enabled") return true;
|
|
52433
51461
|
if (routing === "disabled" || !upstream) return false;
|
|
@@ -52469,12 +51497,12 @@ function injectOpenaiTool(tools) {
|
|
|
52469
51497
|
return [...tools, ...additions];
|
|
52470
51498
|
}
|
|
52471
51499
|
var FORCE_TEXT_PROTOCOL = process.env.ACP_COMPRESS_PROTOCOL === "text";
|
|
52472
|
-
function injectResponsesTool(tools) {
|
|
52473
|
-
if (!Array.isArray(tools)) return [...
|
|
51500
|
+
function injectResponsesTool(tools, toolsToAdd = ACP_TOOLS_RESPONSES) {
|
|
51501
|
+
if (!Array.isArray(tools)) return [...toolsToAdd];
|
|
52474
51502
|
const present = new Set(
|
|
52475
51503
|
tools.map((t) => t?.name).filter((n) => typeof n === "string")
|
|
52476
51504
|
);
|
|
52477
|
-
const additions =
|
|
51505
|
+
const additions = toolsToAdd.filter((t) => !present.has(t.name));
|
|
52478
51506
|
return [...tools, ...additions];
|
|
52479
51507
|
}
|
|
52480
51508
|
async function forward(req, res, opts, body, prepared, core, config, log2, route, affinity) {
|
|
@@ -52527,6 +51555,12 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
|
|
|
52527
51555
|
headers[k2] = Array.isArray(v2) ? v2.join(", ") : v2;
|
|
52528
51556
|
}
|
|
52529
51557
|
headers["host"] = new URL(route ? route.upstream : opts.upstream).host;
|
|
51558
|
+
const betaKey = Object.keys(headers).find((h) => h.toLowerCase() === "x-codex-beta-features");
|
|
51559
|
+
if (betaKey) {
|
|
51560
|
+
const kept = headers[betaKey].split(",").map((s3) => s3.trim()).filter((f2) => f2 && f2 !== "remote_compaction_v2");
|
|
51561
|
+
if (kept.length > 0) headers[betaKey] = kept.join(",");
|
|
51562
|
+
else delete headers[betaKey];
|
|
51563
|
+
}
|
|
52530
51564
|
if (affinity && !clientConversationHeader(req.headers)) {
|
|
52531
51565
|
headers["x-session-id"] = affinity;
|
|
52532
51566
|
}
|
|
@@ -52538,6 +51572,29 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
|
|
|
52538
51572
|
}
|
|
52539
51573
|
log2("info", `[${prepared?.session.id ?? "unknown"}] \u2192 upstream headers: ${JSON.stringify(hdrLog)}`);
|
|
52540
51574
|
}
|
|
51575
|
+
const rawBase = opts.debug ? (() => {
|
|
51576
|
+
try {
|
|
51577
|
+
const rawDir = process.env.ACP_RAW_DUMP_DIR || `${stateDir()}/raw`;
|
|
51578
|
+
fs3.mkdirSync(rawDir, { recursive: true });
|
|
51579
|
+
return `${rawDir}/${Date.now()}-${prepared?.session.id ?? "unknown"}`;
|
|
51580
|
+
} catch {
|
|
51581
|
+
return "";
|
|
51582
|
+
}
|
|
51583
|
+
})() : "";
|
|
51584
|
+
if (rawBase) {
|
|
51585
|
+
try {
|
|
51586
|
+
const maskHdr = (k2, v2) => /key|auth|token/i.test(k2) ? `<masked ${v2.length} chars>` : v2;
|
|
51587
|
+
const hdrText = Object.entries(headers).map(([k2, v2]) => `${k2}: ${maskHdr(k2, String(v2))}`).join("\n");
|
|
51588
|
+
const bodyText = req.method === "GET" || req.method === "HEAD" ? "" : typeof body === "string" ? body : Buffer.from(body).toString("utf8");
|
|
51589
|
+
const reqPath = `${rawBase}-REQ.txt`;
|
|
51590
|
+
fs3.writeFileSync(reqPath, `${req.method ?? "POST"} ${upstreamUrl}
|
|
51591
|
+
${hdrText}
|
|
51592
|
+
|
|
51593
|
+
${bodyText}`);
|
|
51594
|
+
log2("info", `[debug] RAW request dump: ${reqPath}`);
|
|
51595
|
+
} catch {
|
|
51596
|
+
}
|
|
51597
|
+
}
|
|
52541
51598
|
const dispatcher = proxyDispatcher(proxyUrl);
|
|
52542
51599
|
const init = {
|
|
52543
51600
|
method: req.method ?? "GET",
|
|
@@ -52567,6 +51624,18 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
|
|
|
52567
51624
|
});
|
|
52568
51625
|
log2("info", `[${prepared?.session.id ?? "unknown"}] \u2190 upstream response headers: ${JSON.stringify(respLog)}`);
|
|
52569
51626
|
}
|
|
51627
|
+
if (rawBase) {
|
|
51628
|
+
try {
|
|
51629
|
+
const maskHdr = (k2, v2) => /key|auth|token/i.test(k2) ? `<masked ${v2.length} chars>` : v2;
|
|
51630
|
+
const hdrText = Object.entries(respHeaders).map(([k2, v2]) => `${k2}: ${maskHdr(k2, v2)}`).join("\n");
|
|
51631
|
+
const resPath = `${rawBase}-RES.txt`;
|
|
51632
|
+
fs3.writeFileSync(resPath, `${upstream.status}
|
|
51633
|
+
${hdrText}
|
|
51634
|
+
`);
|
|
51635
|
+
log2("info", `[debug] RAW response dump: ${resPath}`);
|
|
51636
|
+
} catch {
|
|
51637
|
+
}
|
|
51638
|
+
}
|
|
52570
51639
|
if (!upstream.ok) {
|
|
52571
51640
|
res.writeHead(upstream.status, respHeaders);
|
|
52572
51641
|
if (upstream.body) await pipeThrough(upstream.body, res);
|
|
@@ -52610,105 +51679,39 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
|
|
|
52610
51679
|
dumpRaw = dumpStreamToFile(b2, opts.dumpSse, `${Date.now()}-${prepared.session.id}-raw.sse`);
|
|
52611
51680
|
}
|
|
52612
51681
|
try {
|
|
52613
|
-
|
|
52614
|
-
|
|
52615
|
-
|
|
52616
|
-
|
|
52617
|
-
|
|
52618
|
-
|
|
52619
|
-
|
|
52620
|
-
|
|
52621
|
-
|
|
52622
|
-
|
|
52623
|
-
|
|
52624
|
-
|
|
52625
|
-
|
|
52626
|
-
|
|
52627
|
-
|
|
52628
|
-
|
|
52629
|
-
|
|
52630
|
-
|
|
52631
|
-
|
|
52632
|
-
|
|
52633
|
-
|
|
52634
|
-
|
|
52635
|
-
|
|
52636
|
-
log2("warn", `[${prepared.session.id}] tag echo: ${prepared.protocol} response stream contains <acp tag`);
|
|
52637
|
-
}
|
|
52638
|
-
}
|
|
52639
|
-
res.write(chunk);
|
|
52640
|
-
if (res.writableNeedDrain) await new Promise((r) => res.once("drain", () => r()));
|
|
52641
|
-
}
|
|
52642
|
-
res.end();
|
|
52643
|
-
} else if (prepared.protocol === "openai") {
|
|
52644
|
-
const parsedReq = JSON.parse(typeof body === "string" ? body : body.toString("utf8"));
|
|
52645
|
-
const reqHeaders = {};
|
|
52646
|
-
for (const [k2, v2] of Object.entries(headers)) {
|
|
52647
|
-
if (k2.toLowerCase() === "content-length" || k2.toLowerCase() === "host") continue;
|
|
52648
|
-
reqHeaders[k2] = v2;
|
|
52649
|
-
}
|
|
52650
|
-
reqHeaders["content-type"] = "application/json";
|
|
52651
|
-
const loop = compressLoopStream(
|
|
52652
|
-
streamToRead,
|
|
52653
|
-
{ core, config, messages: prepared.originalMessages, session: prepared.session, log: ctx.log, proxyUrl },
|
|
52654
|
-
parsedReq,
|
|
52655
|
-
{ url: upstreamUrl, headers: reqHeaders }
|
|
52656
|
-
);
|
|
52657
|
-
for await (const chunk of loop) {
|
|
52658
|
-
{
|
|
52659
|
-
const s3 = chunk.toString("utf8");
|
|
52660
|
-
if (s3.includes("<acp ") || s3.includes("</acp")) {
|
|
52661
|
-
log2("warn", `[${prepared.session.id}] tag echo: openai response stream contains <acp tag`);
|
|
52662
|
-
}
|
|
52663
|
-
}
|
|
52664
|
-
if (!res.write(chunk)) await new Promise((r) => res.once("drain", () => r()));
|
|
52665
|
-
}
|
|
52666
|
-
} else if (prepared.protocol === "responses") {
|
|
52667
|
-
const parsedReq = JSON.parse(typeof body === "string" ? body : body.toString("utf8"));
|
|
52668
|
-
const reqHeaders = {};
|
|
52669
|
-
for (const [k2, v2] of Object.entries(headers)) {
|
|
52670
|
-
if (k2.toLowerCase() === "content-length" || k2.toLowerCase() === "host") continue;
|
|
52671
|
-
reqHeaders[k2] = v2;
|
|
52672
|
-
}
|
|
52673
|
-
reqHeaders["content-type"] = "application/json";
|
|
52674
|
-
const loop = compressLoopResponsesStream(
|
|
52675
|
-
streamToRead,
|
|
52676
|
-
{ core, config, messages: prepared.originalMessages, session: prepared.session, log: ctx.log, proxyUrl, textProtocol: prepared.responsesTextProtocol },
|
|
52677
|
-
parsedReq,
|
|
52678
|
-
{ url: upstreamUrl, headers: reqHeaders }
|
|
52679
|
-
);
|
|
52680
|
-
for await (const chunk of loop) {
|
|
52681
|
-
{
|
|
52682
|
-
const s3 = chunk.toString("utf8");
|
|
52683
|
-
if (s3.includes("<acp ") || s3.includes("</acp")) {
|
|
52684
|
-
log2("warn", `[${prepared.session.id}] tag echo: responses response stream contains <acp tag`);
|
|
52685
|
-
}
|
|
51682
|
+
const parsedReq = JSON.parse(typeof body === "string" ? body : body.toString("utf8"));
|
|
51683
|
+
const reqHeaders = buildForwardHeaders(headers);
|
|
51684
|
+
const textProtocol = prepared.protocol === "responses" && !!prepared.responsesTextProtocol;
|
|
51685
|
+
const systemPrompt = textProtocol ? buildCompressHybridSystemPrompt() : buildCompressSystemPrompt();
|
|
51686
|
+
const adapter = pickAdapter(prepared.protocol, parsedReq, textProtocol, prepared.responsesProjection, prepared.anthropicSystem);
|
|
51687
|
+
const abortCtrl = new AbortController();
|
|
51688
|
+
req.on("close", () => {
|
|
51689
|
+
if (!res.writableEnded) abortCtrl.abort();
|
|
51690
|
+
});
|
|
51691
|
+
const loop = runCompressLoop(
|
|
51692
|
+
streamToRead,
|
|
51693
|
+
{ core, config, messages: prepared.processedMessages.length > 0 ? prepared.processedMessages : prepared.originalMessages, session: prepared.session, log: ctx.log, proxyUrl, textProtocol, debug: opts.debug, nudge: prepared.nudge },
|
|
51694
|
+
parsedReq,
|
|
51695
|
+
{ url: upstreamUrl, headers: reqHeaders },
|
|
51696
|
+
adapter,
|
|
51697
|
+
systemPrompt,
|
|
51698
|
+
abortCtrl.signal
|
|
51699
|
+
);
|
|
51700
|
+
for await (const chunk of loop) {
|
|
51701
|
+
{
|
|
51702
|
+
const s3 = chunk.toString("utf8");
|
|
51703
|
+
if (s3.includes("<acp ") || s3.includes("</acp")) {
|
|
51704
|
+
log2("warn", `[${prepared.session.id}] tag echo: ${prepared.protocol} response stream contains <acp tag`);
|
|
52686
51705
|
}
|
|
52687
|
-
if (!res.write(chunk)) await new Promise((r) => res.once("drain", () => r()));
|
|
52688
51706
|
}
|
|
52689
|
-
|
|
52690
|
-
|
|
52691
|
-
|
|
52692
|
-
|
|
52693
|
-
|
|
52694
|
-
|
|
52695
|
-
}
|
|
52696
|
-
reqHeaders["content-type"] = "application/json";
|
|
52697
|
-
const loop = compressLoopAnthropicStream(
|
|
52698
|
-
streamToRead,
|
|
52699
|
-
{ core, config, messages: prepared.originalMessages, session: prepared.session, log: ctx.log, proxyUrl },
|
|
52700
|
-
parsedReq,
|
|
52701
|
-
{ url: upstreamUrl, headers: reqHeaders }
|
|
52702
|
-
);
|
|
52703
|
-
for await (const chunk of loop) {
|
|
52704
|
-
{
|
|
52705
|
-
const s3 = chunk.toString("utf8");
|
|
52706
|
-
if (s3.includes("<acp ") || s3.includes("</acp")) {
|
|
52707
|
-
log2("warn", `[${prepared.session.id}] tag echo: anthropic response stream contains <acp tag`);
|
|
52708
|
-
}
|
|
52709
|
-
}
|
|
52710
|
-
if (!res.write(chunk)) await new Promise((r) => res.once("drain", () => r()));
|
|
51707
|
+
res.write(chunk);
|
|
51708
|
+
if (res.writableNeedDrain) {
|
|
51709
|
+
await Promise.race([
|
|
51710
|
+
new Promise((r) => res.once("drain", () => r())),
|
|
51711
|
+
new Promise((r) => res.once("close", () => r()))
|
|
51712
|
+
]);
|
|
52711
51713
|
}
|
|
51714
|
+
if (res.destroyed || res.writableEnded) break;
|
|
52712
51715
|
}
|
|
52713
51716
|
res.end();
|
|
52714
51717
|
} catch (e) {
|
|
@@ -52725,12 +51728,7 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
|
|
|
52725
51728
|
let json = JSON.parse(text);
|
|
52726
51729
|
if (prepared.protocol === "responses" && prepared.responsesTextProtocol) {
|
|
52727
51730
|
const requestBody = JSON.parse(typeof body === "string" ? body : body.toString("utf8"));
|
|
52728
|
-
const requestHeaders =
|
|
52729
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
52730
|
-
if (key.toLowerCase() === "content-length" || key.toLowerCase() === "host") continue;
|
|
52731
|
-
requestHeaders[key] = value;
|
|
52732
|
-
}
|
|
52733
|
-
requestHeaders["content-type"] = "application/json";
|
|
51731
|
+
const requestHeaders = buildForwardHeaders(headers);
|
|
52734
51732
|
json = await compressLoopResponsesJson(
|
|
52735
51733
|
json,
|
|
52736
51734
|
{ core, config, messages: prepared.originalMessages, session: prepared.session, log: ctx.log, proxyUrl, textProtocol: true },
|
|
@@ -52791,6 +51789,9 @@ async function dumpStreamToFile(stream2, dir, name) {
|
|
|
52791
51789
|
try {
|
|
52792
51790
|
mkdirSync6(dir, { recursive: true });
|
|
52793
51791
|
const ws2 = createWriteStream2(join4(dir, name));
|
|
51792
|
+
ws2.on("error", (e) => {
|
|
51793
|
+
log("debug", `[dump] write stream error: ${e.message ?? e}`);
|
|
51794
|
+
});
|
|
52794
51795
|
const reader = stream2.getReader();
|
|
52795
51796
|
try {
|
|
52796
51797
|
for (; ; ) {
|
|
@@ -52867,6 +51868,7 @@ function readBody(req) {
|
|
|
52867
51868
|
size += c.length;
|
|
52868
51869
|
if (size > MAX_REQUEST_BYTES) {
|
|
52869
51870
|
aborted = true;
|
|
51871
|
+
req.destroy();
|
|
52870
51872
|
reject(new BodyTooLargeError(MAX_REQUEST_BYTES));
|
|
52871
51873
|
return;
|
|
52872
51874
|
}
|
|
@@ -56056,13 +55058,33 @@ async function installViaTarball(version2, tarballUrl, installDir) {
|
|
|
56056
55058
|
} catch {
|
|
56057
55059
|
return { ok: false, error: `install dir not writable: ${installDir}` };
|
|
56058
55060
|
}
|
|
55061
|
+
const MAX_TARBALL_BYTES = 100 * 1024 * 1024;
|
|
56059
55062
|
let tgzBuffer;
|
|
56060
55063
|
try {
|
|
56061
55064
|
const tgzRes = await fetch(tarballUrl, { signal: AbortSignal.timeout(6e4) });
|
|
56062
55065
|
if (!tgzRes.ok) {
|
|
56063
55066
|
return { ok: false, error: `tarball download failed: HTTP ${tgzRes.status} ${tgzRes.statusText}` };
|
|
56064
55067
|
}
|
|
56065
|
-
|
|
55068
|
+
if (!tgzRes.body) {
|
|
55069
|
+
return { ok: false, error: "tarball download failed: empty response body" };
|
|
55070
|
+
}
|
|
55071
|
+
const reader = tgzRes.body.getReader();
|
|
55072
|
+
const chunks = [];
|
|
55073
|
+
let total = 0;
|
|
55074
|
+
try {
|
|
55075
|
+
for (; ; ) {
|
|
55076
|
+
const { done, value } = await reader.read();
|
|
55077
|
+
if (done) break;
|
|
55078
|
+
total += value.byteLength;
|
|
55079
|
+
if (total > MAX_TARBALL_BYTES) {
|
|
55080
|
+
return { ok: false, error: `tarball exceeds ${MAX_TARBALL_BYTES} byte cap` };
|
|
55081
|
+
}
|
|
55082
|
+
chunks.push(value);
|
|
55083
|
+
}
|
|
55084
|
+
} finally {
|
|
55085
|
+
reader.releaseLock();
|
|
55086
|
+
}
|
|
55087
|
+
tgzBuffer = Buffer.concat(chunks);
|
|
56066
55088
|
} catch (e) {
|
|
56067
55089
|
return { ok: false, error: `tarball download failed: ${String(e)}` };
|
|
56068
55090
|
}
|
|
@@ -56115,14 +55137,556 @@ function startAutoUpdate(opts) {
|
|
|
56115
55137
|
timer.unref?.();
|
|
56116
55138
|
}
|
|
56117
55139
|
|
|
55140
|
+
// src/launcher.ts
|
|
55141
|
+
import fs5 from "fs";
|
|
55142
|
+
import net2 from "net";
|
|
55143
|
+
import os2 from "os";
|
|
55144
|
+
import path7 from "path";
|
|
55145
|
+
import { spawn } from "child_process";
|
|
55146
|
+
var LAUNCHER_DEFAULT_HOST = "127.0.0.1";
|
|
55147
|
+
var LAUNCHER_DEFAULT_PORT = 8787;
|
|
55148
|
+
var LAUNCH_CLIENTS = ["pi", "codex", "claude", "pi-test"];
|
|
55149
|
+
var HEALTH_PATH = "/__bili/health";
|
|
55150
|
+
var HEALTH_POLL_INTERVAL_MS = 200;
|
|
55151
|
+
var SPAWN_WAIT_MS = 2e4;
|
|
55152
|
+
var PROBE_TIMEOUT_MS = 1500;
|
|
55153
|
+
var DEFAULT_MITM_DOMAIN_SET = new Set(DEFAULT_MITM_DOMAINS.map((d) => d.toLowerCase()));
|
|
55154
|
+
function coveredByDefaultMitm(host) {
|
|
55155
|
+
const h = host.toLowerCase();
|
|
55156
|
+
return DEFAULT_MITM_DOMAINS.some((d) => h === d || h.endsWith("." + d));
|
|
55157
|
+
}
|
|
55158
|
+
function domainsNeedFreshProxy(domains) {
|
|
55159
|
+
if (!domains || domains.length === 0) return false;
|
|
55160
|
+
return domains.some((d) => !coveredByDefaultMitm(d));
|
|
55161
|
+
}
|
|
55162
|
+
function isLaunchClient(value) {
|
|
55163
|
+
return LAUNCH_CLIENTS.includes(value);
|
|
55164
|
+
}
|
|
55165
|
+
function baseClientName(client) {
|
|
55166
|
+
return client === "pi-test" ? "pi" : client;
|
|
55167
|
+
}
|
|
55168
|
+
function piTestArgs(client, clientArgs) {
|
|
55169
|
+
return client === "pi-test" ? ["--no-extensions", ...clientArgs] : clientArgs;
|
|
55170
|
+
}
|
|
55171
|
+
function proxyOrigin(host, port) {
|
|
55172
|
+
return `http://${host}:${port}`;
|
|
55173
|
+
}
|
|
55174
|
+
function healthUrl(origin) {
|
|
55175
|
+
return origin + HEALTH_PATH;
|
|
55176
|
+
}
|
|
55177
|
+
function wrapUpstream(origin, upstream) {
|
|
55178
|
+
const u2 = upstream.replace(/\/+$/, "");
|
|
55179
|
+
const prefix = origin + "/bili/";
|
|
55180
|
+
if (u2.startsWith(prefix)) return u2;
|
|
55181
|
+
return prefix + u2;
|
|
55182
|
+
}
|
|
55183
|
+
function unwrapUpstream(url) {
|
|
55184
|
+
const idx = url.indexOf("/bili/");
|
|
55185
|
+
return idx >= 0 ? url.slice(idx + "/bili/".length) : url;
|
|
55186
|
+
}
|
|
55187
|
+
function nonEmpty2(s3) {
|
|
55188
|
+
return typeof s3 === "string" && s3.trim().length > 0;
|
|
55189
|
+
}
|
|
55190
|
+
function resolveCaCertPath(env) {
|
|
55191
|
+
const base = env.XDG_DATA_HOME || path7.join(os2.homedir(), ".local/share");
|
|
55192
|
+
return path7.join(base, "billion-context", "ca", "root-ca.pem");
|
|
55193
|
+
}
|
|
55194
|
+
function resolvePiHome(env) {
|
|
55195
|
+
const h = os2.homedir();
|
|
55196
|
+
return nonEmpty2(env.PI_CODING_AGENT_DIR) ? env.PI_CODING_AGENT_DIR : nonEmpty2(env.PI_HOME) ? env.PI_HOME : path7.join(h, ".pi", "agent");
|
|
55197
|
+
}
|
|
55198
|
+
function discoverRoutes(client, config) {
|
|
55199
|
+
const httpsDomains = [];
|
|
55200
|
+
const httpRewrites = [];
|
|
55201
|
+
const httpsRewrites = [];
|
|
55202
|
+
const httpsSeen = /* @__PURE__ */ new Set();
|
|
55203
|
+
const rewriteKeys = /* @__PURE__ */ new Set();
|
|
55204
|
+
const httpsRewriteKeys = /* @__PURE__ */ new Set();
|
|
55205
|
+
const classify = (raw, key) => {
|
|
55206
|
+
if (!nonEmpty2(raw)) return;
|
|
55207
|
+
let url;
|
|
55208
|
+
try {
|
|
55209
|
+
url = new URL(unwrapUpstream(raw));
|
|
55210
|
+
} catch {
|
|
55211
|
+
return;
|
|
55212
|
+
}
|
|
55213
|
+
if (url.protocol === "https:") {
|
|
55214
|
+
const host = url.hostname;
|
|
55215
|
+
if (host && !httpsSeen.has(host.toLowerCase())) {
|
|
55216
|
+
httpsSeen.add(host.toLowerCase());
|
|
55217
|
+
httpsDomains.push(host);
|
|
55218
|
+
}
|
|
55219
|
+
if (raw !== unwrapUpstream(raw) && !httpsRewriteKeys.has(key)) {
|
|
55220
|
+
httpsRewriteKeys.add(key);
|
|
55221
|
+
httpsRewrites.push({ key, realUpstream: unwrapUpstream(raw) });
|
|
55222
|
+
}
|
|
55223
|
+
} else if (url.protocol === "http:") {
|
|
55224
|
+
if (!rewriteKeys.has(key)) {
|
|
55225
|
+
rewriteKeys.add(key);
|
|
55226
|
+
httpRewrites.push({ key, realUpstream: unwrapUpstream(raw) });
|
|
55227
|
+
}
|
|
55228
|
+
}
|
|
55229
|
+
};
|
|
55230
|
+
if (client === "claude") {
|
|
55231
|
+
const u2 = config.claude?.anthropicBaseUrl;
|
|
55232
|
+
if (nonEmpty2(u2)) classify(u2, "ANTHROPIC_BASE_URL");
|
|
55233
|
+
else classify("https://api.anthropic.com", "ANTHROPIC_BASE_URL");
|
|
55234
|
+
} else if (client === "pi") {
|
|
55235
|
+
for (const [name, prov] of Object.entries(config.pi?.providers ?? {})) {
|
|
55236
|
+
classify(prov.baseUrl, name);
|
|
55237
|
+
}
|
|
55238
|
+
} else {
|
|
55239
|
+
for (const [name, prov] of Object.entries(config.codex?.providers ?? {})) {
|
|
55240
|
+
classify(prov.baseUrl, `model_providers.${name}.base_url`);
|
|
55241
|
+
}
|
|
55242
|
+
classify(config.codex?.openaiBaseUrl, "openai_base_url");
|
|
55243
|
+
}
|
|
55244
|
+
return { httpsDomains, httpRewrites, httpsRewrites };
|
|
55245
|
+
}
|
|
55246
|
+
function discoverDomains(client, config) {
|
|
55247
|
+
return discoverRoutes(client, config).httpsDomains;
|
|
55248
|
+
}
|
|
55249
|
+
function buildPiEnv(origin, caPath, baseEnv) {
|
|
55250
|
+
return { ...baseEnv, HTTPS_PROXY: origin, NODE_EXTRA_CA_CERTS: caPath };
|
|
55251
|
+
}
|
|
55252
|
+
function buildCodexEnv(origin, caPath, baseEnv) {
|
|
55253
|
+
return { ...baseEnv, HTTPS_PROXY: origin, SSL_CERT_FILE: caPath };
|
|
55254
|
+
}
|
|
55255
|
+
function buildCodexArgs(origin, httpRewrites, httpsRewrites, extra) {
|
|
55256
|
+
const args = [];
|
|
55257
|
+
for (const r of httpRewrites) {
|
|
55258
|
+
args.push("-c", `${r.key}=${wrapUpstream(origin, r.realUpstream)}`);
|
|
55259
|
+
}
|
|
55260
|
+
for (const r of httpsRewrites) {
|
|
55261
|
+
args.push("-c", `${r.key}=${r.realUpstream}`);
|
|
55262
|
+
}
|
|
55263
|
+
args.push(...extra);
|
|
55264
|
+
return args;
|
|
55265
|
+
}
|
|
55266
|
+
function buildClaudeEnv(origin, caPath, httpRewrites, httpsRewrites, baseEnv) {
|
|
55267
|
+
const env = { ...baseEnv, HTTPS_PROXY: origin, NODE_EXTRA_CA_CERTS: caPath };
|
|
55268
|
+
const r = httpRewrites.find((rw) => rw.key === "ANTHROPIC_BASE_URL");
|
|
55269
|
+
if (r) env.ANTHROPIC_BASE_URL = wrapUpstream(origin, r.realUpstream);
|
|
55270
|
+
const hr2 = httpsRewrites.find((rw) => rw.key === "ANTHROPIC_BASE_URL");
|
|
55271
|
+
if (hr2) env.ANTHROPIC_BASE_URL = hr2.realUpstream;
|
|
55272
|
+
return env;
|
|
55273
|
+
}
|
|
55274
|
+
function preparePiHttpRewrite(piHome, origin, httpRewrites, httpsRewrites) {
|
|
55275
|
+
if (httpRewrites.length === 0 && httpsRewrites.length === 0) return void 0;
|
|
55276
|
+
const modelsPath = path7.join(piHome, "models.json");
|
|
55277
|
+
let txt;
|
|
55278
|
+
try {
|
|
55279
|
+
txt = fs5.readFileSync(modelsPath, "utf8");
|
|
55280
|
+
} catch {
|
|
55281
|
+
return void 0;
|
|
55282
|
+
}
|
|
55283
|
+
let parsed;
|
|
55284
|
+
try {
|
|
55285
|
+
parsed = JSON.parse(txt);
|
|
55286
|
+
} catch {
|
|
55287
|
+
return void 0;
|
|
55288
|
+
}
|
|
55289
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
|
|
55290
|
+
const root = parsed;
|
|
55291
|
+
const providersVal = root.providers;
|
|
55292
|
+
if (providersVal && typeof providersVal === "object" && !Array.isArray(providersVal)) {
|
|
55293
|
+
const providers = providersVal;
|
|
55294
|
+
for (const r of httpRewrites) {
|
|
55295
|
+
const prov = providers[r.key];
|
|
55296
|
+
if (prov && typeof prov === "object" && !Array.isArray(prov)) {
|
|
55297
|
+
const p2 = prov;
|
|
55298
|
+
const existing = typeof p2.baseUrl === "string" ? p2.baseUrl : r.realUpstream;
|
|
55299
|
+
p2.baseUrl = wrapUpstream(origin, unwrapUpstream(existing));
|
|
55300
|
+
}
|
|
55301
|
+
}
|
|
55302
|
+
for (const r of httpsRewrites) {
|
|
55303
|
+
const prov = providers[r.key];
|
|
55304
|
+
if (prov && typeof prov === "object" && !Array.isArray(prov)) {
|
|
55305
|
+
const p2 = prov;
|
|
55306
|
+
p2.baseUrl = r.realUpstream;
|
|
55307
|
+
}
|
|
55308
|
+
}
|
|
55309
|
+
}
|
|
55310
|
+
const tmp = fs5.mkdtempSync(path7.join(os2.tmpdir(), "bili-pi-"));
|
|
55311
|
+
try {
|
|
55312
|
+
for (const entry of fs5.readdirSync(piHome)) {
|
|
55313
|
+
if (entry === "models.json") continue;
|
|
55314
|
+
try {
|
|
55315
|
+
fs5.symlinkSync(path7.join(piHome, entry), path7.join(tmp, entry));
|
|
55316
|
+
} catch {
|
|
55317
|
+
}
|
|
55318
|
+
}
|
|
55319
|
+
} catch {
|
|
55320
|
+
}
|
|
55321
|
+
fs5.writeFileSync(path7.join(tmp, "models.json"), JSON.stringify(root));
|
|
55322
|
+
return tmp;
|
|
55323
|
+
}
|
|
55324
|
+
function dedupeInOrder(list) {
|
|
55325
|
+
const seen = /* @__PURE__ */ new Set();
|
|
55326
|
+
const out = [];
|
|
55327
|
+
for (const d of list) {
|
|
55328
|
+
if (d && !seen.has(d)) {
|
|
55329
|
+
seen.add(d);
|
|
55330
|
+
out.push(d);
|
|
55331
|
+
}
|
|
55332
|
+
}
|
|
55333
|
+
return out;
|
|
55334
|
+
}
|
|
55335
|
+
async function defaultFetch(url) {
|
|
55336
|
+
const ctrl = new AbortController();
|
|
55337
|
+
const t = setTimeout(() => ctrl.abort(), PROBE_TIMEOUT_MS);
|
|
55338
|
+
try {
|
|
55339
|
+
const res = await fetch(url, { signal: ctrl.signal });
|
|
55340
|
+
return { ok: res.ok };
|
|
55341
|
+
} catch {
|
|
55342
|
+
return { ok: false };
|
|
55343
|
+
} finally {
|
|
55344
|
+
clearTimeout(t);
|
|
55345
|
+
}
|
|
55346
|
+
}
|
|
55347
|
+
async function probeHealth(origin, fetchImpl) {
|
|
55348
|
+
try {
|
|
55349
|
+
const { ok } = await fetchImpl(healthUrl(origin));
|
|
55350
|
+
return ok;
|
|
55351
|
+
} catch {
|
|
55352
|
+
return false;
|
|
55353
|
+
}
|
|
55354
|
+
}
|
|
55355
|
+
function findFreePort(preferred, host = LAUNCHER_DEFAULT_HOST) {
|
|
55356
|
+
const tryBind = (port) => new Promise((resolve) => {
|
|
55357
|
+
const srv = net2.createServer();
|
|
55358
|
+
srv.once("error", () => resolve(false));
|
|
55359
|
+
srv.once("listening", () => srv.close(() => resolve(true)));
|
|
55360
|
+
srv.listen(port, host);
|
|
55361
|
+
});
|
|
55362
|
+
return tryBind(preferred).then((free) => {
|
|
55363
|
+
if (free) return preferred;
|
|
55364
|
+
return new Promise((resolve, reject) => {
|
|
55365
|
+
const srv = net2.createServer();
|
|
55366
|
+
srv.once("error", reject);
|
|
55367
|
+
srv.listen(0, host, () => {
|
|
55368
|
+
const addr = srv.address();
|
|
55369
|
+
srv.close(() => {
|
|
55370
|
+
if (addr && typeof addr === "object") resolve(addr.port);
|
|
55371
|
+
else reject(new Error("could not allocate a free port"));
|
|
55372
|
+
});
|
|
55373
|
+
});
|
|
55374
|
+
});
|
|
55375
|
+
});
|
|
55376
|
+
}
|
|
55377
|
+
var INHERITED_PROXY_VARS = ["http_proxy", "https_proxy", "all_proxy", "HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY"];
|
|
55378
|
+
function stripInheritedProxy(env) {
|
|
55379
|
+
const cleaned = { ...env };
|
|
55380
|
+
for (const key of INHERITED_PROXY_VARS) delete cleaned[key];
|
|
55381
|
+
return cleaned;
|
|
55382
|
+
}
|
|
55383
|
+
function proxyStartArgs(opts) {
|
|
55384
|
+
const args = ["start", "--host", opts.host, "--port", String(opts.port)];
|
|
55385
|
+
if (opts.passthrough) args.push("--passthrough");
|
|
55386
|
+
if (opts.debug) args.push("--debug");
|
|
55387
|
+
return args;
|
|
55388
|
+
}
|
|
55389
|
+
async function ensureProxyRunning(opts, deps = {}) {
|
|
55390
|
+
const fetchImpl = deps.fetchImpl ?? defaultFetch;
|
|
55391
|
+
const spawnImpl = deps.spawnImpl ?? spawn;
|
|
55392
|
+
const now = deps.now ?? Date.now;
|
|
55393
|
+
const sleepImpl = deps.sleep ?? ((ms2) => new Promise((r) => setTimeout(r, ms2)));
|
|
55394
|
+
const preferredOrigin = proxyOrigin(opts.host, opts.port);
|
|
55395
|
+
if (!domainsNeedFreshProxy(opts.mitmDomains) && await probeHealth(preferredOrigin, fetchImpl)) {
|
|
55396
|
+
return { origin: preferredOrigin, port: opts.port, reused: true, child: null };
|
|
55397
|
+
}
|
|
55398
|
+
const port = await findFreePort(opts.port, opts.host);
|
|
55399
|
+
const spawnedOrigin = proxyOrigin(opts.host, port);
|
|
55400
|
+
const script = process.argv[1];
|
|
55401
|
+
if (!script) throw new Error("bili: cannot resolve launcher script path");
|
|
55402
|
+
const logPath2 = path7.join(os2.tmpdir(), `bili-proxy-${port}.log`);
|
|
55403
|
+
const logFd = fs5.openSync(logPath2, "a");
|
|
55404
|
+
let child;
|
|
55405
|
+
try {
|
|
55406
|
+
child = spawnImpl(
|
|
55407
|
+
process.execPath,
|
|
55408
|
+
[script, ...proxyStartArgs({ ...opts, port, debug: true })],
|
|
55409
|
+
{
|
|
55410
|
+
detached: true,
|
|
55411
|
+
stdio: ["ignore", logFd, logFd],
|
|
55412
|
+
env: {
|
|
55413
|
+
...stripInheritedProxy(process.env),
|
|
55414
|
+
...opts.mitmDomains && opts.mitmDomains.length ? { BILI_MITM_DOMAINS: opts.mitmDomains.join(",") } : {}
|
|
55415
|
+
}
|
|
55416
|
+
}
|
|
55417
|
+
);
|
|
55418
|
+
} finally {
|
|
55419
|
+
try {
|
|
55420
|
+
fs5.closeSync(logFd);
|
|
55421
|
+
} catch {
|
|
55422
|
+
}
|
|
55423
|
+
}
|
|
55424
|
+
try {
|
|
55425
|
+
child.unref?.();
|
|
55426
|
+
} catch {
|
|
55427
|
+
}
|
|
55428
|
+
const deadline = now() + SPAWN_WAIT_MS;
|
|
55429
|
+
while (now() < deadline) {
|
|
55430
|
+
await sleepImpl(HEALTH_POLL_INTERVAL_MS);
|
|
55431
|
+
if (await probeHealth(spawnedOrigin, fetchImpl)) {
|
|
55432
|
+
return { origin: spawnedOrigin, port, reused: false, child, logPath: logPath2 };
|
|
55433
|
+
}
|
|
55434
|
+
}
|
|
55435
|
+
throw new Error(`bili: proxy did not become healthy at ${spawnedOrigin} within ${SPAWN_WAIT_MS}ms`);
|
|
55436
|
+
}
|
|
55437
|
+
function stopProxy(handle2) {
|
|
55438
|
+
const child = handle2.child;
|
|
55439
|
+
if (!child || child.pid === void 0) return;
|
|
55440
|
+
if (process.platform !== "win32" && child.pid > 0) {
|
|
55441
|
+
try {
|
|
55442
|
+
process.kill(-child.pid);
|
|
55443
|
+
} catch {
|
|
55444
|
+
}
|
|
55445
|
+
}
|
|
55446
|
+
try {
|
|
55447
|
+
child.kill?.();
|
|
55448
|
+
} catch {
|
|
55449
|
+
}
|
|
55450
|
+
}
|
|
55451
|
+
function runClient(cmd, args, env, deps) {
|
|
55452
|
+
const spawnImpl = deps?.spawnImpl ?? spawn;
|
|
55453
|
+
return new Promise((resolve, reject) => {
|
|
55454
|
+
const child = spawnImpl(cmd, args, { stdio: "inherit", env });
|
|
55455
|
+
child.on?.("error", (...rest) => reject(rest[0]));
|
|
55456
|
+
child.on?.("exit", (...rest) => {
|
|
55457
|
+
const code = rest[0];
|
|
55458
|
+
const signal = rest[1];
|
|
55459
|
+
resolve(signal ? 130 : typeof code === "number" ? code : 0);
|
|
55460
|
+
});
|
|
55461
|
+
});
|
|
55462
|
+
}
|
|
55463
|
+
function isOnPath(name, env) {
|
|
55464
|
+
const p2 = env.PATH;
|
|
55465
|
+
if (!p2) return false;
|
|
55466
|
+
return p2.split(":").some((dir) => {
|
|
55467
|
+
if (!dir) return false;
|
|
55468
|
+
try {
|
|
55469
|
+
const f2 = path7.join(dir, name);
|
|
55470
|
+
return fs5.existsSync(f2) && fs5.statSync(f2).isFile();
|
|
55471
|
+
} catch {
|
|
55472
|
+
return false;
|
|
55473
|
+
}
|
|
55474
|
+
});
|
|
55475
|
+
}
|
|
55476
|
+
function resolveClientCommand(client, env) {
|
|
55477
|
+
if (client === "pi") {
|
|
55478
|
+
const piBin = env.PI_BIN?.trim();
|
|
55479
|
+
if (piBin) return { command: piBin, prefixArgs: [] };
|
|
55480
|
+
if (isOnPath("pi", env)) return { command: "pi", prefixArgs: [] };
|
|
55481
|
+
const cli = path7.join(
|
|
55482
|
+
os2.homedir(),
|
|
55483
|
+
".pi/agent/npm/node_modules/@earendil-works/pi-coding-agent/dist/cli.js"
|
|
55484
|
+
);
|
|
55485
|
+
return { command: process.execPath, prefixArgs: [cli] };
|
|
55486
|
+
}
|
|
55487
|
+
return { command: client, prefixArgs: [] };
|
|
55488
|
+
}
|
|
55489
|
+
function readJsonObject(filePath) {
|
|
55490
|
+
try {
|
|
55491
|
+
const txt = fs5.readFileSync(filePath, "utf8");
|
|
55492
|
+
const parsed = JSON.parse(txt);
|
|
55493
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
55494
|
+
} catch {
|
|
55495
|
+
return null;
|
|
55496
|
+
}
|
|
55497
|
+
}
|
|
55498
|
+
function readClaudeSettings(homeDir, cwd) {
|
|
55499
|
+
const files = [
|
|
55500
|
+
path7.join(homeDir, ".claude", "settings.json"),
|
|
55501
|
+
path7.join(cwd, ".claude", "settings.json")
|
|
55502
|
+
];
|
|
55503
|
+
let anthropicBaseUrl;
|
|
55504
|
+
for (const f2 of files) {
|
|
55505
|
+
const obj = readJsonObject(f2);
|
|
55506
|
+
const env = obj?.env;
|
|
55507
|
+
if (env && typeof env === "object" && !Array.isArray(env)) {
|
|
55508
|
+
const v2 = env.ANTHROPIC_BASE_URL;
|
|
55509
|
+
if (nonEmpty2(v2)) anthropicBaseUrl = v2;
|
|
55510
|
+
}
|
|
55511
|
+
}
|
|
55512
|
+
return anthropicBaseUrl ? { anthropicBaseUrl } : {};
|
|
55513
|
+
}
|
|
55514
|
+
function parseCodexToml(text) {
|
|
55515
|
+
const result = { providers: {} };
|
|
55516
|
+
let table = "";
|
|
55517
|
+
let curProvider = null;
|
|
55518
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
55519
|
+
const line = rawLine.trim();
|
|
55520
|
+
if (!line || line.startsWith("#")) continue;
|
|
55521
|
+
const tableMatch = /^\[([^\]]+)\]$/.exec(line);
|
|
55522
|
+
if (tableMatch) {
|
|
55523
|
+
table = tableMatch[1].trim();
|
|
55524
|
+
curProvider = table.startsWith("model_providers.") ? table.slice("model_providers.".length).trim() : null;
|
|
55525
|
+
if (curProvider && !result.providers[curProvider]) {
|
|
55526
|
+
result.providers[curProvider] = {};
|
|
55527
|
+
}
|
|
55528
|
+
continue;
|
|
55529
|
+
}
|
|
55530
|
+
const m2 = /^([A-Za-z0-9_.-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/.exec(line);
|
|
55531
|
+
if (!m2) continue;
|
|
55532
|
+
const key = m2[1];
|
|
55533
|
+
const val = m2[2] !== void 0 ? m2[2] : m2[3];
|
|
55534
|
+
if (table === "") {
|
|
55535
|
+
if (key === "model_provider") result.modelProvider = val;
|
|
55536
|
+
else if (key === "openai_base_url") result.openaiBaseUrl = val;
|
|
55537
|
+
} else if (curProvider && key === "base_url") {
|
|
55538
|
+
result.providers[curProvider].baseUrl = val;
|
|
55539
|
+
}
|
|
55540
|
+
}
|
|
55541
|
+
return result;
|
|
55542
|
+
}
|
|
55543
|
+
function readCodexConfig(codexHome) {
|
|
55544
|
+
const cfgPath = path7.join(codexHome, "config.toml");
|
|
55545
|
+
let text;
|
|
55546
|
+
try {
|
|
55547
|
+
text = fs5.readFileSync(cfgPath, "utf8");
|
|
55548
|
+
} catch {
|
|
55549
|
+
return { providers: {} };
|
|
55550
|
+
}
|
|
55551
|
+
return parseCodexToml(text);
|
|
55552
|
+
}
|
|
55553
|
+
function readPiConfig(piHome) {
|
|
55554
|
+
const cfgPath = path7.join(piHome, "models.json");
|
|
55555
|
+
const obj = readJsonObject(cfgPath);
|
|
55556
|
+
const providers = {};
|
|
55557
|
+
const rawProviders = obj?.providers;
|
|
55558
|
+
if (rawProviders && typeof rawProviders === "object" && !Array.isArray(rawProviders)) {
|
|
55559
|
+
for (const [name, val] of Object.entries(rawProviders)) {
|
|
55560
|
+
if (val && typeof val === "object" && !Array.isArray(val)) {
|
|
55561
|
+
const baseUrl = val.baseUrl;
|
|
55562
|
+
providers[name] = typeof baseUrl === "string" ? { baseUrl } : {};
|
|
55563
|
+
}
|
|
55564
|
+
}
|
|
55565
|
+
}
|
|
55566
|
+
return { providers };
|
|
55567
|
+
}
|
|
55568
|
+
function loadClientConfig(env, cwd) {
|
|
55569
|
+
const home = os2.homedir();
|
|
55570
|
+
const config = {};
|
|
55571
|
+
config.claude = readClaudeSettings(home, cwd);
|
|
55572
|
+
const codexHome = nonEmpty2(env.CODEX_HOME) ? env.CODEX_HOME : path7.join(home, ".codex");
|
|
55573
|
+
config.codex = readCodexConfig(codexHome);
|
|
55574
|
+
config.pi = readPiConfig(resolvePiHome(env));
|
|
55575
|
+
return config;
|
|
55576
|
+
}
|
|
55577
|
+
function parsePort(raw) {
|
|
55578
|
+
const port = raw && raw.trim() ? parseInt(raw, 10) : LAUNCHER_DEFAULT_PORT;
|
|
55579
|
+
if (!Number.isFinite(port) || port <= 0 || port > 65535) {
|
|
55580
|
+
console.error(`bili: invalid --port "${raw}"`);
|
|
55581
|
+
process.exit(2);
|
|
55582
|
+
}
|
|
55583
|
+
return port;
|
|
55584
|
+
}
|
|
55585
|
+
async function runLaunch(params, deps = {}) {
|
|
55586
|
+
const host = params.overrides.ACP_HOST?.trim() || LAUNCHER_DEFAULT_HOST;
|
|
55587
|
+
const port = parsePort(params.overrides.ACP_PORT ?? process.env.ACP_PORT);
|
|
55588
|
+
const passthrough = params.overrides.ACP_PASSTHROUGH === "1";
|
|
55589
|
+
const debug = params.overrides.ACP_DEBUG === "1";
|
|
55590
|
+
const config = loadClientConfig(process.env, process.cwd());
|
|
55591
|
+
const base = baseClientName(params.client);
|
|
55592
|
+
const routes = discoverRoutes(base, config);
|
|
55593
|
+
const domains = dedupeInOrder([...routes.httpsDomains, ...params.mitmDomains ?? []]);
|
|
55594
|
+
const handle2 = await ensureProxyRunning({ host, port, passthrough, debug, mitmDomains: domains }, deps);
|
|
55595
|
+
console.error(
|
|
55596
|
+
`bili: ${handle2.reused ? "reusing existing" : "started"} proxy at ${handle2.origin} (MITM domains: ${domains.length ? domains.join(", ") : "defaults"})` + (routes.httpRewrites.length > 0 ? ` (HTTP /bili/ rewrites: ${routes.httpRewrites.length})` : "") + (routes.httpsRewrites.length > 0 ? ` (HTTPS cert rewrites: ${routes.httpsRewrites.length})` : "") + (params.client === "pi-test" ? " (no extensions)" : "")
|
|
55597
|
+
);
|
|
55598
|
+
if (handle2.logPath) {
|
|
55599
|
+
console.error(`bili: proxy log: ${handle2.logPath}`);
|
|
55600
|
+
}
|
|
55601
|
+
const ca = resolveCaCertPath(process.env);
|
|
55602
|
+
let env;
|
|
55603
|
+
let clientArgs = params.clientArgs;
|
|
55604
|
+
let piTmpHome;
|
|
55605
|
+
if (base === "pi") {
|
|
55606
|
+
env = buildPiEnv(handle2.origin, ca, process.env);
|
|
55607
|
+
piTmpHome = preparePiHttpRewrite(resolvePiHome(process.env), handle2.origin, routes.httpRewrites, routes.httpsRewrites);
|
|
55608
|
+
if (piTmpHome) env.PI_CODING_AGENT_DIR = piTmpHome;
|
|
55609
|
+
} else if (base === "codex") {
|
|
55610
|
+
env = buildCodexEnv(handle2.origin, ca, process.env);
|
|
55611
|
+
clientArgs = buildCodexArgs(handle2.origin, routes.httpRewrites, routes.httpsRewrites, params.clientArgs);
|
|
55612
|
+
} else {
|
|
55613
|
+
env = buildClaudeEnv(handle2.origin, ca, routes.httpRewrites, routes.httpsRewrites, process.env);
|
|
55614
|
+
}
|
|
55615
|
+
const { command, prefixArgs } = resolveClientCommand(base, process.env);
|
|
55616
|
+
const effectiveClientArgs = piTestArgs(params.client, clientArgs);
|
|
55617
|
+
let code = 0;
|
|
55618
|
+
try {
|
|
55619
|
+
code = await runClient(command, [...prefixArgs, ...effectiveClientArgs], env, {
|
|
55620
|
+
spawnImpl: deps.spawnImpl
|
|
55621
|
+
});
|
|
55622
|
+
} catch (err2) {
|
|
55623
|
+
console.error(`bili: failed to launch ${params.client}: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
55624
|
+
code = 1;
|
|
55625
|
+
} finally {
|
|
55626
|
+
if (!handle2.reused) stopProxy(handle2);
|
|
55627
|
+
if (piTmpHome) {
|
|
55628
|
+
try {
|
|
55629
|
+
fs5.rmSync(piTmpHome, { recursive: true, force: true });
|
|
55630
|
+
} catch {
|
|
55631
|
+
}
|
|
55632
|
+
}
|
|
55633
|
+
}
|
|
55634
|
+
process.exit(code ?? 0);
|
|
55635
|
+
}
|
|
55636
|
+
async function runTestPi(params, deps = {}) {
|
|
55637
|
+
const host = params.overrides.ACP_HOST?.trim() || LAUNCHER_DEFAULT_HOST;
|
|
55638
|
+
const port = parsePort(params.overrides.ACP_PORT ?? process.env.ACP_PORT);
|
|
55639
|
+
const passthrough = params.overrides.ACP_PASSTHROUGH === "1";
|
|
55640
|
+
const debug = params.overrides.ACP_DEBUG === "1";
|
|
55641
|
+
const config = loadClientConfig(process.env, process.cwd());
|
|
55642
|
+
const domains = dedupeInOrder([
|
|
55643
|
+
...discoverDomains("pi", config),
|
|
55644
|
+
...params.mitmDomains ?? []
|
|
55645
|
+
]);
|
|
55646
|
+
const handle2 = await ensureProxyRunning({ host, port, passthrough, debug, mitmDomains: domains }, deps);
|
|
55647
|
+
console.error(
|
|
55648
|
+
`bili: ${handle2.reused ? "reusing existing" : "started"} proxy at ${handle2.origin} (MITM domains: ${domains.length ? domains.join(", ") : "defaults"})`
|
|
55649
|
+
);
|
|
55650
|
+
if (handle2.logPath) {
|
|
55651
|
+
console.error(`bili: proxy log: ${handle2.logPath}`);
|
|
55652
|
+
}
|
|
55653
|
+
const ca = resolveCaCertPath(process.env);
|
|
55654
|
+
const env = buildPiEnv(handle2.origin, ca, process.env);
|
|
55655
|
+
const sessionDir = path7.join(os2.tmpdir(), `bili-pi-test-${Date.now()}`);
|
|
55656
|
+
fs5.mkdirSync(sessionDir, { recursive: true });
|
|
55657
|
+
const args = [
|
|
55658
|
+
"-p",
|
|
55659
|
+
"--no-session",
|
|
55660
|
+
"--no-extensions",
|
|
55661
|
+
"--no-tools",
|
|
55662
|
+
"--no-context-files",
|
|
55663
|
+
"--session-dir",
|
|
55664
|
+
sessionDir,
|
|
55665
|
+
"--mode",
|
|
55666
|
+
"text",
|
|
55667
|
+
"Reply with exactly: OK"
|
|
55668
|
+
];
|
|
55669
|
+
const { command, prefixArgs } = resolveClientCommand("pi", process.env);
|
|
55670
|
+
let code = 0;
|
|
55671
|
+
try {
|
|
55672
|
+
code = await runClient(command, [...prefixArgs, ...args], env, { spawnImpl: deps.spawnImpl });
|
|
55673
|
+
} catch (err2) {
|
|
55674
|
+
console.error(`bili: pi test failed: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
55675
|
+
code = 1;
|
|
55676
|
+
} finally {
|
|
55677
|
+
if (!handle2.reused) stopProxy(handle2);
|
|
55678
|
+
}
|
|
55679
|
+
process.exit(code ?? 0);
|
|
55680
|
+
}
|
|
55681
|
+
|
|
56118
55682
|
// src/cli.ts
|
|
56119
55683
|
import { readFileSync as readFileSync5 } from "fs";
|
|
56120
55684
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
56121
|
-
import
|
|
55685
|
+
import path8 from "path";
|
|
56122
55686
|
var VERSION = (() => {
|
|
56123
55687
|
try {
|
|
56124
55688
|
const here = fileURLToPath3(import.meta.url);
|
|
56125
|
-
const pkg =
|
|
55689
|
+
const pkg = path8.join(path8.dirname(here), "..", "package.json");
|
|
56126
55690
|
return JSON.parse(readFileSync5(pkg, "utf8")).version ?? "dev";
|
|
56127
55691
|
} catch {
|
|
56128
55692
|
return "dev";
|
|
@@ -56131,7 +55695,7 @@ var VERSION = (() => {
|
|
|
56131
55695
|
var PACKAGE_NAME = (() => {
|
|
56132
55696
|
try {
|
|
56133
55697
|
const here = fileURLToPath3(import.meta.url);
|
|
56134
|
-
const pkg =
|
|
55698
|
+
const pkg = path8.join(path8.dirname(here), "..", "package.json");
|
|
56135
55699
|
return JSON.parse(readFileSync5(pkg, "utf8")).name ?? "billion-context";
|
|
56136
55700
|
} catch {
|
|
56137
55701
|
return "billion-context";
|
|
@@ -56140,19 +55704,40 @@ var PACKAGE_NAME = (() => {
|
|
|
56140
55704
|
var HELP = `bili ${VERSION} \u2014 billion-context proxy
|
|
56141
55705
|
|
|
56142
55706
|
Usage:
|
|
56143
|
-
bili [start] [options]
|
|
56144
|
-
bili
|
|
56145
|
-
bili --
|
|
56146
|
-
bili --
|
|
55707
|
+
bili [start] [options] start the proxy (default: reads ${configFile()})
|
|
55708
|
+
bili pi [opts --] [args] start a proxy + launch pi against it (cert-MITM)
|
|
55709
|
+
bili pi-test [opts --] [args] like bili pi but injects --no-extensions (clean test)
|
|
55710
|
+
bili codex [opts --] [args] start a proxy + launch codex against it (cert-MITM)
|
|
55711
|
+
bili claude [opts --] [args] start a proxy + launch claude against it (cert-MITM)
|
|
55712
|
+
bili test pi non-polluting pi smoke test through the proxy
|
|
55713
|
+
bili update check for & install a newer version now
|
|
55714
|
+
bili --version print version
|
|
55715
|
+
bili --help show this help
|
|
55716
|
+
|
|
55717
|
+
Launcher (bili pi / bili codex / bili claude):
|
|
55718
|
+
Brings up a proxy on an independent port (reusing one already running on
|
|
55719
|
+
that port), then runs the client pointed at it via HTTPS_PROXY + the proxy's
|
|
55720
|
+
MITM CA \u2014 no config-file edits. Discovered HTTPS upstream domains are
|
|
55721
|
+
auto-whitelisted for MITM so the proxy TLS-terminates exactly the hosts the
|
|
55722
|
+
client uses; HTTP / localhost providers go direct. pi/claude trust the CA
|
|
55723
|
+
via NODE_EXTRA_CA_CERTS, codex via SSL_CERT_FILE. Proxy killed on client exit.
|
|
55724
|
+
bili pi # launch pi through the proxy
|
|
55725
|
+
bili pi -- print "hi" # args after the client are passed through
|
|
55726
|
+
bili pi-test # pi through the proxy with extensions off (proxy owns compression)
|
|
55727
|
+
bili codex # launch codex through the proxy
|
|
55728
|
+
bili claude # launch claude through the proxy
|
|
55729
|
+
bili test pi # quick end-to-end check of the pi path
|
|
55730
|
+
bili pi --mitm-domain api.foo.com # add a domain to the MITM whitelist
|
|
56147
55731
|
|
|
56148
55732
|
Options (override config file / env):
|
|
56149
|
-
--port <N>
|
|
56150
|
-
--host <ADDR>
|
|
56151
|
-
--
|
|
56152
|
-
--
|
|
56153
|
-
--
|
|
56154
|
-
--
|
|
56155
|
-
--no-
|
|
55733
|
+
--port <N> listen port (default 8787)
|
|
55734
|
+
--host <ADDR> listen host (default 127.0.0.1)
|
|
55735
|
+
--mitm-domain <domain> extra MITM domain (repeatable; launcher only)
|
|
55736
|
+
--config <FILE> path to config JSON (default: XDG location)
|
|
55737
|
+
--debug verbose logging
|
|
55738
|
+
--passthrough forward without compression
|
|
55739
|
+
--no-passthrough force compression on (overrides config)
|
|
55740
|
+
--no-auto-update disable background self-update this run
|
|
56156
55741
|
|
|
56157
55742
|
Config: ${configFile()}
|
|
56158
55743
|
Set port/host/debug/providers/compress/autoUpdate there. See README \xA7Configuration.
|
|
@@ -56164,8 +55749,16 @@ function parseArgs(argv) {
|
|
|
56164
55749
|
const overrides = {};
|
|
56165
55750
|
let command = "start";
|
|
56166
55751
|
const positional = [];
|
|
55752
|
+
let client;
|
|
55753
|
+
let clientArgs = [];
|
|
55754
|
+
const mitmDomains = [];
|
|
56167
55755
|
for (let i = 0; i < argv.length; i++) {
|
|
56168
55756
|
const a = argv[i];
|
|
55757
|
+
if (!client && positional.length === 0 && isLaunchClient(a)) {
|
|
55758
|
+
client = a;
|
|
55759
|
+
clientArgs = argv.slice(i + 1);
|
|
55760
|
+
break;
|
|
55761
|
+
}
|
|
56169
55762
|
switch (a) {
|
|
56170
55763
|
case "--help":
|
|
56171
55764
|
case "-h":
|
|
@@ -56187,6 +55780,15 @@ function parseArgs(argv) {
|
|
|
56187
55780
|
case "--no-passthrough":
|
|
56188
55781
|
overrides.ACP_PASSTHROUGH = "0";
|
|
56189
55782
|
break;
|
|
55783
|
+
case "--mitm-domain": {
|
|
55784
|
+
const val = argv[++i];
|
|
55785
|
+
if (val === void 0) {
|
|
55786
|
+
console.error(`bili: ${a} requires a value`);
|
|
55787
|
+
process.exit(2);
|
|
55788
|
+
}
|
|
55789
|
+
mitmDomains.push(val);
|
|
55790
|
+
break;
|
|
55791
|
+
}
|
|
56190
55792
|
case "--port":
|
|
56191
55793
|
case "--host":
|
|
56192
55794
|
case "--config": {
|
|
@@ -56214,21 +55816,32 @@ function parseArgs(argv) {
|
|
|
56214
55816
|
positional.push(a);
|
|
56215
55817
|
}
|
|
56216
55818
|
}
|
|
56217
|
-
if (
|
|
55819
|
+
if (client) {
|
|
55820
|
+
command = "launch";
|
|
55821
|
+
} else if (positional.length > 0) {
|
|
56218
55822
|
const cmd = positional[0];
|
|
56219
55823
|
if (cmd === "start") {
|
|
56220
55824
|
command = command === "help" || command === "version" ? command : "start";
|
|
56221
55825
|
} else if (cmd === "update") {
|
|
56222
55826
|
command = "update";
|
|
55827
|
+
} else if (cmd === "test") {
|
|
55828
|
+
const target = positional[1];
|
|
55829
|
+
if (target && isLaunchClient(target)) {
|
|
55830
|
+
command = "test";
|
|
55831
|
+
client = target;
|
|
55832
|
+
} else {
|
|
55833
|
+
console.error(`bili test: unknown client "${target ?? ""}" (try "bili test pi")`);
|
|
55834
|
+
process.exit(2);
|
|
55835
|
+
}
|
|
56223
55836
|
} else {
|
|
56224
55837
|
console.error(`bili: unknown command "${cmd}" (try "bili --help")`);
|
|
56225
55838
|
process.exit(2);
|
|
56226
55839
|
}
|
|
56227
55840
|
}
|
|
56228
|
-
return { command, overrides };
|
|
55841
|
+
return { command, client, clientArgs, mitmDomains, overrides };
|
|
56229
55842
|
}
|
|
56230
55843
|
async function main() {
|
|
56231
|
-
const { command, overrides } = parseArgs(process.argv.slice(2));
|
|
55844
|
+
const { command, client, clientArgs, mitmDomains, overrides } = parseArgs(process.argv.slice(2));
|
|
56232
55845
|
if (command === "help") {
|
|
56233
55846
|
process.stdout.write(HELP);
|
|
56234
55847
|
return;
|
|
@@ -56241,6 +55854,18 @@ async function main() {
|
|
|
56241
55854
|
await checkForUpdate({ packageName: PACKAGE_NAME, currentVersion: VERSION, autoUpdate: true }, true);
|
|
56242
55855
|
return;
|
|
56243
55856
|
}
|
|
55857
|
+
if (command === "test") {
|
|
55858
|
+
if (client === "pi") {
|
|
55859
|
+
await runTestPi({ overrides, mitmDomains });
|
|
55860
|
+
return;
|
|
55861
|
+
}
|
|
55862
|
+
console.error("bili test: only 'pi' supported for now");
|
|
55863
|
+
process.exit(2);
|
|
55864
|
+
}
|
|
55865
|
+
if (command === "launch") {
|
|
55866
|
+
await runLaunch({ client, clientArgs, mitmDomains, overrides });
|
|
55867
|
+
return;
|
|
55868
|
+
}
|
|
56244
55869
|
for (const [k2, v2] of Object.entries(overrides)) {
|
|
56245
55870
|
if (v2 !== void 0) process.env[k2] = v2;
|
|
56246
55871
|
}
|