@kuntur/a2a-carbon-chat-adapter 0.1.17 → 1.0.0

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 CHANGED
@@ -1,19 +1,88 @@
1
1
  # @kuntur/a2a-carbon-chat-adapter
2
2
 
3
- A2A protocol adapter for Carbon AI Chat - connect any A2A agent to Carbon UI.
3
+ [![npm version](https://img.shields.io/npm/v/@kuntur/a2a-carbon-chat-adapter.svg)](https://www.npmjs.com/package/@kuntur/a2a-carbon-chat-adapter)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
5
 
5
- ## Installation
6
+ **Production-ready A2A protocol adapter for Carbon AI Chat** - Connect any Agent2Agent protocol agent to IBM Carbon Design System chat UI with full streaming support, multi-agent management, and extensible rendering.
7
+
8
+ ---
9
+
10
+ ## 📋 Table of Contents
11
+
12
+ - [Overview](#overview)
13
+ - [Why This Library?](#why-this-library)
14
+ - [Quick Start](#quick-start)
15
+ - [⚠️ Server Proxy Setup (Required)](#️-server-proxy-setup-required)
16
+ - [Core Concepts](#core-concepts)
17
+ - [API Reference](#api-reference)
18
+ - [Advanced Usage](#advanced-usage)
19
+ - [Examples](#examples)
20
+ - [Architecture](#architecture)
21
+ - [Troubleshooting](#troubleshooting)
22
+ - [Contributing](#contributing)
23
+ - [License](#license)
24
+
25
+ ---
26
+
27
+ ## Overview
28
+
29
+ `@kuntur/a2a-carbon-chat-adapter` is a TypeScript library that bridges the [Agent2Agent (A2A) protocol](https://github.com/agentstack/a2a) with IBM's [Carbon AI Chat](https://github.com/carbon-design-system/carbon-for-ai) component. It provides:
30
+
31
+ - **Protocol Translation**: Converts A2A streaming responses to Carbon AI Chat's message format
32
+ - **Multi-Agent Support**: Built-in context provider for managing multiple agents
33
+ - **Extension System**: Handles A2A UI extensions (citations, forms, errors)
34
+ - **Server Utilities**: Ready-to-use API handlers for Next.js and other frameworks
35
+ - **Type Safety**: Full TypeScript support with comprehensive type definitions
36
+
37
+ This library is designed for production use in enterprise applications requiring AI agent integration with Carbon Design System.
38
+
39
+ ---
40
+
41
+ ## Why This Library?
42
+
43
+ ### The Problem
44
+
45
+ Integrating A2A protocol agents with Carbon AI Chat requires:
46
+
47
+ 1. **Protocol Translation**: A2A uses Server-Sent Events (SSE) streaming; Carbon expects a specific message format
48
+ 2. **CORS Handling**: Browser security prevents direct agent connections
49
+ 3. **Extension Parsing**: A2A UI extensions need custom rendering logic
50
+ 4. **State Management**: Multi-agent scenarios require context management
51
+
52
+ ### The Solution
53
+
54
+ This library provides:
55
+
56
+ - ✅ **Drop-in Components**: `<A2AChat>` component with full A2A support
57
+ - ✅ **Server Proxy**: Pre-built API handlers for streaming proxy setup
58
+ - ✅ **Extension Renderers**: Built-in components for citations, forms, and errors
59
+ - ✅ **Multi-Agent Context**: `AgentProvider` for seamless agent switching
60
+ - ✅ **Programmatic API**: Hooks for custom implementations
61
+
62
+ ### Benefits
63
+
64
+ - **Faster Development**: Skip protocol implementation, focus on your application
65
+ - **Production Ready**: Battle-tested streaming, error handling, and state management
66
+ - **Type Safe**: Full TypeScript support prevents runtime errors
67
+ - **Extensible**: Custom renderers and hooks for advanced use cases
68
+ - **Carbon Native**: Seamless integration with Carbon Design System
69
+
70
+ ---
71
+
72
+ ## Quick Start
73
+
74
+ ### Installation
6
75
 
7
76
  ```bash
8
77
  npm install @kuntur/a2a-carbon-chat-adapter @carbon/ai-chat react react-dom
9
78
  ```
10
79
 
11
- ## Quick Start
80
+ ### Basic Usage
12
81
 
13
82
  ```tsx
14
83
  import { A2AChat } from '@kuntur/a2a-carbon-chat-adapter';
15
- import '@kuntur/a2a-carbon-chat-adapter/styles'; // Adapter layout styles
16
- import '@carbon/ai-chat/dist/styles.css'; // Carbon AI Chat styles (if not already imported)
84
+ import '@kuntur/a2a-carbon-chat-adapter/styles';
85
+ import '@carbon/ai-chat/dist/styles.css';
17
86
 
18
87
  function App() {
19
88
  return (
@@ -26,96 +95,223 @@ function App() {
26
95
  }
27
96
  ```
28
97
 
29
- ## Multi-Agent Setup
98
+ **⚠️ Important**: This requires a server proxy to work. See [Server Proxy Setup](#️-server-proxy-setup-required) below.
30
99
 
31
- ```tsx
32
- import { AgentProvider, A2AChat, AgentSwitcher, useAgentContext } from '@kuntur/a2a-carbon-chat-adapter';
100
+ ---
33
101
 
34
- const agents = [
35
- { id: 'research', name: 'Research Agent', url: 'https://research.example.com' },
36
- { id: 'code', name: 'Code Agent', url: 'https://code.example.com' },
37
- ];
102
+ ## ⚠️ Server Proxy Setup (Required)
38
103
 
39
- function App() {
40
- return (
41
- <AgentProvider agents={agents} defaultAgentId="research">
42
- <ChatWithSwitcher />
43
- </AgentProvider>
44
- );
45
- }
104
+ ### Why You Need a Server Proxy
46
105
 
47
- function ChatWithSwitcher() {
48
- const { agents, currentAgent, selectAgent } = useAgentContext();
106
+ **Direct browser connections to A2A agents are blocked by CORS (Cross-Origin Resource Sharing) security policies.** Browsers prevent JavaScript from making requests to different domains unless the server explicitly allows it.
49
107
 
50
- return (
51
- <div>
52
- <AgentSwitcher
53
- agents={agents}
54
- currentAgentId={currentAgent?.id}
55
- onSelect={selectAgent}
56
- variant="tabs"
57
- />
58
- <A2AChat />
59
- </div>
60
- );
61
- }
62
- ```
108
+ Most A2A agents:
109
+ - Run on different domains than your frontend
110
+ - Don't configure CORS headers for browser access
111
+ - Use streaming protocols that require special handling
112
+
113
+ **Solution**: Route agent requests through your backend server, which:
114
+ 1. Receives requests from your frontend (same origin = no CORS)
115
+ 2. Forwards them to the A2A agent (server-to-server = no CORS restrictions)
116
+ 3. Streams responses back to the frontend
63
117
 
64
- ## Server Setup (Next.js)
118
+ ### Next.js Setup (Recommended)
119
+
120
+ Create an API route that proxies requests to your A2A agent:
65
121
 
66
122
  ```typescript
67
123
  // app/api/agent/chat/route.ts
68
124
  import { createA2AHandler } from '@kuntur/a2a-carbon-chat-adapter/server';
69
125
 
70
126
  export const POST = createA2AHandler({
71
- allowedAgentUrls: ['https://trusted-agent.example.com'],
127
+ // Security: Only allow requests to trusted agent URLs
128
+ allowedAgentUrls: [
129
+ 'https://your-agent.example.com',
130
+ 'https://another-agent.example.com',
131
+ ],
132
+
133
+ // Optional: Set request timeout (default: 120000ms)
72
134
  timeout: 120000,
73
135
  });
74
136
 
137
+ // Required for streaming responses
75
138
  export const runtime = 'nodejs';
76
139
  export const dynamic = 'force-dynamic';
77
140
  ```
78
141
 
79
- ## Programmatic Usage with Hooks
142
+ Then configure your frontend to use this proxy:
80
143
 
81
144
  ```tsx
82
- import { useA2AAgent } from '@kuntur/a2a-carbon-chat-adapter';
145
+ import { A2AChat } from '@kuntur/a2a-carbon-chat-adapter';
146
+
147
+ function App() {
148
+ return (
149
+ <A2AChat
150
+ agentUrl="https://your-agent.example.com"
151
+ agentName="My Agent"
152
+ // Point to your proxy endpoint
153
+ proxyUrl="/api/agent/chat"
154
+ />
155
+ );
156
+ }
157
+ ```
158
+
159
+ ### Express.js Setup
83
160
 
84
- function MyComponent() {
85
- const { sendMessage, isStreaming, state } = useA2AAgent({
86
- agent: { id: 'my-agent', name: 'My Agent', url: 'https://...' },
87
- onMessage: (message) => console.log('Received:', message),
88
- onError: (error) => console.error('Error:', error),
161
+ ```typescript
162
+ import express from 'express';
163
+ import { createA2AHandler } from '@kuntur/a2a-carbon-chat-adapter/server';
164
+
165
+ const app = express();
166
+
167
+ // Create the handler
168
+ const handler = createA2AHandler({
169
+ allowedAgentUrls: ['https://your-agent.example.com'],
170
+ });
171
+
172
+ // Mount as Express middleware
173
+ app.post('/api/agent/chat', async (req, res) => {
174
+ const response = await handler(req);
175
+
176
+ // Copy headers
177
+ response.headers.forEach((value, key) => {
178
+ res.setHeader(key, value);
89
179
  });
180
+
181
+ // Stream response
182
+ const reader = response.body?.getReader();
183
+ if (reader) {
184
+ while (true) {
185
+ const { done, value } = await reader.read();
186
+ if (done) break;
187
+ res.write(value);
188
+ }
189
+ }
190
+ res.end();
191
+ });
192
+ ```
90
193
 
91
- const handleSend = async () => {
92
- await sendMessage('Hello, agent!');
93
- };
194
+ ### Other Frameworks
94
195
 
95
- return (
96
- <button onClick={handleSend} disabled={isStreaming}>
97
- {isStreaming ? 'Sending...' : 'Send Message'}
98
- </button>
99
- );
196
+ The `createA2AHandler` function returns a standard Web API `Response` object, making it compatible with:
197
+
198
+ - **Remix**: Use in `action` functions
199
+ - **SvelteKit**: Use in `+server.ts` endpoints
200
+ - **Astro**: Use in API routes
201
+ - **Cloudflare Workers**: Direct compatibility
202
+ - **Vercel Edge Functions**: Direct compatibility
203
+
204
+ Example for any framework:
205
+
206
+ ```typescript
207
+ import { createA2AHandler } from '@kuntur/a2a-carbon-chat-adapter/server';
208
+
209
+ const handler = createA2AHandler({
210
+ allowedAgentUrls: ['https://your-agent.example.com'],
211
+ });
212
+
213
+ // In your framework's request handler:
214
+ export async function POST(request: Request) {
215
+ return handler(request);
100
216
  }
101
217
  ```
102
218
 
103
- ## Components
219
+ ### Security Considerations
104
220
 
105
- ### A2AChat
221
+ **Always use `allowedAgentUrls`** to prevent your proxy from being used to access arbitrary URLs:
222
+
223
+ ```typescript
224
+ createA2AHandler({
225
+ // ✅ Good: Whitelist specific agents
226
+ allowedAgentUrls: [
227
+ 'https://trusted-agent.example.com',
228
+ 'https://another-trusted-agent.example.com',
229
+ ],
230
+
231
+ // ❌ Bad: Allows any URL (security risk!)
232
+ // allowedAgentUrls: undefined,
233
+ });
234
+ ```
235
+
236
+ ---
237
+
238
+ ## Core Concepts
239
+
240
+ ### A2A Protocol
241
+
242
+ The [Agent2Agent (A2A) protocol](https://github.com/agentstack/a2a) is a standard for AI agent communication. Key features:
243
+
244
+ - **Streaming**: Real-time response delivery via Server-Sent Events (SSE)
245
+ - **Context Management**: Maintains conversation state across requests
246
+ - **UI Extensions**: Agents can request UI components (forms, citations, etc.)
247
+ - **Task Management**: Supports long-running tasks with progress updates
248
+
249
+ ### Extension System
250
+
251
+ A2A agents can send UI extension requests using special URIs:
252
+
253
+ ```
254
+ agentstack://extension/{type}/{id}
255
+ ```
256
+
257
+ This library automatically handles these extensions:
258
+
259
+ - **Citations**: `agentstack://extension/citation/*` → Rendered inline with text
260
+ - **Forms**: `agentstack://extension/form/*` → Interactive form components
261
+ - **Errors**: `agentstack://extension/error/*` → Formatted error displays
262
+
263
+ ### Streaming Architecture
264
+
265
+ ```
266
+ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐
267
+ │ Browser │─────▶│ Your Server │─────▶│ A2A Agent │
268
+ │ (Frontend) │ │ (Proxy) │ │ │
269
+ └─────────────┘ └──────────────┘ └─────────────┘
270
+ │ │ │
271
+ │ 1. POST request │ │
272
+ │────────────────────▶│ │
273
+ │ │ 2. Forward request │
274
+ │ │─────────────────────▶│
275
+ │ │ │
276
+ │ │ 3. SSE stream │
277
+ │ │◀─────────────────────│
278
+ │ 4. SSE stream │ │
279
+ │◀────────────────────│ │
280
+ │ │ │
281
+ ```
282
+
283
+ The library handles:
284
+ 1. Converting Carbon messages to A2A format
285
+ 2. Streaming A2A responses back to Carbon
286
+ 3. Parsing and rendering UI extensions
287
+ 4. Managing conversation context
288
+
289
+ ---
290
+
291
+ ## API Reference
292
+
293
+ ### Components
294
+
295
+ #### `<A2AChat>`
106
296
 
107
297
  Main chat component that connects to A2A agents.
108
298
 
109
299
  ```tsx
300
+ import { A2AChat } from '@kuntur/a2a-carbon-chat-adapter';
301
+
110
302
  <A2AChat
111
- // Agent configuration (pick one)
112
- agent={{ id: 'agent', name: 'Agent', url: '...' }}
303
+ // Agent Configuration (choose one)
304
+ agent={{ id: 'agent', name: 'Agent', url: 'https://...' }}
113
305
  // OR
114
- agentId="agent" // with AgentProvider
306
+ agentId="agent" // Requires AgentProvider
115
307
  // OR
116
- agentUrl="https://..." // simple URL-only
308
+ agentUrl="https://..." // Simple URL-only mode
309
+ agentName="Agent Name" // Optional display name
117
310
 
118
- // Display options
311
+ // Server Proxy (required for CORS)
312
+ proxyUrl="/api/agent/chat"
313
+
314
+ // Display Options
119
315
  layout="fullscreen" // 'fullscreen' | 'sidebar' | 'float'
120
316
  className="my-chat"
121
317
 
@@ -125,23 +321,52 @@ Main chat component that connects to A2A agents.
125
321
  allowCancel={true}
126
322
 
127
323
  // Callbacks
128
- onSend={(message) => {}}
129
- onResponse={(response) => {}}
130
- onError={(error) => {}}
324
+ onSend={(message) => console.log('Sent:', message)}
325
+ onResponse={(response) => console.log('Received:', response)}
326
+ onError={(error) => console.error('Error:', error)}
131
327
 
132
- // Custom renderers
133
- renderCitations={(citations, text) => <MyCitations ... />}
134
- renderError={(error) => <MyError ... />}
328
+ // Custom Renderers
329
+ renderCitations={(citations, text) => <CustomCitations />}
330
+ renderError={(error) => <CustomError />}
331
+ renderForm={(form) => <CustomForm />}
135
332
  />
136
333
  ```
137
334
 
138
- ### AgentProvider
335
+ **Props:**
336
+
337
+ | Prop | Type | Required | Description |
338
+ |------|------|----------|-------------|
339
+ | `agent` | `AgentConfig` | No* | Full agent configuration object |
340
+ | `agentId` | `string` | No* | Agent ID (requires `AgentProvider`) |
341
+ | `agentUrl` | `string` | No* | Direct agent URL |
342
+ | `agentName` | `string` | No | Display name for the agent |
343
+ | `proxyUrl` | `string` | No | Server proxy endpoint (default: `/api/agent/chat`) |
344
+ | `layout` | `'fullscreen' \| 'sidebar' \| 'float'` | No | Chat layout style |
345
+ | `className` | `string` | No | Additional CSS classes |
346
+ | `showThinking` | `boolean` | No | Show agent thinking indicators |
347
+ | `showChainOfThought` | `boolean` | No | Show reasoning steps |
348
+ | `allowCancel` | `boolean` | No | Allow canceling streaming responses |
349
+ | `onSend` | `(message: string) => void` | No | Callback when message is sent |
350
+ | `onResponse` | `(response: any) => void` | No | Callback when response received |
351
+ | `onError` | `(error: Error) => void` | No | Callback on error |
352
+ | `renderCitations` | `(citations, text) => ReactNode` | No | Custom citation renderer |
353
+ | `renderError` | `(error) => ReactNode` | No | Custom error renderer |
354
+ | `renderForm` | `(form) => ReactNode` | No | Custom form renderer |
355
+
356
+ *One of `agent`, `agentId`, or `agentUrl` is required.
357
+
358
+ #### `<AgentProvider>`
139
359
 
140
360
  Context provider for multi-agent applications.
141
361
 
142
362
  ```tsx
363
+ import { AgentProvider } from '@kuntur/a2a-carbon-chat-adapter';
364
+
143
365
  <AgentProvider
144
- agents={[...]}
366
+ agents={[
367
+ { id: 'research', name: 'Research Agent', url: 'https://...' },
368
+ { id: 'code', name: 'Code Agent', url: 'https://...' },
369
+ ]}
145
370
  defaultAgentId="research"
146
371
  persistSelection={true}
147
372
  storageKey="my-app-agent"
@@ -150,11 +375,22 @@ Context provider for multi-agent applications.
150
375
  </AgentProvider>
151
376
  ```
152
377
 
153
- ### AgentSwitcher
378
+ **Props:**
379
+
380
+ | Prop | Type | Required | Description |
381
+ |------|------|----------|-------------|
382
+ | `agents` | `AgentConfig[]` | Yes | Array of agent configurations |
383
+ | `defaultAgentId` | `string` | No | Initially selected agent ID |
384
+ | `persistSelection` | `boolean` | No | Save selection to localStorage |
385
+ | `storageKey` | `string` | No | localStorage key (default: `selected-agent`) |
386
+
387
+ #### `<AgentSwitcher>`
154
388
 
155
389
  UI component for switching between agents.
156
390
 
157
391
  ```tsx
392
+ import { AgentSwitcher } from '@kuntur/a2a-carbon-chat-adapter';
393
+
158
394
  <AgentSwitcher
159
395
  agents={agents}
160
396
  currentAgentId={currentAgentId}
@@ -162,105 +398,693 @@ UI component for switching between agents.
162
398
  variant="dropdown" // 'dropdown' | 'tabs' | 'cards'
163
399
  showDescriptions={true}
164
400
  showIcons={true}
401
+ className="my-switcher"
165
402
  />
166
403
  ```
167
404
 
168
- ## Hooks
405
+ **Props:**
406
+
407
+ | Prop | Type | Required | Description |
408
+ |------|------|----------|-------------|
409
+ | `agents` | `AgentConfig[]` | Yes | Available agents |
410
+ | `currentAgentId` | `string` | No | Currently selected agent ID |
411
+ | `onSelect` | `(agentId: string) => void` | Yes | Selection callback |
412
+ | `variant` | `'dropdown' \| 'tabs' \| 'cards'` | No | Display style |
413
+ | `showDescriptions` | `boolean` | No | Show agent descriptions |
414
+ | `showIcons` | `boolean` | No | Show agent icons |
415
+ | `className` | `string` | No | Additional CSS classes |
416
+
417
+ ### Hooks
169
418
 
170
- ### useA2AAgent
419
+ #### `useA2AAgent`
171
420
 
172
- Programmatic agent interaction.
421
+ Programmatic agent interaction without UI components.
173
422
 
174
423
  ```tsx
424
+ import { useA2AAgent } from '@kuntur/a2a-carbon-chat-adapter';
425
+
175
426
  const {
176
427
  agent, // Current agent config
177
- state, // { connectionState, error, taskId, contextId }
428
+ state, // Connection state
178
429
  sendMessage, // Send a message
179
430
  cancelStream, // Cancel ongoing stream
180
- disconnect, // Disconnect
181
- isStreaming, // Boolean
182
- isConnected, // Boolean
183
- error, // Last error
184
- } = useA2AAgent(options);
431
+ disconnect, // Disconnect from agent
432
+ isStreaming, // Boolean: is currently streaming
433
+ isConnected, // Boolean: is connected
434
+ error, // Last error (if any)
435
+ } = useA2AAgent({
436
+ agent: { id: 'agent', name: 'Agent', url: 'https://...' },
437
+ proxyUrl: '/api/agent/chat',
438
+ onMessage: (message) => console.log('Message:', message),
439
+ onError: (error) => console.error('Error:', error),
440
+ onStreamStart: () => console.log('Stream started'),
441
+ onStreamEnd: () => console.log('Stream ended'),
442
+ });
443
+
444
+ // Send a message
445
+ await sendMessage('Hello, agent!');
446
+
447
+ // Cancel streaming
448
+ cancelStream();
449
+
450
+ // Disconnect
451
+ disconnect();
185
452
  ```
186
453
 
187
- ### useMultiAgent
454
+ **Options:**
455
+
456
+ | Option | Type | Required | Description |
457
+ |--------|------|----------|-------------|
458
+ | `agent` | `AgentConfig` | Yes | Agent configuration |
459
+ | `proxyUrl` | `string` | No | Server proxy endpoint |
460
+ | `onMessage` | `(message: any) => void` | No | Message received callback |
461
+ | `onError` | `(error: Error) => void` | No | Error callback |
462
+ | `onStreamStart` | `() => void` | No | Stream start callback |
463
+ | `onStreamEnd` | `() => void` | No | Stream end callback |
464
+
465
+ **Returns:**
188
466
 
189
- Manage multiple agents without AgentProvider.
467
+ | Property | Type | Description |
468
+ |----------|------|-------------|
469
+ | `agent` | `AgentConfig` | Current agent configuration |
470
+ | `state` | `AgentState` | Connection state object |
471
+ | `sendMessage` | `(message: string) => Promise<void>` | Send message function |
472
+ | `cancelStream` | `() => void` | Cancel streaming function |
473
+ | `disconnect` | `() => void` | Disconnect function |
474
+ | `isStreaming` | `boolean` | Currently streaming |
475
+ | `isConnected` | `boolean` | Currently connected |
476
+ | `error` | `Error \| null` | Last error |
477
+
478
+ #### `useMultiAgent`
479
+
480
+ Manage multiple agents without `AgentProvider`.
190
481
 
191
482
  ```tsx
483
+ import { useMultiAgent } from '@kuntur/a2a-carbon-chat-adapter';
484
+
192
485
  const {
193
- agents, // All agents
194
- currentAgent, // Currently selected
195
- switchAgent, // Switch by ID
486
+ agents, // All registered agents
487
+ currentAgent, // Currently selected agent
488
+ switchAgent, // Switch to agent by ID
196
489
  registerAgent, // Add new agent
197
490
  unregisterAgent, // Remove agent
198
- } = useMultiAgent(options);
491
+ } = useMultiAgent({
492
+ agents: [
493
+ { id: 'research', name: 'Research', url: 'https://...' },
494
+ { id: 'code', name: 'Code', url: 'https://...' },
495
+ ],
496
+ defaultAgentId: 'research',
497
+ });
498
+
499
+ // Switch agents
500
+ switchAgent('code');
501
+
502
+ // Add new agent
503
+ registerAgent({ id: 'new', name: 'New Agent', url: 'https://...' });
504
+
505
+ // Remove agent
506
+ unregisterAgent('old');
199
507
  ```
200
508
 
201
- ### useAgentContext
509
+ #### `useAgentContext`
202
510
 
203
- Access AgentProvider context.
511
+ Access `AgentProvider` context (must be used within `AgentProvider`).
204
512
 
205
513
  ```tsx
206
- const { currentAgent, agents, selectAgent, getAgent, hasAgent } = useAgentContext();
514
+ import { useAgentContext } from '@kuntur/a2a-carbon-chat-adapter';
515
+
516
+ const {
517
+ currentAgent, // Currently selected agent
518
+ agents, // All available agents
519
+ selectAgent, // Select agent by ID
520
+ getAgent, // Get agent by ID
521
+ hasAgent, // Check if agent exists
522
+ } = useAgentContext();
207
523
  ```
208
524
 
209
- ## Renderers
525
+ ### Server Utilities
526
+
527
+ #### `createA2AHandler`
528
+
529
+ Create a server-side API handler for proxying A2A requests.
530
+
531
+ ```typescript
532
+ import { createA2AHandler } from '@kuntur/a2a-carbon-chat-adapter/server';
210
533
 
211
- Built-in renderers for A2A UI extensions:
534
+ const handler = createA2AHandler({
535
+ allowedAgentUrls: ['https://trusted-agent.example.com'],
536
+ timeout: 120000,
537
+ });
212
538
 
213
- - `CitationRenderer` - Renders text with inline citations
214
- - `ErrorRenderer` - Renders error messages with optional stack trace
215
- - `FormRenderer` - Renders dynamic forms from agent requests
539
+ // Returns a function: (request: Request) => Promise<Response>
540
+ ```
216
541
 
217
- ## Exports
542
+ **Options:**
218
543
 
219
- ### From main package
544
+ | Option | Type | Required | Description |
545
+ |--------|------|----------|-------------|
546
+ | `allowedAgentUrls` | `string[]` | No | Whitelist of allowed agent URLs (security) |
547
+ | `timeout` | `number` | No | Request timeout in ms (default: 120000) |
548
+
549
+ **Returns:** `(request: Request) => Promise<Response>`
550
+
551
+ ### Renderers
552
+
553
+ Built-in components for rendering A2A UI extensions.
554
+
555
+ #### `CitationRenderer`
556
+
557
+ Renders text with inline citations.
558
+
559
+ ```tsx
560
+ import { CitationRenderer } from '@kuntur/a2a-carbon-chat-adapter';
561
+
562
+ <CitationRenderer
563
+ text="This is cited text [1]."
564
+ citations={[
565
+ { id: '1', url: 'https://...', title: 'Source' }
566
+ ]}
567
+ />
568
+ ```
569
+
570
+ #### `ErrorRenderer`
571
+
572
+ Renders formatted error messages.
573
+
574
+ ```tsx
575
+ import { ErrorRenderer } from '@kuntur/a2a-carbon-chat-adapter';
576
+
577
+ <ErrorRenderer
578
+ error={{
579
+ message: 'Something went wrong',
580
+ code: 'ERROR_CODE',
581
+ stack: '...',
582
+ }}
583
+ showStack={true}
584
+ />
585
+ ```
586
+
587
+ #### `FormRenderer`
588
+
589
+ Renders dynamic forms from agent requests.
590
+
591
+ ```tsx
592
+ import { FormRenderer } from '@kuntur/a2a-carbon-chat-adapter';
593
+
594
+ <FormRenderer
595
+ form={{
596
+ fields: [
597
+ { name: 'email', type: 'email', label: 'Email', required: true },
598
+ { name: 'message', type: 'textarea', label: 'Message' },
599
+ ],
600
+ }}
601
+ onSubmit={(values) => console.log('Submitted:', values)}
602
+ />
603
+ ```
604
+
605
+ ### Types
606
+
607
+ ```typescript
608
+ import type {
609
+ AgentConfig,
610
+ AgentState,
611
+ A2AChatProps,
612
+ AgentProviderProps,
613
+ AgentSwitcherProps,
614
+ ExtensionResult,
615
+ Citation,
616
+ FormField,
617
+ // ... more types
618
+ } from '@kuntur/a2a-carbon-chat-adapter';
619
+ ```
620
+
621
+ ---
622
+
623
+ ## Advanced Usage
624
+
625
+ ### Multi-Agent Setup
220
626
 
221
627
  ```tsx
222
628
  import {
223
- // Components
224
- A2AChat,
225
629
  AgentProvider,
630
+ A2AChat,
226
631
  AgentSwitcher,
227
- CitationRenderer,
228
- ErrorRenderer,
229
- FormRenderer,
230
-
231
- // Hooks
232
- useA2AAgent,
233
- useMultiAgent,
234
632
  useAgentContext,
235
- useAgentContextOptional,
633
+ } from '@kuntur/a2a-carbon-chat-adapter';
236
634
 
237
- // A2A Client
238
- A2AClient,
239
- EXTENSION_URIS,
635
+ const agents = [
636
+ {
637
+ id: 'research',
638
+ name: 'Research Agent',
639
+ url: 'https://research.example.com',
640
+ description: 'Specialized in research and analysis',
641
+ },
642
+ {
643
+ id: 'code',
644
+ name: 'Code Agent',
645
+ url: 'https://code.example.com',
646
+ description: 'Specialized in code generation',
647
+ },
648
+ ];
240
649
 
241
- // Translator
242
- A2AToCarbonTranslator,
243
- createTranslator,
650
+ function App() {
651
+ return (
652
+ <AgentProvider
653
+ agents={agents}
654
+ defaultAgentId="research"
655
+ persistSelection={true}
656
+ >
657
+ <ChatWithSwitcher />
658
+ </AgentProvider>
659
+ );
660
+ }
244
661
 
245
- // Types
246
- type AgentConfig,
247
- type A2AChatProps,
248
- // ... more types
249
- } from '@kuntur/a2a-carbon-chat-adapter';
662
+ function ChatWithSwitcher() {
663
+ const { agents, currentAgent, selectAgent } = useAgentContext();
664
+
665
+ return (
666
+ <div className="app-container">
667
+ <AgentSwitcher
668
+ agents={agents}
669
+ currentAgentId={currentAgent?.id}
670
+ onSelect={selectAgent}
671
+ variant="tabs"
672
+ showDescriptions={true}
673
+ />
674
+ <A2AChat proxyUrl="/api/agent/chat" />
675
+ </div>
676
+ );
677
+ }
250
678
  ```
251
679
 
252
- ### From server subpath
680
+ ### Custom Citation Renderer
253
681
 
254
682
  ```tsx
255
- import { createA2AHandler } from '@kuntur/a2a-carbon-chat-adapter/server';
683
+ import { A2AChat } from '@kuntur/a2a-carbon-chat-adapter';
684
+
685
+ function CustomCitationRenderer({ citations, text }) {
686
+ return (
687
+ <div className="custom-citations">
688
+ <p>{text}</p>
689
+ <ul>
690
+ {citations.map((citation) => (
691
+ <li key={citation.id}>
692
+ <a href={citation.url} target="_blank" rel="noopener noreferrer">
693
+ [{citation.id}] {citation.title}
694
+ </a>
695
+ </li>
696
+ ))}
697
+ </ul>
698
+ </div>
699
+ );
700
+ }
701
+
702
+ function App() {
703
+ return (
704
+ <A2AChat
705
+ agentUrl="https://agent.example.com"
706
+ renderCitations={(citations, text) => (
707
+ <CustomCitationRenderer citations={citations} text={text} />
708
+ )}
709
+ />
710
+ );
711
+ }
256
712
  ```
257
713
 
258
- ### Styles (optional)
714
+ ### Programmatic Message Sending
259
715
 
260
716
  ```tsx
261
- import '@kuntur/a2a-carbon-chat-adapter/styles';
717
+ import { useA2AAgent } from '@kuntur/a2a-carbon-chat-adapter';
718
+ import { useState } from 'react';
719
+
720
+ function CustomChat() {
721
+ const [messages, setMessages] = useState([]);
722
+
723
+ const { sendMessage, isStreaming, error } = useA2AAgent({
724
+ agent: {
725
+ id: 'agent',
726
+ name: 'My Agent',
727
+ url: 'https://agent.example.com',
728
+ },
729
+ proxyUrl: '/api/agent/chat',
730
+ onMessage: (message) => {
731
+ setMessages((prev) => [...prev, message]);
732
+ },
733
+ });
734
+
735
+ const handleSend = async (text: string) => {
736
+ setMessages((prev) => [...prev, { role: 'user', content: text }]);
737
+ await sendMessage(text);
738
+ };
739
+
740
+ return (
741
+ <div>
742
+ <div className="messages">
743
+ {messages.map((msg, i) => (
744
+ <div key={i} className={`message ${msg.role}`}>
745
+ {msg.content}
746
+ </div>
747
+ ))}
748
+ </div>
749
+ {error && <div className="error">{error.message}</div>}
750
+ <input
751
+ type="text"
752
+ onKeyPress={(e) => {
753
+ if (e.key === 'Enter' && !isStreaming) {
754
+ handleSend(e.currentTarget.value);
755
+ e.currentTarget.value = '';
756
+ }
757
+ }}
758
+ disabled={isStreaming}
759
+ />
760
+ </div>
761
+ );
762
+ }
763
+ ```
764
+
765
+ ### Extension URI Handling
766
+
767
+ The library automatically handles A2A extension URIs:
768
+
769
+ ```typescript
770
+ // Agent sends extension URI in response
771
+ {
772
+ "content": "agentstack://extension/citation/abc123",
773
+ "metadata": {
774
+ "extensions": {
775
+ "abc123": {
776
+ "type": "citation",
777
+ "data": {
778
+ "url": "https://example.com",
779
+ "title": "Example Source"
780
+ }
781
+ }
782
+ }
783
+ }
784
+ }
785
+
786
+ // Library automatically:
787
+ // 1. Detects extension URI
788
+ // 2. Looks up extension data in metadata
789
+ // 3. Renders using CitationRenderer
790
+ // 4. Replaces URI with rendered component
791
+ ```
792
+
793
+ ### Error Handling
794
+
795
+ ```tsx
796
+ import { A2AChat } from '@kuntur/a2a-carbon-chat-adapter';
797
+
798
+ function App() {
799
+ const handleError = (error: Error) => {
800
+ // Log to error tracking service
801
+ console.error('Agent error:', error);
802
+
803
+ // Show user-friendly message
804
+ if (error.message.includes('timeout')) {
805
+ alert('The agent is taking too long to respond. Please try again.');
806
+ } else if (error.message.includes('network')) {
807
+ alert('Network error. Please check your connection.');
808
+ } else {
809
+ alert('An error occurred. Please try again.');
810
+ }
811
+ };
812
+
813
+ return (
814
+ <A2AChat
815
+ agentUrl="https://agent.example.com"
816
+ onError={handleError}
817
+ />
818
+ );
819
+ }
820
+ ```
821
+
822
+ ---
823
+
824
+ ## Examples
825
+
826
+ For complete working examples, see the [examples repository](https://github.com/Xnvargas/a2a-carbon-chat-adapter-examples) (coming soon).
827
+
828
+ Example applications include:
829
+
830
+ - **Basic Chat**: Simple single-agent chat interface
831
+ - **Multi-Agent**: Agent switching with tabs and dropdown
832
+ - **Custom Renderers**: Custom citation and form renderers
833
+ - **Programmatic API**: Using hooks without UI components
834
+ - **Next.js Integration**: Full Next.js app with API routes
835
+ - **Express.js Integration**: Express server with proxy setup
836
+
837
+ ---
838
+
839
+ ## Architecture
840
+
841
+ ### Protocol Flow
842
+
843
+ ```
844
+ ┌─────────────────────────────────────────────────────────────────┐
845
+ │ Browser (Frontend) │
846
+ ├─────────────────────────────────────────────────────────────────┤
847
+ │ │
848
+ │ ┌──────────────┐ ┌─────────────────────────────────┐ │
849
+ │ │ A2AChat │────────▶│ useA2AAgent Hook │ │
850
+ │ │ Component │ │ - Manages connection state │ │
851
+ │ └──────────────┘ │ - Handles streaming │ │
852
+ │ │ │ - Parses extensions │ │
853
+ │ │ └─────────────────────────────────┘ │
854
+ │ │ │ │
855
+ │ ▼ ▼ │
856
+ │ ┌──────────────┐ ┌─────────────────────────────────┐ │
857
+ │ │ Renderers │ │ A2AToCarbonTranslator │ │
858
+ │ │ - Citation │ │ - Converts A2A → Carbon format │ │
859
+ │ │ - Error │ │ - Handles metadata transform │ │
860
+ │ │ - Form │ └─────────────────────────────────┘ │
861
+ │ └──────────────┘ │ │
862
+ │ │ │
863
+ └────────────────────────────────────────┼────────────────────────┘
864
+
865
+ │ POST /api/agent/chat
866
+
867
+ ┌────────────────────────────────────────┼────────────────────────┐
868
+ │ Your Server (Proxy) │ │
869
+ ├────────────────────────────────────────┼────────────────────────┤
870
+ │ │ │
871
+ │ ┌─────────────▼──────────────┐ │
872
+ │ │ createA2AHandler │ │
873
+ │ │ - Validates agent URL │ │
874
+ │ │ - Forwards request │ │
875
+ │ │ - Streams response │ │
876
+ │ └─────────────┬──────────────┘ │
877
+ │ │ │
878
+ └────────────────────────────────────────┼────────────────────────┘
879
+
880
+ │ POST /chat
881
+
882
+ ┌────────────────────────────────────────┼────────────────────────┐
883
+ │ A2A Agent │ │
884
+ ├────────────────────────────────────────┼────────────────────────┤
885
+ │ │ │
886
+ │ ┌─────────────▼──────────────┐ │
887
+ │ │ A2A Protocol Handler │ │
888
+ │ │ - Processes message │ │
889
+ │ │ - Generates response │ │
890
+ │ │ - Streams via SSE │ │
891
+ │ └────────────────────────────┘ │
892
+ │ │
893
+ └──────────────────────────────────────────────────────────────────┘
894
+ ```
895
+
896
+ ### Package Structure
897
+
898
+ ```
899
+ @kuntur/a2a-carbon-chat-adapter/
900
+ ├── dist/ # Built output
901
+ │ ├── index.js # ESM main entry
902
+ │ ├── index.cjs # CJS main entry
903
+ │ ├── index.d.ts # TypeScript definitions
904
+ │ ├── server.js # ESM server utilities
905
+ │ ├── server.cjs # CJS server utilities
906
+ │ ├── server.d.ts # Server TypeScript definitions
907
+ │ └── styles/
908
+ │ └── index.css # Component styles
909
+ ├── src/
910
+ │ ├── components/ # React components
911
+ │ │ ├── A2AChat.tsx
912
+ │ │ ├── AgentProvider.tsx
913
+ │ │ ├── AgentSwitcher.tsx
914
+ │ │ └── renderers/ # Extension renderers
915
+ │ ├── hooks/ # React hooks
916
+ │ │ ├── useA2AAgent.ts
917
+ │ │ ├── useMultiAgent.ts
918
+ │ │ └── useAgentContext.ts
919
+ │ ├── lib/
920
+ │ │ ├── a2a/ # A2A protocol client
921
+ │ │ └── translator/ # Protocol translation
922
+ │ ├── server/ # Server utilities
923
+ │ │ └── create-api-handler.ts
924
+ │ └── types/ # TypeScript types
925
+ └── package.json
926
+ ```
927
+
928
+ ---
929
+
930
+ ## Troubleshooting
931
+
932
+ ### CORS Errors
933
+
934
+ **Problem**: Browser console shows CORS errors when connecting to agent.
935
+
936
+ **Solution**: You must use a server proxy. See [Server Proxy Setup](#️-server-proxy-setup-required).
937
+
938
+ ```
939
+ ❌ Access to fetch at 'https://agent.example.com' from origin 'http://localhost:3000'
940
+ has been blocked by CORS policy
941
+ ```
942
+
943
+ ### Streaming Not Working
944
+
945
+ **Problem**: Messages don't stream; they appear all at once.
946
+
947
+ **Solution**: Ensure your server proxy is configured correctly:
948
+
949
+ ```typescript
950
+ // Next.js: Add these exports
951
+ export const runtime = 'nodejs';
952
+ export const dynamic = 'force-dynamic';
953
+
954
+ // Express: Ensure streaming is not buffered
955
+ app.use(express.json({ limit: '50mb' }));
956
+ ```
957
+
958
+ ### Extension URIs Not Rendering
959
+
960
+ **Problem**: Extension URIs appear as plain text instead of rendered components.
961
+
962
+ **Solution**: Ensure the agent includes extension metadata:
963
+
964
+ ```json
965
+ {
966
+ "content": "See citation [1]",
967
+ "metadata": {
968
+ "extensions": {
969
+ "1": {
970
+ "type": "citation",
971
+ "data": {
972
+ "url": "https://example.com",
973
+ "title": "Example"
974
+ }
975
+ }
976
+ }
977
+ }
978
+ }
262
979
  ```
263
980
 
981
+ ### TypeScript Errors
982
+
983
+ **Problem**: TypeScript can't find module declarations.
984
+
985
+ **Solution**: Ensure `@carbon/ai-chat` is installed and types are available:
986
+
987
+ ```bash
988
+ npm install @carbon/ai-chat @types/react @types/react-dom
989
+ ```
990
+
991
+ ### Agent Not Responding
992
+
993
+ **Problem**: No response from agent after sending message.
994
+
995
+ **Checklist**:
996
+ 1. ✅ Server proxy is running and accessible
997
+ 2. ✅ Agent URL is correct and agent is running
998
+ 3. ✅ Agent URL is in `allowedAgentUrls` (if configured)
999
+ 4. ✅ Network tab shows request reaching `/api/agent/chat`
1000
+ 5. ✅ Check server logs for errors
1001
+
1002
+ ### Performance Issues
1003
+
1004
+ **Problem**: Chat feels slow or laggy.
1005
+
1006
+ **Solutions**:
1007
+ - Reduce `timeout` in `createA2AHandler` for faster failures
1008
+ - Implement message pagination for long conversations
1009
+ - Use `showThinking={false}` to reduce re-renders
1010
+ - Memoize custom renderers with `React.memo()`
1011
+
1012
+ ---
1013
+
1014
+ ## Contributing
1015
+
1016
+ Contributions are welcome! Please follow these guidelines:
1017
+
1018
+ ### Development Setup
1019
+
1020
+ ```bash
1021
+ # Clone repository
1022
+ git clone https://github.com/Xnvargas/a2a-carbon-chat-adapter.git
1023
+ cd a2a-carbon-chat-adapter
1024
+
1025
+ # Install dependencies
1026
+ npm install
1027
+
1028
+ # Run development build (watch mode)
1029
+ npm run dev
1030
+
1031
+ # Run type checking
1032
+ npm run typecheck
1033
+
1034
+ # Run linting
1035
+ npm run lint
1036
+
1037
+ # Build for production
1038
+ npm run build
1039
+ ```
1040
+
1041
+ ### Submitting Changes
1042
+
1043
+ 1. Fork the repository
1044
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
1045
+ 3. Make your changes
1046
+ 4. Add tests if applicable
1047
+ 5. Run `npm run typecheck` and `npm run lint`
1048
+ 6. Commit your changes (`git commit -m 'Add amazing feature'`)
1049
+ 7. Push to the branch (`git push origin feature/amazing-feature`)
1050
+ 8. Open a Pull Request
1051
+
1052
+ ### Code Style
1053
+
1054
+ - Use TypeScript for all new code
1055
+ - Follow existing code formatting (ESLint config)
1056
+ - Add JSDoc comments for public APIs
1057
+ - Update documentation for user-facing changes
1058
+
1059
+ ### Reporting Issues
1060
+
1061
+ When reporting issues, please include:
1062
+
1063
+ - Library version (`npm list @kuntur/a2a-carbon-chat-adapter`)
1064
+ - Framework and version (Next.js, React, etc.)
1065
+ - Minimal reproduction code
1066
+ - Expected vs actual behavior
1067
+ - Browser console errors (if applicable)
1068
+
1069
+ ---
1070
+
264
1071
  ## License
265
1072
 
266
- MIT
1073
+ MIT © [Xavier Vargas](https://github.com/Xnvargas)
1074
+
1075
+ See [LICENSE](LICENSE) file for details.
1076
+
1077
+ ---
1078
+
1079
+ ## Links
1080
+
1081
+ - **GitHub**: https://github.com/Xnvargas/a2a-carbon-chat-adapter
1082
+ - **npm**: https://www.npmjs.com/package/@kuntur/a2a-carbon-chat-adapter
1083
+ - **Issues**: https://github.com/Xnvargas/a2a-carbon-chat-adapter/issues
1084
+ - **Examples**: https://github.com/Xnvargas/a2a-carbon-chat-adapter-examples (coming soon)
1085
+ - **A2A Protocol**: https://github.com/agentstack/a2a
1086
+ - **Carbon AI Chat**: https://github.com/carbon-design-system/carbon-for-ai
1087
+
1088
+ ---
1089
+
1090
+ **Made with ❤️ for the AI agent community**