|
6 | 6 |
|
7 | 7 | import express from 'express';
|
8 | 8 | import http from 'http';
|
9 |
| -import { Server } from 'socket.io'; |
| 9 | +import { Server as SocketIOServer } from 'socket.io'; |
10 | 10 | import { AgentTARS } from '@agent-tars/core';
|
11 | 11 | import { EventStreamBridge } from './event-stream';
|
12 | 12 | import { EventType } from '@multimodal/agent';
|
@@ -78,96 +78,215 @@ export class AgentSession {
|
78 | 78 | }
|
79 | 79 | }
|
80 | 80 |
|
81 |
| -export async function startServer(options: ServerOptions): Promise<http.Server> { |
82 |
| - const { port } = options; |
83 |
| - const app = express(); |
84 |
| - const server = http.createServer(app); |
85 |
| - const io = new Server(server); |
| 81 | +/** |
| 82 | + * Agent TARS Server class that provides an encapsulated interface |
| 83 | + * for creating and managing the server instance |
| 84 | + */ |
| 85 | +export class AgentTARSServer { |
| 86 | + private app: express.Application; |
| 87 | + private server: http.Server; |
| 88 | + private io: SocketIOServer; |
| 89 | + private sessions: Record<string, AgentSession> = {}; |
| 90 | + private isRunning = false; |
| 91 | + private port: number; |
86 | 92 |
|
87 |
| - // Store active agent sessions |
88 |
| - const sessions: Record<string, AgentSession> = {}; |
| 93 | + /** |
| 94 | + * Create a new Agent TARS Server instance |
| 95 | + * @param options Server configuration options |
| 96 | + */ |
| 97 | + constructor(options: ServerOptions) { |
| 98 | + this.port = options.port; |
| 99 | + this.app = express(); |
| 100 | + this.server = http.createServer(this.app); |
| 101 | + this.io = new SocketIOServer(this.server); |
89 | 102 |
|
90 |
| - // Serve API endpoints |
91 |
| - app.use(express.json()); |
| 103 | + this.setupServer(); |
| 104 | + } |
92 | 105 |
|
93 |
| - // Create new agent session |
94 |
| - app.post('/api/sessions', async (req, res) => { |
95 |
| - try { |
96 |
| - const sessionId = `session_${Date.now()}`; |
97 |
| - const workingDirectory = ensureWorkingDirectory(sessionId); |
| 106 | + /** |
| 107 | + * Get the Express application instance |
| 108 | + * @returns Express application |
| 109 | + */ |
| 110 | + getApp(): express.Application { |
| 111 | + return this.app; |
| 112 | + } |
98 | 113 |
|
99 |
| - const session = new AgentSession(sessionId, workingDirectory); |
100 |
| - sessions[sessionId] = session; |
| 114 | + /** |
| 115 | + * Get the HTTP server instance |
| 116 | + * @returns HTTP server |
| 117 | + */ |
| 118 | + getHttpServer(): http.Server { |
| 119 | + return this.server; |
| 120 | + } |
101 | 121 |
|
102 |
| - await session.initialize(); |
| 122 | + /** |
| 123 | + * Get the Socket.IO server instance |
| 124 | + * @returns Socket.IO server |
| 125 | + */ |
| 126 | + getSocketIOServer(): SocketIOServer { |
| 127 | + return this.io; |
| 128 | + } |
103 | 129 |
|
104 |
| - res.status(201).json({ sessionId }); |
105 |
| - } catch (error) { |
106 |
| - console.error('Failed to create session:', error); |
107 |
| - res.status(500).json({ error: 'Failed to create session' }); |
108 |
| - } |
109 |
| - }); |
| 130 | + /** |
| 131 | + * Check if the server is currently running |
| 132 | + * @returns True if server is running |
| 133 | + */ |
| 134 | + isServerRunning(): boolean { |
| 135 | + return this.isRunning; |
| 136 | + } |
110 | 137 |
|
111 |
| - // Send query to specified session |
112 |
| - app.post('/api/sessions/:sessionId/query', async (req, res) => { |
113 |
| - const { sessionId } = req.params; |
114 |
| - const { query } = req.body; |
| 138 | + /** |
| 139 | + * Get an active session by ID |
| 140 | + * @param sessionId The session ID to retrieve |
| 141 | + * @returns The agent session or undefined if not found |
| 142 | + */ |
| 143 | + getSession(sessionId: string): AgentSession | undefined { |
| 144 | + return this.sessions[sessionId]; |
| 145 | + } |
115 | 146 |
|
116 |
| - if (!sessions[sessionId]) { |
117 |
| - return res.status(404).json({ error: 'Session not found' }); |
118 |
| - } |
| 147 | + /** |
| 148 | + * Get all active sessions |
| 149 | + * @returns Record of all sessions |
| 150 | + */ |
| 151 | + getAllSessions(): Record<string, AgentSession> { |
| 152 | + return { ...this.sessions }; |
| 153 | + } |
119 | 154 |
|
120 |
| - try { |
121 |
| - const result = await sessions[sessionId].runQuery(query); |
122 |
| - res.status(200).json({ result }); |
123 |
| - } catch (error) { |
124 |
| - console.error(`Error processing query in session ${sessionId}:`, error); |
125 |
| - res.status(500).json({ error: 'Failed to process query' }); |
126 |
| - } |
127 |
| - }); |
| 155 | + /** |
| 156 | + * Set up server routes and socket handlers |
| 157 | + * @private |
| 158 | + */ |
| 159 | + private setupServer(): void { |
| 160 | + // Serve API endpoints |
| 161 | + this.app.use(express.json()); |
| 162 | + |
| 163 | + // Create new agent session |
| 164 | + this.app.post('/api/sessions', async (req, res) => { |
| 165 | + try { |
| 166 | + const sessionId = `session_${Date.now()}`; |
| 167 | + const workingDirectory = ensureWorkingDirectory(sessionId); |
| 168 | + |
| 169 | + const session = new AgentSession(sessionId, workingDirectory); |
| 170 | + this.sessions[sessionId] = session; |
128 | 171 |
|
129 |
| - // WebSocket connection handling |
130 |
| - io.on('connection', (socket) => { |
131 |
| - console.log('Client connected:', socket.id); |
| 172 | + await session.initialize(); |
132 | 173 |
|
133 |
| - socket.on('join-session', (sessionId) => { |
134 |
| - if (sessions[sessionId]) { |
135 |
| - socket.join(sessionId); |
136 |
| - console.log(`Client ${socket.id} joined session ${sessionId}`); |
| 174 | + res.status(201).json({ sessionId }); |
| 175 | + } catch (error) { |
| 176 | + console.error('Failed to create session:', error); |
| 177 | + res.status(500).json({ error: 'Failed to create session' }); |
| 178 | + } |
| 179 | + }); |
137 | 180 |
|
138 |
| - // Subscribe to session's event stream |
139 |
| - const eventHandler = (eventType: string, data: any) => { |
140 |
| - socket.emit('agent-event', { type: eventType, data }); |
141 |
| - }; |
| 181 | + // Send query to specified session |
| 182 | + this.app.post('/api/sessions/:sessionId/query', async (req, res) => { |
| 183 | + const { sessionId } = req.params; |
| 184 | + const { query } = req.body; |
142 | 185 |
|
143 |
| - sessions[sessionId].eventBridge.subscribe(eventHandler); |
| 186 | + if (!this.sessions[sessionId]) { |
| 187 | + return res.status(404).json({ error: 'Session not found' }); |
| 188 | + } |
144 | 189 |
|
145 |
| - socket.on('disconnect', () => { |
146 |
| - sessions[sessionId].eventBridge.unsubscribe(eventHandler); |
147 |
| - }); |
148 |
| - } else { |
149 |
| - socket.emit('error', 'Session not found'); |
| 190 | + try { |
| 191 | + const result = await this.sessions[sessionId].runQuery(query); |
| 192 | + res.status(200).json({ result }); |
| 193 | + } catch (error) { |
| 194 | + console.error(`Error processing query in session ${sessionId}:`, error); |
| 195 | + res.status(500).json({ error: 'Failed to process query' }); |
150 | 196 | }
|
151 | 197 | });
|
152 | 198 |
|
153 |
| - socket.on('send-query', async ({ sessionId, query }) => { |
154 |
| - if (sessions[sessionId]) { |
155 |
| - try { |
156 |
| - await sessions[sessionId].runQuery(query); |
157 |
| - } catch (error) { |
158 |
| - console.error('Error processing query:', error); |
| 199 | + // WebSocket connection handling |
| 200 | + this.io.on('connection', (socket) => { |
| 201 | + console.log('Client connected:', socket.id); |
| 202 | + |
| 203 | + socket.on('join-session', (sessionId) => { |
| 204 | + if (this.sessions[sessionId]) { |
| 205 | + socket.join(sessionId); |
| 206 | + console.log(`Client ${socket.id} joined session ${sessionId}`); |
| 207 | + |
| 208 | + // Subscribe to session's event stream |
| 209 | + const eventHandler = (eventType: string, data: any) => { |
| 210 | + socket.emit('agent-event', { type: eventType, data }); |
| 211 | + }; |
| 212 | + |
| 213 | + this.sessions[sessionId].eventBridge.subscribe(eventHandler); |
| 214 | + |
| 215 | + socket.on('disconnect', () => { |
| 216 | + if (this.sessions[sessionId]) { |
| 217 | + this.sessions[sessionId].eventBridge.unsubscribe(eventHandler); |
| 218 | + } |
| 219 | + }); |
| 220 | + } else { |
| 221 | + socket.emit('error', 'Session not found'); |
159 | 222 | }
|
160 |
| - } else { |
161 |
| - socket.emit('error', 'Session not found'); |
162 |
| - } |
| 223 | + }); |
| 224 | + |
| 225 | + socket.on('send-query', async ({ sessionId, query }) => { |
| 226 | + if (this.sessions[sessionId]) { |
| 227 | + try { |
| 228 | + await this.sessions[sessionId].runQuery(query); |
| 229 | + } catch (error) { |
| 230 | + console.error('Error processing query:', error); |
| 231 | + } |
| 232 | + } else { |
| 233 | + socket.emit('error', 'Session not found'); |
| 234 | + } |
| 235 | + }); |
163 | 236 | });
|
164 |
| - }); |
| 237 | + } |
165 | 238 |
|
166 |
| - // Start server |
167 |
| - return new Promise((resolve) => { |
168 |
| - server.listen(port, () => { |
169 |
| - console.log(`🚀 Agent TARS Server is running at http://localhost:${port}`); |
170 |
| - resolve(server); |
| 239 | + /** |
| 240 | + * Start the server on the configured port |
| 241 | + * @returns Promise resolving with the server instance |
| 242 | + */ |
| 243 | + async start(): Promise<http.Server> { |
| 244 | + return new Promise((resolve) => { |
| 245 | + this.server.listen(this.port, () => { |
| 246 | + console.log(`🚀 Agent TARS Server is running at http://localhost:${this.port}`); |
| 247 | + this.isRunning = true; |
| 248 | + resolve(this.server); |
| 249 | + }); |
171 | 250 | });
|
172 |
| - }); |
| 251 | + } |
| 252 | + |
| 253 | + /** |
| 254 | + * Stop the server and clean up all resources |
| 255 | + * @returns Promise resolving when server is stopped |
| 256 | + */ |
| 257 | + async stop(): Promise<void> { |
| 258 | + // Clean up all active sessions |
| 259 | + const sessionCleanup = Object.values(this.sessions).map((session) => session.cleanup()); |
| 260 | + await Promise.all(sessionCleanup); |
| 261 | + |
| 262 | + // Clear sessions |
| 263 | + this.sessions = {}; |
| 264 | + |
| 265 | + // Close server if running |
| 266 | + if (this.isRunning) { |
| 267 | + return new Promise((resolve, reject) => { |
| 268 | + this.server.close((err) => { |
| 269 | + if (err) { |
| 270 | + reject(err); |
| 271 | + return; |
| 272 | + } |
| 273 | + |
| 274 | + this.isRunning = false; |
| 275 | + console.log('Server stopped'); |
| 276 | + resolve(); |
| 277 | + }); |
| 278 | + }); |
| 279 | + } |
| 280 | + |
| 281 | + return Promise.resolve(); |
| 282 | + } |
| 283 | +} |
| 284 | + |
| 285 | +/** |
| 286 | + * Legacy function to maintain backward compatibility |
| 287 | + * @deprecated Use the `AgentTARSServer` class directly instead |
| 288 | + */ |
| 289 | +export async function startServer(options: ServerOptions): Promise<http.Server> { |
| 290 | + const server = new AgentTARSServer(options); |
| 291 | + return server.start(); |
173 | 292 | }
|
0 commit comments