import type { CloudUser, DeviceRecord, HostGovernancePolicy, HostTokenUsageSummary, LlmProviderProfile, LlmProviderProfileListResponse, LlmProviderSettings, LlmProviderType, HostRecord, PlannerDecisionItem, PlannerDecisionListResponse, PluginRecord, PluginRegistrationPayload, TaskAttempt, TaskListResponse, TaskSubmissionPayload, TaskStatus, TokenUsageEvent, UserListResponse, UserSubmissionPolicy, CloudSkill, CloudSkillListResponse, CloudSkillEntitlementsResponse, HostSkillInventoryResponse, CloudSkillKind, } from "./types"; const configuredBaseUrl = import.meta.env.VITE_CLOUD_API_BASE_URL as | string | undefined; export const API_BASE_URL = (configuredBaseUrl || window.location.origin).replace( /\/$/, "", ); const CSRF_COOKIE_NAME = "amcp_csrf"; export const AUTH_INVALID_EVENT = "cloud-console:auth-invalid"; export class CloudApiError extends Error { readonly status: number; constructor(status: number, message: string) { super(message); this.status = status; this.name = "CloudApiError"; } } interface RequestInitLike { method?: string; body?: string | null; headers?: Record; } async function request(path: string, init: RequestInitLike = {}): Promise { const method = init.method || "GET"; const headers: Record = { Accept: "application/json", ...init.headers, }; if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) { const csrfToken = getCookie(CSRF_COOKIE_NAME); if (csrfToken) headers["X-CSRF-Token"] = csrfToken; } if (init.body !== undefined && init.body !== null) { headers["Content-Type"] = "application/json"; } const response = await fetch(`${API_BASE_URL}${path}`, { method, body: init.body ?? null, headers, credentials: "include", }); if (response.status === 401) { window.dispatchEvent(new CustomEvent(AUTH_INVALID_EVENT)); throw new CloudApiError(401, await responseDetail(response, "authentication expired")); } if (!response.ok) { throw new CloudApiError( response.status, await responseDetail(response, `${response.status} ${response.statusText}`), ); } if (response.status === 204) return undefined as T; return (await response.json()) as T; } async function responseDetail(response: Response, fallback: string): Promise { try { const payload = (await response.json()) as { detail?: unknown }; if (typeof payload.detail === "string") return payload.detail; if (payload.detail) return JSON.stringify(payload.detail); } catch { // Preserve the fallback for empty or non-JSON responses. } return fallback; } function getCookie(name: string): string | null { const prefix = `${encodeURIComponent(name)}=`; for (const part of document.cookie.split(";")) { const value = part.trim(); if (value.startsWith(prefix)) return decodeURIComponent(value.slice(prefix.length)); } return null; } export function login(username: string, password: string): Promise { return request("/v1/auth/login", { method: "POST", body: JSON.stringify({ username, password }), }); } export function getCurrentUser(): Promise { return request("/v1/auth/me"); } export function logout(): Promise { return request("/v1/auth/logout", { method: "POST" }); } export function changePassword( currentPassword: string, newPassword: string, ): Promise { return request("/v1/auth/password", { method: "POST", body: JSON.stringify({ current_password: currentPassword, new_password: newPassword, }), }); } export function listUsers(): Promise { return request("/v1/users?limit=100&offset=0"); } export function createUser(payload: { username: string; display_name: string; role: "viewer" | "operator" | "admin"; password: string; }): Promise { return request("/v1/users", { method: "POST", body: JSON.stringify(payload), }); } export function updateUser( userId: string, payload: { display_name?: string; role?: "viewer" | "operator" | "admin"; enabled?: boolean }, ): Promise { return request(`/v1/users/${encodeURIComponent(userId)}`, { method: "PATCH", body: JSON.stringify(payload), }); } export function resetUserPassword(userId: string, password: string): Promise { return request(`/v1/users/${encodeURIComponent(userId)}/password`, { method: "POST", body: JSON.stringify({ password }), }); } export function revokeUserSessions(userId: string): Promise { return request(`/v1/users/${encodeURIComponent(userId)}/sessions`, { method: "DELETE", }); } export function getUserSubmissionPolicy(userId: string): Promise { return request( `/v1/users/${encodeURIComponent(userId)}/submission-policy`, ); } export function updateUserSubmissionPolicy( userId: string, payload: Omit & { expected_revision?: number; }, ): Promise { return request( `/v1/users/${encodeURIComponent(userId)}/submission-policy`, { method: "PUT", body: JSON.stringify(payload) }, ); } export function getHostGovernancePolicy(hostId: string): Promise { return request( `/v1/hosts/${encodeURIComponent(hostId)}/governance-policy`, ); } export function updateHostGovernancePolicy( hostId: string, payload: Omit & { expected_revision?: number; }, ): Promise { return request( `/v1/hosts/${encodeURIComponent(hostId)}/governance-policy`, { method: "PUT", body: JSON.stringify(payload) }, ); } export function getHostTokenUsage(hostId: string): Promise { return request( `/v1/hosts/${encodeURIComponent(hostId)}/token-usage`, ); } export function listHostTokenUsageEvents(hostId: string): Promise { return request( `/v1/hosts/${encodeURIComponent(hostId)}/token-usage-events?limit=20&offset=0`, ); } export function listTasks(options?: { status?: TaskStatus; limit?: number; offset?: number; }): Promise { const params = new URLSearchParams(); if (options?.status) params.set("status", options.status); params.set("limit", String(options?.limit ?? 50)); params.set("offset", String(options?.offset ?? 0)); const query = params.toString(); return request(`/v1/tasks${query ? `?${query}` : ""}`); } export function getTaskAttempts(taskId: string): Promise { return request(`/v1/tasks/${encodeURIComponent(taskId)}/attempts`); } export function getTaskPlannerDecisions( taskId: string, attempt: number, ): Promise { return request( `/v1/tasks/${encodeURIComponent(taskId)}/planner-decisions?attempt=${attempt}`, ).then((resp) => resp.items); } export function submitTask(payload: TaskSubmissionPayload): Promise<{ task_id: string }> { return request<{ task_id: string }>("/v1/tasks", { method: "POST", body: JSON.stringify(payload), }); } export function listDevices(): Promise { return request("/v1/devices"); } export function listHosts(): Promise { return request("/v1/hosts"); } export function listPlugins(): Promise { return request("/v1/plugins"); } export function registerPlugin(payload: PluginRegistrationPayload): Promise { return request("/v1/plugins", { method: "POST", body: JSON.stringify(payload), }); } export function listLlmProviderProfiles(): Promise { return request("/v1/planner/providers"); } export function createLlmProviderProfile(payload: { name: string; provider_type: LlmProviderType; model: string; base_url?: string | null; timeout_seconds: number; api_key: string; }): Promise { return request("/v1/planner/providers", { method: "POST", body: JSON.stringify(payload), }); } export function updateLlmProviderProfile( profileId: string, payload: { name?: string; provider_type?: LlmProviderType; model?: string; base_url?: string | null; timeout_seconds?: number; enabled?: boolean; api_key?: string; expected_revision?: number; }, ): Promise { return request(`/v1/planner/providers/${encodeURIComponent(profileId)}`, { method: "PATCH", body: JSON.stringify(payload), }); } export function activateLlmProviderProfile( profileId: string, expectedSettingsRevision: number, ): Promise { return request( `/v1/planner/providers/${encodeURIComponent(profileId)}/activate`, { method: "POST", body: JSON.stringify({ expected_settings_revision: expectedSettingsRevision }), }, ); } export function deleteLlmProviderProfile( profileId: string, expectedRevision: number, ): Promise { return request( `/v1/planner/providers/${encodeURIComponent(profileId)}?expected_revision=${expectedRevision}`, { method: "DELETE" }, ); } export interface CloudSkillPayload { name: string; kind: CloudSkillKind; description: string; tags: string[]; content: string; steps: Record[]; parameters: Record>; } export function listCloudSkills(): Promise { return request("/v1/skills"); } export function createCloudSkill(payload: CloudSkillPayload): Promise { return request("/v1/skills", { method: "POST", body: JSON.stringify(payload), }); } export function updateCloudSkill( skillId: string, payload: CloudSkillPayload, ): Promise { return request(`/v1/skills/${encodeURIComponent(skillId)}`, { method: "PATCH", body: JSON.stringify(payload), }); } export function deleteCloudSkill(skillId: string): Promise { return request(`/v1/skills/${encodeURIComponent(skillId)}`, { method: "DELETE", }); } export function listCloudSkillEntitlements( skillId: string, ): Promise { return request( `/v1/skills/${encodeURIComponent(skillId)}/entitlements`, ); } export function grantCloudSkillEntitlement( skillId: string, hostId: string, ): Promise { return request( `/v1/skills/${encodeURIComponent(skillId)}/entitlements/${encodeURIComponent(hostId)}`, { method: "POST" }, ); } export function revokeCloudSkillEntitlement( skillId: string, hostId: string, ): Promise { return request( `/v1/skills/${encodeURIComponent(skillId)}/entitlements/${encodeURIComponent(hostId)}`, { method: "DELETE" }, ); } export function getHostSkillInventory( hostId: string, ): Promise { return request( `/v1/hosts/${encodeURIComponent(hostId)}/skill-inventory`, ); }