billion-context-omp 0.2.8 → 0.3.0

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
@@ -395,8 +395,13 @@ function resolveBoundaries(input) {
395
395
  input.messages.forEach(
396
396
  (message, index) => indexByRawId.set(message.id, index)
397
397
  );
398
- let startIndex = resolveAnchorIndex(start, input.state, indexByRawId, "start");
399
- let endIndex = resolveAnchorIndex(end, input.state, indexByRawId, "end");
398
+ let snappedBoundaries = [];
399
+ const startAnchor = resolveAnchorIndex(start, input.state, indexByRawId, "start");
400
+ if (startAnchor.snapped) snappedBoundaries.push(startAnchor.snapped);
401
+ const endAnchor = resolveAnchorIndex(end, input.state, indexByRawId, "end");
402
+ if (endAnchor.snapped) snappedBoundaries.push(endAnchor.snapped);
403
+ let startIndex = startAnchor.index;
404
+ let endIndex = endAnchor.index;
400
405
  if (startIndex > endIndex) {
401
406
  [startIndex, endIndex] = [endIndex, startIndex];
402
407
  }
@@ -424,7 +429,8 @@ function resolveBoundaries(input) {
424
429
  messageIds,
425
430
  nestedBlockIds,
426
431
  boundaryKind,
427
- protectedGaps
432
+ protectedGaps,
433
+ snappedBoundaries
428
434
  };
429
435
  }
430
436
  function resolveAnchorIndex(boundary, state, indexByRawId, endpoint) {
@@ -439,14 +445,21 @@ function resolveAnchorIndex(boundary, state, indexByRawId, endpoint) {
439
445
  );
440
446
  }
441
447
  const index = indexByRawId.get(rawId);
442
- if (index === void 0) {
443
- throw new BoundaryNotFoundError(
444
- "consumed",
445
- endpoint,
446
- `${label}="${boundary.raw}" not found in visible context (likely consumed by an existing block).`
447
- );
448
+ if (index !== void 0) {
449
+ return { index, snapped: null };
448
450
  }
449
- return index;
451
+ const owner2 = activeOwnerAnchor(state, [rawId], indexByRawId);
452
+ if (owner2 !== null) {
453
+ return {
454
+ index: owner2,
455
+ snapped: `${label}="${boundary.raw}" refers to a message already compressed into an active block \u2014 anchored to that block's summary instead.`
456
+ };
457
+ }
458
+ throw new BoundaryNotFoundError(
459
+ "consumed",
460
+ endpoint,
461
+ `${label}="${boundary.raw}" not found in visible context (likely consumed by an existing block).`
462
+ );
450
463
  }
451
464
  const block = blockById(state, `b${boundary.numericId}`);
452
465
  if (!block) {
@@ -456,6 +469,19 @@ function resolveAnchorIndex(boundary, state, indexByRawId, endpoint) {
456
469
  `${label}="b${boundary.numericId}" does not exist in this session (typo or wrong session) \u2014 run acp_status for current refs.`
457
470
  );
458
471
  }
472
+ if (block.active) {
473
+ const anchor = earliestIndexOfIds(block.effectiveMessageIds, indexByRawId);
474
+ if (anchor !== null) {
475
+ return { index: anchor, snapped: null };
476
+ }
477
+ }
478
+ const owner = activeOwnerAnchor(state, block.effectiveMessageIds, indexByRawId);
479
+ if (owner !== null) {
480
+ return {
481
+ index: owner,
482
+ snapped: `${label}="b${boundary.numericId}" was consumed by a higher-tier block \u2014 anchored to the active block covering its content instead.`
483
+ };
484
+ }
459
485
  if (!block.active) {
460
486
  throw new BoundaryNotFoundError(
461
487
  "consumed",
@@ -463,15 +489,26 @@ function resolveAnchorIndex(boundary, state, indexByRawId, endpoint) {
463
489
  `${label}="b${boundary.numericId}" not found in visible context (block distilled/consumed by a higher-tier block).`
464
490
  );
465
491
  }
466
- const anchor = earliestIndexOfIds(block.effectiveMessageIds, indexByRawId);
467
- if (anchor === null) {
468
- throw new BoundaryNotFoundError(
469
- "consumed",
470
- endpoint,
471
- `${label}="b${boundary.numericId}" not found in visible context (block messages consumed by a higher-tier block).`
472
- );
492
+ throw new BoundaryNotFoundError(
493
+ "consumed",
494
+ endpoint,
495
+ `${label}="b${boundary.numericId}" not found in visible context (block messages consumed by a higher-tier block).`
496
+ );
497
+ }
498
+ function activeOwnerAnchor(state, ownedIds, indexByRawId) {
499
+ if (ownedIds.length === 0) return null;
500
+ const owned = new Set(ownedIds);
501
+ let best = null;
502
+ for (const block of state.blocks) {
503
+ if (!block.active) continue;
504
+ const anchor = earliestIndexOfIds(block.effectiveMessageIds, indexByRawId);
505
+ if (anchor === null) continue;
506
+ const ownsContent = block.effectiveMessageIds.some((id) => owned.has(id));
507
+ if (ownsContent && (best === null || anchor < best)) {
508
+ best = anchor;
509
+ }
473
510
  }
474
- return anchor;
511
+ return best;
475
512
  }
476
513
  function formatPaddedRef(index) {
477
514
  return `m${String(index).padStart(5, "0")}`;
@@ -1092,6 +1129,10 @@ function runPipeline(nodes, initial, ctx) {
1092
1129
  function rangeError(spec, message) {
1093
1130
  return `range ${spec.startRef}..${spec.endRef}: ${message}`;
1094
1131
  }
1132
+ function numericBlockId(id) {
1133
+ const parsed = /^b(\d+)$/.exec(id);
1134
+ return parsed ? Number(parsed[1]) : 0;
1135
+ }
1095
1136
  function createCore(ports = {}) {
1096
1137
  const countTokens = ports.countTokens ?? defaultCountTokens;
1097
1138
  function applyCompression(input) {
@@ -1137,6 +1178,12 @@ function createCore(ports = {}) {
1137
1178
  }
1138
1179
  }
1139
1180
  }
1181
+ let resolvableCount = 0;
1182
+ let unknownCount = 0;
1183
+ for (const resolution of classifications.values()) {
1184
+ if (resolution.status === "ok") resolvableCount++;
1185
+ else if (resolution.status === "unknown") unknownCount++;
1186
+ }
1140
1187
  const rangeIndexSets = [];
1141
1188
  for (const [spec, resolution] of classifications) {
1142
1189
  if (resolution.status !== "ok") continue;
@@ -1181,7 +1228,9 @@ function createCore(ports = {}) {
1181
1228
  }
1182
1229
  }
1183
1230
  if (!hasBlockBoundaryRange && totalRangeChars < input.config.compress.minCompressRange) {
1184
- const gateMessage = consumedRanges.length > 0 ? `Requested range(s) already compressed (e.g. ${consumedRanges[0].startRef}..${consumedRanges[0].endRef}); remaining compressible content ${totalRangeChars} chars < min ${input.config.compress.minCompressRange}. Nothing to do \u2014 run acp_status to see current compressible ranges.` : `Total compressible content too small (${totalRangeChars} chars across ${countedRanges} range(s), min ${input.config.compress.minCompressRange}). Combine more messages into your range(s) to meet the threshold.`;
1231
+ const live = activeBlocks(state).map((b) => b.blockId).sort((x, y) => numericBlockId(x) - numericBlockId(y));
1232
+ const liveHint = live.length > 0 ? ` Current active blocks span ${live[0]}..${live[live.length - 1]} \u2014 retry with startId/endId set to active block IDs in that span.` : "";
1233
+ const gateMessage = resolvableCount === 0 && consumedRanges.length === 0 && unknownCount > 0 ? `None of the ${input.ranges.length} requested range(s) resolved \u2014 every ref failed with "does not exist in this session". Refs recorded before an earlier compress are stale: each successful compress renumbers the remaining refs. Run acp_status, then re-issue the compress in the same turn using only the refs it reports.` : consumedRanges.length > 0 ? `Requested range(s) already compressed (e.g. ${consumedRanges[0].startRef}..${consumedRanges[0].endRef}); remaining compressible content ${totalRangeChars} chars < min ${input.config.compress.minCompressRange}. Nothing to do.${liveHint}` : `Total compressible content too small (${totalRangeChars} chars across ${countedRanges} range(s), min ${input.config.compress.minCompressRange}). Combine more messages into your range(s) to meet the threshold.`;
1185
1234
  return {
1186
1235
  state: input.state,
1187
1236
  result: {
@@ -1207,6 +1256,7 @@ function createCore(ports = {}) {
1207
1256
  errors.push(rangeError(spec, resolution.error.message));
1208
1257
  continue;
1209
1258
  }
1259
+ warnings.push(...resolution.resolved.snappedBoundaries);
1210
1260
  try {
1211
1261
  const outcome = applySingleRange({
1212
1262
  spec,
@@ -2422,24 +2472,6 @@ function renderCompressedDrilldown(blocks, state, sort, limit, countTokens) {
2422
2472
  }
2423
2473
  return lines.join("\n");
2424
2474
  }
2425
- var substringAlgorithm = {
2426
- name: "substring",
2427
- description: "Exact substring counting (original baseline). Predictable, no normalization.",
2428
- score(docs, query) {
2429
- const terms = query.toLowerCase().trim().split(/\s+/).filter((t) => t.length > 0);
2430
- if (terms.length === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));
2431
- return docs.map((d) => {
2432
- const haystack = d.text.toLowerCase();
2433
- let score = 0;
2434
- for (const term of terms) score += countOccurrences2(haystack, term);
2435
- return { ref: d.ref, score };
2436
- });
2437
- }
2438
- };
2439
- function countOccurrences2(haystack, needle) {
2440
- if (!needle) return 0;
2441
- return haystack.split(needle).length - 1;
2442
- }
2443
2475
  function stem(word) {
2444
2476
  let w = word;
2445
2477
  if (w.length <= 3) return w;
@@ -2458,8 +2490,17 @@ function stem(word) {
2458
2490
  return w;
2459
2491
  }
2460
2492
  var CJK = /[\u3400-\u9fff\uf900-\ufaff\u3040-\u30ff\uac00-\ud7af]/;
2461
- var CJK_RUN = new RegExp(`${CJK.source}+`, "g");
2462
2493
  var LATIN_WORD = /[a-z][a-z0-9_]*[a-z0-9]|[a-z0-9]/g;
2494
+ var cjkSegmenter = new Intl.Segmenter("zh", { granularity: "word" });
2495
+ function cjkRunTokens(segs) {
2496
+ const words = segs.filter((w) => w.length >= 2);
2497
+ if (words.length > 0) return words;
2498
+ const run = segs.join("");
2499
+ const out = [];
2500
+ for (let i = 0; i < run.length - 1; i++) out.push(run.slice(i, i + 2));
2501
+ for (const ch of run) out.push(ch);
2502
+ return out;
2503
+ }
2463
2504
  function tokenize(text, opts = {}) {
2464
2505
  const lower = text.toLowerCase();
2465
2506
  const tokens = [];
@@ -2470,15 +2511,23 @@ function tokenize(text, opts = {}) {
2470
2511
  tokens.push(w);
2471
2512
  }
2472
2513
  }
2473
- const cjkRuns = lower.match(CJK_RUN) ?? [];
2474
- for (const run of cjkRuns) {
2475
- if (run.length === 1) {
2476
- tokens.push(run);
2477
- } else {
2478
- for (let i = 0; i < run.length - 1; i++) tokens.push(run.slice(i, i + 2));
2479
- for (const ch of run) tokens.push(ch);
2514
+ if (!CJK.test(lower)) return tokens;
2515
+ const runSegs = [];
2516
+ let cur = null;
2517
+ for (const s of cjkSegmenter.segment(lower)) {
2518
+ const t = s.segment;
2519
+ if (t.length === 0) continue;
2520
+ if (CJK.test(t)) {
2521
+ (cur ??= []).push(t);
2522
+ } else if (cur) {
2523
+ runSegs.push(cur);
2524
+ cur = null;
2480
2525
  }
2481
2526
  }
2527
+ if (cur) runSegs.push(cur);
2528
+ for (const segs of runSegs) {
2529
+ tokens.push(...cjkRunTokens(segs));
2530
+ }
2482
2531
  return tokens;
2483
2532
  }
2484
2533
  function charBigrams(text) {
@@ -2494,6 +2543,50 @@ function tfMap(text, stem22) {
2494
2543
  for (const t of tokenize(text, { stem: stem22 })) m.set(t, (m.get(t) ?? 0) + 1);
2495
2544
  return m;
2496
2545
  }
2546
+ var DEFAULT_CAP_CHARS = 8 * 1024 * 1024;
2547
+ var capChars = DEFAULT_CAP_CHARS;
2548
+ var cache = /* @__PURE__ */ new Map();
2549
+ var cachedChars = 0;
2550
+ function build(text) {
2551
+ const tf = tfMap(text, true);
2552
+ let len = 0;
2553
+ for (const v of tf.values()) len += v;
2554
+ const lower = text.toLowerCase();
2555
+ return { tf, len, lower, grams: new Set(charBigrams(lower)) };
2556
+ }
2557
+ function docFeatures(text) {
2558
+ const hit = cache.get(text);
2559
+ if (hit) return hit;
2560
+ const f = build(text);
2561
+ if (text.length > 0 && text.length <= capChars) {
2562
+ while (cachedChars + text.length > capChars && cache.size > 0) {
2563
+ const k = cache.keys().next().value;
2564
+ cachedChars -= k.length;
2565
+ cache.delete(k);
2566
+ }
2567
+ cache.set(text, f);
2568
+ cachedChars += text.length;
2569
+ }
2570
+ return f;
2571
+ }
2572
+ var substringAlgorithm = {
2573
+ name: "substring",
2574
+ description: "Exact substring counting (original baseline). Predictable, no normalization.",
2575
+ score(docs, query) {
2576
+ const terms = query.toLowerCase().trim().split(/\s+/).filter((t) => t.length > 0);
2577
+ if (terms.length === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));
2578
+ return docs.map((d) => {
2579
+ const haystack = docFeatures(d.text).lower;
2580
+ let score = 0;
2581
+ for (const term of terms) score += countOccurrences2(haystack, term);
2582
+ return { ref: d.ref, score };
2583
+ });
2584
+ }
2585
+ };
2586
+ function countOccurrences2(haystack, needle) {
2587
+ if (!needle) return 0;
2588
+ return haystack.split(needle).length - 1;
2589
+ }
2497
2590
  var bm25Algorithm = {
2498
2591
  name: "bm25",
2499
2592
  description: "BM25 with stemming + CJK bigram tokenization. IR-standard relevance ranking.",
@@ -2502,11 +2595,8 @@ var bm25Algorithm = {
2502
2595
  const k1 = 1.2;
2503
2596
  const b = 0.75;
2504
2597
  const parsed = docs.map((d) => {
2505
- const text = d.text;
2506
- const tf = tfMap(text, true);
2507
- let len = 0;
2508
- for (const v of tf.values()) len += v;
2509
- return { id: d.ref, tf, len };
2598
+ const f = docFeatures(d.text);
2599
+ return { id: d.ref, tf: f.tf, len: f.len };
2510
2600
  });
2511
2601
  const avgdl = parsed.reduce((s, d) => s + d.len, 0) / (N || 1);
2512
2602
  const qTerms = tokenize(query, { stem: true });
@@ -2533,14 +2623,13 @@ var fuzzyAlgorithm = {
2533
2623
  name: "fuzzy",
2534
2624
  description: "Character bigram overlap. Typo-tolerant, script-agnostic, high recall.",
2535
2625
  score(docs, query) {
2536
- const qTokens = query.toLowerCase().split(/[\s,]+/).filter((t) => t.length >= 4);
2626
+ const qTokens = query.toLowerCase().split(/[\s,]+/).filter((t) => t.length >= 4 || t.length >= 2 && CJK.test(t));
2537
2627
  if (qTokens.length === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));
2538
2628
  const qGrams = /* @__PURE__ */ new Set();
2539
2629
  for (const t of qTokens) for (const g of charBigrams(t)) qGrams.add(g);
2540
2630
  if (qGrams.size === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));
2541
2631
  return docs.map((d) => {
2542
- const haystack = d.text.toLowerCase();
2543
- const docGrams = new Set(charBigrams(haystack));
2632
+ const docGrams = docFeatures(d.text).grams;
2544
2633
  let hits = 0;
2545
2634
  for (const g of qGrams) if (docGrams.has(g)) hits++;
2546
2635
  return { ref: d.ref, score: hits / qGrams.size };
@@ -2916,6 +3005,27 @@ function resolveTransformMode(adapter, model, hostVersion = VERSION) {
2916
3005
  if (api === "openai-completions" && hostVersionAtLeast(OPENAI_COMPLETIONS_VIABLE_FROM, hostVersion)) return "provider";
2917
3006
  return "context";
2918
3007
  }
3008
+ function providerDeliveryWarning(adapter, model, hostVersion = VERSION) {
3009
+ if (adapter.transformMode !== "provider") return void 0;
3010
+ const api = model?.api;
3011
+ const dropWarning = (target) => ({
3012
+ key: `drop:${target}`,
3013
+ reason: `host < 17.3.8 drops the before_provider_request replacement on ${target} (fixed upstream pi-ai 17.3.8, can1357/oh-my-pi#8717)`,
3014
+ message: `\u26A0 billion-context-omp: transformMode "provider" is set, but this host (pi-ai < 17.3.8) discards the rewritten payload on ${target} \u2014 compression is NOT applied. Upgrade the host (omp update) or remove the transformMode override.`
3015
+ });
3016
+ if (api === "openai-completions" && !hostVersionAtLeast(OPENAI_COMPLETIONS_VIABLE_FROM, hostVersion)) {
3017
+ return dropWarning(api);
3018
+ }
3019
+ if (api === "amazon-bedrock" || api === "cursor") {
3020
+ if (!hostVersionAtLeast(OPENAI_COMPLETIONS_VIABLE_FROM, hostVersion)) return dropWarning(api);
3021
+ return {
3022
+ key: `nocodec:${api}`,
3023
+ reason: `${api} honors the replacement from 17.3.8 but its wire body has no codec path yet (issue #83)`,
3024
+ message: `\u26A0 billion-context-omp: transformMode "provider" is set, but the ${api} wire body has no codec path yet (#83) \u2014 compression is NOT applied. Remove the transformMode override to use context mode.`
3025
+ };
3026
+ }
3027
+ return void 0;
3028
+ }
2919
3029
 
2920
3030
  // node_modules/acp-kernel/dist/wire/index.js
2921
3031
  import { createHash } from "crypto";
@@ -3099,14 +3209,17 @@ function openaiToCore(body) {
3099
3209
  }
3100
3210
  case "user": {
3101
3211
  const text = stringContent(m.content);
3102
- const img = firstImagePart(m.content);
3212
+ const imgs = allImageParts(m.content);
3213
+ const firstImg = imgs[0];
3214
+ const firstUrl = firstImg ? firstImg.image_url.url : void 0;
3215
+ const firstParsed = firstUrl ? parseDataUrl(firstUrl) : void 0;
3103
3216
  const base = deriveMessageId("user", "text", text);
3104
3217
  msgs.push({
3105
3218
  id: clusters.next(base),
3106
3219
  role: "user",
3107
3220
  contentType: "text",
3108
3221
  text,
3109
- ...img ? { rawOpenaiContent: img.part, imageMediaType: img.mediaType, imageBase64: img.base64 } : {}
3222
+ ...imgs.length === 1 && firstParsed ? { rawOpenaiContent: imgs[0], imageMediaType: firstParsed.mediaType, imageBase64: firstParsed.base64 } : imgs.length > 1 ? { rawOpenaiContentParts: imgs } : {}
3110
3223
  });
3111
3224
  break;
3112
3225
  }
@@ -3201,10 +3314,12 @@ function coreToOpenai(messages) {
3201
3314
  if (m.role === "system") {
3202
3315
  out.push({ role: m.originalRole === "developer" ? "developer" : "system", content: m.text ?? "" });
3203
3316
  } else if (m.role === "user") {
3204
- if (m.rawOpenaiContent || m.imageBase64) {
3317
+ if (m.rawOpenaiContent || m.imageBase64 || m.rawOpenaiContentParts) {
3205
3318
  const parts = [];
3206
3319
  if (m.text) parts.push({ type: "text", text: m.text });
3207
- if (m.rawOpenaiContent) {
3320
+ if (m.rawOpenaiContentParts && m.rawOpenaiContentParts.length > 0) {
3321
+ for (const part of m.rawOpenaiContentParts) parts.push(part);
3322
+ } else if (m.rawOpenaiContent) {
3208
3323
  parts.push(m.rawOpenaiContent);
3209
3324
  } else if (m.imageBase64 && m.imageMediaType) {
3210
3325
  parts.push({ type: "image_url", image_url: { url: `data:${m.imageMediaType};base64,${m.imageBase64}` } });
@@ -3229,19 +3344,17 @@ function stringContent(content) {
3229
3344
  }
3230
3345
  return "";
3231
3346
  }
3232
- function firstImagePart(content) {
3233
- if (!Array.isArray(content)) return void 0;
3347
+ function allImageParts(content) {
3348
+ if (!Array.isArray(content)) return [];
3349
+ const out = [];
3234
3350
  for (const p of content) {
3235
- if (p && typeof p === "object" && p.type === "image_url") {
3236
- const iu = p.image_url;
3237
- const url = iu?.url;
3238
- if (typeof url === "string") {
3239
- const parsed = parseDataUrl(url);
3240
- if (parsed) return { part: p, mediaType: parsed.mediaType, base64: parsed.base64 };
3241
- }
3242
- }
3351
+ if (typeof p !== "object" || p === null) continue;
3352
+ if (!("type" in p) || p.type !== "image_url" || !("image_url" in p)) continue;
3353
+ const imagePart = p;
3354
+ const url = imagePart.image_url.url;
3355
+ if (typeof url === "string" && parseDataUrl(url)) out.push(p);
3243
3356
  }
3244
- return void 0;
3357
+ return out;
3245
3358
  }
3246
3359
  function createSubagentNamespaces() {
3247
3360
  const anchors = /* @__PURE__ */ new Map();
@@ -3307,6 +3420,22 @@ function toolResultTexts(stream) {
3307
3420
  }
3308
3421
  return results;
3309
3422
  }
3423
+ function lastRejectedCompressPair(stream) {
3424
+ for (let i = stream.length - 1; i >= 0; i--) {
3425
+ const m = stream[i];
3426
+ if (m.role !== "toolResult" || m.toolName !== "compress" || !m.toolCallId) continue;
3427
+ if (!extractText(m.content).includes("No changes applied")) continue;
3428
+ for (let j = stream.length - 1; j >= 0; j--) {
3429
+ if (j === i) continue;
3430
+ const c = stream[j];
3431
+ if (c.role !== "assistant") continue;
3432
+ if (!allToolCalls(c.content).some((call) => call.id === m.toolCallId && compressToolArgs(call))) continue;
3433
+ return [stream[j], stream[i]];
3434
+ }
3435
+ return null;
3436
+ }
3437
+ return null;
3438
+ }
3310
3439
  function findCompressCalls(message) {
3311
3440
  const out = [];
3312
3441
  for (const call of allToolCalls(message.content)) {
@@ -3725,10 +3854,16 @@ function unrepresentableOpenaiMessage(message) {
3725
3854
  if (typeof role !== "string" || !OPENAI_CODEC_ROLES.has(role)) {
3726
3855
  return `openai role ${JSON.stringify(role) ?? "missing"}`;
3727
3856
  }
3857
+ for (const field of ["function_call", "audio", "annotations"]) {
3858
+ if (message[field] !== void 0) {
3859
+ return `openai ${field} field is dropped by the rebuild`;
3860
+ }
3861
+ }
3862
+ const refusal = message.refusal;
3863
+ if (refusal !== null && refusal !== void 0) return "openai refusal content is dropped by the rebuild";
3728
3864
  const content = message.content;
3729
3865
  if (content == null || typeof content === "string") return null;
3730
3866
  if (!Array.isArray(content)) return "content neither string nor part array";
3731
- let dataImages = 0;
3732
3867
  for (const part of content) {
3733
3868
  if (typeof part === "string") continue;
3734
3869
  const type5 = part?.type;
@@ -3736,13 +3871,45 @@ function unrepresentableOpenaiMessage(message) {
3736
3871
  if (type5 === "image_url" && role === "user") {
3737
3872
  const url = part?.image_url?.url;
3738
3873
  if (typeof url !== "string" || !url.startsWith("data:")) return "image_url without a data: URL is dropped";
3739
- if (++dataImages > 1) return "second image_url in one message is dropped";
3740
3874
  continue;
3741
3875
  }
3742
3876
  return `openai content part type ${JSON.stringify(type5) ?? "missing"}`;
3743
3877
  }
3744
3878
  return null;
3745
3879
  }
3880
+ function restoreOpenaiWireFidelity(originalMessages, rebuilt) {
3881
+ const detailsByCall = /* @__PURE__ */ new Map();
3882
+ for (const message of originalMessages) {
3883
+ if (message === null || typeof message !== "object") continue;
3884
+ const calls = message.tool_calls;
3885
+ const details = message.reasoning_details;
3886
+ if (!Array.isArray(calls) || !Array.isArray(details) || details.length === 0) continue;
3887
+ for (const call of calls) {
3888
+ const id = call?.id;
3889
+ if (typeof id === "string" && !detailsByCall.has(id)) detailsByCall.set(id, details);
3890
+ }
3891
+ }
3892
+ return rebuilt.map((message) => {
3893
+ if (message === null || typeof message !== "object") return message;
3894
+ const m = message;
3895
+ if (m.role !== "assistant") return message;
3896
+ const calls = Array.isArray(m.tool_calls) ? m.tool_calls : [];
3897
+ const attached = [];
3898
+ for (const call of calls) {
3899
+ const id = call?.id;
3900
+ if (typeof id !== "string") continue;
3901
+ const d = detailsByCall.get(id);
3902
+ if (d) attached.push(...d);
3903
+ }
3904
+ const hasReasoningField = m.reasoning_content !== void 0 || m.reasoning !== void 0 || m.reasoning_text !== void 0;
3905
+ const emptyContent = m.content === null && (calls.length > 0 || hasReasoningField);
3906
+ if (attached.length === 0 && !emptyContent) return message;
3907
+ const out = { ...m };
3908
+ if (emptyContent) out.content = "";
3909
+ if (attached.length > 0) out.reasoning_details = attached;
3910
+ return out;
3911
+ });
3912
+ }
3746
3913
  var renderRefsAll = createRenderRefsNode("all");
3747
3914
  function applyWireTagContract(msgs, state, scope) {
3748
3915
  const stripAssistantTags = (m) => m.contentType === "text" && m.role === "assistant" ? { ...m, text: stripRefTag(m.text ?? "") } : m;
@@ -3783,6 +3950,18 @@ function toolResultTextsCore(msgs) {
3783
3950
  }
3784
3951
  return results;
3785
3952
  }
3953
+ function lastRejectedPairCore(msgs) {
3954
+ const names = toolCallNames(msgs);
3955
+ for (let i = msgs.length - 1; i >= 0; i--) {
3956
+ const m = msgs[i];
3957
+ if (m.contentType !== "tool-result" || !m.toolCallId) continue;
3958
+ if (!(m.text ?? "").includes("No changes applied")) continue;
3959
+ if (names.get(m.toolCallId) !== "compress") continue;
3960
+ const call = msgs.find((c) => c.contentType === "tool-call" && c.toolCallId === m.toolCallId);
3961
+ return call ? [call, m] : null;
3962
+ }
3963
+ return null;
3964
+ }
3786
3965
  function findCompressCallsCore(msg) {
3787
3966
  if (msg.contentType !== "tool-call" || !msg.toolName) return [];
3788
3967
  const args = compressToolArgs({ name: msg.toolName, arguments: msg.text });
@@ -3901,22 +4080,23 @@ function viewToCoreStream(view, systemText) {
3901
4080
  if (text) messages.push({ role: "user", content: text });
3902
4081
  } else if (m.role === "assistant") {
3903
4082
  const blocks = Array.isArray(m.content) ? m.content : [];
3904
- const calls = blocks.filter(
3905
- (b) => b !== null && typeof b === "object" && b.type === "toolCall"
3906
- );
4083
+ const typed = blocks;
4084
+ const calls = typed.filter((b) => b !== null && typeof b === "object" && b.type === "toolCall");
4085
+ const reasoning = typed.filter((b) => b !== null && typeof b === "object" && b.type === "thinking" && typeof b.thinking === "string" && b.thinking.trim().length > 0).map((b) => b.thinking).join("\n");
3907
4086
  const text = extractViewText(m.content);
3908
4087
  if (calls.length > 0) {
3909
4088
  messages.push({
3910
4089
  role: "assistant",
3911
4090
  content: text,
4091
+ ...reasoning ? { reasoning_content: reasoning } : {},
3912
4092
  tool_calls: calls.map((c) => ({
3913
4093
  id: c.id,
3914
4094
  type: "function",
3915
4095
  function: { name: c.name ?? "", arguments: JSON.stringify(c.arguments ?? {}) }
3916
4096
  }))
3917
4097
  });
3918
- } else if (text) {
3919
- messages.push({ role: "assistant", content: text });
4098
+ } else if (text || reasoning) {
4099
+ messages.push({ role: "assistant", content: text, ...reasoning ? { reasoning_content: reasoning } : {} });
3920
4100
  }
3921
4101
  } else if (m.role === "toolResult") {
3922
4102
  messages.push({ role: "tool", tool_call_id: m.toolCallId ?? "", content: extractViewText(m.content) });
@@ -3937,20 +4117,27 @@ function viewToAnthropicCore(view) {
3937
4117
  if (text) messages.push({ role: "user", content: [{ type: "text", text }] });
3938
4118
  } else if (m.role === "assistant") {
3939
4119
  const blocks = Array.isArray(m.content) ? m.content : [];
3940
- const calls = blocks.filter(
3941
- (b) => b !== null && typeof b === "object" && b.type === "toolCall"
3942
- );
3943
- const text = extractViewText(m.content);
4120
+ const typed = blocks;
3944
4121
  const content = [];
3945
- if (text) content.push({ type: "text", text });
3946
- for (const c of calls) {
3947
- let input = {};
3948
- try {
3949
- input = c.arguments && typeof c.arguments === "object" ? c.arguments : JSON.parse(JSON.stringify(c.arguments ?? {}));
3950
- } catch {
3951
- input = {};
4122
+ for (const b of typed) {
4123
+ if (b === null || typeof b !== "object") continue;
4124
+ if (b.type === "thinking" && typeof b.thinking === "string" && b.thinking.trim().length > 0) {
4125
+ content.push({
4126
+ type: "thinking",
4127
+ thinking: b.thinking,
4128
+ ...typeof b.thinkingSignature === "string" && b.thinkingSignature ? { signature: b.thinkingSignature } : {}
4129
+ });
4130
+ } else if (b.type === "text" && typeof b.text === "string" && stripRefTag(b.text).trim().length > 0) {
4131
+ content.push({ type: "text", text: stripRefTag(b.text) });
4132
+ } else if (b.type === "toolCall") {
4133
+ let input = {};
4134
+ try {
4135
+ input = b.arguments && typeof b.arguments === "object" ? b.arguments : JSON.parse(JSON.stringify(b.arguments ?? {}));
4136
+ } catch {
4137
+ input = {};
4138
+ }
4139
+ content.push({ type: "tool_use", id: b.id, name: b.name ?? "", input });
3952
4140
  }
3953
- content.push({ type: "tool_use", id: c.id, name: c.name ?? "", input });
3954
4141
  }
3955
4142
  if (content.length > 0) messages.push({ role: "assistant", content });
3956
4143
  } else if (m.role === "toolResult") {
@@ -4263,6 +4450,9 @@ ${acp}`);
4263
4450
  slot.rejectStreak = ok ? 0 : slot.rejectStreak + 1;
4264
4451
  return slot.rejectStreak;
4265
4452
  }
4453
+ function rejectStreakFor(ctx) {
4454
+ return slotForMode(ctx, sidOf(ctx)).rejectStreak;
4455
+ }
4266
4456
  return {
4267
4457
  core,
4268
4458
  get adapter() {
@@ -4285,6 +4475,7 @@ ${acp}`);
4285
4475
  commitFoldState,
4286
4476
  recordRebuiltOutput,
4287
4477
  noteCompressOutcome,
4478
+ rejectStreakFor,
4288
4479
  forgetSession,
4289
4480
  primeFold,
4290
4481
  acquireLock
@@ -5096,7 +5287,7 @@ function stem2(word) {
5096
5287
  return w;
5097
5288
  }
5098
5289
  var CJK2 = /[\u3400-\u9fff\uf900-\ufaff\u3040-\u30ff\uac00-\ud7af]/;
5099
- var CJK_RUN2 = new RegExp(`${CJK2.source}+`, "g");
5290
+ var CJK_RUN = new RegExp(`${CJK2.source}+`, "g");
5100
5291
  var LATIN_WORD2 = /[a-z][a-z0-9_]*[a-z0-9]|[a-z0-9]/g;
5101
5292
  function tokenize2(text, opts = {}) {
5102
5293
  const lower = text.toLowerCase();
@@ -5108,7 +5299,7 @@ function tokenize2(text, opts = {}) {
5108
5299
  tokens.push(w);
5109
5300
  }
5110
5301
  }
5111
- const cjkRuns = lower.match(CJK_RUN2) ?? [];
5302
+ const cjkRuns = lower.match(CJK_RUN) ?? [];
5112
5303
  for (const run of cjkRuns) {
5113
5304
  if (run.length === 1) {
5114
5305
  tokens.push(run);
@@ -5472,7 +5663,7 @@ async function statusReport(runtime, ctx) {
5472
5663
  const coveredIds = collectCoveredMessageIds(state);
5473
5664
  const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
5474
5665
  const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount: sentTokens });
5475
- const versionStr = "0.2.8" ? `billion-context-omp@${"0.2.8"}` : void 0;
5666
+ const versionStr = "0.3.0" ? `billion-context-omp@${"0.3.0"}` : void 0;
5476
5667
  return buildStatusPanel({
5477
5668
  version: versionStr,
5478
5669
  tokenCount: sessionTokens,
@@ -5637,7 +5828,7 @@ function stampAndDetect(selfPath, version, now = Date.now()) {
5637
5828
  }
5638
5829
 
5639
5830
  // src/update.ts
5640
- import { readFile, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
5831
+ import { readFile, writeFile as writeFile2, mkdir as mkdir2, access } from "fs/promises";
5641
5832
  import { join as join4, dirname as dirname3 } from "path";
5642
5833
  import { fileURLToPath } from "url";
5643
5834
  import { execFile } from "child_process";
@@ -5703,43 +5894,112 @@ async function findExtensionDir() {
5703
5894
  dir = parent;
5704
5895
  }
5705
5896
  }
5706
- async function autoInstallLatest(latest) {
5897
+ var runNpmImpl = (args, cwd) => new Promise((resolve2) => {
5898
+ execFile(
5899
+ "npm",
5900
+ args,
5901
+ { cwd, timeout: 6e4, shell: process.platform === "win32", maxBuffer: 4 * 1024 * 1024 },
5902
+ (err, stdout, stderr) => {
5903
+ if (err) logWarn("update", { event: "install-exec-failed", error: err.message, stderr: String(stderr).slice(0, 300) });
5904
+ resolve2({ code: err ? 1 : 0, stdout: String(stdout), stderr: String(stderr) });
5905
+ }
5906
+ );
5907
+ });
5908
+ var runNpm = runNpmImpl;
5909
+ var runNodeImpl = (args) => new Promise((resolve2) => {
5910
+ execFile(
5911
+ process.execPath,
5912
+ args,
5913
+ { timeout: 15e3, maxBuffer: 4 * 1024 * 1024, shell: false },
5914
+ (err, stdout, stderr) => {
5915
+ resolve2({ code: err ? 1 : 0, stdout: String(stdout), stderr: String(stderr) });
5916
+ }
5917
+ );
5918
+ });
5919
+ var runNode = runNodeImpl;
5920
+ function declaredEntries(pkg) {
5921
+ const entries = /* @__PURE__ */ new Set();
5922
+ for (const ext of pkg.omp?.extensions ?? []) {
5923
+ if (typeof ext === "string") entries.add(ext);
5924
+ }
5925
+ const dot = pkg.exports?.["."];
5926
+ if (typeof dot === "string") entries.add(dot);
5927
+ else if (dot && typeof dot.import === "string") entries.add(dot.import);
5928
+ if (typeof pkg.main === "string") entries.add(pkg.main);
5929
+ return [...entries];
5930
+ }
5931
+ async function verifyInstall(npmDir, latest) {
5932
+ const extDir = join4(npmDir, "node_modules", PACKAGE_NAME);
5933
+ const pkg = await readPackageJson(join4(extDir, "package.json"));
5934
+ if (!pkg) return { ok: false, reason: "package-json-missing" };
5935
+ if (pkg.version !== latest) return { ok: false, reason: `version-mismatch:${pkg.version ?? "none"}` };
5936
+ const entries = declaredEntries(pkg);
5937
+ if (entries.length === 0) return { ok: false, reason: "no-entry-declared" };
5938
+ for (const rel of entries) {
5939
+ try {
5940
+ await access(join4(extDir, rel));
5941
+ } catch {
5942
+ return { ok: false, reason: `entry-missing:${rel}` };
5943
+ }
5944
+ }
5945
+ const smokeEntry = pkg.omp?.extensions?.[0] ?? entries[0];
5946
+ if (!smokeEntry) return { ok: false, reason: "no-entry-declared" };
5947
+ const smoke = `const{pathToFileURL}=require("node:url");import(pathToFileURL(process.argv[1]).href).then(()=>{},(e)=>{console.error(e&&e.stack||e);process.exit(1)});`;
5948
+ const r = await runNode(["-e", smoke, join4(extDir, smokeEntry)]);
5949
+ if (r.code !== 0) return { ok: false, reason: `entry-import-failed:${r.stderr.slice(0, 500)}` };
5950
+ return { ok: true };
5951
+ }
5952
+ var installArgs = (version) => [
5953
+ "install",
5954
+ `${PACKAGE_NAME}@${version}`,
5955
+ // --no-save: the auto-updater must never mutate the host's package.json
5956
+ // or lockfile.
5957
+ "--no-save",
5958
+ "--silent",
5959
+ "--no-audit",
5960
+ "--no-fund"
5961
+ ];
5962
+ async function autoInstallLatest(latest, npmDirOverride) {
5707
5963
  if (!SEMVER_RE.test(latest)) {
5708
5964
  logWarn("update", { event: "install-abort", reason: "semver", latest });
5709
- return false;
5965
+ return "failed";
5710
5966
  }
5711
5967
  const extDir = await findExtensionDir();
5712
5968
  if (!extDir) {
5713
5969
  logWarn("update", { event: "install-abort", reason: "extdir-not-found", moduleUrl: import.meta.url });
5714
- return false;
5970
+ return "failed";
5715
5971
  }
5716
- const npmDir = findNpmRoot(extDir);
5972
+ const npmDir = npmDirOverride ?? findNpmRoot(extDir);
5717
5973
  if (!npmDir) {
5718
5974
  logWarn("update", { event: "install-abort", reason: "npmroot-not-found", extDir });
5719
- return false;
5975
+ return "failed";
5720
5976
  }
5977
+ const prevVersion = (await readPackageJson(join4(npmDir, "node_modules", PACKAGE_NAME, "package.json")))?.version ?? "0.3.0";
5721
5978
  try {
5722
5979
  const keepAlive = setInterval(() => {
5723
5980
  }, 500);
5724
5981
  try {
5725
- const code = await new Promise((resolve2) => {
5726
- execFile(
5727
- "npm",
5728
- ["install", `${PACKAGE_NAME}@${latest}`, "--silent", "--no-audit", "--no-fund"],
5729
- { cwd: npmDir, timeout: 6e4, shell: process.platform === "win32" },
5730
- (err, _stdout, stderr) => {
5731
- if (err) logWarn("update", { event: "install-exec-failed", error: err.message, stderr: String(stderr).slice(0, 300) });
5732
- resolve2(err ? 1 : 0);
5733
- }
5734
- );
5735
- });
5736
- return code === 0;
5982
+ const res = await runNpm(installArgs(latest), npmDir);
5983
+ if (res.code !== 0) {
5984
+ logWarn("update", { event: "auto-install-failed", latest, stderr: res.stderr.slice(0, 2e3) });
5985
+ return "failed";
5986
+ }
5987
+ const verify = await verifyInstall(npmDir, latest);
5988
+ if (!verify.ok) {
5989
+ const rollbackTo = SEMVER_RE.test(prevVersion) ? prevVersion : "0.3.0";
5990
+ logWarn("update", { event: "auto-install-verify-failed", latest, reason: verify.reason, rollbackTo });
5991
+ const rb = await runNpm(installArgs(rollbackTo), npmDir);
5992
+ logInfo("update", { event: "rollback", from: latest, to: rollbackTo, ok: rb.code === 0 });
5993
+ return "rolled-back";
5994
+ }
5995
+ logInfo("update", { event: "auto-installed", from: prevVersion, to: latest });
5996
+ return "ok";
5737
5997
  } finally {
5738
5998
  clearInterval(keepAlive);
5739
5999
  }
5740
6000
  } catch (e) {
5741
6001
  logWarn("update", { event: "install-throw", error: e instanceof Error ? e.message : String(e) });
5742
- return false;
6002
+ return "failed";
5743
6003
  }
5744
6004
  }
5745
6005
  async function checkForUpdate(autoUpdate, notify) {
@@ -5766,7 +6026,7 @@ async function checkForUpdate(autoUpdate, notify) {
5766
6026
  const data = await res.json();
5767
6027
  const latest = data.version;
5768
6028
  if (!latest) return;
5769
- const current = runtimeVersion ?? "0.2.8";
6029
+ const current = runtimeVersion ?? "0.3.0";
5770
6030
  const hasUpdate = isNewer(latest, current);
5771
6031
  debug.event("update-check", {
5772
6032
  current,
@@ -5775,16 +6035,16 @@ async function checkForUpdate(autoUpdate, notify) {
5775
6035
  });
5776
6036
  logInfo("update", { event: "check", current, latest, hasUpdate });
5777
6037
  if (hasUpdate) {
5778
- const installed = await autoInstallLatest(latest);
5779
- if (installed && notify) {
6038
+ const outcome = await autoInstallLatest(latest);
6039
+ if (!notify) return;
6040
+ if (outcome === "ok") {
6041
+ notify(`\x1B[32m\u2714 ACP auto-updated ${current} \u2192 ${latest}. Restart omp to finish.\x1B[0m`);
6042
+ } else if (outcome === "rolled-back") {
5780
6043
  notify(
5781
- `\x1B[32m\u2714 ACP auto-updated ${current} \u2192 ${latest}. Restart omp to finish.\x1B[0m`
5782
- );
5783
- logInfo("update", { event: "auto-installed", from: current, to: latest });
5784
- } else if (!installed && notify) {
5785
- notify(
5786
- `${PACKAGE_NAME} ${latest} available (you have ${current}). Run: omp install ${PACKAGE_NAME}@latest`
6044
+ `\x1B[33m\u26A0 ${PACKAGE_NAME} ${latest} failed verification and was rolled back. Keeping ${current}. A later release will auto-update.\x1B[0m`
5787
6045
  );
6046
+ } else {
6047
+ notify(`${PACKAGE_NAME} ${latest} available (you have ${current}). Run: omp install ${PACKAGE_NAME}@latest`);
5788
6048
  }
5789
6049
  }
5790
6050
  } catch (e) {
@@ -6022,10 +6282,11 @@ function applyUserConfig(adapter, user) {
6022
6282
  function createAcpExtension(adapter = {}) {
6023
6283
  return (pi) => {
6024
6284
  const runtime = createRuntime(adapter);
6285
+ const warnDelivery = makeDeliveryWarner();
6025
6286
  wireSessionLifecycle(pi, runtime);
6026
- wireContextTransform(pi, runtime);
6287
+ wireContextTransform(pi, runtime, warnDelivery);
6027
6288
  wireSystemPrompt(pi, runtime);
6028
- wireProviderTransform(pi, runtime);
6289
+ wireProviderTransform(pi, runtime, warnDelivery);
6029
6290
  wireProviderDebug(pi);
6030
6291
  wireToolGuardrails(pi, runtime);
6031
6292
  pi.registerTool(makeCompressTool(runtime));
@@ -6039,26 +6300,14 @@ function createAcpExtension(adapter = {}) {
6039
6300
  }
6040
6301
  var index_default = createAcpExtension();
6041
6302
  function wireSessionLifecycle(pi, runtime) {
6042
- pi.on("session_start", async (_event, ctx) => {
6303
+ const prepareAndPrime = async (ctx, phase) => {
6043
6304
  const sid = ctx.sessionManager.getSessionId();
6044
- logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.2.8" : null });
6045
- const selfPath = import.meta.url;
6046
- const conflict = stampAndDetect(selfPath, true ? "0.2.8" : null);
6047
- if (conflict) {
6048
- logWarn("instance", { event: "dual-instance", self: selfPath, other: conflict.path, otherPid: conflict.pid, otherVersion: conflict.version });
6049
- try {
6050
- if (ctx.hasUI) {
6051
- ctx.ui.notify(`\u26A0 billion-context-omp loaded TWICE (also from ${conflict.path}). Two instances corrupt compression state \u2014 remove one (check 'omp plugin list' vs config.yml extensions).`);
6052
- }
6053
- } catch {
6054
- }
6055
- }
6056
6305
  try {
6057
6306
  const user = await loadUserConfig(ctx.cwd);
6058
6307
  runtime.setAdapter(applyUserConfig(runtime.adapter, user));
6059
6308
  if (runtime.adapter.debug !== void 0) setDebugEnabled(runtime.adapter.debug);
6060
6309
  } catch (e) {
6061
- logThrow("config", e, { sid, phase: "session_start" });
6310
+ logThrow("config", e, { sid, phase });
6062
6311
  }
6063
6312
  try {
6064
6313
  runtime.setPrompts(resolvePrompts(runtime.adapter.prompts, { acknowledgeRisk: runtime.adapter.acknowledgePromptsRisk === true }));
@@ -6067,10 +6316,35 @@ function wireSessionLifecycle(pi, runtime) {
6067
6316
  runtime.setPrompts(defaultPrompts);
6068
6317
  }
6069
6318
  runtime.primeFold(ctx);
6319
+ };
6320
+ pi.on("session_start", async (_event, ctx) => {
6321
+ const sid = ctx.sessionManager.getSessionId();
6322
+ const modelInfo = ctx.model;
6323
+ logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.3.0" : null, model: modelInfo?.id ?? null, modelApi: modelInfo?.api ?? null, contextWindow: modelInfo?.contextWindow ?? null });
6324
+ const selfPath = import.meta.url;
6325
+ const conflict = stampAndDetect(selfPath, true ? "0.3.0" : null);
6326
+ if (conflict) {
6327
+ logWarn("instance", { event: "dual-instance", self: selfPath, other: conflict.path, otherPid: conflict.pid, otherVersion: conflict.version });
6328
+ try {
6329
+ if (ctx.hasUI) {
6330
+ ctx.ui.notify(`\u26A0 billion-context-omp loaded TWICE (also from ${conflict.path}). Two instances corrupt compression state \u2014 remove one (check 'omp plugin list' vs config.yml extensions).`);
6331
+ }
6332
+ } catch {
6333
+ }
6334
+ }
6335
+ await prepareAndPrime(ctx, "session_start");
6070
6336
  void checkForUpdate(runtime.adapter.autoUpdate ?? true, (msg) => {
6071
6337
  if (ctx.hasUI) ctx.ui.notify(msg);
6072
6338
  }).catch((e) => logThrow("update", e, { sid, phase: "session_start" }));
6073
6339
  });
6340
+ pi.on("session_switch", async (event, ctx) => {
6341
+ logInfo("session", { event: "switch", sid: ctx.sessionManager.getSessionId(), reason: event.reason, previous: event.previousSessionFile ?? null });
6342
+ await prepareAndPrime(ctx, "session_switch");
6343
+ });
6344
+ pi.on("session_branch", async (_event, ctx) => {
6345
+ logInfo("session", { event: "branch", sid: ctx.sessionManager.getSessionId() });
6346
+ await prepareAndPrime(ctx, "session_branch");
6347
+ });
6074
6348
  pi.on("session_shutdown", (_event, ctx) => {
6075
6349
  try {
6076
6350
  runtime.forgetSession(ctx.sessionManager.getSessionId());
@@ -6114,6 +6388,7 @@ async function transformStream(ctx, runtime, input, mode) {
6114
6388
  runtime.commitFoldState(ctx, turn.state);
6115
6389
  logInfo("turn", {
6116
6390
  sid,
6391
+ model: ctx.model?.id ?? null,
6117
6392
  inMsgs: coreMessages.length,
6118
6393
  outMsgs: turn.messages.length,
6119
6394
  tokens: tokenCount,
@@ -6141,6 +6416,11 @@ async function transformStream(ctx, runtime, input, mode) {
6141
6416
  activeAfter: turn.state.blocks.filter((b) => b.active).length
6142
6417
  });
6143
6418
  const rebuilt = coreOutToAgentMessages(turn.messages, originalById);
6419
+ const rejectedPair = lastRejectedCompressPair(input);
6420
+ if (rejectedPair) {
6421
+ rebuilt.push(...rejectedPair);
6422
+ debug.event("rejected-pair-visible", { sid, callId: rejectedPair[1].toolCallId ?? null });
6423
+ }
6144
6424
  debug.event("core-out", {
6145
6425
  sid,
6146
6426
  coreOutMsgs: turn.messages.length,
@@ -6152,38 +6432,48 @@ async function transformStream(ctx, runtime, input, mode) {
6152
6432
  if (turn.nudge?.shouldInject) {
6153
6433
  const lastUser = [...input].reverse().find((m) => m.role === "user");
6154
6434
  const tailText = lastUser ? JSON.stringify(lastUser.content ?? "") : "";
6155
- const isFeedbackView = tailText.includes("efficiency nudge to compress early") || tailText.includes("Context limit reached");
6435
+ const isFeedbackView = tailText.includes("efficiency nudge to compress early") || tailText.includes("Context limit reached") || tailText.includes("compress calls were rejected in a row");
6156
6436
  if (isFeedbackView) {
6157
6437
  debug.event("nudge-feedback-skip", { sid: ctx.sessionManager.getSessionId(), msgs: input.length });
6158
6438
  } else {
6159
6439
  const emergency = turn.nudge.breakdown?.emergencyOverride === 1;
6440
+ const rejectStreak = runtime.rejectStreakFor(ctx);
6160
6441
  const epochReset = turn.state.nudge.lastPerMessageNudgeTokens !== preTurnNudgeBaseline;
6161
- const prevShown = epochReset ? 0 : preTurnNudgeShownTokens;
6162
- const cadenceFloor = turn.nudge.breakdown?.growthFloor ?? 0;
6163
- const suppressed = !emergency && prevShown > 0 && tokenCount - prevShown < cadenceFloor;
6164
- if (suppressed) {
6165
- turn.state.nudge.lastNudgeShownTokens = prevShown;
6442
+ if (rejectStreak >= LOOP_GUARD_STOP) {
6443
+ turn.state.nudge.lastNudgeShownTokens = epochReset ? 0 : preTurnNudgeShownTokens;
6166
6444
  turn.state.nudge.lastShownByTier = preTurnNudgeShownByTier;
6167
- logInfo("nudge", { sid, event: "cadence-suppressed", growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
6168
- debug.event("nudge-suppressed", { sid, growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
6169
- } else {
6170
6445
  nudgeInjected = true;
6171
- {
6172
- turn.nudge.compressibleRanges = viableRanges(turn.nudge.compressibleRanges);
6173
- const rendered = renderNudgeText(turn.nudge, runtime.prompts);
6174
- const top = [...turn.nudge.compressibleRanges].sort((a, b) => b.tokens - a.tokens)[0];
6175
- const example = top ? `
6446
+ rebuilt.push(holdMessage(rejectStreak));
6447
+ logInfo("nudge", { sid, event: "hold-injected", streak: rejectStreak, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
6448
+ debug.event("nudge-hold", { sid, streak: rejectStreak });
6449
+ } else {
6450
+ const prevShown = epochReset ? 0 : preTurnNudgeShownTokens;
6451
+ const cadenceFloor = turn.nudge.breakdown?.growthFloor ?? 0;
6452
+ const suppressed = !emergency && prevShown > 0 && tokenCount - prevShown < cadenceFloor;
6453
+ if (suppressed) {
6454
+ turn.state.nudge.lastNudgeShownTokens = prevShown;
6455
+ turn.state.nudge.lastShownByTier = preTurnNudgeShownByTier;
6456
+ logInfo("nudge", { sid, event: "cadence-suppressed", growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
6457
+ debug.event("nudge-suppressed", { sid, growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
6458
+ } else {
6459
+ nudgeInjected = true;
6460
+ {
6461
+ turn.nudge.compressibleRanges = viableRanges(turn.nudge.compressibleRanges);
6462
+ const rendered = renderNudgeText(turn.nudge, runtime.prompts);
6463
+ const top = [...turn.nudge.compressibleRanges].sort((a, b) => b.tokens - a.tokens)[0];
6464
+ const example = top ? `
6176
6465
 
6177
6466
  Example: compress({ content: [{ startId: "${top.startRef}", endId: "${top.endRef}", summary: "..." }] })` : "";
6178
- rebuilt.push(nudgeMessage(turn.nudge, turn.state.blocks.filter((b) => b.active), runtime.prompts, example));
6179
- if (emergency) {
6180
- logWarn("nudge", { sid: ctx.sessionManager.getSessionId(), event: "emergency-inject", pct: Math.round(turn.nudge.contextUsage * 100), voice: rendered.voice, compressible: turn.nudge.compressibleRanges.length });
6181
- }
6182
- if (debugOn2 && ctx.hasUI) {
6183
- ctx.ui.notify(`[ACP nudge \u2192 context]${emergency ? " [EMERGENCY]" : ""}
6467
+ rebuilt.push(nudgeMessage(turn.nudge, turn.state.blocks.filter((b) => b.active), runtime.prompts, example));
6468
+ if (emergency) {
6469
+ logWarn("nudge", { sid: ctx.sessionManager.getSessionId(), event: "emergency-inject", pct: Math.round(turn.nudge.contextUsage * 100), voice: rendered.voice, compressible: turn.nudge.compressibleRanges.length });
6470
+ }
6471
+ if (debugOn2 && ctx.hasUI) {
6472
+ ctx.ui.notify(`[ACP nudge \u2192 context]${emergency ? " [EMERGENCY]" : ""}
6184
6473
  ${rendered.text}${example}`);
6474
+ }
6475
+ debug.event("nudge-injected", { sid: ctx.sessionManager.getSessionId(), voice: rendered.voice, channels: ["context", debugOn2 ? "terminal" : null].filter(Boolean), emergency, text: rendered.text + example });
6185
6476
  }
6186
- debug.event("nudge-injected", { sid: ctx.sessionManager.getSessionId(), voice: rendered.voice, channels: ["context", debugOn2 ? "terminal" : null].filter(Boolean), emergency, text: rendered.text + example });
6187
6477
  }
6188
6478
  }
6189
6479
  }
@@ -6208,10 +6498,13 @@ ${rendered.text}${example}`);
6208
6498
  }).catch((e) => logThrow("update", e, { sid, phase: "context" }));
6209
6499
  return result;
6210
6500
  }
6211
- function wireContextTransform(pi, runtime) {
6501
+ function wireContextTransform(pi, runtime, warnDelivery) {
6212
6502
  pi.on("context", async (event, ctx) => {
6213
6503
  if (resolveTransformMode(runtime.adapter, ctx.model) === "provider") {
6214
- debug.event("context-observer-skip", { sid: ctx.sessionManager.getSessionId(), msgs: event.messages?.length ?? 0 });
6504
+ const sid = ctx.sessionManager.getSessionId();
6505
+ debug.event("context-observer-skip", { sid, msgs: event.messages?.length ?? 0 });
6506
+ const warning = providerDeliveryWarning(runtime.adapter, ctx.model);
6507
+ if (warning) warnDelivery(ctx, sid, warning);
6215
6508
  return void 0;
6216
6509
  }
6217
6510
  const result = await transformStream(ctx, runtime, event.messages ?? [], "context");
@@ -6219,7 +6512,20 @@ function wireContextTransform(pi, runtime) {
6219
6512
  return { messages: result.rebuilt };
6220
6513
  });
6221
6514
  }
6222
- function wireProviderTransform(pi, runtime) {
6515
+ function makeDeliveryWarner() {
6516
+ const warned = /* @__PURE__ */ new Set();
6517
+ return (ctx, sid, warning) => {
6518
+ const dedup = `${sid}:${warning.key}`;
6519
+ if (warned.has(dedup)) return;
6520
+ warned.add(dedup);
6521
+ logWarn("provider-transform", { sid, event: "undelivered", reason: warning.reason });
6522
+ try {
6523
+ if (ctx.hasUI) ctx.ui.notify(warning.message);
6524
+ } catch {
6525
+ }
6526
+ };
6527
+ }
6528
+ function wireProviderTransform(pi, runtime, warnDelivery) {
6223
6529
  pi.on("before_provider_request", async (event, ctx) => {
6224
6530
  if (resolveTransformMode(runtime.adapter, ctx.model) !== "provider") return void 0;
6225
6531
  const payload = event.payload;
@@ -6228,6 +6534,13 @@ function wireProviderTransform(pi, runtime) {
6228
6534
  const fmt2 = detectProviderWireFormat(payload);
6229
6535
  if (fmt2 === null) {
6230
6536
  debug.event("provider-transform-unknown-format", { sid });
6537
+ if (runtime.adapter.transformMode === "provider") {
6538
+ warnDelivery(ctx, sid, {
6539
+ key: "unknown-wire-format",
6540
+ reason: "explicit provider mode but the wire body has no codec path (unknown format) \u2014 payload passes through",
6541
+ message: '\u26A0 billion-context-omp: transformMode "provider" is set, but this wire body has no codec path \u2014 compression is NOT applied here. Remove the override to use context mode.'
6542
+ });
6543
+ }
6231
6544
  return void 0;
6232
6545
  }
6233
6546
  const representable = payloadRepresentable(payload, fmt2);
@@ -6241,13 +6554,15 @@ function wireProviderTransform(pi, runtime) {
6241
6554
  if (msgs.length === 0) return void 0;
6242
6555
  const result = await transformStreamCore(ctx, runtime, msgs, fmt2);
6243
6556
  if (!result) return void 0;
6244
- const outMsgs = coreToPayloadMessages(result.coreOut, fmt2, cacheControls).length;
6245
- const inMsgs = payload.messages?.length ?? 0;
6557
+ const inMessages = payload.messages ?? [];
6558
+ const rebuilt = fmt2 === "openai" ? restoreOpenaiWireFidelity(inMessages, coreToPayloadMessages(result.coreOut, fmt2, cacheControls)) : coreToPayloadMessages(result.coreOut, fmt2, cacheControls);
6559
+ const outMsgs = rebuilt.length;
6560
+ const inMsgs = inMessages.length;
6246
6561
  if (outMsgs !== inMsgs) {
6247
6562
  logInfo("provider-transform", { sid, fmt: fmt2, inMsgs, outMsgs, nudge: result.nudgeInjected ? "injected" : "idle" });
6248
6563
  }
6249
6564
  debug.event("provider-transform", { sid, fmt: fmt2, inMsgs, outMsgs, nudgeInjected: result.nudgeInjected });
6250
- return { ...payload, messages: coreToPayloadMessages(result.coreOut, fmt2, cacheControls) };
6565
+ return { ...payload, messages: rebuilt };
6251
6566
  } catch (e) {
6252
6567
  logThrow("provider-transform", e, { sid, fmt: fmt2 });
6253
6568
  return void 0;
@@ -6321,42 +6636,57 @@ async function transformStreamCore(ctx, runtime, wireMsgs, fmt2) {
6321
6636
  turn.state,
6322
6637
  { config, tokenCount }
6323
6638
  );
6639
+ const rejectedPair = lastRejectedPairCore(wireMsgs);
6640
+ if (rejectedPair) {
6641
+ coreOut.push(...rejectedPair);
6642
+ debug.event("rejected-pair-visible", { sid, space: "core", callId: rejectedPair[1]?.toolCallId ?? null });
6643
+ }
6324
6644
  let nudgeInjected = false;
6325
6645
  if (turn.nudge?.shouldInject) {
6326
6646
  const lastUser = [...wireMsgs].reverse().find((m) => m.role === "user");
6327
6647
  const tailText = lastUser ? lastUser.text ?? "" : "";
6328
- const isFeedbackView = tailText.includes("efficiency nudge to compress early") || tailText.includes("Context limit reached");
6648
+ const isFeedbackView = tailText.includes("efficiency nudge to compress early") || tailText.includes("Context limit reached") || tailText.includes("compress calls were rejected in a row");
6329
6649
  if (isFeedbackView) {
6330
6650
  debug.event("nudge-feedback-skip", { sid, msgs: wireMsgs.length });
6331
6651
  } else {
6332
6652
  const emergency = turn.nudge.breakdown?.emergencyOverride === 1;
6333
6653
  const epochReset = turn.state.nudge.lastPerMessageNudgeTokens !== preTurnNudgeBaseline;
6334
- const prevShown = epochReset ? 0 : preTurnNudgeShownTokens;
6335
- const cadenceFloor = turn.nudge.breakdown?.growthFloor ?? 0;
6336
- const suppressed = !emergency && prevShown > 0 && tokenCount - prevShown < cadenceFloor;
6337
- if (suppressed) {
6338
- turn.state.nudge.lastNudgeShownTokens = prevShown;
6654
+ const rejectStreak = runtime.rejectStreakFor(ctx);
6655
+ if (rejectStreak >= LOOP_GUARD_STOP) {
6656
+ turn.state.nudge.lastNudgeShownTokens = epochReset ? 0 : preTurnNudgeShownTokens;
6339
6657
  turn.state.nudge.lastShownByTier = preTurnNudgeShownByTier;
6340
- logInfo("nudge", { sid, event: "cadence-suppressed", growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
6341
- debug.event("nudge-suppressed", { sid, growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
6342
- } else {
6343
6658
  nudgeInjected = true;
6344
- turn.nudge.compressibleRanges = viableRanges(turn.nudge.compressibleRanges);
6345
- const rendered = renderNudgeText(turn.nudge, runtime.prompts);
6346
- const top = [...turn.nudge.compressibleRanges].sort((a, b) => b.tokens - a.tokens)[0];
6347
- const example = top ? `
6659
+ coreOut.push({ id: `acp_hold_${Date.now()}`, role: "user", contentType: "text", text: holdText(rejectStreak) });
6660
+ logInfo("nudge", { sid, event: "hold-injected", streak: rejectStreak, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
6661
+ debug.event("nudge-hold", { sid, streak: rejectStreak });
6662
+ } else {
6663
+ const prevShown = epochReset ? 0 : preTurnNudgeShownTokens;
6664
+ const cadenceFloor = turn.nudge.breakdown?.growthFloor ?? 0;
6665
+ const suppressed = !emergency && prevShown > 0 && tokenCount - prevShown < cadenceFloor;
6666
+ if (suppressed) {
6667
+ turn.state.nudge.lastNudgeShownTokens = prevShown;
6668
+ turn.state.nudge.lastShownByTier = preTurnNudgeShownByTier;
6669
+ logInfo("nudge", { sid, event: "cadence-suppressed", growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
6670
+ debug.event("nudge-suppressed", { sid, growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
6671
+ } else {
6672
+ nudgeInjected = true;
6673
+ turn.nudge.compressibleRanges = viableRanges(turn.nudge.compressibleRanges);
6674
+ const rendered = renderNudgeText(turn.nudge, runtime.prompts);
6675
+ const top = [...turn.nudge.compressibleRanges].sort((a, b) => b.tokens - a.tokens)[0];
6676
+ const example = top ? `
6348
6677
 
6349
6678
  Example: compress({ content: [{ startId: "${top.startRef}", endId: "${top.endRef}", summary: "..." }] })` : "";
6350
- if (emergency) {
6351
- logWarn("nudge", { sid, event: "emergency-inject", pct: Math.round(turn.nudge.contextUsage * 100), voice: rendered.voice, compressible: turn.nudge.compressibleRanges.length });
6352
- }
6353
- const debugOn2 = debug.enabled;
6354
- if (debugOn2 && ctx.hasUI) {
6355
- ctx.ui.notify(`[ACP nudge \u2192 context]${emergency ? " [EMERGENCY]" : ""}
6679
+ if (emergency) {
6680
+ logWarn("nudge", { sid, event: "emergency-inject", pct: Math.round(turn.nudge.contextUsage * 100), voice: rendered.voice, compressible: turn.nudge.compressibleRanges.length });
6681
+ }
6682
+ const debugOn2 = debug.enabled;
6683
+ if (debugOn2 && ctx.hasUI) {
6684
+ ctx.ui.notify(`[ACP nudge \u2192 context]${emergency ? " [EMERGENCY]" : ""}
6356
6685
  ${rendered.text}${example}`);
6686
+ }
6687
+ debug.event("nudge-injected", { sid, voice: rendered.voice, channels: ["wire", debugOn2 ? "terminal" : null].filter(Boolean), emergency, text: rendered.text + example });
6688
+ coreOut.push({ id: `acp_nudge_${Date.now()}`, role: "user", contentType: "text", text: nudgeText(turn.nudge, turn.state.blocks.filter((b) => b.active), runtime.prompts, example) });
6357
6689
  }
6358
- debug.event("nudge-injected", { sid, voice: rendered.voice, channels: ["wire", debugOn2 ? "terminal" : null].filter(Boolean), emergency, text: rendered.text + example });
6359
- coreOut.push({ id: `acp_nudge_${Date.now()}`, role: "user", contentType: "text", text: nudgeText(turn.nudge, turn.state.blocks.filter((b) => b.active), runtime.prompts, example) });
6360
6690
  }
6361
6691
  }
6362
6692
  }
@@ -6389,18 +6719,18 @@ function wireProviderDebug(pi) {
6389
6719
  pi.on("after_provider_response", (event, ctx) => {
6390
6720
  if (!debug.enabled) return;
6391
6721
  const h = event.headers;
6392
- const cache = {};
6722
+ const cache2 = {};
6393
6723
  for (const [k, v] of Object.entries(h)) {
6394
6724
  const lk = k.toLowerCase();
6395
6725
  if (lk.includes("cache") || lk.includes("usage") || lk.includes("token") || lk.includes("rate") || lk.includes("x-")) {
6396
- cache[lk] = v;
6726
+ cache2[lk] = v;
6397
6727
  }
6398
6728
  }
6399
6729
  debug.event("provider-response", {
6400
6730
  sid: ctx.sessionManager.getSessionId(),
6401
6731
  status: event.status,
6402
6732
  requestId: event.requestId ?? null,
6403
- cache
6733
+ cache: cache2
6404
6734
  });
6405
6735
  });
6406
6736
  }
@@ -6432,6 +6762,16 @@ function nudgeMessage(nudge, blocks, prompts, example) {
6432
6762
  timestamp: Date.now()
6433
6763
  };
6434
6764
  }
6765
+ function holdText(streak) {
6766
+ return `[ACP hold] Your last ${streak} compress calls were rejected in a row. Do NOT call compress again now and do NOT retry the same range \u2014 the compress reminder is suspended until context actually changes. Continue the actual task. If context still needs relief, run acp_status first and target ONLY a range that meets the minimum size.`;
6767
+ }
6768
+ function holdMessage(streak) {
6769
+ return {
6770
+ role: "user",
6771
+ content: [{ type: "text", text: holdText(streak) }],
6772
+ timestamp: Date.now()
6773
+ };
6774
+ }
6435
6775
  export {
6436
6776
  createAcpExtension,
6437
6777
  index_default as default