# Codec
A `Codec` turns your framework's events into Ably messages, and turns Ably messages back into events. A [transport](https://ably.com/docs/ai-transport/api/javascript/transport/transport.md) uses one codec for everything it sends, pipes and receives. The Vercel AI SDK, OpenAI and [AG-UI](https://ably.com/docs/ai-transport/api/javascript/ag-ui/codec.md) codecs ship with the SDK, and `defineCodec` builds a codec for any other event type.
A codec only defines the wire format. The transport owns the channel and delivers the decoded events.
#### Javascript
```
import { createTransport, defineCodec } from '@ably/ai-transport';
const chatCodec = defineCodec({
typeOf: (event) => event.type,
events: {
prompt: {
encode: (event) => ({ data: event.text }),
decode: ({ data }) => ({ type: 'prompt', text: String(data) }),
},
},
});
// ably is your Ably.Realtime client.
const transport = createTransport({ client: ably, channelName: 'conversations:42', codec: chatCodec });
```
## Properties
| Property | Description | Type |
| --- | --- | --- |
| adapterTag | Optional. An identifier the transport adds to its channel's `params.agent`, so Ably attributes the traffic to this codec. Omit it to add nothing. | String |
| encode | `(event: E) => EncodedMessage[]`. Encode one event to the Ably messages that carry it, in the order the transport writes them. An empty array publishes nothing. Throws for an event the codec does not know. | Function returning an array of |
| decode | `(message: Ably.InboundMessage) => E[]`. Decode one Ably message to the events it carries, in order. Returns an empty array for a message that belongs to another codec, or for a message that does not add any new content, such as one the codec has already seen. Throws for a message the codec recognises but cannot decode. | Function |
The transport handles a `decode` that throws: it reports the error on the transport's `error` event, and delivers the message with no event.
| Property | Description | Type |
| --- | --- | --- |
| message | The Ably message to write. Its `name`, `data` and `extras` belong to the codec. The transport adds its own fields under `extras.ai` to a message written under a key, and the call's headers under `extras.headers`. | `Ably.Message` |
| publish | Optional. Publish the message and remember the serial Ably returns under this key, so later messages can append to it or update it. Publishing under a key that is still in use throws. | String |
| append | Optional. Append `message.data` to the message remembered under this key. When no message is remembered under the key, publish the message and remember it. | String |
| update | Optional. Replace the content of the message remembered under this key. | String |
| ends | Optional. Forget this key once the message is written. | String |
A message with none of `publish`, `append` or `update` is a plain publish that nothing appends to. [`send()`](https://ably.com/docs/ai-transport/api/javascript/transport/transport.md#send) has no keys to append to, so it rejects an event the codec encodes as an append or an update. Use [`pipe()`](https://ably.com/docs/ai-transport/api/javascript/transport/transport.md#pipe) for streams.
## Build a codec
`defineCodec(config: DefineCodecConfig): Codec`
Build a codec from a table with one row for each event type. Each row has an `encode` function that returns the message body and where it goes, and a `decode` function that rebuilds the event from the body. A row produces at most one message and at most one event.
The builder writes the row's `data` as the message body, and the event type and the row's `fields` under `extras.ai`. It writes the row's `headers` under `extras.headers`. On decode it reads them back and picks the row by the event type.
An append only adds to a message's `data`, so a field that a streaming event needs on every delta goes in `fields`.
The builder also handles the three ways Ably delivers a streamed message. A live append carries only its new text. The first delivery after an attach carries the full content so far, as an update. A history read returns the whole message. The builder reduces all three to the text that delivery adds before the row's `decode` runs, and drops a message it has already seen, so history and live delivery can overlap without duplicates.
### Javascript
```
import { defineCodec } from '@ably/ai-transport';
const chatCodec = defineCodec({
adapterTag: 'my-chat',
typeOf: (event) => event.type,
events: {
prompt: {
encode: (event) => ({ data: event.text }),
decode: ({ data }) => ({ type: 'prompt', text: String(data) }),
},
'text-delta': {
encode: (event) => ({ data: event.delta, fields: { id: event.id }, append: event.id }),
decode: ({ data, fields }) => ({ type: 'text-delta', id: String(fields.id), delta: String(data) }),
},
'text-end': {
encode: (event) => ({ fields: { id: event.id }, ends: event.id }),
decode: ({ fields }) => ({ type: 'text-end', id: String(fields.id) }),
},
},
});
```
### Parameters
| Parameter | Required | Description | Type |
| --- | --- | --- | --- |
| config | required | The type reader and the table of rows. | |
| Property | Description | Type |
| --- | --- | --- |
| typeOf | `(event: E) => K`. Read an event's type. The table must have a row for each type it returns. | Function |
| events | One row for each event type. A key ending in `-*` is a wildcard that matches every type with that prefix, so `data-*` covers `data-weather`. An exact key takes priority over a wildcard. | `Record` of |
| name | Optional. The Ably message name for a message whose row sets none. Defaults to `ai`. | String |
| adapterTag | Optional. The codec's `adapterTag`. | String |
| logger | Optional. A logger for diagnostics. | `Logger` |
| Property | Description | Type |
| --- | --- | --- |
| encode | `(event) => EncodedRow \| undefined`. Build the body for one event and say where it goes, or return `undefined` to publish nothing for it. | |
| decode | `(row: DecodedRow) => E`. Rebuild the event from the body. | |
| Property | Description | Type |
| --- | --- | --- |
| data | Optional. The message body. For a message that is appended to, the text this event adds. For a plain publish, any value, an object included. Defaults to an empty string. | Any |
| fields | Optional. Event fields that travel beside `data` under `extras.ai.fields`, nested values included. | `Record` |
| headers | Optional. Headers written under `extras.headers`, where Ably's server-side filtering reads them. Values must be strings, numbers, booleans or `null`. | `Record` |
| name | Optional. The Ably message name. Defaults to the codec's `name`. | String |
| publish | Optional. Publish and remember the serial under this key. | String |
| append | Optional. Append to the message remembered under this key, or publish and remember it when none is. | String |
| update | Optional. Replace the content of the message remembered under this key. | String |
| ends | Optional. Forget this key once the message is written. | String |
| ephemeral | Optional. Publish an ephemeral message, which reaches subscribers and stays out of history. | Boolean |
| Property | Description | Type |
| --- | --- | --- |
| type | The event type the builder picked this row by. For a wildcard row, the concrete type, such as `data-weather`. | String |
| data | What this delivery adds: an append's text, the unseen end of a full-content update, or a plain publish's body. | Any |
| fields | What the row's `encode` wrote under `extras.ai.fields`. Empty when the message carries none. | `Record` |
| headers | The message's `extras.headers` as delivered. This includes the headers of the `send()` or `pipe()` call that published it, with the row's own value used where both set a key. | `Record` |
| name | The Ably message name. | String |
### Returns
`Codec`. The codec. `defineCodec` throws an [`ErrorInfo`](https://ably.com/docs/ai-transport/api/errors.md#errorinfo) with `InvalidArgument` when a row key is a wildcard with no prefix. A built codec's `encode` throws `InvalidArgument` for an event type with no row, or for a row that sets more than one of `publish`, `append` and `update`. Its `decode` throws `InvalidArgument` for a message whose type has no row.
## Create a decoder core
`createDecoderCore(options?: DecoderCoreOptions): DecoderCore`
You only need a decoder core for a codec you write without `defineCodec`, which creates one for you. [Codec architecture](https://ably.com/docs/ai-transport/internals/codec-architecture.md#decoder) explains what the decoder core does.
Create the decoder core that `defineCodec` uses, for a codec you write yourself. The core keeps, for each streamed message, the text it has passed on and the newest version it has seen. Its `prepare` method reduces an inbound message to the text that delivery adds, and drops a message the core has already seen.
### Javascript
```
import { createDecoderCore } from '@ably/ai-transport';
const core = createDecoderCore();
const decode = (message) => {
const prepared = core.prepare(message);
if (prepared === undefined) return [];
return [toEvent(prepared)];
};
```
### Parameters
| Parameter | Required | Description | Type |
| --- | --- | --- | --- |
| options | optional | The logger. | |
| Property | Description | Type |
| --- | --- | --- |
| logger | Optional. A logger for diagnostics. | `Logger` |
### Returns
`DecoderCore`. The decoder core.
| Property | Description | Type |
| --- | --- | --- |
| prepare | `(message: Ably.InboundMessage) => Ably.InboundMessage \| undefined`. Return the message to decode, with `data` replaced by the text the core has not passed on yet. Returns `undefined` when there is nothing to decode: a message the core has already seen, an update that adds nothing, or a deleted message. | Function |
## Example
A codec for a chat application whose agent streams text. A prompt is a plain publish. Each text delta appends to one message per stream, keyed by the stream id, and the end event forgets the key.
### Javascript
```
// Shared by the agent and the client.
import * as Ably from 'ably';
import { createTransport, defineCodec } from '@ably/ai-transport';
const chatCodec = defineCodec({
adapterTag: 'my-chat',
typeOf: (event) => event.type,
events: {
prompt: {
encode: (event) => ({ data: event.text }),
decode: ({ data }) => ({ type: 'prompt', text: String(data) }),
},
'text-delta': {
encode: (event) => ({ data: event.delta, fields: { id: event.id }, append: event.id }),
decode: ({ data, fields }) => ({ type: 'text-delta', id: String(fields.id), delta: String(data) }),
},
'text-end': {
encode: (event) => ({ fields: { id: event.id }, ends: event.id }),
decode: ({ fields }) => ({ type: 'text-end', id: String(fields.id) }),
},
},
});
const ably = new Ably.Realtime({ authUrl: '/auth' });
const transport = createTransport({ client: ably, channelName: 'conversations:42', codec: chatCodec });
transport.subscribe(({ event }) => {
if (event?.type === 'text-delta') appendText(event.id, event.delta);
});
```
## Related Topics
- [Transport](https://ably.com/docs/ai-transport/api/javascript/transport/transport.md): API reference for the AI Transport Transport: createTransport, send, pipe, subscribe with history replay, fromSerial, untilEvent, history, discontinuity and error events, and close.
## Documentation Index
To discover additional Ably documentation:
1. Fetch [llms.txt](https://ably.com/llms.txt) for the canonical list of available pages.
2. Identify relevant URLs from that index.
3. Fetch target pages as needed.
Avoid using assumed or outdated documentation paths.