@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,95 @@
1
+ // src/loop/index.ts
2
+ function stepCountIs(n) {
3
+ return (s) => s.step >= n;
4
+ }
5
+ function costUsdAtLeast(min) {
6
+ return (s) => s.cost_usd >= min;
7
+ }
8
+ function verifierSatisfied(kernelName) {
9
+ return (s) => {
10
+ if (!s.verifier_results) return false;
11
+ return s.verifier_results.some((r) => r.kernel === kernelName && r.pass === true);
12
+ };
13
+ }
14
+ function anyOf(...preds) {
15
+ return (s) => preds.some((p) => p(s));
16
+ }
17
+ function allOf(...preds) {
18
+ return (s) => preds.every((p) => p(s));
19
+ }
20
+ function verifiedRatchet(opts) {
21
+ const threshold = opts?.minConfidence ?? 0.6;
22
+ return (v) => {
23
+ if (v === void 0) {
24
+ return { advance: false, reason: "no verdict" };
25
+ }
26
+ if (v.verdict !== "sufficient") {
27
+ return {
28
+ advance: false,
29
+ reason: `verdict '${v.verdict}' is not 'sufficient'${v.source ? ` (source: ${v.source})` : ""}`
30
+ };
31
+ }
32
+ if (v.confidence < threshold) {
33
+ return {
34
+ advance: false,
35
+ reason: `verdict is 'sufficient' but confidence ${v.confidence.toFixed(3)} < threshold ${threshold.toFixed(3)}`
36
+ };
37
+ }
38
+ return {
39
+ advance: true,
40
+ reason: `verified sufficient at confidence ${v.confidence.toFixed(3)} >= ${threshold.toFixed(3)}${v.source ? ` (source: ${v.source})` : ""}`
41
+ };
42
+ };
43
+ }
44
+ async function runImprovementCycle(spec) {
45
+ const ratchet = spec.ratchet ?? verifiedRatchet();
46
+ const proposal = await spec.propose();
47
+ const trial = await spec.trial(proposal);
48
+ const verdict = await spec.verify(proposal, trial);
49
+ const decision = ratchet(verdict);
50
+ return { proposal, trial, verdict, decision };
51
+ }
52
+ var SUMMARY_PREFIX = "[Summary of earlier conversation]";
53
+ async function compactHistory(opts) {
54
+ const keepRecent = Math.max(1, Math.floor(opts.keepRecent ?? 6));
55
+ const msgs = opts.messages ?? [];
56
+ const totalChars = msgs.reduce((n, m) => n + (m.content?.length ?? 0), 0);
57
+ const maxChars = opts.maxChars ?? 0;
58
+ if (msgs.length <= keepRecent || maxChars > 0 && totalChars <= maxChars) {
59
+ return { messages: msgs, compacted: false, droppedCount: 0 };
60
+ }
61
+ const older = msgs.slice(0, msgs.length - keepRecent);
62
+ const recent = msgs.slice(msgs.length - keepRecent);
63
+ const summary = String(await opts.summarize(older));
64
+ return {
65
+ messages: [{ role: "system", content: `${SUMMARY_PREFIX}
66
+ ${summary}` }, ...recent],
67
+ compacted: true,
68
+ summary,
69
+ droppedCount: older.length
70
+ };
71
+ }
72
+ async function runUntil(opts) {
73
+ const maxSteps = Math.max(1, Math.floor(opts.maxSteps ?? 100));
74
+ let state = opts.initial;
75
+ for (let i = 0; i < maxSteps; i++) {
76
+ state = await opts.step(state, i);
77
+ if (opts.onCheckpoint) await opts.onCheckpoint(state, i);
78
+ if (opts.stopWhen(state, i)) return { state, steps: i + 1, stopped: "predicate" };
79
+ }
80
+ return { state, steps: maxSteps, stopped: "maxSteps" };
81
+ }
82
+ function boundWorkingSet(items, max) {
83
+ const cap = Math.max(0, Math.floor(max));
84
+ if (items.length <= cap) return { kept: items, evicted: [] };
85
+ const pinned = items.filter((i) => i.pinned);
86
+ const rest = items.filter((i) => !i.pinned);
87
+ const slots = Math.max(0, cap - pinned.length);
88
+ const ranked = [...rest].sort((a, b) => (b.importance ?? 0) - (a.importance ?? 0) || b.seq - a.seq);
89
+ const keepRest = new Set(ranked.slice(0, slots));
90
+ const kept = items.filter((i) => i.pinned || keepRest.has(i));
91
+ const evicted = items.filter((i) => !i.pinned && !keepRest.has(i));
92
+ return { kept, evicted };
93
+ }
94
+
95
+ export { allOf, anyOf, boundWorkingSet, compactHistory, costUsdAtLeast, runImprovementCycle, runUntil, stepCountIs, verifiedRatchet, verifierSatisfied };
@@ -0,0 +1,153 @@
1
+ 'use strict';
2
+
3
+ // src/mcp/index.ts
4
+ var MCP_PROTOCOL_VERSION = "2024-11-05";
5
+ var DEFAULT_TIMEOUT_MS = 12e3;
6
+ var MCPClient = class {
7
+ config;
8
+ initialized = false;
9
+ toolCache = null;
10
+ constructor(config) {
11
+ if (!/^[a-z0-9_-]+$/.test(config.slug)) {
12
+ throw new Error(
13
+ `MCPClient slug "${config.slug}" must match /^[a-z0-9_-]+$/`
14
+ );
15
+ }
16
+ this.config = { timeout_ms: DEFAULT_TIMEOUT_MS, ...config };
17
+ }
18
+ /** Fetch the server's tool catalog. Cached for the lifetime of the client. */
19
+ async listTools(signal) {
20
+ if (this.toolCache) return this.toolCache;
21
+ await this.ensureInitialized(signal);
22
+ const result = await this.rpc("tools/list", {}, signal);
23
+ this.toolCache = Array.isArray(result?.tools) ? result.tools : [];
24
+ return this.toolCache;
25
+ }
26
+ /** Call a tool by its bare (non-namespaced) name. Returns the text payload. */
27
+ async callTool(name, args, signal) {
28
+ await this.ensureInitialized(signal);
29
+ const result = await this.rpc(
30
+ "tools/call",
31
+ { name, arguments: args },
32
+ signal
33
+ );
34
+ const blocks = Array.isArray(result?.content) ? result.content : [];
35
+ const text = blocks.map((b) => b.type === "text" && typeof b.text === "string" ? b.text : "").filter(Boolean).join("\n").trim();
36
+ return text || JSON.stringify(result ?? {}).slice(0, 4e3);
37
+ }
38
+ /**
39
+ * Return the server's tool catalog as SDK Tool[] objects, namespaced so they
40
+ * compose with tools from other sources without colliding.
41
+ *
42
+ * Pass these directly to a Runner. The Runner sees standard SDK tools; it
43
+ * never has to know MCP existed.
44
+ */
45
+ async asTools(signal) {
46
+ const raw = await this.listTools(signal);
47
+ return raw.map((t) => this.toSdkTool(t));
48
+ }
49
+ /** Health-check: returns toolCount on success, throws on failure. */
50
+ async probe(signal) {
51
+ const tools = await this.listTools(signal);
52
+ return { toolCount: tools.length };
53
+ }
54
+ // -------------------------------------------------------------- internals
55
+ toSdkTool(raw) {
56
+ const ns = `${this.config.slug}__${raw.name}`.slice(0, 64).replace(/[^a-zA-Z0-9_-]/g, "_");
57
+ const schema = {
58
+ name: ns,
59
+ description: `[${this.config.name}] ${raw.description || raw.name}`,
60
+ input_schema: raw.inputSchema ?? {
61
+ type: "object",
62
+ properties: {}
63
+ }
64
+ };
65
+ const client = this;
66
+ return {
67
+ schema,
68
+ async execute(input, _ctx) {
69
+ return client.callTool(raw.name, input);
70
+ }
71
+ };
72
+ }
73
+ async ensureInitialized(signal) {
74
+ if (this.initialized) return;
75
+ await this.rpc(
76
+ "initialize",
77
+ {
78
+ protocolVersion: MCP_PROTOCOL_VERSION,
79
+ capabilities: {},
80
+ clientInfo: { name: "theron-agent-sdk", version: "0.1" }
81
+ },
82
+ signal
83
+ );
84
+ this.rpc("notifications/initialized", {}, signal).catch(() => void 0);
85
+ this.initialized = true;
86
+ }
87
+ async rpc(method, params, externalSignal) {
88
+ const ac = new AbortController();
89
+ const timer = setTimeout(
90
+ () => ac.abort(),
91
+ this.config.timeout_ms ?? DEFAULT_TIMEOUT_MS
92
+ );
93
+ if (externalSignal) {
94
+ if (externalSignal.aborted) ac.abort();
95
+ else externalSignal.addEventListener("abort", () => ac.abort());
96
+ }
97
+ const body = {
98
+ jsonrpc: "2.0",
99
+ id: Date.now() + Math.floor(Math.random() * 1e3),
100
+ method,
101
+ params
102
+ };
103
+ try {
104
+ const r = await fetch(this.config.url, {
105
+ method: "POST",
106
+ signal: ac.signal,
107
+ headers: {
108
+ "Content-Type": "application/json",
109
+ Accept: "application/json, text/event-stream",
110
+ ...this.config.token ? { Authorization: `Bearer ${this.config.token}` } : {},
111
+ "MCP-Protocol-Version": MCP_PROTOCOL_VERSION
112
+ },
113
+ body: JSON.stringify(body)
114
+ });
115
+ if (!r.ok) {
116
+ const errText = await r.text().catch(() => "");
117
+ throw new Error(
118
+ `mcp http ${r.status} on ${method}: ${errText.slice(0, 200)}`
119
+ );
120
+ }
121
+ const ct = r.headers.get("content-type") || "";
122
+ if (ct.includes("text/event-stream")) {
123
+ const text = await r.text();
124
+ const m = text.match(/data:\s*(\{[\s\S]*?\})\s*\n/);
125
+ if (!m) throw new Error("mcp sse stream had no data event");
126
+ const env2 = JSON.parse(m[1]);
127
+ if (env2.error) throw new Error(`mcp error: ${env2.error.message}`);
128
+ return env2.result;
129
+ }
130
+ const env = await r.json();
131
+ if (env.error) throw new Error(`mcp error: ${env.error.message}`);
132
+ return env.result;
133
+ } finally {
134
+ clearTimeout(timer);
135
+ }
136
+ }
137
+ };
138
+ async function collectMcpTools(clients, signal) {
139
+ const lists = await Promise.all(
140
+ clients.map(async (c) => {
141
+ try {
142
+ return await c.asTools(signal);
143
+ } catch (err) {
144
+ console.warn(`[mcp] ${c.config.slug} listTools failed:`, err);
145
+ return [];
146
+ }
147
+ })
148
+ );
149
+ return lists.flat();
150
+ }
151
+
152
+ exports.MCPClient = MCPClient;
153
+ exports.collectMcpTools = collectMcpTools;
@@ -0,0 +1,69 @@
1
+ import { Tool } from '../tools/index.cjs';
2
+ import 'zod';
3
+
4
+ interface McpServerConfig {
5
+ /** Display label ("Linear", "GitHub", ...). */
6
+ name: string;
7
+ /** Stable slug used in the namespaced tool name (matches /^[a-z0-9_-]+$/). */
8
+ slug: string;
9
+ /** Streamable-HTTP MCP server URL. */
10
+ url: string;
11
+ /** Optional bearer token; sent as `Authorization: Bearer <token>`. */
12
+ token?: string;
13
+ /** Per-call timeout. Default 12s. */
14
+ timeout_ms?: number;
15
+ }
16
+ interface McpTool {
17
+ name: string;
18
+ description?: string;
19
+ inputSchema?: Record<string, unknown>;
20
+ }
21
+ /**
22
+ * MCPClient — one instance per connected MCP server.
23
+ *
24
+ * Example:
25
+ * const client = new MCPClient({
26
+ * name: "Linear",
27
+ * slug: "linear",
28
+ * url: "https://mcp.linear.app/sse",
29
+ * token: process.env.LINEAR_MCP_TOKEN,
30
+ * });
31
+ * const tools = await client.asTools();
32
+ * // tools[0].schema.name === "linear__create_issue"
33
+ * await runner.run(agent, { query: "open a ticket about X", tools });
34
+ */
35
+ declare class MCPClient {
36
+ readonly config: McpServerConfig;
37
+ private initialized;
38
+ private toolCache;
39
+ constructor(config: McpServerConfig);
40
+ /** Fetch the server's tool catalog. Cached for the lifetime of the client. */
41
+ listTools(signal?: AbortSignal): Promise<McpTool[]>;
42
+ /** Call a tool by its bare (non-namespaced) name. Returns the text payload. */
43
+ callTool(name: string, args: unknown, signal?: AbortSignal): Promise<string>;
44
+ /**
45
+ * Return the server's tool catalog as SDK Tool[] objects, namespaced so they
46
+ * compose with tools from other sources without colliding.
47
+ *
48
+ * Pass these directly to a Runner. The Runner sees standard SDK tools; it
49
+ * never has to know MCP existed.
50
+ */
51
+ asTools(signal?: AbortSignal): Promise<Tool[]>;
52
+ /** Health-check: returns toolCount on success, throws on failure. */
53
+ probe(signal?: AbortSignal): Promise<{
54
+ toolCount: number;
55
+ }>;
56
+ private toSdkTool;
57
+ private ensureInitialized;
58
+ private rpc;
59
+ }
60
+ /**
61
+ * Combine multiple MCP clients into a single namespaced Tool[]. Use when an
62
+ * agent should see every tool from every connected server in one call.
63
+ *
64
+ * const tools = await collectMcpTools([github, postgres, docker]);
65
+ * await runner.run(agent, { query, tools });
66
+ */
67
+ declare function collectMcpTools(clients: MCPClient[], signal?: AbortSignal): Promise<Tool[]>;
68
+
69
+ export { MCPClient, type McpServerConfig, type McpTool, collectMcpTools };
@@ -0,0 +1,69 @@
1
+ import { Tool } from '../tools/index.js';
2
+ import 'zod';
3
+
4
+ interface McpServerConfig {
5
+ /** Display label ("Linear", "GitHub", ...). */
6
+ name: string;
7
+ /** Stable slug used in the namespaced tool name (matches /^[a-z0-9_-]+$/). */
8
+ slug: string;
9
+ /** Streamable-HTTP MCP server URL. */
10
+ url: string;
11
+ /** Optional bearer token; sent as `Authorization: Bearer <token>`. */
12
+ token?: string;
13
+ /** Per-call timeout. Default 12s. */
14
+ timeout_ms?: number;
15
+ }
16
+ interface McpTool {
17
+ name: string;
18
+ description?: string;
19
+ inputSchema?: Record<string, unknown>;
20
+ }
21
+ /**
22
+ * MCPClient — one instance per connected MCP server.
23
+ *
24
+ * Example:
25
+ * const client = new MCPClient({
26
+ * name: "Linear",
27
+ * slug: "linear",
28
+ * url: "https://mcp.linear.app/sse",
29
+ * token: process.env.LINEAR_MCP_TOKEN,
30
+ * });
31
+ * const tools = await client.asTools();
32
+ * // tools[0].schema.name === "linear__create_issue"
33
+ * await runner.run(agent, { query: "open a ticket about X", tools });
34
+ */
35
+ declare class MCPClient {
36
+ readonly config: McpServerConfig;
37
+ private initialized;
38
+ private toolCache;
39
+ constructor(config: McpServerConfig);
40
+ /** Fetch the server's tool catalog. Cached for the lifetime of the client. */
41
+ listTools(signal?: AbortSignal): Promise<McpTool[]>;
42
+ /** Call a tool by its bare (non-namespaced) name. Returns the text payload. */
43
+ callTool(name: string, args: unknown, signal?: AbortSignal): Promise<string>;
44
+ /**
45
+ * Return the server's tool catalog as SDK Tool[] objects, namespaced so they
46
+ * compose with tools from other sources without colliding.
47
+ *
48
+ * Pass these directly to a Runner. The Runner sees standard SDK tools; it
49
+ * never has to know MCP existed.
50
+ */
51
+ asTools(signal?: AbortSignal): Promise<Tool[]>;
52
+ /** Health-check: returns toolCount on success, throws on failure. */
53
+ probe(signal?: AbortSignal): Promise<{
54
+ toolCount: number;
55
+ }>;
56
+ private toSdkTool;
57
+ private ensureInitialized;
58
+ private rpc;
59
+ }
60
+ /**
61
+ * Combine multiple MCP clients into a single namespaced Tool[]. Use when an
62
+ * agent should see every tool from every connected server in one call.
63
+ *
64
+ * const tools = await collectMcpTools([github, postgres, docker]);
65
+ * await runner.run(agent, { query, tools });
66
+ */
67
+ declare function collectMcpTools(clients: MCPClient[], signal?: AbortSignal): Promise<Tool[]>;
68
+
69
+ export { MCPClient, type McpServerConfig, type McpTool, collectMcpTools };
@@ -0,0 +1,150 @@
1
+ // src/mcp/index.ts
2
+ var MCP_PROTOCOL_VERSION = "2024-11-05";
3
+ var DEFAULT_TIMEOUT_MS = 12e3;
4
+ var MCPClient = class {
5
+ config;
6
+ initialized = false;
7
+ toolCache = null;
8
+ constructor(config) {
9
+ if (!/^[a-z0-9_-]+$/.test(config.slug)) {
10
+ throw new Error(
11
+ `MCPClient slug "${config.slug}" must match /^[a-z0-9_-]+$/`
12
+ );
13
+ }
14
+ this.config = { timeout_ms: DEFAULT_TIMEOUT_MS, ...config };
15
+ }
16
+ /** Fetch the server's tool catalog. Cached for the lifetime of the client. */
17
+ async listTools(signal) {
18
+ if (this.toolCache) return this.toolCache;
19
+ await this.ensureInitialized(signal);
20
+ const result = await this.rpc("tools/list", {}, signal);
21
+ this.toolCache = Array.isArray(result?.tools) ? result.tools : [];
22
+ return this.toolCache;
23
+ }
24
+ /** Call a tool by its bare (non-namespaced) name. Returns the text payload. */
25
+ async callTool(name, args, signal) {
26
+ await this.ensureInitialized(signal);
27
+ const result = await this.rpc(
28
+ "tools/call",
29
+ { name, arguments: args },
30
+ signal
31
+ );
32
+ const blocks = Array.isArray(result?.content) ? result.content : [];
33
+ const text = blocks.map((b) => b.type === "text" && typeof b.text === "string" ? b.text : "").filter(Boolean).join("\n").trim();
34
+ return text || JSON.stringify(result ?? {}).slice(0, 4e3);
35
+ }
36
+ /**
37
+ * Return the server's tool catalog as SDK Tool[] objects, namespaced so they
38
+ * compose with tools from other sources without colliding.
39
+ *
40
+ * Pass these directly to a Runner. The Runner sees standard SDK tools; it
41
+ * never has to know MCP existed.
42
+ */
43
+ async asTools(signal) {
44
+ const raw = await this.listTools(signal);
45
+ return raw.map((t) => this.toSdkTool(t));
46
+ }
47
+ /** Health-check: returns toolCount on success, throws on failure. */
48
+ async probe(signal) {
49
+ const tools = await this.listTools(signal);
50
+ return { toolCount: tools.length };
51
+ }
52
+ // -------------------------------------------------------------- internals
53
+ toSdkTool(raw) {
54
+ const ns = `${this.config.slug}__${raw.name}`.slice(0, 64).replace(/[^a-zA-Z0-9_-]/g, "_");
55
+ const schema = {
56
+ name: ns,
57
+ description: `[${this.config.name}] ${raw.description || raw.name}`,
58
+ input_schema: raw.inputSchema ?? {
59
+ type: "object",
60
+ properties: {}
61
+ }
62
+ };
63
+ const client = this;
64
+ return {
65
+ schema,
66
+ async execute(input, _ctx) {
67
+ return client.callTool(raw.name, input);
68
+ }
69
+ };
70
+ }
71
+ async ensureInitialized(signal) {
72
+ if (this.initialized) return;
73
+ await this.rpc(
74
+ "initialize",
75
+ {
76
+ protocolVersion: MCP_PROTOCOL_VERSION,
77
+ capabilities: {},
78
+ clientInfo: { name: "theron-agent-sdk", version: "0.1" }
79
+ },
80
+ signal
81
+ );
82
+ this.rpc("notifications/initialized", {}, signal).catch(() => void 0);
83
+ this.initialized = true;
84
+ }
85
+ async rpc(method, params, externalSignal) {
86
+ const ac = new AbortController();
87
+ const timer = setTimeout(
88
+ () => ac.abort(),
89
+ this.config.timeout_ms ?? DEFAULT_TIMEOUT_MS
90
+ );
91
+ if (externalSignal) {
92
+ if (externalSignal.aborted) ac.abort();
93
+ else externalSignal.addEventListener("abort", () => ac.abort());
94
+ }
95
+ const body = {
96
+ jsonrpc: "2.0",
97
+ id: Date.now() + Math.floor(Math.random() * 1e3),
98
+ method,
99
+ params
100
+ };
101
+ try {
102
+ const r = await fetch(this.config.url, {
103
+ method: "POST",
104
+ signal: ac.signal,
105
+ headers: {
106
+ "Content-Type": "application/json",
107
+ Accept: "application/json, text/event-stream",
108
+ ...this.config.token ? { Authorization: `Bearer ${this.config.token}` } : {},
109
+ "MCP-Protocol-Version": MCP_PROTOCOL_VERSION
110
+ },
111
+ body: JSON.stringify(body)
112
+ });
113
+ if (!r.ok) {
114
+ const errText = await r.text().catch(() => "");
115
+ throw new Error(
116
+ `mcp http ${r.status} on ${method}: ${errText.slice(0, 200)}`
117
+ );
118
+ }
119
+ const ct = r.headers.get("content-type") || "";
120
+ if (ct.includes("text/event-stream")) {
121
+ const text = await r.text();
122
+ const m = text.match(/data:\s*(\{[\s\S]*?\})\s*\n/);
123
+ if (!m) throw new Error("mcp sse stream had no data event");
124
+ const env2 = JSON.parse(m[1]);
125
+ if (env2.error) throw new Error(`mcp error: ${env2.error.message}`);
126
+ return env2.result;
127
+ }
128
+ const env = await r.json();
129
+ if (env.error) throw new Error(`mcp error: ${env.error.message}`);
130
+ return env.result;
131
+ } finally {
132
+ clearTimeout(timer);
133
+ }
134
+ }
135
+ };
136
+ async function collectMcpTools(clients, signal) {
137
+ const lists = await Promise.all(
138
+ clients.map(async (c) => {
139
+ try {
140
+ return await c.asTools(signal);
141
+ } catch (err) {
142
+ console.warn(`[mcp] ${c.config.slug} listTools failed:`, err);
143
+ return [];
144
+ }
145
+ })
146
+ );
147
+ return lists.flat();
148
+ }
149
+
150
+ export { MCPClient, collectMcpTools };
@@ -0,0 +1,53 @@
1
+ 'use strict';
2
+
3
+ // src/memory/index.ts
4
+ var Memory = class {
5
+ };
6
+ var InMemoryStore = class extends Memory {
7
+ records = /* @__PURE__ */ new Map();
8
+ async set(record) {
9
+ const id = record.id ?? `mem_${Math.random().toString(36).slice(2)}_${Date.now()}`;
10
+ const full = {
11
+ ...record,
12
+ id,
13
+ created_at: record.created_at ?? Date.now(),
14
+ last_accessed_at: record.last_accessed_at ?? Date.now()
15
+ };
16
+ this.records.set(id, full);
17
+ return full;
18
+ }
19
+ async get(id) {
20
+ const r = this.records.get(id);
21
+ if (r) {
22
+ r.last_accessed_at = Date.now();
23
+ }
24
+ return r;
25
+ }
26
+ async query(q) {
27
+ let results = Array.from(this.records.values());
28
+ if (q.tenant_id !== void 0) {
29
+ results = results.filter((r) => r.tenant_id === q.tenant_id);
30
+ }
31
+ if (q.key !== void 0) {
32
+ results = results.filter((r) => r.key === q.key);
33
+ }
34
+ if (q.tags !== void 0 && q.tags.length > 0) {
35
+ results = results.filter((r) => r.tags?.some((t) => q.tags.includes(t)));
36
+ }
37
+ if (q.semantic_query !== void 0) {
38
+ const ql = q.semantic_query.toLowerCase();
39
+ results = results.filter((r) => r.value.toLowerCase().includes(ql));
40
+ }
41
+ results.sort((a, b) => b.last_accessed_at - a.last_accessed_at);
42
+ if (q.limit !== void 0) {
43
+ results = results.slice(0, q.limit);
44
+ }
45
+ return results;
46
+ }
47
+ async delete(id) {
48
+ this.records.delete(id);
49
+ }
50
+ };
51
+
52
+ exports.InMemoryStore = InMemoryStore;
53
+ exports.Memory = Memory;
@@ -0,0 +1,73 @@
1
+ interface MemoryRecord {
2
+ /** Unique identifier. */
3
+ id: string;
4
+ /** Key for retrieval. */
5
+ key: string;
6
+ /** The actual content. */
7
+ value: string;
8
+ /** Tenant scope (multi-tenant deployments). Always filter on this in
9
+ * production — there is no implicit isolation. */
10
+ tenant_id?: string;
11
+ /** Optional tags for categorization. */
12
+ tags?: string[];
13
+ /** When this memory was created. */
14
+ created_at: number;
15
+ /** When this memory was last accessed (for LRU eviction). */
16
+ last_accessed_at: number;
17
+ /** Optional embedding for semantic search (1024-dim float32). */
18
+ embedding?: number[];
19
+ }
20
+ interface MemoryQuery {
21
+ /** Filter by tenant. */
22
+ tenant_id?: string;
23
+ /** Exact key lookup. */
24
+ key?: string;
25
+ /** Tag-based filter. */
26
+ tags?: string[];
27
+ /** Semantic search by similarity to this query string. */
28
+ semantic_query?: string;
29
+ /** Max records to return. */
30
+ limit?: number;
31
+ }
32
+ /**
33
+ * Memory — abstract interface; implementations plug in any backend.
34
+ *
35
+ * Built-in implementations:
36
+ * - InMemoryStore (default; for development)
37
+ * - For production, plug in pgvector, R2, SQLite, etc. by extending Memory.
38
+ *
39
+ * Minimal usage:
40
+ * const mem = new InMemoryStore();
41
+ * await mem.set({
42
+ * key: "user_name", value: "Annalea",
43
+ * created_at: Date.now(), last_accessed_at: Date.now(),
44
+ * });
45
+ * const records = await mem.query({ key: "user_name" });
46
+ */
47
+ declare abstract class Memory {
48
+ abstract set(record: Omit<MemoryRecord, "id"> & {
49
+ id?: string;
50
+ }): Promise<MemoryRecord>;
51
+ abstract get(id: string): Promise<MemoryRecord | undefined>;
52
+ abstract query(q: MemoryQuery): Promise<MemoryRecord[]>;
53
+ abstract delete(id: string): Promise<void>;
54
+ }
55
+ /**
56
+ * InMemoryStore — default Memory implementation. Volatile; for development.
57
+ * Production should swap in a persistent backend.
58
+ *
59
+ * NOTE: this implementation does NOT enforce tenant isolation at the storage
60
+ * layer — `query({ tenant_id })` filters but does not partition. Production
61
+ * backends should partition by tenant at the storage layer.
62
+ */
63
+ declare class InMemoryStore extends Memory {
64
+ private records;
65
+ set(record: Omit<MemoryRecord, "id"> & {
66
+ id?: string;
67
+ }): Promise<MemoryRecord>;
68
+ get(id: string): Promise<MemoryRecord | undefined>;
69
+ query(q: MemoryQuery): Promise<MemoryRecord[]>;
70
+ delete(id: string): Promise<void>;
71
+ }
72
+
73
+ export { InMemoryStore, Memory, type MemoryQuery, type MemoryRecord };