UNPKG

adk-typescript

Version:

TypeScript port of Google's Agent Development Kit (ADK)

988 lines (987 loc) 42.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createWebServer = createWebServer; exports.startWebServer = startWebServer; const express_1 = __importDefault(require("express")); const http = __importStar(require("http")); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const cors_1 = __importDefault(require("cors")); const socket_io_1 = require("socket.io"); const InMemorySessionService_1 = require("../sessions/InMemorySessionService"); const InMemoryArtifactService_1 = require("../artifacts/InMemoryArtifactService"); const runners_1 = require("../runners"); const envs = __importStar(require("./utils/envs")); /** * Checks if a directory is a valid agent directory (contains agent.ts) * * @param dirPath Path to directory to check * @returns True if directory is a valid agent directory */ function isAgentDirectory(dirPath) { console.log(`Checking if ${dirPath} is an agent directory`); // Make sure we have an absolute path const absolutePath = path.isAbsolute(dirPath) ? dirPath : path.resolve(process.cwd(), dirPath); console.log(`Resolved absolute path: ${absolutePath}`); // Check if the directory exists if (!fs.existsSync(absolutePath)) { console.log(`Directory ${absolutePath} does not exist`); return false; } // Check if it's a directory if (!fs.statSync(absolutePath).isDirectory()) { console.log(`${absolutePath} is not a directory`); return false; } // First check: Is there a src/agent.ts file (preferred structure) const srcAgentPath = path.join(absolutePath, 'src', 'agent.ts'); if (fs.existsSync(srcAgentPath)) { console.log(`Found agent.ts at ${srcAgentPath}`); return true; } // Second check: Is there a direct agent.ts file const agentPath = path.join(absolutePath, 'agent.ts'); if (fs.existsSync(agentPath)) { console.log(`Found agent.ts at ${agentPath}`); return true; } // Third check: Look for the legacy index.ts file const srcIndexPath = path.join(absolutePath, 'src', 'index.ts'); if (fs.existsSync(srcIndexPath)) { console.log(`Found index.ts at ${srcIndexPath}`); return true; } const indexPath = path.join(absolutePath, 'index.ts'); if (fs.existsSync(indexPath)) { console.log(`Found index.ts at ${indexPath}`); return true; } console.log(`No agent files found in ${absolutePath}`); return false; } /** * Gets all agent directories in a parent directory * * @param parentDir Parent directory to search in * @returns Array of agent directory names */ function getAgentDirectories(parentDir) { if (!fs.existsSync(parentDir)) return []; // Directories to exclude (similar to Python's __pycache__) const excludeDirs = [ 'node_modules', 'dist', '.git', '.github', '.vscode', '.idea', ]; return fs.readdirSync(parentDir) .filter(name => { // Exclude dot directories (like .git) if (name.startsWith('.')) { return false; } // Exclude specific directories if (excludeDirs.includes(name)) { return false; } const dirPath = path.join(parentDir, name); // Must be a directory if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) { return false; } // Must contain agent.ts or index.ts files return isAgentDirectory(dirPath); }); } /** * Creates a web server for agents * * @param params Configuration parameters * @param params.agentDir Directory containing agent modules * @param params.port Port to run the server on * @param params.allowOrigins Allowed origins for CORS * @param params.sessionDbUrl Optional database URL for session storage * @returns Object containing Express app and HTTP server */ function createWebServer(params) { const { agentDir, port, allowOrigins, sessionDbUrl } = params; // Create Express app const app = (0, express_1.default)(); // Setup CORS app.use((0, cors_1.default)({ origin: allowOrigins, methods: ['GET', 'POST'], credentials: true })); // Parse JSON request bodies app.use(express_1.default.json()); // Create HTTP server const server = http.createServer(app); // Create Socket.IO server const io = new socket_io_1.Server(server, { cors: { origin: allowOrigins, methods: ["GET", "POST"], credentials: true } }); // Create shared services // TODO: Implement database session service when sessionDbUrl is provided // For now, we'll continue using the in-memory session service if (sessionDbUrl) { console.log(`Note: Database session service URL provided (${sessionDbUrl}), but not implemented yet in TypeScript. Using in-memory session service.`); } const sessionService = new InMemorySessionService_1.InMemorySessionService(); const artifactService = new InMemoryArtifactService_1.InMemoryArtifactService(); // Map of agents by directory const agentsByDir = new Map(); // Map of runners by socket ID const runnersBySocketId = new Map(); // Map of sessions by socket ID const sessionsBySocketId = new Map(); // Map of user IDs by socket ID const userIdsBySocketId = new Map(); /** * Helper function to find a session, trying multiple app names if needed * * @param appName The app name to search for * @param userId The user ID * @param sessionId The session ID * @returns The session if found, or null */ async function findSession(appName, userId, sessionId) { // First try with the provided app name let session = sessionService.getSession({ appName, userId, sessionId }); // If session not found, try loading the agent to get its actual name if (!session) { try { console.log(`Session not found with app name "${appName}", trying to load agent to get actual name`); // Try to load the agent from either the appName directory or directly let agentModulePath; const isSpecificAgentDir = !agentDir.endsWith('adk-typescript') && !agentDir.endsWith('src'); if (isSpecificAgentDir && path.basename(agentDir) === appName) { agentModulePath = agentDir; } else { agentModulePath = path.join(agentDir, appName); } try { const rootAgent = loadAgent(agentModulePath); console.log(`Loaded agent with name: ${rootAgent.name}, trying this as app name`); // Try with the original app name from request instead of agent's name session = sessionService.getSession({ appName, userId, sessionId }); if (session) { console.log(`Found session using app name: ${appName}`); } } catch (agentError) { // Try loading from agent directory directly as fallback if (agentModulePath !== agentDir) { try { const rootAgent = loadAgent(agentDir); console.log(`Loaded agent directly with name: ${rootAgent.name}, trying this as app name`); // Try with the original app name from request instead of agent's name session = sessionService.getSession({ appName, userId, sessionId }); if (session) { console.log(`Found session using app name: ${appName}`); } } catch (directError) { console.error('Error loading agent directly:', directError); } } } } catch (error) { console.error('Error while trying agent name fallbacks:', error); } } return session; } // REST API Endpoints for session management // Endpoint to list available agents app.get('/list-apps', (req, res) => { try { // When a specific agent directory is provided on the command line (e.g., examples/simple_agent), // we should list only that agent or agents within that directory, not the entire project structure // If this is a specific agent directory (not the project root), prioritize it const isSpecificAgentDir = !agentDir.endsWith('adk-typescript') && !agentDir.endsWith('src'); // Respect the command-line agent directory if specified const basePath = isSpecificAgentDir ? path.resolve(agentDir) : (req.query.relative_path ? path.resolve(process.cwd(), req.query.relative_path) : path.resolve(agentDir)); console.log('List-apps request received'); console.log('Agent directory specified:', agentDir); console.log('Resolved path for listing:', basePath); if (!fs.existsSync(basePath)) { return res.status(404).json({ error: 'Path not found' }); } if (!fs.statSync(basePath).isDirectory()) { return res.status(400).json({ error: 'Not a directory' }); } // If the requested path is a direct agent directory (has agent.ts or index.ts), return just that agent if (isAgentDirectory(basePath)) { const agentName = path.basename(basePath); console.log(`Detected direct agent directory: ${agentName}`); res.json([agentName]); return; } // Otherwise list directories that are valid agents const agentNames = getAgentDirectories(basePath).sort(); console.log('Found agent directories:', agentNames); console.log('Sending response:', agentNames); res.json(agentNames); } catch (error) { console.error('Error listing agents:', error); res.status(500).json({ error: 'Failed to list agents' }); } }); // Create a new session app.post('/apps/:appName/users/:userId/sessions', (req, res) => { try { const { appName, userId } = req.params; const state = req.body.state || {}; console.log(`Creating new session for app: ${appName}, user: ${userId}`); // Load the agent just to verify it exists (same as Python version) let agentModulePath; const isSpecificAgentDir = !agentDir.endsWith('adk-typescript') && !agentDir.endsWith('src'); if (isSpecificAgentDir && path.basename(agentDir) === appName) { agentModulePath = agentDir; console.log(`Using agent directory directly: ${agentModulePath}`); } else { agentModulePath = path.join(agentDir, appName); console.log(`Looking for agent module at: ${agentModulePath}`); } // Try to load the agent to verify it exists (but we don't use its name for session) try { const rootAgent = loadAgent(agentModulePath); console.log(`Successfully loaded agent: ${rootAgent.name}`); } catch (agentError) { console.error('Error loading agent:', agentError); // If not found in the specific path, try to load directly from agentDir try { if (agentModulePath !== agentDir) { console.log(`Trying to load agent directly from: ${agentDir}`); const rootAgent = loadAgent(agentDir); console.log(`Successfully loaded agent directly: ${rootAgent.name}`); } else { throw agentError; } } catch (directError) { console.error('Error loading agent directly:', directError); res.status(404).json({ error: `Agent '${appName}' not found or invalid` }); return; } } // Create a new session using the shared session service // IMPORTANT: Use the app name from the request parameters (like Python implementation) const session = sessionService.createSession({ appName, // Simply use appName as provided in the URL userId, state }); console.log(`Created session: ${session.id} with app name: ${appName}`); // Return the session res.json(session); } catch (error) { console.error('Error creating session:', error); res.status(500).json({ error: 'Failed to create session' }); } }); // Get a specific session app.get('/apps/:appName/users/:userId/sessions/:sessionId', (req, res) => { try { const { appName, userId, sessionId } = req.params; console.log(`Getting session: app=${appName}, user=${userId}, session=${sessionId}`); // Direct lookup without fallbacks (like Python version) const session = sessionService.getSession({ appName, userId, sessionId }); if (!session) { console.log(`Session not found: ${sessionId}`); return res.status(404).json({ error: 'Session not found' }); } res.json(session); } catch (error) { console.error('Error getting session:', error); res.status(404).json({ error: 'Session not found' }); } }); // List sessions for a user app.get('/apps/:appName/users/:userId/sessions', (req, res) => { try { const { appName, userId } = req.params; // Get sessions from the shared service const sessions = sessionService.listSessions({ appName, userId }); // Return the sessions or an empty array res.json(sessions?.sessions || []); } catch (error) { console.error('Error listing sessions:', error); res.status(500).json({ error: 'Failed to list sessions' }); } }); // Delete a session app.delete('/apps/:appName/users/:userId/sessions/:sessionId', (req, res) => { try { const { appName, userId, sessionId } = req.params; // For simplicity, we'll just acknowledge the delete // since InMemorySessionService doesn't persist sessions across requests res.status(204).send(); } catch (error) { console.error('Error deleting session:', error); res.status(500).json({ error: 'Failed to delete session' }); } }); // Regular (non-streaming) agent run endpoint app.post('/run', async (req, res) => { try { const runRequest = req.body; console.log(`Regular run request for app: ${runRequest.app_name}, user: ${runRequest.user_id}, session: ${runRequest.session_id}`); // Verify the session exists - direct lookup without fallbacks const session = sessionService.getSession({ appName: runRequest.app_name, userId: runRequest.user_id, sessionId: runRequest.session_id }); if (!session) { return res.status(404).json({ error: 'Session not found' }); } // Try to load the agent - first from app_name subdirectory, then directly let rootAgent; try { const agentModulePath = path.join(agentDir, runRequest.app_name); rootAgent = loadAgent(agentModulePath); } catch (error) { // Try loading directly from agentDir try { rootAgent = loadAgent(agentDir); } catch (directError) { res.status(404).json({ error: `Agent not found: ${directError.message}` }); return; } } // Create a runner const runner = new runners_1.Runner({ appName: runRequest.app_name, // Use app_name directly from request agent: rootAgent, sessionService, artifactService }); // Collect all events const events = []; for await (const event of runner.runAsync({ userId: runRequest.user_id, sessionId: runRequest.session_id, newMessage: runRequest.new_message })) { events.push(event); } // Return all events res.json(events); } catch (error) { console.error('Error in run endpoint:', error); res.status(500).json({ error: 'Internal server error' }); } }); // SSE endpoint for streaming responses app.post('/run_sse', async (req, res) => { try { const runRequest = req.body; console.log(`SSE run request for app: ${runRequest.app_name}, user: ${runRequest.user_id}, session: ${runRequest.session_id}`); // Verify the session exists - direct lookup without fallbacks const session = sessionService.getSession({ appName: runRequest.app_name, userId: runRequest.user_id, sessionId: runRequest.session_id }); if (!session) { return res.status(404).json({ error: 'Session not found' }); } // Set up SSE headers res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.setHeader('X-Accel-Buffering', 'no'); // Disable buffering for Nginx // Helper to send SSE data const sendEvent = (data) => { res.write(`data: ${JSON.stringify(data)}\n\n`); }; // Try to load the agent - first from app_name subdirectory, then directly let rootAgent; try { const agentModulePath = path.join(agentDir, runRequest.app_name); rootAgent = loadAgent(agentModulePath); } catch (error) { // Try loading directly from agentDir try { rootAgent = loadAgent(agentDir); } catch (directError) { console.error(`Agent not found: ${directError.message}`); sendEvent({ error: `Agent not found: ${directError.message}` }); return res.end(); } } // Create a runner const runner = new runners_1.Runner({ appName: runRequest.app_name, // Use app_name directly from request agent: rootAgent, sessionService, artifactService }); // Run and stream events try { for await (const event of runner.runAsync({ userId: runRequest.user_id, sessionId: runRequest.session_id, newMessage: runRequest.new_message })) { sendEvent(event); } // End the response res.end(); } catch (runError) { console.error('Error in SSE streaming:', runError); sendEvent({ error: String(runError) }); res.end(); } } catch (error) { console.error('Error in run_sse endpoint:', error); // For SSE, we need to send the error as an event if (!res.headersSent) { res.setHeader('Content-Type', 'text/event-stream'); res.write(`data: ${JSON.stringify({ error: 'Internal server error' })}\n\n`); } else { res.write(`data: ${JSON.stringify({ error: 'Internal server error' })}\n\n`); } res.end(); } }); // Load agent from directory function loadAgent(agentDirectory) { if (agentsByDir.has(agentDirectory)) { console.log(`Using cached agent for ${agentDirectory}`); return agentsByDir.get(agentDirectory); } // Handle relative paths let absolutePath; if (path.isAbsolute(agentDirectory)) { absolutePath = agentDirectory; } else { absolutePath = path.resolve(process.cwd(), agentDirectory); } console.log(`Loading agent from directory: ${absolutePath}`); // Check multiple possible locations for the agent file const possibleAgentPaths = [ // Check for src/agent.ts first (preferred structure) path.join(absolutePath, 'src', 'agent.ts'), // Then direct agent.ts path.join(absolutePath, 'agent.ts'), // Then legacy src/index.ts path.join(absolutePath, 'src', 'index.ts'), // Finally legacy index.ts path.join(absolutePath, 'index.ts') ]; let agentModulePath = null; for (const filePath of possibleAgentPaths) { if (fs.existsSync(filePath)) { agentModulePath = filePath; console.log(`Found agent file at: ${filePath}`); break; } } if (!agentModulePath) { throw new Error(`Could not find agent file in ${absolutePath}. Checked: ${possibleAgentPaths.join(', ')}`); } // Get the base name and parent directory for loading environment variables const baseName = path.basename(agentDirectory); const parentDir = path.dirname(absolutePath); // Load environment variables for the agent envs.loadDotenvForAgent(baseName, parentDir); try { // Import agent module console.log(`Requiring agent module from: ${agentModulePath}`); // Use ts-node to handle TypeScript files if needed try { require('ts-node/register'); } catch (err) { console.warn('Failed to register ts-node. If agent uses TypeScript, this might cause issues.'); } const agentModule = require(agentModulePath); // Get the rootAgent let rootAgent; // Check multiple possible export patterns if (agentModule.rootAgent) { // Direct export of rootAgent rootAgent = agentModule.rootAgent; } else if (agentModule.default && agentModule.default.rootAgent) { // Default export with rootAgent property rootAgent = agentModule.default.rootAgent; } else if (typeof agentModule === 'object' && 'name' in agentModule) { // The module itself might be the agent rootAgent = agentModule; } else if (agentModule.default && typeof agentModule.default === 'object' && 'name' in agentModule.default) { // Default export might be the agent rootAgent = agentModule.default; } if (!rootAgent) { throw new Error(`Could not find rootAgent in module ${agentModulePath}. Available exports: ${Object.keys(agentModule).join(', ')}`); } console.log(`Successfully loaded agent: ${rootAgent.name}`); agentsByDir.set(agentDirectory, rootAgent); return rootAgent; } catch (error) { console.error(`Error loading agent from ${agentModulePath}:`, error); throw error; } } // Socket.IO connection handling io.on('connection', (socket) => { console.log(`New client connected: ${socket.id}`); // Handle agent initialization socket.on('initialize_agent', async (data) => { try { const agentPath = data.agentPath || path.basename(agentDir); console.log(`Initializing agent with path: ${agentPath}`); // Create paths to try in order of priority const pathsToTry = [ // 1. As subdirectory of agentDir (the standard case) path.join(agentDir, agentPath), // 2. Direct path if it's absolute path.isAbsolute(agentPath) ? agentPath : null, // 3. Direct path as subdirectory of current directory path.resolve(process.cwd(), agentPath), // 4. The agentDir itself (if it is the agent) agentDir ].filter((p) => p !== null); // Type assertion to make typescript happy console.log('Attempting to load agent from these paths:', pathsToTry); // Try each path in order let rootAgent = null; let effectiveAgentPath = null; for (const tryPath of pathsToTry) { try { console.log(`Trying to load agent from: ${tryPath}`); rootAgent = loadAgent(tryPath); effectiveAgentPath = tryPath; console.log(`Successfully loaded agent from: ${tryPath}`); break; } catch (err) { // Type as any to allow message access console.log(`Failed to load agent from ${tryPath}: ${err.message || String(err)}`); } } if (!rootAgent || !effectiveAgentPath) { throw new Error(`Could not find agent at any of the paths: ${pathsToTry.join(', ')}`); } // Get session ID and user ID from the data or create defaults let sessionId = data.sessionId; const userId = data.userId || socket.id; // Verify the session exists if (sessionId) { const session = sessionService.getSession({ appName: rootAgent.name, userId: userId, sessionId: sessionId }); if (!session) { console.log(`Session ${sessionId} not found, creating a new one`); sessionId = undefined; // Force creation of a new session } else { console.log(`Found existing session: ${session.id}`); } } // Create a new session if needed if (!sessionId) { const session = sessionService.createSession({ appName: rootAgent.name, userId: userId }); sessionId = session.id; console.log(`Created new session: ${sessionId}`); } // Create runner using the shared services const runner = new runners_1.Runner({ appName: rootAgent.name, agent: rootAgent, sessionService, artifactService }); // Store runner and session ID for this socket runnersBySocketId.set(socket.id, runner); sessionsBySocketId.set(socket.id, sessionId); userIdsBySocketId.set(socket.id, userId); socket.emit('agent_initialized', { agentName: rootAgent.name, sessionId: sessionId }); console.log(`Agent initialized for ${socket.id}: ${rootAgent.name}, sessionId: ${sessionId}`); } catch (error) { console.error('Error initializing agent:', error); socket.emit('error', { message: 'Failed to initialize agent: ' + (error instanceof Error ? error.message : String(error)) }); } }); // Handle run_live WebSocket events (similar to Python's /run_live endpoint) socket.on('run_live', async (data) => { try { const { app_name, user_id, session_id } = data; // Verify the session exists const session = sessionService.getSession({ appName: app_name, userId: user_id, sessionId: session_id }); if (!session) { socket.emit('error', { error: 'Session not found' }); return; } // Try to load the agent let rootAgent; try { const agentModulePath = path.join(agentDir, app_name); rootAgent = loadAgent(agentModulePath); } catch (error) { // Try loading directly from agentDir try { rootAgent = loadAgent(agentDir); } catch (directError) { socket.emit('error', { error: `Agent not found: ${directError.message}` }); return; } } // Create a runner const runner = new runners_1.Runner({ appName: app_name, // Use app_name directly from request agent: rootAgent, sessionService, artifactService }); // Create a live request queue (simplified version as we don't have full LiveRequestQueue implementation) const messageQueue = []; let isProcessing = false; // Function to process the next message in the queue const processNextMessage = async () => { if (isProcessing || messageQueue.length === 0) return; isProcessing = true; const messageData = messageQueue.shift(); try { // Process the message const content = { role: 'user', parts: [{ text: messageData.text || messageData.content || '' }] }; // Run the agent for await (const event of runner.runAsync({ userId: user_id, sessionId: session_id, newMessage: content })) { // Send event to client socket.emit('event', event); } } catch (error) { console.error('Error processing message:', error); socket.emit('error', { error: String(error) }); } finally { isProcessing = false; // Process next message if any if (messageQueue.length > 0) { processNextMessage(); } } }; // Set up event handler for messages socket.on('message', (messageData) => { messageQueue.push(messageData); processNextMessage(); }); // Inform client that live session is ready socket.emit('live_ready'); } catch (error) { console.error('Error in run_live:', error); socket.emit('error', { error: String(error) }); } }); // Handle messages socket.on('message', async (data) => { try { const runner = runnersBySocketId.get(socket.id); const sessionId = sessionsBySocketId.get(socket.id); if (!runner) { socket.emit('error', { message: 'Agent not initialized' }); return; } if (!sessionId) { socket.emit('error', { message: 'No session ID available' }); return; } const userId = userIdsBySocketId.get(socket.id) || socket.id; // Verify the session exists - direct lookup without fallbacks const session = sessionService.getSession({ appName: runner.appName, userId: userId, sessionId }); if (!session) { console.error(`Session not found: ${sessionId} for user ${userId} and app ${runner.appName}`); socket.emit('error', { message: 'Session not found. Try reconnecting.' }); return; } const message = data.message.trim(); if (!message) { socket.emit('error', { message: 'Empty message' }); return; } console.log(`Processing message from ${socket.id} (user ${userId}) in session ${sessionId}: "${message}"`); // Create user message const userMessage = { role: 'user', parts: [{ text: message }] }; // Run the agent socket.emit('thinking', { status: 'Agent is thinking...' }); let responseText = ''; // Stream response events try { for await (const event of runner.runAsync({ userId: userId, sessionId: sessionId, newMessage: userMessage })) { if (event.content?.parts) { const text = event.content.parts .filter((part) => part.text) .map((part) => part.text || '') .join(''); if (text) { responseText = text; socket.emit('response_chunk', { text: text, author: event.author, partial: event.partial || false }); } } // Handle function calls if needed if (event.getFunctionCalls && event.getFunctionCalls().length > 0) { socket.emit('function_calls', { calls: event.getFunctionCalls() }); } // Handle function responses if needed if (event.getFunctionResponses && event.getFunctionResponses().length > 0) { socket.emit('function_responses', { responses: event.getFunctionResponses() }); } } // Send complete response socket.emit('response_complete', { text: responseText }); } catch (runError) { console.error('Error running agent:', runError); socket.emit('error', { message: 'Error processing your message: ' + (runError instanceof Error ? runError.message : String(runError)) }); } } catch (error) { console.error('Error processing message:', error); socket.emit('error', { message: 'Error processing your message: ' + (error instanceof Error ? error.message : String(error)) }); } }); // Handle disconnection socket.on('disconnect', () => { console.log(`Client disconnected: ${socket.id}`); runnersBySocketId.delete(socket.id); sessionsBySocketId.delete(socket.id); userIdsBySocketId.delete(socket.id); }); }); // Serve UI files // Check multiple possible locations for browser files const possibleUiDirs = [ path.join(__dirname, 'browser'), // Regular dist location path.join(__dirname, '..', '..', 'src', 'cli', 'browser'), // Source location path.resolve(process.cwd(), 'src', 'cli', 'browser') // Current working directory ]; let uiDir = null; for (const dir of possibleUiDirs) { if (fs.existsSync(dir)) { uiDir = dir; console.log(`Found UI files at: ${dir}`); break; } } if (uiDir) { // Serve static files app.use(express_1.default.static(uiDir)); // Serve index.html for all routes (SPA support) app.get('*', (req, res) => { if (req.path.startsWith('/api/') || req.path === '/list-apps') { // Let API endpoints be handled by their respective handlers return; } res.sendFile(path.join(uiDir, 'index.html')); }); console.log(`Serving UI from: ${uiDir}`); } else { // If no UI is available, provide a simple status page app.get('/', (req, res) => { res.send(` <html> <head> <title>ADK Web Server</title> <style> body { font-family: Arial, sans-serif; margin: 40px; line-height: 1.6; } h1 { color: #333; } .info { background: #f5f5f5; padding: 20px; border-radius: 5px; } code { background: #eee; padding: 2px 5px; border-radius: 3px; } </style> </head> <body> <h1>ADK Web Server</h1> <div class="info"> <p>Server is running on port ${port}</p> <p>Agent directory: ${path.resolve(agentDir)}</p> <p>Connect via Socket.IO to interact with the agent</p> <p>No UI is available. UI directory not found.</p> <p>Checked these locations:</p> <ul> ${possibleUiDirs.map(dir => `<li><code>${dir}</code></li>`).join('\n ')} </ul> <p>This likely means the browser folder was not included in the package.</p> </div> </body> </html> `); }); } return { app, server }; } /** * Starts a web server for agents * * @param params Configuration parameters * @param params.agentDir Directory containing agent modules * @param params.port Port to run the server on * @param params.allowOrigins Allowed origins for CORS * @param params.sessionDbUrl Optional database URL for session storage */ function startWebServer(params) { const { agentDir, port, allowOrigins, sessionDbUrl } = params; try { const { server } = createWebServer({ agentDir, port, allowOrigins, sessionDbUrl }); server.listen(port, () => { console.log(`ADK Web Server running at http://localhost:${port}`); console.log(`Agent directory: ${path.resolve(agentDir)}`); console.log("Open your browser to interact with your agent!"); }); // Handle graceful shutdown process.on('SIGINT', () => { console.log('Shutting down web server...'); server.close(() => { console.log('Web server stopped'); process.exit(0); }); }); } catch (error) { console.error('Error starting web server:', error); process.exit(1); } }