@chroxify/eunoe 1.3.0 → 1.3.1

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.
@@ -545,6 +545,16 @@ function splitTurns(messages) {
545
545
  function turnFingerprint(turn) {
546
546
  return shortHash(JSON.stringify(blocks(turn[0]).map(withoutCacheControl)), 24);
547
547
  }
548
+ function turnKeys(turns) {
549
+ const copies = new Map;
550
+ return turns.map((turn) => {
551
+ const fingerprint = turnFingerprint(turn);
552
+ const earlier = copies.get(fingerprint) ?? 0;
553
+ copies.set(fingerprint, earlier + 1);
554
+ return earlier === 0 ? fingerprint : `${fingerprint}~${earlier}`;
555
+ });
556
+ }
557
+ var fingerprintOfKey = (key) => key.split("~")[0];
548
558
  function isHarness(message) {
549
559
  return message.role === "system";
550
560
  }
@@ -593,8 +603,19 @@ function reduceTurn(turn, options = {}) {
593
603
  }
594
604
 
595
605
  // src/context/fold/strategies.ts
596
- var pinnedBy = ({ pinned }) => (turn) => pinned !== undefined && pinned.size > 0 && pinned.has(turnFingerprint(turn));
597
- var countPinned = (turns, pinned) => pinned.size > 0 ? turns.filter((turn) => pinned.has(turnFingerprint(turn))).length : 0;
606
+ function pinnedAt(turns, { pinned }) {
607
+ if (pinned === undefined || pinned.size === 0)
608
+ return () => false;
609
+ const keys = turnKeys(turns);
610
+ return (index) => pinned.has(keys[index]);
611
+ }
612
+ function pinnedPrompt({ pinned }) {
613
+ if (pinned === undefined || pinned.size === 0)
614
+ return () => false;
615
+ const fingerprints = new Set([...pinned].map(fingerprintOfKey));
616
+ return (turn) => fingerprints.has(turnFingerprint(turn));
617
+ }
618
+ var countPinned = (keys, pinned) => pinned.size > 0 ? keys.filter((key) => pinned.has(key)).length : 0;
598
619
  function trimMessages(messages, options = {}) {
599
620
  const { preamble, turns } = splitTurns(messages);
600
621
  if (turns.length < 2)
@@ -624,18 +645,19 @@ function compactMessages(messages, cutIndex, options) {
624
645
  if (cutIndex <= 0 || cutIndex >= turns.length)
625
646
  return messages;
626
647
  const reduce = options.reduce ?? reduceTurn;
627
- const isPinned = pinnedBy(options);
648
+ const isPinned = pinnedAt(turns, options);
649
+ const indexed = turns.map((turn, index) => ({ turn, pinned: isPinned(index) }));
628
650
  const keptFrom = Math.max(1, cutIndex - options.keepTurns);
629
651
  const [first, ...rest] = turns[0];
630
- const openingPinned = isPinned(turns[0]);
652
+ const openingPinned = indexed[0].pinned;
631
653
  const openingFolded = !openingPinned && foldsOpening(turns[0], keptFrom);
632
- const dropped = turns.slice(1, keptFrom);
633
- const whole = dropped.filter(isPinned);
634
- const pinned = whole.length + turns.slice(keptFrom, cutIndex).filter(isPinned).length + (openingPinned ? 1 : 0);
654
+ const dropped = indexed.slice(1, keptFrom);
655
+ const whole = dropped.filter((t) => t.pinned).map((t) => t.turn);
656
+ const pinned = whole.length + indexed.slice(keptFrom, cutIndex).filter((t) => t.pinned).length + (openingPinned ? 1 : 0);
635
657
  const kept = cutIndex - keptFrom + (openingFolded ? 1 : 0);
636
658
  const notice = compactionNotice({ omitted: dropped.length - whole.length + (options.omittedBefore ?? 0), kept, pinned, guide: options.guide });
637
- const removed = [...openingPinned || openingFolded ? [] : rest, ...dropped.filter((turn) => !isPinned(turn)).flat()];
638
- const shown = (turn) => isPinned(turn) ? turn.map(clean) : reduce(turn, options);
659
+ const removed = [...openingPinned || openingFolded ? [] : rest, ...dropped.filter((t) => !t.pinned).flatMap((t) => t.turn)];
660
+ const shown = (t) => t.pinned ? t.turn.map(clean) : reduce(t.turn, options);
639
661
  return [
640
662
  ...preamble,
641
663
  first,
@@ -643,7 +665,7 @@ function compactMessages(messages, cutIndex, options) {
643
665
  { role: "user", content: [{ type: "text", text: notice }, ...carriedHarness(removed)] },
644
666
  ...openingFolded ? reduce(turns[0], options).slice(1) : [],
645
667
  ...whole.flatMap((turn) => turn.map(clean)),
646
- ...turns.slice(keptFrom, cutIndex).flatMap(shown),
668
+ ...indexed.slice(keptFrom, cutIndex).flatMap(shown),
647
669
  ...turns.slice(cutIndex).flat()
648
670
  ];
649
671
  }
@@ -655,7 +677,7 @@ function capTurns(messages, options) {
655
677
  if (turns.length < 3)
656
678
  return messages;
657
679
  const live = turns.length - 1;
658
- const isPinned = pinnedBy(options);
680
+ const isPinned = pinnedPrompt(options);
659
681
  const over = (turn, i) => i > 0 && i < live && !isPinned(turn) && JSON.stringify(turn).length > capChars;
660
682
  if (!turns.some(over))
661
683
  return messages;
@@ -716,6 +738,7 @@ function appendSystem(system, text) {
716
738
  }
717
739
 
718
740
  // src/context/recall/index.ts
741
+ var RECALL_CHARS_TOTAL = 30000;
719
742
  var SHOWN_LINES = 8;
720
743
  var BM25_K1 = 1.2;
721
744
  var BM25_B = 0.75;
@@ -1037,7 +1060,8 @@ async function pickRecallWith(prompt, chunks, rerank, options = {}) {
1037
1060
  function renderRecall(prompt, chunks, picks, options = {}) {
1038
1061
  if (picks.length === 0)
1039
1062
  return null;
1040
- const budget = queriesOf(prompt).length > 1 ? options.maxCharsMulti ?? 30000 : options.maxChars ?? 18000;
1063
+ const own = queriesOf(prompt).length > 1 ? options.maxCharsMulti ?? 30000 : options.maxChars ?? 18000;
1064
+ const budget = Math.min(own, (options.maxCharsTotal ?? RECALL_CHARS_TOTAL) - (options.charsInUse ?? 0));
1041
1065
  const entries = [];
1042
1066
  let used = 0;
1043
1067
  for (const { index, query } of [...picks].sort((a, b) => chunks[a.index].turn - chunks[b.index].turn || a.index - b.index)) {
@@ -1109,6 +1133,13 @@ function targetSize(size, window) {
1109
1133
  return size.fixed + 0.3 * Math.max(0, window - size.fixed);
1110
1134
  }
1111
1135
 
1136
+ // src/core/fingerprint.ts
1137
+ var isInjected = (text) => /^<(?:recalled-context|handoff)[\s>]/.test(String(text ?? ""));
1138
+ function messageFingerprint(message) {
1139
+ const own = blocks(message).filter((b) => !(b.type === "text" && isInjected(b.text))).map(withoutCacheControl);
1140
+ return shortHash(JSON.stringify(own), 24);
1141
+ }
1142
+
1112
1143
  // src/core/state.ts
1113
1144
  function memoryStore(initial = {}) {
1114
1145
  const threads = new Map(Object.entries(initial).map(([key, state]) => [key, structuredClone(state)]));
@@ -1119,6 +1150,13 @@ function memoryStore(initial = {}) {
1119
1150
  }
1120
1151
  };
1121
1152
  }
1153
+ function retireRecalls(state, folded) {
1154
+ if (!state.recalls)
1155
+ return;
1156
+ for (const message of folded.flat())
1157
+ if (isPrompt(message))
1158
+ delete state.recalls[messageFingerprint(message)];
1159
+ }
1122
1160
  function resetCut(state) {
1123
1161
  delete state.cut;
1124
1162
  delete state.keep;
@@ -1145,15 +1183,14 @@ function reduceRemembered(turn, options) {
1145
1183
  return reduced;
1146
1184
  }
1147
1185
  var PROTECTED_OUTPUTS = 5;
1148
- var STUCK_STEP = 0.1;
1149
1186
  function settledCut(turns, index) {
1150
1187
  let cut = index;
1151
1188
  while (cut > 1 && JSON.stringify(turns[cut][0].content).includes("[Request interrupted"))
1152
1189
  cut -= 1;
1153
1190
  return cut;
1154
1191
  }
1155
- function resolveCut(turns, state, config, rollingKeep) {
1156
- let cutIndex = state.cut ? turns.findIndex((turn) => turnFingerprint(turn) === state.cut) : -1;
1192
+ function resolveCut(turns, keys, state, config, rollingKeep) {
1193
+ let cutIndex = state.cut ? keys.indexOf(state.cut) : -1;
1157
1194
  let stateChanged = false;
1158
1195
  if (state.cut && cutIndex === -1) {
1159
1196
  resetCut(state);
@@ -1161,7 +1198,8 @@ function resolveCut(turns, state, config, rollingKeep) {
1161
1198
  }
1162
1199
  if (cutIndex <= 0 && config.eval?.forceCut && turns.length >= 3) {
1163
1200
  cutIndex = settledCut(turns, Math.max(1, turns.length - 1 - (rollingKeep ?? 1)));
1164
- state.cut = turnFingerprint(turns[cutIndex]);
1201
+ state.cut = keys[cutIndex];
1202
+ retireRecalls(state, turns.slice(0, cutIndex));
1165
1203
  stateChanged = true;
1166
1204
  }
1167
1205
  return { cutIndex, stateChanged };
@@ -1188,29 +1226,55 @@ function shrinkToTarget(over, step, actions, rebuild) {
1188
1226
  const action = step();
1189
1227
  actions.push(action);
1190
1228
  if (action === "stuck")
1191
- return true;
1229
+ return;
1192
1230
  rebuild();
1193
1231
  }
1194
- return false;
1195
1232
  }
1196
- var holdsStuck = (state, estimate, window) => window !== null && state.stuck !== undefined && estimate <= state.stuck + STUCK_STEP * window;
1197
- var headLength = (preamble, first, state) => preamble.length + 1 + (countPinned([first], new Set(state.pins)) > 0 ? first.length : 1);
1233
+ function shrinkOrHold(state, actions, shrink) {
1234
+ const overWindow = (tokens) => shrink.window !== null && tokens > shrink.window;
1235
+ const held = state.stuck === undefined ? null : { keep: state.keep, cleared: state.cleared, actions: actions.length, overWindow: overWindow(shrink.estimate()) };
1236
+ shrinkToTarget(() => shrink.estimate() > shrink.target, shrink.step, actions, shrink.rebuild);
1237
+ const helped = (estimate) => estimate <= shrink.limit || held?.overWindow === true && !overWindow(estimate);
1238
+ if (held && !helped(shrink.estimate())) {
1239
+ restoreShrink(state, held);
1240
+ actions.length = held.actions;
1241
+ shrink.rebuild();
1242
+ }
1243
+ if (shrink.estimate() > shrink.limit)
1244
+ state.stuck = shrink.estimate();
1245
+ else {
1246
+ delete state.stuck;
1247
+ if (actions.at(-1) === "stuck")
1248
+ actions.pop();
1249
+ }
1250
+ }
1251
+ function restoreShrink(state, held) {
1252
+ if (held.keep === undefined)
1253
+ delete state.keep;
1254
+ else
1255
+ state.keep = held.keep;
1256
+ if (held.cleared === undefined)
1257
+ delete state.cleared;
1258
+ else
1259
+ state.cleared = held.cleared;
1260
+ }
1261
+ var headLength = (preamble, first, firstKey, state) => preamble.length + 1 + (countPinned([firstKey], new Set(state.pins)) > 0 ? first.length : 1);
1198
1262
  function stuckSizes(messages, headEnd, liveFrom, size) {
1199
1263
  const of = (list) => size.of(list) - size.fixed;
1200
1264
  return { fixed: size.fixed, head: of(messages.slice(0, headEnd)), folded: of(messages.slice(headEnd, liveFrom)), live: of(messages.slice(liveFrom)) };
1201
1265
  }
1202
- function cutNote(state, turns, cutIndex, configuredKeep, actions) {
1266
+ function cutNote(state, turns, keys, cutIndex, configuredKeep, actions) {
1203
1267
  const pins = new Set(state.pins);
1204
1268
  const keptFrom = cutIndex > 0 ? Math.max(1, cutIndex - (state.keep ?? configuredKeep)) : 0;
1205
- const omitted = cutIndex > 0 ? keptFrom - 1 - countPinned(turns.slice(1, keptFrom), pins) : 0;
1269
+ const omitted = cutIndex > 0 ? keptFrom - 1 - countPinned(keys.slice(1, keptFrom), pins) : 0;
1206
1270
  return {
1207
1271
  action: cutIndex > 0 ? "compact" : "full",
1208
1272
  compacted: actions.includes("cut") || actions.includes("roll"),
1209
1273
  actions: actions.length > 0 ? actions : undefined,
1210
1274
  cutTurn: cutIndex > 0 ? cutIndex : null,
1211
1275
  omitted,
1212
- folded: cutIndex > 0 ? cutIndex - keptFrom - countPinned(turns.slice(keptFrom, cutIndex), pins) + (foldsOpening(turns[0], keptFrom) && countPinned([turns[0]], pins) === 0 ? 1 : 0) : 0,
1213
- pinned: cutIndex > 0 ? countPinned(turns.slice(0, cutIndex), pins) : 0,
1276
+ folded: cutIndex > 0 ? cutIndex - keptFrom - countPinned(keys.slice(keptFrom, cutIndex), pins) + (foldsOpening(turns[0], keptFrom) && countPinned([keys[0]], pins) === 0 ? 1 : 0) : 0,
1277
+ pinned: cutIndex > 0 ? countPinned(keys.slice(0, cutIndex), pins) : 0,
1214
1278
  host: state.host,
1215
1279
  keep: state.keep,
1216
1280
  cleared: state.cleared
@@ -1219,7 +1283,8 @@ function cutNote(state, turns, cutIndex, configuredKeep, actions) {
1219
1283
  function compact({ body, config, state, window, guide, transcriptDir, mayCompact, omittedBefore = 0, decorate = (m) => m, reduce }) {
1220
1284
  const { preamble, turns } = splitTurns(body.messages);
1221
1285
  const rolling = config.eval?.rolling ?? (config.mode === "default" ? { keep: config.keep, budget: config.budget } : undefined);
1222
- const resolved = resolveCut(turns, state, config, rolling?.keep);
1286
+ const keys = turnKeys(turns);
1287
+ const resolved = resolveCut(turns, keys, state, config, rolling?.keep);
1223
1288
  let cutIndex = resolved.cutIndex;
1224
1289
  let stateChanged = resolved.stateChanged;
1225
1290
  const configuredKeep = config.keepTurns === "all" ? Number.MAX_SAFE_INTEGER : config.keepTurns;
@@ -1248,7 +1313,8 @@ function compact({ body, config, state, window, guide, transcriptDir, mayCompact
1248
1313
  const moveCut = (index, action) => {
1249
1314
  cutIndex = index;
1250
1315
  resetCut(state);
1251
- state.cut = turnFingerprint(turns[index]);
1316
+ state.cut = keys[index];
1317
+ retireRecalls(state, turns.slice(0, index));
1252
1318
  actions.push(action);
1253
1319
  rebuild();
1254
1320
  };
@@ -1261,21 +1327,18 @@ function compact({ body, config, state, window, guide, transcriptDir, mayCompact
1261
1327
  const current = turns.length - 1;
1262
1328
  if (current > 0 && current > cutIndex)
1263
1329
  moveCut(current, "cut");
1264
- if (!holdsStuck(state, estimate, window)) {
1265
- const stuck = shrinkToTarget(() => estimate > target, () => shrinkOnce(state, cutIndex, configuredKeep, liveResults), actions, rebuild);
1266
- if (stuck)
1267
- state.stuck = estimate;
1268
- else
1269
- delete state.stuck;
1270
- }
1330
+ shrinkOrHold(state, actions, { estimate: () => estimate, step: () => shrinkOnce(state, cutIndex, configuredKeep, liveResults), rebuild, target, limit, window });
1331
+ stateChanged = true;
1332
+ } else if (state.stuck !== undefined) {
1333
+ delete state.stuck;
1271
1334
  stateChanged = true;
1272
1335
  }
1273
1336
  return {
1274
1337
  messages,
1275
1338
  stateChanged,
1276
1339
  note: {
1277
- ...cutNote(state, turns, cutIndex, configuredKeep, actions),
1278
- stuck: state.stuck === undefined ? undefined : stuckSizes(messages, headLength(preamble, turns[0], state), liveStart(messages), size),
1340
+ ...cutNote(state, turns, keys, cutIndex, configuredKeep, actions),
1341
+ stuck: state.stuck === undefined ? undefined : stuckSizes(messages, headLength(preamble, turns[0], keys[0], state), liveStart(messages), size),
1279
1342
  turns: turns.length,
1280
1343
  messagesIn: body.messages.length,
1281
1344
  messagesOut: messages.length,
@@ -1285,13 +1348,6 @@ function compact({ body, config, state, window, guide, transcriptDir, mayCompact
1285
1348
  };
1286
1349
  }
1287
1350
 
1288
- // src/core/fingerprint.ts
1289
- var isInjected = (text) => /^<(?:recalled-context|handoff)[\s>]/.test(String(text ?? ""));
1290
- function messageFingerprint(message) {
1291
- const own = blocks(message).filter((b) => !(b.type === "text" && isInjected(b.text))).map(withoutCacheControl);
1292
- return shortHash(JSON.stringify(own), 24);
1293
- }
1294
-
1295
1351
  // src/context/fold/index.ts
1296
1352
  var FOLD_CAP_CHARS = 400000;
1297
1353
  var spoken = (message) => textOf(message).replace(/<(system-reminder|system-message|recalled-context|handoff)(?:\s[^>]*)?>[\s\S]*?<\/\1>/g, "").trim();
@@ -1431,6 +1487,26 @@ function recallTarget(original) {
1431
1487
  const live = livePrompt(original.messages);
1432
1488
  return live ? { key: messageFingerprint(original.messages[live.index]), text: live.text } : null;
1433
1489
  }
1490
+ function charsInUse(messages, recalls) {
1491
+ return messages.reduce((sum, message) => sum + (isPrompt(message) ? recalls[messageFingerprint(message)]?.length ?? 0 : 0), 0);
1492
+ }
1493
+ var RECALL_SHARE = 0.25;
1494
+ var RECALL_FLOOR = 6000;
1495
+ function withinTotal(input, result, recalls) {
1496
+ const options = input.config.eval?.recallOptions;
1497
+ const share = Math.max(RECALL_FLOOR, RECALL_SHARE * JSON.stringify(result.body.messages).length);
1498
+ return { ...options, maxCharsTotal: Math.min(options?.maxCharsTotal ?? RECALL_CHARS_TOTAL, share), charsInUse: charsInUse(result.body.messages, recalls) };
1499
+ }
1500
+ function dropLiveRecall(input, threadKey) {
1501
+ const target = recallTarget(input.body);
1502
+ if (!target)
1503
+ return;
1504
+ const state = input.store.read(threadKey);
1505
+ if (state.recalls?.[target.key] === "")
1506
+ return;
1507
+ state.recalls = { ...state.recalls, [target.key]: "" };
1508
+ input.store.save(threadKey, state);
1509
+ }
1434
1510
  function applyRecall(input, result) {
1435
1511
  if (!touched(input, result) || !result.threadKey)
1436
1512
  return result;
@@ -1439,7 +1515,7 @@ function applyRecall(input, result) {
1439
1515
  const target = recallTarget(input.body);
1440
1516
  let note = result.note;
1441
1517
  if (target && !(target.key in state.recalls)) {
1442
- state.recalls[target.key] = recallFor(target.text, droppedChunks(input, result, result.threadKey), input.config.eval?.recallOptions) ?? "";
1518
+ state.recalls[target.key] = recallFor(target.text, droppedChunks(input, result, result.threadKey), withinTotal(input, result, state.recalls)) ?? "";
1443
1519
  input.store.save(result.threadKey, state);
1444
1520
  note = { ...note, recalled: state.recalls[target.key].length };
1445
1521
  }
@@ -1456,12 +1532,112 @@ async function prefetchFor(input, result, rerank) {
1456
1532
  state.recalls ??= {};
1457
1533
  if (target.key in state.recalls)
1458
1534
  return null;
1459
- const options = { candidates: input.config.eval?.rerank?.candidates, ...input.config.eval?.recallOptions };
1535
+ const options = { candidates: input.config.eval?.rerank?.candidates, ...withinTotal(input, result, state.recalls) };
1460
1536
  state.recalls[target.key] = await recallForWith(target.text, droppedChunks(input, result, result.threadKey), rerank, options) ?? "";
1461
1537
  input.store.save(result.threadKey, state);
1462
1538
  return state.recalls[target.key].length;
1463
1539
  }
1464
1540
 
1541
+ // src/core/usage.ts
1542
+ function usageIn(line) {
1543
+ if (!line.startsWith("data:") || !line.includes("message_start"))
1544
+ return null;
1545
+ try {
1546
+ return JSON.parse(line.slice(5)).message?.usage ?? null;
1547
+ } catch {
1548
+ return null;
1549
+ }
1550
+ }
1551
+ function trailingUsage(buffer) {
1552
+ if (!buffer.trim().startsWith("{"))
1553
+ return null;
1554
+ try {
1555
+ const json = JSON.parse(buffer);
1556
+ if (json.usage)
1557
+ return json.usage;
1558
+ return typeof json.input_tokens === "number" ? { input_tokens: json.input_tokens } : null;
1559
+ } catch {
1560
+ return null;
1561
+ }
1562
+ }
1563
+ function scanEvents(buffer) {
1564
+ let rest = buffer;
1565
+ for (let newline = rest.indexOf(`
1566
+ `);newline !== -1; newline = rest.indexOf(`
1567
+ `)) {
1568
+ const usage = usageIn(rest.slice(0, newline).trim());
1569
+ rest = rest.slice(newline + 1);
1570
+ if (usage)
1571
+ return { usage, rest };
1572
+ }
1573
+ return { usage: null, rest };
1574
+ }
1575
+ async function readUsage(stream) {
1576
+ const reader = stream.pipeThrough(new TextDecoderStream).getReader();
1577
+ let buffer = "";
1578
+ let usage = null;
1579
+ while (true) {
1580
+ const { value, done } = await reader.read();
1581
+ if (done)
1582
+ return usage ?? trailingUsage(buffer);
1583
+ if (usage)
1584
+ continue;
1585
+ const scanned = scanEvents(buffer + value);
1586
+ usage = scanned.usage;
1587
+ buffer = scanned.rest.length > 4000000 ? "" : scanned.rest;
1588
+ }
1589
+ }
1590
+ function watchUsage(response, onUsage) {
1591
+ if (!response.body)
1592
+ return response;
1593
+ const [toClient, toUs] = response.body.tee();
1594
+ readUsage(toUs).then((usage) => {
1595
+ if (usage)
1596
+ onUsage(usage);
1597
+ }).catch(() => {
1598
+ return;
1599
+ });
1600
+ return new Response(toClient, { status: response.status, statusText: response.statusText, headers: response.headers });
1601
+ }
1602
+ function inputTokens(usage) {
1603
+ return (usage.input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0);
1604
+ }
1605
+ var OVERFLOW_MARGIN = 1.1;
1606
+ var charsOf = (sent) => typeof sent === "number" ? sent : sizeOf(sent);
1607
+ function overflowOf(errorText) {
1608
+ const match = errorText.match(/prompt is too long[^0-9]*(\d+)\s*tokens?\s*>\s*(\d+)/i);
1609
+ const tokens = Number(match?.[1]);
1610
+ const maximum = Number(match?.[2]);
1611
+ if (!Number.isFinite(tokens) || tokens <= 0)
1612
+ return null;
1613
+ return { tokens, maximum: Number.isFinite(maximum) && maximum > 0 ? maximum : null };
1614
+ }
1615
+ function recordUsage(store, threadKey, sent, usage) {
1616
+ const input = inputTokens(usage);
1617
+ const chars = charsOf(sent);
1618
+ if (input <= 0 || chars <= 0)
1619
+ return false;
1620
+ const state = store.read(threadKey);
1621
+ state.ratio = input / chars;
1622
+ store.save(threadKey, state);
1623
+ return true;
1624
+ }
1625
+ var limitKey = (model) => String(model ?? "");
1626
+ function recordOverflow(store, threadKey, sent, errorText, model = typeof sent === "number" ? undefined : sent.model) {
1627
+ const overflow = overflowOf(errorText);
1628
+ const chars = charsOf(sent);
1629
+ if (!overflow || chars <= 0)
1630
+ return null;
1631
+ const state = store.read(threadKey);
1632
+ state.ratio = Math.max(state.ratio ?? 0, overflow.tokens / chars) * OVERFLOW_MARGIN;
1633
+ if (overflow.maximum) {
1634
+ const key = limitKey(model);
1635
+ state.limits = { ...state.limits, [key]: Math.min(state.limits?.[key] ?? Number.POSITIVE_INFINITY, overflow.maximum) };
1636
+ }
1637
+ store.save(threadKey, state);
1638
+ return overflow.tokens;
1639
+ }
1640
+
1465
1641
  // src/core/rewrite.ts
1466
1642
  var pass = (body) => ({ body, threadKey: null, note: { action: "pass" } });
1467
1643
  var isAgentThread = (body) => Array.isArray(body.tools) && body.tools.length > 0;
@@ -1521,7 +1697,7 @@ function strategize(input) {
1521
1697
  if (body.messages.length < 2)
1522
1698
  return { body: based, threadKey, note: { action: "full", turns: turns.length } };
1523
1699
  const handoffs = activeHandoffs(state.handoffs);
1524
- const recalls = wantsRecall(input) ? activeRecalls(state.recalls) : {};
1700
+ const recalling = wantsRecall(input);
1525
1701
  const result = compact({
1526
1702
  body: based,
1527
1703
  config,
@@ -1531,7 +1707,7 @@ function strategize(input) {
1531
1707
  transcriptDir: sources.transcriptDir ?? null,
1532
1708
  mayCompact: input.mayCompact && !off,
1533
1709
  omittedBefore: input.omittedBefore,
1534
- decorate: (messages) => withRecall(withHandoffs(messages, handoffs), messageFingerprint, recalls)
1710
+ decorate: (messages) => withRecall(withHandoffs(messages, handoffs), messageFingerprint, recalling ? activeRecalls(state.recalls) : {})
1535
1711
  });
1536
1712
  if (result.stateChanged)
1537
1713
  store.save(threadKey, state);
@@ -1566,17 +1742,28 @@ function carried(from, to) {
1566
1742
  return to;
1567
1743
  return { ...to, note: { ...to.note, compacted: Boolean(to.note.compacted || from.note.compacted), actions } };
1568
1744
  }
1569
- function rewrite(input, before) {
1745
+ function withLearnedLimit(input) {
1746
+ const threadKey = input.threadId && Array.isArray(input.body.messages) ? threadKeyOf(input.threadId, input.body.messages) : null;
1747
+ const limit = threadKey ? input.store.read(threadKey).limits?.[limitKey(input.body.model)] : undefined;
1748
+ if (!limit || input.window !== null && input.window <= limit)
1749
+ return input;
1750
+ return { ...input, window: limit };
1751
+ }
1752
+ function rewrite(given, before) {
1753
+ const input = withLearnedLimit(given);
1570
1754
  const result = before ? carried(before, once(input)) : once(input);
1571
1755
  if (!overLimit(input, result))
1572
1756
  return result;
1573
1757
  const recut = carried(result, once(input));
1574
1758
  if (!overLimit(input, recut))
1575
1759
  return { ...recut, note: { ...recut.note, fit: "recut" } };
1576
- const bare = carried(recut, once({ ...input, config: { ...input.config, eval: { ...input.config.eval, recall: false } } }));
1760
+ if (recut.threadKey && wantsRecall(input))
1761
+ dropLiveRecall(input, recut.threadKey);
1762
+ const bare = carried(recut, once(input));
1577
1763
  return { ...bare, note: { ...bare.note, fit: overLimit(input, bare) ? "over" : "bare", fitEstimate: sentEstimate(input, bare) } };
1578
1764
  }
1579
- async function rewriteAsync(input, rerank = identityReranker) {
1765
+ async function rewriteAsync(given, rerank = identityReranker) {
1766
+ const input = withLearnedLimit(given);
1580
1767
  if (!wantsRecall(input) || rerank === identityReranker)
1581
1768
  return rewrite(input);
1582
1769
  const first = strategize(input);
@@ -1590,7 +1777,8 @@ function located({ store, threadId, messages }) {
1590
1777
  const threadKey = threadKeyOf(threadId, messages);
1591
1778
  if (!threadKey)
1592
1779
  throw new RangeError("eunoe can only fold a thread that has at least one user turn");
1593
- return { threadKey, turns: splitTurns(messages).turns, state: store.read(threadKey) };
1780
+ const turns = splitTurns(messages).turns;
1781
+ return { threadKey, turns, keys: turnKeys(turns), state: store.read(threadKey) };
1594
1782
  }
1595
1783
  function checkedTurn(turn, turns, from) {
1596
1784
  if (turns.length <= from)
@@ -1599,19 +1787,19 @@ function checkedTurn(turn, turns, from) {
1599
1787
  throw new RangeError(`turn ${turn} is outside turns ${from}..${turns.length - 1}`);
1600
1788
  return turn;
1601
1789
  }
1602
- function folded2(threadKey, turns, state) {
1603
- const cutTurn = state.cut ? turns.findIndex((turn) => turnFingerprint(turn) === state.cut) : -1;
1790
+ function folded2(threadKey, keys, state) {
1791
+ const cutTurn = state.cut ? keys.indexOf(state.cut) : -1;
1604
1792
  const pins = new Set(state.pins);
1605
- return { threadKey, cutTurn: cutTurn > 0 ? cutTurn : null, pinned: turns.flatMap((turn, i) => pins.has(turnFingerprint(turn)) ? [i] : []) };
1793
+ return { threadKey, cutTurn: cutTurn > 0 ? cutTurn : null, pinned: keys.flatMap((key, i) => pins.has(key) ? [i] : []) };
1606
1794
  }
1607
- function withPins(state, turns, indices, pinned) {
1795
+ function withPins(state, turns, keys, indices, pinned) {
1608
1796
  const pins = new Set(state.pins);
1609
1797
  for (const index of indices) {
1610
- const fingerprint = turnFingerprint(turns[checkedTurn(index, turns, 0)]);
1798
+ const key = keys[checkedTurn(index, turns, 0)];
1611
1799
  if (pinned)
1612
- pins.add(fingerprint);
1800
+ pins.add(key);
1613
1801
  else
1614
- pins.delete(fingerprint);
1802
+ pins.delete(key);
1615
1803
  }
1616
1804
  if (pins.size > 0)
1617
1805
  state.pins = [...pins].sort();
@@ -1619,115 +1807,28 @@ function withPins(state, turns, indices, pinned) {
1619
1807
  delete state.pins;
1620
1808
  }
1621
1809
  function foldThread(target, { at = "latest", pin = [] } = {}) {
1622
- const { threadKey, turns, state } = located(target);
1810
+ const { threadKey, turns, keys, state } = located(target);
1623
1811
  const cut = checkedTurn(at === "latest" ? turns.length - 1 : at, turns, 1);
1624
- withPins(state, turns, pin, true);
1812
+ withPins(state, turns, keys, pin, true);
1625
1813
  resetCut(state);
1626
- state.cut = turnFingerprint(turns[cut]);
1814
+ state.cut = keys[cut];
1815
+ retireRecalls(state, turns.slice(0, cut));
1627
1816
  state.keep = cut - 1;
1628
1817
  state.host = true;
1629
1818
  target.store.save(threadKey, state);
1630
- return folded2(threadKey, turns, state);
1819
+ return folded2(threadKey, keys, state);
1631
1820
  }
1632
1821
  function pinTurns(target, indices, pinned = true) {
1633
- const { threadKey, turns, state } = located(target);
1634
- withPins(state, turns, indices, pinned);
1822
+ const { threadKey, turns, keys, state } = located(target);
1823
+ withPins(state, turns, keys, indices, pinned);
1635
1824
  target.store.save(threadKey, state);
1636
- return folded2(threadKey, turns, state);
1637
- }
1638
- // src/core/usage.ts
1639
- function usageIn(line) {
1640
- if (!line.startsWith("data:") || !line.includes("message_start"))
1641
- return null;
1642
- try {
1643
- return JSON.parse(line.slice(5)).message?.usage ?? null;
1644
- } catch {
1645
- return null;
1646
- }
1647
- }
1648
- function trailingUsage(buffer) {
1649
- if (!buffer.trim().startsWith("{"))
1650
- return null;
1651
- try {
1652
- const json = JSON.parse(buffer);
1653
- if (json.usage)
1654
- return json.usage;
1655
- return typeof json.input_tokens === "number" ? { input_tokens: json.input_tokens } : null;
1656
- } catch {
1657
- return null;
1658
- }
1659
- }
1660
- function scanEvents(buffer) {
1661
- let rest = buffer;
1662
- for (let newline = rest.indexOf(`
1663
- `);newline !== -1; newline = rest.indexOf(`
1664
- `)) {
1665
- const usage = usageIn(rest.slice(0, newline).trim());
1666
- rest = rest.slice(newline + 1);
1667
- if (usage)
1668
- return { usage, rest };
1669
- }
1670
- return { usage: null, rest };
1671
- }
1672
- async function readUsage(stream) {
1673
- const reader = stream.pipeThrough(new TextDecoderStream).getReader();
1674
- let buffer = "";
1675
- let usage = null;
1676
- while (true) {
1677
- const { value, done } = await reader.read();
1678
- if (done)
1679
- return usage ?? trailingUsage(buffer);
1680
- if (usage)
1681
- continue;
1682
- const scanned = scanEvents(buffer + value);
1683
- usage = scanned.usage;
1684
- buffer = scanned.rest.length > 4000000 ? "" : scanned.rest;
1685
- }
1825
+ return folded2(threadKey, keys, state);
1686
1826
  }
1687
- function watchUsage(response, onUsage) {
1688
- if (!response.body)
1689
- return response;
1690
- const [toClient, toUs] = response.body.tee();
1691
- readUsage(toUs).then((usage) => {
1692
- if (usage)
1693
- onUsage(usage);
1694
- }).catch(() => {
1695
- return;
1696
- });
1697
- return new Response(toClient, { status: response.status, statusText: response.statusText, headers: response.headers });
1698
- }
1699
- function inputTokens(usage) {
1700
- return (usage.input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0);
1701
- }
1702
- var OVERFLOW_MARGIN = 1.1;
1703
- var charsOf = (sent) => typeof sent === "number" ? sent : sizeOf(sent);
1704
- function overflowTokens(errorText) {
1705
- const tokens = Number(errorText.match(/prompt is too long[^0-9]*(\d+)\s*tokens?\s*>\s*(\d+)/i)?.[1]);
1706
- return Number.isFinite(tokens) && tokens > 0 ? tokens : null;
1707
- }
1708
- function recordUsage(store, threadKey, sent, usage) {
1709
- const input = inputTokens(usage);
1710
- const chars = charsOf(sent);
1711
- if (input <= 0 || chars <= 0)
1712
- return false;
1713
- const state = store.read(threadKey);
1714
- state.ratio = input / chars;
1715
- store.save(threadKey, state);
1716
- return true;
1717
- }
1718
- function recordOverflow(store, threadKey, sent, errorText) {
1719
- const tokens = overflowTokens(errorText);
1720
- const chars = charsOf(sent);
1721
- if (!tokens || chars <= 0)
1722
- return null;
1723
- const state = store.read(threadKey);
1724
- state.ratio = Math.max(state.ratio ?? 0, tokens / chars) * OVERFLOW_MARGIN;
1725
- store.save(threadKey, state);
1726
- return tokens;
1727
- }
1728
-
1729
1827
  // src/core/window.ts
1730
- var LOOKUP_TIMEOUT_MS = 5000;
1828
+ var LOOKUP_TIMEOUT_MS = 1500;
1829
+ var RETRY_AFTER_MS = 10 * 60000;
1830
+ var LONG_SUFFIX = /\[1m\]$/i;
1831
+ var SUFFIX = /\[[^\]]*\]$/;
1731
1832
  function lookupHeaders(requestHeaders) {
1732
1833
  const headers = new Headers;
1733
1834
  for (const name of ["authorization", "x-api-key", "anthropic-version", "anthropic-beta"]) {
@@ -1739,10 +1840,21 @@ function lookupHeaders(requestHeaders) {
1739
1840
  headers.set("anthropic-version", "2023-06-01");
1740
1841
  return headers;
1741
1842
  }
1742
- function createWindowLookup({ known = new Map, onLearn } = {}) {
1843
+ function familyWindow(model) {
1844
+ if (LONG_SUFFIX.test(model))
1845
+ return 1e6;
1846
+ const id = model.replace(SUFFIX, "");
1847
+ if (/^claude-(?:(?:opus|sonnet|haiku|fable)-5|opus-4-8)/.test(id))
1848
+ return 1e6;
1849
+ if (id.startsWith("claude-"))
1850
+ return 200000;
1851
+ return null;
1852
+ }
1853
+ function createWindowLookup({ known = new Map, onLearn, timeoutMs = LOOKUP_TIMEOUT_MS, retryAfterMs = RETRY_AFTER_MS } = {}) {
1743
1854
  const lookups = new Map;
1855
+ const failedAt = new Map;
1744
1856
  const lookup = async (id, requestHeaders, upstream) => {
1745
- const response = await fetch(`${upstream}/v1/models/${encodeURIComponent(id)}`, { headers: lookupHeaders(requestHeaders), signal: AbortSignal.timeout(LOOKUP_TIMEOUT_MS) });
1857
+ const response = await fetch(`${upstream}/v1/models/${encodeURIComponent(id)}`, { headers: lookupHeaders(requestHeaders), signal: AbortSignal.timeout(timeoutMs) });
1746
1858
  if (!response.ok)
1747
1859
  return null;
1748
1860
  const info = await response.json();
@@ -1753,18 +1865,24 @@ function createWindowLookup({ known = new Map, onLearn } = {}) {
1753
1865
  return info.max_input_tokens;
1754
1866
  };
1755
1867
  return (model, requestHeaders, upstream = "https://api.anthropic.com") => {
1756
- const id = String(model ?? "").replace(/\[[^\]]*\]$/, "");
1868
+ const given = String(model ?? "");
1869
+ const id = given.replace(SUFFIX, "");
1757
1870
  if (!id)
1758
1871
  return Promise.resolve(null);
1759
1872
  const window = known.get(id);
1760
1873
  if (window)
1761
1874
  return Promise.resolve(window);
1762
- const inFlight = lookups.get(id);
1763
- if (inFlight)
1764
- return inFlight;
1765
- const pending = lookup(id, requestHeaders, upstream).catch(() => null).finally(() => lookups.delete(id));
1875
+ const fallback = familyWindow(given);
1876
+ const failed = failedAt.get(id);
1877
+ if (failed !== undefined && Date.now() - failed < retryAfterMs)
1878
+ return Promise.resolve(fallback);
1879
+ const pending = lookups.get(id) ?? lookup(id, requestHeaders, upstream).catch(() => null).then((found) => {
1880
+ if (found === null)
1881
+ failedAt.set(id, Date.now());
1882
+ return found;
1883
+ }).finally(() => lookups.delete(id));
1766
1884
  lookups.set(id, pending);
1767
- return pending;
1885
+ return pending.then((found) => found ?? fallback);
1768
1886
  };
1769
1887
  }
1770
1888
 
@@ -1801,7 +1919,7 @@ function defaultPrepare(options, upstream) {
1801
1919
  return rewriteAsync({ body, threadId, config: options.config ?? CORE_DEFAULTS, store: options.store, sources, window, mayCompact }, options.reranker);
1802
1920
  };
1803
1921
  }
1804
- var passthrough = (bodyText) => ({ note: { action: "pass" }, threadKey: null, bodyText, sentSize: 0, givenSize: 0, fixedSize: 0 });
1922
+ var passthrough = (bodyText) => ({ note: { action: "pass" }, model: undefined, threadKey: null, bodyText, sentSize: 0, givenSize: 0, fixedSize: 0 });
1805
1923
  async function outgoing(prepare, request, givenText, mayCompact) {
1806
1924
  try {
1807
1925
  const given = JSON.parse(givenText);
@@ -1809,6 +1927,7 @@ async function outgoing(prepare, request, givenText, mayCompact) {
1809
1927
  const sentSize = sizeOf(result.body);
1810
1928
  return {
1811
1929
  note: result.note,
1930
+ model: given.model,
1812
1931
  threadKey: result.threadKey,
1813
1932
  bodyText: result.body !== given ? JSON.stringify(result.body, wellFormed) : givenText,
1814
1933
  sentSize,
@@ -1889,7 +2008,7 @@ function createForwarder(options) {
1889
2008
  if (answer.status >= 400 && out.note.action !== "pass") {
1890
2009
  const errorText = await answer.text();
1891
2010
  const logged = { path: url.pathname, status: answer.status, thread: out.threadKey, ...out.note };
1892
- const overflow = out.threadKey && !isCountTokens ? recordOverflow(options.store, out.threadKey, out.sentSize, errorText) : null;
2011
+ const overflow = out.threadKey && !isCountTokens ? recordOverflow(options.store, out.threadKey, out.sentSize, errorText, out.model) : null;
1893
2012
  if (!overflow) {
1894
2013
  log({ ...logged, error: clipped(errorText) });
1895
2014
  return errorResponse(errorText);