generative-a11y

@generative-a11y/ai-sdk

Turn documented AI SDK useChat state and callbacks into generative-a11y events.

Start here

For an existing compatible React + AI SDK app:

Terminal
npm install @generative-a11y/react @generative-a11y/ai-sdk

See the integration guide for peer versions, provider placement, preserved host options, and the preconstructed Chat case. The root observer alone does not provide browser delivery.

For React, start with useChatAccessibility. For a custom state bridge, create an observer and install its composed callbacks when constructing your chat:

Observer setup
import {
  createChatObserver,
  composeChatCallbacks,
} from "@generative-a11y/ai-sdk";

const observer = createChatObserver({ runtime, scopeId: "support" });
const chatCallbacks = composeChatCallbacks({ observer });

Pass public snapshots to observer.observe(snapshot) as they change. Dispose the observer when its owner ends. The non-React reference below includes callback contracts and a complete deterministic example.

Public APIs used

AI SDK adapters read documented messages, status, and error state. onFinish and onError report final response state.

SurfaceExportsUse
Framework-neutralcreateChatObserver, composeChatCallbacksCustom subscription or state bridge
ReactuseChatAccessibility, useObserveChatAccessibilityAI SDK useChat components
MetadataadapterInfoSupported events and framework APIs

Runtime and app ownership

Your app owns the core runtime. Adapters report events without running chat actions or reading rendered messages and private AI SDK fields.

  • scopeId separates response and message IDs across chat instances.
  • Tool arguments do not mean that execution started.
  • Your onFinish and onError callbacks remain intact.
  • Disposing the observer clears only records created by the adapter.

Localized announcements

See catalogs and adapter copy for the typed messages and copy options, language ownership, validation, construction lifetime, and replay requirements. Existing lifecycle evidence and timing remain unchanged.

Public declarations: @generative-a11y/ai-sdk

All exported values and types for this entry point. Import these names from @generative-a11y/ai-sdk; the signatures below are reference material, not a replacement for the ownership and lifecycle guidance above.

adapterInfo — value
declare const adapterInfo: AdapterInfo;
AdapterInfo — type
interface AdapterInfo {
    readonly name: "ai-sdk";
    readonly fidelity: Readonly<Omit<AdapterFidelity, "optionalEvents">> & {
        readonly optionalEvents: readonly NonNullable<AdapterFidelity["optionalEvents"]>[number][];
    };
    readonly observedSnapshotFields: readonly [
        "messages",
        "status",
        "error"
    ];
    readonly terminalEvidence: readonly [
        "onFinish",
        "onError"
    ];
    readonly saturation: "suppress-after-baseline-capacity";
}
ChatFinishOutcome — type
interface ChatFinishOutcome {
    readonly isAbort: boolean;
    readonly isDisconnect: boolean;
    readonly isError: boolean;
}
ChatObserver — type
interface ChatObserver {
    observe(snapshot: ChatSnapshot): void;
    finish(message: UIMessage, outcome: ChatFinishOutcome): void;
    failActiveResponse(): void;
    dispose(): void;
}
ChatObserverOptions — type
interface ChatObserverOptions {
    /** Host-owned localized copy, captured and validated at construction. */
    readonly copy?: AdapterCopy;
    readonly runtime: Pick<Runtime, "dispatch">;
    readonly scopeId: string;
    /** Positive cap for each response, tool, approval, and source identity set. */
    readonly maxTrackedEntities?: number;
    /** Maps tool identity to localized host copy. The default is deliberately generic. */
    readonly getToolLabel?: (context: ToolLabelContext) => string;
}
ChatSnapshot — type
interface ChatSnapshot<UI_MESSAGE extends UIMessage = UIMessage> {
    readonly messages: UI_MESSAGE[];
    readonly status: "submitted" | "streaming" | "ready" | "error";
    readonly error: Error | undefined;
}
composeChatCallbacks — value
/** Composes host callbacks with the exact public AI SDK terminal evidence. */
declare function composeChatCallbacks<UI_MESSAGE extends UIMessage>(options: ComposeChatCallbacksOptions<UI_MESSAGE>): {
    onFinish: ChatOnFinishCallback<UI_MESSAGE>;
    onError: ChatOnErrorCallback;
};
ComposeChatCallbacksOptions — type
interface ComposeChatCallbacksOptions<UI_MESSAGE extends UIMessage = UIMessage> {
    readonly observer: ChatObserver;
    readonly onFinish?: ChatOnFinishCallback<UI_MESSAGE>;
    readonly onError?: ChatOnErrorCallback;
}
createChatObserver — value
/**
 * Creates a bounded, borrowed-runtime observer. The first valid snapshot
 * silently records history; it never interprets `ready` or `error` as a
 * response terminal state.
 */
declare function createChatObserver(options: ChatObserverOptions): ChatObserver;
ToolLabelContext — type
interface ToolLabelContext {
    readonly toolCallId: string;
    readonly toolName: string;
    readonly title: string | undefined;
}

Non-React observer API

For the observer example below, declare the packages it imports directly:

Terminal
npm install @generative-a11y/core @generative-a11y/ai-sdk "ai@~7.0.0"

The example records intents only. A live browser integration also needs a direct @generative-a11y/dom dependency when importing bindRuntime, a delivery binding, and cleanup of that binding before disposing the runtime.

createChatObserver(options) returns a ChatObserver and never owns the core runtime. ChatObserverOptions requires a non-empty scopeId and a runtime with dispatch. Optional copy comes from core/messages; getToolLabel receives ToolLabelContext (toolCallId, toolName, and optional-value title). Its safe label overrides the generic copy. maxTrackedEntities defaults to 1,000 and must be a positive safe integer.

MethodContract
observe(snapshot)Accepts ChatSnapshot: public messages, status, and error. The first valid snapshot establishes a silent baseline. Later append-only parts produce events; it does not subscribe to a framework for you.
finish(message, outcome)Accepts an SDK assistant UIMessage and ChatFinishOutcome with isAbort, isDisconnect, and isError. Disconnect emits connection loss rather than completion; error precedes abort, and ordinary finish completes the active response.
failActiveResponse()Reports failure for the last observed active response. No active response means no output; raw error text is never copied.
dispose()Clears observer state and makes later observations/callbacks inert. It does not dispose the runtime.

composeChatCallbacks({ observer, onFinish?, onError? }) accepts ComposeChatCallbacksOptions and returns the SDK-compatible callbacks. It calls the observer first, then the host callback in a finally block so host handling still runs if observer dispatch throws. Install these callbacks before creating the SDK chat; observe its public snapshots separately.

After identity capacity is exhausted, all later snapshot and terminal-callback lifecycle events are suppressed, including known identities. Create a fresh observer at an intentional session boundary. A non-prefix text rewrite is also suppressed for that part until its message ID changes. These conservative limits prevent guessed lifecycle events.

The following complete deterministic example exercises the public observer contract without a React component or backend. In a live application, use the SDK's real snapshots and callbacks, a browser delivery binding, and cleanup when the surface is removed. Do not recreate the adapter's private scoped IDs.

chat-accessibility.ts
import {
  createRuntime,
  ManualClock,
  type AnnouncementIntent,
} from "@generative-a11y/core";
import {
  createChatObserver,
  composeChatCallbacks,
} from "@generative-a11y/ai-sdk";
import type { UIMessage } from "ai";

// A deterministic demonstration of the non-React API, using public SDK data.
export const announcements: AnnouncementIntent[] = [];
const clock = new ManualClock();
const runtime = createRuntime({
  clock,
  onAnnouncement: (intent) => announcements.push(intent),
});
const observer = createChatObserver({ runtime, scopeId: "support" });
const callbacks = composeChatCallbacks({ observer });

// Supply the initial public snapshot first; historical content is not replayed.
observer.observe({ messages: [], status: "ready", error: undefined });
const message: UIMessage = {
  id: "assistant-1",
  role: "assistant",
  parts: [{ type: "text", text: "Your answer is ready." }],
};
observer.observe({
  messages: [message],
  status: "streaming",
  error: undefined,
});
// In an application, pass callbacks to the SDK when constructing its chat.
callbacks.onFinish({
  message,
  messages: [message],
  isAbort: false,
  isDisconnect: false,
  isError: false,
  finishReason: "stop",
});
clock.runUntilIdle();
observer.dispose();
runtime.dispose();