@vincemakes/kiso-runtime 0.1.38 → 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/agent.d.ts CHANGED
@@ -62,6 +62,7 @@ export interface AgentDefinition {
62
62
  * loop's policy chain. */
63
63
  readonly extensions?: readonly KisoExtension[];
64
64
  }
65
+ /** @deprecated the canonical name is `Agent` (root export, 1.1.0); this alias is removed in the next major. */
65
66
  export declare class AgentRuntime {
66
67
  #private;
67
68
  constructor(definition: AgentDefinition);
package/dist/agent.js CHANGED
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import { EventLog, ToolRegistry } from "@vincemakes/kiso-core";
15
15
  import { AgentSession } from "./session.js";
16
+ /** @deprecated the canonical name is `Agent` (root export, 1.1.0); this alias is removed in the next major. */
16
17
  export class AgentRuntime {
17
18
  #definition;
18
19
  #registry;
@@ -54,6 +55,7 @@ export class AgentRuntime {
54
55
  const adapter = await this.#adapterPromise;
55
56
  const config = {
56
57
  model: this.#definition.model,
58
+ ...(this.#definition.provider !== undefined ? { provider: this.#definition.provider } : {}),
57
59
  ...(this.#definition.systemPrompt !== undefined ? { systemPrompt: this.#definition.systemPrompt } : {}),
58
60
  registry: this.#registry,
59
61
  ...(this.#definition.permissionPolicy !== undefined || this.#definition.hooks !== undefined
package/dist/index.d.ts CHANGED
@@ -1,11 +1,32 @@
1
- export * from "./agent.js";
2
- export * from "./session.js";
3
- export * from "./run.js";
4
- export * from "./recovery.js";
5
- export * from "./compose.js";
6
- export * from "./summarize.js";
7
- export * from "./store.js";
8
- export * from "./lock-adapter.js";
9
- export * from "./ledger.js";
10
- export * from "./extensions.js";
11
- export * from "./trust.js";
1
+ /**
2
+ * The SDK surface — the curated root manifest (S1, 2026-08-12).
3
+ *
4
+ * Every name here is a review-approved contract: adding or removing one
5
+ * WITHOUT the ritual (the review's sign-off + a public-surface.json
6
+ * refresh) turns the public-surface gate red. See docs/sdk.md for the
7
+ * contract, the aliases, and what deliberately lives behind the
8
+ * first-party door.
9
+ *
10
+ * What is NOT here is not the SDK. The recovery / compose / summarize /
11
+ * lock-adapter machinery and buildAdapter live behind "./internal"
12
+ * (unstable, first-party only — see internal.ts).
13
+ *
14
+ * Canonical names: Agent = AgentRuntime, Session = AgentSession. The old
15
+ * names are deprecated aliases, removed in the next major (additive,
16
+ * ADR-0051 Amendment 1 — release rounds move the cli minor).
17
+ */
18
+ export { AgentRuntime, AgentRuntime as Agent, createAgent } from "./agent.js";
19
+ export type { AgentDefinition, PermissionPolicy, PermissionRule } from "./agent.js";
20
+ export { AgentSession, AgentSession as Session } from "./session.js";
21
+ export { PoisonedSessionError, ResumeBlockedError } from "./session.js";
22
+ export type { ApprovalRequest, CompactInfo, SessionConfig, SummarizeResult } from "./session.js";
23
+ export { Run } from "./run.js";
24
+ export { SessionStore, StaleWriterError, StoreCorruptionError } from "./store.js";
25
+ export type { Event, SessionMeta, StoreRecord } from "./store.js";
26
+ export { disposeExtensions, loadExtensions, loadProjectExtensions } from "./extensions.js";
27
+ export type { KisoExtension } from "./extensions.js";
28
+ export { executionForCallId, executionLedger } from "./ledger.js";
29
+ export type { ExecutionRecord, ExecutionStatus } from "./ledger.js";
30
+ export { kisoHome, projectArtifacts, recordTrust, trustFor } from "./trust.js";
31
+ export type { ProjectArtifact, ProjectArtifacts, TrustDecision, TrustRecord } from "./trust.js";
32
+ export { canonicalizeUsage } from "./usage/canonical.js";
package/dist/index.js CHANGED
@@ -1,11 +1,41 @@
1
- export * from "./agent.js";
2
- export * from "./session.js";
3
- export * from "./run.js";
4
- export * from "./recovery.js";
5
- export * from "./compose.js";
6
- export * from "./summarize.js";
7
- export * from "./store.js";
8
- export * from "./lock-adapter.js";
9
- export * from "./ledger.js";
10
- export * from "./extensions.js";
11
- export * from "./trust.js";
1
+ /**
2
+ * The SDK surface — the curated root manifest (S1, 2026-08-12).
3
+ *
4
+ * Every name here is a review-approved contract: adding or removing one
5
+ * WITHOUT the ritual (the review's sign-off + a public-surface.json
6
+ * refresh) turns the public-surface gate red. See docs/sdk.md for the
7
+ * contract, the aliases, and what deliberately lives behind the
8
+ * first-party door.
9
+ *
10
+ * What is NOT here is not the SDK. The recovery / compose / summarize /
11
+ * lock-adapter machinery and buildAdapter live behind "./internal"
12
+ * (unstable, first-party only — see internal.ts).
13
+ *
14
+ * Canonical names: Agent = AgentRuntime, Session = AgentSession. The old
15
+ * names are deprecated aliases, removed in the next major (additive,
16
+ * ADR-0051 Amendment 1 — release rounds move the cli minor).
17
+ */
18
+ // agent
19
+ export { AgentRuntime, AgentRuntime as Agent, createAgent } from "./agent.js";
20
+ // session
21
+ export { AgentSession, AgentSession as Session } from "./session.js";
22
+ export { PoisonedSessionError, ResumeBlockedError } from "./session.js";
23
+ // run
24
+ export { Run } from "./run.js";
25
+ // store
26
+ export { SessionStore, StaleWriterError, StoreCorruptionError } from "./store.js";
27
+ // extensions
28
+ export { disposeExtensions, loadExtensions, loadProjectExtensions } from "./extensions.js";
29
+ // ledger
30
+ export { executionForCallId, executionLedger } from "./ledger.js";
31
+ // trust
32
+ export { kisoHome, projectArtifacts, recordTrust, trustFor } from "./trust.js";
33
+ // usage — the canonical accounting schema (E2/1.3.0, R4b-1 ruling:
34
+ // additive minor, one function; the signature is frozen the moment this
35
+ // line lands — changing it is a MAJOR ritual). R5b-④a set the final
36
+ // shape at freeze time: the trailing `table?: PricingTable` injection
37
+ // slot defaults to the builtin v1 table, so the R5a-1-commercial table
38
+ // rides day one without ever widening this signature. Raw stays
39
+ // provider observation; canonicalizeUsage derives at the accounting
40
+ // boundary (R4 Case B — the frozen usage union does not move).
41
+ export { canonicalizeUsage } from "./usage/canonical.js";
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The FIRST-PARTY door — NOT part of the public SDK contract (S1,
3
+ * 2026-08-12, adjudicated).
4
+ *
5
+ * Unstable: anything here can move or vanish in any release without a
6
+ * major bump. External consumers must import the curated root
7
+ * (index.ts); this file exists for the in-repo consumers (apps/cli,
8
+ * extensions, tests) that need machinery the SDK deliberately does not
9
+ * expose — the recovery / compose / summarize / lock-adapter internals
10
+ * and buildAdapter.
11
+ *
12
+ * This is today's pre-S1 index.ts verbatim: removing the door would be a
13
+ * breaking change for the first-party consumers mid-migration.
14
+ */
15
+ export * from "./agent.js";
16
+ export * from "./session.js";
17
+ export * from "./run.js";
18
+ export * from "./recovery.js";
19
+ export * from "./compose.js";
20
+ export * from "./summarize.js";
21
+ export * from "./store.js";
22
+ export * from "./lock-adapter.js";
23
+ export * from "./ledger.js";
24
+ export * from "./extensions.js";
25
+ export * from "./trust.js";
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The FIRST-PARTY door — NOT part of the public SDK contract (S1,
3
+ * 2026-08-12, adjudicated).
4
+ *
5
+ * Unstable: anything here can move or vanish in any release without a
6
+ * major bump. External consumers must import the curated root
7
+ * (index.ts); this file exists for the in-repo consumers (apps/cli,
8
+ * extensions, tests) that need machinery the SDK deliberately does not
9
+ * expose — the recovery / compose / summarize / lock-adapter internals
10
+ * and buildAdapter.
11
+ *
12
+ * This is today's pre-S1 index.ts verbatim: removing the door would be a
13
+ * breaking change for the first-party consumers mid-migration.
14
+ */
15
+ export * from "./agent.js";
16
+ export * from "./session.js";
17
+ export * from "./run.js";
18
+ export * from "./recovery.js";
19
+ export * from "./compose.js";
20
+ export * from "./summarize.js";
21
+ export * from "./store.js";
22
+ export * from "./lock-adapter.js";
23
+ export * from "./ledger.js";
24
+ export * from "./extensions.js";
25
+ export * from "./trust.js";
@@ -61,6 +61,7 @@
61
61
  * pauses plus SIGSTOP/SIGCONT, and locate the freeze points via
62
62
  * ready-marker files (ADR-0050 §test affordances).
63
63
  */
64
+ import { execFileSync } from "node:child_process";
64
65
  import { closeSync, fsyncSync, linkSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
65
66
  import { randomUUID } from "node:crypto";
66
67
  import { join } from "node:path";
@@ -78,14 +79,58 @@ export class LockUnavailableError extends Error {
78
79
  this.name = "LockUnavailableError";
79
80
  }
80
81
  }
82
+ /**
83
+ * ADR-0050 Amendment 1 (Finding R-I-1): the liveness probe is
84
+ * state-aware. A dead holder can linger in the process table after a
85
+ * kill — the exiting state (STAT E on macOS: the kill landing while a
86
+ * pty syscall is blocked, the dead session's terminal left open) or an
87
+ * un-reaped zombie (STAT Z). POSIX reports BOTH alive to kill(pid, 0)
88
+ * (they exist until reaped), so the takeover refused a holder that can
89
+ * never execute another session write. Both states are probed and
90
+ * judged DEAD. A probe failure (unreadable state) maintains the
91
+ * pre-amendment behavior — alive, the fail-safe refusal (prefer a false
92
+ * refusal over a double-write). Live-process semantics and the PID-reuse
93
+ * rules do not move: a live foreign writer is still refused.
94
+ *
95
+ * ADR-0050 Amendment 2 (Finding R-I-p-3): the state letters are matched
96
+ * ANYWHERE in the state string, not as the first character. The
97
+ * exit-path linger's ps output is "?E" — the first character is the
98
+ * no-controlling-terminal marker "?", with the E sitting AFTER it — so
99
+ * first-character matching judged the finding's own documented shape
100
+ * alive. The ps state alphabet (macOS + Linux) has no flag letters
101
+ * "E"/"Z": an occurrence anywhere is the process-state code, and the
102
+ * holder is dead.
103
+ */
81
104
  function isAlive(pid) {
82
105
  try {
83
106
  process.kill(pid, 0);
84
- return true;
85
107
  }
86
108
  catch (err) {
87
109
  return err.code === "EPERM";
88
110
  }
111
+ const state = processState(pid);
112
+ // A null state (probe failure) keeps the pre-amendment behavior —
113
+ // judged alive, the fail-safe refusal.
114
+ return state === null || (!state.includes("E") && !state.includes("Z"));
115
+ }
116
+ /**
117
+ * The process state via `ps -o state= -p <pid>` (macOS/Linux) — the
118
+ * whole state string (a multi-char string like "Ss+" or "?E" is a
119
+ * combined flag set; the E/Z process-state codes may sit after the "?"
120
+ * no-tty marker or other flag letters, so the matching in isAlive scans
121
+ * the whole string). An empty or unreadable state — the process
122
+ * vanished between the kill and the probe, or ps itself failed —
123
+ * returns null; isAlive's fail-safe branch then judges the holder alive
124
+ * (the pre-amendment behavior).
125
+ */
126
+ function processState(pid) {
127
+ try {
128
+ const state = execFileSync("ps", ["-o", "state=", "-p", String(pid)], { encoding: "utf8" }).trim();
129
+ return state.length > 0 ? state : null;
130
+ }
131
+ catch {
132
+ return null;
133
+ }
89
134
  }
90
135
  /**
91
136
  * Read a lock file's holder identity (round 4 formats, unchanged — the
package/dist/run.js CHANGED
@@ -8,6 +8,8 @@ import { ABORTED, MergedSignal, abortable, openRunId } from "./recovery.js";
8
8
  import { deriveRecoveryPlan, invocationSeqOf } from "./recovery-plan.js";
9
9
  import { composeApprovalChain, composeSystemPrompt, composeToolTable, microcompactFor } from "./compose.js";
10
10
  import { truncationGuard } from "./truncation-guard.js";
11
+ import { RequestTracer, traceGuard } from "./trace/guard.js";
12
+ import { runtimeVersion } from "./trace/writer.js";
11
13
  import { ResumeBlockedError } from "./session.js";
12
14
  /**
13
15
  * A single turn. Async-iterable, so `for await (const ev of session.run(x))`
@@ -47,6 +49,7 @@ export class Run {
47
49
  // The WHOLE body is one try/finally: a consumer that abandons the
48
50
  // run at ANY yield (even the user_input one) must release the
49
51
  // session's single-run slot and its approval resolvers.
52
+ let tracer = null;
50
53
  try {
51
54
  // round 4: health is re-checked when the iterator ACTUALLY starts —
52
55
  // a run constructed before the session was poisoned must fail
@@ -54,10 +57,10 @@ export class Run {
54
57
  this.#session.ensureHealthy();
55
58
  this.#session.beginRun(this);
56
59
  const log = this.#session.log;
57
- const signal = this.#externalSignal ? new MergedSignal(this.#abort.signal, this.#externalSignal) : this.#abort.signal;
58
- // E2: the session's own microcompact wins; otherwise the FIRST
59
- // extension providing a compaction config supplies it.
60
- const microcompact = microcompactFor(this.#config);
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).
61
64
  // 0.1.40 (R-C item 1): the tool substitution table — the ACTIVE tool
62
65
  // set's vocabulary, snippets, and guidelines — sits BETWEEN the
63
66
  // session's base prompt and the extension appends: generated
@@ -67,6 +70,35 @@ export class Run {
67
70
  const basePrompt = toolTable === "" ? this.#config.systemPrompt
68
71
  : this.#config.systemPrompt === undefined ? toolTable
69
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
+ };
83
+ // E1 (1.2.0): the request tracer — the observation ledger. It
84
+ // sits at the adapter boundary; the model-visible byte stream is
85
+ // untouched (I6, trace-bytes.test.ts). Soft-fail: a degraded
86
+ // writer costs one stderr line and the run goes on.
87
+ tracer = new RequestTracer({
88
+ root: this.#store.root,
89
+ sessionId: this.#session.id,
90
+ runId: this.runId,
91
+ provider: this.#config.provider ?? "adapter",
92
+ model: this.#config.model,
93
+ adapterVersion: runtimeVersion(),
94
+ log: log.all,
95
+ rentParts,
96
+ });
97
+ tracer.init();
98
+ const signal = this.#externalSignal ? new MergedSignal(this.#abort.signal, this.#externalSignal) : this.#abort.signal;
99
+ // E2: the session's own microcompact wins; otherwise the FIRST
100
+ // extension providing a compaction config supplies it.
101
+ const microcompact = microcompactFor(this.#config);
70
102
  // E2: the session's own systemPrompt first, then every extension
71
103
  // append in LOAD order — deterministic (same extensions → same
72
104
  // prompt); no appends → byte-identical to the extension-less run.
@@ -75,7 +107,7 @@ export class Run {
75
107
  const loopConfig = () => ({
76
108
  // 0.1.40 (R-C item 3): the truncation guard gates the model
77
109
  // stream — a truncated turn's tool batch never executes.
78
- adapter: truncationGuard(this.#adapter),
110
+ adapter: traceGuard(tracer, truncationGuard(this.#adapter)), // tracer assigned above, before loopConfig
79
111
  model: this.#config.model,
80
112
  sessionId: this.#session.id, // P3: tools see their session (ToolContext.sessionId)
81
113
  ...(systemPrompt !== undefined ? { systemPrompt } : {}),
@@ -230,6 +262,9 @@ export class Run {
230
262
  for (const executionId of this.#uncertaintyIds) {
231
263
  this.#session.dropUncertaintyResolver(executionId);
232
264
  }
265
+ // E1: the run's ledger story — the run_end lands synchronously
266
+ // (a killed run leaves no run_end, and the next init marks it).
267
+ tracer?.finishRun();
233
268
  this.#session.endRun(this);
234
269
  }
235
270
  }
package/dist/session.d.ts CHANGED
@@ -71,6 +71,7 @@ export interface CompactInfo {
71
71
  /** The covered content's estimated tokens (the chars/4 proxy). */
72
72
  readonly tokens: number;
73
73
  }
74
+ /** @deprecated the canonical name is `Session` (root export, 1.1.0); this alias is removed in the next major. */
74
75
  export declare class AgentSession {
75
76
  #private;
76
77
  readonly id: string;
@@ -97,6 +98,11 @@ export declare class AgentSession {
97
98
  * turns (dispatch's /model), never mid-run.
98
99
  */
99
100
  setAdapter(adapter: Adapter): void;
101
+ /** E2: the adapter identity ("anthropic" | "openai-compat") — the route
102
+ * key the canonical consumer (CLI usage, the trace block) keys on. The
103
+ * per-run tracer reads the SAME #config.provider; one source, one
104
+ * route — the CLI and the trace can never disagree. */
105
+ get provider(): "anthropic" | "openai-compat" | undefined;
100
106
  /** Run one user turn. Iterate to consume; `run.abort()` cancels. */
101
107
  run(input: string, options?: {
102
108
  signal?: AbortSignalLike;
@@ -178,6 +184,9 @@ export declare class AgentSession {
178
184
  }
179
185
  export interface SessionConfig {
180
186
  readonly model: string;
187
+ /** E1: the adapter identity ("anthropic" | "openai-compat") — trace
188
+ * provenance, additive (S1 surface untouched: type-only, optional). */
189
+ readonly provider?: "anthropic" | "openai-compat";
181
190
  readonly systemPrompt?: string;
182
191
  readonly tools?: readonly Tool<any>[];
183
192
  readonly registry: import("@vincemakes/kiso-core").ToolRegistry;
package/dist/session.js CHANGED
@@ -54,6 +54,7 @@ export class ResumeBlockedError extends Error {
54
54
  this.uncertain = uncertain;
55
55
  }
56
56
  }
57
+ /** @deprecated the canonical name is `Session` (root export, 1.1.0); this alias is removed in the next major. */
57
58
  export class AgentSession {
58
59
  id;
59
60
  log;
@@ -154,6 +155,13 @@ export class AgentSession {
154
155
  setAdapter(adapter) {
155
156
  this.#adapter = adapter;
156
157
  }
158
+ /** E2: the adapter identity ("anthropic" | "openai-compat") — the route
159
+ * key the canonical consumer (CLI usage, the trace block) keys on. The
160
+ * per-run tracer reads the SAME #config.provider; one source, one
161
+ * route — the CLI and the trace can never disagree. */
162
+ get provider() {
163
+ return this.#config.provider;
164
+ }
157
165
  /** Run one user turn. Iterate to consume; `run.abort()` cancels. */
158
166
  run(input, options) {
159
167
  this.ensureHealthy();
@@ -0,0 +1,35 @@
1
+ /**
2
+ * E1 (1.2.0) — slice 4, the cache-break derivation (proposal §4, ruling
3
+ * R4b: per-segment hashes + prefix fingerprint; the BREAK COUNT is an
4
+ * analysis-side derivation, never a recorded field).
5
+ *
6
+ * The cacheable prefix is every segment that is NOT the current turn —
7
+ * freshness "fresh" is the boundary (manifest.ts). `cacheableHashes`
8
+ * pairs the manifest segments 1:1 with the per-segment hashes and
9
+ * drops the fresh tail, so the fingerprint and the break derivation
10
+ * share one boundary by construction: a current-turn change alone
11
+ * never moves the fingerprint and never counts a break.
12
+ *
13
+ * `prefixBreak` compares two adjacent requests' cacheable prefixes:
14
+ * the first differing segment is the break, at its (0-based) depth;
15
+ * a prefix that merely GREW (a new turn joined) breaks at the old
16
+ * length. Unchanged prefixes → null (0 breaks). This is what
17
+ * bench/trace-report.mjs and the bench render per-request (slice 5).
18
+ */
19
+ import type { TraceSegment } from "./record.js";
20
+ /** The cacheable-prefix hashes: segments[i] ↔ hashes[i], dropping every
21
+ * freshness "fresh" segment (the current turn). */
22
+ export declare function cacheableHashes(segments: readonly TraceSegment[], hashes: readonly string[]): string[];
23
+ export interface PrefixBreak {
24
+ /** 0-based segment index within the cacheable prefix where the
25
+ * prefix first diverges (depth 0 = the system prompt). */
26
+ readonly depth: number;
27
+ }
28
+ /** R4b: compare two adjacent requests' cacheable prefixes. null = the
29
+ * prefix is unchanged (0 breaks). A prefix that grew breaks at the old
30
+ * length — the new segment is where caching can no longer attach. */
31
+ export declare function prefixBreak(prev: readonly string[], next: readonly string[]): PrefixBreak | null;
32
+ /** Per-request breaks across a run's request sequence: request k's
33
+ * break is relative to request k−1; the first request has no
34
+ * predecessor (null). */
35
+ export declare function deriveBreaks(requests: readonly (readonly string[])[]): (PrefixBreak | null)[];
@@ -0,0 +1,51 @@
1
+ /**
2
+ * E1 (1.2.0) — slice 4, the cache-break derivation (proposal §4, ruling
3
+ * R4b: per-segment hashes + prefix fingerprint; the BREAK COUNT is an
4
+ * analysis-side derivation, never a recorded field).
5
+ *
6
+ * The cacheable prefix is every segment that is NOT the current turn —
7
+ * freshness "fresh" is the boundary (manifest.ts). `cacheableHashes`
8
+ * pairs the manifest segments 1:1 with the per-segment hashes and
9
+ * drops the fresh tail, so the fingerprint and the break derivation
10
+ * share one boundary by construction: a current-turn change alone
11
+ * never moves the fingerprint and never counts a break.
12
+ *
13
+ * `prefixBreak` compares two adjacent requests' cacheable prefixes:
14
+ * the first differing segment is the break, at its (0-based) depth;
15
+ * a prefix that merely GREW (a new turn joined) breaks at the old
16
+ * length. Unchanged prefixes → null (0 breaks). This is what
17
+ * bench/trace-report.mjs and the bench render per-request (slice 5).
18
+ */
19
+ /** The cacheable-prefix hashes: segments[i] ↔ hashes[i], dropping every
20
+ * freshness "fresh" segment (the current turn). */
21
+ export function cacheableHashes(segments, hashes) {
22
+ const out = [];
23
+ for (let i = 0; i < segments.length; i++) {
24
+ if (segments[i].freshness !== "fresh")
25
+ out.push(hashes[i]);
26
+ }
27
+ return out;
28
+ }
29
+ /** R4b: compare two adjacent requests' cacheable prefixes. null = the
30
+ * prefix is unchanged (0 breaks). A prefix that grew breaks at the old
31
+ * length — the new segment is where caching can no longer attach. */
32
+ export function prefixBreak(prev, next) {
33
+ const shared = Math.min(prev.length, next.length);
34
+ for (let i = 0; i < shared; i++) {
35
+ if (prev[i] !== next[i])
36
+ return { depth: i };
37
+ }
38
+ if (prev.length !== next.length)
39
+ return { depth: shared };
40
+ return null;
41
+ }
42
+ /** Per-request breaks across a run's request sequence: request k's
43
+ * break is relative to request k−1; the first request has no
44
+ * predecessor (null). */
45
+ export function deriveBreaks(requests) {
46
+ const out = [];
47
+ for (let k = 0; k < requests.length; k++) {
48
+ out.push(k === 0 ? null : prefixBreak(requests[k - 1], requests[k]));
49
+ }
50
+ return out;
51
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * E1 (1.2.0) — slice 3, the request tracer (the guard).
3
+ *
4
+ * One TraceRecord per adapter call, settled when the stream ends. The
5
+ * guard lives AT the adapter boundary (run.ts wires
6
+ * truncationGuard(traceGuard(tracer, adapter))): the kernel, the log,
7
+ * and the model-visible byte stream are untouched (I6 — pinned by
8
+ * trace-bytes.test.ts). retryAttempt counts prior calls in this run
9
+ * with an identical contextHash — the loop re-streams the SAME messages
10
+ * array on retry (loop.ts), so an identical hash is exactly "same
11
+ * request, retried" (§1.4).
12
+ *
13
+ * Soft-fail: any failure in trace assembly (never expected — hashes and
14
+ * the manifest cannot throw today) marks the request ABSENT rather than
15
+ * breaking the stream; the writer's own degradation covers I/O.
16
+ *
17
+ * Usage is provider-raw, never normalized (E2's job): openai-compat
18
+ * reports input TOTAL (fresh = input − cacheRead); anthropic's
19
+ * input_tokens is ALREADY fresh-only (the trace records it as-is).
20
+ * A request that settles with NO usage data records the quartet as
21
+ * zeros with cacheWrite null — "0 = unknown", documented at record.ts;
22
+ * the honest nullable quartet is a schema bump, deferred.
23
+ */
24
+ import type { Adapter, AdapterEvent, Event, StreamOptions } from "@vincemakes/kiso-core";
25
+ import { type RentParts } from "./rent.js";
26
+ export interface RequestTracerDeps {
27
+ root: string;
28
+ sessionId: string;
29
+ runId: string;
30
+ provider: string;
31
+ model: string;
32
+ /** The adapter contract's implementation version (the runtime's own),
33
+ * resolved once at tracer init; null on failure (soft-fail). */
34
+ adapterVersion?: string | null;
35
+ /** The session log — the manifest's seqRange pointers derive from it. */
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;
42
+ }
43
+ export declare class RequestTracer {
44
+ #private;
45
+ constructor(deps: RequestTracerDeps);
46
+ init(): void;
47
+ wrap(options: StreamOptions, upstream: AsyncIterable<AdapterEvent>): AsyncIterable<AdapterEvent>;
48
+ /** Clean-settle marking for the whole run. */
49
+ finishRun(): void;
50
+ }
51
+ /** Wrap the adapter so every stream() call settles a trace record. */
52
+ export declare function traceGuard(tracer: RequestTracer, adapter: Adapter): Adapter;