billion-context 0.1.31 → 0.1.33

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
@@ -43930,14 +43930,49 @@ function truncateLargeToolOutputs(messages, tokenCount, config, countTokens, opt
43930
43930
  return { messages: updated, truncatedCount, savedTokens };
43931
43931
  }
43932
43932
  var KEEP_LAST_ORPHANED = 0;
43933
+ function rangeKey(startRef, endRef) {
43934
+ return `${startRef}::${endRef}`;
43935
+ }
43936
+ function rewriteCompressText(text, liveKeys) {
43937
+ let parsed;
43938
+ try {
43939
+ parsed = JSON.parse(text ?? "");
43940
+ } catch {
43941
+ return null;
43942
+ }
43943
+ if (!parsed || typeof parsed !== "object") return null;
43944
+ const obj = parsed;
43945
+ const content = obj.content;
43946
+ if (!Array.isArray(content) || content.length === 0) return null;
43947
+ const kept = content.filter((entry) => {
43948
+ if (!entry || typeof entry !== "object") return false;
43949
+ const s3 = typeof entry.startId === "string" ? entry.startId : typeof entry.messageId === "string" ? entry.messageId : "";
43950
+ const e = typeof entry.endId === "string" ? entry.endId : typeof entry.messageId === "string" ? entry.messageId : "";
43951
+ return liveKeys.has(rangeKey(s3, e));
43952
+ });
43953
+ if (kept.length === content.length || kept.length === 0) return null;
43954
+ return JSON.stringify({ ...obj, content: kept });
43955
+ }
43933
43956
  function hideConsumedCompressCalls(state, messages) {
43934
- const activeCallIds = /* @__PURE__ */ new Set();
43935
43957
  const allBlockCallIds = /* @__PURE__ */ new Set();
43958
+ const activeCallIds = /* @__PURE__ */ new Set();
43959
+ const liveRangeKeysByCallId = /* @__PURE__ */ new Map();
43960
+ const legacyLiveByCallId = /* @__PURE__ */ new Set();
43936
43961
  for (const block of state.blocks) {
43937
- if (block.compressCallId) {
43938
- allBlockCallIds.add(block.compressCallId);
43939
- if (block.active) activeCallIds.add(block.compressCallId);
43962
+ if (!block.compressCallId) continue;
43963
+ allBlockCallIds.add(block.compressCallId);
43964
+ if (!block.active) continue;
43965
+ activeCallIds.add(block.compressCallId);
43966
+ if (block.startRef === void 0 || block.endRef === void 0) {
43967
+ legacyLiveByCallId.add(block.compressCallId);
43968
+ continue;
43940
43969
  }
43970
+ let keys = liveRangeKeysByCallId.get(block.compressCallId);
43971
+ if (!keys) {
43972
+ keys = /* @__PURE__ */ new Set();
43973
+ liveRangeKeysByCallId.set(block.compressCallId, keys);
43974
+ }
43975
+ keys.add(rangeKey(block.startRef, block.endRef));
43941
43976
  }
43942
43977
  const lastOrphanedCallIds = [];
43943
43978
  for (let i = messages.length - 1; i >= 0 && lastOrphanedCallIds.length < KEEP_LAST_ORPHANED; i--) {
@@ -43966,6 +44001,16 @@ function hideConsumedCompressCalls(state, messages) {
43966
44001
  hidden++;
43967
44002
  continue;
43968
44003
  }
44004
+ if (message.toolName === "compress" && message.contentType === "tool-call" && message.toolCallId && keepCallIds.has(message.toolCallId)) {
44005
+ const liveKeys = liveRangeKeysByCallId.get(message.toolCallId);
44006
+ if (liveKeys && liveKeys.size > 0 && !legacyLiveByCallId.has(message.toolCallId)) {
44007
+ const rewritten = rewriteCompressText(message.text, liveKeys);
44008
+ if (rewritten !== null) {
44009
+ result.push({ ...message, text: rewritten });
44010
+ continue;
44011
+ }
44012
+ }
44013
+ }
43969
44014
  result.push(message);
43970
44015
  }
43971
44016
  return { messages: result, hidden };
@@ -44199,8 +44244,8 @@ function refNum(ref) {
44199
44244
  const n = parseInt(ref.slice(1), 10);
44200
44245
  return Number.isNaN(n) ? -1 : n;
44201
44246
  }
44202
- function estimateMessageTokens(message) {
44203
- return Math.ceil((message.text ?? "").length / 4);
44247
+ function estimateTextTokens(text) {
44248
+ return Math.ceil(text.length / 4);
44204
44249
  }
44205
44250
  function isToolMessage(message) {
44206
44251
  return message.contentType === "tool-call" || message.contentType === "tool-result";
@@ -44212,7 +44257,7 @@ function isSyntheticOrPruned(message, state) {
44212
44257
  }
44213
44258
  return false;
44214
44259
  }
44215
- function computeProtectedRefs(messages, state, config) {
44260
+ function computeProtectedRefs(messages, state, config, countTokens = estimateTextTokens) {
44216
44261
  const preserveN = config.preserveRecentMessages;
44217
44262
  const preserveTokens = config.preserveRecentTokens;
44218
44263
  const result = /* @__PURE__ */ new Set();
@@ -44222,7 +44267,7 @@ function computeProtectedRefs(messages, state, config) {
44222
44267
  if (isNeverPreserveRecent(msg2)) continue;
44223
44268
  const ref = state.messageRefs.byRaw[msg2.id];
44224
44269
  if (!ref || ref === "BLOCKED") continue;
44225
- visible.push({ ref, tokens: estimateMessageTokens(msg2) });
44270
+ visible.push({ ref, tokens: countTokens(msg2.text ?? "") });
44226
44271
  }
44227
44272
  if (preserveN > 0) {
44228
44273
  for (const m2 of visible.slice(-preserveN)) {
@@ -44247,7 +44292,7 @@ function computeProtectedRefs(messages, state, config) {
44247
44292
  }
44248
44293
  return result;
44249
44294
  }
44250
- function buildCompressibleRanges(messages, state, config, protectedZoneRefs) {
44295
+ function buildCompressibleRanges(messages, state, config, protectedZoneRefs, countTokens = estimateTextTokens) {
44251
44296
  const compressibleMsgs = [];
44252
44297
  const protectedMsgs = [];
44253
44298
  const protectedCallIds = collectProtectedToolCallIds(messages, config);
@@ -44260,7 +44305,7 @@ function buildCompressibleRanges(messages, state, config, protectedZoneRefs) {
44260
44305
  protectedMsgs.push({
44261
44306
  ref,
44262
44307
  refNum: rn2,
44263
- tokens: estimateMessageTokens(msg2),
44308
+ tokens: countTokens(msg2.text ?? ""),
44264
44309
  tools: msg2.toolName ? [msg2.toolName] : []
44265
44310
  });
44266
44311
  continue;
@@ -44271,7 +44316,7 @@ function buildCompressibleRanges(messages, state, config, protectedZoneRefs) {
44271
44316
  compressibleMsgs.push({
44272
44317
  ref,
44273
44318
  refNum: rn2,
44274
- tokens: estimateMessageTokens(msg2),
44319
+ tokens: countTokens(msg2.text ?? ""),
44275
44320
  isTool: isToolMessage(msg2),
44276
44321
  isUser: msg2.role === "user"
44277
44322
  });
@@ -44358,7 +44403,7 @@ function createCore(ports = {}) {
44358
44403
  let tokensCompressed = 0;
44359
44404
  const errors = [];
44360
44405
  const warnings = [];
44361
- const protectedMessageIds = input.protectedMessageIds ?? computeProtectedRefs(input.messages, input.state, input.config);
44406
+ const protectedMessageIds = input.protectedMessageIds ?? computeProtectedRefs(input.messages, input.state, input.config, countTokens);
44362
44407
  const preExistingCoverage = collectCoverage(state);
44363
44408
  const rangeIndexSets = [];
44364
44409
  for (const spec of input.ranges) {
@@ -44383,29 +44428,25 @@ function createCore(ports = {}) {
44383
44428
  const bMin = b2.indices.length > 0 ? Math.min(...b2.indices) : Infinity;
44384
44429
  return aMin - bMin;
44385
44430
  });
44386
- for (let i = 1; i < sortedRanges.length; i++) {
44387
- const prev = sortedRanges[i - 1];
44388
- const curr = sortedRanges[i];
44389
- const prevMax = prev.indices.length > 0 ? Math.max(...prev.indices) : -1;
44390
- const currMin = curr.indices.length > 0 ? Math.min(...curr.indices) : -1;
44391
- if (prevMax >= currMin && prevMax >= 0) {
44392
- return {
44393
- state: input.state,
44394
- result: {
44395
- blocksCreated: 0,
44396
- tokensCompressed: 0,
44397
- errors: [
44398
- `content: range (${prev.spec.startRef}..${prev.spec.endRef}) overlaps (${curr.spec.startRef}..${curr.spec.endRef}). Overlapping ranges cannot be compressed in the same batch.`
44399
- ],
44400
- warnings: []
44401
- }
44402
- };
44431
+ const skipSpecs = /* @__PURE__ */ new Set();
44432
+ let acceptedMaxIndex = -1;
44433
+ for (const entry of sortedRanges) {
44434
+ const entryMax = entry.indices.length > 0 ? Math.max(...entry.indices) : -1;
44435
+ const entryMin = entry.indices.length > 0 ? Math.min(...entry.indices) : -1;
44436
+ if (entryMin >= 0 && entryMin <= acceptedMaxIndex) {
44437
+ skipSpecs.add(entry.spec);
44438
+ warnings.push(
44439
+ `Skipped range (${entry.spec.startRef}..${entry.spec.endRef}) \u2014 overlaps an earlier range in the batch; the earlier range takes precedence. Keep ranges disjoint.`
44440
+ );
44441
+ continue;
44403
44442
  }
44443
+ if (entryMax > acceptedMaxIndex) acceptedMaxIndex = entryMax;
44404
44444
  }
44405
44445
  if (input.config.compress.minCompressRange > 0 && input.ranges.length > 0) {
44406
44446
  let totalRangeChars = 0;
44407
44447
  let hasBlockBoundaryRange = false;
44408
44448
  for (const spec of input.ranges) {
44449
+ if (skipSpecs.has(spec)) continue;
44409
44450
  let resolved;
44410
44451
  try {
44411
44452
  resolved = resolveBoundaries({
@@ -44441,6 +44482,7 @@ function createCore(ports = {}) {
44441
44482
  }
44442
44483
  }
44443
44484
  for (const spec of input.ranges) {
44485
+ if (skipSpecs.has(spec)) continue;
44444
44486
  try {
44445
44487
  const outcome = applySingleRange({
44446
44488
  spec,
@@ -44581,13 +44623,15 @@ var recommendNode = {
44581
44623
  const protectedRefs = computeProtectedRefs(
44582
44624
  io2.messages,
44583
44625
  io2.state,
44584
- ctx.config
44626
+ ctx.config,
44627
+ ctx.countTokens
44585
44628
  );
44586
44629
  const contextRanges = buildCompressibleRanges(
44587
44630
  io2.messages,
44588
44631
  io2.state,
44589
44632
  ctx.config,
44590
- protectedRefs
44633
+ protectedRefs,
44634
+ ctx.countTokens
44591
44635
  );
44592
44636
  const nothingToCompress = contextRanges.compressible.length === 0;
44593
44637
  const recommendation = {
@@ -44765,7 +44809,9 @@ function applySingleRange(input) {
44765
44809
  survivedCount: 0,
44766
44810
  generation: "young",
44767
44811
  active: true,
44768
- compressCallId: input.spec.compressCallId
44812
+ compressCallId: input.spec.compressCallId,
44813
+ startRef: input.spec.startRef,
44814
+ endRef: input.spec.endRef
44769
44815
  };
44770
44816
  input.state.blocks.push(block);
44771
44817
  for (const consumedId of consumedBlockIds) {
@@ -45256,7 +45302,7 @@ function renderNudgeText(decision) {
45256
45302
  breakdownStr,
45257
45303
  "",
45258
45304
  `[TIER ${decision.tier} ${isT2 ? "DISTILLATION" : "CONDENSATION"} TRIGGER]`,
45259
- isT2 ? `Your tier-1 compression summaries have accumulated. Distill them into a single denser tier-2 summary. Use block IDs as boundaries.` : `Your tier-2 compression summaries have accumulated. Condense them further into a tier-3 ultra-condensed summary. Use block IDs as boundaries.`,
45305
+ isT2 ? `Your tier-1 compression summaries have accumulated. Distill them into a single denser tier-2 summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-2 block as well \u2014 apply HOW TO COMPRESS to those raw messages and the TIER 2 distillation rules to the existing summaries, so the whole span is covered and nothing is lost.` : `Your tier-2 compression summaries have accumulated. Condense them further into a tier-3 ultra-condensed summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-3 block as well \u2014 apply HOW TO COMPRESS to those raw messages and the TIER 3 condensation rules to the existing summaries, so the whole span is covered and nothing is lost.`,
45260
45306
  blockList,
45261
45307
  `Example: compress({ content: [{ startId: "${startId}", endId: "${endId}", summary: "..." }] })`,
45262
45308
  "",
@@ -46420,7 +46466,6 @@ function rejectLegacyRoute(key, value) {
46420
46466
  // src/server.ts
46421
46467
  import http from "http";
46422
46468
  import fs3 from "fs";
46423
- import { tmpdir as tmpdir2 } from "os";
46424
46469
 
46425
46470
  // src/registry.ts
46426
46471
  import { readFile, writeFile, mkdir } from "fs/promises";
@@ -47698,9 +47743,15 @@ async function flushAllSessions() {
47698
47743
  var COMPRESS_TOOL_NAME = "compress";
47699
47744
  var ACP_TEXT_OPEN = "<acp_compress>";
47700
47745
  var ACP_TEXT_CLOSE = "</acp_compress>";
47746
+ var ACP_STATUS_OPEN = "<acp_status>";
47747
+ var ACP_STATUS_CLOSE = "</acp_status>";
47748
+ var ACP_SEARCH_OPEN = "<acp_search>";
47749
+ var ACP_SEARCH_CLOSE = "</acp_search>";
47750
+ var ACP_DECOMPRESS_OPEN = "<acp_decompress>";
47751
+ var ACP_DECOMPRESS_CLOSE = "</acp_decompress>";
47701
47752
  var COMPRESS_TOOL = {
47702
47753
  name: COMPRESS_TOOL_NAME,
47703
- description: "Replace a contiguous range of older conversation with a detailed summary you write. Use when content is genuinely consumed. Batch form: content=[{startId,endId,summary,topic?}].",
47754
+ description: "Replace a contiguous range of older conversation with a detailed summary you write. Use when content is genuinely consumed. Batch form: content=[{startId,endId,summary,topic?}]. REQUIRED \u2014 compress without content is invalid.",
47704
47755
  input_schema: {
47705
47756
  type: "object",
47706
47757
  properties: {
@@ -47719,23 +47770,23 @@ var COMPRESS_TOOL = {
47719
47770
  required: ["startId", "endId", "summary"]
47720
47771
  }
47721
47772
  }
47722
- }
47773
+ },
47774
+ required: ["content"]
47723
47775
  }
47724
47776
  };
47725
- function parseCompressInput(input) {
47777
+ function parseCompressInput(input, callId) {
47726
47778
  if (!input || typeof input !== "object") {
47727
47779
  log("warn", `[acp-compress-input] rejected: not object (${typeof input})`);
47728
47780
  return [];
47729
47781
  }
47730
47782
  const obj = input;
47731
- if (Array.isArray(obj.content)) {
47732
- const out = obj.content.map((r) => toRange(r)).filter((r) => r !== null);
47733
- if (out.length === 0) log("warn", `[acp-compress-input] content array but 0 valid ranges. keys per item: ${obj.content.map((c) => Object.keys(c ?? {}).join(",")).join(" | ")}`);
47734
- return out;
47735
- }
47736
47783
  const single = toRange(obj);
47737
- if (!single) log("warn", `[acp-compress-input] no content array, single-parse failed. top keys: ${Object.keys(obj).join(",")}`);
47738
- return single ? [single] : [];
47784
+ const ranges = Array.isArray(obj.content) ? obj.content.map((r) => toRange(r)).filter((r) => r !== null) : single ? [single] : [];
47785
+ if (ranges.length === 0) {
47786
+ log("warn", `[acp-compress-input] parsed 0 valid ranges. top keys: ${Object.keys(obj).join(",")}`);
47787
+ }
47788
+ if (callId) for (const r of ranges) r.compressCallId = callId;
47789
+ return ranges;
47739
47790
  }
47740
47791
  function toRange(r) {
47741
47792
  const startRef = pick(r, "startId", "startRef");
@@ -47764,7 +47815,7 @@ var COMPRESS_TOOL_OPENAI = {
47764
47815
  topic: { type: "string", description: "Optional short title for the compressed range" },
47765
47816
  content: {
47766
47817
  type: "array",
47767
- description: "One or more ranges to compress into separate summary blocks",
47818
+ description: "One or more ranges to compress into separate summary blocks. REQUIRED \u2014 compress without content is invalid.",
47768
47819
  items: {
47769
47820
  type: "object",
47770
47821
  properties: {
@@ -47776,7 +47827,8 @@ var COMPRESS_TOOL_OPENAI = {
47776
47827
  required: ["startId", "endId", "summary"]
47777
47828
  }
47778
47829
  }
47779
- }
47830
+ },
47831
+ required: ["content"]
47780
47832
  }
47781
47833
  }
47782
47834
  };
@@ -47826,7 +47878,29 @@ Rules for the trigger:
47826
47878
  - JSON shape matches the compress tool: {"content":[{startId,endId,summary,topic?}]}. Batch multiple ranges in one trigger.
47827
47879
  - After emitting the marker, STOP your turn. Do not continue with other text \u2014 the proxy will execute the compression and return the result, then you continue fresh.
47828
47880
  - Do NOT wrap the marker in code fences, quotes, or commentary.
47829
- - NEVER compress on short conversations or when context is small (well below the window limit). Only compress when context is genuinely large.`;
47881
+ - NEVER compress on short conversations or when context is small (well below the window limit). Only compress when context is genuinely large.
47882
+
47883
+ ACP TOOLS (TEXT TRIGGERS)
47884
+
47885
+ Since host tools cannot coexist with a declared tools field, ALL ACP tools use text triggers. Emit the marker; the proxy intercepts and executes it; the marker is stripped from what the user sees.
47886
+
47887
+ 1. acp_status \u2014 view context usage, compression state, and compressible ranges:
47888
+ ${ACP_STATUS_OPEN}${ACP_STATUS_CLOSE}
47889
+ No payload needed. Use this FIRST when unsure about context state.
47890
+
47891
+ 2. search_context \u2014 search compressed block summaries by keyword:
47892
+ ${ACP_SEARCH_OPEN}{"query":"auth token refresh"}${ACP_SEARCH_CLOSE}
47893
+ Use when you need details that may have been compressed away.
47894
+
47895
+ 3. decompress \u2014 restore compressed content for exact details:
47896
+ ${ACP_DECOMPRESS_OPEN}{"blockId":"b5"}${ACP_DECOMPRESS_CLOSE}
47897
+ Optional: {"blockId":"b5","toFile":"/tmp/b5.txt"} to write to file instead.
47898
+ Optional: {"blockId":"b5","full":true} to restore all the way to original messages.
47899
+
47900
+ Rules for ALL triggers:
47901
+ - Output on its own, NO surrounding prose. Just the raw marker.
47902
+ - After emitting, STOP your turn. The proxy executes and returns the result.
47903
+ - Do NOT wrap in code fences, quotes, or commentary.`;
47830
47904
  }
47831
47905
  var DECOMPRESS_TOOL_NAME = "decompress";
47832
47906
  var DECOMPRESS_TOOL_OPENAI = {
@@ -47936,6 +48010,14 @@ var PROXY_TOOL_NAMES = /* @__PURE__ */ new Set([
47936
48010
  SEARCH_CONTEXT_TOOL_NAME,
47937
48011
  ACP_STATUS_TOOL_NAME
47938
48012
  ]);
48013
+ var MUTATING_PROXY_TOOLS = /* @__PURE__ */ new Set([
48014
+ COMPRESS_TOOL_NAME,
48015
+ DECOMPRESS_TOOL_NAME
48016
+ ]);
48017
+ var READONLY_PROXY_TOOLS = /* @__PURE__ */ new Set([
48018
+ SEARCH_CONTEXT_TOOL_NAME,
48019
+ ACP_STATUS_TOOL_NAME
48020
+ ]);
47939
48021
 
47940
48022
  // src/decompress-shared.ts
47941
48023
  import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
@@ -48595,7 +48677,9 @@ async function* compressLoopStream(initialUpstream, ctx, requestBody, requestOpt
48595
48677
  }).filter((tc) => tc.name.length > 0);
48596
48678
  const proxyCalls = toolCalls.filter((tc) => PROXY_TOOL_NAMES.has(tc.name));
48597
48679
  const realCalls = toolCalls.filter((tc) => !PROXY_TOOL_NAMES.has(tc.name));
48598
- const hasOnlyProxy = proxyCalls.length > 0 && realCalls.length === 0;
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;
48599
48683
  if (usage) {
48600
48684
  const prompt = usage.prompt_tokens ?? usage.input_tokens;
48601
48685
  const det = usage.prompt_tokens_details ?? usage.prompt_cache_hit_tokens;
@@ -48611,7 +48695,27 @@ async function* compressLoopStream(initialUpstream, ctx, requestBody, requestOpt
48611
48695
  ctx.session.stats.cacheSamples += 1;
48612
48696
  }
48613
48697
  }
48614
- if (!hasOnlyProxy) {
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
+ }
48615
48719
  for (const tc of realCalls) {
48616
48720
  yield Buffer.from(buildToolCallSse(makeBase(), tc), "utf8");
48617
48721
  }
@@ -48867,8 +48971,23 @@ async function* compressLoopAnthropicStream(initialUpstream, ctx, requestBody, r
48867
48971
  }
48868
48972
  clientIndex = state.clientIndex;
48869
48973
  const proxyCalls = [...state.toolBlocks.values()].filter((b2) => PROXY_TOOL_NAMES.has(b2.name));
48870
- const hasOnlyProxy = proxyCalls.length > 0 && !hasRealToolUse;
48871
- if (!hasOnlyProxy) {
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
+ }
48872
48991
  const stop = hasRealToolUse ? "tool_use" : roundStopReason ?? "end_turn";
48873
48992
  yield Buffer.from(buildTerminalSse(stop, totalOutputTokens, totalInputTokens, totalCachedTokens, messageId, model), "utf8");
48874
48993
  return;
@@ -49281,6 +49400,31 @@ function replaceResponsesJsonText(parts, text) {
49281
49400
  part.text = index === 0 ? text : "";
49282
49401
  });
49283
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
+ }
49284
49428
  async function compressLoopResponsesJson(initialResponse, ctx, requestBody, requestOptions) {
49285
49429
  let current = initialResponse;
49286
49430
  for (let loopCount = 1; loopCount <= 5; loopCount++) {
@@ -49289,8 +49433,12 @@ async function compressLoopResponsesJson(initialResponse, ctx, requestBody, requ
49289
49433
  const allCalls = [...output.calls, ...extracted.calls].filter((call) => call.name.length > 0);
49290
49434
  const proxyCalls = allCalls.filter((call) => PROXY_TOOL_NAMES.has(call.name));
49291
49435
  const realCalls = allCalls.filter((call) => !PROXY_TOOL_NAMES.has(call.name));
49292
- if (proxyCalls.length === 0 || realCalls.length > 0) {
49293
- if (proxyCalls.length > 0) replaceResponsesJsonText(output.textParts, extracted.clean);
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
+ }
49294
49442
  return current;
49295
49443
  }
49296
49444
  const inputItems = Array.isArray(requestBody.input) ? [...requestBody.input] : [];
@@ -49437,9 +49585,30 @@ async function* compressLoopResponsesStream(initialUpstream, ctx, requestBody, r
49437
49585
  const allCalls = [...fcByItemId.values()].filter((c) => c.name.length > 0);
49438
49586
  const proxyCalls = allCalls.filter((c) => PROXY_TOOL_NAMES.has(c.name));
49439
49587
  const realCalls = allCalls.filter((c) => !PROXY_TOOL_NAMES.has(c.name));
49588
+ const readonlyProxy = proxyCalls.filter((c) => READONLY_PROXY_TOOLS.has(c.name));
49440
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))}`);
49441
- const hasOnlyProxy = proxyCalls.length > 0 && realCalls.length === 0;
49442
- if (!hasOnlyProxy) {
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
+ }
49443
49612
  let oi2 = nextOutputIndex;
49444
49613
  for (const fc of realCalls) {
49445
49614
  yield Buffer.from(buildFunctionCallEvents(fc, oi2), "utf8");
@@ -49451,7 +49620,8 @@ async function* compressLoopResponsesStream(initialUpstream, ctx, requestBody, r
49451
49620
  return;
49452
49621
  }
49453
49622
  const hasUsage = !!responseObj?.usage;
49454
- if (contentText.length === 0 && realCalls.length === 0 && customToolCalls === 0 && !hasUsage) {
49623
+ const emittedReadonly = readonlyProxy.length > 0;
49624
+ if (contentText.length === 0 && realCalls.length === 0 && customToolCalls === 0 && !emittedReadonly && !hasUsage) {
49455
49625
  ctx.log("[acp-proxy: empty upstream response (no content/usage) \u2014 injecting response.failed for client retry]");
49456
49626
  yield Buffer.from(buildFailed(responseObj), "utf8");
49457
49627
  return;
@@ -49532,6 +49702,1070 @@ async function* compressLoopResponsesStream(initialUpstream, ctx, requestBody, r
49532
49702
  }
49533
49703
  }
49534
49704
 
49705
+ // src/loop/core.ts
49706
+ var MAX_LOOP_ROUNDS = 10;
49707
+ function executeProxyTool4(toolName, args, ctx, callId) {
49708
+ if (toolName === "compress") {
49709
+ return applyRanges(parseCompressInput(args, callId), ctx);
49710
+ }
49711
+ if (toolName === "decompress") {
49712
+ return resolveDecompress(args, ctx);
49713
+ }
49714
+ if (toolName === "search_context") {
49715
+ const query = typeof args.query === "string" ? args.query : "";
49716
+ if (query.length === 0) return "[search_context FAILED: query is required]";
49717
+ const limit = typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : 5;
49718
+ const blocks = ctx.core.search(query, ctx.session.state).slice(0, limit);
49719
+ if (blocks.length === 0) return `[No blocks matched "${query}"]`;
49720
+ const lines = blocks.map((b2) => {
49721
+ const topic = b2.topic ?? "(no topic)";
49722
+ const preview = b2.summary.length > 200 ? b2.summary.slice(0, 200) + "..." : b2.summary;
49723
+ return `${b2.blockId} (T${b2.tier}) "${topic}"
49724
+ ${preview}`;
49725
+ });
49726
+ return `Found ${blocks.length} block(s) for "${query}":
49727
+
49728
+ ${lines.join("\n\n")}`;
49729
+ }
49730
+ if (toolName === "acp_status") {
49731
+ return buildStatusReport(ctx.session.state, ctx.messages, estimateTokensFast);
49732
+ }
49733
+ return `[Unknown proxy tool: ${toolName}]`;
49734
+ }
49735
+ function recordUsage(ctx, usage, round) {
49736
+ const prompt = usage.inputTokens;
49737
+ const cached = usage.cachedTokens;
49738
+ const out = usage.outputTokens;
49739
+ if (typeof prompt === "number") ctx.session.stats.inputTokens += prompt;
49740
+ ctx.session.stats.lastInputTokens = (typeof prompt === "number" ? prompt : 0) + (typeof cached === "number" ? cached : 0);
49741
+ if (typeof cached === "number") ctx.session.stats.cachedTokens += cached;
49742
+ if (typeof out === "number") ctx.session.stats.outputTokens += out;
49743
+ ctx.session.stats.cacheSamples += 1;
49744
+ const hitPct = typeof prompt === "number" && typeof cached === "number" && prompt + cached > 0 ? Math.round(cached / (prompt + cached) * 100) : 0;
49745
+ ctx.log(
49746
+ `[acp-usage] round ${round} input=${ctx.session.stats.lastInputTokens} cached=${cached ?? 0} (cache hit ${hitPct}%)`
49747
+ );
49748
+ }
49749
+ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adapter, systemPrompt) {
49750
+ let activeClearTimer = null;
49751
+ let currentUpstream = upstream;
49752
+ const coreMessages = [...ctx.messages];
49753
+ try {
49754
+ for (let round = 1; round <= MAX_LOOP_ROUNDS; round++) {
49755
+ let assistantText = "";
49756
+ const calls = [];
49757
+ let usage = {};
49758
+ let finishReason;
49759
+ for await (const ev of adapter.parseStream(currentUpstream, round)) {
49760
+ if (ev.kind === "text") {
49761
+ assistantText += ev.delta;
49762
+ if (!ctx.textProtocol && round === 1 && ev.raw) {
49763
+ yield ev.raw;
49764
+ }
49765
+ } else if (ev.kind === "tool_call") {
49766
+ calls.push({ name: ev.name, callId: ev.callId, arguments: ev.arguments });
49767
+ } else if (ev.kind === "usage") {
49768
+ usage = {
49769
+ inputTokens: ev.inputTokens,
49770
+ outputTokens: ev.outputTokens,
49771
+ cachedTokens: ev.cachedTokens
49772
+ };
49773
+ } else if (ev.kind === "done") {
49774
+ finishReason = ev.finishReason;
49775
+ } else if (ev.kind === "meta") {
49776
+ if (round === 1 || !ev.firstRoundOnly) {
49777
+ yield ev.chunk;
49778
+ }
49779
+ }
49780
+ }
49781
+ if (usage.inputTokens !== void 0 || usage.outputTokens !== void 0 || usage.cachedTokens !== void 0) {
49782
+ recordUsage(ctx, usage, round);
49783
+ }
49784
+ let resolvedText = assistantText;
49785
+ let allCalls = calls;
49786
+ if (ctx.textProtocol && assistantText.length > 0 && adapter.extractTextTriggers) {
49787
+ const extracted = adapter.extractTextTriggers(assistantText);
49788
+ resolvedText = extracted.clean;
49789
+ allCalls = [...calls, ...extracted.calls];
49790
+ }
49791
+ if (ctx.textProtocol && resolvedText.length > 0) {
49792
+ yield adapter.emitText(resolvedText);
49793
+ } else if (!ctx.textProtocol && round > 1 && resolvedText.length > 0) {
49794
+ yield adapter.emitText(resolvedText);
49795
+ }
49796
+ let realCalls = 0;
49797
+ const realToolCalls = [];
49798
+ const proxyResults = [];
49799
+ for (const call of allCalls) {
49800
+ if (PROXY_TOOL_NAMES.has(call.name)) {
49801
+ let parsedArgs;
49802
+ try {
49803
+ parsedArgs = call.arguments.length > 0 ? JSON.parse(call.arguments) : {};
49804
+ } catch {
49805
+ parsedArgs = {};
49806
+ }
49807
+ const result = executeProxyTool4(call.name, parsedArgs, ctx, call.callId);
49808
+ proxyResults.push({ name: call.name, callId: call.callId, result, arguments: call.arguments });
49809
+ yield adapter.emitMarker(call.name, result);
49810
+ } else {
49811
+ realToolCalls.push(call);
49812
+ realCalls += 1;
49813
+ }
49814
+ }
49815
+ if (ctx.debug) {
49816
+ const callSummary = allCalls.map((c) => {
49817
+ const argSnippet = c.arguments.length > 200 ? c.arguments.slice(0, 200) + "..." : c.arguments;
49818
+ return `${c.name}(${argSnippet})`;
49819
+ }).join(" | ");
49820
+ ctx.log(`[acp-loop] round ${round}: ${allCalls.length} call(s): ${callSummary || "(none)"}`);
49821
+ for (const pr2 of proxyResults) {
49822
+ const resSnippet = pr2.result.length > 300 ? pr2.result.slice(0, 300) + "..." : pr2.result;
49823
+ ctx.log(`[acp-loop] \u2192 ${pr2.name} result: ${resSnippet}`);
49824
+ }
49825
+ if (realCalls > 0) ctx.log(`[acp-loop] round ${round}: ${realCalls} real tool call(s) forwarded to client`);
49826
+ }
49827
+ if (proxyResults.length > 0) {
49828
+ if (resolvedText.length > 0) {
49829
+ coreMessages.push({
49830
+ id: `acp_loop_r${round}_asst`,
49831
+ role: "assistant",
49832
+ contentType: "text",
49833
+ text: resolvedText
49834
+ });
49835
+ }
49836
+ for (const pr2 of proxyResults) {
49837
+ if (ctx.textProtocol) {
49838
+ coreMessages.push({
49839
+ id: `acp_loop_r${round}_marker_${pr2.callId}`,
49840
+ role: "user",
49841
+ contentType: "text",
49842
+ text: buildVisibilityMarker(pr2.name, pr2.result)
49843
+ });
49844
+ } else {
49845
+ coreMessages.push({
49846
+ id: `acp_loop_r${round}_asst_tc_${pr2.callId}`,
49847
+ role: "assistant",
49848
+ contentType: "tool-call",
49849
+ toolName: pr2.name,
49850
+ toolCallId: pr2.callId,
49851
+ text: pr2.arguments
49852
+ });
49853
+ coreMessages.push({
49854
+ id: `acp_loop_r${round}_tool_${pr2.callId}`,
49855
+ role: "tool",
49856
+ contentType: "tool-result",
49857
+ toolCallId: pr2.callId,
49858
+ text: pr2.result
49859
+ });
49860
+ }
49861
+ }
49862
+ if (!ctx.textProtocol) {
49863
+ const hidden = hideConsumedCompressCalls(ctx.session.state, coreMessages);
49864
+ if (hidden.hidden > 0) {
49865
+ ctx.log(`[acp-loop] round ${round} hideConsumed hid ${hidden.hidden} compress record(s)`);
49866
+ coreMessages.length = 0;
49867
+ coreMessages.push(...hidden.messages);
49868
+ }
49869
+ }
49870
+ }
49871
+ for (const tc of realToolCalls) {
49872
+ yield adapter.emitToolCall(tc);
49873
+ }
49874
+ const reRequest = proxyResults.length > 0 && realCalls === 0;
49875
+ if (!reRequest) {
49876
+ yield adapter.emitCompletion({ finishReason, usage });
49877
+ return;
49878
+ }
49879
+ if (round >= MAX_LOOP_ROUNDS) {
49880
+ ctx.log(`[acp-loop] round ${round} hit MAX_LOOP_ROUNDS; completing gracefully`);
49881
+ log("warn", `[acp-loop] loop limit (${MAX_LOOP_ROUNDS}) reached; completing gracefully`);
49882
+ yield adapter.emitCompletion({ finishReason: "length", usage });
49883
+ return;
49884
+ }
49885
+ ctx.log(`[acp-loop] round ${round} saw mutating proxy tool; re-requesting`);
49886
+ const newBody = adapter.buildRequest(coreMessages, systemPrompt, requestBody);
49887
+ if (process.env.ACP_DUMP_REQ !== "0" && ctx.debug) {
49888
+ try {
49889
+ const fs5 = await import("fs");
49890
+ const dumpDir = process.env.ACP_DUMP_DIR || `${process.env.HOME}/.local/state/billion-context/dumps`;
49891
+ fs5.mkdirSync(dumpDir, { recursive: true });
49892
+ const sid = ctx.session.id ?? "unknown";
49893
+ fs5.writeFileSync(`${dumpDir}/req-${Date.now()}-${sid}-REREQUEST.json`, JSON.stringify(newBody, null, 2));
49894
+ } catch {
49895
+ }
49896
+ }
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
+ });
49903
+ if (!resp.ok || !resp.body) {
49904
+ clearTimer();
49905
+ const errText = await resp.text().catch(() => "upstream error");
49906
+ ctx.log(`[acp-proxy: compress loop upstream error ${resp.status}: ${errText.slice(0, 200)}]`);
49907
+ log("error", `[acp-loop] upstream error ${resp.status}: ${errText.slice(0, 200)}`);
49908
+ yield adapter.emitError(`upstream error ${resp.status}: ${errText.slice(0, 200)}`);
49909
+ return;
49910
+ }
49911
+ currentUpstream = resp.body;
49912
+ if (activeClearTimer) activeClearTimer();
49913
+ activeClearTimer = clearTimer;
49914
+ }
49915
+ } finally {
49916
+ if (activeClearTimer) {
49917
+ activeClearTimer();
49918
+ activeClearTimer = null;
49919
+ }
49920
+ }
49921
+ }
49922
+
49923
+ // src/loop/adapter-responses.ts
49924
+ async function* iterSseEvents(stream2) {
49925
+ const reader = stream2.getReader();
49926
+ let buf = "";
49927
+ try {
49928
+ while (true) {
49929
+ let done;
49930
+ let value;
49931
+ try {
49932
+ ({ done, value } = await reader.read());
49933
+ } catch {
49934
+ break;
49935
+ }
49936
+ if (done) break;
49937
+ buf += new TextDecoder().decode(value, { stream: true });
49938
+ buf = buf.replace(/\r\n|\r/g, "\n");
49939
+ let idx;
49940
+ while ((idx = buf.indexOf("\n\n")) >= 0) {
49941
+ const raw = buf.slice(0, idx);
49942
+ buf = buf.slice(idx + 2);
49943
+ if (raw.trim().length > 0) yield raw;
49944
+ }
49945
+ }
49946
+ if (buf.trim().length > 0) yield buf;
49947
+ } finally {
49948
+ reader.releaseLock();
49949
+ }
49950
+ }
49951
+ function extractEventType2(rawEvent) {
49952
+ for (const l of rawEvent.split("\n")) {
49953
+ if (l.startsWith("event:")) return l.slice(6).trim();
49954
+ }
49955
+ return null;
49956
+ }
49957
+ function extractDataLine2(rawEvent) {
49958
+ const parts = [];
49959
+ for (const l of rawEvent.split("\n")) {
49960
+ if (l.startsWith("data:")) {
49961
+ let v2 = l.slice(5);
49962
+ if (v2.startsWith(" ")) v2 = v2.slice(1);
49963
+ parts.push(v2);
49964
+ }
49965
+ }
49966
+ return parts.length ? parts.join("\n") : null;
49967
+ }
49968
+ function buildMessageItemSequence2(itemId, outputIndex, text) {
49969
+ const item = { type: "message", id: itemId, role: "assistant", content: [] };
49970
+ const part = { type: "output_text", text: "" };
49971
+ const doneItem = {
49972
+ type: "message",
49973
+ id: itemId,
49974
+ role: "assistant",
49975
+ content: [{ type: "output_text", text }]
49976
+ };
49977
+ return Buffer.from(
49978
+ [
49979
+ `event: response.output_item.added
49980
+ data: ${JSON.stringify({ type: "response.output_item.added", output_index: outputIndex, item })}
49981
+
49982
+ `,
49983
+ `event: response.content_part.added
49984
+ data: ${JSON.stringify({ type: "response.content_part.added", item_id: itemId, output_index: outputIndex, part })}
49985
+
49986
+ `,
49987
+ `event: response.output_text.delta
49988
+ data: ${JSON.stringify({ type: "response.output_text.delta", item_id: itemId, output_index: outputIndex, delta: text })}
49989
+
49990
+ `,
49991
+ `event: response.output_text.done
49992
+ data: ${JSON.stringify({ type: "response.output_text.done", item_id: itemId, output_index: outputIndex, text })}
49993
+
49994
+ `,
49995
+ `event: response.content_part.done
49996
+ data: ${JSON.stringify({ type: "response.content_part.done", item_id: itemId, output_index: outputIndex, part: { type: "output_text", text } })}
49997
+
49998
+ `,
49999
+ `event: response.output_item.done
50000
+ data: ${JSON.stringify({ type: "response.output_item.done", output_index: outputIndex, item: doneItem })}
50001
+
50002
+ `
50003
+ ].join(""),
50004
+ "utf8"
50005
+ );
50006
+ }
50007
+ function buildFunctionCallEvents2(fc, itemId, outputIndex) {
50008
+ return Buffer.from(
50009
+ [
50010
+ `event: response.output_item.added
50011
+ data: ${JSON.stringify({
50012
+ type: "response.output_item.added",
50013
+ output_index: outputIndex,
50014
+ item: { type: "function_call", id: itemId, call_id: fc.callId, name: fc.name, arguments: "" }
50015
+ })}
50016
+
50017
+ `,
50018
+ `event: response.function_call_arguments.delta
50019
+ data: ${JSON.stringify({
50020
+ type: "response.function_call_arguments.delta",
50021
+ item_id: itemId,
50022
+ delta: fc.arguments
50023
+ })}
50024
+
50025
+ `,
50026
+ `event: response.function_call_arguments.done
50027
+ data: ${JSON.stringify({
50028
+ type: "response.function_call_arguments.done",
50029
+ item_id: itemId,
50030
+ arguments: fc.arguments
50031
+ })}
50032
+
50033
+ `,
50034
+ `event: response.output_item.done
50035
+ data: ${JSON.stringify({
50036
+ type: "response.output_item.done",
50037
+ output_index: outputIndex,
50038
+ item: { type: "function_call", id: itemId, call_id: fc.callId, name: fc.name, arguments: fc.arguments }
50039
+ })}
50040
+
50041
+ `
50042
+ ].join(""),
50043
+ "utf8"
50044
+ );
50045
+ }
50046
+ function buildCompleted2(responseObj) {
50047
+ const resp = responseObj ?? { id: `resp-proxy-${Date.now()}`, status: "completed", output: [] };
50048
+ return Buffer.from(
50049
+ `event: response.completed
50050
+ data: ${JSON.stringify({ type: "response.completed", response: resp })}
50051
+
50052
+ `,
50053
+ "utf8"
50054
+ );
50055
+ }
50056
+ function createResponsesAdapter(textProtocol, projection) {
50057
+ const suppressTextLifecycle = !!textProtocol;
50058
+ let outputIndex = 0;
50059
+ let responseObj = null;
50060
+ let terminalRaw = null;
50061
+ let terminalKind = null;
50062
+ return {
50063
+ buildRequest(coreMessages, systemPrompt, requestBody) {
50064
+ const customToolCallIds = /* @__PURE__ */ new Set();
50065
+ for (const m2 of coreMessages) {
50066
+ const bm = m2;
50067
+ const raw = bm?.rawResponsesItem;
50068
+ if (raw && (raw.type === "custom_tool_call" || raw.type === "custom_tool_call_output")) {
50069
+ const id = typeof raw.call_id === "string" ? raw.call_id : typeof raw.id === "string" ? raw.id : "";
50070
+ if (id) customToolCallIds.add(id);
50071
+ }
50072
+ }
50073
+ let inputItems;
50074
+ if (projection) {
50075
+ const rebuiltInput = patchResponsesInput(projection, coreMessages);
50076
+ inputItems = typeof rebuiltInput === "string" ? [{ type: "message", role: "user", content: rebuiltInput }] : rebuiltInput;
50077
+ } else {
50078
+ inputItems = coreToResponses(coreMessages, customToolCallIds);
50079
+ }
50080
+ const devParts = projection && projection.systemParts.length > 0 ? [...projection.systemParts, systemPrompt] : [systemPrompt];
50081
+ const withDev = injectResponsesDeveloperMessage(inputItems, devParts.join("\n\n---\n\n"));
50082
+ const rebuilt = { ...requestBody, input: withDev };
50083
+ delete rebuilt.previous_response_id;
50084
+ delete rebuilt.instructions;
50085
+ return rebuilt;
50086
+ },
50087
+ async *parseStream(upstream, round) {
50088
+ const pending = /* @__PURE__ */ new Map();
50089
+ for await (const eventStr of iterSseEvents(upstream)) {
50090
+ const type = extractEventType2(eventStr);
50091
+ const dataLine = extractDataLine2(eventStr);
50092
+ if (!type || !dataLine) continue;
50093
+ let obj;
50094
+ try {
50095
+ obj = JSON.parse(dataLine);
50096
+ } catch {
50097
+ continue;
50098
+ }
50099
+ const rawBuf = Buffer.from(eventStr + "\n\n", "utf8");
50100
+ if (round === 1 && typeof obj.output_index === "number") {
50101
+ outputIndex = Math.max(outputIndex, obj.output_index + 1);
50102
+ }
50103
+ if (type === "response.created" || type === "response.in_progress") {
50104
+ yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
50105
+ } else if (type === "response.output_item.added") {
50106
+ const item = obj.item;
50107
+ if (item?.type === "function_call") {
50108
+ const itemId = typeof item.id === "string" ? item.id : "";
50109
+ pending.set(itemId, {
50110
+ itemId,
50111
+ callId: typeof item.call_id === "string" ? item.call_id : "",
50112
+ name: typeof item.name === "string" ? item.name : "",
50113
+ arguments: ""
50114
+ });
50115
+ } else if (item?.type === "custom_tool_call") {
50116
+ yield { kind: "meta", chunk: rawBuf, firstRoundOnly: false };
50117
+ } else if (!suppressTextLifecycle) {
50118
+ yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
50119
+ }
50120
+ } else if (type === "response.content_part.added" || type === "response.content_part.done" || type === "response.output_text.done") {
50121
+ if (!suppressTextLifecycle) {
50122
+ yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
50123
+ }
50124
+ } else if (type === "response.output_text.delta") {
50125
+ const delta = typeof obj.delta === "string" ? obj.delta : "";
50126
+ if (delta.length > 0) {
50127
+ yield { kind: "text", delta, raw: rawBuf };
50128
+ }
50129
+ } else if (type === "response.function_call_arguments.delta") {
50130
+ const itemId = typeof obj.item_id === "string" ? obj.item_id : "";
50131
+ const delta = typeof obj.delta === "string" ? obj.delta : "";
50132
+ const fc = pending.get(itemId);
50133
+ if (fc) fc.arguments += delta;
50134
+ } else if (type === "response.function_call_arguments.done") {
50135
+ const itemId = typeof obj.item_id === "string" ? obj.item_id : "";
50136
+ const args = typeof obj.arguments === "string" ? obj.arguments : "";
50137
+ const fc = pending.get(itemId);
50138
+ if (fc && args) fc.arguments = args;
50139
+ } else if (type === "response.output_item.done") {
50140
+ const item = obj.item;
50141
+ if (item?.type === "function_call") {
50142
+ const itemId = typeof item.id === "string" ? item.id : "";
50143
+ const fc = pending.get(itemId);
50144
+ if (fc) {
50145
+ if (typeof item.arguments === "string" && item.arguments) fc.arguments = item.arguments;
50146
+ pending.delete(itemId);
50147
+ yield {
50148
+ kind: "tool_call",
50149
+ name: fc.name,
50150
+ callId: fc.callId,
50151
+ arguments: fc.arguments
50152
+ };
50153
+ }
50154
+ } else if (item?.type === "custom_tool_call") {
50155
+ yield { kind: "meta", chunk: rawBuf, firstRoundOnly: false };
50156
+ } else if (!suppressTextLifecycle) {
50157
+ yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
50158
+ }
50159
+ } else if (type === "response.completed") {
50160
+ responseObj = obj.response ?? null;
50161
+ terminalKind = "completed";
50162
+ terminalRaw = null;
50163
+ const respUsage = responseObj?.usage;
50164
+ const pd = respUsage?.input_tokens_details;
50165
+ yield {
50166
+ kind: "usage",
50167
+ inputTokens: typeof respUsage?.input_tokens === "number" ? respUsage.input_tokens : void 0,
50168
+ outputTokens: typeof respUsage?.output_tokens === "number" ? respUsage.output_tokens : void 0,
50169
+ cachedTokens: typeof pd?.cached_tokens === "number" ? pd.cached_tokens : void 0
50170
+ };
50171
+ yield { kind: "done", finishReason: "completed" };
50172
+ } else if (type === "response.incomplete") {
50173
+ terminalKind = "incomplete";
50174
+ terminalRaw = rawBuf;
50175
+ yield { kind: "done", finishReason: "incomplete" };
50176
+ } else if (type === "response.failed" || type === "response.error") {
50177
+ terminalKind = "failed";
50178
+ terminalRaw = rawBuf;
50179
+ yield { kind: "done", finishReason: "failed" };
50180
+ } else {
50181
+ yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
50182
+ }
50183
+ }
50184
+ if (!terminalKind) {
50185
+ yield { kind: "done", finishReason: "failed" };
50186
+ }
50187
+ },
50188
+ emitText(delta) {
50189
+ return buildMessageItemSequence2(`msg-proxy-${Date.now()}-${outputIndex}`, outputIndex++, delta);
50190
+ },
50191
+ emitToolCall(call) {
50192
+ const buf = buildFunctionCallEvents2(call, `fc-proxy-${Date.now()}-${outputIndex}`, outputIndex);
50193
+ outputIndex += 1;
50194
+ return buf;
50195
+ },
50196
+ emitMarker(toolName, result) {
50197
+ return buildMessageItemSequence2(
50198
+ `marker-${Date.now()}-${outputIndex}`,
50199
+ outputIndex++,
50200
+ buildVisibilityMarker(toolName, result)
50201
+ );
50202
+ },
50203
+ emitCompletion(opts) {
50204
+ if (terminalRaw && (terminalKind === "failed" || terminalKind === "incomplete")) {
50205
+ return terminalRaw;
50206
+ }
50207
+ if (!responseObj && opts?.finishReason === "failed") {
50208
+ const failed = {
50209
+ id: `resp-error-${Date.now()}`,
50210
+ status: "failed",
50211
+ error: { code: "server_error", message: "upstream returned no response" }
50212
+ };
50213
+ return Buffer.from(
50214
+ `event: response.failed
50215
+ data: ${JSON.stringify({ type: "response.failed", response: failed })}
50216
+
50217
+ `,
50218
+ "utf8"
50219
+ );
50220
+ }
50221
+ let resp = responseObj ? { ...responseObj } : { id: `resp-proxy-${Date.now()}`, status: "completed", output: [] };
50222
+ if (opts?.usage) {
50223
+ const usage = {
50224
+ ...typeof resp.usage === "object" ? resp.usage : {}
50225
+ };
50226
+ if (typeof opts.usage.inputTokens === "number") usage.input_tokens = opts.usage.inputTokens;
50227
+ if (typeof opts.usage.outputTokens === "number") usage.output_tokens = opts.usage.outputTokens;
50228
+ if (typeof opts.usage.cachedTokens === "number") {
50229
+ usage.input_tokens_details = {
50230
+ ...usage.input_tokens_details ?? {},
50231
+ cached_tokens: opts.usage.cachedTokens
50232
+ };
50233
+ }
50234
+ resp = { ...resp, usage };
50235
+ }
50236
+ return buildCompleted2(resp);
50237
+ },
50238
+ emitError(message) {
50239
+ const resp = {
50240
+ id: `resp-error-${Date.now()}`,
50241
+ status: "failed",
50242
+ error: { code: "server_error", message }
50243
+ };
50244
+ return Buffer.from(
50245
+ `event: response.failed
50246
+ data: ${JSON.stringify({ type: "response.failed", response: resp })}
50247
+
50248
+ `,
50249
+ "utf8"
50250
+ );
50251
+ },
50252
+ extractTextTriggers(text) {
50253
+ const calls = [];
50254
+ let clean = text;
50255
+ let hadTrigger = false;
50256
+ const triggers = [
50257
+ { name: "compress", open: ACP_TEXT_OPEN, close: ACP_TEXT_CLOSE, requirePayload: true },
50258
+ { name: "acp_status", open: ACP_STATUS_OPEN, close: ACP_STATUS_CLOSE, requirePayload: false },
50259
+ { name: "search_context", open: ACP_SEARCH_OPEN, close: ACP_SEARCH_CLOSE, requirePayload: true },
50260
+ { name: "decompress", open: ACP_DECOMPRESS_OPEN, close: ACP_DECOMPRESS_CLOSE, requirePayload: true }
50261
+ ];
50262
+ for (const t of triggers) {
50263
+ let start = clean.indexOf(t.open);
50264
+ while (start >= 0) {
50265
+ const end = clean.indexOf(t.close, start + t.open.length);
50266
+ if (end < 0) break;
50267
+ hadTrigger = true;
50268
+ const payload = clean.slice(start + t.open.length, end).trim();
50269
+ if (payload.length > 0 || !t.requirePayload) {
50270
+ const stamp = `${Date.now()}-${calls.length}`;
50271
+ calls.push({
50272
+ name: t.name,
50273
+ callId: `call_text_${stamp}`,
50274
+ arguments: payload.length > 0 ? payload : "{}"
50275
+ });
50276
+ }
50277
+ clean = clean.slice(0, start) + clean.slice(end + t.close.length);
50278
+ start = clean.indexOf(t.open);
50279
+ }
50280
+ }
50281
+ return { clean: hadTrigger ? clean : text, calls };
50282
+ }
50283
+ };
50284
+ }
50285
+
50286
+ // src/loop/adapter-openai.ts
50287
+ async function* iterSseChunks(stream2) {
50288
+ const reader = stream2.getReader();
50289
+ let buf = "";
50290
+ try {
50291
+ while (true) {
50292
+ let done;
50293
+ let value;
50294
+ try {
50295
+ ({ done, value } = await reader.read());
50296
+ } catch {
50297
+ break;
50298
+ }
50299
+ if (done) break;
50300
+ buf += new TextDecoder().decode(value, { stream: true });
50301
+ buf = buf.replace(/\r\n|\r/g, "\n");
50302
+ let idx;
50303
+ while ((idx = buf.indexOf("\n\n")) >= 0) {
50304
+ const raw = buf.slice(0, idx);
50305
+ buf = buf.slice(idx + 2);
50306
+ if (raw.trim().length > 0) yield raw;
50307
+ }
50308
+ }
50309
+ if (buf.trim().length > 0) yield buf;
50310
+ } finally {
50311
+ reader.releaseLock();
50312
+ }
50313
+ }
50314
+ function createOpenaiAdapter(requestBody) {
50315
+ const model = requestBody.model ?? "unknown";
50316
+ let responseId = `chatcmpl-proxy-${Date.now()}`;
50317
+ let toolIndex = 0;
50318
+ const makeBase = () => ({
50319
+ id: responseId,
50320
+ object: "chat.completion.chunk",
50321
+ created: Date.now(),
50322
+ model
50323
+ });
50324
+ const buildContent = (content) => Buffer.from(
50325
+ `data: ${JSON.stringify({
50326
+ id: responseId,
50327
+ object: "chat.completion.chunk",
50328
+ created: Date.now(),
50329
+ model,
50330
+ choices: [{ index: 0, delta: { content }, finish_reason: null }]
50331
+ })}
50332
+
50333
+ `,
50334
+ "utf8"
50335
+ );
50336
+ const buildToolCall = (call) => {
50337
+ const idx = toolIndex++;
50338
+ return Buffer.from(
50339
+ `data: ${JSON.stringify({
50340
+ ...makeBase(),
50341
+ choices: [{
50342
+ index: 0,
50343
+ delta: {
50344
+ tool_calls: [{
50345
+ index: idx,
50346
+ id: call.callId,
50347
+ type: "function",
50348
+ function: { name: call.name, arguments: call.arguments }
50349
+ }]
50350
+ },
50351
+ finish_reason: null
50352
+ }]
50353
+ })}
50354
+
50355
+ `,
50356
+ "utf8"
50357
+ );
50358
+ };
50359
+ const buildFinish = (finishReason, usage) => Buffer.from(
50360
+ `data: ${JSON.stringify({
50361
+ ...makeBase(),
50362
+ choices: [{ index: 0, delta: {}, finish_reason: finishReason }],
50363
+ ...usage ? { usage } : {}
50364
+ })}
50365
+
50366
+ `,
50367
+ "utf8"
50368
+ );
50369
+ return {
50370
+ buildRequest(coreMessages, systemPrompt, body) {
50371
+ const messages = coreToOpenai(coreMessages);
50372
+ const withSys = injectOpenaiSystem(messages, [systemPrompt]);
50373
+ return { ...body, messages: withSys };
50374
+ },
50375
+ async *parseStream(upstream, _round) {
50376
+ const pending = /* @__PURE__ */ new Map();
50377
+ for await (const eventStr of iterSseChunks(upstream)) {
50378
+ const dataLine = eventStr.split("\n").find((l) => l.startsWith("data:"));
50379
+ if (!dataLine) continue;
50380
+ const jsonStr = dataLine.slice(5).trim();
50381
+ if (jsonStr === "[DONE]") {
50382
+ for (const [, tc] of pending) {
50383
+ if (tc.name.length > 0 || tc.id.length > 0) {
50384
+ yield {
50385
+ kind: "tool_call",
50386
+ name: tc.name,
50387
+ callId: tc.id,
50388
+ arguments: tc.arguments
50389
+ };
50390
+ }
50391
+ }
50392
+ pending.clear();
50393
+ yield { kind: "done", finishReason: "stop" };
50394
+ continue;
50395
+ }
50396
+ let parsed;
50397
+ try {
50398
+ parsed = JSON.parse(jsonStr);
50399
+ } catch {
50400
+ continue;
50401
+ }
50402
+ const rawBuf = Buffer.from(eventStr + "\n\n", "utf8");
50403
+ const choices = parsed.choices;
50404
+ const choice = choices?.[0];
50405
+ if (!choice) {
50406
+ if (parsed.usage) {
50407
+ const u2 = parsed.usage;
50408
+ const pd = u2.prompt_tokens_details;
50409
+ yield {
50410
+ kind: "usage",
50411
+ inputTokens: typeof u2.prompt_tokens === "number" ? u2.prompt_tokens : void 0,
50412
+ outputTokens: typeof u2.completion_tokens === "number" ? u2.completion_tokens : void 0,
50413
+ cachedTokens: typeof pd?.cached_tokens === "number" ? pd.cached_tokens : void 0
50414
+ };
50415
+ }
50416
+ continue;
50417
+ }
50418
+ const delta = choice.delta;
50419
+ const finishReason = typeof choice.finish_reason === "string" ? choice.finish_reason : void 0;
50420
+ if (finishReason) {
50421
+ for (const [, tc] of pending) {
50422
+ if (tc.name.length > 0 || tc.id.length > 0) {
50423
+ yield {
50424
+ kind: "tool_call",
50425
+ name: tc.name,
50426
+ callId: tc.id,
50427
+ arguments: tc.arguments
50428
+ };
50429
+ }
50430
+ }
50431
+ pending.clear();
50432
+ const u2 = parsed.usage;
50433
+ const pd = u2?.prompt_tokens_details;
50434
+ yield {
50435
+ kind: "usage",
50436
+ inputTokens: typeof u2?.prompt_tokens === "number" ? u2.prompt_tokens : void 0,
50437
+ outputTokens: typeof u2?.completion_tokens === "number" ? u2.completion_tokens : void 0,
50438
+ cachedTokens: typeof pd?.cached_tokens === "number" ? pd.cached_tokens : void 0
50439
+ };
50440
+ yield { kind: "done", finishReason };
50441
+ }
50442
+ if (!delta) continue;
50443
+ if (delta.tool_calls) {
50444
+ const tcs = delta.tool_calls;
50445
+ for (const tc of tcs) {
50446
+ const idx = typeof tc.index === "number" ? tc.index : 0;
50447
+ const fn = tc.function;
50448
+ const name = typeof fn?.name === "string" ? fn.name : "";
50449
+ const id = typeof tc.id === "string" ? tc.id : "";
50450
+ const args = typeof fn?.arguments === "string" ? fn.arguments : "";
50451
+ let buf = pending.get(idx);
50452
+ if (!buf) {
50453
+ buf = { index: idx, id, name, arguments: args };
50454
+ pending.set(idx, buf);
50455
+ } else {
50456
+ if (id) buf.id = id;
50457
+ if (name) buf.name = name;
50458
+ buf.arguments += args;
50459
+ }
50460
+ }
50461
+ if (typeof delta.content === "string" && delta.content.length > 0) {
50462
+ yield { kind: "text", delta: delta.content };
50463
+ }
50464
+ continue;
50465
+ }
50466
+ if (typeof delta.content === "string" && delta.content.length > 0) {
50467
+ yield { kind: "text", delta: delta.content, raw: rawBuf };
50468
+ } else if (delta.role || Object.keys(delta).length === 0 && !finishReason) {
50469
+ yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
50470
+ }
50471
+ }
50472
+ },
50473
+ emitText(delta) {
50474
+ return buildContent(delta);
50475
+ },
50476
+ emitToolCall(call) {
50477
+ return buildToolCall(call);
50478
+ },
50479
+ emitMarker(toolName, result) {
50480
+ return buildContent(buildVisibilityMarker(toolName, result));
50481
+ },
50482
+ emitCompletion(opts) {
50483
+ const finishReason = opts?.finishReason ?? "stop";
50484
+ const usage = opts?.usage ? {
50485
+ prompt_tokens: opts.usage.inputTokens,
50486
+ completion_tokens: opts.usage.outputTokens,
50487
+ ...typeof opts.usage.cachedTokens === "number" ? { prompt_tokens_details: { cached_tokens: opts.usage.cachedTokens } } : {}
50488
+ } : null;
50489
+ return Buffer.concat([buildFinish(finishReason, usage), Buffer.from("data: [DONE]\n\n", "utf8")]);
50490
+ },
50491
+ emitError(message) {
50492
+ return Buffer.concat([
50493
+ buildContent(`
50494
+ [acp-proxy: ${message}]
50495
+ `),
50496
+ buildFinish("stop", null),
50497
+ Buffer.from("data: [DONE]\n\n", "utf8")
50498
+ ]);
50499
+ }
50500
+ };
50501
+ }
50502
+
50503
+ // src/loop/adapter-anthropic.ts
50504
+ async function* iterSseEvents2(stream2) {
50505
+ const reader = stream2.getReader();
50506
+ let buf = "";
50507
+ try {
50508
+ while (true) {
50509
+ let done;
50510
+ let value;
50511
+ try {
50512
+ ({ done, value } = await reader.read());
50513
+ } catch {
50514
+ break;
50515
+ }
50516
+ if (done) break;
50517
+ buf += new TextDecoder().decode(value, { stream: true });
50518
+ buf = buf.replace(/\r\n|\r/g, "\n");
50519
+ let idx;
50520
+ while ((idx = buf.indexOf("\n\n")) >= 0) {
50521
+ const raw = buf.slice(0, idx);
50522
+ buf = buf.slice(idx + 2);
50523
+ if (raw.trim().length > 0) yield raw;
50524
+ }
50525
+ }
50526
+ if (buf.trim().length > 0) yield buf;
50527
+ } finally {
50528
+ reader.releaseLock();
50529
+ }
50530
+ }
50531
+ function parseAnthropicSse2(eventStr) {
50532
+ const lines = eventStr.split("\n");
50533
+ let type = "";
50534
+ const dataLines = [];
50535
+ for (const l of lines) {
50536
+ if (l.startsWith("event:")) type = l.slice(6).trim();
50537
+ else if (l.startsWith("data:")) dataLines.push(l.slice(5).replace(/^ /, ""));
50538
+ }
50539
+ if (!type) return null;
50540
+ const jsonStr = dataLines.join("\n").trim();
50541
+ if (!jsonStr) return { type, data: {} };
50542
+ try {
50543
+ return { type, data: JSON.parse(jsonStr) };
50544
+ } catch {
50545
+ return { type, data: {} };
50546
+ }
50547
+ }
50548
+ function remapIndexInEvent(eventStr, newIndex) {
50549
+ const lines = eventStr.split("\n");
50550
+ const rebuilt = [];
50551
+ let touched = false;
50552
+ for (const l of lines) {
50553
+ if (!touched && l.startsWith("data:")) {
50554
+ const jsonStr = l.slice(5).replace(/^ /, "");
50555
+ try {
50556
+ const obj = JSON.parse(jsonStr);
50557
+ if (typeof obj === "object" && obj !== null && typeof obj.index === "number") {
50558
+ obj.index = newIndex;
50559
+ rebuilt.push(`data: ${JSON.stringify(obj)}`);
50560
+ touched = true;
50561
+ continue;
50562
+ }
50563
+ } catch {
50564
+ }
50565
+ }
50566
+ rebuilt.push(l);
50567
+ }
50568
+ return Buffer.from(rebuilt.join("\n") + "\n\n", "utf8");
50569
+ }
50570
+ function createAnthropicAdapter(requestBody, originalSystem) {
50571
+ const model = requestBody.model ?? void 0;
50572
+ let messageId;
50573
+ let clientIndex = 0;
50574
+ const buildTextBlock = (index, text) => Buffer.from(
50575
+ `event: content_block_start
50576
+ data: ${JSON.stringify({ type: "content_block_start", index, content_block: { type: "text", text: "" } })}
50577
+
50578
+ event: content_block_delta
50579
+ data: ${JSON.stringify({ type: "content_block_delta", index, delta: { type: "text_delta", text } })}
50580
+
50581
+ event: content_block_stop
50582
+ data: ${JSON.stringify({ type: "content_block_stop", index })}
50583
+
50584
+ `,
50585
+ "utf8"
50586
+ );
50587
+ const buildToolUseBlock = (index, call) => Buffer.from(
50588
+ `event: content_block_start
50589
+ data: ${JSON.stringify({ type: "content_block_start", index, content_block: { type: "tool_use", id: call.callId, name: call.name, input: {} } })}
50590
+
50591
+ event: content_block_delta
50592
+ data: ${JSON.stringify({ type: "content_block_delta", index, delta: { type: "input_json_delta", partial_json: call.arguments } })}
50593
+
50594
+ event: content_block_stop
50595
+ data: ${JSON.stringify({ type: "content_block_stop", index })}
50596
+
50597
+ `,
50598
+ "utf8"
50599
+ );
50600
+ const buildTerminal = (stopReason, outputTokens, inputTokens, cachedTokens) => {
50601
+ const usage = {
50602
+ input_tokens: inputTokens,
50603
+ output_tokens: outputTokens,
50604
+ cache_read_input_tokens: cachedTokens
50605
+ };
50606
+ const extra = {};
50607
+ if (messageId) extra.id = messageId;
50608
+ if (model) extra.model = model;
50609
+ return Buffer.from(
50610
+ `event: message_delta
50611
+ data: ${JSON.stringify({ type: "message_delta", delta: { stop_reason: stopReason, stop_sequence: null }, usage, ...extra })}
50612
+
50613
+ event: message_stop
50614
+ data: ${JSON.stringify({ type: "message_stop" })}
50615
+
50616
+ `,
50617
+ "utf8"
50618
+ );
50619
+ };
50620
+ return {
50621
+ buildRequest(coreMessages, systemPrompt, body) {
50622
+ const messages = coreToAnthropic(coreMessages);
50623
+ const baseText = originalSystem !== void 0 ? extractSystem(originalSystem) : "";
50624
+ const full = baseText ? `${baseText}
50625
+
50626
+ ---
50627
+
50628
+ ${systemPrompt}` : systemPrompt;
50629
+ const system = originalSystem !== void 0 ? buildSystem(full, originalSystem) : full;
50630
+ return { ...body, system, messages };
50631
+ },
50632
+ async *parseStream(upstream, round) {
50633
+ const pending = /* @__PURE__ */ new Map();
50634
+ let roundInput;
50635
+ let roundCached;
50636
+ let roundOutput;
50637
+ let stopReason;
50638
+ let usageYielded = false;
50639
+ const indexMap = /* @__PURE__ */ new Map();
50640
+ for await (const eventStr of iterSseEvents2(upstream)) {
50641
+ const parsed = parseAnthropicSse2(eventStr);
50642
+ if (!parsed) continue;
50643
+ const { type, data } = parsed;
50644
+ const rawBuf = Buffer.from(eventStr + "\n\n", "utf8");
50645
+ if (type === "message_start") {
50646
+ const msg2 = data.message ?? {};
50647
+ if (typeof msg2.id === "string" && !messageId) messageId = msg2.id;
50648
+ const u2 = msg2.usage ?? {};
50649
+ if (typeof u2.input_tokens === "number") roundInput = u2.input_tokens;
50650
+ if (typeof u2.cache_read_input_tokens === "number") roundCached = u2.cache_read_input_tokens;
50651
+ if (round === 1) {
50652
+ yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
50653
+ }
50654
+ } else if (type === "ping") {
50655
+ yield { kind: "meta", chunk: rawBuf };
50656
+ } else if (type === "content_block_start") {
50657
+ const upstreamIndex = data.index ?? 0;
50658
+ const block = data.content_block ?? {};
50659
+ if (block.type === "tool_use") {
50660
+ const name = typeof block.name === "string" ? block.name : "";
50661
+ const id = typeof block.id === "string" ? block.id : `toolu_${upstreamIndex}`;
50662
+ pending.set(upstreamIndex, { id, name, json: "" });
50663
+ } else if (round === 1) {
50664
+ const ci2 = clientIndex++;
50665
+ indexMap.set(upstreamIndex, ci2);
50666
+ yield { kind: "meta", chunk: remapIndexInEvent(eventStr, ci2), firstRoundOnly: true };
50667
+ }
50668
+ } else if (type === "content_block_delta") {
50669
+ const upstreamIndex = data.index ?? 0;
50670
+ const delta = data.delta ?? {};
50671
+ if (pending.has(upstreamIndex)) {
50672
+ if (delta.type === "input_json_delta" && typeof delta.partial_json === "string") {
50673
+ pending.get(upstreamIndex).json += delta.partial_json;
50674
+ }
50675
+ } else if (delta.type === "text_delta" && typeof delta.text === "string") {
50676
+ if (round === 1) {
50677
+ const ci2 = indexMap.get(upstreamIndex) ?? upstreamIndex;
50678
+ yield { kind: "text", delta: delta.text, raw: remapIndexInEvent(eventStr, ci2) };
50679
+ } else {
50680
+ yield { kind: "text", delta: delta.text };
50681
+ }
50682
+ } else if (round === 1) {
50683
+ const ci2 = indexMap.get(upstreamIndex) ?? upstreamIndex;
50684
+ yield { kind: "meta", chunk: remapIndexInEvent(eventStr, ci2), firstRoundOnly: true };
50685
+ }
50686
+ } else if (type === "content_block_stop") {
50687
+ const upstreamIndex = data.index ?? 0;
50688
+ const tb = pending.get(upstreamIndex);
50689
+ if (tb) {
50690
+ pending.delete(upstreamIndex);
50691
+ yield {
50692
+ kind: "tool_call",
50693
+ name: tb.name,
50694
+ callId: tb.id,
50695
+ arguments: tb.json
50696
+ };
50697
+ } else if (round === 1) {
50698
+ const ci2 = indexMap.get(upstreamIndex) ?? upstreamIndex;
50699
+ yield { kind: "meta", chunk: remapIndexInEvent(eventStr, ci2), firstRoundOnly: true };
50700
+ }
50701
+ } else if (type === "message_delta") {
50702
+ const u2 = data.usage ?? {};
50703
+ if (typeof u2.output_tokens === "number") roundOutput = u2.output_tokens;
50704
+ if (typeof u2.input_tokens === "number") roundInput = u2.input_tokens;
50705
+ if (typeof u2.cache_read_input_tokens === "number") roundCached = u2.cache_read_input_tokens;
50706
+ const d = data.delta ?? {};
50707
+ if (typeof d.stop_reason === "string") stopReason = d.stop_reason;
50708
+ if (!usageYielded) {
50709
+ usageYielded = true;
50710
+ yield {
50711
+ kind: "usage",
50712
+ inputTokens: roundInput,
50713
+ outputTokens: roundOutput,
50714
+ cachedTokens: roundCached
50715
+ };
50716
+ }
50717
+ yield { kind: "done", finishReason: stopReason };
50718
+ } else if (type === "message_stop") {
50719
+ if (!usageYielded) {
50720
+ usageYielded = true;
50721
+ yield {
50722
+ kind: "usage",
50723
+ inputTokens: roundInput,
50724
+ outputTokens: roundOutput,
50725
+ cachedTokens: roundCached
50726
+ };
50727
+ }
50728
+ yield { kind: "done", finishReason: stopReason ?? "end_turn" };
50729
+ } else if (round === 1) {
50730
+ yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
50731
+ }
50732
+ }
50733
+ },
50734
+ emitText(delta) {
50735
+ return buildTextBlock(clientIndex++, delta);
50736
+ },
50737
+ emitToolCall(call) {
50738
+ return buildToolUseBlock(clientIndex++, call);
50739
+ },
50740
+ emitMarker(toolName, result) {
50741
+ return buildTextBlock(clientIndex++, buildVisibilityMarker(toolName, result));
50742
+ },
50743
+ emitCompletion(opts) {
50744
+ const stopReason = opts?.finishReason ?? "end_turn";
50745
+ return buildTerminal(
50746
+ stopReason,
50747
+ opts?.usage?.outputTokens ?? 0,
50748
+ opts?.usage?.inputTokens ?? 0,
50749
+ opts?.usage?.cachedTokens ?? 0
50750
+ );
50751
+ },
50752
+ emitError(message) {
50753
+ const errBlock = buildTextBlock(clientIndex++, `
50754
+ [acp-proxy: ${message}]
50755
+ `);
50756
+ return Buffer.concat([errBlock, buildTerminal("end_turn", 0, 0, 0)]);
50757
+ }
50758
+ };
50759
+ }
50760
+
50761
+ // src/loop/index.ts
50762
+ function pickAdapter(protocol, requestBody, textProtocol, responsesProjection, anthropicSystem) {
50763
+ if (protocol === "responses") return createResponsesAdapter(textProtocol, responsesProjection);
50764
+ if (protocol === "openai") return createOpenaiAdapter(requestBody);
50765
+ if (protocol === "anthropic") return createAnthropicAdapter(requestBody, anthropicSystem);
50766
+ throw new Error(`[acp-loop] unknown protocol: ${protocol}`);
50767
+ }
50768
+
49535
50769
  // src/stream-openai.ts
49536
50770
  function rewriteOpenaiJsonResponse(body, ctx) {
49537
50771
  if (!body || typeof body !== "object") return body;
@@ -49668,7 +50902,7 @@ function extractKey(headers) {
49668
50902
  return "(no-key)";
49669
50903
  }
49670
50904
  function clientConversationHeader(headers) {
49671
- const names = ["x-session-affinity", "x-acp-session", "x-session-id", "x-opencode-session", "session-id", "session_id"];
50905
+ const names = ["x-claude-code-session-id", "x-session-affinity", "x-acp-session", "x-session-id", "x-opencode-session", "session-id", "session_id"];
49672
50906
  for (const name of names) {
49673
50907
  const v2 = headers[name];
49674
50908
  if (typeof v2 === "string" && v2.trim().length > 0) return v2.trim();
@@ -50830,11 +52064,24 @@ async function handle(req, res, opts, core, config, log2) {
50830
52064
  return;
50831
52065
  }
50832
52066
  let bodyBuffer;
52067
+ let urlPath;
52068
+ let responsesCompact;
52069
+ let route;
52070
+ let upstreamOrigin;
52071
+ let protocol;
50833
52072
  try {
50834
52073
  bodyBuffer = await readBody(req);
50835
- const decoded = await decodeRequestBody(headerValue(req, "content-encoding"), bodyBuffer, MAX_REQUEST_BYTES);
50836
- bodyBuffer = decoded.body;
50837
- if (decoded.decoded) delete req.headers["content-encoding"];
52074
+ const url = req.url ?? "";
52075
+ urlPath = url.split("?", 2)[0];
52076
+ responsesCompact = urlPath.endsWith("/responses/compact");
52077
+ route = resolveUpstream(opts, req.url ?? "", req);
52078
+ upstreamOrigin = route ? route.upstream : opts.upstream;
52079
+ protocol = route?.explicitProtocol ?? (req.method === "POST" && bodyBuffer.length > 0 ? urlPath.endsWith("/chat/completions") ? "openai" : urlPath.endsWith("/v1/messages") || urlPath.endsWith("/messages") ? "anthropic" : urlPath.endsWith("/responses") || responsesCompact ? "responses" : null : null);
52080
+ if (protocol !== null && bodyBuffer.length > 0) {
52081
+ const decoded = await decodeRequestBody(headerValue(req, "content-encoding"), bodyBuffer, MAX_REQUEST_BYTES);
52082
+ bodyBuffer = decoded.body;
52083
+ if (decoded.decoded) delete req.headers["content-encoding"];
52084
+ }
50838
52085
  } catch (err2) {
50839
52086
  if (err2 instanceof BodyTooLargeError) {
50840
52087
  log2("warn", `413: request body exceeds ${err2.limit} bytes`);
@@ -50847,13 +52094,7 @@ async function handle(req, res, opts, core, config, log2) {
50847
52094
  res.end(JSON.stringify({ error: { type: "invalid_request", message: String(err2) } }));
50848
52095
  return;
50849
52096
  }
50850
- const url = req.url ?? "";
50851
- const urlPath = url.split("?", 2)[0];
50852
- const responsesCompact = urlPath.endsWith("/responses/compact");
50853
52097
  const countTokens = isCountTokensRequest(req.method ?? "GET", urlPath, bodyBuffer.length > 0);
50854
- const route = resolveUpstream(opts, req.url ?? "", req);
50855
- const upstreamOrigin = route ? route.upstream : opts.upstream;
50856
- const protocol = route?.explicitProtocol ?? (req.method === "POST" && bodyBuffer.length > 0 ? urlPath.endsWith("/chat/completions") ? "openai" : urlPath.endsWith("/v1/messages") || urlPath.endsWith("/messages") ? "anthropic" : urlPath.endsWith("/responses") || responsesCompact ? "responses" : null : null);
50857
52098
  let parsed = null;
50858
52099
  if (protocol && bodyBuffer.length > 0) {
50859
52100
  try {
@@ -50910,7 +52151,7 @@ async function handle(req, res, opts, core, config, log2) {
50910
52151
  }
50911
52152
  if (!prepared) {
50912
52153
  if (protocol === null && !opts.passthrough) {
50913
- log2("warn", `unrecognized path ${url} \u2014 not a known protocol (/chat/completions, /v1/messages, /responses, /responses/compact); forwarding unchanged`);
52154
+ log2("warn", `unrecognized path ${req.url ?? ""} \u2014 not a known protocol (/chat/completions, /v1/messages, /responses, /responses/compact); forwarding unchanged`);
50914
52155
  }
50915
52156
  await forward(req, res, opts, bodyBuffer, null, core, reqConfig, log2, route, void 0);
50916
52157
  }
@@ -50985,7 +52226,7 @@ function prepareAnthropic(parsed, req, opts, core, config, log2, session) {
50985
52226
  }
50986
52227
  const rebuilt = { ...parsed, messages: rebuiltMessages, system: systemOut, tools: toolsOut };
50987
52228
  markDirty(session);
50988
- return { body: JSON.stringify(rebuilt), session, processedMessages, originalMessages, protocol: "anthropic", stream: stream2, compressInjected: opts.compress.injectTool };
52229
+ return { body: JSON.stringify(rebuilt), session, processedMessages, originalMessages, anthropicSystem: parsed.system, protocol: "anthropic", stream: stream2, compressInjected: opts.compress.injectTool };
50989
52230
  }
50990
52231
  function prepareOpenai(parsed, req, opts, core, config, log2, session) {
50991
52232
  const sessionId = session.id;
@@ -51049,12 +52290,14 @@ function prepareResponses(parsed, req, opts, core, config, log2, session, identi
51049
52290
  }
51050
52291
  let processedMessages = [];
51051
52292
  let originalMessages = [];
52293
+ let responsesProjection;
51052
52294
  let rebuiltInput = parsed.input;
51053
52295
  let toolsOut = parsed.tools;
51054
52296
  const shouldInject = opts.compress.injectTool;
51055
52297
  const responsesTextProtocol = FORCE_TEXT_PROTOCOL || isChatGptCodexUpstream(session.meta.upstreamOrigin) || isCodexResponsesLite(req.headers, parsed);
51056
52298
  try {
51057
52299
  const projection = responsesToCore(parsed);
52300
+ responsesProjection = projection;
51058
52301
  const { msgs } = projection;
51059
52302
  originalMessages = msgs;
51060
52303
  if (process.env.ACP_DEBUG) {
@@ -51120,6 +52363,7 @@ function prepareResponses(parsed, req, opts, core, config, log2, session, identi
51120
52363
  session,
51121
52364
  processedMessages,
51122
52365
  originalMessages,
52366
+ responsesProjection,
51123
52367
  protocol: "responses",
51124
52368
  stream: stream2,
51125
52369
  compressInjected: shouldInject,
@@ -51258,9 +52502,20 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
51258
52502
  return fn?.name ?? t.name ?? "?";
51259
52503
  });
51260
52504
  log2("info", `[debug] tools=[${toolNames.join(",")}] msgs=${parsed.messages?.length ?? 0} stream=${parsed.stream ?? false} system_len=${JSON.stringify(parsed.messages?.find((m2) => m2.role === "system")?.content ?? "").length}`);
51261
- if (process.env.ACP_DUMP_REQ === "1") {
51262
- const out = `${tmpdir2()}/acp-proxy-debug-req-${Date.now()}.json`;
51263
- fs3.writeFileSync(out, body.slice(0, 5e4));
52505
+ if (process.env.ACP_DUMP_REQ !== "0") {
52506
+ const dumpDir = process.env.ACP_DUMP_DIR || `${stateDir()}/dumps`;
52507
+ try {
52508
+ fs3.mkdirSync(dumpDir, { recursive: true });
52509
+ } catch {
52510
+ }
52511
+ const sid = prepared?.session.id ?? "unknown";
52512
+ const out = `${dumpDir}/req-${Date.now()}-${sid}.json`;
52513
+ try {
52514
+ const pretty = JSON.stringify(JSON.parse(body), null, 2);
52515
+ fs3.writeFileSync(out, pretty);
52516
+ } catch {
52517
+ fs3.writeFileSync(out, body);
52518
+ }
51264
52519
  log2("info", `[debug] forwarded body written to ${out}`);
51265
52520
  }
51266
52521
  } catch {
@@ -51276,6 +52531,13 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
51276
52531
  headers["x-session-id"] = affinity;
51277
52532
  }
51278
52533
  const proxyUrl = resolveProxy(opts.routes, opts.proxy, route?.rewrittenUrl ?? upstreamUrl, opts.proxyFallback);
52534
+ if (opts.debug) {
52535
+ const hdrLog = {};
52536
+ for (const [hk, hv] of Object.entries(headers)) {
52537
+ if (typeof hv === "string") hdrLog[hk] = hv.length > 200 ? hv.slice(0, 200) + "..." : hv;
52538
+ }
52539
+ log2("info", `[${prepared?.session.id ?? "unknown"}] \u2192 upstream headers: ${JSON.stringify(hdrLog)}`);
52540
+ }
51279
52541
  const dispatcher = proxyDispatcher(proxyUrl);
51280
52542
  const init = {
51281
52543
  method: req.method ?? "GET",
@@ -51297,6 +52559,14 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
51297
52559
  if (UPSTREAM_HOP_HEADERS.has(k2.toLowerCase())) return;
51298
52560
  respHeaders[k2] = v2;
51299
52561
  });
52562
+ if (opts.debug) {
52563
+ const respLog = {};
52564
+ upstream.headers.forEach((v2, k2) => {
52565
+ if (UPSTREAM_HOP_HEADERS.has(k2.toLowerCase())) return;
52566
+ respLog[k2] = v2.length > 300 ? v2.slice(0, 300) + "..." : v2;
52567
+ });
52568
+ log2("info", `[${prepared?.session.id ?? "unknown"}] \u2190 upstream response headers: ${JSON.stringify(respLog)}`);
52569
+ }
51300
52570
  if (!upstream.ok) {
51301
52571
  res.writeHead(upstream.status, respHeaders);
51302
52572
  if (upstream.body) await pipeThrough(upstream.body, res);
@@ -51340,7 +52610,37 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
51340
52610
  dumpRaw = dumpStreamToFile(b2, opts.dumpSse, `${Date.now()}-${prepared.session.id}-raw.sse`);
51341
52611
  }
51342
52612
  try {
51343
- if (prepared.protocol === "openai") {
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") {
51344
52644
  const parsedReq = JSON.parse(typeof body === "string" ? body : body.toString("utf8"));
51345
52645
  const reqHeaders = {};
51346
52646
  for (const [k2, v2] of Object.entries(headers)) {