@genesislcap/ai-assistant 15.22.2 → 15.23.1-FUI-0.4

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 (36) hide show
  1. package/dist/ai-assistant.api.json +269 -5
  2. package/dist/ai-assistant.d.ts +256 -31
  3. package/dist/chat-driver.cjs +197 -84
  4. package/dist/chat-driver.cjs.map +2 -2
  5. package/dist/chat-driver.mjs +197 -84
  6. package/dist/chat-driver.mjs.map +2 -2
  7. package/dist/custom-elements.json +5483 -5364
  8. package/dist/dts/components/ai-driver/ai-driver.d.ts +63 -0
  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 +7 -0
  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 +9 -1
  13. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
  14. package/dist/dts/config/config.d.ts +53 -17
  15. package/dist/dts/config/config.d.ts.map +1 -1
  16. package/dist/dts/config/define-stateful-agent.d.ts +16 -1
  17. package/dist/dts/config/define-stateful-agent.d.ts.map +1 -1
  18. package/dist/dts/main/main.d.ts +102 -14
  19. package/dist/dts/main/main.d.ts.map +1 -1
  20. package/dist/dts/react.d.ts +12 -12
  21. package/dist/dts/utils/context-tokens.d.ts +100 -43
  22. package/dist/dts/utils/context-tokens.d.ts.map +1 -1
  23. package/dist/dts/utils/history-transform.d.ts +7 -1
  24. package/dist/dts/utils/history-transform.d.ts.map +1 -1
  25. package/dist/dts/utils/resolve-context-budget.d.ts +8 -5
  26. package/dist/dts/utils/resolve-context-budget.d.ts.map +1 -1
  27. package/dist/esm/components/chat-driver/chat-driver.js +22 -2
  28. package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +92 -0
  29. package/dist/esm/config/define-stateful-agent.js +1 -0
  30. package/dist/esm/main/main.js +170 -27
  31. package/dist/esm/utils/context-tokens.js +331 -106
  32. package/dist/esm/utils/history-transform.js +5 -5
  33. package/dist/esm/utils/resolve-context-budget.js +37 -7
  34. package/dist/react.cjs +21 -21
  35. package/dist/react.mjs +20 -20
  36. package/package.json +16 -16
@@ -47,18 +47,6 @@ interface HTMLWCProps extends React.AriaAttributes {
47
47
  onInput?(e: Event): void;
48
48
  }
49
49
 
50
- export declare const FoundationAiAssistant: React.ForwardRefExoticComponent<
51
- React.PropsWithChildren<
52
- Omit<PublicOf<FoundationAiAssistantWC>, 'children' | 'style'> &
53
- HTMLWCProps & {
54
- onBudgetRefreshRequested?: (event: CustomEvent<unknown>) => void;
55
- onChatHeaderMousedown?: (event: CustomEvent<ChatHeaderMouseDownDetail>) => void;
56
- onSessionCleared?: (event: CustomEvent<SessionClearedDetail>) => void;
57
- }
58
- > & React.RefAttributes<FoundationAiAssistantWC>
59
- >;
60
- export type FoundationAiAssistantRef = FoundationAiAssistantWC;
61
-
62
50
  export declare const AiFlowingWavesIndicator: React.ForwardRefExoticComponent<
63
51
  React.PropsWithChildren<
64
52
  Omit<PublicOf<AiFlowingWavesIndicatorWC>, 'children' | 'style'> &
@@ -95,6 +83,18 @@ export declare const AiWavesIndicator: React.ForwardRefExoticComponent<
95
83
  >;
96
84
  export type AiWavesIndicatorRef = AiWavesIndicatorWC;
97
85
 
86
+ export declare const FoundationAiAssistant: React.ForwardRefExoticComponent<
87
+ React.PropsWithChildren<
88
+ Omit<PublicOf<FoundationAiAssistantWC>, 'children' | 'style'> &
89
+ HTMLWCProps & {
90
+ onBudgetRefreshRequested?: (event: CustomEvent<unknown>) => void;
91
+ onChatHeaderMousedown?: (event: CustomEvent<ChatHeaderMouseDownDetail>) => void;
92
+ onSessionCleared?: (event: CustomEvent<SessionClearedDetail>) => void;
93
+ }
94
+ > & React.RefAttributes<FoundationAiAssistantWC>
95
+ >;
96
+ export type FoundationAiAssistantRef = FoundationAiAssistantWC;
97
+
98
98
  export declare const ChatSuggestions: React.ForwardRefExoticComponent<
99
99
  React.PropsWithChildren<
100
100
  Omit<PublicOf<ChatSuggestionsWC>, 'children' | 'style'> &
@@ -16,6 +16,52 @@ import type { ChatMessage } from '@genesislcap/foundation-ai';
16
16
  * shed as tokens) — one definition so the two never drift.
17
17
  */
18
18
  export declare const APPROX_CHARS_PER_TOKEN = 4;
19
+ /**
20
+ * Token cost of everything a request carries that is NOT a message — the system
21
+ * prompt, the tool definitions, and the framed tail (GENC-1582).
22
+ *
23
+ * **Why this is measured per call rather than derived.** The preamble used to be
24
+ * inferred as a residual: the first measured prompt minus the char estimate of the
25
+ * messages it covered. That derivation is accurate at the time, but it is taken
26
+ * ONCE and never updated, while the preamble changes every time the agent changes
27
+ * — each agent brings its own system prompt and its own tool surface. Every
28
+ * attempt to patch the staleness downstream made it worse; the cap that used to
29
+ * sit in `calibrate` compared stored CHARACTERS against a wire-measured TOKEN
30
+ * total and collapsed the preamble to zero on any condensed session, which is the
31
+ * same category error this module exists to fix. Measuring what is actually being
32
+ * sent removes the whole class of problem.
33
+ *
34
+ * **What is measured and what is not.** From the isa-platform session of
35
+ * 2026-09-03 (project 067f0fca, `claude-sonnet-5`), the first anchor measured
36
+ * 5,249 tokens against a single 290-character user message, so:
37
+ *
38
+ * | quantity | value | how |
39
+ * |---|---|---|
40
+ * | whole preamble, Bootstrap's first call | **5,173 tok** | MEASURED (5,249 − ~76 for the message) |
41
+ * | system prompt | **8,387 chars** | MEASURED (diag `turn` records it verbatim) |
42
+ * | system prompt in tokens | 2,097 | prose ratio applied to the measured chars |
43
+ * | 3 tool definitions | 3,076 tok (~1,025/tool) | **INFERRED as the residual** |
44
+ *
45
+ * The tools figure is a residual in TOKENS, not a ratio: diagnostics record
46
+ * `toolNames` only, never the schemas, so their character count is unavailable and
47
+ * `PREAMBLE_JSON_CHARS_PER_TOKEN` could not be calibrated from it. It is consistent
48
+ * with 3 — ~1,025 tokens per tool implies ~3,075 characters of schema per tool,
49
+ * which is the right order for a Create tool carrying descriptions and enum
50
+ * options — but consistent is not measured, and the next person should not read it
51
+ * as such. To calibrate it properly, log serialized schema characters alongside the
52
+ * first anchor for one real session and solve for the ratio.
53
+ *
54
+ * `tailContext` counts. It is regenerated per turn rather than accumulated, so
55
+ * compaction cannot remove it any more than it can remove the system prompt, which
56
+ * is the property that matters to every consumer of this figure.
57
+ */
58
+ export declare function estimatePreambleTokens(input: {
59
+ systemPrompt?: string;
60
+ toolDefinitions?: readonly {
61
+ name: string;
62
+ }[];
63
+ tailContext?: string;
64
+ }): number;
19
65
  /**
20
66
  * Allowance for the summary a compaction will produce, used when projecting the
21
67
  * post-compaction size before the summarizer has run. The system prompt asks for
@@ -83,48 +129,48 @@ export declare function reachesProvider(m: ChatMessage): boolean;
83
129
  */
84
130
  export declare function isContextMeasurable(history: readonly ChatMessage[]): boolean;
85
131
  /**
86
- * Per-message token estimates, calibrated against measured prompt sizes wherever
87
- * the transcript provides them.
88
- *
89
- * **How the calibration works.** `inputTokens` on the message produced by a call
90
- * is the size of that call's WHOLE prompt (the cache buckets break it down
91
- * rather than add to it), so it covers `history[0 … i-1]`. Two consecutive
92
- * anchors therefore bracket a segment whose exact measured cost is their
93
- * difference: `inputTokens[b] − inputTokens[a]` is what `history[a … b-1]` cost.
94
- * Within a segment the measured total is redistributed across its messages in
95
- * proportion to their baseline estimate — segment granularity is all that is
96
- * needed, since compaction only ever cuts on boundaries.
97
- *
98
- * A non-positive delta means history SHRANK between the two calls (a compaction
99
- * or a `condenseWhen` collapse landed in between); there is nothing to
100
- * distribute, so those messages keep their baseline.
101
- *
102
- * The region before the first anchor keeps its baseline too: that anchor's
103
- * measurement also contains the system prompt and tool definitions, which are
104
- * not attributable to any message — see {@link estimateSystemOverhead}.
132
+ * Per-message token estimates — each message's share of {@link calibrate}'s
133
+ * total, in proportion to how big that message is NOW.
134
+ *
135
+ * Used to choose and evaluate a compaction cut, so what matters is that the
136
+ * shares are in the same unit as the total and that they track the current size
137
+ * of each message. A payload `condenseWhen` has stubbed costs what the stub
138
+ * costs; under the segment-chained scheme this replaced, it kept the cost it had
139
+ * when it was full and the difference was merely redistributed to its neighbours.
140
+ *
141
+ * Sums to exactly `estimateContextTokens(history) - estimateSystemOverhead(history)`.
105
142
  */
106
- export declare function estimateMessageTokens(history: readonly ChatMessage[]): number[];
143
+ export declare function estimateMessageTokens(history: readonly ChatMessage[], preambleTokens?: number): number[];
107
144
  /**
108
145
  * Context consumed by everything that is NOT a message — the system prompt and
109
146
  * the tool definitions.
110
147
  *
111
148
  * Derived rather than guessed: the first measured prompt covers the messages
112
- * before it plus that fixed preamble, so subtracting our estimate of those
113
- * messages leaves the preamble. Floored at zero, since an over-estimate of the
114
- * early messages would otherwise produce a negative overhead.
149
+ * before it plus the preamble, so subtracting our estimate of those messages
150
+ * leaves the preamble. Floored at zero, since an over-estimate of the early
151
+ * messages would otherwise produce a negative overhead, and capped at what the
152
+ * current total has left after paying for each message's characters — see
153
+ * {@link calibrate} for why that cap is load-bearing.
154
+ *
155
+ * It is NOT a constant, which the first cut of this assumed. Each agent brings its
156
+ * own system prompt and its own tool surface, so it steps up and down as agents
157
+ * hand over; what is true is that it does not shrink when the MESSAGES do.
115
158
  *
116
159
  * It matters for the gate because it is real context the user never sees in the
117
- * transcript, it does not shrink when history does, and on an agent with a large
118
- * tool surface it is far from negligible.
160
+ * transcript, and on an agent with a large tool surface it is far from negligible.
119
161
  */
120
- export declare function estimateSystemOverhead(history: readonly ChatMessage[]): number;
162
+ export declare function estimateSystemOverhead(history: readonly ChatMessage[], preambleTokens?: number): number;
121
163
  /**
122
- * Total estimated context for a transcript — the preamble plus every message.
164
+ * Total estimated context for a transcript — the figure the gauge shows and the
165
+ * composer gate is judged against.
166
+ *
167
+ * The most recent usable measurement plus the char estimate of everything
168
+ * appended since it, which is as close as anything gets to "how big is the prompt
169
+ * we are about to send". See {@link calibrate} for why it is anchored on that one
170
+ * measurement rather than accumulated across all of them.
123
171
  *
124
- * On a transcript with measured anchors this lands close to the last observed
125
- * `inputTokens` by construction; the value of computing it rather than reading
126
- * that figure is that it stays defined for a hypothetical history (the tail a
127
- * compaction would leave behind), which is what {@link projectCompaction} needs.
172
+ * Stays defined for a hypothetical history (the tail a compaction would leave
173
+ * behind), which is what {@link projectCompaction} needs.
128
174
  */
129
175
  export declare function estimateContextTokens(history: readonly ChatMessage[]): number;
130
176
  /**
@@ -137,18 +183,29 @@ export declare function estimateContextTokens(history: readonly ChatMessage[]):
137
183
  * log. A guard that measured stored history would therefore refuse turns whose
138
184
  * real request is a fraction of the size.
139
185
  *
140
- * Naively estimating the transformed copy does NOT work either, and the reason is
141
- * subtle: `inputTokens` anchors live on the stored messages and measured the
142
- * untransformed prompt, so calibrating against them re-inflates the estimate back
143
- * to roughly what it was before the transform — exactly the figure we were trying
144
- * to avoid.
145
- *
146
- * So the calibrated total is scaled by how much smaller the request is in
147
- * characters. That keeps the accuracy the measurements buy while still reflecting
148
- * what the transform removed. The preamble is carried across unscaled: the system
149
- * prompt and tool definitions are not what condensation touches.
186
+ * So the request is estimated from its OWN characters, plus the preamble. Nothing
187
+ * is scaled: `requestHistory` IS the payload, so `baselineTokens` is already
188
+ * measuring the right bytes, and there is no transform left to correct for.
189
+ *
190
+ * **This previously applied a ratio, and a docblock here previously claimed it did
191
+ * not.** Both were wrong, and the second is the part worth recording. The original
192
+ * expression scaled the stored-history total by `requestChars / storedChars`,
193
+ * reasoning that `inputTokens` "measured the untransformed prompt". That premise is
194
+ * false — `inputTokens` measures the prompt that was SENT, transform already
195
+ * applied — and it is what made every figure in this module too large. The GENC-1582
196
+ * rewrite claimed to drop the ratio but only re-factored it: `scale` is
197
+ * `(total - overhead) / storedBaseline`, so `overhead + requestBaseline * scale` is
198
+ * the identical expression rearranged. With the total now anchored on a measurement
199
+ * of an already-condensed prompt, that discounted the same shrinkage twice —
200
+ * measured at **5,034 against a last measured prompt of 120,000**, i.e. a guard that
201
+ * believed it had 995k spare. Under-estimating is the one direction this figure must
202
+ * not err in, since its only job is refusing an overflow.
203
+ *
204
+ * The preamble must be passed in for the same reason: `requestBaseline` counts
205
+ * message characters, and the system prompt and tool definitions are not messages,
206
+ * so without it the guard silently ignores the entire tool surface.
150
207
  */
151
- export declare function estimateRequestTokens(storedHistory: readonly ChatMessage[], requestHistory: readonly ChatMessage[]): number;
208
+ export declare function estimateRequestTokens(storedHistory: readonly ChatMessage[], requestHistory: readonly ChatMessage[], preambleTokens?: number): number;
152
209
  /** What a compaction at a given cut would cost and reclaim. */
153
210
  export interface CompactionProjection {
154
211
  /** Index the verbatim tail starts at — everything before it is summarized. */
@@ -174,5 +231,5 @@ export interface CompactionProjection {
174
231
  * The system preamble is carried across unchanged: compaction removes messages,
175
232
  * never the system prompt or the tool definitions.
176
233
  */
177
- export declare function projectCompaction(history: readonly ChatMessage[], cut: number, summaryTokens?: number): CompactionProjection;
234
+ export declare function projectCompaction(history: readonly ChatMessage[], cut: number, summaryTokens?: number, preambleTokens?: number): CompactionProjection;
178
235
  //# sourceMappingURL=context-tokens.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"context-tokens.d.ts","sourceRoot":"","sources":["../../../src/utils/context-tokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE9D;;;;;;;;;;GAUG;AAEH;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAexC;;;;;;GAMG;AACH,eAAO,MAAM,iCAAiC,OAAQ,CAAC;AAEvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,WAAW,GAAG,OAAO,CAKvD;AAmGD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,OAAO,CAE5E;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,EAAE,CAmC/E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,CAO9E;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,CAK7E;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,qBAAqB,CACnC,aAAa,EAAE,SAAS,WAAW,EAAE,EACrC,cAAc,EAAE,SAAS,WAAW,EAAE,GACrC,MAAM,CAaR;AAED,+DAA+D;AAC/D,MAAM,WAAW,oBAAoB;IACnC,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAC;IACZ,mDAAmD;IACnD,cAAc,EAAE,MAAM,CAAC;IACvB,6BAA6B;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,GAAG,EAAE,MAAM,EACX,aAAa,GAAE,MAA0C,GACxD,oBAAoB,CAkBtB"}
1
+ {"version":3,"file":"context-tokens.d.ts","sourceRoot":"","sources":["../../../src/utils/context-tokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE9D;;;;;;;;;;GAUG;AAEH;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAC;AA6CxC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,MAAM,CAiBT;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iCAAiC,OAAQ,CAAC;AAEvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,WAAW,GAAG,OAAO,CAKvD;AA6SD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,OAAO,CAE5E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,cAAc,CAAC,EAAE,MAAM,GACtB,MAAM,EAAE,CAEV;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,cAAc,CAAC,EAAE,MAAM,GACtB,MAAM,CAER;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,CAE7E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,qBAAqB,CACnC,aAAa,EAAE,SAAS,WAAW,EAAE,EACrC,cAAc,EAAE,SAAS,WAAW,EAAE,EACtC,cAAc,CAAC,EAAE,MAAM,GACtB,MAAM,CAKR;AAED,+DAA+D;AAC/D,MAAM,WAAW,oBAAoB;IACnC,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAC;IACZ,mDAAmD;IACnD,cAAc,EAAE,MAAM,CAAC;IACvB,6BAA6B;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,GAAG,EAAE,MAAM,EACX,aAAa,GAAE,MAA0C,EACzD,cAAc,CAAC,EAAE,MAAM,GACtB,oBAAoB,CAkBtB"}
@@ -81,13 +81,19 @@ export declare function isSafeCompactionCut(history: readonly ChatMessage[], i:
81
81
  * enormous, indivisible tool group, which no cut can shrink because a cut cannot
82
82
  * split a message.
83
83
  */
84
- export declare function findCompactionCut(history: readonly ChatMessage[], tailTokenBudget?: number): number | null;
84
+ export declare function findCompactionCut(history: readonly ChatMessage[], tailTokenBudget?: number, preambleTokens?: number): number | null;
85
85
  /** Options for {@link planCompaction}. */
86
86
  export interface CompactionPlanOptions {
87
87
  /** Token budget for the verbatim tail. */
88
88
  tailTokenBudget?: number;
89
89
  /** Minimum reclaim for the plan to be considered worth running. */
90
90
  minReclaimTokens?: number;
91
+ /**
92
+ * Measured preamble cost, from `AiDriver.getPreambleTokens()` (GENC-1582).
93
+ * Without it the projection sizes the post-compaction context as if the system
94
+ * prompt and tool definitions were free, so it lands above the line it aimed at.
95
+ */
96
+ preambleTokens?: number;
91
97
  }
92
98
  /**
93
99
  * The single answer to "what would compacting do right now, and is it worth it?"
@@ -1 +1 @@
1
- {"version":3,"file":"history-transform.d.ts","sourceRoot":"","sources":["../../../src/utils/history-transform.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EACL,KAAK,oBAAoB,EAI1B,MAAM,kBAAkB,CAAC;AAoC1B;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,EAAE,CAsBjG;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,WAAW,EAAE,CAG3F;AAID;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAEhD;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,OAAQ,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CASvF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,eAAe,GAAE,MAAkC,GAClD,MAAM,GAAG,IAAI,CAkBf;AAED,0CAA0C;AAC1C,MAAM,WAAW,qBAAqB;IACpC,0CAA0C;IAC1C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mEAAmE;IACnE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,OAAO,GAAE,qBAA0B,GAClC,oBAAoB,GAAG,IAAI,CAS7B;AAkCD;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,SAAS,WAAW,EAAE,GAAG;IAC/E,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB,CAYA;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,WAAW,EAAE,CAanF;AAED,oEAAoE;AACpE,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,WAAW,GAAG,MAAM,CAG7D"}
1
+ {"version":3,"file":"history-transform.d.ts","sourceRoot":"","sources":["../../../src/utils/history-transform.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EAEL,KAAK,oBAAoB,EAI1B,MAAM,kBAAkB,CAAC;AAoC1B;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,EAAE,CAsBjG;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,WAAW,EAAE,CAG3F;AAID;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAEhD;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,OAAQ,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CASvF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,eAAe,GAAE,MAAkC,EACnD,cAAc,CAAC,EAAE,MAAM,GACtB,MAAM,GAAG,IAAI,CAkBf;AAED,0CAA0C;AAC1C,MAAM,WAAW,qBAAqB;IACpC,0CAA0C;IAC1C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mEAAmE;IACnE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,OAAO,GAAE,qBAA0B,GAClC,oBAAoB,GAAG,IAAI,CAc7B;AAkCD;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,SAAS,WAAW,EAAE,GAAG;IAC/E,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB,CAYA;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,WAAW,EAAE,CAanF;AAED,oEAAoE;AACpE,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,WAAW,GAAG,MAAM,CAG7D"}
@@ -77,15 +77,18 @@ export interface ContextBudgetInput {
77
77
  /** Context window reported by the active provider, if any. */
78
78
  contextLimit?: number;
79
79
  /**
80
- * Reserve declared by the active agent, if any — overrides the configured
81
- * global reserve for this agent only.
80
+ * Reserve declared by the agent the next turn could land on, if any — overrides
81
+ * the configured global reserve for that agent only.
82
82
  */
83
83
  agentReserveTokens?: number;
84
84
  /**
85
- * Estimated system prompt + tool definitions. Subtracted when sizing the tail,
86
- * since compaction removes messages and never the preamble.
85
+ * Extra tokens above {@link ContextBudgetInput.agentReserveTokens} at which the
86
+ * warning appears, when that agent declared one (GENC-1582). Takes precedence
87
+ * over `warnMultiplier`, which cannot express it: the multiplier scales the whole
88
+ * reserve, so an agent that peaks large and keeps little gets a warning line
89
+ * every one of its turns crosses transiently and then clears.
87
90
  */
88
- systemOverhead?: number;
91
+ agentWarnAheadTokens?: number;
89
92
  }
90
93
  /**
91
94
  * Resolve the headroom thresholds for the active model and agent.
@@ -1 +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
+ {"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;AAM7D,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;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,kBAAkB,GAAG,qBAAqB,CAgFrF"}
@@ -5,7 +5,7 @@ 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
7
  import { applyCondensation, condenseForPersistence, } from '../../utils/condense-history';
8
- import { estimateRequestTokens } from '../../utils/context-tokens';
8
+ import { estimatePreambleTokens, estimateRequestTokens, } from '../../utils/context-tokens';
9
9
  import { applyHistoryCap, buildCompactionSummaryPrompt, planCompaction, normalizeForProvider, } from '../../utils/history-transform';
10
10
  import { logger } from '../../utils/logger';
11
11
  import { messageUsage, sumUsage } from '../../utils/sum-usage';
@@ -1037,7 +1037,14 @@ export class ChatDriver extends EventTarget {
1037
1037
  },
1038
1038
  ]
1039
1039
  : requestHistory;
1040
- const estimated = estimateRequestTokens(this.history, request);
1040
+ // The preamble is passed EXPLICITLY, and this guard is the reason the parameter
1041
+ // exists (GENC-1582). `requestBaseline` counts message characters, and the
1042
+ // system prompt and tool definitions are not messages — so without it the
1043
+ // guard silently ignores the entire tool surface and, on a history with no
1044
+ // usable anchor, the whole preamble. `lastPreambleTokens` is assigned for THIS
1045
+ // request earlier in the same loop iteration, so it is fresh rather than a
1046
+ // previous turn's.
1047
+ const estimated = estimateRequestTokens(this.history, request, this.lastPreambleTokens);
1041
1048
  if (estimated < threshold)
1042
1049
  return undefined;
1043
1050
  logger.error(`ChatDriver: ending the turn — the request would fill the context window (~${estimated} of ${limit})`);
@@ -1280,6 +1287,10 @@ export class ChatDriver extends EventTarget {
1280
1287
  return summaryMessage;
1281
1288
  });
1282
1289
  }
1290
+ /** {@inheritDoc AiDriver.getPreambleTokens} */
1291
+ getPreambleTokens() {
1292
+ return this.lastPreambleTokens;
1293
+ }
1283
1294
  isBusy() {
1284
1295
  return this.busy;
1285
1296
  }
@@ -2458,6 +2469,15 @@ export class ChatDriver extends EventTarget {
2458
2469
  const attachmentsForCall = currentAttachments;
2459
2470
  currentInput = '';
2460
2471
  currentAttachments = undefined;
2472
+ // Record what this request's preamble costs, at the one point everything it
2473
+ // is made of has been resolved (GENC-1582). Read back by the host through
2474
+ // `getPreambleTokens()` to size the context gate and the compaction target
2475
+ // against the ACTIVE agent's surface rather than a stale first-call residual.
2476
+ this.lastPreambleTokens = estimatePreambleTokens({
2477
+ systemPrompt,
2478
+ toolDefinitions: this.toolDefinitions,
2479
+ tailContext,
2480
+ });
2461
2481
  const options = {
2462
2482
  systemPrompt,
2463
2483
  // Strip fold-only properties (foldEvent, foldPath) before sending to provider
@@ -46,6 +46,40 @@ function isSpecialist(agent) {
46
46
  function isFallback(agent) {
47
47
  return agent.fallback === true;
48
48
  }
49
+ /**
50
+ * Flatten a `contextReserve` declaration to the two numbers the gate reads
51
+ * (GENC-1582), or `undefined` when the agent declared nothing.
52
+ *
53
+ * A bare number is the peak-only form and leaves the warning to the host's
54
+ * `warnMultiplier`. Non-positive `tokens` is treated as no declaration rather
55
+ * than as "needs no headroom": zero would resolve to a reserve of 0, which sets
56
+ * `blockAt` to the whole window and switches the gate off for every agent the
57
+ * turn could reach — the opposite of what declaring a reserve is for.
58
+ *
59
+ * **Validated at runtime, not just by the type.** `contextReserve` is host-supplied
60
+ * `@beta` config and can arrive from JS, from JSON, or from a dynamic source, so
61
+ * `tokens` may be absent or not a number whatever the signature says. That matters
62
+ * more than a violated return type: `undefined <= 0` is `false`, so a bare
63
+ * comparison passed a malformed reserve straight through as `{ tokens: undefined }`,
64
+ * and the caller's `reserve.tokens > winner.tokens` then compared against
65
+ * `undefined` — always false. One malformed entry on an earlier-listed agent
66
+ * therefore SUPPRESSED every valid reserve behind it, and the gate fell back to the
67
+ * ambient default while still naming the malformed agent. Silent, because
68
+ * `resolveContextBudget` coalesces an undefined reserve to that same default
69
+ * (reviewer catch on #2504).
70
+ */
71
+ function normalizeContextReserve(input) {
72
+ if (input == null)
73
+ return undefined;
74
+ const positive = (v) => typeof v === 'number' && Number.isFinite(v) && v > 0;
75
+ if (typeof input === 'number')
76
+ return positive(input) ? { tokens: input } : undefined;
77
+ if (!positive(input.tokens))
78
+ return undefined;
79
+ return positive(input.warnAheadTokens)
80
+ ? { tokens: input.tokens, warnAheadTokens: input.warnAheadTokens }
81
+ : { tokens: input.tokens };
82
+ }
49
83
  /**
50
84
  * Placeholder a consumer can put in its fallback prompt to position
51
85
  * {@link UNROUTABLE_INSTRUCTION} itself, instead of having it appended.
@@ -293,6 +327,10 @@ export class OrchestratingDriver extends EventTarget {
293
327
  canCompact(options) {
294
328
  return this.chatDriver.canCompact(options);
295
329
  }
330
+ /** {@inheritDoc AiDriver.getPreambleTokens} */
331
+ getPreambleTokens() {
332
+ return this.chatDriver.getPreambleTokens();
333
+ }
296
334
  /** {@inheritDoc AiDriver.getCompactionPlan} */
297
335
  getCompactionPlan(options) {
298
336
  return this.chatDriver.getCompactionPlan(options);
@@ -499,6 +537,60 @@ export class OrchestratingDriver extends EventTarget {
499
537
  primeRestoredAgentState(snapshots) {
500
538
  this.restoredAgentStates = Object.assign({}, snapshots);
501
539
  }
540
+ /**
541
+ * {@inheritDoc AiDriver.getContextReserve}
542
+ *
543
+ * Two cases, because routing has two modes and they need opposite answers.
544
+ */
545
+ getContextReserve() {
546
+ // LOCKED — a user pin or a flow-owner lock. `handleTurn` routes straight to
547
+ // this agent and the handoff branch at the bottom of the routing loop refuses
548
+ // to route away while a lock is held, so no other agent's headroom can come
549
+ // into play until it clears. Its reserve is the whole requirement.
550
+ const locked = this.resolvePinnedAgent();
551
+ if (locked) {
552
+ const reserve = normalizeContextReserve(locked.contextReserve);
553
+ return reserve == null ? undefined : Object.assign(Object.assign({}, reserve), { agentName: locked.name, basis: 'locked' });
554
+ }
555
+ // AUTOMATIC — the classifier picks a specialist, may fall through to the
556
+ // fallback, and may hand off and re-classify more than once inside the one
557
+ // turn. Any of them could be the agent that runs, and the send is allowed or
558
+ // refused before we know which, so the requirement is the largest reserve
559
+ // among them: the turn has to fit whichever way it routes.
560
+ //
561
+ // The MAX, not the sum, even though a handoff chain really does add each leg's
562
+ // growth on top of the last. Summing every reachable agent would reserve room
563
+ // for a journey nobody is taking and strand most of the window on a two-line
564
+ // question; the mid-loop context guard is what bounds a chain that does run
565
+ // long. The max is the smallest reserve that makes the FIRST leg safe, which
566
+ // is the only leg this gate can speak for.
567
+ //
568
+ // `excludeFromClassifier` agents are absent by construction — they are
569
+ // reachable only by pin or by a restored flow-owner lock, and both of those
570
+ // are the locked arm above. The active agent is folded in as a floor: on the
571
+ // turn after a lock clears it is still the agent holding the context, and it
572
+ // need not be one the classifier could have picked.
573
+ const candidates = [...this.specialists];
574
+ if (this.fallback)
575
+ candidates.push(this.fallback);
576
+ if (this.activeAgent)
577
+ candidates.push(this.activeAgent);
578
+ // Compared on `tokens` — the figure that decides whether a turn may start.
579
+ // `warnAheadTokens` is not a candidate for the max in its own right; it rides
580
+ // along with whichever agent wins.
581
+ let winner;
582
+ let agentName = '';
583
+ for (const agent of candidates) {
584
+ const reserve = normalizeContextReserve(agent.contextReserve);
585
+ if (!reserve)
586
+ continue;
587
+ if (!winner || reserve.tokens > winner.tokens) {
588
+ winner = reserve;
589
+ agentName = agent.name;
590
+ }
591
+ }
592
+ return winner ? Object.assign(Object.assign({}, winner), { agentName, basis: 'reachable-max' }) : undefined;
593
+ }
502
594
  /** {@inheritDoc AiDriver.getActiveAgent} */
503
595
  getActiveAgent() {
504
596
  return this.activeAgent;
@@ -264,6 +264,7 @@ export function defineStatefulAgent(opts) {
264
264
  onUnresolvedTool: wrappedOnUnresolvedTool,
265
265
  resumable: wrappedResumable,
266
266
  notResumableMessage: opts.notResumableMessage,
267
+ contextReserve: opts.contextReserve,
267
268
  onActivate: (ctx) => __awaiter(this, void 0, void 0, function* () {
268
269
  state = yield opts.init(ctx);
269
270
  // Sample once with the init state to fail loud on fold misuse at the