/** Same-origin endpoint descriptors for the future HUX-07 backend. */ import type { ArtifactScope, HuxIdentity } from "../artifacts/types.ts"; import { multimodalEnabled } from "./model.ts"; import { normalizeIdentity, normalizeScope, safeEndpointPart, sameIdentity, sameScope } from "./security.ts"; import type { CaptureKind, MultimodalEndpoint, MultimodalEndpointContract, } from "./types.ts"; interface FoundationClient { apiVersion: string; identity: HuxIdentity; endpoint(path: string): string; } function endpoint(method: MultimodalEndpoint["method"], path: string, responseSchema: string, requiredBody?: readonly string[]): MultimodalEndpoint { return Object.freeze({method, path, responseSchema, ...(requiredBody ? {requiredBody} : {})}); } /** Describe endpoints only. The caller owns authenticated transport and approval execution. */ export function createMultimodalEndpointContract(client: FoundationClient, scope: ArtifactScope, flags?: Iterable): MultimodalEndpointContract | null { if (!multimodalEnabled(flags)) return null; if (client.apiVersion !== "hux.v1" || !sameIdentity(normalizeIdentity(client.identity), client.identity) || !sameScope(normalizeScope(scope), scope)) throw new TypeError("Scoped HUX v1 client required"); const base = client.endpoint(`/projects/${safeEndpointPart(scope.projectId, "prj")}` + `/conversations/${safeEndpointPart(scope.conversationId, "conv")}/multimodal`); const child = (root: string, id: unknown, prefix: string, suffix = "") => `${base}/${root}/${safeEndpointPart(id, prefix)}${suffix}`; return Object.freeze({ page: endpoint("GET", base, "hux.multimodal_page.v1"), beginUpload: endpoint("POST", `${base}/uploads`, "hux.media_upload.v1", ["file_name", "mime", "bytes", "authorization_id"]), finalizeUpload(uploadId) { return endpoint("POST", child("uploads", uploadId, "upl", "/finalize"), "hux.media.v1", ["content_ref", "expected_upload_id"]); }, editImage(mediaId) { return endpoint("POST", child("media", mediaId, "med", "/edits"), "hux.media.v1", ["artifact_id", "expected_current_version", "instruction", "lineage"]); }, annotate(mediaId) { return endpoint("POST", child("media", mediaId, "med", "/annotations"), "hux.media.v1", ["artifact_id", "expected_current_version", "page", "note"]); }, correctTranscript(transcriptId) { return endpoint("POST", child("transcripts", transcriptId, "trn", "/corrections"), "hux.voice_transcript.v1", ["turn_id", "expected_revision", "corrected_text"]); }, requestCapture(kind: CaptureKind) { if (!['camera', 'screen'].includes(kind)) throw new TypeError("Capture kind is invalid"); return endpoint("POST", `${base}/captures/${kind}/approvals`, "hux.approval.v1", ["capability", "required_decision", "conversation_id"]); }, createVariant(threadId) { return endpoint("POST", child("threads", threadId, "thr", "/variants"), "hux.generation_thread.v1", ["media_id", "artifact_id", "artifact_version", "parent_variant_id"]); }, }); }