@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.
- package/CHANGELOG.md +59 -0
- package/LICENSE +21 -0
- package/README.md +270 -0
- package/dist/adapters/theron.cjs +92 -0
- package/dist/adapters/theron.d.cts +42 -0
- package/dist/adapters/theron.d.ts +42 -0
- package/dist/adapters/theron.js +89 -0
- package/dist/agent/index.cjs +33 -0
- package/dist/agent/index.d.cts +84 -0
- package/dist/agent/index.d.ts +84 -0
- package/dist/agent/index.js +31 -0
- package/dist/council/index.cjs +68 -0
- package/dist/council/index.d.cts +96 -0
- package/dist/council/index.d.ts +96 -0
- package/dist/council/index.js +66 -0
- package/dist/index.cjs +1288 -0
- package/dist/index.d.cts +60 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +1244 -0
- package/dist/loop/index.cjs +106 -0
- package/dist/loop/index.d.cts +285 -0
- package/dist/loop/index.d.ts +285 -0
- package/dist/loop/index.js +95 -0
- package/dist/mcp/index.cjs +153 -0
- package/dist/mcp/index.d.cts +69 -0
- package/dist/mcp/index.d.ts +69 -0
- package/dist/mcp/index.js +150 -0
- package/dist/memory/index.cjs +53 -0
- package/dist/memory/index.d.cts +73 -0
- package/dist/memory/index.d.ts +73 -0
- package/dist/memory/index.js +50 -0
- package/dist/patterns/index.cjs +159 -0
- package/dist/patterns/index.d.cts +200 -0
- package/dist/patterns/index.d.ts +200 -0
- package/dist/patterns/index.js +150 -0
- package/dist/receipts/index.cjs +151 -0
- package/dist/receipts/index.d.cts +132 -0
- package/dist/receipts/index.d.ts +132 -0
- package/dist/receipts/index.js +146 -0
- package/dist/runtime/index.cjs +205 -0
- package/dist/runtime/index.d.cts +148 -0
- package/dist/runtime/index.d.ts +148 -0
- package/dist/runtime/index.js +203 -0
- package/dist/session/index.cjs +49 -0
- package/dist/session/index.d.cts +79 -0
- package/dist/session/index.d.ts +79 -0
- package/dist/session/index.js +47 -0
- package/dist/tools/index.cjs +51 -0
- package/dist/tools/index.d.cts +52 -0
- package/dist/tools/index.d.ts +52 -0
- package/dist/tools/index.js +46 -0
- package/dist/verifiers/index.cjs +96 -0
- package/dist/verifiers/index.d.cts +63 -0
- package/dist/verifiers/index.d.ts +63 -0
- package/dist/verifiers/index.js +93 -0
- package/examples/01_code_reviewer.ts +90 -0
- package/examples/02_research_assistant.ts +85 -0
- package/examples/03_council_of_three.ts +91 -0
- package/examples/_adapters/openrouter.ts +90 -0
- package/examples/adapters/openrouter.ts +144 -0
- package/examples/adapters/theron.ts +105 -0
- package/examples/basic-agent.ts +56 -0
- package/examples/council-deliberation.ts +90 -0
- package/examples/cyber-recon-bot.ts +163 -0
- package/examples/loop-primitives.ts +50 -0
- package/examples/meeting-prep-bot.ts +172 -0
- package/examples/reasoning-patterns.ts +125 -0
- package/examples/support-triage-bot.ts +181 -0
- package/examples/verifier-kernel.ts +108 -0
- package/package.json +154 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// src/receipts/index.ts
|
|
2
|
+
var ReceiptEmitter = class {
|
|
3
|
+
sinks;
|
|
4
|
+
signer;
|
|
5
|
+
issuer;
|
|
6
|
+
actor;
|
|
7
|
+
tenant_id;
|
|
8
|
+
constructor(config) {
|
|
9
|
+
if (!config.sinks || config.sinks.length === 0) {
|
|
10
|
+
throw new Error("ReceiptEmitter requires at least one sink.");
|
|
11
|
+
}
|
|
12
|
+
this.sinks = config.sinks;
|
|
13
|
+
this.signer = config.signer;
|
|
14
|
+
this.issuer = config.signer?.issuer ?? config.issuer ?? "did:web:local";
|
|
15
|
+
this.actor = config.actor;
|
|
16
|
+
this.tenant_id = config.tenant_id;
|
|
17
|
+
}
|
|
18
|
+
async emit(input) {
|
|
19
|
+
const ts = Date.now();
|
|
20
|
+
const payload = {
|
|
21
|
+
input: input.input,
|
|
22
|
+
output: input.output,
|
|
23
|
+
...input.metadata !== void 0 ? { metadata: input.metadata } : {}
|
|
24
|
+
};
|
|
25
|
+
const content_hash = await sha256Hex(canonicalize(payload));
|
|
26
|
+
let receipt = {
|
|
27
|
+
v: "stoa.receipt.v1",
|
|
28
|
+
id: ulid(),
|
|
29
|
+
cap: input.cap,
|
|
30
|
+
issuer: this.issuer,
|
|
31
|
+
actor: input.actor ?? this.actor,
|
|
32
|
+
ts,
|
|
33
|
+
session_id: input.session_id,
|
|
34
|
+
tenant_id: input.tenant_id ?? this.tenant_id,
|
|
35
|
+
content_hash,
|
|
36
|
+
payload
|
|
37
|
+
};
|
|
38
|
+
if (this.signer) {
|
|
39
|
+
const signature = await this.signer.sign(receipt);
|
|
40
|
+
receipt = { ...receipt, signature };
|
|
41
|
+
}
|
|
42
|
+
await Promise.all(
|
|
43
|
+
this.sinks.map(async (sink) => {
|
|
44
|
+
try {
|
|
45
|
+
await sink.emit(receipt);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.warn(`[receipts] sink "${sink.name}" failed:`, err);
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
return receipt;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var InMemoryReceiptSink = class {
|
|
55
|
+
name = "in-memory";
|
|
56
|
+
records = [];
|
|
57
|
+
async emit(receipt) {
|
|
58
|
+
this.records.push(receipt);
|
|
59
|
+
}
|
|
60
|
+
list() {
|
|
61
|
+
return this.records;
|
|
62
|
+
}
|
|
63
|
+
clear() {
|
|
64
|
+
this.records.length = 0;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
function fileReceiptSink(path) {
|
|
68
|
+
return {
|
|
69
|
+
name: `file:${path}`,
|
|
70
|
+
async emit(receipt) {
|
|
71
|
+
const fs = await import('fs/promises');
|
|
72
|
+
await fs.appendFile(path, JSON.stringify(receipt) + "\n", "utf8");
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function httpReceiptSink(opts) {
|
|
77
|
+
const timeout_ms = opts.timeout_ms ?? 5e3;
|
|
78
|
+
return {
|
|
79
|
+
name: `http:${new URL(opts.url).host}`,
|
|
80
|
+
async emit(receipt) {
|
|
81
|
+
const ac = new AbortController();
|
|
82
|
+
const timer = setTimeout(() => ac.abort(), timeout_ms);
|
|
83
|
+
try {
|
|
84
|
+
const res = await fetch(opts.url, {
|
|
85
|
+
method: "POST",
|
|
86
|
+
signal: ac.signal,
|
|
87
|
+
headers: {
|
|
88
|
+
"Content-Type": "application/json",
|
|
89
|
+
...opts.token ? { Authorization: `Bearer ${opts.token}` } : {}
|
|
90
|
+
},
|
|
91
|
+
body: JSON.stringify(receipt)
|
|
92
|
+
});
|
|
93
|
+
if (!res.ok) {
|
|
94
|
+
const text = await res.text().catch(() => "");
|
|
95
|
+
throw new Error(
|
|
96
|
+
`http sink ${opts.url} returned ${res.status}: ${text.slice(0, 200)}`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
} finally {
|
|
100
|
+
clearTimeout(timer);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function canonicalize(value) {
|
|
106
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
107
|
+
if (Array.isArray(value)) {
|
|
108
|
+
return "[" + value.map((v) => canonicalize(v)).join(",") + "]";
|
|
109
|
+
}
|
|
110
|
+
const keys = Object.keys(value).sort();
|
|
111
|
+
return "{" + keys.map(
|
|
112
|
+
(k) => JSON.stringify(k) + ":" + canonicalize(value[k])
|
|
113
|
+
).join(",") + "}";
|
|
114
|
+
}
|
|
115
|
+
async function sha256Hex(input) {
|
|
116
|
+
const data = new TextEncoder().encode(input);
|
|
117
|
+
const buf = await globalThis.crypto.subtle.digest("SHA-256", data);
|
|
118
|
+
const bytes = new Uint8Array(buf);
|
|
119
|
+
let hex = "";
|
|
120
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
121
|
+
hex += bytes[i].toString(16).padStart(2, "0");
|
|
122
|
+
}
|
|
123
|
+
return hex;
|
|
124
|
+
}
|
|
125
|
+
function ulid() {
|
|
126
|
+
const ts = Date.now();
|
|
127
|
+
const ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
128
|
+
let tsPart = "";
|
|
129
|
+
let t = ts;
|
|
130
|
+
for (let i = 9; i >= 0; i--) {
|
|
131
|
+
tsPart = ALPHABET[t % 32] + tsPart;
|
|
132
|
+
t = Math.floor(t / 32);
|
|
133
|
+
}
|
|
134
|
+
let randPart = "";
|
|
135
|
+
if (globalThis.crypto?.getRandomValues) {
|
|
136
|
+
const bytes = new Uint8Array(16);
|
|
137
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
138
|
+
for (let i = 0; i < 16; i++) randPart += ALPHABET[bytes[i] % 32];
|
|
139
|
+
} else {
|
|
140
|
+
for (let i = 0; i < 16; i++)
|
|
141
|
+
randPart += ALPHABET[Math.floor(Math.random() * 32)];
|
|
142
|
+
}
|
|
143
|
+
return tsPart + randPart;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export { InMemoryReceiptSink, ReceiptEmitter, fileReceiptSink, httpReceiptSink };
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/runtime/index.ts
|
|
4
|
+
var Runner = class {
|
|
5
|
+
model;
|
|
6
|
+
default_model;
|
|
7
|
+
memory;
|
|
8
|
+
session;
|
|
9
|
+
tool_context;
|
|
10
|
+
listeners = [];
|
|
11
|
+
constructor(config) {
|
|
12
|
+
if (!config.model) throw new Error("Runner requires a `model` adapter.");
|
|
13
|
+
if (!config.default_model) throw new Error("Runner requires a `default_model` identifier.");
|
|
14
|
+
this.model = config.model;
|
|
15
|
+
this.default_model = config.default_model;
|
|
16
|
+
this.memory = config.memory;
|
|
17
|
+
this.session = config.session;
|
|
18
|
+
this.tool_context = config.tool_context ?? { cwd: process.cwd(), yolo: false };
|
|
19
|
+
}
|
|
20
|
+
/** Subscribe to runner events for streaming UIs / observability. */
|
|
21
|
+
on(handler) {
|
|
22
|
+
this.listeners.push(handler);
|
|
23
|
+
return () => {
|
|
24
|
+
this.listeners = this.listeners.filter((h) => h !== handler);
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
emit(event) {
|
|
28
|
+
for (const h of this.listeners) h(event);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Run a single agent on a query.
|
|
32
|
+
*
|
|
33
|
+
* Loop:
|
|
34
|
+
* 1. Send messages + tool schemas to the model
|
|
35
|
+
* 2. If model returns a tool call → execute the tool → append result to messages → repeat
|
|
36
|
+
* 3. If model returns content + end_turn → finalize
|
|
37
|
+
* 4. Run any registered verifier kernels on the final output
|
|
38
|
+
* 5. Return the AgentResult
|
|
39
|
+
*/
|
|
40
|
+
async run(agent, query) {
|
|
41
|
+
const startedAt = Date.now();
|
|
42
|
+
this.emit({ type: "agent_start", agent: agent.name, query });
|
|
43
|
+
const messages = [
|
|
44
|
+
{ role: "system", content: agent.instruction.system }
|
|
45
|
+
];
|
|
46
|
+
for (const ex of agent.instruction.examples ?? []) {
|
|
47
|
+
messages.push({ role: "user", content: ex.user });
|
|
48
|
+
messages.push({ role: "assistant", content: ex.assistant });
|
|
49
|
+
}
|
|
50
|
+
messages.push({ role: "user", content: query });
|
|
51
|
+
const toolCalls = [];
|
|
52
|
+
let tokensIn = 0;
|
|
53
|
+
let tokensOut = 0;
|
|
54
|
+
let finalOutput = "";
|
|
55
|
+
for (let turn = 0; turn < agent.max_turns; turn++) {
|
|
56
|
+
const response = await this.model.chat({
|
|
57
|
+
model: agent.model ?? this.default_model,
|
|
58
|
+
messages,
|
|
59
|
+
tools: agent.toolSchemas(),
|
|
60
|
+
onDelta: (delta) => this.emit({ type: "agent_thinking", agent: agent.name, delta })
|
|
61
|
+
});
|
|
62
|
+
tokensIn += response.tokens.input;
|
|
63
|
+
tokensOut += response.tokens.output;
|
|
64
|
+
if (response.tool_calls && response.tool_calls.length > 0) {
|
|
65
|
+
messages.push({ role: "assistant", content: response.content });
|
|
66
|
+
for (const call of response.tool_calls) {
|
|
67
|
+
const tool = agent.tools.find((t) => t.schema.name === call.name);
|
|
68
|
+
if (!tool) {
|
|
69
|
+
this.emit({
|
|
70
|
+
type: "error",
|
|
71
|
+
agent: agent.name,
|
|
72
|
+
message: `Model called unknown tool: ${call.name}`
|
|
73
|
+
});
|
|
74
|
+
messages.push({ role: "tool", content: `error: unknown tool ${call.name}` });
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
this.emit({ type: "tool_call_start", agent: agent.name, tool: call.name, input: call.input });
|
|
78
|
+
const t0 = Date.now();
|
|
79
|
+
try {
|
|
80
|
+
const output = await tool.execute(call.input, this.tool_context);
|
|
81
|
+
const ms = Date.now() - t0;
|
|
82
|
+
this.emit({ type: "tool_call_done", agent: agent.name, tool: call.name, output, ms });
|
|
83
|
+
toolCalls.push({ name: call.name, input: call.input, output });
|
|
84
|
+
messages.push({ role: "tool", content: JSON.stringify(output) });
|
|
85
|
+
} catch (err) {
|
|
86
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
87
|
+
this.emit({ type: "error", agent: agent.name, message: `Tool ${call.name} threw: ${msg}` });
|
|
88
|
+
messages.push({ role: "tool", content: `error: ${msg}` });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
finalOutput = response.content;
|
|
94
|
+
messages.push({ role: "assistant", content: finalOutput });
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
this.emit({ type: "agent_output", agent: agent.name, output: finalOutput });
|
|
98
|
+
const verifier_results = [];
|
|
99
|
+
for (const v of agent.verifiers) {
|
|
100
|
+
try {
|
|
101
|
+
const result = await v.check(finalOutput);
|
|
102
|
+
verifier_results.push(result);
|
|
103
|
+
this.emit({ type: "verifier_run", agent: agent.name, kernel: v.name, result });
|
|
104
|
+
} catch (err) {
|
|
105
|
+
this.emit({
|
|
106
|
+
type: "error",
|
|
107
|
+
agent: agent.name,
|
|
108
|
+
message: `Verifier ${v.name} threw: ${err instanceof Error ? err.message : String(err)}`
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const latency_ms = Date.now() - startedAt;
|
|
113
|
+
return {
|
|
114
|
+
agent: agent.name,
|
|
115
|
+
output: finalOutput,
|
|
116
|
+
tool_calls: toolCalls,
|
|
117
|
+
verifier_results,
|
|
118
|
+
tokens_used: { input: tokensIn, output: tokensOut },
|
|
119
|
+
cost_usd: 0,
|
|
120
|
+
// adapter-specific; populated by adapter
|
|
121
|
+
latency_ms
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Run a Council on a query.
|
|
126
|
+
*
|
|
127
|
+
* Fan out to all specialists in parallel (with timeout), gather outputs,
|
|
128
|
+
* run council-level verifier kernels on each, and reconcile.
|
|
129
|
+
*/
|
|
130
|
+
async runCouncil(council, query) {
|
|
131
|
+
const startedAt = Date.now();
|
|
132
|
+
this.emit({ type: "council_start", council: council.name, query });
|
|
133
|
+
const withTimeout = (p, ms) => Promise.race([
|
|
134
|
+
p,
|
|
135
|
+
new Promise((resolve) => setTimeout(() => resolve(null), ms))
|
|
136
|
+
]);
|
|
137
|
+
const specialistResults = await Promise.all(
|
|
138
|
+
council.specialists.map(async (spec) => {
|
|
139
|
+
try {
|
|
140
|
+
const result = await withTimeout(this.run(spec, query), council.specialist_timeout_ms);
|
|
141
|
+
if (result === null) {
|
|
142
|
+
this.emit({
|
|
143
|
+
type: "error",
|
|
144
|
+
agent: spec.name,
|
|
145
|
+
message: `Specialist timed out after ${council.specialist_timeout_ms}ms`
|
|
146
|
+
});
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const councilVerifierResults = [];
|
|
150
|
+
for (const v of council.verifiers) {
|
|
151
|
+
try {
|
|
152
|
+
const r = await v.check(result.output);
|
|
153
|
+
councilVerifierResults.push(r);
|
|
154
|
+
this.emit({ type: "verifier_run", agent: spec.name, kernel: v.name, result: r });
|
|
155
|
+
} catch (err) {
|
|
156
|
+
this.emit({
|
|
157
|
+
type: "error",
|
|
158
|
+
agent: spec.name,
|
|
159
|
+
message: `Council verifier ${v.name} threw: ${err instanceof Error ? err.message : String(err)}`
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const out = {
|
|
164
|
+
specialist: spec.name,
|
|
165
|
+
output: result.output,
|
|
166
|
+
claims: [],
|
|
167
|
+
// claim extraction is the reconciler's job
|
|
168
|
+
// AgentResult.verifier_results widens issues to unknown[]; at the
|
|
169
|
+
// runtime layer we know every entry came from a Verifier.check()
|
|
170
|
+
// call (which produces VerifierIssue[]), so the cast is sound.
|
|
171
|
+
verifier_results: [
|
|
172
|
+
...result.verifier_results,
|
|
173
|
+
...councilVerifierResults
|
|
174
|
+
],
|
|
175
|
+
cost_usd: result.cost_usd,
|
|
176
|
+
latency_ms: result.latency_ms
|
|
177
|
+
};
|
|
178
|
+
this.emit({ type: "specialist_done", specialist: spec.name, output: out });
|
|
179
|
+
return out;
|
|
180
|
+
} catch (err) {
|
|
181
|
+
this.emit({
|
|
182
|
+
type: "error",
|
|
183
|
+
agent: spec.name,
|
|
184
|
+
message: err instanceof Error ? err.message : String(err)
|
|
185
|
+
});
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
);
|
|
190
|
+
const survivors = specialistResults.filter((s) => s !== null);
|
|
191
|
+
const reconciled = await council.reconciler(survivors);
|
|
192
|
+
const output = {
|
|
193
|
+
answer: reconciled.answer,
|
|
194
|
+
specialists: survivors,
|
|
195
|
+
consensus: reconciled.consensus,
|
|
196
|
+
disagreements: reconciled.disagreements,
|
|
197
|
+
total_cost_usd: survivors.reduce((s, x) => s + x.cost_usd, 0),
|
|
198
|
+
total_latency_ms: Date.now() - startedAt
|
|
199
|
+
};
|
|
200
|
+
this.emit({ type: "council_done", council: council.name, output });
|
|
201
|
+
return output;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
exports.Runner = Runner;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Agent, AgentResult } from '../agent/index.cjs';
|
|
2
|
+
import { CouncilSpecialistOutput, CouncilOutput, Council } from '../council/index.cjs';
|
|
3
|
+
import { Session } from '../session/index.cjs';
|
|
4
|
+
import { Memory } from '../memory/index.cjs';
|
|
5
|
+
import { ToolContext } from '../tools/index.cjs';
|
|
6
|
+
import { VerifierResult } from '../verifiers/index.cjs';
|
|
7
|
+
import 'zod';
|
|
8
|
+
|
|
9
|
+
/** Events the Runner emits as it executes. Subscribe via runner.on(). */
|
|
10
|
+
type RunnerEvent = {
|
|
11
|
+
type: "agent_start";
|
|
12
|
+
agent: string;
|
|
13
|
+
query: string;
|
|
14
|
+
} | {
|
|
15
|
+
type: "agent_thinking";
|
|
16
|
+
agent: string;
|
|
17
|
+
delta: string;
|
|
18
|
+
} | {
|
|
19
|
+
type: "tool_call_start";
|
|
20
|
+
agent: string;
|
|
21
|
+
tool: string;
|
|
22
|
+
input: unknown;
|
|
23
|
+
} | {
|
|
24
|
+
type: "tool_call_done";
|
|
25
|
+
agent: string;
|
|
26
|
+
tool: string;
|
|
27
|
+
output: unknown;
|
|
28
|
+
ms: number;
|
|
29
|
+
} | {
|
|
30
|
+
type: "verifier_run";
|
|
31
|
+
agent: string;
|
|
32
|
+
kernel: string;
|
|
33
|
+
result: VerifierResult;
|
|
34
|
+
} | {
|
|
35
|
+
type: "agent_output";
|
|
36
|
+
agent: string;
|
|
37
|
+
output: string;
|
|
38
|
+
} | {
|
|
39
|
+
type: "council_start";
|
|
40
|
+
council: string;
|
|
41
|
+
query: string;
|
|
42
|
+
} | {
|
|
43
|
+
type: "specialist_done";
|
|
44
|
+
specialist: string;
|
|
45
|
+
output: CouncilSpecialistOutput;
|
|
46
|
+
} | {
|
|
47
|
+
type: "council_done";
|
|
48
|
+
council: string;
|
|
49
|
+
output: CouncilOutput;
|
|
50
|
+
} | {
|
|
51
|
+
type: "error";
|
|
52
|
+
agent: string;
|
|
53
|
+
message: string;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Model adapter — the function the Runner calls to talk to an LLM.
|
|
57
|
+
*
|
|
58
|
+
* Implement once per provider. Three reference adapters ship in
|
|
59
|
+
* `examples/adapters/`: OpenRouter, OpenAI, Anthropic. Production users write
|
|
60
|
+
* their own adapter for their preferred endpoint (Vext-hosted Theron, AWS
|
|
61
|
+
* Bedrock, Cerebras, Groq, your own OSS endpoint).
|
|
62
|
+
*/
|
|
63
|
+
interface ModelAdapter {
|
|
64
|
+
name: string;
|
|
65
|
+
/** Chat completion. Streams deltas via the optional `onDelta` callback. */
|
|
66
|
+
chat(opts: {
|
|
67
|
+
model: string;
|
|
68
|
+
messages: Array<{
|
|
69
|
+
role: "system" | "user" | "assistant" | "tool";
|
|
70
|
+
content: string;
|
|
71
|
+
}>;
|
|
72
|
+
tools?: Array<{
|
|
73
|
+
name: string;
|
|
74
|
+
description: string;
|
|
75
|
+
input_schema: Record<string, unknown>;
|
|
76
|
+
}>;
|
|
77
|
+
max_tokens?: number;
|
|
78
|
+
temperature?: number;
|
|
79
|
+
onDelta?: (delta: string) => void;
|
|
80
|
+
}): Promise<{
|
|
81
|
+
content: string;
|
|
82
|
+
tool_calls?: Array<{
|
|
83
|
+
name: string;
|
|
84
|
+
input: unknown;
|
|
85
|
+
}>;
|
|
86
|
+
tokens: {
|
|
87
|
+
input: number;
|
|
88
|
+
output: number;
|
|
89
|
+
};
|
|
90
|
+
}>;
|
|
91
|
+
}
|
|
92
|
+
interface RunnerConfig {
|
|
93
|
+
/** The model adapter to use. */
|
|
94
|
+
model: ModelAdapter;
|
|
95
|
+
/** Default model identifier (e.g., "gpt-4o", "claude-3-5-sonnet"). */
|
|
96
|
+
default_model: string;
|
|
97
|
+
/** Optional Memory implementation. */
|
|
98
|
+
memory?: Memory;
|
|
99
|
+
/** Optional Session for event logging. */
|
|
100
|
+
session?: Session;
|
|
101
|
+
/** Optional tool execution context (cwd, yolo, tenant). */
|
|
102
|
+
tool_context?: ToolContext;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Runner — executes Agents and Councils.
|
|
106
|
+
*
|
|
107
|
+
* Minimal usage:
|
|
108
|
+
* const runner = new Runner({
|
|
109
|
+
* model: openrouterAdapter({ apiKey: process.env.OPENROUTER_API_KEY }),
|
|
110
|
+
* default_model: "openai/gpt-4o-mini",
|
|
111
|
+
* });
|
|
112
|
+
* const result = await runner.run(myAgent, "What's 2+2?");
|
|
113
|
+
*
|
|
114
|
+
* With a council:
|
|
115
|
+
* const result = await runner.runCouncil(myCouncil, "Review this PR");
|
|
116
|
+
*/
|
|
117
|
+
declare class Runner {
|
|
118
|
+
readonly model: ModelAdapter;
|
|
119
|
+
readonly default_model: string;
|
|
120
|
+
readonly memory: Memory | undefined;
|
|
121
|
+
readonly session: Session | undefined;
|
|
122
|
+
readonly tool_context: ToolContext;
|
|
123
|
+
private listeners;
|
|
124
|
+
constructor(config: RunnerConfig);
|
|
125
|
+
/** Subscribe to runner events for streaming UIs / observability. */
|
|
126
|
+
on(handler: (event: RunnerEvent) => void): () => void;
|
|
127
|
+
private emit;
|
|
128
|
+
/**
|
|
129
|
+
* Run a single agent on a query.
|
|
130
|
+
*
|
|
131
|
+
* Loop:
|
|
132
|
+
* 1. Send messages + tool schemas to the model
|
|
133
|
+
* 2. If model returns a tool call → execute the tool → append result to messages → repeat
|
|
134
|
+
* 3. If model returns content + end_turn → finalize
|
|
135
|
+
* 4. Run any registered verifier kernels on the final output
|
|
136
|
+
* 5. Return the AgentResult
|
|
137
|
+
*/
|
|
138
|
+
run(agent: Agent, query: string): Promise<AgentResult>;
|
|
139
|
+
/**
|
|
140
|
+
* Run a Council on a query.
|
|
141
|
+
*
|
|
142
|
+
* Fan out to all specialists in parallel (with timeout), gather outputs,
|
|
143
|
+
* run council-level verifier kernels on each, and reconcile.
|
|
144
|
+
*/
|
|
145
|
+
runCouncil(council: Council, query: string): Promise<CouncilOutput>;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export { type ModelAdapter, Runner, type RunnerConfig, type RunnerEvent };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Agent, AgentResult } from '../agent/index.js';
|
|
2
|
+
import { CouncilSpecialistOutput, CouncilOutput, Council } from '../council/index.js';
|
|
3
|
+
import { Session } from '../session/index.js';
|
|
4
|
+
import { Memory } from '../memory/index.js';
|
|
5
|
+
import { ToolContext } from '../tools/index.js';
|
|
6
|
+
import { VerifierResult } from '../verifiers/index.js';
|
|
7
|
+
import 'zod';
|
|
8
|
+
|
|
9
|
+
/** Events the Runner emits as it executes. Subscribe via runner.on(). */
|
|
10
|
+
type RunnerEvent = {
|
|
11
|
+
type: "agent_start";
|
|
12
|
+
agent: string;
|
|
13
|
+
query: string;
|
|
14
|
+
} | {
|
|
15
|
+
type: "agent_thinking";
|
|
16
|
+
agent: string;
|
|
17
|
+
delta: string;
|
|
18
|
+
} | {
|
|
19
|
+
type: "tool_call_start";
|
|
20
|
+
agent: string;
|
|
21
|
+
tool: string;
|
|
22
|
+
input: unknown;
|
|
23
|
+
} | {
|
|
24
|
+
type: "tool_call_done";
|
|
25
|
+
agent: string;
|
|
26
|
+
tool: string;
|
|
27
|
+
output: unknown;
|
|
28
|
+
ms: number;
|
|
29
|
+
} | {
|
|
30
|
+
type: "verifier_run";
|
|
31
|
+
agent: string;
|
|
32
|
+
kernel: string;
|
|
33
|
+
result: VerifierResult;
|
|
34
|
+
} | {
|
|
35
|
+
type: "agent_output";
|
|
36
|
+
agent: string;
|
|
37
|
+
output: string;
|
|
38
|
+
} | {
|
|
39
|
+
type: "council_start";
|
|
40
|
+
council: string;
|
|
41
|
+
query: string;
|
|
42
|
+
} | {
|
|
43
|
+
type: "specialist_done";
|
|
44
|
+
specialist: string;
|
|
45
|
+
output: CouncilSpecialistOutput;
|
|
46
|
+
} | {
|
|
47
|
+
type: "council_done";
|
|
48
|
+
council: string;
|
|
49
|
+
output: CouncilOutput;
|
|
50
|
+
} | {
|
|
51
|
+
type: "error";
|
|
52
|
+
agent: string;
|
|
53
|
+
message: string;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Model adapter — the function the Runner calls to talk to an LLM.
|
|
57
|
+
*
|
|
58
|
+
* Implement once per provider. Three reference adapters ship in
|
|
59
|
+
* `examples/adapters/`: OpenRouter, OpenAI, Anthropic. Production users write
|
|
60
|
+
* their own adapter for their preferred endpoint (Vext-hosted Theron, AWS
|
|
61
|
+
* Bedrock, Cerebras, Groq, your own OSS endpoint).
|
|
62
|
+
*/
|
|
63
|
+
interface ModelAdapter {
|
|
64
|
+
name: string;
|
|
65
|
+
/** Chat completion. Streams deltas via the optional `onDelta` callback. */
|
|
66
|
+
chat(opts: {
|
|
67
|
+
model: string;
|
|
68
|
+
messages: Array<{
|
|
69
|
+
role: "system" | "user" | "assistant" | "tool";
|
|
70
|
+
content: string;
|
|
71
|
+
}>;
|
|
72
|
+
tools?: Array<{
|
|
73
|
+
name: string;
|
|
74
|
+
description: string;
|
|
75
|
+
input_schema: Record<string, unknown>;
|
|
76
|
+
}>;
|
|
77
|
+
max_tokens?: number;
|
|
78
|
+
temperature?: number;
|
|
79
|
+
onDelta?: (delta: string) => void;
|
|
80
|
+
}): Promise<{
|
|
81
|
+
content: string;
|
|
82
|
+
tool_calls?: Array<{
|
|
83
|
+
name: string;
|
|
84
|
+
input: unknown;
|
|
85
|
+
}>;
|
|
86
|
+
tokens: {
|
|
87
|
+
input: number;
|
|
88
|
+
output: number;
|
|
89
|
+
};
|
|
90
|
+
}>;
|
|
91
|
+
}
|
|
92
|
+
interface RunnerConfig {
|
|
93
|
+
/** The model adapter to use. */
|
|
94
|
+
model: ModelAdapter;
|
|
95
|
+
/** Default model identifier (e.g., "gpt-4o", "claude-3-5-sonnet"). */
|
|
96
|
+
default_model: string;
|
|
97
|
+
/** Optional Memory implementation. */
|
|
98
|
+
memory?: Memory;
|
|
99
|
+
/** Optional Session for event logging. */
|
|
100
|
+
session?: Session;
|
|
101
|
+
/** Optional tool execution context (cwd, yolo, tenant). */
|
|
102
|
+
tool_context?: ToolContext;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Runner — executes Agents and Councils.
|
|
106
|
+
*
|
|
107
|
+
* Minimal usage:
|
|
108
|
+
* const runner = new Runner({
|
|
109
|
+
* model: openrouterAdapter({ apiKey: process.env.OPENROUTER_API_KEY }),
|
|
110
|
+
* default_model: "openai/gpt-4o-mini",
|
|
111
|
+
* });
|
|
112
|
+
* const result = await runner.run(myAgent, "What's 2+2?");
|
|
113
|
+
*
|
|
114
|
+
* With a council:
|
|
115
|
+
* const result = await runner.runCouncil(myCouncil, "Review this PR");
|
|
116
|
+
*/
|
|
117
|
+
declare class Runner {
|
|
118
|
+
readonly model: ModelAdapter;
|
|
119
|
+
readonly default_model: string;
|
|
120
|
+
readonly memory: Memory | undefined;
|
|
121
|
+
readonly session: Session | undefined;
|
|
122
|
+
readonly tool_context: ToolContext;
|
|
123
|
+
private listeners;
|
|
124
|
+
constructor(config: RunnerConfig);
|
|
125
|
+
/** Subscribe to runner events for streaming UIs / observability. */
|
|
126
|
+
on(handler: (event: RunnerEvent) => void): () => void;
|
|
127
|
+
private emit;
|
|
128
|
+
/**
|
|
129
|
+
* Run a single agent on a query.
|
|
130
|
+
*
|
|
131
|
+
* Loop:
|
|
132
|
+
* 1. Send messages + tool schemas to the model
|
|
133
|
+
* 2. If model returns a tool call → execute the tool → append result to messages → repeat
|
|
134
|
+
* 3. If model returns content + end_turn → finalize
|
|
135
|
+
* 4. Run any registered verifier kernels on the final output
|
|
136
|
+
* 5. Return the AgentResult
|
|
137
|
+
*/
|
|
138
|
+
run(agent: Agent, query: string): Promise<AgentResult>;
|
|
139
|
+
/**
|
|
140
|
+
* Run a Council on a query.
|
|
141
|
+
*
|
|
142
|
+
* Fan out to all specialists in parallel (with timeout), gather outputs,
|
|
143
|
+
* run council-level verifier kernels on each, and reconcile.
|
|
144
|
+
*/
|
|
145
|
+
runCouncil(council: Council, query: string): Promise<CouncilOutput>;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export { type ModelAdapter, Runner, type RunnerConfig, type RunnerEvent };
|