tpmjs/RAILWAY_DEPLOYMENT_NOTE.md
Ajax Davis 2158ee6dfd feat: add dynamic tool loading system with Railway executor
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>
2025-12-04 08:25:53 +10:00

2.9 KiB

Railway Executor - Deployment Status

Issue Discovered

Node.js does not support HTTP(S) imports by default, even with --experimental-network-imports flag (that flag doesn't exist in current Node versions).

Solutions Considered

  1. Custom ESM Loader - Complex, requires Node.js 18.19+ with --loader flag
  2. fetch + eval - Security concerns, doesn't handle ES modules properly
  3. Bundler approach - Would defeat the purpose of dynamic imports
  4. Deno - Supports HTTP imports natively, but different ecosystem

Since the core issue is that we need truly dynamic runtime imports from HTTP URLs, and Node.js doesn't support this, we have two viable paths:

Deno supports HTTP imports natively:

// server.ts (Deno)
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";

const moduleCache = new Map();

async function loadTool(url: string, exportName: string) {
  if (moduleCache.has(url)) {
    return moduleCache.get(url);
  }

  // Deno supports this natively!
  const module = await import(url);
  const tool = module[exportName];
  moduleCache.set(url, tool);
  return tool;
}

serve(async (req) => {
  // ... handle requests
}, { port: 3002 });

Deploy to Railway:

# In Railway dashboard:
# - Set Start Command: deno run --allow-net --allow-env server.ts
# - Or use railway.json with deno runtime

Option B: Pre-build Bundle Approach

Instead of truly dynamic imports, pre-fetch and cache tools:

  1. Playground searches for tools
  2. Backend fetches tool code once and caches it
  3. Use vm2 or similar to execute in sandbox
  4. Not truly "dynamic" but works with Node.js

Current Status

The Railway executor service is created but not deployed because Node.js doesn't support the required HTTP imports.

Files created:

  • apps/railway-executor/package.json
  • apps/railway-executor/server.js (incomplete - needs Deno or vm2 approach)
  • apps/railway-executor/README.md

Next Steps

If using Deno (recommended):

  1. Rewrite server.js as server.ts for Deno
  2. Deploy to Railway with Deno runtime
  3. Test HTTP imports work
  4. Update playground to use Railway URL

If sticking with Node.js:

  1. Install vm2 package for sandboxed execution
  2. Implement fetch + vm2 approach
  3. Deploy to Railway
  4. Accept limitations (less dynamic, more complex)

Alternative: Skip Railway, Use Different Architecture

Since the original issue is Next.js bundler limitations, consider:

Web Workers in Browser - Load tools client-side using native import()

  • Pros: No server needed, truly dynamic
  • Cons: Exposes API keys, security concerns

Serverless Functions with Pre-installed Tools - Deploy each tool as separate function

  • Pros: Works with Vercel/Next.js
  • Cons: Not truly dynamic, requires redeployment for new tools

Recommendation: Use Deno on Railway. It's designed for exactly this use case.