@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.
- package/.prettierrc +9 -0
- package/CHANGELOG.md +8 -0
- package/eslint.config.mjs +28 -0
- package/package.json +53 -0
- package/src/engine/agent.ts +478 -0
- package/src/engine/llm-provider.test.ts +275 -0
- package/src/engine/llm-provider.ts +330 -0
- package/src/engine/stream-parser.ts +170 -0
- package/src/index.ts +142 -0
- package/src/mounts/mount-manager.test.ts +516 -0
- package/src/mounts/mount-manager.ts +327 -0
- package/src/mounts/mount-registry.ts +196 -0
- package/src/mounts/zod-to-string.test.ts +154 -0
- package/src/mounts/zod-to-string.ts +213 -0
- package/src/presets/agent-tools.ts +57 -0
- package/src/presets/index.ts +5 -0
- package/src/sandbox/README.md +1321 -0
- package/src/sandbox/bridges/README.md +571 -0
- package/src/sandbox/bridges/actor.test.ts +229 -0
- package/src/sandbox/bridges/actor.ts +195 -0
- package/src/sandbox/bridges/bridge-fixes.test.ts +614 -0
- package/src/sandbox/bridges/bucket.test.ts +300 -0
- package/src/sandbox/bridges/cleanup-reproduction.test.ts +225 -0
- package/src/sandbox/bridges/console-multiple.test.ts +187 -0
- package/src/sandbox/bridges/console.test.ts +157 -0
- package/src/sandbox/bridges/console.ts +122 -0
- package/src/sandbox/bridges/fetch.ts +93 -0
- package/src/sandbox/bridges/index.ts +78 -0
- package/src/sandbox/bridges/readable-stream.ts +323 -0
- package/src/sandbox/bridges/response.test.ts +154 -0
- package/src/sandbox/bridges/response.ts +123 -0
- package/src/sandbox/bridges/review-fixes.test.ts +331 -0
- package/src/sandbox/bridges/search.test.ts +475 -0
- package/src/sandbox/bridges/search.ts +264 -0
- package/src/sandbox/bridges/shared/body-methods.ts +93 -0
- package/src/sandbox/bridges/shared/cleanup.ts +112 -0
- package/src/sandbox/bridges/shared/convert.ts +76 -0
- package/src/sandbox/bridges/shared/headers.ts +181 -0
- package/src/sandbox/bridges/shared/index.ts +36 -0
- package/src/sandbox/bridges/shared/json-helpers.ts +77 -0
- package/src/sandbox/bridges/shared/path-parser.ts +109 -0
- package/src/sandbox/bridges/shared/promise-helper.ts +108 -0
- package/src/sandbox/bridges/shared/registry-setup.ts +84 -0
- package/src/sandbox/bridges/shared/response-object.ts +280 -0
- package/src/sandbox/bridges/shared/result-builder.ts +130 -0
- package/src/sandbox/bridges/shared/scope-helpers.ts +44 -0
- package/src/sandbox/bridges/shared/stream-reader.ts +90 -0
- package/src/sandbox/bridges/storage-bridge.test.ts +893 -0
- package/src/sandbox/bridges/storage.ts +421 -0
- package/src/sandbox/bridges/text-decoder.ts +190 -0
- package/src/sandbox/bridges/text-encoder.ts +102 -0
- package/src/sandbox/bridges/types.ts +39 -0
- package/src/sandbox/bridges/utils.ts +123 -0
- package/src/sandbox/index.ts +6 -0
- package/src/sandbox/quickjs-wasm.d.ts +9 -0
- package/src/sandbox/sandbox.test.ts +191 -0
- package/src/sandbox/sandbox.ts +831 -0
- package/src/sandbox/test-helper.ts +43 -0
- package/src/sandbox/test-mocks.ts +154 -0
- package/src/sandbox/user-stream.test.ts +77 -0
- package/src/skills/frontmatter.test.ts +305 -0
- package/src/skills/frontmatter.ts +200 -0
- package/src/skills/index.ts +9 -0
- package/src/skills/skills-loader.test.ts +237 -0
- package/src/skills/skills-loader.ts +200 -0
- package/src/tools/actor-storage-tools.ts +250 -0
- package/src/tools/code-tools.test.ts +199 -0
- package/src/tools/code-tools.ts +444 -0
- package/src/tools/file-tools.ts +206 -0
- package/src/tools/registry.ts +125 -0
- package/src/tools/script-tools.ts +145 -0
- package/src/tools/smartbucket-tools.ts +203 -0
- package/src/tools/sql-tools.ts +213 -0
- package/src/tools/tool-factory.ts +119 -0
- package/src/types.ts +512 -0
- package/tsconfig.eslint.json +5 -0
- package/tsconfig.json +15 -0
- 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];
|