activo 0.2.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 (86) hide show
  1. package/FINAL_SIMPLIFIED_SPEC.md +456 -0
  2. package/README.md +62 -0
  3. package/TODO.md +193 -0
  4. package/dist/cli/banner.d.ts +3 -0
  5. package/dist/cli/banner.d.ts.map +1 -0
  6. package/dist/cli/banner.js +30 -0
  7. package/dist/cli/banner.js.map +1 -0
  8. package/dist/cli/headless.d.ts +3 -0
  9. package/dist/cli/headless.d.ts.map +1 -0
  10. package/dist/cli/headless.js +34 -0
  11. package/dist/cli/headless.js.map +1 -0
  12. package/dist/cli/index.d.ts +3 -0
  13. package/dist/cli/index.d.ts.map +1 -0
  14. package/dist/cli/index.js +42 -0
  15. package/dist/cli/index.js.map +1 -0
  16. package/dist/core/agent.d.ts +23 -0
  17. package/dist/core/agent.d.ts.map +1 -0
  18. package/dist/core/agent.js +171 -0
  19. package/dist/core/agent.js.map +1 -0
  20. package/dist/core/config.d.ts +24 -0
  21. package/dist/core/config.d.ts.map +1 -0
  22. package/dist/core/config.js +66 -0
  23. package/dist/core/config.js.map +1 -0
  24. package/dist/core/llm/ollama.d.ts +30 -0
  25. package/dist/core/llm/ollama.d.ts.map +1 -0
  26. package/dist/core/llm/ollama.js +173 -0
  27. package/dist/core/llm/ollama.js.map +1 -0
  28. package/dist/core/mcp/client.d.ts +22 -0
  29. package/dist/core/mcp/client.d.ts.map +1 -0
  30. package/dist/core/mcp/client.js +116 -0
  31. package/dist/core/mcp/client.js.map +1 -0
  32. package/dist/core/tools/builtIn.d.ts +9 -0
  33. package/dist/core/tools/builtIn.d.ts.map +1 -0
  34. package/dist/core/tools/builtIn.js +219 -0
  35. package/dist/core/tools/builtIn.js.map +1 -0
  36. package/dist/core/tools/index.d.ts +13 -0
  37. package/dist/core/tools/index.d.ts.map +1 -0
  38. package/dist/core/tools/index.js +43 -0
  39. package/dist/core/tools/index.js.map +1 -0
  40. package/dist/core/tools/standards.d.ts +6 -0
  41. package/dist/core/tools/standards.d.ts.map +1 -0
  42. package/dist/core/tools/standards.js +215 -0
  43. package/dist/core/tools/standards.js.map +1 -0
  44. package/dist/core/tools/types.d.ts +34 -0
  45. package/dist/core/tools/types.d.ts.map +1 -0
  46. package/dist/core/tools/types.js +2 -0
  47. package/dist/core/tools/types.js.map +1 -0
  48. package/dist/ui/App.d.ts +10 -0
  49. package/dist/ui/App.d.ts.map +1 -0
  50. package/dist/ui/App.js +167 -0
  51. package/dist/ui/App.js.map +1 -0
  52. package/dist/ui/components/InputBox.d.ts +11 -0
  53. package/dist/ui/components/InputBox.d.ts.map +1 -0
  54. package/dist/ui/components/InputBox.js +7 -0
  55. package/dist/ui/components/InputBox.js.map +1 -0
  56. package/dist/ui/components/MessageList.d.ts +17 -0
  57. package/dist/ui/components/MessageList.d.ts.map +1 -0
  58. package/dist/ui/components/MessageList.js +18 -0
  59. package/dist/ui/components/MessageList.js.map +1 -0
  60. package/dist/ui/components/StatusBar.d.ts +9 -0
  61. package/dist/ui/components/StatusBar.d.ts.map +1 -0
  62. package/dist/ui/components/StatusBar.js +6 -0
  63. package/dist/ui/components/StatusBar.js.map +1 -0
  64. package/dist/ui/components/ToolStatus.d.ts +8 -0
  65. package/dist/ui/components/ToolStatus.d.ts.map +1 -0
  66. package/dist/ui/components/ToolStatus.js +7 -0
  67. package/dist/ui/components/ToolStatus.js.map +1 -0
  68. package/package.json +64 -0
  69. package/screenshot.png +0 -0
  70. package/src/cli/banner.ts +34 -0
  71. package/src/cli/headless.ts +37 -0
  72. package/src/cli/index.ts +53 -0
  73. package/src/core/agent.ts +235 -0
  74. package/src/core/config.ts +98 -0
  75. package/src/core/llm/ollama.ts +238 -0
  76. package/src/core/mcp/client.ts +143 -0
  77. package/src/core/tools/builtIn.ts +221 -0
  78. package/src/core/tools/index.ts +53 -0
  79. package/src/core/tools/standards.ts +246 -0
  80. package/src/core/tools/types.ts +37 -0
  81. package/src/ui/App.tsx +238 -0
  82. package/src/ui/components/InputBox.tsx +37 -0
  83. package/src/ui/components/MessageList.tsx +80 -0
  84. package/src/ui/components/StatusBar.tsx +36 -0
  85. package/src/ui/components/ToolStatus.tsx +38 -0
  86. package/tsconfig.json +21 -0
@@ -0,0 +1,238 @@
1
+ import { OllamaConfig } from "../config.js";
2
+ import { Tool, ToolCall } from "../tools/types.js";
3
+
4
+ export interface ChatMessage {
5
+ role: "system" | "user" | "assistant" | "tool";
6
+ content: string;
7
+ toolCalls?: ToolCall[];
8
+ toolCallId?: string;
9
+ }
10
+
11
+ export interface StreamEvent {
12
+ type: "content" | "tool_call" | "done" | "error";
13
+ content?: string;
14
+ toolCall?: ToolCall;
15
+ error?: string;
16
+ }
17
+
18
+ interface OllamaChatResponse {
19
+ model: string;
20
+ message: {
21
+ role: string;
22
+ content: string;
23
+ tool_calls?: Array<{
24
+ function: {
25
+ name: string;
26
+ arguments: Record<string, unknown>;
27
+ };
28
+ }>;
29
+ };
30
+ done: boolean;
31
+ }
32
+
33
+ export class OllamaClient {
34
+ private baseUrl: string;
35
+ private model: string;
36
+ private contextLength: number;
37
+ private keepAlive: number;
38
+
39
+ constructor(config: OllamaConfig) {
40
+ this.baseUrl = config.baseUrl;
41
+ this.model = config.model;
42
+ this.contextLength = config.contextLength;
43
+ this.keepAlive = config.keepAlive;
44
+ }
45
+
46
+ async isConnected(): Promise<boolean> {
47
+ try {
48
+ const response = await fetch(`${this.baseUrl}/api/tags`);
49
+ return response.ok;
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
54
+
55
+ async listModels(): Promise<string[]> {
56
+ const response = await fetch(`${this.baseUrl}/api/tags`);
57
+ if (!response.ok) throw new Error("Failed to list models");
58
+ const data = (await response.json()) as { models: Array<{ name: string }> };
59
+ return data.models.map((m) => m.name);
60
+ }
61
+
62
+ async chat(
63
+ messages: ChatMessage[],
64
+ tools?: Tool[]
65
+ ): Promise<ChatMessage> {
66
+ const ollamaMessages = this.convertMessages(messages);
67
+
68
+ const body: Record<string, unknown> = {
69
+ model: this.model,
70
+ messages: ollamaMessages,
71
+ stream: false,
72
+ options: {
73
+ num_ctx: this.contextLength,
74
+ },
75
+ keep_alive: this.keepAlive,
76
+ };
77
+
78
+ // Add tools if last message is from user
79
+ if (tools?.length && messages[messages.length - 1]?.role === "user") {
80
+ body.tools = tools.map((tool) => ({
81
+ type: "function",
82
+ function: {
83
+ name: tool.name,
84
+ description: tool.description,
85
+ parameters: tool.parameters,
86
+ },
87
+ }));
88
+ }
89
+
90
+ const response = await fetch(`${this.baseUrl}/api/chat`, {
91
+ method: "POST",
92
+ headers: { "Content-Type": "application/json" },
93
+ body: JSON.stringify(body),
94
+ });
95
+
96
+ if (!response.ok) {
97
+ const error = await response.text();
98
+ throw new Error(`Ollama error: ${error}`);
99
+ }
100
+
101
+ const data = (await response.json()) as OllamaChatResponse;
102
+ return this.parseResponse(data);
103
+ }
104
+
105
+ async *streamChat(
106
+ messages: ChatMessage[],
107
+ tools?: Tool[],
108
+ abortSignal?: AbortSignal
109
+ ): AsyncGenerator<StreamEvent> {
110
+ const ollamaMessages = this.convertMessages(messages);
111
+
112
+ const body: Record<string, unknown> = {
113
+ model: this.model,
114
+ messages: ollamaMessages,
115
+ stream: true,
116
+ options: {
117
+ num_ctx: this.contextLength,
118
+ },
119
+ keep_alive: this.keepAlive,
120
+ };
121
+
122
+ if (tools?.length && messages[messages.length - 1]?.role === "user") {
123
+ body.tools = tools.map((tool) => ({
124
+ type: "function",
125
+ function: {
126
+ name: tool.name,
127
+ description: tool.description,
128
+ parameters: tool.parameters,
129
+ },
130
+ }));
131
+ }
132
+
133
+ const response = await fetch(`${this.baseUrl}/api/chat`, {
134
+ method: "POST",
135
+ headers: { "Content-Type": "application/json" },
136
+ body: JSON.stringify(body),
137
+ signal: abortSignal,
138
+ });
139
+
140
+ if (!response.ok) {
141
+ const error = await response.text();
142
+ yield { type: "error", error: `Ollama error: ${error}` };
143
+ return;
144
+ }
145
+
146
+ const reader = response.body?.getReader();
147
+ if (!reader) {
148
+ yield { type: "error", error: "No response body" };
149
+ return;
150
+ }
151
+
152
+ const decoder = new TextDecoder();
153
+ let buffer = "";
154
+ let accumulatedToolCalls: ToolCall[] = [];
155
+
156
+ while (true) {
157
+ const { done, value } = await reader.read();
158
+ if (done) break;
159
+
160
+ buffer += decoder.decode(value, { stream: true });
161
+ const lines = buffer.split("\n");
162
+ buffer = lines.pop() || "";
163
+
164
+ for (const line of lines) {
165
+ if (!line.trim()) continue;
166
+
167
+ try {
168
+ const data = JSON.parse(line) as OllamaChatResponse;
169
+
170
+ if (data.message?.content) {
171
+ yield { type: "content", content: data.message.content };
172
+ }
173
+
174
+ if (data.message?.tool_calls?.length) {
175
+ for (const tc of data.message.tool_calls) {
176
+ const toolCall: ToolCall = {
177
+ id: `tc_${Date.now()}_${Math.random().toString(36).slice(2)}`,
178
+ name: tc.function.name,
179
+ arguments: tc.function.arguments,
180
+ };
181
+ accumulatedToolCalls.push(toolCall);
182
+ yield { type: "tool_call", toolCall };
183
+ }
184
+ }
185
+
186
+ if (data.done) {
187
+ yield { type: "done" };
188
+ }
189
+ } catch {
190
+ // Skip invalid JSON
191
+ }
192
+ }
193
+ }
194
+ }
195
+
196
+ private convertMessages(messages: ChatMessage[]): Array<{
197
+ role: string;
198
+ content: string;
199
+ }> {
200
+ return messages.map((msg) => {
201
+ if (msg.role === "tool") {
202
+ return {
203
+ role: "tool",
204
+ content: msg.content,
205
+ };
206
+ }
207
+ return {
208
+ role: msg.role,
209
+ content: msg.content,
210
+ };
211
+ });
212
+ }
213
+
214
+ private parseResponse(data: OllamaChatResponse): ChatMessage {
215
+ const result: ChatMessage = {
216
+ role: "assistant",
217
+ content: data.message.content || "",
218
+ };
219
+
220
+ if (data.message.tool_calls?.length) {
221
+ result.toolCalls = data.message.tool_calls.map((tc, idx) => ({
222
+ id: `tc_${Date.now()}_${idx}`,
223
+ name: tc.function.name,
224
+ arguments: tc.function.arguments,
225
+ }));
226
+ }
227
+
228
+ return result;
229
+ }
230
+
231
+ getModel(): string {
232
+ return this.model;
233
+ }
234
+
235
+ setModel(model: string): void {
236
+ this.model = model;
237
+ }
238
+ }
@@ -0,0 +1,143 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
+ import { MCPServerConfig } from "../config.js";
4
+ import { Tool, ToolResult } from "../tools/types.js";
5
+
6
+ export interface MCPConnection {
7
+ id: string;
8
+ client: Client;
9
+ transport: StdioClientTransport;
10
+ tools: Tool[];
11
+ }
12
+
13
+ export class MCPManager {
14
+ private connections: Map<string, MCPConnection> = new Map();
15
+
16
+ async connect(id: string, config: MCPServerConfig): Promise<MCPConnection> {
17
+ // Check if already connected
18
+ if (this.connections.has(id)) {
19
+ return this.connections.get(id)!;
20
+ }
21
+
22
+ const transport = new StdioClientTransport({
23
+ command: config.command,
24
+ args: config.args,
25
+ env: config.env,
26
+ });
27
+
28
+ const client = new Client({
29
+ name: "activo",
30
+ version: "0.2.0",
31
+ });
32
+
33
+ await client.connect(transport);
34
+
35
+ // Get available tools
36
+ const toolsResult = await client.listTools();
37
+ const tools: Tool[] = toolsResult.tools.map((t) => ({
38
+ name: `mcp_${id}_${t.name}`,
39
+ description: t.description || `MCP tool: ${t.name}`,
40
+ parameters: {
41
+ type: "object" as const,
42
+ properties: (t.inputSchema as any)?.properties || {},
43
+ required: (t.inputSchema as any)?.required,
44
+ },
45
+ handler: async (args: Record<string, unknown>): Promise<ToolResult> => {
46
+ return this.callTool(id, t.name, args);
47
+ },
48
+ }));
49
+
50
+ const connection: MCPConnection = {
51
+ id,
52
+ client,
53
+ transport,
54
+ tools,
55
+ };
56
+
57
+ this.connections.set(id, connection);
58
+ return connection;
59
+ }
60
+
61
+ async disconnect(id: string): Promise<void> {
62
+ const connection = this.connections.get(id);
63
+ if (connection) {
64
+ await connection.client.close();
65
+ this.connections.delete(id);
66
+ }
67
+ }
68
+
69
+ async disconnectAll(): Promise<void> {
70
+ for (const id of this.connections.keys()) {
71
+ await this.disconnect(id);
72
+ }
73
+ }
74
+
75
+ async callTool(connectionId: string, toolName: string, args: Record<string, unknown>): Promise<ToolResult> {
76
+ const connection = this.connections.get(connectionId);
77
+ if (!connection) {
78
+ return {
79
+ success: false,
80
+ content: "",
81
+ error: `MCP connection not found: ${connectionId}`,
82
+ };
83
+ }
84
+
85
+ try {
86
+ const result = await connection.client.callTool({
87
+ name: toolName,
88
+ arguments: args,
89
+ });
90
+
91
+ if (result.isError) {
92
+ return {
93
+ success: false,
94
+ content: "",
95
+ error: JSON.stringify(result.content),
96
+ };
97
+ }
98
+
99
+ const contentArray = result.content as Array<{ type: string; text?: string }>;
100
+ const content = contentArray
101
+ .filter((c) => c.type === "text" && c.text)
102
+ .map((c) => c.text!)
103
+ .join("\n");
104
+
105
+ return {
106
+ success: true,
107
+ content,
108
+ };
109
+ } catch (error) {
110
+ return {
111
+ success: false,
112
+ content: "",
113
+ error: String(error),
114
+ };
115
+ }
116
+ }
117
+
118
+ getAllTools(): Tool[] {
119
+ const tools: Tool[] = [];
120
+ for (const connection of this.connections.values()) {
121
+ tools.push(...connection.tools);
122
+ }
123
+ return tools;
124
+ }
125
+
126
+ getConnection(id: string): MCPConnection | undefined {
127
+ return this.connections.get(id);
128
+ }
129
+
130
+ isConnected(id: string): boolean {
131
+ return this.connections.has(id);
132
+ }
133
+ }
134
+
135
+ // Singleton instance
136
+ let mcpManager: MCPManager | null = null;
137
+
138
+ export function getMCPManager(): MCPManager {
139
+ if (!mcpManager) {
140
+ mcpManager = new MCPManager();
141
+ }
142
+ return mcpManager;
143
+ }
@@ -0,0 +1,221 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { execSync } from "child_process";
4
+ import { glob } from "glob";
5
+ import { Tool, ToolResult } from "./types.js";
6
+
7
+ // Read File Tool
8
+ export const readFileTool: Tool = {
9
+ name: "read_file",
10
+ description: "Read the contents of a file. Use this to view source code or any text file.",
11
+ parameters: {
12
+ type: "object",
13
+ required: ["filepath"],
14
+ properties: {
15
+ filepath: {
16
+ type: "string",
17
+ description: "Path to the file (relative or absolute)",
18
+ },
19
+ },
20
+ },
21
+ handler: async (args): Promise<ToolResult> => {
22
+ try {
23
+ const filepath = path.resolve(args.filepath as string);
24
+ if (!fs.existsSync(filepath)) {
25
+ return { success: false, content: "", error: `File not found: ${filepath}` };
26
+ }
27
+ const stat = fs.statSync(filepath);
28
+ if (stat.isDirectory()) {
29
+ return { success: false, content: "", error: "Path is a directory" };
30
+ }
31
+ const content = fs.readFileSync(filepath, "utf-8");
32
+ return { success: true, content };
33
+ } catch (error) {
34
+ return { success: false, content: "", error: String(error) };
35
+ }
36
+ },
37
+ };
38
+
39
+ // Write File Tool
40
+ export const writeFileTool: Tool = {
41
+ name: "write_file",
42
+ description: "Write content to a file. Creates directories if needed.",
43
+ parameters: {
44
+ type: "object",
45
+ required: ["filepath", "content"],
46
+ properties: {
47
+ filepath: {
48
+ type: "string",
49
+ description: "Path to write the file",
50
+ },
51
+ content: {
52
+ type: "string",
53
+ description: "Content to write",
54
+ },
55
+ },
56
+ },
57
+ handler: async (args): Promise<ToolResult> => {
58
+ try {
59
+ const filepath = path.resolve(args.filepath as string);
60
+ const dir = path.dirname(filepath);
61
+ if (!fs.existsSync(dir)) {
62
+ fs.mkdirSync(dir, { recursive: true });
63
+ }
64
+ fs.writeFileSync(filepath, args.content as string);
65
+ return { success: true, content: `Written to ${filepath}` };
66
+ } catch (error) {
67
+ return { success: false, content: "", error: String(error) };
68
+ }
69
+ },
70
+ };
71
+
72
+ // List Directory Tool
73
+ export const listDirectoryTool: Tool = {
74
+ name: "list_directory",
75
+ description: "List files and directories in a path.",
76
+ parameters: {
77
+ type: "object",
78
+ required: ["path"],
79
+ properties: {
80
+ path: {
81
+ type: "string",
82
+ description: "Directory path to list",
83
+ },
84
+ },
85
+ },
86
+ handler: async (args): Promise<ToolResult> => {
87
+ try {
88
+ const dirPath = path.resolve((args.path as string) || ".");
89
+ if (!fs.existsSync(dirPath)) {
90
+ return { success: false, content: "", error: `Directory not found: ${dirPath}` };
91
+ }
92
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
93
+ const result = entries.map((e) => `${e.isDirectory() ? "[DIR]" : "[FILE]"} ${e.name}`);
94
+ return { success: true, content: result.join("\n") };
95
+ } catch (error) {
96
+ return { success: false, content: "", error: String(error) };
97
+ }
98
+ },
99
+ };
100
+
101
+ // Grep Search Tool
102
+ export const grepSearchTool: Tool = {
103
+ name: "grep_search",
104
+ description: "Search for a pattern in files using regex.",
105
+ parameters: {
106
+ type: "object",
107
+ required: ["pattern"],
108
+ properties: {
109
+ pattern: {
110
+ type: "string",
111
+ description: "Search pattern (regex)",
112
+ },
113
+ path: {
114
+ type: "string",
115
+ description: "Directory or file to search (default: current)",
116
+ },
117
+ filePattern: {
118
+ type: "string",
119
+ description: "File pattern filter (e.g., *.ts)",
120
+ },
121
+ },
122
+ },
123
+ handler: async (args): Promise<ToolResult> => {
124
+ try {
125
+ const searchPath = (args.path as string) || ".";
126
+ let cmd = `grep -rn "${args.pattern}" "${searchPath}"`;
127
+ if (args.filePattern) {
128
+ cmd += ` --include="${args.filePattern}"`;
129
+ }
130
+ cmd += " 2>/dev/null || true";
131
+
132
+ const output = execSync(cmd, { encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 });
133
+ if (!output.trim()) {
134
+ return { success: true, content: "No matches found" };
135
+ }
136
+ const lines = output.trim().split("\n").slice(0, 50);
137
+ return { success: true, content: lines.join("\n") };
138
+ } catch (error) {
139
+ return { success: false, content: "", error: String(error) };
140
+ }
141
+ },
142
+ };
143
+
144
+ // Glob Search Tool
145
+ export const globSearchTool: Tool = {
146
+ name: "glob_search",
147
+ description: "Find files matching a glob pattern.",
148
+ parameters: {
149
+ type: "object",
150
+ required: ["pattern"],
151
+ properties: {
152
+ pattern: {
153
+ type: "string",
154
+ description: "Glob pattern (e.g., **/*.ts)",
155
+ },
156
+ path: {
157
+ type: "string",
158
+ description: "Base directory (default: current)",
159
+ },
160
+ },
161
+ },
162
+ handler: async (args): Promise<ToolResult> => {
163
+ try {
164
+ const basePath = (args.path as string) || ".";
165
+ const fullPattern = path.join(basePath, args.pattern as string);
166
+ const files = await glob(fullPattern, {
167
+ ignore: ["**/node_modules/**", "**/.git/**"],
168
+ });
169
+ if (files.length === 0) {
170
+ return { success: true, content: "No files found" };
171
+ }
172
+ return { success: true, content: files.slice(0, 100).join("\n") };
173
+ } catch (error) {
174
+ return { success: false, content: "", error: String(error) };
175
+ }
176
+ },
177
+ };
178
+
179
+ // Run Command Tool
180
+ export const runCommandTool: Tool = {
181
+ name: "run_command",
182
+ description: "Execute a shell command. Be careful with destructive commands.",
183
+ parameters: {
184
+ type: "object",
185
+ required: ["command"],
186
+ properties: {
187
+ command: {
188
+ type: "string",
189
+ description: "Command to execute",
190
+ },
191
+ },
192
+ },
193
+ handler: async (args): Promise<ToolResult> => {
194
+ try {
195
+ const cmd = args.command as string;
196
+ // Block dangerous commands
197
+ const blocked = ["rm -rf /", "mkfs", "dd if=", "> /dev/"];
198
+ if (blocked.some((b) => cmd.includes(b))) {
199
+ return { success: false, content: "", error: "Command blocked for safety" };
200
+ }
201
+ const output = execSync(cmd, {
202
+ encoding: "utf-8",
203
+ maxBuffer: 10 * 1024 * 1024,
204
+ timeout: 30000,
205
+ });
206
+ return { success: true, content: output };
207
+ } catch (error: any) {
208
+ return { success: false, content: error.stdout || "", error: error.stderr || error.message };
209
+ }
210
+ },
211
+ };
212
+
213
+ // All built-in tools
214
+ export const builtInTools: Tool[] = [
215
+ readFileTool,
216
+ writeFileTool,
217
+ listDirectoryTool,
218
+ grepSearchTool,
219
+ globSearchTool,
220
+ runCommandTool,
221
+ ];
@@ -0,0 +1,53 @@
1
+ import { Tool, ToolCall, ToolResult } from "./types.js";
2
+ import { builtInTools } from "./builtIn.js";
3
+ import { standardsTools } from "./standards.js";
4
+
5
+ export * from "./types.js";
6
+ export * from "./builtIn.js";
7
+ export * from "./standards.js";
8
+
9
+ // All available tools
10
+ export function getAllTools(): Tool[] {
11
+ return [...builtInTools, ...standardsTools];
12
+ }
13
+
14
+ // Get tool by name
15
+ export function getTool(name: string): Tool | undefined {
16
+ return getAllTools().find((t) => t.name === name);
17
+ }
18
+
19
+ // Execute a tool call
20
+ export async function executeTool(toolCall: ToolCall): Promise<ToolResult> {
21
+ const tool = getTool(toolCall.name);
22
+
23
+ if (!tool) {
24
+ return {
25
+ success: false,
26
+ content: "",
27
+ error: `Unknown tool: ${toolCall.name}`,
28
+ };
29
+ }
30
+
31
+ try {
32
+ return await tool.handler(toolCall.arguments);
33
+ } catch (error) {
34
+ return {
35
+ success: false,
36
+ content: "",
37
+ error: `Tool execution error: ${error}`,
38
+ };
39
+ }
40
+ }
41
+
42
+ // Get tool definitions for LLM
43
+ export function getToolDefinitions(): Array<{
44
+ name: string;
45
+ description: string;
46
+ parameters: Tool["parameters"];
47
+ }> {
48
+ return getAllTools().map((t) => ({
49
+ name: t.name,
50
+ description: t.description,
51
+ parameters: t.parameters,
52
+ }));
53
+ }