@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,203 @@
1
+ // src/runtime/index.ts
2
+ var Runner = class {
3
+ model;
4
+ default_model;
5
+ memory;
6
+ session;
7
+ tool_context;
8
+ listeners = [];
9
+ constructor(config) {
10
+ if (!config.model) throw new Error("Runner requires a `model` adapter.");
11
+ if (!config.default_model) throw new Error("Runner requires a `default_model` identifier.");
12
+ this.model = config.model;
13
+ this.default_model = config.default_model;
14
+ this.memory = config.memory;
15
+ this.session = config.session;
16
+ this.tool_context = config.tool_context ?? { cwd: process.cwd(), yolo: false };
17
+ }
18
+ /** Subscribe to runner events for streaming UIs / observability. */
19
+ on(handler) {
20
+ this.listeners.push(handler);
21
+ return () => {
22
+ this.listeners = this.listeners.filter((h) => h !== handler);
23
+ };
24
+ }
25
+ emit(event) {
26
+ for (const h of this.listeners) h(event);
27
+ }
28
+ /**
29
+ * Run a single agent on a query.
30
+ *
31
+ * Loop:
32
+ * 1. Send messages + tool schemas to the model
33
+ * 2. If model returns a tool call → execute the tool → append result to messages → repeat
34
+ * 3. If model returns content + end_turn → finalize
35
+ * 4. Run any registered verifier kernels on the final output
36
+ * 5. Return the AgentResult
37
+ */
38
+ async run(agent, query) {
39
+ const startedAt = Date.now();
40
+ this.emit({ type: "agent_start", agent: agent.name, query });
41
+ const messages = [
42
+ { role: "system", content: agent.instruction.system }
43
+ ];
44
+ for (const ex of agent.instruction.examples ?? []) {
45
+ messages.push({ role: "user", content: ex.user });
46
+ messages.push({ role: "assistant", content: ex.assistant });
47
+ }
48
+ messages.push({ role: "user", content: query });
49
+ const toolCalls = [];
50
+ let tokensIn = 0;
51
+ let tokensOut = 0;
52
+ let finalOutput = "";
53
+ for (let turn = 0; turn < agent.max_turns; turn++) {
54
+ const response = await this.model.chat({
55
+ model: agent.model ?? this.default_model,
56
+ messages,
57
+ tools: agent.toolSchemas(),
58
+ onDelta: (delta) => this.emit({ type: "agent_thinking", agent: agent.name, delta })
59
+ });
60
+ tokensIn += response.tokens.input;
61
+ tokensOut += response.tokens.output;
62
+ if (response.tool_calls && response.tool_calls.length > 0) {
63
+ messages.push({ role: "assistant", content: response.content });
64
+ for (const call of response.tool_calls) {
65
+ const tool = agent.tools.find((t) => t.schema.name === call.name);
66
+ if (!tool) {
67
+ this.emit({
68
+ type: "error",
69
+ agent: agent.name,
70
+ message: `Model called unknown tool: ${call.name}`
71
+ });
72
+ messages.push({ role: "tool", content: `error: unknown tool ${call.name}` });
73
+ continue;
74
+ }
75
+ this.emit({ type: "tool_call_start", agent: agent.name, tool: call.name, input: call.input });
76
+ const t0 = Date.now();
77
+ try {
78
+ const output = await tool.execute(call.input, this.tool_context);
79
+ const ms = Date.now() - t0;
80
+ this.emit({ type: "tool_call_done", agent: agent.name, tool: call.name, output, ms });
81
+ toolCalls.push({ name: call.name, input: call.input, output });
82
+ messages.push({ role: "tool", content: JSON.stringify(output) });
83
+ } catch (err) {
84
+ const msg = err instanceof Error ? err.message : String(err);
85
+ this.emit({ type: "error", agent: agent.name, message: `Tool ${call.name} threw: ${msg}` });
86
+ messages.push({ role: "tool", content: `error: ${msg}` });
87
+ }
88
+ }
89
+ continue;
90
+ }
91
+ finalOutput = response.content;
92
+ messages.push({ role: "assistant", content: finalOutput });
93
+ break;
94
+ }
95
+ this.emit({ type: "agent_output", agent: agent.name, output: finalOutput });
96
+ const verifier_results = [];
97
+ for (const v of agent.verifiers) {
98
+ try {
99
+ const result = await v.check(finalOutput);
100
+ verifier_results.push(result);
101
+ this.emit({ type: "verifier_run", agent: agent.name, kernel: v.name, result });
102
+ } catch (err) {
103
+ this.emit({
104
+ type: "error",
105
+ agent: agent.name,
106
+ message: `Verifier ${v.name} threw: ${err instanceof Error ? err.message : String(err)}`
107
+ });
108
+ }
109
+ }
110
+ const latency_ms = Date.now() - startedAt;
111
+ return {
112
+ agent: agent.name,
113
+ output: finalOutput,
114
+ tool_calls: toolCalls,
115
+ verifier_results,
116
+ tokens_used: { input: tokensIn, output: tokensOut },
117
+ cost_usd: 0,
118
+ // adapter-specific; populated by adapter
119
+ latency_ms
120
+ };
121
+ }
122
+ /**
123
+ * Run a Council on a query.
124
+ *
125
+ * Fan out to all specialists in parallel (with timeout), gather outputs,
126
+ * run council-level verifier kernels on each, and reconcile.
127
+ */
128
+ async runCouncil(council, query) {
129
+ const startedAt = Date.now();
130
+ this.emit({ type: "council_start", council: council.name, query });
131
+ const withTimeout = (p, ms) => Promise.race([
132
+ p,
133
+ new Promise((resolve) => setTimeout(() => resolve(null), ms))
134
+ ]);
135
+ const specialistResults = await Promise.all(
136
+ council.specialists.map(async (spec) => {
137
+ try {
138
+ const result = await withTimeout(this.run(spec, query), council.specialist_timeout_ms);
139
+ if (result === null) {
140
+ this.emit({
141
+ type: "error",
142
+ agent: spec.name,
143
+ message: `Specialist timed out after ${council.specialist_timeout_ms}ms`
144
+ });
145
+ return null;
146
+ }
147
+ const councilVerifierResults = [];
148
+ for (const v of council.verifiers) {
149
+ try {
150
+ const r = await v.check(result.output);
151
+ councilVerifierResults.push(r);
152
+ this.emit({ type: "verifier_run", agent: spec.name, kernel: v.name, result: r });
153
+ } catch (err) {
154
+ this.emit({
155
+ type: "error",
156
+ agent: spec.name,
157
+ message: `Council verifier ${v.name} threw: ${err instanceof Error ? err.message : String(err)}`
158
+ });
159
+ }
160
+ }
161
+ const out = {
162
+ specialist: spec.name,
163
+ output: result.output,
164
+ claims: [],
165
+ // claim extraction is the reconciler's job
166
+ // AgentResult.verifier_results widens issues to unknown[]; at the
167
+ // runtime layer we know every entry came from a Verifier.check()
168
+ // call (which produces VerifierIssue[]), so the cast is sound.
169
+ verifier_results: [
170
+ ...result.verifier_results,
171
+ ...councilVerifierResults
172
+ ],
173
+ cost_usd: result.cost_usd,
174
+ latency_ms: result.latency_ms
175
+ };
176
+ this.emit({ type: "specialist_done", specialist: spec.name, output: out });
177
+ return out;
178
+ } catch (err) {
179
+ this.emit({
180
+ type: "error",
181
+ agent: spec.name,
182
+ message: err instanceof Error ? err.message : String(err)
183
+ });
184
+ return null;
185
+ }
186
+ })
187
+ );
188
+ const survivors = specialistResults.filter((s) => s !== null);
189
+ const reconciled = await council.reconciler(survivors);
190
+ const output = {
191
+ answer: reconciled.answer,
192
+ specialists: survivors,
193
+ consensus: reconciled.consensus,
194
+ disagreements: reconciled.disagreements,
195
+ total_cost_usd: survivors.reduce((s, x) => s + x.cost_usd, 0),
196
+ total_latency_ms: Date.now() - startedAt
197
+ };
198
+ this.emit({ type: "council_done", council: council.name, output });
199
+ return output;
200
+ }
201
+ };
202
+
203
+ export { Runner };
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ // src/session/index.ts
4
+ var Session = class _Session {
5
+ id;
6
+ tenant_id;
7
+ state;
8
+ events;
9
+ constructor(config = {}) {
10
+ this.id = config.id ?? cryptoRandomId();
11
+ this.tenant_id = config.tenant_id;
12
+ this.state = new Map(Object.entries(config.initial_state ?? {}));
13
+ this.events = [];
14
+ }
15
+ append(event) {
16
+ this.events.push(event);
17
+ }
18
+ /** Get a read-only snapshot of all events. */
19
+ getEvents() {
20
+ return this.events;
21
+ }
22
+ /** Get events of a specific type (typed). */
23
+ getEventsOfType(type) {
24
+ return this.events.filter((e) => e.type === type);
25
+ }
26
+ /** Serialize the session for persistence. */
27
+ toJSON() {
28
+ return {
29
+ id: this.id,
30
+ tenant_id: this.tenant_id,
31
+ state: Object.fromEntries(this.state.entries()),
32
+ events: this.events.slice()
33
+ };
34
+ }
35
+ /** Restore a session from its serialized form. */
36
+ static fromJSON(data) {
37
+ const sess = new _Session({ id: data.id, tenant_id: data.tenant_id, initial_state: data.state });
38
+ for (const e of data.events) sess.events.push(e);
39
+ return sess;
40
+ }
41
+ };
42
+ function cryptoRandomId() {
43
+ if (typeof globalThis.crypto?.randomUUID === "function") {
44
+ return globalThis.crypto.randomUUID();
45
+ }
46
+ return `sess_${Math.random().toString(36).slice(2)}_${Date.now()}`;
47
+ }
48
+
49
+ exports.Session = Session;
@@ -0,0 +1,79 @@
1
+ type SessionEvent = {
2
+ type: "user_input";
3
+ content: string;
4
+ ts: number;
5
+ } | {
6
+ type: "agent_output";
7
+ agent: string;
8
+ content: string;
9
+ ts: number;
10
+ } | {
11
+ type: "tool_call";
12
+ tool: string;
13
+ input: unknown;
14
+ output: unknown;
15
+ ts: number;
16
+ ms: number;
17
+ } | {
18
+ type: "verifier_result";
19
+ kernel: string;
20
+ pass: boolean;
21
+ issues: unknown[];
22
+ ts: number;
23
+ } | {
24
+ type: "state_change";
25
+ key: string;
26
+ before: unknown;
27
+ after: unknown;
28
+ ts: number;
29
+ } | {
30
+ type: "error";
31
+ agent: string;
32
+ message: string;
33
+ ts: number;
34
+ };
35
+ interface SessionConfig {
36
+ /** Unique session identifier. Generated if not provided. */
37
+ id?: string;
38
+ /** Optional tenant scope. */
39
+ tenant_id?: string;
40
+ /** Optional initial state. */
41
+ initial_state?: Record<string, unknown>;
42
+ }
43
+ type SessionJSON = {
44
+ id: string;
45
+ tenant_id?: string;
46
+ state: Record<string, unknown>;
47
+ events: SessionEvent[];
48
+ };
49
+ /**
50
+ * Session — append-only event log + scoped state.
51
+ *
52
+ * Minimal usage:
53
+ * const sess = new Session();
54
+ * sess.append({ type: "user_input", content: "Hello", ts: Date.now() });
55
+ * sess.state.set("user_name", "Annalea");
56
+ *
57
+ * Persistence: by default, sessions are in-memory. Plug in a persistence
58
+ * adapter (Postgres, R2, SQLite) via the toJSON / fromJSON pair.
59
+ */
60
+ declare class Session {
61
+ readonly id: string;
62
+ readonly tenant_id: string | undefined;
63
+ readonly state: Map<string, unknown>;
64
+ private readonly events;
65
+ constructor(config?: SessionConfig);
66
+ append(event: SessionEvent): void;
67
+ /** Get a read-only snapshot of all events. */
68
+ getEvents(): readonly SessionEvent[];
69
+ /** Get events of a specific type (typed). */
70
+ getEventsOfType<T extends SessionEvent["type"]>(type: T): Extract<SessionEvent, {
71
+ type: T;
72
+ }>[];
73
+ /** Serialize the session for persistence. */
74
+ toJSON(): SessionJSON;
75
+ /** Restore a session from its serialized form. */
76
+ static fromJSON(data: SessionJSON): Session;
77
+ }
78
+
79
+ export { Session, type SessionConfig, type SessionEvent, type SessionJSON };
@@ -0,0 +1,79 @@
1
+ type SessionEvent = {
2
+ type: "user_input";
3
+ content: string;
4
+ ts: number;
5
+ } | {
6
+ type: "agent_output";
7
+ agent: string;
8
+ content: string;
9
+ ts: number;
10
+ } | {
11
+ type: "tool_call";
12
+ tool: string;
13
+ input: unknown;
14
+ output: unknown;
15
+ ts: number;
16
+ ms: number;
17
+ } | {
18
+ type: "verifier_result";
19
+ kernel: string;
20
+ pass: boolean;
21
+ issues: unknown[];
22
+ ts: number;
23
+ } | {
24
+ type: "state_change";
25
+ key: string;
26
+ before: unknown;
27
+ after: unknown;
28
+ ts: number;
29
+ } | {
30
+ type: "error";
31
+ agent: string;
32
+ message: string;
33
+ ts: number;
34
+ };
35
+ interface SessionConfig {
36
+ /** Unique session identifier. Generated if not provided. */
37
+ id?: string;
38
+ /** Optional tenant scope. */
39
+ tenant_id?: string;
40
+ /** Optional initial state. */
41
+ initial_state?: Record<string, unknown>;
42
+ }
43
+ type SessionJSON = {
44
+ id: string;
45
+ tenant_id?: string;
46
+ state: Record<string, unknown>;
47
+ events: SessionEvent[];
48
+ };
49
+ /**
50
+ * Session — append-only event log + scoped state.
51
+ *
52
+ * Minimal usage:
53
+ * const sess = new Session();
54
+ * sess.append({ type: "user_input", content: "Hello", ts: Date.now() });
55
+ * sess.state.set("user_name", "Annalea");
56
+ *
57
+ * Persistence: by default, sessions are in-memory. Plug in a persistence
58
+ * adapter (Postgres, R2, SQLite) via the toJSON / fromJSON pair.
59
+ */
60
+ declare class Session {
61
+ readonly id: string;
62
+ readonly tenant_id: string | undefined;
63
+ readonly state: Map<string, unknown>;
64
+ private readonly events;
65
+ constructor(config?: SessionConfig);
66
+ append(event: SessionEvent): void;
67
+ /** Get a read-only snapshot of all events. */
68
+ getEvents(): readonly SessionEvent[];
69
+ /** Get events of a specific type (typed). */
70
+ getEventsOfType<T extends SessionEvent["type"]>(type: T): Extract<SessionEvent, {
71
+ type: T;
72
+ }>[];
73
+ /** Serialize the session for persistence. */
74
+ toJSON(): SessionJSON;
75
+ /** Restore a session from its serialized form. */
76
+ static fromJSON(data: SessionJSON): Session;
77
+ }
78
+
79
+ export { Session, type SessionConfig, type SessionEvent, type SessionJSON };
@@ -0,0 +1,47 @@
1
+ // src/session/index.ts
2
+ var Session = class _Session {
3
+ id;
4
+ tenant_id;
5
+ state;
6
+ events;
7
+ constructor(config = {}) {
8
+ this.id = config.id ?? cryptoRandomId();
9
+ this.tenant_id = config.tenant_id;
10
+ this.state = new Map(Object.entries(config.initial_state ?? {}));
11
+ this.events = [];
12
+ }
13
+ append(event) {
14
+ this.events.push(event);
15
+ }
16
+ /** Get a read-only snapshot of all events. */
17
+ getEvents() {
18
+ return this.events;
19
+ }
20
+ /** Get events of a specific type (typed). */
21
+ getEventsOfType(type) {
22
+ return this.events.filter((e) => e.type === type);
23
+ }
24
+ /** Serialize the session for persistence. */
25
+ toJSON() {
26
+ return {
27
+ id: this.id,
28
+ tenant_id: this.tenant_id,
29
+ state: Object.fromEntries(this.state.entries()),
30
+ events: this.events.slice()
31
+ };
32
+ }
33
+ /** Restore a session from its serialized form. */
34
+ static fromJSON(data) {
35
+ const sess = new _Session({ id: data.id, tenant_id: data.tenant_id, initial_state: data.state });
36
+ for (const e of data.events) sess.events.push(e);
37
+ return sess;
38
+ }
39
+ };
40
+ function cryptoRandomId() {
41
+ if (typeof globalThis.crypto?.randomUUID === "function") {
42
+ return globalThis.crypto.randomUUID();
43
+ }
44
+ return `sess_${Math.random().toString(36).slice(2)}_${Date.now()}`;
45
+ }
46
+
47
+ export { Session };
@@ -0,0 +1,51 @@
1
+ 'use strict';
2
+
3
+ var zod = require('zod');
4
+
5
+ // src/tools/index.ts
6
+ function defineTool(opts) {
7
+ if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(opts.name)) {
8
+ throw new Error(
9
+ `Tool name "${opts.name}" is invalid. Must match /^[a-zA-Z][a-zA-Z0-9_-]*$/ for OpenAI/Anthropic tool-call compatibility.`
10
+ );
11
+ }
12
+ return {
13
+ schema: {
14
+ name: opts.name,
15
+ description: opts.description,
16
+ input_schema: zodToJsonSchema(opts.input)
17
+ },
18
+ async execute(input, ctx) {
19
+ const parsed = opts.input.safeParse(input);
20
+ if (!parsed.success) {
21
+ const issues = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
22
+ throw new Error(`Tool "${opts.name}" received invalid input: ${issues}`);
23
+ }
24
+ return opts.execute(parsed.data, ctx);
25
+ }
26
+ };
27
+ }
28
+ function zodToJsonSchema(schema) {
29
+ if (schema instanceof zod.z.ZodObject) {
30
+ const properties = {};
31
+ const required = [];
32
+ for (const [key, value] of Object.entries(schema.shape)) {
33
+ properties[key] = zodToJsonSchema(value);
34
+ if (!(value instanceof zod.z.ZodOptional)) required.push(key);
35
+ }
36
+ return { type: "object", properties, ...required.length > 0 ? { required } : {} };
37
+ }
38
+ if (schema instanceof zod.z.ZodString) return { type: "string" };
39
+ if (schema instanceof zod.z.ZodNumber) return { type: "number" };
40
+ if (schema instanceof zod.z.ZodBoolean) return { type: "boolean" };
41
+ if (schema instanceof zod.z.ZodArray) return { type: "array", items: zodToJsonSchema(schema.element) };
42
+ if (schema instanceof zod.z.ZodOptional) return zodToJsonSchema(schema.unwrap());
43
+ if (schema instanceof zod.z.ZodEnum) return { type: "string", enum: schema.options };
44
+ return { type: "string" };
45
+ }
46
+
47
+ Object.defineProperty(exports, "zod", {
48
+ enumerable: true,
49
+ get: function () { return zod.z; }
50
+ });
51
+ exports.defineTool = defineTool;
@@ -0,0 +1,52 @@
1
+ import { z } from 'zod';
2
+ export { z as zod } from 'zod';
3
+
4
+ interface ToolContext {
5
+ /** Current working directory (for file-system tools). */
6
+ cwd: string;
7
+ /** Whether to auto-approve potentially destructive operations.
8
+ * Default `false`. The SDK does NOT enforce sandboxing — tool authors are
9
+ * responsible for honoring this flag when their tool can mutate state. */
10
+ yolo: boolean;
11
+ /** Session identifier (for memory + audit logging). */
12
+ session_id?: string;
13
+ /** Tenant identifier (for multi-tenant deployments). */
14
+ tenant_id?: string;
15
+ }
16
+ interface ToolSchema {
17
+ name: string;
18
+ description: string;
19
+ /** JSON schema describing the input shape. */
20
+ input_schema: Record<string, unknown>;
21
+ }
22
+ interface Tool<TInput = unknown, TOutput = unknown> {
23
+ schema: ToolSchema;
24
+ execute(input: TInput, ctx: ToolContext): Promise<TOutput>;
25
+ }
26
+ /**
27
+ * defineTool — ergonomic tool factory.
28
+ *
29
+ * Pass a Zod schema for input + an async execute fn. The schema is converted
30
+ * to JSON-schema automatically and used to validate tool-call args at runtime.
31
+ *
32
+ * Example:
33
+ * const greet = defineTool({
34
+ * name: "greet",
35
+ * description: "Greet a person by name.",
36
+ * input: z.object({ name: z.string() }),
37
+ * async execute({ name }) {
38
+ * return `Hello, ${name}!`;
39
+ * },
40
+ * });
41
+ *
42
+ * On invalid input, throws an Error with a structured message that includes
43
+ * the tool name + Zod's parse issues — surface this to your end user.
44
+ */
45
+ declare function defineTool<TSchema extends z.ZodTypeAny, TOutput>(opts: {
46
+ name: string;
47
+ description: string;
48
+ input: TSchema;
49
+ execute: (input: z.infer<TSchema>, ctx: ToolContext) => Promise<TOutput>;
50
+ }): Tool<z.infer<TSchema>, TOutput>;
51
+
52
+ export { type Tool, type ToolContext, type ToolSchema, defineTool };
@@ -0,0 +1,52 @@
1
+ import { z } from 'zod';
2
+ export { z as zod } from 'zod';
3
+
4
+ interface ToolContext {
5
+ /** Current working directory (for file-system tools). */
6
+ cwd: string;
7
+ /** Whether to auto-approve potentially destructive operations.
8
+ * Default `false`. The SDK does NOT enforce sandboxing — tool authors are
9
+ * responsible for honoring this flag when their tool can mutate state. */
10
+ yolo: boolean;
11
+ /** Session identifier (for memory + audit logging). */
12
+ session_id?: string;
13
+ /** Tenant identifier (for multi-tenant deployments). */
14
+ tenant_id?: string;
15
+ }
16
+ interface ToolSchema {
17
+ name: string;
18
+ description: string;
19
+ /** JSON schema describing the input shape. */
20
+ input_schema: Record<string, unknown>;
21
+ }
22
+ interface Tool<TInput = unknown, TOutput = unknown> {
23
+ schema: ToolSchema;
24
+ execute(input: TInput, ctx: ToolContext): Promise<TOutput>;
25
+ }
26
+ /**
27
+ * defineTool — ergonomic tool factory.
28
+ *
29
+ * Pass a Zod schema for input + an async execute fn. The schema is converted
30
+ * to JSON-schema automatically and used to validate tool-call args at runtime.
31
+ *
32
+ * Example:
33
+ * const greet = defineTool({
34
+ * name: "greet",
35
+ * description: "Greet a person by name.",
36
+ * input: z.object({ name: z.string() }),
37
+ * async execute({ name }) {
38
+ * return `Hello, ${name}!`;
39
+ * },
40
+ * });
41
+ *
42
+ * On invalid input, throws an Error with a structured message that includes
43
+ * the tool name + Zod's parse issues — surface this to your end user.
44
+ */
45
+ declare function defineTool<TSchema extends z.ZodTypeAny, TOutput>(opts: {
46
+ name: string;
47
+ description: string;
48
+ input: TSchema;
49
+ execute: (input: z.infer<TSchema>, ctx: ToolContext) => Promise<TOutput>;
50
+ }): Tool<z.infer<TSchema>, TOutput>;
51
+
52
+ export { type Tool, type ToolContext, type ToolSchema, defineTool };
@@ -0,0 +1,46 @@
1
+ import { z } from 'zod';
2
+ export { z as zod } from 'zod';
3
+
4
+ // src/tools/index.ts
5
+ function defineTool(opts) {
6
+ if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(opts.name)) {
7
+ throw new Error(
8
+ `Tool name "${opts.name}" is invalid. Must match /^[a-zA-Z][a-zA-Z0-9_-]*$/ for OpenAI/Anthropic tool-call compatibility.`
9
+ );
10
+ }
11
+ return {
12
+ schema: {
13
+ name: opts.name,
14
+ description: opts.description,
15
+ input_schema: zodToJsonSchema(opts.input)
16
+ },
17
+ async execute(input, ctx) {
18
+ const parsed = opts.input.safeParse(input);
19
+ if (!parsed.success) {
20
+ const issues = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
21
+ throw new Error(`Tool "${opts.name}" received invalid input: ${issues}`);
22
+ }
23
+ return opts.execute(parsed.data, ctx);
24
+ }
25
+ };
26
+ }
27
+ function zodToJsonSchema(schema) {
28
+ if (schema instanceof z.ZodObject) {
29
+ const properties = {};
30
+ const required = [];
31
+ for (const [key, value] of Object.entries(schema.shape)) {
32
+ properties[key] = zodToJsonSchema(value);
33
+ if (!(value instanceof z.ZodOptional)) required.push(key);
34
+ }
35
+ return { type: "object", properties, ...required.length > 0 ? { required } : {} };
36
+ }
37
+ if (schema instanceof z.ZodString) return { type: "string" };
38
+ if (schema instanceof z.ZodNumber) return { type: "number" };
39
+ if (schema instanceof z.ZodBoolean) return { type: "boolean" };
40
+ if (schema instanceof z.ZodArray) return { type: "array", items: zodToJsonSchema(schema.element) };
41
+ if (schema instanceof z.ZodOptional) return zodToJsonSchema(schema.unwrap());
42
+ if (schema instanceof z.ZodEnum) return { type: "string", enum: schema.options };
43
+ return { type: "string" };
44
+ }
45
+
46
+ export { defineTool };