Spaces:
Runtime error
Runtime error
| import { NextRequest, NextResponse } from 'next/server' | |
| import { getDatabase, db_helpers } from '@/lib/db' | |
| import { requireRole } from '@/lib/auth' | |
| import { validateBody, connectSchema } from '@/lib/validation' | |
| import { eventBus } from '@/lib/event-bus' | |
| import { randomUUID } from 'crypto' | |
| /** | |
| * POST /api/connect — Register a direct CLI connection | |
| * | |
| * Auto-creates agent if name doesn't exist, deactivates previous connections | |
| * for the same agent, and returns connection details + helper URLs. | |
| */ | |
| export async function POST(request: NextRequest) { | |
| const auth = requireRole(request, 'operator') | |
| if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) | |
| const validation = await validateBody(request, connectSchema) | |
| if ('error' in validation) return validation.error | |
| const { tool_name, tool_version, agent_name, agent_role, metadata } = validation.data | |
| const db = getDatabase() | |
| const now = Math.floor(Date.now() / 1000) | |
| const workspaceId = auth.user.workspace_id ?? 1; | |
| // Find or create agent | |
| let agent = db.prepare('SELECT * FROM agents WHERE name = ? AND workspace_id = ?').get(agent_name, workspaceId) as any | |
| if (!agent) { | |
| const result = db.prepare( | |
| `INSERT INTO agents (name, role, status, created_at, updated_at, workspace_id) | |
| VALUES (?, ?, 'online', ?, ?, ?)` | |
| ).run(agent_name, agent_role || 'cli', now, now, workspaceId) | |
| agent = { id: result.lastInsertRowid, name: agent_name } | |
| db_helpers.logActivity('agent_created', 'agent', agent.id as number, 'system', | |
| `Auto-created agent "${agent_name}" via direct CLI connection`, undefined, workspaceId) | |
| eventBus.broadcast('agent.created', { id: agent.id, name: agent_name }) | |
| } else { | |
| // Set agent online | |
| db.prepare('UPDATE agents SET status = ?, updated_at = ? WHERE id = ? AND workspace_id = ?') | |
| .run('online', now, agent.id, workspaceId) | |
| eventBus.broadcast('agent.status_changed', { id: agent.id, name: agent.name, status: 'online' }) | |
| } | |
| // Deactivate previous connections for this agent | |
| db.prepare( | |
| `UPDATE direct_connections SET status = 'disconnected', updated_at = ? WHERE agent_id = ? AND status = 'connected'` | |
| ).run(now, agent.id) | |
| // Create new connection | |
| const connectionId = randomUUID() | |
| db.prepare( | |
| `INSERT INTO direct_connections (agent_id, tool_name, tool_version, connection_id, status, last_heartbeat, metadata, created_at, updated_at, workspace_id) | |
| VALUES (?, ?, ?, ?, 'connected', ?, ?, ?, ?, ?)` | |
| ).run(agent.id, tool_name, tool_version || null, connectionId, now, metadata ? JSON.stringify(metadata) : null, now, now, workspaceId) | |
| db_helpers.logActivity('connection_created', 'agent', agent.id as number, agent_name, | |
| `CLI connection established via ${tool_name}${tool_version ? ` v${tool_version}` : ''}`, undefined, workspaceId) | |
| eventBus.broadcast('connection.created', { | |
| connection_id: connectionId, | |
| agent_id: agent.id, | |
| agent_name, | |
| tool_name, | |
| }) | |
| return NextResponse.json({ | |
| connection_id: connectionId, | |
| agent_id: agent.id, | |
| agent_name, | |
| status: 'connected', | |
| sse_url: `/api/events`, | |
| heartbeat_url: `/api/agents/${agent.id}/heartbeat`, | |
| token_report_url: `/api/tokens`, | |
| }) | |
| } | |
| /** | |
| * GET /api/connect — List all direct connections | |
| */ | |
| export async function GET(request: NextRequest) { | |
| const auth = requireRole(request, 'viewer') | |
| if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) | |
| const db = getDatabase() | |
| const workspaceId = auth.user.workspace_id ?? 1; | |
| const connections = db.prepare(` | |
| SELECT dc.*, a.name as agent_name, a.status as agent_status, a.role as agent_role | |
| FROM direct_connections dc | |
| JOIN agents a ON dc.agent_id = a.id | |
| WHERE a.workspace_id = ? | |
| ORDER BY dc.created_at DESC | |
| `).all(workspaceId) | |
| return NextResponse.json({ connections }) | |
| } | |
| /** | |
| * DELETE /api/connect — Disconnect by connection_id | |
| */ | |
| export async function DELETE(request: NextRequest) { | |
| const auth = requireRole(request, 'operator') | |
| if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) | |
| let body: any | |
| try { | |
| body = await request.json() | |
| } catch { | |
| return NextResponse.json({ error: 'Invalid request body' }, { status: 400 }) | |
| } | |
| const { connection_id } = body | |
| if (!connection_id) { | |
| return NextResponse.json({ error: 'connection_id is required' }, { status: 400 }) | |
| } | |
| const db = getDatabase() | |
| const now = Math.floor(Date.now() / 1000) | |
| const workspaceId = auth.user.workspace_id ?? 1; | |
| const conn = db.prepare(` | |
| SELECT dc.* | |
| FROM direct_connections dc | |
| JOIN agents a ON a.id = dc.agent_id | |
| WHERE dc.connection_id = ? AND a.workspace_id = ? | |
| `).get(connection_id, workspaceId) as any | |
| if (!conn) { | |
| return NextResponse.json({ error: 'Connection not found' }, { status: 404 }) | |
| } | |
| db.prepare('UPDATE direct_connections SET status = ?, updated_at = ? WHERE connection_id = ?') | |
| .run('disconnected', now, connection_id) | |
| // Check if agent has other active connections; if not, set offline | |
| const otherActive = db.prepare( | |
| 'SELECT COUNT(*) as count FROM direct_connections WHERE agent_id = ? AND status = ? AND connection_id != ?' | |
| ).get(conn.agent_id, 'connected', connection_id) as any | |
| if (!otherActive?.count) { | |
| db.prepare('UPDATE agents SET status = ?, updated_at = ? WHERE id = ? AND workspace_id = ?') | |
| .run('offline', now, conn.agent_id, workspaceId) | |
| } | |
| const agent = db.prepare('SELECT name FROM agents WHERE id = ? AND workspace_id = ?').get(conn.agent_id, workspaceId) as any | |
| db_helpers.logActivity('connection_disconnected', 'agent', conn.agent_id, agent?.name || 'unknown', | |
| `CLI connection disconnected (${conn.tool_name})`, undefined, workspaceId) | |
| eventBus.broadcast('connection.disconnected', { | |
| connection_id, | |
| agent_id: conn.agent_id, | |
| agent_name: agent?.name, | |
| }) | |
| return NextResponse.json({ status: 'disconnected', connection_id }) | |
| } | |