From 8a79396a0a2e0c5ddfe86b5861c986f83dace1c3 Mon Sep 17 00:00:00 2001 From: Ajax Davis Date: Mon, 15 Dec 2025 12:34:22 +1000 Subject: [PATCH] fix: inject env vars before factory calls and skip caching factory tools - Move env var injection to happen BEFORE cache check and factory function calls - This ensures process.env is set when factory functions like Valyu's webSearch() read from it - Skip caching factory-created tools since they may read env vars at creation time - Fixes issue where Valyu tools fail with 'VALYU_API_KEY is required' even when key is provided --- apps/railway-executor/server.ts | 81 +++++++++++++++++---------------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/apps/railway-executor/server.ts b/apps/railway-executor/server.ts index 3d8e321..61bb33c 100644 --- a/apps/railway-executor/server.ts +++ b/apps/railway-executor/server.ts @@ -408,6 +408,34 @@ async function executeTool(req: Request): Promise { const cacheKey = `${packageName}::${exportName}`; + // Inject environment variables FIRST - before cache check and factory calls + // This ensures process.env is set when factory functions read from it + if (env && typeof env === 'object') { + const envKeys = Object.keys(env); + if (envKeys.length > 0) { + console.log(`🔐 Injecting ${envKeys.length} environment variables:`, envKeys); + for (const [key, value] of Object.entries(env)) { + const stringValue = String(value); + + // Set in Deno environment (for esm.sh imports) + Deno.env.set(key, stringValue); + + // ALSO set in Node.js process.env (for npm: imports) + // @ts-ignore - process is available in Node.js compatibility mode + if (typeof globalThis.process !== 'undefined' && globalThis.process.env) { + // @ts-ignore - process.env exists in Node compat mode + globalThis.process.env[key] = stringValue; + } + + console.log(` ✅ Set ${key} = ${stringValue.substring(0, 10)}...`); + } + } else { + console.log('⚠️ No env vars provided in request'); + } + } else { + console.log('⚠️ No env object in request body'); + } + // biome-ignore lint/suspicious/noImplicitAnyLet: Tool type is determined dynamically after import let toolModule; @@ -433,9 +461,14 @@ async function executeTool(req: Request): Promise { ); } + // Track if this is a factory function - we won't cache factory-created tools + // because they may read env vars at creation time + let isFactoryFunction = false; + // Check if it's a factory function (not a direct tool) if (typeof rawExport === 'function' && !rawExport.description && !rawExport.execute) { console.log(`🏭 Detected factory function for ${cacheKey}, attempting to call...`); + isFactoryFunction = true; let factoryResult = null; @@ -530,49 +563,19 @@ async function executeTool(req: Request): Promise { ); } - moduleCache.set(cacheKey, toolModule); - } - - // Inject environment variables from client - if (env && typeof env === 'object') { - const envKeys = Object.keys(env); - if (envKeys.length > 0) { - console.log(`🔐 Injecting ${envKeys.length} environment variables:`, envKeys); - for (const [key, value] of Object.entries(env)) { - const stringValue = String(value); - - // Set in Deno environment (for esm.sh imports) - Deno.env.set(key, stringValue); - - // ALSO set in Node.js process.env (for npm: imports) - // @ts-ignore - process is available in Node.js compatibility mode - if (typeof globalThis.process !== 'undefined' && globalThis.process.env) { - // @ts-ignore - process.env exists in Node compat mode - globalThis.process.env[key] = stringValue; - } - - console.log(` ✅ Set ${key} = ${stringValue.substring(0, 10)}...`); - } - // Verify they're set in both places - console.log( - '🔍 Verification - Deno.env has:', - envKeys.map((k) => `${k}=${Deno.env.get(k)?.substring(0, 10)}...`) - ); - // @ts-ignore - process is available in Node.js compatibility mode - if (typeof globalThis.process !== 'undefined' && globalThis.process.env) { - console.log( - '🔍 Verification - process.env has:', - // @ts-ignore - process.env exists in Node compat mode - envKeys.map((k) => `${k}=${globalThis.process.env[k]?.substring(0, 10)}...`) - ); - } + // Only cache non-factory tools - factory tools need to be re-created each time + // because they may read env vars at creation time (e.g., Valyu SDK reads process.env.VALYU_API_KEY) + if (!isFactoryFunction) { + moduleCache.set(cacheKey, toolModule); + console.log(`📦 Cached tool: ${cacheKey}`); } else { - console.log('⚠️ No env vars provided in request'); + console.log(`🏭 Skipping cache for factory tool: ${cacheKey} (env-dependent)`); } - } else { - console.log('⚠️ No env object in request body'); } + // Note: Environment variables are already injected at the start of this function + // before cache check and factory calls, so they're available when tools read process.env + // Execute the tool with AI SDK execution context // Some tools expect a second argument with { abortSignal, ... } const abortController = new AbortController();