billion-context 0.1.33 → 0.1.34

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
@@ -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,
@@ -46091,7 +46091,7 @@ function resolveProxyDecision(routes, globalProxy, upstreamUrl, fallback = {}) {
46091
46091
  return { proxy: parsed.url, source: "provider" };
46092
46092
  }
46093
46093
  }
46094
- if (globalProxy === "") return { source: "direct" };
46094
+ if (globalProxy === "" && fallback.explicitDirect) return { source: "direct" };
46095
46095
  const explicit = parseHttpProxy(globalProxy, fallback.biliPort)?.url;
46096
46096
  if (explicit) return { proxy: explicit, source: fallback.globalSource ?? "global" };
46097
46097
  if (target && matchesNoProxy(target, fallback.noProxy)) return { source: "no-proxy" };
@@ -46329,34 +46329,19 @@ function loadRoutes(env = process.env) {
46329
46329
  function loadOptions(env = process.env) {
46330
46330
  const fileConfig = loadConfigFile();
46331
46331
  const port = parseInt(env.ACP_PORT ?? env.PORT ?? `${fileConfig.port ?? 8787}`, 10);
46332
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
46333
+ throw new Error(`Invalid port ${Number.isNaN(port) ? "(not a number)" : port}; must be 1-65535`);
46334
+ }
46332
46335
  const host = env.ACP_HOST ?? fileConfig.host ?? "127.0.0.1";
46333
46336
  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
- }
46337
+ const routes = loadRoutes(env);
46353
46338
  const modelContextLimit = parseInt(env.ACP_MODEL_CONTEXT_LIMIT ?? `${fileConfig.modelContextLimit ?? 2e5}`, 10);
46354
46339
  const biliProxy = nonEmpty(env.BILI_UPSTREAM_PROXY);
46355
46340
  const webProxy = nonEmpty(fileConfig.upstreamProxy);
46356
46341
  const configProxy = nonEmpty(fileConfig.proxy);
46357
- const proxyMode = parseUpstreamProxyMode(
46358
- env.BILI_UPSTREAM_PROXY_MODE ?? fileConfig.upstreamProxyMode ?? (webProxy ? "manual" : void 0)
46359
- );
46342
+ const rawProxyMode = env.BILI_UPSTREAM_PROXY_MODE ?? fileConfig.upstreamProxyMode ?? (webProxy ? "manual" : void 0);
46343
+ const proxyMode = parseUpstreamProxyMode(rawProxyMode);
46344
+ const explicitDirect = proxyMode === "direct" && rawProxyMode === "direct";
46360
46345
  const proxy = biliProxy ?? (proxyMode === "direct" ? "" : proxyMode === "manual" ? webProxy ?? configProxy : configProxy);
46361
46346
  const proxySource = biliProxy ? "bili-env" : proxyMode === "direct" ? "direct" : proxyMode === "manual" && webProxy ? "web-manual" : configProxy ? "config" : "auto";
46362
46347
  const httpProxy = nonEmpty(env.HTTP_PROXY ?? env.http_proxy);
@@ -46368,8 +46353,9 @@ function loadOptions(env = process.env) {
46368
46353
  ...httpsProxy ? { httpsProxy } : {},
46369
46354
  ...allProxy ? { allProxy } : {},
46370
46355
  ...noProxy ? { noProxy } : {},
46371
- biliPort: Number.isFinite(port) ? port : 8787,
46372
- globalSource: proxySource
46356
+ biliPort: port,
46357
+ globalSource: proxySource,
46358
+ explicitDirect
46373
46359
  };
46374
46360
  validateHttpProxy(proxy, proxyFallback.biliPort);
46375
46361
  for (const [url, route] of Object.entries(routes)) {
@@ -46380,7 +46366,7 @@ function loadOptions(env = process.env) {
46380
46366
  }
46381
46367
  }
46382
46368
  return {
46383
- port: Number.isFinite(port) ? port : 8787,
46369
+ port,
46384
46370
  host,
46385
46371
  upstream,
46386
46372
  routes,
@@ -46590,15 +46576,30 @@ async function contextFromRegistry(model, host) {
46590
46576
  // src/fetch-util.ts
46591
46577
  var MAX_REQUEST_BYTES = 100 * 1024 * 1024;
46592
46578
  var UPSTREAM_TIMEOUT_MS = 10 * 60 * 1e3;
46593
- async function fetchWithTimeout(url, opts, timeoutMs = UPSTREAM_TIMEOUT_MS) {
46579
+ async function fetchWithTimeout(url, opts, timeoutMs = UPSTREAM_TIMEOUT_MS, externalSignal) {
46594
46580
  const controller = new AbortController();
46595
46581
  const timer2 = setTimeout(() => controller.abort(), timeoutMs);
46582
+ let onExternalAbort = null;
46583
+ if (externalSignal) {
46584
+ if (externalSignal.aborted) controller.abort();
46585
+ else {
46586
+ onExternalAbort = () => controller.abort();
46587
+ externalSignal.addEventListener("abort", onExternalAbort, { once: true });
46588
+ }
46589
+ }
46596
46590
  try {
46597
46591
  const finalOpts = { ...opts, signal: controller.signal };
46598
46592
  const response = await fetch(url, finalOpts);
46599
- return { response, clearTimer: () => clearTimeout(timer2) };
46593
+ return {
46594
+ response,
46595
+ clearTimer: () => {
46596
+ clearTimeout(timer2);
46597
+ if (onExternalAbort && externalSignal) externalSignal.removeEventListener("abort", onExternalAbort);
46598
+ }
46599
+ };
46600
46600
  } catch (e) {
46601
46601
  clearTimeout(timer2);
46602
+ if (onExternalAbort && externalSignal) externalSignal.removeEventListener("abort", onExternalAbort);
46602
46603
  throw e;
46603
46604
  }
46604
46605
  }
@@ -47608,7 +47609,7 @@ function getStore() {
47608
47609
 
47609
47610
  // src/session.ts
47610
47611
  var sessions = /* @__PURE__ */ new Map();
47611
- var MAX_SESSIONS = Number.parseInt(process.env.BILI_MAX_SESSIONS ?? "256", 10) || 256;
47612
+ var MAX_SESSIONS = Math.max(1, Number.parseInt(process.env.BILI_MAX_SESSIONS ?? "256", 10) || 256);
47612
47613
  var initialized = false;
47613
47614
  async function initSessions() {
47614
47615
  if (initialized) return;
@@ -47643,7 +47644,12 @@ function getSession(id, meta) {
47643
47644
  sessions.set(id, reloaded);
47644
47645
  return reloaded;
47645
47646
  }
47646
- if (sessions.size >= MAX_SESSIONS) evictOldest();
47647
+ if (sessions.size >= MAX_SESSIONS) {
47648
+ const evicted = evictOldest();
47649
+ if (!evicted) {
47650
+ throw new Error(`session pool exhausted (MAX_SESSIONS=${MAX_SESSIONS}; all in-flight)`);
47651
+ }
47652
+ }
47647
47653
  const session = {
47648
47654
  id,
47649
47655
  meta: { protocol: meta?.protocol, upstreamOrigin: meta?.upstreamOrigin, label: meta?.label },
@@ -47727,13 +47733,14 @@ function evictOldest() {
47727
47733
  oldestId = id;
47728
47734
  }
47729
47735
  }
47730
- if (!oldestId) return;
47736
+ if (!oldestId) return false;
47731
47737
  const s3 = sessions.get(oldestId);
47732
47738
  const ok = getStore().flushSync(s3);
47733
47739
  if (!ok && !s3.persisted) {
47734
- return;
47740
+ return false;
47735
47741
  }
47736
47742
  sessions.delete(oldestId);
47743
+ return true;
47737
47744
  }
47738
47745
  async function flushAllSessions() {
47739
47746
  await getStore().flushAll(sessions.values());
@@ -48014,15 +48021,38 @@ var MUTATING_PROXY_TOOLS = /* @__PURE__ */ new Set([
48014
48021
  COMPRESS_TOOL_NAME,
48015
48022
  DECOMPRESS_TOOL_NAME
48016
48023
  ]);
48017
- var READONLY_PROXY_TOOLS = /* @__PURE__ */ new Set([
48018
- SEARCH_CONTEXT_TOOL_NAME,
48019
- ACP_STATUS_TOOL_NAME
48020
- ]);
48021
48024
 
48022
48025
  // src/decompress-shared.ts
48023
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
48026
+ import { mkdirSync as mkdirSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
48024
48027
  import { dirname as dirname3, join as join2 } from "path";
48025
48028
  import { tmpdir } from "os";
48029
+ var trackedTempFiles = [];
48030
+ function getDecompressTmpCap() {
48031
+ const raw = process.env.BILI_DECOMPRESS_TMP_CAP;
48032
+ const parsed = raw ? Number.parseInt(raw, 10) : NaN;
48033
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 50;
48034
+ }
48035
+ function reapTempFiles() {
48036
+ const cap = getDecompressTmpCap();
48037
+ while (trackedTempFiles.length > cap) {
48038
+ trackedTempFiles.sort((a, b2) => a.mtimeMs - b2.mtimeMs);
48039
+ const oldest = trackedTempFiles.shift();
48040
+ if (!oldest) break;
48041
+ try {
48042
+ unlinkSync2(oldest.path);
48043
+ } catch {
48044
+ }
48045
+ }
48046
+ }
48047
+ process.on("beforeExit", () => {
48048
+ for (const f2 of trackedTempFiles) {
48049
+ try {
48050
+ unlinkSync2(f2.path);
48051
+ } catch {
48052
+ }
48053
+ }
48054
+ trackedTempFiles.length = 0;
48055
+ });
48026
48056
  function resolveDecompress(args, ctx) {
48027
48057
  const rawBlockId = args.blockId;
48028
48058
  if (typeof rawBlockId !== "string" || rawBlockId.length === 0) {
@@ -48055,6 +48085,8 @@ function resolveDecompress(args, ctx) {
48055
48085
  try {
48056
48086
  mkdirSync4(dirname3(outPath), { recursive: true });
48057
48087
  writeFileSync3(outPath, body, "utf8");
48088
+ trackedTempFiles.push({ path: outPath, mtimeMs: Date.now() });
48089
+ reapTempFiles();
48058
48090
  return `${header}
48059
48091
  Content (${body.length} chars) written to: ${outPath}
48060
48092
  Use the read tool to access it.`;
@@ -48068,12 +48100,6 @@ ${body.slice(0, 4e3)}...`;
48068
48100
  ${body}`;
48069
48101
  }
48070
48102
 
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
48103
  // src/stream.ts
48078
48104
  function executeAnthropicProxyTool(toolName, args, ctx) {
48079
48105
  if (toolName === COMPRESS_TOOL_NAME) {
@@ -48237,7 +48263,7 @@ function renderPage(origin, version2) {
48237
48263
  }
48238
48264
 
48239
48265
  // src/web/api.ts
48240
- import { closeSync, fsyncSync, mkdirSync as mkdirSync5, openSync, renameSync as renameSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
48266
+ import { closeSync, fsyncSync, mkdirSync as mkdirSync5, openSync, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "fs";
48241
48267
  import { dirname as dirname4 } from "path";
48242
48268
  import { randomUUID } from "crypto";
48243
48269
  function readConfig() {
@@ -48270,7 +48296,7 @@ function atomicWriteConfig(config) {
48270
48296
  } catch (error) {
48271
48297
  if (descriptor !== void 0) closeSync(descriptor);
48272
48298
  try {
48273
- unlinkSync2(tempPath);
48299
+ unlinkSync3(tempPath);
48274
48300
  } catch {
48275
48301
  }
48276
48302
  throw error;
@@ -48403,1308 +48429,48 @@ function reapOrphanBlocks(session, visible, deactivate) {
48403
48429
  const hasHit = block.effectiveMessageIds.some((id) => presentIds.has(id));
48404
48430
  if (hasHit) {
48405
48431
  orphanStreaks.delete(block);
48406
- continue;
48407
- }
48408
- const streak = (orphanStreaks.get(block) ?? 0) + 1;
48409
- orphanStreaks.set(block, streak);
48410
- if (streak >= ORPHAN_THRESHOLD) {
48411
- reaped.push(block.blockId);
48412
- }
48413
- }
48414
- if (reaped.length === 0) return { reaped: [] };
48415
- session.state = deactivate(session.state, reaped);
48416
- for (const id of reaped) session.blockContents.delete(id);
48417
- return { reaped };
48418
- }
48419
-
48420
- // 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
- function buildVisibilityMarker(toolName, result) {
48538
- const lines = result.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
48539
- const failed = lines.some(
48540
- (l) => l.includes("FAILED") || l.includes("not found") || l.includes("is required") || l.includes("No blocks matched")
48541
- );
48542
- const icons = {
48543
- compress: "\u{1F4E6}",
48544
- decompress: "\u{1F4E4}",
48545
- search_context: "\u{1F50D}",
48546
- acp_status: "\u{1F4CA}"
48547
- };
48548
- 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, " ");
48551
- return `
48552
- ${icon} [ACP] ${dataLine}
48553
- `;
48554
- }
48555
- const inner = (lines[0] ?? "").replace(/^\[/, "").replace(/\]$/, "").trim();
48556
- return `
48557
- ${icon} [ACP] ${inner}
48558
- `;
48559
- }
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
-
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) {
49150
- if (toolName === "compress") {
49151
- return applyRanges(parseCompressInput(args), ctx);
49152
- }
49153
- if (toolName === "decompress") {
49154
- return resolveDecompress(args, ctx);
49155
- }
49156
- if (toolName === "search_context") {
49157
- const query = typeof args.query === "string" ? args.query : "";
49158
- if (query.length === 0) return "[search_context FAILED: query is required]";
49159
- const limit = typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : 5;
49160
- const blocks = ctx.core.search(query, ctx.session.state).slice(0, limit);
49161
- if (blocks.length === 0) return `[No blocks matched "${query}"]`;
49162
- const lines = blocks.map((b2) => {
49163
- const topic = b2.topic ?? "(no topic)";
49164
- const preview = b2.summary.length > 200 ? b2.summary.slice(0, 200) + "..." : b2.summary;
49165
- return `${b2.blockId} (T${b2.tier}) "${topic}"
49166
- ${preview}`;
49167
- });
49168
- return `Found ${blocks.length} block(s) for "${query}":
49169
-
49170
- ${lines.join("\n\n")}`;
49171
- }
49172
- if (toolName === "acp_status") {
49173
- return buildStatusReport(ctx.session.state, ctx.messages, estimateTokensFast);
49174
- }
49175
- return `[Unknown proxy tool: ${toolName}]`;
49176
- }
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 });
48432
+ continue;
49676
48433
  }
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;
48434
+ const streak = (orphanStreaks.get(block) ?? 0) + 1;
48435
+ orphanStreaks.set(block, streak);
48436
+ if (streak >= ORPHAN_THRESHOLD) {
48437
+ reaped.push(block.blockId);
49698
48438
  }
49699
- upstream = resp.body;
49700
- if (activeClearTimer) activeClearTimer();
49701
- activeClearTimer = clearTimer;
49702
48439
  }
48440
+ if (reaped.length === 0) return { reaped: [] };
48441
+ session.state = deactivate(session.state, reaped);
48442
+ for (const id of reaped) session.blockContents.delete(id);
48443
+ return { reaped };
48444
+ }
48445
+
48446
+ // src/compress-loop.ts
48447
+ function buildVisibilityMarker(toolName, result) {
48448
+ const lines = result.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
48449
+ const failed = lines.some(
48450
+ (l) => l.includes("FAILED") || l.includes("not found") || l.includes("is required") || l.includes("No blocks matched")
48451
+ );
48452
+ const icons = {
48453
+ compress: "\u{1F4E6}",
48454
+ decompress: "\u{1F4E4}",
48455
+ search_context: "\u{1F50D}",
48456
+ acp_status: "\u{1F4CA}"
48457
+ };
48458
+ const icon = failed ? "\u274C" : icons[toolName] ?? "\u{1F4E6}";
48459
+ if (toolName === "acp_status") {
48460
+ return `
48461
+ ${icon} [ACP] acp_status result:
48462
+ ${result.trim()}
48463
+ `;
48464
+ }
48465
+ const inner = (lines[0] ?? "").replace(/^\[/, "").replace(/\]$/, "").trim();
48466
+ return `
48467
+ ${icon} [ACP] ${inner}
48468
+ `;
49703
48469
  }
49704
48470
 
49705
48471
  // src/loop/core.ts
49706
48472
  var MAX_LOOP_ROUNDS = 10;
49707
- function executeProxyTool4(toolName, args, ctx, callId) {
48473
+ function executeProxyTool(toolName, args, ctx, callId) {
49708
48474
  if (toolName === "compress") {
49709
48475
  return applyRanges(parseCompressInput(args, callId), ctx);
49710
48476
  }
@@ -49728,35 +48494,62 @@ function executeProxyTool4(toolName, args, ctx, callId) {
49728
48494
  ${lines.join("\n\n")}`;
49729
48495
  }
49730
48496
  if (toolName === "acp_status") {
49731
- return buildStatusReport(ctx.session.state, ctx.messages, estimateTokensFast);
48497
+ return handleAcpStatus(args, ctx);
49732
48498
  }
49733
48499
  return `[Unknown proxy tool: ${toolName}]`;
49734
48500
  }
48501
+ function handleAcpStatus(args, ctx) {
48502
+ const scope = typeof args.scope === "string" ? args.scope : void 0;
48503
+ const view = typeof args.view === "string" ? args.view : void 0;
48504
+ const tool = typeof args.tool === "string" ? args.tool : void 0;
48505
+ const sort = typeof args.sort === "string" ? args.sort : void 0;
48506
+ const limit = typeof args.limit === "number" ? args.limit : void 0;
48507
+ const base = buildStatusReport(ctx.session.state, ctx.messages, estimateTokensFast, { scope, view, tool, sort, limit });
48508
+ if (scope) return base;
48509
+ const nudge = ctx.nudge;
48510
+ const ranges = nudge?.compressibleRanges ?? [];
48511
+ const protectedRanges = nudge?.protectedRanges ?? [];
48512
+ const extra = [];
48513
+ if (nudge) {
48514
+ extra.push("");
48515
+ extra.push(nudge.shouldInject ? `Nudge: ACTIVE \u2014 ${nudge.reason}` : `Nudge: idle \u2014 ${nudge.reason}`);
48516
+ }
48517
+ if (ranges.length > 0 || protectedRanges.length > 0) {
48518
+ extra.push("");
48519
+ extra.push(formatRanges(ranges, protectedRanges));
48520
+ }
48521
+ return extra.length > 0 ? `${base}
48522
+ ${extra.join("\n")}` : base;
48523
+ }
49735
48524
  function recordUsage(ctx, usage, round) {
49736
48525
  const prompt = usage.inputTokens;
49737
48526
  const cached = usage.cachedTokens;
49738
48527
  const out = usage.outputTokens;
49739
48528
  if (typeof prompt === "number") ctx.session.stats.inputTokens += prompt;
49740
48529
  ctx.session.stats.lastInputTokens = (typeof prompt === "number" ? prompt : 0) + (typeof cached === "number" ? cached : 0);
49741
- if (typeof cached === "number") ctx.session.stats.cachedTokens += cached;
48530
+ if (typeof cached === "number") {
48531
+ ctx.session.stats.cachedTokens += cached;
48532
+ ctx.session.stats.cacheSamples += 1;
48533
+ }
49742
48534
  if (typeof out === "number") ctx.session.stats.outputTokens += out;
49743
- ctx.session.stats.cacheSamples += 1;
49744
48535
  const hitPct = typeof prompt === "number" && typeof cached === "number" && prompt + cached > 0 ? Math.round(cached / (prompt + cached) * 100) : 0;
49745
48536
  ctx.log(
49746
48537
  `[acp-usage] round ${round} input=${ctx.session.stats.lastInputTokens} cached=${cached ?? 0} (cache hit ${hitPct}%)`
49747
48538
  );
49748
48539
  }
49749
- async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adapter, systemPrompt) {
48540
+ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adapter, systemPrompt, signal) {
49750
48541
  let activeClearTimer = null;
49751
48542
  let currentUpstream = upstream;
49752
48543
  const coreMessages = [...ctx.messages];
49753
48544
  try {
49754
48545
  for (let round = 1; round <= MAX_LOOP_ROUNDS; round++) {
48546
+ if (signal?.aborted) break;
49755
48547
  let assistantText = "";
49756
48548
  const calls = [];
49757
48549
  let usage = {};
49758
48550
  let finishReason;
49759
48551
  for await (const ev of adapter.parseStream(currentUpstream, round)) {
48552
+ if (signal?.aborted) break;
49760
48553
  if (ev.kind === "text") {
49761
48554
  assistantText += ev.delta;
49762
48555
  if (!ctx.textProtocol && round === 1 && ev.raw) {
@@ -49804,7 +48597,7 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
49804
48597
  } catch {
49805
48598
  parsedArgs = {};
49806
48599
  }
49807
- const result = executeProxyTool4(call.name, parsedArgs, ctx, call.callId);
48600
+ const result = executeProxyTool(call.name, parsedArgs, ctx, call.callId);
49808
48601
  proxyResults.push({ name: call.name, callId: call.callId, result, arguments: call.arguments });
49809
48602
  yield adapter.emitMarker(call.name, result);
49810
48603
  } else {
@@ -49837,7 +48630,7 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
49837
48630
  if (ctx.textProtocol) {
49838
48631
  coreMessages.push({
49839
48632
  id: `acp_loop_r${round}_marker_${pr2.callId}`,
49840
- role: "user",
48633
+ role: "system",
49841
48634
  contentType: "text",
49842
48635
  text: buildVisibilityMarker(pr2.name, pr2.result)
49843
48636
  });
@@ -49883,6 +48676,7 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
49883
48676
  return;
49884
48677
  }
49885
48678
  ctx.log(`[acp-loop] round ${round} saw mutating proxy tool; re-requesting`);
48679
+ if (signal?.aborted) break;
49886
48680
  const newBody = adapter.buildRequest(coreMessages, systemPrompt, requestBody);
49887
48681
  if (process.env.ACP_DUMP_REQ !== "0" && ctx.debug) {
49888
48682
  try {
@@ -49894,12 +48688,17 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
49894
48688
  } catch {
49895
48689
  }
49896
48690
  }
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
- });
48691
+ const { response: resp, clearTimer } = await fetchWithTimeout(
48692
+ requestOptions.url,
48693
+ {
48694
+ method: "POST",
48695
+ headers: requestOptions.headers,
48696
+ body: JSON.stringify(newBody),
48697
+ ...ctx.proxyUrl ? { dispatcher: proxyDispatcher(ctx.proxyUrl) } : {}
48698
+ },
48699
+ void 0,
48700
+ signal
48701
+ );
49903
48702
  if (!resp.ok || !resp.body) {
49904
48703
  clearTimer();
49905
48704
  const errText = await resp.text().catch(() => "upstream error");
@@ -49948,13 +48747,13 @@ async function* iterSseEvents(stream2) {
49948
48747
  reader.releaseLock();
49949
48748
  }
49950
48749
  }
49951
- function extractEventType2(rawEvent) {
48750
+ function extractEventType(rawEvent) {
49952
48751
  for (const l of rawEvent.split("\n")) {
49953
48752
  if (l.startsWith("event:")) return l.slice(6).trim();
49954
48753
  }
49955
48754
  return null;
49956
48755
  }
49957
- function extractDataLine2(rawEvent) {
48756
+ function extractDataLine(rawEvent) {
49958
48757
  const parts = [];
49959
48758
  for (const l of rawEvent.split("\n")) {
49960
48759
  if (l.startsWith("data:")) {
@@ -49965,7 +48764,7 @@ function extractDataLine2(rawEvent) {
49965
48764
  }
49966
48765
  return parts.length ? parts.join("\n") : null;
49967
48766
  }
49968
- function buildMessageItemSequence2(itemId, outputIndex, text) {
48767
+ function buildMessageItemSequence(itemId, outputIndex, text) {
49969
48768
  const item = { type: "message", id: itemId, role: "assistant", content: [] };
49970
48769
  const part = { type: "output_text", text: "" };
49971
48770
  const doneItem = {
@@ -50004,7 +48803,7 @@ data: ${JSON.stringify({ type: "response.output_item.done", output_index: output
50004
48803
  "utf8"
50005
48804
  );
50006
48805
  }
50007
- function buildFunctionCallEvents2(fc, itemId, outputIndex) {
48806
+ function buildFunctionCallEvents(fc, itemId, outputIndex) {
50008
48807
  return Buffer.from(
50009
48808
  [
50010
48809
  `event: response.output_item.added
@@ -50043,7 +48842,7 @@ data: ${JSON.stringify({
50043
48842
  "utf8"
50044
48843
  );
50045
48844
  }
50046
- function buildCompleted2(responseObj) {
48845
+ function buildCompleted(responseObj) {
50047
48846
  const resp = responseObj ?? { id: `resp-proxy-${Date.now()}`, status: "completed", output: [] };
50048
48847
  return Buffer.from(
50049
48848
  `event: response.completed
@@ -50087,8 +48886,8 @@ function createResponsesAdapter(textProtocol, projection) {
50087
48886
  async *parseStream(upstream, round) {
50088
48887
  const pending = /* @__PURE__ */ new Map();
50089
48888
  for await (const eventStr of iterSseEvents(upstream)) {
50090
- const type = extractEventType2(eventStr);
50091
- const dataLine = extractDataLine2(eventStr);
48889
+ const type = extractEventType(eventStr);
48890
+ const dataLine = extractDataLine(eventStr);
50092
48891
  if (!type || !dataLine) continue;
50093
48892
  let obj;
50094
48893
  try {
@@ -50186,15 +48985,15 @@ function createResponsesAdapter(textProtocol, projection) {
50186
48985
  }
50187
48986
  },
50188
48987
  emitText(delta) {
50189
- return buildMessageItemSequence2(`msg-proxy-${Date.now()}-${outputIndex}`, outputIndex++, delta);
48988
+ return buildMessageItemSequence(`msg-proxy-${Date.now()}-${outputIndex}`, outputIndex++, delta);
50190
48989
  },
50191
48990
  emitToolCall(call) {
50192
- const buf = buildFunctionCallEvents2(call, `fc-proxy-${Date.now()}-${outputIndex}`, outputIndex);
48991
+ const buf = buildFunctionCallEvents(call, `fc-proxy-${Date.now()}-${outputIndex}`, outputIndex);
50193
48992
  outputIndex += 1;
50194
48993
  return buf;
50195
48994
  },
50196
48995
  emitMarker(toolName, result) {
50197
- return buildMessageItemSequence2(
48996
+ return buildMessageItemSequence(
50198
48997
  `marker-${Date.now()}-${outputIndex}`,
50199
48998
  outputIndex++,
50200
48999
  buildVisibilityMarker(toolName, result)
@@ -50233,7 +49032,7 @@ data: ${JSON.stringify({ type: "response.failed", response: failed })}
50233
49032
  }
50234
49033
  resp = { ...resp, usage };
50235
49034
  }
50236
- return buildCompleted2(resp);
49035
+ return buildCompleted(resp);
50237
49036
  },
50238
49037
  emitError(message) {
50239
49038
  const resp = {
@@ -50528,7 +49327,7 @@ async function* iterSseEvents2(stream2) {
50528
49327
  reader.releaseLock();
50529
49328
  }
50530
49329
  }
50531
- function parseAnthropicSse2(eventStr) {
49330
+ function parseAnthropicSse(eventStr) {
50532
49331
  const lines = eventStr.split("\n");
50533
49332
  let type = "";
50534
49333
  const dataLines = [];
@@ -50638,7 +49437,7 @@ ${systemPrompt}` : systemPrompt;
50638
49437
  let usageYielded = false;
50639
49438
  const indexMap = /* @__PURE__ */ new Map();
50640
49439
  for await (const eventStr of iterSseEvents2(upstream)) {
50641
- const parsed = parseAnthropicSse2(eventStr);
49440
+ const parsed = parseAnthropicSse(eventStr);
50642
49441
  if (!parsed) continue;
50643
49442
  const { type, data } = parsed;
50644
49443
  const rawBuf = Buffer.from(eventStr + "\n\n", "utf8");
@@ -50766,6 +49565,184 @@ function pickAdapter(protocol, requestBody, textProtocol, responsesProjection, a
50766
49565
  throw new Error(`[acp-loop] unknown protocol: ${protocol}`);
50767
49566
  }
50768
49567
 
49568
+ // src/compress-loop-responses.ts
49569
+ function extractTextTriggers(text) {
49570
+ const calls = [];
49571
+ let clean = "";
49572
+ let i = 0;
49573
+ let n = 0;
49574
+ while (i < text.length) {
49575
+ const open = text.indexOf(ACP_TEXT_OPEN, i);
49576
+ if (open === -1) {
49577
+ clean += text.slice(i);
49578
+ break;
49579
+ }
49580
+ clean += text.slice(i, open);
49581
+ const after = open + ACP_TEXT_OPEN.length;
49582
+ const close = text.indexOf(ACP_TEXT_CLOSE, after);
49583
+ if (close === -1) {
49584
+ clean += text.slice(open);
49585
+ break;
49586
+ }
49587
+ const payload = text.slice(after, close).trim();
49588
+ if (payload) {
49589
+ const stamp = `${Date.now()}_${n++}`;
49590
+ calls.push({
49591
+ itemId: `fc_text_${stamp}`,
49592
+ callId: `call_text_${stamp}`,
49593
+ name: COMPRESS_TOOL_NAME,
49594
+ arguments: payload
49595
+ });
49596
+ }
49597
+ i = close + ACP_TEXT_CLOSE.length;
49598
+ }
49599
+ return { clean, calls };
49600
+ }
49601
+ function executeProxyTool2(toolName, args, ctx) {
49602
+ if (toolName === "compress") {
49603
+ return applyRanges(parseCompressInput(args), ctx);
49604
+ }
49605
+ if (toolName === "decompress") {
49606
+ return resolveDecompress(args, ctx);
49607
+ }
49608
+ if (toolName === "search_context") {
49609
+ const query = typeof args.query === "string" ? args.query : "";
49610
+ if (query.length === 0) return "[search_context FAILED: query is required]";
49611
+ const limit = typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : 5;
49612
+ const blocks = ctx.core.search(query, ctx.session.state).slice(0, limit);
49613
+ if (blocks.length === 0) return `[No blocks matched "${query}"]`;
49614
+ const lines = blocks.map((b2) => {
49615
+ const topic = b2.topic ?? "(no topic)";
49616
+ const preview = b2.summary.length > 200 ? b2.summary.slice(0, 200) + "..." : b2.summary;
49617
+ return `${b2.blockId} (T${b2.tier}) "${topic}"
49618
+ ${preview}`;
49619
+ });
49620
+ return `Found ${blocks.length} block(s) for "${query}":
49621
+
49622
+ ${lines.join("\n\n")}`;
49623
+ }
49624
+ if (toolName === "acp_status") {
49625
+ return buildStatusReport(ctx.session.state, ctx.messages, estimateTokensFast);
49626
+ }
49627
+ return `[Unknown proxy tool: ${toolName}]`;
49628
+ }
49629
+ function responsesJsonOutput(response) {
49630
+ const textParts = [];
49631
+ const calls = [];
49632
+ for (const item of Array.isArray(response.output) ? response.output : []) {
49633
+ if (!item || typeof item !== "object") continue;
49634
+ const value = item;
49635
+ if (value.type === "message") {
49636
+ for (const part of Array.isArray(value.content) ? value.content : []) {
49637
+ if (part && typeof part === "object" && part.type === "output_text") {
49638
+ textParts.push(part);
49639
+ }
49640
+ }
49641
+ } else if (value.type === "function_call") {
49642
+ calls.push({
49643
+ itemId: typeof value.id === "string" ? value.id : "",
49644
+ callId: typeof value.call_id === "string" ? value.call_id : "",
49645
+ name: typeof value.name === "string" ? value.name : "",
49646
+ arguments: typeof value.arguments === "string" ? value.arguments : ""
49647
+ });
49648
+ }
49649
+ }
49650
+ return {
49651
+ text: textParts.map((part) => typeof part.text === "string" ? part.text : "").join(""),
49652
+ textParts,
49653
+ calls
49654
+ };
49655
+ }
49656
+ function replaceResponsesJsonText(parts, text) {
49657
+ parts.forEach((part, index) => {
49658
+ part.text = index === 0 ? text : "";
49659
+ });
49660
+ }
49661
+ function surfaceReadonlyJson(current, proxyCalls, ctx) {
49662
+ const markers = [];
49663
+ for (const call of proxyCalls) {
49664
+ if (MUTATING_PROXY_TOOLS.has(call.name)) continue;
49665
+ let args = {};
49666
+ try {
49667
+ args = JSON.parse(call.arguments);
49668
+ } catch {
49669
+ args = {};
49670
+ }
49671
+ let result;
49672
+ try {
49673
+ result = executeProxyTool2(call.name, args, ctx);
49674
+ ctx.log(`[acp-proxy: responses JSON ${call.name} (read-only) \u2192 ${result.slice(0, 120).replace(/\n/g, " ")}]`);
49675
+ } catch (e) {
49676
+ result = `\u274C [ACP] ${call.name} FAILED: ${String(e)}`;
49677
+ ctx.log(`[acp-proxy: responses JSON ${call.name} (read-only) FAILED: ${String(e)}]`);
49678
+ }
49679
+ markers.push(buildVisibilityMarker(call.name, result));
49680
+ }
49681
+ if (markers.length === 0) return current;
49682
+ const out = Array.isArray(current.output) ? [...current.output] : [];
49683
+ out.push({ type: "message", id: `msg_acp_ro_${Date.now()}_${markers.length}`, role: "assistant", content: [{ type: "output_text", text: markers.join("\n") }] });
49684
+ return { ...current, output: out };
49685
+ }
49686
+ async function compressLoopResponsesJson(initialResponse, ctx, requestBody, requestOptions) {
49687
+ let current = initialResponse;
49688
+ for (let loopCount = 1; loopCount <= MAX_LOOP_ROUNDS; loopCount++) {
49689
+ const output = responsesJsonOutput(current);
49690
+ const extracted = extractTextTriggers(output.text);
49691
+ const allCalls = [...output.calls, ...extracted.calls].filter((call) => call.name.length > 0);
49692
+ const proxyCalls = allCalls.filter((call) => PROXY_TOOL_NAMES.has(call.name));
49693
+ const realCalls = allCalls.filter((call) => !PROXY_TOOL_NAMES.has(call.name));
49694
+ const mutatingProxy = proxyCalls.filter((call) => MUTATING_PROXY_TOOLS.has(call.name));
49695
+ if (mutatingProxy.length === 0 || realCalls.length > 0) {
49696
+ if (proxyCalls.length > 0) {
49697
+ replaceResponsesJsonText(output.textParts, extracted.clean);
49698
+ current = surfaceReadonlyJson(current, proxyCalls, ctx);
49699
+ }
49700
+ return current;
49701
+ }
49702
+ const inputItems = Array.isArray(requestBody.input) ? [...requestBody.input] : [];
49703
+ if (extracted.clean.trim()) {
49704
+ inputItems.push({ type: "message", role: "assistant", content: [{ type: "output_text", text: extracted.clean }] });
49705
+ }
49706
+ let mutatedThisTurn = false;
49707
+ for (const call of proxyCalls) {
49708
+ let args = {};
49709
+ try {
49710
+ args = JSON.parse(call.arguments);
49711
+ } catch (error) {
49712
+ log("warn", `[acp-compress-args] ${call.name} JSON.parse failed: ${String(error)}`);
49713
+ }
49714
+ let result;
49715
+ if (MUTATING_PROXY_TOOLS.has(call.name) && mutatedThisTurn) {
49716
+ result = `Already ${call.name}ed once this turn. Do not ${call.name} again; generate your normal response now.`;
49717
+ ctx.log(`[acp-proxy: responses JSON ${call.name} skipped (state already mutated this turn)]`);
49718
+ } else {
49719
+ result = executeProxyTool2(call.name, args, ctx);
49720
+ if (MUTATING_PROXY_TOOLS.has(call.name)) mutatedThisTurn = true;
49721
+ ctx.log(`[acp-proxy: responses JSON ${call.name} \u2192 ${result.slice(0, 120).replace(/\n/g, " ")}]`);
49722
+ }
49723
+ inputItems.push({ type: "message", role: "developer", content: buildVisibilityMarker(call.name, result) });
49724
+ }
49725
+ requestBody.input = inputItems;
49726
+ const { response, clearTimer } = await fetchWithTimeout(requestOptions.url, {
49727
+ method: "POST",
49728
+ headers: requestOptions.headers,
49729
+ body: JSON.stringify(requestBody),
49730
+ ...ctx.proxyUrl ? { dispatcher: proxyDispatcher(ctx.proxyUrl) } : {}
49731
+ });
49732
+ try {
49733
+ if (!response.ok) {
49734
+ const detail = await response.text().catch(() => "upstream error");
49735
+ throw new Error(`responses compress loop upstream error ${response.status}: ${detail.slice(0, 200)}`);
49736
+ }
49737
+ current = await response.json();
49738
+ } finally {
49739
+ clearTimer();
49740
+ }
49741
+ }
49742
+ ctx.log(`[acp-proxy: responses JSON compress loop limit (${MAX_LOOP_ROUNDS}) reached]`);
49743
+ return current;
49744
+ }
49745
+
50769
49746
  // src/stream-openai.ts
50770
49747
  function rewriteOpenaiJsonResponse(body, ctx) {
50771
49748
  if (!body || typeof body !== "object") return body;
@@ -50809,7 +49786,7 @@ ${note}` : note;
50809
49786
  }
50810
49787
 
50811
49788
  // src/stream-responses.ts
50812
- var TEXT_PROTOCOL2 = process.env.ACP_COMPRESS_PROTOCOL === "text";
49789
+ var TEXT_PROTOCOL = process.env.ACP_COMPRESS_PROTOCOL === "text";
50813
49790
  function rewriteResponsesJsonResponse(body, ctx) {
50814
49791
  if (!body || typeof body !== "object") return body;
50815
49792
  const b2 = body;
@@ -51841,6 +50818,15 @@ var UPSTREAM_HOP_HEADERS = /* @__PURE__ */ new Set([
51841
50818
  // streamed from fetch, otherwise clients try to decompress plain bytes.
51842
50819
  "content-encoding"
51843
50820
  ]);
50821
+ function buildForwardHeaders(headers) {
50822
+ const out = {};
50823
+ for (const [k2, v2] of Object.entries(headers)) {
50824
+ if (k2.toLowerCase() === "content-length" || k2.toLowerCase() === "host") continue;
50825
+ out[k2] = v2;
50826
+ }
50827
+ out["content-type"] = "application/json";
50828
+ return out;
50829
+ }
51844
50830
  function resolveUpstream(_opts, reqUrl, req) {
51845
50831
  const mitmUpstream = readMitmUpstream(req?.socket);
51846
50832
  if (mitmUpstream) {
@@ -52139,15 +51125,15 @@ async function handle(req, res, opts, core, config, log2) {
52139
51125
  });
52140
51126
  const clientLabel = responsesIdentity?.clientProvided ? responsesIdentity.value : clientConversationHeader(req.headers);
52141
51127
  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 {
51128
+ acquireInFlight(session);
51129
+ try {
51130
+ await withSessionLock(session, async () => {
51131
+ 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
51132
  await forward(req, res, opts, prepared.body, prepared, core, reqConfig, log2, route, affinity);
52147
- } finally {
52148
- releaseInFlight(session);
52149
- }
52150
- });
51133
+ });
51134
+ } finally {
51135
+ releaseInFlight(session);
51136
+ }
52151
51137
  }
52152
51138
  if (!prepared) {
52153
51139
  if (protocol === null && !opts.passthrough) {
@@ -52187,6 +51173,7 @@ function prepareAnthropic(parsed, req, opts, core, config, log2, session) {
52187
51173
  ++session.stats.requests;
52188
51174
  let processedMessages = [];
52189
51175
  let originalMessages = [];
51176
+ let nudge;
52190
51177
  let rebuiltMessages = parsed.messages;
52191
51178
  let systemOut = parsed.system;
52192
51179
  let toolsOut = parsed.tools;
@@ -52197,6 +51184,7 @@ function prepareAnthropic(parsed, req, opts, core, config, log2, session) {
52197
51184
  const tokenCount = session.stats.lastInputTokens;
52198
51185
  const turn = core.processTurn({ messages: msgs, state: session.state, config, tokenCount, renderTags: "text-only" });
52199
51186
  session.state = turn.state;
51187
+ nudge = turn.nudge;
52200
51188
  session.stats.contextTokens = tokenCount;
52201
51189
  if (!session.meta.title) {
52202
51190
  const t = deriveTitle(msgs);
@@ -52226,7 +51214,7 @@ function prepareAnthropic(parsed, req, opts, core, config, log2, session) {
52226
51214
  }
52227
51215
  const rebuilt = { ...parsed, messages: rebuiltMessages, system: systemOut, tools: toolsOut };
52228
51216
  markDirty(session);
52229
- return { body: JSON.stringify(rebuilt), session, processedMessages, originalMessages, anthropicSystem: parsed.system, protocol: "anthropic", stream: stream2, compressInjected: opts.compress.injectTool };
51217
+ return { body: JSON.stringify(rebuilt), session, processedMessages, originalMessages, anthropicSystem: parsed.system, protocol: "anthropic", stream: stream2, compressInjected: opts.compress.injectTool, nudge };
52230
51218
  }
52231
51219
  function prepareOpenai(parsed, req, opts, core, config, log2, session) {
52232
51220
  const sessionId = session.id;
@@ -52234,6 +51222,7 @@ function prepareOpenai(parsed, req, opts, core, config, log2, session) {
52234
51222
  ++session.stats.requests;
52235
51223
  let processedMessages = [];
52236
51224
  let originalMessages = [];
51225
+ let nudge;
52237
51226
  let rebuiltMessages = parsed.messages;
52238
51227
  let toolsOut = parsed.tools;
52239
51228
  const maxTokens = typeof parsed.max_tokens === "number" ? parsed.max_tokens : 8192;
@@ -52245,6 +51234,7 @@ function prepareOpenai(parsed, req, opts, core, config, log2, session) {
52245
51234
  const tokenCount = session.stats.lastInputTokens;
52246
51235
  const turn = core.processTurn({ messages: msgs, state: session.state, config, tokenCount, renderTags: "text-only" });
52247
51236
  session.state = turn.state;
51237
+ nudge = turn.nudge;
52248
51238
  session.stats.contextTokens = tokenCount;
52249
51239
  if (!session.meta.title) {
52250
51240
  const t = deriveTitle(msgs);
@@ -52279,7 +51269,7 @@ function prepareOpenai(parsed, req, opts, core, config, log2, session) {
52279
51269
  rebuilt.stream_options = { include_usage: true };
52280
51270
  }
52281
51271
  markDirty(session);
52282
- return { body: JSON.stringify(rebuilt), session, processedMessages, originalMessages, protocol: "openai", stream: stream2, compressInjected: shouldInject };
51272
+ return { body: JSON.stringify(rebuilt), session, processedMessages, originalMessages, protocol: "openai", stream: stream2, compressInjected: shouldInject, nudge };
52283
51273
  }
52284
51274
  function prepareResponses(parsed, req, opts, core, config, log2, session, identity) {
52285
51275
  const sessionId = session.id;
@@ -52290,6 +51280,7 @@ function prepareResponses(parsed, req, opts, core, config, log2, session, identi
52290
51280
  }
52291
51281
  let processedMessages = [];
52292
51282
  let originalMessages = [];
51283
+ let nudge;
52293
51284
  let responsesProjection;
52294
51285
  let rebuiltInput = parsed.input;
52295
51286
  let toolsOut = parsed.tools;
@@ -52306,6 +51297,7 @@ function prepareResponses(parsed, req, opts, core, config, log2, session, identi
52306
51297
  const tokenCount = session.stats.lastInputTokens;
52307
51298
  const turn = core.processTurn({ messages: msgs, state: session.state, config, tokenCount, renderTags: process.env.ACP_RENDER_NONE ? "none" : "text-only" });
52308
51299
  session.state = turn.state;
51300
+ nudge = turn.nudge;
52309
51301
  session.stats.contextTokens = tokenCount;
52310
51302
  if (!session.meta.title) {
52311
51303
  const t = deriveTitle(msgs);
@@ -52367,7 +51359,8 @@ function prepareResponses(parsed, req, opts, core, config, log2, session, identi
52367
51359
  protocol: "responses",
52368
51360
  stream: stream2,
52369
51361
  compressInjected: shouldInject,
52370
- responsesTextProtocol
51362
+ responsesTextProtocol,
51363
+ nudge
52371
51364
  };
52372
51365
  }
52373
51366
  function isCountTokensRequest(method, urlPath, hasBody) {
@@ -52610,105 +51603,39 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
52610
51603
  dumpRaw = dumpStreamToFile(b2, opts.dumpSse, `${Date.now()}-${prepared.session.id}-raw.sse`);
52611
51604
  }
52612
51605
  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
- }
51606
+ const parsedReq = JSON.parse(typeof body === "string" ? body : body.toString("utf8"));
51607
+ const reqHeaders = buildForwardHeaders(headers);
51608
+ const textProtocol = prepared.protocol === "responses" && !!prepared.responsesTextProtocol;
51609
+ const systemPrompt = textProtocol ? buildCompressTextSystemPrompt() : buildCompressSystemPrompt();
51610
+ const adapter = pickAdapter(prepared.protocol, parsedReq, textProtocol, prepared.responsesProjection, prepared.anthropicSystem);
51611
+ const abortCtrl = new AbortController();
51612
+ req.on("close", () => {
51613
+ if (!res.writableEnded) abortCtrl.abort();
51614
+ });
51615
+ const loop = runCompressLoop(
51616
+ streamToRead,
51617
+ { 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 },
51618
+ parsedReq,
51619
+ { url: upstreamUrl, headers: reqHeaders },
51620
+ adapter,
51621
+ systemPrompt,
51622
+ abortCtrl.signal
51623
+ );
51624
+ for await (const chunk of loop) {
51625
+ {
51626
+ const s3 = chunk.toString("utf8");
51627
+ if (s3.includes("<acp ") || s3.includes("</acp")) {
51628
+ log2("warn", `[${prepared.session.id}] tag echo: ${prepared.protocol} response stream contains <acp tag`);
52686
51629
  }
52687
- if (!res.write(chunk)) await new Promise((r) => res.once("drain", () => r()));
52688
51630
  }
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()));
51631
+ res.write(chunk);
51632
+ if (res.writableNeedDrain) {
51633
+ await Promise.race([
51634
+ new Promise((r) => res.once("drain", () => r())),
51635
+ new Promise((r) => res.once("close", () => r()))
51636
+ ]);
52711
51637
  }
51638
+ if (res.destroyed || res.writableEnded) break;
52712
51639
  }
52713
51640
  res.end();
52714
51641
  } catch (e) {
@@ -52725,12 +51652,7 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
52725
51652
  let json = JSON.parse(text);
52726
51653
  if (prepared.protocol === "responses" && prepared.responsesTextProtocol) {
52727
51654
  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";
51655
+ const requestHeaders = buildForwardHeaders(headers);
52734
51656
  json = await compressLoopResponsesJson(
52735
51657
  json,
52736
51658
  { core, config, messages: prepared.originalMessages, session: prepared.session, log: ctx.log, proxyUrl, textProtocol: true },
@@ -52867,6 +51789,7 @@ function readBody(req) {
52867
51789
  size += c.length;
52868
51790
  if (size > MAX_REQUEST_BYTES) {
52869
51791
  aborted = true;
51792
+ req.destroy();
52870
51793
  reject(new BodyTooLargeError(MAX_REQUEST_BYTES));
52871
51794
  return;
52872
51795
  }