From ee825aa97cde733b08fa5a096ec7056808d716b8 Mon Sep 17 00:00:00 2001 From: Ajax Davis Date: Fri, 12 Dec 2025 07:29:06 +1000 Subject: [PATCH] feat: add @tpmjs/registrySearch and @tpmjs/registryExecute SDK packages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create @tpmjs/registrySearch package for searching tool registry - Create @tpmjs/registryExecute package for executing tools via sandbox - Support self-hosted registries via TPMJS_API_URL and TPMJS_EXECUTOR_URL env vars - Add /sdk documentation page with usage examples and architecture - Add SDK link to navigation menu - Include design doc for registry SDK architecture πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/web/src/app/sdk/page.tsx | 558 +++++++++++++++++++ apps/web/src/components/AppHeader.tsx | 5 + docs/REGISTRY_SDK_DESIGN.md | 438 +++++++++++++++ packages/tools/registryExecute/README.md | 102 ++++ packages/tools/registryExecute/package.json | 72 +++ packages/tools/registryExecute/src/index.ts | 120 ++++ packages/tools/registryExecute/tsconfig.json | 12 + packages/tools/registrySearch/README.md | 102 ++++ packages/tools/registrySearch/package.json | 73 +++ packages/tools/registrySearch/src/index.ts | 102 ++++ packages/tools/registrySearch/tsconfig.json | 12 + pnpm-lock.yaml | 34 +- 12 files changed, 1626 insertions(+), 4 deletions(-) create mode 100644 apps/web/src/app/sdk/page.tsx create mode 100644 docs/REGISTRY_SDK_DESIGN.md create mode 100644 packages/tools/registryExecute/README.md create mode 100644 packages/tools/registryExecute/package.json create mode 100644 packages/tools/registryExecute/src/index.ts create mode 100644 packages/tools/registryExecute/tsconfig.json create mode 100644 packages/tools/registrySearch/README.md create mode 100644 packages/tools/registrySearch/package.json create mode 100644 packages/tools/registrySearch/src/index.ts create mode 100644 packages/tools/registrySearch/tsconfig.json diff --git a/apps/web/src/app/sdk/page.tsx b/apps/web/src/app/sdk/page.tsx new file mode 100644 index 0000000..09c1522 --- /dev/null +++ b/apps/web/src/app/sdk/page.tsx @@ -0,0 +1,558 @@ +import { Button } from '@tpmjs/ui/Button/Button'; +import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock'; +import { Container } from '@tpmjs/ui/Container/Container'; +import Link from 'next/link'; +import { AppHeader } from '~/components/AppHeader'; + +export const metadata = { + title: 'SDK - Registry Tools | TPMJS', + description: + 'Add two tools to your AI agent and instantly access thousands of tools from the TPMJS registry', +}; + +export default function SDKPage(): React.ReactElement { + return ( +
+ + +
+ + {/* Hero */} +
+
+ + New + +
+

+ Give Your Agent Access to Every Tool +

+

+ Add two tools to your AI SDK agent and instantly access thousands of tools from the + TPMJS registry. No configuration, no manual importsβ€”just dynamic tool discovery and + execution. +

+
+ + {/* Quick Start */} +
+

Quick Start

+
+ {/* Install */} +
+
+ + 1 + +

Install the packages

+
+ +
+ + {/* Add to agent */} +
+
+ + 2 + +

Add to your agent

+
+ +
+ + {/* That's it */} +
+
+ + 3 + +

That's it!

+
+

+ Your agent can now discover and execute any tool from the registry. Here's + what happens when a user asks for something: +

+
+
+ User: "Search the web for AI news and + summarize it" +
+
+ Agent: +
+
+
+ 1. Calls registrySearch + {`({ query: "web search" })`} +
+
+ 2. Finds @exalabs/ai-sdk::webSearch +
+
+ 3. Calls registryExecute + {`({ toolId: "@exalabs/ai-sdk::webSearch", params: {...} })`} +
+
4. Returns results to user
+
+
+
+
+
+ + {/* How It Works */} +
+

How It Works

+
+
+                {`β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
+β”‚                      Your AI Agent                               β”‚
+β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
+β”‚  β”‚ Your Tools  β”‚  β”‚ registrySearch β”‚  β”‚   registryExecute   β”‚   β”‚
+β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
+β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
+                            β”‚                      β”‚
+                            β–Ό                      β–Ό
+                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
+                  β”‚  TPMJS Registry β”‚   β”‚   Sandbox Executor      β”‚
+                  β”‚  tpmjs.com/api  β”‚   β”‚  executor.tpmjs.com     β”‚
+                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
+                            β”‚                      β”‚
+                            β–Ό                      β–Ό
+                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
+                  β”‚  Tool Metadata  β”‚   β”‚   Secure Deno Runtime   β”‚
+                  β”‚  1000+ tools    β”‚   β”‚   Isolated execution    β”‚
+                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜`}
+              
+
+
+ + {/* registrySearchTool */} +
+

registrySearchTool

+

+ Search the TPMJS registry to find tools for any task. Returns metadata including the{' '} + toolId needed for execution. +

+ +
+ {/* Parameters */} +
+

Parameters

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeRequiredDescription
querystringYesSearch query (keywords, tool names, descriptions)
categorystringNoFilter by category
limitnumberNoMax results (1-20, default 5)
+
+
+ + {/* Categories */} +
+

Categories

+
+ {[ + 'web-scraping', + 'data-processing', + 'file-operations', + 'communication', + 'database', + 'api-integration', + 'image-processing', + 'text-analysis', + 'automation', + 'ai-ml', + 'security', + 'monitoring', + ].map((category) => ( + + {category} + + ))} +
+
+ + {/* Return Value */} +
+

Return Value

+ +
+
+
+ + {/* registryExecuteTool */} +
+

registryExecuteTool

+

+ Execute any tool from the registry by its toolId + . Tools run in a secure sandboxβ€”no local installation required. +

+ +
+ {/* Parameters */} +
+

Parameters

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeRequiredDescription
toolIdstringYes + Tool identifier (format: package::exportName) +
paramsobjectYesParameters to pass to the tool
envobjectNoEnvironment variables (API keys)
+
+
+ + {/* Example */} +
+

Example

+ +
+ + {/* Return Value */} +
+

Return Value

+ +
+
+
+ + {/* Environment Variables */} +
+

Environment Variables

+

+ Both packages support self-hosted registries via environment variables. This is useful + for enterprise deployments or running your own tool registry. +

+ +
+
+ + + + + + + + + + + + + + + + + + + + +
VariableDefaultDescription
TPMJS_API_URLhttps://tpmjs.comBase URL for the registry API
TPMJS_EXECUTOR_URLhttps://executor.tpmjs.comURL for the sandbox executor
+
+ +
+

Self-Hosted Example

+ +
+
+
+ + {/* Security */} +
+

Security

+
+
+
🏝️
+

Sandboxed Execution

+

+ All tools run in an isolated Deno runtime on Railway. They cannot access your + local filesystem or environment. +

+
+
+
πŸ”
+

API Key Isolation

+

+ API keys are passed per-request and never stored. Each execution is stateless and + isolated. +

+
+
+
βœ…
+

Registry-Only Execution

+

+ Only tools registered in TPMJS can be executed. No arbitrary code execution is + possible. +

+
+
+
πŸ₯
+

Health Monitoring

+

+ Every tool is continuously health-checked. Broken tools are flagged and filtered + from search results. +

+
+
+
+ + {/* Vision & Future */} +
+

The Vision

+
+

+ We're building the{' '} + npm for AI tools. Just as npm + revolutionized JavaScript package sharing, TPMJS aims to create a universal + ecosystem where AI agents can discover, share, and execute tools seamlessly. +

+

+ The registrySearch and{' '} + registryExecute tools are just the beginning. + Here's what's coming: +

+
+ +
+ {/* Collections */} +
+
+ + Coming Soon + +

Collections

+
+

+ Pre-configured tool bundles for specific domains. Think of them as “skill + packs” for your AI agent. +

+ +
+ + {/* API Keys */} +
+
+ + Planned + +

+ API Keys & Rate Limiting +

+
+

+ Personal API keys for authentication, usage tracking, and rate limiting. + Enterprise features for teams including usage analytics and billing. +

+
+ + {/* Tool Versioning */} +
+
+ + Planned + +

Tool Versioning

+
+

+ Pin specific tool versions in your agent configuration. Automatic compatibility + checking and migration guides when tools update. +

+
+ + {/* Private Registries */} +
+
+ + Planned + +

Private Registries

+
+

+ Run your own TPMJS instance for internal tools. Connect multiple registries + (public + private) in a single agent. Enterprise SSO and access controls. +

+
+ + {/* Streaming */} +
+
+ + Planned + +

Streaming Execution

+
+

+ Stream tool outputs for long-running operations. Real-time progress updates and + partial results for better UX. +

+
+
+
+ + {/* CTA */} +
+

Ready to Get Started?

+

+ Give your AI agent access to thousands of tools in minutes. +

+
+ + + + + + + + + +
+
+
+
+
+ ); +} diff --git a/apps/web/src/components/AppHeader.tsx b/apps/web/src/components/AppHeader.tsx index 36f4280..d1cba1b 100644 --- a/apps/web/src/components/AppHeader.tsx +++ b/apps/web/src/components/AppHeader.tsx @@ -43,6 +43,11 @@ export function AppHeader(): React.ReactElement { Spec + + + { + const params = new URLSearchParams({ + q: query, + limit: String(limit), + ...(category && { category }), + }); + + const response = await fetch(`${TPMJS_API_URL}/api/tools/search?${params}`); + const data = await response.json(); + + return { + query, + matchCount: data.data.length, + tools: data.data.map((tool: any) => ({ + // Unique identifier for registryExecuteTool + toolId: `${tool.package.npmPackageName}::${tool.exportName}`, + + // Human-readable info + name: tool.exportName, + package: tool.package.npmPackageName, + description: tool.description, + category: tool.category, + + // Execution requirements + requiredEnvVars: tool.env?.filter((e: any) => e.required).map((e: any) => e.name) || [], + + // Quality indicators + healthStatus: tool.executionHealth, + qualityScore: tool.qualityScore, + })), + }; + }, +}); +``` + +### 2. `@tpmjs/registryExecute` + +```typescript +import { tool } from 'ai'; +import { z } from 'zod'; + +const TPMJS_API_URL = process.env.TPMJS_API_URL || 'https://tpmjs.com'; +const TPMJS_EXECUTOR_URL = process.env.TPMJS_EXECUTOR_URL || 'https://executor.tpmjs.com'; + +export const registryExecuteTool = tool({ + description: `Execute a tool from the TPMJS registry. +Use registrySearchTool first to find the toolId, then call this with the toolId and parameters. +The tool runs in a secure sandbox - you don't need to install anything.`, + + parameters: z.object({ + toolId: z.string().describe('Tool identifier from registrySearchTool (format: "package::exportName")'), + params: z.record(z.any()).describe('Parameters to pass to the tool'), + env: z.record(z.string()).optional().describe('Environment variables (API keys) if required'), + }), + + execute: async ({ toolId, params, env }) => { + const [packageName, exportName] = toolId.split('::'); + + if (!packageName || !exportName) { + throw new Error(`Invalid toolId format. Expected "package::exportName", got "${toolId}"`); + } + + // Get tool metadata to find version and importUrl + const metaResponse = await fetch( + `${TPMJS_API_URL}/api/tools?package=${encodeURIComponent(packageName)}&export=${encodeURIComponent(exportName)}` + ); + const metaData = await metaResponse.json(); + const toolMeta = metaData.data?.[0]; + + if (!toolMeta) { + throw new Error(`Tool not found: ${toolId}`); + } + + // Execute via sandbox executor + const response = await fetch(`${TPMJS_EXECUTOR_URL}/execute-tool`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + packageName, + exportName, + version: toolMeta.package.npmVersion, + importUrl: toolMeta.importUrl || `https://esm.sh/${packageName}@${toolMeta.package.npmVersion}`, + params, + env: env || {}, + }), + }); + + const result = await response.json(); + + if (!result.success) { + throw new Error(result.error || 'Tool execution failed'); + } + + return { + toolId, + executionTimeMs: result.executionTimeMs, + output: result.output, + }; + }, +}); +``` + +## Usage Examples + +### Basic Usage + +```typescript +import { Agent } from 'ai'; +import { registrySearchTool } from '@tpmjs/registrySearch'; +import { registryExecuteTool } from '@tpmjs/registryExecute'; + +const agent = new Agent({ + model: 'anthropic/claude-sonnet-4-20250514', + tools: { + registrySearch: registrySearchTool, + registryExecute: registryExecuteTool, + }, +}); + +// User: "Search the web for latest AI news" +// Agent: +// 1. Calls registrySearch({ query: "web search" }) +// 2. Finds @exalabs/ai-sdk::webSearch +// 3. Calls registryExecute({ +// toolId: "@exalabs/ai-sdk::webSearch", +// params: { query: "latest AI news" }, +// env: { EXA_API_KEY: "..." } +// }) +// 4. Returns results to user +``` + +### With Their Own Tools + +```typescript +import { Agent } from 'ai'; +import { registrySearchTool } from '@tpmjs/registrySearch'; +import { registryExecuteTool } from '@tpmjs/registryExecute'; +import { weatherTool } from './tools/weather'; +import { databaseTool } from './tools/database'; + +const agent = new Agent({ + model: 'anthropic/claude-sonnet-4-20250514', + instructions: `You are a helpful assistant. + +For common tasks, use the built-in tools (weather, database). +For anything else, search the TPMJS registry to find appropriate tools.`, + + tools: { + // Their custom tools + weather: weatherTool, + database: databaseTool, + + // TPMJS registry access + registrySearch: registrySearchTool, + registryExecute: registryExecuteTool, + }, +}); +``` + +### Self-Hosted Registry + +```typescript +// Set environment variables for your own registry +// TPMJS_API_URL=https://registry.mycompany.com +// TPMJS_EXECUTOR_URL=https://executor.mycompany.com + +import { registrySearchTool } from '@tpmjs/registrySearch'; +import { registryExecuteTool } from '@tpmjs/registryExecute'; + +// Tools will automatically use your self-hosted URLs +const agent = new Agent({ + model: 'anthropic/claude-sonnet-4-20250514', + tools: { + registrySearch: registrySearchTool, + registryExecute: registryExecuteTool, + }, +}); +``` + +## API Endpoints Required + +### 1. Search API (existing, may need updates) + +``` +GET https://tpmjs.com/api/tools/search?q=web+search&limit=5&category=search +``` + +Response: +```json +{ + "success": true, + "data": [ + { + "id": "...", + "exportName": "webSearch", + "description": "Search the web...", + "category": "search", + "executionHealth": "HEALTHY", + "qualityScore": 0.9, + "env": [ + { "name": "EXA_API_KEY", "required": true } + ], + "package": { + "npmPackageName": "@exalabs/ai-sdk", + "npmVersion": "1.0.5" + }, + "importUrl": "https://esm.sh/@exalabs/ai-sdk@1.0.5" + } + ] +} +``` + +### 2. Execute API (existing Railway executor) + +``` +POST https://executor.tpmjs.com/execute-tool +``` + +Request: +```json +{ + "packageName": "@exalabs/ai-sdk", + "exportName": "webSearch", + "version": "1.0.5", + "importUrl": "https://esm.sh/@exalabs/ai-sdk@1.0.5", + "params": { "query": "latest AI news" }, + "env": { "EXA_API_KEY": "..." } +} +``` + +Response: +```json +{ + "success": true, + "output": { ... }, + "executionTimeMs": 1234 +} +``` + +## Package Structure + +### @tpmjs/registrySearch + +``` +packages/registrySearch/ +β”œβ”€β”€ package.json +β”œβ”€β”€ tsconfig.json +β”œβ”€β”€ src/ +β”‚ └── index.ts # Exports registrySearchTool +└── README.md +``` + +### @tpmjs/registryExecute + +``` +packages/registryExecute/ +β”œβ”€β”€ package.json +β”œβ”€β”€ tsconfig.json +β”œβ”€β”€ src/ +β”‚ └── index.ts # Exports registryExecuteTool +└── README.md +``` + +### package.json (@tpmjs/registrySearch) + +```json +{ + "name": "@tpmjs/registrySearch", + "version": "0.1.0", + "description": "Search the TPMJS tool registry from any AI SDK agent", + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "keywords": ["tpmjs", "tpmjs-tool", "ai-sdk", "vercel-ai", "tools", "registry", "search"], + "peerDependencies": { + "ai": "^4.0.0", + "zod": "^3.0.0" + } +} +``` + +### package.json (@tpmjs/registryExecute) + +```json +{ + "name": "@tpmjs/registryExecute", + "version": "0.1.0", + "description": "Execute tools from the TPMJS registry in any AI SDK agent", + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "keywords": ["tpmjs", "tpmjs-tool", "ai-sdk", "vercel-ai", "tools", "registry", "execute"], + "peerDependencies": { + "ai": "^4.0.0", + "zod": "^3.0.0" + } +} +``` + +## Security Considerations + +1. **Sandboxed Execution**: All tools run in the Railway Deno sandbox, not in the user's process +2. **No Code Injection**: Users can't execute arbitrary code, only registered tools +3. **API Key Isolation**: Keys are passed per-request, not stored +4. **Health Checks**: Only HEALTHY tools should be returned in search results by default + +## Next Steps + +1. [ ] Create `packages/registrySearch` directory +2. [ ] Create `packages/registryExecute` directory +3. [ ] Implement `registrySearchTool` +4. [ ] Implement `registryExecuteTool` +5. [ ] Add search API endpoint if not exists +6. [ ] Write README with examples for each package +7. [ ] Publish to npm +8. [ ] Create demo agent using the SDK + +## Future Enhancements + +1. **Tool Recommendations**: Based on conversation context, proactively suggest tools +2. **Tool Chaining**: Pre-built workflows combining multiple tools +3. **Local Caching**: Cache tool metadata for faster searches +4. **Type Generation**: Generate TypeScript types for popular tools +5. **Usage Analytics**: Track which tools are used most (anonymized) +6. **Rate Limiting**: Add rate limiting when needed diff --git a/packages/tools/registryExecute/README.md b/packages/tools/registryExecute/README.md new file mode 100644 index 0000000..b3d03c2 --- /dev/null +++ b/packages/tools/registryExecute/README.md @@ -0,0 +1,102 @@ +# @tpmjs/registryExecute + +Execute tools from the TPMJS registry in any AI SDK agent. Tools run in a secure sandbox - no local installation required. + +## Installation + +```bash +npm install @tpmjs/registryExecute +# or +pnpm add @tpmjs/registryExecute +``` + +## Usage + +```typescript +import { Agent } from 'ai'; +import { registrySearchTool } from '@tpmjs/registrySearch'; +import { registryExecuteTool } from '@tpmjs/registryExecute'; + +const agent = new Agent({ + model: 'anthropic/claude-sonnet-4-20250514', + tools: { + registrySearch: registrySearchTool, + registryExecute: registryExecuteTool, + }, +}); + +// The agent can now: +// 1. Search for tools: registrySearch({ query: "web scraping" }) +// 2. Execute found tools: registryExecute({ toolId: "@firecrawl/ai-sdk::scrapeTool", params: { url: "..." } }) +``` + +## Tool: registryExecuteTool + +Execute a tool from the TPMJS registry by its toolId. + +### Parameters + +| Name | Type | Required | Description | +|------|------|----------|-------------| +| `toolId` | string | Yes | Tool identifier (format: `package::exportName`) | +| `params` | object | Yes | Parameters to pass to the tool | +| `env` | object | No | Environment variables (API keys) if required | + +### Example + +```typescript +// Execute a web search tool +const result = await registryExecuteTool.execute({ + toolId: '@exalabs/ai-sdk::webSearch', + params: { query: 'latest AI news' }, + env: { EXA_API_KEY: 'your-api-key' }, +}); + +// Result: +// { +// toolId: '@exalabs/ai-sdk::webSearch', +// executionTimeMs: 1234, +// output: { results: [...] } +// } +``` + +### Returns + +```json +{ + "toolId": "@exalabs/ai-sdk::webSearch", + "executionTimeMs": 1234, + "output": { ... } +} +``` + +## Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `TPMJS_API_URL` | `https://tpmjs.com` | Base URL for the registry API | +| `TPMJS_EXECUTOR_URL` | `https://executor.tpmjs.com` | URL for the sandbox executor | + +### Self-Hosted Registry + +To use your own TPMJS registry and executor: + +```bash +export TPMJS_API_URL=https://registry.mycompany.com +export TPMJS_EXECUTOR_URL=https://executor.mycompany.com +``` + +## Security + +- All tools run in a sandboxed Deno environment on Railway +- API keys are passed per-request, never stored +- Only registered tools can be executed (no arbitrary code) + +## Related + +- [@tpmjs/registrySearch](https://www.npmjs.com/package/@tpmjs/registrySearch) - Find tools to execute +- [TPMJS Registry](https://tpmjs.com) - Browse all available tools + +## License + +MIT diff --git a/packages/tools/registryExecute/package.json b/packages/tools/registryExecute/package.json new file mode 100644 index 0000000..747ef63 --- /dev/null +++ b/packages/tools/registryExecute/package.json @@ -0,0 +1,72 @@ +{ + "name": "@tpmjs/registryExecute", + "version": "0.1.0", + "description": "Execute tools from the TPMJS registry in any AI SDK agent", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "type-check": "tsc --noEmit" + }, + "keywords": ["tpmjs-tool", "ai-sdk", "vercel-ai", "registry", "execute"], + "dependencies": { + "ai": "6.0.0-beta.124" + }, + "devDependencies": { + "@tpmjs/tsconfig": "workspace:*", + "typescript": "^5.7.2" + }, + "files": ["dist", "README.md"], + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/ajaxdavis/tpmjs.git", + "directory": "packages/tools/registryExecute" + }, + "homepage": "https://tpmjs.com", + "license": "MIT", + "tpmjs": { + "category": "api-integration", + "frameworks": ["vercel-ai"], + "tools": [ + { + "exportName": "registryExecuteTool", + "description": "Execute a tool from the TPMJS registry by toolId. Use registrySearchTool first to find toolIds.", + "parameters": [ + { + "name": "toolId", + "type": "string", + "description": "Tool identifier from registrySearchTool (format: 'package::exportName')", + "required": true + }, + { + "name": "params", + "type": "object", + "description": "Parameters to pass to the tool", + "required": true + }, + { + "name": "env", + "type": "object", + "description": "Environment variables (API keys) if required by the tool", + "required": false + } + ], + "returns": { + "type": "object", + "description": "Tool execution result with output and timing" + }, + "aiAgent": { + "useCase": "Use after finding a tool with registrySearchTool. Execute the tool by its toolId with the required parameters.", + "examples": [ + "Execute '@exalabs/ai-sdk::webSearch' with query parameter", + "Execute '@firecrawl/ai-sdk::scrapeTool' with url parameter" + ] + } + } + ] + } +} diff --git a/packages/tools/registryExecute/src/index.ts b/packages/tools/registryExecute/src/index.ts new file mode 100644 index 0000000..52ea991 --- /dev/null +++ b/packages/tools/registryExecute/src/index.ts @@ -0,0 +1,120 @@ +import { jsonSchema, tool } from 'ai'; + +const TPMJS_API_URL = process.env.TPMJS_API_URL || 'https://tpmjs.com'; +const TPMJS_EXECUTOR_URL = process.env.TPMJS_EXECUTOR_URL || 'https://executor.tpmjs.com'; + +/** + * Input type for Registry Execute Tool + */ +type RegistryExecuteInput = { + toolId: string; + params: Record; + env?: Record; +}; + +/** + * AI SDK tool for executing tools from the TPMJS registry + * + * Use registrySearchTool first to find the toolId, then execute with this tool. + * Tools run in a secure sandbox - no local installation required. + * + * Supports self-hosted registries via environment variables: + * - TPMJS_API_URL: Registry API (default: https://tpmjs.com) + * - TPMJS_EXECUTOR_URL: Sandbox executor (default: https://executor.tpmjs.com) + */ +export const registryExecuteTool = tool({ + description: + 'Execute a tool from the TPMJS registry. Use registrySearchTool first to find the toolId. Tools run in a secure sandbox.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + toolId: { + type: 'string', + description: "Tool identifier from registrySearchTool (format: 'package::exportName')", + }, + params: { + type: 'object', + description: 'Parameters to pass to the tool', + additionalProperties: true, + }, + env: { + type: 'object', + description: 'Environment variables (API keys) if required by the tool', + additionalProperties: { + type: 'string', + }, + }, + }, + required: ['toolId', 'params'], + additionalProperties: false, + }), + async execute({ toolId, params, env }) { + // Parse toolId format: "package::exportName" + const separatorIndex = toolId.lastIndexOf('::'); + if (separatorIndex === -1) { + throw new Error(`Invalid toolId format. Expected "package::exportName", got "${toolId}"`); + } + + const packageName = toolId.substring(0, separatorIndex); + const exportName = toolId.substring(separatorIndex + 2); + + if (!packageName || !exportName) { + throw new Error(`Invalid toolId format. Expected "package::exportName", got "${toolId}"`); + } + + // Fetch tool metadata to get version and importUrl + const metaParams = new URLSearchParams({ + q: exportName, + limit: '10', + }); + const metaResponse = await fetch(`${TPMJS_API_URL}/api/tools/search?${metaParams}`); + + if (!metaResponse.ok) { + throw new Error(`Failed to fetch tool metadata: ${metaResponse.statusText}`); + } + + // biome-ignore lint/suspicious/noExplicitAny: API response types vary + const metaData = (await metaResponse.json()) as any; + const toolsArray = metaData.results?.tools || []; + + // Find the exact tool match + // biome-ignore lint/suspicious/noExplicitAny: API response types vary + const toolMeta = toolsArray.find( + (t: any) => t.package.npmPackageName === packageName && t.exportName === exportName + ); + + if (!toolMeta) { + throw new Error(`Tool not found: ${toolId}`); + } + + const version = toolMeta.package.npmVersion; + const importUrl = `https://esm.sh/${packageName}@${version}`; + + // Execute via sandbox executor + const response = await fetch(`${TPMJS_EXECUTOR_URL}/execute-tool`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + packageName, + exportName, + version, + importUrl, + params, + env: env || {}, + }), + }); + + // biome-ignore lint/suspicious/noExplicitAny: API response types vary + const result = (await response.json()) as any; + + if (!result.success) { + throw new Error(result.error || 'Tool execution failed'); + } + + return { + toolId, + executionTimeMs: result.executionTimeMs, + output: result.output, + }; + }, +}); diff --git a/packages/tools/registryExecute/tsconfig.json b/packages/tools/registryExecute/tsconfig.json new file mode 100644 index 0000000..eb15958 --- /dev/null +++ b/packages/tools/registryExecute/tsconfig.json @@ -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"] +} diff --git a/packages/tools/registrySearch/README.md b/packages/tools/registrySearch/README.md new file mode 100644 index 0000000..320f7fe --- /dev/null +++ b/packages/tools/registrySearch/README.md @@ -0,0 +1,102 @@ +# @tpmjs/registrySearch + +Search the TPMJS tool registry from any AI SDK agent. Discover thousands of tools dynamically. + +## Installation + +```bash +npm install @tpmjs/registrySearch +# or +pnpm add @tpmjs/registrySearch +``` + +## Usage + +```typescript +import { Agent } from 'ai'; +import { registrySearchTool } from '@tpmjs/registrySearch'; +import { registryExecuteTool } from '@tpmjs/registryExecute'; + +const agent = new Agent({ + model: 'anthropic/claude-sonnet-4-20250514', + tools: { + registrySearch: registrySearchTool, + registryExecute: registryExecuteTool, + }, +}); + +// The agent can now: +// 1. Search for tools: registrySearch({ query: "web scraping" }) +// 2. Execute found tools: registryExecute({ toolId: "@firecrawl/ai-sdk::scrapeTool", params: { url: "..." } }) +``` + +## Tool: registrySearchTool + +Search the TPMJS registry to find AI SDK tools. + +### Parameters + +| Name | Type | Required | Description | +|------|------|----------|-------------| +| `query` | string | Yes | Search query (keywords, tool names, descriptions) | +| `category` | string | No | Filter by category | +| `limit` | number | No | Max results (1-20, default 5) | + +### Categories + +- `web-scraping` +- `data-processing` +- `file-operations` +- `communication` +- `database` +- `api-integration` +- `image-processing` +- `text-analysis` +- `automation` +- `ai-ml` +- `security` +- `monitoring` + +### Returns + +```json +{ + "query": "web scraping", + "matchCount": 3, + "tools": [ + { + "toolId": "@firecrawl/ai-sdk::scrapeTool", + "name": "scrapeTool", + "package": "@firecrawl/ai-sdk", + "description": "Scrape any website into clean markdown", + "category": "web-scraping", + "requiredEnvVars": ["FIRECRAWL_API_KEY"], + "healthStatus": "HEALTHY", + "qualityScore": 0.9 + } + ] +} +``` + +## Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `TPMJS_API_URL` | `https://tpmjs.com` | Base URL for the registry API | + +### Self-Hosted Registry + +To use your own TPMJS registry: + +```bash +export TPMJS_API_URL=https://registry.mycompany.com +``` + +## Related + +- [@tpmjs/registryExecute](https://www.npmjs.com/package/@tpmjs/registryExecute) - Execute tools found with this package +- [TPMJS Registry](https://tpmjs.com) - Browse all available tools + +## License + +MIT diff --git a/packages/tools/registrySearch/package.json b/packages/tools/registrySearch/package.json new file mode 100644 index 0000000..e52cb15 --- /dev/null +++ b/packages/tools/registrySearch/package.json @@ -0,0 +1,73 @@ +{ + "name": "@tpmjs/registrySearch", + "version": "0.1.0", + "description": "Search the TPMJS tool registry from any AI SDK agent", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "type-check": "tsc --noEmit" + }, + "keywords": ["tpmjs-tool", "ai-sdk", "vercel-ai", "registry", "search"], + "dependencies": { + "ai": "6.0.0-beta.124" + }, + "devDependencies": { + "@tpmjs/tsconfig": "workspace:*", + "typescript": "^5.7.2" + }, + "files": ["dist", "README.md"], + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/ajaxdavis/tpmjs.git", + "directory": "packages/tools/registrySearch" + }, + "homepage": "https://tpmjs.com", + "license": "MIT", + "tpmjs": { + "category": "api-integration", + "frameworks": ["vercel-ai"], + "tools": [ + { + "exportName": "registrySearchTool", + "description": "Search the TPMJS tool registry to find AI SDK tools. Returns tool metadata including toolId for use with registryExecuteTool.", + "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 5)", + "required": false + } + ], + "returns": { + "type": "object", + "description": "Search results with tool metadata including toolId, packageName, exportName, description, requiredEnvVars" + }, + "aiAgent": { + "useCase": "Use when you need to find a tool for a specific task. Search returns toolIds that can be executed with registryExecuteTool.", + "examples": [ + "Search for 'web scraping' to find scraping tools", + "Search for 'weather' to find weather API tools", + "Search for 'database' to find database tools" + ] + } + } + ] + } +} diff --git a/packages/tools/registrySearch/src/index.ts b/packages/tools/registrySearch/src/index.ts new file mode 100644 index 0000000..ef52213 --- /dev/null +++ b/packages/tools/registrySearch/src/index.ts @@ -0,0 +1,102 @@ +import { jsonSchema, tool } from 'ai'; + +const TPMJS_API_URL = process.env.TPMJS_API_URL || 'https://tpmjs.com'; + +/** + * Input type for Registry Search Tool + */ +type RegistrySearchInput = { + query: string; + category?: string; + limit?: number; +}; + +/** + * AI SDK tool for searching the TPMJS tool registry + * + * This tool enables agents to discover tools dynamically from the TPMJS registry. + * Search results include toolIds that can be executed with @tpmjs/registryExecute. + * + * Supports self-hosted registries via TPMJS_API_URL environment variable. + */ +export const registrySearchTool = tool({ + description: + 'Search the TPMJS tool registry to find AI SDK tools. Use this to discover tools for any task. Returns toolIds that can be executed with registryExecuteTool.', + inputSchema: jsonSchema({ + 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 5)', + minimum: 1, + maximum: 20, + }, + }, + required: ['query'], + additionalProperties: false, + }), + async execute({ query, category, limit = 5 }) { + const params = new URLSearchParams({ + q: query, + limit: String(limit), + ...(category && { category }), + }); + + const url = `${TPMJS_API_URL}/api/tools/search?${params}`; + const response = await fetch(url); + + if (!response.ok) { + throw new Error(`Search failed: ${response.statusText}`); + } + + // biome-ignore lint/suspicious/noExplicitAny: API response types vary + const data = (await response.json()) as any; + const toolsArray = data.results?.tools || []; + + return { + query, + matchCount: toolsArray.length, + // biome-ignore lint/suspicious/noExplicitAny: Tool types from API vary + tools: toolsArray.map((t: any) => ({ + // Unique identifier for registryExecuteTool + toolId: `${t.package.npmPackageName}::${t.exportName}`, + + // Human-readable info + name: t.exportName, + package: t.package.npmPackageName, + description: t.description, + category: t.package.category, + + // Execution requirements + requiredEnvVars: + t.package.env?.filter((e: any) => e.required).map((e: any) => e.name) || [], + + // Quality indicators + healthStatus: t.executionHealth, + qualityScore: t.qualityScore, + })), + }; + }, +}); diff --git a/packages/tools/registrySearch/tsconfig.json b/packages/tools/registrySearch/tsconfig.json new file mode 100644 index 0000000..eb15958 --- /dev/null +++ b/packages/tools/registrySearch/tsconfig.json @@ -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"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a5161a..6392560 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -131,10 +131,10 @@ importers: version: 10.4.22(postcss@8.5.6) eslint: specifier: ^9.39.1 - version: 9.39.1(jiti@2.6.1) + version: 9.39.1(jiti@1.21.7) eslint-config-next: specifier: ^16.0.4 - version: 16.0.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + version: 16.0.4(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) postcss: specifier: ^8.5.1 version: 8.5.6 @@ -249,10 +249,10 @@ importers: version: 10.4.22(postcss@8.5.6) eslint: specifier: ^9.39.1 - version: 9.39.1(jiti@1.21.7) + version: 9.39.1(jiti@2.6.1) eslint-config-next: specifier: ^16.0.4 - version: 16.0.4(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + version: 16.0.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) postcss: specifier: ^8.5.1 version: 8.5.6 @@ -600,6 +600,32 @@ importers: specifier: ^5.9.3 version: 5.9.3 + packages/tools/registryExecute: + dependencies: + ai: + specifier: 6.0.0-beta.124 + version: 6.0.0-beta.124(effect@3.18.4)(zod@4.1.13) + devDependencies: + '@tpmjs/tsconfig': + specifier: workspace:* + version: link:../../config/tsconfig + typescript: + specifier: ^5.7.2 + version: 5.9.3 + + packages/tools/registrySearch: + dependencies: + ai: + specifier: 6.0.0-beta.124 + version: 6.0.0-beta.124(effect@3.18.4)(zod@4.1.13) + devDependencies: + '@tpmjs/tsconfig': + specifier: workspace:* + version: link:../../config/tsconfig + typescript: + specifier: ^5.7.2 + version: 5.9.3 + packages/tools/search-registry: dependencies: ai: