Interface RealtimeOpenOptionsExperimental

Options the KERNEL reads, accepted alongside whatever an extension declares.

Separate from an extension's own Options because they belong to different owners: the extension defines its product inputs, the kernel defines cancellation and reporting. Declaring them here is what makes them reachable through the typed open(extension, options) overload, which otherwise types the options bag as the extension's alone and rejects every kernel option at the call site.

An extension with NO options of its own must declare Record<never, never>, not Record<string, never>. The latter asserts that every string key maps to never, which makes Options & RealtimeOpenOptions contradictory and rejects onMedia, onState and onDiagnostic at the call site — leaving an extension that takes no product inputs unable to receive any kernel option at all.

The fal.realtime.open() extension API is experimental and may change in a minor release.

interface RealtimeOpenOptions {
    abortSignal?: AbortSignal;
    endpointId?: string;
    onData?: ((raw: string) => void);
    onDiagnostic?: ((event: RealtimeDiagnostic) => void);
    onError?: ((error: unknown) => void);
    onMedia?: ((stream: MediaStream) => void);
    onState?: ((state: RealtimeState) => void);
}

Properties

abortSignal?: AbortSignal

Cancels opening, and closes the session if it is already open.

endpointId?: string

The endpoint to open, when the extension's default is not the one wanted. Declared HERE because the KERNEL reads it — an extension never has to redeclare it, and a third-party extension that forgets to would otherwise strand its callers unable to select an endpoint.

onData?: ((raw: string) => void)

Inbound application data, once per message.

Deliberately a string rather than a parsed object: the kernel cannot know a model's schema, and pretending otherwise would put one protocol's vocabulary in the transport. The extension delivers; the application parses and validates.

Declared for every extension rather than only the ones that obviously need it, because "media up, data down" is a shape any of them can take, and an extension whose surface omits this cannot express it even when its protocol allows it.

onDiagnostic?: ((event: RealtimeDiagnostic) => void)

Progress and failure reports. See RealtimeDiagnostic.

onError?: ((error: unknown) => void)

The terminal FAILURE, delivered once: the error that failed opening, or the transport failure an extension reported through context.fail(). A caller's own cancellation — close() or an aborted abortSignal — is an orderly ending, not a failure: it reaches onState("closed") and rejects ready, and deliberately never fires this.

This is the failure channel for the synchronous open() shape — there is no returned promise whose rejection could carry it, and requiring every caller to attach to ready would turn an optional convenience into an obligation.

onMedia?: ((stream: MediaStream) => void)

Inbound media, once per stream, for any extension that returns some.

Named HERE rather than per extension because "a remote stream arrived" is one concept, and left to the extensions it acquires one name per protocol — every one of them defensible, none of them shared. An application offering two extensions then needs a branch to do the single thing it actually wants, which is attach a video element. Same tax onState removes for lifecycle.

Not every extension calls it. An app can send a camera up and get its answer back as data with no inbound media at all, which is why this is optional on both sides rather than part of opening.

onState?: ((state: RealtimeState) => void)

Coarse lifecycle transitions, uniform across every extension.