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:
Ajax Davis 2026-01-14 14:18:36 +10:00
parent b1dd3371cd
commit 2cd2b10cd0
91 changed files with 4019 additions and 195 deletions

View file

@ -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
};

View file

@ -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...`);
}
}

View file

@ -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;
}

View file

@ -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" },

View file

@ -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,

View file

@ -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,

View file

@ -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