@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
@@ -1,108 +0,0 @@
1
- "use client";
2
-
3
- import { useExternalMessageConverter } from "@assistant-ui/react";
4
- import { A2AMessage } from "./types";
5
- import { ToolCallMessagePart } from "@assistant-ui/react";
6
- import { ThreadUserMessage } from "@assistant-ui/react";
7
-
8
- const contentToParts = (content: A2AMessage["content"]) => {
9
- if (typeof content === "string")
10
- return [{ type: "text" as const, text: content }];
11
- return content
12
- .map((part): ThreadUserMessage["content"][number] | null => {
13
- const type = part.type;
14
- switch (type) {
15
- case "text":
16
- return { type: "text", text: part.text };
17
- case "image_url":
18
- if (typeof part.image_url === "string") {
19
- return { type: "image", image: part.image_url };
20
- } else {
21
- return {
22
- type: "image",
23
- image: part.image_url.url,
24
- };
25
- }
26
- case "data":
27
- // Convert data parts to text representation for display
28
- return {
29
- type: "text",
30
- text: `[Data: ${JSON.stringify(part.data)}]`,
31
- };
32
- default:
33
- return null;
34
- }
35
- })
36
- .filter((part): part is NonNullable<typeof part> => part !== null);
37
- };
38
-
39
- export const convertA2AMessage = (message: A2AMessage) => {
40
- const role = message.role;
41
- switch (role) {
42
- case "system":
43
- return {
44
- id: message.id,
45
- role: "system" as const,
46
- content: [{ type: "text" as const, text: message.content as string }],
47
- };
48
-
49
- case "user":
50
- return {
51
- id: message.id,
52
- role: "user" as const,
53
- content: contentToParts(message.content),
54
- };
55
-
56
- case "assistant": {
57
- const toolCallParts: ToolCallMessagePart[] =
58
- message.tool_calls?.map((toolCall) => ({
59
- type: "tool-call",
60
- toolCallId: toolCall.id,
61
- toolName: toolCall.name,
62
- args: toolCall.args,
63
- argsText: toolCall.argsText ?? JSON.stringify(toolCall.args),
64
- })) ?? [];
65
-
66
- return {
67
- id: message.id,
68
- role: "assistant" as const,
69
- content: [...contentToParts(message.content), ...toolCallParts],
70
- status: message.status,
71
- };
72
- }
73
-
74
- case "tool":
75
- return {
76
- id: message.id,
77
- role: "user" as const,
78
- content: [
79
- {
80
- type: "tool-call" as const,
81
- toolCallId: message.tool_call_id!,
82
- toolName: "", // A2A doesn't store tool name in tool messages
83
- result: JSON.parse(message.content as string),
84
- isError:
85
- message.status?.type === "incomplete" &&
86
- message.status?.reason === "error",
87
- },
88
- ],
89
- };
90
-
91
- default:
92
- const _exhaustiveCheck: never = role;
93
- throw new Error(`Unknown message role: ${_exhaustiveCheck}`);
94
- }
95
- };
96
-
97
- export const convertA2AMessages = (messages: A2AMessage[]) =>
98
- messages.map(convertA2AMessage);
99
-
100
- export const useA2AMessageConverter = (
101
- messages: A2AMessage[],
102
- isRunning: boolean,
103
- ) =>
104
- useExternalMessageConverter({
105
- callback: convertA2AMessage,
106
- messages,
107
- isRunning,
108
- });
package/src/testUtils.ts DELETED
@@ -1,11 +0,0 @@
1
- import { A2AMessage } from "./types";
2
- import { A2AMessagesEvent } from "./useA2AMessages";
3
-
4
- export const mockStreamCallbackFactory = (
5
- events: Array<A2AMessagesEvent<A2AMessage>>,
6
- ) =>
7
- async function* () {
8
- for (const event of events) {
9
- yield event;
10
- }
11
- };
@@ -1,180 +0,0 @@
1
- import { useState, useCallback, useRef, useMemo } from "react";
2
- import { v4 as uuidv4 } from "uuid";
3
- import { A2AMessageAccumulator } from "./A2AMessageAccumulator";
4
- import {
5
- A2AEvent,
6
- A2AKnownEventTypes,
7
- A2ATaskState,
8
- A2AArtifact,
9
- A2ASendMessageConfig,
10
- A2AStreamCallback,
11
- OnTaskUpdateEventCallback,
12
- OnArtifactsEventCallback,
13
- OnErrorEventCallback,
14
- OnStateUpdateEventCallback,
15
- OnCustomEventCallback,
16
- } from "./types";
17
-
18
- export type A2AMessagesEvent<_TMessage> = A2AEvent;
19
-
20
- const DEFAULT_APPEND_MESSAGE = <TMessage>(
21
- _: TMessage | undefined,
22
- curr: TMessage,
23
- ) => curr;
24
-
25
- export const useA2AMessages = <TMessage extends { id?: string }>({
26
- stream,
27
- appendMessage = DEFAULT_APPEND_MESSAGE,
28
- eventHandlers,
29
- }: {
30
- stream: A2AStreamCallback<TMessage>;
31
- appendMessage?: (prev: TMessage | undefined, curr: TMessage) => TMessage;
32
- eventHandlers?: {
33
- onTaskUpdate?: OnTaskUpdateEventCallback;
34
- onArtifacts?: OnArtifactsEventCallback;
35
- onError?: OnErrorEventCallback;
36
- onStateUpdate?: OnStateUpdateEventCallback;
37
- onCustomEvent?: OnCustomEventCallback;
38
- };
39
- }) => {
40
- const [messages, setMessages] = useState<TMessage[]>([]);
41
- const [taskState, setTaskState] = useState<A2ATaskState | undefined>();
42
- const [artifacts, setArtifacts] = useState<A2AArtifact[]>([]);
43
- const abortControllerRef = useRef<AbortController | null>(null);
44
-
45
- const { onTaskUpdate, onArtifacts, onError, onStateUpdate, onCustomEvent } =
46
- useMemo(() => eventHandlers ?? {}, [eventHandlers]);
47
-
48
- const sendMessage = useCallback(
49
- async (newMessages: TMessage[], config: A2ASendMessageConfig) => {
50
- // ensure all messages have an ID
51
- const newMessagesWithId = newMessages.map((m) =>
52
- m.id ? m : { ...m, id: uuidv4() },
53
- );
54
-
55
- const accumulator = new A2AMessageAccumulator({
56
- initialMessages: messages,
57
- appendMessage,
58
- });
59
- setMessages(accumulator.addMessages(newMessagesWithId));
60
-
61
- const abortController = new AbortController();
62
- abortControllerRef.current = abortController;
63
- const response = await stream(newMessagesWithId, {
64
- ...config,
65
- abortSignal: abortController.signal,
66
- });
67
-
68
- for await (const event of response) {
69
- switch (event.event) {
70
- case A2AKnownEventTypes.TaskUpdate:
71
- const taskData = event.data as A2ATaskState;
72
- setTaskState(taskData);
73
- onTaskUpdate?.(taskData);
74
- break;
75
-
76
- case A2AKnownEventTypes.TaskComplete:
77
- // Extract messages and artifacts from completed task
78
- const { messages: taskMessages, artifacts: taskArtifacts } =
79
- event.data;
80
- if (taskMessages) {
81
- setMessages(accumulator.addMessages(taskMessages));
82
- }
83
- if (taskArtifacts) {
84
- setArtifacts(taskArtifacts);
85
- onArtifacts?.(taskArtifacts);
86
- }
87
- // Clear task state on completion
88
- setTaskState(undefined);
89
- break;
90
-
91
- case A2AKnownEventTypes.TaskFailed:
92
- onError?.(event.data);
93
- // Update task state to failed
94
- if (taskState) {
95
- setTaskState({
96
- ...taskState,
97
- state: "failed",
98
- message: event.data?.message,
99
- });
100
- }
101
- break;
102
-
103
- case A2AKnownEventTypes.Artifacts:
104
- const artifactData = event.data as A2AArtifact[];
105
- setArtifacts(artifactData);
106
- onArtifacts?.(artifactData);
107
- break;
108
-
109
- case A2AKnownEventTypes.StateUpdate:
110
- onStateUpdate?.(event.data);
111
- break;
112
-
113
- case A2AKnownEventTypes.Error:
114
- onError?.(event.data);
115
- // Update the last assistant message with error status if available
116
- const messages = accumulator.getMessages();
117
- const lastAssistantMessage = messages.findLast(
118
- (m): m is TMessage & { role: string; id: string } =>
119
- m != null &&
120
- "role" in m &&
121
- m.role === "assistant" &&
122
- m.id != null,
123
- );
124
- if (lastAssistantMessage) {
125
- const errorMessage = {
126
- ...lastAssistantMessage,
127
- status: {
128
- type: "incomplete" as const,
129
- reason: "error" as const,
130
- error: event.data,
131
- },
132
- };
133
- setMessages(accumulator.addMessages([errorMessage]));
134
- }
135
- break;
136
-
137
- default:
138
- if (onCustomEvent) {
139
- onCustomEvent(event.event, event.data);
140
- } else {
141
- console.warn(
142
- "Unhandled A2A event received:",
143
- event.event,
144
- event.data,
145
- );
146
- }
147
- break;
148
- }
149
- }
150
- },
151
- [
152
- messages,
153
- appendMessage,
154
- stream,
155
- onTaskUpdate,
156
- onArtifacts,
157
- onError,
158
- onStateUpdate,
159
- onCustomEvent,
160
- taskState,
161
- ],
162
- );
163
-
164
- const cancel = useCallback(() => {
165
- if (abortControllerRef.current) {
166
- abortControllerRef.current.abort();
167
- }
168
- }, []);
169
-
170
- return {
171
- messages,
172
- artifacts,
173
- taskState,
174
- sendMessage,
175
- cancel,
176
- setMessages,
177
- setArtifacts,
178
- setTaskState,
179
- };
180
- };