@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,96 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/verifiers/index.ts
|
|
4
|
+
function defineVerifier(opts) {
|
|
5
|
+
return {
|
|
6
|
+
name: opts.name,
|
|
7
|
+
description: opts.description,
|
|
8
|
+
async check(output, context) {
|
|
9
|
+
const t0 = Date.now();
|
|
10
|
+
const result = await opts.check(output, context);
|
|
11
|
+
return { kernel: opts.name, pass: result.pass, issues: result.issues, ms: Date.now() - t0 };
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
var VerifierKernels = {
|
|
16
|
+
/** Block em-dashes (common AI tell). */
|
|
17
|
+
emDash: defineVerifier({
|
|
18
|
+
name: "em_dash_check",
|
|
19
|
+
description: "Block em-dashes in output (common AI tell).",
|
|
20
|
+
check: async (output) => {
|
|
21
|
+
const matches = [...output.matchAll(/—/g)];
|
|
22
|
+
const issues = matches.map((m) => ({
|
|
23
|
+
kernel: "em_dash_check",
|
|
24
|
+
severity: "error",
|
|
25
|
+
message: "Em-dash detected",
|
|
26
|
+
span: { start: m.index, end: m.index + 1 }
|
|
27
|
+
}));
|
|
28
|
+
return { pass: issues.length === 0, issues };
|
|
29
|
+
}
|
|
30
|
+
}),
|
|
31
|
+
/** Block common AI-ism words. Word-boundary aware to avoid false positives
|
|
32
|
+
* (e.g., "leverage" matches but not "delivered"). */
|
|
33
|
+
aiIsm: defineVerifier({
|
|
34
|
+
name: "ai_ism_check",
|
|
35
|
+
description: "Block common AI-ism words.",
|
|
36
|
+
check: async (output) => {
|
|
37
|
+
const aiisms = ["delve", "tapestry", "leverage", "robust", "seamless", "navigate", "embark"];
|
|
38
|
+
const issues = [];
|
|
39
|
+
for (const w of aiisms) {
|
|
40
|
+
const re = new RegExp(`\\b${w}\\b`, "gi");
|
|
41
|
+
if (re.test(output)) {
|
|
42
|
+
issues.push({
|
|
43
|
+
kernel: "ai_ism_check",
|
|
44
|
+
severity: "warning",
|
|
45
|
+
message: `AI-ism detected: "${w}"`
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { pass: issues.length === 0, issues };
|
|
50
|
+
}
|
|
51
|
+
}),
|
|
52
|
+
/** Re-evaluate arithmetic claims like "X op Y = Z". */
|
|
53
|
+
arithmetic: defineVerifier({
|
|
54
|
+
name: "arithmetic_recheck",
|
|
55
|
+
description: "Re-evaluate arithmetic in 'X op Y = Z' form.",
|
|
56
|
+
check: async (output) => {
|
|
57
|
+
const pattern = /(-?\d+(?:\.\d+)?)\s*([+\-*/])\s*(-?\d+(?:\.\d+)?)\s*=\s*(-?\d+(?:\.\d+)?)/g;
|
|
58
|
+
const issues = [];
|
|
59
|
+
for (const m of output.matchAll(pattern)) {
|
|
60
|
+
const a = parseFloat(m[1]);
|
|
61
|
+
const op = m[2];
|
|
62
|
+
const b = parseFloat(m[3]);
|
|
63
|
+
const claimed = parseFloat(m[4]);
|
|
64
|
+
const actual = op === "+" ? a + b : op === "-" ? a - b : op === "*" ? a * b : op === "/" ? b === 0 ? NaN : a / b : NaN;
|
|
65
|
+
if (!Number.isFinite(actual) || Math.abs(actual - claimed) > 1e-6) {
|
|
66
|
+
issues.push({
|
|
67
|
+
kernel: "arithmetic_recheck",
|
|
68
|
+
severity: "error",
|
|
69
|
+
message: `Claimed ${a} ${op} ${b} = ${claimed}; actual ${Number.isFinite(actual) ? actual : "undefined"}`
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { pass: issues.length === 0, issues };
|
|
74
|
+
}
|
|
75
|
+
}),
|
|
76
|
+
/** Require at least one citation pattern ([N], (Author Year), https://...). */
|
|
77
|
+
citationPresence: defineVerifier({
|
|
78
|
+
name: "citation_presence",
|
|
79
|
+
description: "Require at least one citation in output.",
|
|
80
|
+
check: async (output) => {
|
|
81
|
+
const patterns = [/\[\d+\]/, /\([A-Z][a-z]+(?: et al\.?)? \d{4}\)/, /https?:\/\//];
|
|
82
|
+
const found = patterns.some((p) => p.test(output));
|
|
83
|
+
const issues = found ? [] : [
|
|
84
|
+
{
|
|
85
|
+
kernel: "citation_presence",
|
|
86
|
+
severity: "error",
|
|
87
|
+
message: "No citation found. Expected one of: [N], (Author YEAR), or a URL."
|
|
88
|
+
}
|
|
89
|
+
];
|
|
90
|
+
return { pass: found, issues };
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
exports.VerifierKernels = VerifierKernels;
|
|
96
|
+
exports.defineVerifier = defineVerifier;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
interface VerifierIssue {
|
|
2
|
+
/** Which verifier kernel raised this. */
|
|
3
|
+
kernel: string;
|
|
4
|
+
/** Severity. */
|
|
5
|
+
severity: "error" | "warning" | "info";
|
|
6
|
+
/** Human-readable message. */
|
|
7
|
+
message: string;
|
|
8
|
+
/** Optional pointer to the offending span in the output. */
|
|
9
|
+
span?: {
|
|
10
|
+
start: number;
|
|
11
|
+
end: number;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
interface VerifierResult {
|
|
15
|
+
kernel: string;
|
|
16
|
+
pass: boolean;
|
|
17
|
+
issues: VerifierIssue[];
|
|
18
|
+
ms: number;
|
|
19
|
+
}
|
|
20
|
+
interface Verifier {
|
|
21
|
+
name: string;
|
|
22
|
+
description: string;
|
|
23
|
+
check(output: string, context?: unknown): Promise<VerifierResult>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* defineVerifier — ergonomic verifier factory.
|
|
27
|
+
*
|
|
28
|
+
* Example:
|
|
29
|
+
* const noEmDashes = defineVerifier({
|
|
30
|
+
* name: "em_dash_check",
|
|
31
|
+
* description: "Block em-dashes in user-facing copy.",
|
|
32
|
+
* check: async (output) => {
|
|
33
|
+
* const issues = [...output.matchAll(/—/g)].map((m) => ({
|
|
34
|
+
* kernel: "em_dash_check",
|
|
35
|
+
* severity: "error" as const,
|
|
36
|
+
* message: "Em-dash detected",
|
|
37
|
+
* span: { start: m.index!, end: m.index! + 1 },
|
|
38
|
+
* }));
|
|
39
|
+
* return { pass: issues.length === 0, issues };
|
|
40
|
+
* },
|
|
41
|
+
* });
|
|
42
|
+
*/
|
|
43
|
+
declare function defineVerifier(opts: {
|
|
44
|
+
name: string;
|
|
45
|
+
description: string;
|
|
46
|
+
check: (output: string, context?: unknown) => Promise<{
|
|
47
|
+
pass: boolean;
|
|
48
|
+
issues: VerifierIssue[];
|
|
49
|
+
}>;
|
|
50
|
+
}): Verifier;
|
|
51
|
+
declare const VerifierKernels: {
|
|
52
|
+
/** Block em-dashes (common AI tell). */
|
|
53
|
+
emDash: Verifier;
|
|
54
|
+
/** Block common AI-ism words. Word-boundary aware to avoid false positives
|
|
55
|
+
* (e.g., "leverage" matches but not "delivered"). */
|
|
56
|
+
aiIsm: Verifier;
|
|
57
|
+
/** Re-evaluate arithmetic claims like "X op Y = Z". */
|
|
58
|
+
arithmetic: Verifier;
|
|
59
|
+
/** Require at least one citation pattern ([N], (Author Year), https://...). */
|
|
60
|
+
citationPresence: Verifier;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export { type Verifier, type VerifierIssue, VerifierKernels, type VerifierResult, defineVerifier };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
interface VerifierIssue {
|
|
2
|
+
/** Which verifier kernel raised this. */
|
|
3
|
+
kernel: string;
|
|
4
|
+
/** Severity. */
|
|
5
|
+
severity: "error" | "warning" | "info";
|
|
6
|
+
/** Human-readable message. */
|
|
7
|
+
message: string;
|
|
8
|
+
/** Optional pointer to the offending span in the output. */
|
|
9
|
+
span?: {
|
|
10
|
+
start: number;
|
|
11
|
+
end: number;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
interface VerifierResult {
|
|
15
|
+
kernel: string;
|
|
16
|
+
pass: boolean;
|
|
17
|
+
issues: VerifierIssue[];
|
|
18
|
+
ms: number;
|
|
19
|
+
}
|
|
20
|
+
interface Verifier {
|
|
21
|
+
name: string;
|
|
22
|
+
description: string;
|
|
23
|
+
check(output: string, context?: unknown): Promise<VerifierResult>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* defineVerifier — ergonomic verifier factory.
|
|
27
|
+
*
|
|
28
|
+
* Example:
|
|
29
|
+
* const noEmDashes = defineVerifier({
|
|
30
|
+
* name: "em_dash_check",
|
|
31
|
+
* description: "Block em-dashes in user-facing copy.",
|
|
32
|
+
* check: async (output) => {
|
|
33
|
+
* const issues = [...output.matchAll(/—/g)].map((m) => ({
|
|
34
|
+
* kernel: "em_dash_check",
|
|
35
|
+
* severity: "error" as const,
|
|
36
|
+
* message: "Em-dash detected",
|
|
37
|
+
* span: { start: m.index!, end: m.index! + 1 },
|
|
38
|
+
* }));
|
|
39
|
+
* return { pass: issues.length === 0, issues };
|
|
40
|
+
* },
|
|
41
|
+
* });
|
|
42
|
+
*/
|
|
43
|
+
declare function defineVerifier(opts: {
|
|
44
|
+
name: string;
|
|
45
|
+
description: string;
|
|
46
|
+
check: (output: string, context?: unknown) => Promise<{
|
|
47
|
+
pass: boolean;
|
|
48
|
+
issues: VerifierIssue[];
|
|
49
|
+
}>;
|
|
50
|
+
}): Verifier;
|
|
51
|
+
declare const VerifierKernels: {
|
|
52
|
+
/** Block em-dashes (common AI tell). */
|
|
53
|
+
emDash: Verifier;
|
|
54
|
+
/** Block common AI-ism words. Word-boundary aware to avoid false positives
|
|
55
|
+
* (e.g., "leverage" matches but not "delivered"). */
|
|
56
|
+
aiIsm: Verifier;
|
|
57
|
+
/** Re-evaluate arithmetic claims like "X op Y = Z". */
|
|
58
|
+
arithmetic: Verifier;
|
|
59
|
+
/** Require at least one citation pattern ([N], (Author Year), https://...). */
|
|
60
|
+
citationPresence: Verifier;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export { type Verifier, type VerifierIssue, VerifierKernels, type VerifierResult, defineVerifier };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// src/verifiers/index.ts
|
|
2
|
+
function defineVerifier(opts) {
|
|
3
|
+
return {
|
|
4
|
+
name: opts.name,
|
|
5
|
+
description: opts.description,
|
|
6
|
+
async check(output, context) {
|
|
7
|
+
const t0 = Date.now();
|
|
8
|
+
const result = await opts.check(output, context);
|
|
9
|
+
return { kernel: opts.name, pass: result.pass, issues: result.issues, ms: Date.now() - t0 };
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
var VerifierKernels = {
|
|
14
|
+
/** Block em-dashes (common AI tell). */
|
|
15
|
+
emDash: defineVerifier({
|
|
16
|
+
name: "em_dash_check",
|
|
17
|
+
description: "Block em-dashes in output (common AI tell).",
|
|
18
|
+
check: async (output) => {
|
|
19
|
+
const matches = [...output.matchAll(/—/g)];
|
|
20
|
+
const issues = matches.map((m) => ({
|
|
21
|
+
kernel: "em_dash_check",
|
|
22
|
+
severity: "error",
|
|
23
|
+
message: "Em-dash detected",
|
|
24
|
+
span: { start: m.index, end: m.index + 1 }
|
|
25
|
+
}));
|
|
26
|
+
return { pass: issues.length === 0, issues };
|
|
27
|
+
}
|
|
28
|
+
}),
|
|
29
|
+
/** Block common AI-ism words. Word-boundary aware to avoid false positives
|
|
30
|
+
* (e.g., "leverage" matches but not "delivered"). */
|
|
31
|
+
aiIsm: defineVerifier({
|
|
32
|
+
name: "ai_ism_check",
|
|
33
|
+
description: "Block common AI-ism words.",
|
|
34
|
+
check: async (output) => {
|
|
35
|
+
const aiisms = ["delve", "tapestry", "leverage", "robust", "seamless", "navigate", "embark"];
|
|
36
|
+
const issues = [];
|
|
37
|
+
for (const w of aiisms) {
|
|
38
|
+
const re = new RegExp(`\\b${w}\\b`, "gi");
|
|
39
|
+
if (re.test(output)) {
|
|
40
|
+
issues.push({
|
|
41
|
+
kernel: "ai_ism_check",
|
|
42
|
+
severity: "warning",
|
|
43
|
+
message: `AI-ism detected: "${w}"`
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return { pass: issues.length === 0, issues };
|
|
48
|
+
}
|
|
49
|
+
}),
|
|
50
|
+
/** Re-evaluate arithmetic claims like "X op Y = Z". */
|
|
51
|
+
arithmetic: defineVerifier({
|
|
52
|
+
name: "arithmetic_recheck",
|
|
53
|
+
description: "Re-evaluate arithmetic in 'X op Y = Z' form.",
|
|
54
|
+
check: async (output) => {
|
|
55
|
+
const pattern = /(-?\d+(?:\.\d+)?)\s*([+\-*/])\s*(-?\d+(?:\.\d+)?)\s*=\s*(-?\d+(?:\.\d+)?)/g;
|
|
56
|
+
const issues = [];
|
|
57
|
+
for (const m of output.matchAll(pattern)) {
|
|
58
|
+
const a = parseFloat(m[1]);
|
|
59
|
+
const op = m[2];
|
|
60
|
+
const b = parseFloat(m[3]);
|
|
61
|
+
const claimed = parseFloat(m[4]);
|
|
62
|
+
const actual = op === "+" ? a + b : op === "-" ? a - b : op === "*" ? a * b : op === "/" ? b === 0 ? NaN : a / b : NaN;
|
|
63
|
+
if (!Number.isFinite(actual) || Math.abs(actual - claimed) > 1e-6) {
|
|
64
|
+
issues.push({
|
|
65
|
+
kernel: "arithmetic_recheck",
|
|
66
|
+
severity: "error",
|
|
67
|
+
message: `Claimed ${a} ${op} ${b} = ${claimed}; actual ${Number.isFinite(actual) ? actual : "undefined"}`
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { pass: issues.length === 0, issues };
|
|
72
|
+
}
|
|
73
|
+
}),
|
|
74
|
+
/** Require at least one citation pattern ([N], (Author Year), https://...). */
|
|
75
|
+
citationPresence: defineVerifier({
|
|
76
|
+
name: "citation_presence",
|
|
77
|
+
description: "Require at least one citation in output.",
|
|
78
|
+
check: async (output) => {
|
|
79
|
+
const patterns = [/\[\d+\]/, /\([A-Z][a-z]+(?: et al\.?)? \d{4}\)/, /https?:\/\//];
|
|
80
|
+
const found = patterns.some((p) => p.test(output));
|
|
81
|
+
const issues = found ? [] : [
|
|
82
|
+
{
|
|
83
|
+
kernel: "citation_presence",
|
|
84
|
+
severity: "error",
|
|
85
|
+
message: "No citation found. Expected one of: [N], (Author YEAR), or a URL."
|
|
86
|
+
}
|
|
87
|
+
];
|
|
88
|
+
return { pass: found, issues };
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export { VerifierKernels, defineVerifier };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sample agent 01: Code Reviewer
|
|
3
|
+
*
|
|
4
|
+
* Reviews a diff and produces structured feedback. Works against any
|
|
5
|
+
* OpenRouter-compatible model — no Vext API key needed.
|
|
6
|
+
*
|
|
7
|
+
* Run:
|
|
8
|
+
* OPENROUTER_API_KEY=sk-or-... npx tsx examples/01_code_reviewer.ts
|
|
9
|
+
*
|
|
10
|
+
* What this demonstrates:
|
|
11
|
+
* - The 5-line agent pattern (Agent + tools + Runner)
|
|
12
|
+
* - Tool definition via defineTool + Zod schema
|
|
13
|
+
* - Streaming via runner.on("agent_thinking", ...)
|
|
14
|
+
* - Verifier kernels gating the final output (no extra LLM cost)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { Agent, Runner, defineTool, zod as z, VerifierKernels } from "../src/index.js";
|
|
18
|
+
import { openrouterAdapter } from "./adapters/openrouter.js";
|
|
19
|
+
|
|
20
|
+
const readDiff = defineTool({
|
|
21
|
+
name: "read_diff",
|
|
22
|
+
description: "Read the unified-diff of a PR. For demo: returns a fixed diff.",
|
|
23
|
+
input: z.object({ pr_url: z.string().url() }),
|
|
24
|
+
async execute({ pr_url }) {
|
|
25
|
+
// In production: fetch the diff from GitHub API.
|
|
26
|
+
// For the sample: return a fixed diff that contains a deliberate
|
|
27
|
+
// SQL-injection regression the reviewer should catch.
|
|
28
|
+
return {
|
|
29
|
+
pr_url,
|
|
30
|
+
diff: `
|
|
31
|
+
--- a/src/auth.ts
|
|
32
|
+
+++ b/src/auth.ts
|
|
33
|
+
@@ -10,7 +10,7 @@ export async function login(username: string, password: string) {
|
|
34
|
+
- const user = await db.users.findOne({ username });
|
|
35
|
+
+ const user = await db.query(\`SELECT * FROM users WHERE username = '\${username}'\`);
|
|
36
|
+
if (!user) return null;
|
|
37
|
+
if (user.password === password) return user;
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
`.trim(),
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const reviewer = new Agent({
|
|
46
|
+
name: "code-reviewer",
|
|
47
|
+
instruction: `You are a senior code reviewer. Read the diff and identify:
|
|
48
|
+
1. Security issues (SQL injection, auth bypass, etc.) — severity HIGH if present
|
|
49
|
+
2. Bug risks (null deref, race conditions, etc.) — severity MEDIUM
|
|
50
|
+
3. Style or clarity issues — severity LOW
|
|
51
|
+
|
|
52
|
+
Output a structured review with file:line references and one-line explanations per issue.
|
|
53
|
+
Do not use em-dashes or AI-ism words.`,
|
|
54
|
+
tools: [readDiff],
|
|
55
|
+
verifiers: [VerifierKernels.emDash, VerifierKernels.aiIsm],
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
async function main() {
|
|
59
|
+
const apiKey = process.env.OPENROUTER_API_KEY;
|
|
60
|
+
if (!apiKey) {
|
|
61
|
+
console.error("Set OPENROUTER_API_KEY (https://openrouter.ai/keys) and rerun.");
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const runner = new Runner({
|
|
66
|
+
model: openrouterAdapter({ apiKey }),
|
|
67
|
+
default_model: "openai/gpt-4o-mini",
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
runner.on((event) => {
|
|
71
|
+
if (event.type === "agent_thinking") process.stdout.write(event.delta);
|
|
72
|
+
if (event.type === "tool_call_start") console.log(`\n→ ${event.tool}(${JSON.stringify(event.input)})`);
|
|
73
|
+
if (event.type === "agent_output") console.log(`\n\n=== Review ===\n${event.output}`);
|
|
74
|
+
if (event.type === "verifier_run" && !event.result.pass) {
|
|
75
|
+
console.log(`\n[verifier ${event.kernel}] ${event.result.issues.length} issue(s)`);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const result = await runner.run(reviewer, "Review https://github.com/example/repo/pull/42");
|
|
80
|
+
const failed = result.verifier_results.filter((v) => !v.pass);
|
|
81
|
+
if (failed.length > 0) {
|
|
82
|
+
console.log(`\nVerifier failures: ${failed.map((v) => v.kernel).join(", ")}`);
|
|
83
|
+
process.exitCode = 1;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
main().catch((err) => {
|
|
88
|
+
console.error(err);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sample agent 02: Research Assistant
|
|
3
|
+
*
|
|
4
|
+
* Answers a research question with web-fetched citations. Output is gated
|
|
5
|
+
* by the citation_presence verifier kernel.
|
|
6
|
+
*
|
|
7
|
+
* Run:
|
|
8
|
+
* OPENROUTER_API_KEY=sk-or-... npx tsx examples/02_research_assistant.ts
|
|
9
|
+
*
|
|
10
|
+
* What this demonstrates:
|
|
11
|
+
* - Verifier kernel composition (citation_presence)
|
|
12
|
+
* - Sub-tools that take web actions
|
|
13
|
+
* - Free verifier guarantees (no LLM "judge" cost; pure regex check)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { Agent, Runner, defineTool, zod as z, VerifierKernels } from "../src/index.js";
|
|
17
|
+
import { openrouterAdapter } from "./adapters/openrouter.js";
|
|
18
|
+
|
|
19
|
+
const webSearch = defineTool({
|
|
20
|
+
name: "web_search",
|
|
21
|
+
description: "Search the web. Returns titles + URLs + snippets.",
|
|
22
|
+
input: z.object({ query: z.string() }),
|
|
23
|
+
async execute({ query }) {
|
|
24
|
+
// In production: call a real search API (Tavily, Serper, etc.).
|
|
25
|
+
// For the sample: return fixed mock results.
|
|
26
|
+
return {
|
|
27
|
+
query,
|
|
28
|
+
results: [
|
|
29
|
+
{
|
|
30
|
+
title: "Chinchilla scaling laws",
|
|
31
|
+
url: "https://arxiv.org/abs/2203.15556",
|
|
32
|
+
snippet: "Hoffmann et al. 2022 — compute-optimal model + token ratio.",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
title: "Scaling Laws for Neural Language Models",
|
|
36
|
+
url: "https://arxiv.org/abs/2001.08361",
|
|
37
|
+
snippet: "Kaplan et al. 2020 — original power-law claims.",
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const researcher = new Agent({
|
|
45
|
+
name: "research-assistant",
|
|
46
|
+
instruction: `You are a careful research assistant. Answer the user's question
|
|
47
|
+
with citations. Every factual claim must reference a source from web_search.
|
|
48
|
+
Format citations inline as [N] with a numbered references list at the end.`,
|
|
49
|
+
tools: [webSearch],
|
|
50
|
+
verifiers: [VerifierKernels.citationPresence],
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
async function main() {
|
|
54
|
+
const apiKey = process.env.OPENROUTER_API_KEY;
|
|
55
|
+
if (!apiKey) {
|
|
56
|
+
console.error("Set OPENROUTER_API_KEY (https://openrouter.ai/keys) and rerun.");
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const runner = new Runner({
|
|
61
|
+
model: openrouterAdapter({ apiKey }),
|
|
62
|
+
default_model: "openai/gpt-4o-mini",
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const result = await runner.run(
|
|
66
|
+
researcher,
|
|
67
|
+
"Did the Chinchilla paper overturn the 2020 scaling laws? Cite your sources.",
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
console.log("\n=== Answer ===");
|
|
71
|
+
console.log(result.output);
|
|
72
|
+
|
|
73
|
+
// result.verifier_results already includes the citation_presence outcome
|
|
74
|
+
// (the Runner ran every verifier on the agent). This is just for display.
|
|
75
|
+
for (const v of result.verifier_results) {
|
|
76
|
+
console.log(`\n=== Verifier: ${v.kernel} ===`);
|
|
77
|
+
console.log(`Pass: ${v.pass}`);
|
|
78
|
+
if (!v.pass) console.log("Issues:", v.issues);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
main().catch((err) => {
|
|
83
|
+
console.error(err);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sample agent 03: Council of Three — deliberation with verifier kernels.
|
|
3
|
+
*
|
|
4
|
+
* Three generic agents (Engineer, Security, Product) deliberate on a question.
|
|
5
|
+
* Outputs are checked by verifier kernels before reconciliation.
|
|
6
|
+
*
|
|
7
|
+
* This is the 15-line Council pattern — the Theron Agent SDK moat in a single
|
|
8
|
+
* example.
|
|
9
|
+
*
|
|
10
|
+
* Run:
|
|
11
|
+
* OPENROUTER_API_KEY=sk-or-... npx tsx examples/03_council_of_three.ts
|
|
12
|
+
*
|
|
13
|
+
* What this demonstrates:
|
|
14
|
+
* - Council primitive (the flagship Theron Agent SDK feature)
|
|
15
|
+
* - Multi-specialist deliberation
|
|
16
|
+
* - Verifier kernels applied across the council
|
|
17
|
+
* - Reconciler (deterministic claim-merge by default)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { Agent, Council, Runner, VerifierKernels } from "../src/index.js";
|
|
21
|
+
import { openrouterAdapter } from "./adapters/openrouter.js";
|
|
22
|
+
|
|
23
|
+
const engineer = new Agent({
|
|
24
|
+
name: "engineer",
|
|
25
|
+
instruction: `You are a senior backend engineer. Answer from a system-design + reliability perspective.
|
|
26
|
+
Be specific about trade-offs. Cite RFCs / specs / public benchmarks where applicable.
|
|
27
|
+
Do not use em-dashes or AI-ism words.`,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const security = new Agent({
|
|
31
|
+
name: "security",
|
|
32
|
+
instruction: `You are an application-security engineer. Answer from a threat-model + attack-surface perspective.
|
|
33
|
+
Flag anything that could be exploited. Cite OWASP / CWE / CVE where applicable.
|
|
34
|
+
Do not use em-dashes or AI-ism words.`,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const product = new Agent({
|
|
38
|
+
name: "product",
|
|
39
|
+
instruction: `You are a product manager. Answer from a user-impact + adoption perspective.
|
|
40
|
+
Be specific about who this helps, who it doesn't, and what could backfire.
|
|
41
|
+
Do not use em-dashes or AI-ism words.`,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const council = new Council({
|
|
45
|
+
name: "engineering-review",
|
|
46
|
+
specialists: [engineer, security, product],
|
|
47
|
+
verifiers: [VerifierKernels.emDash, VerifierKernels.aiIsm],
|
|
48
|
+
// No reconciler specified → defaults to deterministic claim-merge.
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
async function main() {
|
|
52
|
+
const apiKey = process.env.OPENROUTER_API_KEY;
|
|
53
|
+
if (!apiKey) {
|
|
54
|
+
console.error("Set OPENROUTER_API_KEY (https://openrouter.ai/keys) and rerun.");
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const runner = new Runner({
|
|
59
|
+
model: openrouterAdapter({ apiKey }),
|
|
60
|
+
default_model: "openai/gpt-4o-mini",
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
runner.on((event) => {
|
|
64
|
+
if (event.type === "specialist_done") {
|
|
65
|
+
console.log(`\n--- ${event.specialist} ---`);
|
|
66
|
+
console.log(event.output.output.slice(0, 300) + "...");
|
|
67
|
+
}
|
|
68
|
+
if (event.type === "council_done") {
|
|
69
|
+
console.log(`\n=== Council answer (${event.output.consensus}) ===`);
|
|
70
|
+
console.log(event.output.answer);
|
|
71
|
+
if (event.output.disagreements && event.output.disagreements.length > 0) {
|
|
72
|
+
console.log(`\n=== Disagreements ===`);
|
|
73
|
+
for (const d of event.output.disagreements) {
|
|
74
|
+
console.log(`Claim: "${d.claim}"`);
|
|
75
|
+
console.log(` for: ${d.specialists_for.join(", ")}`);
|
|
76
|
+
console.log(` against: ${d.specialists_against.join(", ")}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
await runner.runCouncil(
|
|
83
|
+
council,
|
|
84
|
+
"Should we let users store API keys in localStorage instead of a cookie?",
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
main().catch((err) => {
|
|
89
|
+
console.error(err);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenRouter ModelAdapter — works against 200+ models for free-tier users.
|
|
3
|
+
*
|
|
4
|
+
* Used by the sample agents in the SDK. Production users should write their
|
|
5
|
+
* own adapter for their preferred provider (OpenAI direct, Anthropic, Vext
|
|
6
|
+
* managed Theron, etc.).
|
|
7
|
+
*/
|
|
8
|
+
import type { ModelAdapter } from "../../src/runtime/index.js";
|
|
9
|
+
|
|
10
|
+
export function openrouterAdapter(opts: { apiKey: string; siteName?: string; siteUrl?: string }): ModelAdapter {
|
|
11
|
+
return {
|
|
12
|
+
name: "openrouter",
|
|
13
|
+
async chat({ model, messages, tools, max_tokens, temperature, onDelta }) {
|
|
14
|
+
const body: Record<string, unknown> = {
|
|
15
|
+
model,
|
|
16
|
+
messages,
|
|
17
|
+
max_tokens: max_tokens ?? 2048,
|
|
18
|
+
temperature: temperature ?? 0.2,
|
|
19
|
+
stream: !!onDelta,
|
|
20
|
+
};
|
|
21
|
+
if (tools && tools.length > 0) {
|
|
22
|
+
body.tools = tools.map((t) => ({
|
|
23
|
+
type: "function",
|
|
24
|
+
function: { name: t.name, description: t.description, parameters: t.input_schema },
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: {
|
|
30
|
+
Authorization: `Bearer ${opts.apiKey}`,
|
|
31
|
+
"Content-Type": "application/json",
|
|
32
|
+
...(opts.siteUrl ? { "HTTP-Referer": opts.siteUrl } : {}),
|
|
33
|
+
...(opts.siteName ? { "X-Title": opts.siteName } : {}),
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify(body),
|
|
36
|
+
});
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
throw new Error(`OpenRouter ${res.status}: ${await res.text()}`);
|
|
39
|
+
}
|
|
40
|
+
if (onDelta && res.body) {
|
|
41
|
+
// Streaming path — parse SSE
|
|
42
|
+
const reader = res.body.getReader();
|
|
43
|
+
const decoder = new TextDecoder();
|
|
44
|
+
let content = "";
|
|
45
|
+
let inputTokens = 0;
|
|
46
|
+
let outputTokens = 0;
|
|
47
|
+
while (true) {
|
|
48
|
+
const { value, done } = await reader.read();
|
|
49
|
+
if (done) break;
|
|
50
|
+
const chunk = decoder.decode(value);
|
|
51
|
+
for (const line of chunk.split("\n")) {
|
|
52
|
+
if (!line.startsWith("data: ")) continue;
|
|
53
|
+
const data = line.slice(6).trim();
|
|
54
|
+
if (data === "[DONE]") continue;
|
|
55
|
+
try {
|
|
56
|
+
const json = JSON.parse(data);
|
|
57
|
+
const delta = json.choices?.[0]?.delta?.content;
|
|
58
|
+
if (delta) {
|
|
59
|
+
onDelta(delta);
|
|
60
|
+
content += delta;
|
|
61
|
+
}
|
|
62
|
+
if (json.usage) {
|
|
63
|
+
inputTokens = json.usage.prompt_tokens ?? inputTokens;
|
|
64
|
+
outputTokens = json.usage.completion_tokens ?? outputTokens;
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// ignore malformed SSE lines
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { content, tokens: { input: inputTokens, output: outputTokens } };
|
|
72
|
+
}
|
|
73
|
+
// Non-streaming path
|
|
74
|
+
const json = await res.json() as {
|
|
75
|
+
choices: Array<{ message: { content: string; tool_calls?: Array<{ function: { name: string; arguments: string } }> } }>;
|
|
76
|
+
usage: { prompt_tokens: number; completion_tokens: number };
|
|
77
|
+
};
|
|
78
|
+
const msg = json.choices[0].message;
|
|
79
|
+
const tool_calls = msg.tool_calls?.map((tc) => ({
|
|
80
|
+
name: tc.function.name,
|
|
81
|
+
input: JSON.parse(tc.function.arguments || "{}"),
|
|
82
|
+
}));
|
|
83
|
+
return {
|
|
84
|
+
content: msg.content ?? "",
|
|
85
|
+
tool_calls,
|
|
86
|
+
tokens: { input: json.usage.prompt_tokens, output: json.usage.completion_tokens },
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|