@genesislcap/ai-assistant 15.19.6 → 15.20.0-alpha.3

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.
Files changed (52) hide show
  1. package/dist/ai-assistant.api.json +707 -69
  2. package/dist/ai-assistant.d.ts +443 -25
  3. package/dist/chat-driver.cjs +440 -34
  4. package/dist/chat-driver.cjs.map +4 -4
  5. package/dist/chat-driver.mjs +440 -34
  6. package/dist/chat-driver.mjs.map +4 -4
  7. package/dist/custom-elements.json +709 -47
  8. package/dist/dts/components/ai-driver/ai-driver.d.ts +40 -7
  9. package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
  10. package/dist/dts/components/chat-driver/chat-driver.d.ts +81 -2
  11. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  12. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +23 -3
  13. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
  14. package/dist/dts/config/config.d.ts +44 -0
  15. package/dist/dts/config/config.d.ts.map +1 -1
  16. package/dist/dts/main/main.d.ts +187 -5
  17. package/dist/dts/main/main.d.ts.map +1 -1
  18. package/dist/dts/main/main.styles.d.ts.map +1 -1
  19. package/dist/dts/main/main.template.d.ts.map +1 -1
  20. package/dist/dts/state/persistence/diagnostics-cursors.d.ts +7 -2
  21. package/dist/dts/state/persistence/diagnostics-cursors.d.ts.map +1 -1
  22. package/dist/dts/state/persistence/diagnostics.d.ts +30 -0
  23. package/dist/dts/state/persistence/diagnostics.d.ts.map +1 -1
  24. package/dist/dts/state/persistence/session-persister.d.ts +18 -0
  25. package/dist/dts/state/persistence/session-persister.d.ts.map +1 -1
  26. package/dist/dts/utils/condense-history.d.ts +35 -0
  27. package/dist/dts/utils/condense-history.d.ts.map +1 -1
  28. package/dist/dts/utils/context-tokens.d.ts +156 -0
  29. package/dist/dts/utils/context-tokens.d.ts.map +1 -0
  30. package/dist/dts/utils/history-transform.d.ts +76 -14
  31. package/dist/dts/utils/history-transform.d.ts.map +1 -1
  32. package/dist/dts/utils/resolve-context-budget.d.ts +98 -0
  33. package/dist/dts/utils/resolve-context-budget.d.ts.map +1 -0
  34. package/dist/esm/components/chat-driver/chat-driver.js +200 -35
  35. package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +28 -4
  36. package/dist/esm/main/main.js +400 -24
  37. package/dist/esm/main/main.styles.js +128 -0
  38. package/dist/esm/main/main.template.js +64 -29
  39. package/dist/esm/state/debug-event-log.js +1 -1
  40. package/dist/esm/state/persistence/diagnostics-cursors.js +1 -1
  41. package/dist/esm/state/persistence/diagnostics.js +49 -0
  42. package/dist/esm/state/persistence/session-persister.js +38 -18
  43. package/dist/esm/utils/condense-history.js +125 -26
  44. package/dist/esm/utils/context-tokens.js +339 -0
  45. package/dist/esm/utils/history-transform.js +101 -19
  46. package/dist/esm/utils/resolve-context-budget.js +84 -0
  47. package/package.json +16 -16
  48. package/sandbox/README.md +93 -4
  49. package/sandbox/controls.ts +77 -10
  50. package/sandbox/fixtures.ts +163 -6
  51. package/sandbox/sandbox.css +54 -1
  52. package/sandbox/sandbox.ts +384 -7
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-context-budget.d.ts","sourceRoot":"","sources":["../../../src/utils/resolve-context-budget.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAO7D,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,EAAE,0BAA0B,EAAE,CAAC;AAEtC;;;;GAIG;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C,6DAA6D;AAC7D,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,OAAQ,CAAC;AAEjD,iEAAiE;AACjE,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;OAOG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,2DAA2D;IAC3D,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,8CAA8C;AAC9C,MAAM,WAAW,kBAAkB;IACjC,0BAA0B;IAC1B,UAAU,EAAE,UAAU,CAAC;IACvB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,kBAAkB,GAAG,qBAAqB,CAmDrF"}
@@ -1,11 +1,12 @@
1
1
  import { __awaiter, __rest } from "tslib";
2
- import { BudgetExhaustedError, DEFAULT_BUDGET_EXHAUSTED_MESSAGE, DEFAULT_PROVIDER_REFUSED_MESSAGE, ProviderRefusedError, isObservableAIProviderRegistry, MalformedFunctionCallError, ResponseTruncatedError, vendorTypeOfLabel, } from '@genesislcap/foundation-ai';
2
+ import { BudgetExhaustedError, DEFAULT_BUDGET_EXHAUSTED_MESSAGE, DEFAULT_PROVIDER_REFUSED_MESSAGE, ContextOverflowError, DEFAULT_CONTEXT_OVERFLOW_MESSAGE, ProviderRefusedError, isObservableAIProviderRegistry, MalformedFunctionCallError, ResponseTruncatedError, vendorTypeOfLabel, } from '@genesislcap/foundation-ai';
3
3
  import { NOOP_ACTIVITY_BUS } from '../../channel/ai-activity-bus';
4
4
  import { resolveChatProvider } from '../../config/validate-providers';
5
5
  import { clearSession, getMetaEvents, mergeMetaEvents, recordMetaEvent, recordTurnError, recordTurnRetry, } from '../../state/debug-event-log';
6
6
  import { createInteractionContext, } from '../../state/interaction-context';
7
- import { applyCondensation } from '../../utils/condense-history';
8
- import { applyHistoryCap, buildCompactionSummaryPrompt, findCompactionCut, normalizeForProvider, } from '../../utils/history-transform';
7
+ import { applyCondensation, condenseForPersistence, } from '../../utils/condense-history';
8
+ import { estimateRequestTokens } from '../../utils/context-tokens';
9
+ import { applyHistoryCap, buildCompactionSummaryPrompt, planCompaction, normalizeForProvider, } from '../../utils/history-transform';
9
10
  import { logger } from '../../utils/logger';
10
11
  import { messageUsage, sumUsage } from '../../utils/sum-usage';
11
12
  import { TOOL_FOLD_SYMBOL } from '../../utils/tool-fold';
@@ -115,16 +116,6 @@ const HARVESTED_SUBAGENT_EVENTS = new Set([
115
116
  export const REQUEST_CONTINUATION_TOOL = 'request_continuation';
116
117
  /** Paired in history for each `request_continuation` so tool_calls stay balanced for the provider. */
117
118
  const HANDOFF_TOOL_RESULT_PLACEHOLDER = 'Handoff to another specialist — routing continues on the next turn.';
118
- /**
119
- * Plain TS class that drives a multi-turn chat conversation, including the tool-call loop.
120
- * Owned by `FoundationAiAssistant` — created in `connectedCallback`, torn down in `disconnectedCallback`.
121
- *
122
- * Dispatches `'history-updated'` events on itself so the owning element can observe changes.
123
- *
124
- * @fires history-updated - Fired whenever the in-memory chat history changes (append, tool loop, interaction resolution, post-resolve external cost) with the full history snapshot. detail: `ReadonlyArray<ChatMessage>`
125
- *
126
- * @beta
127
- */
128
119
  export class ChatDriver extends EventTarget {
129
120
  constructor(providerRegistry, config = {}) {
130
121
  super();
@@ -755,6 +746,12 @@ export class ChatDriver extends EventTarget {
755
746
  const status = yield this.resolveStatusForProvider(resolvedName, provider);
756
747
  this.lastResolvedModel = status.model;
757
748
  this.lastResolvedProvider = status.provider;
749
+ // The window of the provider THIS call will go to. Read here rather than
750
+ // pushed down by the host, because the host cannot know it until the later
751
+ // `provider-changed` event — so a per-agent provider switch would otherwise
752
+ // have its first call guarded by the previous model's limit, refusing a valid
753
+ // turn on the way up and waving an oversized one through on the way down.
754
+ this.lastResolvedContextLimit = status.contextLimit;
758
755
  if (resolvedName !== this.lastDispatchedProviderName) {
759
756
  this.lastDispatchedProviderName = resolvedName;
760
757
  recordMetaEvent(this.sessionKey, 'provider.selected', {
@@ -788,7 +785,13 @@ export class ChatDriver extends EventTarget {
788
785
  try {
789
786
  const resolved = yield ((_a = provider.getStatus) === null || _a === void 0 ? void 0 : _a.call(provider));
790
787
  if (resolved) {
791
- status = { model: resolved.model, provider: resolved.provider };
788
+ status = {
789
+ model: resolved.model,
790
+ provider: resolved.provider,
791
+ // Carried so the mid-loop guard can measure against the window of the
792
+ // provider this call actually resolved to (GENC-1567).
793
+ contextLimit: resolved.contextLimit,
794
+ };
792
795
  }
793
796
  }
794
797
  catch (_b) {
@@ -962,9 +965,62 @@ export class ChatDriver extends EventTarget {
962
965
  return snapshot;
963
966
  }
964
967
  /**
965
- * Optional transform applied to conversation history immediately before each LLM request.
966
- * Cleared when `undefined`. Does not alter stored history.
968
+ * Set the mid-loop context guard. Its margin is deliberately far smaller than
969
+ * the reserve that blocks NEW turns: that reserve exists so an accepted turn can
970
+ * spend it, and a guard set at the same line would kill every turn that used
971
+ * the headroom it was given.
967
972
  */
973
+ setContextGuard(policy) {
974
+ this.contextGuard = policy && policy.marginTokens > 0 ? policy : undefined;
975
+ }
976
+ /**
977
+ * Whether issuing `requestHistory` would run the context window out.
978
+ *
979
+ * Inert unless a margin has been set AND the resolved provider reports a
980
+ * window — with no window there is nothing to measure against, and inventing
981
+ * one to refuse a request is worse than letting the provider answer.
982
+ *
983
+ * Stopping here is strictly better than letting the request go: the provider
984
+ * would reject an oversized prompt outright, leaving a transcript still too
985
+ * large to retry and no explanation the user can act on, whereas ending the
986
+ * turn keeps history intact so compaction is still available and the work so
987
+ * far is not lost.
988
+ */
989
+ contextExhausted(requestHistory, pendingInput) {
990
+ var _a, _b;
991
+ const guard = this.contextGuard;
992
+ if (!guard)
993
+ return undefined;
994
+ // The resolved provider's own window, falling back to the host's assumption
995
+ // only when the provider reports none. Without the fallback a host that knows
996
+ // its window but uses a provider that does not report it got a UI gate and no
997
+ // running-turn guard — the fallback protecting the composer and nothing else.
998
+ const limit = (_a = this.lastResolvedContextLimit) !== null && _a !== void 0 ? _a : guard.fallbackLimit;
999
+ if (limit == null || limit <= 0)
1000
+ return undefined;
1001
+ const threshold = Math.max(0, limit - guard.marginTokens);
1002
+ // The first call of a turn passes the user's text and attachments SEPARATELY
1003
+ // (`sendMessage` appended them to history, and `baseHistory` drops that last
1004
+ // message precisely because they are supplied out of band). Measuring only
1005
+ // `requestHistory` therefore omitted them — so a large paste or a set of
1006
+ // images, the very payloads most likely to overrun a window, were invisible to
1007
+ // the guard that exists to catch them.
1008
+ const request = pendingInput && (pendingInput.content || ((_b = pendingInput.attachments) === null || _b === void 0 ? void 0 : _b.length))
1009
+ ? [
1010
+ ...requestHistory,
1011
+ {
1012
+ role: 'user',
1013
+ content: pendingInput.content,
1014
+ attachments: pendingInput.attachments,
1015
+ },
1016
+ ]
1017
+ : requestHistory;
1018
+ const estimated = estimateRequestTokens(this.history, request);
1019
+ if (estimated < threshold)
1020
+ return undefined;
1021
+ logger.error(`ChatDriver: ending the turn — the request would fill the context window (~${estimated} of ${limit})`);
1022
+ return { estimated, threshold };
1023
+ }
968
1024
  setProviderHistoryTransform(transform) {
969
1025
  this.providerHistoryTransform = transform;
970
1026
  }
@@ -978,6 +1034,26 @@ export class ChatDriver extends EventTarget {
978
1034
  getActiveFoldNames() {
979
1035
  return this.foldStack.map((f) => f.foldName);
980
1036
  }
1037
+ /**
1038
+ * The transcript as it should be **stored**: every tool payload the model has already stopped
1039
+ * seeing replaced by the same stub it was sent (GENC-1566). Live history is untouched — this
1040
+ * returns a copy.
1041
+ *
1042
+ * Condensation deliberately spares stored history, because the live transcript and the debug
1043
+ * export both read it. A host writing that transcript to durable storage inherits the other
1044
+ * half of that decision: it stores payloads the driver has already retired, on every autosave,
1045
+ * for the life of the session. Measured on one small Create session — 58,187 chars, 7.4% of a
1046
+ * 790 KB session object, across five payloads worth 14,434 tokens of context, written 86 times.
1047
+ *
1048
+ * Only already-collapsed payloads are touched, so this can never store a stub for something the
1049
+ * model can still see, and it has no side effects (see `condenseForPersistence`, which is
1050
+ * internal). A host that wants the unredacted transcript still calls
1051
+ * {@link ChatDriver.getRawHistory}, and a diagnostics stream that keeps full payloads is
1052
+ * unaffected — which is what makes the elision recoverable.
1053
+ */
1054
+ getHistoryForPersistence() {
1055
+ return condenseForPersistence([...this.history], this.condensePolicies);
1056
+ }
981
1057
  getSuggestions(history, prompt, count, allAgentInfo) {
982
1058
  return __awaiter(this, void 0, void 0, function* () {
983
1059
  // Suggestions are an out-of-turn UI helper, not bound to any single agent —
@@ -1085,8 +1161,19 @@ export class ChatDriver extends EventTarget {
1085
1161
  * turns exists behind a clean boundary. Uses the same `history` `compact()`
1086
1162
  * acts on, so the UI's gate can't disagree with the action (GENC-1351 follow-up).
1087
1163
  */
1088
- canCompact() {
1089
- return findCompactionCut(this.history) != null;
1164
+ canCompact(options) {
1165
+ return this.getCompactionPlan(options) != null;
1166
+ }
1167
+ /**
1168
+ * {@inheritDoc AiDriver.getCompactionPlan}
1169
+ *
1170
+ * Runs against the driver's own `history` — the exact list `compact()` acts on
1171
+ * — so a projection and the compaction it describes can never be computed from
1172
+ * different transcripts (the GENC-1351 follow-up that first made `canCompact`
1173
+ * read `history` rather than a mirrored copy).
1174
+ */
1175
+ getCompactionPlan(options) {
1176
+ return planCompaction(this.history, options);
1090
1177
  }
1091
1178
  /**
1092
1179
  * Destructively compact older turns into a single `compacted-summary` message
@@ -1097,9 +1184,9 @@ export class ChatDriver extends EventTarget {
1097
1184
  * the new one. Returns the created summary message, or `null` when there is
1098
1185
  * nothing worth compacting or the default provider cannot summarize.
1099
1186
  */
1100
- compact() {
1187
+ compact(options) {
1101
1188
  return __awaiter(this, void 0, void 0, function* () {
1102
- var _a, _b, _c, _d, _e, _f;
1189
+ var _a, _b, _c, _d, _e, _f, _g;
1103
1190
  if (this.busy)
1104
1191
  return null;
1105
1192
  const defaultProvider = this.providerRegistry.default();
@@ -1108,7 +1195,10 @@ export class ChatDriver extends EventTarget {
1108
1195
  return null;
1109
1196
  }
1110
1197
  const history = this.history;
1111
- const cut = findCompactionCut(history);
1198
+ // Same plan the affordance was gated on — including its worth-it floor, so a
1199
+ // compaction that would reclaim too little is declined here as well as being
1200
+ // greyed out in the UI.
1201
+ const cut = (_a = planCompaction(history, options)) === null || _a === void 0 ? void 0 : _a.cut;
1112
1202
  if (cut == null)
1113
1203
  return null;
1114
1204
  const toCompact = history.slice(0, cut);
@@ -1122,7 +1212,7 @@ export class ChatDriver extends EventTarget {
1122
1212
  const failMessage = "Sorry, I couldn't compact the conversation just now. Nothing was changed — please try again.";
1123
1213
  let summaryText;
1124
1214
  try {
1125
- summaryText = (_b = (_a = (yield defaultProvider.prompt(userMessage, { systemPrompt }))) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : '';
1215
+ summaryText = (_c = (_b = (yield defaultProvider.prompt(userMessage, { systemPrompt }))) === null || _b === void 0 ? void 0 : _b.trim()) !== null && _c !== void 0 ? _c : '';
1126
1216
  }
1127
1217
  catch (e) {
1128
1218
  logger.warn(`ChatDriver: compaction summary failed: ${e instanceof Error ? e.message : e}`);
@@ -1141,7 +1231,7 @@ export class ChatDriver extends EventTarget {
1141
1231
  break;
1142
1232
  }
1143
1233
  }
1144
- const status = yield ((_c = defaultProvider.getStatus) === null || _c === void 0 ? void 0 : _c.call(defaultProvider));
1234
+ const status = yield ((_d = defaultProvider.getStatus) === null || _d === void 0 ? void 0 : _d.call(defaultProvider));
1145
1235
  const createdAt = new Date().toISOString();
1146
1236
  const summaryMessage = {
1147
1237
  role: 'compacted-summary',
@@ -1150,10 +1240,10 @@ export class ChatDriver extends EventTarget {
1150
1240
  // for the oldest messages and sits at the head of history, so this keeps it
1151
1241
  // sorted to the head in any timestamp-ordered view (the debug timeline) and
1152
1242
  // history time-monotonic. Its true creation time is in compaction.createdAt.
1153
- timestamp: (_e = (_d = toCompact[0]) === null || _d === void 0 ? void 0 : _d.timestamp) !== null && _e !== void 0 ? _e : createdAt,
1243
+ timestamp: (_f = (_e = toCompact[0]) === null || _e === void 0 ? void 0 : _e.timestamp) !== null && _f !== void 0 ? _f : createdAt,
1154
1244
  compaction: {
1155
1245
  compactedCount: toCompact.length,
1156
- coveredThroughTimestamp: (_f = toCompact[toCompact.length - 1]) === null || _f === void 0 ? void 0 : _f.timestamp,
1246
+ coveredThroughTimestamp: (_g = toCompact[toCompact.length - 1]) === null || _g === void 0 ? void 0 : _g.timestamp,
1157
1247
  tokensBefore,
1158
1248
  model: status === null || status === void 0 ? void 0 : status.model,
1159
1249
  createdAt,
@@ -1701,6 +1791,11 @@ export class ChatDriver extends EventTarget {
1701
1791
  // Mark before the first turn so the child forces tool use and reports a
1702
1792
  // typed failure (rather than user-facing text) if it never completes.
1703
1793
  child.markAsSubAgent();
1794
+ // The guard is policy, and a sub-agent runs the same tool loop against the
1795
+ // same window. Without this every child was inert — and the
1796
+ // `context_exhausted` sub-agent failure this feature added was unreachable by
1797
+ // any normal path.
1798
+ child.setContextGuard(this.contextGuard);
1704
1799
  // Propagate disposal: if this (parent) driver is torn down while the
1705
1800
  // sub-agent is mid-flight, dispose the child too so its in-flight request
1706
1801
  // aborts. Detached in the `finally` below once the sub-agent completes.
@@ -2085,7 +2180,7 @@ export class ChatDriver extends EventTarget {
2085
2180
  // oxlint-disable-next-line complexity
2086
2181
  runToolLoop(userInput, attachments, transientPrimer) {
2087
2182
  return __awaiter(this, void 0, void 0, function* () {
2088
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
2183
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
2089
2184
  if (!this.systemPrompt) {
2090
2185
  logger.warn('ChatDriver: no systemPrompt set. The assistant will have no instructions — provide a systemPrompt via agents config or the foundation-ai-assistant property.');
2091
2186
  }
@@ -2399,6 +2494,43 @@ export class ChatDriver extends EventTarget {
2399
2494
  turnSnapshot.provider = this.lastResolvedProvider;
2400
2495
  if (this.lastResolvedModel !== undefined)
2401
2496
  turnSnapshot.model = this.lastResolvedModel;
2497
+ // The context wall (GENC-1567) — checked HERE, at the last possible moment,
2498
+ // rather than at the top of the iteration where it started life.
2499
+ //
2500
+ // Two things are only knowable at this point, and getting either wrong
2501
+ // ended valid turns. `historyForCall` is what will actually be sent, after
2502
+ // `condenseWhen` has stubbed spent tool payloads and per-agent masking has
2503
+ // blanked another agent's — stored history deliberately keeps them in full,
2504
+ // so measuring it refused turns whose real request was a fraction of the
2505
+ // size. And `lastResolvedContextLimit` is the window of the provider THIS
2506
+ // call resolved to, which on an agent switch is not the one the host knew
2507
+ // about when it last pushed a threshold down.
2508
+ const contextStop = this.contextExhausted(historyForCall, {
2509
+ content: userInputForCall,
2510
+ attachments: attachmentsForCall,
2511
+ });
2512
+ if (contextStop) {
2513
+ recordTurnError(this.sessionKey, 'context-exhausted', {
2514
+ agent: this.activeAgentName,
2515
+ provider: this.lastResolvedProviderName,
2516
+ contextTokens: contextStop.estimated,
2517
+ guard: contextStop.threshold,
2518
+ limit: this.lastResolvedContextLimit,
2519
+ iterations,
2520
+ isSubAgent: this.isSubAgent,
2521
+ });
2522
+ if (this.isSubAgent) {
2523
+ this.failSubAgent('context_exhausted');
2524
+ }
2525
+ else {
2526
+ this.appendToHistory({
2527
+ role: 'assistant',
2528
+ content: 'I had to stop here — this conversation has filled the available context. ' +
2529
+ 'Compact it to free up room, then ask me to continue.',
2530
+ });
2531
+ }
2532
+ return this.turnDone('context-exhausted');
2533
+ }
2402
2534
  let response;
2403
2535
  try {
2404
2536
  // oxlint-disable-next-line no-await-in-loop
@@ -2484,13 +2616,46 @@ export class ChatDriver extends EventTarget {
2484
2616
  // error is an untyped transport failure, gets re-issued MAX_SETUP_TRANSPORT_RETRIES times
2485
2617
  // against a wall that cannot move, and then surfaces as "something went wrong on my end" —
2486
2618
  // wrong twice over, since nothing went wrong on our end and trying again will not help.
2619
+ // The REQUEST wall (GENC-1567) — the prompt outgrew the window. Placed
2620
+ // beside the account walls and before the transient-retry step for the
2621
+ // same reason: re-issuing an oversized prompt cannot make it fit, and
2622
+ // without this it surfaces as "something went wrong on my end" when in
2623
+ // fact the user has a clear, effective remedy.
2624
+ //
2625
+ // A backstop rather than the main defence — the mid-loop guard should
2626
+ // normally stop the turn before a request this large is built. It is
2627
+ // reached when the provider reports no context window (nothing for the
2628
+ // guard to measure against), when the estimate ran under, or when a
2629
+ // single request was already too large.
2630
+ if (e instanceof ContextOverflowError) {
2631
+ logger.error('ChatDriver: the prompt exceeded the model context window', e);
2632
+ recordTurnError(this.sessionKey, 'context-exhausted', {
2633
+ agent: this.activeAgentName,
2634
+ provider: this.lastResolvedProviderName,
2635
+ vendor: (_e = vendorTypeOfLabel(e.vendorLabel)) !== null && _e !== void 0 ? _e : this.lastResolvedProvider,
2636
+ promptTokens: e.promptTokens,
2637
+ limitTokens: e.limitTokens,
2638
+ via: 'provider',
2639
+ isSubAgent: this.isSubAgent,
2640
+ });
2641
+ if (this.isSubAgent) {
2642
+ this.failSubAgent('context_exhausted');
2643
+ }
2644
+ else {
2645
+ this.appendToHistory({
2646
+ role: 'assistant',
2647
+ content: DEFAULT_CONTEXT_OVERFLOW_MESSAGE,
2648
+ });
2649
+ }
2650
+ return this.turnDone('context-exhausted');
2651
+ }
2487
2652
  if (e instanceof ProviderRefusedError) {
2488
2653
  if (this.isSubAgent) {
2489
2654
  logger.error('ChatDriver: provider refused the request', e);
2490
2655
  recordTurnError(this.sessionKey, 'provider-refused', {
2491
2656
  agent: this.activeAgentName,
2492
2657
  provider: this.lastResolvedProviderName,
2493
- vendor: (_e = vendorTypeOfLabel(e.vendorLabel)) !== null && _e !== void 0 ? _e : this.lastResolvedProvider,
2658
+ vendor: (_f = vendorTypeOfLabel(e.vendorLabel)) !== null && _f !== void 0 ? _f : this.lastResolvedProvider,
2494
2659
  kind: e.kind,
2495
2660
  upstreamStatus: e.upstreamStatus,
2496
2661
  upstreamType: e.upstreamType,
@@ -2524,7 +2689,7 @@ export class ChatDriver extends EventTarget {
2524
2689
  recordTurnError(this.sessionKey, 'budget-exhausted', {
2525
2690
  agent: this.activeAgentName,
2526
2691
  provider: this.lastResolvedProviderName,
2527
- vendor: (_g = (_f = vendorTypeOfLabel(e.vendorLabel)) !== null && _f !== void 0 ? _f : vendorTypeOfLabel(e.serverVendor)) !== null && _g !== void 0 ? _g : this.lastResolvedProvider,
2692
+ vendor: (_h = (_g = vendorTypeOfLabel(e.vendorLabel)) !== null && _g !== void 0 ? _g : vendorTypeOfLabel(e.serverVendor)) !== null && _h !== void 0 ? _h : this.lastResolvedProvider,
2528
2693
  budgetUsd: e.budgetUsd,
2529
2694
  spentUsd: e.spentUsd,
2530
2695
  isSubAgent: true,
@@ -2622,15 +2787,15 @@ export class ChatDriver extends EventTarget {
2622
2787
  // fallback chain answered on a different model than the one we asked for.
2623
2788
  if (response.model !== undefined)
2624
2789
  turnSnapshot.model = response.model;
2625
- const isThinkingStep = response.content && ((_h = response.toolCalls) === null || _h === void 0 ? void 0 : _h.length);
2626
- const isEmptyResponse = !((_j = response.content) === null || _j === void 0 ? void 0 : _j.trim()) && !((_k = response.toolCalls) === null || _k === void 0 ? void 0 : _k.length);
2790
+ const isThinkingStep = response.content && ((_j = response.toolCalls) === null || _j === void 0 ? void 0 : _j.length);
2791
+ const isEmptyResponse = !((_k = response.content) === null || _k === void 0 ? void 0 : _k.trim()) && !((_l = response.toolCalls) === null || _l === void 0 ? void 0 : _l.length);
2627
2792
  // A pre-output refusal (safety-classifier decline, e.g. Fable 5 `stop_reason: 'refusal'`)
2628
2793
  // comes back with empty content, so it looks like a blank response — but it is deterministic:
2629
2794
  // retrying re-sends the identical request and refuses again, burning up to
2630
2795
  // MAX_EMPTY_RESPONSE_RETRIES turns on the most expensive models for the same outcome, ending
2631
2796
  // in the misleading "blank response" message. Treat it as a terminal, non-retried failure with
2632
2797
  // its own reason and message. (GENC-1461)
2633
- const isRefusal = ((_l = response.responseMeta) === null || _l === void 0 ? void 0 : _l.finishReason) === 'refusal';
2798
+ const isRefusal = ((_m = response.responseMeta) === null || _m === void 0 ? void 0 : _m.finishReason) === 'refusal';
2634
2799
  if (isEmptyResponse) {
2635
2800
  emptyResponseAttempts += 1;
2636
2801
  if (!isRefusal && emptyResponseAttempts < MAX_EMPTY_RESPONSE_RETRIES) {
@@ -2704,7 +2869,7 @@ export class ChatDriver extends EventTarget {
2704
2869
  emptyResponseAttempts = 0;
2705
2870
  malformedAttempts = 0;
2706
2871
  setupTransportAttempts = 0;
2707
- if (!((_m = response.toolCalls) === null || _m === void 0 ? void 0 : _m.length)) {
2872
+ if (!((_o = response.toolCalls) === null || _o === void 0 ? void 0 : _o.length)) {
2708
2873
  break;
2709
2874
  }
2710
2875
  const [toolCalls, systemCalls] = response.toolCalls.reduce((acc, tc) => {
@@ -2934,7 +3099,7 @@ export class ChatDriver extends EventTarget {
2934
3099
  // The response was appended before execution — find it and annotate.
2935
3100
  let tcMsgIdx = -1;
2936
3101
  for (let i = this.history.length - 1; i >= 0; i -= 1) {
2937
- if (this.history[i].role === 'assistant' && ((_o = this.history[i].toolCalls) === null || _o === void 0 ? void 0 : _o.length)) {
3102
+ if (this.history[i].role === 'assistant' && ((_p = this.history[i].toolCalls) === null || _p === void 0 ? void 0 : _p.length)) {
2938
3103
  tcMsgIdx = i;
2939
3104
  break;
2940
3105
  }
@@ -2972,7 +3137,7 @@ export class ChatDriver extends EventTarget {
2972
3137
  const unknownTools = [
2973
3138
  ...new Set([
2974
3139
  ...this.recentUnknownToolNames,
2975
- ...((_p = response.toolCalls) !== null && _p !== void 0 ? _p : [])
3140
+ ...((_q = response.toolCalls) !== null && _q !== void 0 ? _q : [])
2976
3141
  .filter((tc) => unknownToolIds.has(tc.id))
2977
3142
  .map((tc) => tc.name),
2978
3143
  ]),
@@ -2984,7 +3149,7 @@ export class ChatDriver extends EventTarget {
2984
3149
  const staleTools = [
2985
3150
  ...new Set([
2986
3151
  ...this.recentStaleToolNames,
2987
- ...((_q = response.toolCalls) !== null && _q !== void 0 ? _q : [])
3152
+ ...((_r = response.toolCalls) !== null && _r !== void 0 ? _r : [])
2988
3153
  .filter((tc) => staleToolIds.has(tc.id))
2989
3154
  .map((tc) => tc.name),
2990
3155
  ]),
@@ -203,13 +203,37 @@ export class OrchestratingDriver extends EventTarget {
203
203
  getRawHistory() {
204
204
  return this.chatDriver.getHistory();
205
205
  }
206
+ /**
207
+ * Delegates to the inner {@link ChatDriver} — condensation policies are registered there, so it
208
+ * is the only object that knows which payloads the model has stopped seeing (GENC-1566).
209
+ *
210
+ * **This forward is the whole feature, for any multi-agent host.** `AiDriver` declares
211
+ * `getHistoryForPersistence` optional, and `SessionPersister` calls it through `?.`, falling back
212
+ * to {@link OrchestratingDriver.getRawHistory}. This class does NOT extend `ChatDriver` — it
213
+ * wraps one — so without an explicit forward the optional call resolves to `undefined` and every
214
+ * host on an orchestrating driver silently keeps persisting the full payloads. Measured exactly
215
+ * that way on a Create run: 10 `context.condensed` events fired, and the session still stored
216
+ * the two largest `read_context` results at 26,907 and 23,060 chars. The method existed, the
217
+ * tests passed, and the feature did nothing.
218
+ */
219
+ getHistoryForPersistence() {
220
+ return this.chatDriver.getHistoryForPersistence();
221
+ }
222
+ /** {@inheritDoc AiDriver.setContextGuard} */
223
+ setContextGuard(policy) {
224
+ this.chatDriver.setContextGuard(policy);
225
+ }
206
226
  /** {@inheritDoc AiDriver.compact} */
207
- compact() {
208
- return this.chatDriver.compact();
227
+ compact(options) {
228
+ return this.chatDriver.compact(options);
209
229
  }
210
230
  /** {@inheritDoc AiDriver.canCompact} */
211
- canCompact() {
212
- return this.chatDriver.canCompact();
231
+ canCompact(options) {
232
+ return this.chatDriver.canCompact(options);
233
+ }
234
+ /** {@inheritDoc AiDriver.getCompactionPlan} */
235
+ getCompactionPlan(options) {
236
+ return this.chatDriver.getCompactionPlan(options);
213
237
  }
214
238
  /** Delegates to the inner {@link ChatDriver} — turns are captured there. */
215
239
  getTurnSnapshots() {