torus-ai 0.1.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/src/tools.ts ADDED
@@ -0,0 +1,84 @@
1
+ import type {
2
+ JSONSchema,
3
+ SdkMcpServer,
4
+ ToolContext,
5
+ ToolDefinition,
6
+ ToolResultPayload,
7
+ ToolSchema,
8
+ } from "./types.ts";
9
+
10
+ /**
11
+ * Define a custom tool. Mirrors `tool()` from the Claude Agent SDK.
12
+ * tool("get_temp", "Get temperature", { type:"object", ... }, async (input, ctx) => ...)
13
+ */
14
+ export function tool(
15
+ name: string,
16
+ description: string,
17
+ inputSchema: JSONSchema,
18
+ handler: (input: any, ctx: ToolContext) => Promise<ToolResultPayload> | ToolResultPayload,
19
+ ): ToolDefinition {
20
+ return { name, description, inputSchema, handler };
21
+ }
22
+
23
+ /**
24
+ * Bundle tools into an in-process MCP server. Mirrors `createSdkMcpServer()`.
25
+ * Tools become namespaced `mcp__<name>__<tool>` when registered.
26
+ */
27
+ export function createSdkMcpServer(opts: {
28
+ name: string;
29
+ version?: string;
30
+ tools: ToolDefinition[];
31
+ }): SdkMcpServer {
32
+ return { kind: "sdk-mcp", name: opts.name, version: opts.version ?? "1.0.0", tools: opts.tools };
33
+ }
34
+
35
+ export interface RegisteredTool {
36
+ fullName: string; // "mcp__research__lookup" or built-in "read_file"
37
+ def: ToolDefinition;
38
+ }
39
+
40
+ /** Holds the model-facing tool catalog and executes calls by namespaced name. */
41
+ export class ToolRegistry {
42
+ private map = new Map<string, ToolDefinition>();
43
+
44
+ /** Built-ins register under their bare name (no namespace). */
45
+ addBuiltins(defs: ToolDefinition[]): this {
46
+ for (const d of defs) this.map.set(d.name, d);
47
+ return this;
48
+ }
49
+
50
+ /** SDK MCP server tools register as mcp__<server>__<tool>. */
51
+ addServer(server: SdkMcpServer): this {
52
+ for (const t of server.tools) this.map.set(`mcp__${server.name}__${t.name}`, t);
53
+ return this;
54
+ }
55
+
56
+ has(fullName: string): boolean {
57
+ return this.map.has(fullName);
58
+ }
59
+
60
+ list(): RegisteredTool[] {
61
+ return [...this.map.entries()].map(([fullName, def]) => ({ fullName, def }));
62
+ }
63
+
64
+ /** Tool schemas to hand the model, optionally filtered to a stage's allowlist. */
65
+ schemas(filter?: (fullName: string) => boolean): ToolSchema[] {
66
+ return this.list()
67
+ .filter((t) => !filter || filter(t.fullName))
68
+ .map((t) => ({ name: t.fullName, description: t.def.description, inputSchema: t.def.inputSchema }));
69
+ }
70
+
71
+ async execute(
72
+ fullName: string,
73
+ input: Record<string, unknown>,
74
+ ctx: ToolContext,
75
+ ): Promise<ToolResultPayload> {
76
+ const def = this.map.get(fullName);
77
+ if (!def) return { content: `Unknown tool: ${fullName}`, isError: true };
78
+ try {
79
+ return await def.handler(input, ctx);
80
+ } catch (err) {
81
+ return { content: `Tool ${fullName} threw: ${(err as Error).message}`, isError: true };
82
+ }
83
+ }
84
+ }
package/src/types.ts ADDED
@@ -0,0 +1,111 @@
1
+ // ─────────────────────────────────────────────────────────────────────────
2
+ // Core wire types — the plain-data interface every layer speaks (ICM principle 2:
3
+ // "plain text/structured data is the interface"). No provider-specific shapes leak
4
+ // past this file; the AnthropicProvider/MockProvider translate to and from these.
5
+ // ─────────────────────────────────────────────────────────────────────────
6
+
7
+ export type Role = "user" | "assistant";
8
+
9
+ export interface TextBlock {
10
+ type: "text";
11
+ text: string;
12
+ }
13
+ export interface ToolUseBlock {
14
+ type: "tool_use";
15
+ id: string;
16
+ name: string; // namespaced name as offered to the model, e.g. "mcp__research__lookup"
17
+ input: Record<string, unknown>;
18
+ }
19
+ export interface ToolResultBlock {
20
+ type: "tool_result";
21
+ toolUseId: string;
22
+ content: string;
23
+ isError?: boolean;
24
+ }
25
+ export type ContentBlock = TextBlock | ToolUseBlock | ToolResultBlock;
26
+
27
+ export interface Message {
28
+ role: Role;
29
+ content: ContentBlock[];
30
+ }
31
+
32
+ export type StopReason = "end_turn" | "tool_use" | "max_turns";
33
+
34
+ export interface ModelResponse {
35
+ content: ContentBlock[];
36
+ stopReason: StopReason;
37
+ }
38
+
39
+ export type JSONSchema = Record<string, unknown>;
40
+
41
+ export interface ToolSchema {
42
+ name: string;
43
+ description: string;
44
+ inputSchema: JSONSchema;
45
+ }
46
+
47
+ export interface ModelRequest {
48
+ system: string;
49
+ messages: Message[];
50
+ tools: ToolSchema[];
51
+ }
52
+
53
+ /** The one capability the SDK needs from any model backend. Swap freely. */
54
+ export interface ModelProvider {
55
+ readonly name: string;
56
+ generate(req: ModelRequest): Promise<ModelResponse>;
57
+ }
58
+
59
+ // ── Tools ────────────────────────────────────────────────────────────────
60
+
61
+ export interface ToolResultPayload {
62
+ content: string;
63
+ isError?: boolean;
64
+ }
65
+
66
+ export interface ToolContext {
67
+ workspaceDir: string;
68
+ stageDir?: string;
69
+ signal?: AbortSignal;
70
+ }
71
+
72
+ export interface ToolDefinition {
73
+ name: string; // bare name, e.g. "lookup"; namespacing is applied at registration
74
+ description: string;
75
+ inputSchema: JSONSchema;
76
+ handler: (
77
+ input: any,
78
+ ctx: ToolContext,
79
+ ) => Promise<ToolResultPayload> | ToolResultPayload;
80
+ }
81
+
82
+ /** An in-process MCP server: tools that run in this same process, no subprocess. */
83
+ export interface SdkMcpServer {
84
+ kind: "sdk-mcp";
85
+ name: string; // becomes the mcp__<name>__ namespace prefix
86
+ version: string;
87
+ tools: ToolDefinition[];
88
+ }
89
+
90
+ // ── Permissions ────────────────────────────────────────────────────────────
91
+
92
+ export type PermissionDecision =
93
+ | { behavior: "allow"; updatedInput?: Record<string, unknown> }
94
+ | { behavior: "deny"; message: string };
95
+
96
+ export type CanUseTool = (
97
+ toolName: string,
98
+ input: Record<string, unknown>,
99
+ ) => Promise<PermissionDecision> | PermissionDecision;
100
+
101
+ // ── Streaming events ───────────────────────────────────────────────────────
102
+
103
+ export type AgentEvent =
104
+ | { type: "assistant_text"; text: string; stage?: string }
105
+ | { type: "tool_use"; name: string; input: Record<string, unknown>; stage?: string }
106
+ | { type: "tool_result"; name: string; content: string; isError: boolean; stage?: string }
107
+ | { type: "permission_denied"; name: string; message: string; stage?: string }
108
+ | { type: "stage_start"; stage: string }
109
+ | { type: "stage_output"; stage: string; artifact: string; path: string }
110
+ | { type: "context_loaded"; stage?: string; tokensEstimated: number; files: string[] }
111
+ | { type: "result"; finalText: string; turns: number; stage?: string };