fix: sanitize tool names for OpenAI API compatibility in playground
Add sanitizeToolName function to convert package names to OpenAI-compatible format that matches pattern ^[a-zA-Z0-9_-]+$. Removes @ symbols, replaces / with _, and replaces other invalid characters with _. This fixes the error: "Invalid 'tools[0].name': string does not match pattern" when loading tools in the playground chat interface. Example transformations: - @tpmjs/hello-helloWorldTool → tpmjs_hello-helloWorldTool - firecrawl-aisdk-scrapeTool → firecrawl-aisdk-scrapeTool 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
0658aea425
commit
f59c2c5123
1 changed files with 74 additions and 47 deletions
|
|
@ -3,36 +3,49 @@ import { helloNameTool, helloWorldTool } from '@tpmjs/hello';
|
|||
import { crawlTool, scrapeTool, searchTool } from 'firecrawl-aisdk';
|
||||
|
||||
/**
|
||||
* Load a specific TPMJS tool by package name
|
||||
* Tool registry mapping package names + export names to actual tool functions
|
||||
* This is a static mapping required for Next.js/webpack bundling
|
||||
*/
|
||||
const TOOL_REGISTRY: Record<string, Record<string, any>> = {
|
||||
'@tpmjs/hello': {
|
||||
helloWorldTool,
|
||||
helloNameTool,
|
||||
},
|
||||
'firecrawl-aisdk': {
|
||||
scrapeTool,
|
||||
crawlTool,
|
||||
searchTool,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Load a specific TPMJS tool by package name and export name
|
||||
* Uses static imports to work with Next.js/webpack bundling
|
||||
*/
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Tool types from AI SDK are complex and using any is appropriate here
|
||||
export async function loadTpmjsTool(packageName: string): Promise<Record<string, any>> {
|
||||
export async function loadTpmjsTool(packageName: string, exportName: string): Promise<any | null> {
|
||||
try {
|
||||
// Map package names to their tool functions
|
||||
switch (packageName) {
|
||||
case '@tpmjs/hello':
|
||||
// Hello has multiple tools, return all of them
|
||||
return {
|
||||
helloWorld: helloWorldTool,
|
||||
helloName: helloNameTool,
|
||||
};
|
||||
|
||||
case 'firecrawl-aisdk':
|
||||
// Firecrawl has multiple tools, return all of them
|
||||
return {
|
||||
scrapeTool,
|
||||
crawlTool,
|
||||
searchTool,
|
||||
};
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown tool package: ${packageName}`);
|
||||
// Look up the package in the registry
|
||||
const packageTools = TOOL_REGISTRY[packageName];
|
||||
if (!packageTools) {
|
||||
console.warn(`Package not found in registry: ${packageName}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Look up the specific tool export
|
||||
const tool = packageTools[exportName];
|
||||
if (!tool) {
|
||||
console.warn(
|
||||
`Export '${exportName}' not found in package ${packageName}. Available exports:`,
|
||||
Object.keys(packageTools)
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return tool;
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
throw new Error(`Failed to load tool from package ${packageName}: ${error.message}`);
|
||||
}
|
||||
throw new Error(`Failed to load tool from package ${packageName}: Unknown error`);
|
||||
console.error(`Failed to load tool ${packageName}/${exportName}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -55,38 +68,52 @@ function isCoreTool(obj: unknown): obj is Record<string, any> {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize tool name to match OpenAI's requirements
|
||||
* Pattern: ^[a-zA-Z0-9_-]+$ (only letters, numbers, underscores, hyphens)
|
||||
*/
|
||||
function sanitizeToolName(name: string): string {
|
||||
return name
|
||||
.replace(/@/g, '') // Remove @ symbols
|
||||
.replace(/\//g, '_') // Replace / with _
|
||||
.replace(/[^a-zA-Z0-9_-]/g, '_'); // Replace any other invalid chars with _
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all installed TPMJS tools
|
||||
*
|
||||
* For now, this is a manual list. In the future, we can scan node_modules
|
||||
* for packages with the "tpmjs-tool" keyword.
|
||||
* Returns a flat object with all tools keyed by sanitized packageName-exportName
|
||||
*/
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Tool types from AI SDK are complex and using any is appropriate here
|
||||
export async function loadAllTools(): Promise<Record<string, any>> {
|
||||
const installedTools = ['@tpmjs/hello', 'firecrawl-aisdk'];
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Tool types from AI SDK are complex and using any is appropriate here
|
||||
const tools: Record<string, any> = {};
|
||||
|
||||
for (const packageName of installedTools) {
|
||||
try {
|
||||
const tool = await loadTpmjsTool(packageName);
|
||||
|
||||
// If the tool returns an object with multiple tools (like firecrawl), spread them
|
||||
if (tool && typeof tool === 'object' && !tool.description) {
|
||||
Object.assign(tools, tool);
|
||||
} else {
|
||||
// Single tool - use a cleaned name (remove hyphens, camelCase)
|
||||
const toolName = packageName
|
||||
.replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase())
|
||||
.replace(/-/g, '');
|
||||
tools[toolName] = tool;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to load tool ${packageName}:`, error);
|
||||
// Continue loading other tools even if one fails
|
||||
// Iterate through all registered packages
|
||||
for (const [packageName, packageTools] of Object.entries(TOOL_REGISTRY)) {
|
||||
for (const [exportName, tool] of Object.entries(packageTools)) {
|
||||
// Create a unique, sanitized key for this tool
|
||||
const toolKey = sanitizeToolName(`${packageName}-${exportName}`);
|
||||
tools[toolKey] = tool;
|
||||
}
|
||||
}
|
||||
|
||||
return tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of all available package names
|
||||
*/
|
||||
export function getAvailablePackages(): string[] {
|
||||
return Object.keys(TOOL_REGISTRY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of all export names for a given package
|
||||
*/
|
||||
export function getPackageExports(packageName: string): string[] {
|
||||
const packageTools = TOOL_REGISTRY[packageName];
|
||||
if (!packageTools) {
|
||||
return [];
|
||||
}
|
||||
return Object.keys(packageTools);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue