fix: properly pass env vars to cached tools and add extensive logging

PROBLEM:
- Tool wrappers cached env vars in closure, so cached tools used stale env
- Client env vars weren't reaching Railway executor even when provided
- No visibility into env var flow through the system

SOLUTION:
1. Store env vars per conversation in conversationEnv Map
2. Tool execute functions look up latest env from Map (not closure)
3. Chat API calls setConversationEnv() on each request
4. Added logging at every step

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ajax Davis 2025-12-04 14:18:32 +10:00
parent d339637ebf
commit 4094c661be
3 changed files with 65 additions and 10 deletions

View file

@ -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<string, string> = 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

View file

@ -6,6 +6,9 @@ const moduleCache = new Map<string, any>();
// Cache for per-conversation active tools
const conversationTools = new Map<string, Set<string>>();
// Cache for per-conversation env vars (updated on each request)
const conversationEnv = new Map<string, Record<string, string>>();
// 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<string, string>): 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<string, string> {
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<string, string>
): Promise<any | null> {
@ -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<string, string>
): Promise<Record<string, any>> {
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);

View file

@ -173,6 +173,14 @@ async function executeTool(req: Request): Promise<Response> {
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<Response> {
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