import { config } from '../config' import type { Platform } from '../types' export class AgentClient { async health(platform: Platform): Promise<{ ok: boolean; message?: string }> { if (!platform.agentUrl) return { ok: false, message: 'platform_agent_url_missing' } const controller = new AbortController() const timeout = setTimeout(() => controller.abort(), Math.min(config.agentRequestTimeoutMs, 3000)) try { const response = await fetch(`${platform.agentUrl.replace(/\/+$/, '')}/health`, { headers: this.agentHeaders(), signal: controller.signal }) if (!response.ok) return { ok: false, message: `agent_health_failed:${response.status}` } const payload = await response.json().catch(() => ({ ok: true })) return { ok: payload?.ok !== false } } catch (error) { return { ok: false, message: error instanceof Error ? error.message : String(error) } } finally { clearTimeout(timeout) } } async visualizationStart(platform: Platform, input: { stack?: string; rosDomainId?: string; profile?: unknown; }): Promise<{ ok: boolean; running: boolean; stack: string | null; message?: string }> { return this.agentJson(platform, '/visualization/start', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(input), }); } async visualizationStop(platform: Platform, input: { stack?: string; profile?: unknown; } = {}): Promise<{ ok: boolean; running: boolean; stack: string | null; message?: string }> { return this.agentJson(platform, '/visualization/stop', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(input), }); } async visualizationStatus(platform: Platform): Promise<{ ok: boolean; running: boolean; stack: string | null; message?: string }> { return this.agentJson(platform, '/visualization/status'); } async moduleStatuses(platform: Platform): Promise { return this.agentJson(platform, '/api/modules/docker-statuses'); } async moduleStart(platform: Platform, input: { folderName?: string; rosDomainId?: string }): Promise { return this.agentJson(platform, '/api/modules/start', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(input), }); } async moduleStop(platform: Platform, input: { containerKey?: string }): Promise { return this.agentJson(platform, '/api/modules/stop', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(input), }); } async syncAuthorizedKeys(platform: Platform, publicKeys: string[], sshUser?: string | null): Promise { return this.agentJson(platform, '/api/ssh/authorized-keys', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ publicKeys, sshUser }), }); } async runCommand(platform: Platform, command: string, args: string[]): Promise<{ commandId: string; accepted: boolean }> { if (!platform.agentUrl) return { commandId: crypto.randomUUID(), accepted: false } const controller = new AbortController() const timeout = setTimeout(() => controller.abort(), config.agentRequestTimeoutMs) try { const response = await fetch(`${platform.agentUrl}/commands`, { method: 'POST', headers: this.agentHeaders({ 'content-type': 'application/json' }), body: JSON.stringify({ command, args }), signal: controller.signal }) if (!response.ok) throw new Error(`agent_command_failed:${response.status}`) const payload = await response.json().catch(() => ({})) return { commandId: String(payload.commandId ?? crypto.randomUUID()), accepted: true } } catch { return { commandId: crypto.randomUUID(), accepted: false } } finally { clearTimeout(timeout) } } async emergencyStop(platform: Platform): Promise<{ accepted: boolean }> { const controller = new AbortController() const timeout = setTimeout(() => controller.abort(), config.agentRequestTimeoutMs) try { const response = await fetch(`${platform.agentUrl}/emergency-stop`, { method: 'POST', headers: this.agentHeaders(), signal: controller.signal }) return { accepted: response.ok } } catch { return { accepted: false } } finally { clearTimeout(timeout) } } private async agentJson(platform: Platform, path: string, init?: RequestInit): Promise { if (!platform.agentUrl) throw new Error('platform_agent_url_missing') const controller = new AbortController() const timeout = setTimeout(() => controller.abort(), config.agentRequestTimeoutMs) try { const response = await fetch(`${platform.agentUrl.replace(/\/+$/, '')}${path}`, { ...init, headers: this.agentHeaders(init?.headers), signal: controller.signal, }) const payload = await response.json().catch(() => ({})) if (!response.ok) throw new Error(String(payload?.message ?? payload?.error ?? `agent_request_failed:${response.status}`)) return payload as T } finally { clearTimeout(timeout) } } private agentHeaders(headers?: HeadersInit): HeadersInit { const next = new Headers(headers) if (config.agentGatewayToken) { next.set('x-agent-gateway-token', config.agentGatewayToken) } return next } } export const agentClient = new AgentClient()