@genesislcap/ai-assistant 15.6.0 → 15.6.2
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/chat-driver.cjs +6 -1
- package/dist/chat-driver.cjs.map +2 -2
- package/dist/chat-driver.mjs +6 -1
- package/dist/chat-driver.mjs.map +2 -2
- package/dist/dts/utils/condense-history.d.ts +4 -0
- package/dist/dts/utils/condense-history.d.ts.map +1 -1
- package/dist/esm/utils/condense-history.js +18 -3
- package/dist/esm/utils/condense-history.test.js +30 -0
- package/package.json +17 -17
- package/src/utils/condense-history.test.ts +63 -0
- package/src/utils/condense-history.ts +26 -3
|
@@ -76,6 +76,10 @@ export interface CondenseContext {
|
|
|
76
76
|
* contain. Batching holds the collapse set still between boundaries so the prefix is
|
|
77
77
|
* append-only in between, trading up to `batchCalls` calls' worth of extra context for
|
|
78
78
|
* one cache break per batch instead of one per call.
|
|
79
|
+
*
|
|
80
|
+
* `agentEnd` and `phaseEnd` are exempt and still collapse as they fire: they mark a
|
|
81
|
+
* discontinuity that invalidates the cached prefix anyway, so deferring them would hold
|
|
82
|
+
* stale payloads longer for nothing.
|
|
79
83
|
*/
|
|
80
84
|
batchCalls?: number;
|
|
81
85
|
}
|
|
@@ -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;IAC5C
|
|
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;;;;;;;;;;;;;;;;OAgBG;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;AAgED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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,CA0Jf"}
|
|
@@ -60,6 +60,23 @@ function triggerLabel(trigger) {
|
|
|
60
60
|
function estimateTokensSaved(origLen, stubLen) {
|
|
61
61
|
return Math.max(0, Math.ceil((origLen - stubLen) / APPROX_CHARS_PER_TOKEN));
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Whether a trigger marks a genuine discontinuity rather than firing continuously, and so is
|
|
65
|
+
* exempt from batching.
|
|
66
|
+
*
|
|
67
|
+
* `agentEnd` advances only when the active agent's name changes (or a sub-agent completes /
|
|
68
|
+
* the agent is released), which swaps the system prompt and tool definitions — those render
|
|
69
|
+
* before the messages, so the cached prefix is invalidated at that point regardless. `phaseEnd`
|
|
70
|
+
* is the app declaring the same kind of boundary explicitly, and falls back to `agentEnd`.
|
|
71
|
+
* Deferring either to the next batch would buy no cache and only hold stale payloads longer.
|
|
72
|
+
*
|
|
73
|
+
* The rest fire continuously (`superseded`, `age`) or at a point where the history is otherwise
|
|
74
|
+
* append-only (`turnEnd` — a new turn appends a user message, leaving the previous entry live),
|
|
75
|
+
* so batching them is exactly where the cache is won.
|
|
76
|
+
*/
|
|
77
|
+
function marksDiscontinuity(kind) {
|
|
78
|
+
return kind === 'agentEnd' || kind === 'phaseEnd';
|
|
79
|
+
}
|
|
63
80
|
/**
|
|
64
81
|
* Collapse stale tool payloads (declared via `condenseWhen`) out of the
|
|
65
82
|
* model-bound history. Pure over the `history` array — it emits new message
|
|
@@ -155,9 +172,7 @@ export function applyCondensation(history, policies, ctx, onCondensed) {
|
|
|
155
172
|
};
|
|
156
173
|
// The first listed trigger that fires — drives the collapse, the stub reason,
|
|
157
174
|
// and the event label. `undefined` means the payload stays full.
|
|
158
|
-
const firstFired = (toolCallId, entry) => withinBatch(entry)
|
|
159
|
-
? triggersOf(entry).find((t) => triggerFires(toolCallId, entry, t))
|
|
160
|
-
: undefined;
|
|
175
|
+
const firstFired = (toolCallId, entry) => triggersOf(entry).find((t) => (marksDiscontinuity(t.kind) || withinBatch(entry)) && triggerFires(toolCallId, entry, t));
|
|
161
176
|
return history.map((msg) => {
|
|
162
177
|
var _a, _b, _c, _d;
|
|
163
178
|
// Tool-call ARGS live on the assistant message.
|
|
@@ -402,4 +402,34 @@ batch('batchCalls of 1, 0 or absent all keep the historical behaviour', () => {
|
|
|
402
402
|
assert.is(out.filter((c) => c.startsWith('[')).length, 2, `batchCalls=${batchCalls}`);
|
|
403
403
|
}
|
|
404
404
|
});
|
|
405
|
+
batch('agentEnd is exempt from batching — it collapses as it fires', () => {
|
|
406
|
+
// An agent swap changes the system prompt and tool defs, which invalidates the cached
|
|
407
|
+
// prefix anyway, so deferring the collapse would hold a stale payload for nothing.
|
|
408
|
+
const history = [asstCall('a1', 'read_context', {}), toolMsg('a1', big())];
|
|
409
|
+
const policies = new Map([
|
|
410
|
+
['a1', reg({ on: { kind: 'agentEnd' }, response: 'pointer' }, { iteration: 7, activation: 1 })],
|
|
411
|
+
]);
|
|
412
|
+
const out = results(applyCondensation(history, policies, ctx({ modelCall: 7, batchCalls: 10, activationEnded: () => true }), sink().on));
|
|
413
|
+
// Boundary is 0 and the call sits at 7, so a batched trigger would have been deferred.
|
|
414
|
+
assert.ok(out[0].startsWith('['));
|
|
415
|
+
});
|
|
416
|
+
batch('phaseEnd is exempt too', () => {
|
|
417
|
+
const history = [asstCall('p1', 'read_docs', {}), toolMsg('p1', big())];
|
|
418
|
+
const policies = new Map([
|
|
419
|
+
['p1', reg({ on: { kind: 'phaseEnd' }, response: 'pointer' }, { iteration: 7, phaseEpoch: 1 })],
|
|
420
|
+
]);
|
|
421
|
+
const out = results(applyCondensation(history, policies, ctx({ modelCall: 7, batchCalls: 10, phaseEnded: () => true }), sink().on));
|
|
422
|
+
assert.ok(out[0].startsWith('['));
|
|
423
|
+
});
|
|
424
|
+
batch('a mixed policy batches superseded but still honours agentEnd immediately', () => {
|
|
425
|
+
// The vfs shape: `on: [{ superseded }, { agentEnd }]`. Mid-run the supersession is
|
|
426
|
+
// deferred; once the agent ends the payload collapses regardless of the boundary.
|
|
427
|
+
const { history, policies } = reReadLoop(2);
|
|
428
|
+
for (const id of ['r1', 'r2'])
|
|
429
|
+
policies.set(id, reg({ on: [{ kind: 'superseded', by: 'A.tsx' }, { kind: 'agentEnd' }], response: 'pointer' }, { iteration: id === 'r1' ? 1 : 2, activation: 1 }));
|
|
430
|
+
const midRun = results(applyCondensation(history, policies, ctx({ modelCall: 4, batchCalls: 10 }), sink().on));
|
|
431
|
+
assert.is(midRun.filter((c) => c.startsWith('[')).length, 0, 'supersession is deferred');
|
|
432
|
+
const afterSwap = results(applyCondensation(history, policies, ctx({ modelCall: 4, batchCalls: 10, activationEnded: () => true }), sink().on));
|
|
433
|
+
assert.is(afterSwap.filter((c) => c.startsWith('[')).length, 2, 'agentEnd collapses both');
|
|
434
|
+
});
|
|
405
435
|
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.6.
|
|
4
|
+
"version": "15.6.2",
|
|
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.6.
|
|
77
|
-
"@genesislcap/genx": "15.6.
|
|
78
|
-
"@genesislcap/rollup-builder": "15.6.
|
|
79
|
-
"@genesislcap/ts-builder": "15.6.
|
|
80
|
-
"@genesislcap/uvu-playwright-builder": "15.6.
|
|
81
|
-
"@genesislcap/vite-builder": "15.6.
|
|
82
|
-
"@genesislcap/webpack-builder": "15.6.
|
|
76
|
+
"@genesislcap/foundation-testing": "15.6.2",
|
|
77
|
+
"@genesislcap/genx": "15.6.2",
|
|
78
|
+
"@genesislcap/rollup-builder": "15.6.2",
|
|
79
|
+
"@genesislcap/ts-builder": "15.6.2",
|
|
80
|
+
"@genesislcap/uvu-playwright-builder": "15.6.2",
|
|
81
|
+
"@genesislcap/vite-builder": "15.6.2",
|
|
82
|
+
"@genesislcap/webpack-builder": "15.6.2",
|
|
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.6.
|
|
89
|
-
"@genesislcap/foundation-logger": "15.6.
|
|
90
|
-
"@genesislcap/foundation-notifications": "15.6.
|
|
91
|
-
"@genesislcap/foundation-redux": "15.6.
|
|
92
|
-
"@genesislcap/foundation-ui": "15.6.
|
|
93
|
-
"@genesislcap/foundation-utils": "15.6.
|
|
94
|
-
"@genesislcap/rapid-design-system": "15.6.
|
|
95
|
-
"@genesislcap/web-core": "15.6.
|
|
88
|
+
"@genesislcap/foundation-ai": "15.6.2",
|
|
89
|
+
"@genesislcap/foundation-logger": "15.6.2",
|
|
90
|
+
"@genesislcap/foundation-notifications": "15.6.2",
|
|
91
|
+
"@genesislcap/foundation-redux": "15.6.2",
|
|
92
|
+
"@genesislcap/foundation-ui": "15.6.2",
|
|
93
|
+
"@genesislcap/foundation-utils": "15.6.2",
|
|
94
|
+
"@genesislcap/rapid-design-system": "15.6.2",
|
|
95
|
+
"@genesislcap/web-core": "15.6.2",
|
|
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": "a5781780cb56906755ddbfc4be75e221719ae4b4"
|
|
109
109
|
}
|
|
@@ -536,4 +536,67 @@ batch('batchCalls of 1, 0 or absent all keep the historical behaviour', () => {
|
|
|
536
536
|
}
|
|
537
537
|
});
|
|
538
538
|
|
|
539
|
+
batch('agentEnd is exempt from batching — it collapses as it fires', () => {
|
|
540
|
+
// An agent swap changes the system prompt and tool defs, which invalidates the cached
|
|
541
|
+
// prefix anyway, so deferring the collapse would hold a stale payload for nothing.
|
|
542
|
+
const history = [asstCall('a1', 'read_context', {}), toolMsg('a1', big())];
|
|
543
|
+
const policies = new Map([
|
|
544
|
+
['a1', reg({ on: { kind: 'agentEnd' }, response: 'pointer' }, { iteration: 7, activation: 1 })],
|
|
545
|
+
]);
|
|
546
|
+
const out = results(
|
|
547
|
+
applyCondensation(
|
|
548
|
+
history,
|
|
549
|
+
policies,
|
|
550
|
+
ctx({ modelCall: 7, batchCalls: 10, activationEnded: () => true }),
|
|
551
|
+
sink().on,
|
|
552
|
+
),
|
|
553
|
+
);
|
|
554
|
+
// Boundary is 0 and the call sits at 7, so a batched trigger would have been deferred.
|
|
555
|
+
assert.ok(out[0].startsWith('['));
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
batch('phaseEnd is exempt too', () => {
|
|
559
|
+
const history = [asstCall('p1', 'read_docs', {}), toolMsg('p1', big())];
|
|
560
|
+
const policies = new Map([
|
|
561
|
+
['p1', reg({ on: { kind: 'phaseEnd' }, response: 'pointer' }, { iteration: 7, phaseEpoch: 1 })],
|
|
562
|
+
]);
|
|
563
|
+
const out = results(
|
|
564
|
+
applyCondensation(
|
|
565
|
+
history,
|
|
566
|
+
policies,
|
|
567
|
+
ctx({ modelCall: 7, batchCalls: 10, phaseEnded: () => true }),
|
|
568
|
+
sink().on,
|
|
569
|
+
),
|
|
570
|
+
);
|
|
571
|
+
assert.ok(out[0].startsWith('['));
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
batch('a mixed policy batches superseded but still honours agentEnd immediately', () => {
|
|
575
|
+
// The vfs shape: `on: [{ superseded }, { agentEnd }]`. Mid-run the supersession is
|
|
576
|
+
// deferred; once the agent ends the payload collapses regardless of the boundary.
|
|
577
|
+
const { history, policies } = reReadLoop(2);
|
|
578
|
+
for (const id of ['r1', 'r2'])
|
|
579
|
+
policies.set(
|
|
580
|
+
id,
|
|
581
|
+
reg(
|
|
582
|
+
{ on: [{ kind: 'superseded', by: 'A.tsx' }, { kind: 'agentEnd' }], response: 'pointer' },
|
|
583
|
+
{ iteration: id === 'r1' ? 1 : 2, activation: 1 },
|
|
584
|
+
),
|
|
585
|
+
);
|
|
586
|
+
const midRun = results(
|
|
587
|
+
applyCondensation(history, policies, ctx({ modelCall: 4, batchCalls: 10 }), sink().on),
|
|
588
|
+
);
|
|
589
|
+
assert.is(midRun.filter((c) => c.startsWith('[')).length, 0, 'supersession is deferred');
|
|
590
|
+
|
|
591
|
+
const afterSwap = results(
|
|
592
|
+
applyCondensation(
|
|
593
|
+
history,
|
|
594
|
+
policies,
|
|
595
|
+
ctx({ modelCall: 4, batchCalls: 10, activationEnded: () => true }),
|
|
596
|
+
sink().on,
|
|
597
|
+
),
|
|
598
|
+
);
|
|
599
|
+
assert.is(afterSwap.filter((c) => c.startsWith('[')).length, 2, 'agentEnd collapses both');
|
|
600
|
+
});
|
|
601
|
+
|
|
539
602
|
batch.run();
|
|
@@ -87,6 +87,10 @@ export interface CondenseContext {
|
|
|
87
87
|
* contain. Batching holds the collapse set still between boundaries so the prefix is
|
|
88
88
|
* append-only in between, trading up to `batchCalls` calls' worth of extra context for
|
|
89
89
|
* one cache break per batch instead of one per call.
|
|
90
|
+
*
|
|
91
|
+
* `agentEnd` and `phaseEnd` are exempt and still collapse as they fire: they mark a
|
|
92
|
+
* discontinuity that invalidates the cached prefix anyway, so deferring them would hold
|
|
93
|
+
* stale payloads longer for nothing.
|
|
90
94
|
*/
|
|
91
95
|
batchCalls?: number;
|
|
92
96
|
}
|
|
@@ -155,6 +159,24 @@ function estimateTokensSaved(origLen: number, stubLen: number): number {
|
|
|
155
159
|
return Math.max(0, Math.ceil((origLen - stubLen) / APPROX_CHARS_PER_TOKEN));
|
|
156
160
|
}
|
|
157
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Whether a trigger marks a genuine discontinuity rather than firing continuously, and so is
|
|
164
|
+
* exempt from batching.
|
|
165
|
+
*
|
|
166
|
+
* `agentEnd` advances only when the active agent's name changes (or a sub-agent completes /
|
|
167
|
+
* the agent is released), which swaps the system prompt and tool definitions — those render
|
|
168
|
+
* before the messages, so the cached prefix is invalidated at that point regardless. `phaseEnd`
|
|
169
|
+
* is the app declaring the same kind of boundary explicitly, and falls back to `agentEnd`.
|
|
170
|
+
* Deferring either to the next batch would buy no cache and only hold stale payloads longer.
|
|
171
|
+
*
|
|
172
|
+
* The rest fire continuously (`superseded`, `age`) or at a point where the history is otherwise
|
|
173
|
+
* append-only (`turnEnd` — a new turn appends a user message, leaving the previous entry live),
|
|
174
|
+
* so batching them is exactly where the cache is won.
|
|
175
|
+
*/
|
|
176
|
+
function marksDiscontinuity(kind: CondenseTrigger['kind']): boolean {
|
|
177
|
+
return kind === 'agentEnd' || kind === 'phaseEnd';
|
|
178
|
+
}
|
|
179
|
+
|
|
158
180
|
/**
|
|
159
181
|
* Collapse stale tool payloads (declared via `condenseWhen`) out of the
|
|
160
182
|
* model-bound history. Pure over the `history` array — it emits new message
|
|
@@ -266,9 +288,10 @@ export function applyCondensation(
|
|
|
266
288
|
toolCallId: string,
|
|
267
289
|
entry: RegisteredCondensePolicy,
|
|
268
290
|
): CondenseTrigger | undefined =>
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
291
|
+
triggersOf(entry).find(
|
|
292
|
+
(t) =>
|
|
293
|
+
(marksDiscontinuity(t.kind) || withinBatch(entry)) && triggerFires(toolCallId, entry, t),
|
|
294
|
+
);
|
|
272
295
|
|
|
273
296
|
return history.map((msg) => {
|
|
274
297
|
// Tool-call ARGS live on the assistant message.
|