refactor: replace exportName with name throughout codebase
- Update TpmjsToolDefinitionSchema to only use 'name' field - Add 'sandbox' as valid category for sprites tools - Update all package.json files to use 'name' instead of 'exportName' - Update documentation and source files accordingly - Add 11 new sprites tools for sandbox/code-execution
This commit is contained in:
parent
b1dd3371cd
commit
2cd2b10cd0
91 changed files with 4019 additions and 195 deletions
|
|
@ -122,7 +122,7 @@ TPMJS provides two main tools:
|
|||
```typescript
|
||||
import { registrySearchTool } from '@tpmjs/registry-search';
|
||||
|
||||
// Returns tools with toolIds in format: "package::exportName"
|
||||
// Returns tools with toolIds in format: "package::name"
|
||||
const result = await registrySearchTool.execute({
|
||||
query: 'web scraping',
|
||||
category: 'web-scraping',
|
||||
|
|
@ -203,9 +203,9 @@ const myRegistryExecuteTool = tool({
|
|||
// YOUR CUSTOM EXECUTE FUNCTION
|
||||
async execute({ toolId, params, env }) {
|
||||
// Option 1: Execute locally instead of sandbox
|
||||
const [packageName, exportName] = toolId.split('::');
|
||||
const [packageName, name] = toolId.split('::');
|
||||
const pkg = await import(packageName);
|
||||
const toolFn = pkg[exportName];
|
||||
const toolFn = pkg[name];
|
||||
return toolFn.execute(params);
|
||||
|
||||
// Option 2: Route to your own executor
|
||||
|
|
@ -700,7 +700,7 @@ async function executeSandbox(
|
|||
timeout: number,
|
||||
abortSignal?: AbortSignal
|
||||
) {
|
||||
const [packageName, exportName] = toolId.split('::');
|
||||
const [packageName, name] = toolId.split('::');
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
||||
|
|
@ -714,7 +714,7 @@ async function executeSandbox(
|
|||
const response = await fetch(`${executorUrl}/execute-tool`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ packageName, exportName, params, env }),
|
||||
body: JSON.stringify({ packageName, name, params, env }),
|
||||
signal: combinedSignal,
|
||||
});
|
||||
|
||||
|
|
@ -735,14 +735,14 @@ async function executeLocal(
|
|||
params: any,
|
||||
basePath?: string
|
||||
) {
|
||||
const [packageName, exportName] = toolId.split('::');
|
||||
const [packageName, name] = toolId.split('::');
|
||||
const modulePath = basePath ? `${basePath}/${packageName}` : packageName;
|
||||
|
||||
const module = await import(modulePath);
|
||||
const toolFn = module[exportName] || module.default;
|
||||
const toolFn = module[name] || module.default;
|
||||
|
||||
if (!toolFn?.execute) {
|
||||
throw new Error(`Tool ${exportName} not found or missing execute function`);
|
||||
throw new Error(`Tool ${name} not found or missing execute function`);
|
||||
}
|
||||
|
||||
return toolFn.execute(params);
|
||||
|
|
@ -1151,7 +1151,7 @@ type SearchOutput = {
|
|||
query: string;
|
||||
matchCount: number;
|
||||
tools: Array<{
|
||||
toolId: string; // "package::exportName"
|
||||
toolId: string; // "package::name"
|
||||
name: string;
|
||||
package: string;
|
||||
description: string;
|
||||
|
|
@ -1170,7 +1170,7 @@ import { registryExecuteTool } from '@tpmjs/registry-execute';
|
|||
|
||||
// Input
|
||||
type ExecuteInput = {
|
||||
toolId: string; // "package::exportName"
|
||||
toolId: string; // "package::name"
|
||||
params: Record<string, unknown>; // Tool parameters
|
||||
env?: Record<string, string>; // Environment variables
|
||||
};
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ A **tool** in TPMJS is an atomic unit of computation that:
|
|||
```typescript
|
||||
// Tool metadata structure
|
||||
interface Tool {
|
||||
exportName: string;
|
||||
name: string;
|
||||
description: string;
|
||||
parameters: Parameter[];
|
||||
returns: ReturnType;
|
||||
|
|
@ -336,7 +336,7 @@ interface ExecutionStep {
|
|||
// Tool reference
|
||||
tool: {
|
||||
packageName: string;
|
||||
exportName: string;
|
||||
name: string;
|
||||
version?: string;
|
||||
};
|
||||
|
||||
|
|
@ -415,7 +415,7 @@ interface ExecutionStep {
|
|||
"order": 1,
|
||||
"tool": {
|
||||
"packageName": "tpmjs-web-scraper",
|
||||
"exportName": "scrapeUrl"
|
||||
"name": "scrapeUrl"
|
||||
},
|
||||
"purpose": "Fetch the product listings page HTML",
|
||||
"input": {
|
||||
|
|
@ -448,7 +448,7 @@ interface ExecutionStep {
|
|||
"order": 2,
|
||||
"tool": {
|
||||
"packageName": "tpmjs-html-parser",
|
||||
"exportName": "extractElements"
|
||||
"name": "extractElements"
|
||||
},
|
||||
"purpose": "Extract product cards from the HTML",
|
||||
"input": {
|
||||
|
|
@ -477,7 +477,7 @@ interface ExecutionStep {
|
|||
"order": 3,
|
||||
"tool": {
|
||||
"packageName": "tpmjs-price-extractor",
|
||||
"exportName": "extractPrices"
|
||||
"name": "extractPrices"
|
||||
},
|
||||
"purpose": "Parse and normalize price values",
|
||||
"input": {
|
||||
|
|
@ -507,7 +507,7 @@ interface ExecutionStep {
|
|||
"order": 3,
|
||||
"tool": {
|
||||
"packageName": "tpmjs-text-cleaner",
|
||||
"exportName": "cleanProductNames"
|
||||
"name": "cleanProductNames"
|
||||
},
|
||||
"purpose": "Clean and normalize product names",
|
||||
"input": {
|
||||
|
|
@ -533,7 +533,7 @@ interface ExecutionStep {
|
|||
"order": 4,
|
||||
"tool": {
|
||||
"packageName": "tpmjs-data-merger",
|
||||
"exportName": "mergeArrays"
|
||||
"name": "mergeArrays"
|
||||
},
|
||||
"purpose": "Combine prices and names into product objects",
|
||||
"input": {
|
||||
|
|
@ -561,7 +561,7 @@ interface ExecutionStep {
|
|||
"order": 5,
|
||||
"tool": {
|
||||
"packageName": "tpmjs-spreadsheet-generator",
|
||||
"exportName": "createXlsx"
|
||||
"name": "createXlsx"
|
||||
},
|
||||
"purpose": "Generate Excel spreadsheet with price comparison",
|
||||
"input": {
|
||||
|
|
@ -1587,7 +1587,7 @@ async function executeWithFallback(
|
|||
const modifiedStep = { ...step, tool };
|
||||
return await executeStep(modifiedStep, context);
|
||||
} catch (error) {
|
||||
console.log(`Tool ${tool.exportName} failed, trying fallback...`);
|
||||
console.log(`Tool ${tool.name} failed, trying fallback...`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
* │ TOOL │
|
||||
* │ ───── │
|
||||
* │ packageName: "tpmjs-web-scraper" │
|
||||
* │ exportName: "scrapeUrl" │
|
||||
* │ name: "scrapeUrl" │
|
||||
* │ description: "Fetches webpage..." │
|
||||
* │ parameters: [...] │
|
||||
* │ returns: { type: "string" } │
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
export interface Tool {
|
||||
id: string;
|
||||
packageName: string;
|
||||
exportName: string;
|
||||
name: string;
|
||||
description: string;
|
||||
parameters: ToolParameter[];
|
||||
returns: ToolReturn;
|
||||
|
|
@ -254,7 +254,7 @@ export interface PlanStep {
|
|||
|
||||
export interface ToolReference {
|
||||
packageName: string;
|
||||
exportName: string;
|
||||
name: string;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
|
|
@ -743,7 +743,7 @@ export interface ToolContext {
|
|||
}
|
||||
|
||||
export interface ToolPreview {
|
||||
exportName: string;
|
||||
name: string;
|
||||
purpose: string;
|
||||
tokenEstimate: number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,10 +112,10 @@ Returns tool metadata including name, description, required env vars, and how to
|
|||
matchCount: data.data.length,
|
||||
tools: data.data.map((tool: any) => ({
|
||||
// Unique identifier for registryExecuteTool
|
||||
toolId: `${tool.package.npmPackageName}::${tool.exportName}`,
|
||||
toolId: `${tool.package.npmPackageName}::${tool.name}`,
|
||||
|
||||
// Human-readable info
|
||||
name: tool.exportName,
|
||||
name: tool.name,
|
||||
package: tool.package.npmPackageName,
|
||||
description: tool.description,
|
||||
category: tool.category,
|
||||
|
|
@ -147,21 +147,21 @@ Use registrySearchTool first to find the toolId, then call this with the toolId
|
|||
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")'),
|
||||
toolId: z.string().describe('Tool identifier from registrySearchTool (format: "package::name")'),
|
||||
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('::');
|
||||
const [packageName, name] = toolId.split('::');
|
||||
|
||||
if (!packageName || !exportName) {
|
||||
throw new Error(`Invalid toolId format. Expected "package::exportName", got "${toolId}"`);
|
||||
if (!packageName || !name) {
|
||||
throw new Error(`Invalid toolId format. Expected "package::name", 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)}`
|
||||
`${TPMJS_API_URL}/api/tools?package=${encodeURIComponent(packageName)}&export=${encodeURIComponent(name)}`
|
||||
);
|
||||
const metaData = await metaResponse.json();
|
||||
const toolMeta = metaData.data?.[0];
|
||||
|
|
@ -176,7 +176,7 @@ The tool runs in a secure sandbox - you don't need to install anything.`,
|
|||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
packageName,
|
||||
exportName,
|
||||
name,
|
||||
version: toolMeta.package.npmVersion,
|
||||
importUrl: toolMeta.importUrl || `https://esm.sh/${packageName}@${toolMeta.package.npmVersion}`,
|
||||
params,
|
||||
|
|
@ -291,7 +291,7 @@ Response:
|
|||
"data": [
|
||||
{
|
||||
"id": "...",
|
||||
"exportName": "webSearch",
|
||||
"name": "webSearch",
|
||||
"description": "Search the web...",
|
||||
"category": "search",
|
||||
"executionHealth": "HEALTHY",
|
||||
|
|
@ -319,7 +319,7 @@ Request:
|
|||
```json
|
||||
{
|
||||
"packageName": "@exalabs/ai-sdk",
|
||||
"exportName": "webSearch",
|
||||
"name": "webSearch",
|
||||
"version": "1.0.5",
|
||||
"importUrl": "https://esm.sh/@exalabs/ai-sdk@1.0.5",
|
||||
"params": { "query": "latest AI news" },
|
||||
|
|
|
|||
|
|
@ -132,10 +132,10 @@ After every tool execution, the executor reports the result:
|
|||
|
||||
```typescript
|
||||
// On successful execution
|
||||
reportToolHealth(packageName, exportName, true).catch(() => {});
|
||||
reportToolHealth(packageName, name, true).catch(() => {});
|
||||
|
||||
// On failed execution
|
||||
reportToolHealth(packageName, exportName, false, error.message).catch(() => {});
|
||||
reportToolHealth(packageName, name, false, error.message).catch(() => {});
|
||||
```
|
||||
|
||||
The reporting is non-blocking (fire-and-forget) to avoid slowing down tool execution.
|
||||
|
|
@ -164,7 +164,7 @@ curl -s 'https://tpmjs.com/api/tools?limit=50' \
|
|||
-H 'Authorization: Bearer tpmjs_sk_your_api_key_here' | \
|
||||
jq '.data[] | select(.package.npmPackageName == "PACKAGE_NAME") | {
|
||||
packageName: .package.npmPackageName,
|
||||
exportName: .exportName,
|
||||
name: .name,
|
||||
importHealth: .importHealth,
|
||||
executionHealth: .executionHealth,
|
||||
healthCheckError: .healthCheckError,
|
||||
|
|
@ -188,7 +188,7 @@ curl -X POST 'https://tpmjs.com/api/tools/report-health' \
|
|||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"packageName": "@scope/package",
|
||||
"exportName": "toolName",
|
||||
"name": "toolName",
|
||||
"success": true
|
||||
}'
|
||||
```
|
||||
|
|
@ -197,7 +197,7 @@ curl -X POST 'https://tpmjs.com/api/tools/report-health' \
|
|||
|
||||
### 1. Executor Bugs Masking Tool Errors
|
||||
|
||||
**Problem:** Our executor had variables like `startTime`, `packageName`, and `exportName` declared inside try blocks but referenced in catch blocks. When errors occurred early (like during JSON parsing), the catch block crashed first, showing errors like "startTime is not defined" or "packageName is not defined" instead of the actual tool error.
|
||||
**Problem:** Our executor had variables like `startTime`, `packageName`, and `name` declared inside try blocks but referenced in catch blocks. When errors occurred early (like during JSON parsing), the catch block crashed first, showing errors like "startTime is not defined" or "packageName is not defined" instead of the actual tool error.
|
||||
|
||||
**Lesson:** Always ensure executor error handling is bulletproof. Any variable used in a catch block MUST be declared before the try block with sensible defaults:
|
||||
|
||||
|
|
@ -206,16 +206,16 @@ async function executeTool(req: Request): Promise<Response> {
|
|||
const startTime = Date.now();
|
||||
// Declare with defaults BEFORE try
|
||||
let packageName = 'unknown';
|
||||
let exportName = 'unknown';
|
||||
let name = 'unknown';
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { packageName: pkg, exportName: exp, ... } = body;
|
||||
const { packageName: pkg, name: exp, ... } = body;
|
||||
packageName = pkg || 'unknown';
|
||||
exportName = exp || 'unknown';
|
||||
name = exp || 'unknown';
|
||||
// ... rest of execution
|
||||
} catch (error) {
|
||||
// Now these are always in scope
|
||||
reportToolHealth(packageName, exportName, false, error.message);
|
||||
reportToolHealth(packageName, name, false, error.message);
|
||||
return Response.json({
|
||||
success: false,
|
||||
error: error.message,
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ A tutorial on implementing a hierarchical planning agent that dynamically loads
|
|||
interface Tool {
|
||||
id: string;
|
||||
packageName: string;
|
||||
exportName: string;
|
||||
name: string;
|
||||
description: string;
|
||||
parameters: Parameter[];
|
||||
returns: ReturnType;
|
||||
|
|
@ -447,7 +447,7 @@ function buildStepContext(
|
|||
const parts: string[] = [];
|
||||
|
||||
// 1. Current tool description
|
||||
parts.push(`## Current Tool: ${current.tool.exportName}`);
|
||||
parts.push(`## Current Tool: ${current.tool.name}`);
|
||||
parts.push(current.tool.description);
|
||||
parts.push(formatParameters(current.tool.parameters));
|
||||
|
||||
|
|
@ -463,7 +463,7 @@ function buildStepContext(
|
|||
// 3. Upcoming tools (just names, for continuity)
|
||||
if (upcoming.length > 0) {
|
||||
parts.push(`## Coming Next`);
|
||||
parts.push(upcoming.map(s => `- ${s.tool.exportName}: ${s.purpose}`).join('\n'));
|
||||
parts.push(upcoming.map(s => `- ${s.tool.name}: ${s.purpose}`).join('\n'));
|
||||
}
|
||||
|
||||
// 4. Relevant prior results (summarized)
|
||||
|
|
@ -500,7 +500,7 @@ async function executeWithFallbacks(
|
|||
try {
|
||||
return await executeTool(tool, input, context);
|
||||
} catch (error) {
|
||||
console.log(`Tool ${tool.exportName} failed, trying fallback...`);
|
||||
console.log(`Tool ${tool.name} failed, trying fallback...`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -516,7 +516,7 @@ async function executeTool(
|
|||
context: string
|
||||
): Promise<any> {
|
||||
const response = await fetch(
|
||||
`/api/tools/execute/${tool.packageName}/${tool.exportName}`,
|
||||
`/api/tools/execute/${tool.packageName}/${tool.name}`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ input, context }),
|
||||
|
|
@ -973,7 +973,7 @@ export async function POST(req: Request) {
|
|||
id: p.id,
|
||||
steps: p.steps.map(s => ({
|
||||
id: s.id,
|
||||
tool: s.tool.exportName,
|
||||
tool: s.tool.name,
|
||||
purpose: s.purpose
|
||||
})),
|
||||
estimatedCost: p.estimatedCost,
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ Tools declare their capabilities via a `tpmjs` field in package.json:
|
|||
],
|
||||
"tools": [
|
||||
{
|
||||
"exportName": "scrapeTool",
|
||||
"name": "scrapeTool",
|
||||
"description": "Scrape content from any webpage and return structured data",
|
||||
"parameters": [
|
||||
{
|
||||
|
|
@ -149,15 +149,15 @@ Tools declare their capabilities via a `tpmjs` field in package.json:
|
|||
|
||||
**1. Multi-tool packages**
|
||||
|
||||
One npm package can export multiple tools. Each has its own `exportName`:
|
||||
One npm package can export multiple tools. Each has its own `name`:
|
||||
|
||||
```json
|
||||
{
|
||||
"tpmjs": {
|
||||
"tools": [
|
||||
{ "exportName": "scrapeTool", "description": "..." },
|
||||
{ "exportName": "screenshotTool", "description": "..." },
|
||||
{ "exportName": "pdfExtractTool", "description": "..." }
|
||||
{ "name": "scrapeTool", "description": "..." },
|
||||
{ "name": "screenshotTool", "description": "..." },
|
||||
{ "name": "pdfExtractTool", "description": "..." }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -327,12 +327,12 @@ const conversationEnv = new Map<string, Record<string, string>>();
|
|||
|
||||
export async function loadToolDynamically(
|
||||
packageName: string,
|
||||
exportName: string,
|
||||
name: string,
|
||||
version: string,
|
||||
conversationId: string,
|
||||
env?: Record<string, string>
|
||||
): Promise<Tool | null> {
|
||||
const cacheKey = `${packageName}::${exportName}`;
|
||||
const cacheKey = `${packageName}::${name}`;
|
||||
|
||||
// Return cached tool if available
|
||||
if (moduleCache.has(cacheKey)) {
|
||||
|
|
@ -354,7 +354,7 @@ export async function loadToolDynamically(
|
|||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
packageName,
|
||||
exportName,
|
||||
name,
|
||||
version,
|
||||
importUrl: `https://esm.sh/${packageName}@${version}`,
|
||||
env: env || {},
|
||||
|
|
@ -378,7 +378,7 @@ export async function loadToolDynamically(
|
|||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
packageName,
|
||||
exportName,
|
||||
name,
|
||||
version,
|
||||
params,
|
||||
env: currentEnv, // Fresh on every execution
|
||||
|
|
@ -395,7 +395,7 @@ export async function loadToolDynamically(
|
|||
return toolWrapper;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Failed to load ${packageName}/${exportName}:`, error);
|
||||
console.error(`Failed to load ${packageName}/${name}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -469,7 +469,7 @@ export async function POST(request: Request) {
|
|||
const loadPromises = searchResults.tools.map(meta =>
|
||||
loadToolDynamically(
|
||||
meta.packageName,
|
||||
meta.exportName,
|
||||
meta.name,
|
||||
meta.version,
|
||||
conversationId,
|
||||
env
|
||||
|
|
@ -481,7 +481,7 @@ export async function POST(request: Request) {
|
|||
// 7. Add to toolset with sanitized names
|
||||
searchResults.tools.forEach((meta, i) => {
|
||||
if (loadedTools[i]) {
|
||||
const key = sanitizeToolName(`${meta.packageName}-${meta.exportName}`);
|
||||
const key = sanitizeToolName(`${meta.packageName}-${meta.name}`);
|
||||
discoveredTools[key] = loadedTools[i];
|
||||
}
|
||||
});
|
||||
|
|
@ -583,7 +583,7 @@ Run a separate service (Railway, Fly.io, AWS Lambda) that:
|
|||
```typescript
|
||||
// Sandbox service (runs on Railway/Fly.io)
|
||||
app.post('/execute-tool', async (req, res) => {
|
||||
const { packageName, exportName, version, params, env } = req.body;
|
||||
const { packageName, name, version, params, env } = req.body;
|
||||
|
||||
// Set env vars for this execution only
|
||||
const originalEnv = { ...process.env };
|
||||
|
|
@ -594,9 +594,9 @@ app.post('/execute-tool', async (req, res) => {
|
|||
const importUrl = `https://esm.sh/${packageName}@${version}`;
|
||||
const module = await import(importUrl);
|
||||
|
||||
const tool = module[exportName] || module.default;
|
||||
const tool = module[name] || module.default;
|
||||
if (!tool?.execute) {
|
||||
throw new Error(`No executable tool found at ${exportName}`);
|
||||
throw new Error(`No executable tool found at ${name}`);
|
||||
}
|
||||
|
||||
// Execute with timeout
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue