feat: add dynamic tool loading system with Railway executor
Implements a complete dynamic tool loading system that allows the playground to discover and load tools from the TPMJS registry at runtime. **Architecture:** - Search tool package (@tpmjs/search-registry) - Searches registry for tools - Search API endpoint (/api/tools/search) - Text-based search with scoring - Pre-flight tool loading - Automatically searches and loads tools on every message - Railway executor service (Deno) - Loads tools from esm.sh via HTTP imports - Dynamic tool loader - Calls Railway to load and execute tools remotely **Key Components:** 1. Railway Executor (apps/railway-executor/) - Deno-based service that natively supports HTTP imports - Endpoints: /load-and-describe, /execute-tool, /cache/stats, /cache/clear - Deploys to Railway with deno run --allow-net --allow-env server.ts 2. Search Tool Package (packages/tools/search-registry/) - AI SDK v6 tool for searching TPMJS registry - Uses jsonSchema + inputSchema pattern - Searches /api/tools/search endpoint 3. Search API (apps/web/src/app/api/tools/search/) - Text-based search with composite scoring - Scores: text relevance + quality boost + download boost - Returns tool metadata with importUrl for dynamic loading 4. Dynamic Tool Loader (apps/playground/src/lib/dynamic-tool-loader.ts) - Calls Railway service to load tools from esm.sh - Creates tool wrappers that execute remotely - Process-level module cache + per-conversation tracking 5. Pre-flight Loading (apps/playground/src/app/api/chat/route.ts) - Automatically searches for tools on every user message - Loads top 5 matching tools before agent processes request - Merges with static tools for seamless experience **Technical Decisions:** - Deno over Node.js: Native HTTP import support without flags - Remote execution: Tools run in Railway sandbox, not Vercel - Pre-flight loading: Better UX than two-turn search pattern - Text search: BM25 had dependency issues, simple scoring works well **Environment Variables:** - RAILWAY_SERVICE_URL: https://endearing-commitment-production.up.railway.app 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
0612eac5e2
commit
2158ee6dfd
28 changed files with 37043 additions and 27 deletions
5
packages/tools/search-registry/.npmignore
Normal file
5
packages/tools/search-registry/.npmignore
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
src/
|
||||
tsconfig.json
|
||||
*.test.ts
|
||||
*.spec.ts
|
||||
.turbo
|
||||
63
packages/tools/search-registry/package.json
Normal file
63
packages/tools/search-registry/package.json
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
"name": "@tpmjs/search-registry",
|
||||
"version": "0.1.0",
|
||||
"description": "AI SDK tool for searching the TPMJS tool registry",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"keywords": ["tpmjs-tool", "ai", "search", "tool-registry"],
|
||||
"dependencies": {
|
||||
"ai": "6.0.0-beta.124",
|
||||
"zod": "^3.23.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tpmjs/tsconfig": "workspace:*",
|
||||
"typescript": "^5.7.2"
|
||||
},
|
||||
"tpmjs": {
|
||||
"category": "api-integration",
|
||||
"frameworks": ["vercel-ai"],
|
||||
"tools": [
|
||||
{
|
||||
"exportName": "searchTpmjsToolsTool",
|
||||
"description": "Search the TPMJS tool registry to find AI SDK tools by keyword, category, or description. Returns tool metadata for dynamic loading.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "query",
|
||||
"type": "string",
|
||||
"description": "Search query (keywords, tool names, descriptions)",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "category",
|
||||
"type": "string",
|
||||
"description": "Filter by category (optional)",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"type": "number",
|
||||
"description": "Maximum results (1-20, default 10)",
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"returns": {
|
||||
"type": "object",
|
||||
"description": "Search results with tool metadata including packageName, exportName, version, importUrl"
|
||||
},
|
||||
"aiAgent": {
|
||||
"useCase": "Use when you need a tool that isn't currently available. Search for tools by keyword or domain.",
|
||||
"examples": [
|
||||
"Search for 'weather' when asked about weather",
|
||||
"Search for 'wikipedia' when asked to search Wikipedia",
|
||||
"Search for 'database' when working with SQL"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
101
packages/tools/search-registry/src/index.ts
Normal file
101
packages/tools/search-registry/src/index.ts
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import { jsonSchema, tool } from 'ai';
|
||||
|
||||
/**
|
||||
* Input type for Search TPMJS Tools
|
||||
*/
|
||||
type SearchTpmjsToolsInput = {
|
||||
query: string;
|
||||
category?: string;
|
||||
limit?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* AI SDK tool for searching the TPMJS tool registry
|
||||
*
|
||||
* This meta-tool enables agents to discover and load tools dynamically from the registry.
|
||||
* When an agent needs a tool that isn't currently available, it can search for it by keyword,
|
||||
* category, or description. The search results include all metadata needed to dynamically
|
||||
* import the tool at runtime.
|
||||
*/
|
||||
export const searchTpmjsToolsTool = tool({
|
||||
description:
|
||||
"Search the TPMJS tool registry to find AI SDK tools. Use this when you need a tool that isn't currently available. Returns tool metadata including package names, export names, and import URLs.",
|
||||
inputSchema: jsonSchema<SearchTpmjsToolsInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
query: {
|
||||
type: 'string',
|
||||
description: 'Search query (keywords, tool names, descriptions)',
|
||||
},
|
||||
category: {
|
||||
type: 'string',
|
||||
description: 'Filter by tool category (optional)',
|
||||
enum: [
|
||||
'web-scraping',
|
||||
'data-processing',
|
||||
'file-operations',
|
||||
'communication',
|
||||
'database',
|
||||
'api-integration',
|
||||
'image-processing',
|
||||
'text-analysis',
|
||||
'automation',
|
||||
'ai-ml',
|
||||
'security',
|
||||
'monitoring',
|
||||
],
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
description: 'Maximum number of results (1-20, default 10)',
|
||||
minimum: 1,
|
||||
maximum: 20,
|
||||
},
|
||||
},
|
||||
required: ['query'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute({ query, category, limit = 10 }) {
|
||||
console.log('🔍 searchTpmjsTools.execute() called with:', { query, category, limit });
|
||||
|
||||
const params = new URLSearchParams({
|
||||
q: query,
|
||||
limit: String(limit),
|
||||
...(category && { category }),
|
||||
});
|
||||
|
||||
// Use environment variable or default to localhost for development
|
||||
const baseUrl = process.env.TPMJS_API_URL || 'http://localhost:3000';
|
||||
const url = `${baseUrl}/api/tools/search?${params}`;
|
||||
|
||||
console.log(`🌐 Fetching: ${url}`);
|
||||
|
||||
const response = await fetch(url);
|
||||
|
||||
console.log(`📡 Response status: ${response.status} ${response.statusText}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Search failed: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = (await response.json()) as any;
|
||||
console.log('📦 Search response data:', JSON.stringify(data, null, 2));
|
||||
|
||||
return {
|
||||
query,
|
||||
matchCount: data.results?.total || data.data?.length || 0,
|
||||
tools: (data.results?.tools || data.data || []).map((tool: any) => ({
|
||||
toolId: tool.id,
|
||||
packageName: tool.package.npmPackageName,
|
||||
exportName: tool.exportName,
|
||||
description: tool.description,
|
||||
category: tool.package.category,
|
||||
qualityScore: tool.qualityScore,
|
||||
frameworks: tool.package.frameworks,
|
||||
env: tool.package.env,
|
||||
version: tool.package.npmVersion,
|
||||
importUrl: `https://esm.sh/${tool.package.npmPackageName}@${tool.package.npmVersion}`,
|
||||
})),
|
||||
};
|
||||
},
|
||||
});
|
||||
12
packages/tools/search-registry/tsconfig.json
Normal file
12
packages/tools/search-registry/tsconfig.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": "@tpmjs/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue