49 lines
2.6 KiB
TypeScript
Raw Normal View History

/** Same-origin endpoint descriptors for the future HUX-05 backend. */
import { ACTIVITY_TIMELINE_FLAG, AUTONOMY_FLAG, FOUNDATION_FLAG } from "./model.ts";
import { isOpaqueId, normalizeIdentity, sameIdentity } from "./security.ts";
import type { AutonomyEndpoint, AutonomyEndpointContract, HuxIdentity } from "./types.ts";
interface FoundationClient {
apiVersion: string;
identity: HuxIdentity;
enabled(flag: string): boolean;
endpoint(path: string): string;
}
function endpoint(method: AutonomyEndpoint["method"], path: string,
responseSchema: string, requiredBody?: readonly string[]): AutonomyEndpoint {
return Object.freeze({method, path, responseSchema, ...(requiredBody ? {requiredBody} : {})});
}
/** Describe, but never call, the tenant-scoped HUX-05 interface. */
export function createAutonomyEndpointContract(client: FoundationClient): AutonomyEndpointContract | null {
if (client.apiVersion !== "hux.v1" ||
![FOUNDATION_FLAG, ACTIVITY_TIMELINE_FLAG, AUTONOMY_FLAG].every((flag) => client.enabled(flag))) return null;
const identity = normalizeIdentity(client.identity);
if (!identity || !sameIdentity(identity, client.identity)) throw new TypeError("Scoped HUX identity required");
const scoped = (root: string, id: string, prefix: string): string => {
if (!isOpaqueId(id, prefix)) throw new TypeError(`Opaque ${prefix} id required`);
return client.endpoint(`${root}/${encodeURIComponent(id)}`);
};
return Object.freeze({
identity: Object.freeze({...identity}),
policy: endpoint("GET", client.endpoint("/autonomy/policy"), "hux.policy.v1"),
updatePolicy: endpoint("PUT", client.endpoint("/autonomy/policy"), "hux.policy.v1",
["expected_policy_id", "expected_updated_at", "autonomy", "grants", "budgets", "guardrails"]),
approvals: endpoint("GET", client.endpoint("/approvals?status=pending&limit=100"), "hux.approval_page.v1"),
decideApproval(approvalId: string) {
return endpoint("POST", `${scoped("/approvals", approvalId, "apr")}/decision`, "hux.approval.v1",
["expected_run_id", "expected_conversation_id", "expected_capability", "expected_requested_at", "choice"]);
},
stop(runId: string) {
if (!/^run_[A-Za-z0-9._-]{1,116}$/.test(runId)) throw new TypeError("Exact run id required");
return endpoint("POST", client.endpoint(`/runs/${encodeURIComponent(runId)}/stop`),
"hux.cancel_receipt.v1", ["conversation_id"]);
},
release: Object.freeze({schema: "hux.release.v1" as const,
requiredState: "live_verified" as const,
featureFlags: Object.freeze([FOUNDATION_FLAG, ACTIVITY_TIMELINE_FLAG, AUTONOMY_FLAG])}),
});
}