62 lines
3.1 KiB
TypeScript
62 lines
3.1 KiB
TypeScript
|
|
/** 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"]);
|
||
|
|
},
|
||
|
|
});
|
||
|
|
}
|