@kuntur/a2a-carbon-chat-adapter 0.1.1 → 0.1.3

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
@@ -12,14 +12,15 @@ npm install @kuntur/a2a-carbon-chat-adapter @carbon/ai-chat react react-dom
12
12
 
13
13
  ```tsx
14
14
  import { A2AChat } from '@kuntur/a2a-carbon-chat-adapter';
15
- import '@carbon/ai-chat/styles.css';
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)
16
17
 
17
18
  function App() {
18
19
  return (
19
20
  <A2AChat
20
21
  agentUrl="https://your-agent.example.com"
21
22
  agentName="My Agent"
22
- layout="fullscreen"
23
+ layout="sidebar"
23
24
  />
24
25
  );
25
26
  }
@@ -67,9 +68,12 @@ function ChatWithSwitcher() {
67
68
  import { createA2AHandler } from '@kuntur/a2a-carbon-chat-adapter/server';
68
69
 
69
70
  export const POST = createA2AHandler({
70
- allowedAgentUrls: ['https://trusted-agents.example.com'],
71
+ allowedAgentUrls: ['https://trusted-agent.example.com'],
71
72
  timeout: 120000,
72
73
  });
74
+
75
+ export const runtime = 'nodejs';
76
+ export const dynamic = 'force-dynamic';
73
77
  ```
74
78
 
75
79
  ## Programmatic Usage with Hooks
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/server/index.ts
21
+ var server_exports = {};
22
+ __export(server_exports, {
23
+ createA2AHandler: () => createA2AHandler
24
+ });
25
+ module.exports = __toCommonJS(server_exports);
26
+
27
+ // src/server/create-api-handler.ts
28
+ function generateUUID() {
29
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
30
+ const r = Math.random() * 16 | 0;
31
+ const v = c === "x" ? r : r & 3 | 8;
32
+ return v.toString(16);
33
+ });
34
+ }
35
+ function createA2AHandler(options = {}) {
36
+ const { onRequest, onError, timeout = 12e4, allowedAgentUrls } = options;
37
+ return async function handler(request) {
38
+ try {
39
+ let body = await request.json();
40
+ if (!body.agentUrl || !body.message) {
41
+ return new Response(
42
+ JSON.stringify({ error: "Missing required fields: agentUrl, message" }),
43
+ { status: 400, headers: { "Content-Type": "application/json" } }
44
+ );
45
+ }
46
+ if (allowedAgentUrls && allowedAgentUrls.length > 0) {
47
+ const isAllowed = allowedAgentUrls.some((pattern) => {
48
+ if (typeof pattern === "string") {
49
+ return body.agentUrl.startsWith(pattern);
50
+ }
51
+ return pattern.test(body.agentUrl);
52
+ });
53
+ if (!isAllowed) {
54
+ return new Response(
55
+ JSON.stringify({ error: "Agent URL not allowed" }),
56
+ { status: 403, headers: { "Content-Type": "application/json" } }
57
+ );
58
+ }
59
+ }
60
+ if (onRequest) {
61
+ body = await onRequest(body);
62
+ }
63
+ let normalizedUrl = body.agentUrl.replace(/\/$/, "");
64
+ if (!normalizedUrl.endsWith("/jsonrpc")) {
65
+ normalizedUrl = `${normalizedUrl}/jsonrpc/`;
66
+ } else {
67
+ normalizedUrl = `${normalizedUrl}/`;
68
+ }
69
+ const payload = {
70
+ jsonrpc: "2.0",
71
+ method: "message/stream",
72
+ params: {
73
+ message: {
74
+ role: "user",
75
+ messageId: generateUUID(),
76
+ parts: [{ kind: "text", text: body.message }]
77
+ },
78
+ ...body.extensions && { extensions: body.extensions }
79
+ },
80
+ id: generateUUID()
81
+ };
82
+ const headers = {
83
+ "Content-Type": "application/json",
84
+ Accept: "text/event-stream"
85
+ };
86
+ if (body.apiKey) {
87
+ headers["Authorization"] = `Bearer ${body.apiKey}`;
88
+ }
89
+ const controller = new AbortController();
90
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
91
+ try {
92
+ const agentResponse = await fetch(normalizedUrl, {
93
+ method: "POST",
94
+ headers,
95
+ body: JSON.stringify(payload),
96
+ signal: controller.signal
97
+ });
98
+ clearTimeout(timeoutId);
99
+ if (!agentResponse.ok) {
100
+ const errorText = await agentResponse.text();
101
+ return new Response(
102
+ JSON.stringify({
103
+ error: `Agent error: ${agentResponse.status}`,
104
+ details: errorText
105
+ }),
106
+ { status: agentResponse.status, headers: { "Content-Type": "application/json" } }
107
+ );
108
+ }
109
+ if (!agentResponse.body) {
110
+ return new Response(
111
+ JSON.stringify({ error: "Agent response body is null" }),
112
+ { status: 500, headers: { "Content-Type": "application/json" } }
113
+ );
114
+ }
115
+ return new Response(agentResponse.body, {
116
+ headers: {
117
+ "Content-Type": "text/event-stream",
118
+ "Cache-Control": "no-cache",
119
+ Connection: "keep-alive"
120
+ }
121
+ });
122
+ } finally {
123
+ clearTimeout(timeoutId);
124
+ }
125
+ } catch (error) {
126
+ onError?.(error);
127
+ if (error.name === "AbortError") {
128
+ return new Response(
129
+ JSON.stringify({ error: "Request timeout" }),
130
+ { status: 504, headers: { "Content-Type": "application/json" } }
131
+ );
132
+ }
133
+ return new Response(
134
+ JSON.stringify({
135
+ error: "Internal server error",
136
+ message: error.message
137
+ }),
138
+ { status: 500, headers: { "Content-Type": "application/json" } }
139
+ );
140
+ }
141
+ };
142
+ }
143
+ // Annotate the CommonJS export names for ESM import in node:
144
+ 0 && (module.exports = {
145
+ createA2AHandler
146
+ });
147
+ //# sourceMappingURL=server.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/index.ts","../src/server/create-api-handler.ts"],"sourcesContent":["/**\n * Server-side utilities\n *\n * @example\n * import { createA2AHandler } from '@kuntur/a2a-carbon-chat-adapter/server';\n */\n\nexport { createA2AHandler } from './create-api-handler';\nexport type { A2AHandlerOptions } from './create-api-handler';\n","/**\n * Factory for creating Next.js API route handlers for A2A proxy\n *\n * @example\n * // app/api/agent/chat/route.ts\n * import { createA2AHandler } from '@kuntur/a2a-carbon-chat-adapter/server';\n *\n * export const POST = createA2AHandler({\n * allowedAgentUrls: ['https://trusted-agents.example.com']\n * });\n */\n\nexport interface A2AHandlerOptions {\n /**\n * Called before forwarding request to agent\n * Use for authentication, validation, rate limiting\n */\n onRequest?: (request: {\n agentUrl: string;\n apiKey?: string;\n message: string;\n extensions?: Record<string, unknown>;\n }) => Promise<typeof request> | typeof request;\n\n /**\n * Called on error\n */\n onError?: (error: Error) => void;\n\n /**\n * Request timeout in milliseconds\n * @default 120000 (2 minutes)\n */\n timeout?: number;\n\n /**\n * Allowed agent URL patterns (for security)\n * If provided, requests to non-matching URLs will be rejected\n */\n allowedAgentUrls?: (string | RegExp)[];\n}\n\nfunction generateUUID(): string {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\nexport function createA2AHandler(options: A2AHandlerOptions = {}) {\n const { onRequest, onError, timeout = 120000, allowedAgentUrls } = options;\n\n return async function handler(request: Request): Promise<Response> {\n try {\n let body = await request.json();\n\n // Validate required fields\n if (!body.agentUrl || !body.message) {\n return new Response(\n JSON.stringify({ error: 'Missing required fields: agentUrl, message' }),\n { status: 400, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n // Check allowed URLs\n if (allowedAgentUrls && allowedAgentUrls.length > 0) {\n const isAllowed = allowedAgentUrls.some((pattern) => {\n if (typeof pattern === 'string') {\n return body.agentUrl.startsWith(pattern);\n }\n return pattern.test(body.agentUrl);\n });\n\n if (!isAllowed) {\n return new Response(\n JSON.stringify({ error: 'Agent URL not allowed' }),\n { status: 403, headers: { 'Content-Type': 'application/json' } }\n );\n }\n }\n\n // Allow request transformation\n if (onRequest) {\n body = await onRequest(body);\n }\n\n // Normalize URL\n let normalizedUrl = body.agentUrl.replace(/\\/$/, '');\n if (!normalizedUrl.endsWith('/jsonrpc')) {\n normalizedUrl = `${normalizedUrl}/jsonrpc/`;\n } else {\n normalizedUrl = `${normalizedUrl}/`;\n }\n\n // Build A2A payload\n const payload = {\n jsonrpc: '2.0',\n method: 'message/stream',\n params: {\n message: {\n role: 'user',\n messageId: generateUUID(),\n parts: [{ kind: 'text', text: body.message }],\n },\n ...(body.extensions && { extensions: body.extensions }),\n },\n id: generateUUID(),\n };\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'text/event-stream',\n };\n\n if (body.apiKey) {\n headers['Authorization'] = `Bearer ${body.apiKey}`;\n }\n\n // Create abort controller for timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const agentResponse = await fetch(normalizedUrl, {\n method: 'POST',\n headers,\n body: JSON.stringify(payload),\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!agentResponse.ok) {\n const errorText = await agentResponse.text();\n return new Response(\n JSON.stringify({\n error: `Agent error: ${agentResponse.status}`,\n details: errorText,\n }),\n { status: agentResponse.status, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n if (!agentResponse.body) {\n return new Response(\n JSON.stringify({ error: 'Agent response body is null' }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n // Stream the response\n return new Response(agentResponse.body, {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n } finally {\n clearTimeout(timeoutId);\n }\n } catch (error) {\n onError?.(error as Error);\n\n if ((error as Error).name === 'AbortError') {\n return new Response(\n JSON.stringify({ error: 'Request timeout' }),\n { status: 504, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n return new Response(\n JSON.stringify({\n error: 'Internal server error',\n message: (error as Error).message,\n }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n );\n }\n };\n}\n\nexport default createA2AHandler;\n"],"mappings":"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","names":[]}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Factory for creating Next.js API route handlers for A2A proxy
3
+ *
4
+ * @example
5
+ * // app/api/agent/chat/route.ts
6
+ * import { createA2AHandler } from '@kuntur/a2a-carbon-chat-adapter/server';
7
+ *
8
+ * export const POST = createA2AHandler({
9
+ * allowedAgentUrls: ['https://trusted-agents.example.com']
10
+ * });
11
+ */
12
+ interface A2AHandlerOptions {
13
+ /**
14
+ * Called before forwarding request to agent
15
+ * Use for authentication, validation, rate limiting
16
+ */
17
+ onRequest?: (request: {
18
+ agentUrl: string;
19
+ apiKey?: string;
20
+ message: string;
21
+ extensions?: Record<string, unknown>;
22
+ }) => Promise<typeof request> | typeof request;
23
+ /**
24
+ * Called on error
25
+ */
26
+ onError?: (error: Error) => void;
27
+ /**
28
+ * Request timeout in milliseconds
29
+ * @default 120000 (2 minutes)
30
+ */
31
+ timeout?: number;
32
+ /**
33
+ * Allowed agent URL patterns (for security)
34
+ * If provided, requests to non-matching URLs will be rejected
35
+ */
36
+ allowedAgentUrls?: (string | RegExp)[];
37
+ }
38
+ declare function createA2AHandler(options?: A2AHandlerOptions): (request: Request) => Promise<Response>;
39
+
40
+ export { type A2AHandlerOptions, createA2AHandler };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Factory for creating Next.js API route handlers for A2A proxy
3
+ *
4
+ * @example
5
+ * // app/api/agent/chat/route.ts
6
+ * import { createA2AHandler } from '@kuntur/a2a-carbon-chat-adapter/server';
7
+ *
8
+ * export const POST = createA2AHandler({
9
+ * allowedAgentUrls: ['https://trusted-agents.example.com']
10
+ * });
11
+ */
12
+ interface A2AHandlerOptions {
13
+ /**
14
+ * Called before forwarding request to agent
15
+ * Use for authentication, validation, rate limiting
16
+ */
17
+ onRequest?: (request: {
18
+ agentUrl: string;
19
+ apiKey?: string;
20
+ message: string;
21
+ extensions?: Record<string, unknown>;
22
+ }) => Promise<typeof request> | typeof request;
23
+ /**
24
+ * Called on error
25
+ */
26
+ onError?: (error: Error) => void;
27
+ /**
28
+ * Request timeout in milliseconds
29
+ * @default 120000 (2 minutes)
30
+ */
31
+ timeout?: number;
32
+ /**
33
+ * Allowed agent URL patterns (for security)
34
+ * If provided, requests to non-matching URLs will be rejected
35
+ */
36
+ allowedAgentUrls?: (string | RegExp)[];
37
+ }
38
+ declare function createA2AHandler(options?: A2AHandlerOptions): (request: Request) => Promise<Response>;
39
+
40
+ export { type A2AHandlerOptions, createA2AHandler };
package/dist/server.js ADDED
@@ -0,0 +1,120 @@
1
+ // src/server/create-api-handler.ts
2
+ function generateUUID() {
3
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
4
+ const r = Math.random() * 16 | 0;
5
+ const v = c === "x" ? r : r & 3 | 8;
6
+ return v.toString(16);
7
+ });
8
+ }
9
+ function createA2AHandler(options = {}) {
10
+ const { onRequest, onError, timeout = 12e4, allowedAgentUrls } = options;
11
+ return async function handler(request) {
12
+ try {
13
+ let body = await request.json();
14
+ if (!body.agentUrl || !body.message) {
15
+ return new Response(
16
+ JSON.stringify({ error: "Missing required fields: agentUrl, message" }),
17
+ { status: 400, headers: { "Content-Type": "application/json" } }
18
+ );
19
+ }
20
+ if (allowedAgentUrls && allowedAgentUrls.length > 0) {
21
+ const isAllowed = allowedAgentUrls.some((pattern) => {
22
+ if (typeof pattern === "string") {
23
+ return body.agentUrl.startsWith(pattern);
24
+ }
25
+ return pattern.test(body.agentUrl);
26
+ });
27
+ if (!isAllowed) {
28
+ return new Response(
29
+ JSON.stringify({ error: "Agent URL not allowed" }),
30
+ { status: 403, headers: { "Content-Type": "application/json" } }
31
+ );
32
+ }
33
+ }
34
+ if (onRequest) {
35
+ body = await onRequest(body);
36
+ }
37
+ let normalizedUrl = body.agentUrl.replace(/\/$/, "");
38
+ if (!normalizedUrl.endsWith("/jsonrpc")) {
39
+ normalizedUrl = `${normalizedUrl}/jsonrpc/`;
40
+ } else {
41
+ normalizedUrl = `${normalizedUrl}/`;
42
+ }
43
+ const payload = {
44
+ jsonrpc: "2.0",
45
+ method: "message/stream",
46
+ params: {
47
+ message: {
48
+ role: "user",
49
+ messageId: generateUUID(),
50
+ parts: [{ kind: "text", text: body.message }]
51
+ },
52
+ ...body.extensions && { extensions: body.extensions }
53
+ },
54
+ id: generateUUID()
55
+ };
56
+ const headers = {
57
+ "Content-Type": "application/json",
58
+ Accept: "text/event-stream"
59
+ };
60
+ if (body.apiKey) {
61
+ headers["Authorization"] = `Bearer ${body.apiKey}`;
62
+ }
63
+ const controller = new AbortController();
64
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
65
+ try {
66
+ const agentResponse = await fetch(normalizedUrl, {
67
+ method: "POST",
68
+ headers,
69
+ body: JSON.stringify(payload),
70
+ signal: controller.signal
71
+ });
72
+ clearTimeout(timeoutId);
73
+ if (!agentResponse.ok) {
74
+ const errorText = await agentResponse.text();
75
+ return new Response(
76
+ JSON.stringify({
77
+ error: `Agent error: ${agentResponse.status}`,
78
+ details: errorText
79
+ }),
80
+ { status: agentResponse.status, headers: { "Content-Type": "application/json" } }
81
+ );
82
+ }
83
+ if (!agentResponse.body) {
84
+ return new Response(
85
+ JSON.stringify({ error: "Agent response body is null" }),
86
+ { status: 500, headers: { "Content-Type": "application/json" } }
87
+ );
88
+ }
89
+ return new Response(agentResponse.body, {
90
+ headers: {
91
+ "Content-Type": "text/event-stream",
92
+ "Cache-Control": "no-cache",
93
+ Connection: "keep-alive"
94
+ }
95
+ });
96
+ } finally {
97
+ clearTimeout(timeoutId);
98
+ }
99
+ } catch (error) {
100
+ onError?.(error);
101
+ if (error.name === "AbortError") {
102
+ return new Response(
103
+ JSON.stringify({ error: "Request timeout" }),
104
+ { status: 504, headers: { "Content-Type": "application/json" } }
105
+ );
106
+ }
107
+ return new Response(
108
+ JSON.stringify({
109
+ error: "Internal server error",
110
+ message: error.message
111
+ }),
112
+ { status: 500, headers: { "Content-Type": "application/json" } }
113
+ );
114
+ }
115
+ };
116
+ }
117
+ export {
118
+ createA2AHandler
119
+ };
120
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/create-api-handler.ts"],"sourcesContent":["/**\n * Factory for creating Next.js API route handlers for A2A proxy\n *\n * @example\n * // app/api/agent/chat/route.ts\n * import { createA2AHandler } from '@kuntur/a2a-carbon-chat-adapter/server';\n *\n * export const POST = createA2AHandler({\n * allowedAgentUrls: ['https://trusted-agents.example.com']\n * });\n */\n\nexport interface A2AHandlerOptions {\n /**\n * Called before forwarding request to agent\n * Use for authentication, validation, rate limiting\n */\n onRequest?: (request: {\n agentUrl: string;\n apiKey?: string;\n message: string;\n extensions?: Record<string, unknown>;\n }) => Promise<typeof request> | typeof request;\n\n /**\n * Called on error\n */\n onError?: (error: Error) => void;\n\n /**\n * Request timeout in milliseconds\n * @default 120000 (2 minutes)\n */\n timeout?: number;\n\n /**\n * Allowed agent URL patterns (for security)\n * If provided, requests to non-matching URLs will be rejected\n */\n allowedAgentUrls?: (string | RegExp)[];\n}\n\nfunction generateUUID(): string {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\nexport function createA2AHandler(options: A2AHandlerOptions = {}) {\n const { onRequest, onError, timeout = 120000, allowedAgentUrls } = options;\n\n return async function handler(request: Request): Promise<Response> {\n try {\n let body = await request.json();\n\n // Validate required fields\n if (!body.agentUrl || !body.message) {\n return new Response(\n JSON.stringify({ error: 'Missing required fields: agentUrl, message' }),\n { status: 400, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n // Check allowed URLs\n if (allowedAgentUrls && allowedAgentUrls.length > 0) {\n const isAllowed = allowedAgentUrls.some((pattern) => {\n if (typeof pattern === 'string') {\n return body.agentUrl.startsWith(pattern);\n }\n return pattern.test(body.agentUrl);\n });\n\n if (!isAllowed) {\n return new Response(\n JSON.stringify({ error: 'Agent URL not allowed' }),\n { status: 403, headers: { 'Content-Type': 'application/json' } }\n );\n }\n }\n\n // Allow request transformation\n if (onRequest) {\n body = await onRequest(body);\n }\n\n // Normalize URL\n let normalizedUrl = body.agentUrl.replace(/\\/$/, '');\n if (!normalizedUrl.endsWith('/jsonrpc')) {\n normalizedUrl = `${normalizedUrl}/jsonrpc/`;\n } else {\n normalizedUrl = `${normalizedUrl}/`;\n }\n\n // Build A2A payload\n const payload = {\n jsonrpc: '2.0',\n method: 'message/stream',\n params: {\n message: {\n role: 'user',\n messageId: generateUUID(),\n parts: [{ kind: 'text', text: body.message }],\n },\n ...(body.extensions && { extensions: body.extensions }),\n },\n id: generateUUID(),\n };\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'text/event-stream',\n };\n\n if (body.apiKey) {\n headers['Authorization'] = `Bearer ${body.apiKey}`;\n }\n\n // Create abort controller for timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const agentResponse = await fetch(normalizedUrl, {\n method: 'POST',\n headers,\n body: JSON.stringify(payload),\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!agentResponse.ok) {\n const errorText = await agentResponse.text();\n return new Response(\n JSON.stringify({\n error: `Agent error: ${agentResponse.status}`,\n details: errorText,\n }),\n { status: agentResponse.status, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n if (!agentResponse.body) {\n return new Response(\n JSON.stringify({ error: 'Agent response body is null' }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n // Stream the response\n return new Response(agentResponse.body, {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n } finally {\n clearTimeout(timeoutId);\n }\n } catch (error) {\n onError?.(error as Error);\n\n if ((error as Error).name === 'AbortError') {\n return new Response(\n JSON.stringify({ error: 'Request timeout' }),\n { status: 504, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n return new Response(\n JSON.stringify({\n error: 'Internal server error',\n message: (error as Error).message,\n }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n );\n }\n };\n}\n\nexport default createA2AHandler;\n"],"mappings":"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","names":[]}
@@ -0,0 +1,284 @@
1
+ /* src/styles/index.css */
2
+ .a2a-chat {
3
+ position: relative;
4
+ }
5
+ .a2a-chat--fullscreen {
6
+ height: 100vh;
7
+ width: 100%;
8
+ }
9
+ .a2a-chat--sidebar {
10
+ height: 100%;
11
+ width: 400px;
12
+ border-left: 1px solid var(--cds-border-subtle, #e0e0e0);
13
+ }
14
+ .a2a-chat--float {
15
+ position: fixed;
16
+ bottom: 20px;
17
+ right: 20px;
18
+ z-index: 1000;
19
+ }
20
+ .a2a-chat--loading {
21
+ display: flex;
22
+ align-items: center;
23
+ justify-content: center;
24
+ min-height: 200px;
25
+ }
26
+ .a2a-chat__spinner {
27
+ width: 40px;
28
+ height: 40px;
29
+ border: 3px solid var(--cds-border-subtle, #e0e0e0);
30
+ border-top-color: var(--cds-interactive, #0f62fe);
31
+ border-radius: 50%;
32
+ animation: a2a-spin 1s linear infinite;
33
+ }
34
+ @keyframes a2a-spin {
35
+ to {
36
+ transform: rotate(360deg);
37
+ }
38
+ }
39
+ .a2a-chat--error {
40
+ padding: 1rem;
41
+ color: var(--cds-text-error, #da1e28);
42
+ }
43
+ .a2a-chat__form-overlay {
44
+ position: absolute;
45
+ inset: 0;
46
+ background: var(--cds-overlay, rgba(22, 22, 22, 0.5));
47
+ display: flex;
48
+ align-items: center;
49
+ justify-content: center;
50
+ z-index: 100;
51
+ }
52
+ .a2a-sources-list {
53
+ margin-top: 1rem;
54
+ padding-top: 1rem;
55
+ border-top: 1px solid var(--cds-border-subtle, #e0e0e0);
56
+ }
57
+ .a2a-sources-list__title {
58
+ font-size: 0.875rem;
59
+ font-weight: 600;
60
+ margin-bottom: 0.5rem;
61
+ color: var(--cds-text-secondary, #525252);
62
+ }
63
+ .a2a-sources-list__items {
64
+ list-style: decimal inside;
65
+ margin: 0;
66
+ padding: 0;
67
+ }
68
+ .a2a-sources-list__item {
69
+ font-size: 0.875rem;
70
+ margin-bottom: 0.25rem;
71
+ }
72
+ .a2a-sources-list__item a {
73
+ color: var(--cds-link-primary, #0f62fe);
74
+ text-decoration: none;
75
+ }
76
+ .a2a-sources-list__item a:hover {
77
+ text-decoration: underline;
78
+ }
79
+ .a2a-agent-switcher {
80
+ display: flex;
81
+ align-items: center;
82
+ gap: 0.5rem;
83
+ }
84
+ .a2a-agent-switcher__label {
85
+ font-size: 0.875rem;
86
+ color: var(--cds-text-secondary, #525252);
87
+ }
88
+ .a2a-agent-switcher__select {
89
+ padding: 0.5rem;
90
+ border: 1px solid var(--cds-border-strong, #8d8d8d);
91
+ border-radius: 4px;
92
+ background: var(--cds-field, #f4f4f4);
93
+ font-size: 0.875rem;
94
+ }
95
+ .a2a-agent-switcher--tabs .a2a-agent-switcher__tabs {
96
+ display: flex;
97
+ gap: 0;
98
+ border-bottom: 1px solid var(--cds-border-subtle, #e0e0e0);
99
+ }
100
+ .a2a-agent-switcher__tab {
101
+ padding: 0.75rem 1rem;
102
+ border: none;
103
+ background: transparent;
104
+ cursor: pointer;
105
+ font-size: 0.875rem;
106
+ color: var(--cds-text-secondary, #525252);
107
+ border-bottom: 2px solid transparent;
108
+ display: flex;
109
+ align-items: center;
110
+ gap: 0.5rem;
111
+ transition: color 0.15s, border-color 0.15s;
112
+ }
113
+ .a2a-agent-switcher__tab:hover {
114
+ color: var(--cds-text-primary, #161616);
115
+ }
116
+ .a2a-agent-switcher__tab--active {
117
+ color: var(--cds-text-primary, #161616);
118
+ border-bottom-color: var(--cds-interactive, #0f62fe);
119
+ }
120
+ .a2a-agent-switcher__icon {
121
+ width: 20px;
122
+ height: 20px;
123
+ border-radius: 50%;
124
+ }
125
+ .a2a-agent-switcher--cards .a2a-agent-switcher__cards {
126
+ display: flex;
127
+ flex-wrap: wrap;
128
+ gap: 0.75rem;
129
+ }
130
+ .a2a-agent-switcher__card {
131
+ display: flex;
132
+ align-items: center;
133
+ gap: 0.75rem;
134
+ padding: 0.75rem 1rem;
135
+ border: 1px solid var(--cds-border-subtle, #e0e0e0);
136
+ border-radius: 8px;
137
+ background: var(--cds-layer, #ffffff);
138
+ cursor: pointer;
139
+ transition: border-color 0.2s, box-shadow 0.2s;
140
+ }
141
+ .a2a-agent-switcher__card:hover {
142
+ border-color: var(--cds-interactive, #0f62fe);
143
+ }
144
+ .a2a-agent-switcher__card--active {
145
+ border-color: var(--cds-interactive, #0f62fe);
146
+ box-shadow: 0 0 0 1px var(--cds-interactive, #0f62fe);
147
+ }
148
+ .a2a-agent-switcher__card-icon {
149
+ width: 32px;
150
+ height: 32px;
151
+ border-radius: 50%;
152
+ }
153
+ .a2a-agent-switcher__card-content {
154
+ display: flex;
155
+ flex-direction: column;
156
+ gap: 0.25rem;
157
+ }
158
+ .a2a-agent-switcher__card-name {
159
+ font-size: 0.875rem;
160
+ font-weight: 500;
161
+ color: var(--cds-text-primary, #161616);
162
+ }
163
+ .a2a-agent-switcher__card-description {
164
+ font-size: 0.75rem;
165
+ color: var(--cds-text-secondary, #525252);
166
+ }
167
+ .a2a-citation-marker {
168
+ display: inline-flex;
169
+ align-items: center;
170
+ justify-content: center;
171
+ min-width: 1.25rem;
172
+ height: 1.25rem;
173
+ padding: 0 0.25rem;
174
+ margin: 0 0.125rem;
175
+ font-size: 0.75rem;
176
+ font-weight: 500;
177
+ color: var(--cds-link-primary, #0f62fe);
178
+ background: var(--cds-background-hover, #e8e8e8);
179
+ border-radius: 0.25rem;
180
+ cursor: pointer;
181
+ vertical-align: super;
182
+ transition: background-color 0.15s;
183
+ }
184
+ .a2a-citation-marker:hover {
185
+ background: var(--cds-background-active, #c6c6c6);
186
+ }
187
+ .a2a-error {
188
+ padding: 1rem;
189
+ background: var(--cds-notification-error-background, #fff1f1);
190
+ border-left: 3px solid var(--cds-support-error, #da1e28);
191
+ border-radius: 0 4px 4px 0;
192
+ }
193
+ .a2a-error__title {
194
+ font-weight: 600;
195
+ color: var(--cds-text-error, #da1e28);
196
+ margin-bottom: 0.5rem;
197
+ }
198
+ .a2a-error__message {
199
+ font-size: 0.875rem;
200
+ color: var(--cds-text-primary, #161616);
201
+ }
202
+ .a2a-error__stack {
203
+ margin-top: 0.75rem;
204
+ padding: 0.75rem;
205
+ background: var(--cds-field, #f4f4f4);
206
+ border-radius: 4px;
207
+ font-family: monospace;
208
+ font-size: 0.75rem;
209
+ white-space: pre-wrap;
210
+ overflow-x: auto;
211
+ }
212
+ .a2a-form {
213
+ background: var(--cds-layer, #ffffff);
214
+ border-radius: 8px;
215
+ padding: 1.5rem;
216
+ max-width: 400px;
217
+ width: 100%;
218
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
219
+ }
220
+ .a2a-form__title {
221
+ font-size: 1.125rem;
222
+ font-weight: 600;
223
+ margin-bottom: 0.5rem;
224
+ }
225
+ .a2a-form__description {
226
+ font-size: 0.875rem;
227
+ color: var(--cds-text-secondary, #525252);
228
+ margin-bottom: 1.5rem;
229
+ }
230
+ .a2a-form__fields {
231
+ display: flex;
232
+ flex-direction: column;
233
+ gap: 1rem;
234
+ }
235
+ .a2a-form__field {
236
+ display: flex;
237
+ flex-direction: column;
238
+ gap: 0.375rem;
239
+ }
240
+ .a2a-form__label {
241
+ font-size: 0.875rem;
242
+ font-weight: 500;
243
+ }
244
+ .a2a-form__input {
245
+ padding: 0.625rem 0.75rem;
246
+ border: 1px solid var(--cds-border-strong, #8d8d8d);
247
+ border-radius: 4px;
248
+ font-size: 0.875rem;
249
+ background: var(--cds-field, #f4f4f4);
250
+ }
251
+ .a2a-form__input:focus {
252
+ outline: 2px solid var(--cds-focus, #0f62fe);
253
+ outline-offset: -2px;
254
+ }
255
+ .a2a-form__actions {
256
+ display: flex;
257
+ justify-content: flex-end;
258
+ gap: 0.75rem;
259
+ margin-top: 1.5rem;
260
+ }
261
+ .a2a-form__button {
262
+ padding: 0.625rem 1rem;
263
+ border-radius: 4px;
264
+ font-size: 0.875rem;
265
+ font-weight: 500;
266
+ cursor: pointer;
267
+ transition: background-color 0.15s;
268
+ }
269
+ .a2a-form__button--secondary {
270
+ background: transparent;
271
+ border: 1px solid var(--cds-border-strong, #8d8d8d);
272
+ color: var(--cds-text-primary, #161616);
273
+ }
274
+ .a2a-form__button--secondary:hover {
275
+ background: var(--cds-background-hover, #e8e8e8);
276
+ }
277
+ .a2a-form__button--primary {
278
+ background: var(--cds-interactive, #0f62fe);
279
+ border: none;
280
+ color: #ffffff;
281
+ }
282
+ .a2a-form__button--primary:hover {
283
+ background: var(--cds-interactive-hover, #0353e9);
284
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kuntur/a2a-carbon-chat-adapter",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "A2A protocol adapter for Carbon AI Chat - connect any A2A agent to Carbon Chat UI",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",