@genesislcap/ai-assistant 15.5.0 → 15.6.1
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 +15 -4
- package/dist/chat-driver.cjs.map +2 -2
- package/dist/chat-driver.mjs +15 -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 +18 -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 +33 -5
- package/dist/esm/utils/condense-history.test.js +97 -0
- package/package.json +17 -17
- package/src/components/chat-driver/chat-driver.ts +15 -0
- package/src/utils/condense-history.test.ts +166 -0
- package/src/utils/condense-history.ts +56 -3
|
@@ -75,6 +75,24 @@ 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
|
+
* `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.
|
|
94
|
+
*/
|
|
95
|
+
batchCalls?: number;
|
|
78
96
|
}
|
|
79
97
|
|
|
80
98
|
/**
|
|
@@ -141,6 +159,24 @@ function estimateTokensSaved(origLen: number, stubLen: number): number {
|
|
|
141
159
|
return Math.max(0, Math.ceil((origLen - stubLen) / APPROX_CHARS_PER_TOKEN));
|
|
142
160
|
}
|
|
143
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
|
+
|
|
144
180
|
/**
|
|
145
181
|
* Collapse stale tool payloads (declared via `condenseWhen`) out of the
|
|
146
182
|
* model-bound history. Pure over the `history` array — it emits new message
|
|
@@ -193,6 +229,20 @@ export function applyCondensation(
|
|
|
193
229
|
// lookup. For each `superseded` key the LAST call in history order survives.
|
|
194
230
|
// The name lookup lets a tool message (which carries only an id) name its tool
|
|
195
231
|
// in stubs and events.
|
|
232
|
+
// Batch boundary: the collapse set is resolved as of this model-call and stays put
|
|
233
|
+
// until the next boundary, so the prefix is append-only in between. `batchCalls` of 1
|
|
234
|
+
// (the default) makes this `ctx.modelCall` — i.e. no batching.
|
|
235
|
+
const batchCalls = Math.max(1, Math.floor(ctx.batchCalls ?? 1));
|
|
236
|
+
const asOf =
|
|
237
|
+
batchCalls === 1 ? ctx.modelCall : Math.floor(ctx.modelCall / batchCalls) * batchCalls;
|
|
238
|
+
// A call made after the boundary is invisible to this pass in BOTH directions: it
|
|
239
|
+
// cannot supersede an older call (which would collapse the older payload early), and
|
|
240
|
+
// it cannot itself collapse (which would elide the very result just fetched). Unbatched,
|
|
241
|
+
// the boundary is the live clock and this gate is off entirely — a call registered on
|
|
242
|
+
// the current model-call is still eligible, as it always was.
|
|
243
|
+
const withinBatch = (entry: RegisteredCondensePolicy): boolean =>
|
|
244
|
+
batchCalls === 1 || entry.iteration <= asOf;
|
|
245
|
+
|
|
196
246
|
const latestByKey = new Map<string, string>();
|
|
197
247
|
const nameById = new Map<string, string>();
|
|
198
248
|
for (const msg of history) {
|
|
@@ -200,7 +250,7 @@ export function applyCondensation(
|
|
|
200
250
|
for (const tc of msg.toolCalls) {
|
|
201
251
|
nameById.set(tc.id, tc.name);
|
|
202
252
|
const entry = policies.get(tc.id);
|
|
203
|
-
if (!entry) continue;
|
|
253
|
+
if (!entry || !withinBatch(entry)) continue;
|
|
204
254
|
for (const t of triggersOf(entry)) {
|
|
205
255
|
if (t.kind === 'superseded') latestByKey.set(t.by, tc.id);
|
|
206
256
|
}
|
|
@@ -217,7 +267,7 @@ export function applyCondensation(
|
|
|
217
267
|
// past the last allowed view: turns:1 → seen once, then gone. Monotonic
|
|
218
268
|
// clock, so this holds across turn boundaries too.
|
|
219
269
|
case 'age':
|
|
220
|
-
return
|
|
270
|
+
return asOf - entry.iteration > trig.turns;
|
|
221
271
|
case 'turnEnd':
|
|
222
272
|
return ctx.turn > entry.turn;
|
|
223
273
|
case 'agentEnd':
|
|
@@ -238,7 +288,10 @@ export function applyCondensation(
|
|
|
238
288
|
toolCallId: string,
|
|
239
289
|
entry: RegisteredCondensePolicy,
|
|
240
290
|
): CondenseTrigger | undefined =>
|
|
241
|
-
triggersOf(entry).find(
|
|
291
|
+
triggersOf(entry).find(
|
|
292
|
+
(t) =>
|
|
293
|
+
(marksDiscontinuity(t.kind) || withinBatch(entry)) && triggerFires(toolCallId, entry, t),
|
|
294
|
+
);
|
|
242
295
|
|
|
243
296
|
return history.map((msg) => {
|
|
244
297
|
// Tool-call ARGS live on the assistant message.
|