@genesislcap/ai-assistant 15.23.0 → 15.23.1-FUI-2603.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-assistant.api.json +269 -5
- package/dist/ai-assistant.d.ts +256 -31
- package/dist/chat-driver.cjs +125 -58
- package/dist/chat-driver.cjs.map +2 -2
- package/dist/chat-driver.mjs +125 -58
- package/dist/chat-driver.mjs.map +2 -2
- package/dist/custom-elements.json +125 -6
- package/dist/dts/components/ai-driver/ai-driver.d.ts +63 -0
- package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts +7 -0
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +9 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
- package/dist/dts/config/config.d.ts +53 -17
- package/dist/dts/config/config.d.ts.map +1 -1
- package/dist/dts/config/define-stateful-agent.d.ts +16 -1
- package/dist/dts/config/define-stateful-agent.d.ts.map +1 -1
- package/dist/dts/main/main.d.ts +102 -14
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/utils/context-tokens.d.ts +100 -43
- package/dist/dts/utils/context-tokens.d.ts.map +1 -1
- package/dist/dts/utils/history-transform.d.ts +7 -1
- package/dist/dts/utils/history-transform.d.ts.map +1 -1
- package/dist/dts/utils/resolve-context-budget.d.ts +8 -5
- package/dist/dts/utils/resolve-context-budget.d.ts.map +1 -1
- package/dist/esm/components/chat-driver/chat-driver.js +22 -2
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +92 -0
- package/dist/esm/config/define-stateful-agent.js +1 -0
- package/dist/esm/main/main.js +170 -27
- package/dist/esm/utils/context-tokens.js +331 -106
- package/dist/esm/utils/history-transform.js +5 -5
- package/dist/esm/utils/resolve-context-budget.js +37 -7
- package/package.json +16 -16
package/dist/ai-assistant.d.ts
CHANGED
|
@@ -645,6 +645,39 @@ export declare interface AiDriver extends EventTarget {
|
|
|
645
645
|
* retain the active config implement it.
|
|
646
646
|
*/
|
|
647
647
|
getActiveAgent?(): AgentConfig | undefined;
|
|
648
|
+
/**
|
|
649
|
+
* Headroom the next turn could need, and which agent set that requirement
|
|
650
|
+
* (GENC-1582). `undefined` when no agent the turn could land on declares a
|
|
651
|
+
* `contextReserve`, which leaves the ambient default in force.
|
|
652
|
+
*
|
|
653
|
+
* The host cannot work this out for itself: it depends on the user pin, the
|
|
654
|
+
* flow-owner lock and the classifier's candidate set, none of which it can see.
|
|
655
|
+
* Reading the ACTIVE agent instead — which is what the gate did first — asks the
|
|
656
|
+
* wrong question, because the agent that last ran is not the agent about to run.
|
|
657
|
+
* Under automatic routing they are routinely different, and the turn that hands
|
|
658
|
+
* over is exactly the one that needs the room.
|
|
659
|
+
*
|
|
660
|
+
* Optional — only drivers that route implement it.
|
|
661
|
+
*/
|
|
662
|
+
getContextReserve?(): ContextReserveResolution | undefined;
|
|
663
|
+
/**
|
|
664
|
+
* Token cost of the last request's preamble — system prompt, tool definitions
|
|
665
|
+
* and framed tail (GENC-1582). `undefined` before the first call of a turn.
|
|
666
|
+
*
|
|
667
|
+
* A live getter rather than a field stamped on each message, deliberately: every
|
|
668
|
+
* consumer wants the CURRENT preamble, never its history, and a message field
|
|
669
|
+
* would mean a type change in `@genesislcap/foundation-ai` plus churn in every
|
|
670
|
+
* persisted session for a value nobody reads twice.
|
|
671
|
+
*
|
|
672
|
+
* It exists because the preamble cannot be derived reliably. Inferring it as
|
|
673
|
+
* "first measured prompt minus the messages it covered" is accurate once and then
|
|
674
|
+
* goes stale, since each agent brings its own prompt and tool surface — and every
|
|
675
|
+
* attempt to compensate downstream ended up comparing stored characters against
|
|
676
|
+
* wire tokens. The driver is the only place that knows what it just sent.
|
|
677
|
+
*
|
|
678
|
+
* Optional — only drivers that build requests implement it.
|
|
679
|
+
*/
|
|
680
|
+
getPreambleTokens?(): number | undefined;
|
|
648
681
|
/**
|
|
649
682
|
* Set the mid-loop context guard (GENC-1567). `undefined` disables it.
|
|
650
683
|
*
|
|
@@ -1175,13 +1208,15 @@ declare interface BaseAgentConfig {
|
|
|
1175
1208
|
* room it needs so that it does not have to be. An agent whose flow runs a long
|
|
1176
1209
|
* tool loop, or returns large payloads, needs more than the global default.
|
|
1177
1210
|
*
|
|
1178
|
-
* **When it takes effect.**
|
|
1179
|
-
*
|
|
1180
|
-
*
|
|
1181
|
-
*
|
|
1182
|
-
*
|
|
1183
|
-
*
|
|
1184
|
-
*
|
|
1211
|
+
* **When it takes effect.** Before the send, as a real pre-flight (GENC-1582).
|
|
1212
|
+
* The driver resolves it against how routing is currently constrained — the
|
|
1213
|
+
* pinned or flow-owning agent when there is one, otherwise the largest reserve
|
|
1214
|
+
* among the agents the classifier could route to — so a turn is refused before
|
|
1215
|
+
* it can start rather than after. This used to read the agent that was already
|
|
1216
|
+
* ACTIVE, which is a different agent from the one about to run whenever the
|
|
1217
|
+
* orchestrator is doing its job, and the handover turn is exactly the one that
|
|
1218
|
+
* needs the room. The mid-loop context guard remains the backstop for a turn
|
|
1219
|
+
* that outgrows what it reserved.
|
|
1185
1220
|
*
|
|
1186
1221
|
* **Set this from measurement, not from a guess.** Per-turn `inputTokens` is
|
|
1187
1222
|
* recorded for every call, so a single real run of the flow tells you its peak
|
|
@@ -1278,6 +1313,11 @@ export declare class ChatDriver extends EventTarget implements AiDriver {
|
|
|
1278
1313
|
*/
|
|
1279
1314
|
private interactionContexts;
|
|
1280
1315
|
private systemPrompt?;
|
|
1316
|
+
/**
|
|
1317
|
+
* Preamble cost of the most recent request, set at the request-build site.
|
|
1318
|
+
* Read through {@link ChatDriver.getPreambleTokens}.
|
|
1319
|
+
*/
|
|
1320
|
+
private lastPreambleTokens?;
|
|
1281
1321
|
/**
|
|
1282
1322
|
* Resolved tool definitions visible to the LLM. Folds mutate this in place
|
|
1283
1323
|
* (push/pop on open/close). When `toolDefinitionsFactory` is set, this is
|
|
@@ -1950,6 +1990,8 @@ export declare class ChatDriver extends EventTarget implements AiDriver {
|
|
|
1950
1990
|
* nothing worth compacting or the default provider cannot summarize.
|
|
1951
1991
|
*/
|
|
1952
1992
|
compact(options?: CompactionPlanOptions): Promise<ChatMessage | null>;
|
|
1993
|
+
/** {@inheritDoc AiDriver.getPreambleTokens} */
|
|
1994
|
+
getPreambleTokens(): number | undefined;
|
|
1953
1995
|
isBusy(): boolean;
|
|
1954
1996
|
/**
|
|
1955
1997
|
* Wire a parent driver as the host for this driver's interactions. When set,
|
|
@@ -2240,6 +2282,12 @@ declare interface CompactionPlanOptions {
|
|
|
2240
2282
|
tailTokenBudget?: number;
|
|
2241
2283
|
/** Minimum reclaim for the plan to be considered worth running. */
|
|
2242
2284
|
minReclaimTokens?: number;
|
|
2285
|
+
/**
|
|
2286
|
+
* Measured preamble cost, from `AiDriver.getPreambleTokens()` (GENC-1582).
|
|
2287
|
+
* Without it the projection sizes the post-compaction context as if the system
|
|
2288
|
+
* prompt and tool definitions were free, so it lands above the line it aimed at.
|
|
2289
|
+
*/
|
|
2290
|
+
preambleTokens?: number;
|
|
2243
2291
|
}
|
|
2244
2292
|
|
|
2245
2293
|
/** What a compaction at a given cut would cost and reclaim. */
|
|
@@ -2310,21 +2358,87 @@ export declare interface ContextGuardPolicy {
|
|
|
2310
2358
|
}
|
|
2311
2359
|
|
|
2312
2360
|
/**
|
|
2313
|
-
* Context headroom an agent reserves for itself
|
|
2361
|
+
* Context headroom an agent reserves for itself (GENC-1582).
|
|
2362
|
+
*
|
|
2363
|
+
* Two figures, because a turn has two costs and only one of them is permanent.
|
|
2364
|
+
*
|
|
2365
|
+
* @beta
|
|
2366
|
+
*/
|
|
2367
|
+
export declare interface ContextReserve {
|
|
2368
|
+
/**
|
|
2369
|
+
* Tokens that must be free before one of this agent's turns may START — the
|
|
2370
|
+
* agent's PEAK growth, not its lasting cost.
|
|
2371
|
+
*
|
|
2372
|
+
* The peak is the binding figure even for an agent that sheds most of it again:
|
|
2373
|
+
* `condenseWhen` stubs a spent payload at the end of the turn, so the room has
|
|
2374
|
+
* to be there while the turn is running, before any of that fires.
|
|
2375
|
+
*/
|
|
2376
|
+
tokens: number;
|
|
2377
|
+
/**
|
|
2378
|
+
* Extra tokens ABOVE {@link ContextReserve.tokens} at which the warning
|
|
2379
|
+
* appears. Omit to fall back to `chatConfig.context.warnMultiplier`.
|
|
2380
|
+
*
|
|
2381
|
+
* The figure that belongs here is what one of this agent's turns costs
|
|
2382
|
+
* PERMANENTLY — its growth measured after condensation, not at its peak. That
|
|
2383
|
+
* makes the warning land exactly two turns before the block: at the warning
|
|
2384
|
+
* line there is room for a full turn plus the residue it leaves, which is one
|
|
2385
|
+
* turn, and then room for one more.
|
|
2386
|
+
*
|
|
2387
|
+
* It exists because the multiplier cannot express that. `warnMultiplier` scales
|
|
2388
|
+
* the whole reserve, so an agent that peaks at 400k and keeps 30k gets a warning
|
|
2389
|
+
* 800k below the window — a line every one of its turns crosses transiently and
|
|
2390
|
+
* then clears, which is noise rather than a signal.
|
|
2391
|
+
*/
|
|
2392
|
+
warnAheadTokens?: number;
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
/**
|
|
2396
|
+
* Context headroom an agent reserves for itself: a token count, or a
|
|
2397
|
+
* {@link ContextReserve} when the agent also wants to say where the warning goes.
|
|
2314
2398
|
*
|
|
2315
|
-
*
|
|
2316
|
-
*
|
|
2317
|
-
*
|
|
2318
|
-
*
|
|
2319
|
-
*
|
|
2320
|
-
* offering it — a warning after the fact still leaves the configuration inert.
|
|
2399
|
+
* NOT the value-or-resolver shape `provider` / `temperature` / `resumable` use.
|
|
2400
|
+
* The resolver form was drafted and removed before release: the gate is recomputed
|
|
2401
|
+
* from a synchronous getter with no turn context to resolve against, so no path
|
|
2402
|
+
* could ever have invoked one. Accepting a type-correct option guaranteed to do
|
|
2403
|
+
* nothing is worse than not offering it.
|
|
2321
2404
|
*
|
|
2322
|
-
*
|
|
2323
|
-
* existing `number` stays valid.
|
|
2405
|
+
* The object arm was added the way the plain form's docblock anticipated —
|
|
2406
|
+
* additively, so every existing `number` stays valid and means what it did.
|
|
2324
2407
|
*
|
|
2325
2408
|
* @beta
|
|
2326
2409
|
*/
|
|
2327
|
-
export declare type ContextReserveInput = number;
|
|
2410
|
+
export declare type ContextReserveInput = number | ContextReserve;
|
|
2411
|
+
|
|
2412
|
+
/**
|
|
2413
|
+
* How much context headroom the next turn needs, and where that figure came from
|
|
2414
|
+
* (GENC-1582).
|
|
2415
|
+
*
|
|
2416
|
+
* @internal
|
|
2417
|
+
*/
|
|
2418
|
+
declare interface ContextReserveResolution {
|
|
2419
|
+
/** Tokens to keep free for one turn — the winning agent's peak growth. */
|
|
2420
|
+
tokens: number;
|
|
2421
|
+
/**
|
|
2422
|
+
* Extra tokens above {@link ContextReserveResolution.tokens} at which the
|
|
2423
|
+
* warning appears, when the winning agent declared one. Travels WITH the winner
|
|
2424
|
+
* rather than being resolved separately: the two figures describe the same
|
|
2425
|
+
* agent's turn, and pairing a lean agent's warning with a hungry agent's block
|
|
2426
|
+
* would put the warning on the wrong side of it.
|
|
2427
|
+
*/
|
|
2428
|
+
warnAheadTokens?: number;
|
|
2429
|
+
/** The agent whose `contextReserve` this is. */
|
|
2430
|
+
agentName: string;
|
|
2431
|
+
/**
|
|
2432
|
+
* Why this agent's reserve governs — which is what lets a blocked composer say
|
|
2433
|
+
* something TRUE about an agent the user has not invoked yet.
|
|
2434
|
+
*
|
|
2435
|
+
* `locked`: a user pin or a flow-owner lock means the next turn can only go to
|
|
2436
|
+
* this agent, so its reserve is the whole requirement. `reachable-max`: routing
|
|
2437
|
+
* is automatic, so the turn could land on any agent the classifier can pick, and
|
|
2438
|
+
* the hungriest of them is the one that has to fit.
|
|
2439
|
+
*/
|
|
2440
|
+
basis: 'locked' | 'reachable-max';
|
|
2441
|
+
}
|
|
2328
2442
|
|
|
2329
2443
|
/** A model used during a recorded cost session. */
|
|
2330
2444
|
export declare interface CostSessionModelEntry {
|
|
@@ -2734,12 +2848,66 @@ export declare class FoundationAiAssistant extends GenesisElement {
|
|
|
2734
2848
|
* redux actions re-renders without this; a store *swap* needs it.)
|
|
2735
2849
|
*/
|
|
2736
2850
|
private _sessionRef?;
|
|
2851
|
+
/**
|
|
2852
|
+
* {@link FoundationAiAssistant.contextTokens} as of the last moment no turn was
|
|
2853
|
+
* running — the figure {@link FoundationAiAssistant.contextGate} is judged
|
|
2854
|
+
* against (GENC-1582).
|
|
2855
|
+
*
|
|
2856
|
+
* **Why the gate reads a held figure and the gauge does not.** They answer
|
|
2857
|
+
* different questions. The gauge is a meter: "how much context is in play right
|
|
2858
|
+
* now", and a build that momentarily fills 900k of a 1M window should say so.
|
|
2859
|
+
* The gate answers "may the next turn START", which is only meaningful when no
|
|
2860
|
+
* turn is running — the composer is disabled by `busy` throughout one anyway, so
|
|
2861
|
+
* a warning raised mid-turn is unactionable by construction.
|
|
2862
|
+
*
|
|
2863
|
+
* Reading the live figure meant the pill went amber part-way through every large
|
|
2864
|
+
* build and cleared itself at the end, because a `condenseWhen` agent's context
|
|
2865
|
+
* peaks mid-turn and is shed before the turn finishes. A signal that fires and
|
|
2866
|
+
* withdraws on its own each time teaches people to ignore it.
|
|
2867
|
+
*
|
|
2868
|
+
* **Held, not latched.** The distinction matters, because `contextGate` is
|
|
2869
|
+
* documented as derived-never-latched and the reason is a real dead end: a figure
|
|
2870
|
+
* pinned to a conversation that has since shrunk blocks the user out of the very
|
|
2871
|
+
* actions that would fix it. This holds only for the duration of a turn and is
|
|
2872
|
+
* refreshed the moment one settles, so no shrink can outlive it — a compaction
|
|
2873
|
+
* runs under `compacting` rather than `busy` and therefore updates it as it goes,
|
|
2874
|
+
* and Clear drops it outright. What it costs is one turn of lag: a turn that
|
|
2875
|
+
* balloons the resting size does not warn until it ends, which is the same trade
|
|
2876
|
+
* the flow-owner soften already makes deliberately.
|
|
2877
|
+
*
|
|
2878
|
+
* Unset falls back to the live figure, which is correct wherever there is no held
|
|
2879
|
+
* value yet — first paint, and a remount, both of which happen at rest.
|
|
2880
|
+
*/
|
|
2881
|
+
private _gateContextTokens?;
|
|
2737
2882
|
get messages(): ChatMessage[];
|
|
2738
2883
|
set messages(value: ChatMessage[]);
|
|
2739
2884
|
get state(): AiAssistantState;
|
|
2740
2885
|
set state(value: AiAssistantState);
|
|
2741
2886
|
/** Whether a state value counts as "busy" — a turn is active. */
|
|
2742
2887
|
private isBusyState;
|
|
2888
|
+
/**
|
|
2889
|
+
* Re-arm {@link FoundationAiAssistant.contextGate} against the size the turn
|
|
2890
|
+
* just left behind (GENC-1582).
|
|
2891
|
+
*
|
|
2892
|
+
* The final call of a turn is made AFTER `agentEnd` condensation fires, so by
|
|
2893
|
+
* the time a turn settles `contextTokens` is the resting figure rather than the
|
|
2894
|
+
* mid-turn peak — which is the number the reserve's thresholds are drawn
|
|
2895
|
+
* against.
|
|
2896
|
+
*
|
|
2897
|
+
* **Called from BOTH settle paths, and it has to be.** A turn does not end
|
|
2898
|
+
* through the `state` setter: `send()`'s `finally` writes `setState('idle')`
|
|
2899
|
+
* straight to the captured session ref, because the element may have
|
|
2900
|
+
* disconnected mid-turn, and that block then replicates the parts of the
|
|
2901
|
+
* setter's settle that matter. Arming the gate inline in the setter therefore
|
|
2902
|
+
* armed it on the one path a real turn never takes — the gate silently kept
|
|
2903
|
+
* falling back to the live figure and the hold did nothing at all. Found by
|
|
2904
|
+
* driving a real UI Builder turn and reading `_gateContextTokens`, which was
|
|
2905
|
+
* `undefined` from start to finish; no unit test noticed, because the test
|
|
2906
|
+
* harness sets `state` through the property.
|
|
2907
|
+
*
|
|
2908
|
+
* One method rather than two assignments so the two paths cannot drift again.
|
|
2909
|
+
*/
|
|
2910
|
+
private rearmContextGate;
|
|
2743
2911
|
/**
|
|
2744
2912
|
* True while a turn is active — `loading` or `cancelling` (a stop was
|
|
2745
2913
|
* requested but the turn is still winding down). The single "is a turn
|
|
@@ -4053,11 +4221,11 @@ export declare class FoundationAiAssistant extends GenesisElement {
|
|
|
4053
4221
|
* Context-headroom thresholds for the active model (GENC-1567) — the warning
|
|
4054
4222
|
* and block lines, plus the tail budget compaction aims at.
|
|
4055
4223
|
*
|
|
4056
|
-
* The agent
|
|
4057
|
-
*
|
|
4058
|
-
*
|
|
4059
|
-
*
|
|
4060
|
-
*
|
|
4224
|
+
* The per-agent `contextReserve` IS folded in, via the driver (GENC-1582). It
|
|
4225
|
+
* used to be described here as a separate, later check made when an agent took
|
|
4226
|
+
* ownership of a flow — but there is no such check, and by flow entry the send
|
|
4227
|
+
* has already been allowed. Resolving it here, before the send, is what makes it
|
|
4228
|
+
* a gate rather than a label.
|
|
4061
4229
|
*
|
|
4062
4230
|
* Overhead is read off `messages` (the Redux mirror) rather than the driver's
|
|
4063
4231
|
* own history, which the compaction plan uses. That is safe rather than sloppy:
|
|
@@ -4067,22 +4235,26 @@ export declare class FoundationAiAssistant extends GenesisElement {
|
|
|
4067
4235
|
*/
|
|
4068
4236
|
get contextBudget(): ResolvedContextBudget;
|
|
4069
4237
|
/**
|
|
4070
|
-
*
|
|
4238
|
+
* Headroom the next turn needs, resolved against how routing is currently
|
|
4239
|
+
* constrained (GENC-1582), and which agent set it.
|
|
4240
|
+
*
|
|
4241
|
+
* The driver owns the answer because the driver owns routing: whether a pin or a
|
|
4242
|
+
* flow-owner lock is in force, which agents the classifier can reach, and which
|
|
4243
|
+
* one is active. This used to read `getActiveAgent()?.contextReserve` — the agent
|
|
4244
|
+
* that LAST ran — which is a different agent from the one about to run whenever
|
|
4245
|
+
* the orchestrator is doing its job, and the handover turn is precisely the one
|
|
4246
|
+
* that needs the room.
|
|
4071
4247
|
*
|
|
4072
|
-
*
|
|
4073
|
-
*
|
|
4074
|
-
*
|
|
4075
|
-
* is not a pre-flight hook, and gating there would mean aborting a turn in
|
|
4076
|
-
* flight. Reading the active agent instead means a hungry agent's reserve
|
|
4077
|
-
* governs the gate from its next send onwards: one turn later than a true
|
|
4078
|
-
* pre-flight, but synchronous, and it never has to interrupt anything.
|
|
4248
|
+
* Falls back to the active agent for a host-supplied driver that does not
|
|
4249
|
+
* implement the resolution, so an existing single-agent driver keeps the old
|
|
4250
|
+
* behaviour rather than silently losing its reserve.
|
|
4079
4251
|
*
|
|
4080
4252
|
* `contextReserve` is a plain number by design — see `ContextReserveInput` for
|
|
4081
4253
|
* why a resolver form was dropped rather than accepted and ignored.
|
|
4082
4254
|
*
|
|
4083
4255
|
* @internal
|
|
4084
4256
|
*/
|
|
4085
|
-
private get
|
|
4257
|
+
private get contextReserve();
|
|
4086
4258
|
/**
|
|
4087
4259
|
* What compacting right now would reclaim, or `null` when it would not be
|
|
4088
4260
|
* worth it (GENC-1567). Sized against {@link FoundationAiAssistant.contextBudget},
|
|
@@ -4122,6 +4294,36 @@ export declare class FoundationAiAssistant extends GenesisElement {
|
|
|
4122
4294
|
* and gating on the way out is the lesser harm; the headroom reserve exists so
|
|
4123
4295
|
* this is rare rather than routine.
|
|
4124
4296
|
*/
|
|
4297
|
+
/**
|
|
4298
|
+
* WHY the composer is blocked, when it is (GENC-1582). `undefined` unless
|
|
4299
|
+
* {@link FoundationAiAssistant.contextGate} is `'blocked'`.
|
|
4300
|
+
*
|
|
4301
|
+
* A cause rather than a fourth gate level: the severity is identical — sends are
|
|
4302
|
+
* refused either way — and every consumer switches on the three levels. What
|
|
4303
|
+
* differs is what the user can do about it, which is worth splitting because the
|
|
4304
|
+
* two answers have no remedy in common.
|
|
4305
|
+
*
|
|
4306
|
+
* - `'transcript'` — the conversation has grown into the reserve. Compacting or
|
|
4307
|
+
* clearing helps, and the copy says so.
|
|
4308
|
+
* - `'model'` — the active agent's reserve meets or exceeds the whole window, so
|
|
4309
|
+
* `blockAt` is 0 and EVERY message is refused, including the first of an empty
|
|
4310
|
+
* chat. Neither compacting nor clearing can help: the constraint is the model,
|
|
4311
|
+
* not the transcript. Before this existed the user was told to clear the chat,
|
|
4312
|
+
* did, and hit the identical block with no way to understand why.
|
|
4313
|
+
*
|
|
4314
|
+
* Keyed off `blockAt === 0`, deliberately NOT off whether a compaction plan
|
|
4315
|
+
* exists. A null plan has two unrelated causes — nothing left worth summarizing,
|
|
4316
|
+
* versus a window that cannot host the agent at all — and conflating them is what
|
|
4317
|
+
* produced the misleading copy.
|
|
4318
|
+
*
|
|
4319
|
+
* Note the block itself is correct in both cases and must stay: an agent that
|
|
4320
|
+
* measurably cannot complete a turn in this window would otherwise start one and
|
|
4321
|
+
* die mid-loop on a provider context error, with partial writes and no clean
|
|
4322
|
+
* exit. Refusing up front is the better failure.
|
|
4323
|
+
*
|
|
4324
|
+
* @internal
|
|
4325
|
+
*/
|
|
4326
|
+
get contextBlockCause(): 'transcript' | 'model' | undefined;
|
|
4125
4327
|
get contextGate(): 'ok' | 'warn' | 'blocked';
|
|
4126
4328
|
/**
|
|
4127
4329
|
* What the composer notice says about the context gate.
|
|
@@ -5008,6 +5210,8 @@ export declare class OrchestratingDriver extends EventTarget implements AiDriver
|
|
|
5008
5210
|
compact(options?: CompactionPlanOptions): Promise<ChatMessage | null>;
|
|
5009
5211
|
/** {@inheritDoc AiDriver.canCompact} */
|
|
5010
5212
|
canCompact(options?: CompactionPlanOptions): boolean;
|
|
5213
|
+
/** {@inheritDoc AiDriver.getPreambleTokens} */
|
|
5214
|
+
getPreambleTokens(): number | undefined;
|
|
5011
5215
|
/** {@inheritDoc AiDriver.getCompactionPlan} */
|
|
5012
5216
|
getCompactionPlan(options?: CompactionPlanOptions): CompactionProjection | null;
|
|
5013
5217
|
/** Delegates to the inner {@link ChatDriver} — turns are captured there. */
|
|
@@ -5020,6 +5224,12 @@ export declare class OrchestratingDriver extends EventTarget implements AiDriver
|
|
|
5020
5224
|
continueFromHistory(transientPrimer?: ChatMessage[]): Promise<ChatDriverResult>;
|
|
5021
5225
|
/** {@inheritDoc AiDriver.primeRestoredAgentState} */
|
|
5022
5226
|
primeRestoredAgentState(snapshots: Record<string, unknown>): void;
|
|
5227
|
+
/**
|
|
5228
|
+
* {@inheritDoc AiDriver.getContextReserve}
|
|
5229
|
+
*
|
|
5230
|
+
* Two cases, because routing has two modes and they need opposite answers.
|
|
5231
|
+
*/
|
|
5232
|
+
getContextReserve(): ContextReserveResolution | undefined;
|
|
5023
5233
|
/** {@inheritDoc AiDriver.getActiveAgent} */
|
|
5024
5234
|
getActiveAgent(): AgentConfig | undefined;
|
|
5025
5235
|
private applyAgent;
|
|
@@ -5745,6 +5955,21 @@ export declare interface StatefulAgentInit<S> {
|
|
|
5745
5955
|
* (fail-safe — never drop a resumable chat on a predicate bug).
|
|
5746
5956
|
*/
|
|
5747
5957
|
resumable?: (ctx: StatefulAgentPersistenceContext<S>) => boolean | Promise<boolean>;
|
|
5958
|
+
/**
|
|
5959
|
+
* Tokens of context headroom to keep free for one of this agent's turns —
|
|
5960
|
+
* see `AgentConfig.contextReserve`, which this is passed straight through to.
|
|
5961
|
+
*
|
|
5962
|
+
* The third member of the flow-safety group with {@link StatefulAgentInit.resumable}
|
|
5963
|
+
* and {@link StatefulAgentInit.notResumableMessage}, and the one that was missing
|
|
5964
|
+
* (GENC-1582): a stateful flow is the case the reserve was designed for — it is
|
|
5965
|
+
* what runs the long tool loop — and yet the helper for building one could not
|
|
5966
|
+
* express it. Both agents that needed it in the field are built here.
|
|
5967
|
+
*
|
|
5968
|
+
* Set it from measurement. Per-turn `inputTokens` is recorded for every call, so
|
|
5969
|
+
* one real run of the flow gives you its peak growth; leaving it unset inherits
|
|
5970
|
+
* the global default, which is the right starting position.
|
|
5971
|
+
*/
|
|
5972
|
+
contextReserve?: ContextReserveInput;
|
|
5748
5973
|
/**
|
|
5749
5974
|
* Message shown on return when {@link StatefulAgentInit.resumable} returned
|
|
5750
5975
|
* `false`. Falls back to a built-in default when omitted.
|
package/dist/chat-driver.cjs
CHANGED
|
@@ -3858,6 +3858,21 @@ function createInteractionContext(interactionId) {
|
|
|
3858
3858
|
var APPROX_CHARS_PER_TOKEN = 4;
|
|
3859
3859
|
var IMAGE_TOKEN_ESTIMATE = 1600;
|
|
3860
3860
|
var PER_MESSAGE_FRAMING_TOKENS = 4;
|
|
3861
|
+
var PREAMBLE_PROSE_CHARS_PER_TOKEN = 4;
|
|
3862
|
+
var PREAMBLE_JSON_CHARS_PER_TOKEN = 3;
|
|
3863
|
+
var PER_TOOL_FRAMING_TOKENS = 8;
|
|
3864
|
+
function estimatePreambleTokens(input) {
|
|
3865
|
+
const prose = (input.systemPrompt?.length ?? 0) + (input.tailContext?.length ?? 0);
|
|
3866
|
+
let json = 0;
|
|
3867
|
+
for (const def of input.toolDefinitions ?? []) {
|
|
3868
|
+
try {
|
|
3869
|
+
json += JSON.stringify(def).length;
|
|
3870
|
+
} catch {
|
|
3871
|
+
json += def.name?.length ?? 0;
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
return Math.ceil(prose / PREAMBLE_PROSE_CHARS_PER_TOKEN) + Math.ceil(json / PREAMBLE_JSON_CHARS_PER_TOKEN) + (input.toolDefinitions?.length ?? 0) * PER_TOOL_FRAMING_TOKENS;
|
|
3875
|
+
}
|
|
3861
3876
|
var COMPACTION_SUMMARY_TOKEN_ESTIMATE = 1200;
|
|
3862
3877
|
function reachesProvider(m) {
|
|
3863
3878
|
if (m.role === "system-event" || m.role === "synthetic-user") return false;
|
|
@@ -3902,66 +3917,66 @@ function usableAnchor(m, compactedAt) {
|
|
|
3902
3917
|
function firstAnchor(history, compactedAt) {
|
|
3903
3918
|
return history.findIndex((m) => usableAnchor(m, compactedAt));
|
|
3904
3919
|
}
|
|
3905
|
-
function
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
const anchors = [];
|
|
3909
|
-
history.forEach((m, i) => {
|
|
3910
|
-
if (usableAnchor(m, compactedAt)) anchors.push(i);
|
|
3911
|
-
});
|
|
3912
|
-
for (let a = 0; a < anchors.length - 1; a += 1) {
|
|
3913
|
-
const from = anchors[a];
|
|
3914
|
-
const to = anchors[a + 1];
|
|
3915
|
-
const measured = history[to].inputTokens - history[from].inputTokens;
|
|
3916
|
-
if (measured <= 0) continue;
|
|
3917
|
-
let baseline = 0;
|
|
3918
|
-
for (let i = from; i < to; i += 1) baseline += estimates[i];
|
|
3919
|
-
if (baseline <= 0) {
|
|
3920
|
-
estimates[from] = measured;
|
|
3921
|
-
continue;
|
|
3922
|
-
}
|
|
3923
|
-
const scale = measured / baseline;
|
|
3924
|
-
let assigned = 0;
|
|
3925
|
-
for (let i = from; i < to - 1; i += 1) {
|
|
3926
|
-
estimates[i] = Math.round(estimates[i] * scale);
|
|
3927
|
-
assigned += estimates[i];
|
|
3928
|
-
}
|
|
3929
|
-
estimates[to - 1] = measured - assigned;
|
|
3920
|
+
function lastAnchor(history, compactedAt) {
|
|
3921
|
+
for (let i = history.length - 1; i >= 0; i -= 1) {
|
|
3922
|
+
if (usableAnchor(history[i], compactedAt)) return i;
|
|
3930
3923
|
}
|
|
3931
|
-
return
|
|
3924
|
+
return -1;
|
|
3932
3925
|
}
|
|
3933
|
-
function
|
|
3934
|
-
const
|
|
3935
|
-
|
|
3936
|
-
const
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
let
|
|
3951
|
-
|
|
3926
|
+
function calibrate(history, preambleTokens) {
|
|
3927
|
+
const baselines = history.map(baselineTokens);
|
|
3928
|
+
let baselineTotal = 0;
|
|
3929
|
+
for (const b of baselines) baselineTotal += b;
|
|
3930
|
+
const compactedAt = compactedAtOf(history);
|
|
3931
|
+
const first = firstAnchor(history, compactedAt);
|
|
3932
|
+
const last = lastAnchor(history, compactedAt);
|
|
3933
|
+
if (first < 0 || last < 0) {
|
|
3934
|
+
const measured = Math.max(0, preambleTokens ?? 0);
|
|
3935
|
+
return {
|
|
3936
|
+
overhead: measured,
|
|
3937
|
+
total: baselineTotal + measured,
|
|
3938
|
+
estimates: baselines,
|
|
3939
|
+
scale: 1
|
|
3940
|
+
};
|
|
3941
|
+
}
|
|
3942
|
+
let sinceLast = 0;
|
|
3943
|
+
for (let i = last; i < baselines.length; i += 1) sinceLast += baselines[i];
|
|
3944
|
+
const total = history[last].inputTokens + sinceLast;
|
|
3945
|
+
let beforeFirst = 0;
|
|
3946
|
+
for (let i = 0; i < first; i += 1) beforeFirst += baselines[i];
|
|
3947
|
+
const derived = Math.max(0, history[first].inputTokens - beforeFirst);
|
|
3948
|
+
const claimed = preambleTokens ?? derived;
|
|
3949
|
+
const contradicted = baselineTotal > 0 && claimed >= total;
|
|
3950
|
+
const overhead = Math.min(contradicted ? derived : claimed, total);
|
|
3951
|
+
const messages = Math.max(0, total - overhead);
|
|
3952
|
+
if (baselineTotal <= 0) {
|
|
3953
|
+
return { overhead, total, estimates: baselines, scale: 1 };
|
|
3954
|
+
}
|
|
3955
|
+
const scale = messages / baselineTotal;
|
|
3956
|
+
const estimates = baselines.map((b) => Math.round(b * scale));
|
|
3957
|
+
let assigned = 0;
|
|
3958
|
+
let heaviest = -1;
|
|
3959
|
+
for (let i = 0; i < estimates.length; i += 1) {
|
|
3960
|
+
assigned += estimates[i];
|
|
3961
|
+
if (baselines[i] > 0 && (heaviest < 0 || baselines[i] > baselines[heaviest])) heaviest = i;
|
|
3962
|
+
}
|
|
3963
|
+
if (heaviest >= 0) estimates[heaviest] += messages - assigned;
|
|
3964
|
+
return { overhead, total, estimates, scale };
|
|
3965
|
+
}
|
|
3966
|
+
function estimateMessageTokens(history, preambleTokens) {
|
|
3967
|
+
return calibrate(history, preambleTokens).estimates;
|
|
3968
|
+
}
|
|
3969
|
+
function estimateRequestTokens(storedHistory, requestHistory, preambleTokens) {
|
|
3970
|
+
const { overhead } = calibrate(storedHistory, preambleTokens);
|
|
3952
3971
|
let requestBaseline = 0;
|
|
3953
3972
|
for (const m of requestHistory) requestBaseline += baselineTokens(m);
|
|
3954
|
-
|
|
3955
|
-
return Math.round(overhead + calibratedMessages * (requestBaseline / storedBaseline));
|
|
3973
|
+
return Math.round(overhead + requestBaseline);
|
|
3956
3974
|
}
|
|
3957
|
-
function projectCompaction(history, cut, summaryTokens = COMPACTION_SUMMARY_TOKEN_ESTIMATE) {
|
|
3958
|
-
const estimates =
|
|
3959
|
-
const overhead = estimateSystemOverhead(history);
|
|
3975
|
+
function projectCompaction(history, cut, summaryTokens = COMPACTION_SUMMARY_TOKEN_ESTIMATE, preambleTokens) {
|
|
3976
|
+
const { estimates, overhead, total } = calibrate(history, preambleTokens);
|
|
3960
3977
|
let tail = 0;
|
|
3961
3978
|
for (let i = cut; i < estimates.length; i += 1) tail += estimates[i];
|
|
3962
|
-
|
|
3963
|
-
for (const t of estimates) all += t;
|
|
3964
|
-
const tokensBefore = overhead + all;
|
|
3979
|
+
const tokensBefore = total;
|
|
3965
3980
|
const tokensAfter = overhead + summaryTokens + tail;
|
|
3966
3981
|
return {
|
|
3967
3982
|
cut,
|
|
@@ -4229,8 +4244,8 @@ function isSafeCompactionCut(history, i) {
|
|
|
4229
4244
|
}
|
|
4230
4245
|
return true;
|
|
4231
4246
|
}
|
|
4232
|
-
function findCompactionCut(history, tailTokenBudget = DEFAULT_TAIL_TOKEN_BUDGET) {
|
|
4233
|
-
const estimates = estimateMessageTokens(history);
|
|
4247
|
+
function findCompactionCut(history, tailTokenBudget = DEFAULT_TAIL_TOKEN_BUDGET, preambleTokens) {
|
|
4248
|
+
const estimates = estimateMessageTokens(history, preambleTokens);
|
|
4234
4249
|
let messagesTotal = 0;
|
|
4235
4250
|
for (const t of estimates) messagesTotal += t;
|
|
4236
4251
|
if (messagesTotal <= tailTokenBudget) return null;
|
|
@@ -4251,9 +4266,14 @@ function planCompaction(history, options = {}) {
|
|
|
4251
4266
|
tailTokenBudget = DEFAULT_TAIL_TOKEN_BUDGET,
|
|
4252
4267
|
minReclaimTokens = DEFAULT_MIN_RECLAIM_TOKENS
|
|
4253
4268
|
} = options;
|
|
4254
|
-
const cut = findCompactionCut(history, tailTokenBudget);
|
|
4269
|
+
const cut = findCompactionCut(history, tailTokenBudget, options.preambleTokens);
|
|
4255
4270
|
if (cut == null) return null;
|
|
4256
|
-
const projection = projectCompaction(
|
|
4271
|
+
const projection = projectCompaction(
|
|
4272
|
+
history,
|
|
4273
|
+
cut,
|
|
4274
|
+
COMPACTION_SUMMARY_TOKEN_ESTIMATE,
|
|
4275
|
+
options.preambleTokens
|
|
4276
|
+
);
|
|
4257
4277
|
return projection.reclaimed >= minReclaimTokens ? projection : null;
|
|
4258
4278
|
}
|
|
4259
4279
|
function renderMessageForSummary(m) {
|
|
@@ -5217,7 +5237,7 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
5217
5237
|
attachments: pendingInput.attachments
|
|
5218
5238
|
}
|
|
5219
5239
|
] : requestHistory;
|
|
5220
|
-
const estimated = estimateRequestTokens(this.history, request);
|
|
5240
|
+
const estimated = estimateRequestTokens(this.history, request, this.lastPreambleTokens);
|
|
5221
5241
|
if (estimated < threshold) return void 0;
|
|
5222
5242
|
logger2.error(
|
|
5223
5243
|
`ChatDriver: ending the turn \u2014 the request would fill the context window (~${estimated} of ${limit})`
|
|
@@ -5405,6 +5425,10 @@ Output format (strict):
|
|
|
5405
5425
|
this.loadHistory([summaryMessage, ...tail]);
|
|
5406
5426
|
return summaryMessage;
|
|
5407
5427
|
}
|
|
5428
|
+
/** {@inheritDoc AiDriver.getPreambleTokens} */
|
|
5429
|
+
getPreambleTokens() {
|
|
5430
|
+
return this.lastPreambleTokens;
|
|
5431
|
+
}
|
|
5408
5432
|
isBusy() {
|
|
5409
5433
|
return this.busy;
|
|
5410
5434
|
}
|
|
@@ -6267,6 +6291,11 @@ ${tailBody}
|
|
|
6267
6291
|
const attachmentsForCall = currentAttachments;
|
|
6268
6292
|
currentInput = "";
|
|
6269
6293
|
currentAttachments = void 0;
|
|
6294
|
+
this.lastPreambleTokens = estimatePreambleTokens({
|
|
6295
|
+
systemPrompt,
|
|
6296
|
+
toolDefinitions: this.toolDefinitions,
|
|
6297
|
+
tailContext
|
|
6298
|
+
});
|
|
6270
6299
|
const options = {
|
|
6271
6300
|
systemPrompt,
|
|
6272
6301
|
// Strip fold-only properties (foldEvent, foldPath) before sending to provider
|
|
@@ -6936,6 +6965,13 @@ function isSpecialist(agent) {
|
|
|
6936
6965
|
function isFallback(agent) {
|
|
6937
6966
|
return agent.fallback === true;
|
|
6938
6967
|
}
|
|
6968
|
+
function normalizeContextReserve(input) {
|
|
6969
|
+
if (input == null) return void 0;
|
|
6970
|
+
const positive = (v) => typeof v === "number" && Number.isFinite(v) && v > 0;
|
|
6971
|
+
if (typeof input === "number") return positive(input) ? { tokens: input } : void 0;
|
|
6972
|
+
if (!positive(input.tokens)) return void 0;
|
|
6973
|
+
return positive(input.warnAheadTokens) ? { tokens: input.tokens, warnAheadTokens: input.warnAheadTokens } : { tokens: input.tokens };
|
|
6974
|
+
}
|
|
6939
6975
|
var UNROUTABLE_PLACEHOLDER = "{{unroutable}}";
|
|
6940
6976
|
var UNROUTABLE_INSTRUCTION = `When the request is substantive but nothing above covers it, say so directly. Do not offer to do it yourself, and do not imply a specialist will pick it up.
|
|
6941
6977
|
|
|
@@ -7137,6 +7173,10 @@ var OrchestratingDriver = class extends EventTarget {
|
|
|
7137
7173
|
canCompact(options) {
|
|
7138
7174
|
return this.chatDriver.canCompact(options);
|
|
7139
7175
|
}
|
|
7176
|
+
/** {@inheritDoc AiDriver.getPreambleTokens} */
|
|
7177
|
+
getPreambleTokens() {
|
|
7178
|
+
return this.chatDriver.getPreambleTokens();
|
|
7179
|
+
}
|
|
7140
7180
|
/** {@inheritDoc AiDriver.getCompactionPlan} */
|
|
7141
7181
|
getCompactionPlan(options) {
|
|
7142
7182
|
return this.chatDriver.getCompactionPlan(options);
|
|
@@ -7258,6 +7298,32 @@ var OrchestratingDriver = class extends EventTarget {
|
|
|
7258
7298
|
primeRestoredAgentState(snapshots) {
|
|
7259
7299
|
this.restoredAgentStates = { ...snapshots };
|
|
7260
7300
|
}
|
|
7301
|
+
/**
|
|
7302
|
+
* {@inheritDoc AiDriver.getContextReserve}
|
|
7303
|
+
*
|
|
7304
|
+
* Two cases, because routing has two modes and they need opposite answers.
|
|
7305
|
+
*/
|
|
7306
|
+
getContextReserve() {
|
|
7307
|
+
const locked = this.resolvePinnedAgent();
|
|
7308
|
+
if (locked) {
|
|
7309
|
+
const reserve = normalizeContextReserve(locked.contextReserve);
|
|
7310
|
+
return reserve == null ? void 0 : { ...reserve, agentName: locked.name, basis: "locked" };
|
|
7311
|
+
}
|
|
7312
|
+
const candidates = [...this.specialists];
|
|
7313
|
+
if (this.fallback) candidates.push(this.fallback);
|
|
7314
|
+
if (this.activeAgent) candidates.push(this.activeAgent);
|
|
7315
|
+
let winner;
|
|
7316
|
+
let agentName = "";
|
|
7317
|
+
for (const agent of candidates) {
|
|
7318
|
+
const reserve = normalizeContextReserve(agent.contextReserve);
|
|
7319
|
+
if (!reserve) continue;
|
|
7320
|
+
if (!winner || reserve.tokens > winner.tokens) {
|
|
7321
|
+
winner = reserve;
|
|
7322
|
+
agentName = agent.name;
|
|
7323
|
+
}
|
|
7324
|
+
}
|
|
7325
|
+
return winner ? { ...winner, agentName, basis: "reachable-max" } : void 0;
|
|
7326
|
+
}
|
|
7261
7327
|
/** {@inheritDoc AiDriver.getActiveAgent} */
|
|
7262
7328
|
getActiveAgent() {
|
|
7263
7329
|
return this.activeAgent;
|
|
@@ -7767,6 +7833,7 @@ function defineStatefulAgent(opts) {
|
|
|
7767
7833
|
onUnresolvedTool: wrappedOnUnresolvedTool,
|
|
7768
7834
|
resumable: wrappedResumable,
|
|
7769
7835
|
notResumableMessage: opts.notResumableMessage,
|
|
7836
|
+
contextReserve: opts.contextReserve,
|
|
7770
7837
|
onActivate: async (ctx) => {
|
|
7771
7838
|
state = await opts.init(ctx);
|
|
7772
7839
|
if (opts.toolHandlers) {
|