# WireCodec The `WireCodec` is the whole contract the transports require: a way to turn your events into Ably messages, and a way to turn Ably messages back into your events. It is the wire tier of the full [`Codec`](https://ably.com/docs/ai-transport/api/javascript/core/codec.md), so anything satisfying `Codec` satisfies `WireCodec` too, and a transport-only application supplies just this part of it. Build one with `defineCodec`, which assembles the encoder and decoder from a declarative descriptor table. Pass the result to [`createClientTransport`](https://ably.com/docs/ai-transport/api/javascript/transport/client-transport.md) or [`createAgentTransport`](https://ably.com/docs/ai-transport/api/javascript/transport/agent-transport.md) as `codec`. #### Javascript ``` import { createClientTransport } from '@ably/ai-transport'; import { createUIMessageCodec } from '@ably/ai-transport/vercel'; // The shipped codecs are full Codecs, which satisfy WireCodec. const transport = createClientTransport({ channel, codec: createUIMessageCodec() }); ``` ## Properties | Property | Description | Type | | --- | --- | --- | | adapterTag | Optional. An Ably-Agent identifier registered on the channel, so traffic is attributed to this codec. Omit to opt out of registration. | String | | createEncoder | Create a stateful encoder bound to a channel. See [Create an encoder](#create-encoder). | Function | | createDecoder | Create a stateful decoder for inbound messages. See [Create a decoder](#create-decoder). | Function |
## Create an encoder `createEncoder(channel: ChannelWriter, options?: EncoderOptions): Encoder` Create a stateful encoder bound to one channel. The transports call this once per published message: one encoder for each client input, each steering message, and each `pipe` or `send` call, closed straight afterwards. You implement it, and only reach for the result directly when writing to a channel outside a transport. Stream-tracker state lives inside the encoder and is shared across both directions, so one encoder cannot be reused across channels. ### Parameters | Parameter | Required | Description | Type | | --- | --- | --- | --- | | channel | required | The channel the encoder publishes to. | `ChannelWriter` | | options | optional | Encoder configuration, including the default extras and headers to set on every message. | `EncoderOptions` |
### Returns | Property | Description | Type | | --- | --- | --- | | publishInput | `(input, options?) => Promise`. Encode and publish one client input on the `ai-input` wire. Rejects if the codec cannot encode the given input variant. | Function | | publishOutput | `(output, options?) => Promise`. Encode and publish one agent output on the `ai-output` wire. Rejects if the codec cannot encode the given output variant. | Function | | cancelStreams | `() => Promise`. Close every in-progress streamed message as `status: cancelled` and flush pending appends. Pure transport mechanics, emitting no codec output; run termination is signalled separately by `ai-run-end`. Idempotent, and throws if called after `close`. | Function | | close | `() => Promise`. Flush pending appends and release encoder resources. | Function |
## Create a decoder `createDecoder(): Decoder` Create a stateful decoder for one channel subscription. The decoder keeps stream-tracker state across messages, so on a mid-stream join, whether from history compaction, a partial history page, or a rewind miss, the decoder synthesises the missing start events before it emits any delta. Your code therefore always sees a clean start, delta, end sequence. The decoder's stream trackers are version-guarded: a delivery whose append version serial is at or below the version already incorporated decodes to nothing. One decoder instance is therefore safe to share between the live subscription and history paging, which is exactly what the transports do. ### Returns | Property | Description | Type | | --- | --- | --- | | decode | `(message: Ably.InboundMessage) => DecodedMessage`. Decode one inbound Ably message into its input and output halves. | Function |
| Property | Description | Type | | --- | --- | --- | | inputs | Inputs decoded from the inbound message. Populated only when the wire name is `ai-input`. | `TInput[]` | | outputs | Outputs decoded from the inbound message. Populated only when the wire name is `ai-output`. | `TOutput[]` |
The shape `decode` returns is a . ## Build one with defineCodec `function defineCodec(): (config: DefineCodecConfig) => WireCodec` Assemble a codec from declarative descriptor tables rather than hand-writing `createEncoder` and `createDecoder`. `defineCodec` is curried on the input and output unions, so the descriptor callbacks narrow to each member without casts. `defineCodec` produces a `WireCodec` and nothing more: there is no reducer slot and no message extraction, so the descriptor tables are the whole codec. That is all either transport needs, and the [custom wire codec quickstart](https://ably.com/docs/ai-transport/streaming/quickstart-wire-codec.md) builds one end to end. The codecs a durable session consumes are assembled by a different factory, which the SDK does not currently export. Until it does, a session takes one of the two pre-built session codecs: [`createUIMessageSessionCodec()`](https://ably.com/docs/ai-transport/api/javascript/vercel/codec.md#session-codec) or [`ResponsesSessionCodec`](https://ably.com/docs/ai-transport/api/javascript/openai/codec.md#session-codec). ### Parameters
| Parameter | Required | Description | Type | | --- | --- | --- | --- | | config | required | The descriptor tables. |
|
| Property | Description | Type | | --- | --- | --- | | output | `(b: OutputBuilder) => OutputDescriptor[]`. The `ai-output` descriptor table, built from the injected `{ event, stream, drop }` builder. An output type that is neither described nor dropped throws on encode. | Function | | input | `(b: InputBuilder) => InputDescriptor[]`. The `ai-input` descriptor table, built from the injected `{ event, batch }` builder. | Function | | adapterTag | Optional. Ably-Agent identifier registered on the channel. | String | | decoderSynthesiseLifecycle | Optional. Factory for a per-decoder lifecycle-repair policy, used to synthesise the missing start events on a mid-stream join. Omit for a codec with no repair. | Function |
| Property | Description | Type | | --- | --- | --- | | event | `(type, spec?) => OutputDescriptor`. Declare one discrete output event, published as a single message. The `type` literal is set as the wire `kind` dispatch header. | Function | | stream | `(kind, spec) => OutputDescriptor`. Declare a streamed group of start, delta, and end chunks that become one durable message growing by append. The group id is set as the wire `kind` header on every phase. | Function | | drop | `(type) => OutputDescriptor`. Declare an output type deliberately kept off the wire. The encoder publishes nothing for it, silently, so comment each entry with why the event is redundant. | Function |
| Property | Description | Type | | --- | --- | --- | | event | `(kind, spec?) => InputDescriptor`. Declare a single-event input. `fields` and `data` read and write that member's `payload`; a member with no payload may only be declared `wireOnly`. | Function | | batch | `(kind, spec) => InputDescriptor`. Declare a multi-part input: one domain message fanned out into one wire event per part, sharing the kind and codec-message-id, each carrying a `partType`. Use it for a user message with several parts. | Function |
### Returns `WireCodec`: `adapterTag`, `createEncoder` and `createDecoder`. It passes straight to either transport. ## Example ### Javascript ``` import { defineCodec, strField } from '@ably/ai-transport'; const fId = strField('id'); const fReason = strField('reason', ''); const fMessageId = strField('messageId', ''); export const myCodec = defineCodec()({ adapterTag: 'my-provider', output: ({ event, stream, drop }) => [ // One growing message per assistant reply. stream('text', { streamId: (chunk) => chunk.id, fields: [fId], start: { type: 'text-start' }, delta: { type: 'text-delta', field: 'delta', decode: ({ rebuild }) => rebuild([fId]) }, end: { type: 'text-end' }, }), // One publish per occurrence. event('done', { fields: [fReason] }), // Deliberately off the wire: a keepalive nothing downstream reads. drop('ping'), ], input: ({ batch, event }) => [ batch('user-message', { explode: (input) => [{ type: 'text', text: input.message.text }], partTypeOf: (part) => part.type, parts: (p) => [ p('text', { data: { encode: (part) => part.text, decode: (d) => ({ text: String(d ?? '') }) } }), ], messageHeaders: (input) => ({ codecHeaders: { messageId: input.message.id }, transportHeaders: { role: input.message.role }, }), assemble: (part, ctx) => ({ message: { id: fMessageId.read(ctx.codecHeaders), role: 'user', text: part.text }, }), }), event('regenerate', { wireOnly: true }), ], }); ``` ## Related Topics - [Client transport](https://ably.com/docs/ai-transport/api/javascript/transport/client-transport.md): API reference for the AI Transport ClientTransport: the factory, connect, subscribe, publishInput, cancel, steer, history, and the classified transport event stream. - [Agent transport](https://ably.com/docs/ai-transport/api/javascript/transport/agent-transport.md): API reference for the AI Transport AgentTransport: the factory, connect, openRun, locateInput, history, and the run and step handles that publish a run's output and lifecycle. ## 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.