refactor: rename exportName to name across entire codebase
- Database: Migrate column export_name to name in tools table - Prisma schema: Update Tool model to use name field - Sync routes: Update keyword and changes sync to use name - Railway executor: Update API endpoints to use name parameter - API routes: Update all tool routes to use name field - Web app: Update all pages and components - Playground: Update tool loader and sidebar - create-basic-tools: Update types and generators - Scripts: Update sync and test scripts Database migration was done via direct SQL: ALTER TABLE tools RENAME COLUMN export_name TO name; The unique constraint remains on (package_id, name). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
deb9e3ae06
commit
e84eda7525
44 changed files with 222 additions and 233 deletions
|
|
@ -144,7 +144,7 @@ export async function POST(request: NextRequest) {
|
||||||
if (searchResult.tools && searchResult.tools.length > 0) {
|
if (searchResult.tools && searchResult.tools.length > 0) {
|
||||||
console.log(
|
console.log(
|
||||||
'🔧 Tools found:',
|
'🔧 Tools found:',
|
||||||
searchResult.tools.map((t: any) => `${t.packageName}/${t.exportName}`)
|
searchResult.tools.map((t: any) => `${t.packageName}/${t.name}`)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Dynamically load tools from esm.sh
|
// Dynamically load tools from esm.sh
|
||||||
|
|
@ -152,7 +152,7 @@ export async function POST(request: NextRequest) {
|
||||||
|
|
||||||
const toolsToLoad = searchResult.tools.map((meta: any) => ({
|
const toolsToLoad = searchResult.tools.map((meta: any) => ({
|
||||||
packageName: meta.packageName,
|
packageName: meta.packageName,
|
||||||
exportName: meta.exportName,
|
name: meta.name,
|
||||||
version: meta.version,
|
version: meta.version,
|
||||||
importUrl: meta.importUrl,
|
importUrl: meta.importUrl,
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
interface RawTool {
|
interface RawTool {
|
||||||
id: string;
|
id: string;
|
||||||
exportName: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
qualityScore: number;
|
qualityScore: number;
|
||||||
importHealth: 'HEALTHY' | 'BROKEN' | 'UNKNOWN';
|
importHealth: 'HEALTHY' | 'BROKEN' | 'UNKNOWN';
|
||||||
|
|
@ -25,7 +25,7 @@ function transformTool(tool: RawTool) {
|
||||||
return {
|
return {
|
||||||
toolId: tool.id,
|
toolId: tool.id,
|
||||||
packageName: tool.package?.npmPackageName,
|
packageName: tool.package?.npmPackageName,
|
||||||
exportName: tool.exportName,
|
name: tool.name,
|
||||||
description: tool.description,
|
description: tool.description,
|
||||||
category: tool.package?.category,
|
category: tool.package?.category,
|
||||||
version: tool.package?.npmVersion,
|
version: tool.package?.npmVersion,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { useEffect, useState } from 'react';
|
||||||
interface Tool {
|
interface Tool {
|
||||||
toolId?: string;
|
toolId?: string;
|
||||||
packageName: string;
|
packageName: string;
|
||||||
exportName: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
category: string;
|
category: string;
|
||||||
version: string;
|
version: string;
|
||||||
|
|
@ -62,7 +62,7 @@ export function ToolsSidebar(): React.ReactElement {
|
||||||
// Filter by search text
|
// Filter by search text
|
||||||
const matchesFilter =
|
const matchesFilter =
|
||||||
tool.packageName?.toLowerCase().includes(filter.toLowerCase()) ||
|
tool.packageName?.toLowerCase().includes(filter.toLowerCase()) ||
|
||||||
tool.exportName?.toLowerCase().includes(filter.toLowerCase()) ||
|
tool.name?.toLowerCase().includes(filter.toLowerCase()) ||
|
||||||
tool.description?.toLowerCase().includes(filter.toLowerCase()) ||
|
tool.description?.toLowerCase().includes(filter.toLowerCase()) ||
|
||||||
tool.category?.toLowerCase().includes(filter.toLowerCase());
|
tool.category?.toLowerCase().includes(filter.toLowerCase());
|
||||||
|
|
||||||
|
|
@ -124,7 +124,7 @@ export function ToolsSidebar(): React.ReactElement {
|
||||||
filteredTools.map((tool) => (
|
filteredTools.map((tool) => (
|
||||||
// biome-ignore lint/a11y/useSemanticElements: Custom styled card with complex layout
|
// biome-ignore lint/a11y/useSemanticElements: Custom styled card with complex layout
|
||||||
<div
|
<div
|
||||||
key={`${tool.packageName}-${tool.exportName}`}
|
key={`${tool.packageName}-${tool.name}`}
|
||||||
className="cursor-pointer rounded-lg border border-border bg-background p-3 transition-all hover:border-foreground-tertiary hover:shadow-sm"
|
className="cursor-pointer rounded-lg border border-border bg-background p-3 transition-all hover:border-foreground-tertiary hover:shadow-sm"
|
||||||
onClick={() => setSelectedTool(tool)}
|
onClick={() => setSelectedTool(tool)}
|
||||||
onKeyDown={(e) => e.key === 'Enter' && setSelectedTool(tool)}
|
onKeyDown={(e) => e.key === 'Enter' && setSelectedTool(tool)}
|
||||||
|
|
@ -132,7 +132,7 @@ export function ToolsSidebar(): React.ReactElement {
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<div className="mb-1 flex items-start justify-between gap-2">
|
<div className="mb-1 flex items-start justify-between gap-2">
|
||||||
<h3 className="text-sm font-medium leading-tight">{tool.exportName}</h3>
|
<h3 className="text-sm font-medium leading-tight">{tool.name}</h3>
|
||||||
<ToolHealthBadge
|
<ToolHealthBadge
|
||||||
importHealth={tool.importHealth}
|
importHealth={tool.importHealth}
|
||||||
executionHealth={tool.executionHealth}
|
executionHealth={tool.executionHealth}
|
||||||
|
|
@ -200,7 +200,7 @@ export function ToolsSidebar(): React.ReactElement {
|
||||||
|
|
||||||
{/* Tool header */}
|
{/* Tool header */}
|
||||||
<div className="mb-6 border-b border-border pb-4">
|
<div className="mb-6 border-b border-border pb-4">
|
||||||
<h2 className="mb-2 text-2xl font-bold text-foreground">{selectedTool.exportName}</h2>
|
<h2 className="mb-2 text-2xl font-bold text-foreground">{selectedTool.name}</h2>
|
||||||
<p className="mb-2 text-sm text-foreground-secondary">{selectedTool.packageName}</p>
|
<p className="mb-2 text-sm text-foreground-secondary">{selectedTool.packageName}</p>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Badge variant="secondary">{selectedTool.category}</Badge>
|
<Badge variant="secondary">{selectedTool.category}</Badge>
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@ const RAILWAY_SERVICE_URL =
|
||||||
/**
|
/**
|
||||||
* Generate cache key for a tool
|
* Generate cache key for a tool
|
||||||
*/
|
*/
|
||||||
function getCacheKey(packageName: string, exportName: string): string {
|
function getCacheKey(packageName: string, name: string): string {
|
||||||
return `${packageName}::${exportName}`;
|
return `${packageName}::${name}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -43,14 +43,14 @@ function getConversationEnv(conversationId: string): Record<string, string> {
|
||||||
*/
|
*/
|
||||||
export async function loadToolDynamically(
|
export async function loadToolDynamically(
|
||||||
packageName: string,
|
packageName: string,
|
||||||
exportName: string,
|
name: string,
|
||||||
version: string,
|
version: string,
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
importUrl?: string,
|
importUrl?: string,
|
||||||
env?: Record<string, string>
|
env?: Record<string, string>
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: Tool types from AI SDK are complex
|
// biome-ignore lint/suspicious/noExplicitAny: Tool types from AI SDK are complex
|
||||||
): Promise<any | null> {
|
): Promise<any | null> {
|
||||||
const cacheKey = getCacheKey(packageName, exportName);
|
const cacheKey = getCacheKey(packageName, name);
|
||||||
|
|
||||||
// Check cache first
|
// Check cache first
|
||||||
if (moduleCache.has(cacheKey)) {
|
if (moduleCache.has(cacheKey)) {
|
||||||
|
|
@ -59,7 +59,7 @@ export async function loadToolDynamically(
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`📦 Loading from Railway: ${packageName}/${exportName}`);
|
console.log(`📦 Loading from Railway: ${packageName}/${name}`);
|
||||||
console.log(`🔗 Railway URL: ${RAILWAY_SERVICE_URL}`);
|
console.log(`🔗 Railway URL: ${RAILWAY_SERVICE_URL}`);
|
||||||
|
|
||||||
// Call Railway service to load and describe tool
|
// Call Railway service to load and describe tool
|
||||||
|
|
@ -78,7 +78,7 @@ export async function loadToolDynamically(
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
packageName,
|
packageName,
|
||||||
exportName,
|
name,
|
||||||
version,
|
version,
|
||||||
importUrl: importUrl || `https://esm.sh/${packageName}@${version}`,
|
importUrl: importUrl || `https://esm.sh/${packageName}@${version}`,
|
||||||
env: env || {},
|
env: env || {},
|
||||||
|
|
@ -105,7 +105,7 @@ export async function loadToolDynamically(
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
|
|
||||||
if (fetchError instanceof Error && fetchError.name === 'AbortError') {
|
if (fetchError instanceof Error && fetchError.name === 'AbortError') {
|
||||||
console.error(`❌ Railway request timeout after 120s for ${packageName}/${exportName}`);
|
console.error(`❌ Railway request timeout after 120s for ${packageName}/${name}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,7 +130,7 @@ export async function loadToolDynamically(
|
||||||
: jsonSchema({ type: 'object', properties: {}, additionalProperties: false }),
|
: jsonSchema({ type: 'object', properties: {}, additionalProperties: false }),
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: Tool params are dynamic
|
// biome-ignore lint/suspicious/noExplicitAny: Tool params are dynamic
|
||||||
execute: async (params: any) => {
|
execute: async (params: any) => {
|
||||||
console.log(`🚀 Executing ${packageName}/${exportName} remotely with params:`, params);
|
console.log(`🚀 Executing ${packageName}/${name} remotely with params:`, params);
|
||||||
|
|
||||||
// Get the latest env vars for this conversation (not from closure!)
|
// Get the latest env vars for this conversation (not from closure!)
|
||||||
const currentEnv = getConversationEnv(conversationId);
|
const currentEnv = getConversationEnv(conversationId);
|
||||||
|
|
@ -144,7 +144,7 @@ export async function loadToolDynamically(
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
packageName,
|
packageName,
|
||||||
exportName,
|
name,
|
||||||
version,
|
version,
|
||||||
importUrl: importUrl || `https://esm.sh/${packageName}@${version}`,
|
importUrl: importUrl || `https://esm.sh/${packageName}@${version}`,
|
||||||
params,
|
params,
|
||||||
|
|
@ -171,7 +171,7 @@ export async function loadToolDynamically(
|
||||||
|
|
||||||
return toolWrapper;
|
return toolWrapper;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`❌ Failed to load ${packageName}#${exportName}:`, error);
|
console.error(`❌ Failed to load ${packageName}#${name}:`, error);
|
||||||
console.error(' Stack:', error instanceof Error ? error.stack : 'No stack trace');
|
console.error(' Stack:', error instanceof Error ? error.stack : 'No stack trace');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -183,7 +183,7 @@ export async function loadToolDynamically(
|
||||||
export async function loadToolsBatch(
|
export async function loadToolsBatch(
|
||||||
toolMetadata: Array<{
|
toolMetadata: Array<{
|
||||||
packageName: string;
|
packageName: string;
|
||||||
exportName: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
importUrl?: string;
|
importUrl?: string;
|
||||||
}>,
|
}>,
|
||||||
|
|
@ -197,15 +197,15 @@ export async function loadToolsBatch(
|
||||||
const promises = toolMetadata.map((meta) =>
|
const promises = toolMetadata.map((meta) =>
|
||||||
loadToolDynamically(
|
loadToolDynamically(
|
||||||
meta.packageName,
|
meta.packageName,
|
||||||
meta.exportName,
|
meta.name,
|
||||||
meta.version,
|
meta.version,
|
||||||
conversationId,
|
conversationId,
|
||||||
meta.importUrl,
|
meta.importUrl,
|
||||||
env
|
env
|
||||||
).then((tool) => ({
|
).then((tool) => ({
|
||||||
packageName: meta.packageName,
|
packageName: meta.packageName,
|
||||||
exportName: meta.exportName,
|
name: meta.name,
|
||||||
key: getCacheKey(meta.packageName, meta.exportName),
|
key: getCacheKey(meta.packageName, meta.name),
|
||||||
tool,
|
tool,
|
||||||
success: tool !== null,
|
success: tool !== null,
|
||||||
}))
|
}))
|
||||||
|
|
@ -232,7 +232,7 @@ export async function loadToolsBatch(
|
||||||
if (failed.length > 0) {
|
if (failed.length > 0) {
|
||||||
console.log('\n❌ Failed Tools:');
|
console.log('\n❌ Failed Tools:');
|
||||||
for (const result of failed) {
|
for (const result of failed) {
|
||||||
console.log(` - ${result.packageName}/${result.exportName}`);
|
console.log(` - ${result.packageName}/${result.name}`);
|
||||||
}
|
}
|
||||||
console.log('\n💡 Note: Tool failures have been reported to the health service.');
|
console.log('\n💡 Note: Tool failures have been reported to the health service.');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ const TOOL_REGISTRY: Record<string, Record<string, any>> = {
|
||||||
* Uses static imports to work with Next.js/webpack bundling
|
* 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
|
// biome-ignore lint/suspicious/noExplicitAny: Tool types from AI SDK are complex and using any is appropriate here
|
||||||
export async function loadTpmjsTool(packageName: string, exportName: string): Promise<any | null> {
|
export async function loadTpmjsTool(packageName: string, name: string): Promise<any | null> {
|
||||||
try {
|
try {
|
||||||
// Look up the package in the registry
|
// Look up the package in the registry
|
||||||
const packageTools = TOOL_REGISTRY[packageName];
|
const packageTools = TOOL_REGISTRY[packageName];
|
||||||
|
|
@ -27,10 +27,10 @@ export async function loadTpmjsTool(packageName: string, exportName: string): Pr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up the specific tool export
|
// Look up the specific tool export
|
||||||
const tool = packageTools[exportName];
|
const tool = packageTools[name];
|
||||||
if (!tool) {
|
if (!tool) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Export '${exportName}' not found in package ${packageName}. Available exports:`,
|
`Export '${name}' not found in package ${packageName}. Available exports:`,
|
||||||
Object.keys(packageTools)
|
Object.keys(packageTools)
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -38,7 +38,7 @@ export async function loadTpmjsTool(packageName: string, exportName: string): Pr
|
||||||
|
|
||||||
return tool;
|
return tool;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to load tool ${packageName}/${exportName}:`, error);
|
console.error(`Failed to load tool ${packageName}/${name}:`, error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -56,7 +56,7 @@ export function sanitizeToolName(name: string): string {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load all installed TPMJS tools
|
* Load all installed TPMJS tools
|
||||||
* Returns a flat object with all tools keyed by sanitized packageName-exportName
|
* Returns a flat object with all tools keyed by sanitized packageName-name
|
||||||
*/
|
*/
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: Tool types from AI SDK are complex and using any is appropriate here
|
// 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>> {
|
export async function loadAllTools(): Promise<Record<string, any>> {
|
||||||
|
|
@ -65,9 +65,9 @@ export async function loadAllTools(): Promise<Record<string, any>> {
|
||||||
|
|
||||||
// Iterate through all registered packages
|
// Iterate through all registered packages
|
||||||
for (const [packageName, packageTools] of Object.entries(TOOL_REGISTRY)) {
|
for (const [packageName, packageTools] of Object.entries(TOOL_REGISTRY)) {
|
||||||
for (const [exportName, tool] of Object.entries(packageTools)) {
|
for (const [name, tool] of Object.entries(packageTools)) {
|
||||||
// Create a unique, sanitized key for this tool
|
// Create a unique, sanitized key for this tool
|
||||||
const toolKey = sanitizeToolName(`${packageName}-${exportName}`);
|
const toolKey = sanitizeToolName(`${packageName}-${name}`);
|
||||||
tools[toolKey] = tool;
|
tools[toolKey] = tool;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ const TPMJS_API_URL = Deno.env.get('TPMJS_API_URL') || 'https://tpmjs.com';
|
||||||
*/
|
*/
|
||||||
async function reportToolHealth(
|
async function reportToolHealth(
|
||||||
packageName: string,
|
packageName: string,
|
||||||
exportName: string,
|
name: string,
|
||||||
success: boolean,
|
success: boolean,
|
||||||
error?: string
|
error?: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
|
@ -88,7 +88,7 @@ async function reportToolHealth(
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
packageName,
|
packageName,
|
||||||
exportName,
|
name,
|
||||||
success,
|
success,
|
||||||
error,
|
error,
|
||||||
}),
|
}),
|
||||||
|
|
@ -96,7 +96,7 @@ async function reportToolHealth(
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
console.log(
|
console.log(
|
||||||
`📊 Health reported for ${packageName}/${exportName}: ${success ? 'SUCCESS' : 'FAILURE'}`
|
`📊 Health reported for ${packageName}/${name}: ${success ? 'SUCCESS' : 'FAILURE'}`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
console.warn(`⚠️ Failed to report health: ${response.status}`);
|
console.warn(`⚠️ Failed to report health: ${response.status}`);
|
||||||
|
|
@ -113,7 +113,7 @@ async function reportToolHealth(
|
||||||
*/
|
*/
|
||||||
async function updateToolSchema(
|
async function updateToolSchema(
|
||||||
packageName: string,
|
packageName: string,
|
||||||
exportName: string,
|
name: string,
|
||||||
description: string,
|
description: string,
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: JSON Schema can have any structure
|
// biome-ignore lint/suspicious/noExplicitAny: JSON Schema can have any structure
|
||||||
inputSchema: any
|
inputSchema: any
|
||||||
|
|
@ -124,7 +124,7 @@ async function updateToolSchema(
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
packageName,
|
packageName,
|
||||||
exportName,
|
name,
|
||||||
description,
|
description,
|
||||||
inputSchema,
|
inputSchema,
|
||||||
}),
|
}),
|
||||||
|
|
@ -133,7 +133,7 @@ async function updateToolSchema(
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log(
|
console.log(
|
||||||
`📋 Schema updated for ${packageName}/${exportName}:`,
|
`📋 Schema updated for ${packageName}/${name}:`,
|
||||||
data.updated ? 'UPDATED' : 'NO CHANGE'
|
data.updated ? 'UPDATED' : 'NO CHANGE'
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -216,19 +216,19 @@ function sanitizeJsonSchema(schema: any): any {
|
||||||
async function loadAndDescribe(req: Request): Promise<Response> {
|
async function loadAndDescribe(req: Request): Promise<Response> {
|
||||||
try {
|
try {
|
||||||
const body = await req.json();
|
const body = await req.json();
|
||||||
const { packageName, exportName, version, importUrl, env } = body;
|
const { packageName, name, version, importUrl, env } = body;
|
||||||
|
|
||||||
if (!packageName || !exportName || !version) {
|
if (!packageName || !name || !version) {
|
||||||
return Response.json(
|
return Response.json(
|
||||||
{
|
{
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Missing required fields: packageName, exportName, version',
|
error: 'Missing required fields: packageName, name, version',
|
||||||
},
|
},
|
||||||
{ status: 400 }
|
{ status: 400 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const cacheKey = `${packageName}::${exportName}`;
|
const cacheKey = `${packageName}::${name}`;
|
||||||
|
|
||||||
// biome-ignore lint/suspicious/noImplicitAnyLet: Tool type is determined dynamically after import
|
// biome-ignore lint/suspicious/noImplicitAnyLet: Tool type is determined dynamically after import
|
||||||
let toolModule;
|
let toolModule;
|
||||||
|
|
@ -244,14 +244,14 @@ async function loadAndDescribe(req: Request): Promise<Response> {
|
||||||
console.log(`📦 Importing: ${url}`);
|
console.log(`📦 Importing: ${url}`);
|
||||||
|
|
||||||
const module = await import(url);
|
const module = await import(url);
|
||||||
let rawExport = module[exportName];
|
let rawExport = module[name];
|
||||||
|
|
||||||
if (!rawExport) {
|
if (!rawExport) {
|
||||||
console.error(`❌ Export "${exportName}" not found. Available:`, Object.keys(module));
|
console.error(`❌ Export "${name}" not found. Available:`, Object.keys(module));
|
||||||
return Response.json(
|
return Response.json(
|
||||||
{
|
{
|
||||||
success: false,
|
success: false,
|
||||||
error: `Export "${exportName}" not found in module`,
|
error: `Export "${name}" not found in module`,
|
||||||
availableExports: Object.keys(module),
|
availableExports: Object.keys(module),
|
||||||
},
|
},
|
||||||
{ status: 404 }
|
{ status: 404 }
|
||||||
|
|
@ -266,7 +266,7 @@ async function loadAndDescribe(req: Request): Promise<Response> {
|
||||||
|
|
||||||
// Strategy 1: Try calling with no arguments
|
// Strategy 1: Try calling with no arguments
|
||||||
try {
|
try {
|
||||||
console.log(` Trying: ${exportName}()`);
|
console.log(` Trying: ${name}()`);
|
||||||
factoryResult = rawExport();
|
factoryResult = rawExport();
|
||||||
if (factoryResult?.description && factoryResult?.execute) {
|
if (factoryResult?.description && factoryResult?.execute) {
|
||||||
console.log(' ✅ Success with no-args factory');
|
console.log(' ✅ Success with no-args factory');
|
||||||
|
|
@ -300,7 +300,7 @@ async function loadAndDescribe(req: Request): Promise<Response> {
|
||||||
// Try each config variation
|
// Try each config variation
|
||||||
for (const config of configVariations) {
|
for (const config of configVariations) {
|
||||||
try {
|
try {
|
||||||
console.log(` Trying: ${exportName}(`, Object.keys(config), ')');
|
console.log(` Trying: ${name}(`, Object.keys(config), ')');
|
||||||
factoryResult = rawExport(config);
|
factoryResult = rawExport(config);
|
||||||
if (factoryResult?.description && factoryResult?.execute) {
|
if (factoryResult?.description && factoryResult?.execute) {
|
||||||
console.log(' ✅ Success with config:', Object.keys(config));
|
console.log(' ✅ Success with config:', Object.keys(config));
|
||||||
|
|
@ -318,7 +318,7 @@ async function loadAndDescribe(req: Request): Promise<Response> {
|
||||||
try {
|
try {
|
||||||
const firstValue = Object.values(env)[0];
|
const firstValue = Object.values(env)[0];
|
||||||
if (firstValue) {
|
if (firstValue) {
|
||||||
console.log(` Trying: ${exportName}(firstEnvValue)`);
|
console.log(` Trying: ${name}(firstEnvValue)`);
|
||||||
factoryResult = rawExport(firstValue);
|
factoryResult = rawExport(firstValue);
|
||||||
if (factoryResult?.description && factoryResult?.execute) {
|
if (factoryResult?.description && factoryResult?.execute) {
|
||||||
console.log(' ✅ Success with single-arg factory');
|
console.log(' ✅ Success with single-arg factory');
|
||||||
|
|
@ -336,7 +336,7 @@ async function loadAndDescribe(req: Request): Promise<Response> {
|
||||||
return Response.json(
|
return Response.json(
|
||||||
{
|
{
|
||||||
success: false,
|
success: false,
|
||||||
error: `Tool "${exportName}" is a factory function but couldn't be initialized. Tried: no-args, config object, and single-arg patterns.`,
|
error: `Tool "${name}" is a factory function but couldn't be initialized. Tried: no-args, config object, and single-arg patterns.`,
|
||||||
hint: 'This tool may require specific configuration. Check package documentation.',
|
hint: 'This tool may require specific configuration. Check package documentation.',
|
||||||
},
|
},
|
||||||
{ status: 400 }
|
{ status: 400 }
|
||||||
|
|
@ -436,7 +436,7 @@ async function loadAndDescribe(req: Request): Promise<Response> {
|
||||||
return Response.json(
|
return Response.json(
|
||||||
{
|
{
|
||||||
success: false,
|
success: false,
|
||||||
error: `Tool "${exportName}" has no valid inputSchema. Tools must use AI SDK jsonSchema(), Zod v4 toJSONSchema(), or Zod v3 schemas.`,
|
error: `Tool "${name}" has no valid inputSchema. Tools must use AI SDK jsonSchema(), Zod v4 toJSONSchema(), or Zod v3 schemas.`,
|
||||||
debug: {
|
debug: {
|
||||||
hasInputSchema: !!toolModule.inputSchema,
|
hasInputSchema: !!toolModule.inputSchema,
|
||||||
availableMethods: toolModule.inputSchema ? Object.keys(toolModule.inputSchema) : [],
|
availableMethods: toolModule.inputSchema ? Object.keys(toolModule.inputSchema) : [],
|
||||||
|
|
@ -453,16 +453,14 @@ async function loadAndDescribe(req: Request): Promise<Response> {
|
||||||
const sanitizedSchema = sanitizeJsonSchema(rawJsonSchema);
|
const sanitizedSchema = sanitizeJsonSchema(rawJsonSchema);
|
||||||
|
|
||||||
// Update TPM.js database with the schema (async, non-blocking)
|
// Update TPM.js database with the schema (async, non-blocking)
|
||||||
updateToolSchema(packageName, exportName, toolModule.description, sanitizedSchema).catch(
|
updateToolSchema(packageName, name, toolModule.description, sanitizedSchema).catch((err) => {
|
||||||
(err) => {
|
console.warn('⚠️ Failed to update schema in database:', err);
|
||||||
console.warn('⚠️ Failed to update schema in database:', err);
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return Response.json({
|
return Response.json({
|
||||||
success: true,
|
success: true,
|
||||||
tool: {
|
tool: {
|
||||||
exportName,
|
name,
|
||||||
description: toolModule.description,
|
description: toolModule.description,
|
||||||
inputSchema: sanitizedSchema, // Plain JSON Schema - fully serializable
|
inputSchema: sanitizedSchema, // Plain JSON Schema - fully serializable
|
||||||
},
|
},
|
||||||
|
|
@ -486,32 +484,32 @@ async function executeTool(req: Request): Promise<Response> {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
// Declare these before try block so they're available in catch for error reporting
|
// Declare these before try block so they're available in catch for error reporting
|
||||||
let packageName = 'unknown';
|
let packageName = 'unknown';
|
||||||
let exportName = 'unknown';
|
let toolName = 'unknown';
|
||||||
try {
|
try {
|
||||||
const body = await req.json();
|
const body = await req.json();
|
||||||
const { packageName: pkg, exportName: exp, version, importUrl, params, env } = body;
|
const { packageName: pkg, name, version, importUrl, params, env } = body;
|
||||||
packageName = pkg || 'unknown';
|
packageName = pkg || 'unknown';
|
||||||
exportName = exp || 'unknown';
|
toolName = name || 'unknown';
|
||||||
|
|
||||||
console.log('📥 Execute request:', {
|
console.log('📥 Execute request:', {
|
||||||
packageName,
|
packageName,
|
||||||
exportName,
|
name: toolName,
|
||||||
version,
|
version,
|
||||||
envKeys: env ? Object.keys(env) : [],
|
envKeys: env ? Object.keys(env) : [],
|
||||||
envValues: env || {},
|
envValues: env || {},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!packageName || !exportName || !version) {
|
if (!packageName || !toolName || !version) {
|
||||||
return Response.json(
|
return Response.json(
|
||||||
{
|
{
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Missing required fields: packageName, exportName, version',
|
error: 'Missing required fields: packageName, name, version',
|
||||||
},
|
},
|
||||||
{ status: 400 }
|
{ status: 400 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const cacheKey = `${packageName}::${exportName}`;
|
const cacheKey = `${packageName}::${toolName}`;
|
||||||
|
|
||||||
// Inject environment variables FIRST - before cache check and factory calls
|
// Inject environment variables FIRST - before cache check and factory calls
|
||||||
// This ensures process.env is set when factory functions read from it
|
// This ensures process.env is set when factory functions read from it
|
||||||
|
|
@ -561,7 +559,7 @@ async function executeTool(req: Request): Promise<Response> {
|
||||||
console.log(`📦 Importing for execution: ${url}`);
|
console.log(`📦 Importing for execution: ${url}`);
|
||||||
|
|
||||||
const module = await import(url);
|
const module = await import(url);
|
||||||
let rawExport = module[exportName];
|
let rawExport = module[toolName];
|
||||||
|
|
||||||
if (!rawExport) {
|
if (!rawExport) {
|
||||||
return Response.json(
|
return Response.json(
|
||||||
|
|
@ -587,7 +585,7 @@ async function executeTool(req: Request): Promise<Response> {
|
||||||
|
|
||||||
// Strategy 1: Try calling with no arguments
|
// Strategy 1: Try calling with no arguments
|
||||||
try {
|
try {
|
||||||
console.log(` Trying: ${exportName}()`);
|
console.log(` Trying: ${toolName}()`);
|
||||||
factoryResult = rawExport();
|
factoryResult = rawExport();
|
||||||
if (factoryResult?.execute) {
|
if (factoryResult?.execute) {
|
||||||
console.log(' ✅ Success with no-args factory');
|
console.log(' ✅ Success with no-args factory');
|
||||||
|
|
@ -621,7 +619,7 @@ async function executeTool(req: Request): Promise<Response> {
|
||||||
// Try each config variation
|
// Try each config variation
|
||||||
for (const config of configVariations) {
|
for (const config of configVariations) {
|
||||||
try {
|
try {
|
||||||
console.log(` Trying: ${exportName}(`, Object.keys(config), ')');
|
console.log(` Trying: ${toolName}(`, Object.keys(config), ')');
|
||||||
factoryResult = rawExport(config);
|
factoryResult = rawExport(config);
|
||||||
if (factoryResult?.execute) {
|
if (factoryResult?.execute) {
|
||||||
console.log(' ✅ Success with config:', Object.keys(config));
|
console.log(' ✅ Success with config:', Object.keys(config));
|
||||||
|
|
@ -639,7 +637,7 @@ async function executeTool(req: Request): Promise<Response> {
|
||||||
try {
|
try {
|
||||||
const firstValue = Object.values(env)[0];
|
const firstValue = Object.values(env)[0];
|
||||||
if (firstValue) {
|
if (firstValue) {
|
||||||
console.log(` Trying: ${exportName}(firstEnvValue)`);
|
console.log(` Trying: ${toolName}(firstEnvValue)`);
|
||||||
factoryResult = rawExport(firstValue);
|
factoryResult = rawExport(firstValue);
|
||||||
if (factoryResult?.execute) {
|
if (factoryResult?.execute) {
|
||||||
console.log(' ✅ Success with single-arg factory');
|
console.log(' ✅ Success with single-arg factory');
|
||||||
|
|
@ -655,7 +653,7 @@ async function executeTool(req: Request): Promise<Response> {
|
||||||
return Response.json(
|
return Response.json(
|
||||||
{
|
{
|
||||||
success: false,
|
success: false,
|
||||||
error: `Tool "${exportName}" is a factory function but couldn't be initialized`,
|
error: `Tool "${toolName}" is a factory function but couldn't be initialized`,
|
||||||
executionTimeMs: Date.now() - startTime,
|
executionTimeMs: Date.now() - startTime,
|
||||||
},
|
},
|
||||||
{ status: 400 }
|
{ status: 400 }
|
||||||
|
|
@ -700,7 +698,7 @@ async function executeTool(req: Request): Promise<Response> {
|
||||||
console.log(`✅ Execution complete in ${executionTimeMs}ms`);
|
console.log(`✅ Execution complete in ${executionTimeMs}ms`);
|
||||||
|
|
||||||
// Report successful execution to health service (non-blocking)
|
// Report successful execution to health service (non-blocking)
|
||||||
reportToolHealth(packageName, exportName, true).catch(() => {});
|
reportToolHealth(packageName, toolName, true).catch(() => {});
|
||||||
|
|
||||||
return Response.json({
|
return Response.json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -712,7 +710,7 @@ async function executeTool(req: Request): Promise<Response> {
|
||||||
console.error('❌ Tool execution failed:', error);
|
console.error('❌ Tool execution failed:', error);
|
||||||
|
|
||||||
// Report failed execution to health service (non-blocking)
|
// Report failed execution to health service (non-blocking)
|
||||||
reportToolHealth(packageName, exportName, false, error.message).catch(() => {});
|
reportToolHealth(packageName, toolName, false, error.message).catch(() => {});
|
||||||
|
|
||||||
return Response.json(
|
return Response.json(
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -176,14 +176,14 @@ export async function POST(request: NextRequest) {
|
||||||
|
|
||||||
const upsertedTool = await prisma.tool.upsert({
|
const upsertedTool = await prisma.tool.upsert({
|
||||||
where: {
|
where: {
|
||||||
packageId_exportName: {
|
packageId_name: {
|
||||||
packageId: packageRecord.id,
|
packageId: packageRecord.id,
|
||||||
exportName: toolName,
|
name: toolName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
packageId: packageRecord.id,
|
packageId: packageRecord.id,
|
||||||
exportName: toolName,
|
name: toolName,
|
||||||
description: toolDef.description || 'No description provided',
|
description: toolDef.description || 'No description provided',
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: Prisma Json type compatibility workaround
|
// biome-ignore lint/suspicious/noExplicitAny: Prisma Json type compatibility workaround
|
||||||
parameters: toolDef.parameters ? (toolDef.parameters as any) : undefined,
|
parameters: toolDef.parameters ? (toolDef.parameters as any) : undefined,
|
||||||
|
|
@ -254,8 +254,7 @@ export async function POST(request: NextRequest) {
|
||||||
|
|
||||||
// Delete orphaned tools (tools removed from package.json)
|
// Delete orphaned tools (tools removed from package.json)
|
||||||
const orphanedTools = existingTools.filter(
|
const orphanedTools = existingTools.filter(
|
||||||
(existingTool) =>
|
(existingTool) => !toolsToProcess.some((toolDef) => toolDef.name === existingTool.name)
|
||||||
!toolsToProcess.some((toolDef) => toolDef.name === existingTool.exportName)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (orphanedTools.length > 0) {
|
if (orphanedTools.length > 0) {
|
||||||
|
|
|
||||||
|
|
@ -195,14 +195,14 @@ export async function POST(request: NextRequest) {
|
||||||
|
|
||||||
const upsertedTool = await prisma.tool.upsert({
|
const upsertedTool = await prisma.tool.upsert({
|
||||||
where: {
|
where: {
|
||||||
packageId_exportName: {
|
packageId_name: {
|
||||||
packageId: packageRecord.id,
|
packageId: packageRecord.id,
|
||||||
exportName: toolName,
|
name: toolName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
packageId: packageRecord.id,
|
packageId: packageRecord.id,
|
||||||
exportName: toolName,
|
name: toolName,
|
||||||
description: toolDef.description || 'No description provided',
|
description: toolDef.description || 'No description provided',
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: Prisma Json type compatibility workaround
|
// biome-ignore lint/suspicious/noExplicitAny: Prisma Json type compatibility workaround
|
||||||
parameters: toolDef.parameters ? (toolDef.parameters as any) : undefined,
|
parameters: toolDef.parameters ? (toolDef.parameters as any) : undefined,
|
||||||
|
|
@ -273,8 +273,7 @@ export async function POST(request: NextRequest) {
|
||||||
|
|
||||||
// Delete orphaned tools (tools removed from package.json)
|
// Delete orphaned tools (tools removed from package.json)
|
||||||
const orphanedTools = existingTools.filter(
|
const orphanedTools = existingTools.filter(
|
||||||
(existingTool) =>
|
(existingTool) => !toolsToProcess.some((toolDef) => toolDef.name === existingTool.name)
|
||||||
!toolsToProcess.some((toolDef) => toolDef.name === existingTool.exportName)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (orphanedTools.length > 0) {
|
if (orphanedTools.length > 0) {
|
||||||
|
|
|
||||||
|
|
@ -10,30 +10,30 @@ export const maxDuration = 60;
|
||||||
/**
|
/**
|
||||||
* Parse tool slug to extract package name and export name
|
* Parse tool slug to extract package name and export name
|
||||||
*/
|
*/
|
||||||
function parseSlug(slug: string[]): { packageName: string; exportName: string | undefined } {
|
function parseSlug(slug: string[]): { packageName: string; name: string | undefined } {
|
||||||
let packageName: string;
|
let packageName: string;
|
||||||
let exportName: string | undefined;
|
let name: string | undefined;
|
||||||
|
|
||||||
if (slug.length === 1) {
|
if (slug.length === 1) {
|
||||||
// Single slug - package name without scope
|
// Single slug - package name without scope
|
||||||
packageName = slug[0] || '';
|
packageName = slug[0] || '';
|
||||||
} else if (slug.length === 2) {
|
} else if (slug.length === 2) {
|
||||||
// Could be: @scope/package OR package/exportName
|
// Could be: @scope/package OR package/name
|
||||||
if (slug[0]?.startsWith('@')) {
|
if (slug[0]?.startsWith('@')) {
|
||||||
// @scope/package
|
// @scope/package
|
||||||
packageName = slug.join('/');
|
packageName = slug.join('/');
|
||||||
} else {
|
} else {
|
||||||
// package + exportName
|
// package + name
|
||||||
packageName = slug[0] || '';
|
packageName = slug[0] || '';
|
||||||
exportName = slug[1];
|
name = slug[1];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 3+ slugs: @scope/package/exportName
|
// 3+ slugs: @scope/package/name
|
||||||
packageName = slug.slice(0, slug[0]?.startsWith('@') ? 2 : 1).join('/');
|
packageName = slug.slice(0, slug[0]?.startsWith('@') ? 2 : 1).join('/');
|
||||||
exportName = slug[slug.length - 1];
|
name = slug[slug.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
return { packageName, exportName };
|
return { packageName, name };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -54,14 +54,14 @@ export async function GET(
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
const { packageName, exportName } = parseSlug(slug);
|
const { packageName, name } = parseSlug(slug);
|
||||||
|
|
||||||
if (exportName) {
|
if (name) {
|
||||||
// Find specific tool by package name and export name
|
// Find specific tool by package name and export name
|
||||||
const tool = await prisma.tool.findFirst({
|
const tool = await prisma.tool.findFirst({
|
||||||
where: {
|
where: {
|
||||||
package: { npmPackageName: packageName },
|
package: { npmPackageName: packageName },
|
||||||
exportName: exportName,
|
name: name,
|
||||||
},
|
},
|
||||||
include: { package: true },
|
include: { package: true },
|
||||||
});
|
});
|
||||||
|
|
@ -138,10 +138,10 @@ export async function POST(
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
const { packageName, exportName } = parseSlug(slug);
|
const { packageName, name } = parseSlug(slug);
|
||||||
|
|
||||||
// Health checks require export name
|
// Health checks require export name
|
||||||
if (!exportName) {
|
if (!name) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
success: false,
|
success: false,
|
||||||
|
|
@ -155,7 +155,7 @@ export async function POST(
|
||||||
const tool = await prisma.tool.findFirst({
|
const tool = await prisma.tool.findFirst({
|
||||||
where: {
|
where: {
|
||||||
package: { npmPackageName: packageName },
|
package: { npmPackageName: packageName },
|
||||||
exportName: exportName,
|
name: name,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
|
|
@ -190,7 +190,7 @@ export async function POST(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform health check
|
// Perform health check
|
||||||
console.log(`🏥 Manual health check triggered for ${packageName}/${exportName}`);
|
console.log(`🏥 Manual health check triggered for ${packageName}/${name}`);
|
||||||
const result = await performHealthCheck(tool.id, 'manual');
|
const result = await performHealthCheck(tool.id, 'manual');
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
|
|
@ -198,7 +198,7 @@ export async function POST(
|
||||||
data: {
|
data: {
|
||||||
toolId: result.toolId,
|
toolId: result.toolId,
|
||||||
packageName: packageName,
|
packageName: packageName,
|
||||||
exportName: exportName,
|
name: name,
|
||||||
importStatus: result.importStatus,
|
importStatus: result.importStatus,
|
||||||
importError: result.importError,
|
importError: result.importError,
|
||||||
importTimeMs: result.importTimeMs,
|
importTimeMs: result.importTimeMs,
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ interface ExecuteRequest {
|
||||||
* POST /api/tools/execute/[...slug]
|
* POST /api/tools/execute/[...slug]
|
||||||
* Executes a tool with an AI agent and streams the response via SSE
|
* Executes a tool with an AI agent and streams the response via SSE
|
||||||
*
|
*
|
||||||
* Slug format: [toolId] or [packageName, exportName]
|
* Slug format: [toolId] or [packageName, name]
|
||||||
* Examples:
|
* Examples:
|
||||||
* /api/tools/execute/clx123abc (by tool ID)
|
* /api/tools/execute/clx123abc (by tool ID)
|
||||||
* /api/tools/execute/@tpmjs/hello/helloWorldTool (by package and export name)
|
* /api/tools/execute/@tpmjs/hello/helloWorldTool (by package and export name)
|
||||||
|
|
@ -68,7 +68,7 @@ export async function POST(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch tool from database with package relation
|
// Fetch tool from database with package relation
|
||||||
// Support both ID-based lookup and packageName/exportName lookup
|
// Support both ID-based lookup and packageName/name lookup
|
||||||
const tool =
|
const tool =
|
||||||
slug.length === 1
|
slug.length === 1
|
||||||
? // Single slug - treat as tool ID
|
? // Single slug - treat as tool ID
|
||||||
|
|
@ -76,11 +76,11 @@ export async function POST(
|
||||||
where: { id: slug[0] || '' },
|
where: { id: slug[0] || '' },
|
||||||
include: { package: true },
|
include: { package: true },
|
||||||
})
|
})
|
||||||
: // Multiple slugs - treat as packageName/exportName
|
: // Multiple slugs - treat as packageName/name
|
||||||
await prisma.tool.findFirst({
|
await prisma.tool.findFirst({
|
||||||
where: {
|
where: {
|
||||||
package: { npmPackageName: decodeURIComponent(slug.slice(0, -1).join('/')) },
|
package: { npmPackageName: decodeURIComponent(slug.slice(0, -1).join('/')) },
|
||||||
exportName: decodeURIComponent(slug[slug.length - 1] || ''),
|
name: decodeURIComponent(slug[slug.length - 1] || ''),
|
||||||
},
|
},
|
||||||
include: { package: true },
|
include: { package: true },
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -11,28 +11,28 @@ export const dynamic = 'force-dynamic';
|
||||||
*
|
*
|
||||||
* Body:
|
* Body:
|
||||||
* - packageName: npm package name
|
* - packageName: npm package name
|
||||||
* - exportName: exported function name
|
* - name: exported function name
|
||||||
*
|
*
|
||||||
* Rate limited to 1 extraction per minute per tool
|
* Rate limited to 1 extraction per minute per tool
|
||||||
*/
|
*/
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const { packageName, exportName } = body;
|
const { packageName, name } = body;
|
||||||
|
|
||||||
if (!packageName || !exportName) {
|
if (!packageName || !name) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ success: false, error: 'packageName and exportName are required' },
|
{ success: false, error: 'packageName and name are required' },
|
||||||
{ status: 400 }
|
{ status: 400 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[Extract Schema] Looking up tool:', { packageName, exportName });
|
console.log('[Extract Schema] Looking up tool:', { packageName, name });
|
||||||
|
|
||||||
// Find the tool by package name and export name
|
// Find the tool by package name and export name
|
||||||
const tool = await prisma.tool.findFirst({
|
const tool = await prisma.tool.findFirst({
|
||||||
where: {
|
where: {
|
||||||
exportName,
|
name,
|
||||||
package: {
|
package: {
|
||||||
npmPackageName: packageName,
|
npmPackageName: packageName,
|
||||||
},
|
},
|
||||||
|
|
@ -49,7 +49,7 @@ export async function POST(request: NextRequest) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!tool) {
|
if (!tool) {
|
||||||
console.log('[Extract Schema] Tool not found:', { packageName, exportName });
|
console.log('[Extract Schema] Tool not found:', { packageName, name });
|
||||||
return NextResponse.json({ success: false, error: 'Tool not found' }, { status: 404 });
|
return NextResponse.json({ success: false, error: 'Tool not found' }, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,14 +74,14 @@ export async function POST(request: NextRequest) {
|
||||||
|
|
||||||
console.log('[Extract Schema] Extracting schema for:', {
|
console.log('[Extract Schema] Extracting schema for:', {
|
||||||
packageName: tool.package.npmPackageName,
|
packageName: tool.package.npmPackageName,
|
||||||
exportName: tool.exportName,
|
name: tool.name,
|
||||||
version: tool.package.npmVersion,
|
version: tool.package.npmVersion,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Extract schema from executor
|
// Extract schema from executor
|
||||||
const schemaResult = await extractToolSchema(
|
const schemaResult = await extractToolSchema(
|
||||||
tool.package.npmPackageName,
|
tool.package.npmPackageName,
|
||||||
tool.exportName,
|
tool.name,
|
||||||
tool.package.npmVersion,
|
tool.package.npmVersion,
|
||||||
tool.package.env as Record<string, unknown> | null
|
tool.package.env as Record<string, unknown> | null
|
||||||
);
|
);
|
||||||
|
|
@ -101,7 +101,7 @@ export async function POST(request: NextRequest) {
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
exportName: true,
|
name: true,
|
||||||
inputSchema: true,
|
inputSchema: true,
|
||||||
parameters: true,
|
parameters: true,
|
||||||
schemaSource: true,
|
schemaSource: true,
|
||||||
|
|
@ -111,7 +111,7 @@ export async function POST(request: NextRequest) {
|
||||||
|
|
||||||
console.log('[Extract Schema] Schema extracted successfully:', {
|
console.log('[Extract Schema] Schema extracted successfully:', {
|
||||||
toolId: updatedTool.id,
|
toolId: updatedTool.id,
|
||||||
exportName: updatedTool.exportName,
|
name: updatedTool.name,
|
||||||
schemaSource: updatedTool.schemaSource,
|
schemaSource: updatedTool.schemaSource,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -126,7 +126,7 @@ export async function POST(request: NextRequest) {
|
||||||
// Extraction failed
|
// Extraction failed
|
||||||
console.log('[Extract Schema] Extraction failed:', {
|
console.log('[Extract Schema] Extraction failed:', {
|
||||||
packageName,
|
packageName,
|
||||||
exportName,
|
name,
|
||||||
error: schemaResult.error,
|
error: schemaResult.error,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ function isNonBreakingError(error: string): boolean {
|
||||||
|
|
||||||
interface ReportHealthRequest {
|
interface ReportHealthRequest {
|
||||||
packageName: string;
|
packageName: string;
|
||||||
exportName: string;
|
name: string;
|
||||||
success: boolean;
|
success: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
@ -87,11 +87,11 @@ export async function POST(request: NextRequest): Promise<NextResponse> {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const body: ReportHealthRequest = await request.json();
|
const body: ReportHealthRequest = await request.json();
|
||||||
const { packageName, exportName, success, error } = body;
|
const { packageName, name, success, error } = body;
|
||||||
|
|
||||||
if (!packageName || !exportName) {
|
if (!packageName || !name) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ success: false, error: 'packageName and exportName are required' },
|
{ success: false, error: 'packageName and name are required' },
|
||||||
{ status: 400 }
|
{ status: 400 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -99,7 +99,7 @@ export async function POST(request: NextRequest): Promise<NextResponse> {
|
||||||
// Find the tool
|
// Find the tool
|
||||||
const tool = await prisma.tool.findFirst({
|
const tool = await prisma.tool.findFirst({
|
||||||
where: {
|
where: {
|
||||||
exportName,
|
name,
|
||||||
package: { npmPackageName: packageName },
|
package: { npmPackageName: packageName },
|
||||||
},
|
},
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
|
|
@ -119,9 +119,7 @@ export async function POST(request: NextRequest): Promise<NextResponse> {
|
||||||
} else if (error && isNonBreakingError(error)) {
|
} else if (error && isNonBreakingError(error)) {
|
||||||
// Failed due to config/validation = HEALTHY (tool works, just needs setup)
|
// Failed due to config/validation = HEALTHY (tool works, just needs setup)
|
||||||
healthStatus = 'HEALTHY';
|
healthStatus = 'HEALTHY';
|
||||||
console.log(
|
console.log(`ℹ️ ${packageName}/${name} failed due to config issue (not broken): ${error}`);
|
||||||
`ℹ️ ${packageName}/${exportName} failed due to config issue (not broken): ${error}`
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// Real failure = BROKEN
|
// Real failure = BROKEN
|
||||||
healthStatus = 'BROKEN';
|
healthStatus = 'BROKEN';
|
||||||
|
|
@ -138,7 +136,7 @@ export async function POST(request: NextRequest): Promise<NextResponse> {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`🏥 Health updated for ${packageName}/${exportName}: ${healthStatus}`);
|
console.log(`🏥 Health updated for ${packageName}/${name}: ${healthStatus}`);
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ export async function GET(request: NextRequest) {
|
||||||
tool,
|
tool,
|
||||||
text: [
|
text: [
|
||||||
tool.description,
|
tool.description,
|
||||||
tool.exportName,
|
tool.name,
|
||||||
tool.package.npmPackageName,
|
tool.package.npmPackageName,
|
||||||
tool.package.npmDescription || '',
|
tool.package.npmDescription || '',
|
||||||
...(tool.package.npmKeywords || []),
|
...(tool.package.npmKeywords || []),
|
||||||
|
|
@ -157,7 +157,7 @@ export async function GET(request: NextRequest) {
|
||||||
returned: results.length,
|
returned: results.length,
|
||||||
tools: results.map(({ tool }) => ({
|
tools: results.map(({ tool }) => ({
|
||||||
id: tool.id,
|
id: tool.id,
|
||||||
exportName: tool.exportName,
|
name: tool.name,
|
||||||
description: tool.description,
|
description: tool.description,
|
||||||
qualityScore: tool.qualityScore,
|
qualityScore: tool.qualityScore,
|
||||||
importHealth: tool.importHealth,
|
importHealth: tool.importHealth,
|
||||||
|
|
|
||||||
|
|
@ -30,14 +30,14 @@ export async function GET(
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Multiple slugs - treat as packageName/exportName
|
// Multiple slugs - treat as packageName/name
|
||||||
const packageName = decodeURIComponent(slug.slice(0, -1).join('/'));
|
const packageName = decodeURIComponent(slug.slice(0, -1).join('/'));
|
||||||
const exportName = decodeURIComponent(slug[slug.length - 1] || '');
|
const name = decodeURIComponent(slug[slug.length - 1] || '');
|
||||||
|
|
||||||
tool = await prisma.tool.findFirst({
|
tool = await prisma.tool.findFirst({
|
||||||
where: {
|
where: {
|
||||||
package: { npmPackageName: packageName },
|
package: { npmPackageName: packageName },
|
||||||
exportName: exportName,
|
name: name,
|
||||||
},
|
},
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -10,23 +10,23 @@ export const dynamic = 'force-dynamic';
|
||||||
* Update a tool's input schema
|
* Update a tool's input schema
|
||||||
*
|
*
|
||||||
* Called by the executor when it loads a tool and discovers its schema.
|
* Called by the executor when it loads a tool and discovers its schema.
|
||||||
* Looks up tool by packageName + exportName (unique constraint).
|
* Looks up tool by packageName + name (unique constraint).
|
||||||
* Stores the full JSON Schema and also converts to parameters array for backward compatibility.
|
* Stores the full JSON Schema and also converts to parameters array for backward compatibility.
|
||||||
*
|
*
|
||||||
* Body:
|
* Body:
|
||||||
* - packageName: npm package name
|
* - packageName: npm package name
|
||||||
* - exportName: exported function name
|
* - name: exported function name
|
||||||
* - inputSchema: The JSON Schema for the tool's input parameters
|
* - inputSchema: The JSON Schema for the tool's input parameters
|
||||||
* - description: Optional updated description from the tool
|
* - description: Optional updated description from the tool
|
||||||
*/
|
*/
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const { packageName, exportName, inputSchema, description } = body;
|
const { packageName, name, inputSchema, description } = body;
|
||||||
|
|
||||||
if (!packageName || !exportName) {
|
if (!packageName || !name) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ success: false, error: 'packageName and exportName are required' },
|
{ success: false, error: 'packageName and name are required' },
|
||||||
{ status: 400 }
|
{ status: 400 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -38,12 +38,12 @@ export async function POST(request: NextRequest) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[Update Schema] Looking up tool:', { packageName, exportName });
|
console.log('[Update Schema] Looking up tool:', { packageName, name });
|
||||||
|
|
||||||
// Find the tool by package name and export name
|
// Find the tool by package name and name
|
||||||
const tool = await prisma.tool.findFirst({
|
const tool = await prisma.tool.findFirst({
|
||||||
where: {
|
where: {
|
||||||
exportName,
|
name,
|
||||||
package: {
|
package: {
|
||||||
npmPackageName: packageName,
|
npmPackageName: packageName,
|
||||||
},
|
},
|
||||||
|
|
@ -56,7 +56,7 @@ export async function POST(request: NextRequest) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!tool) {
|
if (!tool) {
|
||||||
console.log('[Update Schema] Tool not found:', { packageName, exportName });
|
console.log('[Update Schema] Tool not found:', { packageName, name });
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ success: false, error: 'Tool not found', updated: false },
|
{ success: false, error: 'Tool not found', updated: false },
|
||||||
{ status: 404 }
|
{ status: 404 }
|
||||||
|
|
@ -73,7 +73,7 @@ export async function POST(request: NextRequest) {
|
||||||
tool.schemaSource === 'extracted' &&
|
tool.schemaSource === 'extracted' &&
|
||||||
JSON.stringify(existingSchema) === JSON.stringify(inputSchema)
|
JSON.stringify(existingSchema) === JSON.stringify(inputSchema)
|
||||||
) {
|
) {
|
||||||
console.log('[Update Schema] Schema already up to date:', { packageName, exportName });
|
console.log('[Update Schema] Schema already up to date:', { packageName, name });
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
updated: false,
|
updated: false,
|
||||||
|
|
@ -107,7 +107,7 @@ export async function POST(request: NextRequest) {
|
||||||
data: updateData,
|
data: updateData,
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
exportName: true,
|
name: true,
|
||||||
description: true,
|
description: true,
|
||||||
parameters: true,
|
parameters: true,
|
||||||
inputSchema: true,
|
inputSchema: true,
|
||||||
|
|
@ -118,7 +118,7 @@ export async function POST(request: NextRequest) {
|
||||||
|
|
||||||
console.log('[Update Schema] Tool updated:', {
|
console.log('[Update Schema] Tool updated:', {
|
||||||
id: updatedTool.id,
|
id: updatedTool.id,
|
||||||
exportName: updatedTool.exportName,
|
name: updatedTool.name,
|
||||||
parameterCount: parameters.length,
|
parameterCount: parameters.length,
|
||||||
parameterNames: parameters.map((p) => p.name),
|
parameterNames: parameters.map((p) => p.name),
|
||||||
schemaSource: updatedTool.schemaSource,
|
schemaSource: updatedTool.schemaSource,
|
||||||
|
|
|
||||||
|
|
@ -492,7 +492,7 @@ Use registrySearch to find tools, then registryExecute to run them.\`,
|
||||||
name: 'toolId',
|
name: 'toolId',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
description: 'Tool identifier (format: package::exportName)',
|
description: 'Tool identifier (format: package::name)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'params',
|
name: 'params',
|
||||||
|
|
@ -1146,7 +1146,7 @@ export TPMJS_EXECUTOR_URL=https://executor.mycompany.com`}
|
||||||
<DocSubSection title="Execution failing">
|
<DocSubSection title="Execution failing">
|
||||||
<ul className="list-disc list-inside space-y-2 text-foreground-secondary">
|
<ul className="list-disc list-inside space-y-2 text-foreground-secondary">
|
||||||
<li>Check that required environment variables are passed</li>
|
<li>Check that required environment variables are passed</li>
|
||||||
<li>Verify the toolId format is correct (package::exportName)</li>
|
<li>Verify the toolId format is correct (package::name)</li>
|
||||||
<li>Check the tool's health status on tpmjs.com</li>
|
<li>Check the tool's health status on tpmjs.com</li>
|
||||||
</ul>
|
</ul>
|
||||||
</DocSubSection>
|
</DocSubSection>
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,7 @@ export default function FAQPage(): React.ReactElement {
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>
|
||||||
This simplifies publishing - you only need to provide category, description, and
|
This simplifies publishing - you only need to provide category, description, and
|
||||||
exportName. See our{' '}
|
name. See our{' '}
|
||||||
<Link href="/spec" className="text-primary hover:underline font-medium">
|
<Link href="/spec" className="text-primary hover:underline font-medium">
|
||||||
specification
|
specification
|
||||||
</Link>{' '}
|
</Link>{' '}
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ export default function HowItWorksPage(): React.ReactElement {
|
||||||
"category": "text-analysis",
|
"category": "text-analysis",
|
||||||
"frameworks": ["vercel-ai"],
|
"frameworks": ["vercel-ai"],
|
||||||
"tools": [{
|
"tools": [{
|
||||||
"exportName": "analyzeSentiment",
|
"name": "analyzeSentiment",
|
||||||
"description": "Analyze sentiment of text and return positive/negative/neutral"
|
"description": "Analyze sentiment of text and return positive/negative/neutral"
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ async function getHomePageData() {
|
||||||
take: 6,
|
take: 6,
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
exportName: true,
|
name: true,
|
||||||
description: true,
|
description: true,
|
||||||
qualityScore: true,
|
qualityScore: true,
|
||||||
package: {
|
package: {
|
||||||
|
|
@ -100,7 +100,7 @@ export default async function HomePage(): Promise<React.ReactElement> {
|
||||||
{data.featuredTools.map((tool) => (
|
{data.featuredTools.map((tool) => (
|
||||||
<Link
|
<Link
|
||||||
key={tool.id}
|
key={tool.id}
|
||||||
href={`/tool/${tool.package.npmPackageName}/${tool.exportName}`}
|
href={`/tool/${tool.package.npmPackageName}/${tool.name}`}
|
||||||
className="group"
|
className="group"
|
||||||
>
|
>
|
||||||
<div className="p-6 border border-border rounded-lg bg-surface hover:border-foreground transition-colors h-full flex flex-col">
|
<div className="p-6 border border-border rounded-lg bg-surface hover:border-foreground transition-colors h-full flex flex-col">
|
||||||
|
|
@ -108,7 +108,7 @@ export default async function HomePage(): Promise<React.ReactElement> {
|
||||||
<h3 className="text-lg font-semibold text-foreground group-hover:text-brutalist-accent transition-colors">
|
<h3 className="text-lg font-semibold text-foreground group-hover:text-brutalist-accent transition-colors">
|
||||||
{tool.package.npmPackageName}
|
{tool.package.npmPackageName}
|
||||||
<span className="text-xs text-foreground-tertiary ml-2">
|
<span className="text-xs text-foreground-tertiary ml-2">
|
||||||
({tool.exportName})
|
({tool.name})
|
||||||
</span>
|
</span>
|
||||||
</h3>
|
</h3>
|
||||||
{tool.package.isOfficial && (
|
{tool.package.isOfficial && (
|
||||||
|
|
|
||||||
|
|
@ -352,7 +352,7 @@ Use registrySearch to find tools, then registryExecute to run them.\`,
|
||||||
<td className="py-2 pr-4">string</td>
|
<td className="py-2 pr-4">string</td>
|
||||||
<td className="py-2 pr-4">Yes</td>
|
<td className="py-2 pr-4">Yes</td>
|
||||||
<td className="py-2">
|
<td className="py-2">
|
||||||
Tool identifier (format: <code>package::exportName</code>)
|
Tool identifier (format: <code>package::name</code>)
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="border-b border-border/50">
|
<tr className="border-b border-border/50">
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||||
updatedAt: true,
|
updatedAt: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
exportName: true,
|
name: true,
|
||||||
updatedAt: true,
|
updatedAt: true,
|
||||||
},
|
},
|
||||||
orderBy: {
|
orderBy: {
|
||||||
|
|
@ -83,7 +83,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||||
tool.updatedAt > tool.package.updatedAt ? tool.updatedAt : tool.package.updatedAt;
|
tool.updatedAt > tool.package.updatedAt ? tool.updatedAt : tool.package.updatedAt;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url: `${baseUrl}/tool/${tool.package.npmPackageName}/${tool.exportName}`,
|
url: `${baseUrl}/tool/${tool.package.npmPackageName}/${tool.name}`,
|
||||||
lastModified,
|
lastModified,
|
||||||
changeFrequency: 'weekly' as const,
|
changeFrequency: 'weekly' as const,
|
||||||
priority: 0.7,
|
priority: 0.7,
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ interface Package {
|
||||||
|
|
||||||
interface Tool {
|
interface Tool {
|
||||||
id: string;
|
id: string;
|
||||||
exportName: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
parameters: Array<{
|
parameters: Array<{
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -152,7 +152,7 @@ export default function ToolDetailPage({
|
||||||
const softwareApplicationSchema = {
|
const softwareApplicationSchema = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@type': 'SoftwareApplication',
|
'@type': 'SoftwareApplication',
|
||||||
name: tool.exportName,
|
name: tool.name,
|
||||||
description: tool.description,
|
description: tool.description,
|
||||||
applicationCategory: 'DeveloperApplication',
|
applicationCategory: 'DeveloperApplication',
|
||||||
operatingSystem: 'Any',
|
operatingSystem: 'Any',
|
||||||
|
|
@ -165,7 +165,7 @@ export default function ToolDetailPage({
|
||||||
'@type': authorName ? 'Person' : 'Organization',
|
'@type': authorName ? 'Person' : 'Organization',
|
||||||
name: authorName || 'Unknown',
|
name: authorName || 'Unknown',
|
||||||
},
|
},
|
||||||
url: `https://tpmjs.com/tool/${pkg.npmPackageName}/${tool.exportName}`,
|
url: `https://tpmjs.com/tool/${pkg.npmPackageName}/${tool.name}`,
|
||||||
softwareVersion: pkg.npmVersion,
|
softwareVersion: pkg.npmVersion,
|
||||||
...(pkg.npmHomepage && { mainEntityOfPage: pkg.npmHomepage }),
|
...(pkg.npmHomepage && { mainEntityOfPage: pkg.npmHomepage }),
|
||||||
...(pkg.npmRepository &&
|
...(pkg.npmRepository &&
|
||||||
|
|
@ -223,7 +223,7 @@ export default function ToolDetailPage({
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
packageName: tool.package.npmPackageName,
|
packageName: tool.package.npmPackageName,
|
||||||
exportName: tool.exportName,
|
name: tool.name,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -274,7 +274,7 @@ export default function ToolDetailPage({
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex items-start justify-between mb-4">
|
<div className="flex items-start justify-between mb-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-4xl font-bold text-foreground mb-2">{tool.exportName}</h1>
|
<h1 className="text-4xl font-bold text-foreground mb-2">{tool.name}</h1>
|
||||||
<p className="text-sm text-foreground-tertiary font-mono mb-2">
|
<p className="text-sm text-foreground-tertiary font-mono mb-2">
|
||||||
{pkg.npmPackageName}
|
{pkg.npmPackageName}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -409,7 +409,7 @@ export default function ToolDetailPage({
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-sm font-semibold text-foreground mb-3">2. Import the tool</h4>
|
<h4 className="text-sm font-semibold text-foreground mb-3">2. Import the tool</h4>
|
||||||
<CodeBlock
|
<CodeBlock
|
||||||
code={`import { ${tool.exportName} } from '${pkg.npmPackageName}';`}
|
code={`import { ${tool.name} } from '${pkg.npmPackageName}';`}
|
||||||
language="typescript"
|
language="typescript"
|
||||||
showCopy={true}
|
showCopy={true}
|
||||||
/>
|
/>
|
||||||
|
|
@ -420,11 +420,11 @@ export default function ToolDetailPage({
|
||||||
<CodeBlock
|
<CodeBlock
|
||||||
code={`import { generateText } from 'ai';
|
code={`import { generateText } from 'ai';
|
||||||
import { openai } from '@ai-sdk/openai';
|
import { openai } from '@ai-sdk/openai';
|
||||||
import { ${tool.exportName} } from '${pkg.npmPackageName}';
|
import { ${tool.name} } from '${pkg.npmPackageName}';
|
||||||
|
|
||||||
const result = await generateText({
|
const result = await generateText({
|
||||||
model: openai('gpt-4o'),
|
model: openai('gpt-4o'),
|
||||||
tools: { ${tool.exportName} },
|
tools: { ${tool.name} },
|
||||||
prompt: 'Your prompt here...',
|
prompt: 'Your prompt here...',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import { AppHeader } from '~/components/AppHeader';
|
||||||
|
|
||||||
interface BrokenTool {
|
interface BrokenTool {
|
||||||
id: string;
|
id: string;
|
||||||
exportName: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
importHealth: 'HEALTHY' | 'BROKEN' | 'UNKNOWN';
|
importHealth: 'HEALTHY' | 'BROKEN' | 'UNKNOWN';
|
||||||
executionHealth: 'HEALTHY' | 'BROKEN' | 'UNKNOWN';
|
executionHealth: 'HEALTHY' | 'BROKEN' | 'UNKNOWN';
|
||||||
|
|
@ -142,7 +142,7 @@ export default function BrokenToolsPage(): React.ReactElement {
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
{/* biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Broken tools page requires conditional rendering for health status */}
|
{/* biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Broken tools page requires conditional rendering for health status */}
|
||||||
{tools.map((tool) => {
|
{tools.map((tool) => {
|
||||||
const toolUrl = `/tool/${tool.package.npmPackageName}/${tool.exportName}`;
|
const toolUrl = `/tool/${tool.package.npmPackageName}/${tool.name}`;
|
||||||
const lastCheckedDate = tool.lastHealthCheck
|
const lastCheckedDate = tool.lastHealthCheck
|
||||||
? new Date(tool.lastHealthCheck)
|
? new Date(tool.lastHealthCheck)
|
||||||
: null;
|
: null;
|
||||||
|
|
@ -153,9 +153,7 @@ export default function BrokenToolsPage(): React.ReactElement {
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<CardTitle>
|
<CardTitle>
|
||||||
{tool.exportName !== 'default'
|
{tool.name !== 'default' ? tool.name : tool.package.npmPackageName}
|
||||||
? tool.exportName
|
|
||||||
: tool.package.npmPackageName}
|
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<div className="text-sm text-foreground-secondary mt-1">
|
<div className="text-sm text-foreground-secondary mt-1">
|
||||||
{tool.package.npmPackageName}
|
{tool.package.npmPackageName}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import { AppHeader } from '~/components/AppHeader';
|
||||||
|
|
||||||
interface Tool {
|
interface Tool {
|
||||||
id: string;
|
id: string;
|
||||||
exportName: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
qualityScore: string;
|
qualityScore: string;
|
||||||
importHealth?: 'HEALTHY' | 'BROKEN' | 'UNKNOWN';
|
importHealth?: 'HEALTHY' | 'BROKEN' | 'UNKNOWN';
|
||||||
|
|
@ -245,7 +245,7 @@ export default function ToolSearchPage(): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
key={tool.id}
|
key={tool.id}
|
||||||
href={`/tool/${tool.package.npmPackageName}/${tool.exportName}`}
|
href={`/tool/${tool.package.npmPackageName}/${tool.name}`}
|
||||||
className="block select-text"
|
className="block select-text"
|
||||||
>
|
>
|
||||||
<Card className="flex flex-col h-full hover:border-foreground-tertiary transition-colors cursor-pointer select-text">
|
<Card className="flex flex-col h-full hover:border-foreground-tertiary transition-colors cursor-pointer select-text">
|
||||||
|
|
@ -254,9 +254,7 @@ export default function ToolSearchPage(): React.ReactElement {
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<CardTitle className="truncate">
|
<CardTitle className="truncate">
|
||||||
{tool.exportName !== 'default'
|
{tool.name !== 'default' ? tool.name : tool.package.npmPackageName}
|
||||||
? tool.exportName
|
|
||||||
: tool.package.npmPackageName}
|
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<div className="text-sm text-foreground-secondary mt-1 truncate">
|
<div className="text-sm text-foreground-secondary mt-1 truncate">
|
||||||
{tool.package.npmPackageName}
|
{tool.package.npmPackageName}
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ export function ToolPlayground({ tool }: ToolPlaygroundProps): React.ReactElemen
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`/api/tools/execute/${encodeURIComponent(tool.package.npmPackageName)}/${encodeURIComponent(tool.exportName)}`,
|
`/api/tools/execute/${encodeURIComponent(tool.package.npmPackageName)}/${encodeURIComponent(tool.name)}`,
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -196,7 +196,7 @@ export function ToolPlayground({ tool }: ToolPlaygroundProps): React.ReactElemen
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-semibold text-foreground">Interactive Playground</h2>
|
<h2 className="text-xl font-semibold text-foreground">Interactive Playground</h2>
|
||||||
<p className="text-sm text-foreground-secondary mt-1">
|
<p className="text-sm text-foreground-secondary mt-1">
|
||||||
Test {tool.package.npmPackageName} ({tool.exportName}) with AI-powered execution
|
Test {tool.package.npmPackageName} ({tool.name}) with AI-powered execution
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{rateLimitInfo && (
|
{rateLimitInfo && (
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ export function createToolDefinition(tool: Tool & { package: Package }) {
|
||||||
? (tool.parameters as unknown as TPMJSParameter[])
|
? (tool.parameters as unknown as TPMJSParameter[])
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
console.log('[createToolDefinition] Tool:', tool.package.npmPackageName, '/', tool.exportName);
|
console.log('[createToolDefinition] Tool:', tool.package.npmPackageName, '/', tool.name);
|
||||||
console.log('[createToolDefinition] Parameters array:', JSON.stringify(parameters));
|
console.log('[createToolDefinition] Parameters array:', JSON.stringify(parameters));
|
||||||
console.log('[createToolDefinition] Parameters length:', parameters.length);
|
console.log('[createToolDefinition] Parameters length:', parameters.length);
|
||||||
|
|
||||||
|
|
@ -109,7 +109,7 @@ export function createToolDefinition(tool: Tool & { package: Package }) {
|
||||||
|
|
||||||
console.log('[createToolDefinition] Created Zod schema:', inputSchema);
|
console.log('[createToolDefinition] Created Zod schema:', inputSchema);
|
||||||
|
|
||||||
const sanitizedName = sanitizeToolName(`${tool.package.npmPackageName}-${tool.exportName}`);
|
const sanitizedName = sanitizeToolName(`${tool.package.npmPackageName}-${tool.name}`);
|
||||||
|
|
||||||
// AI SDK v6 tool definition
|
// AI SDK v6 tool definition
|
||||||
return {
|
return {
|
||||||
|
|
@ -122,7 +122,7 @@ export function createToolDefinition(tool: Tool & { package: Package }) {
|
||||||
// Use the actual export name from the Tool record
|
// Use the actual export name from the Tool record
|
||||||
const result = await executePackage(
|
const result = await executePackage(
|
||||||
tool.package.npmPackageName,
|
tool.package.npmPackageName,
|
||||||
tool.exportName, // Use actual export name (e.g., "helloWorldTool", "default")
|
tool.name, // Use actual export name (e.g., "helloWorldTool", "default")
|
||||||
params,
|
params,
|
||||||
{ timeout: 5000 }
|
{ timeout: 5000 }
|
||||||
);
|
);
|
||||||
|
|
@ -197,7 +197,7 @@ export async function executeToolWithAgent(
|
||||||
onTokenUpdate?: (tokens: Partial<TokenBreakdown>) => void
|
onTokenUpdate?: (tokens: Partial<TokenBreakdown>) => void
|
||||||
) {
|
) {
|
||||||
const toolDef = createToolDefinition(tool);
|
const toolDef = createToolDefinition(tool);
|
||||||
const sanitizedToolName = sanitizeToolName(`${tool.package.npmPackageName}-${tool.exportName}`);
|
const sanitizedToolName = sanitizeToolName(`${tool.package.npmPackageName}-${tool.name}`);
|
||||||
|
|
||||||
console.log('[executeToolWithAgent] Tool name:', sanitizedToolName);
|
console.log('[executeToolWithAgent] Tool name:', sanitizedToolName);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ async function checkImportHealth(tool: Tool & { package: Package }): Promise<{
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
packageName: tool.package.npmPackageName,
|
packageName: tool.package.npmPackageName,
|
||||||
exportName: tool.exportName,
|
name: tool.name,
|
||||||
version: tool.package.npmVersion,
|
version: tool.package.npmVersion,
|
||||||
env: tool.package.env || {},
|
env: tool.package.env || {},
|
||||||
}),
|
}),
|
||||||
|
|
@ -118,7 +118,7 @@ async function checkExecutionHealth(tool: Tool & { package: Package }): Promise<
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
packageName: tool.package.npmPackageName,
|
packageName: tool.package.npmPackageName,
|
||||||
exportName: tool.exportName,
|
name: tool.name,
|
||||||
version: tool.package.npmVersion,
|
version: tool.package.npmVersion,
|
||||||
params: testParams,
|
params: testParams,
|
||||||
env: tool.package.env || {},
|
env: tool.package.env || {},
|
||||||
|
|
@ -284,7 +284,7 @@ export async function performHealthCheck(
|
||||||
throw new Error(`Tool not found: ${toolId}`);
|
throw new Error(`Tool not found: ${toolId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`🏥 Health check starting for ${tool.package.npmPackageName}/${tool.exportName}`);
|
console.log(`🏥 Health check starting for ${tool.package.npmPackageName}/${tool.name}`);
|
||||||
|
|
||||||
// Check import health
|
// Check import health
|
||||||
const importResult = await checkImportHealth(tool);
|
const importResult = await checkImportHealth(tool);
|
||||||
|
|
|
||||||
|
|
@ -107,14 +107,14 @@ export type SchemaExtractionResult = SchemaExtractionSuccess | SchemaExtractionF
|
||||||
* Extract inputSchema from a tool by calling the executor's /load-and-describe endpoint
|
* Extract inputSchema from a tool by calling the executor's /load-and-describe endpoint
|
||||||
*
|
*
|
||||||
* @param packageName - NPM package name (e.g., "@tpmjs/hello-world")
|
* @param packageName - NPM package name (e.g., "@tpmjs/hello-world")
|
||||||
* @param exportName - Export name (e.g., "helloWorldTool" or "default")
|
* @param name - Export name (e.g., "helloWorldTool" or "default")
|
||||||
* @param version - Package version (e.g., "1.0.0")
|
* @param version - Package version (e.g., "1.0.0")
|
||||||
* @param packageEnv - Package-level environment variables (optional)
|
* @param packageEnv - Package-level environment variables (optional)
|
||||||
* @returns Schema extraction result with inputSchema or error
|
* @returns Schema extraction result with inputSchema or error
|
||||||
*/
|
*/
|
||||||
export async function extractToolSchema(
|
export async function extractToolSchema(
|
||||||
packageName: string,
|
packageName: string,
|
||||||
exportName: string,
|
name: string,
|
||||||
version: string,
|
version: string,
|
||||||
packageEnv?: Record<string, unknown> | null
|
packageEnv?: Record<string, unknown> | null
|
||||||
): Promise<SchemaExtractionResult> {
|
): Promise<SchemaExtractionResult> {
|
||||||
|
|
@ -124,7 +124,7 @@ export async function extractToolSchema(
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
packageName,
|
packageName,
|
||||||
exportName,
|
name,
|
||||||
version,
|
version,
|
||||||
env: packageEnv || {},
|
env: packageEnv || {},
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -767,7 +767,7 @@ export const manualTools: ManualTool[] = [
|
||||||
returns: {
|
returns: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
description:
|
description:
|
||||||
'Search results with tool metadata including packageName, exportName, version, importUrl',
|
'Search results with tool metadata including packageName, name, version, importUrl',
|
||||||
},
|
},
|
||||||
aiAgent: {
|
aiAgent: {
|
||||||
useCase:
|
useCase:
|
||||||
|
|
@ -786,7 +786,7 @@ export const manualTools: ManualTool[] = [
|
||||||
npmPackageName: '@airweave/vercel-ai-sdk',
|
npmPackageName: '@airweave/vercel-ai-sdk',
|
||||||
category: 'search',
|
category: 'search',
|
||||||
frameworks: ['vercel-ai'],
|
frameworks: ['vercel-ai'],
|
||||||
exportName: 'airweaveSearch',
|
name: 'airweaveSearch',
|
||||||
description: 'Provides unified search across all connected data sources using semantic search.',
|
description: 'Provides unified search across all connected data sources using semantic search.',
|
||||||
tags: ['search', 'rag', 'data-sources', 'semantic-search'],
|
tags: ['search', 'rag', 'data-sources', 'semantic-search'],
|
||||||
env: [
|
env: [
|
||||||
|
|
@ -794,7 +794,7 @@ export const manualTools: ManualTool[] = [
|
||||||
name: 'AIRWEAVE_API_KEY',
|
name: 'AIRWEAVE_API_KEY',
|
||||||
description: 'API key for authenticating with Airweave services.',
|
description: 'API key for authenticating with Airweave services.',
|
||||||
required: true,
|
required: true,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
parameters: [
|
parameters: [
|
||||||
{
|
{
|
||||||
|
|
@ -802,7 +802,7 @@ export const manualTools: ManualTool[] = [
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'The default collection to search within.',
|
description: 'The default collection to search within.',
|
||||||
required: true,
|
required: true,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
returns: {
|
returns: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ model Tool {
|
||||||
package Package @relation(fields: [packageId], references: [id], onDelete: Cascade)
|
package Package @relation(fields: [packageId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
// Tool Identity
|
// Tool Identity
|
||||||
exportName String @map("export_name") @db.VarChar(100) // e.g., "helloWorldTool", "default"
|
name String @db.VarChar(100) // e.g., "helloWorldTool", "default"
|
||||||
|
|
||||||
// Tool Metadata
|
// Tool Metadata
|
||||||
description String @db.Text
|
description String @db.Text
|
||||||
|
|
@ -95,7 +95,7 @@ model Tool {
|
||||||
simulations Simulation[]
|
simulations Simulation[]
|
||||||
healthChecks HealthCheck[]
|
healthChecks HealthCheck[]
|
||||||
|
|
||||||
@@unique([packageId, exportName])
|
@@unique([packageId, name])
|
||||||
@@index([qualityScore])
|
@@index([qualityScore])
|
||||||
@@index([importHealth])
|
@@index([importHealth])
|
||||||
@@index([executionHealth])
|
@@index([executionHealth])
|
||||||
|
|
|
||||||
|
|
@ -61,11 +61,11 @@ export async function runInteractiveCLI(): Promise<GenerationResult> {
|
||||||
|
|
||||||
const tools = [
|
const tools = [
|
||||||
{
|
{
|
||||||
exportName: 'exampleTool',
|
name: 'exampleTool',
|
||||||
description: 'An example tool - customize this for your use case',
|
description: 'An example tool - customize this for your use case',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
exportName: 'anotherTool',
|
name: 'anotherTool',
|
||||||
description: 'Another example tool - add your implementation here',
|
description: 'Another example tool - add your implementation here',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
@ -124,9 +124,9 @@ async function generatePackage(config: GeneratorConfig): Promise<GenerationResul
|
||||||
|
|
||||||
// Generate tool files
|
// Generate tool files
|
||||||
for (const tool of tools) {
|
for (const tool of tools) {
|
||||||
const toolPath = path.join(outputPath, 'src', 'tools', `${tool.exportName}.ts`);
|
const toolPath = path.join(outputPath, 'src', 'tools', `${tool.name}.ts`);
|
||||||
await writeFile(toolPath, generateToolFile(tool));
|
await writeFile(toolPath, generateToolFile(tool));
|
||||||
filesCreated.push(`src/tools/${tool.exportName}.ts`);
|
filesCreated.push(`src/tools/${tool.name}.ts`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate index.ts
|
// Generate index.ts
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ export function generatePackageJson(config: GeneratorConfig): string {
|
||||||
tpmjs: {
|
tpmjs: {
|
||||||
category: packageInfo.category,
|
category: packageInfo.category,
|
||||||
tools: tools.map((tool) => ({
|
tools: tools.map((tool) => ({
|
||||||
name: tool.exportName,
|
name: tool.name,
|
||||||
description: tool.description,
|
description: tool.description,
|
||||||
})),
|
})),
|
||||||
...(tools.some((t) => t.env) && {
|
...(tools.some((t) => t.env) && {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import type { GeneratorConfig } from '../types.js';
|
||||||
export function generateReadme(config: GeneratorConfig): string {
|
export function generateReadme(config: GeneratorConfig): string {
|
||||||
const { packageInfo, tools } = config;
|
const { packageInfo, tools } = config;
|
||||||
|
|
||||||
const toolsList = tools.map((tool) => `- **${tool.exportName}**: ${tool.description}`).join('\n');
|
const toolsList = tools.map((tool) => `- **${tool.name}**: ${tool.description}`).join('\n');
|
||||||
|
|
||||||
const usageExample = tools[0];
|
const usageExample = tools[0];
|
||||||
if (!usageExample) {
|
if (!usageExample) {
|
||||||
|
|
@ -32,7 +32,7 @@ ${toolsList}
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
\`\`\`typescript
|
\`\`\`typescript
|
||||||
import { ${usageExample.exportName} } from '${packageInfo.name}';
|
import { ${usageExample.name} } from '${packageInfo.name}';
|
||||||
import { generateText } from 'ai';
|
import { generateText } from 'ai';
|
||||||
import { openai } from '@ai-sdk/openai';
|
import { openai } from '@ai-sdk/openai';
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ const result = await generateText({
|
||||||
model: openai('gpt-4'),
|
model: openai('gpt-4'),
|
||||||
prompt: 'Process this text for me',
|
prompt: 'Process this text for me',
|
||||||
tools: {
|
tools: {
|
||||||
${usageExample.exportName},
|
${usageExample.name},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ import type { ToolDefinition } from '../types.js';
|
||||||
* Generates a single tool file with Zod schema
|
* Generates a single tool file with Zod schema
|
||||||
*/
|
*/
|
||||||
export function generateToolFile(tool: ToolDefinition): string {
|
export function generateToolFile(tool: ToolDefinition): string {
|
||||||
const { exportName, description } = tool;
|
const { name, description } = tool;
|
||||||
|
|
||||||
// Generate schema name (capitalize first letter + "Schema")
|
// Generate schema name (capitalize first letter + "Schema")
|
||||||
const schemaName = `${exportName.charAt(0).toUpperCase()}${exportName.slice(1)}Schema`;
|
const schemaName = `${name.charAt(0).toUpperCase()}${name.slice(1)}Schema`;
|
||||||
|
|
||||||
// Generate simple Zod schema (can be enhanced in advanced mode)
|
// Generate simple Zod schema (can be enhanced in advanced mode)
|
||||||
const schemaContent = generateSimpleSchema();
|
const schemaContent = generateSimpleSchema();
|
||||||
|
|
@ -19,7 +19,7 @@ const ${schemaName} = z.object({
|
||||||
${schemaContent}
|
${schemaContent}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ${exportName} = tool({
|
export const ${name} = tool({
|
||||||
description: '${description}',
|
description: '${description}',
|
||||||
inputSchema: ${schemaName},
|
inputSchema: ${schemaName},
|
||||||
async execute(input: z.infer<typeof ${schemaName}>) {
|
async execute(input: z.infer<typeof ${schemaName}>) {
|
||||||
|
|
@ -33,7 +33,7 @@ export const ${exportName} = tool({
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement the tool logic here
|
// TODO: Implement the tool logic here
|
||||||
console.log('${exportName} called with:', input);
|
console.log('${name} called with:', input);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -61,7 +61,7 @@ function generateSimpleSchema(): string {
|
||||||
*/
|
*/
|
||||||
export function generateIndexFile(tools: ToolDefinition[]): string {
|
export function generateIndexFile(tools: ToolDefinition[]): string {
|
||||||
const exports = tools
|
const exports = tools
|
||||||
.map((tool) => `export { ${tool.exportName} } from './tools/${tool.exportName}.js';`)
|
.map((tool) => `export { ${tool.name} } from './tools/${tool.name}.js';`)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
|
|
||||||
return `/**
|
return `/**
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ program
|
||||||
.option('--name <name>', 'Package name (e.g., @myorg/content-tools)')
|
.option('--name <name>', 'Package name (e.g., @myorg/content-tools)')
|
||||||
.option('--description <description>', 'Package description')
|
.option('--description <description>', 'Package description')
|
||||||
.option('--category <category>', 'Tool category')
|
.option('--category <category>', 'Tool category')
|
||||||
.option('--tool <tool...>', 'Tool definition (format: "exportName:description")')
|
.option('--tool <tool...>', 'Tool definition (format: "name:description")')
|
||||||
.option('--output <path>', 'Output path')
|
.option('--output <path>', 'Output path')
|
||||||
.option('--yes', 'Skip confirmation prompt')
|
.option('--yes', 'Skip confirmation prompt')
|
||||||
.action(async (options) => {
|
.action(async (options) => {
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ export async function promptTools(): Promise<ToolDefinition[] | null> {
|
||||||
* Prompts for a single tool definition
|
* Prompts for a single tool definition
|
||||||
*/
|
*/
|
||||||
async function promptSingleTool(number: number): Promise<ToolDefinition | null> {
|
async function promptSingleTool(number: number): Promise<ToolDefinition | null> {
|
||||||
const exportName = await clack.text({
|
const name = await clack.text({
|
||||||
message: `Tool #${number} export name`,
|
message: `Tool #${number} export name`,
|
||||||
placeholder: number === 1 ? 'summarizeText' : number === 2 ? 'extractKeywords' : 'myTool',
|
placeholder: number === 1 ? 'summarizeText' : number === 2 ? 'extractKeywords' : 'myTool',
|
||||||
validate: (value) => {
|
validate: (value) => {
|
||||||
|
|
@ -67,7 +67,7 @@ async function promptSingleTool(number: number): Promise<ToolDefinition | null>
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (clack.isCancel(exportName)) {
|
if (clack.isCancel(name)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,7 +93,7 @@ async function promptSingleTool(number: number): Promise<ToolDefinition | null>
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
exportName: exportName as string,
|
name: name as string,
|
||||||
description: description as string,
|
description: description as string,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface ToolDefinition {
|
export interface ToolDefinition {
|
||||||
exportName: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
parameters?: ParameterDefinition[];
|
parameters?: ParameterDefinition[];
|
||||||
returns?: ReturnDefinition;
|
returns?: ReturnDefinition;
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export const registryExecuteTool = tool({
|
||||||
properties: {
|
properties: {
|
||||||
toolId: {
|
toolId: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: "Tool identifier from registrySearchTool (format: 'package::exportName')",
|
description: "Tool identifier from registrySearchTool (format: 'package::name')",
|
||||||
},
|
},
|
||||||
params: {
|
params: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
|
@ -49,22 +49,22 @@ export const registryExecuteTool = tool({
|
||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
}),
|
}),
|
||||||
async execute({ toolId, params, env }) {
|
async execute({ toolId, params, env }) {
|
||||||
// Parse toolId format: "package::exportName"
|
// Parse toolId format: "package::name"
|
||||||
const separatorIndex = toolId.lastIndexOf('::');
|
const separatorIndex = toolId.lastIndexOf('::');
|
||||||
if (separatorIndex === -1) {
|
if (separatorIndex === -1) {
|
||||||
throw new Error(`Invalid toolId format. Expected "package::exportName", got "${toolId}"`);
|
throw new Error(`Invalid toolId format. Expected "package::name", got "${toolId}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const packageName = toolId.substring(0, separatorIndex);
|
const packageName = toolId.substring(0, separatorIndex);
|
||||||
const exportName = toolId.substring(separatorIndex + 2);
|
const name = toolId.substring(separatorIndex + 2);
|
||||||
|
|
||||||
if (!packageName || !exportName) {
|
if (!packageName || !name) {
|
||||||
throw new Error(`Invalid toolId format. Expected "package::exportName", got "${toolId}"`);
|
throw new Error(`Invalid toolId format. Expected "package::name", got "${toolId}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch tool metadata to get version and importUrl
|
// Fetch tool metadata to get version and importUrl
|
||||||
const metaParams = new URLSearchParams({
|
const metaParams = new URLSearchParams({
|
||||||
q: exportName,
|
q: name,
|
||||||
limit: '10',
|
limit: '10',
|
||||||
});
|
});
|
||||||
const metaResponse = await fetch(`${TPMJS_API_URL}/api/tools/search?${metaParams}`);
|
const metaResponse = await fetch(`${TPMJS_API_URL}/api/tools/search?${metaParams}`);
|
||||||
|
|
@ -80,7 +80,7 @@ export const registryExecuteTool = tool({
|
||||||
// Find the exact tool match
|
// Find the exact tool match
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: API response types vary
|
// biome-ignore lint/suspicious/noExplicitAny: API response types vary
|
||||||
const toolMeta = toolsArray.find(
|
const toolMeta = toolsArray.find(
|
||||||
(t: any) => t.package.npmPackageName === packageName && t.exportName === exportName
|
(t: any) => t.package.npmPackageName === packageName && t.name === name
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!toolMeta) {
|
if (!toolMeta) {
|
||||||
|
|
@ -96,7 +96,7 @@ export const registryExecuteTool = tool({
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
packageName,
|
packageName,
|
||||||
exportName,
|
name,
|
||||||
version,
|
version,
|
||||||
importUrl,
|
importUrl,
|
||||||
params,
|
params,
|
||||||
|
|
|
||||||
|
|
@ -81,10 +81,10 @@ export const registrySearchTool = tool({
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: Tool types from API vary
|
// biome-ignore lint/suspicious/noExplicitAny: Tool types from API vary
|
||||||
tools: toolsArray.map((t: any) => ({
|
tools: toolsArray.map((t: any) => ({
|
||||||
// Unique identifier for registryExecuteTool
|
// Unique identifier for registryExecuteTool
|
||||||
toolId: `${t.package.npmPackageName}::${t.exportName}`,
|
toolId: `${t.package.npmPackageName}::${t.name}`,
|
||||||
|
|
||||||
// Human-readable info
|
// Human-readable info
|
||||||
name: t.exportName,
|
name: t.name,
|
||||||
package: t.package.npmPackageName,
|
package: t.package.npmPackageName,
|
||||||
description: t.description,
|
description: t.description,
|
||||||
category: t.package.category,
|
category: t.package.category,
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ export const searchTpmjsToolsTool = tool({
|
||||||
tools: toolsArray.map((tool: any) => ({
|
tools: toolsArray.map((tool: any) => ({
|
||||||
toolId: tool.id,
|
toolId: tool.id,
|
||||||
packageName: tool.package.npmPackageName,
|
packageName: tool.package.npmPackageName,
|
||||||
exportName: tool.exportName,
|
name: tool.name,
|
||||||
description: tool.description,
|
description: tool.description,
|
||||||
category: tool.package.category,
|
category: tool.package.category,
|
||||||
qualityScore: tool.qualityScore,
|
qualityScore: tool.qualityScore,
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ async function main() {
|
||||||
const tools = await prisma.tool.findMany({
|
const tools = await prisma.tool.findMany({
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
exportName: true,
|
name: true,
|
||||||
package: {
|
package: {
|
||||||
select: {
|
select: {
|
||||||
npmPackageName: true,
|
npmPackageName: true,
|
||||||
|
|
@ -97,7 +97,7 @@ async function main() {
|
||||||
OR: [{ importHealth: 'BROKEN' }, { executionHealth: 'BROKEN' }],
|
OR: [{ importHealth: 'BROKEN' }, { executionHealth: 'BROKEN' }],
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
exportName: true,
|
name: true,
|
||||||
package: {
|
package: {
|
||||||
select: {
|
select: {
|
||||||
npmPackageName: true,
|
npmPackageName: true,
|
||||||
|
|
@ -113,7 +113,7 @@ async function main() {
|
||||||
console.log(`\n⚠️ Broken Tools (${brokenTools.length}):`);
|
console.log(`\n⚠️ Broken Tools (${brokenTools.length}):`);
|
||||||
for (const tool of brokenTools) {
|
for (const tool of brokenTools) {
|
||||||
console.log(
|
console.log(
|
||||||
` - ${tool.package.npmPackageName}/${tool.exportName} (Import: ${tool.importHealth}, Execution: ${tool.executionHealth})`
|
` - ${tool.package.npmPackageName}/${tool.name} (Import: ${tool.importHealth}, Execution: ${tool.executionHealth})`
|
||||||
);
|
);
|
||||||
if (tool.healthCheckError) {
|
if (tool.healthCheckError) {
|
||||||
console.log(` Error: ${tool.healthCheckError.slice(0, 100)}...`);
|
console.log(` Error: ${tool.healthCheckError.slice(0, 100)}...`);
|
||||||
|
|
|
||||||
|
|
@ -66,14 +66,14 @@ async function syncHello() {
|
||||||
for (const toolDef of validation.tools || []) {
|
for (const toolDef of validation.tools || []) {
|
||||||
const tool = await prisma.tool.upsert({
|
const tool = await prisma.tool.upsert({
|
||||||
where: {
|
where: {
|
||||||
packageId_exportName: {
|
packageId_name: {
|
||||||
packageId: packageRecord.id,
|
packageId: packageRecord.id,
|
||||||
exportName: toolDef.exportName,
|
name: toolDef.name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
packageId: packageRecord.id,
|
packageId: packageRecord.id,
|
||||||
exportName: toolDef.exportName,
|
name: toolDef.name,
|
||||||
description: toolDef.description,
|
description: toolDef.description,
|
||||||
parameters: toolDef.parameters ? (toolDef.parameters as any) : undefined,
|
parameters: toolDef.parameters ? (toolDef.parameters as any) : undefined,
|
||||||
returns: toolDef.returns ? (toolDef.returns as any) : undefined,
|
returns: toolDef.returns ? (toolDef.returns as any) : undefined,
|
||||||
|
|
@ -88,13 +88,12 @@ async function syncHello() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`✅ Tool upserted: ${tool.exportName} (${tool.id})`);
|
console.log(`✅ Tool upserted: ${tool.name} (${tool.id})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete orphaned tools
|
// Delete orphaned tools
|
||||||
const orphanedTools = existingTools.filter(
|
const orphanedTools = existingTools.filter(
|
||||||
(existingTool) =>
|
(existingTool) => !validation.tools?.some((toolDef) => toolDef.name === existingTool.name)
|
||||||
!validation.tools?.some((toolDef) => toolDef.exportName === existingTool.exportName)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (orphanedTools.length > 0) {
|
if (orphanedTools.length > 0) {
|
||||||
|
|
|
||||||
|
|
@ -83,14 +83,14 @@ async function syncManualTools() {
|
||||||
// Upsert the tool
|
// Upsert the tool
|
||||||
const tool = await prisma.tool.upsert({
|
const tool = await prisma.tool.upsert({
|
||||||
where: {
|
where: {
|
||||||
packageId_exportName: {
|
packageId_name: {
|
||||||
packageId: packageRecord.id,
|
packageId: packageRecord.id,
|
||||||
exportName: manualTool.name,
|
name: manualTool.name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
packageId: packageRecord.id,
|
packageId: packageRecord.id,
|
||||||
exportName: manualTool.name,
|
name: manualTool.name,
|
||||||
description: manualTool.description,
|
description: manualTool.description,
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: Prisma Json type compatibility
|
// biome-ignore lint/suspicious/noExplicitAny: Prisma Json type compatibility
|
||||||
parameters: manualTool.parameters ? (manualTool.parameters as any) : null,
|
parameters: manualTool.parameters ? (manualTool.parameters as any) : null,
|
||||||
|
|
@ -110,7 +110,7 @@ async function syncManualTools() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(` ✅ Tool upserted: ${tool.exportName} (${tool.id})`);
|
console.log(` ✅ Tool upserted: ${tool.name} (${tool.id})`);
|
||||||
processed++;
|
processed++;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
|
|
|
||||||
|
|
@ -26,20 +26,20 @@ async function testSchema() {
|
||||||
const tool1 = await prisma.tool.create({
|
const tool1 = await prisma.tool.create({
|
||||||
data: {
|
data: {
|
||||||
packageId: pkg.id,
|
packageId: pkg.id,
|
||||||
exportName: 'helloWorldTool',
|
name: 'helloWorldTool',
|
||||||
description: 'Returns a simple Hello World greeting',
|
description: 'Returns a simple Hello World greeting',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
console.log(`✅ Tool 1 created: ${tool1.exportName}`);
|
console.log(`✅ Tool 1 created: ${tool1.name}`);
|
||||||
|
|
||||||
const tool2 = await prisma.tool.create({
|
const tool2 = await prisma.tool.create({
|
||||||
data: {
|
data: {
|
||||||
packageId: pkg.id,
|
packageId: pkg.id,
|
||||||
exportName: 'helloNameTool',
|
name: 'helloNameTool',
|
||||||
description: 'Returns a personalized greeting with name',
|
description: 'Returns a personalized greeting with name',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
console.log(`✅ Tool 2 created: ${tool2.exportName}\n`);
|
console.log(`✅ Tool 2 created: ${tool2.name}\n`);
|
||||||
|
|
||||||
// Test 3: Query package with tools
|
// Test 3: Query package with tools
|
||||||
console.log('3. Querying package with tools...');
|
console.log('3. Querying package with tools...');
|
||||||
|
|
@ -49,7 +49,7 @@ async function testSchema() {
|
||||||
});
|
});
|
||||||
console.log(`✅ Found package with ${packageWithTools?.tools.length} tools:`);
|
console.log(`✅ Found package with ${packageWithTools?.tools.length} tools:`);
|
||||||
packageWithTools?.tools.forEach((t) => {
|
packageWithTools?.tools.forEach((t) => {
|
||||||
console.log(` - ${t.exportName}: ${t.description}`);
|
console.log(` - ${t.name}: ${t.description}`);
|
||||||
});
|
});
|
||||||
console.log();
|
console.log();
|
||||||
|
|
||||||
|
|
@ -58,11 +58,11 @@ async function testSchema() {
|
||||||
const toolWithPackage = await prisma.tool.findFirst({
|
const toolWithPackage = await prisma.tool.findFirst({
|
||||||
where: {
|
where: {
|
||||||
package: { npmPackageName: '@test/hello' },
|
package: { npmPackageName: '@test/hello' },
|
||||||
exportName: 'helloWorldTool',
|
name: 'helloWorldTool',
|
||||||
},
|
},
|
||||||
include: { package: true },
|
include: { package: true },
|
||||||
});
|
});
|
||||||
console.log(`✅ Found tool: ${toolWithPackage?.exportName}`);
|
console.log(`✅ Found tool: ${toolWithPackage?.name}`);
|
||||||
console.log(` Package: ${toolWithPackage?.package.npmPackageName}`);
|
console.log(` Package: ${toolWithPackage?.package.npmPackageName}`);
|
||||||
console.log(` Category: ${toolWithPackage?.package.category}\n`);
|
console.log(` Category: ${toolWithPackage?.package.category}\n`);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue