import type { FnMap } from '@astrale-os/kernel-client' import type { BoundClientSessionView } from '@astrale-os/kernel-client/session' import type { UnresolvedIdentityExpr } from '@astrale-os/kernel-core' import { createUnresolvedGrant } from '@astrale-os/kernel-core' import type { RemoteIdentityConfig } from './identity.js' import { buildSelfExpr, buildSelfGrant } from './compose.js' import { bindCredentialSession } from './kernel-client.js' import { signCredential } from './sign.js' export type FunctionCredentialOptions = { /** Credential audience. Defaults to the current/default kernel URL when known. */ audience?: string /** Grant carried by the top-level kernel bearer. Defaults to self. */ grant?: UnresolvedIdentityExpr /** JWT lifetime, jose-style. Default is the SDK per-request kernel TTL. */ ttl?: string } export type FunctionKernelOptions = FunctionCredentialOptions & { /** Kernel URL to dial. Defaults to `audience`, then the current/default kernel URL. */ url?: string /** Next-hop delegation for remote redirects. Defaults to the credential grant. */ delegation?: UnresolvedIdentityExpr } export type FunctionContextApi = { readonly ref: string credential(opts?: FunctionCredentialOptions): Promise kernel(opts?: FunctionKernelOptions): Promise> } export function makeFunctionContext( identity: RemoteIdentityConfig, deps: unknown, opts: { ref: string; defaultKernelUrl?: string }, ): FunctionContextApi { return { ref: opts.ref, credential: (params) => signFunctionCredential( identity, resolveAudience(params?.audience, opts.defaultKernelUrl, deps), { grant: params?.grant, ttl: params?.ttl, }, ), kernel: async (params) => { const kernelUrl = resolveKernelUrl(params?.url, params?.audience, opts.defaultKernelUrl, deps) const grant = params?.grant ?? buildSelfExpr() const credential = await signFunctionCredential(identity, params?.audience ?? kernelUrl, { grant, ttl: params?.ttl, }) return bindCredentialSession(kernelUrl, credential, { delegation: params?.delegation ?? grant, }) }, } } async function signFunctionCredential( identity: RemoteIdentityConfig, audience: string, opts: { grant?: UnresolvedIdentityExpr; ttl?: string }, ): Promise { const grant = opts.grant === undefined ? buildSelfGrant().grant : createUnresolvedGrant(opts.grant) return signCredential( { grant }, { issuer: identity.issuer, subject: identity.subject, audience, privateKey: identity.privateKey, ttl: opts.ttl ?? '30m', }, ) } function resolveAudience( audience: string | undefined, defaultKernelUrl: string | undefined, deps: unknown, ): string { return resolveKernelUrl(undefined, audience, defaultKernelUrl, deps) } function resolveKernelUrl( url: string | undefined, audience: string | undefined, defaultKernelUrl: string | undefined, deps: unknown, ): string { const env = deps as { INSTANCE_KERNEL_URL?: unknown; KERNEL_URL?: unknown } | null | undefined const resolved = url ?? audience ?? defaultKernelUrl ?? env?.INSTANCE_KERNEL_URL ?? env?.KERNEL_URL if (typeof resolved !== 'string' || resolved.length === 0) { throw new Error( 'fn.kernel: no kernel URL — pass one explicitly or set INSTANCE_KERNEL_URL in the ' + 'worker env (managed deploys set it automatically).', ) } return resolved }