import { fetchJson, fetchJsonWithRetry, type ApiRequestOptions } from "../client"; import { getBackendConfig } from "@/lib/config"; import type { Agent, ListAgentsResponse, ListAgentDiscoveryResponse, ListAvailableAgentsResponse, AgentProfileMcpConfig, ListExecutorsResponse, ListExecutorProfilesResponse, ExecutorProfile, ProfileEnvVar, NotificationProvidersResponse, NotificationProvider, EditorsResponse, EditorOption, CustomPrompt, PromptsResponse, SavedLayout, SidebarViewApi, UserSettingsResponse, DynamicModelsResponse, } from "@/lib/types/http"; import type { VoiceModeSettings } from "@/lib/types/http-voice"; // User settings export async function fetchUserSettings(options?: ApiRequestOptions) { return fetchJsonWithRetry("/api/v1/user/settings", options); } export async function updateUserSettings( payload: { workspace_id?: string; workflow_filter_id?: string; kanban_view_mode?: string; repository_ids?: string[]; preferred_shell?: string; default_editor_id?: string; enable_preview_on_click?: boolean; chat_submit_key?: "enter" | "cmd_enter"; review_auto_mark_on_scroll?: boolean; show_release_notification?: boolean; release_notes_last_seen_version?: string; lsp_auto_start_languages?: string[]; lsp_auto_install_languages?: string[]; lsp_server_configs?: Record>; saved_layouts?: SavedLayout[]; sidebar_views?: SidebarViewApi[]; default_utility_agent_id?: string; default_utility_model?: string; keyboard_shortcuts?: Record }>; terminal_link_behavior?: "new_tab" | "browser_panel"; terminal_font_family?: string; terminal_font_size?: number; changes_panel_layout?: "flat" | "tree"; voice_mode?: VoiceModeSettings; }, options?: ApiRequestOptions, ) { return fetchJson("/api/v1/user/settings", { ...options, init: { method: "PATCH", body: JSON.stringify(payload), ...(options?.init ?? {}) }, }); } // Executors export async function listExecutors(options?: ApiRequestOptions): Promise { return fetchJson("/api/v1/executors", options); } export async function fetchExecutor( executorId: string, options?: ApiRequestOptions, ): Promise<{ id: string; name: string; type: string; config?: Record }> { return fetchJson<{ id: string; name: string; type: string; config?: Record }>( `/api/v1/executors/${executorId}`, options, ); } export async function createExecutor( payload: { name: string; type: string; config?: Record; }, options?: ApiRequestOptions, ): Promise<{ id: string; name: string; type: string; config?: Record }> { return fetchJson<{ id: string; name: string; type: string; config?: Record }>( "/api/v1/executors", { ...options, init: { method: "POST", body: JSON.stringify(payload), ...(options?.init ?? {}) }, }, ); } export async function updateExecutor( executorId: string, payload: { name?: string; config?: Record }, options?: ApiRequestOptions, ): Promise { await fetchJson(`/api/v1/executors/${executorId}`, { ...options, init: { method: "PATCH", body: JSON.stringify(payload), ...(options?.init ?? {}) }, }); } // Executor profiles export async function listExecutorProfiles( executorId: string, options?: ApiRequestOptions, ): Promise { return fetchJson( `/api/v1/executors/${executorId}/profiles`, options, ); } export async function listAllExecutorProfiles( options?: ApiRequestOptions, ): Promise { return fetchJson("/api/v1/executor-profiles", options); } export type ScriptPlaceholder = { key: string; description: string; example: string; executor_types: string[]; }; export async function listScriptPlaceholders( options?: ApiRequestOptions, ): Promise<{ placeholders: ScriptPlaceholder[] }> { return fetchJson<{ placeholders: ScriptPlaceholder[] }>("/api/v1/script-placeholders", options); } export async function fetchDefaultScripts( executorType: string, options?: ApiRequestOptions, ): Promise<{ prepare_script: string; cleanup_script: string }> { return fetchJson<{ prepare_script: string; cleanup_script: string }>( `/api/v1/executor-profiles/default-script?type=${encodeURIComponent(executorType)}`, options, ); } export async function createExecutorProfile( executorId: string, payload: { name: string; mcp_policy?: string; config?: Record; prepare_script?: string; cleanup_script?: string; env_vars?: ProfileEnvVar[]; }, options?: ApiRequestOptions, ): Promise { return fetchJson(`/api/v1/executors/${executorId}/profiles`, { ...options, init: { method: "POST", body: JSON.stringify(payload), ...(options?.init ?? {}) }, }); } export async function updateExecutorProfile( executorId: string, profileId: string, payload: { name?: string; mcp_policy?: string; config?: Record; prepare_script?: string; cleanup_script?: string; env_vars?: ProfileEnvVar[]; }, options?: ApiRequestOptions, ): Promise { return fetchJson(`/api/v1/executors/${executorId}/profiles/${profileId}`, { ...options, init: { method: "PATCH", body: JSON.stringify(payload), ...(options?.init ?? {}) }, }); } export async function deleteExecutorProfile( executorId: string, profileId: string, options?: ApiRequestOptions, ): Promise<{ success: boolean }> { return fetchJson<{ success: boolean }>(`/api/v1/executors/${executorId}/profiles/${profileId}`, { ...options, init: { method: "DELETE", ...(options?.init ?? {}) }, }); } // Agents import { normalizeAgentProfile } from "@/lib/api/domains/agent-profile-normalize"; function normalizeAgentResponse(agent: Agent): Agent { return { ...agent, profiles: (agent.profiles ?? []).map((profile) => normalizeAgentProfile(profile)), }; } export async function listAgents(options?: ApiRequestOptions): Promise { const res = await fetchJson("/api/v1/agents", options); return { ...res, agents: (res.agents ?? []).map(normalizeAgentResponse) }; } export async function listAgentDiscovery( options?: ApiRequestOptions, ): Promise { return fetchJson("/api/v1/agents/discovery", options); } export async function listAvailableAgents( options?: ApiRequestOptions, ): Promise { return fetchJson("/api/v1/agents/available", options); } export async function getAgentProfileMcpConfig( profileId: string, options?: ApiRequestOptions, ): Promise { return fetchJson( `/api/v1/agent-profiles/${profileId}/mcp-config`, options, ); } export type CommandPreviewRequest = { model: string; permission_settings: Record; cli_passthrough: boolean; }; export type CommandPreviewResponse = { supported: boolean; command: string[]; command_string: string; }; export async function previewAgentCommand( agentName: string, payload: CommandPreviewRequest, options?: ApiRequestOptions, ): Promise { return fetchJson(`/api/v1/agent-command-preview/${agentName}`, { ...options, init: { method: "POST", body: JSON.stringify(payload), ...(options?.init ?? {}) }, }); } export type InstallJobStatus = "queued" | "running" | "succeeded" | "failed"; export type InstallJob = { job_id: string; agent_name: string; status: InstallJobStatus; output?: string; error?: string; exit_code?: number; started_at: string; finished_at?: string; }; /** Enqueue an install. Returns the job snapshot (status=queued or running). */ export async function installAgent( agentName: string, options?: ApiRequestOptions, ): Promise { return fetchJson(`/api/v1/agent-install/${agentName}`, { ...options, init: { method: "POST", ...(options?.init ?? {}) }, }); } export async function listInstallJobs( options?: ApiRequestOptions, ): Promise<{ jobs: InstallJob[] }> { return fetchJson<{ jobs: InstallJob[] }>("/api/v1/agent-install/jobs", options); } export async function getInstallJob( jobId: string, options?: ApiRequestOptions, ): Promise { return fetchJson(`/api/v1/agent-install/jobs/${jobId}`, options); } export type AgentLoginSession = { session_id: string; agent_id: string; cmd: string[]; running: boolean; started_at: string; finished_at?: string; exit_code?: number; }; export async function startAgentLogin( agentName: string, size: { cols: number; rows: number }, options?: ApiRequestOptions, ): Promise { return fetchJson(`/api/v1/agent-login/agents/${agentName}/start`, { ...options, init: { method: "POST", body: JSON.stringify(size), ...(options?.init ?? {}), }, }); } export async function stopAgentLogin(sessionID: string): Promise { await fetchJson<{ ok: boolean }>(`/api/v1/agent-login/sessions/${sessionID}/stop`, { init: { method: "POST" }, }); } export async function resizeAgentLogin( sessionID: string, size: { cols: number; rows: number }, ): Promise { await fetchJson<{ ok: boolean }>(`/api/v1/agent-login/sessions/${sessionID}/resize`, { init: { method: "POST", body: JSON.stringify(size) }, }); } /** * Build the bi-directional WS URL for streaming a login session. * Derives the host from the backend config (NOT window.location) so dev mode * — where the browser is on :37429 and the API is on :38429 — routes to the * Go backend, not the Next dev server. */ export function agentLoginStreamUrl(sessionID: string): string { const { apiBaseUrl } = getBackendConfig(); const url = new URL(apiBaseUrl); const proto = url.protocol === "https:" ? "wss:" : "ws:"; return `${proto}//${url.host}/api/v1/agent-login/sessions/${sessionID}/stream`; } /** * Start a plain host shell PTY (spawns $SHELL, or bash/sh fallback). Reuses * the same session manager as agent-login, so stop/resize/stream all use the * same session-ID-based endpoints. */ export async function startHostShell( size: { cols: number; rows: number }, options?: ApiRequestOptions, ): Promise { return fetchJson("/api/v1/host-shell/start", { ...options, init: { method: "POST", body: JSON.stringify(size), ...(options?.init ?? {}), }, }); } export async function createCustomTUIAgent( payload: { display_name: string; model?: string; command: string; description?: string }, options?: ApiRequestOptions, ): Promise { const res = await fetchJson("/api/v1/agents/tui", { ...options, init: { method: "POST", body: JSON.stringify(payload), ...(options?.init ?? {}) }, }); return normalizeAgentResponse(res); } export async function fetchDynamicModels( agentName: string, options?: ApiRequestOptions & { refresh?: boolean }, ): Promise { const refresh = options?.refresh ?? false; const url = `/api/v1/agent-models/${agentName}${refresh ? "?refresh=true" : ""}`; return fetchJson(url, options); } // Editors export async function listEditors(options?: ApiRequestOptions) { return fetchJson("/api/v1/editors", options); } export async function createEditor( payload: { name: string; kind: string; config?: Record; enabled?: boolean; }, options?: ApiRequestOptions, ) { return fetchJson("/api/v1/editors", { ...options, init: { method: "POST", body: JSON.stringify(payload), ...(options?.init ?? {}) }, }); } export async function updateEditor( editorId: string, payload: { name?: string; kind?: string; config?: Record; enabled?: boolean; }, options?: ApiRequestOptions, ) { return fetchJson(`/api/v1/editors/${editorId}`, { ...options, init: { method: "PATCH", body: JSON.stringify(payload), ...(options?.init ?? {}) }, }); } export async function deleteEditor(editorId: string, options?: ApiRequestOptions) { return fetchJson<{ success: boolean }>(`/api/v1/editors/${editorId}`, { ...options, init: { method: "DELETE", ...(options?.init ?? {}) }, }); } // Prompts export async function listPrompts(options?: ApiRequestOptions) { return fetchJson("/api/v1/prompts", options); } export async function createPrompt( payload: { name: string; content: string }, options?: ApiRequestOptions, ) { return fetchJson("/api/v1/prompts", { ...options, init: { method: "POST", body: JSON.stringify(payload), ...(options?.init ?? {}) }, }); } export async function updatePrompt( promptId: string, payload: { name?: string; content?: string }, options?: ApiRequestOptions, ) { return fetchJson(`/api/v1/prompts/${promptId}`, { ...options, init: { method: "PATCH", body: JSON.stringify(payload), ...(options?.init ?? {}) }, }); } export async function deletePrompt(promptId: string, options?: ApiRequestOptions) { return fetchJson<{ success: boolean }>(`/api/v1/prompts/${promptId}`, { ...options, init: { method: "DELETE", ...(options?.init ?? {}) }, }); } // Notification providers export async function listNotificationProviders(options?: ApiRequestOptions) { return fetchJson("/api/v1/notification-providers", options); } export async function createNotificationProvider( payload: { name: string; type: NotificationProvider["type"]; config?: NotificationProvider["config"]; enabled?: boolean; events?: string[]; }, options?: ApiRequestOptions, ) { return fetchJson("/api/v1/notification-providers", { ...options, init: { method: "POST", body: JSON.stringify(payload), ...(options?.init ?? {}) }, }); } export async function updateNotificationProvider( providerId: string, payload: Partial<{ name: string; type: NotificationProvider["type"]; config: NotificationProvider["config"]; enabled: boolean; events: string[]; }>, options?: ApiRequestOptions, ) { return fetchJson(`/api/v1/notification-providers/${providerId}`, { ...options, init: { method: "PATCH", body: JSON.stringify(payload), ...(options?.init ?? {}) }, }); } export async function testNotificationProvider(providerId: string, options?: ApiRequestOptions) { return fetchJson(`/api/v1/notification-providers/${providerId}/test`, { ...options, init: { method: "POST", ...(options?.init ?? {}) }, }); } export async function deleteNotificationProvider(providerId: string, options?: ApiRequestOptions) { return fetchJson(`/api/v1/notification-providers/${providerId}`, { ...options, init: { method: "DELETE", ...(options?.init ?? {}) }, }); } // Docker management export type DockerContainer = { id: string; name: string; image: string; state: string; status: string; started_at?: string; labels?: Record; }; export function buildDockerImageUrl(payload: { dockerfile: string; tag: string; build_args?: Record; }): string { // Returns the URL for SSE streaming. Caller should use EventSource or fetch with streaming. const params = new URLSearchParams(); params.set("dockerfile", payload.dockerfile); params.set("tag", payload.tag); if (payload.build_args) { params.set("build_args", JSON.stringify(payload.build_args)); } return `/api/v1/docker/build?${params.toString()}`; } export async function buildDockerImage( payload: { dockerfile: string; tag: string; build_args?: Record; }, options?: ApiRequestOptions, ): Promise { const baseUrl = options?.baseUrl ?? getBackendConfig().apiBaseUrl; return fetch(`${baseUrl}/api/v1/docker/build`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), ...(options?.init ?? {}), }); } export async function listDockerContainers( filter?: { image?: string; labels?: Record }, options?: ApiRequestOptions, ): Promise<{ containers: DockerContainer[] }> { const params = new URLSearchParams(); if (filter?.image) params.set("image", filter.image); if (filter?.labels) { const labelPairs = Object.entries(filter.labels).map(([k, v]) => `${k}=${v}`); params.set("labels", labelPairs.join(",")); } const qs = params.toString(); return fetchJson<{ containers: DockerContainer[] }>( `/api/v1/docker/containers${qs ? `?${qs}` : ""}`, options, ); } export async function stopDockerContainer(id: string, options?: ApiRequestOptions): Promise { await fetchJson<{ success: boolean }>(`/api/v1/docker/containers/${id}/stop`, { ...options, init: { method: "POST", ...(options?.init ?? {}) }, }); } export async function removeDockerContainer( id: string, options?: ApiRequestOptions, ): Promise { await fetchJson<{ success: boolean }>(`/api/v1/docker/containers/${id}`, { ...options, init: { method: "DELETE", ...(options?.init ?? {}) }, }); } // Remote auth methods (for remote executors like Sprites) export type RemoteAuthMethod = { method_id: string; type: "env" | "files" | "gh_cli_token"; env_var?: string; setup_hint?: string; source_files?: string[]; target_rel_dir?: string; label?: string; has_local_files?: boolean; }; export type RemoteAuthSpec = { id: string; display_name: string; methods: RemoteAuthMethod[]; }; export type ListRemoteCredentialsResponse = { auth_specs: RemoteAuthSpec[]; }; export async function listRemoteCredentials( options?: ApiRequestOptions, ): Promise { return fetchJson("/api/v1/remote-credentials", options); } export type LocalGitIdentityResponse = { user_name: string; user_email: string; detected: boolean; }; export async function fetchLocalGitIdentity( options?: ApiRequestOptions, ): Promise { return fetchJson("/api/v1/git/identity", options); } // Sprites network policies export type NetworkPolicyRule = { domain: string; action: "allow" | "deny"; include?: string; }; export type NetworkPolicy = { rules: NetworkPolicyRule[]; }; export async function getSpritesNetworkPolicies( secretId: string, spriteName: string, options?: ApiRequestOptions, ): Promise { const params = new URLSearchParams({ secret_id: secretId, sprite_name: spriteName }); return fetchJson(`/api/v1/sprites/network-policies?${params.toString()}`, options); } export async function updateSpritesNetworkPolicies( secretId: string, spriteName: string, policy: NetworkPolicy, options?: ApiRequestOptions, ): Promise { const params = new URLSearchParams({ secret_id: secretId, sprite_name: spriteName }); await fetchJson(`/api/v1/sprites/network-policies?${params.toString()}`, { ...options, init: { method: "PUT", body: JSON.stringify(policy), ...(options?.init ?? {}) }, }); }