@assistant-ui/react-a2a 0.2.20 → 0.2.22
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/dist/A2AClient.d.ts +8 -5
- package/dist/A2AClient.d.ts.map +1 -1
- package/dist/A2AClient.js +26 -20
- package/dist/A2AClient.js.map +1 -1
- package/dist/A2AThreadRuntimeCore.d.ts +0 -1
- package/dist/A2AThreadRuntimeCore.d.ts.map +1 -1
- package/dist/a2aExtras.d.ts +6 -0
- package/dist/a2aExtras.d.ts.map +1 -0
- package/dist/a2aExtras.js +7 -0
- package/dist/a2aExtras.js.map +1 -0
- package/dist/conversions.d.ts +0 -1
- package/dist/conversions.d.ts.map +1 -1
- package/dist/conversions.js +2 -2
- package/dist/conversions.js.map +1 -1
- package/dist/hooks.d.ts +11 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +13 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/types.d.ts +55 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/useA2ARuntime.d.ts +3 -38
- package/dist/useA2ARuntime.d.ts.map +1 -1
- package/dist/useA2ARuntime.js +4 -19
- package/dist/useA2ARuntime.js.map +1 -1
- package/package.json +6 -5
- package/src/A2AClient.test.ts +179 -4
- package/src/A2AClient.ts +44 -41
- package/src/a2aExtras.ts +4 -0
- package/src/hooks.ts +12 -0
- package/src/index.ts +3 -10
- package/src/types.ts +66 -0
- package/src/useA2ARuntime.ts +4 -108
package/dist/useA2ARuntime.js
CHANGED
|
@@ -1,24 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { A2AClient } from "./A2AClient.js";
|
|
3
3
|
import { A2AThreadRuntimeCore } from "./A2AThreadRuntimeCore.js";
|
|
4
|
+
import { a2aExtras } from "./a2aExtras.js";
|
|
4
5
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
5
6
|
import { useExternalStoreRuntime, useExternalStoreSharedOptions, useRuntimeAdapters } from "@assistant-ui/core/react";
|
|
6
|
-
import { useAuiState } from "@assistant-ui/store";
|
|
7
7
|
//#region src/useA2ARuntime.ts
|
|
8
|
-
const symbolA2AExtras = Symbol("a2a-extras");
|
|
9
|
-
const asA2AExtras = (extras) => {
|
|
10
|
-
if (typeof extras !== "object" || extras == null || !(symbolA2AExtras in extras)) throw new Error("This hook can only be used inside a useA2ARuntime provider");
|
|
11
|
-
return extras;
|
|
12
|
-
};
|
|
13
|
-
const useA2ATask = () => {
|
|
14
|
-
return useAuiState((s) => asA2AExtras(s.thread.extras).task);
|
|
15
|
-
};
|
|
16
|
-
const useA2AArtifacts = () => {
|
|
17
|
-
return useAuiState((s) => asA2AExtras(s.thread.extras).artifacts);
|
|
18
|
-
};
|
|
19
|
-
const useA2AAgentCard = () => {
|
|
20
|
-
return useAuiState((s) => asA2AExtras(s.thread.extras).agentCard);
|
|
21
|
-
};
|
|
22
8
|
function useA2ARuntime(options) {
|
|
23
9
|
const [_version, setVersion] = useState(0);
|
|
24
10
|
const notifyUpdate = useCallback(() => setVersion((v) => v + 1), []);
|
|
@@ -93,12 +79,11 @@ function useA2ARuntime(options) {
|
|
|
93
79
|
isLoading: core.isLoading,
|
|
94
80
|
messages: core.getMessages(),
|
|
95
81
|
isRunning: core.isRunning(),
|
|
96
|
-
extras: {
|
|
97
|
-
[symbolA2AExtras]: true,
|
|
82
|
+
extras: a2aExtras.provide({
|
|
98
83
|
task: core.getTask(),
|
|
99
84
|
artifacts: core.getArtifacts(),
|
|
100
85
|
agentCard: core.getAgentCard()
|
|
101
|
-
},
|
|
86
|
+
}),
|
|
102
87
|
onNew: (message) => core.append(message),
|
|
103
88
|
onEdit: (message) => core.edit(message),
|
|
104
89
|
onReload: (parentId) => core.reload(parentId),
|
|
@@ -125,6 +110,6 @@ function useA2ARuntime(options) {
|
|
|
125
110
|
return runtime;
|
|
126
111
|
}
|
|
127
112
|
//#endregion
|
|
128
|
-
export {
|
|
113
|
+
export { useA2ARuntime };
|
|
129
114
|
|
|
130
115
|
//# sourceMappingURL=useA2ARuntime.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useA2ARuntime.js","names":[],"sources":["../src/useA2ARuntime.ts"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n useExternalStoreRuntime,\n useExternalStoreSharedOptions,\n useRuntimeAdapters,\n} from \"@assistant-ui/core/react\";\nimport type {\n AssistantRuntime,\n AppendMessage,\n AttachmentAdapter,\n DictationAdapter,\n ExternalStoreAdapter,\n ExternalStoreSharedOptions,\n FeedbackAdapter,\n RealtimeVoiceAdapter,\n SpeechSynthesisAdapter,\n ThreadHistoryAdapter,\n ThreadMessage,\n} from \"@assistant-ui/core\";\nimport { useAuiState } from \"@assistant-ui/store\";\nimport { A2AClient } from \"./A2AClient\";\nimport type { A2AClientOptions } from \"./A2AClient\";\nimport { A2AThreadRuntimeCore } from \"./A2AThreadRuntimeCore\";\nimport type {\n A2AArtifact,\n A2AAgentCard,\n A2ASendMessageConfiguration,\n A2ATask,\n} from \"./types\";\n\n// --- Extras symbol for A2A-specific state ---\n\nconst symbolA2AExtras = Symbol(\"a2a-extras\");\n\ntype A2AExtras = {\n [symbolA2AExtras]: true;\n task: A2ATask | undefined;\n artifacts: readonly A2AArtifact[];\n agentCard: A2AAgentCard | undefined;\n};\n\nconst asA2AExtras = (extras: unknown): A2AExtras => {\n if (\n typeof extras !== \"object\" ||\n extras == null ||\n !(symbolA2AExtras in extras)\n )\n throw new Error(\n \"This hook can only be used inside a useA2ARuntime provider\",\n );\n return extras as A2AExtras;\n};\n\n// --- Public hooks for A2A state ---\n\nexport const useA2ATask = () => {\n return useAuiState((s) => asA2AExtras(s.thread.extras).task);\n};\n\nexport const useA2AArtifacts = () => {\n return useAuiState((s) => asA2AExtras(s.thread.extras).artifacts);\n};\n\nexport const useA2AAgentCard = () => {\n return useAuiState((s) => asA2AExtras(s.thread.extras).agentCard);\n};\n\n// --- Thread list adapter type ---\n\nexport type UseA2AThreadListAdapter = {\n threadId?: string;\n onSwitchToNewThread?: () => Promise<void> | void;\n onSwitchToThread?: (threadId: string) => Promise<{\n messages: readonly ThreadMessage[];\n }>;\n};\n\n// --- Options ---\n\nexport type UseA2ARuntimeOptions = ExternalStoreSharedOptions & {\n /** Pre-built A2A client instance. Provide this OR baseUrl. */\n client?: A2AClient;\n /** Base URL of the A2A server. Used to create a client if `client` is not provided. */\n baseUrl?: string;\n /** Optional path prefix for all API endpoints (e.g. \"/v1\"). Does not affect agent card discovery. Only used with baseUrl. */\n basePath?: string;\n /** Optional tenant ID for multi-tenant servers. Only used with baseUrl. */\n tenant?: string;\n /** Headers for the A2A client (only used with baseUrl). */\n headers?: A2AClientOptions[\"headers\"];\n /** A2A extension URIs to negotiate. Only used with baseUrl. */\n extensions?: string[];\n /** Extra fetch options (e.g. `{ credentials: 'include' }`). Only used with `baseUrl`. */\n fetchOptions?: A2AClientOptions[\"fetchOptions\"];\n\n /** Initial context ID for the conversation. */\n contextId?: string;\n /** Default send message configuration. */\n configuration?: A2ASendMessageConfiguration;\n\n /** Called when an error occurs. */\n onError?: (error: Error) => void;\n /** Called when a run is cancelled. */\n onCancel?: () => void;\n /** Called when an artifact is fully received (lastChunk). */\n onArtifactComplete?: (artifact: import(\"./types\").A2AArtifact) => void;\n\n adapters?: {\n attachments?: AttachmentAdapter;\n speech?: SpeechSynthesisAdapter;\n dictation?: DictationAdapter;\n voice?: RealtimeVoiceAdapter;\n feedback?: FeedbackAdapter;\n history?: ThreadHistoryAdapter;\n threadList?: UseA2AThreadListAdapter;\n };\n};\n\n// --- Main hook ---\n\nexport function useA2ARuntime(options: UseA2ARuntimeOptions): AssistantRuntime {\n const [_version, setVersion] = useState(0);\n const notifyUpdate = useCallback(() => setVersion((v) => v + 1), []);\n const runtimeAdapters = useRuntimeAdapters();\n const historyAdapter = options.adapters?.history ?? runtimeAdapters?.history;\n const threadListAdapter = options.adapters?.threadList;\n\n // Create or reuse client\n const clientRef = useRef<A2AClient | null>(null);\n if (!clientRef.current) {\n if (options.client) {\n clientRef.current = options.client;\n } else if (options.baseUrl) {\n clientRef.current = new A2AClient({\n baseUrl: options.baseUrl,\n basePath: options.basePath,\n tenant: options.tenant,\n headers: options.headers,\n extensions: options.extensions,\n fetchOptions: options.fetchOptions,\n });\n } else {\n throw new Error(\"useA2ARuntime requires either `client` or `baseUrl`\");\n }\n }\n const client = clientRef.current;\n\n // Create or reuse core\n const coreRef = useRef<A2AThreadRuntimeCore | null>(null);\n if (!coreRef.current) {\n coreRef.current = new A2AThreadRuntimeCore({\n client,\n contextId: options.contextId,\n configuration: options.configuration,\n ...(options.onError && { onError: options.onError }),\n ...(options.onCancel && { onCancel: options.onCancel }),\n ...(options.onArtifactComplete && {\n onArtifactComplete: options.onArtifactComplete,\n }),\n ...(historyAdapter && { history: historyAdapter }),\n notifyUpdate,\n });\n }\n\n const core = coreRef.current;\n core.updateOptions({\n client,\n contextId: options.contextId,\n configuration: options.configuration,\n ...(options.onError && { onError: options.onError }),\n ...(options.onCancel && { onCancel: options.onCancel }),\n ...(options.onArtifactComplete && {\n onArtifactComplete: options.onArtifactComplete,\n }),\n ...(historyAdapter && { history: historyAdapter }),\n });\n\n // Thread list\n const threadList = useMemo(() => {\n if (!threadListAdapter) return undefined;\n\n const { onSwitchToNewThread, onSwitchToThread } = threadListAdapter;\n\n return {\n threadId: threadListAdapter.threadId,\n onSwitchToNewThread: onSwitchToNewThread\n ? async () => {\n await onSwitchToNewThread();\n core.applyExternalMessages([]);\n }\n : undefined,\n onSwitchToThread: onSwitchToThread\n ? async (threadId: string) => {\n const result = await onSwitchToThread(threadId);\n core.applyExternalMessages(result.messages);\n }\n : undefined,\n };\n }, [threadListAdapter, core]);\n\n // Adapters\n const adapters = options.adapters;\n const adapterAdapters = useMemo(\n () => ({\n attachments: adapters?.attachments ?? runtimeAdapters?.attachments,\n speech: adapters?.speech,\n dictation: adapters?.dictation,\n voice: adapters?.voice,\n feedback: adapters?.feedback,\n threadList,\n }),\n [adapters, runtimeAdapters, threadList],\n );\n\n // Build store adapter\n const shared = useExternalStoreSharedOptions(options);\n const store = useMemo(() => {\n void _version;\n\n return {\n ...shared,\n isLoading: core.isLoading,\n messages: core.getMessages(),\n isRunning: core.isRunning(),\n extras: {\n [symbolA2AExtras]: true,\n task: core.getTask(),\n artifacts: core.getArtifacts(),\n agentCard: core.getAgentCard(),\n } satisfies A2AExtras,\n onNew: (message: AppendMessage) => core.append(message),\n onEdit: (message: AppendMessage) => core.edit(message),\n onReload: (parentId: string | null) => core.reload(parentId),\n onCancel: () => core.cancel(),\n setMessages: (messages: readonly ThreadMessage[]) =>\n core.applyExternalMessages(messages),\n onImport: (messages: readonly ThreadMessage[]) =>\n core.applyExternalMessages(messages),\n adapters: adapterAdapters,\n } satisfies ExternalStoreAdapter<ThreadMessage>;\n }, [adapterAdapters, core, _version, shared]);\n\n const runtime = useExternalStoreRuntime(store);\n\n useEffect(() => {\n core.attachRuntime(runtime);\n return () => {\n core.detachRuntime();\n };\n }, [core, runtime]);\n\n useEffect(() => {\n core.__internal_load();\n }, [core]);\n\n return runtime;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useA2ARuntime.js","names":[],"sources":["../src/useA2ARuntime.ts"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n useExternalStoreRuntime,\n useExternalStoreSharedOptions,\n useRuntimeAdapters,\n} from \"@assistant-ui/core/react\";\nimport type {\n AssistantRuntime,\n AppendMessage,\n ExternalStoreAdapter,\n ThreadMessage,\n} from \"@assistant-ui/core\";\nimport { A2AClient } from \"./A2AClient\";\nimport { A2AThreadRuntimeCore } from \"./A2AThreadRuntimeCore\";\nimport { a2aExtras } from \"./a2aExtras\";\nimport type { UseA2ARuntimeOptions } from \"./types\";\n\nexport function useA2ARuntime(options: UseA2ARuntimeOptions): AssistantRuntime {\n const [_version, setVersion] = useState(0);\n const notifyUpdate = useCallback(() => setVersion((v) => v + 1), []);\n const runtimeAdapters = useRuntimeAdapters();\n const historyAdapter = options.adapters?.history ?? runtimeAdapters?.history;\n const threadListAdapter = options.adapters?.threadList;\n\n // Create or reuse client\n const clientRef = useRef<A2AClient | null>(null);\n if (!clientRef.current) {\n if (options.client) {\n clientRef.current = options.client;\n } else if (options.baseUrl) {\n clientRef.current = new A2AClient({\n baseUrl: options.baseUrl,\n basePath: options.basePath,\n tenant: options.tenant,\n headers: options.headers,\n extensions: options.extensions,\n fetchOptions: options.fetchOptions,\n });\n } else {\n throw new Error(\"useA2ARuntime requires either `client` or `baseUrl`\");\n }\n }\n const client = clientRef.current;\n\n // Create or reuse core\n const coreRef = useRef<A2AThreadRuntimeCore | null>(null);\n if (!coreRef.current) {\n coreRef.current = new A2AThreadRuntimeCore({\n client,\n contextId: options.contextId,\n configuration: options.configuration,\n ...(options.onError && { onError: options.onError }),\n ...(options.onCancel && { onCancel: options.onCancel }),\n ...(options.onArtifactComplete && {\n onArtifactComplete: options.onArtifactComplete,\n }),\n ...(historyAdapter && { history: historyAdapter }),\n notifyUpdate,\n });\n }\n\n const core = coreRef.current;\n core.updateOptions({\n client,\n contextId: options.contextId,\n configuration: options.configuration,\n ...(options.onError && { onError: options.onError }),\n ...(options.onCancel && { onCancel: options.onCancel }),\n ...(options.onArtifactComplete && {\n onArtifactComplete: options.onArtifactComplete,\n }),\n ...(historyAdapter && { history: historyAdapter }),\n });\n\n // Thread list\n const threadList = useMemo(() => {\n if (!threadListAdapter) return undefined;\n\n const { onSwitchToNewThread, onSwitchToThread } = threadListAdapter;\n\n return {\n threadId: threadListAdapter.threadId,\n onSwitchToNewThread: onSwitchToNewThread\n ? async () => {\n await onSwitchToNewThread();\n core.applyExternalMessages([]);\n }\n : undefined,\n onSwitchToThread: onSwitchToThread\n ? async (threadId: string) => {\n const result = await onSwitchToThread(threadId);\n core.applyExternalMessages(result.messages);\n }\n : undefined,\n };\n }, [threadListAdapter, core]);\n\n // Adapters\n const adapters = options.adapters;\n const adapterAdapters = useMemo(\n () => ({\n attachments: adapters?.attachments ?? runtimeAdapters?.attachments,\n speech: adapters?.speech,\n dictation: adapters?.dictation,\n voice: adapters?.voice,\n feedback: adapters?.feedback,\n threadList,\n }),\n [adapters, runtimeAdapters, threadList],\n );\n\n // Build store adapter\n const shared = useExternalStoreSharedOptions(options);\n const store = useMemo(() => {\n void _version;\n\n return {\n ...shared,\n isLoading: core.isLoading,\n messages: core.getMessages(),\n isRunning: core.isRunning(),\n extras: a2aExtras.provide({\n task: core.getTask(),\n artifacts: core.getArtifacts(),\n agentCard: core.getAgentCard(),\n }),\n onNew: (message: AppendMessage) => core.append(message),\n onEdit: (message: AppendMessage) => core.edit(message),\n onReload: (parentId: string | null) => core.reload(parentId),\n onCancel: () => core.cancel(),\n setMessages: (messages: readonly ThreadMessage[]) =>\n core.applyExternalMessages(messages),\n onImport: (messages: readonly ThreadMessage[]) =>\n core.applyExternalMessages(messages),\n adapters: adapterAdapters,\n } satisfies ExternalStoreAdapter<ThreadMessage>;\n }, [adapterAdapters, core, _version, shared]);\n\n const runtime = useExternalStoreRuntime(store);\n\n useEffect(() => {\n core.attachRuntime(runtime);\n return () => {\n core.detachRuntime();\n };\n }, [core, runtime]);\n\n useEffect(() => {\n core.__internal_load();\n }, [core]);\n\n return runtime;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/react-a2a",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.22",
|
|
4
4
|
"description": "A2A (Agent-to-Agent) v1.0 protocol adapter for assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"a2a",
|
|
@@ -29,8 +29,9 @@
|
|
|
29
29
|
],
|
|
30
30
|
"sideEffects": false,
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@assistant-ui/core": "^0.2.
|
|
33
|
-
"@assistant-ui/store": "^0.2.
|
|
32
|
+
"@assistant-ui/core": "^0.2.21",
|
|
33
|
+
"@assistant-ui/store": "^0.2.20",
|
|
34
|
+
"assistant-stream": "^0.3.26"
|
|
34
35
|
},
|
|
35
36
|
"peerDependencies": {
|
|
36
37
|
"@types/react": "*",
|
|
@@ -44,8 +45,8 @@
|
|
|
44
45
|
"devDependencies": {
|
|
45
46
|
"@types/react": "^19.2.17",
|
|
46
47
|
"react": "^19.2.7",
|
|
47
|
-
"vitest": "^4.1.
|
|
48
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
48
|
+
"vitest": "^4.1.10",
|
|
49
|
+
"@assistant-ui/x-buildutils": "0.0.18"
|
|
49
50
|
},
|
|
50
51
|
"publishConfig": {
|
|
51
52
|
"access": "public",
|
package/src/A2AClient.test.ts
CHANGED
|
@@ -14,17 +14,23 @@ function mockFetchResponse(body: unknown, ok = true, status = 200): Response {
|
|
|
14
14
|
} as unknown as Response;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
function
|
|
18
|
-
|
|
17
|
+
function mockSSETextResponse(
|
|
18
|
+
text: string | string[],
|
|
19
|
+
contentType: string | null = "text/event-stream",
|
|
20
|
+
): Response {
|
|
19
21
|
const encoder = new TextEncoder();
|
|
20
|
-
const chunks =
|
|
22
|
+
const chunks = (Array.isArray(text) ? text : [text]).map((chunk) =>
|
|
23
|
+
encoder.encode(chunk),
|
|
24
|
+
);
|
|
21
25
|
let index = 0;
|
|
26
|
+
const headers = new Headers();
|
|
27
|
+
if (contentType !== null) headers.set("content-type", contentType);
|
|
22
28
|
|
|
23
29
|
return {
|
|
24
30
|
ok: true,
|
|
25
31
|
status: 200,
|
|
26
32
|
statusText: "OK",
|
|
27
|
-
headers
|
|
33
|
+
headers,
|
|
28
34
|
body: {
|
|
29
35
|
getReader: () => ({
|
|
30
36
|
read: vi.fn().mockImplementation(() => {
|
|
@@ -42,6 +48,13 @@ function mockSSEResponse(lines: string[]): Response {
|
|
|
42
48
|
} as unknown as Response;
|
|
43
49
|
}
|
|
44
50
|
|
|
51
|
+
function mockSSEResponse(
|
|
52
|
+
lines: string[],
|
|
53
|
+
contentType: string | null = "text/event-stream",
|
|
54
|
+
): Response {
|
|
55
|
+
return mockSSETextResponse(lines.join("\n"), contentType);
|
|
56
|
+
}
|
|
57
|
+
|
|
45
58
|
const userMessage: A2AMessage = {
|
|
46
59
|
messageId: "msg-1",
|
|
47
60
|
role: "user",
|
|
@@ -799,6 +812,111 @@ describe("A2AClient", () => {
|
|
|
799
812
|
expect(evt.event.status.message?.role).toBe("agent");
|
|
800
813
|
});
|
|
801
814
|
|
|
815
|
+
it("parses CRLF-delimited SSE events", async () => {
|
|
816
|
+
const sseData = JSON.stringify({
|
|
817
|
+
status_update: {
|
|
818
|
+
task_id: "t1",
|
|
819
|
+
context_id: "ctx-1",
|
|
820
|
+
status: { state: "TASK_STATE_WORKING" },
|
|
821
|
+
},
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
fetchMock.mockResolvedValue(
|
|
825
|
+
mockSSETextResponse(`data: ${sseData}\r\n\r\n`),
|
|
826
|
+
);
|
|
827
|
+
|
|
828
|
+
const events: A2AStreamEvent[] = [];
|
|
829
|
+
for await (const event of client.streamMessage(userMessage)) {
|
|
830
|
+
events.push(event);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
expect(events).toHaveLength(1);
|
|
834
|
+
expect(events[0]!.type).toBe("statusUpdate");
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
it("parses CR-delimited SSE events", async () => {
|
|
838
|
+
const sseData = JSON.stringify({
|
|
839
|
+
status_update: {
|
|
840
|
+
task_id: "t1",
|
|
841
|
+
context_id: "ctx-1",
|
|
842
|
+
status: { state: "TASK_STATE_WORKING" },
|
|
843
|
+
},
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
fetchMock.mockResolvedValue(mockSSETextResponse(`data: ${sseData}\r\r`));
|
|
847
|
+
|
|
848
|
+
const events: A2AStreamEvent[] = [];
|
|
849
|
+
for await (const event of client.streamMessage(userMessage)) {
|
|
850
|
+
events.push(event);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
expect(events).toHaveLength(1);
|
|
854
|
+
expect(events[0]!.type).toBe("statusUpdate");
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
it("parses CRLF delimiters split across chunks", async () => {
|
|
858
|
+
const sseData = JSON.stringify({
|
|
859
|
+
status_update: {
|
|
860
|
+
task_id: "t1",
|
|
861
|
+
context_id: "ctx-1",
|
|
862
|
+
status: { state: "TASK_STATE_WORKING" },
|
|
863
|
+
},
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
fetchMock.mockResolvedValue(
|
|
867
|
+
mockSSETextResponse([`data: ${sseData}\r`, "\n\r", "\n"]),
|
|
868
|
+
);
|
|
869
|
+
|
|
870
|
+
const events: A2AStreamEvent[] = [];
|
|
871
|
+
for await (const event of client.streamMessage(userMessage)) {
|
|
872
|
+
events.push(event);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
expect(events).toHaveLength(1);
|
|
876
|
+
expect(events[0]!.type).toBe("statusUpdate");
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
it("parses CR-delimited events split across chunks", async () => {
|
|
880
|
+
const sseData = JSON.stringify({
|
|
881
|
+
status_update: {
|
|
882
|
+
task_id: "t1",
|
|
883
|
+
context_id: "ctx-1",
|
|
884
|
+
status: { state: "TASK_STATE_WORKING" },
|
|
885
|
+
},
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
fetchMock.mockResolvedValue(
|
|
889
|
+
mockSSETextResponse([`data: ${sseData}\r`, "\r"]),
|
|
890
|
+
);
|
|
891
|
+
|
|
892
|
+
const events: A2AStreamEvent[] = [];
|
|
893
|
+
for await (const event of client.streamMessage(userMessage)) {
|
|
894
|
+
events.push(event);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
expect(events).toHaveLength(1);
|
|
898
|
+
expect(events[0]!.type).toBe("statusUpdate");
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
it("does not dispatch unterminated SSE events", async () => {
|
|
902
|
+
const sseData = JSON.stringify({
|
|
903
|
+
status_update: {
|
|
904
|
+
task_id: "t1",
|
|
905
|
+
context_id: "ctx-1",
|
|
906
|
+
status: { state: "TASK_STATE_WORKING" },
|
|
907
|
+
},
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
fetchMock.mockResolvedValue(mockSSETextResponse(`data: ${sseData}`));
|
|
911
|
+
|
|
912
|
+
const events: A2AStreamEvent[] = [];
|
|
913
|
+
for await (const event of client.streamMessage(userMessage)) {
|
|
914
|
+
events.push(event);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
expect(events).toHaveLength(0);
|
|
918
|
+
});
|
|
919
|
+
|
|
802
920
|
it("parses SSE artifact update events", async () => {
|
|
803
921
|
const sseData = JSON.stringify({
|
|
804
922
|
artifact_update: {
|
|
@@ -887,6 +1005,63 @@ describe("A2AClient", () => {
|
|
|
887
1005
|
expect(events).toHaveLength(2);
|
|
888
1006
|
});
|
|
889
1007
|
|
|
1008
|
+
it("accepts parameterized event-stream content types", async () => {
|
|
1009
|
+
const sseData = JSON.stringify({
|
|
1010
|
+
status_update: {
|
|
1011
|
+
task_id: "t1",
|
|
1012
|
+
context_id: "ctx-1",
|
|
1013
|
+
status: { state: "TASK_STATE_COMPLETED" },
|
|
1014
|
+
},
|
|
1015
|
+
});
|
|
1016
|
+
|
|
1017
|
+
fetchMock.mockResolvedValue(
|
|
1018
|
+
mockSSEResponse(
|
|
1019
|
+
[`data: ${sseData}`, "", ""],
|
|
1020
|
+
"text/event-stream; charset=utf-8",
|
|
1021
|
+
),
|
|
1022
|
+
);
|
|
1023
|
+
|
|
1024
|
+
const events: A2AStreamEvent[] = [];
|
|
1025
|
+
for await (const event of client.streamMessage(userMessage)) {
|
|
1026
|
+
events.push(event);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
expect(events).toHaveLength(1);
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
it("rejects successful responses that are not event streams", async () => {
|
|
1033
|
+
fetchMock.mockResolvedValue(
|
|
1034
|
+
mockSSETextResponse(
|
|
1035
|
+
"<html><body>Please sign in</body></html>",
|
|
1036
|
+
"text/html; charset=utf-8",
|
|
1037
|
+
),
|
|
1038
|
+
);
|
|
1039
|
+
|
|
1040
|
+
const consumeStream = async () => {
|
|
1041
|
+
for await (const event of client.streamMessage(userMessage)) {
|
|
1042
|
+
void event;
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
await expect(consumeStream()).rejects.toThrow(
|
|
1047
|
+
'Expected A2A stream response Content-Type "text/event-stream", received "text/html; charset=utf-8"',
|
|
1048
|
+
);
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
it("rejects task subscriptions without a content type", async () => {
|
|
1052
|
+
fetchMock.mockResolvedValue(mockSSETextResponse("", null));
|
|
1053
|
+
|
|
1054
|
+
const consumeStream = async () => {
|
|
1055
|
+
for await (const event of client.subscribeToTask("t1")) {
|
|
1056
|
+
void event;
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1060
|
+
await expect(consumeStream()).rejects.toThrow(
|
|
1061
|
+
'Expected A2A stream response Content-Type "text/event-stream", received no Content-Type header',
|
|
1062
|
+
);
|
|
1063
|
+
});
|
|
1064
|
+
|
|
890
1065
|
it("normalizes 'content' array from v0.3 server response to 'parts' in SSE artifact update events", async () => {
|
|
891
1066
|
const sseData = JSON.stringify({
|
|
892
1067
|
artifact_update: {
|
package/src/A2AClient.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SSEEventDecoder, type SSEEvent } from "assistant-stream/utils";
|
|
1
2
|
import type {
|
|
2
3
|
A2AAgentCard,
|
|
3
4
|
A2AErrorInfo,
|
|
@@ -493,57 +494,59 @@ export class A2AClient {
|
|
|
493
494
|
// --- SSE Parsing ---
|
|
494
495
|
|
|
495
496
|
private async *parseSSE(response: Response): AsyncGenerator<A2AStreamEvent> {
|
|
497
|
+
const contentType = response.headers.get("Content-Type");
|
|
498
|
+
const mediaType = contentType?.split(";", 1)[0]?.trim().toLowerCase();
|
|
499
|
+
if (mediaType !== "text/event-stream") {
|
|
500
|
+
const received = contentType
|
|
501
|
+
? `"${contentType}"`
|
|
502
|
+
: "no Content-Type header";
|
|
503
|
+
throw new Error(
|
|
504
|
+
`Expected A2A stream response Content-Type "text/event-stream", received ${received}`,
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
|
|
496
508
|
const reader = response.body?.getReader();
|
|
497
509
|
if (!reader) throw new Error("No response body");
|
|
498
510
|
|
|
499
511
|
const decoder = new TextDecoder();
|
|
500
|
-
|
|
512
|
+
const sseDecoder = new SSEEventDecoder();
|
|
513
|
+
|
|
514
|
+
const readEvent = (event: SSEEvent): A2AStreamEvent | null => {
|
|
515
|
+
try {
|
|
516
|
+
let parsed = JSON.parse(event.data);
|
|
517
|
+
|
|
518
|
+
if (
|
|
519
|
+
parsed &&
|
|
520
|
+
typeof parsed === "object" &&
|
|
521
|
+
"jsonrpc" in parsed &&
|
|
522
|
+
"result" in parsed
|
|
523
|
+
) {
|
|
524
|
+
parsed = parsed.result;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const normalized = normalizeKeys(parsed) as Record<string, unknown>;
|
|
528
|
+
return discriminateStreamResponse(normalized);
|
|
529
|
+
} catch {
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
};
|
|
501
533
|
|
|
502
534
|
try {
|
|
503
535
|
while (true) {
|
|
504
536
|
const { done, value } = await reader.read();
|
|
505
|
-
if (done)
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
let eventEnd: number = buffer.indexOf("\n\n");
|
|
510
|
-
while (eventEnd !== -1) {
|
|
511
|
-
const eventText = buffer.slice(0, eventEnd);
|
|
512
|
-
buffer = buffer.slice(eventEnd + 2);
|
|
513
|
-
|
|
514
|
-
const dataLines: string[] = [];
|
|
515
|
-
|
|
516
|
-
for (const line of eventText.split("\n")) {
|
|
517
|
-
const trimmed = line.replace(/\r$/, "");
|
|
518
|
-
if (trimmed.startsWith("data:")) {
|
|
519
|
-
dataLines.push(trimmed.slice(5).trim());
|
|
520
|
-
}
|
|
521
|
-
// event:, id:, retry: lines are parsed but not used —
|
|
522
|
-
// we discriminate event type from the JSON payload.
|
|
537
|
+
if (done) {
|
|
538
|
+
for (const event of sseDecoder.push(decoder.decode())) {
|
|
539
|
+
const parsed = readEvent(event);
|
|
540
|
+
if (parsed) yield parsed;
|
|
523
541
|
}
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
524
544
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
// Unwrap JSON-RPC envelope if present
|
|
531
|
-
if (
|
|
532
|
-
parsed &&
|
|
533
|
-
typeof parsed === "object" &&
|
|
534
|
-
"jsonrpc" in parsed &&
|
|
535
|
-
"result" in parsed
|
|
536
|
-
) {
|
|
537
|
-
parsed = parsed.result;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
const normalized = normalizeKeys(parsed) as Record<string, unknown>;
|
|
541
|
-
const event = discriminateStreamResponse(normalized);
|
|
542
|
-
if (event) yield event;
|
|
543
|
-
} catch {
|
|
544
|
-
// Skip malformed events
|
|
545
|
-
}
|
|
546
|
-
eventEnd = buffer.indexOf("\n\n");
|
|
545
|
+
for (const event of sseDecoder.push(
|
|
546
|
+
decoder.decode(value, { stream: true }),
|
|
547
|
+
)) {
|
|
548
|
+
const parsed = readEvent(event);
|
|
549
|
+
if (parsed) yield parsed;
|
|
547
550
|
}
|
|
548
551
|
}
|
|
549
552
|
} finally {
|
package/src/a2aExtras.ts
ADDED
package/src/hooks.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { a2aExtras } from "./a2aExtras";
|
|
4
|
+
|
|
5
|
+
/** The current A2A task, if a run is in progress. */
|
|
6
|
+
export const useA2ATask = () => a2aExtras.use((e) => e.task);
|
|
7
|
+
|
|
8
|
+
/** The artifacts accumulated for the current A2A run. */
|
|
9
|
+
export const useA2AArtifacts = () => a2aExtras.use((e) => e.artifacts);
|
|
10
|
+
|
|
11
|
+
/** The discovered A2A agent card. */
|
|
12
|
+
export const useA2AAgentCard = () => a2aExtras.use((e) => e.agentCard);
|
package/src/index.ts
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
// Main hook
|
|
2
|
-
export {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
useA2AArtifacts,
|
|
6
|
-
useA2AAgentCard,
|
|
7
|
-
} from "./useA2ARuntime";
|
|
8
|
-
export type {
|
|
9
|
-
UseA2ARuntimeOptions,
|
|
10
|
-
UseA2AThreadListAdapter,
|
|
11
|
-
} from "./useA2ARuntime";
|
|
2
|
+
export { useA2ARuntime } from "./useA2ARuntime";
|
|
3
|
+
export { useA2ATask, useA2AArtifacts, useA2AAgentCard } from "./hooks";
|
|
4
|
+
export type { UseA2ARuntimeOptions, UseA2AThreadListAdapter } from "./types";
|
|
12
5
|
|
|
13
6
|
// Client
|
|
14
7
|
export { A2AClient, A2AError } from "./A2AClient";
|
package/src/types.ts
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
// Enum values use lowercase internally; normalized from ProtoJSON SCREAMING_SNAKE_CASE on read.
|
|
3
3
|
// Wire format uses ROLE_USER/ROLE_AGENT and TASK_STATE_* per ADR-001.
|
|
4
4
|
|
|
5
|
+
import type {
|
|
6
|
+
AttachmentAdapter,
|
|
7
|
+
DictationAdapter,
|
|
8
|
+
ExternalStoreSharedOptions,
|
|
9
|
+
FeedbackAdapter,
|
|
10
|
+
RealtimeVoiceAdapter,
|
|
11
|
+
SpeechSynthesisAdapter,
|
|
12
|
+
ThreadHistoryAdapter,
|
|
13
|
+
ThreadMessage,
|
|
14
|
+
} from "@assistant-ui/core";
|
|
15
|
+
import type { A2AClient, A2AClientOptions } from "./A2AClient";
|
|
16
|
+
|
|
5
17
|
export const A2A_PROTOCOL_VERSION = "1.0";
|
|
6
18
|
|
|
7
19
|
export type A2ARole = "unspecified" | "user" | "agent";
|
|
@@ -281,3 +293,57 @@ export type A2AAgentCard = {
|
|
|
281
293
|
signatures?: A2AAgentCardSignature[] | undefined;
|
|
282
294
|
iconUrl?: string | undefined;
|
|
283
295
|
};
|
|
296
|
+
|
|
297
|
+
/** Private state `useA2ARuntime` exposes through `thread.extras`. */
|
|
298
|
+
export type A2AExtras = {
|
|
299
|
+
task: A2ATask | undefined;
|
|
300
|
+
artifacts: readonly A2AArtifact[];
|
|
301
|
+
agentCard: A2AAgentCard | undefined;
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
export type UseA2AThreadListAdapter = {
|
|
305
|
+
threadId?: string;
|
|
306
|
+
onSwitchToNewThread?: () => Promise<void> | void;
|
|
307
|
+
onSwitchToThread?: (threadId: string) => Promise<{
|
|
308
|
+
messages: readonly ThreadMessage[];
|
|
309
|
+
}>;
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
export type UseA2ARuntimeOptions = ExternalStoreSharedOptions & {
|
|
313
|
+
/** Pre-built A2A client instance. Provide this OR baseUrl. */
|
|
314
|
+
client?: A2AClient;
|
|
315
|
+
/** Base URL of the A2A server. Used to create a client if `client` is not provided. */
|
|
316
|
+
baseUrl?: string;
|
|
317
|
+
/** Optional path prefix for all API endpoints (e.g. "/v1"). Does not affect agent card discovery. Only used with baseUrl. */
|
|
318
|
+
basePath?: string;
|
|
319
|
+
/** Optional tenant ID for multi-tenant servers. Only used with baseUrl. */
|
|
320
|
+
tenant?: string;
|
|
321
|
+
/** Headers for the A2A client (only used with baseUrl). */
|
|
322
|
+
headers?: A2AClientOptions["headers"];
|
|
323
|
+
/** A2A extension URIs to negotiate. Only used with baseUrl. */
|
|
324
|
+
extensions?: string[];
|
|
325
|
+
/** Extra fetch options (e.g. `{ credentials: 'include' }`). Only used with `baseUrl`. */
|
|
326
|
+
fetchOptions?: A2AClientOptions["fetchOptions"];
|
|
327
|
+
|
|
328
|
+
/** Initial context ID for the conversation. */
|
|
329
|
+
contextId?: string;
|
|
330
|
+
/** Default send message configuration. */
|
|
331
|
+
configuration?: A2ASendMessageConfiguration;
|
|
332
|
+
|
|
333
|
+
/** Called when an error occurs. */
|
|
334
|
+
onError?: (error: Error) => void;
|
|
335
|
+
/** Called when a run is cancelled. */
|
|
336
|
+
onCancel?: () => void;
|
|
337
|
+
/** Called when an artifact is fully received (lastChunk). */
|
|
338
|
+
onArtifactComplete?: (artifact: A2AArtifact) => void;
|
|
339
|
+
|
|
340
|
+
adapters?: {
|
|
341
|
+
attachments?: AttachmentAdapter;
|
|
342
|
+
speech?: SpeechSynthesisAdapter;
|
|
343
|
+
dictation?: DictationAdapter;
|
|
344
|
+
voice?: RealtimeVoiceAdapter;
|
|
345
|
+
feedback?: FeedbackAdapter;
|
|
346
|
+
history?: ThreadHistoryAdapter;
|
|
347
|
+
threadList?: UseA2AThreadListAdapter;
|
|
348
|
+
};
|
|
349
|
+
};
|