CRITICAL FIX: Was creating plain objects instead of using tool() from AI SDK.
The issue:
- Creating { description, inputSchema, execute } plain objects
- OpenAI receives invalid tool format: "type: None"
- AI SDK needs tools created with tool() function
The fix:
- Import tool() and jsonSchema() from 'ai'
- Use tool() to wrap the remote execution
- Use jsonSchema() to wrap the JSON Schema received from Railway
- Matches the format used in packages/tools/hello
Example from hello tool:
```ts
tool({
description: "...",
inputSchema: jsonSchema({ type: 'object', properties: {...} }),
execute: async (params) => {...}
})
```
Now the playground creates tools the same way!
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
THE BREAKTHROUGH: AI SDK v6 tools use jsonSchema() which wraps plain JSON Schema objects, NOT Zod schemas. JSON Schema is fully serializable.
Changes:
1. Railway server: Extract raw JSON Schema from toolModule.inputSchema?.schema
2. Playground loader: Wrap received JSON Schema with { type: 'json_schema', schema: ... }
3. This matches AI SDK v6 format exactly - no Zod serialization needed
How it works:
- Tools define inputSchema: jsonSchema({ type: 'object', properties: {...} })
- AI SDK stores it as { type: 'json_schema', schema: {...} }
- Railway extracts the plain JSON Schema (.schema property)
- Sends it as plain JSON (fully serializable)
- Playground wraps it back in AI SDK format
- OpenAI receives valid JSON Schema for function calling
This fixes both errors:
✅ No more "def.shape is not a function" (not using Zod)
✅ No more "Invalid schema type None" (proper JSON Schema provided)
Note: Tools using Zod instead of jsonSchema() will need to migrate.
TPMJS standard: All tools MUST use jsonSchema() with plain JSON Schema.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Railway is deprecating Nixpacks, so switching to a standard Dockerfile
with the official Deno image. This provides a cleaner and more maintainable
deployment configuration.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implements a complete dynamic tool loading system that allows the playground to discover and load tools from the TPMJS registry at runtime.
**Architecture:**
- Search tool package (@tpmjs/search-registry) - Searches registry for tools
- Search API endpoint (/api/tools/search) - Text-based search with scoring
- Pre-flight tool loading - Automatically searches and loads tools on every message
- Railway executor service (Deno) - Loads tools from esm.sh via HTTP imports
- Dynamic tool loader - Calls Railway to load and execute tools remotely
**Key Components:**
1. Railway Executor (apps/railway-executor/)
- Deno-based service that natively supports HTTP imports
- Endpoints: /load-and-describe, /execute-tool, /cache/stats, /cache/clear
- Deploys to Railway with deno run --allow-net --allow-env server.ts
2. Search Tool Package (packages/tools/search-registry/)
- AI SDK v6 tool for searching TPMJS registry
- Uses jsonSchema + inputSchema pattern
- Searches /api/tools/search endpoint
3. Search API (apps/web/src/app/api/tools/search/)
- Text-based search with composite scoring
- Scores: text relevance + quality boost + download boost
- Returns tool metadata with importUrl for dynamic loading
4. Dynamic Tool Loader (apps/playground/src/lib/dynamic-tool-loader.ts)
- Calls Railway service to load tools from esm.sh
- Creates tool wrappers that execute remotely
- Process-level module cache + per-conversation tracking
5. Pre-flight Loading (apps/playground/src/app/api/chat/route.ts)
- Automatically searches for tools on every user message
- Loads top 5 matching tools before agent processes request
- Merges with static tools for seamless experience
**Technical Decisions:**
- Deno over Node.js: Native HTTP import support without flags
- Remote execution: Tools run in Railway sandbox, not Vercel
- Pre-flight loading: Better UX than two-turn search pattern
- Text search: BM25 had dependency issues, simple scoring works well
**Environment Variables:**
- RAILWAY_SERVICE_URL: https://endearing-commitment-production.up.railway.app🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
BREAKING CHANGE: Complete refactoring from single-tool to multi-tool package support
Database Schema:
- Split Tool model into Package (1) and Tool (many) with one-to-many relationship
- Package stores npm metadata and package-level tpmjs fields (category, env, frameworks, tier)
- Tool stores individual tool exports with tool-level metadata (exportName, description, parameters, returns, aiAgent)
- Unique constraint on (packageId, exportName) to prevent duplicate tools
- Cascade deletes when packages are removed
Type System:
- Updated tpmjs field schema to support tools array
- Each tool has exportName, description, parameters, returns, aiAgent
- Package-level fields: category, env, frameworks shared across all tools
- Backward compatible with legacy single-tool format (auto-migrates to exportName: "default")
API Updates:
- Updated all /api/tools routes to query Tool model with Package relations
- Updated /api/tools/[slug] to accept package/export path segments
- Updated tool-executor-agent to use actual exportName instead of hardcoded "default"
- Updated metrics sync to calculate quality scores per Tool
Frontend Updates:
- Updated tool search page to display exportName as primary heading
- Updated tool detail pages to show package name as secondary info
- Removed tag-based filtering (tags moved to package level)
Manual Tool Registry:
- Added manual-tools.ts with 23 curated tools from major providers
- Created sync-manual-tools.ts script to sync manual tools to database
- Added MANUAL_TOOLS.md documentation for manual tool system
- Added GitHub workflow for automated daily sync
- Includes tools from: Vercel, Exa, Firecrawl, AWS Bedrock, Perplexity, Tavily, Superagent, Valyu
Playground Updates:
- Updated tool loader to load multiple tools per package
- Added sanitizeToolName for OpenAI API compatibility
Sync System Updates:
- Updated changes feed sync to handle multi-tool packages
- Updated keyword sync to upsert multiple tools per package
- Added orphaned tool deletion when tools removed from package.json
Migration Strategy:
- Database uses same Neon instance for dev and prod
- Schema updated via prisma db push (no migration files yet)
- All data repopulates from npm via sync system
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add sanitizeToolName function to convert package names to OpenAI-compatible
format that matches pattern ^[a-zA-Z0-9_-]+$. Removes @ symbols, replaces
/ with _, and replaces other invalid characters with _.
This fixes the error: "Invalid 'tools[0].name': string does not match pattern"
when loading tools in the playground chat interface.
Example transformations:
- @tpmjs/hello-helloWorldTool → tpmjs_hello-helloWorldTool
- firecrawl-aisdk-scrapeTool → firecrawl-aisdk-scrapeTool
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create new Next.js app at apps/playground for testing TPMJS tools
- Implement AI SDK v6 patterns with DefaultChatTransport and UIMessage format
- Create template tool package at packages/tools/hello with hello-world and hello-name tools
- Use tool() and jsonSchema() helpers to avoid Zod 4 conversion issues with OpenAI
- Add static tool loading system with switch statement (Next.js/webpack compatible)
- Implement chat interface with tool call visualization showing inputs/outputs
- Support multi-step tool execution with stepCountIs(5)
- Stream responses with toUIMessageStreamResponse() for full tool support
- Add sidebar showing available tools (static list)
- Use parts-based message rendering for text and tool calls
- Integrate firecrawl-aisdk tools (scrape, crawl, search)
- Add theme toggle in header (defaults to light mode)
- Fix responsive layout with max-width for message bubbles
- Use biome-ignore comments for legitimate any types in tool loading
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>