diff --git a/apps/playground/src/app/api/chat/route.ts b/apps/playground/src/app/api/chat/route.ts index 4ef7247..11c0ba2 100644 --- a/apps/playground/src/app/api/chat/route.ts +++ b/apps/playground/src/app/api/chat/route.ts @@ -3,7 +3,11 @@ import { searchTpmjsToolsTool } from '@tpmjs/search-registry'; import { type UIMessage, convertToModelMessages, stepCountIs, streamText } from 'ai'; import type { NextRequest } from 'next/server'; import { env } from '~/env'; -import { addConversationTools, loadToolsBatch } from '~/lib/dynamic-tool-loader'; +import { + addConversationTools, + loadToolsBatch, + setConversationEnv, +} from '~/lib/dynamic-tool-loader'; import { loadAllTools, sanitizeToolName } from '~/lib/tool-loader'; export const runtime = 'nodejs'; @@ -28,7 +32,10 @@ export async function POST(request: NextRequest) { const clientEnv: Record = body.env || {}; console.log(`🔑 Conversation ID: ${conversationId}`); - console.log(`🔐 Client env vars: ${Object.keys(clientEnv).length} keys`); + console.log(`🔐 Client env vars: ${Object.keys(clientEnv).length} keys`, Object.keys(clientEnv)); + + // Store env vars for this conversation (so cached tools can access them) + setConversationEnv(conversationId, clientEnv); // Initialize OpenAI with client-provided or server API key const apiKey = clientEnv.OPENAI_API_KEY || env.OPENAI_API_KEY; @@ -148,7 +155,7 @@ export async function POST(request: NextRequest) { })); try { - const loadedTools = await loadToolsBatch(toolsToLoad, clientEnv); + const loadedTools = await loadToolsBatch(toolsToLoad, conversationId, clientEnv); console.log(`✅ Successfully loaded ${Object.keys(loadedTools).length} tools`); // Add sanitized tools to conversation state diff --git a/apps/playground/src/lib/dynamic-tool-loader.ts b/apps/playground/src/lib/dynamic-tool-loader.ts index 438afd7..8acf005 100644 --- a/apps/playground/src/lib/dynamic-tool-loader.ts +++ b/apps/playground/src/lib/dynamic-tool-loader.ts @@ -6,6 +6,9 @@ const moduleCache = new Map(); // Cache for per-conversation active tools const conversationTools = new Map>(); +// Cache for per-conversation env vars (updated on each request) +const conversationEnv = new Map>(); + // Railway service URL const RAILWAY_SERVICE_URL = process.env.RAILWAY_SERVICE_URL || process.env.SANDBOX_EXECUTOR_URL || 'http://localhost:3001'; @@ -17,6 +20,22 @@ function getCacheKey(packageName: string, exportName: string): string { return `${packageName}::${exportName}`; } +/** + * Set environment variables for a conversation + * This allows tools to access the latest env vars even when cached + */ +export function setConversationEnv(conversationId: string, env: Record): void { + console.log(`🔑 Setting env for conversation ${conversationId}:`, Object.keys(env)); + conversationEnv.set(conversationId, env); +} + +/** + * Get environment variables for a conversation + */ +function getConversationEnv(conversationId: string): Record { + return conversationEnv.get(conversationId) || {}; +} + /** * Dynamically load a tool via Railway service * Railway service runs with --experimental-network-imports and can import from esm.sh @@ -26,6 +45,7 @@ export async function loadToolDynamically( packageName: string, exportName: string, version: string, + conversationId: string, importUrl?: string, env?: Record ): Promise { @@ -81,6 +101,10 @@ export async function loadToolDynamically( execute: async (params: any) => { console.log(`🚀 Executing ${packageName}/${exportName} remotely with params:`, params); + // Get the latest env vars for this conversation (not from closure!) + const currentEnv = getConversationEnv(conversationId); + console.log(`🔐 Using env vars for conversation ${conversationId}:`, Object.keys(currentEnv)); + const execResponse = await fetch(`${RAILWAY_SERVICE_URL}/execute-tool`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -90,7 +114,7 @@ export async function loadToolDynamically( version, importUrl: importUrl || `https://esm.sh/${packageName}@${version}`, params, - env: env || {}, + env: currentEnv, }), }); @@ -129,15 +153,24 @@ export async function loadToolsBatch( version: string; importUrl?: string; }>, + conversationId: string, env?: Record ): Promise> { + console.log(`📦 Loading ${toolMetadata.length} tools for conversation ${conversationId}`); + console.log(`🔑 Env vars being passed:`, Object.keys(env || {})); + const promises = toolMetadata.map((meta) => - loadToolDynamically(meta.packageName, meta.exportName, meta.version, meta.importUrl, env).then( - (tool) => ({ - key: getCacheKey(meta.packageName, meta.exportName), - tool, - }) - ) + loadToolDynamically( + meta.packageName, + meta.exportName, + meta.version, + conversationId, + meta.importUrl, + env + ).then((tool) => ({ + key: getCacheKey(meta.packageName, meta.exportName), + tool, + })) ); const results = await Promise.all(promises); diff --git a/apps/railway-executor/server.ts b/apps/railway-executor/server.ts index 117f0b0..c866293 100644 --- a/apps/railway-executor/server.ts +++ b/apps/railway-executor/server.ts @@ -173,6 +173,14 @@ async function executeTool(req: Request): Promise { const body = await req.json(); const { packageName, exportName, version, importUrl, params, env } = body; + console.log(`📥 Execute request:`, { + packageName, + exportName, + version, + envKeys: env ? Object.keys(env) : [], + envValues: env || {}, + }); + if (!packageName || !exportName || !version) { return Response.json( { @@ -220,8 +228,15 @@ async function executeTool(req: Request): Promise { console.log(`🔐 Injecting ${envKeys.length} environment variables:`, envKeys); for (const [key, value] of Object.entries(env)) { Deno.env.set(key, String(value)); + console.log(` ✅ Set ${key} = ${String(value).substring(0, 10)}...`); } + // Verify they're set + console.log(`🔍 Verification - Deno.env has:`, envKeys.map(k => `${k}=${Deno.env.get(k)?.substring(0, 10)}...`)); + } else { + console.log(`⚠️ No env vars provided in request`); } + } else { + console.log(`⚠️ No env object in request body`); } // Execute the tool