fix(tools): return error objects instead of throwing in registry tools
All tool executions now return error objects instead of throwing exceptions. This allows the AI model to see errors and respond appropriately instead of causing the entire stream to fail silently.
This commit is contained in:
parent
88e66d54ce
commit
9fc928adae
2 changed files with 148 additions and 103 deletions
|
|
@ -49,72 +49,101 @@ export const registryExecuteTool = tool({
|
|||
additionalProperties: false,
|
||||
}),
|
||||
async execute({ toolId, params, env }) {
|
||||
// Parse toolId format: "package::name"
|
||||
const separatorIndex = toolId.lastIndexOf('::');
|
||||
if (separatorIndex === -1) {
|
||||
throw new Error(`Invalid toolId format. Expected "package::name", got "${toolId}"`);
|
||||
try {
|
||||
// Parse toolId format: "package::name"
|
||||
const separatorIndex = toolId.lastIndexOf('::');
|
||||
if (separatorIndex === -1) {
|
||||
return {
|
||||
error: true,
|
||||
message: `Invalid toolId format. Expected "package::name", got "${toolId}"`,
|
||||
toolId,
|
||||
};
|
||||
}
|
||||
|
||||
const packageName = toolId.substring(0, separatorIndex);
|
||||
const name = toolId.substring(separatorIndex + 2);
|
||||
|
||||
if (!packageName || !name) {
|
||||
return {
|
||||
error: true,
|
||||
message: `Invalid toolId format. Expected "package::name", got "${toolId}"`,
|
||||
toolId,
|
||||
};
|
||||
}
|
||||
|
||||
// Fetch tool metadata to get version and importUrl
|
||||
const metaParams = new URLSearchParams({
|
||||
q: name,
|
||||
limit: '10',
|
||||
});
|
||||
const metaResponse = await fetch(`${TPMJS_API_URL}/api/tools/search?${metaParams}`);
|
||||
|
||||
if (!metaResponse.ok) {
|
||||
return {
|
||||
error: true,
|
||||
message: `Failed to fetch tool metadata: ${metaResponse.statusText}`,
|
||||
toolId,
|
||||
};
|
||||
}
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: API response types vary
|
||||
const metaData = (await metaResponse.json()) as any;
|
||||
const toolsArray = metaData.results?.tools || [];
|
||||
|
||||
// Find the exact tool match
|
||||
// biome-ignore lint/suspicious/noExplicitAny: API response types vary
|
||||
const toolMeta = toolsArray.find(
|
||||
(t: any) => t.package.npmPackageName === packageName && t.name === name
|
||||
);
|
||||
|
||||
if (!toolMeta) {
|
||||
return {
|
||||
error: true,
|
||||
message: `Tool not found: ${toolId}. Try using registrySearchTool to find available tools.`,
|
||||
toolId,
|
||||
};
|
||||
}
|
||||
|
||||
const version = toolMeta.package.npmVersion;
|
||||
const importUrl = `https://esm.sh/${packageName}@${version}`;
|
||||
|
||||
// Execute via sandbox executor
|
||||
const response = await fetch(`${TPMJS_EXECUTOR_URL}/execute-tool`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
packageName,
|
||||
name,
|
||||
version,
|
||||
importUrl,
|
||||
params,
|
||||
env: env || {},
|
||||
}),
|
||||
});
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: API response types vary
|
||||
const result = (await response.json()) as any;
|
||||
|
||||
if (!result.success) {
|
||||
return {
|
||||
error: true,
|
||||
message: result.error || 'Tool execution failed',
|
||||
toolId,
|
||||
executionTimeMs: result.executionTimeMs,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
toolId,
|
||||
executionTimeMs: result.executionTimeMs,
|
||||
output: result.output,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
error: true,
|
||||
message: error instanceof Error ? error.message : 'Unknown execution error',
|
||||
toolId,
|
||||
};
|
||||
}
|
||||
|
||||
const packageName = toolId.substring(0, separatorIndex);
|
||||
const name = toolId.substring(separatorIndex + 2);
|
||||
|
||||
if (!packageName || !name) {
|
||||
throw new Error(`Invalid toolId format. Expected "package::name", got "${toolId}"`);
|
||||
}
|
||||
|
||||
// Fetch tool metadata to get version and importUrl
|
||||
const metaParams = new URLSearchParams({
|
||||
q: name,
|
||||
limit: '10',
|
||||
});
|
||||
const metaResponse = await fetch(`${TPMJS_API_URL}/api/tools/search?${metaParams}`);
|
||||
|
||||
if (!metaResponse.ok) {
|
||||
throw new Error(`Failed to fetch tool metadata: ${metaResponse.statusText}`);
|
||||
}
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: API response types vary
|
||||
const metaData = (await metaResponse.json()) as any;
|
||||
const toolsArray = metaData.results?.tools || [];
|
||||
|
||||
// Find the exact tool match
|
||||
// biome-ignore lint/suspicious/noExplicitAny: API response types vary
|
||||
const toolMeta = toolsArray.find(
|
||||
(t: any) => t.package.npmPackageName === packageName && t.name === name
|
||||
);
|
||||
|
||||
if (!toolMeta) {
|
||||
throw new Error(`Tool not found: ${toolId}`);
|
||||
}
|
||||
|
||||
const version = toolMeta.package.npmVersion;
|
||||
const importUrl = `https://esm.sh/${packageName}@${version}`;
|
||||
|
||||
// Execute via sandbox executor
|
||||
const response = await fetch(`${TPMJS_EXECUTOR_URL}/execute-tool`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
packageName,
|
||||
name,
|
||||
version,
|
||||
importUrl,
|
||||
params,
|
||||
env: env || {},
|
||||
}),
|
||||
});
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: API response types vary
|
||||
const result = (await response.json()) as any;
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Tool execution failed');
|
||||
}
|
||||
|
||||
return {
|
||||
toolId,
|
||||
executionTimeMs: result.executionTimeMs,
|
||||
output: result.output,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -58,45 +58,61 @@ export const registrySearchTool = tool({
|
|||
additionalProperties: false,
|
||||
}),
|
||||
async execute({ query, category, limit = 5 }) {
|
||||
const params = new URLSearchParams({
|
||||
q: query,
|
||||
limit: String(limit),
|
||||
...(category && { category }),
|
||||
});
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
q: query,
|
||||
limit: String(limit),
|
||||
...(category && { category }),
|
||||
});
|
||||
|
||||
const url = `${TPMJS_API_URL}/api/tools/search?${params}`;
|
||||
const response = await fetch(url);
|
||||
const url = `${TPMJS_API_URL}/api/tools/search?${params}`;
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Search failed: ${response.statusText}`);
|
||||
if (!response.ok) {
|
||||
return {
|
||||
error: true,
|
||||
message: `Search failed: ${response.statusText}`,
|
||||
query,
|
||||
matchCount: 0,
|
||||
tools: [],
|
||||
};
|
||||
}
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: API response types vary
|
||||
const data = (await response.json()) as any;
|
||||
const toolsArray = data.results?.tools || [];
|
||||
|
||||
return {
|
||||
query,
|
||||
matchCount: toolsArray.length,
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Tool types from API vary
|
||||
tools: toolsArray.map((t: any) => ({
|
||||
// Unique identifier for registryExecuteTool
|
||||
toolId: `${t.package.npmPackageName}::${t.name}`,
|
||||
|
||||
// Human-readable info
|
||||
name: t.name,
|
||||
package: t.package.npmPackageName,
|
||||
description: t.description,
|
||||
category: t.package.category,
|
||||
|
||||
// Execution requirements
|
||||
requiredEnvVars:
|
||||
t.package.env?.filter((e: any) => e.required).map((e: any) => e.name) || [],
|
||||
|
||||
// Quality indicators
|
||||
healthStatus: t.executionHealth,
|
||||
qualityScore: t.qualityScore,
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
error: true,
|
||||
message: error instanceof Error ? error.message : 'Unknown search error',
|
||||
query,
|
||||
matchCount: 0,
|
||||
tools: [],
|
||||
};
|
||||
}
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: API response types vary
|
||||
const data = (await response.json()) as any;
|
||||
const toolsArray = data.results?.tools || [];
|
||||
|
||||
return {
|
||||
query,
|
||||
matchCount: toolsArray.length,
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Tool types from API vary
|
||||
tools: toolsArray.map((t: any) => ({
|
||||
// Unique identifier for registryExecuteTool
|
||||
toolId: `${t.package.npmPackageName}::${t.name}`,
|
||||
|
||||
// Human-readable info
|
||||
name: t.name,
|
||||
package: t.package.npmPackageName,
|
||||
description: t.description,
|
||||
category: t.package.category,
|
||||
|
||||
// Execution requirements
|
||||
requiredEnvVars:
|
||||
t.package.env?.filter((e: any) => e.required).map((e: any) => e.name) || [],
|
||||
|
||||
// Quality indicators
|
||||
healthStatus: t.executionHealth,
|
||||
qualityScore: t.qualityScore,
|
||||
})),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue