@vincemakes/kiso-core 0.42.3 → 0.44.0

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 CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from "./protocol/index.js";
2
2
  export * from "./tools/tool.js";
3
3
  export * from "./tools/registry.js";
4
+ export { validateArgs } from "./tools/validate.js";
4
5
  export * from "./errors.js";
5
6
  export * from "./kernel/event-log.js";
6
7
  export * from "./kernel/hooks.js";
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from "./protocol/index.js";
2
2
  export * from "./tools/tool.js";
3
3
  export * from "./tools/registry.js";
4
+ export { validateArgs } from "./tools/validate.js";
4
5
  export * from "./errors.js";
5
6
  export * from "./kernel/event-log.js";
6
7
  export * from "./kernel/hooks.js";
@@ -27,6 +27,13 @@ export interface ToolCallPayload {
27
27
  readonly callId: string;
28
28
  readonly name: string;
29
29
  readonly input: Readonly<Record<string, unknown>>;
30
+ /** 0.44.0 (#20): the invocation's arguments as the model streamed them —
31
+ * the same lexical evidence the tool receives as `ctx.rawInput`, on
32
+ * onPreTool and onPostTool alike, fresh or recovered; absent when no
33
+ * delta was streamed. Evidence for the host, never a control input: the
34
+ * approval chain never receives it, and validation, permission and
35
+ * execution read the parsed input. */
36
+ readonly rawInput?: string;
30
37
  }
31
38
  export interface HookHost {
32
39
  /** Assemble: rewrite or veto the incoming user message. null = drop. */
@@ -33,6 +33,7 @@
33
33
  * re-stream that duplicates output or tool calls.
34
34
  */
35
35
  import { isAdapterEvent } from "../protocol/adapter.js";
36
+ import { ERROR_CODES } from "../protocol/error-codes.js";
36
37
  import { EventLog } from "./event-log.js";
37
38
  import { ToolRegistry } from "../tools/registry.js";
38
39
  import { validateArgs } from "../tools/validate.js";
@@ -343,7 +344,7 @@ export async function* loop(config) {
343
344
  }
344
345
  return { wait, release };
345
346
  };
346
- const launch = (call) => {
347
+ const launch = (call, rawInput) => {
347
348
  execActive += 1;
348
349
  const tool = table.get(call.name);
349
350
  const slot = reserve(tool);
@@ -356,7 +357,7 @@ export async function* loop(config) {
356
357
  launches.push((async () => {
357
358
  try {
358
359
  decideChain = decideChain.then(async () => {
359
- const v = await decideCall(call, table, hooks, { signal: signal ?? NEVER_ABORT, ...(config.sessionId !== undefined ? { sessionId: config.sessionId } : {}) }, log, config.resolveApproval, config.approvalVerdict, signal, config.approvalPolicy, nextDecisionId, pushExec);
360
+ const v = await decideCall(call, table, hooks, { signal: signal ?? NEVER_ABORT, ...(config.sessionId !== undefined ? { sessionId: config.sessionId } : {}) }, log, config.resolveApproval, config.approvalVerdict, signal, config.approvalPolicy, nextDecisionId, pushExec, rawInput);
360
361
  askOrder.ahead = askGate;
361
362
  if (v.action === "ask") {
362
363
  askGate = new Promise((res) => {
@@ -455,7 +456,7 @@ export async function* loop(config) {
455
456
  return;
456
457
  await acquireWindow();
457
458
  try {
458
- await runLedgered(call, table, hooks, { signal: signal ?? NEVER_ABORT, ...(config.sessionId !== undefined ? { sessionId: config.sessionId } : {}) }, signal, pushExec);
459
+ await runLedgered(call, table, hooks, { signal: signal ?? NEVER_ABORT, ...(config.sessionId !== undefined ? { sessionId: config.sessionId } : {}), ...(rawInput !== undefined ? { rawInput } : {}) }, signal, pushExec);
459
460
  }
460
461
  finally {
461
462
  releaseWindow();
@@ -588,6 +589,12 @@ export async function* loop(config) {
588
589
  yield await terminal({ kind: "aborted", by: "user" });
589
590
  return;
590
591
  }
592
+ // 0.43.0 (#13): the lexical arguments, buffered PER ATTEMPT — a new
593
+ // table for every stream, so a retry after an abandoned draft never
594
+ // pastes that draft's text onto a re-used callId; consumed at the
595
+ // call's end. callId is the attempt's correlation key only; the
596
+ // durable identity stays the tool_call_end's seq.
597
+ const raw = new Map();
591
598
  try {
592
599
  const stream = config.adapter.stream({
593
600
  model: config.model,
@@ -649,13 +656,16 @@ export async function* loop(config) {
649
656
  // R-E 0.1.43: the append precedes the launch — the call's
650
657
  // framework seq is assigned here; invocationSeq must never
651
658
  // be the adapter's stream-local hint (ADR-0002).
659
+ if (ev.type === "tool_call_input_delta")
660
+ raw.set(ev.callId, (raw.get(ev.callId) ?? "") + ev.inputJsonDelta);
652
661
  const full = log.append(ev);
653
662
  if (full.type === "tool_call_end") {
654
663
  pending.push(full);
655
664
  // streaming execution: the call launches immediately — the decide
656
665
  // and the ledgered run proceed in parallel with the
657
666
  // model stream.
658
- launch(full);
667
+ launch(full, raw.get(full.callId));
668
+ raw.delete(full.callId);
659
669
  }
660
670
  if (hooks.onEvent)
661
671
  await hooks.onEvent(full, {}).catch(() => { });
@@ -1010,7 +1020,7 @@ function resultEvent(call, result, executionId) {
1010
1020
  * caller runs it (conservative ordering: the calls after an ask wait for
1011
1021
  * its resolution).
1012
1022
  */
1013
- async function decideCall(call, registry, hooks, ctx, log, resolveApproval, resolveApprovalVerdict, signal, approvalPolicy, nextDecisionId, push) {
1023
+ async function decideCall(call, registry, hooks, ctx, log, resolveApproval, resolveApprovalVerdict, signal, approvalPolicy, nextDecisionId, push, rawInput) {
1014
1024
  const payload = {
1015
1025
  callId: call.callId,
1016
1026
  name: call.name,
@@ -1147,7 +1157,9 @@ async function decideCall(call, registry, hooks, ctx, log, resolveApproval, reso
1147
1157
  // resolved by the human pause above — the static hook never speaks for
1148
1158
  // it, and a durable decision already spoke for the call).
1149
1159
  if (durable === undefined && chainVerdict === undefined && hooks.onPreTool) {
1150
- const decision = await raceAbort(hooks.onPreTool(payload, ctx), signal);
1160
+ // #20: the hook gets the lexical evidence; the chain above never did —
1161
+ // it decides on the parsed call, the object it has always received.
1162
+ const decision = await raceAbort(hooks.onPreTool(rawInput !== undefined ? { ...payload, rawInput } : payload, ctx), signal);
1151
1163
  if (signal?.aborted)
1152
1164
  throw ABORTED;
1153
1165
  if (decision.action === "defer") {
@@ -1259,7 +1271,7 @@ async function runLedgered(call, registry, hooks, ctx, signal, push) {
1259
1271
  };
1260
1272
  }
1261
1273
  if (hooks.onPostTool) {
1262
- result = await hooks.onPostTool({ callId: call.callId, name: call.name, input: call.input ?? {} }, result, ctx);
1274
+ result = await hooks.onPostTool({ callId: call.callId, name: call.name, input: call.input ?? {}, ...(ctx.rawInput !== undefined ? { rawInput: ctx.rawInput } : {}) }, result, ctx);
1263
1275
  }
1264
1276
  // ruling #12 correction one: a non-idempotent failure's side effects may have
1265
1277
  // partially applied — an honest note rides the RESULT (and the failed
@@ -1345,11 +1357,19 @@ export function toStructuredError(err) {
1345
1357
  if (typeof err === "object" && err !== null) {
1346
1358
  const e = err;
1347
1359
  if (typeof e.code === "string" && typeof e.retryable === "boolean") {
1360
+ // 0.43.0 (#19): every value this returns satisfies the durable
1361
+ // StructuredError schema the store reads back with — a host
1362
+ // adapter's vendor code becomes "unknown" with the code kept in the
1363
+ // message; a status outside the non-negative safe integers is
1364
+ // dropped; retryable rides as given. The kernel never writes a
1365
+ // terminal the store cannot read. The set is the protocol's own,
1366
+ // internal — a host cannot see or change it.
1367
+ const known = ERROR_CODES.has(e.code);
1348
1368
  return {
1349
- code: e.code,
1350
- ...(e.status !== undefined ? { status: e.status } : {}),
1369
+ code: known ? e.code : "unknown",
1370
+ ...(typeof e.status === "number" && Number.isSafeInteger(e.status) && e.status >= 0 ? { status: e.status } : {}),
1351
1371
  retryable: e.retryable,
1352
- message: typeof e.message === "string" ? e.message : String(err),
1372
+ message: `${known ? "" : `[${e.code}] `}${typeof e.message === "string" ? e.message : String(err)}`,
1353
1373
  // CX-1 F8: the provider's Retry-After rides through to the kernel
1354
1374
  ...(typeof e.retryAfterMs === "number" && Number.isFinite(e.retryAfterMs) && e.retryAfterMs >= 0 ? { retryAfterMs: e.retryAfterMs } : {}),
1355
1375
  };
@@ -206,6 +206,9 @@ export function projectMessages(events) {
206
206
  let resultBuf = [];
207
207
  const callOrder = new Map();
208
208
  let callOrderNext = 0;
209
+ // 0.43.0 (#13): the lexical arguments per open call — set on the first
210
+ // delta (presence, even empty), taken at the call's end.
211
+ const raw = new Map();
209
212
  // R-E 0.1.44 (sentence 1): the declarations that PROJECTED (their
210
213
  // tool_use blocks were actually pushed) — the pair-atomicity signal a
211
214
  // tool_result consults. A declaration the void skipped is not here, so
@@ -366,8 +369,10 @@ export function projectMessages(events) {
366
369
  case "tool_call_start":
367
370
  if (ev.source !== undefined)
368
371
  assistantSource = ev.source;
372
+ raw.delete(ev.callId);
369
373
  break;
370
374
  case "tool_call_input_delta":
375
+ raw.set(ev.callId, (raw.get(ev.callId) ?? "") + ev.inputJsonDelta);
371
376
  break; // the parsed input arrives at tool_call_end
372
377
  case "tool_call_end":
373
378
  // 0.1.26: a tool_call_end with BUFFERED RESULTS opens the
@@ -388,7 +393,9 @@ export function projectMessages(events) {
388
393
  callId: ev.callId,
389
394
  name: ev.name,
390
395
  input: ev.input ?? {},
396
+ ...(raw.has(ev.callId) ? { rawInput: raw.get(ev.callId) } : {}),
391
397
  });
398
+ raw.delete(ev.callId); // consumed by THIS invocation — a later same-callId call inherits nothing
392
399
  break;
393
400
  case "tool_result": {
394
401
  // R-E 0.1.44 (sentence 1): PAIR ATOMICITY — a result whose
@@ -597,6 +604,9 @@ export function messagesToEvents(messages) {
597
604
  name: block.name,
598
605
  ...(msg.source !== undefined ? { source: msg.source } : {}),
599
606
  });
607
+ // 0.43.0 (#13): a block with rawInput encodes ONE delta (even ""), so Message → Events → Message is lossless
608
+ if (block.rawInput !== undefined)
609
+ out.push({ type: "tool_call_input_delta", callId: block.callId, inputJsonDelta: block.rawInput });
600
610
  out.push({
601
611
  type: "tool_call_end",
602
612
  callId: block.callId,
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The durable protocol's error codes — ONE set, shared by the validator
3
+ * (isKisoEvent) and the kernel's normalizer (toStructuredError), so the
4
+ * kernel can never write a terminal the store will not read back.
5
+ *
6
+ * INTERNAL: not re-exported from `protocol/index.ts` or the package root,
7
+ * on purpose — a host must not be able to see, let alone mutate, the
8
+ * whitelist the two sides agree on. The public type is `ErrorCode`.
9
+ */
10
+ import type { ErrorCode } from "./events.js";
11
+ export declare const ERROR_CODES: ReadonlySet<ErrorCode>;
@@ -0,0 +1,11 @@
1
+ export const ERROR_CODES = new Set([
2
+ "rate_limit",
3
+ "overloaded",
4
+ "network",
5
+ "timeout",
6
+ "quota",
7
+ "api_5xx",
8
+ "context_overflow",
9
+ "invalid_request",
10
+ "unknown",
11
+ ]);
@@ -22,13 +22,6 @@
22
22
  * This module is almost types-only: the only runtime value it emits is
23
23
  * `isKisoEvent`, the type guard the session store validates records with.
24
24
  */
25
- /**
26
- * Why the model stopped producing. Provider-specific reasons are mapped at
27
- * the ADAPTER boundary into this closed union (Area 6) — `refusal`,
28
- * `pause_turn`, `content_filter`, and `context_window` are never allowed
29
- * to degrade into a normal `end_turn`. A new SDK enum that lacks a mapping
30
- * is a compile error in the adapters' exhaustive switches.
31
- */
32
25
  export type StopReason = "end_turn" | "tool_use" | "max_tokens" | "stop_sequence" | "abort" | "error" | "refusal" | "pause_turn" | "content_filter" | "context_window" | "function_call";
33
26
  /**
34
27
  * Structured failure classification, layered ON TOP OF `isError: boolean`.
@@ -22,6 +22,14 @@
22
22
  * This module is almost types-only: the only runtime value it emits is
23
23
  * `isKisoEvent`, the type guard the session store validates records with.
24
24
  */
25
+ /**
26
+ * Why the model stopped producing. Provider-specific reasons are mapped at
27
+ * the ADAPTER boundary into this closed union (Area 6) — `refusal`,
28
+ * `pause_turn`, `content_filter`, and `context_window` are never allowed
29
+ * to degrade into a normal `end_turn`. A new SDK enum that lacks a mapping
30
+ * is a compile error in the adapters' exhaustive switches.
31
+ */
32
+ import { ERROR_CODES } from "./error-codes.js";
25
33
  // ── deep-shape helpers (round 5): every variant is validated field by field —
26
34
  // legal enums, Terminal union members, Usage known/token combos, content
27
35
  // blocks, plain-object inputs, optional fields when present. A record that
@@ -45,17 +53,6 @@ const STOP_REASONS = new Set([
45
53
  "function_call",
46
54
  ]);
47
55
  const TOOL_ERROR_KINDS = new Set(["invalid_input", "precondition", "transient", "fatal"]);
48
- const ERROR_CODES = new Set([
49
- "rate_limit",
50
- "overloaded",
51
- "network",
52
- "timeout",
53
- "quota",
54
- "api_5xx",
55
- "context_overflow",
56
- "invalid_request",
57
- "unknown",
58
- ]);
59
56
  const MESSAGE_SOURCES = new Set([
60
57
  "user",
61
58
  "suggestion",
@@ -70,6 +70,15 @@ export interface AssistantToolUseBlock {
70
70
  readonly callId: string;
71
71
  readonly name: string;
72
72
  readonly input: Readonly<Record<string, unknown>>;
73
+ /** 0.43.0 (#13): the arguments exactly as the model streamed them — the
74
+ * LEXICAL companion of `input` (`5.0` stays `5.0`), present when the
75
+ * provider streamed deltas, absent otherwise. Carried for the tool
76
+ * and the host; NOT replayed to the provider (0430-B1: replaying it
77
+ * changed every call's bytes and the paired bench read more requests
78
+ * on that arm — the adapters replay the canonical form). Never a
79
+ * second argument channel: `input` stays the semantic basis of
80
+ * validation, permission and execution. */
81
+ readonly rawInput?: string;
73
82
  }
74
83
  export type AssistantBlock = AssistantTextBlock | AssistantToolUseBlock;
75
84
  export interface AssistantMessage {
@@ -38,6 +38,16 @@ export interface ToolContext {
38
38
  * tool runs with the exact call, even among same-named parallel calls. */
39
39
  readonly callId?: string;
40
40
  readonly executionId?: string;
41
+ /** 0.43.0 (#13): the invocation's arguments exactly as the model
42
+ * streamed them — the lexical companion of the parsed input the
43
+ * handler receives; absent when no delta was streamed. It is NOT a
44
+ * second argument channel: validation, permission and execution read
45
+ * the parsed input. ToolContext is derived from the durable invocation
46
+ * and execution, never from ephemeral process state: a fresh and a
47
+ * recovered execution receive the same invocation context (sessionId,
48
+ * callId, rawInput), and executionId is the id of the durable
49
+ * execution each path writes. */
50
+ readonly rawInput?: string;
41
51
  }
42
52
  /**
43
53
  * round 5(P1-9): a DISCRIMINATED union — `errorKind` is structurally
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-core",
3
- "version": "0.42.3",
3
+ "version": "0.44.0",
4
4
  "description": "kiso 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.42.3",
36
+ "@vincemakes/kiso-evals": "0.44.0",
37
37
  "@types/node": "^26.1.2",
38
38
  "typescript": "^5.7.2",
39
39
  "vitest": "^3.0.0"