@vincemakes/kiso-core 0.1.0 → 0.1.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/kernel/loop.d.ts +11 -0
- package/dist/kernel/loop.js +27 -1
- package/dist/kernel/project.d.ts +7 -1
- package/dist/kernel/project.js +61 -1
- package/dist/protocol/events.d.ts +15 -1
- package/dist/protocol/events.js +1 -0
- package/package.json +2 -2
package/dist/kernel/loop.d.ts
CHANGED
|
@@ -58,6 +58,17 @@ export interface LoopConfig {
|
|
|
58
58
|
readonly compaction?: {
|
|
59
59
|
readonly thresholdTokens: number;
|
|
60
60
|
};
|
|
61
|
+
/**
|
|
62
|
+
* C 区: MICROCOMPACT — when the projected context exceeds the threshold,
|
|
63
|
+
* append ONE durable `microcompacted` boundary (clearing compactable tool
|
|
64
|
+
* results older than the recent turns). The decision is a persisted fact:
|
|
65
|
+
* the projection derives the same cleared view from the same events,
|
|
66
|
+
* byte for byte, across crash/resume. Never a per-turn progressive
|
|
67
|
+
* clearing.
|
|
68
|
+
*/
|
|
69
|
+
readonly microcompact?: {
|
|
70
|
+
readonly thresholdTokens: number;
|
|
71
|
+
};
|
|
61
72
|
readonly signal?: AbortSignalLike;
|
|
62
73
|
readonly temperature?: number;
|
|
63
74
|
readonly maxTokens?: number;
|
package/dist/kernel/loop.js
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* re-stream that duplicates output or tool calls.
|
|
28
28
|
*/
|
|
29
29
|
import { isAdapterEvent } from "../protocol/adapter.js";
|
|
30
|
-
import { estimateTokens, microcompact } from "./compaction.js";
|
|
30
|
+
import { estimateTokens, KEEP_RECENT_TURNS, microcompact } from "./compaction.js";
|
|
31
31
|
import { EventLog } from "./event-log.js";
|
|
32
32
|
import { ToolRegistry } from "../tools/registry.js";
|
|
33
33
|
import { validateArgs } from "../tools/validate.js";
|
|
@@ -149,6 +149,17 @@ export async function* loop(config) {
|
|
|
149
149
|
await hooks.onPostCompact(messages, {}).catch(() => { });
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
|
+
// ── C 区: one-shot microcompact boundary when over the threshold ──
|
|
153
|
+
if (config.microcompact !== undefined && estimateTokens(messages) > config.microcompact.thresholdTokens) {
|
|
154
|
+
const beforeSeq = microcompactBoundarySeq(log.all);
|
|
155
|
+
if (beforeSeq !== undefined) {
|
|
156
|
+
const full = log.append({ type: "microcompacted", beforeSeq });
|
|
157
|
+
if (hooks.onEvent)
|
|
158
|
+
await hooks.onEvent(full, {}).catch(() => { });
|
|
159
|
+
yield full;
|
|
160
|
+
messages = derive();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
152
163
|
if (hooks.onPreLlm)
|
|
153
164
|
await hooks.onPreLlm({ model: config.model, turns }, {});
|
|
154
165
|
if (aborted()) {
|
|
@@ -785,6 +796,21 @@ function sleep(ms, signal) {
|
|
|
785
796
|
}, { once: true });
|
|
786
797
|
});
|
|
787
798
|
}
|
|
799
|
+
/**
|
|
800
|
+
* C 区: the boundary seq for a microcompact — the seq of the user input
|
|
801
|
+
* KEEP_RECENT_TURNS+1 places from the end (everything BEFORE that user
|
|
802
|
+
* input is old enough to clear; the recent turns stay intact). Undefined
|
|
803
|
+
* when the history is too short to clear anything.
|
|
804
|
+
*/
|
|
805
|
+
function microcompactBoundarySeq(events) {
|
|
806
|
+
const userSeqs = [];
|
|
807
|
+
for (const ev of events) {
|
|
808
|
+
if (ev.type === "user_input")
|
|
809
|
+
userSeqs.push(ev.seq);
|
|
810
|
+
}
|
|
811
|
+
const boundary = userSeqs[userSeqs.length - KEEP_RECENT_TURNS - 1];
|
|
812
|
+
return boundary !== undefined ? boundary - 1 : undefined;
|
|
813
|
+
}
|
|
788
814
|
/** A signal that never aborts — for executions outside any abort scope. */
|
|
789
815
|
const NEVER_ABORT = {
|
|
790
816
|
aborted: false,
|
package/dist/kernel/project.d.ts
CHANGED
|
@@ -22,9 +22,15 @@
|
|
|
22
22
|
import type { Event } from "../protocol/events.js";
|
|
23
23
|
import type { EventInput } from "./event-log.js";
|
|
24
24
|
import type { AssistantBlock, Message, MessageSource } from "../protocol/messages.js";
|
|
25
|
+
/** The tag that makes a tool result un-clearable (C 区). */
|
|
26
|
+
export declare const DO_NOT_COMPACT = "do-not-compact";
|
|
25
27
|
/**
|
|
26
28
|
* Rebuild the message array from events. Deterministic and order-sensitive:
|
|
27
|
-
* replaying the same log always produces the same messages
|
|
29
|
+
* replaying the same log always produces the same messages — BYTE FOR BYTE
|
|
30
|
+
* (D 区): the same event prefix derives the same message prefix; the only
|
|
31
|
+
* events that change already-derived messages are `microcompacted`
|
|
32
|
+
* boundaries, which are themselves persisted facts (their replay derives
|
|
33
|
+
* the same projection every time).
|
|
28
34
|
*
|
|
29
35
|
* Text block boundaries are preserved: `text_end` closes the current text
|
|
30
36
|
* block (an explicit boundary); `text_start` after a block opens a new one.
|
package/dist/kernel/project.js
CHANGED
|
@@ -19,9 +19,41 @@
|
|
|
19
19
|
* microcompact is deterministic and idempotent, so the replay equals the
|
|
20
20
|
* live run. See ADR-0002.
|
|
21
21
|
*/
|
|
22
|
+
/**
|
|
23
|
+
* C 区: tools whose output is eligible for microcompact clearing — reads,
|
|
24
|
+
* listings, searches, and shell output. write/edit outputs are short and
|
|
25
|
+
* never cleared.
|
|
26
|
+
*/
|
|
27
|
+
const MICROCOMPACTABLE = new Set(["read_file", "list_dir", "search_text", "shell"]);
|
|
28
|
+
/** The tag that makes a tool result un-clearable (C 区). */
|
|
29
|
+
export const DO_NOT_COMPACT = "do-not-compact";
|
|
30
|
+
/**
|
|
31
|
+
* C 区: the fixed placeholder for a cleared tool output, derived ONLY from
|
|
32
|
+
* the event stream (deterministic across replay): the tool's name and its
|
|
33
|
+
* primary argument (the first string-valued field of its input).
|
|
34
|
+
*/
|
|
35
|
+
function microPlaceholder(name, arg) {
|
|
36
|
+
return arg === undefined
|
|
37
|
+
? `[old tool output cleared: ${name}]`
|
|
38
|
+
: `[old tool output cleared: ${name} ${arg}]`;
|
|
39
|
+
}
|
|
40
|
+
/** The first string-valued argument of a tool input, if any. */
|
|
41
|
+
function primaryArg(input) {
|
|
42
|
+
if (input === null || input === undefined)
|
|
43
|
+
return undefined;
|
|
44
|
+
for (const value of Object.values(input)) {
|
|
45
|
+
if (typeof value === "string" && value !== "")
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
22
50
|
/**
|
|
23
51
|
* Rebuild the message array from events. Deterministic and order-sensitive:
|
|
24
|
-
* replaying the same log always produces the same messages
|
|
52
|
+
* replaying the same log always produces the same messages — BYTE FOR BYTE
|
|
53
|
+
* (D 区): the same event prefix derives the same message prefix; the only
|
|
54
|
+
* events that change already-derived messages are `microcompacted`
|
|
55
|
+
* boundaries, which are themselves persisted facts (their replay derives
|
|
56
|
+
* the same projection every time).
|
|
25
57
|
*
|
|
26
58
|
* Text block boundaries are preserved: `text_end` closes the current text
|
|
27
59
|
* block (an explicit boundary); `text_start` after a block opens a new one.
|
|
@@ -68,6 +100,12 @@ export function projectMessages(events) {
|
|
|
68
100
|
});
|
|
69
101
|
}
|
|
70
102
|
}
|
|
103
|
+
// C 区: callId → {name, input} for the microcompact placeholder.
|
|
104
|
+
const callMeta = new Map();
|
|
105
|
+
for (const ev of events) {
|
|
106
|
+
if (ev.type === "tool_call_end")
|
|
107
|
+
callMeta.set(ev.callId, { name: ev.name, input: ev.input });
|
|
108
|
+
}
|
|
71
109
|
let explicitAssistant = false;
|
|
72
110
|
for (const ev of events) {
|
|
73
111
|
switch (ev.type) {
|
|
@@ -171,6 +209,27 @@ export function projectMessages(events) {
|
|
|
171
209
|
out.push(message);
|
|
172
210
|
break;
|
|
173
211
|
}
|
|
212
|
+
case "microcompacted": {
|
|
213
|
+
flushAssistant();
|
|
214
|
+
// C 区: replace every eligible OLD tool result with the fixed
|
|
215
|
+
// placeholder. Eligibility: the result's own event seq <= the
|
|
216
|
+
// boundary, its tool in the whitelist, and no do-not-compact
|
|
217
|
+
// tag. Deterministic — the boundary event IS the decision.
|
|
218
|
+
const replaced = out.map((m) => {
|
|
219
|
+
if (m.role !== "tool")
|
|
220
|
+
return m;
|
|
221
|
+
if (m.eventSeq === undefined || m.eventSeq > ev.beforeSeq)
|
|
222
|
+
return m;
|
|
223
|
+
const meta = callMeta.get(m.callId);
|
|
224
|
+
if (meta === undefined || !MICROCOMPACTABLE.has(meta.name))
|
|
225
|
+
return m;
|
|
226
|
+
if ((m.tags ?? []).includes(DO_NOT_COMPACT))
|
|
227
|
+
return m;
|
|
228
|
+
return { ...m, content: microPlaceholder(meta.name, primaryArg(meta.input)) };
|
|
229
|
+
});
|
|
230
|
+
out.splice(0, out.length, ...replaced);
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
174
233
|
case "compacted": {
|
|
175
234
|
flushAssistant();
|
|
176
235
|
// Apply the EXACT persisted replacements — never re-run the
|
|
@@ -200,6 +259,7 @@ export function projectMessages(events) {
|
|
|
200
259
|
case "usage":
|
|
201
260
|
case "stop":
|
|
202
261
|
case "terminal":
|
|
262
|
+
case "microcompacted":
|
|
203
263
|
case "tool_execution_started":
|
|
204
264
|
case "tool_execution_succeeded":
|
|
205
265
|
case "tool_execution_failed":
|
|
@@ -310,6 +310,20 @@ export interface UserInputReplaced {
|
|
|
310
310
|
/** Provenance of the replacement — preserved from the hook (三). */
|
|
311
311
|
readonly source?: import("./messages.js").MessageSource;
|
|
312
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* C 区: a MICROCOMPACT boundary — the durable record of a context-clearing
|
|
315
|
+
* decision. `beforeSeq` is the event seq up to which eligible tool results
|
|
316
|
+
* are cleared: the projection replaces every tool_result with seq <=
|
|
317
|
+
* beforeSeq (whose tool is in the compactable whitelist and carries no
|
|
318
|
+
* do-not-compact tag) with a fixed placeholder derived from the stream
|
|
319
|
+
* itself. The decision is a PERSISTED FACT — replaying the same events
|
|
320
|
+
* derives the same messages, byte for byte, after a crash or resume.
|
|
321
|
+
*/
|
|
322
|
+
export interface MicroCompactEvent {
|
|
323
|
+
readonly seq: number;
|
|
324
|
+
readonly type: "microcompacted";
|
|
325
|
+
readonly beforeSeq: number;
|
|
326
|
+
}
|
|
313
327
|
/** Extended-thinking content. Providers without it emit nothing here. */
|
|
314
328
|
export interface Thinking {
|
|
315
329
|
readonly seq: number;
|
|
@@ -400,7 +414,7 @@ export interface TerminalEvent {
|
|
|
400
414
|
* The union. Consume it with a `switch (event.type)`; with
|
|
401
415
|
* `strictNullChecks` on, an unhandled variant is a compile error.
|
|
402
416
|
*/
|
|
403
|
-
export type Event = AssistantStart | AssistantEnd | TextStart | TextDelta | TextEnd | ToolCallStart | ToolCallInputDelta | ToolCallEnd | ToolResultEvent | Thinking | Usage | Stop | UserInputEvent | CompactedEvent | ToolExecutionStarted | ToolExecutionSucceeded | ToolExecutionFailed | ToolExecutionResolved | PermissionRequested | PermissionDecided | PermissionExpired | UncertainPending | UserInputReplaced | TerminalEvent;
|
|
417
|
+
export type Event = AssistantStart | AssistantEnd | TextStart | TextDelta | TextEnd | ToolCallStart | ToolCallInputDelta | ToolCallEnd | ToolResultEvent | Thinking | Usage | Stop | UserInputEvent | CompactedEvent | ToolExecutionStarted | ToolExecutionSucceeded | ToolExecutionFailed | ToolExecutionResolved | PermissionRequested | PermissionDecided | PermissionExpired | UncertainPending | UserInputReplaced | MicroCompactEvent | TerminalEvent;
|
|
404
418
|
/**
|
|
405
419
|
* Runtime type guard for the union. The store validates every JSONL record
|
|
406
420
|
* with it: valid JSON that is not a kiso event is corruption, not history.
|
package/dist/protocol/events.js
CHANGED
|
@@ -212,6 +212,7 @@ const EVENT_VALIDATORS = {
|
|
|
212
212
|
(v.reason === undefined || typeof v.reason === "string"),
|
|
213
213
|
permission_expired: (v) => typeof v.decisionId === "string" && typeof v.reason === "string",
|
|
214
214
|
uncertain_pending: (v) => typeof v.executionId === "string" && typeof v.callId === "string" && typeof v.name === "string" && typeof v.error === "string",
|
|
215
|
+
microcompacted: (v) => isNonNegativeInt(v.beforeSeq),
|
|
215
216
|
user_input_replaced: (v) => isNonNegativeInt(v.replaces) && (v.content === null || isContent(v.content)) && isSource(v),
|
|
216
217
|
terminal: (v) => isTerminal(v.outcome),
|
|
217
218
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "kiso(\u57fa\u790e) 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.1.
|
|
36
|
+
"@vincemakes/kiso-evals": "0.1.1",
|
|
37
37
|
"@types/node": "^26.1.2",
|
|
38
38
|
"typescript": "^5.7.2",
|
|
39
39
|
"vitest": "^3.0.0"
|