@vincemakes/kiso-core 0.1.32 → 0.1.34
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/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/kernel/loop.js +17 -5
- package/dist/kernel/project.js +112 -49
- package/dist/protocol/events.d.ts +31 -1
- package/dist/protocol/events.js +13 -7
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/kernel/loop.js
CHANGED
|
@@ -374,14 +374,17 @@ export async function* loop(config) {
|
|
|
374
374
|
lastStop = ev.reason;
|
|
375
375
|
stopCount += 1;
|
|
376
376
|
}
|
|
377
|
-
|
|
378
|
-
|
|
377
|
+
// R-E 0.1.43: the append precedes the launch — the call's
|
|
378
|
+
// framework seq is assigned here; invocationSeq must never
|
|
379
|
+
// be the adapter's stream-local hint (ADR-0002).
|
|
380
|
+
const full = log.append(ev);
|
|
381
|
+
if (full.type === "tool_call_end") {
|
|
382
|
+
pending.push(full);
|
|
379
383
|
// streaming execution: the call launches immediately — the decide
|
|
380
384
|
// and the ledgered run proceed in parallel with the
|
|
381
385
|
// model stream.
|
|
382
|
-
launch(
|
|
386
|
+
launch(full);
|
|
383
387
|
}
|
|
384
|
-
const full = log.append(ev);
|
|
385
388
|
if (hooks.onEvent)
|
|
386
389
|
await hooks.onEvent(full, {}).catch(() => { });
|
|
387
390
|
yield full;
|
|
@@ -578,11 +581,13 @@ function terminalForStop(reason) {
|
|
|
578
581
|
}
|
|
579
582
|
}
|
|
580
583
|
/** The tool_result event for a call — the shared shape (executionId rides
|
|
581
|
-
* it as the durable correlation, round 5
|
|
584
|
+
* it as the durable correlation, round 5; invocationSeq = the framework
|
|
585
|
+
* identity, R-E 0.1.43). */
|
|
582
586
|
function resultEvent(call, result, executionId) {
|
|
583
587
|
return {
|
|
584
588
|
type: "tool_result",
|
|
585
589
|
callId: call.callId,
|
|
590
|
+
invocationSeq: call.seq,
|
|
586
591
|
content: result.content,
|
|
587
592
|
isError: result.isError,
|
|
588
593
|
// P1-9: errorKind only exists on errors (the type now enforces it;
|
|
@@ -665,6 +670,7 @@ async function decideCall(call, registry, hooks, ctx, log, resolveApproval, reso
|
|
|
665
670
|
type: "permission_decided",
|
|
666
671
|
decisionId: nextDecisionId(),
|
|
667
672
|
callId: call.callId,
|
|
673
|
+
invocationSeq: call.seq,
|
|
668
674
|
decision: chainVerdict.action === "allow" ? "approved" : "denied",
|
|
669
675
|
...(chainVerdict.action === "deny" && chainVerdict.reason !== undefined
|
|
670
676
|
? { reason: chainVerdict.reason }
|
|
@@ -733,6 +739,7 @@ async function humanPause(call, decisionId, hooks, log, resolveApproval, resolve
|
|
|
733
739
|
type: "permission_requested",
|
|
734
740
|
decisionId,
|
|
735
741
|
callId: call.callId,
|
|
742
|
+
invocationSeq: call.seq,
|
|
736
743
|
name: call.name,
|
|
737
744
|
input: call.input ?? {},
|
|
738
745
|
// W21: the ask verdict's speaker — the panel's why-asked line (a
|
|
@@ -759,6 +766,7 @@ async function humanPause(call, decisionId, hooks, log, resolveApproval, resolve
|
|
|
759
766
|
type: "permission_decided",
|
|
760
767
|
decisionId,
|
|
761
768
|
callId: call.callId,
|
|
769
|
+
invocationSeq: call.seq,
|
|
762
770
|
decision: verdict ? "approved" : "denied",
|
|
763
771
|
...(verdict ? {} : { reason: "denied by user" }),
|
|
764
772
|
});
|
|
@@ -774,6 +782,7 @@ async function humanPause(call, decisionId, hooks, log, resolveApproval, resolve
|
|
|
774
782
|
type: "permission_decided",
|
|
775
783
|
decisionId,
|
|
776
784
|
callId: call.callId, // binds the decision to the invocation (B group)
|
|
785
|
+
invocationSeq: call.seq,
|
|
777
786
|
decision: finalDecision.action === "allow" ? "approved" : "denied",
|
|
778
787
|
...(finalDecision.action === "deny" && finalDecision.reason !== undefined
|
|
779
788
|
? { reason: finalDecision.reason }
|
|
@@ -806,6 +815,7 @@ async function runLedgered(call, registry, hooks, ctx, signal, push) {
|
|
|
806
815
|
push({
|
|
807
816
|
type: "tool_execution_started",
|
|
808
817
|
callId: call.callId,
|
|
818
|
+
invocationSeq: call.seq,
|
|
809
819
|
name: call.name,
|
|
810
820
|
input: call.input, // non-null: decideCall denied a null input before this ran
|
|
811
821
|
}, (id) => res(id ?? ""));
|
|
@@ -851,6 +861,7 @@ async function runLedgered(call, registry, hooks, ctx, signal, push) {
|
|
|
851
861
|
type: "tool_execution_failed",
|
|
852
862
|
executionId,
|
|
853
863
|
callId: call.callId,
|
|
864
|
+
invocationSeq: call.seq,
|
|
854
865
|
error: result.content,
|
|
855
866
|
// P1-9: errorKind only exists on errors (the type now enforces it;
|
|
856
867
|
// the runtime guard keeps a JS tool's illegal combination out of
|
|
@@ -865,6 +876,7 @@ async function runLedgered(call, registry, hooks, ctx, signal, push) {
|
|
|
865
876
|
type: "tool_execution_succeeded",
|
|
866
877
|
executionId,
|
|
867
878
|
callId: call.callId,
|
|
879
|
+
invocationSeq: call.seq,
|
|
868
880
|
result: { content: result.content, isError: false },
|
|
869
881
|
...(result.tags !== undefined ? { tags: result.tags } : {}),
|
|
870
882
|
});
|
package/dist/kernel/project.js
CHANGED
|
@@ -85,6 +85,7 @@ export function projectMessages(events) {
|
|
|
85
85
|
assistantSource = undefined;
|
|
86
86
|
return;
|
|
87
87
|
}
|
|
88
|
+
const callIds = blocks.filter((b) => b.type === "tool_use").map((b) => b.callId);
|
|
88
89
|
const reasoning = pendingReasoning;
|
|
89
90
|
pendingReasoning = null;
|
|
90
91
|
out.push({
|
|
@@ -95,6 +96,15 @@ export function projectMessages(events) {
|
|
|
95
96
|
});
|
|
96
97
|
blocks = [];
|
|
97
98
|
assistantSource = undefined;
|
|
99
|
+
// P1: the closed assistant's calls whose results are NOT already
|
|
100
|
+
// buffered are pending — their results land later (post-stop late
|
|
101
|
+
// results, the straddle's shape). The mid-execution inputs hold
|
|
102
|
+
// until those results flush.
|
|
103
|
+
// the guard keeps a still-pending pair holding when a call-less
|
|
104
|
+
// assistant closes mid-hold (the empty set would release the input)
|
|
105
|
+
const buffered = new Set(resultBuf.map((r) => r.callId));
|
|
106
|
+
if (callIds.length > 0)
|
|
107
|
+
pendingCalls = new Set(callIds.filter((c) => !buffered.has(c)));
|
|
98
108
|
};
|
|
99
109
|
// C group/round 6: vetoed/rewritten user inputs. Collect the replacement map
|
|
100
110
|
// first — the FINAL replacement for each input wins (later replacements
|
|
@@ -136,7 +146,17 @@ export function projectMessages(events) {
|
|
|
136
146
|
}
|
|
137
147
|
}
|
|
138
148
|
}
|
|
139
|
-
|
|
149
|
+
// R-E 0.1.43 (Gap B): a model_output_abandoned marker voids the range
|
|
150
|
+
// (voidFromSeq, its seq] — the abandoned draft's events. The ranges are
|
|
151
|
+
// disjoint and in seq order; the skip below treats them exactly like the
|
|
152
|
+
// summary ranges (the marker itself renders nothing and skips itself).
|
|
153
|
+
const voidRanges = [];
|
|
154
|
+
for (const ev of events) {
|
|
155
|
+
if (ev.type === "model_output_abandoned")
|
|
156
|
+
voidRanges.push({ from: ev.voidFromSeq, to: ev.seq ?? -1 });
|
|
157
|
+
}
|
|
158
|
+
const isCovered = (seq) => summaryRanges.some((r) => seq > r.from && seq <= r.to) ||
|
|
159
|
+
voidRanges.some((r) => seq > r.from && seq <= r.to);
|
|
140
160
|
// Summaries render in range order as the pass crosses their boundaries.
|
|
141
161
|
let renderedSummaries = 0;
|
|
142
162
|
// 0.1.26 (ADR-0024 Amd, parallel execution): the tool results of ONE turn
|
|
@@ -150,15 +170,32 @@ export function projectMessages(events) {
|
|
|
150
170
|
let resultBuf = [];
|
|
151
171
|
const callOrder = new Map();
|
|
152
172
|
let callOrderNext = 0;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
173
|
+
// P1 (0.1.42): the mid-execution input deferral. A user input arriving
|
|
174
|
+
// while the last closed assistant's tool_calls are UNANSWERED (the
|
|
175
|
+
// result lands later in the log — the boundary straddle's durable
|
|
176
|
+
// shape) must never separate the call from its result: the request
|
|
177
|
+
// [assistant, user, tool] is a real provider 400. Such inputs are
|
|
178
|
+
// HELD and released right after the results flush — the mid-execution
|
|
179
|
+
// input takes effect when the turn's execution completes.
|
|
180
|
+
let pendingCalls = new Set();
|
|
181
|
+
let heldUsers = [];
|
|
182
|
+
const flushResults = (force = false) => {
|
|
183
|
+
if (resultBuf.length > 0) {
|
|
184
|
+
resultBuf.sort((a, b) => (callOrder.get(a.callId) ?? 0) - (callOrder.get(b.callId) ?? 0));
|
|
185
|
+
for (const r of resultBuf)
|
|
186
|
+
out.push(r.message);
|
|
187
|
+
resultBuf = [];
|
|
188
|
+
callOrder.clear();
|
|
189
|
+
callOrderNext = 0;
|
|
190
|
+
}
|
|
191
|
+
// The held inputs release ONLY when the pair resolved (or the
|
|
192
|
+
// projection ends — force): a still-pending pair must keep holding,
|
|
193
|
+
// or the next user_input's leading flush would dump the inputs
|
|
194
|
+
// between the call and its late result (the 400 again).
|
|
195
|
+
if (force || pendingCalls.size === 0) {
|
|
196
|
+
out.push(...heldUsers);
|
|
197
|
+
heldUsers = [];
|
|
198
|
+
}
|
|
162
199
|
};
|
|
163
200
|
let explicitAssistant = false;
|
|
164
201
|
for (const ev of events) {
|
|
@@ -186,30 +223,41 @@ export function projectMessages(events) {
|
|
|
186
223
|
case "user_input": {
|
|
187
224
|
// 0.1.26: the previous turn closes here — the assistant
|
|
188
225
|
// first, then its results in call order (the turn boundary).
|
|
189
|
-
flushAssistant();
|
|
190
|
-
flushResults();
|
|
191
226
|
// round 6: the final replacement renders HERE, at the input's own
|
|
192
227
|
// position — the original is skipped, the replacement event
|
|
193
228
|
// itself produces nothing (a later replacement for the same
|
|
194
|
-
// input never becomes a second message)
|
|
229
|
+
// input never becomes a second message); a null content is a
|
|
230
|
+
// true veto (nothing renders at that position).
|
|
231
|
+
flushAssistant();
|
|
232
|
+
flushResults();
|
|
233
|
+
let content = ev.content;
|
|
234
|
+
let source = ev.source;
|
|
235
|
+
let veto = false;
|
|
195
236
|
if ("seq" in ev && typeof ev.seq === "number" && replaced.has(ev.seq)) {
|
|
196
237
|
const replacement = replaced.get(ev.seq);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
});
|
|
238
|
+
if (replacement.content === null) {
|
|
239
|
+
veto = true;
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
content = replacement.content;
|
|
243
|
+
source = replacement.source ?? ev.source;
|
|
204
244
|
}
|
|
205
|
-
break;
|
|
206
245
|
}
|
|
207
|
-
|
|
208
|
-
|
|
246
|
+
if (veto)
|
|
247
|
+
break;
|
|
248
|
+
const message = {
|
|
209
249
|
role: "user",
|
|
210
|
-
content
|
|
211
|
-
...(
|
|
212
|
-
}
|
|
250
|
+
content,
|
|
251
|
+
...(source !== undefined ? { source } : {}),
|
|
252
|
+
};
|
|
253
|
+
// P1: the mid-execution deferral — an input arriving while
|
|
254
|
+
// the turn's calls are unanswered is HELD and released right
|
|
255
|
+
// after the results flush (the pairing invariant: the
|
|
256
|
+
// results must follow their calls before any user message).
|
|
257
|
+
if (pendingCalls.size > 0)
|
|
258
|
+
heldUsers.push(message);
|
|
259
|
+
else
|
|
260
|
+
out.push(message);
|
|
213
261
|
break;
|
|
214
262
|
}
|
|
215
263
|
case "user_input_replaced":
|
|
@@ -253,18 +301,16 @@ export function projectMessages(events) {
|
|
|
253
301
|
case "text_delta":
|
|
254
302
|
// 0.1.26: a text delta with BUFFERED RESULTS opens the NEXT
|
|
255
303
|
// turn's stream — the previous turn closes HERE: its
|
|
256
|
-
// assistant
|
|
257
|
-
//
|
|
258
|
-
//
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
flushAssistant();
|
|
267
|
-
flushResults();
|
|
304
|
+
// assistant (closed at the stop), then its buffered results
|
|
305
|
+
// in call order (the API requires each tool_calls message
|
|
306
|
+
// to be followed by its tool messages — a real 400 without
|
|
307
|
+
// the boundary). P1 (0.1.42): the flush keys on OPEN
|
|
308
|
+
// assistant content — an open block or text proves the
|
|
309
|
+
// model is still streaming THIS turn, and a same-turn
|
|
310
|
+
// buffered result must stay buffered (flushing it here
|
|
311
|
+
// split the turn's message — the fresh2 400).
|
|
312
|
+
if (blocks.length === 0 && text === null && resultBuf.length > 0)
|
|
313
|
+
flushResults();
|
|
268
314
|
text = (text ?? "") + ev.text;
|
|
269
315
|
break;
|
|
270
316
|
case "tool_call_start":
|
|
@@ -276,11 +322,14 @@ export function projectMessages(events) {
|
|
|
276
322
|
case "tool_call_end":
|
|
277
323
|
// 0.1.26: a tool_call_end with BUFFERED RESULTS opens the
|
|
278
324
|
// NEXT turn's stream (the model called again) — the
|
|
279
|
-
// previous turn
|
|
280
|
-
//
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
325
|
+
// previous turn's results close first; a same-turn call
|
|
326
|
+
// keeps the assistant open. P1 (0.1.42): open assistant
|
|
327
|
+
// content proves the same-turn call — the buffered results
|
|
328
|
+
// close at the stop, NEVER here (flushing a same-turn
|
|
329
|
+
// result at a later call_end split the turn's message —
|
|
330
|
+
// the fresh2 400).
|
|
331
|
+
if (blocks.length === 0 && text === null && resultBuf.length > 0)
|
|
332
|
+
flushResults();
|
|
284
333
|
pushText();
|
|
285
334
|
callOrder.set(ev.callId, callOrderNext++);
|
|
286
335
|
blocks.push({
|
|
@@ -315,6 +364,9 @@ export function projectMessages(events) {
|
|
|
315
364
|
}
|
|
316
365
|
// 0.1.26: BUFFERED — the results flush in call order at the
|
|
317
366
|
// turn boundary (flushResults), not in completion order.
|
|
367
|
+
// P1: a pending call's result resolves the pair — the held
|
|
368
|
+
// mid-execution inputs release with the flush.
|
|
369
|
+
pendingCalls.delete(ev.callId);
|
|
318
370
|
resultBuf.push({ callId: ev.callId, message });
|
|
319
371
|
break;
|
|
320
372
|
}
|
|
@@ -374,9 +426,11 @@ export function projectMessages(events) {
|
|
|
374
426
|
// the assistant message that follows carries it. 0.1.26: the
|
|
375
427
|
// assistant flush is guarded on the buffered results (a
|
|
376
428
|
// mid-turn reasoning must not split the current message).
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
429
|
+
// P1 (0.1.42): the flush keys on OPEN assistant content —
|
|
430
|
+
// a mid-turn reasoning with a same-turn result buffered
|
|
431
|
+
// must NOT split the message (the fresh2 400 family).
|
|
432
|
+
if (blocks.length === 0 && text === null && resultBuf.length > 0)
|
|
433
|
+
flushResults();
|
|
380
434
|
pendingReasoning = (pendingReasoning ?? "") + ev.text;
|
|
381
435
|
break;
|
|
382
436
|
case "summarized":
|
|
@@ -397,9 +451,14 @@ export function projectMessages(events) {
|
|
|
397
451
|
break;
|
|
398
452
|
case "stop":
|
|
399
453
|
// The turn boundary: the assistant closes at the provider's
|
|
400
|
-
// stop
|
|
401
|
-
// turn
|
|
454
|
+
// stop — and the turn's BUFFERED results close WITH it (P1
|
|
455
|
+
// 0.1.42). Parallel execution lands same-turn results
|
|
456
|
+
// mid-stream; flushing them here — the true boundary —
|
|
457
|
+
// projects [assistant, results…] in reading order, and the
|
|
458
|
+
// stream-open guards below only ever flush a CLOSED turn's
|
|
459
|
+
// late (post-stop) results.
|
|
402
460
|
flushAssistant();
|
|
461
|
+
flushResults();
|
|
403
462
|
break;
|
|
404
463
|
case "terminal":
|
|
405
464
|
case "tool_execution_started":
|
|
@@ -410,6 +469,10 @@ export function projectMessages(events) {
|
|
|
410
469
|
case "permission_decided":
|
|
411
470
|
case "permission_expired":
|
|
412
471
|
case "uncertain_pending":
|
|
472
|
+
case "model_output_abandoned":
|
|
473
|
+
// R-E 0.1.43: renders nothing — the marker is always skipped by
|
|
474
|
+
// its own range; the case documents the intent (and no flush:
|
|
475
|
+
// it must never split a message).
|
|
413
476
|
break;
|
|
414
477
|
case "microcompacted":
|
|
415
478
|
// handled above (the replacement pass) — no open message.
|
|
@@ -417,7 +480,7 @@ export function projectMessages(events) {
|
|
|
417
480
|
}
|
|
418
481
|
}
|
|
419
482
|
flushAssistant();
|
|
420
|
-
flushResults();
|
|
483
|
+
flushResults(true); // the log's end is the last resort: held inputs never drop
|
|
421
484
|
return out;
|
|
422
485
|
}
|
|
423
486
|
/**
|
|
@@ -134,6 +134,7 @@ export interface ToolResultEvent {
|
|
|
134
134
|
readonly seq: number;
|
|
135
135
|
readonly type: "tool_result";
|
|
136
136
|
readonly callId: string;
|
|
137
|
+
readonly invocationSeq?: number;
|
|
137
138
|
/** Full content — blocks preserved losslessly (D group). */
|
|
138
139
|
readonly content: string | readonly import("./messages.js").ContentBlock[];
|
|
139
140
|
readonly isError: boolean;
|
|
@@ -199,6 +200,7 @@ export interface ToolExecutionStarted {
|
|
|
199
200
|
readonly type: "tool_execution_started";
|
|
200
201
|
readonly executionId: string;
|
|
201
202
|
readonly callId: string;
|
|
203
|
+
readonly invocationSeq?: number;
|
|
202
204
|
readonly name: string;
|
|
203
205
|
readonly input: Readonly<Record<string, unknown>>;
|
|
204
206
|
}
|
|
@@ -208,6 +210,7 @@ export interface ToolExecutionSucceeded {
|
|
|
208
210
|
readonly type: "tool_execution_succeeded";
|
|
209
211
|
readonly executionId: string;
|
|
210
212
|
readonly callId: string;
|
|
213
|
+
readonly invocationSeq?: number;
|
|
211
214
|
readonly result: {
|
|
212
215
|
readonly content: string;
|
|
213
216
|
readonly isError: false;
|
|
@@ -227,6 +230,7 @@ export interface ToolExecutionFailed {
|
|
|
227
230
|
readonly type: "tool_execution_failed";
|
|
228
231
|
readonly executionId: string;
|
|
229
232
|
readonly callId: string;
|
|
233
|
+
readonly invocationSeq?: number;
|
|
230
234
|
readonly error: string;
|
|
231
235
|
readonly errorKind?: ToolErrorKind;
|
|
232
236
|
readonly safeToRetry: boolean;
|
|
@@ -243,6 +247,7 @@ export interface ToolExecutionResolved {
|
|
|
243
247
|
readonly type: "tool_execution_resolved";
|
|
244
248
|
readonly executionId: string;
|
|
245
249
|
readonly callId: string;
|
|
250
|
+
readonly invocationSeq?: number;
|
|
246
251
|
readonly resolution: "rerun" | "abandoned";
|
|
247
252
|
}
|
|
248
253
|
/**
|
|
@@ -256,6 +261,7 @@ export interface PermissionRequested {
|
|
|
256
261
|
readonly type: "permission_requested";
|
|
257
262
|
readonly decisionId: string;
|
|
258
263
|
readonly callId: string;
|
|
264
|
+
readonly invocationSeq?: number;
|
|
259
265
|
readonly name: string;
|
|
260
266
|
readonly input: Readonly<Record<string, unknown>>;
|
|
261
267
|
/** W21: the first non-abstain extension's name — the panel's why-asked
|
|
@@ -269,6 +275,7 @@ export interface PermissionDecided {
|
|
|
269
275
|
readonly decisionId: string;
|
|
270
276
|
/** The invocation this decision binds to (B group). */
|
|
271
277
|
readonly callId?: string;
|
|
278
|
+
readonly invocationSeq?: number;
|
|
272
279
|
readonly decision: "approved" | "denied";
|
|
273
280
|
readonly reason?: string;
|
|
274
281
|
/** E1: the deciding extension's name — present ONLY on policy decisions;
|
|
@@ -353,6 +360,22 @@ export interface SummarizedEvent {
|
|
|
353
360
|
/** The model's compression — replaces the covered range in the projection. */
|
|
354
361
|
readonly summary: string;
|
|
355
362
|
}
|
|
363
|
+
/**
|
|
364
|
+
* R-E 0.1.43 / ADR-0047 (Gap B) — a model output suffix WITHOUT a committed
|
|
365
|
+
* stop was abandoned on resume. `voidFromSeq` is the last committed-boundary
|
|
366
|
+
* event's seq (stop / user_input / terminal / compaction / summarized); the
|
|
367
|
+
* voided range is (voidFromSeq, this.seq]. The projection skips the range and
|
|
368
|
+
* the marker itself renders nothing — "a model output suffix without a
|
|
369
|
+
* committed stop is an incomplete draft and must never become committed
|
|
370
|
+
* provider history." Kernel-owned: the AdapterEvent whitelist excludes it, so
|
|
371
|
+
* adapter/extension forgery is invalid_request by construction.
|
|
372
|
+
*/
|
|
373
|
+
export interface ModelOutputAbandoned {
|
|
374
|
+
readonly seq: number;
|
|
375
|
+
readonly type: "model_output_abandoned";
|
|
376
|
+
readonly voidFromSeq: number;
|
|
377
|
+
readonly reason: string;
|
|
378
|
+
}
|
|
356
379
|
/** Extended-thinking content. Providers without it emit nothing here. */
|
|
357
380
|
export interface Thinking {
|
|
358
381
|
readonly seq: number;
|
|
@@ -440,10 +463,17 @@ export interface TerminalEvent {
|
|
|
440
463
|
readonly outcome: Terminal;
|
|
441
464
|
}
|
|
442
465
|
/**
|
|
466
|
+
* IDENTITY (the three-identity model, R-E 0.1.43 / ADR-0047): an invocation
|
|
467
|
+
* carries THREE ids — `callId` (provider correlation only, may repeat across
|
|
468
|
+
* runs), `invocationSeq` (the framework identity = the call's tool_call_end
|
|
469
|
+
* .seq; OPTIONAL — absent on old logs, present on everything written from
|
|
470
|
+
* 0.1.43), and `executionId` (a specific execution of that invocation, one
|
|
471
|
+
* per started event). Old logs keep the callId + seq-proximity fallback.
|
|
472
|
+
*
|
|
443
473
|
* The union. Consume it with a `switch (event.type)`; with
|
|
444
474
|
* `strictNullChecks` on, an unhandled variant is a compile error.
|
|
445
475
|
*/
|
|
446
|
-
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 | SummarizedEvent | TerminalEvent;
|
|
476
|
+
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 | SummarizedEvent | ModelOutputAbandoned | TerminalEvent;
|
|
447
477
|
/**
|
|
448
478
|
* Runtime type guard for the union. The store validates every JSONL record
|
|
449
479
|
* with it: valid JSON that is not a kiso event is corruption, not history.
|
package/dist/protocol/events.js
CHANGED
|
@@ -102,6 +102,9 @@ function isErrorKind(v) {
|
|
|
102
102
|
function isExecutionId(v) {
|
|
103
103
|
return v.executionId === undefined || typeof v.executionId === "string";
|
|
104
104
|
}
|
|
105
|
+
/** R-E 0.1.43: the optional framework invocation identity — absent on old
|
|
106
|
+
* logs, otherwise a non-negative safe integer (the tool_call_end.seq). */
|
|
107
|
+
const isInvocationSeq = (v) => v.invocationSeq === undefined || isNonNegativeInt(v.invocationSeq);
|
|
105
108
|
/** A StructuredError — the shape the `error` terminal carries. round 9: a
|
|
106
109
|
* status, when present, is a non-negative safe integer (no negatives,
|
|
107
110
|
* NaN, Infinity, or fractions). */
|
|
@@ -170,7 +173,7 @@ const EVENT_VALIDATORS = {
|
|
|
170
173
|
isErrorKind(v) &&
|
|
171
174
|
isExecutionId(v) &&
|
|
172
175
|
isSource(v) &&
|
|
173
|
-
isTags(v),
|
|
176
|
+
isTags(v) && isInvocationSeq(v),
|
|
174
177
|
thinking: (v) => typeof v.text === "string",
|
|
175
178
|
usage: isUsage,
|
|
176
179
|
stop: (v) => STOP_REASONS.has(v.reason),
|
|
@@ -186,32 +189,32 @@ const EVENT_VALIDATORS = {
|
|
|
186
189
|
tool_execution_started: (v) => typeof v.executionId === "string" &&
|
|
187
190
|
typeof v.callId === "string" &&
|
|
188
191
|
typeof v.name === "string" &&
|
|
189
|
-
isPlainObject(v.input),
|
|
192
|
+
isPlainObject(v.input) && isInvocationSeq(v),
|
|
190
193
|
tool_execution_succeeded: (v) => typeof v.executionId === "string" &&
|
|
191
194
|
typeof v.callId === "string" &&
|
|
192
195
|
isPlainObject(v.result) &&
|
|
193
196
|
typeof v.result.content === "string" &&
|
|
194
197
|
v.result.isError === false &&
|
|
195
|
-
isTags(v),
|
|
198
|
+
isTags(v) && isInvocationSeq(v),
|
|
196
199
|
tool_execution_failed: (v) => typeof v.executionId === "string" &&
|
|
197
200
|
typeof v.callId === "string" &&
|
|
198
201
|
typeof v.error === "string" &&
|
|
199
202
|
typeof v.safeToRetry === "boolean" &&
|
|
200
203
|
isErrorKind(v) &&
|
|
201
|
-
isTags(v),
|
|
204
|
+
isTags(v) && isInvocationSeq(v),
|
|
202
205
|
tool_execution_resolved: (v) => typeof v.executionId === "string" &&
|
|
203
206
|
typeof v.callId === "string" &&
|
|
204
|
-
(v.resolution === "rerun" || v.resolution === "abandoned"),
|
|
207
|
+
(v.resolution === "rerun" || v.resolution === "abandoned") && isInvocationSeq(v),
|
|
205
208
|
permission_requested: (v) => typeof v.decisionId === "string" &&
|
|
206
209
|
typeof v.callId === "string" &&
|
|
207
210
|
typeof v.name === "string" &&
|
|
208
211
|
isPlainObject(v.input) &&
|
|
209
|
-
(v.speaker === undefined || typeof v.speaker === "string"),
|
|
212
|
+
(v.speaker === undefined || typeof v.speaker === "string") && isInvocationSeq(v),
|
|
210
213
|
permission_decided: (v) => typeof v.decisionId === "string" &&
|
|
211
214
|
(v.decision === "approved" || v.decision === "denied") &&
|
|
212
215
|
(v.callId === undefined || typeof v.callId === "string") &&
|
|
213
216
|
(v.reason === undefined || typeof v.reason === "string") &&
|
|
214
|
-
(v.decidedBy === undefined || typeof v.decidedBy === "string"),
|
|
217
|
+
(v.decidedBy === undefined || typeof v.decidedBy === "string") && isInvocationSeq(v),
|
|
215
218
|
permission_expired: (v) => typeof v.decisionId === "string" && typeof v.reason === "string",
|
|
216
219
|
uncertain_pending: (v) => typeof v.executionId === "string" && typeof v.callId === "string" && typeof v.name === "string" && typeof v.error === "string",
|
|
217
220
|
microcompacted: (v) => isNonNegativeInt(v.beforeSeq),
|
|
@@ -221,6 +224,9 @@ const EVENT_VALIDATORS = {
|
|
|
221
224
|
typeof v.summary === "string" &&
|
|
222
225
|
v.summary.length > 0 &&
|
|
223
226
|
v.coversToSeq < v.seq,
|
|
227
|
+
// R-E 0.1.43: the void starts at a committed boundary BEFORE the marker —
|
|
228
|
+
// a marker that voids nothing (or itself) is corruption, not history.
|
|
229
|
+
model_output_abandoned: (v) => isNonNegativeInt(v.voidFromSeq) && typeof v.reason === "string" && v.voidFromSeq < v.seq,
|
|
224
230
|
user_input_replaced: (v) => isNonNegativeInt(v.replaces) && (v.content === null || isContent(v.content)) && isSource(v),
|
|
225
231
|
terminal: (v) => isTerminal(v.outcome),
|
|
226
232
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.34",
|
|
4
4
|
"description": "kiso (foundation) core — 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.35",
|
|
37
37
|
"@types/node": "^26.1.2",
|
|
38
38
|
"typescript": "^5.7.2",
|
|
39
39
|
"vitest": "^3.0.0"
|