@genesislcap/ai-assistant 15.5.0 → 15.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-assistant.api.json +27 -0
- package/dist/ai-assistant.d.ts +12 -0
- package/dist/chat-driver.cjs +10 -4
- package/dist/chat-driver.cjs.map +2 -2
- package/dist/chat-driver.mjs +10 -4
- package/dist/chat-driver.mjs.map +2 -2
- package/dist/custom-elements.json +11 -0
- package/dist/dts/components/chat-driver/chat-driver.d.ts +12 -0
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/utils/condense-history.d.ts +14 -0
- package/dist/dts/utils/condense-history.d.ts.map +1 -1
- package/dist/esm/components/chat-driver/chat-driver.js +3 -1
- package/dist/esm/utils/condense-history.js +18 -5
- package/dist/esm/utils/condense-history.test.js +67 -0
- package/package.json +17 -17
- package/src/components/chat-driver/chat-driver.ts +15 -0
- package/src/utils/condense-history.test.ts +103 -0
- package/src/utils/condense-history.ts +33 -3
|
@@ -7505,6 +7505,17 @@
|
|
|
7505
7505
|
"description": "Hard cap on tool-loop iterations.",
|
|
7506
7506
|
"default": "maxToolIterations"
|
|
7507
7507
|
},
|
|
7508
|
+
{
|
|
7509
|
+
"kind": "field",
|
|
7510
|
+
"name": "condenseBatchCalls",
|
|
7511
|
+
"type": {
|
|
7512
|
+
"text": "number"
|
|
7513
|
+
},
|
|
7514
|
+
"privacy": "private",
|
|
7515
|
+
"readonly": true,
|
|
7516
|
+
"description": "Model-calls per condensation batch; `1` collapses as soon as a trigger fires.",
|
|
7517
|
+
"default": "condenseBatchCalls"
|
|
7518
|
+
},
|
|
7508
7519
|
{
|
|
7509
7520
|
"kind": "field",
|
|
7510
7521
|
"name": "sessionKey",
|
|
@@ -96,6 +96,16 @@ export interface ChatDriverConfig {
|
|
|
96
96
|
maxToolIterations?: number;
|
|
97
97
|
/** Hard cap on fold operations. Default `5`. */
|
|
98
98
|
maxFoldOperations?: number;
|
|
99
|
+
/**
|
|
100
|
+
* Collapse `condenseWhen` payloads in batches of this many model-calls rather than as
|
|
101
|
+
* soon as each trigger fires. A positive integer; decimals are floored and anything
|
|
102
|
+
* below `1` becomes `1`. Default `1` — collapse immediately, the historical behaviour.
|
|
103
|
+
*
|
|
104
|
+
* Condensation rewrites history in place, so collapsing continuously breaks the
|
|
105
|
+
* provider's prompt cache on nearly every call of a re-read loop. Raising this trades
|
|
106
|
+
* up to that many calls' worth of extra context for one cache break per batch.
|
|
107
|
+
*/
|
|
108
|
+
condenseBatchCalls?: number;
|
|
99
109
|
/** Ring-buffer size for per-turn snapshots. Default `400`. */
|
|
100
110
|
maxTurnSnapshots?: number;
|
|
101
111
|
/** Session identity used to file meta events onto the shared debug-log timeline. */
|
|
@@ -419,6 +429,8 @@ export declare class ChatDriver extends EventTarget implements AiDriver {
|
|
|
419
429
|
private unsubscribeRegistry?;
|
|
420
430
|
/** Hard cap on tool-loop iterations. */
|
|
421
431
|
private readonly maxToolIterations;
|
|
432
|
+
/** Model-calls per condensation batch; `1` collapses as soon as a trigger fires. */
|
|
433
|
+
private readonly condenseBatchCalls;
|
|
422
434
|
/** Session identity used to file meta events onto the shared debug-log timeline. */
|
|
423
435
|
private readonly sessionKey;
|
|
424
436
|
/** Injected activity bus; defaults to a no-op off-browser (Node/tests/headless). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-driver.d.ts","sourceRoot":"","sources":["../../../../src/components/chat-driver/chat-driver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,kBAAkB,EAGlB,cAAc,EACd,gBAAgB,EAEhB,WAAW,EAGX,cAAc,EAKd,yBAAyB,EAEzB,qBAAqB,EAGtB,MAAM,4BAA4B,CAAC;AAMpC,OAAO,EAAE,KAAK,WAAW,EAAqB,MAAM,+BAA+B,CAAC;AACpF,OAAO,KAAK,EACV,WAAW,EAKX,iBAAiB,EAIjB,oBAAoB,EACpB,iBAAiB,EAElB,MAAM,qBAAqB,CAAC;AAgB7B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAC3E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAW1E,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AA8CxE,wFAAwF;AACxF,eAAO,MAAM,yBAAyB,yBAAyB,CAAC;AAMhE;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,WAAW,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC;AAE9E;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;;OAOG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qGAAqG;IACrG,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gGAAgG;IAChG,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAQD;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,YAAY,CAAC,EAAE,iBAAiB,CAAC;IACjC,iFAAiF;IACjF,eAAe,CAAC,EAAE,oBAAoB,CAAC;IACvC,2DAA2D;IAC3D,YAAY,CAAC,EAAE,iBAAiB,CAAC;IACjC,oDAAoD;IACpD,aAAa,CAAC,EAAE,WAAW,EAAE,CAAC;IAC9B,sDAAsD;IACtD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gDAAgD;IAChD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,qBAAa,UAAW,SAAQ,WAAY,YAAW,QAAQ;
|
|
1
|
+
{"version":3,"file":"chat-driver.d.ts","sourceRoot":"","sources":["../../../../src/components/chat-driver/chat-driver.ts"],"names":[],"mappings":"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"}
|
|
@@ -64,6 +64,20 @@ export interface CondenseContext {
|
|
|
64
64
|
* `phaseEnd` clock. True once `endPhase()` has been called since the call.
|
|
65
65
|
*/
|
|
66
66
|
phaseEnded: (phaseEpoch: number) => boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Collapse in batches of this many model-calls instead of continuously. A positive
|
|
69
|
+
* integer; decimals are floored and anything below `1` becomes `1`. Omit (or `1`) to
|
|
70
|
+
* collapse as soon as a trigger fires, which is the historical behaviour.
|
|
71
|
+
*
|
|
72
|
+
* Condensation rewrites a payload *in place*, so the model-bound history is not
|
|
73
|
+
* append-only and every prompt-cache entry written before the rewrite is invalidated
|
|
74
|
+
* from that position on. Collapsing the moment each trigger fires therefore breaks the
|
|
75
|
+
* cache on nearly every call of a re-read loop — the case condensation exists to
|
|
76
|
+
* contain. Batching holds the collapse set still between boundaries so the prefix is
|
|
77
|
+
* append-only in between, trading up to `batchCalls` calls' worth of extra context for
|
|
78
|
+
* one cache break per batch instead of one per call.
|
|
79
|
+
*/
|
|
80
|
+
batchCalls?: number;
|
|
67
81
|
}
|
|
68
82
|
/**
|
|
69
83
|
* Structured detail for a `context.condensed` meta-event. The index signature
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"condense-history.d.ts","sourceRoot":"","sources":["../../../src/utils/condense-history.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAmB,MAAM,4BAA4B,CAAC;AAE/F;;;;;;;;;;;;;GAaG;AAEH;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAEvC,uFAAuF;AACvF,eAAO,MAAM,kBAAkB,cAAc,CAAC;AAQ9C;;;;;GAKG;AACH,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,cAAc,CAAC;IACvB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,UAAU,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,4FAA4F;IAC5F,eAAe,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;IACjD;;;OAGG;IACH,UAAU,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"condense-history.d.ts","sourceRoot":"","sources":["../../../src/utils/condense-history.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAmB,MAAM,4BAA4B,CAAC;AAE/F;;;;;;;;;;;;;GAaG;AAEH;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAEvC,uFAAuF;AACvF,eAAO,MAAM,kBAAkB,cAAc,CAAC;AAQ9C;;;;;GAKG;AACH,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,cAAc,CAAC;IACvB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,UAAU,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,4FAA4F;IAC5F,eAAe,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;IACjD;;;OAGG;IACH,UAAU,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;IAC5C;;;;;;;;;;;;OAYG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC;IAC5B,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,WAAW,EAAE,MAAM,CAAC;CACrB;AA8CD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,WAAW,EAAE,EACtB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,wBAAwB,CAAC,EAC/C,GAAG,EAAE,eAAe,EACpB,WAAW,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,GAClD,WAAW,EAAE,CAyJf"}
|
|
@@ -230,8 +230,9 @@ export class ChatDriver extends EventTarget {
|
|
|
230
230
|
* picked up on the next turn.
|
|
231
231
|
*/
|
|
232
232
|
this.resolvedStatusCache = new Map();
|
|
233
|
-
const { toolHandlers = {}, toolDefinitions = [], systemPrompt, primerHistory, maxToolIterations = DEFAULT_MAX_TOOL_ITERATIONS, maxFoldOperations = DEFAULT_MAX_FOLD_OPERATIONS, maxTurnSnapshots = DEFAULT_MAX_TURN_SNAPSHOTS, sessionKey = '', activityBus = NOOP_ACTIVITY_BUS, } = config;
|
|
233
|
+
const { toolHandlers = {}, toolDefinitions = [], systemPrompt, primerHistory, maxToolIterations = DEFAULT_MAX_TOOL_ITERATIONS, maxFoldOperations = DEFAULT_MAX_FOLD_OPERATIONS, condenseBatchCalls = 1, maxTurnSnapshots = DEFAULT_MAX_TURN_SNAPSHOTS, sessionKey = '', activityBus = NOOP_ACTIVITY_BUS, } = config;
|
|
234
234
|
this.maxToolIterations = maxToolIterations;
|
|
235
|
+
this.condenseBatchCalls = condenseBatchCalls;
|
|
235
236
|
this.sessionKey = sessionKey;
|
|
236
237
|
this.activityBus = activityBus;
|
|
237
238
|
if (typeof toolHandlers === 'function') {
|
|
@@ -1824,6 +1825,7 @@ export class ChatDriver extends EventTarget {
|
|
|
1824
1825
|
// A call's phase has ended once the app declared a later phase epoch
|
|
1825
1826
|
// current via `endPhase()`.
|
|
1826
1827
|
phaseEnded: (phaseEpoch) => phaseEpoch < this.currentPhaseEpoch,
|
|
1828
|
+
batchCalls: this.condenseBatchCalls,
|
|
1827
1829
|
}, (detail) => recordMetaEvent(this.sessionKey, 'context.condensed', detail));
|
|
1828
1830
|
const normalizedHistory = normalizeForProvider(condensedHistory);
|
|
1829
1831
|
const historyForProvider = this.providerHistoryTransform
|
|
@@ -98,7 +98,7 @@ function estimateTokensSaved(origLen, stubLen) {
|
|
|
98
98
|
* @param onCondensed - Invoked once per payload at its full→stub transition.
|
|
99
99
|
*/
|
|
100
100
|
export function applyCondensation(history, policies, ctx, onCondensed) {
|
|
101
|
-
var _a;
|
|
101
|
+
var _a, _b;
|
|
102
102
|
if (policies.size === 0)
|
|
103
103
|
return history;
|
|
104
104
|
const triggersOf = (entry) => Array.isArray(entry.policy.on) ? entry.policy.on : [entry.policy.on];
|
|
@@ -106,15 +106,26 @@ export function applyCondensation(history, policies, ctx, onCondensed) {
|
|
|
106
106
|
// lookup. For each `superseded` key the LAST call in history order survives.
|
|
107
107
|
// The name lookup lets a tool message (which carries only an id) name its tool
|
|
108
108
|
// in stubs and events.
|
|
109
|
+
// Batch boundary: the collapse set is resolved as of this model-call and stays put
|
|
110
|
+
// until the next boundary, so the prefix is append-only in between. `batchCalls` of 1
|
|
111
|
+
// (the default) makes this `ctx.modelCall` — i.e. no batching.
|
|
112
|
+
const batchCalls = Math.max(1, Math.floor((_a = ctx.batchCalls) !== null && _a !== void 0 ? _a : 1));
|
|
113
|
+
const asOf = batchCalls === 1 ? ctx.modelCall : Math.floor(ctx.modelCall / batchCalls) * batchCalls;
|
|
114
|
+
// A call made after the boundary is invisible to this pass in BOTH directions: it
|
|
115
|
+
// cannot supersede an older call (which would collapse the older payload early), and
|
|
116
|
+
// it cannot itself collapse (which would elide the very result just fetched). Unbatched,
|
|
117
|
+
// the boundary is the live clock and this gate is off entirely — a call registered on
|
|
118
|
+
// the current model-call is still eligible, as it always was.
|
|
119
|
+
const withinBatch = (entry) => batchCalls === 1 || entry.iteration <= asOf;
|
|
109
120
|
const latestByKey = new Map();
|
|
110
121
|
const nameById = new Map();
|
|
111
122
|
for (const msg of history) {
|
|
112
|
-
if (!((
|
|
123
|
+
if (!((_b = msg.toolCalls) === null || _b === void 0 ? void 0 : _b.length))
|
|
113
124
|
continue;
|
|
114
125
|
for (const tc of msg.toolCalls) {
|
|
115
126
|
nameById.set(tc.id, tc.name);
|
|
116
127
|
const entry = policies.get(tc.id);
|
|
117
|
-
if (!entry)
|
|
128
|
+
if (!entry || !withinBatch(entry))
|
|
118
129
|
continue;
|
|
119
130
|
for (const t of triggersOf(entry)) {
|
|
120
131
|
if (t.kind === 'superseded')
|
|
@@ -128,7 +139,7 @@ export function applyCondensation(history, policies, ctx, onCondensed) {
|
|
|
128
139
|
// past the last allowed view: turns:1 → seen once, then gone. Monotonic
|
|
129
140
|
// clock, so this holds across turn boundaries too.
|
|
130
141
|
case 'age':
|
|
131
|
-
return
|
|
142
|
+
return asOf - entry.iteration > trig.turns;
|
|
132
143
|
case 'turnEnd':
|
|
133
144
|
return ctx.turn > entry.turn;
|
|
134
145
|
case 'agentEnd':
|
|
@@ -144,7 +155,9 @@ export function applyCondensation(history, policies, ctx, onCondensed) {
|
|
|
144
155
|
};
|
|
145
156
|
// The first listed trigger that fires — drives the collapse, the stub reason,
|
|
146
157
|
// and the event label. `undefined` means the payload stays full.
|
|
147
|
-
const firstFired = (toolCallId, entry) =>
|
|
158
|
+
const firstFired = (toolCallId, entry) => withinBatch(entry)
|
|
159
|
+
? triggersOf(entry).find((t) => triggerFires(toolCallId, entry, t))
|
|
160
|
+
: undefined;
|
|
148
161
|
return history.map((msg) => {
|
|
149
162
|
var _a, _b, _c, _d;
|
|
150
163
|
// Tool-call ARGS live on the assistant message.
|
|
@@ -27,6 +27,7 @@ const ctx = (o = {}) => {
|
|
|
27
27
|
turn: (_b = o.turn) !== null && _b !== void 0 ? _b : 0,
|
|
28
28
|
activationEnded: (_c = o.activationEnded) !== null && _c !== void 0 ? _c : (() => false),
|
|
29
29
|
phaseEnded: (_d = o.phaseEnded) !== null && _d !== void 0 ? _d : (() => false),
|
|
30
|
+
batchCalls: o.batchCalls,
|
|
30
31
|
});
|
|
31
32
|
};
|
|
32
33
|
const sink = () => {
|
|
@@ -336,3 +337,69 @@ suite('multi-trigger first-wins: the earliest-listed firing trigger is the reaso
|
|
|
336
337
|
assert.is((_b = events.find((e) => e.toolCallId === 'm2')) === null || _b === void 0 ? void 0 : _b.trigger, 'turnEnd', 'the supersession survivor still collapses via the next trigger');
|
|
337
338
|
});
|
|
338
339
|
suite.run();
|
|
340
|
+
// ── batched collapse (GENC-1476) ───────────────────────────────────────────
|
|
341
|
+
// Condensation rewrites history in place, so collapsing the instant a trigger fires
|
|
342
|
+
// breaks the prompt cache on nearly every call of a re-read loop. Batching holds the
|
|
343
|
+
// collapse set still between boundaries.
|
|
344
|
+
const batch = createLogicSuite('applyCondensation batching');
|
|
345
|
+
/** A re-read loop: `n` reads of the same path, each registered one model-call apart. */
|
|
346
|
+
const reReadLoop = (n) => {
|
|
347
|
+
const history = [];
|
|
348
|
+
const policies = new Map();
|
|
349
|
+
for (let i = 1; i <= n; i += 1) {
|
|
350
|
+
history.push(asstCall(`r${i}`, 'vfs_read', { path: 'A.tsx' }), toolMsg(`r${i}`, big()));
|
|
351
|
+
policies.set(`r${i}`, reg({ on: { kind: 'superseded', by: 'A.tsx' }, response: 'pointer' }, { iteration: i }));
|
|
352
|
+
}
|
|
353
|
+
return { history, policies };
|
|
354
|
+
};
|
|
355
|
+
/** Result content of every tool message, in order. */
|
|
356
|
+
const results = (out) => out.filter((m) => m.role === 'tool').map((m) => { var _a, _b; return (_b = (_a = m.toolResult) === null || _a === void 0 ? void 0 : _a.content) !== null && _b !== void 0 ? _b : ''; });
|
|
357
|
+
batch('without batching every superseded read collapses immediately', () => {
|
|
358
|
+
const { history, policies } = reReadLoop(4);
|
|
359
|
+
const out = results(applyCondensation(history, policies, ctx({ modelCall: 4 }), sink().on));
|
|
360
|
+
// Only the newest survives; the other three are already stubs.
|
|
361
|
+
assert.is(out.filter((c) => c.startsWith('[')).length, 3);
|
|
362
|
+
assert.is(out[3].length, big().length);
|
|
363
|
+
});
|
|
364
|
+
batch('batching leaves the prefix untouched between boundaries', () => {
|
|
365
|
+
const { history, policies } = reReadLoop(4);
|
|
366
|
+
// Boundary is floor(4/10)*10 = 0, so no call is within the batch yet.
|
|
367
|
+
const out = results(applyCondensation(history, policies, ctx({ modelCall: 4, batchCalls: 10 }), sink().on));
|
|
368
|
+
assert.equal(out.map((c) => c.length), [big().length, big().length, big().length, big().length]);
|
|
369
|
+
});
|
|
370
|
+
batch('the batch boundary collapses the backlog in one step', () => {
|
|
371
|
+
const { history, policies } = reReadLoop(12);
|
|
372
|
+
const before = results(applyCondensation(history, policies, ctx({ modelCall: 9, batchCalls: 10 }), sink().on));
|
|
373
|
+
const after = results(applyCondensation(history, policies, ctx({ modelCall: 10, batchCalls: 10 }), sink().on));
|
|
374
|
+
assert.is(before.filter((c) => c.startsWith('[')).length, 0);
|
|
375
|
+
// At the boundary, reads 1-9 collapse together; 10-12 are past it and stay full.
|
|
376
|
+
assert.is(after.filter((c) => c.startsWith('[')).length, 9);
|
|
377
|
+
});
|
|
378
|
+
batch('a call past the boundary neither collapses nor supersedes an older one', () => {
|
|
379
|
+
// The regression this guards: excluding the new call as a SUPERSEDER but still letting
|
|
380
|
+
// it collapse would elide the very result the agent just fetched.
|
|
381
|
+
const { history, policies } = reReadLoop(2);
|
|
382
|
+
policies.set('r2', reg({ on: { kind: 'superseded', by: 'A.tsx' }, response: 'pointer' }, { iteration: 7 }));
|
|
383
|
+
const out = results(applyCondensation(history, policies, ctx({ modelCall: 7, batchCalls: 5 }), sink().on));
|
|
384
|
+
assert.is(out[1].length, big().length, 'the newest read must stay full');
|
|
385
|
+
assert.is(out[0].length, big().length, 'the older read must not collapse early either');
|
|
386
|
+
});
|
|
387
|
+
batch('age is measured against the boundary, not the live clock', () => {
|
|
388
|
+
const history = [asstCall('g1', 'grep', {}), toolMsg('g1', big())];
|
|
389
|
+
const policies = new Map([
|
|
390
|
+
['g1', reg({ on: { kind: 'age', turns: 1 }, response: 'pointer' }, { iteration: 1 })],
|
|
391
|
+
]);
|
|
392
|
+
// Live clock says age 4 > 1 (would collapse); the boundary says 0, so it holds.
|
|
393
|
+
const held = results(applyCondensation(history, policies, ctx({ modelCall: 5, batchCalls: 10 }), sink().on));
|
|
394
|
+
assert.is(held[0].length, big().length);
|
|
395
|
+
const collapsed = results(applyCondensation(history, policies, ctx({ modelCall: 10, batchCalls: 10 }), sink().on));
|
|
396
|
+
assert.ok(collapsed[0].startsWith('['));
|
|
397
|
+
});
|
|
398
|
+
batch('batchCalls of 1, 0 or absent all keep the historical behaviour', () => {
|
|
399
|
+
for (const batchCalls of [1, 0, undefined]) {
|
|
400
|
+
const { history, policies } = reReadLoop(3);
|
|
401
|
+
const out = results(applyCondensation(history, policies, ctx({ modelCall: 3, batchCalls }), sink().on));
|
|
402
|
+
assert.is(out.filter((c) => c.startsWith('[')).length, 2, `batchCalls=${batchCalls}`);
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
batch.run();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genesislcap/ai-assistant",
|
|
3
3
|
"description": "Genesis AI Assistant micro-frontend",
|
|
4
|
-
"version": "15.
|
|
4
|
+
"version": "15.6.0",
|
|
5
5
|
"license": "SEE LICENSE IN license.txt",
|
|
6
6
|
"main": "dist/esm/index.js",
|
|
7
7
|
"types": "dist/ai-assistant.d.ts",
|
|
@@ -73,26 +73,26 @@
|
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@genesislcap/foundation-testing": "15.
|
|
77
|
-
"@genesislcap/genx": "15.
|
|
78
|
-
"@genesislcap/rollup-builder": "15.
|
|
79
|
-
"@genesislcap/ts-builder": "15.
|
|
80
|
-
"@genesislcap/uvu-playwright-builder": "15.
|
|
81
|
-
"@genesislcap/vite-builder": "15.
|
|
82
|
-
"@genesislcap/webpack-builder": "15.
|
|
76
|
+
"@genesislcap/foundation-testing": "15.6.0",
|
|
77
|
+
"@genesislcap/genx": "15.6.0",
|
|
78
|
+
"@genesislcap/rollup-builder": "15.6.0",
|
|
79
|
+
"@genesislcap/ts-builder": "15.6.0",
|
|
80
|
+
"@genesislcap/uvu-playwright-builder": "15.6.0",
|
|
81
|
+
"@genesislcap/vite-builder": "15.6.0",
|
|
82
|
+
"@genesislcap/webpack-builder": "15.6.0",
|
|
83
83
|
"@types/dompurify": "^3.0.5",
|
|
84
84
|
"@types/marked": "^5.0.2",
|
|
85
85
|
"esbuild": "0.25.12"
|
|
86
86
|
},
|
|
87
87
|
"dependencies": {
|
|
88
|
-
"@genesislcap/foundation-ai": "15.
|
|
89
|
-
"@genesislcap/foundation-logger": "15.
|
|
90
|
-
"@genesislcap/foundation-notifications": "15.
|
|
91
|
-
"@genesislcap/foundation-redux": "15.
|
|
92
|
-
"@genesislcap/foundation-ui": "15.
|
|
93
|
-
"@genesislcap/foundation-utils": "15.
|
|
94
|
-
"@genesislcap/rapid-design-system": "15.
|
|
95
|
-
"@genesislcap/web-core": "15.
|
|
88
|
+
"@genesislcap/foundation-ai": "15.6.0",
|
|
89
|
+
"@genesislcap/foundation-logger": "15.6.0",
|
|
90
|
+
"@genesislcap/foundation-notifications": "15.6.0",
|
|
91
|
+
"@genesislcap/foundation-redux": "15.6.0",
|
|
92
|
+
"@genesislcap/foundation-ui": "15.6.0",
|
|
93
|
+
"@genesislcap/foundation-utils": "15.6.0",
|
|
94
|
+
"@genesislcap/rapid-design-system": "15.6.0",
|
|
95
|
+
"@genesislcap/web-core": "15.6.0",
|
|
96
96
|
"dompurify": "^3.3.1",
|
|
97
97
|
"marked": "^17.0.3"
|
|
98
98
|
},
|
|
@@ -105,5 +105,5 @@
|
|
|
105
105
|
"access": "public"
|
|
106
106
|
},
|
|
107
107
|
"customElements": "dist/custom-elements.json",
|
|
108
|
-
"gitHead": "
|
|
108
|
+
"gitHead": "dbf070bac8a4bfc2a178e335b1faffb130e8f761"
|
|
109
109
|
}
|
|
@@ -218,6 +218,16 @@ export interface ChatDriverConfig {
|
|
|
218
218
|
maxToolIterations?: number;
|
|
219
219
|
/** Hard cap on fold operations. Default `5`. */
|
|
220
220
|
maxFoldOperations?: number;
|
|
221
|
+
/**
|
|
222
|
+
* Collapse `condenseWhen` payloads in batches of this many model-calls rather than as
|
|
223
|
+
* soon as each trigger fires. A positive integer; decimals are floored and anything
|
|
224
|
+
* below `1` becomes `1`. Default `1` — collapse immediately, the historical behaviour.
|
|
225
|
+
*
|
|
226
|
+
* Condensation rewrites history in place, so collapsing continuously breaks the
|
|
227
|
+
* provider's prompt cache on nearly every call of a re-read loop. Raising this trades
|
|
228
|
+
* up to that many calls' worth of extra context for one cache break per batch.
|
|
229
|
+
*/
|
|
230
|
+
condenseBatchCalls?: number;
|
|
221
231
|
/** Ring-buffer size for per-turn snapshots. Default `400`. */
|
|
222
232
|
maxTurnSnapshots?: number;
|
|
223
233
|
/** Session identity used to file meta events onto the shared debug-log timeline. */
|
|
@@ -566,6 +576,8 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
566
576
|
|
|
567
577
|
/** Hard cap on tool-loop iterations. */
|
|
568
578
|
private readonly maxToolIterations: number;
|
|
579
|
+
/** Model-calls per condensation batch; `1` collapses as soon as a trigger fires. */
|
|
580
|
+
private readonly condenseBatchCalls: number;
|
|
569
581
|
/** Session identity used to file meta events onto the shared debug-log timeline. */
|
|
570
582
|
private readonly sessionKey: string;
|
|
571
583
|
/** Injected activity bus; defaults to a no-op off-browser (Node/tests/headless). */
|
|
@@ -583,11 +595,13 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
583
595
|
primerHistory,
|
|
584
596
|
maxToolIterations = DEFAULT_MAX_TOOL_ITERATIONS,
|
|
585
597
|
maxFoldOperations = DEFAULT_MAX_FOLD_OPERATIONS,
|
|
598
|
+
condenseBatchCalls = 1,
|
|
586
599
|
maxTurnSnapshots = DEFAULT_MAX_TURN_SNAPSHOTS,
|
|
587
600
|
sessionKey = '',
|
|
588
601
|
activityBus = NOOP_ACTIVITY_BUS,
|
|
589
602
|
} = config;
|
|
590
603
|
this.maxToolIterations = maxToolIterations;
|
|
604
|
+
this.condenseBatchCalls = condenseBatchCalls;
|
|
591
605
|
this.sessionKey = sessionKey;
|
|
592
606
|
this.activityBus = activityBus;
|
|
593
607
|
if (typeof toolHandlers === 'function') {
|
|
@@ -2335,6 +2349,7 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
2335
2349
|
// A call's phase has ended once the app declared a later phase epoch
|
|
2336
2350
|
// current via `endPhase()`.
|
|
2337
2351
|
phaseEnded: (phaseEpoch) => phaseEpoch < this.currentPhaseEpoch,
|
|
2352
|
+
batchCalls: this.condenseBatchCalls,
|
|
2338
2353
|
},
|
|
2339
2354
|
(detail) => recordMetaEvent(this.sessionKey, 'context.condensed', detail),
|
|
2340
2355
|
);
|
|
@@ -44,12 +44,14 @@ const ctx = (
|
|
|
44
44
|
turn?: number;
|
|
45
45
|
activationEnded?: (a: number) => boolean;
|
|
46
46
|
phaseEnded?: (e: number) => boolean;
|
|
47
|
+
batchCalls?: number;
|
|
47
48
|
} = {},
|
|
48
49
|
): CondenseContext => ({
|
|
49
50
|
modelCall: o.modelCall ?? 0,
|
|
50
51
|
turn: o.turn ?? 0,
|
|
51
52
|
activationEnded: o.activationEnded ?? (() => false),
|
|
52
53
|
phaseEnded: o.phaseEnded ?? (() => false),
|
|
54
|
+
batchCalls: o.batchCalls,
|
|
53
55
|
});
|
|
54
56
|
|
|
55
57
|
const sink = () => {
|
|
@@ -434,3 +436,104 @@ suite('multi-trigger first-wins: the earliest-listed firing trigger is the reaso
|
|
|
434
436
|
});
|
|
435
437
|
|
|
436
438
|
suite.run();
|
|
439
|
+
|
|
440
|
+
// ── batched collapse (GENC-1476) ───────────────────────────────────────────
|
|
441
|
+
// Condensation rewrites history in place, so collapsing the instant a trigger fires
|
|
442
|
+
// breaks the prompt cache on nearly every call of a re-read loop. Batching holds the
|
|
443
|
+
// collapse set still between boundaries.
|
|
444
|
+
|
|
445
|
+
const batch = createLogicSuite('applyCondensation batching');
|
|
446
|
+
|
|
447
|
+
/** A re-read loop: `n` reads of the same path, each registered one model-call apart. */
|
|
448
|
+
const reReadLoop = (n: number) => {
|
|
449
|
+
const history: ChatMessage[] = [];
|
|
450
|
+
const policies = new Map<string, RegisteredCondensePolicy>();
|
|
451
|
+
for (let i = 1; i <= n; i += 1) {
|
|
452
|
+
history.push(asstCall(`r${i}`, 'vfs_read', { path: 'A.tsx' }), toolMsg(`r${i}`, big()));
|
|
453
|
+
policies.set(
|
|
454
|
+
`r${i}`,
|
|
455
|
+
reg({ on: { kind: 'superseded', by: 'A.tsx' }, response: 'pointer' }, { iteration: i }),
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
return { history, policies };
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
/** Result content of every tool message, in order. */
|
|
462
|
+
const results = (out: ChatMessage[]): string[] =>
|
|
463
|
+
out.filter((m) => m.role === 'tool').map((m) => m.toolResult?.content ?? '');
|
|
464
|
+
|
|
465
|
+
batch('without batching every superseded read collapses immediately', () => {
|
|
466
|
+
const { history, policies } = reReadLoop(4);
|
|
467
|
+
const out = results(applyCondensation(history, policies, ctx({ modelCall: 4 }), sink().on));
|
|
468
|
+
// Only the newest survives; the other three are already stubs.
|
|
469
|
+
assert.is(out.filter((c) => c.startsWith('[')).length, 3);
|
|
470
|
+
assert.is(out[3].length, big().length);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
batch('batching leaves the prefix untouched between boundaries', () => {
|
|
474
|
+
const { history, policies } = reReadLoop(4);
|
|
475
|
+
// Boundary is floor(4/10)*10 = 0, so no call is within the batch yet.
|
|
476
|
+
const out = results(
|
|
477
|
+
applyCondensation(history, policies, ctx({ modelCall: 4, batchCalls: 10 }), sink().on),
|
|
478
|
+
);
|
|
479
|
+
assert.equal(
|
|
480
|
+
out.map((c) => c.length),
|
|
481
|
+
[big().length, big().length, big().length, big().length],
|
|
482
|
+
);
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
batch('the batch boundary collapses the backlog in one step', () => {
|
|
486
|
+
const { history, policies } = reReadLoop(12);
|
|
487
|
+
const before = results(
|
|
488
|
+
applyCondensation(history, policies, ctx({ modelCall: 9, batchCalls: 10 }), sink().on),
|
|
489
|
+
);
|
|
490
|
+
const after = results(
|
|
491
|
+
applyCondensation(history, policies, ctx({ modelCall: 10, batchCalls: 10 }), sink().on),
|
|
492
|
+
);
|
|
493
|
+
assert.is(before.filter((c) => c.startsWith('[')).length, 0);
|
|
494
|
+
// At the boundary, reads 1-9 collapse together; 10-12 are past it and stay full.
|
|
495
|
+
assert.is(after.filter((c) => c.startsWith('[')).length, 9);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
batch('a call past the boundary neither collapses nor supersedes an older one', () => {
|
|
499
|
+
// The regression this guards: excluding the new call as a SUPERSEDER but still letting
|
|
500
|
+
// it collapse would elide the very result the agent just fetched.
|
|
501
|
+
const { history, policies } = reReadLoop(2);
|
|
502
|
+
policies.set(
|
|
503
|
+
'r2',
|
|
504
|
+
reg({ on: { kind: 'superseded', by: 'A.tsx' }, response: 'pointer' }, { iteration: 7 }),
|
|
505
|
+
);
|
|
506
|
+
const out = results(
|
|
507
|
+
applyCondensation(history, policies, ctx({ modelCall: 7, batchCalls: 5 }), sink().on),
|
|
508
|
+
);
|
|
509
|
+
assert.is(out[1].length, big().length, 'the newest read must stay full');
|
|
510
|
+
assert.is(out[0].length, big().length, 'the older read must not collapse early either');
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
batch('age is measured against the boundary, not the live clock', () => {
|
|
514
|
+
const history = [asstCall('g1', 'grep', {}), toolMsg('g1', big())];
|
|
515
|
+
const policies = new Map([
|
|
516
|
+
['g1', reg({ on: { kind: 'age', turns: 1 }, response: 'pointer' }, { iteration: 1 })],
|
|
517
|
+
]);
|
|
518
|
+
// Live clock says age 4 > 1 (would collapse); the boundary says 0, so it holds.
|
|
519
|
+
const held = results(
|
|
520
|
+
applyCondensation(history, policies, ctx({ modelCall: 5, batchCalls: 10 }), sink().on),
|
|
521
|
+
);
|
|
522
|
+
assert.is(held[0].length, big().length);
|
|
523
|
+
const collapsed = results(
|
|
524
|
+
applyCondensation(history, policies, ctx({ modelCall: 10, batchCalls: 10 }), sink().on),
|
|
525
|
+
);
|
|
526
|
+
assert.ok(collapsed[0].startsWith('['));
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
batch('batchCalls of 1, 0 or absent all keep the historical behaviour', () => {
|
|
530
|
+
for (const batchCalls of [1, 0, undefined]) {
|
|
531
|
+
const { history, policies } = reReadLoop(3);
|
|
532
|
+
const out = results(
|
|
533
|
+
applyCondensation(history, policies, ctx({ modelCall: 3, batchCalls }), sink().on),
|
|
534
|
+
);
|
|
535
|
+
assert.is(out.filter((c) => c.startsWith('[')).length, 2, `batchCalls=${batchCalls}`);
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
batch.run();
|
|
@@ -75,6 +75,20 @@ export interface CondenseContext {
|
|
|
75
75
|
* `phaseEnd` clock. True once `endPhase()` has been called since the call.
|
|
76
76
|
*/
|
|
77
77
|
phaseEnded: (phaseEpoch: number) => boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Collapse in batches of this many model-calls instead of continuously. A positive
|
|
80
|
+
* integer; decimals are floored and anything below `1` becomes `1`. Omit (or `1`) to
|
|
81
|
+
* collapse as soon as a trigger fires, which is the historical behaviour.
|
|
82
|
+
*
|
|
83
|
+
* Condensation rewrites a payload *in place*, so the model-bound history is not
|
|
84
|
+
* append-only and every prompt-cache entry written before the rewrite is invalidated
|
|
85
|
+
* from that position on. Collapsing the moment each trigger fires therefore breaks the
|
|
86
|
+
* cache on nearly every call of a re-read loop — the case condensation exists to
|
|
87
|
+
* contain. Batching holds the collapse set still between boundaries so the prefix is
|
|
88
|
+
* append-only in between, trading up to `batchCalls` calls' worth of extra context for
|
|
89
|
+
* one cache break per batch instead of one per call.
|
|
90
|
+
*/
|
|
91
|
+
batchCalls?: number;
|
|
78
92
|
}
|
|
79
93
|
|
|
80
94
|
/**
|
|
@@ -193,6 +207,20 @@ export function applyCondensation(
|
|
|
193
207
|
// lookup. For each `superseded` key the LAST call in history order survives.
|
|
194
208
|
// The name lookup lets a tool message (which carries only an id) name its tool
|
|
195
209
|
// in stubs and events.
|
|
210
|
+
// Batch boundary: the collapse set is resolved as of this model-call and stays put
|
|
211
|
+
// until the next boundary, so the prefix is append-only in between. `batchCalls` of 1
|
|
212
|
+
// (the default) makes this `ctx.modelCall` — i.e. no batching.
|
|
213
|
+
const batchCalls = Math.max(1, Math.floor(ctx.batchCalls ?? 1));
|
|
214
|
+
const asOf =
|
|
215
|
+
batchCalls === 1 ? ctx.modelCall : Math.floor(ctx.modelCall / batchCalls) * batchCalls;
|
|
216
|
+
// A call made after the boundary is invisible to this pass in BOTH directions: it
|
|
217
|
+
// cannot supersede an older call (which would collapse the older payload early), and
|
|
218
|
+
// it cannot itself collapse (which would elide the very result just fetched). Unbatched,
|
|
219
|
+
// the boundary is the live clock and this gate is off entirely — a call registered on
|
|
220
|
+
// the current model-call is still eligible, as it always was.
|
|
221
|
+
const withinBatch = (entry: RegisteredCondensePolicy): boolean =>
|
|
222
|
+
batchCalls === 1 || entry.iteration <= asOf;
|
|
223
|
+
|
|
196
224
|
const latestByKey = new Map<string, string>();
|
|
197
225
|
const nameById = new Map<string, string>();
|
|
198
226
|
for (const msg of history) {
|
|
@@ -200,7 +228,7 @@ export function applyCondensation(
|
|
|
200
228
|
for (const tc of msg.toolCalls) {
|
|
201
229
|
nameById.set(tc.id, tc.name);
|
|
202
230
|
const entry = policies.get(tc.id);
|
|
203
|
-
if (!entry) continue;
|
|
231
|
+
if (!entry || !withinBatch(entry)) continue;
|
|
204
232
|
for (const t of triggersOf(entry)) {
|
|
205
233
|
if (t.kind === 'superseded') latestByKey.set(t.by, tc.id);
|
|
206
234
|
}
|
|
@@ -217,7 +245,7 @@ export function applyCondensation(
|
|
|
217
245
|
// past the last allowed view: turns:1 → seen once, then gone. Monotonic
|
|
218
246
|
// clock, so this holds across turn boundaries too.
|
|
219
247
|
case 'age':
|
|
220
|
-
return
|
|
248
|
+
return asOf - entry.iteration > trig.turns;
|
|
221
249
|
case 'turnEnd':
|
|
222
250
|
return ctx.turn > entry.turn;
|
|
223
251
|
case 'agentEnd':
|
|
@@ -238,7 +266,9 @@ export function applyCondensation(
|
|
|
238
266
|
toolCallId: string,
|
|
239
267
|
entry: RegisteredCondensePolicy,
|
|
240
268
|
): CondenseTrigger | undefined =>
|
|
241
|
-
|
|
269
|
+
withinBatch(entry)
|
|
270
|
+
? triggersOf(entry).find((t) => triggerFires(toolCallId, entry, t))
|
|
271
|
+
: undefined;
|
|
242
272
|
|
|
243
273
|
return history.map((msg) => {
|
|
244
274
|
// Tool-call ARGS live on the assistant message.
|