@vextlabs/theron-agent-sdk 0.3.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.
Files changed (70) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/LICENSE +21 -0
  3. package/README.md +270 -0
  4. package/dist/adapters/theron.cjs +92 -0
  5. package/dist/adapters/theron.d.cts +42 -0
  6. package/dist/adapters/theron.d.ts +42 -0
  7. package/dist/adapters/theron.js +89 -0
  8. package/dist/agent/index.cjs +33 -0
  9. package/dist/agent/index.d.cts +84 -0
  10. package/dist/agent/index.d.ts +84 -0
  11. package/dist/agent/index.js +31 -0
  12. package/dist/council/index.cjs +68 -0
  13. package/dist/council/index.d.cts +96 -0
  14. package/dist/council/index.d.ts +96 -0
  15. package/dist/council/index.js +66 -0
  16. package/dist/index.cjs +1288 -0
  17. package/dist/index.d.cts +60 -0
  18. package/dist/index.d.ts +60 -0
  19. package/dist/index.js +1244 -0
  20. package/dist/loop/index.cjs +106 -0
  21. package/dist/loop/index.d.cts +285 -0
  22. package/dist/loop/index.d.ts +285 -0
  23. package/dist/loop/index.js +95 -0
  24. package/dist/mcp/index.cjs +153 -0
  25. package/dist/mcp/index.d.cts +69 -0
  26. package/dist/mcp/index.d.ts +69 -0
  27. package/dist/mcp/index.js +150 -0
  28. package/dist/memory/index.cjs +53 -0
  29. package/dist/memory/index.d.cts +73 -0
  30. package/dist/memory/index.d.ts +73 -0
  31. package/dist/memory/index.js +50 -0
  32. package/dist/patterns/index.cjs +159 -0
  33. package/dist/patterns/index.d.cts +200 -0
  34. package/dist/patterns/index.d.ts +200 -0
  35. package/dist/patterns/index.js +150 -0
  36. package/dist/receipts/index.cjs +151 -0
  37. package/dist/receipts/index.d.cts +132 -0
  38. package/dist/receipts/index.d.ts +132 -0
  39. package/dist/receipts/index.js +146 -0
  40. package/dist/runtime/index.cjs +205 -0
  41. package/dist/runtime/index.d.cts +148 -0
  42. package/dist/runtime/index.d.ts +148 -0
  43. package/dist/runtime/index.js +203 -0
  44. package/dist/session/index.cjs +49 -0
  45. package/dist/session/index.d.cts +79 -0
  46. package/dist/session/index.d.ts +79 -0
  47. package/dist/session/index.js +47 -0
  48. package/dist/tools/index.cjs +51 -0
  49. package/dist/tools/index.d.cts +52 -0
  50. package/dist/tools/index.d.ts +52 -0
  51. package/dist/tools/index.js +46 -0
  52. package/dist/verifiers/index.cjs +96 -0
  53. package/dist/verifiers/index.d.cts +63 -0
  54. package/dist/verifiers/index.d.ts +63 -0
  55. package/dist/verifiers/index.js +93 -0
  56. package/examples/01_code_reviewer.ts +90 -0
  57. package/examples/02_research_assistant.ts +85 -0
  58. package/examples/03_council_of_three.ts +91 -0
  59. package/examples/_adapters/openrouter.ts +90 -0
  60. package/examples/adapters/openrouter.ts +144 -0
  61. package/examples/adapters/theron.ts +105 -0
  62. package/examples/basic-agent.ts +56 -0
  63. package/examples/council-deliberation.ts +90 -0
  64. package/examples/cyber-recon-bot.ts +163 -0
  65. package/examples/loop-primitives.ts +50 -0
  66. package/examples/meeting-prep-bot.ts +172 -0
  67. package/examples/reasoning-patterns.ts +125 -0
  68. package/examples/support-triage-bot.ts +181 -0
  69. package/examples/verifier-kernel.ts +108 -0
  70. package/package.json +154 -0
@@ -0,0 +1,150 @@
1
+ // src/patterns/index.ts
2
+ async function selfConsistency(opts) {
3
+ const n = Math.max(1, Math.floor(opts.samples));
4
+ const keyOf = opts.key ?? ((v) => JSON.stringify(v));
5
+ const clusters = /* @__PURE__ */ new Map();
6
+ let total = 0;
7
+ for (let i = 0; i < n; i++) {
8
+ const v = await opts.generate(i);
9
+ if (v === void 0 || v === null) continue;
10
+ total += 1;
11
+ const k = keyOf(v);
12
+ const c = clusters.get(k) ?? { count: 0, sample: v };
13
+ c.count += 1;
14
+ clusters.set(k, c);
15
+ }
16
+ if (total === 0) throw new Error("selfConsistency: generate produced no values");
17
+ const ranked = [...clusters.entries()].map(([key, { count, sample }]) => ({ key, count, sample })).sort((a, b) => b.count - a.count);
18
+ const winner = ranked[0];
19
+ return {
20
+ answer: winner.sample,
21
+ consistency: Math.round(winner.count / total * 1e3) / 1e3,
22
+ votes: winner.count,
23
+ total,
24
+ clusters: ranked
25
+ };
26
+ }
27
+ async function bestOfN(opts) {
28
+ const n = Math.max(1, Math.floor(opts.n));
29
+ let best = null;
30
+ const scores = [];
31
+ for (let i = 0; i < n; i++) {
32
+ const v = await opts.generate(i);
33
+ const s = await opts.score(v, i);
34
+ scores.push(s);
35
+ if (!best || s > best.score) best = { value: v, score: s, index: i };
36
+ }
37
+ if (!best) throw new Error("bestOfN: generate produced no candidates");
38
+ return { best: best.value, score: best.score, index: best.index, scores };
39
+ }
40
+ var DEFAULT_CLEAN = /\b(no (issues|problems|flaws|changes)|looks good|lgtm|nothing to (fix|improve))\b/i;
41
+ async function selfRefine(opts) {
42
+ const maxIters = Math.max(1, Math.floor(opts.maxIters ?? 2));
43
+ const isClean = opts.isClean ?? ((c) => DEFAULT_CLEAN.test(c));
44
+ let value = await opts.draft();
45
+ const trace = [];
46
+ let revised = 0;
47
+ for (let iter = 1; iter <= maxIters; iter++) {
48
+ const critique = String(await opts.critique(value, iter));
49
+ if (isClean(critique)) {
50
+ trace.push({ iter, critique, revised: false });
51
+ break;
52
+ }
53
+ value = await opts.revise(value, critique, iter);
54
+ revised += 1;
55
+ trace.push({ iter, critique, revised: true });
56
+ }
57
+ return { answer: value, iterations: trace.length, revised, trace };
58
+ }
59
+ async function treeOfThoughts(opts) {
60
+ const breadth = Math.max(1, Math.floor(opts.breadth));
61
+ const depth = Math.max(1, Math.floor(opts.depth));
62
+ const path = [];
63
+ const scored = [];
64
+ for (let d = 0; d < depth; d++) {
65
+ let best = null;
66
+ for (let b = 0; b < breadth; b++) {
67
+ const cand = await opts.expand(path, b);
68
+ const s = await opts.score(cand, path);
69
+ if (!best || s > best.score) best = { thought: cand, score: s };
70
+ }
71
+ if (!best) break;
72
+ path.push(best.thought);
73
+ scored.push(best);
74
+ }
75
+ const answer = opts.synthesize ? await opts.synthesize(path) : path[path.length - 1];
76
+ return { answer, path: scored };
77
+ }
78
+ async function chainOfVerification(opts) {
79
+ const draft = await opts.draft();
80
+ const questions = await opts.planChecks(draft) ?? [];
81
+ const checks = [];
82
+ for (const q of questions) {
83
+ checks.push({ q, a: String(await opts.answerCheck(q)) });
84
+ }
85
+ const answer = checks.length ? await opts.revise(draft, checks) : draft;
86
+ return { answer, checks };
87
+ }
88
+ async function reflexion(opts) {
89
+ const maxAttempts = Math.max(1, Math.floor(opts.maxAttempts));
90
+ const reflections = [];
91
+ let last;
92
+ for (let i = 0; i < maxAttempts; i++) {
93
+ last = await opts.attempt(reflections, i);
94
+ const { success, feedback } = await opts.evaluate(last, i);
95
+ if (success) return { answer: last, attempts: i + 1, succeeded: true, reflections };
96
+ if (i < maxAttempts - 1) reflections.push(String(await opts.reflect(last, feedback, i)));
97
+ }
98
+ return { answer: last, attempts: maxAttempts, succeeded: false, reflections };
99
+ }
100
+ async function mixtureOfAgents(opts) {
101
+ const agents = Math.max(1, Math.floor(opts.agents));
102
+ const layers = Math.max(1, Math.floor(opts.layers));
103
+ const layerOutputs = [];
104
+ let current = [];
105
+ for (let a = 0; a < agents; a++) current.push(String(await opts.propose(a)));
106
+ layerOutputs.push([...current]);
107
+ for (let layer = 2; layer <= layers; layer++) {
108
+ if (!opts.refine) break;
109
+ const next = [];
110
+ for (let a = 0; a < agents; a++) {
111
+ const others = current.filter((_, i) => i !== a);
112
+ next.push(String(await opts.refine(a, others, layer)));
113
+ }
114
+ current = next;
115
+ layerOutputs.push([...current]);
116
+ }
117
+ const answer = String(await opts.aggregate(current));
118
+ return { answer, layerOutputs };
119
+ }
120
+ var mean = (xs) => xs.length ? xs.reduce((a, b) => a + b, 0) / xs.length : 0;
121
+ var round = (x) => Math.round(x * 1e3) / 1e3;
122
+ async function measureLift(opts) {
123
+ const perTask = [];
124
+ const baseScores = [];
125
+ const treatScores = [];
126
+ let wins = 0;
127
+ for (let i = 0; i < opts.tasks.length; i++) {
128
+ const task = opts.tasks[i];
129
+ const bOut = await opts.baseline(task, i);
130
+ const tOut = await opts.treatment(task, i);
131
+ const b = await opts.score(task, bOut, i);
132
+ const t = await opts.score(task, tOut, i);
133
+ baseScores.push(b);
134
+ treatScores.push(t);
135
+ if (t > b) wins += 1;
136
+ perTask.push({ task, baseline: round(b), treatment: round(t), delta: round(t - b) });
137
+ }
138
+ const baselineMean = round(mean(baseScores));
139
+ const treatmentMean = round(mean(treatScores));
140
+ return {
141
+ n: opts.tasks.length,
142
+ baselineMean,
143
+ treatmentMean,
144
+ lift: round(treatmentMean - baselineMean),
145
+ winRate: opts.tasks.length ? round(wins / opts.tasks.length) : 0,
146
+ perTask
147
+ };
148
+ }
149
+
150
+ export { bestOfN, chainOfVerification, measureLift, mixtureOfAgents, reflexion, selfConsistency, selfRefine, treeOfThoughts };
@@ -0,0 +1,151 @@
1
+ 'use strict';
2
+
3
+ // src/receipts/index.ts
4
+ var ReceiptEmitter = class {
5
+ sinks;
6
+ signer;
7
+ issuer;
8
+ actor;
9
+ tenant_id;
10
+ constructor(config) {
11
+ if (!config.sinks || config.sinks.length === 0) {
12
+ throw new Error("ReceiptEmitter requires at least one sink.");
13
+ }
14
+ this.sinks = config.sinks;
15
+ this.signer = config.signer;
16
+ this.issuer = config.signer?.issuer ?? config.issuer ?? "did:web:local";
17
+ this.actor = config.actor;
18
+ this.tenant_id = config.tenant_id;
19
+ }
20
+ async emit(input) {
21
+ const ts = Date.now();
22
+ const payload = {
23
+ input: input.input,
24
+ output: input.output,
25
+ ...input.metadata !== void 0 ? { metadata: input.metadata } : {}
26
+ };
27
+ const content_hash = await sha256Hex(canonicalize(payload));
28
+ let receipt = {
29
+ v: "stoa.receipt.v1",
30
+ id: ulid(),
31
+ cap: input.cap,
32
+ issuer: this.issuer,
33
+ actor: input.actor ?? this.actor,
34
+ ts,
35
+ session_id: input.session_id,
36
+ tenant_id: input.tenant_id ?? this.tenant_id,
37
+ content_hash,
38
+ payload
39
+ };
40
+ if (this.signer) {
41
+ const signature = await this.signer.sign(receipt);
42
+ receipt = { ...receipt, signature };
43
+ }
44
+ await Promise.all(
45
+ this.sinks.map(async (sink) => {
46
+ try {
47
+ await sink.emit(receipt);
48
+ } catch (err) {
49
+ console.warn(`[receipts] sink "${sink.name}" failed:`, err);
50
+ }
51
+ })
52
+ );
53
+ return receipt;
54
+ }
55
+ };
56
+ var InMemoryReceiptSink = class {
57
+ name = "in-memory";
58
+ records = [];
59
+ async emit(receipt) {
60
+ this.records.push(receipt);
61
+ }
62
+ list() {
63
+ return this.records;
64
+ }
65
+ clear() {
66
+ this.records.length = 0;
67
+ }
68
+ };
69
+ function fileReceiptSink(path) {
70
+ return {
71
+ name: `file:${path}`,
72
+ async emit(receipt) {
73
+ const fs = await import('fs/promises');
74
+ await fs.appendFile(path, JSON.stringify(receipt) + "\n", "utf8");
75
+ }
76
+ };
77
+ }
78
+ function httpReceiptSink(opts) {
79
+ const timeout_ms = opts.timeout_ms ?? 5e3;
80
+ return {
81
+ name: `http:${new URL(opts.url).host}`,
82
+ async emit(receipt) {
83
+ const ac = new AbortController();
84
+ const timer = setTimeout(() => ac.abort(), timeout_ms);
85
+ try {
86
+ const res = await fetch(opts.url, {
87
+ method: "POST",
88
+ signal: ac.signal,
89
+ headers: {
90
+ "Content-Type": "application/json",
91
+ ...opts.token ? { Authorization: `Bearer ${opts.token}` } : {}
92
+ },
93
+ body: JSON.stringify(receipt)
94
+ });
95
+ if (!res.ok) {
96
+ const text = await res.text().catch(() => "");
97
+ throw new Error(
98
+ `http sink ${opts.url} returned ${res.status}: ${text.slice(0, 200)}`
99
+ );
100
+ }
101
+ } finally {
102
+ clearTimeout(timer);
103
+ }
104
+ }
105
+ };
106
+ }
107
+ function canonicalize(value) {
108
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
109
+ if (Array.isArray(value)) {
110
+ return "[" + value.map((v) => canonicalize(v)).join(",") + "]";
111
+ }
112
+ const keys = Object.keys(value).sort();
113
+ return "{" + keys.map(
114
+ (k) => JSON.stringify(k) + ":" + canonicalize(value[k])
115
+ ).join(",") + "}";
116
+ }
117
+ async function sha256Hex(input) {
118
+ const data = new TextEncoder().encode(input);
119
+ const buf = await globalThis.crypto.subtle.digest("SHA-256", data);
120
+ const bytes = new Uint8Array(buf);
121
+ let hex = "";
122
+ for (let i = 0; i < bytes.length; i++) {
123
+ hex += bytes[i].toString(16).padStart(2, "0");
124
+ }
125
+ return hex;
126
+ }
127
+ function ulid() {
128
+ const ts = Date.now();
129
+ const ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
130
+ let tsPart = "";
131
+ let t = ts;
132
+ for (let i = 9; i >= 0; i--) {
133
+ tsPart = ALPHABET[t % 32] + tsPart;
134
+ t = Math.floor(t / 32);
135
+ }
136
+ let randPart = "";
137
+ if (globalThis.crypto?.getRandomValues) {
138
+ const bytes = new Uint8Array(16);
139
+ globalThis.crypto.getRandomValues(bytes);
140
+ for (let i = 0; i < 16; i++) randPart += ALPHABET[bytes[i] % 32];
141
+ } else {
142
+ for (let i = 0; i < 16; i++)
143
+ randPart += ALPHABET[Math.floor(Math.random() * 32)];
144
+ }
145
+ return tsPart + randPart;
146
+ }
147
+
148
+ exports.InMemoryReceiptSink = InMemoryReceiptSink;
149
+ exports.ReceiptEmitter = ReceiptEmitter;
150
+ exports.fileReceiptSink = fileReceiptSink;
151
+ exports.httpReceiptSink = httpReceiptSink;
@@ -0,0 +1,132 @@
1
+ interface ReceiptInput {
2
+ /** Stable capability identifier. Examples: "agent.run", "council.deliberate",
3
+ * "vext.calendar.list_events", "vext.github.create_pr". Free-form; pick a
4
+ * reverse-DNS-ish scheme so receipts cluster by domain. */
5
+ cap: string;
6
+ /** Inputs the agent saw. Hashed into the content_hash. */
7
+ input: unknown;
8
+ /** Outputs the agent produced. Hashed into the content_hash. */
9
+ output: unknown;
10
+ /** Actor — who/what produced this. Free-form: agent name, council name,
11
+ * user id, tenant. */
12
+ actor?: string;
13
+ /** Optional session id for correlation with a Session log. */
14
+ session_id?: string;
15
+ /** Optional tenant id for multi-tenant deployments. */
16
+ tenant_id?: string;
17
+ /** Optional metadata. Hashed into content_hash. */
18
+ metadata?: Record<string, unknown>;
19
+ }
20
+ interface Receipt {
21
+ /** Schema version. */
22
+ v: "stoa.receipt.v1";
23
+ /** ULID-shaped id. */
24
+ id: string;
25
+ /** Capability the receipt covers. */
26
+ cap: string;
27
+ /** Issuer DID or label. Defaults to "did:web:local". Production users set
28
+ * this to their issuer DID (did:web:tryvext.com, did:web:acme.com, ...). */
29
+ issuer: string;
30
+ /** Actor — who/what produced this. */
31
+ actor?: string;
32
+ /** Unix ms timestamp. */
33
+ ts: number;
34
+ /** Session correlation id. */
35
+ session_id?: string;
36
+ /** Tenant id. */
37
+ tenant_id?: string;
38
+ /** SHA-256 hex of canonical(input + output + metadata). Lets downstream
39
+ * systems verify the receipt without seeing the payload. */
40
+ content_hash: string;
41
+ /** Inline payload. Implementations MAY null this out before transmission
42
+ * if the sink already has the data; the content_hash is the source of
43
+ * truth. */
44
+ payload: {
45
+ input: unknown;
46
+ output: unknown;
47
+ metadata?: Record<string, unknown>;
48
+ };
49
+ /** Optional detached signature (base64). Populated by a signer. The SDK
50
+ * ships an unsigned receipt by default; plug a signer into the emitter
51
+ * to populate this. ES256 over canonical(receipt without `signature`)
52
+ * is the canonical scheme. */
53
+ signature?: string;
54
+ }
55
+ /** A sink receives receipts and persists / forwards them. */
56
+ interface ReceiptSink {
57
+ name: string;
58
+ emit(receipt: Receipt): Promise<void>;
59
+ }
60
+ /** A signer turns an unsigned receipt into a signed one. Implementations:
61
+ * ES256 (default Stoa scheme), Ed25519, HMAC-SHA256 (for internal flows). */
62
+ interface ReceiptSigner {
63
+ algorithm: "ES256" | "Ed25519" | "HMAC-SHA256" | string;
64
+ issuer: string;
65
+ sign(receipt: Receipt): Promise<string>;
66
+ }
67
+ interface ReceiptEmitterConfig {
68
+ /** Sinks the emitter writes to. Multiple sinks fan out in parallel. */
69
+ sinks: ReceiptSink[];
70
+ /** Optional signer. If provided, every receipt is signed before sink emit. */
71
+ signer?: ReceiptSigner;
72
+ /** Default issuer if no signer is configured. Defaults to "did:web:local". */
73
+ issuer?: string;
74
+ /** Default actor. Overridable per-emit. */
75
+ actor?: string;
76
+ /** Default tenant id. Overridable per-emit. */
77
+ tenant_id?: string;
78
+ }
79
+ /**
80
+ * ReceiptEmitter — the main primitive callers use.
81
+ *
82
+ * Minimal usage:
83
+ * const receipts = new ReceiptEmitter({ sinks: [new InMemoryReceiptSink()] });
84
+ * await receipts.emit({
85
+ * cap: "agent.run",
86
+ * actor: "code-reviewer",
87
+ * input: { query: "review PR 42" },
88
+ * output: { review: "LGTM with two nits" },
89
+ * });
90
+ *
91
+ * Wire into a Runner via the runner.on() callback:
92
+ * runner.on(async (event) => {
93
+ * if (event.type === "agent_output") {
94
+ * await receipts.emit({ cap: "agent.run", actor: event.agent,
95
+ * input: { agent: event.agent }, output: event.output });
96
+ * }
97
+ * });
98
+ */
99
+ declare class ReceiptEmitter {
100
+ readonly sinks: ReceiptSink[];
101
+ readonly signer: ReceiptSigner | undefined;
102
+ readonly issuer: string;
103
+ readonly actor: string | undefined;
104
+ readonly tenant_id: string | undefined;
105
+ constructor(config: ReceiptEmitterConfig);
106
+ emit(input: ReceiptInput): Promise<Receipt>;
107
+ }
108
+ /** In-memory sink — for tests + ephemeral inspection. */
109
+ declare class InMemoryReceiptSink implements ReceiptSink {
110
+ readonly name = "in-memory";
111
+ private readonly records;
112
+ emit(receipt: Receipt): Promise<void>;
113
+ list(): readonly Receipt[];
114
+ clear(): void;
115
+ }
116
+ /**
117
+ * JSONL file sink — appends each receipt as a single JSON line to `path`.
118
+ * Lazily imports `node:fs/promises` so the SDK stays bundleable for non-Node
119
+ * runtimes (Workers, Deno). Throws on use outside Node.
120
+ */
121
+ declare function fileReceiptSink(path: string): ReceiptSink;
122
+ /**
123
+ * HTTP sink — POSTs each receipt to a Stoa-conformant sink URL. The URL must
124
+ * accept `application/json` and return 2xx on accept.
125
+ */
126
+ declare function httpReceiptSink(opts: {
127
+ url: string;
128
+ token?: string;
129
+ timeout_ms?: number;
130
+ }): ReceiptSink;
131
+
132
+ export { InMemoryReceiptSink, type Receipt, ReceiptEmitter, type ReceiptEmitterConfig, type ReceiptInput, type ReceiptSigner, type ReceiptSink, fileReceiptSink, httpReceiptSink };
@@ -0,0 +1,132 @@
1
+ interface ReceiptInput {
2
+ /** Stable capability identifier. Examples: "agent.run", "council.deliberate",
3
+ * "vext.calendar.list_events", "vext.github.create_pr". Free-form; pick a
4
+ * reverse-DNS-ish scheme so receipts cluster by domain. */
5
+ cap: string;
6
+ /** Inputs the agent saw. Hashed into the content_hash. */
7
+ input: unknown;
8
+ /** Outputs the agent produced. Hashed into the content_hash. */
9
+ output: unknown;
10
+ /** Actor — who/what produced this. Free-form: agent name, council name,
11
+ * user id, tenant. */
12
+ actor?: string;
13
+ /** Optional session id for correlation with a Session log. */
14
+ session_id?: string;
15
+ /** Optional tenant id for multi-tenant deployments. */
16
+ tenant_id?: string;
17
+ /** Optional metadata. Hashed into content_hash. */
18
+ metadata?: Record<string, unknown>;
19
+ }
20
+ interface Receipt {
21
+ /** Schema version. */
22
+ v: "stoa.receipt.v1";
23
+ /** ULID-shaped id. */
24
+ id: string;
25
+ /** Capability the receipt covers. */
26
+ cap: string;
27
+ /** Issuer DID or label. Defaults to "did:web:local". Production users set
28
+ * this to their issuer DID (did:web:tryvext.com, did:web:acme.com, ...). */
29
+ issuer: string;
30
+ /** Actor — who/what produced this. */
31
+ actor?: string;
32
+ /** Unix ms timestamp. */
33
+ ts: number;
34
+ /** Session correlation id. */
35
+ session_id?: string;
36
+ /** Tenant id. */
37
+ tenant_id?: string;
38
+ /** SHA-256 hex of canonical(input + output + metadata). Lets downstream
39
+ * systems verify the receipt without seeing the payload. */
40
+ content_hash: string;
41
+ /** Inline payload. Implementations MAY null this out before transmission
42
+ * if the sink already has the data; the content_hash is the source of
43
+ * truth. */
44
+ payload: {
45
+ input: unknown;
46
+ output: unknown;
47
+ metadata?: Record<string, unknown>;
48
+ };
49
+ /** Optional detached signature (base64). Populated by a signer. The SDK
50
+ * ships an unsigned receipt by default; plug a signer into the emitter
51
+ * to populate this. ES256 over canonical(receipt without `signature`)
52
+ * is the canonical scheme. */
53
+ signature?: string;
54
+ }
55
+ /** A sink receives receipts and persists / forwards them. */
56
+ interface ReceiptSink {
57
+ name: string;
58
+ emit(receipt: Receipt): Promise<void>;
59
+ }
60
+ /** A signer turns an unsigned receipt into a signed one. Implementations:
61
+ * ES256 (default Stoa scheme), Ed25519, HMAC-SHA256 (for internal flows). */
62
+ interface ReceiptSigner {
63
+ algorithm: "ES256" | "Ed25519" | "HMAC-SHA256" | string;
64
+ issuer: string;
65
+ sign(receipt: Receipt): Promise<string>;
66
+ }
67
+ interface ReceiptEmitterConfig {
68
+ /** Sinks the emitter writes to. Multiple sinks fan out in parallel. */
69
+ sinks: ReceiptSink[];
70
+ /** Optional signer. If provided, every receipt is signed before sink emit. */
71
+ signer?: ReceiptSigner;
72
+ /** Default issuer if no signer is configured. Defaults to "did:web:local". */
73
+ issuer?: string;
74
+ /** Default actor. Overridable per-emit. */
75
+ actor?: string;
76
+ /** Default tenant id. Overridable per-emit. */
77
+ tenant_id?: string;
78
+ }
79
+ /**
80
+ * ReceiptEmitter — the main primitive callers use.
81
+ *
82
+ * Minimal usage:
83
+ * const receipts = new ReceiptEmitter({ sinks: [new InMemoryReceiptSink()] });
84
+ * await receipts.emit({
85
+ * cap: "agent.run",
86
+ * actor: "code-reviewer",
87
+ * input: { query: "review PR 42" },
88
+ * output: { review: "LGTM with two nits" },
89
+ * });
90
+ *
91
+ * Wire into a Runner via the runner.on() callback:
92
+ * runner.on(async (event) => {
93
+ * if (event.type === "agent_output") {
94
+ * await receipts.emit({ cap: "agent.run", actor: event.agent,
95
+ * input: { agent: event.agent }, output: event.output });
96
+ * }
97
+ * });
98
+ */
99
+ declare class ReceiptEmitter {
100
+ readonly sinks: ReceiptSink[];
101
+ readonly signer: ReceiptSigner | undefined;
102
+ readonly issuer: string;
103
+ readonly actor: string | undefined;
104
+ readonly tenant_id: string | undefined;
105
+ constructor(config: ReceiptEmitterConfig);
106
+ emit(input: ReceiptInput): Promise<Receipt>;
107
+ }
108
+ /** In-memory sink — for tests + ephemeral inspection. */
109
+ declare class InMemoryReceiptSink implements ReceiptSink {
110
+ readonly name = "in-memory";
111
+ private readonly records;
112
+ emit(receipt: Receipt): Promise<void>;
113
+ list(): readonly Receipt[];
114
+ clear(): void;
115
+ }
116
+ /**
117
+ * JSONL file sink — appends each receipt as a single JSON line to `path`.
118
+ * Lazily imports `node:fs/promises` so the SDK stays bundleable for non-Node
119
+ * runtimes (Workers, Deno). Throws on use outside Node.
120
+ */
121
+ declare function fileReceiptSink(path: string): ReceiptSink;
122
+ /**
123
+ * HTTP sink — POSTs each receipt to a Stoa-conformant sink URL. The URL must
124
+ * accept `application/json` and return 2xx on accept.
125
+ */
126
+ declare function httpReceiptSink(opts: {
127
+ url: string;
128
+ token?: string;
129
+ timeout_ms?: number;
130
+ }): ReceiptSink;
131
+
132
+ export { InMemoryReceiptSink, type Receipt, ReceiptEmitter, type ReceiptEmitterConfig, type ReceiptInput, type ReceiptSigner, type ReceiptSink, fileReceiptSink, httpReceiptSink };