@vincemakes/kiso-runtime 0.2.0 → 0.2.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/run.js CHANGED
@@ -57,6 +57,29 @@ export class Run {
57
57
  this.#session.ensureHealthy();
58
58
  this.#session.beginRun(this);
59
59
  const log = this.#session.log;
60
+ // The static prompt parts — computed ONCE, before the tracer, and
61
+ // reused for both the composed string (below) and the rent ledger
62
+ // (E3): a single evaluation keeps the model-visible byte stream
63
+ // byte-identical to the pre-E3 run (I6, trace-bytes.test.ts).
64
+ // 0.1.40 (R-C item 1): the tool substitution table — the ACTIVE tool
65
+ // set's vocabulary, snippets, and guidelines — sits BETWEEN the
66
+ // session's base prompt and the extension appends: generated
67
+ // machinery never outranks the deliberate extension text (the E2
68
+ // "append lands at the END" contract holds). "" when empty.
69
+ const toolTable = composeToolTable(this.#config.registry);
70
+ const basePrompt = toolTable === "" ? this.#config.systemPrompt
71
+ : this.#config.systemPrompt === undefined ? toolTable
72
+ : `${this.#config.systemPrompt}\n\n${toolTable}`;
73
+ // E3 — the ledger's parts: the base as CONFIGURED (what the CLI
74
+ // handed the runtime — the tool table is generated machinery, R3)
75
+ // and the extension appends in load order (R4 attribution). The
76
+ // composed string below is their result; the ledger counts the
77
+ // parts, observation-only. exactOptionalPropertyTypes: an absent
78
+ // surface is an absent key — never an explicit undefined (R9).
79
+ const rentParts = {
80
+ ...(this.#config.systemPrompt !== undefined ? { base: this.#config.systemPrompt } : {}),
81
+ appends: (this.#config.extensions ?? []).flatMap((e) => e.systemPrompt?.append === undefined ? [] : [{ name: e.name, text: e.systemPrompt.append }]),
82
+ };
60
83
  // E1 (1.2.0): the request tracer — the observation ledger. It
61
84
  // sits at the adapter boundary; the model-visible byte stream is
62
85
  // untouched (I6, trace-bytes.test.ts). Soft-fail: a degraded
@@ -69,21 +92,13 @@ export class Run {
69
92
  model: this.#config.model,
70
93
  adapterVersion: runtimeVersion(),
71
94
  log: log.all,
95
+ rentParts,
72
96
  });
73
97
  tracer.init();
74
98
  const signal = this.#externalSignal ? new MergedSignal(this.#abort.signal, this.#externalSignal) : this.#abort.signal;
75
99
  // E2: the session's own microcompact wins; otherwise the FIRST
76
100
  // extension providing a compaction config supplies it.
77
101
  const microcompact = microcompactFor(this.#config);
78
- // 0.1.40 (R-C item 1): the tool substitution table — the ACTIVE tool
79
- // set's vocabulary, snippets, and guidelines — sits BETWEEN the
80
- // session's base prompt and the extension appends: generated
81
- // machinery never outranks the deliberate extension text (the E2
82
- // "append lands at the END" contract holds). "" when empty.
83
- const toolTable = composeToolTable(this.#config.registry);
84
- const basePrompt = toolTable === "" ? this.#config.systemPrompt
85
- : this.#config.systemPrompt === undefined ? toolTable
86
- : `${this.#config.systemPrompt}\n\n${toolTable}`;
87
102
  // E2: the session's own systemPrompt first, then every extension
88
103
  // append in LOAD order — deterministic (same extensions → same
89
104
  // prompt); no appends → byte-identical to the extension-less run.
@@ -22,6 +22,7 @@
22
22
  * the honest nullable quartet is a schema bump, deferred.
23
23
  */
24
24
  import type { Adapter, AdapterEvent, Event, StreamOptions } from "@vincemakes/kiso-core";
25
+ import { type RentParts } from "./rent.js";
25
26
  export interface RequestTracerDeps {
26
27
  root: string;
27
28
  sessionId: string;
@@ -33,6 +34,11 @@ export interface RequestTracerDeps {
33
34
  adapterVersion?: string | null;
34
35
  /** The session log — the manifest's seqRange pointers derive from it. */
35
36
  log: readonly Event[];
37
+ /** E3 — the rent ledger's inputs: the base prompt as configured and
38
+ * the per-extension appends in load order (the adapter's composed
39
+ * systemPrompt is their RESULT — the parts are what the ledger
40
+ * counts; the composed string itself is unchanged, I6). */
41
+ rentParts?: RentParts;
36
42
  }
37
43
  export declare class RequestTracer {
38
44
  #private;
@@ -26,6 +26,7 @@ import { buildContextManifest, segmentHashes } from "./manifest.js";
26
26
  import { cacheableHashes } from "./analyze.js";
27
27
  import { hashContext, hashSystemPrompt, hashToolSpecs, stablePrefixFingerprint } from "./hash.js";
28
28
  import { PRICING_TABLE_V1, canonicalizeUsage } from "../usage/canonical.js";
29
+ import { buildRentLedger } from "./rent.js";
29
30
  import { TRACE_SCHEMA_VERSION } from "./record.js";
30
31
  import { TraceWriter } from "./writer.js";
31
32
  export class RequestTracer {
@@ -34,6 +35,7 @@ export class RequestTracer {
34
35
  #provider;
35
36
  #runId;
36
37
  #adapterVersion;
38
+ #rentParts;
37
39
  #requestIndex = 0;
38
40
  #contextHashCounts = new Map();
39
41
  constructor(deps) {
@@ -42,6 +44,7 @@ export class RequestTracer {
42
44
  this.#provider = deps.provider;
43
45
  this.#runId = deps.runId;
44
46
  this.#adapterVersion = deps.adapterVersion ?? null;
47
+ this.#rentParts = deps.rentParts;
45
48
  }
46
49
  init() {
47
50
  this.#writer.init();
@@ -119,6 +122,18 @@ export class RequestTracer {
119
122
  this.#contextHashCounts.set(contextHash, retryAttempt + 1);
120
123
  const manifest = buildContextManifest({ log: this.#log, systemPrompt, tools, messages });
121
124
  const hashes = segmentHashes(systemPrompt, tools, messages);
125
+ // E3 — the static rent ledger, one line per surface; the envelope
126
+ // derives from the request the guard already has (R5), the base and
127
+ // the appends from the threaded parts (R3/R4 — observation only).
128
+ // exactOptionalPropertyTypes: an absent surface is an absent key —
129
+ // never an explicit undefined (R9).
130
+ const rentParts = this.#rentParts;
131
+ const rent = buildRentLedger({
132
+ model: options.model,
133
+ ...(rentParts?.base !== undefined ? { base: rentParts.base } : {}),
134
+ ...(rentParts?.appends !== undefined ? { appends: rentParts.appends } : {}),
135
+ ...(tools !== undefined ? { tools } : {}),
136
+ });
122
137
  return {
123
138
  schemaVersion: TRACE_SCHEMA_VERSION,
124
139
  kind: "request",
@@ -156,6 +171,7 @@ export class RequestTracer {
156
171
  pricingTableId: PRICING_TABLE_V1.id,
157
172
  pricingTableVersion: PRICING_TABLE_V1.version,
158
173
  },
174
+ rent,
159
175
  latencyMs: 0,
160
176
  ttftMs: 0,
161
177
  toolCalls: [],
@@ -19,15 +19,24 @@
19
19
  * quartet stays as provider observation above it). The validators accept
20
20
  * BOTH generations (R1d-1): a v1 sidecar has no canonical block and reads
21
21
  * as defaults at every consumer — never a crash.
22
+ *
23
+ * E3 (0.2.1) — schemaVersion 3: the record gains the `rent` block — the
24
+ * static rent ledger, one line per surface (trace/rent.ts). The v3
25
+ * writers record it; v1/v2 sidecars keep reading as defaults (R1d-1,
26
+ * R2-1): no rent block = no rent lines = the zero-rent reading, never a
27
+ * crash.
22
28
  */
23
- /** schemaVersion: 2 for 1.3.0 (the canonical block). Version 1 = the 1.2.0
24
- * shape, kept for generation-compat reads (R1d-1). Algorithm and shape
25
- * changes bump it (ADR-0051 §6 OUT-side versioning). */
26
- export declare const TRACE_SCHEMA_VERSION = 2;
27
- /** The versions a reader may meet in a ledger. v1 records are accepted
28
- * (generation-compat, R1d-1) and read as defaults no canonical block. */
29
+ /** schemaVersion: 3 for 0.2.1 (the rent block). Version 1 = the 1.2.0
30
+ * shape, version 2 = the 1.3.0 shape; both kept for generation-compat
31
+ * reads (R1d-1, R2-1). Algorithm and shape changes bump it (ADR-0051 §6
32
+ * OUT-side versioning). */
33
+ export declare const TRACE_SCHEMA_VERSION = 3;
34
+ /** The versions a reader may meet in a ledger. v1 and v2 records are
35
+ * accepted (generation-compat) and read as defaults — no canonical
36
+ * block (v1), no rent block (v1, v2). */
29
37
  export declare const TRACE_SCHEMA_VERSIONS: Readonly<Set<number>>;
30
38
  import type { CanonicalUsage } from "../usage/canonical.js";
39
+ import { type RentLine } from "./rent.js";
31
40
  export type Freshness = "fresh" | "cache_read" | "cache_write";
32
41
  /** That is the complete set for 1.2.0. */
33
42
  export type Outcome = "ok" | "provider_error" | "aborted";
@@ -43,7 +52,7 @@ export interface TraceSegment {
43
52
  }
44
53
  /** That is the complete set for 1.2.0. */
45
54
  export interface TraceRecord {
46
- schemaVersion: 2;
55
+ schemaVersion: 3;
47
56
  kind: "request";
48
57
  requestId: string;
49
58
  runId: string;
@@ -77,6 +86,11 @@ export interface TraceRecord {
77
86
  * the validator pins the equality — and carries the cost from the
78
87
  * versioned pricing table (every cost records its table version). */
79
88
  canonical: CanonicalUsage;
89
+ /** E3 — the static rent ledger: one line per surface (system:base,
90
+ * system:ext:<name>, tool:<name>, envelope), counts never payloads —
91
+ * see trace/rent.ts. v3 requires the block; a v1/v2 sidecar has none
92
+ * and reads as the zero-rent ledger (R2-1). */
93
+ rent: RentLine[];
80
94
  latencyMs: number;
81
95
  ttftMs: number;
82
96
  toolCalls: string[];
@@ -91,21 +105,21 @@ export interface TraceRecord {
91
105
  }
92
106
  /** That is the complete set for 1.2.0. */
93
107
  export interface HeaderLine {
94
- schemaVersion: 2;
108
+ schemaVersion: 3;
95
109
  kind: "header";
96
110
  sessionId: string;
97
111
  kisoVersion: string;
98
112
  createdAt: number;
99
113
  }
100
114
  export interface RunEndLine {
101
- schemaVersion: 2;
115
+ schemaVersion: 3;
102
116
  kind: "run_end";
103
117
  runId: string;
104
118
  ts: number;
105
119
  lastRequestIndex: number;
106
120
  }
107
121
  export interface CrashLine {
108
- schemaVersion: 2;
122
+ schemaVersion: 3;
109
123
  kind: "crash";
110
124
  ts: number;
111
125
  note: string;
@@ -122,7 +136,9 @@ export declare function hashSpecFor(version: number): HashSpec;
122
136
  * canonical block and reads as defaults at every consumer. */
123
137
  export declare const TRACE_RECORD_FIELDS_V1: 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"];
124
138
  /** The 1.3.0 field set (schemaVersion 2) = the v1 set + `canonical`. */
125
- 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"];
139
+ export declare const TRACE_RECORD_FIELDS_V2: 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"];
140
+ /** The 0.2.1 field set (schemaVersion 3) = the v2 set + `rent`. */
141
+ 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"];
126
142
  export declare const TRACE_SEGMENT_FIELDS: readonly ["role", "seqRange", "estTokens", "freshness"];
127
143
  export declare function validateTraceSegment(v: unknown): v is TraceSegment;
128
144
  export declare function validateTraceRecord(v: unknown): v is TraceRecord;
@@ -19,18 +19,28 @@
19
19
  * quartet stays as provider observation above it). The validators accept
20
20
  * BOTH generations (R1d-1): a v1 sidecar has no canonical block and reads
21
21
  * as defaults at every consumer — never a crash.
22
+ *
23
+ * E3 (0.2.1) — schemaVersion 3: the record gains the `rent` block — the
24
+ * static rent ledger, one line per surface (trace/rent.ts). The v3
25
+ * writers record it; v1/v2 sidecars keep reading as defaults (R1d-1,
26
+ * R2-1): no rent block = no rent lines = the zero-rent reading, never a
27
+ * crash.
22
28
  */
23
- /** schemaVersion: 2 for 1.3.0 (the canonical block). Version 1 = the 1.2.0
24
- * shape, kept for generation-compat reads (R1d-1). Algorithm and shape
25
- * changes bump it (ADR-0051 §6 OUT-side versioning). */
26
- export const TRACE_SCHEMA_VERSION = 2;
27
- /** The versions a reader may meet in a ledger. v1 records are accepted
28
- * (generation-compat, R1d-1) and read as defaults no canonical block. */
29
- export const TRACE_SCHEMA_VERSIONS = new Set([1, TRACE_SCHEMA_VERSION]);
29
+ /** schemaVersion: 3 for 0.2.1 (the rent block). Version 1 = the 1.2.0
30
+ * shape, version 2 = the 1.3.0 shape; both kept for generation-compat
31
+ * reads (R1d-1, R2-1). Algorithm and shape changes bump it (ADR-0051 §6
32
+ * OUT-side versioning). */
33
+ export const TRACE_SCHEMA_VERSION = 3;
34
+ /** The versions a reader may meet in a ledger. v1 and v2 records are
35
+ * accepted (generation-compat) and read as defaults — no canonical
36
+ * block (v1), no rent block (v1, v2). */
37
+ export const TRACE_SCHEMA_VERSIONS = new Set([1, 2, TRACE_SCHEMA_VERSION]);
30
38
  import { PRICING_TABLE_V1, priceFor, pricingTableFor, validateCanonicalUsage } from "../usage/canonical.js";
39
+ import { validateRentLine } from "./rent.js";
31
40
  export const HASH_SPEC_BY_VERSION = {
32
41
  1: { algorithm: "sha-256", output: "full-hex" },
33
42
  2: { algorithm: "sha-256", output: "full-hex" }, // E2 — the algorithms do not change
43
+ 3: { algorithm: "sha-256", output: "full-hex" }, // E3 — same algorithms, re-pinned (the E2 ritual)
34
44
  };
35
45
  export function hashSpecFor(version) {
36
46
  const spec = HASH_SPEC_BY_VERSION[version];
@@ -72,7 +82,9 @@ export const TRACE_RECORD_FIELDS_V1 = [
72
82
  "ts",
73
83
  ];
74
84
  /** The 1.3.0 field set (schemaVersion 2) = the v1 set + `canonical`. */
75
- export const TRACE_RECORD_FIELDS = [...TRACE_RECORD_FIELDS_V1, "canonical"];
85
+ export const TRACE_RECORD_FIELDS_V2 = [...TRACE_RECORD_FIELDS_V1, "canonical"];
86
+ /** The 0.2.1 field set (schemaVersion 3) = the v2 set + `rent`. */
87
+ export const TRACE_RECORD_FIELDS = [...TRACE_RECORD_FIELDS_V2, "rent"];
76
88
  export const TRACE_SEGMENT_FIELDS = ["role", "seqRange", "estTokens", "freshness"];
77
89
  // ── Validators ────────────────────────────────────────────────────────────
78
90
  // Strict by design: extra keys are rejected (the closed set), so a
@@ -119,12 +131,13 @@ export function validateTraceRecord(v) {
119
131
  if (!isRecord(v))
120
132
  return false;
121
133
  const version = v.schemaVersion;
122
- // generation-compat (R1d-1): a v1 sidecar has no canonical block and
123
- // reads as defaults accepted, never a crash; the current version is
124
- // fully checked (shape + the canonical block + its consistency).
125
- if (version !== 1 && version !== TRACE_SCHEMA_VERSION)
134
+ // generation-compat (R1d-1, R2-1): a v1 sidecar has no canonical
135
+ // block, a v2 sidecar has no rent block both read as defaults,
136
+ // accepted, never a crash; the current version is fully checked
137
+ // (shape + the canonical block + the rent ledger).
138
+ if (version !== 1 && version !== 2 && version !== TRACE_SCHEMA_VERSION)
126
139
  return false;
127
- const fields = version === 1 ? TRACE_RECORD_FIELDS_V1 : TRACE_RECORD_FIELDS;
140
+ const fields = version === 1 ? TRACE_RECORD_FIELDS_V1 : version === 2 ? TRACE_RECORD_FIELDS_V2 : TRACE_RECORD_FIELDS;
128
141
  if (!hasClosedKeys(v, fields, ["lineageLink"]))
129
142
  return false;
130
143
  if (v.kind !== "request")
@@ -197,6 +210,13 @@ export function validateTraceRecord(v) {
197
210
  return false;
198
211
  }
199
212
  }
213
+ if (version === TRACE_SCHEMA_VERSION) {
214
+ // the rent block: every line validates (closed fields, non-empty
215
+ // surface, non-negative integer chars, the estTokens == ceil(chars/4)
216
+ // cross-check — R6). v1/v2 sidecars have no block (R2-1).
217
+ if (!Array.isArray(v.rent) || !v.rent.every(validateRentLine))
218
+ return false;
219
+ }
200
220
  if (!isNumber(v.ts))
201
221
  return false;
202
222
  return true;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * E3 (0.2.1) — the rent ledger: every request's static model-side rent,
3
+ * one line per surface.
4
+ *
5
+ * The static rent is the surface the model pays REGARDLESS of the turn's
6
+ * content: the base system prompt, each extension's append, every tool's
7
+ * serialized spec, and the per-request envelope. One line per surface,
8
+ * four classes, measured in chars + the rounds' est-token convention
9
+ * (chars/4, ceil — the E1 script's formula, the work order's own words,
10
+ * R6):
11
+ *
12
+ * - system:base the session's own prompt as the CLI handed it
13
+ * (built-in constant + project instructions — the
14
+ * runtime cannot split those, R3). The runtime's
15
+ * generated tool table is machinery BETWEEN base and
16
+ * appends; it stays out of the ledger (counts live
17
+ * in tool:<name> lines and the base's own line).
18
+ * - system:ext:<name> one line per extension with a non-empty
19
+ * systemPrompt.append, measured on the append
20
+ * string, in load order.
21
+ * - tool:<name> one line per tool, measured on the exact
22
+ * serialized ToolSpec projection the adapters
23
+ * receive ({name, description, inputSchema} — the
24
+ * registry.toSpecs() shape, protocol/messages.ts).
25
+ * - envelope the per-request fixed overhead OUTSIDE the
26
+ * conversation payloads: the R5 skeleton
27
+ * JSON.stringify({model, messages: [], tools: []})
28
+ * — a function of the model string only, never of
29
+ * the payloads (the skeleton is the definition).
30
+ *
31
+ * R9: an absent surface is an absent line — an unconfigured mcp, a
32
+ * session with no project instructions: no line. The absence IS the
33
+ * ledger statement (the 0.1.45 diet-A precedent: not paid = no rent).
34
+ *
35
+ * Determinism (the reviewer-facing property): every line is derivable
36
+ * from components the reviewer can recompute — the exported prompt
37
+ * constant, the appends, the ToolSpec array, the request skeleton. The
38
+ * ledger stores COUNTS, never payloads (the seqRange thin-pointer
39
+ * discipline applied to rent).
40
+ *
41
+ * buildRentLedger is the SINGLE source: the R7 star gate drives a real
42
+ * session with the script's predicted composition and asserts the
43
+ * recorded ledger equals the prediction line for line.
44
+ */
45
+ import type { ToolSpec } from "@vincemakes/kiso-core";
46
+ export interface RentLine {
47
+ /** "system:base" | "system:ext:<name>" | "tool:<name>" | "envelope" */
48
+ surface: string;
49
+ /** length of the serialized surface, measured, never a copy */
50
+ chars: number;
51
+ /** Math.ceil(chars / 4) — the E1 script convention, pinned (R6) */
52
+ estTokens: number;
53
+ }
54
+ /** The closed line set (the same gate discipline as the record fields). */
55
+ export declare const RENT_LINE_FIELDS: readonly ["surface", "chars", "estTokens"];
56
+ /** What the runtime knows about the static surface beyond what the
57
+ * adapter call itself carries: the base prompt as configured, and the
58
+ * per-extension appends in load order (the adapter's composed
59
+ * systemPrompt is the result — the parts are the ledger's inputs). */
60
+ export interface RentParts {
61
+ base?: string;
62
+ appends?: readonly {
63
+ name: string;
64
+ text: string;
65
+ }[];
66
+ }
67
+ export interface RentInput extends RentParts {
68
+ model: string;
69
+ tools?: readonly ToolSpec[];
70
+ }
71
+ /** The one ledger: system:base, then system:ext:* (load order), then
72
+ * tool:* (the array's order — the registry's toSpecs() order), then the
73
+ * envelope. Absent surfaces contribute no lines (R9). */
74
+ export declare function buildRentLedger(input: RentInput): RentLine[];
75
+ /** The line validator — the same strict discipline as the record's other
76
+ * closed sets: no key outside the spec, every spec'd key present,
77
+ * non-empty surface (R9: a line exists only for a surface that exists),
78
+ * non-negative integer chars, and the estTokens cross-check (R6). The
79
+ * schema pins the LINE, not the multiset — duplicate surfaces are the
80
+ * writer's business, never a schema crash. */
81
+ export declare function validateRentLine(v: unknown): v is RentLine;
@@ -0,0 +1,96 @@
1
+ /**
2
+ * E3 (0.2.1) — the rent ledger: every request's static model-side rent,
3
+ * one line per surface.
4
+ *
5
+ * The static rent is the surface the model pays REGARDLESS of the turn's
6
+ * content: the base system prompt, each extension's append, every tool's
7
+ * serialized spec, and the per-request envelope. One line per surface,
8
+ * four classes, measured in chars + the rounds' est-token convention
9
+ * (chars/4, ceil — the E1 script's formula, the work order's own words,
10
+ * R6):
11
+ *
12
+ * - system:base the session's own prompt as the CLI handed it
13
+ * (built-in constant + project instructions — the
14
+ * runtime cannot split those, R3). The runtime's
15
+ * generated tool table is machinery BETWEEN base and
16
+ * appends; it stays out of the ledger (counts live
17
+ * in tool:<name> lines and the base's own line).
18
+ * - system:ext:<name> one line per extension with a non-empty
19
+ * systemPrompt.append, measured on the append
20
+ * string, in load order.
21
+ * - tool:<name> one line per tool, measured on the exact
22
+ * serialized ToolSpec projection the adapters
23
+ * receive ({name, description, inputSchema} — the
24
+ * registry.toSpecs() shape, protocol/messages.ts).
25
+ * - envelope the per-request fixed overhead OUTSIDE the
26
+ * conversation payloads: the R5 skeleton
27
+ * JSON.stringify({model, messages: [], tools: []})
28
+ * — a function of the model string only, never of
29
+ * the payloads (the skeleton is the definition).
30
+ *
31
+ * R9: an absent surface is an absent line — an unconfigured mcp, a
32
+ * session with no project instructions: no line. The absence IS the
33
+ * ledger statement (the 0.1.45 diet-A precedent: not paid = no rent).
34
+ *
35
+ * Determinism (the reviewer-facing property): every line is derivable
36
+ * from components the reviewer can recompute — the exported prompt
37
+ * constant, the appends, the ToolSpec array, the request skeleton. The
38
+ * ledger stores COUNTS, never payloads (the seqRange thin-pointer
39
+ * discipline applied to rent).
40
+ *
41
+ * buildRentLedger is the SINGLE source: the R7 star gate drives a real
42
+ * session with the script's predicted composition and asserts the
43
+ * recorded ledger equals the prediction line for line.
44
+ */
45
+ /** The closed line set (the same gate discipline as the record fields). */
46
+ export const RENT_LINE_FIELDS = ["surface", "chars", "estTokens"];
47
+ const line = (surface, text) => ({
48
+ surface,
49
+ chars: text.length,
50
+ estTokens: Math.ceil(text.length / 4),
51
+ });
52
+ /** The one ledger: system:base, then system:ext:* (load order), then
53
+ * tool:* (the array's order — the registry's toSpecs() order), then the
54
+ * envelope. Absent surfaces contribute no lines (R9). */
55
+ export function buildRentLedger(input) {
56
+ const lines = [];
57
+ if (input.base !== undefined)
58
+ lines.push(line("system:base", input.base));
59
+ for (const append of input.appends ?? [])
60
+ lines.push(line(`system:ext:${append.name}`, append.text));
61
+ for (const tool of input.tools ?? []) {
62
+ lines.push(line(`tool:${tool.name}`, JSON.stringify({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema })));
63
+ }
64
+ // R5: the envelope is the request skeleton — a model-string function,
65
+ // never the payloads; the empty arrays are literal (the definition).
66
+ lines.push(line("envelope", JSON.stringify({ model: input.model, messages: [], tools: [] })));
67
+ return lines;
68
+ }
69
+ /** The line validator — the same strict discipline as the record's other
70
+ * closed sets: no key outside the spec, every spec'd key present,
71
+ * non-empty surface (R9: a line exists only for a surface that exists),
72
+ * non-negative integer chars, and the estTokens cross-check (R6). The
73
+ * schema pins the LINE, not the multiset — duplicate surfaces are the
74
+ * writer's business, never a schema crash. */
75
+ export function validateRentLine(v) {
76
+ if (typeof v !== "object" || v === null || Array.isArray(v))
77
+ return false;
78
+ const o = v;
79
+ const keys = Object.keys(o);
80
+ const fields = RENT_LINE_FIELDS;
81
+ if (keys.some((k) => !fields.includes(k)))
82
+ return false;
83
+ if (!fields.every((k) => k in o))
84
+ return false;
85
+ if (typeof o.surface !== "string" || o.surface.length === 0)
86
+ return false;
87
+ const chars = o.chars;
88
+ const estTokens = o.estTokens;
89
+ if (typeof chars !== "number" || !Number.isInteger(chars) || chars < 0)
90
+ return false;
91
+ if (typeof estTokens !== "number" || !Number.isInteger(estTokens) || estTokens < 0)
92
+ return false;
93
+ if (estTokens !== Math.ceil(chars / 4))
94
+ return false;
95
+ return true;
96
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-runtime",
3
- "version": "0.2.0",
4
- "description": "kiso runtime durable multi-turn agent sessions: AgentDefinition, AgentRuntime, AgentSession, Run, append-only JSONL store.",
3
+ "version": "0.2.1",
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",
7
7
  "exports": {