@vincemakes/kiso-runtime 0.40.6 → 0.41.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/agent.d.ts CHANGED
@@ -80,6 +80,15 @@ export interface AgentDefinition {
80
80
  * before the request is aborted and retried (default 120 s; 0 off).
81
81
  * Type-only additive; read by the runtime's idle guard. */
82
82
  readonly streamIdleMs?: number;
83
+ /** R1 (2026-09-23): the tool table's vocabulary rows — the product's
84
+ * routing policy, one line per tool NAME, injected only while that tool
85
+ * is active. Absent = no vocabulary lines (the fixed directives and the
86
+ * tools' own snippets still compose). Type-only additive; never part of
87
+ * the profile digest. */
88
+ readonly toolRules?: ReadonlyArray<{
89
+ readonly tool: string;
90
+ readonly line: string;
91
+ }>;
83
92
  /** E1: loaded extensions — their tools merge into the registry (a name
84
93
  * collision with a built-in is a loud startup error), their hooks
85
94
  * compose after the agent's own (the existing come first), their approvals join the
package/dist/agent.js CHANGED
@@ -177,6 +177,7 @@ export class AgentRuntime {
177
177
  ...(this.#definition.contextPolicy !== undefined ? { contextPolicy: this.#definition.contextPolicy } : {}),
178
178
  ...(this.#definition.maxRetries !== undefined ? { maxRetries: this.#definition.maxRetries } : {}),
179
179
  ...(this.#definition.streamIdleMs !== undefined ? { streamIdleMs: this.#definition.streamIdleMs } : {}),
180
+ ...(this.#definition.toolRules !== undefined ? { toolRules: this.#definition.toolRules } : {}),
180
181
  ...(this.#definition.extensions !== undefined ? { extensions: this.#definition.extensions } : {}),
181
182
  };
182
183
  // 0.40.0: the last bill's time rides the RECORD, not the event.
package/dist/compose.d.ts CHANGED
@@ -5,13 +5,33 @@
5
5
  */
6
6
  import type { ApprovalChain, Event, HookHost, KisoExtension, ToolRegistry } from "@vincemakes/kiso-core";
7
7
  import type { SessionConfig } from "./session.js";
8
+ /**
9
+ * 0.1.40 (R-C item 1) — the tool substitution table: vocabulary rows, each
10
+ * bound to the tool that makes it true, filtered to the ACTIVE tool set +
11
+ * each active tool's ONE-line snippet + its guideline bullets. The full
12
+ * descriptions NEVER enter the system prompt — the provider transmits them
13
+ * in the JSON schema anyway (never pay twice). Deterministic: same registry
14
+ * + same rows → same table.
15
+ *
16
+ * R1 (2026-09-23): the ROWS are the product's (`AgentDefinition.toolRules`),
17
+ * not the runtime's. They were a constant here — the coding agent's routing
18
+ * policy, keyed by tool name — and every host whose tool happened to be
19
+ * called read_file received "never shell cat/head/tail" whether or not it
20
+ * had a shell. The runtime keeps the mechanism: the filter, the position,
21
+ * the joining. It knows no row. No rows → no vocabulary lines; the two
22
+ * fixed directives and the tools' own snippets remain.
23
+ */
24
+ export type ToolRules = ReadonlyArray<{
25
+ readonly tool: string;
26
+ readonly line: string;
27
+ }>;
8
28
  /** The base a run's system prompt is composed on: the session's prompt,
9
29
  * then the tool table (generated machinery sits BETWEEN the base and the
10
30
  * extension appends). One function, so an in-band summary outside a run
11
31
  * (ADR-0055 A2, /compact) sends the exact prefix a run sends. */
12
- export declare function runBasePrompt(systemPrompt: string | undefined, registry: ToolRegistry): string | undefined;
32
+ export declare function runBasePrompt(systemPrompt: string | undefined, registry: ToolRegistry, rules?: ToolRules): string | undefined;
13
33
  /** The table, or "" when the registry is empty (no vocabulary, no tools). */
14
- export declare function composeToolTable(registry: ToolRegistry): string;
34
+ export declare function composeToolTable(registry: ToolRegistry, rules?: ToolRules): string;
15
35
  /**
16
36
  * E2: the session's systemPrompt plus every extension's append, in LOAD
17
37
  * order, \n\n-joined — deterministic (same extension list → same prompt).
package/dist/compose.js CHANGED
@@ -3,38 +3,23 @@
3
3
  * from session.ts: the extension system-prompt appends, the extension
4
4
  * hook composition (the existing come first), and the loop's microcompact config lookup.
5
5
  */
6
- /**
7
- * 0.1.40 (R-C item 1) — the tool substitution table: the fixed vocabulary
8
- * (the reference implementation's content in kiso's voice, each line bound
9
- * to the tool that makes
10
- * it true) filtered to the ACTIVE tool set + each active tool's ONE-line
11
- * snippet + its guideline bullets. The full descriptions NEVER enter the
12
- * system prompt — the provider transmits them in the JSON schema anyway
13
- * (never pay twice). Deterministic: same registry → same table.
14
- */
15
- const TOOL_RULES = [
16
- { tool: "read_file", line: "read files with read_file, never shell cat/head/tail" },
17
- { tool: "search_text", line: "search with search_text, never shell grep/rg" },
18
- { tool: "list_dir", line: "list with list_dir, never ls" },
19
- { tool: "shell", line: "shell for what the file tools cannot do: commands, git, the network, the system" },
20
- ];
21
6
  /** The base a run's system prompt is composed on: the session's prompt,
22
7
  * then the tool table (generated machinery sits BETWEEN the base and the
23
8
  * extension appends). One function, so an in-band summary outside a run
24
9
  * (ADR-0055 A2, /compact) sends the exact prefix a run sends. */
25
- export function runBasePrompt(systemPrompt, registry) {
26
- const toolTable = composeToolTable(registry);
10
+ export function runBasePrompt(systemPrompt, registry, rules = []) {
11
+ const toolTable = composeToolTable(registry, rules);
27
12
  return toolTable === "" ? systemPrompt : systemPrompt === undefined ? toolTable : `${systemPrompt}\n\n${toolTable}`;
28
13
  }
29
14
  /** The table, or "" when the registry is empty (no vocabulary, no tools). */
30
- export function composeToolTable(registry) {
15
+ export function composeToolTable(registry, rules = []) {
31
16
  const tools = registry.list();
32
17
  if (tools.length === 0)
33
18
  return "";
34
19
  const active = new Set(tools.map((t) => t.name));
35
20
  const lines = [
36
21
  "Tool use:",
37
- ...TOOL_RULES.filter((r) => active.has(r.tool)).map((r) => `- ${r.line}`),
22
+ ...rules.filter((r) => active.has(r.tool)).map((r) => `- ${r.line}`),
38
23
  // the parallel directive: the window applies to every active turn.
39
24
  "- batch independent tool calls into one reply — they run in parallel",
40
25
  // D1: a tool result is evidence, not an answer — the turn ends with
package/dist/run.js CHANGED
@@ -88,7 +88,7 @@ export class Run {
88
88
  // session's base prompt and the extension appends: generated
89
89
  // machinery never outranks the deliberate extension text (the E2
90
90
  // "append lands at the END" contract holds). "" when empty.
91
- const basePrompt = runBasePrompt(this.#config.systemPrompt, this.#config.registry);
91
+ const basePrompt = runBasePrompt(this.#config.systemPrompt, this.#config.registry, this.#config.toolRules ?? []);
92
92
  // E3 — the ledger's parts: the base as CONFIGURED (what the CLI
93
93
  // handed the runtime — the tool table is generated machinery, R3)
94
94
  // and the extension appends in load order (R4 attribution). The
package/dist/session.d.ts CHANGED
@@ -554,6 +554,11 @@ export interface SessionConfig {
554
554
  readonly maxRetries?: number;
555
555
  /** LT-1: the stream watchdog's idle bound (ms); default 120 s, 0 off. */
556
556
  readonly streamIdleMs?: number;
557
+ /** R1: the tool table's vocabulary rows, the product's (see AgentDefinition). */
558
+ readonly toolRules?: ReadonlyArray<{
559
+ readonly tool: string;
560
+ readonly line: string;
561
+ }>;
557
562
  /**
558
563
  * E1: loaded extensions — their tools join the registry (idempotently;
559
564
  * a collision with a built-in name was already rejected at agent
package/dist/session.js CHANGED
@@ -842,7 +842,7 @@ export class AgentSession {
842
842
  // the one fallback. The budget is unchanged: measured for the
843
843
  // manual gesture, fixed for the policy (0.39.2).
844
844
  const cfg = this.#effectiveConfig();
845
- const systemPrompt = composeSystemPrompt(runBasePrompt(cfg.systemPrompt, cfg.registry), cfg.extensions ?? []);
845
+ const systemPrompt = composeSystemPrompt(runBasePrompt(cfg.systemPrompt, cfg.registry, cfg.toolRules ?? []), cfg.extensions ?? []);
846
846
  const wire = cfg.reasoning === undefined ? undefined : resolveReasoning(binding.model, cfg.reasoning, binding.baseUrl);
847
847
  const { result: call, path } = await this.#checkpointCall({
848
848
  messages: this.projected(),
@@ -23,6 +23,17 @@
23
23
  */
24
24
  import type { Adapter, AdapterEvent, Event, StreamOptions } from "@vincemakes/kiso-core";
25
25
  import { type RentParts } from "./rent.js";
26
+ import { type ProviderErrorNote } from "./record.js";
27
+ /** SMK0400-F1: what a provider error is allowed to leave in the ledger — a
28
+ * message capped, and with anything credential-shaped removed first: an
29
+ * `sk-`/`Bearer` token, an `api_key=`/`token:` value, and any unbroken run
30
+ * of 32+ key characters (a provider that echoes a masked or whole key
31
+ * back in its refusal must not get it written to disk). */
32
+ export declare function redactProviderMessage(message: string): string;
33
+ /** SMK0400-F1: the note a `provider_error` record carries. The adapters
34
+ * throw the structured error (code, message, status when there was one);
35
+ * anything else thrown is named by its error name. */
36
+ export declare function providerErrorNote(err: unknown): ProviderErrorNote;
26
37
  export interface RequestTracerDeps {
27
38
  root: string;
28
39
  sessionId: string;
@@ -27,7 +27,29 @@ import { cacheableHashes } from "./analyze.js";
27
27
  import { hashContext, hashSystemPrompt, hashToolSpecs, stablePrefixFingerprint } from "./hash.js";
28
28
  import { PRICING_TABLE_V1, canonicalizeUsageForModel } from "../usage/canonical.js";
29
29
  import { buildRentLedger } from "./rent.js";
30
- import { TRACE_SCHEMA_VERSION } from "./record.js";
30
+ import { PROVIDER_ERROR_MESSAGE_MAX, TRACE_SCHEMA_VERSION } from "./record.js";
31
+ /** SMK0400-F1: what a provider error is allowed to leave in the ledger — a
32
+ * message capped, and with anything credential-shaped removed first: an
33
+ * `sk-`/`Bearer` token, an `api_key=`/`token:` value, and any unbroken run
34
+ * of 32+ key characters (a provider that echoes a masked or whole key
35
+ * back in its refusal must not get it written to disk). */
36
+ export function redactProviderMessage(message) {
37
+ return message
38
+ .replace(/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/gi, "$1 <redacted>")
39
+ .replace(/\b(api[_-]?key|access[_-]?token|token|secret|password|authorization)(["']?\s*[:=]\s*["']?)[^\s"',;]+/gi, "$1$2<redacted>")
40
+ .replace(/\bsk-[A-Za-z0-9_-]{6,}/g, "sk-<redacted>")
41
+ .replace(/[A-Za-z0-9_-]{32,}/g, "<redacted>")
42
+ .slice(0, PROVIDER_ERROR_MESSAGE_MAX);
43
+ }
44
+ /** SMK0400-F1: the note a `provider_error` record carries. The adapters
45
+ * throw the structured error (code, message, status when there was one);
46
+ * anything else thrown is named by its error name. */
47
+ export function providerErrorNote(err) {
48
+ const e = (typeof err === "object" && err !== null ? err : {});
49
+ const code = typeof e.code === "string" && e.code !== "" ? e.code : typeof e.name === "string" && e.name !== "" ? e.name : "error";
50
+ const message = typeof e.message === "string" ? e.message : String(err);
51
+ return { code, ...(typeof e.status === "number" && Number.isInteger(e.status) ? { status: e.status } : {}), message: redactProviderMessage(message) };
52
+ }
31
53
  import { TraceWriter } from "./writer.js";
32
54
  export class RequestTracer {
33
55
  #writer;
@@ -81,6 +103,8 @@ export class RequestTracer {
81
103
  let reasoningTokens;
82
104
  const toolCalls = [];
83
105
  let outcome = "ok";
106
+ // SMK0400-F1: what the provider said, when the outcome is its error
107
+ let providerError;
84
108
  try {
85
109
  for await (const ev of upstream) {
86
110
  // the null check must be the ONLY guard — a 0-initialized
@@ -109,6 +133,8 @@ export class RequestTracer {
109
133
  }
110
134
  catch (err) {
111
135
  outcome = this.#classifyOutcome(err, options);
136
+ if (outcome === "provider_error")
137
+ providerError = providerErrorNote(err);
112
138
  throw err;
113
139
  }
114
140
  finally {
@@ -125,6 +151,7 @@ export class RequestTracer {
125
151
  usageKnown,
126
152
  servedModel,
127
153
  reasoningTokens,
154
+ providerError,
128
155
  });
129
156
  }
130
157
  }
@@ -239,6 +266,9 @@ export class RequestTracer {
239
266
  // the agreement the reconciliation exists to test.
240
267
  if (p.servedModel !== null)
241
268
  record.servedModel = p.servedModel;
269
+ // SMK0400-F1: on a provider_error record only; absent otherwise
270
+ if (p.providerError !== undefined)
271
+ record.providerError = p.providerError;
242
272
  if (p.usageKnown) {
243
273
  record.freshInput =
244
274
  this.#provider === "anthropic"
@@ -68,13 +68,22 @@
68
68
  * as requested". That default is the whole defect: a field filled in with
69
69
  * the request manufactures the agreement the reconciliation exists to
70
70
  * test.
71
+ *
72
+ * SMK0400-F1 (0.40.7) — schemaVersion 7: a `provider_error` record gains
73
+ * `providerError` — the error's code, the HTTP status when there was one,
74
+ * and the provider's message (capped, and redacted of anything
75
+ * credential-shaped). Until now the record said only `provider_error`, so
76
+ * a failed request could not be named after the fact (the 0.40.0 smoke's
77
+ * two failures). OPTIONAL: absent on every other outcome, and on every
78
+ * record a v1–v6 writer produced.
71
79
  */
72
- /** schemaVersion: 6 for the `servedModel` statement (TRACE-F1);
80
+ /** schemaVersion: 7 for the `providerError` statement (SMK0400-F1);
81
+ * 6 was the `servedModel` statement (TRACE-F1);
73
82
  * 5 was the `usageKnown` marker (F33-1). Version
74
83
  * 1 = the 1.2.0 shape, 2 = the 1.3.0 shape, 3 = the 0.2.1 shape; all
75
84
  * kept for generation-compat reads (R1d-1, R2-1). Algorithm and shape
76
85
  * changes bump it (ADR-0051 §6 OUT-side versioning). */
77
- export declare const TRACE_SCHEMA_VERSION = 6;
86
+ export declare const TRACE_SCHEMA_VERSION = 7;
78
87
  /** The versions a reader may meet in a ledger. v1 and v2 records are
79
88
  * accepted (generation-compat) and read as defaults — no canonical
80
89
  * block (v1), no rent block (v1, v2). */
@@ -111,7 +120,7 @@ export interface TraceSegment {
111
120
  }
112
121
  /** That is the complete set for 1.2.0. */
113
122
  export interface TraceRecord {
114
- schemaVersion: 6;
123
+ schemaVersion: 7;
115
124
  kind: "request";
116
125
  requestId: string;
117
126
  runId: string;
@@ -178,25 +187,29 @@ export interface TraceRecord {
178
187
  * four days. The requested id stays in `model`; these are two facts,
179
188
  * never merged. */
180
189
  servedModel?: string;
190
+ /** SMK0400-F1 (v7) — on a `provider_error` record: what the provider
191
+ * said. `message` is capped at PROVIDER_ERROR_MESSAGE_MAX characters and
192
+ * redacted of credential-shaped strings before it is written. */
193
+ providerError?: ProviderErrorNote;
181
194
  ts: number;
182
195
  }
183
196
  /** That is the complete set for 1.2.0. */
184
197
  export interface HeaderLine {
185
- schemaVersion: 6;
198
+ schemaVersion: 7;
186
199
  kind: "header";
187
200
  sessionId: string;
188
201
  kisoVersion: string;
189
202
  createdAt: number;
190
203
  }
191
204
  export interface RunEndLine {
192
- schemaVersion: 6;
205
+ schemaVersion: 7;
193
206
  kind: "run_end";
194
207
  runId: string;
195
208
  ts: number;
196
209
  lastRequestIndex: number;
197
210
  }
198
211
  export interface CrashLine {
199
- schemaVersion: 6;
212
+ schemaVersion: 7;
200
213
  kind: "crash";
201
214
  ts: number;
202
215
  note: string;
@@ -229,9 +242,21 @@ export declare const TRACE_RECORD_FIELDS_V5: readonly ["schemaVersion", "kind",
229
242
  /** TRACE-F1 (v6): the model the SERVER said it served. A v5 ledger has none
230
243
  * on any record, which reads as "nobody asked the server" — the true
231
244
  * statement about a ledger written before the adapters read the field. */
232
- export declare const TRACE_RECORD_FIELDS: readonly ["schemaVersion", "kind", "requestId", "runId", "requestIndex", "retryAttempt", "provider", "model", "adapterVersion", "systemPromptHash", "toolSchemaHash", "contextHash", "contextManifest", "segmentHashes", "stablePrefixFingerprint", "freshInput", "cacheRead", "cacheWrite", "output", "latencyMs", "ttftMs", "toolCalls", "outcome", "lineageLink", "ts", "canonical", "rent", "purpose", "usageKnown", "servedModel"];
245
+ export declare const TRACE_RECORD_FIELDS_V6: readonly ["schemaVersion", "kind", "requestId", "runId", "requestIndex", "retryAttempt", "provider", "model", "adapterVersion", "systemPromptHash", "toolSchemaHash", "contextHash", "contextManifest", "segmentHashes", "stablePrefixFingerprint", "freshInput", "cacheRead", "cacheWrite", "output", "latencyMs", "ttftMs", "toolCalls", "outcome", "lineageLink", "ts", "canonical", "rent", "purpose", "usageKnown", "servedModel"];
246
+ /** SMK0400-F1 (v7): what the provider said on a `provider_error` record. */
247
+ export declare const TRACE_RECORD_FIELDS: readonly ["schemaVersion", "kind", "requestId", "runId", "requestIndex", "retryAttempt", "provider", "model", "adapterVersion", "systemPromptHash", "toolSchemaHash", "contextHash", "contextManifest", "segmentHashes", "stablePrefixFingerprint", "freshInput", "cacheRead", "cacheWrite", "output", "latencyMs", "ttftMs", "toolCalls", "outcome", "lineageLink", "ts", "canonical", "rent", "purpose", "usageKnown", "servedModel", "providerError"];
233
248
  /** The fields the closed-set check does not require to be present. */
234
- export declare const TRACE_RECORD_OPTIONAL: readonly ["purpose", "usageKnown", "servedModel"];
249
+ export declare const TRACE_RECORD_OPTIONAL: readonly ["purpose", "usageKnown", "servedModel", "providerError"];
250
+ /** SMK0400-F1: the shape of `providerError`. */
251
+ export interface ProviderErrorNote {
252
+ /** the structured error's code (`rate_limit`, `network`, …), or the
253
+ * thrown error's name when it carried none */
254
+ code: string;
255
+ /** the HTTP status, when the failure had one */
256
+ status?: number;
257
+ message: string;
258
+ }
259
+ export declare const PROVIDER_ERROR_MESSAGE_MAX = 300;
235
260
  export declare const TRACE_SEGMENT_FIELDS: readonly ["role", "seqRange", "estTokens", "freshness"];
236
261
  export declare function validateTraceSegment(v: unknown): v is TraceSegment;
237
262
  export declare function validateTraceRecord(v: unknown): v is TraceRecord;
@@ -68,13 +68,22 @@
68
68
  * as requested". That default is the whole defect: a field filled in with
69
69
  * the request manufactures the agreement the reconciliation exists to
70
70
  * test.
71
+ *
72
+ * SMK0400-F1 (0.40.7) — schemaVersion 7: a `provider_error` record gains
73
+ * `providerError` — the error's code, the HTTP status when there was one,
74
+ * and the provider's message (capped, and redacted of anything
75
+ * credential-shaped). Until now the record said only `provider_error`, so
76
+ * a failed request could not be named after the fact (the 0.40.0 smoke's
77
+ * two failures). OPTIONAL: absent on every other outcome, and on every
78
+ * record a v1–v6 writer produced.
71
79
  */
72
- /** schemaVersion: 6 for the `servedModel` statement (TRACE-F1);
80
+ /** schemaVersion: 7 for the `providerError` statement (SMK0400-F1);
81
+ * 6 was the `servedModel` statement (TRACE-F1);
73
82
  * 5 was the `usageKnown` marker (F33-1). Version
74
83
  * 1 = the 1.2.0 shape, 2 = the 1.3.0 shape, 3 = the 0.2.1 shape; all
75
84
  * kept for generation-compat reads (R1d-1, R2-1). Algorithm and shape
76
85
  * changes bump it (ADR-0051 §6 OUT-side versioning). */
77
- export const TRACE_SCHEMA_VERSION = 6;
86
+ export const TRACE_SCHEMA_VERSION = 7;
78
87
  /** The versions a reader may meet in a ledger. v1 and v2 records are
79
88
  * accepted (generation-compat) and read as defaults — no canonical
80
89
  * block (v1), no rent block (v1, v2). */
@@ -93,7 +102,7 @@ export const TRACE_SCHEMA_VERSION = 6;
93
102
  * spec. Three lists to extend is the point: a bump that forgets one is a
94
103
  * bump that fails loudly rather than a reader that quietly narrows.
95
104
  */
96
- export const TRACE_SCHEMA_VERSIONS = new Set([1, 2, 3, 4, 5, 6]);
105
+ export const TRACE_SCHEMA_VERSIONS = new Set([1, 2, 3, 4, 5, 6, 7]);
97
106
  import { PRICING_TABLE_V1, priceFor, pricingTableFor, validateCanonicalUsage } from "../usage/canonical.js";
98
107
  import { validateRentLine } from "./rent.js";
99
108
  export const HASH_SPEC_BY_VERSION = {
@@ -103,6 +112,7 @@ export const HASH_SPEC_BY_VERSION = {
103
112
  4: { algorithm: "sha-256", output: "full-hex" }, // TUI2-R3v2 — `purpose` is a marker, not an input to any hash; re-pinned by the same ritual
104
113
  5: { algorithm: "sha-256", output: "full-hex" }, // F33-1 — `usageKnown` is a marker, not an input to any hash; re-pinned by the same ritual
105
114
  6: { algorithm: "sha-256", output: "full-hex" }, // TRACE-F1 — `servedModel` is the server's statement, not an input to any hash; re-pinned by the same ritual
115
+ 7: { algorithm: "sha-256", output: "full-hex" }, // SMK0400-F1 — `providerError` is the provider's statement, not an input to any hash; re-pinned by the same ritual
106
116
  };
107
117
  export function hashSpecFor(version) {
108
118
  const spec = HASH_SPEC_BY_VERSION[version];
@@ -160,9 +170,12 @@ export const TRACE_RECORD_FIELDS_V5 = [...TRACE_RECORD_FIELDS_V4, "usageKnown"];
160
170
  /** TRACE-F1 (v6): the model the SERVER said it served. A v5 ledger has none
161
171
  * on any record, which reads as "nobody asked the server" — the true
162
172
  * statement about a ledger written before the adapters read the field. */
163
- export const TRACE_RECORD_FIELDS = [...TRACE_RECORD_FIELDS_V5, "servedModel"];
173
+ export const TRACE_RECORD_FIELDS_V6 = [...TRACE_RECORD_FIELDS_V5, "servedModel"];
174
+ /** SMK0400-F1 (v7): what the provider said on a `provider_error` record. */
175
+ export const TRACE_RECORD_FIELDS = [...TRACE_RECORD_FIELDS_V6, "providerError"];
164
176
  /** The fields the closed-set check does not require to be present. */
165
- export const TRACE_RECORD_OPTIONAL = ["purpose", "usageKnown", "servedModel"];
177
+ export const TRACE_RECORD_OPTIONAL = ["purpose", "usageKnown", "servedModel", "providerError"];
178
+ export const PROVIDER_ERROR_MESSAGE_MAX = 300;
166
179
  export const TRACE_SEGMENT_FIELDS = ["role", "seqRange", "estTokens", "freshness"];
167
180
  // ── Validators ────────────────────────────────────────────────────────────
168
181
  // Strict by design: extra keys are rejected (the closed set), so a
@@ -232,7 +245,9 @@ export function validateTraceRecord(v) {
232
245
  ? TRACE_RECORD_FIELDS_V4
233
246
  : version === 5
234
247
  ? TRACE_RECORD_FIELDS_V5
235
- : TRACE_RECORD_FIELDS;
248
+ : version === 6
249
+ ? TRACE_RECORD_FIELDS_V6
250
+ : TRACE_RECORD_FIELDS;
236
251
  if (!hasClosedKeys(v, fields, ["lineageLink", ...TRACE_RECORD_OPTIONAL]))
237
252
  return false;
238
253
  if (v.purpose !== undefined && (typeof v.purpose !== "string" || v.purpose === ""))
@@ -248,6 +263,17 @@ export function validateTraceRecord(v) {
248
263
  // honest "the server said nothing".
249
264
  if (v.servedModel !== undefined && (typeof v.servedModel !== "string" || v.servedModel === ""))
250
265
  return false;
266
+ // SMK0400-F1: closed keys; a non-empty code; an integer status when
267
+ // stated; the message a string within the cap (the writer caps it)
268
+ if (v.providerError !== undefined) {
269
+ const e = v.providerError;
270
+ if (!isRecord(e) || !hasClosedKeys(e, ["code", "message", "status"], ["status"]))
271
+ return false;
272
+ if (typeof e.code !== "string" || e.code === "" || typeof e.message !== "string" || e.message.length > PROVIDER_ERROR_MESSAGE_MAX)
273
+ return false;
274
+ if (e.status !== undefined && !Number.isInteger(e.status))
275
+ return false;
276
+ }
251
277
  if (v.kind !== "request")
252
278
  return false;
253
279
  if (typeof v.requestId !== "string" || typeof v.runId !== "string")
@@ -318,7 +344,9 @@ export function validateTraceRecord(v) {
318
344
  return false;
319
345
  }
320
346
  }
321
- if (version === TRACE_SCHEMA_VERSION) {
347
+ // SMK0400-F1: `>= 6`, not `=== TRACE_SCHEMA_VERSION` — the bump to 7
348
+ // must not stop checking the rent lines of the v6 records already written
349
+ if (version >= 6) {
322
350
  // the rent block: every line validates (closed fields, non-empty
323
351
  // surface, non-negative integer chars, the estTokens == ceil(chars/4)
324
352
  // cross-check — R6). v1/v2 sidecars have no block (R2-1).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-runtime",
3
- "version": "0.40.6",
3
+ "version": "0.41.0",
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.40.6"
28
+ "@vincemakes/kiso-core": "0.41.0"
29
29
  },
30
30
  "peerDependencies": {
31
- "@vincemakes/kiso-provider-anthropic": "0.40.6",
32
- "@vincemakes/kiso-provider-openai": "0.40.6",
33
- "@vincemakes/kiso-provider-openai-responses": "0.40.6"
31
+ "@vincemakes/kiso-provider-anthropic": "0.41.0",
32
+ "@vincemakes/kiso-provider-openai": "0.41.0",
33
+ "@vincemakes/kiso-provider-openai-responses": "0.41.0"
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.40.6",
47
+ "@vincemakes/kiso-evals": "0.41.0",
48
48
  "@types/node": "^26.1.2",
49
49
  "typescript": "^5.7.2",
50
50
  "vitest": "^3.0.0"