feat(tpmjs-spec): add auto-discovery of tools and rename exportName to name

Major changes to the TPMJS specification:

1. Auto-Discovery: The `tools` array is now optional. If omitted, TPMJS
   automatically scans package exports and registers any export with
   `description` and `execute` properties (standard AI SDK tool format).

2. Renamed `exportName` to `name` in tool definitions for cleaner spec.

3. Added `/list-exports` endpoint to Railway executor that:
   - Lists all exports from a package
   - Identifies valid AI SDK tools
   - Extracts descriptions for auto-discovered tools

4. Added `toolDiscoverySource` field to track 'auto' vs 'manual' discovery.

5. Updated tool page UI with:
   - Auto-discovery warning banner
   - Badge showing discovery source

6. Updated all documentation pages (docs, spec, publish) to reflect:
   - Optional tools array with auto-discovery
   - Use of `name` instead of `exportName`
   - Auto-extraction of schema and description

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ajax Davis 2025-12-17 14:02:26 +10:00
parent 70d112982e
commit 5fc584fc66
10 changed files with 501 additions and 112 deletions

View file

@ -80,8 +80,10 @@ export type TpmjsAiAgent = z.infer<typeof TpmjsAiAgentSchema>;
* Individual tool definition within a multi-tool package
*
* Required fields:
* - exportName: The export name of the tool from the package
* - description: A description of what the tool does (20-500 chars)
* - name: The export name of the tool from the package
*
* Optional fields (auto-extracted if not provided):
* - description: A description of what the tool does (20-500 chars) - auto-extracted from tool
*
* @deprecated fields (now auto-extracted, kept for backward compatibility):
* - parameters: Tool input parameters - auto-extracted from inputSchema
@ -89,8 +91,9 @@ export type TpmjsAiAgent = z.infer<typeof TpmjsAiAgentSchema>;
* - aiAgent: AI agent guidance - auto-extracted from tool
*/
export const TpmjsToolDefinitionSchema = z.object({
exportName: z.string().min(1, 'Export name is required'),
description: z.string().min(20, 'Description must be at least 20 characters').max(500),
name: z.string().min(1, 'Tool name is required'),
// Optional - auto-extracted from tool if not provided
description: z.string().min(20, 'Description must be at least 20 characters').max(500).optional(),
// @deprecated - now auto-extracted from tool's inputSchema
parameters: z.array(TpmjsParameterSchema).optional(),
// @deprecated - now auto-extracted from tool
@ -103,13 +106,17 @@ export type TpmjsToolDefinition = z.infer<typeof TpmjsToolDefinitionSchema>;
/**
* Multi-tool format - NEW SCHEMA
* Package-level metadata with array of tools
* Package-level metadata with optional array of tools
*
* If tools is not provided, TPMJS will auto-discover exports from the package.
* Authors can override auto-discovery by providing explicit tool definitions.
*/
export const TpmjsMultiToolSchema = z.object({
category: z.enum(TPMJS_CATEGORIES, {
message: `Category must be one of: ${TPMJS_CATEGORIES.join(', ')}`,
}),
tools: z.array(TpmjsToolDefinitionSchema).min(1, 'At least one tool is required'),
// Optional - if not provided, tools are auto-discovered from package exports
tools: z.array(TpmjsToolDefinitionSchema).optional(),
env: z.array(TpmjsEnvSchema).optional(),
frameworks: z
.array(z.enum(['vercel-ai', 'langchain', 'llamaindex', 'haystack', 'semantic-kernel']))
@ -179,6 +186,8 @@ export interface ValidationResult {
};
tools?: TpmjsToolDefinition[];
wasLegacyFormat?: boolean;
// When true, tools need to be auto-discovered from package exports
needsAutoDiscovery?: boolean;
}
/**
@ -191,9 +200,12 @@ export function validateTpmjsField(tpmjs: unknown): ValidationResult {
if (multiResult.success) {
const data = multiResult.data;
// Check if tools need auto-discovery
const needsAutoDiscovery = !data.tools || data.tools.length === 0;
// Determine tier based on tool richness
const hasRichFields =
data.tools.some((tool) => tool.parameters || tool.returns || tool.aiAgent) ||
(data.tools?.some((tool) => tool.parameters || tool.returns || tool.aiAgent) ?? false) ||
data.env ||
data.frameworks;
@ -208,6 +220,7 @@ export function validateTpmjsField(tpmjs: unknown): ValidationResult {
},
tools: data.tools,
wasLegacyFormat: false,
needsAutoDiscovery,
};
}
@ -218,7 +231,7 @@ export function validateTpmjsField(tpmjs: unknown): ValidationResult {
// Auto-migrate to multi-tool format
const tool: TpmjsToolDefinition = {
exportName: 'default',
name: 'default',
description: legacyData.description,
parameters: legacyData.parameters,
returns: legacyData.returns,
@ -243,6 +256,7 @@ export function validateTpmjsField(tpmjs: unknown): ValidationResult {
},
tools: [tool],
wasLegacyFormat: true,
needsAutoDiscovery: false,
};
}
@ -251,7 +265,7 @@ export function validateTpmjsField(tpmjs: unknown): ValidationResult {
if (minimalResult.success) {
// Auto-migrate to multi-tool format
const tool: TpmjsToolDefinition = {
exportName: 'default',
name: 'default',
description: minimalResult.data.description,
};
@ -264,6 +278,7 @@ export function validateTpmjsField(tpmjs: unknown): ValidationResult {
},
tools: [tool],
wasLegacyFormat: true,
needsAutoDiscovery: false,
};
}