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/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 net2 = __require("net");
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 path8 = url.path != null ? url.path : `${url.pathname || ""}${url.search || ""}`;
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 (path8 && path8[0] !== "/") {
1149
- path8 = `/${path8}`;
1148
+ if (path9 && path9[0] !== "/") {
1149
+ path9 = `/${path9}`;
1150
1150
  }
1151
- return new URL(`${origin}${path8}`);
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 (net2.isIP(servername)) {
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: path8, origin }
1972
+ request: { method, path: path9, origin }
1973
1973
  } = evt;
1974
- debugLog("sending request to %s %s%s", method, origin, path8);
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: path8, origin },
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
- path8,
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: path8, origin }
2008
+ request: { method, path: path9, origin }
2009
2009
  } = evt;
2010
- debugLog("trailers received from %s %s%s", method, origin, path8);
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: path8, origin },
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
- path8,
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: path8,
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 path8 !== "string") {
2156
+ if (typeof path9 !== "string") {
2157
2157
  throw new InvalidArgumentError("path must be a string");
2158
- } else if (path8[0] !== "/" && !(path8.startsWith("http://") || path8.startsWith("https://")) && method !== "CONNECT") {
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(path8)) {
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(path8, query) : path8;
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 net2 = __require("net");
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 = net2.connect({
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: path8, host, upgrade, blocking, reset } = request;
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} ${path8} HTTP/1.1\r
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: path8, host, upgrade, expectContinue, signal, protocol, headers: reqHeaders } = request;
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] = path8;
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] = path8;
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 net2 = __require("net");
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" || net2.isIP(localAddress) === 0)) {
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(net2.isIPv6(ip));
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 net2 = __require("net");
9848
+ var net3 = __require("net");
9849
9849
  var { InvalidArgumentError } = require_errors();
9850
9850
  function parseAddress(address) {
9851
- if (net2.isIPv4(address)) {
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 (net2.isIPv6(address)) {
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 (net2.isIPv4(ipv4Part)) {
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: path8 = "/",
10601
+ path: path9 = "/",
10602
10602
  headers = {}
10603
10603
  } = opts;
10604
- opts.path = origin + path8;
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(path8) {
12688
- if (typeof path8 !== "string") {
12689
- return path8;
12687
+ function safeUrl(path9) {
12688
+ if (typeof path9 !== "string") {
12689
+ return path9;
12690
12690
  }
12691
- const pathSegments = path8.split("?", 3);
12691
+ const pathSegments = path9.split("?", 3);
12692
12692
  if (pathSegments.length !== 2) {
12693
- return path8;
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: path8, method, body, headers }) {
12700
- const pathMatch = matchValue(mockDispatch2.path, path8);
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: path8, ignoreTrailingSlash }) => {
12726
- return ignoreTrailingSlash ? matchValue(removeTrailingSlash(safeUrl(path8)), resolvedPathWithoutTrailingSlash) : matchValue(safeUrl(path8), resolvedPath);
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(path8) {
12766
- while (path8.endsWith("/")) {
12767
- path8 = path8.slice(0, -1);
12765
+ function removeTrailingSlash(path9) {
12766
+ while (path9.endsWith("/")) {
12767
+ path9 = path9.slice(0, -1);
12768
12768
  }
12769
- if (path8.length === 0) {
12770
- path8 = "/";
12769
+ if (path9.length === 0) {
12770
+ path9 = "/";
12771
12771
  }
12772
- return path8;
12772
+ return path9;
12773
12773
  }
12774
12774
  function buildKey(opts) {
12775
- const { path: path8, method, body, headers, query } = opts;
12775
+ const { path: path9, method, body, headers, query } = opts;
12776
12776
  return {
12777
- path: path8,
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: path8, data: { statusCode }, persist, times, timesInvoked, origin }) => ({
13467
+ ({ method, path: path9, data: { statusCode }, persist, times, timesInvoked, origin }) => ({
13468
13468
  Method: method,
13469
13469
  Origin: origin,
13470
- Path: path8,
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 [path8, searchParams] = dispatchOpts.path.split("?");
13552
+ const [path9, searchParams] = dispatchOpts.path.split("?");
13553
13553
  const normalizedSearchParams = normalizeSearchParams(searchParams, acceptNonStandardSearchParameters);
13554
- dispatchOpts.path = `${path8}?${normalizedSearchParams}`;
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 path8 = filePath || this.#snapshotPath;
13956
- if (!path8) {
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(path8), "utf8");
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 ${path8}`, { cause: error });
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 path8 = filePath || this.#snapshotPath;
13986
- if (!path8) {
13985
+ const path9 = filePath || this.#snapshotPath;
13986
+ if (!path9) {
13987
13987
  throw new InvalidArgumentError("Snapshot path is required");
13988
13988
  }
13989
- const resolvedPath = resolve(path8);
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 path8 = search ? `${pathname}${search}` : pathname;
14622
- const redirectUrlString = `${origin}${path8}`;
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 = path8;
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, path8) {
16398
+ function deleteCachedUri(store, cacheKey, path9) {
16399
16399
  deleteCachedValue(store, {
16400
16400
  ...cacheKey,
16401
- path: path8
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: path8
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 path8 = getSameOriginPath(cacheKey, values[i]);
16421
- if (path8 !== void 0) {
16422
- deleteCachedUri(store, cacheKey, path8);
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 path8 = url.pathname + url.search;
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 ? `${path8}?` : path8,
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(path8) {
22252
- for (let i = 0; i < path8.length; ++i) {
22253
- const code = path8.charCodeAt(i);
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 path8 = opts.path;
25490
+ let path9 = opts.path;
25491
25491
  if (!opts.path.startsWith("/")) {
25492
- path8 = `/${path8}`;
25492
+ path9 = `/${path9}`;
25493
25493
  }
25494
- url = new URL(util.parseOrigin(url).origin + path8);
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: Math.max(2e4, Math.round(modelContextLimit * 0.05)),
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
- const s3 = getStream();
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(path8) {
46255
+ function safeReadJson(path9) {
46255
46256
  try {
46256
- const raw = readFileSync(path8, "utf8").replace(/^\uFEFF/, "");
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 ${path8}: ${String(e)}`);
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
- let routes = {};
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 proxyMode = parseUpstreamProxyMode(
46358
- env.BILI_UPSTREAM_PROXY_MODE ?? fileConfig.upstreamProxyMode ?? (webProxy ? "manual" : void 0)
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: Number.isFinite(port) ? port : 8787,
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: Number.isFinite(port) ? port : 8787,
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: fileConfig.mitm?.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 } = __require("fs").statSync(CACHE_FILE);
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 { response, clearTimer: () => clearTimeout(timer2) };
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) evictOldest();
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 buildCompressTextSystemPrompt() {
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 tokens="2.1K" type="tool:bash">m00175</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.
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 matches the compress tool: {"content":[{startId,endId,summary,topic?}]}. Batch multiple ranges in one trigger.
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 (TEXT TRIGGERS)
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
- 1. acp_status \u2014 view context usage, compression state, and compressible ranges:
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
- 2. search_context \u2014 search compressed block summaries by keyword:
47892
- ${ACP_SEARCH_OPEN}{"query":"auth token refresh"}${ACP_SEARCH_CLOSE}
47893
- Use when you need details that may have been compressed away.
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
- 3. decompress \u2014 restore compressed content for exact details:
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} Do not retry the same range.]`;
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)} Do not retry the same range.]`;
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 unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
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
- unlinkSync2(tempPath);
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" && lines.length >= 2) {
48550
- const dataLine = lines.slice(0, 3).join(" | ").replace(/\s+/g, " ");
48483
+ if (toolName === "acp_status") {
48551
48484
  return `
48552
- ${icon} [ACP] ${dataLine}
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
- event: message_stop
48874
- data: ${JSON.stringify({ type: "message_stop" })}
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 buildStatusReport(ctx.session.state, ctx.messages, estimateTokensFast);
48521
+ return handleAcpStatus(args, ctx);
49174
48522
  }
49175
48523
  return `[Unknown proxy tool: ${toolName}]`;
49176
48524
  }
49177
- function extractEventType(rawEvent) {
49178
- for (const l of rawEvent.split("\n")) {
49179
- if (l.startsWith("event:")) return l.slice(6).trim();
49180
- }
49181
- return null;
49182
- }
49183
- function extractDataLine(rawEvent) {
49184
- const parts = [];
49185
- for (const l of rawEvent.split("\n")) {
49186
- if (l.startsWith("data:")) {
49187
- let v2 = l.slice(5);
49188
- if (v2.startsWith(" ")) v2 = v2.slice(1);
49189
- parts.push(v2);
49190
- }
49191
- }
49192
- return parts.length ? parts.join("\n") : null;
49193
- }
49194
- function classifyResponsesSseEvent(eventStr) {
49195
- const type = extractEventType(eventStr);
49196
- const dataLine = extractDataLine(eventStr);
49197
- if (!type || !dataLine) return {};
49198
- let obj;
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") ctx.session.stats.cachedTokens += cached;
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 = executeProxyTool4(call.name, parsedArgs, ctx, call.callId);
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 (resolvedText.length > 0) {
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: resolvedText
48651
+ text: assistantText
49834
48652
  });
49835
48653
  }
49836
48654
  for (const pr2 of proxyResults) {
49837
- if (ctx.textProtocol) {
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
- if (!ctx.textProtocol) {
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} saw mutating proxy tool; re-requesting`);
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 fs5 = await import("fs");
48711
+ const fs6 = await import("fs");
49890
48712
  const dumpDir = process.env.ACP_DUMP_DIR || `${process.env.HOME}/.local/state/billion-context/dumps`;
49891
- fs5.mkdirSync(dumpDir, { recursive: true });
48713
+ fs6.mkdirSync(dumpDir, { recursive: true });
49892
48714
  const sid = ctx.session.id ?? "unknown";
49893
- fs5.writeFileSync(`${dumpDir}/req-${Date.now()}-${sid}-REREQUEST.json`, JSON.stringify(newBody, null, 2));
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(requestOptions.url, {
49898
- method: "POST",
49899
- headers: requestOptions.headers,
49900
- body: JSON.stringify(newBody),
49901
- ...ctx.proxyUrl ? { dispatcher: proxyDispatcher(ctx.proxyUrl) } : {}
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 extractEventType2(rawEvent) {
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 extractDataLine2(rawEvent) {
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 buildMessageItemSequence2(itemId, outputIndex, text) {
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 buildFunctionCallEvents2(fc, itemId, outputIndex) {
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 buildCompleted2(responseObj) {
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 = extractEventType2(eventStr);
50091
- const dataLine = extractDataLine2(eventStr);
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 buildMessageItemSequence2(`msg-proxy-${Date.now()}-${outputIndex}`, outputIndex++, delta);
49016
+ return buildMessageItemSequence(`msg-proxy-${Date.now()}-${outputIndex}`, outputIndex++, delta);
50190
49017
  },
50191
49018
  emitToolCall(call) {
50192
- const buf = buildFunctionCallEvents2(call, `fc-proxy-${Date.now()}-${outputIndex}`, outputIndex);
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 buildMessageItemSequence2(
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 buildCompleted2(resp);
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 parseAnthropicSse2(eventStr) {
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 = parseAnthropicSse2(eventStr);
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 TEXT_PROTOCOL2 = process.env.ACP_COMPRESS_PROTOCOL === "text";
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
- await withSessionLock(session, async () => {
52143
- 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);
52144
- acquireInFlight(session);
52145
- try {
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
- } finally {
52148
- releaseInFlight(session);
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 || isChatGptCodexUpstream(session.meta.upstreamOrigin) || isCodexResponsesLite(req.headers, parsed);
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 ? buildCompressTextSystemPrompt() : buildCompressSystemPrompt();
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 (!responsesTextProtocol && !process.env.ACP_NO_INJECT_TOOL) toolsOut = injectResponsesTool(parsed.tools);
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 rebuiltMessages = coreToAnthropic(turn.messages, cacheControls);
52382
- log2("info", `[${sessionId}] count_tokens pruned: ${msgs.length} \u2192 ${turn.messages.length} msgs`);
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 [...ACP_TOOLS_RESPONSES];
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 = ACP_TOOLS_RESPONSES.filter((t) => !present.has(t.name));
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
- if (process.env.ACP_LOOP_V2 !== "0") {
52614
- const parsedReq = JSON.parse(typeof body === "string" ? body : body.toString("utf8"));
52615
- const reqHeaders = {};
52616
- for (const [k2, v2] of Object.entries(headers)) {
52617
- if (k2.toLowerCase() === "content-length" || k2.toLowerCase() === "host") continue;
52618
- reqHeaders[k2] = v2;
52619
- }
52620
- reqHeaders["content-type"] = "application/json";
52621
- const textProtocol = prepared.protocol === "responses" && !!prepared.responsesTextProtocol;
52622
- const systemPrompt = textProtocol ? buildCompressTextSystemPrompt() : buildCompressSystemPrompt();
52623
- const adapter = pickAdapter(prepared.protocol, parsedReq, textProtocol, prepared.responsesProjection, prepared.anthropicSystem);
52624
- const loop = runCompressLoop(
52625
- streamToRead,
52626
- { core, config, messages: prepared.processedMessages.length > 0 ? prepared.processedMessages : prepared.originalMessages, session: prepared.session, log: ctx.log, proxyUrl, textProtocol, debug: opts.debug },
52627
- parsedReq,
52628
- { url: upstreamUrl, headers: reqHeaders },
52629
- adapter,
52630
- systemPrompt
52631
- );
52632
- for await (const chunk of loop) {
52633
- {
52634
- const s3 = chunk.toString("utf8");
52635
- if (s3.includes("<acp ") || s3.includes("</acp")) {
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
- } else {
52690
- const parsedReq = JSON.parse(typeof body === "string" ? body : body.toString("utf8"));
52691
- const reqHeaders = {};
52692
- for (const [k2, v2] of Object.entries(headers)) {
52693
- if (k2.toLowerCase() === "content-length" || k2.toLowerCase() === "host") continue;
52694
- reqHeaders[k2] = v2;
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
- tgzBuffer = Buffer.from(await tgzRes.arrayBuffer());
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 path7 from "path";
55685
+ import path8 from "path";
56122
55686
  var VERSION = (() => {
56123
55687
  try {
56124
55688
  const here = fileURLToPath3(import.meta.url);
56125
- const pkg = path7.join(path7.dirname(here), "..", "package.json");
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 = path7.join(path7.dirname(here), "..", "package.json");
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] start the proxy (default: reads ${configFile()})
56144
- bili update check for & install a newer version now
56145
- bili --version print version
56146
- bili --help show this help
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> listen port (default 8787)
56150
- --host <ADDR> listen host (default 127.0.0.1)
56151
- --config <FILE> path to config JSON (default: XDG location)
56152
- --debug verbose logging
56153
- --passthrough forward without compression
56154
- --no-passthrough force compression on (overrides config)
56155
- --no-auto-update disable background self-update this run
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 (positional.length > 0) {
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
  }