@assistant-ui/mcp-docs-server 0.1.30 → 0.1.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/.docs/organized/code-examples/waterfall.md +1 -1
  2. package/.docs/organized/code-examples/with-a2a.md +2 -2
  3. package/.docs/organized/code-examples/with-ag-ui.md +3 -3
  4. package/.docs/organized/code-examples/with-ai-sdk-v6.md +5 -5
  5. package/.docs/organized/code-examples/with-artifacts.md +5 -5
  6. package/.docs/organized/code-examples/with-assistant-transport.md +3 -3
  7. package/.docs/organized/code-examples/with-chain-of-thought.md +79 -50
  8. package/.docs/organized/code-examples/with-cloud-standalone.md +4 -4
  9. package/.docs/organized/code-examples/with-cloud.md +4 -4
  10. package/.docs/organized/code-examples/with-custom-thread-list.md +56 -11
  11. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +7 -7
  12. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +7 -7
  13. package/.docs/organized/code-examples/with-expo.md +16 -16
  14. package/.docs/organized/code-examples/with-external-store.md +2 -2
  15. package/.docs/organized/code-examples/with-ffmpeg.md +5 -5
  16. package/.docs/organized/code-examples/with-generative-ui.md +5 -5
  17. package/.docs/organized/code-examples/with-google-adk.md +4 -4
  18. package/.docs/organized/code-examples/with-heat-graph.md +1 -1
  19. package/.docs/organized/code-examples/with-interactables.md +5 -5
  20. package/.docs/organized/code-examples/with-langchain.md +3 -3
  21. package/.docs/organized/code-examples/with-langgraph.md +3 -3
  22. package/.docs/organized/code-examples/with-livekit.md +8 -8
  23. package/.docs/organized/code-examples/with-opencode.md +99 -54
  24. package/.docs/organized/code-examples/with-parent-id-grouping.md +4 -4
  25. package/.docs/organized/code-examples/with-react-hook-form.md +5 -5
  26. package/.docs/organized/code-examples/with-react-ink.md +1 -1
  27. package/.docs/organized/code-examples/with-react-router.md +8 -8
  28. package/.docs/organized/code-examples/with-store.md +1 -1
  29. package/.docs/organized/code-examples/with-tanstack.md +5 -5
  30. package/.docs/organized/code-examples/with-tap-runtime.md +2 -2
  31. package/.docs/raw/docs/(docs)/cli.mdx +2 -1
  32. package/.docs/raw/docs/(docs)/copilots/assistant-frame.mdx +1 -0
  33. package/.docs/raw/docs/(docs)/copilots/make-assistant-tool-ui.mdx +10 -3
  34. package/.docs/raw/docs/(docs)/copilots/make-assistant-tool.mdx +8 -3
  35. package/.docs/raw/docs/(docs)/copilots/make-assistant-visible.mdx +1 -0
  36. package/.docs/raw/docs/(docs)/copilots/model-context.mdx +1 -0
  37. package/.docs/raw/docs/(docs)/copilots/motivation.mdx +1 -0
  38. package/.docs/raw/docs/(docs)/copilots/use-assistant-instructions.mdx +1 -0
  39. package/.docs/raw/docs/(docs)/devtools.mdx +1 -0
  40. package/.docs/raw/docs/(docs)/index.mdx +1 -0
  41. package/.docs/raw/docs/(docs)/installation.mdx +1 -0
  42. package/.docs/raw/docs/(docs)/rtl.mdx +1 -0
  43. package/.docs/raw/docs/(reference)/api-reference/adapters/attachments.mdx +34 -0
  44. package/.docs/raw/docs/(reference)/api-reference/adapters/feedback-speech.mdx +41 -0
  45. package/.docs/raw/docs/(reference)/api-reference/adapters/index.mdx +26 -0
  46. package/.docs/raw/docs/(reference)/api-reference/adapters/persistence.mdx +34 -0
  47. package/.docs/raw/docs/(reference)/api-reference/adapters/runtime.mdx +31 -0
  48. package/.docs/raw/docs/(reference)/api-reference/context-providers/index.mdx +20 -0
  49. package/.docs/raw/docs/(reference)/api-reference/hooks/index.mdx +26 -0
  50. package/.docs/raw/docs/(reference)/api-reference/hooks/model-context.mdx +72 -0
  51. package/.docs/raw/docs/(reference)/api-reference/hooks/runtimes.mdx +41 -0
  52. package/.docs/raw/docs/(reference)/api-reference/hooks/state.mdx +48 -0
  53. package/.docs/raw/docs/(reference)/api-reference/hooks/utilities.mdx +30 -0
  54. package/.docs/raw/docs/(reference)/api-reference/integrations/index.mdx +23 -0
  55. package/.docs/raw/docs/(reference)/api-reference/overview.mdx +21 -0
  56. package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +7 -0
  57. package/.docs/raw/docs/(reference)/api-reference/primitives/index.mdx +65 -0
  58. package/.docs/raw/docs/(reference)/api-reference/primitives/message.mdx +50 -6
  59. package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list.mdx +15 -0
  60. package/.docs/raw/docs/(reference)/api-reference/primitives/thread.mdx +36 -1
  61. package/.docs/raw/docs/(reference)/api-reference/runtimes/index.mdx +38 -0
  62. package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-list-runtime.mdx +5 -0
  63. package/.docs/raw/docs/(reference)/migrations/v0-14.mdx +144 -6
  64. package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +230 -2
  65. package/.docs/raw/docs/cloud/ai-sdk.mdx +221 -3
  66. package/.docs/raw/docs/cloud/langgraph.mdx +274 -2
  67. package/.docs/raw/docs/{(docs)/guides → guides}/attachments.mdx +41 -36
  68. package/.docs/raw/docs/guides/branching.mdx +76 -0
  69. package/.docs/raw/docs/guides/chain-of-thought.mdx +166 -0
  70. package/.docs/raw/docs/{(docs)/guides → guides}/context-api.mdx +50 -22
  71. package/.docs/raw/docs/{(docs)/guides → guides}/dictation.mdx +2 -0
  72. package/.docs/raw/docs/guides/editing.mdx +102 -0
  73. package/.docs/raw/docs/guides/index.mdx +103 -0
  74. package/.docs/raw/docs/{(docs)/guides → guides}/interactables.mdx +49 -0
  75. package/.docs/raw/docs/{(docs)/guides → guides}/latex.mdx +51 -8
  76. package/.docs/raw/docs/{(docs)/guides → guides}/mentions.mdx +61 -86
  77. package/.docs/raw/docs/{(docs)/guides → guides}/message-timing.mdx +8 -2
  78. package/.docs/raw/docs/{(docs)/guides → guides}/multi-agent.mdx +64 -4
  79. package/.docs/raw/docs/{(docs)/guides → guides}/quoting.mdx +10 -17
  80. package/.docs/raw/docs/{(docs)/guides → guides}/slash-commands.mdx +103 -37
  81. package/.docs/raw/docs/guides/speech.mdx +156 -0
  82. package/.docs/raw/docs/{(docs)/guides → guides}/suggestions.mdx +21 -83
  83. package/.docs/raw/docs/{(docs)/guides → guides}/tool-ui.mdx +108 -36
  84. package/.docs/raw/docs/{(docs)/guides → guides}/tools.mdx +131 -35
  85. package/.docs/raw/docs/{(docs)/guides → guides}/voice.mdx +39 -0
  86. package/.docs/raw/docs/ink/index.mdx +1 -3
  87. package/.docs/raw/docs/ink/migration.mdx +1 -3
  88. package/.docs/raw/docs/ink/primitives.mdx +37 -1
  89. package/.docs/raw/docs/integrations/attachments/custom-adapter.mdx +520 -0
  90. package/.docs/raw/docs/integrations/auth/better-auth.mdx +191 -0
  91. package/.docs/raw/docs/integrations/auth/clerk.mdx +172 -0
  92. package/.docs/raw/docs/integrations/auth/next-auth.mdx +196 -0
  93. package/.docs/raw/docs/integrations/frameworks/ai-sdk.mdx +79 -0
  94. package/.docs/raw/docs/integrations/frameworks/mastra/full-stack.mdx +188 -0
  95. package/.docs/raw/docs/integrations/frameworks/mastra/overview.mdx +57 -0
  96. package/.docs/raw/docs/integrations/frameworks/mastra/separate-server.mdx +201 -0
  97. package/.docs/raw/docs/integrations/gateways/index.mdx +157 -0
  98. package/.docs/raw/docs/integrations/index.mdx +173 -0
  99. package/.docs/raw/docs/integrations/observability/helicone.mdx +130 -0
  100. package/.docs/raw/docs/integrations/observability/langfuse.mdx +156 -0
  101. package/.docs/raw/docs/integrations/observability/langsmith.mdx +146 -0
  102. package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +712 -0
  103. package/.docs/raw/docs/integrations/tools/mcp.mdx +267 -0
  104. package/.docs/raw/docs/primitives/action-bar.mdx +1 -0
  105. package/.docs/raw/docs/primitives/assistant-modal.mdx +1 -0
  106. package/.docs/raw/docs/primitives/attachment.mdx +1 -0
  107. package/.docs/raw/docs/primitives/branch-picker.mdx +1 -0
  108. package/.docs/raw/docs/primitives/chain-of-thought.mdx +90 -85
  109. package/.docs/raw/docs/primitives/composer.mdx +2 -1
  110. package/.docs/raw/docs/primitives/error.mdx +1 -0
  111. package/.docs/raw/docs/primitives/index.mdx +2 -1
  112. package/.docs/raw/docs/primitives/message.mdx +68 -5
  113. package/.docs/raw/docs/primitives/selection-toolbar.mdx +1 -0
  114. package/.docs/raw/docs/primitives/suggestion.mdx +1 -0
  115. package/.docs/raw/docs/primitives/thread-list.mdx +39 -0
  116. package/.docs/raw/docs/primitives/thread.mdx +16 -13
  117. package/.docs/raw/docs/react-native/index.mdx +1 -3
  118. package/.docs/raw/docs/react-native/migration.mdx +1 -3
  119. package/.docs/raw/docs/runtimes/a2a/client-and-hooks.mdx +396 -0
  120. package/.docs/raw/docs/runtimes/a2a/overview.mdx +60 -0
  121. package/.docs/raw/docs/runtimes/a2a/quickstart.mdx +216 -0
  122. package/.docs/raw/docs/runtimes/ag-ui/overview.mdx +70 -0
  123. package/.docs/raw/docs/runtimes/ag-ui/quickstart.mdx +243 -0
  124. package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +123 -0
  125. package/.docs/raw/docs/runtimes/ai-sdk/overview.mdx +52 -0
  126. package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +71 -131
  127. package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +69 -63
  128. package/.docs/raw/docs/runtimes/ai-sdk/v6.mdx +330 -123
  129. package/.docs/raw/docs/runtimes/concepts/adapters.mdx +265 -0
  130. package/.docs/raw/docs/runtimes/concepts/architecture.mdx +125 -0
  131. package/.docs/raw/docs/runtimes/concepts/stability.mdx +67 -0
  132. package/.docs/raw/docs/runtimes/concepts/threads.mdx +428 -0
  133. package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +703 -0
  134. package/.docs/raw/docs/runtimes/custom/data-stream.mdx +323 -0
  135. package/.docs/raw/docs/runtimes/custom/external-store.mdx +253 -1236
  136. package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +746 -0
  137. package/.docs/raw/docs/runtimes/custom/overview.mdx +71 -0
  138. package/.docs/raw/docs/runtimes/google-adk/api.mdx +256 -0
  139. package/.docs/raw/docs/runtimes/google-adk/hooks.mdx +717 -0
  140. package/.docs/raw/docs/runtimes/google-adk/overview.mdx +69 -0
  141. package/.docs/raw/docs/runtimes/google-adk/quickstart.mdx +229 -0
  142. package/.docs/raw/docs/runtimes/langchain.mdx +533 -0
  143. package/.docs/raw/docs/runtimes/langgraph/generative-ui.mdx +305 -0
  144. package/.docs/raw/docs/runtimes/langgraph/interrupts.mdx +104 -0
  145. package/.docs/raw/docs/runtimes/langgraph/overview.mdx +84 -0
  146. package/.docs/raw/docs/runtimes/langgraph/quickstart.mdx +496 -0
  147. package/.docs/raw/docs/runtimes/langgraph/streaming.mdx +127 -0
  148. package/.docs/raw/docs/runtimes/langgraph/threads.mdx +113 -0
  149. package/.docs/raw/docs/runtimes/langgraph/tutorial/introduction.mdx +3 -3
  150. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-1.mdx +0 -23
  151. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +1 -1
  152. package/.docs/raw/docs/runtimes/opencode/hooks.mdx +191 -0
  153. package/.docs/raw/docs/runtimes/opencode/overview.mdx +48 -0
  154. package/.docs/raw/docs/runtimes/opencode/quickstart.mdx +119 -0
  155. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +71 -203
  156. package/.docs/raw/docs/ui/accordion.mdx +1 -0
  157. package/.docs/raw/docs/ui/assistant-modal.mdx +1 -0
  158. package/.docs/raw/docs/ui/assistant-sidebar.mdx +1 -0
  159. package/.docs/raw/docs/ui/attachment.mdx +1 -0
  160. package/.docs/raw/docs/ui/badge.mdx +1 -0
  161. package/.docs/raw/docs/ui/composer-trigger-popover.mdx +1 -0
  162. package/.docs/raw/docs/ui/context-display.mdx +1 -0
  163. package/.docs/raw/docs/ui/diff-viewer.mdx +1 -0
  164. package/.docs/raw/docs/ui/directive-text.mdx +1 -0
  165. package/.docs/raw/docs/ui/file.mdx +1 -0
  166. package/.docs/raw/docs/ui/image.mdx +1 -0
  167. package/.docs/raw/docs/ui/markdown.mdx +2 -14
  168. package/.docs/raw/docs/ui/mermaid.mdx +1 -0
  169. package/.docs/raw/docs/ui/message-timing.mdx +3 -2
  170. package/.docs/raw/docs/ui/model-selector.mdx +1 -0
  171. package/.docs/raw/docs/ui/part-grouping.mdx +325 -313
  172. package/.docs/raw/docs/ui/quote.mdx +1 -0
  173. package/.docs/raw/docs/ui/reasoning.mdx +66 -33
  174. package/.docs/raw/docs/ui/scrollbar.mdx +1 -0
  175. package/.docs/raw/docs/ui/select.mdx +1 -0
  176. package/.docs/raw/docs/ui/sources.mdx +1 -0
  177. package/.docs/raw/docs/ui/streamdown.mdx +1 -0
  178. package/.docs/raw/docs/ui/syntax-highlighting.mdx +1 -0
  179. package/.docs/raw/docs/ui/tabs.mdx +1 -0
  180. package/.docs/raw/docs/ui/thread-list.mdx +17 -0
  181. package/.docs/raw/docs/ui/thread.mdx +56 -1
  182. package/.docs/raw/docs/ui/tool-fallback.mdx +1 -0
  183. package/.docs/raw/docs/ui/tool-group.mdx +39 -11
  184. package/.docs/raw/docs/ui/voice.mdx +1 -0
  185. package/.docs/raw/docs/utilities/heat-graph.mdx +1 -0
  186. package/.docs/raw/docs/utilities/react-o11y.mdx +278 -0
  187. package/.docs/raw/docs/utilities/tw-shimmer.mdx +1 -0
  188. package/package.json +3 -3
  189. package/src/tools/tests/path-traversal.test.ts +1 -1
  190. package/.docs/raw/docs/(docs)/guides/branching.mdx +0 -65
  191. package/.docs/raw/docs/(docs)/guides/chain-of-thought.mdx +0 -164
  192. package/.docs/raw/docs/(docs)/guides/editing.mdx +0 -66
  193. package/.docs/raw/docs/(docs)/guides/speech.mdx +0 -38
  194. package/.docs/raw/docs/runtimes/a2a/index.mdx +0 -298
  195. package/.docs/raw/docs/runtimes/assistant-transport.mdx +0 -1033
  196. package/.docs/raw/docs/runtimes/custom/custom-thread-list.mdx +0 -314
  197. package/.docs/raw/docs/runtimes/custom/local.mdx +0 -1464
  198. package/.docs/raw/docs/runtimes/data-stream.mdx +0 -422
  199. package/.docs/raw/docs/runtimes/google-adk/index.mdx +0 -686
  200. package/.docs/raw/docs/runtimes/helicone.mdx +0 -61
  201. package/.docs/raw/docs/runtimes/langchain/comparison.mdx +0 -60
  202. package/.docs/raw/docs/runtimes/langchain/index.mdx +0 -210
  203. package/.docs/raw/docs/runtimes/langgraph/index.mdx +0 -699
  204. package/.docs/raw/docs/runtimes/langgraph/tutorial/index.mdx +0 -12
  205. package/.docs/raw/docs/runtimes/langserve.mdx +0 -116
  206. package/.docs/raw/docs/runtimes/mastra/full-stack-integration.mdx +0 -218
  207. package/.docs/raw/docs/runtimes/mastra/overview.mdx +0 -18
  208. package/.docs/raw/docs/runtimes/mastra/separate-server-integration.mdx +0 -217
@@ -1,1033 +0,0 @@
1
- ---
2
- title: Assistant Transport
3
- description: Stream agent state to the frontend and handle user commands for custom agents.
4
- ---
5
-
6
- If you've built an agent as a Python or TypeScript script and want to add a UI to it, you need to solve two problems: streaming updates to the frontend and integrating with the UI framework. Assistant Transport handles both.
7
-
8
- Assistant Transport streams your agent's complete state to the frontend in real-time. Unlike traditional approaches that only stream predefined message types (like text or tool calls), it streams your entire agent state—whatever structure your agent uses internally.
9
-
10
- It consists of:
11
-
12
- - **State streaming**: Efficiently streams updates to your agent state (supports any JSON object)
13
- - **UI integration**: Converts your agent's state into assistant-ui components that render in the browser
14
- - **Command handling**: Sends user actions (messages, tool executions, custom commands) back to your agent
15
-
16
- ## When to Use Assistant Transport
17
-
18
- Use Assistant Transport when:
19
-
20
- - You don't have a streaming protocol yet and need one
21
- - You want your agent's native state to be directly accessible in the frontend
22
- - You're building a custom agent framework or one without a streaming protocol (e.g. OSS LangGraph)
23
-
24
- ## Mental Model
25
-
26
- ```mermaid
27
- graph LR
28
- Frontend -->|Commands| Agent[Agent Server]
29
- Agent -->|State Snapshots| Frontend
30
- ```
31
-
32
- The frontend receives state snapshots and converts them to React components. The goal is to have the UI be a stateless view on top of the agent framework state.
33
-
34
- The agent server receives commands from the frontend. When a user interacts with the UI (sends a message, clicks a button, etc.), the frontend queues a command and sends it to the backend. Assistant Transport defines standard commands like `add-message` and `add-tool-result`, and you can define custom commands.
35
-
36
- ### Command Lifecycle
37
-
38
- Commands go through the following lifecycle:
39
-
40
- ```mermaid
41
- graph LR
42
- queued -->|sent to backend| in_transit
43
- in_transit -->|backend processes| applied
44
- ```
45
-
46
- The runtime alternates between **idle** (no active backend request) and **sending** (request in flight). When a new command is created while idle, it's immediately sent. Otherwise, it's queued until the current request completes.
47
-
48
- ```mermaid
49
- graph LR
50
- idle -->|new command| sending
51
- sending -->|request completes| check{check queue}
52
- check -->|queue has commands| sending
53
- check -->|queue empty| idle
54
- ```
55
-
56
- To implement this architecture, you need to build 2 pieces:
57
-
58
- 1. **Backend endpoint** on the agent server that accepts commands and returns a stream of state snapshots
59
- 2. **Frontend-side [state converter](#state-converter)** that converts state snapshots to assistant-ui's data format so that the UI primitives work
60
-
61
- ## Building a Backend Endpoint
62
-
63
- Let's build the backend endpoint step by step. You'll need to handle incoming commands, update your agent state, and stream the updates back to the frontend.
64
-
65
- The backend endpoint receives POST requests with the following payload:
66
-
67
- ```typescript
68
- {
69
- state: T, // The previous state that the frontend has access to
70
- commands: AssistantTransportCommand[],
71
- system?: string,
72
- tools?: Record<string, ToolJSONSchema>, // Tool definitions keyed by tool name
73
- threadId: string | null, // The current thread/conversation identifier (null for new threads)
74
- parentId?: string | null, // The parent message ID (included when editing or branching)
75
- callSettings?: { maxTokens, temperature, topP, presencePenalty, frequencyPenalty, seed },
76
- config?: { apiKey, baseUrl, modelName },
77
- }
78
- ```
79
-
80
- <Callout type="warn">
81
- **Migrating from top-level fields:** `callSettings` and `config` fields were previously spread at the top level of the request body (e.g. `body.modelName` instead of `body.config.modelName`). Both formats are currently sent for backward compatibility, but the top-level fields are deprecated and will be removed in a future version. Update your backend to read from the nested objects.
82
- </Callout>
83
-
84
- The backend endpoint returns a stream of state snapshots using the `assistant-stream` library ([npm](https://www.npmjs.com/package/assistant-stream) / [PyPI](https://pypi.org/project/assistant-stream/)).
85
-
86
- ### Handling Commands
87
-
88
- The backend endpoint processes commands from the `commands` array:
89
-
90
- ```python
91
- for command in request.commands:
92
- if command.type == "add-message":
93
- # Handle adding a user message
94
- elif command.type == "add-tool-result":
95
- # Handle tool execution result
96
- elif command.type == "my-custom-command":
97
- # Handle your custom command
98
- ```
99
-
100
- ### Streaming Updates
101
-
102
- To stream state updates, modify `controller.state` within your run callback:
103
-
104
- ```python
105
- from assistant_stream import RunController, create_run
106
- from assistant_stream.serialization import DataStreamResponse
107
-
108
- @app.post("/assistant")
109
- async def chat_endpoint(request: ChatRequest):
110
- async def run_callback(controller: RunController):
111
- # Emits "set" at path ["message"] with value "Hello"
112
- controller.state["message"] = "Hello"
113
-
114
- # Emits "append-text" at path ["message"] with value " World"
115
- controller.state["message"] += " World"
116
-
117
- # Create and return the stream
118
- stream = create_run(run_callback, state=request.state)
119
- return DataStreamResponse(stream)
120
- ```
121
-
122
- The state snapshots are automatically streamed to the frontend using the operations described in [Streaming Protocol](#streaming-protocol).
123
-
124
- > **Cancellation:** `create_run` exposes `controller.is_cancelled` and `controller.cancelled_event`.
125
- > If the response stream is closed early (for example user cancel or client disconnect),
126
- > these are set so your backend loop can exit cooperatively.
127
- > `controller.cancelled_event` is a read-only signal object with `wait()` and `is_set()`.
128
- > `create_run` gives callbacks a ~50ms cooperative shutdown window before forced task cancellation.
129
- > Callback exceptions that happen during early-close cleanup are not re-raised to the stream consumer,
130
- > but are logged with traceback at warning level for debugging.
131
- > Put critical cleanup in `finally` blocks, since forced cancellation may happen after the grace window.
132
- >
133
- > ```python
134
- > async def run_callback(controller: RunController):
135
- > while not controller.is_cancelled:
136
- > # Long-running work / model loop
137
- > await asyncio.sleep(0.05)
138
- > ```
139
- >
140
- > ```python
141
- > async def run_callback(controller: RunController):
142
- > await controller.cancelled_event.wait()
143
- > # cancellation-aware shutdown path
144
- > ```
145
-
146
- ### Backend Reference Implementation
147
-
148
- <Tabs items={["Minimal", "Example", "LangGraph"]}>
149
- <Tab>
150
-
151
- ```python
152
- from assistant_stream import RunController, create_run
153
- from assistant_stream.serialization import DataStreamResponse
154
-
155
- async def run_callback(controller: RunController):
156
- # Initialize state
157
- if controller.state is None:
158
- controller.state = {}
159
-
160
- # Process commands
161
- for command in request.commands:
162
- # Handle commands...
163
-
164
- # Run your agent and stream updates
165
- async for event in agent.stream():
166
- # update controller.state
167
- pass
168
-
169
- # Create and return the stream
170
- stream = create_run(run_callback, state=request.state)
171
- return DataStreamResponse(stream)
172
- ```
173
-
174
- </Tab>
175
- <Tab>
176
-
177
- ```python
178
- from assistant_stream.serialization import DataStreamResponse
179
- from assistant_stream import RunController, create_run
180
-
181
- @app.post("/assistant")
182
- async def chat_endpoint(request: ChatRequest):
183
- """Chat endpoint with custom agent streaming."""
184
-
185
- async def run_callback(controller: RunController):
186
- # Initialize controller state
187
- if controller.state is None:
188
- controller.state = {"messages": []}
189
-
190
- # Process commands
191
- for command in request.commands:
192
- if command.type == "add-message":
193
- # Add message to messages array
194
- controller.state["messages"].append(command.message)
195
-
196
- # Run your custom agent and stream updates
197
- async for message in your_agent.stream():
198
- # Push message to messages array
199
- controller.state["messages"].append(message)
200
-
201
- # Create streaming response
202
- stream = create_run(run_callback, state=request.state)
203
- return DataStreamResponse(stream)
204
- ```
205
-
206
- </Tab>
207
- <Tab>
208
-
209
- ```python
210
- from assistant_stream.serialization import DataStreamResponse
211
- from assistant_stream import RunController, create_run
212
- from assistant_stream.modules.langgraph import append_langgraph_event
213
-
214
- @app.post("/assistant")
215
- async def chat_endpoint(request: ChatRequest):
216
- """Chat endpoint using LangGraph with streaming."""
217
-
218
- async def run_callback(controller: RunController):
219
- # Initialize controller state
220
- if controller.state is None:
221
- controller.state = {}
222
- if "messages" not in controller.state:
223
- controller.state["messages"] = []
224
-
225
- input_messages = []
226
-
227
- # Process commands
228
- for command in request.commands:
229
- if command.type == "add-message":
230
- text_parts = [
231
- part.text for part in command.message.parts
232
- if part.type == "text" and part.text
233
- ]
234
- if text_parts:
235
- input_messages.append(HumanMessage(content=" ".join(text_parts)))
236
-
237
- # Create initial state for LangGraph
238
- input_state = {"messages": input_messages}
239
-
240
- # Stream events from LangGraph
241
- async for namespace, event_type, chunk in graph.astream(
242
- input_state,
243
- stream_mode=["messages", "updates"],
244
- subgraphs=True
245
- ):
246
- append_langgraph_event(
247
- controller.state,
248
- namespace,
249
- event_type,
250
- chunk
251
- )
252
-
253
- # Create streaming response
254
- stream = create_run(run_callback, state=request.state)
255
- return DataStreamResponse(stream)
256
- ```
257
-
258
- </Tab>
259
- </Tabs>
260
-
261
- Full example: [`python/assistant-transport-backend-langgraph`](https://github.com/assistant-ui/assistant-ui/tree/main/python/assistant-transport-backend-langgraph)
262
-
263
- ## Streaming Protocol
264
-
265
- The assistant-stream state replication protocol allows for streaming updates to an arbitrary JSON object.
266
-
267
- ### Operations
268
-
269
- The protocol supports two operations:
270
-
271
- > **Note:** We've found that these two operations are enough to handle all sorts of complex state operations efficiently. `set` handles value updates and nested structures, while `append-text` enables efficient streaming of text content.
272
-
273
- #### `set`
274
-
275
- Sets a value at a specific path in the JSON object.
276
-
277
- ```json
278
- // Operation
279
- { "type": "set", "path": ["status"], "value": "completed" }
280
-
281
- // Before
282
- { "status": "pending" }
283
-
284
- // After
285
- { "status": "completed" }
286
- ```
287
-
288
- #### `append-text`
289
-
290
- Appends text to an existing string value at a path.
291
-
292
- ```json
293
- // Operation
294
- { "type": "append-text", "path": ["message"], "value": " World" }
295
-
296
- // Before
297
- { "message": "Hello" }
298
-
299
- // After
300
- { "message": "Hello World" }
301
- ```
302
-
303
- ### Wire Format
304
-
305
- <Callout type="warn">
306
- The wire format will be migrated to Server-Sent Events (SSE) in a future
307
- release.
308
- </Callout>
309
-
310
- The wire format is inspired by [AI SDK's data stream protocol](https://sdk.vercel.ai/docs/ai-sdk-ui/stream-protocol).
311
-
312
- **State Update:**
313
-
314
- ```
315
- aui-state:ObjectStreamOperation[]
316
- ```
317
-
318
- ```
319
- aui-state:[{"type":"set","path":["status"],"value":"completed"}]
320
- ```
321
-
322
- **Error:**
323
-
324
- ```
325
- 3:string
326
- ```
327
-
328
- ```
329
- 3:"error message"
330
- ```
331
-
332
- ## Building a Frontend
333
-
334
- Now let's set up the frontend. The state converter is the heart of the integration—it transforms your agent's state into the format assistant-ui expects.
335
-
336
- The `useAssistantTransportRuntime` hook is used to configure the runtime. It accepts the following config:
337
-
338
- ```typescript
339
- {
340
- initialState: T,
341
- api: string,
342
- resumeApi?: string,
343
- protocol?: "data-stream" | "assistant-transport",
344
- converter: (state: T, connectionMetadata: ConnectionMetadata) => AssistantTransportState,
345
- headers: Record<string, string> | Headers | (() => Promise<Record<string, string> | Headers>),
346
- body?: object | (() => Promise<object | undefined>),
347
- prepareSendCommandsRequest?: (body: SendCommandsRequestBody) => Record<string, unknown> | Promise<Record<string, unknown>>,
348
- capabilities?: { edit?: boolean },
349
- adapters?: { attachments?: AttachmentAdapter; history?: ThreadHistoryAdapter },
350
- onResponse?: (response: Response) => void,
351
- onFinish?: () => void,
352
- onError?: (error: Error, params: { commands: AssistantTransportCommand[]; updateState: (updater: (state: T) => T) => void }) => void | Promise<void>,
353
- onCancel?: (params: { commands: AssistantTransportCommand[]; updateState: (updater: (state: T) => T) => void; error?: Error }) => void
354
- }
355
- ```
356
-
357
- ### State Converter
358
-
359
- The state converter is the core of your frontend integration. It transforms your agent's state into assistant-ui's message format.
360
-
361
- ```typescript
362
- (
363
- state: T, // Your agent's state
364
- connectionMetadata: {
365
- pendingCommands: Command[], // Commands not yet sent to backend
366
- isSending: boolean, // Whether a request is in flight
367
- toolStatuses: Record<string, ToolExecutionStatus> // Tool execution status tracking
368
- }
369
- ) => {
370
- messages: ThreadMessage[], // Messages to display
371
- isRunning: boolean, // Whether the agent is running
372
- state?: ReadonlyJSONValue // Optional custom agent state
373
- }
374
- ```
375
-
376
- ### Converting Messages
377
-
378
- Use the `createMessageConverter` API to transform your agent's messages to assistant-ui format:
379
-
380
- <Tabs items={["Example", "LangChain"]}>
381
- <Tab>
382
-
383
- ```typescript
384
- import { unstable_createMessageConverter as createMessageConverter } from "@assistant-ui/react";
385
-
386
- // Define your message type
387
- type YourMessageType = {
388
- id: string;
389
- role: "user" | "assistant";
390
- content: string;
391
- timestamp: number;
392
- };
393
-
394
- // Define a converter function for a single message
395
- const exampleMessageConverter = (message: YourMessageType) => {
396
- // Transform a single message to assistant-ui format
397
- return {
398
- role: message.role,
399
- content: [{ type: "text", text: message.content }]
400
- };
401
- };
402
-
403
- const messageConverter = createMessageConverter(exampleMessageConverter);
404
-
405
- const converter = (state: YourAgentState) => {
406
- return {
407
- messages: messageConverter.toThreadMessages(state.messages),
408
- isRunning: false
409
- };
410
- };
411
- ```
412
-
413
- </Tab>
414
- <Tab>
415
-
416
- ```typescript
417
- import { unstable_createMessageConverter as createMessageConverter } from "@assistant-ui/react";
418
- import { convertLangChainMessages } from "@assistant-ui/react-langgraph";
419
-
420
- const messageConverter = createMessageConverter(convertLangChainMessages);
421
-
422
- const converter = (state: YourAgentState) => {
423
- return {
424
- messages: messageConverter.toThreadMessages(state.messages),
425
- isRunning: false
426
- };
427
- };
428
- ```
429
-
430
- </Tab>
431
- </Tabs>
432
-
433
- **Reverse mapping:**
434
-
435
- The message converter allows you to retrieve the original message format anywhere inside assistant-ui. This lets you access your agent's native message structure from any assistant-ui component:
436
-
437
- ```typescript
438
- // Get original message(s) from a ThreadMessage anywhere in assistant-ui
439
- const originalMessage = messageConverter.toOriginalMessage(threadMessage);
440
- ```
441
-
442
- ### Optimistic Updates from Commands
443
-
444
- The converter also receives `connectionMetadata` which contains pending commands. Use this to show optimistic updates:
445
-
446
- ```typescript
447
- const converter = (state: State, connectionMetadata: ConnectionMetadata) => {
448
- // Extract pending messages from commands
449
- const optimisticMessages = connectionMetadata.pendingCommands
450
- .filter((c) => c.type === "add-message")
451
- .map((c) => c.message);
452
-
453
- return {
454
- messages: [...state.messages, ...optimisticMessages],
455
- isRunning: connectionMetadata.isSending || false
456
- };
457
- };
458
- ```
459
-
460
- ## Handling Errors and Cancellations
461
-
462
- The `onError` and `onCancel` callbacks receive an `updateState` function that allows you to update the agent state on the client side without making a server request:
463
-
464
- ```typescript
465
- const runtime = useAssistantTransportRuntime({
466
- // ... other options
467
- onError: (error, { commands, updateState }) => {
468
- console.error("Error occurred:", error);
469
- console.log("Commands in transit:", commands);
470
-
471
- // Update state to reflect the error
472
- updateState((currentState) => ({
473
- ...currentState,
474
- lastError: error.message,
475
- }));
476
- },
477
- onCancel: ({ commands, updateState }) => {
478
- console.log("Request cancelled");
479
- console.log("Commands (in-transit + queued, or queued-only if called after error):", commands);
480
-
481
- // Update state to reflect cancellation
482
- updateState((currentState) => ({
483
- ...currentState,
484
- status: "cancelled",
485
- }));
486
- },
487
- });
488
- ```
489
-
490
- > **Note:** `onError` receives commands that were in transit. `onCancel` receives both in-transit and queued commands when the user cancels directly; when called after an error, it only receives queued commands (in-transit commands are passed to `onError` instead).
491
-
492
- ## Custom Headers and Body
493
-
494
- You can pass custom headers and body to the backend endpoint:
495
-
496
- ```typescript
497
- const runtime = useAssistantTransportRuntime({
498
- // ... other options
499
- headers: {
500
- "Authorization": "Bearer token",
501
- "X-Custom-Header": "value",
502
- },
503
- body: {
504
- customField: "value",
505
- },
506
- });
507
- ```
508
-
509
- ### Dynamic Headers and Body
510
-
511
- You can also evaluate the header and body payloads on every request by passing an async function:
512
-
513
- ```typescript
514
- const runtime = useAssistantTransportRuntime({
515
- // ... other options
516
- headers: async () => ({
517
- "Authorization": `Bearer ${await getAccessToken()}`,
518
- "X-Request-ID": crypto.randomUUID(),
519
- }),
520
- body: async () => ({
521
- customField: "value",
522
- requestId: crypto.randomUUID(),
523
- timestamp: Date.now(),
524
- }),
525
- });
526
- ```
527
-
528
- ### Transforming the Request Body
529
-
530
- Use `prepareSendCommandsRequest` to transform the entire request body before it is sent to the backend. This receives the fully assembled body object and returns the (potentially transformed) body.
531
-
532
- ```typescript
533
- const runtime = useAssistantTransportRuntime({
534
- // ... other options
535
- prepareSendCommandsRequest: (body) => ({
536
- ...body,
537
- trackingId: crypto.randomUUID(),
538
- }),
539
- });
540
- ```
541
-
542
- This is useful for adding tracking IDs, transforming commands, or injecting metadata that depends on the assembled request:
543
-
544
- ```typescript
545
- const runtime = useAssistantTransportRuntime({
546
- // ... other options
547
- prepareSendCommandsRequest: (body) => ({
548
- ...body,
549
- commands: body.commands.map((cmd) =>
550
- cmd.type === "add-message"
551
- ? { ...cmd, trackingId: crypto.randomUUID() }
552
- : cmd,
553
- ),
554
- }),
555
- });
556
- ```
557
-
558
- ## Editing Messages
559
-
560
- By default, editing messages is disabled. To enable it, set `capabilities.edit` to `true`:
561
-
562
- ```typescript
563
- const runtime = useAssistantTransportRuntime({
564
- // ... other options
565
- capabilities: {
566
- edit: true,
567
- },
568
- });
569
- ```
570
-
571
- `add-message` commands always include `parentId` and `sourceId` fields:
572
-
573
- ```typescript
574
- {
575
- type: "add-message",
576
- message: { role: "user", parts: [...] },
577
- parentId: "msg-3", // The message after which this message should be inserted
578
- sourceId: "msg-4", // The ID of the message being replaced (null for new messages)
579
- }
580
- ```
581
-
582
- ### Backend Handling
583
-
584
- When the backend receives an `add-message` command with a `parentId`, it should:
585
-
586
- 1. Truncate all messages after the message with `parentId`
587
- 2. Append the new message
588
- 3. Stream the updated state back to the frontend
589
-
590
- ```python
591
- for command in request.commands:
592
- if command.type == "add-message":
593
- if hasattr(command, "parentId") and command.parentId is not None:
594
- # Find the parent message index and truncate
595
- parent_idx = next(
596
- i for i, m in enumerate(messages) if m.id == command.parentId
597
- )
598
- messages = messages[:parent_idx + 1]
599
- # Append the new message
600
- messages.append(command.message)
601
- ```
602
-
603
- <Callout type="info">
604
- `parentId` and `sourceId` are always included on `add-message` commands. For new messages, `sourceId` will be `null`.
605
- </Callout>
606
-
607
- ## Resuming from a Sync Server
608
-
609
- <Callout type="info">
610
- We provide a sync server currently only as part of the enterprise plan. Please
611
- contact us for more information.
612
- </Callout>
613
-
614
- When a user refreshes the page, switches tabs, or reconnects after a network interruption, the backend may still be generating a response. `resumeRun` allows the frontend to reconnect to the active backend stream.
615
-
616
- ### Setup
617
-
618
- Pass a `resumeApi` URL to `useAssistantTransportRuntime` that points to your sync server:
619
-
620
- ```typescript
621
- const runtime = useAssistantTransportRuntime({
622
- // ... other options
623
- api: "http://localhost:8010/assistant",
624
- resumeApi: "http://localhost:8010/resume", // Sync server endpoint
625
- });
626
- ```
627
-
628
- ### Resuming on thread switch or page load
629
-
630
- When switching to a thread or mounting a component, check if the backend is still running and call `resumeRun`:
631
-
632
- ```typescript
633
- import { useAui } from "@assistant-ui/react";
634
- import { useEffect, useRef } from "react";
635
-
636
- function useResumeOnMount(threadId: string) {
637
- const aui = useAui();
638
- const hasCheckedRef = useRef(false);
639
-
640
- useEffect(() => {
641
- if (hasCheckedRef.current) return;
642
- hasCheckedRef.current = true;
643
-
644
- const checkAndResume = async () => {
645
- const status = await fetch(
646
- `/api/sync-server/status/${threadId}`,
647
- ).then((r) => r.json());
648
-
649
- if (status.isRunning) {
650
- const parentId =
651
- aui.thread().getState().messages.at(-1)?.id ?? null;
652
- aui.thread().resumeRun({ parentId });
653
- }
654
- };
655
-
656
- checkAndResume();
657
- }, [aui, threadId]);
658
- }
659
- ```
660
-
661
- For the AssistantTransport runtime, you do not need to pass a `stream` parameter — the runtime uses the configured `resumeApi` endpoint to reconnect.
662
-
663
- ## Accessing Runtime State
664
-
665
- Use the `useAssistantTransportState` hook to access the current agent state from any component:
666
-
667
- ```typescript
668
- import { useAssistantTransportState } from "@assistant-ui/react";
669
-
670
- function MyComponent() {
671
- const state = useAssistantTransportState();
672
-
673
- return <div>{JSON.stringify(state)}</div>;
674
- }
675
- ```
676
-
677
- You can also pass a selector function to extract specific values:
678
-
679
- ```typescript
680
- function MyComponent() {
681
- const messages = useAssistantTransportState((state) => state.messages);
682
-
683
- return <div>Message count: {messages.length}</div>;
684
- }
685
- ```
686
-
687
- ### Type Safety
688
-
689
- Use module augmentation to add types for your agent state:
690
-
691
- ```typescript title="assistant.config.ts"
692
- import "@assistant-ui/react";
693
-
694
- declare module "@assistant-ui/react" {
695
- namespace Assistant {
696
- interface ExternalState {
697
- myState: {
698
- messages: Message[];
699
- customField: string;
700
- };
701
- }
702
- }
703
- }
704
- ```
705
-
706
- > **Note:** Place this file anywhere in your project (e.g., `src/assistant.config.ts` or at the project root). TypeScript will automatically pick up the type augmentation through module resolution—you don't need to import this file anywhere.
707
-
708
- After adding the type augmentation, `useAssistantTransportState` will be fully typed:
709
-
710
- ```typescript
711
- function MyComponent() {
712
- // TypeScript knows about your custom fields
713
- const customField = useAssistantTransportState((state) => state.customField);
714
-
715
- return <div>{customField}</div>;
716
- }
717
- ```
718
-
719
- ### Accessing the Original Message
720
-
721
- If you're using `createMessageConverter`, you can access the original message format from any assistant-ui component using the converter's `toOriginalMessage` method:
722
-
723
- ```typescript
724
- import { unstable_createMessageConverter as createMessageConverter } from "@assistant-ui/react";
725
- import { useAuiState } from "@assistant-ui/react";
726
-
727
- const messageConverter = createMessageConverter(yourMessageConverter);
728
-
729
- function MyMessageComponent() {
730
- const message = useAuiState((s) => s.message);
731
-
732
- // Get the original message(s) from the converted ThreadMessage
733
- const originalMessage = messageConverter.toOriginalMessage(message);
734
-
735
- // Access your agent's native message structure
736
- return <div>{originalMessage.yourCustomField}</div>;
737
- }
738
- ```
739
-
740
- You can also use `toOriginalMessages` to get all original messages when a ThreadMessage was created from multiple source messages:
741
-
742
- ```typescript
743
- const originalMessages = messageConverter.toOriginalMessages(message);
744
- ```
745
-
746
- ## Frontend Reference Implementation
747
-
748
- <Tabs items={["Example", "LangGraph"]}>
749
- <Tab>
750
-
751
- ```tsx
752
- "use client";
753
-
754
- import {
755
- AssistantRuntimeProvider,
756
- AssistantTransportConnectionMetadata,
757
- useAssistantTransportRuntime,
758
- } from "@assistant-ui/react";
759
-
760
- type State = {
761
- messages: Message[];
762
- };
763
-
764
- // Converter function: transforms agent state to assistant-ui format
765
- const converter = (
766
- state: State,
767
- connectionMetadata: AssistantTransportConnectionMetadata,
768
- ) => {
769
- // Add optimistic updates for pending commands
770
- const optimisticMessages = connectionMetadata.pendingCommands
771
- .filter((c) => c.type === "add-message")
772
- .map((c) => c.message);
773
-
774
- return {
775
- messages: [...state.messages, ...optimisticMessages],
776
- isRunning: connectionMetadata.isSending || false,
777
- };
778
- };
779
-
780
- export function MyRuntimeProvider({ children }) {
781
- const runtime = useAssistantTransportRuntime({
782
- initialState: {
783
- messages: [],
784
- },
785
- api: "http://localhost:8010/assistant",
786
- converter,
787
- headers: async () => ({
788
- "Authorization": "Bearer token",
789
- }),
790
- body: {
791
- "custom-field": "custom-value",
792
- },
793
- onResponse: (response) => {
794
- console.log("Response received from server");
795
- },
796
- onFinish: () => {
797
- console.log("Conversation completed");
798
- },
799
- onError: (error, { commands, updateState }) => {
800
- console.error("Assistant transport error:", error);
801
- console.log("Commands in transit:", commands);
802
- },
803
- onCancel: ({ commands, updateState }) => {
804
- console.log("Request cancelled");
805
- console.log("Commands (in-transit + queued, or queued-only if called after error):", commands);
806
- },
807
- });
808
-
809
- return (
810
- <AssistantRuntimeProvider runtime={runtime}>
811
- {children}
812
- </AssistantRuntimeProvider>
813
- );
814
- }
815
- ```
816
-
817
- </Tab>
818
- <Tab>
819
-
820
- ```tsx
821
- "use client";
822
-
823
- import {
824
- AssistantRuntimeProvider,
825
- AssistantTransportConnectionMetadata,
826
- unstable_createMessageConverter as createMessageConverter,
827
- useAssistantTransportRuntime,
828
- } from "@assistant-ui/react";
829
- import {
830
- convertLangChainMessages,
831
- LangChainMessage,
832
- } from "@assistant-ui/react-langgraph";
833
-
834
- type State = {
835
- messages: LangChainMessage[];
836
- };
837
-
838
- const LangChainMessageConverter = createMessageConverter(
839
- convertLangChainMessages,
840
- );
841
-
842
- // Converter function: transforms agent state to assistant-ui format
843
- const converter = (
844
- state: State,
845
- connectionMetadata: AssistantTransportConnectionMetadata,
846
- ) => {
847
- // Add optimistic updates for pending commands
848
- const optimisticStateMessages = connectionMetadata.pendingCommands.map(
849
- (c): LangChainMessage[] => {
850
- if (c.type === "add-message") {
851
- return [
852
- {
853
- type: "human" as const,
854
- content: [
855
- {
856
- type: "text" as const,
857
- text: c.message.parts
858
- .map((p) => (p.type === "text" ? p.text : ""))
859
- .join("\n"),
860
- },
861
- ],
862
- },
863
- ];
864
- }
865
- return [];
866
- },
867
- );
868
-
869
- const messages = [...state.messages, ...optimisticStateMessages.flat()];
870
-
871
- return {
872
- messages: LangChainMessageConverter.toThreadMessages(messages),
873
- isRunning: connectionMetadata.isSending || false,
874
- };
875
- };
876
-
877
- export function MyRuntimeProvider({ children }) {
878
- const runtime = useAssistantTransportRuntime({
879
- initialState: {
880
- messages: [],
881
- },
882
- api: "http://localhost:8010/assistant",
883
- converter,
884
- headers: async () => ({
885
- "Authorization": "Bearer token",
886
- }),
887
- body: {
888
- "custom-field": "custom-value",
889
- },
890
- onResponse: (response) => {
891
- console.log("Response received from server");
892
- },
893
- onFinish: () => {
894
- console.log("Conversation completed");
895
- },
896
- onError: (error, { commands, updateState }) => {
897
- console.error("Assistant transport error:", error);
898
- console.log("Commands in transit:", commands);
899
- },
900
- onCancel: ({ commands, updateState }) => {
901
- console.log("Request cancelled");
902
- console.log("Commands (in-transit + queued, or queued-only if called after error):", commands);
903
- },
904
- });
905
-
906
- return (
907
- <AssistantRuntimeProvider runtime={runtime}>
908
- {children}
909
- </AssistantRuntimeProvider>
910
- );
911
- }
912
- ```
913
-
914
- </Tab>
915
- </Tabs>
916
-
917
- Full example: [`examples/with-assistant-transport`](https://github.com/assistant-ui/assistant-ui/tree/main/examples/with-assistant-transport)
918
-
919
- ## Custom Commands
920
-
921
- ### Defining Custom Commands
922
-
923
- Use module augmentation to define a custom command:
924
-
925
- ```typescript title="assistant.config.ts"
926
- import "@assistant-ui/react";
927
-
928
- declare module "@assistant-ui/react" {
929
- namespace Assistant {
930
- interface Commands {
931
- myCustomCommand: {
932
- type: "my-custom-command";
933
- data: string;
934
- };
935
- }
936
- }
937
- }
938
- ```
939
-
940
- ### Issuing Commands
941
-
942
- Use the `useAssistantTransportSendCommand` hook to send custom commands:
943
-
944
- ```typescript
945
- import { useAssistantTransportSendCommand } from "@assistant-ui/react";
946
-
947
- function MyComponent() {
948
- const sendCommand = useAssistantTransportSendCommand();
949
-
950
- const handleClick = () => {
951
- sendCommand({
952
- type: "my-custom-command",
953
- data: "Hello, world!",
954
- });
955
- };
956
-
957
- return <button onClick={handleClick}>Send Custom Command</button>;
958
- }
959
- ```
960
-
961
- ### Backend Integration
962
-
963
- The backend receives custom commands in the `commands` array, just like built-in commands:
964
-
965
- ```python
966
- for command in request.commands:
967
- if command.type == "add-message":
968
- # Handle add-message command
969
- elif command.type == "add-tool-result":
970
- # Handle add-tool-result command
971
- elif command.type == "my-custom-command":
972
- # Handle your custom command
973
- data = command.data
974
- ```
975
-
976
- ### Optimistic Updates
977
-
978
- Update the [state converter](#state-converter) to optimistically handle the custom command:
979
-
980
- ```typescript
981
- const converter = (state: State, connectionMetadata: ConnectionMetadata) => {
982
- // Filter custom commands from pending commands
983
- const customCommands = connectionMetadata.pendingCommands.filter(
984
- (c) => c.type === "my-custom-command"
985
- );
986
-
987
- // Apply optimistic updates based on custom commands
988
- const optimisticState = {
989
- ...state,
990
- customData: customCommands.map((c) => c.data),
991
- };
992
-
993
- return {
994
- messages: state.messages,
995
- state: optimisticState,
996
- isRunning: connectionMetadata.isSending || false,
997
- };
998
- };
999
- ```
1000
-
1001
- ### Cancellation and Error Behavior
1002
-
1003
- Custom commands follow the same lifecycle as built-in commands. You can update your `onError` and `onCancel` handlers to take custom commands into account:
1004
-
1005
- ```typescript
1006
- const runtime = useAssistantTransportRuntime({
1007
- // ... other options
1008
- onError: (error, { commands, updateState }) => {
1009
- // Check if any custom commands were in transit
1010
- const customCommands = commands.filter((c) => c.type === "my-custom-command");
1011
-
1012
- if (customCommands.length > 0) {
1013
- // Handle custom command errors
1014
- updateState((state) => ({
1015
- ...state,
1016
- customCommandFailed: true,
1017
- }));
1018
- }
1019
- },
1020
- onCancel: ({ commands, updateState }) => {
1021
- // Check if any custom commands were queued or in transit
1022
- const customCommands = commands.filter((c) => c.type === "my-custom-command");
1023
-
1024
- if (customCommands.length > 0) {
1025
- // Handle custom command cancellation
1026
- updateState((state) => ({
1027
- ...state,
1028
- customCommandCancelled: true,
1029
- }));
1030
- }
1031
- },
1032
- });
1033
- ```