@assistant-ui/react-a2a 0.2.37 → 0.2.39
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/LICENSE +1 -1
- package/dist/useA2ARuntime.d.ts.map +1 -1
- package/dist/useA2ARuntime.js +34 -9
- package/dist/useA2ARuntime.js.map +1 -1
- package/package.json +8 -5
- package/src/useA2ARuntime.fast-refresh.test.tsx +159 -0
- package/src/useA2ARuntime.ts +38 -12
package/LICENSE
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useA2ARuntime.d.ts","sourceRoot":"","sources":["../src/useA2ARuntime.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useA2ARuntime.d.ts","sourceRoot":"","sources":["../src/useA2ARuntime.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EACV,gBAAgB,EAIjB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAgB;AAmBpD,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,gBAAgB,CAmM7E"}
|
package/dist/useA2ARuntime.js
CHANGED
|
@@ -4,6 +4,7 @@ import { A2AThreadRuntimeCore } from "./A2AThreadRuntimeCore.js";
|
|
|
4
4
|
import { a2aExtras } from "./a2aExtras.js";
|
|
5
5
|
import { useExternalStoreRuntime, useExternalStoreSharedOptions, useRuntimeAdapters } from "@assistant-ui/core/react";
|
|
6
6
|
import { useCallback, useEffect, useInsertionEffect, useMemo, useRef, useState } from "react";
|
|
7
|
+
import { useReplaySafeEffect } from "@assistant-ui/store/internal";
|
|
7
8
|
//#region src/useA2ARuntime.ts
|
|
8
9
|
const serializeManagedClientOptions = (options) => {
|
|
9
10
|
const fetchOptions = Object.fromEntries(Object.entries(options.fetchOptions ?? {}).sort(([a], [b]) => a.localeCompare(b)));
|
|
@@ -33,18 +34,29 @@ function useA2ARuntime(options) {
|
|
|
33
34
|
extensions: options.extensions,
|
|
34
35
|
fetchOptions: options.fetchOptions
|
|
35
36
|
}) : null;
|
|
36
|
-
const
|
|
37
|
+
const createClient = () => {
|
|
37
38
|
if (options.client) return options.client;
|
|
38
39
|
if (!managedClientOptionsKey) throw new Error("useA2ARuntime requires either `client` or `baseUrl`");
|
|
39
40
|
return new A2AClient({
|
|
40
41
|
...JSON.parse(managedClientOptionsKey),
|
|
41
42
|
headers: resolveHeaders
|
|
42
43
|
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
};
|
|
45
|
+
const [pinnedClient, setPinnedClient] = useState(() => ({
|
|
46
|
+
key: managedClientOptionsKey,
|
|
47
|
+
provided: options.client,
|
|
48
|
+
client: createClient()
|
|
49
|
+
}));
|
|
50
|
+
let currentClient = pinnedClient;
|
|
51
|
+
if (pinnedClient.key !== managedClientOptionsKey || pinnedClient.provided !== options.client) {
|
|
52
|
+
currentClient = {
|
|
53
|
+
key: managedClientOptionsKey,
|
|
54
|
+
provided: options.client,
|
|
55
|
+
client: createClient()
|
|
56
|
+
};
|
|
57
|
+
setPinnedClient(currentClient);
|
|
58
|
+
}
|
|
59
|
+
const client = currentClient.client;
|
|
48
60
|
const coreOptions = {
|
|
49
61
|
client,
|
|
50
62
|
contextId: options.contextId,
|
|
@@ -56,11 +68,24 @@ function useA2ARuntime(options) {
|
|
|
56
68
|
};
|
|
57
69
|
const coreOptionsRef = useRef(coreOptions);
|
|
58
70
|
coreOptionsRef.current = coreOptions;
|
|
59
|
-
const
|
|
71
|
+
const createCore = () => new A2AThreadRuntimeCore({
|
|
60
72
|
...coreOptionsRef.current,
|
|
61
73
|
client,
|
|
62
74
|
notifyUpdate
|
|
63
|
-
})
|
|
75
|
+
});
|
|
76
|
+
const [pinnedCore, setPinnedCore] = useState(() => ({
|
|
77
|
+
client,
|
|
78
|
+
core: createCore()
|
|
79
|
+
}));
|
|
80
|
+
let currentCore = pinnedCore;
|
|
81
|
+
if (pinnedCore.client !== client) {
|
|
82
|
+
currentCore = {
|
|
83
|
+
client,
|
|
84
|
+
core: createCore()
|
|
85
|
+
};
|
|
86
|
+
setPinnedCore(currentCore);
|
|
87
|
+
}
|
|
88
|
+
const core = currentCore.core;
|
|
64
89
|
useEffect(() => {
|
|
65
90
|
core.updateOptions(coreOptions);
|
|
66
91
|
});
|
|
@@ -134,7 +159,7 @@ function useA2ARuntime(options) {
|
|
|
134
159
|
shared
|
|
135
160
|
]);
|
|
136
161
|
const runtime = useExternalStoreRuntime(store);
|
|
137
|
-
|
|
162
|
+
useReplaySafeEffect(() => {
|
|
138
163
|
core.attachRuntime(runtime);
|
|
139
164
|
return () => {
|
|
140
165
|
core.detachRuntime();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useA2ARuntime.js","names":[],"sources":["../src/useA2ARuntime.ts"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useInsertionEffect,\n useMemo,\n useRef,\n useState,\n} 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, type A2AClientOptions } from \"./A2AClient\";\nimport { A2AThreadRuntimeCore } from \"./A2AThreadRuntimeCore\";\nimport { a2aExtras } from \"./a2aExtras\";\nimport type { UseA2ARuntimeOptions } from \"./types\";\n\ntype ManagedA2AClientOptions = Omit<A2AClientOptions, \"headers\">;\n\nconst serializeManagedClientOptions = (\n options: ManagedA2AClientOptions,\n): string => {\n const fetchOptions = Object.fromEntries(\n Object.entries(options.fetchOptions ?? {}).sort(([a], [b]) =>\n a.localeCompare(b),\n ),\n );\n\n return JSON.stringify({\n ...options,\n fetchOptions,\n });\n};\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 const headersRef = useRef(options.headers);\n useInsertionEffect(() => {\n headersRef.current = options.headers;\n });\n const resolveHeaders = useCallback(() => {\n const headers = headersRef.current;\n return typeof headers === \"function\" ? headers() : (headers ?? {});\n }, []);\n\n const managedClientOptionsKey = options.client\n ? null\n : options.baseUrl\n ? serializeManagedClientOptions({\n baseUrl: options.baseUrl,\n basePath: options.basePath,\n tenant: options.tenant,\n extensions: options.extensions,\n fetchOptions: options.fetchOptions,\n })\n : null;\n\n const client = useMemo(() => {\n if (options.client) return options.client;\n if (!managedClientOptionsKey) {\n throw new Error(\"useA2ARuntime requires either `client` or `baseUrl`\");\n }\n\n return new A2AClient({\n ...(JSON.parse(managedClientOptionsKey) as ManagedA2AClientOptions),\n headers: resolveHeaders,\n });\n }, [managedClientOptionsKey, options.client, resolveHeaders]);\n\n const coreOptions = {\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 const coreOptionsRef = useRef(coreOptions);\n coreOptionsRef.current = coreOptions;\n\n const core = useMemo(\n () =>\n new A2AThreadRuntimeCore({\n ...coreOptionsRef.current,\n client,\n notifyUpdate,\n }),\n [client, notifyUpdate],\n );\n\n useEffect(() => {\n core.updateOptions(coreOptions);\n });\n\n // Thread list\n const threadSwitchGenerationRef = useRef(0);\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 const generation = ++threadSwitchGenerationRef.current;\n // Clear before the thread id flips, or the old messages leak\n // into the new thread as a sibling branch.\n core.applyExternalMessages([]);\n core.resetContext();\n await onSwitchToNewThread();\n if (generation !== threadSwitchGenerationRef.current) return;\n // Apply first so the abort inside resetContext finds an already\n // cleared repository and cannot persist the old thread's partial\n // assistant message.\n core.applyExternalMessages([]);\n core.resetContext();\n }\n : undefined,\n onSwitchToThread: onSwitchToThread\n ? async (threadId: string) => {\n const generation = ++threadSwitchGenerationRef.current;\n // Clear before the thread id flips, or the old messages leak\n // into the new thread as a sibling branch.\n core.applyExternalMessages([]);\n core.resetContext();\n const result = await onSwitchToThread(threadId);\n if (generation !== threadSwitchGenerationRef.current) return;\n core.applyExternalMessages([]);\n core.applyExternalMessages(result.messages);\n core.resetContext();\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 messageRepository: core.getMessageRepository(),\n isRunning: core.isRunning(),\n unstable_persistsHistory: true,\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 onVoiceTranscript: (message: ThreadMessage) =>\n core.appendVoiceTranscript(message),\n onEdit: (message: AppendMessage) => core.edit(message),\n onReload: (parentId: string | null) => core.reload(parentId),\n onCancel: () => core.cancel(),\n unstable_onRecordToolInteraction: (options) =>\n core.recordToolInteraction(options),\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 {\n useCallback,\n useEffect,\n useInsertionEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport {\n useExternalStoreRuntime,\n useExternalStoreSharedOptions,\n useRuntimeAdapters,\n} from \"@assistant-ui/core/react\";\nimport { useReplaySafeEffect } from \"@assistant-ui/store/internal\";\nimport type {\n AssistantRuntime,\n AppendMessage,\n ExternalStoreAdapter,\n ThreadMessage,\n} from \"@assistant-ui/core\";\nimport { A2AClient, type A2AClientOptions } from \"./A2AClient\";\nimport { A2AThreadRuntimeCore } from \"./A2AThreadRuntimeCore\";\nimport { a2aExtras } from \"./a2aExtras\";\nimport type { UseA2ARuntimeOptions } from \"./types\";\n\ntype ManagedA2AClientOptions = Omit<A2AClientOptions, \"headers\">;\n\nconst serializeManagedClientOptions = (\n options: ManagedA2AClientOptions,\n): string => {\n const fetchOptions = Object.fromEntries(\n Object.entries(options.fetchOptions ?? {}).sort(([a], [b]) =>\n a.localeCompare(b),\n ),\n );\n\n return JSON.stringify({\n ...options,\n fetchOptions,\n });\n};\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 const headersRef = useRef(options.headers);\n useInsertionEffect(() => {\n headersRef.current = options.headers;\n });\n const resolveHeaders = useCallback(() => {\n const headers = headersRef.current;\n return typeof headers === \"function\" ? headers() : (headers ?? {});\n }, []);\n\n const managedClientOptionsKey = options.client\n ? null\n : options.baseUrl\n ? serializeManagedClientOptions({\n baseUrl: options.baseUrl,\n basePath: options.basePath,\n tenant: options.tenant,\n extensions: options.extensions,\n fetchOptions: options.fetchOptions,\n })\n : null;\n\n const createClient = () => {\n if (options.client) return options.client;\n if (!managedClientOptionsKey) {\n throw new Error(\"useA2ARuntime requires either `client` or `baseUrl`\");\n }\n\n return new A2AClient({\n ...(JSON.parse(managedClientOptionsKey) as ManagedA2AClientOptions),\n headers: resolveHeaders,\n });\n };\n const [pinnedClient, setPinnedClient] = useState(() => ({\n key: managedClientOptionsKey,\n provided: options.client,\n client: createClient(),\n }));\n let currentClient = pinnedClient;\n if (\n pinnedClient.key !== managedClientOptionsKey ||\n pinnedClient.provided !== options.client\n ) {\n currentClient = {\n key: managedClientOptionsKey,\n provided: options.client,\n client: createClient(),\n };\n setPinnedClient(currentClient);\n }\n const client = currentClient.client;\n\n const coreOptions = {\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 const coreOptionsRef = useRef(coreOptions);\n coreOptionsRef.current = coreOptions;\n\n const createCore = () =>\n new A2AThreadRuntimeCore({\n ...coreOptionsRef.current,\n client,\n notifyUpdate,\n });\n const [pinnedCore, setPinnedCore] = useState(() => ({\n client,\n core: createCore(),\n }));\n let currentCore = pinnedCore;\n if (pinnedCore.client !== client) {\n currentCore = { client, core: createCore() };\n setPinnedCore(currentCore);\n }\n const core = currentCore.core;\n\n useEffect(() => {\n core.updateOptions(coreOptions);\n });\n\n // Thread list\n const threadSwitchGenerationRef = useRef(0);\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 const generation = ++threadSwitchGenerationRef.current;\n // Clear before the thread id flips, or the old messages leak\n // into the new thread as a sibling branch.\n core.applyExternalMessages([]);\n core.resetContext();\n await onSwitchToNewThread();\n if (generation !== threadSwitchGenerationRef.current) return;\n // Apply first so the abort inside resetContext finds an already\n // cleared repository and cannot persist the old thread's partial\n // assistant message.\n core.applyExternalMessages([]);\n core.resetContext();\n }\n : undefined,\n onSwitchToThread: onSwitchToThread\n ? async (threadId: string) => {\n const generation = ++threadSwitchGenerationRef.current;\n // Clear before the thread id flips, or the old messages leak\n // into the new thread as a sibling branch.\n core.applyExternalMessages([]);\n core.resetContext();\n const result = await onSwitchToThread(threadId);\n if (generation !== threadSwitchGenerationRef.current) return;\n core.applyExternalMessages([]);\n core.applyExternalMessages(result.messages);\n core.resetContext();\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 messageRepository: core.getMessageRepository(),\n isRunning: core.isRunning(),\n unstable_persistsHistory: true,\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 onVoiceTranscript: (message: ThreadMessage) =>\n core.appendVoiceTranscript(message),\n onEdit: (message: AppendMessage) => core.edit(message),\n onReload: (parentId: string | null) => core.reload(parentId),\n onCancel: () => core.cancel(),\n unstable_onRecordToolInteraction: (options) =>\n core.recordToolInteraction(options),\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 useReplaySafeEffect(() => {\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.39",
|
|
4
4
|
"description": "A2A (Agent-to-Agent) v1.0 protocol adapter for assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"a2a",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
],
|
|
30
30
|
"sideEffects": false,
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@assistant-ui/core": "^0.3.
|
|
33
|
-
"@assistant-ui/react-generative-ui": "^0.0.
|
|
34
|
-
"@assistant-ui/store": "^0.3.
|
|
35
|
-
"assistant-stream": "^0.3.
|
|
32
|
+
"@assistant-ui/core": "^0.3.22",
|
|
33
|
+
"@assistant-ui/react-generative-ui": "^0.0.22",
|
|
34
|
+
"@assistant-ui/store": "^0.3.16",
|
|
35
|
+
"assistant-stream": "^0.3.46"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@types/react": "*",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@assistant-ui/x-buildutils": "0.0.29",
|
|
48
|
+
"@assistant-ui/x-react18": "0.0.0",
|
|
48
49
|
"@testing-library/dom": "^10.4.2",
|
|
49
50
|
"@testing-library/react": "^16.3.3",
|
|
50
51
|
"@types/react": "^19.3.0",
|
|
@@ -70,6 +71,8 @@
|
|
|
70
71
|
"build": "aui-build",
|
|
71
72
|
"test": "vitest run",
|
|
72
73
|
"typecheck": "tsc --noEmit",
|
|
74
|
+
"test:peer-react18": "vitest run --config vitest.peer-react18.config.ts",
|
|
75
|
+
"test:types:peer-react18": "aui-react18-types",
|
|
73
76
|
"test:watch": "vitest"
|
|
74
77
|
}
|
|
75
78
|
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { act, Activity, version } from "react";
|
|
4
|
+
import { afterAll, afterEach, expect, it, vi } from "vitest";
|
|
5
|
+
import type { AssistantRuntime } from "@assistant-ui/core";
|
|
6
|
+
import { A2AClient } from "./A2AClient";
|
|
7
|
+
import { A2AThreadRuntimeCore } from "./A2AThreadRuntimeCore";
|
|
8
|
+
import type { UseA2ARuntimeOptions } from "./types";
|
|
9
|
+
import { useA2ARuntime } from "./useA2ARuntime";
|
|
10
|
+
|
|
11
|
+
const onReact18 = version.startsWith("18.");
|
|
12
|
+
|
|
13
|
+
type Family = { current: unknown };
|
|
14
|
+
type RendererInternals = {
|
|
15
|
+
setRefreshHandler: (resolve: (type: unknown) => Family | undefined) => void;
|
|
16
|
+
scheduleRefresh: (
|
|
17
|
+
root: unknown,
|
|
18
|
+
update: { staleFamilies: Set<Family>; updatedFamilies: Set<Family> },
|
|
19
|
+
) => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
let renderer: RendererInternals | undefined;
|
|
23
|
+
const fiberRoots = new Set<unknown>();
|
|
24
|
+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
25
|
+
vi.stubGlobal("__REACT_DEVTOOLS_GLOBAL_HOOK__", {
|
|
26
|
+
supportsFiber: true,
|
|
27
|
+
inject: (internals: RendererInternals) => {
|
|
28
|
+
renderer = internals;
|
|
29
|
+
return 1;
|
|
30
|
+
},
|
|
31
|
+
onScheduleFiberRoot: () => {},
|
|
32
|
+
onCommitFiberRoot: (_id: number, root: unknown) => fiberRoots.add(root),
|
|
33
|
+
onCommitFiberUnmount: () => {},
|
|
34
|
+
});
|
|
35
|
+
const { cleanup, render, waitFor } = await import("@testing-library/react");
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
cleanup();
|
|
39
|
+
fiberRoots.clear();
|
|
40
|
+
vi.restoreAllMocks();
|
|
41
|
+
});
|
|
42
|
+
afterAll(() => vi.unstubAllGlobals());
|
|
43
|
+
|
|
44
|
+
let options: UseA2ARuntimeOptions;
|
|
45
|
+
let runtime: AssistantRuntime;
|
|
46
|
+
const createProbe = () => {
|
|
47
|
+
const Before = () => {
|
|
48
|
+
runtime = useA2ARuntime(options);
|
|
49
|
+
return null;
|
|
50
|
+
};
|
|
51
|
+
const After = () => {
|
|
52
|
+
runtime = useA2ARuntime(options);
|
|
53
|
+
return null;
|
|
54
|
+
};
|
|
55
|
+
return { Before, After };
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const refresh = async (Before: unknown, After: unknown) => {
|
|
59
|
+
const family: Family = { current: After };
|
|
60
|
+
renderer!.setRefreshHandler((type) =>
|
|
61
|
+
type === Before || type === After ? family : undefined,
|
|
62
|
+
);
|
|
63
|
+
await act(async () => {
|
|
64
|
+
for (const root of fiberRoots) {
|
|
65
|
+
renderer!.scheduleRefresh(root, {
|
|
66
|
+
staleFamilies: new Set(),
|
|
67
|
+
updatedFamilies: new Set([family]),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
await act(async () => {});
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
it("keeps the managed client and core across refresh, then replaces both when options change", async () => {
|
|
75
|
+
vi.spyOn(A2AClient.prototype, "getAgentCard").mockResolvedValue(
|
|
76
|
+
{} as Awaited<ReturnType<A2AClient["getAgentCard"]>>,
|
|
77
|
+
);
|
|
78
|
+
const attach = vi.spyOn(A2AThreadRuntimeCore.prototype, "attachRuntime");
|
|
79
|
+
const update = vi.spyOn(A2AThreadRuntimeCore.prototype, "updateOptions");
|
|
80
|
+
options = { baseUrl: "https://first.example" };
|
|
81
|
+
const { Before, After } = createProbe();
|
|
82
|
+
const view = render(<Before />);
|
|
83
|
+
const firstCore = attach.mock.instances[0]! as A2AThreadRuntimeCore;
|
|
84
|
+
const firstClient = update.mock.lastCall![0].client;
|
|
85
|
+
|
|
86
|
+
await refresh(Before, After);
|
|
87
|
+
expect(attach.mock.instances.at(-1)).toBe(firstCore);
|
|
88
|
+
expect(update.mock.lastCall![0].client).toBe(firstClient);
|
|
89
|
+
expect(firstCore.getRuntime()).toBe(runtime);
|
|
90
|
+
|
|
91
|
+
options = { baseUrl: "https://second.example" };
|
|
92
|
+
view.rerender(<After />);
|
|
93
|
+
await act(async () => {});
|
|
94
|
+
expect(attach.mock.instances.at(-1)).not.toBe(firstCore);
|
|
95
|
+
expect(update.mock.lastCall![0].client).not.toBe(firstClient);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const keepsStreamAcrossRefresh = async (
|
|
99
|
+
exit: "hidden" | "unmount",
|
|
100
|
+
inActivity: boolean,
|
|
101
|
+
) => {
|
|
102
|
+
let signal: AbortSignal | undefined;
|
|
103
|
+
const client = {
|
|
104
|
+
getAgentCard: vi.fn().mockResolvedValue(undefined),
|
|
105
|
+
streamMessage: vi.fn((_message, _configuration, _metadata, runSignal) => {
|
|
106
|
+
signal = runSignal as AbortSignal;
|
|
107
|
+
return {
|
|
108
|
+
async *[Symbol.asyncIterator]() {
|
|
109
|
+
await new Promise<void>((resolve) =>
|
|
110
|
+
signal!.addEventListener("abort", () => resolve(), {
|
|
111
|
+
once: true,
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}),
|
|
117
|
+
} as unknown as A2AClient;
|
|
118
|
+
const attach = vi.spyOn(A2AThreadRuntimeCore.prototype, "attachRuntime");
|
|
119
|
+
const detach = vi.spyOn(A2AThreadRuntimeCore.prototype, "detachRuntime");
|
|
120
|
+
options = { client };
|
|
121
|
+
const { Before, After } = createProbe();
|
|
122
|
+
const Shell = ({ mode }: { mode: "visible" | "hidden" }) => (
|
|
123
|
+
<Activity mode={mode}>
|
|
124
|
+
<Before />
|
|
125
|
+
</Activity>
|
|
126
|
+
);
|
|
127
|
+
const view = render(inActivity ? <Shell mode="visible" /> : <Before />);
|
|
128
|
+
const core = attach.mock.instances[0]!;
|
|
129
|
+
act(() => {
|
|
130
|
+
void runtime.thread.append("hello");
|
|
131
|
+
});
|
|
132
|
+
await waitFor(() => expect(client.streamMessage).toHaveBeenCalledOnce());
|
|
133
|
+
|
|
134
|
+
await refresh(Before, After);
|
|
135
|
+
expect(signal?.aborted).toBe(false);
|
|
136
|
+
expect(detach).not.toHaveBeenCalled();
|
|
137
|
+
expect(attach.mock.instances.at(-1)).toBe(core);
|
|
138
|
+
|
|
139
|
+
if (exit === "hidden") view.rerender(<Shell mode="hidden" />);
|
|
140
|
+
else view.unmount();
|
|
141
|
+
await act(async () => {});
|
|
142
|
+
expect(detach).toHaveBeenCalledOnce();
|
|
143
|
+
expect(signal?.aborted).toBe(true);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// Activity is React 19 only
|
|
147
|
+
it.skipIf(onReact18)(
|
|
148
|
+
"keeps an in-flight stream across refresh and detaches when hidden",
|
|
149
|
+
() => keepsStreamAcrossRefresh("hidden", true),
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// Activity is React 19 only
|
|
153
|
+
it.skipIf(onReact18)(
|
|
154
|
+
"keeps an in-flight stream across refresh and detaches when unmounted inside Activity",
|
|
155
|
+
() => keepsStreamAcrossRefresh("unmount", true),
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
it("keeps an in-flight stream across refresh and detaches when unmounted", () =>
|
|
159
|
+
keepsStreamAcrossRefresh("unmount", false));
|
package/src/useA2ARuntime.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
useExternalStoreSharedOptions,
|
|
14
14
|
useRuntimeAdapters,
|
|
15
15
|
} from "@assistant-ui/core/react";
|
|
16
|
+
import { useReplaySafeEffect } from "@assistant-ui/store/internal";
|
|
16
17
|
import type {
|
|
17
18
|
AssistantRuntime,
|
|
18
19
|
AppendMessage,
|
|
@@ -69,7 +70,7 @@ export function useA2ARuntime(options: UseA2ARuntimeOptions): AssistantRuntime {
|
|
|
69
70
|
})
|
|
70
71
|
: null;
|
|
71
72
|
|
|
72
|
-
const
|
|
73
|
+
const createClient = () => {
|
|
73
74
|
if (options.client) return options.client;
|
|
74
75
|
if (!managedClientOptionsKey) {
|
|
75
76
|
throw new Error("useA2ARuntime requires either `client` or `baseUrl`");
|
|
@@ -79,7 +80,25 @@ export function useA2ARuntime(options: UseA2ARuntimeOptions): AssistantRuntime {
|
|
|
79
80
|
...(JSON.parse(managedClientOptionsKey) as ManagedA2AClientOptions),
|
|
80
81
|
headers: resolveHeaders,
|
|
81
82
|
});
|
|
82
|
-
}
|
|
83
|
+
};
|
|
84
|
+
const [pinnedClient, setPinnedClient] = useState(() => ({
|
|
85
|
+
key: managedClientOptionsKey,
|
|
86
|
+
provided: options.client,
|
|
87
|
+
client: createClient(),
|
|
88
|
+
}));
|
|
89
|
+
let currentClient = pinnedClient;
|
|
90
|
+
if (
|
|
91
|
+
pinnedClient.key !== managedClientOptionsKey ||
|
|
92
|
+
pinnedClient.provided !== options.client
|
|
93
|
+
) {
|
|
94
|
+
currentClient = {
|
|
95
|
+
key: managedClientOptionsKey,
|
|
96
|
+
provided: options.client,
|
|
97
|
+
client: createClient(),
|
|
98
|
+
};
|
|
99
|
+
setPinnedClient(currentClient);
|
|
100
|
+
}
|
|
101
|
+
const client = currentClient.client;
|
|
83
102
|
|
|
84
103
|
const coreOptions = {
|
|
85
104
|
client,
|
|
@@ -95,15 +114,22 @@ export function useA2ARuntime(options: UseA2ARuntimeOptions): AssistantRuntime {
|
|
|
95
114
|
const coreOptionsRef = useRef(coreOptions);
|
|
96
115
|
coreOptionsRef.current = coreOptions;
|
|
97
116
|
|
|
98
|
-
const
|
|
99
|
-
(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
117
|
+
const createCore = () =>
|
|
118
|
+
new A2AThreadRuntimeCore({
|
|
119
|
+
...coreOptionsRef.current,
|
|
120
|
+
client,
|
|
121
|
+
notifyUpdate,
|
|
122
|
+
});
|
|
123
|
+
const [pinnedCore, setPinnedCore] = useState(() => ({
|
|
124
|
+
client,
|
|
125
|
+
core: createCore(),
|
|
126
|
+
}));
|
|
127
|
+
let currentCore = pinnedCore;
|
|
128
|
+
if (pinnedCore.client !== client) {
|
|
129
|
+
currentCore = { client, core: createCore() };
|
|
130
|
+
setPinnedCore(currentCore);
|
|
131
|
+
}
|
|
132
|
+
const core = currentCore.core;
|
|
107
133
|
|
|
108
134
|
useEffect(() => {
|
|
109
135
|
core.updateOptions(coreOptions);
|
|
@@ -199,7 +225,7 @@ export function useA2ARuntime(options: UseA2ARuntimeOptions): AssistantRuntime {
|
|
|
199
225
|
|
|
200
226
|
const runtime = useExternalStoreRuntime(store);
|
|
201
227
|
|
|
202
|
-
|
|
228
|
+
useReplaySafeEffect(() => {
|
|
203
229
|
core.attachRuntime(runtime);
|
|
204
230
|
return () => {
|
|
205
231
|
core.detachRuntime();
|