Skip to content

Conversation and send lifecycle

Conversation normalization and send lifecycle

Section titled “Conversation normalization and send lifecycle”

Use @alneas/ai-core/ai-chat (or its /conversation and /session subpaths) without React, an AI SDK, storage, or a provider SDK:

import { normalizeAIChatMessages, streamAIChat } from '@alneas/ai-core/ai-chat';
const messages = normalizeAIChatMessages([
{ id: 'user-1', role: 'user', parts: [{ type: 'text', text: 'Hello' }] },
]);
for await (const event of streamAIChat(adapter, { messages, signal })) {
// Render or route normalized events in the consuming application.
}

adapter is an AIChatAdapter, for example createOpenAIChatAdapter or createVercelAIChatAdapter, or an injected implementation. signal is an optional AbortSignal. The Vercel adapter parses its HTTP protocol; it does not import the ai npm SDK. Credentials and endpoint configuration belong to the application.

History normalization preserves order, nonempty IDs, roles (system/user/assistant/ tool), text/file/tool-call/tool-result/data parts, and JSON metadata. Tool-role messages contain only tool results. It does not invent messages, trim history, match tool call/result pairs globally, authorize tools or execute them. A missing required field or non-JSON value fails before the adapter is started. Unknown JSON properties are preserved. URLs/media types are checked as nonempty strings, not as safe navigation targets; enforce file/content policies in the application.

The result is detached: a new outer array containing recursively frozen snapshots. The entire history is bounded by depth 64, 10,000 visited values and 1,000,000 serialized JavaScript UTF-16 code units. Accessors, cycles, undefined properties, non-finite numbers and non-JSON objects are rejected. No silent truncation occurs. Stream events undergo the same JSON boundary plus event-shape validation.

createAIChatStream(adapter, request) snapshots the request immediately and starts the adapter lazily on read. streamAIChat is the async-iterable convenience form; it snapshots on first iteration. Tools and parameters are detached snapshots too. A stream must emit an explicit finish, then EOF. Missing finish and events after finish reject. maxResponseChars defaults to 1,000,000, configurable from 1 through 10,000,000; it counts the sum of serialized normalized events, not wire bytes. Each event also has the JSON boundary limits. HTTP adapters separately bound the received protocol. Configure network/request limits at the transport/server too.

Parent abort, reader cancellation, validation failures and early iteration return abort the supplied adapter signal and request iterator cleanup. Pending reads settle promptly even if the source ignores cancellation. An uncooperative iterator’s return() is requested without awaiting a blocked next(); it can finish later. Adapters must honor AbortSignal to actually release network work. A consumer must drain or cancel its stream. Streams are not resumable and do not retry, persist messages, subscribe to browser events or execute tools automatically.

For AI SDK 7 UIMessage/ChatTransport, use the optional @alneas/react-ai/ai-chat binding. SDK shape mapping belongs there; generic validation and send lifecycle remain here. Browser persistence remains in the UI.