{"id":48436141,"url":"https://github.com/ably/ably-ai-transport-js","last_synced_at":"2026-04-06T13:02:09.566Z","repository":{"id":346987715,"uuid":"1190534252","full_name":"ably/ably-ai-transport-js","owner":"ably","description":null,"archived":false,"fork":false,"pushed_at":"2026-04-02T16:34:31.000Z","size":948,"stargazers_count":0,"open_issues_count":14,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-03T04:43:58.090Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ably.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":"MAINTAINERS.md","copyright":"COPYRIGHT","agents":null,"dco":null,"cla":null}},"created_at":"2026-03-24T11:29:15.000Z","updated_at":"2026-04-02T10:03:47.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ably/ably-ai-transport-js","commit_stats":null,"previous_names":["ably/ably-ai-transport-js"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ably/ably-ai-transport-js","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ably%2Fably-ai-transport-js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ably%2Fably-ai-transport-js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ably%2Fably-ai-transport-js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ably%2Fably-ai-transport-js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ably","download_url":"https://codeload.github.com/ably/ably-ai-transport-js/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ably%2Fably-ai-transport-js/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31473273,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-06T08:36:52.050Z","status":"ssl_error","status_checked_at":"2026-04-06T08:36:51.267Z","response_time":112,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-06T13:02:08.938Z","updated_at":"2026-04-06T13:02:09.556Z","avatar_url":"https://github.com/ably.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ably AI Transport SDK\n\nA durable transport layer between AI agents and users. Streams AI responses over [Ably](https://ably.com/) channels - responses resume after disconnections, conversations persist across page reloads and devices, with support for cancellation, branching conversations, and multi-user sync.\n\n\u003e **Status:** Pre-release (`0.x`). The API is evolving. Feedback and contributions are welcome.\n\n## The problem\n\nMost AI frameworks stream tokens over HTTP response bodies or SSE. That works until it doesn't: connections drop through corporate proxies, responses vanish on page refresh, and sessions are stuck on a single device or tab. Once an agent starts a long-running task, the user has no way to interrupt it, check if it's still running, or continue the conversation from another device. If a human needs to take over from the agent, the session context is lost.\n\nAbly AI Transport replaces the HTTP stream with an Ably channel. The server publishes tokens to the channel as they arrive from the LLM; the response accumulates on the channel and persists, so partial responses survive disconnection. Any client can subscribe to the same channel from any device. Cancel signals, turn lifecycle events, and conversation history all flow through the channel rather than depending on a single HTTP connection.\n\n```mermaid\nsequenceDiagram\n    participant U as User\n    participant CT as Client Transport\n    participant AC as Ably Channel\n    participant ST as Server Transport\n    participant LLM\n\n    U-\u003e\u003eCT: type message\n    CT-\u003e\u003eST: HTTP POST (messages)\n    ST-\u003e\u003eLLM: prompt\n    LLM--\u003e\u003eST: token stream\n    ST-\u003e\u003eAC: publish chunks\n    AC-\u003e\u003eCT: subscribe (decode)\n    CT-\u003e\u003eU: render tokens\n```\n\nAbly AI Transport SDK is not an agent framework or orchestration layer - it works alongside whatever agent framework/model provider you choose, through a pluggable codec architecture (Vercel AI SDK supported now, more frameworks and models coming soon). It can be used in a serverless architecture (e.g. Next.js), with a durable execution framework (e.g. Temporal, Vercel Workflow DevKit) or in a traditional client-server architecture.\n\n## What this gives you\n\n- **Resumable streaming** - If a connection drops mid-response, client reconnects and picks up where it left off. The response persists on the channel, so nothing is lost.\n- **Session continuity across surfaces** - The session belongs to the channel, not the connection. A user can change tab or device and pick up at the same point.\n- **Multi-client sync** - Multiple users, agents, or operators subscribe to the same channel. Human-AI handover is a channel operation, not a session migration.\n- **Cancellation** - Cancel signals travel over the Ably channel, not the HTTP connection, and the server turn's `abortSignal` fires automatically.\n- **Interruption** - Users send new messages while the AI is still responding, with composable primitives for cancel-and-resend or queue-until-complete.\n- **Concurrent turns** - Multiple request-response cycles run in parallel on the same channel. Each turn has its own stream and abort signal.\n- **History** - The Ably channel is the conversation record. Clients hydrate from channel history on load - no separate database query needed.\n- **Branching** - Regenerate or edit messages to fork the conversation. The SDK tracks parent/child relationships and exposes a navigable tree.\n- **Framework-agnostic** - A codec interface decouples transport from the AI framework. Ships with a Vercel AI SDK codec; bring your own for any other stack.\n\n### When you need this\n\n- AI products where connection reliability and session durability are non-negotiable\n- Multi-surface experiences where a user needs to see the session in multiple tabs or devices\n- Collaborative AI where multiple users or agents interact in the same conversation\n- Customer support products where AI conversations are handed to human agents\n\n---\n\n## Getting started\n\n### Installation\n\n```sh\nnpm install @ably/ai-transport ably\n```\n\nFor Vercel AI SDK projects, also install the `ai` package:\n\n```sh\nnpm install @ably/ai-transport ably ai\n```\n\n### Supported platforms\n\n| Platform      | Support                                            |\n| ------------- | -------------------------------------------------- |\n| Node.js       | 20+                                                |\n| Browsers      | All major browsers (Chrome, Firefox, Edge, Safari) |\n| TypeScript    | Written in TypeScript, ships with types            |\n| React         | 18+ and 19+ via dedicated hooks                    |\n| Vercel AI SDK | v6 via dedicated codec and transport adapters      |\n\n---\n\n## Usage with Vercel AI SDK\n\nAI Transport is complementary to the Vercel AI SDK, not a replacement. The Vercel AI SDK handles model calls, message formatting, and React hooks. AI Transport replaces the transport layer underneath, so tokens stream over Ably instead of an HTTP response body. You keep `useChat`, `streamText`, and everything else you're used to.\n\n### Server - Next.js API route\n\n```typescript\nimport { after } from 'next/server';\nimport { streamText, convertToModelMessages } from 'ai';\nimport type { UIMessage } from 'ai';\nimport { anthropic } from '@ai-sdk/anthropic';\nimport Ably from 'ably';\nimport { createServerTransport } from '@ably/ai-transport/vercel';\nimport type { TreeNode } from '@ably/ai-transport';\n\ninterface ChatRequestBody {\n  turnId: string;\n  clientId: string;\n  messages: TreeNode\u003cUIMessage\u003e[];\n  history?: TreeNode\u003cUIMessage\u003e[];\n  id: string;\n  forkOf?: string;\n  parent?: string | null;\n}\n\nconst ably = new Ably.Realtime({ key: process.env.ABLY_API_KEY });\n\nexport async function POST(req: Request) {\n  const { messages, history, id, turnId, clientId, forkOf, parent } = (await req.json()) as ChatRequestBody;\n\n  const channel = ably.channels.get(id);\n  const transport = createServerTransport({ channel });\n  const turn = transport.newTurn({ turnId, clientId, parent, forkOf });\n\n  await turn.start();\n\n  if (messages.length \u003e 0) {\n    await turn.addMessages(messages, { clientId });\n  }\n\n  const historyMsgs = (history ?? []).map((h) =\u003e h.message);\n  const newMsgs = messages.map((m) =\u003e m.message);\n\n  const result = streamText({\n    model: anthropic('claude-sonnet-4-20250514'),\n    system: 'You are a helpful assistant.',\n    messages: await convertToModelMessages([...historyMsgs, ...newMsgs]),\n    abortSignal: turn.abortSignal,\n  });\n\n  // Stream the response over Ably in the background\n  after(async () =\u003e {\n    const { reason } = await turn.streamResponse(result.toUIMessageStream());\n    await turn.end(reason);\n    transport.close();\n  });\n\n  return new Response(null, { status: 200 });\n}\n```\n\n### Client - React with `useChat`\n\n```tsx\n'use client';\n\nimport { useChat } from '@ai-sdk/react';\nimport { useChannel } from 'ably/react';\nimport { useClientTransport, useActiveTurns, useView } from '@ably/ai-transport/react';\nimport { useChatTransport, useMessageSync } from '@ably/ai-transport/vercel/react';\nimport { UIMessageCodec } from '@ably/ai-transport/vercel';\n\nfunction Chat({ chatId, clientId }: { chatId: string; clientId?: string }) {\n  const { channel } = useChannel({ channelName: chatId });\n\n  const transport = useClientTransport({ channel, codec: UIMessageCodec, clientId });\n  const chatTransport = useChatTransport(transport);\n\n  const { messages, setMessages, sendMessage, stop } = useChat({\n    id: chatId,\n    transport: chatTransport,\n  });\n\n  useMessageSync(transport, setMessages);\n\n  const activeTurns = useActiveTurns(transport);\n  const view = useView(transport, { limit: 30 });\n\n  return (\n    \u003cdiv\u003e\n      {messages.map((msg) =\u003e (\n        \u003cdiv key={msg.id}\u003e{msg.parts.map((part, i) =\u003e (part.type === 'text' ? \u003cp key={i}\u003e{part.text}\u003c/p\u003e : null))}\u003c/div\u003e\n      ))}\n      \u003cform\n        onSubmit={(e) =\u003e {\n          e.preventDefault();\n          sendMessage({ text: 'Hello' });\n        }}\n      \u003e\n        {activeTurns.size \u003e 0 ? (\n          \u003cbutton\n            type=\"button\"\n            onClick={stop}\n          \u003e\n            Stop\n          \u003c/button\u003e\n        ) : (\n          \u003cbutton type=\"submit\"\u003eSend\u003c/button\u003e\n        )}\n      \u003c/form\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n### Authentication\n\nThe Ably client authenticates via token auth. Create an endpoint that issues token requests:\n\n```typescript\n// app/api/auth/ably-token/route.ts\nimport Ably from 'ably';\n\nconst ably = new Ably.Rest({ key: process.env.ABLY_API_KEY });\n\nexport async function GET(req: Request) {\n  const { searchParams } = new URL(req.url);\n  const clientId = searchParams.get('clientId') ?? 'anonymous';\n  const token = await ably.auth.createTokenRequest({ clientId });\n  return Response.json(token);\n}\n```\n\n```typescript\n// Client-side Ably setup\nconst ably = new Ably.Realtime({\n  authCallback: async (_params, callback) =\u003e {\n    const response = await fetch('/api/auth/ably-token');\n    const token = await response.json();\n    callback(null, token);\n  },\n});\n```\n\n---\n\n## Core usage with a custom codec\n\nThe core entry point is framework-agnostic. Bring your own `Codec` to map between your AI framework's event/message types and the Ably wire format.\n\n### Client\n\n```typescript\nimport { createClientTransport } from '@ably/ai-transport';\nimport { myCodec } from './my-codec';\n\nconst transport = createClientTransport({\n  channel, // Ably RealtimeChannel\n  codec: myCodec,\n  clientId: 'user-123',\n  api: '/api/chat',\n});\n\nconst turn = await transport.send(messages);\n\n// Read the stream\nconst reader = turn.stream.getReader();\nwhile (true) {\n  const { done, value } = await reader.read();\n  if (done) break;\n  console.log(value); // Your codec's event type\n}\n```\n\n### Server\n\n```typescript\nimport { createServerTransport } from '@ably/ai-transport';\nimport { myCodec } from './my-codec';\n\nconst transport = createServerTransport({ channel, codec: myCodec });\nconst turn = transport.newTurn({ turnId, clientId, parent, forkOf });\n\nawait turn.start();\nawait turn.addMessages(messages, { clientId });\n\nconst { reason } = await turn.streamResponse(aiStream);\nawait turn.end(reason);\ntransport.close();\n```\n\n---\n\n## Package exports\n\n| Export path                               | Purpose                                     | Peer dependencies     |\n| ----------------------------------------- | ------------------------------------------- | --------------------- |\n| `@ably/ai-transport`              | Core transport, codec interfaces, utilities | `ably`                |\n| `@ably/ai-transport/react`        | React hooks for any codec                   | `ably`, `react`       |\n| `@ably/ai-transport/vercel`       | Vercel AI SDK codec, transport factories    | `ably`, `ai`          |\n| `@ably/ai-transport/vercel/react` | React hooks for Vercel's `useChat`          | `ably`, `ai`, `react` |\n\n### React hooks\n\n| Hook                  | Entry point     | Description                                         |\n| --------------------- | --------------- | --------------------------------------------------- |\n| `useClientTransport`  | `/react`        | Create and memoize a client transport instance      |\n| `useView`             | `/react`        | Subscribe to messages with history loading          |\n| `useSend`             | `/react`        | Stable send callback                                |\n| `useRegenerate`       | `/react`        | Regenerate a message (fork the conversation)        |\n| `useEdit`             | `/react`        | Edit a message and regenerate from that point       |\n| `useActiveTurns`      | `/react`        | Track active turns by client ID                     |\n| `useTree`             | `/react`        | Navigate branches in a forked conversation          |\n| `useAblyMessages`     | `/react`        | Access raw Ably messages                            |\n| `useChatTransport`    | `/vercel/react` | Wrap transport for Vercel's `useChat`               |\n| `useMessageSync`      | `/vercel/react` | Sync transport state with `useChat`'s `setMessages` |\n\n---\n\n## Key features\n\n### Connection recovery\n\nTwo mechanisms cover different failure modes:\n\n- **Network blips** - Ably's connection protocol automatically reconnects and delivers any messages published while the client was disconnected. No application code required.\n- **Resumable streams** - A client that joins or rejoins a channel mid-response (after a page refresh, on a new device, or as a second participant) receives the in-progress stream immediately on subscribing. Load previous conversation history from the channel via `view.loadOlder()`, or from your own database.\n\n### Cancellation\n\n```typescript\n// Client: cancel your own active turns\nawait transport.cancel();\n\n// Cancel a specific turn\nawait transport.cancel({ turnId: 'turn-abc' });\n\n// Server: the turn's abortSignal fires automatically\nconst result = streamText({\n  model: anthropic('claude-sonnet-4-20250514'),\n  messages,\n  abortSignal: turn.abortSignal, // Aborted when client cancels\n});\n```\n\n### Branching conversations\n\nRegenerate or edit messages to create forks in the conversation tree. The SDK tracks parent/child relationships and exposes a navigable tree.\n\n```typescript\n// Regenerate the last assistant message\nconst turn = await transport.regenerate(assistantMessageId);\n\n// Edit a user message and regenerate from that point\nconst turn = await transport.edit(userMessageId, [newMessage]);\n\n// Navigate branches\nconst tree = transport.tree;\nconst siblings = tree.getSiblings(messageId);\ntree.select(messageId, 1); // Switch to second branch\n```\n\n### History and hydration\n\nLoad previous conversation state when a client joins or returns to a session.\n\n```typescript\nconst view = transport.view;\nawait view.loadOlder(50);\n// view.flattenNodes() returns the messages loaded so far\n\n// Load more older messages\nawait view.loadOlder(50);\n```\n\n### Events\n\n```typescript\ntransport.view.on('update', () =\u003e {\n  console.log(transport.view.flattenNodes().map(n =\u003e n.message));\n});\n\ntransport.tree.on('turn', (event) =\u003e {\n  console.log(event.turnId, event.type); // 'x-ably-turn-start' | 'x-ably-turn-end'\n});\n\ntransport.on('error', (error) =\u003e {\n  console.error(error.code, error.message);\n});\n```\n\n---\n\n## Documentation\n\nDetailed documentation lives in the [`docs/`](./docs/) directory:\n\n- **[Concepts](./docs/concepts/)** - [Transport architecture](./docs/concepts/transport.md), [Turns](./docs/concepts/turns.md)\n- **[Get started](./docs/get-started/)** - [Vercel AI SDK with useChat](./docs/get-started/vercel-use-chat.md), [Vercel AI SDK with useClientTransport](./docs/get-started/vercel-use-client-transport.md)\n- **[Frameworks](./docs/frameworks/)** - [Vercel AI SDK](./docs/frameworks/vercel-ai-sdk.md)\n- **[Features](./docs/features/)** - [Streaming](./docs/features/streaming.md), [Cancellation](./docs/features/cancel.md), [Barge-in](./docs/features/barge-in.md), [Optimistic updates](./docs/features/optimistic-updates.md), [History](./docs/features/history.md), [Branching](./docs/features/branching.md), [Multi-client sync](./docs/features/multi-client.md), [Tool calls](./docs/features/tool-calls.md), [Concurrent turns](./docs/features/concurrent-turns.md)\n- **[Reference](./docs/reference/)** - [React hooks](./docs/reference/react-hooks.md), [Error codes](./docs/reference/error-codes.md)\n- **[Internals](./docs/internals/)** - Architecture details for contributors\n\n---\n\n## Demo apps\n\nWorking demo applications live in the [`demo/`](./demo/) directory:\n\n- **[`demo/vercel/react/use-chat/`](./demo/vercel/react/use-chat/)** - Vercel AI SDK with `useChat` integration\n- **[`demo/vercel/react/use-client-transport/`](./demo/vercel/react/use-client-transport/)** - Vercel AI SDK with direct `useClientTransport` hooks\n\n---\n\n## Development\n\n```bash\nnpm install\nnpm run build             # Build all entry points (ESM + UMD/CJS + .d.ts)\nnpm run typecheck         # Type check\nnpm run lint              # Lint\nnpm test                  # Unit tests (mocks only)\nnpm run test:integration  # Integration tests (needs ABLY_API_KEY)\nnpm run precommit         # format:check + lint + typecheck\n```\n\n### Project structure\n\n```\nsrc/\n├── core/               # Generic transport and codec (no framework deps)\n│   ├── codec/          # Codec interfaces and core encoder/decoder\n│   └── transport/      # ClientTransport, ServerTransport, Tree\n├── react/              # React hooks for any codec\n├── vercel/             # Vercel AI SDK codec and transport adapters\n│   ├── codec/          # UIMessageCodec\n│   ├── transport/      # Vercel-specific factories, ChatTransport\n│   └── react/          # useChatTransport, useMessageSync\n└── index.ts            # Core entry point\n```\n\n---\n\n## Contributing\n\nSee [CONTRIBUTING.md](./CONTRIBUTING.md). [Open an issue](https://github.com/ably/ably-ai-transport-js/issues) to share feedback or request a feature.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fably%2Fably-ai-transport-js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fably%2Fably-ai-transport-js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fably%2Fably-ai-transport-js/lists"}