@vincemakes/kiso-core 0.15.2 → 0.15.3
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/package.json +2 -2
- package/dist/governance/delivery.d.ts +0 -46
- package/dist/governance/delivery.js +0 -58
- package/dist/kernel/ledger.d.ts +0 -45
- package/dist/kernel/ledger.js +0 -90
- package/dist/kernel/summarize.d.ts +0 -58
- package/dist/kernel/summarize.js +0 -106
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-core",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.3",
|
|
4
4
|
"description": "kiso (foundation) core \u2014 protocol, event log, loop, hooks, modes, permissions, compaction, delivery truth. The 2,000-line kernel at the bottom of the kiso framework.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"openai"
|
|
34
34
|
],
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@vincemakes/kiso-evals": "0.15.
|
|
36
|
+
"@vincemakes/kiso-evals": "0.15.3",
|
|
37
37
|
"@types/node": "^26.1.2",
|
|
38
38
|
"typescript": "^5.7.2",
|
|
39
39
|
"vitest": "^3.0.0"
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Delivery truth — "done" means the ledger says so, not the model (uooki
|
|
3
|
-
* done_guard, 30 incidents distilled).
|
|
4
|
-
*
|
|
5
|
-
* The kernel's terminal is honest but shallow: `completed` means "the loop
|
|
6
|
-
* ended on its own terms". Whether the turn DELIVERED what was asked is a
|
|
7
|
-
* harness-side verdict over the trajectory — this module computes it from
|
|
8
|
-
* the same events the loop yielded, so the verdict is replayable and the
|
|
9
|
-
* model's narration never participates in its own grading.
|
|
10
|
-
*
|
|
11
|
-
* Producers are named by the CALLER, in `DeliveryConfig.producers` — the
|
|
12
|
-
* hand-maintained set is the ONLY source of delivery truth. SC-1b removed
|
|
13
|
-
* `Tool.delivers`, the flag this note used to describe as the eventual
|
|
14
|
-
* replacement: it was declared on the contract and read by nothing, here
|
|
15
|
-
* or anywhere. Should a tool's own declaration ever supersede the caller's
|
|
16
|
-
* set, it enters as a new field with a wiring and a gate, never as a
|
|
17
|
-
* standing promise the code has not kept.
|
|
18
|
-
*
|
|
19
|
-
* The verdict counts producer calls that COMPLETED (non-error results).
|
|
20
|
-
* `claimedInText` is computed and REPORTED but does not enter `passed` —
|
|
21
|
-
* a caller that wants "claimed but not delivered" to fail combines the two
|
|
22
|
-
* itself (the evals fixture does). With `required: false`, text claiming
|
|
23
|
-
* delivery over zero producer calls passes here. The canonical lie —
|
|
24
|
-
* "generated-document" with zero producer calls and a clean completed
|
|
25
|
-
* terminal — is caught by that combination.
|
|
26
|
-
*
|
|
27
|
-
* In M3.5 the emission side (artifact URLs extracted from results) joins;
|
|
28
|
-
* today a completed producer IS the emission.
|
|
29
|
-
*/
|
|
30
|
-
import type { Event } from "../protocol/events.js";
|
|
31
|
-
export interface DeliveryConfig {
|
|
32
|
-
/** Whether this turn was required to deliver at all. */
|
|
33
|
-
readonly required: boolean;
|
|
34
|
-
/** Tool names that produce a deliverable. */
|
|
35
|
-
readonly producers: ReadonlySet<string>;
|
|
36
|
-
}
|
|
37
|
-
export interface DeliveryVerdict {
|
|
38
|
-
readonly passed: boolean;
|
|
39
|
-
/** Producer calls the model actually made. */
|
|
40
|
-
readonly producerCalls: readonly string[];
|
|
41
|
-
/** Producer calls that completed (non-error result). */
|
|
42
|
-
readonly completedProducers: readonly string[];
|
|
43
|
-
/** The text claimed delivery (a claim is a lie only when unbacked). */
|
|
44
|
-
readonly claimedInText: boolean;
|
|
45
|
-
}
|
|
46
|
-
export declare function analyzeDelivery(events: readonly Event[], config: DeliveryConfig): DeliveryVerdict;
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Delivery truth — "done" means the ledger says so, not the model (uooki
|
|
3
|
-
* done_guard, 30 incidents distilled).
|
|
4
|
-
*
|
|
5
|
-
* The kernel's terminal is honest but shallow: `completed` means "the loop
|
|
6
|
-
* ended on its own terms". Whether the turn DELIVERED what was asked is a
|
|
7
|
-
* harness-side verdict over the trajectory — this module computes it from
|
|
8
|
-
* the same events the loop yielded, so the verdict is replayable and the
|
|
9
|
-
* model's narration never participates in its own grading.
|
|
10
|
-
*
|
|
11
|
-
* Producers are named by the CALLER, in `DeliveryConfig.producers` — the
|
|
12
|
-
* hand-maintained set is the ONLY source of delivery truth. SC-1b removed
|
|
13
|
-
* `Tool.delivers`, the flag this note used to describe as the eventual
|
|
14
|
-
* replacement: it was declared on the contract and read by nothing, here
|
|
15
|
-
* or anywhere. Should a tool's own declaration ever supersede the caller's
|
|
16
|
-
* set, it enters as a new field with a wiring and a gate, never as a
|
|
17
|
-
* standing promise the code has not kept.
|
|
18
|
-
*
|
|
19
|
-
* The verdict counts producer calls that COMPLETED (non-error results).
|
|
20
|
-
* `claimedInText` is computed and REPORTED but does not enter `passed` —
|
|
21
|
-
* a caller that wants "claimed but not delivered" to fail combines the two
|
|
22
|
-
* itself (the evals fixture does). With `required: false`, text claiming
|
|
23
|
-
* delivery over zero producer calls passes here. The canonical lie —
|
|
24
|
-
* "generated-document" with zero producer calls and a clean completed
|
|
25
|
-
* terminal — is caught by that combination.
|
|
26
|
-
*
|
|
27
|
-
* In M3.5 the emission side (artifact URLs extracted from results) joins;
|
|
28
|
-
* today a completed producer IS the emission.
|
|
29
|
-
*/
|
|
30
|
-
const CLAIM_PATTERN = /created|completed|delivered|done/i;
|
|
31
|
-
export function analyzeDelivery(events, config) {
|
|
32
|
-
const producerCalls = [];
|
|
33
|
-
const completedProducers = [];
|
|
34
|
-
let claimedInText = false;
|
|
35
|
-
for (const ev of events) {
|
|
36
|
-
switch (ev.type) {
|
|
37
|
-
case "text_delta":
|
|
38
|
-
if (CLAIM_PATTERN.test(ev.text))
|
|
39
|
-
claimedInText = true;
|
|
40
|
-
break;
|
|
41
|
-
case "tool_call_end":
|
|
42
|
-
if (config.producers.has(ev.name))
|
|
43
|
-
producerCalls.push(ev.callId);
|
|
44
|
-
break;
|
|
45
|
-
case "tool_result":
|
|
46
|
-
if (producerCalls.includes(ev.callId) && !ev.isError) {
|
|
47
|
-
completedProducers.push(ev.callId);
|
|
48
|
-
}
|
|
49
|
-
break;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return {
|
|
53
|
-
passed: !config.required || completedProducers.length > 0,
|
|
54
|
-
producerCalls,
|
|
55
|
-
completedProducers,
|
|
56
|
-
claimedInText,
|
|
57
|
-
};
|
|
58
|
-
}
|
package/dist/kernel/ledger.d.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* L2 — the execution ledger: exactly-once side effects from the event log.
|
|
3
|
-
*
|
|
4
|
-
* Every tool execution writes `tool_execution_started` before the handler
|
|
5
|
-
* and `tool_execution_succeeded` / `tool_execution_failed` after
|
|
6
|
-
* (kernel/loop.ts). From those events alone — no second store — this module
|
|
7
|
-
* answers the recovery questions:
|
|
8
|
-
*
|
|
9
|
-
* 1. What is the durable status of execution X? (`executionLedger`)
|
|
10
|
-
* 2. What is the latest execution of call Y? (`executionForCallId`)
|
|
11
|
-
*
|
|
12
|
-
* IDENTITY (Area 3): the ledger is keyed by `executionId` — a persistent,
|
|
13
|
-
* framework-generated id unique per log (one per started event). The
|
|
14
|
-
* provider's `callId` is correlation only and may repeat; two logical calls
|
|
15
|
-
* with identical (name, input) are two executions.
|
|
16
|
-
*
|
|
17
|
-
* Status derivation:
|
|
18
|
-
* started, no terminal event yet → "uncertain" (interrupted: human)
|
|
19
|
-
* succeeded → "succeeded" (confirmed, never re-run)
|
|
20
|
-
* failed (any) → "failed" (a complete receipt IS
|
|
21
|
-
* the outcome — ruling #12 / ADR-0038;
|
|
22
|
-
* safeToRetry stays on the event for
|
|
23
|
-
* history, it no longer feeds status)
|
|
24
|
-
* resolved "rerun" → "rerun" (human cleared it)
|
|
25
|
-
* resolved "abandoned" → "abandoned" (human killed it)
|
|
26
|
-
*/
|
|
27
|
-
import type { Event } from "../protocol/events.js";
|
|
28
|
-
export type ExecutionStatus = "uncertain" | "succeeded" | "failed" | "rerun" | "abandoned";
|
|
29
|
-
export interface ExecutionRecord {
|
|
30
|
-
readonly executionId: string;
|
|
31
|
-
readonly callId: string;
|
|
32
|
-
readonly name: string;
|
|
33
|
-
readonly input: Readonly<Record<string, unknown>>;
|
|
34
|
-
readonly status: ExecutionStatus;
|
|
35
|
-
/** Present when `status` is "succeeded" — the durable result to replay. */
|
|
36
|
-
readonly result?: {
|
|
37
|
-
readonly content: string;
|
|
38
|
-
readonly isError: false;
|
|
39
|
-
};
|
|
40
|
-
readonly error?: string;
|
|
41
|
-
}
|
|
42
|
-
/** executionId → durable status, rebuilt purely from events (ADR-0002). */
|
|
43
|
-
export declare function executionLedger(events: readonly Event[]): Map<string, ExecutionRecord>;
|
|
44
|
-
/** The LATEST execution record for a provider call id (correlation only). */
|
|
45
|
-
export declare function executionForCallId(events: readonly Event[], callId: string): ExecutionRecord | undefined;
|
package/dist/kernel/ledger.js
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* L2 — the execution ledger: exactly-once side effects from the event log.
|
|
3
|
-
*
|
|
4
|
-
* Every tool execution writes `tool_execution_started` before the handler
|
|
5
|
-
* and `tool_execution_succeeded` / `tool_execution_failed` after
|
|
6
|
-
* (kernel/loop.ts). From those events alone — no second store — this module
|
|
7
|
-
* answers the recovery questions:
|
|
8
|
-
*
|
|
9
|
-
* 1. What is the durable status of execution X? (`executionLedger`)
|
|
10
|
-
* 2. What is the latest execution of call Y? (`executionForCallId`)
|
|
11
|
-
*
|
|
12
|
-
* IDENTITY (Area 3): the ledger is keyed by `executionId` — a persistent,
|
|
13
|
-
* framework-generated id unique per log (one per started event). The
|
|
14
|
-
* provider's `callId` is correlation only and may repeat; two logical calls
|
|
15
|
-
* with identical (name, input) are two executions.
|
|
16
|
-
*
|
|
17
|
-
* Status derivation:
|
|
18
|
-
* started, no terminal event yet → "uncertain" (interrupted: human)
|
|
19
|
-
* succeeded → "succeeded" (confirmed, never re-run)
|
|
20
|
-
* failed (any) → "failed" (a complete receipt IS
|
|
21
|
-
* the outcome — ruling #12 / ADR-0038;
|
|
22
|
-
* safeToRetry stays on the event for
|
|
23
|
-
* history, it no longer feeds status)
|
|
24
|
-
* resolved "rerun" → "rerun" (human cleared it)
|
|
25
|
-
* resolved "abandoned" → "abandoned" (human killed it)
|
|
26
|
-
*/
|
|
27
|
-
/** executionId → durable status, rebuilt purely from events (ADR-0002). */
|
|
28
|
-
export function executionLedger(events) {
|
|
29
|
-
const ledger = new Map();
|
|
30
|
-
for (const ev of events) {
|
|
31
|
-
switch (ev.type) {
|
|
32
|
-
case "tool_execution_started":
|
|
33
|
-
ledger.set(ev.executionId, {
|
|
34
|
-
executionId: ev.executionId,
|
|
35
|
-
callId: ev.callId,
|
|
36
|
-
name: ev.name,
|
|
37
|
-
input: ev.input,
|
|
38
|
-
status: "uncertain",
|
|
39
|
-
});
|
|
40
|
-
break;
|
|
41
|
-
case "tool_execution_succeeded": {
|
|
42
|
-
const prior = ledger.get(ev.executionId);
|
|
43
|
-
if (prior) {
|
|
44
|
-
ledger.set(ev.executionId, { ...prior, status: "succeeded", result: ev.result });
|
|
45
|
-
}
|
|
46
|
-
break;
|
|
47
|
-
}
|
|
48
|
-
case "tool_execution_failed": {
|
|
49
|
-
const prior = ledger.get(ev.executionId);
|
|
50
|
-
if (prior) {
|
|
51
|
-
ledger.set(ev.executionId, {
|
|
52
|
-
...prior,
|
|
53
|
-
// ruling #12 (ADR-0038): a complete receipt IS the outcome —
|
|
54
|
-
// failed is "failed", never "uncertain"; uncertainty
|
|
55
|
-
// belongs to the crash window alone (started, no receipt).
|
|
56
|
-
status: "failed",
|
|
57
|
-
...(ev.error !== undefined ? { error: ev.error } : {}),
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
case "tool_execution_resolved": {
|
|
63
|
-
const prior = ledger.get(ev.executionId);
|
|
64
|
-
if (prior) {
|
|
65
|
-
ledger.set(ev.executionId, {
|
|
66
|
-
...prior,
|
|
67
|
-
status: ev.resolution === "rerun" ? "rerun" : "abandoned",
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
break;
|
|
71
|
-
}
|
|
72
|
-
default:
|
|
73
|
-
break;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return ledger;
|
|
77
|
-
}
|
|
78
|
-
/** The LATEST execution record for a provider call id (correlation only). */
|
|
79
|
-
export function executionForCallId(events, callId) {
|
|
80
|
-
const ledger = executionLedger(events);
|
|
81
|
-
let found;
|
|
82
|
-
for (const ev of events) {
|
|
83
|
-
if (ev.type !== "tool_execution_started")
|
|
84
|
-
continue;
|
|
85
|
-
if (ev.callId !== callId)
|
|
86
|
-
continue;
|
|
87
|
-
found = ledger.get(ev.executionId);
|
|
88
|
-
}
|
|
89
|
-
return found;
|
|
90
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* L2 — the /compact summary layer (ADR-0044): the MODEL-GENERATED half of
|
|
3
|
-
* context economy. The mechanical half (microcompact, compaction.ts)
|
|
4
|
-
* clears TOOL RESULTS only; this layer compresses the CONVERSATION itself
|
|
5
|
-
* into one durable `summarized` event per call, replacing the covered
|
|
6
|
-
* range with a single assistant summary message in the projection.
|
|
7
|
-
*
|
|
8
|
-
* The summary call is OFF-LOOP: it goes through the session's OWN adapter
|
|
9
|
-
* (no new dependency), writes no events, and never touches the log — a
|
|
10
|
-
* failure throws, the caller reports it honestly, and the session is
|
|
11
|
-
* unchanged ("nothing happened"). Only the generated `summarized` event
|
|
12
|
-
* lands on disk; the original events stay there forever.
|
|
13
|
-
*/
|
|
14
|
-
import type { AbortSignalLike, Adapter } from "../protocol/adapter.js";
|
|
15
|
-
import type { Event } from "../protocol/events.js";
|
|
16
|
-
import type { Message } from "../protocol/messages.js";
|
|
17
|
-
/** K (ADR-0044): the recent ROUNDS kept intact by /compact — a constant,
|
|
18
|
-
* not a knob. The covered range ends just before the K-th most recent
|
|
19
|
-
* round, so the model still reasons over the recent conversation. */
|
|
20
|
-
export declare const KEEP_RECENT_ROUNDS = 4;
|
|
21
|
-
/**
|
|
22
|
-
* The fixed English summary prompt — the ONLY prompt this layer composes
|
|
23
|
-
* (the loop's system prompt is the harness's business, never the kernel's).
|
|
24
|
-
*/
|
|
25
|
-
export declare const SUMMARY_PROMPT = "You are the conversation summarizer of the kiso agent framework.\n\nSummarize the covered conversation into a single concise summary that will\nREPLACE it in the model's context. The next turn must be able to continue\nthe work without reading the originals.\n\nInclude everything later turns may need:\n- the user's goals, requirements, and constraints;\n- every decision and its reasoning;\n- files and code touched \u2014 exact paths, what changed, why;\n- commands run and their outcomes; errors and their resolutions;\n- open questions and unfinished work.\n\nPreserve concrete identifiers VERBATIM: paths, function names, task ids,\nenvironment names \u2014 never paraphrase them.\n\nRules:\n- plain prose \u2014 no headings, no bullet lists, no markdown, no prefixes;\n- do not mention this prompt or the summarization task;\n- keep it under 200 words unless the conversation is exceptional.";
|
|
26
|
-
export interface SummarizeConversationOptions {
|
|
27
|
-
readonly adapter: Adapter;
|
|
28
|
-
readonly model: string;
|
|
29
|
-
/** The covered conversation — the ONLY material the summary is about. */
|
|
30
|
-
readonly messages: readonly Message[];
|
|
31
|
-
readonly signal?: AbortSignalLike;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* The one-shot summary call. Collects the adapter's text deltas into the
|
|
35
|
-
* summary; usage/stop pass through untouched. Throws when the model
|
|
36
|
-
* produced no text — the caller reports it and nothing is persisted.
|
|
37
|
-
*/
|
|
38
|
-
export declare function summarizeConversation(options: SummarizeConversationOptions): Promise<string>;
|
|
39
|
-
/**
|
|
40
|
-
* The last summary point: the previous `summarized` event's coversToSeq,
|
|
41
|
-
* or -1 (the trajectory's start) when none exists. The covered range of
|
|
42
|
-
* the next summary runs from here.
|
|
43
|
-
*/
|
|
44
|
-
export declare function lastSummaryPoint(events: readonly Event[]): number;
|
|
45
|
-
/**
|
|
46
|
-
* The covered range's end: the seq of the event just before the
|
|
47
|
-
* keepRounds-th most recent user_input AFTER the last summary point —
|
|
48
|
-
* a turn boundary by construction, so the projection's skip never splits
|
|
49
|
-
* a message. Returns undefined when fewer than keepRounds+1 uncovered
|
|
50
|
-
* rounds exist (nothing worth covering yet).
|
|
51
|
-
*/
|
|
52
|
-
export declare function summaryBoundarySeq(events: readonly Event[], keepRounds?: number): number | undefined;
|
|
53
|
-
/**
|
|
54
|
-
* The NoticeCell's number: estimated tokens of the covered content minus
|
|
55
|
-
* the summary's own — the same chars/4 proxy as estimateTokens (a stable
|
|
56
|
-
* MONOTONE savings figure, not a bill).
|
|
57
|
-
*/
|
|
58
|
-
export declare function estimateSummarySavings(covered: readonly Message[], summary: string): number;
|
package/dist/kernel/summarize.js
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* L2 — the /compact summary layer (ADR-0044): the MODEL-GENERATED half of
|
|
3
|
-
* context economy. The mechanical half (microcompact, compaction.ts)
|
|
4
|
-
* clears TOOL RESULTS only; this layer compresses the CONVERSATION itself
|
|
5
|
-
* into one durable `summarized` event per call, replacing the covered
|
|
6
|
-
* range with a single assistant summary message in the projection.
|
|
7
|
-
*
|
|
8
|
-
* The summary call is OFF-LOOP: it goes through the session's OWN adapter
|
|
9
|
-
* (no new dependency), writes no events, and never touches the log — a
|
|
10
|
-
* failure throws, the caller reports it honestly, and the session is
|
|
11
|
-
* unchanged ("nothing happened"). Only the generated `summarized` event
|
|
12
|
-
* lands on disk; the original events stay there forever.
|
|
13
|
-
*/
|
|
14
|
-
import { estimateTokens } from "./compaction.js";
|
|
15
|
-
/** K (ADR-0044): the recent ROUNDS kept intact by /compact — a constant,
|
|
16
|
-
* not a knob. The covered range ends just before the K-th most recent
|
|
17
|
-
* round, so the model still reasons over the recent conversation. */
|
|
18
|
-
export const KEEP_RECENT_ROUNDS = 4;
|
|
19
|
-
/**
|
|
20
|
-
* The fixed English summary prompt — the ONLY prompt this layer composes
|
|
21
|
-
* (the loop's system prompt is the harness's business, never the kernel's).
|
|
22
|
-
*/
|
|
23
|
-
export const SUMMARY_PROMPT = `You are the conversation summarizer of the kiso agent framework.
|
|
24
|
-
|
|
25
|
-
Summarize the covered conversation into a single concise summary that will
|
|
26
|
-
REPLACE it in the model's context. The next turn must be able to continue
|
|
27
|
-
the work without reading the originals.
|
|
28
|
-
|
|
29
|
-
Include everything later turns may need:
|
|
30
|
-
- the user's goals, requirements, and constraints;
|
|
31
|
-
- every decision and its reasoning;
|
|
32
|
-
- files and code touched — exact paths, what changed, why;
|
|
33
|
-
- commands run and their outcomes; errors and their resolutions;
|
|
34
|
-
- open questions and unfinished work.
|
|
35
|
-
|
|
36
|
-
Preserve concrete identifiers VERBATIM: paths, function names, task ids,
|
|
37
|
-
environment names — never paraphrase them.
|
|
38
|
-
|
|
39
|
-
Rules:
|
|
40
|
-
- plain prose — no headings, no bullet lists, no markdown, no prefixes;
|
|
41
|
-
- do not mention this prompt or the summarization task;
|
|
42
|
-
- keep it under 200 words unless the conversation is exceptional.`;
|
|
43
|
-
/**
|
|
44
|
-
* The one-shot summary call. Collects the adapter's text deltas into the
|
|
45
|
-
* summary; usage/stop pass through untouched. Throws when the model
|
|
46
|
-
* produced no text — the caller reports it and nothing is persisted.
|
|
47
|
-
*/
|
|
48
|
-
export async function summarizeConversation(options) {
|
|
49
|
-
const { adapter, model, messages } = options;
|
|
50
|
-
let text = "";
|
|
51
|
-
for await (const ev of adapter.stream({
|
|
52
|
-
model,
|
|
53
|
-
messages,
|
|
54
|
-
systemPrompt: SUMMARY_PROMPT,
|
|
55
|
-
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
56
|
-
})) {
|
|
57
|
-
if (ev.type === "text_delta")
|
|
58
|
-
text += ev.text;
|
|
59
|
-
}
|
|
60
|
-
const trimmed = text.trim();
|
|
61
|
-
if (trimmed === "") {
|
|
62
|
-
throw new Error("the summary call produced no text");
|
|
63
|
-
}
|
|
64
|
-
return trimmed;
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* The last summary point: the previous `summarized` event's coversToSeq,
|
|
68
|
-
* or -1 (the trajectory's start) when none exists. The covered range of
|
|
69
|
-
* the next summary runs from here.
|
|
70
|
-
*/
|
|
71
|
-
export function lastSummaryPoint(events) {
|
|
72
|
-
let prev = -1;
|
|
73
|
-
for (const ev of events) {
|
|
74
|
-
if (ev.type === "summarized" && ev.coversToSeq > prev)
|
|
75
|
-
prev = ev.coversToSeq;
|
|
76
|
-
}
|
|
77
|
-
return prev;
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* The covered range's end: the seq of the event just before the
|
|
81
|
-
* keepRounds-th most recent user_input AFTER the last summary point —
|
|
82
|
-
* a turn boundary by construction, so the projection's skip never splits
|
|
83
|
-
* a message. Returns undefined when fewer than keepRounds+1 uncovered
|
|
84
|
-
* rounds exist (nothing worth covering yet).
|
|
85
|
-
*/
|
|
86
|
-
export function summaryBoundarySeq(events, keepRounds = KEEP_RECENT_ROUNDS) {
|
|
87
|
-
const prevPoint = lastSummaryPoint(events);
|
|
88
|
-
const uncoveredInputs = [];
|
|
89
|
-
for (const ev of events) {
|
|
90
|
-
if (ev.type === "user_input" && ev.seq > prevPoint)
|
|
91
|
-
uncoveredInputs.push(ev.seq);
|
|
92
|
-
}
|
|
93
|
-
if (uncoveredInputs.length <= keepRounds)
|
|
94
|
-
return undefined;
|
|
95
|
-
// The input at m - keepRounds opens the FIRST KEPT round; everything
|
|
96
|
-
// before it (m - keepRounds ≥ 1 covered rounds) is summarizable.
|
|
97
|
-
return uncoveredInputs[uncoveredInputs.length - keepRounds] - 1;
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* The NoticeCell's number: estimated tokens of the covered content minus
|
|
101
|
-
* the summary's own — the same chars/4 proxy as estimateTokens (a stable
|
|
102
|
-
* MONOTONE savings figure, not a bill).
|
|
103
|
-
*/
|
|
104
|
-
export function estimateSummarySavings(covered, summary) {
|
|
105
|
-
return Math.max(0, estimateTokens(covered) - Math.ceil(summary.length / 4));
|
|
106
|
-
}
|