62 lines
3.1 KiB
TypeScript
Raw Normal View History

/** 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<string>): 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"]);
},
});
}