/** Same-origin endpoint descriptors for the future HUX-08 backend. */ import { researchEnabled } from "./model.ts"; import { normalizeIdentity, normalizeScope, safeMessageId, safePathId, sameIdentity, sameScope } from "./security.ts"; import type { HuxIdentity, ResearchEndpoint, ResearchEndpointContract, ResearchScope } from "./types.ts"; interface FoundationClient { apiVersion: string; identity: HuxIdentity; endpoint(path: string): string; } function endpoint(method: ResearchEndpoint["method"], path: string, responseSchema: string, requiredBody?: readonly string[]): ResearchEndpoint { return Object.freeze({method, path, responseSchema, ...(requiredBody ? {requiredBody} : {})}); } /** Describes authenticated endpoints without issuing network requests. */ export function createResearchEndpointContract(client: FoundationClient, scope: ResearchScope, flags?: Iterable): ResearchEndpointContract | null { if (!researchEnabled(flags)) return null; const rawIdentity = {tenant_ref: client.identity.tenantRef, user_ref: client.identity.userRef, surface: client.identity.surface}; const rawScope = {project_id: scope.projectId, conversation_id: scope.conversationId}; if (client.apiVersion !== "hux.v1" || !sameIdentity(normalizeIdentity(rawIdentity), client.identity) || !sameScope(normalizeScope(rawScope), scope)) throw new TypeError("Scoped HUX v1 research client required"); const base = client.endpoint(`/projects/${safePathId(scope.projectId, "prj")}` + `/conversations/${safePathId(scope.conversationId, "conv")}/research`); return Object.freeze({ page: endpoint("GET", base, "hux.research_page.v1"), citationsForMessage(messageId) { return endpoint("GET", `${base}/messages/${safeMessageId(messageId)}/citations`, "hux.citation_page.v1"); }, notebook(notebookId) { return endpoint("GET", `${base}/notebooks/${safePathId(notebookId, "nb")}`, "hux.research_notebook.v1"); }, updateNotebook(notebookId) { return endpoint("PUT", `${base}/notebooks/${safePathId(notebookId, "nb")}`, "hux.research_notebook.v1", ["expected_updated_at", "status", "assumptions", "unresolved_questions"]); }, attachCitation(messageId) { return endpoint("POST", `${base}/messages/${safeMessageId(messageId)}/citations`, "hux.citation.v1", ["claim", "passage_ids", "support", "note"]); }, }); }