import { fetchJson, type ApiRequestOptions } from "../client"; export type CanvasScopeKind = "task" | "workspace"; export type CanvasStatus = "pending" | "active" | "disabled" | "archived" | "error" | "removed"; export type CanvasReleaseStatus = "valid" | "pending_permission" | "invalid" | "unavailable"; export type CanvasRelease = { id: string; package_id?: string; version?: string; display_name?: string; description?: string; author?: string; license?: string; source_mode?: "static" | "project" | string; min_kandev_version?: string; repo_url?: string; package_digest?: string; validation_status: CanvasReleaseStatus | string; validation_error?: string; permissions?: CanvasPermissionReview; missing_permissions?: string[]; permission_digest?: string; source_actor_kind?: string; source_user_id?: string; source_task_id?: string; source_task_title?: string; source_session_id?: string; source_session_name?: string; protocol_version?: number; created_at?: string; }; export type Canvas = { id: string; plugin_instance_id: string; plugin_id: string; workspace_id: string; task_id?: string; origin_task_id?: string; created_by_session_id?: string; scope_kind: CanvasScopeKind | string; data_scope_kind?: CanvasScopeKind | string; title: string; status: CanvasStatus | string; active_release_id?: string; active_release_status?: CanvasReleaseStatus | string; active_release_error?: string; grant_generation?: number; effective_grants?: CanvasGrantProjection[]; active_release?: CanvasRelease; pending_release?: CanvasRelease; created_at?: string; updated_at?: string; }; export function canvasDataScope(canvas: Pick): string { return canvas.data_scope_kind || canvas.scope_kind; } export function canvasCanEnableWorkspaceData(canvas: Canvas): boolean { return ( canvas.scope_kind === "task" && canvasDataScope(canvas) === "task" && canvas.status === "active" && canvas.active_release_status === "valid" ); } export type CanvasListResponse = { canvases: Canvas[]; total?: number; }; export type CanvasRuntimeResponse = { runtime_url?: string | null; release_id?: string; active_release_id?: string; web_app_key?: string; placement?: string; expires_in_seconds?: number; canvas?: Canvas; binding?: { scope_kind?: CanvasScopeKind | string; data_scope_kind?: CanvasScopeKind | string; grant_generation?: number; }; }; export type CanvasPermissionReview = { reads?: string[]; writes?: string[]; events?: string[]; shared_state?: boolean; external_origins?: string[]; }; export type CanvasGrantProjection = { permission_kind: string; resource?: string; network_origin?: string; scope_ceiling: string; }; export type CanvasPromotionPreview = { canvas_id: string; title?: string; origin_task_id?: string; source_actor_kind?: string; source_user_id?: string; source_task_id?: string; source_task_title?: string; source_session_id?: string; source_session_name?: string; active_release?: CanvasRelease; permissions?: CanvasPermissionReview; active_release_id?: string; permission_digest?: string; grant_generation?: number; current_scope?: CanvasScopeKind | string; target_scope?: CanvasScopeKind | string; current_data_scope_kind?: CanvasScopeKind | string; target_data_scope_kind?: CanvasScopeKind | string; placement?: string; }; export type CanvasWorkspaceDataPreview = { canvas: Canvas; active_release_id: string; permission_digest: string; grant_generation: number; current_data_scope_kind: CanvasScopeKind | string; target_data_scope_kind: CanvasScopeKind | string; permissions?: CanvasPermissionReview; }; export type CanvasReleaseListResponse = { releases: CanvasRelease[]; }; export type CanvasEditResponse = { task_id?: string; session_id?: string; canvas_id: string; }; export type CanvasRequestOptions = ApiRequestOptions & { includeArchived?: boolean; }; export function canvasHref(canvasId: string): string { return `/canvases/${encodeURIComponent(canvasId)}`; } export function workspaceCanvasSettingsHref(workspaceId: string): string { return `/settings/workspaces/${encodeURIComponent(workspaceId)}/canvases`; } function canvasPath(canvasId: string, suffix = ""): string { return `/api/v1/canvases/${encodeURIComponent(canvasId)}${suffix}`; } function withArchived(path: string, includeArchived?: boolean): string { return includeArchived ? `${path}${path.includes("?") ? "&" : "?"}include_archived=true` : path; } export function listWorkspaceCanvases( workspaceId: string, options?: CanvasRequestOptions, ): Promise { const { includeArchived, ...requestOptions } = options ?? {}; return fetchJson( withArchived(`/api/v1/workspaces/${encodeURIComponent(workspaceId)}/canvases`, includeArchived), requestOptions, ); } export function listTaskCanvases( taskId: string, options?: CanvasRequestOptions & { workspaceId?: string }, ): Promise { const { includeArchived, workspaceId, ...requestOptions } = options ?? {}; const query = workspaceId ? `?workspace_id=${encodeURIComponent(workspaceId)}` : ""; return fetchJson( withArchived(`/api/v1/tasks/${encodeURIComponent(taskId)}/canvases${query}`, includeArchived), requestOptions, ); } export function getCanvas(canvasId: string, options?: ApiRequestOptions): Promise { return fetchJson(canvasPath(canvasId), options); } export function renameCanvas( canvasId: string, title: string, options?: ApiRequestOptions, ): Promise { return mutate(canvasPath(canvasId), "PATCH", { title }, options); } export function getCanvasRuntime( canvasId: string, options?: ApiRequestOptions, ): Promise { return fetchJson(canvasPath(canvasId, "/runtime"), options); } export function requestCanvasPromotion( canvasId: string, options?: ApiRequestOptions, ): Promise { return fetchJson(canvasPath(canvasId, "/promotion-preview"), options); } export function confirmCanvasPromotion( canvasId: string, review: { expected_release_id: string; expected_permission_digest: string; expected_grant_generation: number; }, options?: ApiRequestOptions, ): Promise { return mutate(canvasPath(canvasId, "/promotion"), "POST", review, options); } export function requestCanvasWorkspaceData( canvasId: string, options?: ApiRequestOptions, ): Promise { return fetchJson( canvasPath(canvasId, "/workspace-data-preview"), options, ); } export function enableCanvasWorkspaceData( canvasId: string, review: { expected_release_id: string; expected_permission_digest: string; expected_grant_generation: number; }, options?: ApiRequestOptions, ): Promise { return mutate(canvasPath(canvasId, "/workspace-data"), "POST", review, options); } export function listCanvasReleases( canvasId: string, options?: ApiRequestOptions, ): Promise { return fetchJson(canvasPath(canvasId, "/releases"), options); } export function approveCanvasRelease( canvasId: string, releaseId: string, options?: ApiRequestOptions, ): Promise { return mutate( canvasPath(canvasId, `/releases/${encodeURIComponent(releaseId)}/approve`), "POST", undefined, options, ); } export function rejectCanvasRelease( canvasId: string, releaseId: string, options?: ApiRequestOptions, ): Promise { return mutate( canvasPath(canvasId, `/releases/${encodeURIComponent(releaseId)}/reject`), "POST", undefined, options, ); } export function rollbackCanvas( canvasId: string, releaseId?: string, options?: ApiRequestOptions, ): Promise { return mutate( canvasPath(canvasId, "/rollback"), "POST", { ...(releaseId ? { release_id: releaseId } : {}), }, options, ); } export function startCanvasEdit( canvasId: string, options?: ApiRequestOptions, ): Promise { return mutate(canvasPath(canvasId, "/edit"), "POST", undefined, options); } export function archiveCanvas(canvasId: string, options?: ApiRequestOptions): Promise { return mutate(canvasPath(canvasId, "/archive"), "POST", undefined, options); } export function restoreCanvas(canvasId: string, options?: ApiRequestOptions): Promise { return mutate(canvasPath(canvasId, "/restore"), "POST", undefined, options); } export function removeCanvas(canvasId: string, options?: ApiRequestOptions): Promise { return mutate(canvasPath(canvasId), "DELETE", undefined, options); } function mutate( path: string, method: "POST" | "PATCH" | "DELETE", body: unknown, options?: ApiRequestOptions, ): Promise { return fetchJson(path, { ...options, init: { ...(options?.init ?? {}), method, ...(body === undefined ? {} : { body: JSON.stringify(body) }), }, }); }