@assistant-ui/react-a2a 0.2.4 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +47 -1
  2. package/dist/A2AClient.d.ts +43 -0
  3. package/dist/A2AClient.d.ts.map +1 -0
  4. package/dist/A2AClient.js +358 -0
  5. package/dist/A2AClient.js.map +1 -0
  6. package/dist/A2AThreadRuntimeCore.d.ts +75 -0
  7. package/dist/A2AThreadRuntimeCore.d.ts.map +1 -0
  8. package/dist/A2AThreadRuntimeCore.js +483 -0
  9. package/dist/A2AThreadRuntimeCore.js.map +1 -0
  10. package/dist/conversions.d.ts +14 -0
  11. package/dist/conversions.d.ts.map +1 -0
  12. package/dist/conversions.js +92 -0
  13. package/dist/conversions.js.map +1 -0
  14. package/dist/index.d.ts +7 -6
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +7 -6
  17. package/dist/index.js.map +1 -1
  18. package/dist/types.d.ts +228 -84
  19. package/dist/types.d.ts.map +1 -1
  20. package/dist/types.js +4 -9
  21. package/dist/types.js.map +1 -1
  22. package/dist/useA2ARuntime.d.ts +35 -48
  23. package/dist/useA2ARuntime.d.ts.map +1 -1
  24. package/dist/useA2ARuntime.js +126 -172
  25. package/dist/useA2ARuntime.js.map +1 -1
  26. package/package.json +10 -10
  27. package/src/A2AClient.test.ts +773 -0
  28. package/src/A2AClient.ts +519 -0
  29. package/src/A2AThreadRuntimeCore.test.ts +692 -0
  30. package/src/A2AThreadRuntimeCore.ts +633 -0
  31. package/src/conversions.test.ts +276 -0
  32. package/src/conversions.ts +115 -0
  33. package/src/index.ts +66 -6
  34. package/src/types.ts +276 -95
  35. package/src/useA2ARuntime.ts +204 -296
  36. package/dist/A2AMessageAccumulator.d.ts +0 -16
  37. package/dist/A2AMessageAccumulator.d.ts.map +0 -1
  38. package/dist/A2AMessageAccumulator.js +0 -29
  39. package/dist/A2AMessageAccumulator.js.map +0 -1
  40. package/dist/appendA2AChunk.d.ts +0 -3
  41. package/dist/appendA2AChunk.d.ts.map +0 -1
  42. package/dist/appendA2AChunk.js +0 -110
  43. package/dist/appendA2AChunk.js.map +0 -1
  44. package/dist/convertA2AMessages.d.ts +0 -64
  45. package/dist/convertA2AMessages.d.ts.map +0 -1
  46. package/dist/convertA2AMessages.js +0 -90
  47. package/dist/convertA2AMessages.js.map +0 -1
  48. package/dist/testUtils.d.ts +0 -4
  49. package/dist/testUtils.d.ts.map +0 -1
  50. package/dist/testUtils.js +0 -6
  51. package/dist/testUtils.js.map +0 -1
  52. package/dist/useA2AMessages.d.ts +0 -25
  53. package/dist/useA2AMessages.d.ts.map +0 -1
  54. package/dist/useA2AMessages.js +0 -122
  55. package/dist/useA2AMessages.js.map +0 -1
  56. package/src/A2AMessageAccumulator.ts +0 -48
  57. package/src/appendA2AChunk.ts +0 -121
  58. package/src/convertA2AMessages.ts +0 -108
  59. package/src/testUtils.ts +0 -11
  60. package/src/useA2AMessages.ts +0 -180
@@ -0,0 +1,276 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ a2aPartToContent,
4
+ a2aPartsToContent,
5
+ a2aMessageToContent,
6
+ taskStateToMessageStatus,
7
+ contentPartsToA2AParts,
8
+ isTerminalTaskState,
9
+ isInterruptedTaskState,
10
+ } from "./conversions";
11
+ import type { A2APart, A2AMessage, A2ATaskState } from "./types";
12
+
13
+ describe("a2aPartToContent", () => {
14
+ it("converts text part", () => {
15
+ const part: A2APart = { text: "Hello world" };
16
+ expect(a2aPartToContent(part)).toEqual({
17
+ type: "text",
18
+ text: "Hello world",
19
+ });
20
+ });
21
+
22
+ it("converts image URL part", () => {
23
+ const part: A2APart = {
24
+ url: "https://example.com/img.png",
25
+ mediaType: "image/png",
26
+ };
27
+ expect(a2aPartToContent(part)).toEqual({
28
+ type: "image",
29
+ image: "https://example.com/img.png",
30
+ });
31
+ });
32
+
33
+ it("converts non-image URL as text link", () => {
34
+ const part: A2APart = {
35
+ url: "https://example.com/doc.pdf",
36
+ mediaType: "application/pdf",
37
+ filename: "doc.pdf",
38
+ };
39
+ expect(a2aPartToContent(part)).toEqual({
40
+ type: "text",
41
+ text: "[doc.pdf](https://example.com/doc.pdf)",
42
+ });
43
+ });
44
+
45
+ it("converts URL without filename as plain text", () => {
46
+ const part: A2APart = {
47
+ url: "https://example.com/doc.pdf",
48
+ mediaType: "application/pdf",
49
+ };
50
+ expect(a2aPartToContent(part)).toEqual({
51
+ type: "text",
52
+ text: "https://example.com/doc.pdf",
53
+ });
54
+ });
55
+
56
+ it("converts raw image bytes to data URI", () => {
57
+ const part: A2APart = {
58
+ raw: "iVBORw0KGgo=",
59
+ mediaType: "image/png",
60
+ };
61
+ expect(a2aPartToContent(part)).toEqual({
62
+ type: "image",
63
+ image: "data:image/png;base64,iVBORw0KGgo=",
64
+ });
65
+ });
66
+
67
+ it("converts raw non-image bytes as file reference", () => {
68
+ const part: A2APart = {
69
+ raw: "AAAA",
70
+ mediaType: "application/pdf",
71
+ filename: "report.pdf",
72
+ };
73
+ expect(a2aPartToContent(part)).toEqual({
74
+ type: "text",
75
+ text: "[File: report.pdf]",
76
+ });
77
+ });
78
+
79
+ it("converts raw bytes without filename", () => {
80
+ const part: A2APart = {
81
+ raw: "AAAA",
82
+ mediaType: "application/octet-stream",
83
+ };
84
+ expect(a2aPartToContent(part)).toEqual({
85
+ type: "text",
86
+ text: "[File: download]",
87
+ });
88
+ });
89
+
90
+ it("converts data part as JSON", () => {
91
+ const part: A2APart = { data: { key: "value", count: 42 } };
92
+ const result = a2aPartToContent(part);
93
+ expect(result.type).toBe("text");
94
+ expect(JSON.parse((result as { text: string }).text)).toEqual({
95
+ key: "value",
96
+ count: 42,
97
+ });
98
+ });
99
+
100
+ it("returns empty text for empty part", () => {
101
+ const part: A2APart = {};
102
+ expect(a2aPartToContent(part)).toEqual({ type: "text", text: "" });
103
+ });
104
+ });
105
+
106
+ describe("a2aPartsToContent", () => {
107
+ it("converts multiple parts", () => {
108
+ const parts: A2APart[] = [
109
+ { text: "Hello" },
110
+ { url: "https://img.com/a.jpg", mediaType: "image/jpeg" },
111
+ ];
112
+ const result = a2aPartsToContent(parts);
113
+ expect(result).toHaveLength(2);
114
+ expect(result[0]).toEqual({ type: "text", text: "Hello" });
115
+ expect(result[1]).toEqual({
116
+ type: "image",
117
+ image: "https://img.com/a.jpg",
118
+ });
119
+ });
120
+
121
+ it("handles empty parts array", () => {
122
+ expect(a2aPartsToContent([])).toEqual([]);
123
+ });
124
+ });
125
+
126
+ describe("a2aMessageToContent", () => {
127
+ it("converts message parts to content", () => {
128
+ const msg: A2AMessage = {
129
+ messageId: "m1",
130
+ role: "agent",
131
+ parts: [{ text: "Hello" }, { text: " world" }],
132
+ };
133
+ const result = a2aMessageToContent(msg);
134
+ expect(result).toHaveLength(2);
135
+ expect(result[0]).toEqual({ type: "text", text: "Hello" });
136
+ expect(result[1]).toEqual({ type: "text", text: " world" });
137
+ });
138
+ });
139
+
140
+ describe("taskStateToMessageStatus", () => {
141
+ it("maps submitted to running", () => {
142
+ expect(taskStateToMessageStatus("submitted")).toEqual({
143
+ type: "running",
144
+ });
145
+ });
146
+
147
+ it("maps working to running", () => {
148
+ expect(taskStateToMessageStatus("working")).toEqual({
149
+ type: "running",
150
+ });
151
+ });
152
+
153
+ it("maps completed to complete", () => {
154
+ expect(taskStateToMessageStatus("completed")).toEqual({
155
+ type: "complete",
156
+ reason: "stop",
157
+ });
158
+ });
159
+
160
+ it("maps failed to error", () => {
161
+ expect(taskStateToMessageStatus("failed")).toEqual({
162
+ type: "incomplete",
163
+ reason: "error",
164
+ });
165
+ });
166
+
167
+ it("maps canceled to cancelled", () => {
168
+ expect(taskStateToMessageStatus("canceled")).toEqual({
169
+ type: "incomplete",
170
+ reason: "cancelled",
171
+ });
172
+ });
173
+
174
+ it("maps rejected to error", () => {
175
+ expect(taskStateToMessageStatus("rejected")).toEqual({
176
+ type: "incomplete",
177
+ reason: "error",
178
+ });
179
+ });
180
+
181
+ it("maps input_required to requires-action", () => {
182
+ expect(taskStateToMessageStatus("input_required")).toEqual({
183
+ type: "requires-action",
184
+ reason: "interrupt",
185
+ });
186
+ });
187
+
188
+ it("maps auth_required to requires-action", () => {
189
+ expect(taskStateToMessageStatus("auth_required")).toEqual({
190
+ type: "requires-action",
191
+ reason: "interrupt",
192
+ });
193
+ });
194
+
195
+ it("maps unspecified to running", () => {
196
+ expect(taskStateToMessageStatus("unspecified")).toEqual({
197
+ type: "running",
198
+ });
199
+ });
200
+ });
201
+
202
+ describe("isTerminalTaskState", () => {
203
+ it.each([
204
+ "completed",
205
+ "failed",
206
+ "canceled",
207
+ "rejected",
208
+ ] as A2ATaskState[])("returns true for %s", (state) => {
209
+ expect(isTerminalTaskState(state)).toBe(true);
210
+ });
211
+
212
+ it.each([
213
+ "submitted",
214
+ "working",
215
+ "input_required",
216
+ "auth_required",
217
+ "unspecified",
218
+ ] as A2ATaskState[])("returns false for %s", (state) => {
219
+ expect(isTerminalTaskState(state)).toBe(false);
220
+ });
221
+ });
222
+
223
+ describe("isInterruptedTaskState", () => {
224
+ it.each([
225
+ "input_required",
226
+ "auth_required",
227
+ ] as A2ATaskState[])("returns true for %s", (state) => {
228
+ expect(isInterruptedTaskState(state)).toBe(true);
229
+ });
230
+
231
+ it.each([
232
+ "submitted",
233
+ "working",
234
+ "completed",
235
+ "failed",
236
+ "canceled",
237
+ "rejected",
238
+ "unspecified",
239
+ ] as A2ATaskState[])("returns false for %s", (state) => {
240
+ expect(isInterruptedTaskState(state)).toBe(false);
241
+ });
242
+ });
243
+
244
+ describe("contentPartsToA2AParts", () => {
245
+ it("converts text parts", () => {
246
+ const result = contentPartsToA2AParts([{ type: "text", text: "hi" }]);
247
+ expect(result).toEqual([{ text: "hi" }]);
248
+ });
249
+
250
+ it("converts image parts", () => {
251
+ const result = contentPartsToA2AParts([
252
+ { type: "image", image: "https://img.com/a.png" },
253
+ ]);
254
+ expect(result).toEqual([
255
+ { url: "https://img.com/a.png", mediaType: "image/*" },
256
+ ]);
257
+ });
258
+
259
+ it("skips image parts with no URL", () => {
260
+ const result = contentPartsToA2AParts([{ type: "image" }]);
261
+ expect(result).toEqual([]);
262
+ });
263
+
264
+ it("skips unknown part types", () => {
265
+ const result = contentPartsToA2AParts([
266
+ { type: "text", text: "hi" },
267
+ { type: "audio" },
268
+ { type: "video" },
269
+ ]);
270
+ expect(result).toEqual([{ text: "hi" }]);
271
+ });
272
+
273
+ it("handles empty input", () => {
274
+ expect(contentPartsToA2AParts([])).toEqual([]);
275
+ });
276
+ });
@@ -0,0 +1,115 @@
1
+ "use client";
2
+
3
+ import type { MessageStatus, ThreadAssistantMessage } from "@assistant-ui/core";
4
+ import type { A2AMessage, A2APart, A2ATaskState } from "./types";
5
+
6
+ function isImageMediaType(mediaType?: string): boolean {
7
+ return !!mediaType && mediaType.startsWith("image/");
8
+ }
9
+
10
+ export function a2aPartToContent(
11
+ part: A2APart,
12
+ ): ThreadAssistantMessage["content"][number] {
13
+ if (part.text !== undefined) {
14
+ return { type: "text", text: part.text };
15
+ }
16
+ if (part.url !== undefined) {
17
+ if (isImageMediaType(part.mediaType)) {
18
+ return { type: "image", image: part.url };
19
+ }
20
+ return {
21
+ type: "text",
22
+ text: part.filename ? `[${part.filename}](${part.url})` : part.url,
23
+ };
24
+ }
25
+ if (part.raw !== undefined) {
26
+ if (isImageMediaType(part.mediaType)) {
27
+ return {
28
+ type: "image",
29
+ image: `data:${part.mediaType};base64,${part.raw}`,
30
+ };
31
+ }
32
+ return {
33
+ type: "text",
34
+ text: `[File: ${part.filename ?? "download"}]`,
35
+ };
36
+ }
37
+ if (part.data !== undefined) {
38
+ return { type: "text", text: JSON.stringify(part.data, null, 2) };
39
+ }
40
+ return { type: "text", text: "" };
41
+ }
42
+
43
+ export function a2aPartsToContent(
44
+ parts: A2APart[],
45
+ ): ThreadAssistantMessage["content"] {
46
+ return parts.map(a2aPartToContent);
47
+ }
48
+
49
+ const TERMINAL_STATES = new Set<A2ATaskState>([
50
+ "completed",
51
+ "failed",
52
+ "canceled",
53
+ "rejected",
54
+ ]);
55
+
56
+ const INTERRUPTED_STATES = new Set<A2ATaskState>([
57
+ "input_required",
58
+ "auth_required",
59
+ ]);
60
+
61
+ export function isTerminalTaskState(state: A2ATaskState): boolean {
62
+ return TERMINAL_STATES.has(state);
63
+ }
64
+
65
+ export function isInterruptedTaskState(state: A2ATaskState): boolean {
66
+ return INTERRUPTED_STATES.has(state);
67
+ }
68
+
69
+ export function taskStateToMessageStatus(state: A2ATaskState): MessageStatus {
70
+ switch (state) {
71
+ case "submitted":
72
+ case "working":
73
+ return { type: "running" };
74
+ case "completed":
75
+ return { type: "complete", reason: "stop" };
76
+ case "failed":
77
+ case "rejected":
78
+ return { type: "incomplete", reason: "error" };
79
+ case "canceled":
80
+ return { type: "incomplete", reason: "cancelled" };
81
+ case "input_required":
82
+ case "auth_required":
83
+ return { type: "requires-action", reason: "interrupt" };
84
+ default:
85
+ return { type: "running" };
86
+ }
87
+ }
88
+
89
+ export function contentPartsToA2AParts(
90
+ content: ReadonlyArray<{
91
+ type: string;
92
+ text?: string;
93
+ image?: string;
94
+ }>,
95
+ ): A2APart[] {
96
+ return content
97
+ .map((part): A2APart | null => {
98
+ switch (part.type) {
99
+ case "text":
100
+ return { text: part.text ?? "" };
101
+ case "image":
102
+ if (!part.image) return null;
103
+ return { url: part.image, mediaType: "image/*" };
104
+ default:
105
+ return null;
106
+ }
107
+ })
108
+ .filter((p): p is A2APart => p !== null);
109
+ }
110
+
111
+ export function a2aMessageToContent(
112
+ message: A2AMessage,
113
+ ): ThreadAssistantMessage["content"] {
114
+ return a2aPartsToContent(message.parts);
115
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,66 @@
1
- export * from "./useA2ARuntime";
2
- export * from "./useA2AMessages";
3
- export * from "./convertA2AMessages";
4
- export * from "./types";
5
- export * from "./A2AMessageAccumulator";
6
- export * from "./appendA2AChunk";
1
+ // Main hook
2
+ export {
3
+ useA2ARuntime,
4
+ useA2ATask,
5
+ useA2AArtifacts,
6
+ useA2AAgentCard,
7
+ } from "./useA2ARuntime";
8
+ export type {
9
+ UseA2ARuntimeOptions,
10
+ UseA2AThreadListAdapter,
11
+ } from "./useA2ARuntime";
12
+
13
+ // Client
14
+ export { A2AClient, A2AError } from "./A2AClient";
15
+ export type { A2AClientOptions } from "./A2AClient";
16
+
17
+ // Protocol types
18
+ export type {
19
+ A2ARole,
20
+ A2APart,
21
+ A2AMessage,
22
+ A2ATaskState,
23
+ A2ATaskStatus,
24
+ A2AArtifact,
25
+ A2ATask,
26
+ A2AStreamEvent,
27
+ A2ATaskStatusUpdateEvent,
28
+ A2ATaskArtifactUpdateEvent,
29
+ A2AAuthenticationInfo,
30
+ A2ATaskPushNotificationConfig,
31
+ A2AListTaskPushNotificationConfigsResponse,
32
+ A2ASendMessageConfiguration,
33
+ A2AListTasksRequest,
34
+ A2AListTasksResponse,
35
+ A2AErrorInfo,
36
+ A2AApiKeySecurityScheme,
37
+ A2AHttpAuthSecurityScheme,
38
+ A2AAuthorizationCodeOAuthFlow,
39
+ A2AClientCredentialsOAuthFlow,
40
+ A2ADeviceCodeOAuthFlow,
41
+ A2AImplicitOAuthFlow,
42
+ A2APasswordOAuthFlow,
43
+ A2AOAuthFlows,
44
+ A2AOAuth2SecurityScheme,
45
+ A2AOpenIdConnectSecurityScheme,
46
+ A2AMutualTlsSecurityScheme,
47
+ A2ASecurityScheme,
48
+ A2ASecurityRequirement,
49
+ A2AAgentCardSignature,
50
+ A2AAgentSkill,
51
+ A2AAgentCapabilities,
52
+ A2AAgentInterface,
53
+ A2AAgentCard,
54
+ } from "./types";
55
+ export { A2A_PROTOCOL_VERSION } from "./types";
56
+
57
+ // Conversion utilities (for advanced usage)
58
+ export {
59
+ a2aPartToContent,
60
+ a2aPartsToContent,
61
+ a2aMessageToContent,
62
+ taskStateToMessageStatus,
63
+ contentPartsToA2AParts,
64
+ isTerminalTaskState,
65
+ isInterruptedTaskState,
66
+ } from "./conversions";