@multiplayer-app/ai-agent-react 0.0.1-beta.1

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 (139) hide show
  1. package/README.md +453 -0
  2. package/dist/ai-agent-react.cjs.js +8623 -0
  3. package/dist/ai-agent-react.cjs.js.map +1 -0
  4. package/dist/ai-agent-react.es.js +92883 -0
  5. package/dist/ai-agent-react.es.js.map +1 -0
  6. package/dist/components/Attachments/Attachments.d.ts +9 -0
  7. package/dist/components/Attachments/Attachments.d.ts.map +1 -0
  8. package/dist/components/Attachments/index.d.ts +2 -0
  9. package/dist/components/Attachments/index.d.ts.map +1 -0
  10. package/dist/components/ChatWindow/AgentControls/AgentControls.d.ts +5 -0
  11. package/dist/components/ChatWindow/AgentControls/AgentControls.d.ts.map +1 -0
  12. package/dist/components/ChatWindow/AgentControls/index.d.ts +4 -0
  13. package/dist/components/ChatWindow/AgentControls/index.d.ts.map +1 -0
  14. package/dist/components/ChatWindow/Attachments/AttachmentChips.d.ts +1 -0
  15. package/dist/components/ChatWindow/Attachments/AttachmentChips.d.ts.map +1 -0
  16. package/dist/components/ChatWindow/ChatContentContainer/ChatContentContainer.d.ts +7 -0
  17. package/dist/components/ChatWindow/ChatContentContainer/ChatContentContainer.d.ts.map +1 -0
  18. package/dist/components/ChatWindow/ChatContentContainer/index.d.ts +4 -0
  19. package/dist/components/ChatWindow/ChatContentContainer/index.d.ts.map +1 -0
  20. package/dist/components/ChatWindow/ChatHistoryPanel/ChatHistory/ChatHistory.d.ts +3 -0
  21. package/dist/components/ChatWindow/ChatHistoryPanel/ChatHistory/ChatHistory.d.ts.map +1 -0
  22. package/dist/components/ChatWindow/ChatHistoryPanel/ChatHistory/indext.d.ts +2 -0
  23. package/dist/components/ChatWindow/ChatHistoryPanel/ChatHistory/indext.d.ts.map +1 -0
  24. package/dist/components/ChatWindow/ChatHistoryPanel/ChatHistoryPanel.d.ts +5 -0
  25. package/dist/components/ChatWindow/ChatHistoryPanel/ChatHistoryPanel.d.ts.map +1 -0
  26. package/dist/components/ChatWindow/ChatHistoryPanel/index.d.ts +4 -0
  27. package/dist/components/ChatWindow/ChatHistoryPanel/index.d.ts.map +1 -0
  28. package/dist/components/ChatWindow/ChatWindow.d.ts +7 -0
  29. package/dist/components/ChatWindow/ChatWindow.d.ts.map +1 -0
  30. package/dist/components/ChatWindow/Composer/Composer.d.ts +7 -0
  31. package/dist/components/ChatWindow/Composer/Composer.d.ts.map +1 -0
  32. package/dist/components/ChatWindow/Composer/ComposerInputRow.d.ts +15 -0
  33. package/dist/components/ChatWindow/Composer/ComposerInputRow.d.ts.map +1 -0
  34. package/dist/components/ChatWindow/Composer/ComposerQueue.d.ts +10 -0
  35. package/dist/components/ChatWindow/Composer/ComposerQueue.d.ts.map +1 -0
  36. package/dist/components/ChatWindow/Composer/ModelSelector.d.ts +3 -0
  37. package/dist/components/ChatWindow/Composer/ModelSelector.d.ts.map +1 -0
  38. package/dist/components/ChatWindow/Composer/index.d.ts +3 -0
  39. package/dist/components/ChatWindow/Composer/index.d.ts.map +1 -0
  40. package/dist/components/ChatWindow/Composer/types.d.ts +13 -0
  41. package/dist/components/ChatWindow/Composer/types.d.ts.map +1 -0
  42. package/dist/components/ChatWindow/ContextControls/ContextControls.d.ts +3 -0
  43. package/dist/components/ChatWindow/ContextControls/ContextControls.d.ts.map +1 -0
  44. package/dist/components/ChatWindow/ContextControls/index.d.ts +2 -0
  45. package/dist/components/ChatWindow/ContextControls/index.d.ts.map +1 -0
  46. package/dist/components/ChatWindow/MessageList/Message/MarkdownComponents.d.ts +4 -0
  47. package/dist/components/ChatWindow/MessageList/Message/MarkdownComponents.d.ts.map +1 -0
  48. package/dist/components/ChatWindow/MessageList/Message/MessageActions.d.ts +7 -0
  49. package/dist/components/ChatWindow/MessageList/Message/MessageActions.d.ts.map +1 -0
  50. package/dist/components/ChatWindow/MessageList/Message/MessageAttachments.d.ts +7 -0
  51. package/dist/components/ChatWindow/MessageList/Message/MessageAttachments.d.ts.map +1 -0
  52. package/dist/components/ChatWindow/MessageList/Message/MessageAvatar.d.ts +9 -0
  53. package/dist/components/ChatWindow/MessageList/Message/MessageAvatar.d.ts.map +1 -0
  54. package/dist/components/ChatWindow/MessageList/Message/MessageBubble.d.ts +8 -0
  55. package/dist/components/ChatWindow/MessageList/Message/MessageBubble.d.ts.map +1 -0
  56. package/dist/components/ChatWindow/MessageList/Message/MessageCodeBlock.d.ts +8 -0
  57. package/dist/components/ChatWindow/MessageList/Message/MessageCodeBlock.d.ts.map +1 -0
  58. package/dist/components/ChatWindow/MessageList/Message/MessageContent.d.ts +9 -0
  59. package/dist/components/ChatWindow/MessageList/Message/MessageContent.d.ts.map +1 -0
  60. package/dist/components/ChatWindow/MessageList/Message/MessageHeader.d.ts +10 -0
  61. package/dist/components/ChatWindow/MessageList/Message/MessageHeader.d.ts.map +1 -0
  62. package/dist/components/ChatWindow/MessageList/Message/MessageTypingAnimation.d.ts +3 -0
  63. package/dist/components/ChatWindow/MessageList/Message/MessageTypingAnimation.d.ts.map +1 -0
  64. package/dist/components/ChatWindow/MessageList/Message/ReasoningPanel.d.ts +11 -0
  65. package/dist/components/ChatWindow/MessageList/Message/ReasoningPanel.d.ts.map +1 -0
  66. package/dist/components/ChatWindow/MessageList/Message/index.d.ts +7 -0
  67. package/dist/components/ChatWindow/MessageList/Message/index.d.ts.map +1 -0
  68. package/dist/components/ChatWindow/MessageList/MessageGroup.d.ts +10 -0
  69. package/dist/components/ChatWindow/MessageList/MessageGroup.d.ts.map +1 -0
  70. package/dist/components/ChatWindow/MessageList/MessageList.d.ts +3 -0
  71. package/dist/components/ChatWindow/MessageList/MessageList.d.ts.map +1 -0
  72. package/dist/components/ChatWindow/MessageList/ToolCallCard.d.ts +10 -0
  73. package/dist/components/ChatWindow/MessageList/ToolCallCard.d.ts.map +1 -0
  74. package/dist/components/ChatWindow/MessageList/index.d.ts +3 -0
  75. package/dist/components/ChatWindow/MessageList/index.d.ts.map +1 -0
  76. package/dist/components/ChatWindow/ToolShelf/ToolShelf.d.ts +8 -0
  77. package/dist/components/ChatWindow/ToolShelf/ToolShelf.d.ts.map +1 -0
  78. package/dist/components/ChatWindow/ToolShelf/index.d.ts +3 -0
  79. package/dist/components/ChatWindow/ToolShelf/index.d.ts.map +1 -0
  80. package/dist/components/ChatWindow/index.d.ts +7 -0
  81. package/dist/components/ChatWindow/index.d.ts.map +1 -0
  82. package/dist/components/ui/CopyButton/CopyButton.d.ts +6 -0
  83. package/dist/components/ui/CopyButton/CopyButton.d.ts.map +1 -0
  84. package/dist/components/ui/CopyButton/index.d.ts +2 -0
  85. package/dist/components/ui/CopyButton/index.d.ts.map +1 -0
  86. package/dist/components/ui/Dropdown/Dropdown.d.ts +25 -0
  87. package/dist/components/ui/Dropdown/Dropdown.d.ts.map +1 -0
  88. package/dist/components/ui/Dropdown/index.d.ts +2 -0
  89. package/dist/components/ui/Dropdown/index.d.ts.map +1 -0
  90. package/dist/components/ui/IconButton/IconButton.d.ts +18 -0
  91. package/dist/components/ui/IconButton/IconButton.d.ts.map +1 -0
  92. package/dist/components/ui/IconButton/index.d.ts +2 -0
  93. package/dist/components/ui/IconButton/index.d.ts.map +1 -0
  94. package/dist/config/types.d.ts +523 -0
  95. package/dist/config/types.d.ts.map +1 -0
  96. package/dist/context/AgentProvider.d.ts +13 -0
  97. package/dist/context/AgentProvider.d.ts.map +1 -0
  98. package/dist/context/SocketProvider.d.ts +14 -0
  99. package/dist/context/SocketProvider.d.ts.map +1 -0
  100. package/dist/hooks/useAutoScroll.d.ts +42 -0
  101. package/dist/hooks/useAutoScroll.d.ts.map +1 -0
  102. package/dist/hooks/useChatBootstrap.d.ts +2 -0
  103. package/dist/hooks/useChatBootstrap.d.ts.map +1 -0
  104. package/dist/hooks/useChatSendController.d.ts +18 -0
  105. package/dist/hooks/useChatSendController.d.ts.map +1 -0
  106. package/dist/hooks/useDeleteChat.d.ts +6 -0
  107. package/dist/hooks/useDeleteChat.d.ts.map +1 -0
  108. package/dist/hooks/useModels.d.ts +4 -0
  109. package/dist/hooks/useModels.d.ts.map +1 -0
  110. package/dist/index.d.ts +11 -0
  111. package/dist/index.d.ts.map +1 -0
  112. package/dist/runtime/AgentRuntime.d.ts +30 -0
  113. package/dist/runtime/AgentRuntime.d.ts.map +1 -0
  114. package/dist/runtime/ToolRegistry.d.ts +20 -0
  115. package/dist/runtime/ToolRegistry.d.ts.map +1 -0
  116. package/dist/runtime/transports/AgentTransport.d.ts +49 -0
  117. package/dist/runtime/transports/AgentTransport.d.ts.map +1 -0
  118. package/dist/runtime/transports/ChatStorage.d.ts +18 -0
  119. package/dist/runtime/transports/ChatStorage.d.ts.map +1 -0
  120. package/dist/runtime/transports/DirectTransport.d.ts +71 -0
  121. package/dist/runtime/transports/DirectTransport.d.ts.map +1 -0
  122. package/dist/runtime/transports/ProxyTransport.d.ts +46 -0
  123. package/dist/runtime/transports/ProxyTransport.d.ts.map +1 -0
  124. package/dist/runtime/types.d.ts +2 -0
  125. package/dist/runtime/types.d.ts.map +1 -0
  126. package/dist/state/useAgentStore.d.ts +61 -0
  127. package/dist/state/useAgentStore.d.ts.map +1 -0
  128. package/dist/style.css +10 -0
  129. package/dist/theme/AgentThemeProvider.d.ts +10 -0
  130. package/dist/theme/AgentThemeProvider.d.ts.map +1 -0
  131. package/dist/theme/defaultTheme.d.ts +5 -0
  132. package/dist/theme/defaultTheme.d.ts.map +1 -0
  133. package/dist/utils/guestUserId.d.ts +6 -0
  134. package/dist/utils/guestUserId.d.ts.map +1 -0
  135. package/dist/utils/modelSelection.d.ts +22 -0
  136. package/dist/utils/modelSelection.d.ts.map +1 -0
  137. package/dist/utils/normalizeUrl.d.ts +3 -0
  138. package/dist/utils/normalizeUrl.d.ts.map +1 -0
  139. package/package.json +77 -0
package/README.md ADDED
@@ -0,0 +1,453 @@
1
+ # ai-agent-react
2
+
3
+ Composable React/Vite component library that ships the Multiplayer AI Agents UI primitives – chat, model/context controls, tool discovery, reasoning trace, artifacts, and background-agent management hooks. The package is designed to drop into any React (or Vite) web-app and talk either to Multiplayer's proxy backend or directly to an LLM provider such as OpenRouter/OpenAI.
4
+
5
+ ## Highlights
6
+
7
+ - **Dual-transport runtime** – switch between `proxy` (Multiplayer backend) and `direct` (OpenRouter/OpenAI/custom) without touching UI code.
8
+ - **Context-aware UI** – bind chats to context keys that automatically flip tool availability, default models, and confirmation policies per page/feature.
9
+ - **Reasoning + artifacts** – dedicated sidebar surfaces thinking traces, background plans, and generated artifacts.
10
+ - **Tool registry** – declare tools once, reuse across contexts, and wire custom launchers or approval flows.
11
+ - **Theme tokens** – override a small set of CSS variables or wrap components with your own layout.
12
+ - **React Query + Zustand** – predictable caching/state that can be extended by host apps.
13
+
14
+ ## Installation
15
+
16
+ The package is managed inside the monorepo workspace. From the repo root:
17
+
18
+ ```bash
19
+ npm install
20
+ npm run dev -w ai-agent-react # optional preview sandbox
21
+ npm run build -w ai-agent-react
22
+ ```
23
+
24
+ ## Quick start
25
+
26
+ ```tsx
27
+ import { AgentProvider, AgentChatWindow } from 'ai-agent-react'
28
+
29
+ const config = {
30
+ appId: 'your-app-id',
31
+ workspaceId: 'workspace-123',
32
+ contextKeys: [
33
+ { key: 'global', label: 'Global', tools: ['kb-search', 'jira'] },
34
+ {
35
+ key: 'filing',
36
+ label: 'Filing Prep',
37
+ tools: ['kb-search', 'pdf-draft'],
38
+ defaultModel: 'gpt-4o'
39
+ }
40
+ ],
41
+ models: [
42
+ { id: 'gpt-4o', label: 'GPT-4o', reasoning: 'concise' },
43
+ { id: 'sonnet', label: 'Claude Sonnet', reasoning: 'deep' }
44
+ ],
45
+ tools: [
46
+ { name: 'kb-search', label: 'Knowledge Base' },
47
+ { name: 'jira', label: 'Jira' }
48
+ ],
49
+ transport: {
50
+ mode: 'proxy',
51
+ baseUrl: '/api' // never ship raw API keys to the browser
52
+ },
53
+ features: {
54
+ reasoning: 'panel',
55
+ artifactsPanel: true,
56
+ modelSwitching: true
57
+ }
58
+ }
59
+
60
+ export const YourAppAgent = () => (
61
+ <AgentProvider config={config}>
62
+ <AgentChatWindow />
63
+ </AgentProvider>
64
+ )
65
+ ```
66
+
67
+ ### Backend proxy example (Next.js / Route Handler)
68
+
69
+ ```ts
70
+ // app/api/agents/route.ts
71
+ import { NextResponse } from 'next/server'
72
+
73
+ const AGENT_API = 'https://agents.api.multiplayer.app'
74
+ const AGENT_KEY = process.env.AGENTS_API_KEY
75
+
76
+ export async function POST(req: Request) {
77
+ if (!AGENT_KEY) {
78
+ return NextResponse.json({ error: 'Missing AGENTS_API_KEY' }, { status: 500 })
79
+ }
80
+
81
+ const upstream = await fetch(`${AGENT_API}/proxy`, {
82
+ method: 'POST',
83
+ headers: {
84
+ 'content-type': 'application/json',
85
+ authorization: `Bearer ${AGENT_KEY}`
86
+ },
87
+ body: await req.text()
88
+ })
89
+
90
+ return NextResponse.json(await upstream.json(), { status: upstream.status })
91
+ }
92
+ ```
93
+
94
+ Host apps should hit `/api/agents` from the browser and keep provider credentials on the server (or issue short-lived tokens) so the public bundle never contains secrets.
95
+
96
+ ## Agent runtime loop
97
+
98
+ The UI is a thin layer over the runtime primitives inside `src/runtime`. Every send follows the same deterministic loop:
99
+
100
+ 1. **Payload assembly (`AgentComposer.tsx`)** – builds a `SendMessagePayload` with the active `contextKey`, composer overrides, and the existing `chatId`. Pending assistant state is queued in `AgentStore` so the transcript can optimistically render “Thinking…” bubbles.
101
+ 2. **Transport execution (`ProxyTransport` or `DirectTransport`)** – the runtime picks the correct transport based on `config.transport.mode`. `ProxyTransport` streams Server-Sent Events and enforces timeouts/headers; `DirectTransport` fabricates an in-memory chat, forwards OpenAI/OpenRouter style payloads, and stitches streaming deltas into a pending assistant message.
102
+ 3. **Store reconciliation (`useAgentStore`)** – incoming chunks get merged via `consumeStreamChunk`, reasoning traces are appended per chat, and once the transport resolves the final `AgentChat`, `upsertChat` hydrates the full transcript + artifacts.
103
+ 4. **Tool call hydration (`ToolCallCard.tsx`)** – any `AgentMessage.toolCalls` entries are rendered immediately. If you registered a renderer via `AgentRuntime.registerToolRenderer`, the UI swaps the JSON viewer for your custom card.
104
+
105
+ The runtime never mutates chats outside of these store actions, so you can safely swap in your own components as long as they call the same selectors/actions.
106
+
107
+ ### Sequence overview
108
+
109
+ ```
110
+ User input
111
+
112
+
113
+ AgentComposer ──┐
114
+ │ payload (`SendMessagePayload`)
115
+
116
+ AgentRuntime
117
+ │ picks transport
118
+
119
+ │──────────► ProxyTransport → Multiplayer backend (SSE/event stream)
120
+
121
+ └──────────► DirectTransport → Provider API (OpenAI/OpenRouter/custom)
122
+
123
+
124
+ AgentStore (Zustand)
125
+
126
+ ├─ updates chats/reasoning/tool calls
127
+ └─ exposes selectors
128
+
129
+
130
+ AgentMessageList / AgentToolShelf / AgentSidebar (render)
131
+ ```
132
+
133
+ ### Tool invocation flow
134
+
135
+ ```
136
+ User selects tool chip / runtime.tools.invoke(...)
137
+
138
+
139
+ AgentRuntime.invokeTool
140
+ │ validates chat context
141
+
142
+ ToolRegistry.execute
143
+ │ runs handler (client) or awaits backend status
144
+
145
+ AgentStore.upsertToolCall
146
+
147
+
148
+ ToolCallCard renderer
149
+ ```
150
+
151
+ ### Runtime bootstrap checklist
152
+
153
+ 1. **Config validation** – keep the `config` object colocated with a Zod schema (or reuse Multiplayer's built-in schema) so CI fails on breaking changes.
154
+ 2. **Transport secret management** – never inline API keys; wire them through env vars or a server-side token exchange endpoint.
155
+ 3. **Context discipline** – prefer <10 `contextKeys`. Each key should map to a clear product surface to avoid combinatorial explosion in tool policies.
156
+ 4. **Tool UX** – every tool entry needs `label`, `description`, and ideally `icon` + `confirmation`. Missing metadata means degraded UX.
157
+
158
+ ## Configuration surface
159
+
160
+ | Field | Purpose |
161
+ | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
162
+ | `appId` | Tenant/application identifier issued by Multiplayer. Used for auth and metrics. |
163
+ | `workspaceId` | Optional per-user workspace scoping. Leave undefined if the backend infers it from the session. |
164
+ | `contextKeys` | Array of `{ key, label, tools, defaultModel?, autoConfirmTools? }` describing per-surface policies. Mirrors backend `contextKey` rules. |
165
+ | `defaultContextKey` | Forces the initial chat context; otherwise the first item in `contextKeys` is used. |
166
+ | `tools` | Optional registry with metadata (`description`, `icon`, `category`, `confirmation`, `handler`). Powers tool shelves and runtime overrides. |
167
+ | `models` | Optional catalog with per-model reasoning depth + allowed contexts. Drives the model switcher + default-model hints. |
168
+ | `transport` | `{ mode: 'proxy', baseUrl, headers?, timeoutMs? }` to hit Multiplayer backend, or `{ mode: 'direct', provider, endpoint?, apiKey?, model? }` for raw LLMs. |
169
+ | `features` | Fine-grained toggles: reasoning panel location, artifact visibility, multi-agent controls, sandbox switches, etc. |
170
+ | `theme` | Partial overrides for the default theme tokens (background, accent, font, radius, etc.). |
171
+ | `toolRenderers` | Map tool names to React components when tool output needs rich visuals (tables, charts, custom viewers). |
172
+ | `user` | Optional `{ id, displayName, email, avatarUrl }` shape. If provided, user metadata is attached to tool calls/history. |
173
+
174
+ All configs are validated through `zod` at runtime, so invalid configurations fail fast.
175
+
176
+ ## Theming
177
+
178
+ ```tsx
179
+ <AgentProvider
180
+ config={{
181
+ ...config,
182
+ theme: { background: '#050b16', accent: '#2FE6FF', radius: 16 }
183
+ }}
184
+ >
185
+ <AgentChatWindow />
186
+ </AgentProvider>
187
+ ```
188
+
189
+ Under the hood `AgentThemeProvider` exposes CSS variables (`--mp-agents-*`). If your product already has a design system, wrap `AgentChatWindow` and override the variables to match the host theme.
190
+
191
+ ## Tooling integration
192
+
193
+ ### Tool type contract
194
+
195
+ Tool metadata is strongly typed and validated in `src/config/types.ts`:
196
+
197
+ ```ts
198
+ export interface AgentToolDefinition {
199
+ name: string
200
+ label: string
201
+ description?: string
202
+ icon?: string
203
+ schema?: Record<string, unknown>
204
+ confirmation?: 'auto' | 'manual'
205
+ category?: string
206
+ handler?: AgentToolInvoke
207
+ }
208
+ ```
209
+
210
+ ```ts
211
+ export type AgentToolCall = {
212
+ id: string
213
+ name: string
214
+ input: Record<string, unknown>
215
+ status: 'pending' | 'running' | 'succeeded' | 'failed'
216
+ output?: Record<string, unknown>
217
+ error?: string
218
+ requiresConfirmation?: boolean
219
+ }
220
+ ```
221
+
222
+ The UI never guesses fields—if `handler` is omitted, the tool is display-only; if the backend emits `requiresConfirmation`, the shelf can branch into your own approval UX.
223
+
224
+ ### Tool lifecycle (single agent)
225
+
226
+ 1. `AgentToolShelf` filters the runtime registry against the active `contextKey -> tools` list. Chips are pure metadata.
227
+ 2. When the backend/LLM emits a tool call, the transport serializes it into `AgentMessage.toolCalls`. The store keeps those calls as-is (including `status`, `error`), which means you must propagate the tool status from the server.
228
+ 3. `ToolCallCard` asks `AgentRuntime` for a renderer. If none exists, it falls back to a JSON viewer so the transcript always stays auditable.
229
+ 4. If you register a `handler` via `runtime.tools.register`, the invocation happens purely on the client (synchronous or async) and the resulting `AgentToolCall` is returned to the caller. This is ideal for local helpers (opening drawers, querying browser APIs) but you should keep privileged work on the server.
230
+
231
+ ### Step 1 – Declare tools with rich metadata
232
+
233
+ ```ts
234
+ // tooling/registry.ts
235
+ import type { AgentToolDefinition } from 'ai-agent-react'
236
+
237
+ export const baseTools: AgentToolDefinition[] = [
238
+ {
239
+ name: 'kb-search',
240
+ label: 'Knowledge Base',
241
+ description: 'Semantic search across product docs',
242
+ icon: 'search',
243
+ confirmation: 'auto'
244
+ },
245
+ {
246
+ name: 'jira',
247
+ label: 'Jira Issues',
248
+ description: 'Open, triage, or update tickets',
249
+ icon: 'jira',
250
+ confirmation: 'manual'
251
+ }
252
+ ]
253
+ ```
254
+
255
+ Feed the array into `config.tools`. Context gating still happens via each `ContextKeyConfig.tools` array, so a tool never surfaces outside the contexts you list there.
256
+
257
+ To register ad-hoc client helpers, reach for `runtime.registerTool` (a thin wrapper over `ToolRegistry`):
258
+
259
+ ```ts
260
+ runtime.registerTool({
261
+ name: 'copy-last-answer',
262
+ label: 'Copy answer to clipboard',
263
+ handler: async (_input, ctx) => {
264
+ const chat = runtime.store.getState().chats[ctx.chatId]
265
+ const lastAssistant = chat?.messages
266
+ .slice()
267
+ .reverse()
268
+ .find((m) => m.role === 'assistant')
269
+ if (lastAssistant) {
270
+ await navigator.clipboard.writeText(lastAssistant.content)
271
+ ctx.appendSystemMessage('Copied last assistant response to clipboard.')
272
+ }
273
+ }
274
+ })
275
+ ```
276
+
277
+ ### Step 2 – Wire server handlers
278
+
279
+ ```ts
280
+ // pages/api/agents/tools/jira.ts
281
+ import type { NextApiRequest, NextApiResponse } from 'next'
282
+ import { jiraClient } from '@/lib/jira'
283
+
284
+ export default async function handler(req: NextApiRequest, res: NextApiResponse) {
285
+ if (req.method !== 'POST') return res.status(405).end()
286
+ const { input, contextKey, actor } = req.body
287
+
288
+ if (contextKey !== 'filing') {
289
+ return res.status(403).json({ error: 'Jira disabled for this context' })
290
+ }
291
+
292
+ try {
293
+ const issue = await jiraClient.search(input.query, { assignee: actor.id })
294
+ return res.json({
295
+ output: issue,
296
+ observations: [`Returned ${issue.total} issues`]
297
+ })
298
+ } catch (err) {
299
+ console.error(err)
300
+ return res.status(502).json({
301
+ error: 'Jira search failed',
302
+ observations: [err instanceof Error ? err.message : 'Unknown Jira error']
303
+ })
304
+ }
305
+ }
306
+ ```
307
+
308
+ When using the Multiplayer proxy, mirror the same policies server-side via your `/tools` definitions so the UI never drifts from backend enforcement.
309
+
310
+ ### Step 3 – Register runtime overrides
311
+
312
+ ```tsx
313
+ import { AgentProvider, AgentRuntime, AgentSidebar } from 'ai-agent-react'
314
+ import { baseTools } from './tooling/registry'
315
+
316
+ const runtime = new AgentRuntime({ ...config, tools: baseTools })
317
+ runtime.tools.register({
318
+ name: 'feature-flags',
319
+ label: 'Toggle FF',
320
+ description: 'Flip LaunchDarkly flags scoped to the current workspace',
321
+ onInvoke: async ({ input, context }) => {
322
+ try {
323
+ const resp = await fetch('/api/feature-flags/toggle', {
324
+ method: 'POST',
325
+ headers: { 'content-type': 'application/json' },
326
+ body: JSON.stringify({ ...input, context })
327
+ })
328
+
329
+ if (!resp.ok) {
330
+ throw new Error(`Toggle failed with ${resp.status}`)
331
+ }
332
+
333
+ return resp.json()
334
+ } catch (error) {
335
+ context.appendSystemMessage(`Feature-flag tool failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
336
+ throw error
337
+ }
338
+ }
339
+ })
340
+
341
+ export function AgentsPanel() {
342
+ return (
343
+ <AgentProvider runtime={runtime}>
344
+ <AgentSidebar />
345
+ </AgentProvider>
346
+ )
347
+ }
348
+ ```
349
+
350
+ `runtime.tools.register` accepts inline handlers (`onInvoke`) for front-end only tools (e.g., opening a modal, reading local storage) or can proxy to your server routes.
351
+
352
+ ### Step 4 – Custom launch & confirmation UX
353
+
354
+ `AgentToolShelf` exposes the selected `AgentToolDefinition` before execution so you can gate, hydrate, or redirect calls:
355
+
356
+ ```tsx
357
+ import { AgentToolShelf, useAgentRuntime } from 'ai-agent-react'
358
+
359
+ export const ToolShelfWithReview = () => {
360
+ const runtime = useAgentRuntime()
361
+
362
+ const handleToolSelected = async (tool) => {
363
+ if (tool.name === 'prod-deploy') {
364
+ const ok = window.confirm('Ship to production?')
365
+ if (!ok) return
366
+ }
367
+ runtime.tools.invoke(tool.name, { target: 'production' })
368
+ }
369
+
370
+ return <AgentToolShelf onSelectTool={handleToolSelected} />
371
+ }
372
+ ```
373
+
374
+ ### Tool output renderers
375
+
376
+ The transcript renders every tool call as a dedicated card. Provide custom components when you want richer visuals (charts, tables, external viewers):
377
+
378
+ ```tsx
379
+ import type { ToolRendererProps } from 'ai-agent-react';
380
+
381
+ const FilingChartRenderer = ({ call }: ToolRendererProps) => {
382
+ const rows = call.output?.rows as Array<{ label: string; value: number }>;
383
+ if (!rows) return null;
384
+ return (
385
+ <div style={{ display: 'flex', gap: 12 }}>
386
+ {rows.map((row) => (
387
+ <div key={row.label} style={{ flex: 1 }}>
388
+ <div style={{ fontSize: 12, opacity: 0.7 }}>{row.label}</div>
389
+ <div style={{ fontSize: 28, fontWeight: 600 }}>{row.value}</div>
390
+ </div>
391
+ ))}
392
+ </div>
393
+ );
394
+ };
395
+
396
+ const config = {
397
+ ...,
398
+ toolRenderers: {
399
+ 'filing-stats': FilingChartRenderer
400
+ }
401
+ };
402
+ ```
403
+
404
+ At runtime you can register or override a renderer:
405
+
406
+ ```ts
407
+ const runtime = new AgentRuntime(config)
408
+ runtime.registerToolRenderer('jira-search', JiraTableRenderer)
409
+ ```
410
+
411
+ If no renderer is provided, the library falls back to a JSON viewer so every tool call remains auditable.
412
+
413
+ ### Tool testing recipe
414
+
415
+ 1. Build a Storybook story with mocked `AgentRuntime` handlers (`msw` highly recommended).
416
+ 2. Record golden transcripts (JSON) and feed them into visual regression tests to lock UX.
417
+ 3. Exercise the same handler via Playwright/E2E in the host app to guarantee auth + transport policies stay aligned.
418
+
419
+ ### Failure & authorization contract
420
+
421
+ - Tool handlers **must** handle authorization before running side effects. Front-end context checks are advisory; the backend is the source of truth.
422
+ - Always wrap outbound API calls in `try/catch` and emit actionable `observations` so the agent transcript preserves failure context.
423
+ - Convert third-party errors to deterministic messages (`res.status(502).json({ error: 'Jira search failed' })`) to avoid leaking stack traces.
424
+ - From the runtime, bubble surfaced errors to the UI via `appendSystemMessage` or `runtime.store.setError(...)` so operators know a tool degraded.
425
+ - Return HTTP 4xx for policy violations and 5xx for execution failures; the Multiplayer proxy replays those codes directly to the UI.
426
+
427
+ ### Where this diverges from @openai/agents
428
+
429
+ - **Single-agent focus** – the React package ships UI primitives only. Handoffs/agent orchestration are expected to happen server-side; surface them to the UI by emitting intermediary messages or tool cards.
430
+ - **Transparent tool cards** – instead of wrapping tool execution in SDK magic, every tool call is rendered verbatim (input/output/error) unless you override it with a renderer. This favors auditing over abstraction.
431
+ - **Bring-your-own loop** – Multiplayer does not expose a runner like `run(agent, prompt)`. Your backend decides when to call tools, switch contexts, or stop the loop, and the UI simply mirrors whatever the transport returns.
432
+
433
+ If you need OpenAI-style agent handoffs, build them into your backend transport first—once your API emits the intermediate messages/tool calls, the React package will visualize them without extra work.
434
+
435
+ ## Direct vs proxy transports
436
+
437
+ | Mode | Recommended when | Notes |
438
+ | -------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
439
+ | `proxy` | The host app can reach Multiplayer's backend (preferred). | Enables background agents, artifacts, storage APIs, and server-side tool execution. |
440
+ | `direct` | On-prem or hybrid deployments that must talk straight to a provider. | The library stores chats in-memory and calls OpenRouter/OpenAI style APIs. Ideal for single-page use cases or POCs. |
441
+
442
+ ## Extending the UI
443
+
444
+ - Drop in your own message renderer by composing `AgentMessageList` and handing it custom markdown/render logic.
445
+ - Compose `AgentComposer`, `AgentToolShelf`, or `AgentSidebar` individually when embedding within an existing layout.
446
+ - Use `useAgentRuntime()` + `useAgentStore()` to build custom widgets (e.g., active-agent dashboards, analytics, or workspace switchers).
447
+
448
+ ## Roadmap
449
+
450
+ - SSE-first streaming pathway once the backend contract is finalized.
451
+ - Background agent monitor panel (list/stop/resume) backed by `/agents` APIs.
452
+ - Pluggable artifact viewers (render notebooks, diff viewers, etc.).
453
+ - Cypress/storybook fixtures to harden component behaviors.