@vincemakes/kiso-runtime 0.39.2 → 0.40.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/agent.d.ts +20 -1
- package/dist/agent.js +44 -8
- package/dist/compaction-policy.d.ts +82 -0
- package/dist/compaction-policy.js +197 -0
- package/dist/compose.d.ts +5 -0
- package/dist/compose.js +12 -1
- package/dist/context-anchor.d.ts +25 -0
- package/dist/context-anchor.js +46 -0
- package/dist/internal.d.ts +3 -0
- package/dist/internal.js +4 -0
- package/dist/profile.d.ts +25 -6
- package/dist/profile.js +68 -7
- package/dist/run.d.ts +1 -1
- package/dist/run.js +35 -9
- package/dist/session-summary.d.ts +87 -0
- package/dist/session-summary.js +166 -0
- package/dist/session.d.ts +77 -2
- package/dist/session.js +258 -22
- package/dist/store.d.ts +8 -0
- package/dist/store.js +27 -2
- package/dist/summarize.d.ts +65 -7
- package/dist/summarize.js +98 -31
- package/package.json +6 -6
package/dist/summarize.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* unchanged ("nothing happened"). Only the generated `summarized` event
|
|
14
14
|
* lands on disk; the original events stay there forever.
|
|
15
15
|
*/
|
|
16
|
-
import { estimateTokens, DO_NOT_COMPACT } from "@vincemakes/kiso-core";
|
|
16
|
+
import { estimateTokens, DO_NOT_COMPACT, DEFAULT_MAX_RETRIES, RETRY_AFTER_MAX_MS, retryDelayMs } from "@vincemakes/kiso-core";
|
|
17
17
|
/** K (ADR-0044): the recent ROUNDS kept intact by /compact — a constant,
|
|
18
18
|
* not a knob. The covered range ends just before the K-th most recent
|
|
19
19
|
* round, so the model still reasons over the recent conversation. */
|
|
@@ -87,7 +87,7 @@ export const MANUAL_SUMMARY_BUDGET = 32_000;
|
|
|
87
87
|
* budget: "not a complete turn" alone told the person nothing about what
|
|
88
88
|
* ran out. Still an `Error`, and still carries the `max_tokens … not a
|
|
89
89
|
* complete turn` wording the CX-1 F3 gate matches. */
|
|
90
|
-
class SummaryBudgetExhausted extends Error {
|
|
90
|
+
export class SummaryBudgetExhausted extends Error {
|
|
91
91
|
budget;
|
|
92
92
|
constructor(budget) {
|
|
93
93
|
super(budget === undefined
|
|
@@ -212,6 +212,14 @@ Rules:
|
|
|
212
212
|
- do not mention this prompt or the summarization task;
|
|
213
213
|
- the summary may be as long as it needs to be within the output budget —
|
|
214
214
|
there is no word cap; completeness wins.`;
|
|
215
|
+
/**
|
|
216
|
+
* ADR-0055 A2 — the IN-BAND form: the run's own request with this appended
|
|
217
|
+
* as the last user message, so everything before it reads at the cache-hit
|
|
218
|
+
* price. The same sections, the same validation; the guard sentence first.
|
|
219
|
+
*/
|
|
220
|
+
export const SUMMARY_IN_BAND = `Stop the task for this one reply and write a checkpoint of the conversation above instead.
|
|
221
|
+
|
|
222
|
+
${SUMMARY_PROMPT}`;
|
|
215
223
|
/**
|
|
216
224
|
* E6 (b) — the output-side validation (the finding E6-F4/F5 follow-up):
|
|
217
225
|
* a summary must be a complete checkpoint or NOTHING. The marker family
|
|
@@ -254,17 +262,26 @@ function retryableStructured(err) {
|
|
|
254
262
|
const e = err;
|
|
255
263
|
return typeof e.code === "string" && typeof e.message === "string" && e.retryable === true;
|
|
256
264
|
}
|
|
257
|
-
/**
|
|
258
|
-
|
|
259
|
-
* token pause — the failure this exists for is a dropped stream, not a
|
|
260
|
-
* rate limit, and an immediate second attempt is the right response to
|
|
261
|
-
* a socket that died. */
|
|
262
|
-
function retryWaitMs(err) {
|
|
265
|
+
/** The provider's own Retry-After, when the adapter normalized one. */
|
|
266
|
+
function askedWaitMs(err) {
|
|
263
267
|
const asked = err.retryAfterMs;
|
|
264
|
-
return typeof asked === "number" && Number.isFinite(asked) && asked >= 0
|
|
268
|
+
return typeof asked === "number" && Number.isFinite(asked) && asked >= 0 ? asked : undefined;
|
|
269
|
+
}
|
|
270
|
+
/** The kernel's abortable wait, restated for the one call that is not the
|
|
271
|
+
* kernel's: a cancel during the backoff wakes it at once. */
|
|
272
|
+
function wait(ms, signal) {
|
|
273
|
+
return new Promise((resolve) => {
|
|
274
|
+
if (signal?.aborted === true)
|
|
275
|
+
return resolve();
|
|
276
|
+
const timer = setTimeout(resolve, ms);
|
|
277
|
+
signal?.addEventListener?.("abort", () => {
|
|
278
|
+
clearTimeout(timer);
|
|
279
|
+
resolve();
|
|
280
|
+
}, { once: true });
|
|
281
|
+
});
|
|
265
282
|
}
|
|
266
283
|
/**
|
|
267
|
-
* The summary call,
|
|
284
|
+
* The summary call, retried under the KERNEL'S policy.
|
|
268
285
|
*
|
|
269
286
|
* 0.39.1: `/compact` had no retry at all. The kernel owns retries
|
|
270
287
|
* (ADR-0005) and this path does not go through the kernel — it calls the
|
|
@@ -274,11 +291,18 @@ function retryWaitMs(err) {
|
|
|
274
291
|
* (the transport-failure-after-headers class); here is the only place
|
|
275
292
|
* that can act on it.
|
|
276
293
|
*
|
|
277
|
-
* ONE retry
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
294
|
+
* 0.39.1 gave it ONE retry after 250 ms. ADR-0005 Amendment 2 gives it the
|
|
295
|
+
* kernel's rule instead, because a dropped summary stream is the same
|
|
296
|
+
* failure as a dropped turn and meets the same gateway: the same curve,
|
|
297
|
+
* the same budget (the same `maxRetries` knob), Retry-After as a floor and
|
|
298
|
+
* a wait beyond RETRY_AFTER_MAX_MS as an explicit stop — never a retry
|
|
299
|
+
* that comes early — and the same announcement before each wait. Each
|
|
300
|
+
* retry re-pays the summary's input, exactly as each turn retry re-pays
|
|
301
|
+
* the turn's; the budget that bounds one bounds the other.
|
|
302
|
+
*
|
|
303
|
+
* A permanent error still throws on the first attempt — the retry is
|
|
304
|
+
* keyed on the classification the adapter already made, never on the
|
|
305
|
+
* fact that something failed.
|
|
282
306
|
*/
|
|
283
307
|
export async function summarizeConversation(options) {
|
|
284
308
|
// Asked as a CALL, twice, because `aborted` is a readonly property and
|
|
@@ -290,18 +314,32 @@ export async function summarizeConversation(options) {
|
|
|
290
314
|
// having if the first budget was chosen small — which is why the manual
|
|
291
315
|
// gesture now asks for the whole measured budget up front instead
|
|
292
316
|
// (see `MANUAL_SUMMARY_BUDGET`).
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
317
|
+
const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
318
|
+
for (let attempts = 0;; attempts += 1) {
|
|
319
|
+
try {
|
|
320
|
+
return await summaryAttempt(options);
|
|
321
|
+
}
|
|
322
|
+
catch (err) {
|
|
323
|
+
// The caller's cancel is the caller's: an aborted `/compact` must
|
|
324
|
+
// not spend another call on its way out.
|
|
325
|
+
if (!retryableStructured(err) || aborted() || attempts >= maxRetries)
|
|
326
|
+
throw err;
|
|
327
|
+
const asked = askedWaitMs(err);
|
|
328
|
+
const delay = retryDelayMs(attempts + 1, asked);
|
|
329
|
+
const e = err;
|
|
330
|
+
if (delay > RETRY_AFTER_MAX_MS) {
|
|
331
|
+
throw { ...e, retryable: false, message: `${e.message} (the provider asked to wait ${asked}ms — beyond the ${RETRY_AFTER_MAX_MS}ms cap; not retried)` };
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
await options.onRetry?.({ attempt: attempts + 1, maxRetries, code: e.code, delayMs: delay, midStream: false });
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
// observation only
|
|
338
|
+
}
|
|
339
|
+
await wait(delay, options.signal);
|
|
340
|
+
if (aborted())
|
|
341
|
+
throw err;
|
|
342
|
+
}
|
|
305
343
|
}
|
|
306
344
|
}
|
|
307
345
|
/**
|
|
@@ -323,18 +361,44 @@ async function summaryAttempt(options) {
|
|
|
323
361
|
let stopReason;
|
|
324
362
|
let toolCalls = 0;
|
|
325
363
|
let afterStop;
|
|
364
|
+
// 0.40.0: the progress this attempt reports — it starts at zero, so a
|
|
365
|
+
// retry visibly starts the bar again
|
|
366
|
+
let streamedChars = 0;
|
|
367
|
+
let sawReasoning = false;
|
|
368
|
+
const report = (progress) => {
|
|
369
|
+
try {
|
|
370
|
+
options.onProgress?.(progress);
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
// observation only
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
const budget = options.maxOutputTokens ?? null;
|
|
377
|
+
report({ produced: 0, budget, reasoningUnseen: false, reported: false });
|
|
378
|
+
const inBand = options.inBand;
|
|
326
379
|
for await (const ev of adapter.stream({
|
|
327
380
|
model,
|
|
328
|
-
|
|
329
|
-
|
|
381
|
+
...(inBand === undefined
|
|
382
|
+
? { messages, systemPrompt: SUMMARY_PROMPT }
|
|
383
|
+
: {
|
|
384
|
+
messages: [...messages, { role: "user", content: inBand.focus === undefined ? SUMMARY_IN_BAND : `${SUMMARY_IN_BAND}\n\nFocus the summary on: ${inBand.focus}` }],
|
|
385
|
+
...(inBand.systemPrompt !== undefined ? { systemPrompt: inBand.systemPrompt } : {}),
|
|
386
|
+
tools: inBand.tools,
|
|
387
|
+
}),
|
|
330
388
|
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
331
389
|
...(options.maxOutputTokens !== undefined ? { maxTokens: options.maxOutputTokens } : {}),
|
|
332
390
|
...(options.reasoning !== undefined ? { reasoning: options.reasoning } : {}),
|
|
333
391
|
})) {
|
|
334
392
|
if (stops > 0 && ev.type !== "usage" && ev.type !== "stop" && afterStop === undefined)
|
|
335
393
|
afterStop = ev.type;
|
|
336
|
-
if (ev.type === "text_delta")
|
|
337
|
-
|
|
394
|
+
if (ev.type === "text_delta" || ev.type === "thinking") {
|
|
395
|
+
if (ev.type === "text_delta")
|
|
396
|
+
text += ev.text;
|
|
397
|
+
else
|
|
398
|
+
sawReasoning = true;
|
|
399
|
+
streamedChars += ev.text.length;
|
|
400
|
+
report({ produced: Math.ceil(streamedChars / 4), budget, reasoningUnseen: false, reported: false });
|
|
401
|
+
}
|
|
338
402
|
else if (ev.type === "tool_call_start" || ev.type === "tool_call_end" || ev.type === "tool_call_input_delta")
|
|
339
403
|
toolCalls += 1;
|
|
340
404
|
else if (ev.type === "stop") {
|
|
@@ -356,6 +420,9 @@ async function summaryAttempt(options) {
|
|
|
356
420
|
cacheWrite: ev.cacheWrite,
|
|
357
421
|
...(ev.reasoningTokens !== undefined ? { reasoningTokens: ev.reasoningTokens } : {}),
|
|
358
422
|
};
|
|
423
|
+
if (ev.outputTokens !== null) {
|
|
424
|
+
report({ produced: ev.outputTokens, budget, reasoningUnseen: !sawReasoning && (ev.reasoningTokens ?? 0) > 0, reported: true });
|
|
425
|
+
}
|
|
359
426
|
}
|
|
360
427
|
}
|
|
361
428
|
if (stops === 0)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.1",
|
|
4
4
|
"description": "kiso runtime \u2014 durable multi-turn agent sessions: AgentDefinition, AgentRuntime, AgentSession, Run, append-only JSONL store.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
"test": "vitest run"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@vincemakes/kiso-core": "0.
|
|
28
|
+
"@vincemakes/kiso-core": "0.40.1"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@vincemakes/kiso-provider-anthropic": "0.
|
|
32
|
-
"@vincemakes/kiso-provider-openai": "0.
|
|
33
|
-
"@vincemakes/kiso-provider-openai-responses": "0.
|
|
31
|
+
"@vincemakes/kiso-provider-anthropic": "0.40.1",
|
|
32
|
+
"@vincemakes/kiso-provider-openai": "0.40.1",
|
|
33
|
+
"@vincemakes/kiso-provider-openai-responses": "0.40.1"
|
|
34
34
|
},
|
|
35
35
|
"peerDependenciesMeta": {
|
|
36
36
|
"@vincemakes/kiso-provider-anthropic": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
}
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@vincemakes/kiso-evals": "0.
|
|
47
|
+
"@vincemakes/kiso-evals": "0.40.1",
|
|
48
48
|
"@types/node": "^26.1.2",
|
|
49
49
|
"typescript": "^5.7.2",
|
|
50
50
|
"vitest": "^3.0.0"
|