export interface HiveCastProviderConfig { readonly cloud: string; readonly apiKey: string; readonly space: string; readonly profile?: string; readonly fetch?: typeof fetch; } export interface HiveCastActorCredentialRequest { readonly mount: string; readonly actorId?: string; readonly packageId?: string; readonly runtimeId?: string; readonly accepts?: Record; readonly emits?: Record; readonly subscribes?: Record; readonly ttlSeconds?: number; } export interface HiveCastCredentialPair { readonly jwt: string; readonly seed: string; readonly userPublicKey?: string; readonly expiresAt?: string; } export interface HiveCastTransportInfo { readonly root: string; readonly natsUrl?: string; readonly natsWsUrl?: string; readonly leafnodeUrl?: string; } export interface HiveCastActorCredential { readonly ok: true; readonly cloud: string; readonly space: string; readonly root: string; readonly mount: string; readonly effectiveMount?: string; readonly transport: HiveCastTransportInfo; readonly credentials: HiveCastCredentialPair; readonly key?: Record; readonly source?: string; readonly credentialPipeline?: string; } export interface HiveCastKeyInfo { readonly ok: true; readonly cloud: string; readonly space: string; readonly key?: Record; readonly principal?: Record; readonly profile?: Record; } function requiredString(value: unknown, label: string): string { if (typeof value !== 'string' || !value.trim()) { throw new Error(`HiveCast response missing ${label}`); } return value.trim(); } function optionalString(value: unknown): string | undefined { return typeof value === 'string' && value.trim() ? value.trim() : undefined; } function record(value: unknown): Record { return typeof value === 'object' && value !== null && !Array.isArray(value) ? value as Record : {}; } function normalizeCloud(cloud: string): string { const normalized = cloud.trim().replace(/\/+$/g, ''); if (!normalized) throw new Error('HiveCast cloud URL is required'); return normalized; } async function postHiveCastJson( config: HiveCastProviderConfig, path: string, body: Record, ): Promise> { const cloud = normalizeCloud(config.cloud); const fetchImpl = config.fetch ?? globalThis.fetch; if (typeof fetchImpl !== 'function') { throw new Error('HiveCast provider requires fetch'); } const response = await fetchImpl(`${cloud}${path}`, { method: 'POST', headers: { authorization: `Bearer ${config.apiKey}`, 'content-type': 'application/json', }, body: JSON.stringify(body), }); const text = await response.text(); const parsed = text.trim() ? record(JSON.parse(text)) : {}; if (!response.ok || parsed.ok !== true) { const error = optionalString(parsed.error) ?? `HTTP_${response.status}`; throw new Error(`HiveCast ${path} failed: ${error}`); } return parsed; } export async function validateHiveCastApiKey(config: HiveCastProviderConfig): Promise { const cloud = normalizeCloud(config.cloud); const body = await postHiveCastJson(config, '/api/install/key-info', { space: config.space, ...(config.profile ? { profile: config.profile } : {}), }); return { ok: true, cloud, space: config.space, key: record(body.key), principal: record(body.principal), profile: record(body.profile), }; } export async function exchangeHiveCastActorCredential( config: HiveCastProviderConfig, request: HiveCastActorCredentialRequest, ): Promise { const cloud = normalizeCloud(config.cloud); if (!config.apiKey.trim()) throw new Error('HiveCast API key is required'); if (!config.space.trim()) throw new Error('HiveCast space is required'); if (!request.mount.trim()) throw new Error('HiveCast actor credential exchange requires mount'); const body = await postHiveCastJson(config, '/api/install/actor-credential', { space: config.space, mount: request.mount, ...(config.profile ? { profile: config.profile } : {}), ...(request.actorId ? { actorId: request.actorId } : {}), ...(request.packageId ? { packageId: request.packageId } : {}), ...(request.runtimeId ? { runtimeId: request.runtimeId } : {}), ...(request.accepts ? { accepts: request.accepts } : {}), ...(request.emits ? { emits: request.emits } : {}), ...(request.subscribes ? { subscribes: request.subscribes } : {}), ...(request.ttlSeconds ? { ttlSeconds: request.ttlSeconds } : {}), }); const transport = record(body.transport); const credentials = record(body.credentials); const space = record(body.space); const root = optionalString(transport.root) ?? optionalString(space.authorityRoot) ?? optionalString(body.authorityRoot) ?? optionalString(body.root) ?? config.space; return { ok: true, cloud, space: config.space, root, mount: request.mount, effectiveMount: optionalString(body.effectiveMount), transport: { root, natsUrl: optionalString(transport.natsUrl), natsWsUrl: optionalString(transport.natsWsUrl), leafnodeUrl: optionalString(transport.leafnodeUrl), }, credentials: { jwt: requiredString(credentials.jwt, 'credentials.jwt'), seed: requiredString(credentials.seed, 'credentials.seed'), userPublicKey: optionalString(credentials.userPublicKey), expiresAt: optionalString(credentials.expiresAt), }, key: record(body.key), source: optionalString(body.source), credentialPipeline: optionalString(body.credentialPipeline), }; }