@assistant-ui/react-a2a 0.2.35 → 0.2.37
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 +45 -48
- package/dist/A2AClient.d.ts.map +1 -1
- package/dist/A2AClient.js +43 -7
- package/dist/A2AClient.js.map +1 -1
- package/dist/A2AThreadRuntimeCore.d.ts +100 -87
- package/dist/A2AThreadRuntimeCore.d.ts.map +1 -1
- package/dist/A2AThreadRuntimeCore.js +218 -20
- package/dist/A2AThreadRuntimeCore.js.map +1 -1
- package/dist/a2aExtras.d.ts +2 -5
- package/dist/a2aExtras.d.ts.map +1 -1
- package/dist/conversions.d.ts +26 -26
- package/dist/conversions.d.ts.map +1 -1
- package/dist/conversions.js +24 -2
- package/dist/conversions.js.map +1 -1
- package/dist/hooks.d.ts +3 -7
- package/dist/hooks.d.ts.map +1 -1
- package/dist/index.d.ts +9 -6
- package/dist/index.d.ts.map +1 -0
- package/dist/types.d.ts +276 -279
- package/dist/types.d.ts.map +1 -1
- package/dist/useA2ARuntime.d.ts +3 -6
- package/dist/useA2ARuntime.d.ts.map +1 -1
- package/dist/useA2ARuntime.js +8 -0
- package/dist/useA2ARuntime.js.map +1 -1
- package/package.json +12 -10
- package/src/A2AClient.test.ts +41 -0
- package/src/A2AClient.ts +50 -6
- package/src/A2AThreadRuntimeCore.test.ts +1134 -10
- package/src/A2AThreadRuntimeCore.ts +372 -21
- package/src/conversions.test.ts +116 -0
- package/src/conversions.ts +48 -1
- package/src/useA2ARuntime.test.tsx +336 -1
- package/src/useA2ARuntime.ts +14 -0
- package/src/useA2ARuntime.voice.test.tsx +370 -0
package/dist/useA2ARuntime.js
CHANGED
|
@@ -72,6 +72,8 @@ function useA2ARuntime(options) {
|
|
|
72
72
|
threadId: threadListAdapter.threadId,
|
|
73
73
|
onSwitchToNewThread: onSwitchToNewThread ? async () => {
|
|
74
74
|
const generation = ++threadSwitchGenerationRef.current;
|
|
75
|
+
core.applyExternalMessages([]);
|
|
76
|
+
core.resetContext();
|
|
75
77
|
await onSwitchToNewThread();
|
|
76
78
|
if (generation !== threadSwitchGenerationRef.current) return;
|
|
77
79
|
core.applyExternalMessages([]);
|
|
@@ -79,8 +81,11 @@ function useA2ARuntime(options) {
|
|
|
79
81
|
} : void 0,
|
|
80
82
|
onSwitchToThread: onSwitchToThread ? async (threadId) => {
|
|
81
83
|
const generation = ++threadSwitchGenerationRef.current;
|
|
84
|
+
core.applyExternalMessages([]);
|
|
85
|
+
core.resetContext();
|
|
82
86
|
const result = await onSwitchToThread(threadId);
|
|
83
87
|
if (generation !== threadSwitchGenerationRef.current) return;
|
|
88
|
+
core.applyExternalMessages([]);
|
|
84
89
|
core.applyExternalMessages(result.messages);
|
|
85
90
|
core.resetContext();
|
|
86
91
|
} : void 0
|
|
@@ -106,15 +111,18 @@ function useA2ARuntime(options) {
|
|
|
106
111
|
isLoading: core.isLoading,
|
|
107
112
|
messageRepository: core.getMessageRepository(),
|
|
108
113
|
isRunning: core.isRunning(),
|
|
114
|
+
unstable_persistsHistory: true,
|
|
109
115
|
extras: a2aExtras.provide({
|
|
110
116
|
task: core.getTask(),
|
|
111
117
|
artifacts: core.getArtifacts(),
|
|
112
118
|
agentCard: core.getAgentCard()
|
|
113
119
|
}),
|
|
114
120
|
onNew: (message) => core.append(message),
|
|
121
|
+
onVoiceTranscript: (message) => core.appendVoiceTranscript(message),
|
|
115
122
|
onEdit: (message) => core.edit(message),
|
|
116
123
|
onReload: (parentId) => core.reload(parentId),
|
|
117
124
|
onCancel: () => core.cancel(),
|
|
125
|
+
unstable_onRecordToolInteraction: (options) => core.recordToolInteraction(options),
|
|
118
126
|
setMessages: (messages) => core.applyExternalMessages(messages),
|
|
119
127
|
onImport: (messages) => core.applyExternalMessages(messages),
|
|
120
128
|
adapters: adapterAdapters
|
|
@@ -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 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 const result = await onSwitchToThread(threadId);\n if (generation !== threadSwitchGenerationRef.current) return;\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 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":";;;;;;;AA4BA,MAAM,iCACJ,YACW;CACX,MAAM,eAAe,OAAO,YAC1B,OAAO,QAAQ,QAAQ,gBAAgB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OACrD,EAAE,cAAc,CAAC,CACnB,CACF;CAEA,OAAO,KAAK,UAAU;EACpB,GAAG;EACH;CACF,CAAC;AACH;AAEA,SAAgB,cAAc,SAAiD;CAC7E,MAAM,CAAC,UAAU,cAAc,SAAS,CAAC;CACzC,MAAM,eAAe,kBAAkB,YAAY,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;CACnE,MAAM,kBAAkB,mBAAmB;CAC3C,MAAM,iBAAiB,QAAQ,UAAU,WAAW,iBAAiB;CACrE,MAAM,oBAAoB,QAAQ,UAAU;CAE5C,MAAM,aAAa,OAAO,QAAQ,OAAO;CACzC,yBAAyB;EACvB,WAAW,UAAU,QAAQ;CAC/B,CAAC;CACD,MAAM,iBAAiB,kBAAkB;EACvC,MAAM,UAAU,WAAW;EAC3B,OAAO,OAAO,YAAY,aAAa,QAAQ,IAAK,WAAW,CAAC;CAClE,GAAG,CAAC,CAAC;CAEL,MAAM,0BAA0B,QAAQ,SACpC,OACA,QAAQ,UACN,8BAA8B;EAC5B,SAAS,QAAQ;EACjB,UAAU,QAAQ;EAClB,QAAQ,QAAQ;EAChB,YAAY,QAAQ;EACpB,cAAc,QAAQ;CACxB,CAAC,IACD;CAEN,MAAM,SAAS,cAAc;EAC3B,IAAI,QAAQ,QAAQ,OAAO,QAAQ;EACnC,IAAI,CAAC,yBACH,MAAM,IAAI,MAAM,qDAAqD;EAGvE,OAAO,IAAI,UAAU;GACnB,GAAI,KAAK,MAAM,uBAAuB;GACtC,SAAS;EACX,CAAC;CACH,GAAG;EAAC;EAAyB,QAAQ;EAAQ;CAAc,CAAC;CAE5D,MAAM,cAAc;EAClB;EACA,WAAW,QAAQ;EACnB,eAAe,QAAQ;EACvB,GAAI,QAAQ,WAAW,EAAE,SAAS,QAAQ,QAAQ;EAClD,GAAI,QAAQ,YAAY,EAAE,UAAU,QAAQ,SAAS;EACrD,GAAI,QAAQ,sBAAsB,EAChC,oBAAoB,QAAQ,mBAC9B;EACA,GAAI,kBAAkB,EAAE,SAAS,eAAe;CAClD;CACA,MAAM,iBAAiB,OAAO,WAAW;CACzC,eAAe,UAAU;CAEzB,MAAM,OAAO,cAET,IAAI,qBAAqB;EACvB,GAAG,eAAe;EAClB;EACA;CACF,CAAC,GACH,CAAC,QAAQ,YAAY,CACvB;CAEA,gBAAgB;EACd,KAAK,cAAc,WAAW;CAChC,CAAC;CAGD,MAAM,4BAA4B,OAAO,CAAC;CAC1C,MAAM,aAAa,cAAc;EAC/B,IAAI,CAAC,mBAAmB,OAAO,KAAA;EAE/B,MAAM,EAAE,qBAAqB,qBAAqB;EAElD,OAAO;GACL,UAAU,kBAAkB;GAC5B,qBAAqB,sBACjB,YAAY;IACV,MAAM,aAAa,EAAE,0BAA0B;IAC/C,MAAM,oBAAoB;IAC1B,IAAI,eAAe,0BAA0B,SAAS;IAItD,KAAK,sBAAsB,CAAC,CAAC;IAC7B,KAAK,aAAa;GACpB,IACA,KAAA;GACJ,kBAAkB,mBACd,OAAO,aAAqB;IAC1B,MAAM,aAAa,EAAE,0BAA0B;IAC/C,MAAM,SAAS,MAAM,iBAAiB,QAAQ;IAC9C,IAAI,eAAe,0BAA0B,SAAS;IACtD,KAAK,sBAAsB,OAAO,QAAQ;IAC1C,KAAK,aAAa;GACpB,IACA,KAAA;EACN;CACF,GAAG,CAAC,mBAAmB,IAAI,CAAC;CAG5B,MAAM,WAAW,QAAQ;CACzB,MAAM,kBAAkB,eACf;EACL,aAAa,UAAU,eAAe,iBAAiB;EACvD,QAAQ,UAAU;EAClB,WAAW,UAAU;EACrB,OAAO,UAAU;EACjB,UAAU,UAAU;EACpB;CACF,IACA;EAAC;EAAU;EAAiB;CAAU,CACxC;CAGA,MAAM,SAAS,8BAA8B,OAAO;CACpD,MAAM,QAAQ,cAAc;EAG1B,OAAO;GACL,GAAG;GACH,WAAW,KAAK;GAChB,mBAAmB,KAAK,qBAAqB;GAC7C,WAAW,KAAK,UAAU;GAC1B,QAAQ,UAAU,QAAQ;IACxB,MAAM,KAAK,QAAQ;IACnB,WAAW,KAAK,aAAa;IAC7B,WAAW,KAAK,aAAa;GAC/B,CAAC;GACD,QAAQ,YAA2B,KAAK,OAAO,OAAO;GACtD,SAAS,YAA2B,KAAK,KAAK,OAAO;GACrD,WAAW,aAA4B,KAAK,OAAO,QAAQ;GAC3D,gBAAgB,KAAK,OAAO;GAC5B,cAAc,aACZ,KAAK,sBAAsB,QAAQ;GACrC,WAAW,aACT,KAAK,sBAAsB,QAAQ;GACrC,UAAU;EACZ;CACF,GAAG;EAAC;EAAiB;EAAM;EAAU;CAAM,CAAC;CAE5C,MAAM,UAAU,wBAAwB,KAAK;CAE7C,gBAAgB;EACd,KAAK,cAAc,OAAO;EAC1B,aAAa;GACX,KAAK,cAAc;EACrB;CACF,GAAG,CAAC,MAAM,OAAO,CAAC;CAElB,gBAAgB;EACd,KAAK,gBAAgB;CACvB,GAAG,CAAC,IAAI,CAAC;CAET,OAAO;AACT"}
|
|
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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"}
|
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.37",
|
|
4
4
|
"description": "A2A (Agent-to-Agent) v1.0 protocol adapter for assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"a2a",
|
|
@@ -29,9 +29,10 @@
|
|
|
29
29
|
],
|
|
30
30
|
"sideEffects": false,
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@assistant-ui/core": "^0.3.
|
|
33
|
-
"@assistant-ui/
|
|
34
|
-
"assistant-
|
|
32
|
+
"@assistant-ui/core": "^0.3.21",
|
|
33
|
+
"@assistant-ui/react-generative-ui": "^0.0.20",
|
|
34
|
+
"@assistant-ui/store": "^0.3.15",
|
|
35
|
+
"assistant-stream": "^0.3.45"
|
|
35
36
|
},
|
|
36
37
|
"peerDependencies": {
|
|
37
38
|
"@types/react": "*",
|
|
@@ -43,14 +44,14 @@
|
|
|
43
44
|
}
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
47
|
-
"@testing-library/dom": "^10.4.
|
|
47
|
+
"@assistant-ui/x-buildutils": "0.0.29",
|
|
48
|
+
"@testing-library/dom": "^10.4.2",
|
|
48
49
|
"@testing-library/react": "^16.3.3",
|
|
49
|
-
"@types/react": "^19.
|
|
50
|
+
"@types/react": "^19.3.0",
|
|
50
51
|
"jsdom": "^30.0.1",
|
|
51
|
-
"react": "^19.
|
|
52
|
-
"react-dom": "^19.
|
|
53
|
-
"vitest": "^5.0.
|
|
52
|
+
"react": "^19.3.0",
|
|
53
|
+
"react-dom": "^19.3.0",
|
|
54
|
+
"vitest": "^5.0.1"
|
|
54
55
|
},
|
|
55
56
|
"publishConfig": {
|
|
56
57
|
"access": "public",
|
|
@@ -68,6 +69,7 @@
|
|
|
68
69
|
"scripts": {
|
|
69
70
|
"build": "aui-build",
|
|
70
71
|
"test": "vitest run",
|
|
72
|
+
"typecheck": "tsc --noEmit",
|
|
71
73
|
"test:watch": "vitest"
|
|
72
74
|
}
|
|
73
75
|
}
|
package/src/A2AClient.test.ts
CHANGED
|
@@ -160,6 +160,47 @@ describe("A2AClient", () => {
|
|
|
160
160
|
const [, init] = fetchMock.mock.calls[0]!;
|
|
161
161
|
expect(init.headers.Authorization).toBe("Bearer tok123");
|
|
162
162
|
});
|
|
163
|
+
|
|
164
|
+
it("aborts a request while dynamic headers are pending", async () => {
|
|
165
|
+
const dynamicClient = new A2AClient({
|
|
166
|
+
baseUrl: "https://agent.test",
|
|
167
|
+
headers: () => new Promise<Record<string, string>>(() => {}),
|
|
168
|
+
});
|
|
169
|
+
const controller = new AbortController();
|
|
170
|
+
const reason = new Error("cancelled");
|
|
171
|
+
|
|
172
|
+
const request = dynamicClient.sendMessage(
|
|
173
|
+
userMessage,
|
|
174
|
+
undefined,
|
|
175
|
+
undefined,
|
|
176
|
+
controller.signal,
|
|
177
|
+
);
|
|
178
|
+
controller.abort(reason);
|
|
179
|
+
|
|
180
|
+
await expect(request).rejects.toBe(reason);
|
|
181
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("aborts a stream while dynamic headers are pending", async () => {
|
|
185
|
+
const dynamicClient = new A2AClient({
|
|
186
|
+
baseUrl: "https://agent.test",
|
|
187
|
+
headers: () => new Promise<Record<string, string>>(() => {}),
|
|
188
|
+
});
|
|
189
|
+
const controller = new AbortController();
|
|
190
|
+
const reason = new Error("cancelled");
|
|
191
|
+
const stream = dynamicClient.streamMessage(
|
|
192
|
+
userMessage,
|
|
193
|
+
undefined,
|
|
194
|
+
undefined,
|
|
195
|
+
controller.signal,
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const next = stream.next();
|
|
199
|
+
controller.abort(reason);
|
|
200
|
+
|
|
201
|
+
await expect(next).rejects.toBe(reason);
|
|
202
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
203
|
+
});
|
|
163
204
|
});
|
|
164
205
|
|
|
165
206
|
describe("fetchOptions", () => {
|
package/src/A2AClient.ts
CHANGED
|
@@ -680,6 +680,49 @@ function signalInit(signal?: AbortSignal): RequestInit {
|
|
|
680
680
|
return signal ? { signal } : {};
|
|
681
681
|
}
|
|
682
682
|
|
|
683
|
+
const getAbortReason = (signal: AbortSignal): unknown => {
|
|
684
|
+
if (signal.reason !== undefined) return signal.reason;
|
|
685
|
+
const error = new Error("The operation was aborted");
|
|
686
|
+
error.name = "AbortError";
|
|
687
|
+
return error;
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
const raceWithAbortSignal = <T>(
|
|
691
|
+
signal: AbortSignal | undefined,
|
|
692
|
+
operation: () => T | PromiseLike<T>,
|
|
693
|
+
): Promise<T> => {
|
|
694
|
+
if (!signal) return Promise.resolve().then(operation);
|
|
695
|
+
if (signal.aborted) return Promise.reject(getAbortReason(signal));
|
|
696
|
+
|
|
697
|
+
return new Promise<T>((resolve, reject) => {
|
|
698
|
+
let settled = false;
|
|
699
|
+
const cleanup = () => signal.removeEventListener("abort", handleAbort);
|
|
700
|
+
const resolveOnce = (value: T) => {
|
|
701
|
+
if (settled) return;
|
|
702
|
+
settled = true;
|
|
703
|
+
cleanup();
|
|
704
|
+
resolve(value);
|
|
705
|
+
};
|
|
706
|
+
const rejectOnce = (error: unknown) => {
|
|
707
|
+
if (settled) return;
|
|
708
|
+
settled = true;
|
|
709
|
+
cleanup();
|
|
710
|
+
reject(error);
|
|
711
|
+
};
|
|
712
|
+
const handleAbort = () => rejectOnce(getAbortReason(signal));
|
|
713
|
+
|
|
714
|
+
signal.addEventListener("abort", handleAbort, { once: true });
|
|
715
|
+
let result: T | PromiseLike<T>;
|
|
716
|
+
try {
|
|
717
|
+
result = operation();
|
|
718
|
+
} catch (error) {
|
|
719
|
+
rejectOnce(error);
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
Promise.resolve(result).then(resolveOnce, rejectOnce);
|
|
723
|
+
});
|
|
724
|
+
};
|
|
725
|
+
|
|
683
726
|
const SKIPPED_FRAME_SNIPPET_LENGTH = 120;
|
|
684
727
|
|
|
685
728
|
function describeSkippedFrame(data: string, reason: string): string {
|
|
@@ -728,10 +771,11 @@ export class A2AClient {
|
|
|
728
771
|
|
|
729
772
|
private async getHeaders(
|
|
730
773
|
includeContentType = true,
|
|
774
|
+
signal?: AbortSignal,
|
|
731
775
|
): Promise<Record<string, string>> {
|
|
732
776
|
const custom =
|
|
733
777
|
typeof this.headersFn === "function"
|
|
734
|
-
? await this.headersFn
|
|
778
|
+
? await raceWithAbortSignal(signal, this.headersFn)
|
|
735
779
|
: this.headersFn;
|
|
736
780
|
const headers: Record<string, string> = {
|
|
737
781
|
Accept: "application/a2a+json, application/json",
|
|
@@ -777,7 +821,7 @@ export class A2AClient {
|
|
|
777
821
|
options: RequestInit = {},
|
|
778
822
|
): Promise<T> {
|
|
779
823
|
const isGet = !options.method || options.method.toUpperCase() === "GET";
|
|
780
|
-
const headers = await this.getHeaders(!isGet);
|
|
824
|
+
const headers = await this.getHeaders(!isGet, options.signal ?? undefined);
|
|
781
825
|
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
782
826
|
...this.fetchOptions,
|
|
783
827
|
...options,
|
|
@@ -809,7 +853,7 @@ export class A2AClient {
|
|
|
809
853
|
// --- Agent Card ---
|
|
810
854
|
|
|
811
855
|
async getAgentCard(signal?: AbortSignal): Promise<A2AAgentCard> {
|
|
812
|
-
const headers = await this.getHeaders(false); // GET: no Content-Type
|
|
856
|
+
const headers = await this.getHeaders(false, signal); // GET: no Content-Type
|
|
813
857
|
const url = `${this.baseUrl}/.well-known/agent-card.json`;
|
|
814
858
|
const response = await fetch(url, {
|
|
815
859
|
...this.fetchOptions,
|
|
@@ -863,7 +907,7 @@ export class A2AClient {
|
|
|
863
907
|
metadata?: Record<string, unknown>,
|
|
864
908
|
signal?: AbortSignal,
|
|
865
909
|
): AsyncGenerator<A2AStreamEvent> {
|
|
866
|
-
const headers = await this.getHeaders(true);
|
|
910
|
+
const headers = await this.getHeaders(true, signal);
|
|
867
911
|
headers.Accept = "text/event-stream";
|
|
868
912
|
|
|
869
913
|
const body: Record<string, unknown> = {
|
|
@@ -954,7 +998,7 @@ export class A2AClient {
|
|
|
954
998
|
taskId: string,
|
|
955
999
|
signal?: AbortSignal,
|
|
956
1000
|
): AsyncGenerator<A2AStreamEvent> {
|
|
957
|
-
const headers = await this.getHeaders(false); // GET: no Content-Type
|
|
1001
|
+
const headers = await this.getHeaders(false, signal); // GET: no Content-Type
|
|
958
1002
|
headers.Accept = "text/event-stream";
|
|
959
1003
|
|
|
960
1004
|
const response = await fetch(
|
|
@@ -1032,7 +1076,7 @@ export class A2AClient {
|
|
|
1032
1076
|
signal?: AbortSignal,
|
|
1033
1077
|
): Promise<void> {
|
|
1034
1078
|
const isGet = false;
|
|
1035
|
-
const headers = await this.getHeaders(!isGet);
|
|
1079
|
+
const headers = await this.getHeaders(!isGet, signal);
|
|
1036
1080
|
const response = await fetch(
|
|
1037
1081
|
`${this.baseUrl}${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}/pushNotificationConfigs/${encodeURIComponent(configId)}`,
|
|
1038
1082
|
{
|