import type { AppConfig, AppConfigUpdateRequest, BinaryCreateRequest, BinaryListResponse, BinaryUpdateRequest, BinaryValidationResult, FileSystemEntry, FileSystemListResponse, InstanceData, ServerMeta, WorkspaceCreateRequest, WorkspaceDescriptor, WorkspaceFileResponse, WorkspaceLogEntry, WorkspaceEventPayload, WorkspaceEventType, } from "../../../cli/src/api-types" const FALLBACK_API_BASE = "http://127.0.0.1:9898" const DEFAULT_BASE = typeof window !== "undefined" ? window.__CODENOMAD_API_BASE__ ?? FALLBACK_API_BASE : FALLBACK_API_BASE const DEFAULT_EVENTS_URL = typeof window !== "undefined" ? window.__CODENOMAD_EVENTS_URL__ ?? "/api/events" : "/api/events" const API_BASE = import.meta.env.VITE_CODENOMAD_API_BASE ?? DEFAULT_BASE const EVENTS_URL = API_BASE ? `${API_BASE}${DEFAULT_EVENTS_URL}` : DEFAULT_EVENTS_URL const HTTP_PREFIX = "[HTTP]" function logHttp(message: string, context?: Record) { if (context) { console.log(`${HTTP_PREFIX} ${message}`, context) return } console.log(`${HTTP_PREFIX} ${message}`) } async function request(path: string, init?: RequestInit): Promise { const url = API_BASE ? new URL(path, API_BASE).toString() : path const headers: HeadersInit = { "Content-Type": "application/json", ...(init?.headers ?? {}), } const method = (init?.method ?? "GET").toUpperCase() const startedAt = Date.now() logHttp(`${method} ${path}`) try { const response = await fetch(url, { ...init, headers }) if (!response.ok) { const message = await response.text() logHttp(`${method} ${path} -> ${response.status}`, { durationMs: Date.now() - startedAt, error: message }) throw new Error(message || `Request failed with ${response.status}`) } const duration = Date.now() - startedAt logHttp(`${method} ${path} -> ${response.status}`, { durationMs: duration }) if (response.status === 204) { return undefined as T } return (await response.json()) as T } catch (error) { logHttp(`${method} ${path} failed`, { durationMs: Date.now() - startedAt, error }) throw error } } export const cliApi = { fetchWorkspaces(): Promise { return request("/api/workspaces") }, createWorkspace(payload: WorkspaceCreateRequest): Promise { return request("/api/workspaces", { method: "POST", body: JSON.stringify(payload), }) }, fetchServerMeta(): Promise { return request("/api/meta") }, deleteWorkspace(id: string): Promise { return request(`/api/workspaces/${encodeURIComponent(id)}`, { method: "DELETE" }) }, listWorkspaceFiles(id: string, relativePath = "."): Promise { const params = new URLSearchParams({ path: relativePath }) return request(`/api/workspaces/${encodeURIComponent(id)}/files?${params.toString()}`) }, readWorkspaceFile(id: string, relativePath: string): Promise { const params = new URLSearchParams({ path: relativePath }) return request( `/api/workspaces/${encodeURIComponent(id)}/files/content?${params.toString()}`, ) }, fetchConfig(): Promise { return request("/api/config/app") }, updateConfig(payload: AppConfig): Promise { return request("/api/config/app", { method: "PUT", body: JSON.stringify(payload), }) }, patchConfig(payload: AppConfigUpdateRequest): Promise { return request("/api/config/app", { method: "PATCH", body: JSON.stringify(payload), }) }, listBinaries(): Promise { return request("/api/config/binaries") }, createBinary(payload: BinaryCreateRequest) { return request<{ binary: BinaryListResponse["binaries"][number] }>("/api/config/binaries", { method: "POST", body: JSON.stringify(payload), }) }, updateBinary(id: string, updates: BinaryUpdateRequest) { return request<{ binary: BinaryListResponse["binaries"][number] }>(`/api/config/binaries/${encodeURIComponent(id)}`, { method: "PATCH", body: JSON.stringify(updates), }) }, deleteBinary(id: string): Promise { return request(`/api/config/binaries/${encodeURIComponent(id)}`, { method: "DELETE" }) }, validateBinary(path: string): Promise { return request("/api/config/binaries/validate", { method: "POST", body: JSON.stringify({ path }), }) }, listFileSystem(path?: string, options?: { includeFiles?: boolean }): Promise { const params = new URLSearchParams() if (path && path !== ".") { params.set("path", path) } if (options?.includeFiles !== undefined) { params.set("includeFiles", String(options.includeFiles)) } const query = params.toString() return request(query ? `/api/filesystem?${query}` : "/api/filesystem") }, readInstanceData(id: string): Promise { return request(`/api/storage/instances/${encodeURIComponent(id)}`) }, writeInstanceData(id: string, data: InstanceData): Promise { return request(`/api/storage/instances/${encodeURIComponent(id)}`, { method: "PUT", body: JSON.stringify(data), }) }, deleteInstanceData(id: string): Promise { return request(`/api/storage/instances/${encodeURIComponent(id)}`, { method: "DELETE" }) }, connectEvents(onEvent: (event: WorkspaceEventPayload) => void, onError?: () => void) { console.log(`[SSE] Connecting to ${EVENTS_URL}`) const source = new EventSource(EVENTS_URL) source.onmessage = (event) => { try { const payload = JSON.parse(event.data) as WorkspaceEventPayload onEvent(payload) } catch (error) { console.error("[SSE] Failed to parse event", error) } } source.onerror = () => { console.warn("[SSE] EventSource error, closing stream") onError?.() } return source }, } export type { WorkspaceDescriptor, WorkspaceLogEntry, WorkspaceEventPayload, WorkspaceEventType }