152 lines
5.5 KiB
TypeScript
152 lines
5.5 KiB
TypeScript
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<unknown> {
|
|
return this.agentJson(platform, '/api/modules/docker-statuses');
|
|
}
|
|
|
|
async moduleStart(platform: Platform, input: { folderName?: string; rosDomainId?: string }): Promise<unknown> {
|
|
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<unknown> {
|
|
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<unknown> {
|
|
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<T>(platform: Platform, path: string, init?: RequestInit): Promise<T> {
|
|
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()
|