@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.
- package/README.md +453 -0
- package/dist/ai-agent-react.cjs.js +8623 -0
- package/dist/ai-agent-react.cjs.js.map +1 -0
- package/dist/ai-agent-react.es.js +92883 -0
- package/dist/ai-agent-react.es.js.map +1 -0
- package/dist/components/Attachments/Attachments.d.ts +9 -0
- package/dist/components/Attachments/Attachments.d.ts.map +1 -0
- package/dist/components/Attachments/index.d.ts +2 -0
- package/dist/components/Attachments/index.d.ts.map +1 -0
- package/dist/components/ChatWindow/AgentControls/AgentControls.d.ts +5 -0
- package/dist/components/ChatWindow/AgentControls/AgentControls.d.ts.map +1 -0
- package/dist/components/ChatWindow/AgentControls/index.d.ts +4 -0
- package/dist/components/ChatWindow/AgentControls/index.d.ts.map +1 -0
- package/dist/components/ChatWindow/Attachments/AttachmentChips.d.ts +1 -0
- package/dist/components/ChatWindow/Attachments/AttachmentChips.d.ts.map +1 -0
- package/dist/components/ChatWindow/ChatContentContainer/ChatContentContainer.d.ts +7 -0
- package/dist/components/ChatWindow/ChatContentContainer/ChatContentContainer.d.ts.map +1 -0
- package/dist/components/ChatWindow/ChatContentContainer/index.d.ts +4 -0
- package/dist/components/ChatWindow/ChatContentContainer/index.d.ts.map +1 -0
- package/dist/components/ChatWindow/ChatHistoryPanel/ChatHistory/ChatHistory.d.ts +3 -0
- package/dist/components/ChatWindow/ChatHistoryPanel/ChatHistory/ChatHistory.d.ts.map +1 -0
- package/dist/components/ChatWindow/ChatHistoryPanel/ChatHistory/indext.d.ts +2 -0
- package/dist/components/ChatWindow/ChatHistoryPanel/ChatHistory/indext.d.ts.map +1 -0
- package/dist/components/ChatWindow/ChatHistoryPanel/ChatHistoryPanel.d.ts +5 -0
- package/dist/components/ChatWindow/ChatHistoryPanel/ChatHistoryPanel.d.ts.map +1 -0
- package/dist/components/ChatWindow/ChatHistoryPanel/index.d.ts +4 -0
- package/dist/components/ChatWindow/ChatHistoryPanel/index.d.ts.map +1 -0
- package/dist/components/ChatWindow/ChatWindow.d.ts +7 -0
- package/dist/components/ChatWindow/ChatWindow.d.ts.map +1 -0
- package/dist/components/ChatWindow/Composer/Composer.d.ts +7 -0
- package/dist/components/ChatWindow/Composer/Composer.d.ts.map +1 -0
- package/dist/components/ChatWindow/Composer/ComposerInputRow.d.ts +15 -0
- package/dist/components/ChatWindow/Composer/ComposerInputRow.d.ts.map +1 -0
- package/dist/components/ChatWindow/Composer/ComposerQueue.d.ts +10 -0
- package/dist/components/ChatWindow/Composer/ComposerQueue.d.ts.map +1 -0
- package/dist/components/ChatWindow/Composer/ModelSelector.d.ts +3 -0
- package/dist/components/ChatWindow/Composer/ModelSelector.d.ts.map +1 -0
- package/dist/components/ChatWindow/Composer/index.d.ts +3 -0
- package/dist/components/ChatWindow/Composer/index.d.ts.map +1 -0
- package/dist/components/ChatWindow/Composer/types.d.ts +13 -0
- package/dist/components/ChatWindow/Composer/types.d.ts.map +1 -0
- package/dist/components/ChatWindow/ContextControls/ContextControls.d.ts +3 -0
- package/dist/components/ChatWindow/ContextControls/ContextControls.d.ts.map +1 -0
- package/dist/components/ChatWindow/ContextControls/index.d.ts +2 -0
- package/dist/components/ChatWindow/ContextControls/index.d.ts.map +1 -0
- package/dist/components/ChatWindow/MessageList/Message/MarkdownComponents.d.ts +4 -0
- package/dist/components/ChatWindow/MessageList/Message/MarkdownComponents.d.ts.map +1 -0
- package/dist/components/ChatWindow/MessageList/Message/MessageActions.d.ts +7 -0
- package/dist/components/ChatWindow/MessageList/Message/MessageActions.d.ts.map +1 -0
- package/dist/components/ChatWindow/MessageList/Message/MessageAttachments.d.ts +7 -0
- package/dist/components/ChatWindow/MessageList/Message/MessageAttachments.d.ts.map +1 -0
- package/dist/components/ChatWindow/MessageList/Message/MessageAvatar.d.ts +9 -0
- package/dist/components/ChatWindow/MessageList/Message/MessageAvatar.d.ts.map +1 -0
- package/dist/components/ChatWindow/MessageList/Message/MessageBubble.d.ts +8 -0
- package/dist/components/ChatWindow/MessageList/Message/MessageBubble.d.ts.map +1 -0
- package/dist/components/ChatWindow/MessageList/Message/MessageCodeBlock.d.ts +8 -0
- package/dist/components/ChatWindow/MessageList/Message/MessageCodeBlock.d.ts.map +1 -0
- package/dist/components/ChatWindow/MessageList/Message/MessageContent.d.ts +9 -0
- package/dist/components/ChatWindow/MessageList/Message/MessageContent.d.ts.map +1 -0
- package/dist/components/ChatWindow/MessageList/Message/MessageHeader.d.ts +10 -0
- package/dist/components/ChatWindow/MessageList/Message/MessageHeader.d.ts.map +1 -0
- package/dist/components/ChatWindow/MessageList/Message/MessageTypingAnimation.d.ts +3 -0
- package/dist/components/ChatWindow/MessageList/Message/MessageTypingAnimation.d.ts.map +1 -0
- package/dist/components/ChatWindow/MessageList/Message/ReasoningPanel.d.ts +11 -0
- package/dist/components/ChatWindow/MessageList/Message/ReasoningPanel.d.ts.map +1 -0
- package/dist/components/ChatWindow/MessageList/Message/index.d.ts +7 -0
- package/dist/components/ChatWindow/MessageList/Message/index.d.ts.map +1 -0
- package/dist/components/ChatWindow/MessageList/MessageGroup.d.ts +10 -0
- package/dist/components/ChatWindow/MessageList/MessageGroup.d.ts.map +1 -0
- package/dist/components/ChatWindow/MessageList/MessageList.d.ts +3 -0
- package/dist/components/ChatWindow/MessageList/MessageList.d.ts.map +1 -0
- package/dist/components/ChatWindow/MessageList/ToolCallCard.d.ts +10 -0
- package/dist/components/ChatWindow/MessageList/ToolCallCard.d.ts.map +1 -0
- package/dist/components/ChatWindow/MessageList/index.d.ts +3 -0
- package/dist/components/ChatWindow/MessageList/index.d.ts.map +1 -0
- package/dist/components/ChatWindow/ToolShelf/ToolShelf.d.ts +8 -0
- package/dist/components/ChatWindow/ToolShelf/ToolShelf.d.ts.map +1 -0
- package/dist/components/ChatWindow/ToolShelf/index.d.ts +3 -0
- package/dist/components/ChatWindow/ToolShelf/index.d.ts.map +1 -0
- package/dist/components/ChatWindow/index.d.ts +7 -0
- package/dist/components/ChatWindow/index.d.ts.map +1 -0
- package/dist/components/ui/CopyButton/CopyButton.d.ts +6 -0
- package/dist/components/ui/CopyButton/CopyButton.d.ts.map +1 -0
- package/dist/components/ui/CopyButton/index.d.ts +2 -0
- package/dist/components/ui/CopyButton/index.d.ts.map +1 -0
- package/dist/components/ui/Dropdown/Dropdown.d.ts +25 -0
- package/dist/components/ui/Dropdown/Dropdown.d.ts.map +1 -0
- package/dist/components/ui/Dropdown/index.d.ts +2 -0
- package/dist/components/ui/Dropdown/index.d.ts.map +1 -0
- package/dist/components/ui/IconButton/IconButton.d.ts +18 -0
- package/dist/components/ui/IconButton/IconButton.d.ts.map +1 -0
- package/dist/components/ui/IconButton/index.d.ts +2 -0
- package/dist/components/ui/IconButton/index.d.ts.map +1 -0
- package/dist/config/types.d.ts +523 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/context/AgentProvider.d.ts +13 -0
- package/dist/context/AgentProvider.d.ts.map +1 -0
- package/dist/context/SocketProvider.d.ts +14 -0
- package/dist/context/SocketProvider.d.ts.map +1 -0
- package/dist/hooks/useAutoScroll.d.ts +42 -0
- package/dist/hooks/useAutoScroll.d.ts.map +1 -0
- package/dist/hooks/useChatBootstrap.d.ts +2 -0
- package/dist/hooks/useChatBootstrap.d.ts.map +1 -0
- package/dist/hooks/useChatSendController.d.ts +18 -0
- package/dist/hooks/useChatSendController.d.ts.map +1 -0
- package/dist/hooks/useDeleteChat.d.ts +6 -0
- package/dist/hooks/useDeleteChat.d.ts.map +1 -0
- package/dist/hooks/useModels.d.ts +4 -0
- package/dist/hooks/useModels.d.ts.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/runtime/AgentRuntime.d.ts +30 -0
- package/dist/runtime/AgentRuntime.d.ts.map +1 -0
- package/dist/runtime/ToolRegistry.d.ts +20 -0
- package/dist/runtime/ToolRegistry.d.ts.map +1 -0
- package/dist/runtime/transports/AgentTransport.d.ts +49 -0
- package/dist/runtime/transports/AgentTransport.d.ts.map +1 -0
- package/dist/runtime/transports/ChatStorage.d.ts +18 -0
- package/dist/runtime/transports/ChatStorage.d.ts.map +1 -0
- package/dist/runtime/transports/DirectTransport.d.ts +71 -0
- package/dist/runtime/transports/DirectTransport.d.ts.map +1 -0
- package/dist/runtime/transports/ProxyTransport.d.ts +46 -0
- package/dist/runtime/transports/ProxyTransport.d.ts.map +1 -0
- package/dist/runtime/types.d.ts +2 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/state/useAgentStore.d.ts +61 -0
- package/dist/state/useAgentStore.d.ts.map +1 -0
- package/dist/style.css +10 -0
- package/dist/theme/AgentThemeProvider.d.ts +10 -0
- package/dist/theme/AgentThemeProvider.d.ts.map +1 -0
- package/dist/theme/defaultTheme.d.ts +5 -0
- package/dist/theme/defaultTheme.d.ts.map +1 -0
- package/dist/utils/guestUserId.d.ts +6 -0
- package/dist/utils/guestUserId.d.ts.map +1 -0
- package/dist/utils/modelSelection.d.ts +22 -0
- package/dist/utils/modelSelection.d.ts.map +1 -0
- package/dist/utils/normalizeUrl.d.ts +3 -0
- package/dist/utils/normalizeUrl.d.ts.map +1 -0
- 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.
|