@liquidmetal-ai/precip 1.0.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 (78) hide show
  1. package/.prettierrc +9 -0
  2. package/CHANGELOG.md +8 -0
  3. package/eslint.config.mjs +28 -0
  4. package/package.json +53 -0
  5. package/src/engine/agent.ts +478 -0
  6. package/src/engine/llm-provider.test.ts +275 -0
  7. package/src/engine/llm-provider.ts +330 -0
  8. package/src/engine/stream-parser.ts +170 -0
  9. package/src/index.ts +142 -0
  10. package/src/mounts/mount-manager.test.ts +516 -0
  11. package/src/mounts/mount-manager.ts +327 -0
  12. package/src/mounts/mount-registry.ts +196 -0
  13. package/src/mounts/zod-to-string.test.ts +154 -0
  14. package/src/mounts/zod-to-string.ts +213 -0
  15. package/src/presets/agent-tools.ts +57 -0
  16. package/src/presets/index.ts +5 -0
  17. package/src/sandbox/README.md +1321 -0
  18. package/src/sandbox/bridges/README.md +571 -0
  19. package/src/sandbox/bridges/actor.test.ts +229 -0
  20. package/src/sandbox/bridges/actor.ts +195 -0
  21. package/src/sandbox/bridges/bridge-fixes.test.ts +614 -0
  22. package/src/sandbox/bridges/bucket.test.ts +300 -0
  23. package/src/sandbox/bridges/cleanup-reproduction.test.ts +225 -0
  24. package/src/sandbox/bridges/console-multiple.test.ts +187 -0
  25. package/src/sandbox/bridges/console.test.ts +157 -0
  26. package/src/sandbox/bridges/console.ts +122 -0
  27. package/src/sandbox/bridges/fetch.ts +93 -0
  28. package/src/sandbox/bridges/index.ts +78 -0
  29. package/src/sandbox/bridges/readable-stream.ts +323 -0
  30. package/src/sandbox/bridges/response.test.ts +154 -0
  31. package/src/sandbox/bridges/response.ts +123 -0
  32. package/src/sandbox/bridges/review-fixes.test.ts +331 -0
  33. package/src/sandbox/bridges/search.test.ts +475 -0
  34. package/src/sandbox/bridges/search.ts +264 -0
  35. package/src/sandbox/bridges/shared/body-methods.ts +93 -0
  36. package/src/sandbox/bridges/shared/cleanup.ts +112 -0
  37. package/src/sandbox/bridges/shared/convert.ts +76 -0
  38. package/src/sandbox/bridges/shared/headers.ts +181 -0
  39. package/src/sandbox/bridges/shared/index.ts +36 -0
  40. package/src/sandbox/bridges/shared/json-helpers.ts +77 -0
  41. package/src/sandbox/bridges/shared/path-parser.ts +109 -0
  42. package/src/sandbox/bridges/shared/promise-helper.ts +108 -0
  43. package/src/sandbox/bridges/shared/registry-setup.ts +84 -0
  44. package/src/sandbox/bridges/shared/response-object.ts +280 -0
  45. package/src/sandbox/bridges/shared/result-builder.ts +130 -0
  46. package/src/sandbox/bridges/shared/scope-helpers.ts +44 -0
  47. package/src/sandbox/bridges/shared/stream-reader.ts +90 -0
  48. package/src/sandbox/bridges/storage-bridge.test.ts +893 -0
  49. package/src/sandbox/bridges/storage.ts +421 -0
  50. package/src/sandbox/bridges/text-decoder.ts +190 -0
  51. package/src/sandbox/bridges/text-encoder.ts +102 -0
  52. package/src/sandbox/bridges/types.ts +39 -0
  53. package/src/sandbox/bridges/utils.ts +123 -0
  54. package/src/sandbox/index.ts +6 -0
  55. package/src/sandbox/quickjs-wasm.d.ts +9 -0
  56. package/src/sandbox/sandbox.test.ts +191 -0
  57. package/src/sandbox/sandbox.ts +831 -0
  58. package/src/sandbox/test-helper.ts +43 -0
  59. package/src/sandbox/test-mocks.ts +154 -0
  60. package/src/sandbox/user-stream.test.ts +77 -0
  61. package/src/skills/frontmatter.test.ts +305 -0
  62. package/src/skills/frontmatter.ts +200 -0
  63. package/src/skills/index.ts +9 -0
  64. package/src/skills/skills-loader.test.ts +237 -0
  65. package/src/skills/skills-loader.ts +200 -0
  66. package/src/tools/actor-storage-tools.ts +250 -0
  67. package/src/tools/code-tools.test.ts +199 -0
  68. package/src/tools/code-tools.ts +444 -0
  69. package/src/tools/file-tools.ts +206 -0
  70. package/src/tools/registry.ts +125 -0
  71. package/src/tools/script-tools.ts +145 -0
  72. package/src/tools/smartbucket-tools.ts +203 -0
  73. package/src/tools/sql-tools.ts +213 -0
  74. package/src/tools/tool-factory.ts +119 -0
  75. package/src/types.ts +512 -0
  76. package/tsconfig.eslint.json +5 -0
  77. package/tsconfig.json +15 -0
  78. package/vitest.config.ts +33 -0
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Tool Registry - Manages tool registration and execution
3
+ */
4
+
5
+ import type { Tool, ToolContext, ToolResult, Logger } from '../types.js';
6
+
7
+ export class ToolRegistry {
8
+ private tools = new Map<string, Tool>();
9
+ private logger?: Logger;
10
+
11
+ constructor(logger?: Logger) {
12
+ this.logger = logger;
13
+ }
14
+
15
+ /**
16
+ * Register a tool
17
+ */
18
+ register(tool: Tool): void {
19
+ if (this.tools.has(tool.definition.name)) {
20
+ throw new Error(`Tool already registered: ${tool.definition.name}`);
21
+ }
22
+
23
+ this.tools.set(tool.definition.name, tool);
24
+ }
25
+
26
+ /**
27
+ * Register multiple tools
28
+ */
29
+ registerAll(tools: Tool[]): void {
30
+ for (const tool of tools) {
31
+ this.register(tool);
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Unregister a tool
37
+ */
38
+ unregister(name: string): boolean {
39
+ return this.tools.delete(name);
40
+ }
41
+
42
+ /**
43
+ * Get a tool by name
44
+ */
45
+ getTool(name: string): Tool | undefined {
46
+ return this.tools.get(name);
47
+ }
48
+
49
+ /**
50
+ * Get all tools
51
+ */
52
+ getAllTools(): Tool[] {
53
+ return Array.from(this.tools.values());
54
+ }
55
+
56
+ /**
57
+ * Get tool definitions for LLM
58
+ * Returns OpenAI-compatible tool definitions
59
+ */
60
+ getToolDefinitions(): any[] {
61
+ return this.getAllTools().map(tool => ({
62
+ type: 'function',
63
+ function: {
64
+ name: tool.definition.name,
65
+ description: tool.definition.description,
66
+ parameters: {
67
+ type: 'object',
68
+ properties: Object.fromEntries(
69
+ Object.entries(tool.definition.parameters).map(([name, param]) => {
70
+ // Strip out 'required' field from individual parameters
71
+ const { required: _, ...cleanParam } = param;
72
+ return [name, cleanParam];
73
+ })
74
+ ),
75
+ required: Object.entries(tool.definition.parameters)
76
+ .filter(([_, param]) => param.required)
77
+ .map(([name, _]) => name)
78
+ }
79
+ }
80
+ }));
81
+ }
82
+
83
+ /**
84
+ * Execute a tool
85
+ */
86
+ async execute(toolName: string, parameters: any, context: ToolContext): Promise<ToolResult> {
87
+ const tool = this.getTool(toolName);
88
+
89
+ if (!tool) {
90
+ return {
91
+ success: false,
92
+ error: `Tool not found: ${toolName}`
93
+ };
94
+ }
95
+
96
+ try {
97
+ const result = await tool.execute(parameters, context);
98
+
99
+ // Validate that the result is serializable (catches circular references, etc.)
100
+ try {
101
+ JSON.stringify(result);
102
+ } catch {
103
+ this.logger?.warn?.(`Tool '${toolName}' returned non-serializable result`);
104
+ return {
105
+ success: true,
106
+ result: { error: 'Tool result was not serializable' }
107
+ };
108
+ }
109
+
110
+ return {
111
+ success: true,
112
+ result
113
+ };
114
+ } catch (error) {
115
+ const errorMessage = error instanceof Error ? error.message : String(error);
116
+
117
+ this.logger?.error?.(`Tool execution failed: ${toolName}`, { error: errorMessage });
118
+
119
+ return {
120
+ success: false,
121
+ error: errorMessage
122
+ };
123
+ }
124
+ }
125
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Script Execution Tool - Run JavaScript scripts from mounted storage
3
+ *
4
+ * Reads a script from a mount path and executes it in the QuickJS sandbox
5
+ * with all the same bridges as run_code, plus an `args` global for passing arguments.
6
+ *
7
+ * Designed to work with the Agent Skills specification's scripts/ directory convention.
8
+ * @see https://agentskills.io/specification
9
+ */
10
+
11
+ import type { Tool, ToolContext, SandboxGlobals } from '../types.js';
12
+ import type { Bucket } from '@liquidmetal-ai/raindrop-framework';
13
+ import { Sandbox } from '../sandbox/index.js';
14
+ import type { CodeToolOptions } from './code-tools.js';
15
+ import { formatToolOutput, createMountGlobals, createBridgeInstallers } from './code-tools.js';
16
+
17
+ const DESCRIPTION = `Execute a JavaScript script stored in a mount path.
18
+
19
+ The script runs in the same sandbox as run_code with all the same APIs available
20
+ (read, write, list, remove, query, execute, search, fetch, console, etc.).
21
+
22
+ Additionally, the script receives an \`args\` global object containing the arguments
23
+ passed to this tool. Use \`args.paramName\` to access parameters.
24
+
25
+ Path format: /mount-name/path/to/script.js
26
+
27
+ Example: run_script({ path: "/knowledge/skills/data-cleanup/scripts/normalize.js", args: { table: "users", domain: "gmail.com" } })`;
28
+
29
+ /**
30
+ * Options for creating a script execution tool
31
+ */
32
+ export interface ScriptToolOptions extends CodeToolOptions {
33
+ /**
34
+ * Custom tool name (default: 'run_script')
35
+ */
36
+ name?: string;
37
+ }
38
+
39
+ /**
40
+ * Create a script execution tool
41
+ *
42
+ * Reads a script from a mount and executes it in the sandbox with args injection.
43
+ */
44
+ export function createScriptExecutionTool(options: ScriptToolOptions = {}): Tool {
45
+ const description = options.descriptionSuffix
46
+ ? DESCRIPTION + options.descriptionSuffix
47
+ : DESCRIPTION;
48
+
49
+ return {
50
+ definition: {
51
+ name: options.name || 'run_script',
52
+ description,
53
+ parameters: {
54
+ path: {
55
+ type: 'string',
56
+ description:
57
+ 'Path to the script file in a mount, e.g. /knowledge/skills/data-cleanup/scripts/normalize.js',
58
+ required: true
59
+ },
60
+ args: {
61
+ type: 'object',
62
+ description:
63
+ 'Arguments to pass to the script. Available as the `args` global inside the script.',
64
+ required: false
65
+ },
66
+ timeout: {
67
+ type: 'number',
68
+ description: 'Execution timeout in milliseconds (default: 30000)',
69
+ required: false
70
+ }
71
+ }
72
+ },
73
+
74
+ async execute(
75
+ params: { path: string; args?: Record<string, any>; timeout?: number },
76
+ context: ToolContext
77
+ ) {
78
+ const { mounts, logger } = context;
79
+ const mountManager = mounts;
80
+
81
+ // 1. Read the script from the mount
82
+ const parsed = mountManager.parsePath(params.path);
83
+ const mount = mountManager.getMount(parsed.mountName);
84
+
85
+ if (!mount) {
86
+ const available = Array.from(mountManager.getAllMounts().keys()).join(', ');
87
+ throw new Error(`Mount not found: ${parsed.mountName}. Available: ${available}`);
88
+ }
89
+
90
+ if (mount.type !== 'bucket' && mount.type !== 'smartbucket') {
91
+ throw new Error(
92
+ `Scripts can only be loaded from bucket or smartbucket mounts, got: ${mount.type}`
93
+ );
94
+ }
95
+
96
+ const bucket = mount.resource as Bucket;
97
+ const obj = await bucket.get(parsed.path);
98
+
99
+ if (!obj) {
100
+ throw new Error(`Script not found: ${params.path}`);
101
+ }
102
+
103
+ const code = await obj.text();
104
+
105
+ // 2. Build sandbox globals: mount globals + args + custom bridge
106
+ const customBridge =
107
+ typeof options.bridge === 'function' ? options.bridge(context) : options.bridge || {};
108
+
109
+ const mountGlobals = createMountGlobals(mountManager);
110
+
111
+ // Inject args as a plain object global (not a sandbox function)
112
+ const argsGlobal = params.args || {};
113
+
114
+ const asyncGlobals: SandboxGlobals = {
115
+ ...mountGlobals,
116
+ ...customBridge,
117
+ args: argsGlobal
118
+ };
119
+
120
+ // 3. Build bridge installers (shared with run_code)
121
+ const bridgeInstallers = createBridgeInstallers(mountManager);
122
+
123
+ // 4. Execute in sandbox
124
+ const sandbox = await Sandbox.getInstance();
125
+ const result = await sandbox.execute(code, asyncGlobals, {
126
+ timeoutMs: params.timeout || 30000,
127
+ memoryLimitBytes: 64 * 1024 * 1024,
128
+ logger,
129
+ bridgeInstallers
130
+ });
131
+
132
+ return formatToolOutput(result);
133
+ }
134
+ };
135
+ }
136
+
137
+ /**
138
+ * Default script execution tool
139
+ */
140
+ export const ScriptExecutionTool: Tool = createScriptExecutionTool();
141
+
142
+ /**
143
+ * All script tools
144
+ */
145
+ export const ScriptTools: Tool[] = [ScriptExecutionTool];
@@ -0,0 +1,203 @@
1
+ /**
2
+ * SmartBucket Tools - Semantic search and chunk search
3
+ * Mount-aware: operates on SmartBuckets via path prefixes
4
+ */
5
+
6
+ import type { Tool } from '../types.js';
7
+ import type { SmartBucket } from '@liquidmetal-ai/raindrop-framework';
8
+ import { createPathBasedTool } from './tool-factory.js';
9
+
10
+ /**
11
+ * Perform semantic search on a SmartBucket mount
12
+ *
13
+ * Example: semantic_search("/knowledge", "machine learning algorithms")
14
+ */
15
+ export const SemanticSearchTool: Tool = createPathBasedTool<{ path: string; query: string }, any>({
16
+ name: 'semantic_search',
17
+ description: `Perform semantic search on AI-indexed content in a SmartBucket.
18
+
19
+ This search uses AI embeddings to find relevant content based on meaning, not just keywords.
20
+
21
+ Path format: /smartbucket-name
22
+
23
+ Parameters:
24
+ • path - The SmartBucket mount path (e.g., /knowledge)
25
+ • query - The search query (semantic search finds similar content)
26
+
27
+ Returns: {results: [{text, source, score}], pagination: {total, page, pageSize, hasMore}, requestId}
28
+
29
+ Use the requestId with get_search_page to retrieve more results if hasMore is true.
30
+
31
+ Example: semantic_search("/knowledge", "machine learning algorithms")`,
32
+ parameters: {
33
+ path: {
34
+ type: 'string',
35
+ description: 'SmartBucket mount path, e.g., /knowledge',
36
+ required: true
37
+ },
38
+ query: {
39
+ type: 'string',
40
+ description: 'Search query for semantic search',
41
+ required: true
42
+ }
43
+ },
44
+ allowedMountTypes: ['smartbucket'],
45
+ mode: 'read',
46
+
47
+ async executor(mount, parsed, params) {
48
+ const smartbucket = mount.resource as SmartBucket;
49
+ const requestId = `search-${Date.now()}-${Math.random().toString(36).substring(7)}`;
50
+
51
+ const result = await smartbucket.search({
52
+ input: params.query,
53
+ requestId
54
+ });
55
+
56
+ return {
57
+ path: params.path,
58
+ query: params.query,
59
+ requestId,
60
+ results: result.results.map(r => ({
61
+ text: r.text,
62
+ source: r.source,
63
+ score: r.score
64
+ })),
65
+ pagination: {
66
+ total: result.pagination.total,
67
+ page: result.pagination.page,
68
+ pageSize: result.pagination.pageSize,
69
+ hasMore: result.pagination.hasMore
70
+ }
71
+ };
72
+ }
73
+ });
74
+
75
+ /**
76
+ * Perform chunk search on a SmartBucket mount
77
+ *
78
+ * Example: chunk_search("/knowledge", "neural network architecture")
79
+ */
80
+ export const ChunkSearchTool: Tool = createPathBasedTool<{ path: string; query: string }, any>({
81
+ name: 'chunk_search',
82
+ description: `Perform RAG (Retrieval-Augmented Generation) chunk search on a SmartBucket.
83
+
84
+ This search is optimized for finding specific chunks/sections of documents relevant to the query.
85
+ Best for finding specific facts, code examples, or detailed information.
86
+
87
+ Path format: /smartbucket-name
88
+
89
+ Parameters:
90
+ • path - The SmartBucket mount path (e.g., /knowledge)
91
+ • query - The search query
92
+
93
+ Returns: {results: [{text, source, score, chunkSignature}]}
94
+
95
+ Example: chunk_search("/knowledge", "how to configure authentication")`,
96
+ parameters: {
97
+ path: {
98
+ type: 'string',
99
+ description: 'SmartBucket mount path, e.g., /knowledge',
100
+ required: true
101
+ },
102
+ query: {
103
+ type: 'string',
104
+ description: 'Search query for chunk search',
105
+ required: true
106
+ }
107
+ },
108
+ allowedMountTypes: ['smartbucket'],
109
+ mode: 'read',
110
+
111
+ async executor(mount, parsed, params) {
112
+ const smartbucket = mount.resource as SmartBucket;
113
+ const requestId = `chunk-${Date.now()}-${Math.random().toString(36).substring(7)}`;
114
+
115
+ const result = await smartbucket.chunkSearch({
116
+ input: params.query,
117
+ requestId
118
+ });
119
+
120
+ return {
121
+ path: params.path,
122
+ query: params.query,
123
+ results: result.results.map(r => ({
124
+ text: r.text,
125
+ source: r.source,
126
+ score: r.score,
127
+ chunkSignature: r.chunkSignature
128
+ }))
129
+ };
130
+ }
131
+ });
132
+
133
+ /**
134
+ * Get paginated results from a previous SmartBucket search
135
+ *
136
+ * Example: get_search_page("/knowledge", "request-id-123", 2)
137
+ */
138
+ export const GetSearchPageTool: Tool = createPathBasedTool<{ path: string; requestId: string; page?: number }, any>({
139
+ name: 'get_search_page',
140
+ description: `Get the next page of results from a previous semantic_search.
141
+
142
+ Use this after a semantic_search when pagination.hasMore is true.
143
+
144
+ Path format: /smartbucket-name
145
+
146
+ Parameters:
147
+ • path - The SmartBucket mount path (e.g., /knowledge)
148
+ • requestId - The requestId from the previous semantic_search
149
+ • page - Page number to retrieve (default: 2)
150
+
151
+ Returns: {results: [{text, source, score}], pagination: {total, page, pageSize, hasMore}}
152
+
153
+ Example: get_search_page("/knowledge", "search-123456-abc", 2)`,
154
+ parameters: {
155
+ path: {
156
+ type: 'string',
157
+ description: 'SmartBucket mount path, e.g., /knowledge',
158
+ required: true
159
+ },
160
+ requestId: {
161
+ type: 'string',
162
+ description: 'Request ID from previous semantic_search',
163
+ required: true
164
+ },
165
+ page: {
166
+ type: 'number',
167
+ description: 'Page number to retrieve (default: 2)',
168
+ required: false
169
+ }
170
+ },
171
+ allowedMountTypes: ['smartbucket'],
172
+ mode: 'read',
173
+
174
+ async executor(mount, parsed, params) {
175
+ const smartbucket = mount.resource as SmartBucket;
176
+
177
+ const result = await smartbucket.getPaginatedResults({
178
+ requestId: params.requestId,
179
+ page: params.page || 2
180
+ });
181
+
182
+ return {
183
+ path: params.path,
184
+ requestId: params.requestId,
185
+ results: result.results.map(r => ({
186
+ text: r.text,
187
+ source: r.source,
188
+ score: r.score
189
+ })),
190
+ pagination: {
191
+ total: result.pagination.total,
192
+ page: result.pagination.page,
193
+ pageSize: result.pagination.pageSize,
194
+ hasMore: result.pagination.hasMore
195
+ }
196
+ };
197
+ }
198
+ });
199
+
200
+ /**
201
+ * All SmartBucket tools (excluding DocumentChat as requested)
202
+ */
203
+ export const SmartBucketTools: Tool[] = [SemanticSearchTool, ChunkSearchTool, GetSearchPageTool];
@@ -0,0 +1,213 @@
1
+ /**
2
+ * SQL Tools - Execute SQL queries on SqlDatabase
3
+ * Mount-aware: operates on multiple databases via sql:mount-name prefix
4
+ */
5
+
6
+ import type { Tool } from '../types.js';
7
+ import type { SqlDatabase } from '@liquidmetal-ai/raindrop-framework';
8
+ import { createPathBasedTool } from './tool-factory.js';
9
+
10
+ /**
11
+ * Execute a SQL query (SELECT, etc.)
12
+ *
13
+ * Example: sql_query("sql:analytics", "SELECT * FROM events LIMIT 10")
14
+ */
15
+ export const SqlQueryTool: Tool = createPathBasedTool<{ database: string; query: string; params?: any[] }, any>({
16
+ name: 'sql_query',
17
+ description: `Execute a SQL query (SELECT) and return results.
18
+
19
+ Database format: sql:mount-name
20
+
21
+ Returns: {results: Array<object>, success, rowCount, meta}
22
+
23
+ Example: sql_query("sql:analytics", "SELECT * FROM events LIMIT 10")
24
+ Returns: {"results": [{"id": 1, "type": "click"}], "success": true, "rowCount": 1}
25
+
26
+ With parameters: sql_query("sql:main", "SELECT * FROM users WHERE id = ?", [123])`,
27
+ parameters: {
28
+ database: {
29
+ type: 'string',
30
+ description: 'Database mount name with sql: prefix, e.g., sql:analytics',
31
+ required: true
32
+ },
33
+ query: {
34
+ type: 'string',
35
+ description: 'SQL query to execute',
36
+ required: true
37
+ },
38
+ params: {
39
+ type: 'array',
40
+ description: 'Optional query parameters for prepared statement',
41
+ required: false,
42
+ items: {
43
+ type: 'string',
44
+ description: 'Parameter value'
45
+ }
46
+ }
47
+ },
48
+ allowedMountTypes: ['database'],
49
+ pathParameterName: 'database',
50
+ // Note: This is marked as 'read' mode but does not validate SQL statement type.
51
+ // Database-specific write-capable queries (e.g., SELECT ... FOR UPDATE) may still
52
+ // execute. Use sql_execute for explicit write operations (INSERT/UPDATE/DELETE).
53
+ mode: 'read',
54
+
55
+ async executor(mount, parsed, params) {
56
+ const db = mount.resource as SqlDatabase;
57
+
58
+ // Execute query
59
+ const stmt = params.params
60
+ ? db.prepare(params.query).bind(...params.params)
61
+ : db.prepare(params.query);
62
+
63
+ const result = await stmt.all();
64
+
65
+ return {
66
+ results: result.results,
67
+ success: result.success,
68
+ rowCount: result.results?.length || 0,
69
+ meta: result.meta
70
+ };
71
+ }
72
+ });
73
+
74
+ /**
75
+ * Execute a SQL statement (INSERT, UPDATE, DELETE, etc.)
76
+ *
77
+ * Example: sql_execute("sql:main", "INSERT INTO users (name) VALUES (?)", ["Alice"])
78
+ */
79
+ export const SqlExecuteTool: Tool = createPathBasedTool<{ database: string; statement: string; params?: any[] }, any>({
80
+ name: 'sql_execute',
81
+ description: `Execute a SQL statement (INSERT, UPDATE, DELETE, CREATE, etc.) that modifies data.
82
+
83
+ Database format: sql:mount-name
84
+
85
+ Returns: {success, changes, lastRowId, meta}
86
+
87
+ Example: sql_execute("sql:main", "INSERT INTO users (name) VALUES (?)", ["Alice"])
88
+ Returns: {"success": true, "changes": 1, "lastRowId": 42}
89
+
90
+ Example: sql_execute("sql:main", "DELETE FROM users WHERE id = ?", [123])
91
+ Returns: {"success": true, "changes": 1}`,
92
+ parameters: {
93
+ database: {
94
+ type: 'string',
95
+ description: 'Database mount name with sql: prefix, e.g., sql:main',
96
+ required: true
97
+ },
98
+ statement: {
99
+ type: 'string',
100
+ description: 'SQL statement to execute',
101
+ required: true
102
+ },
103
+ params: {
104
+ type: 'array',
105
+ description: 'Optional statement parameters for prepared statement',
106
+ required: false,
107
+ items: {
108
+ type: 'string',
109
+ description: 'Parameter value'
110
+ }
111
+ }
112
+ },
113
+ allowedMountTypes: ['database'],
114
+ pathParameterName: 'database',
115
+ mode: 'write',
116
+
117
+ async executor(mount, parsed, params) {
118
+ const db = mount.resource as SqlDatabase;
119
+
120
+ // Execute statement
121
+ const stmt = params.params
122
+ ? db.prepare(params.statement).bind(...params.params)
123
+ : db.prepare(params.statement);
124
+
125
+ const result = await stmt.run();
126
+
127
+ return {
128
+ success: result.success,
129
+ changes: result.meta?.changes || 0,
130
+ lastRowId: result.meta?.last_row_id,
131
+ meta: result.meta
132
+ };
133
+ }
134
+ });
135
+
136
+ /**
137
+ * Execute a batch of SQL statements
138
+ *
139
+ * Example: sql_batch("sql:main", [
140
+ * { sql: "INSERT INTO users (name) VALUES (?)", params: ["Alice"] },
141
+ * { sql: "INSERT INTO users (name) VALUES (?)", params: ["Bob"] }
142
+ * ])
143
+ */
144
+ export const SqlBatchTool: Tool = createPathBasedTool<{ database: string; statements: Array<{ sql: string; params?: any[] }> }, any>({
145
+ name: 'sql_batch',
146
+ description: `Execute multiple SQL statements in a single batch transaction.
147
+
148
+ Database format: sql:mount-name
149
+
150
+ Returns: {success, count, results: [{success, changes, lastRowId}]}
151
+
152
+ Example: sql_batch("sql:main", [
153
+ {"sql": "INSERT INTO users (name) VALUES (?)", "params": ["Alice"]},
154
+ {"sql": "INSERT INTO users (name) VALUES (?)", "params": ["Bob"]}
155
+ ])
156
+ Returns: {"success": true, "count": 2, "results": [{"success": true, "changes": 1}, {"success": true, "changes": 1}]}`,
157
+ parameters: {
158
+ database: {
159
+ type: 'string',
160
+ description: 'Database mount name with sql: prefix, e.g., sql:main',
161
+ required: true
162
+ },
163
+ statements: {
164
+ type: 'array',
165
+ description: 'Array of SQL statements to execute',
166
+ required: true,
167
+ items: {
168
+ type: 'object',
169
+ description: 'SQL statement with optional parameters',
170
+ properties: {
171
+ sql: {
172
+ type: 'string',
173
+ description: 'SQL statement'
174
+ },
175
+ params: {
176
+ type: 'array',
177
+ description: 'Optional parameters'
178
+ }
179
+ }
180
+ }
181
+ }
182
+ },
183
+ allowedMountTypes: ['database'],
184
+ pathParameterName: 'database',
185
+ mode: 'write',
186
+
187
+ async executor(mount, parsed, params) {
188
+ const db = mount.resource as SqlDatabase;
189
+
190
+ // Prepare statements
191
+ const stmts = params.statements.map(({ sql, params }) => {
192
+ return params ? db.prepare(sql).bind(...params) : db.prepare(sql);
193
+ });
194
+
195
+ // Execute batch
196
+ const results = await db.batch(stmts);
197
+
198
+ return {
199
+ success: true,
200
+ count: results.length,
201
+ results: results.map((r: any) => ({
202
+ success: r.success,
203
+ changes: r.meta?.changes,
204
+ lastRowId: r.meta?.last_row_id
205
+ }))
206
+ };
207
+ }
208
+ });
209
+
210
+ /**
211
+ * All SQL tools
212
+ */
213
+ export const SqlTools: Tool[] = [SqlQueryTool, SqlExecuteTool, SqlBatchTool];