@assistant-ui/react-ag-ui 0.0.47 → 0.0.49
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/agUiExtras.d.ts +1 -1
- package/dist/agUiExtras.d.ts.map +1 -1
- package/dist/agUiExtras.js +1 -1
- package/dist/agUiExtras.js.map +1 -1
- package/dist/hooks.d.ts +6 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +10 -1
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/runtime/AgUiThreadRuntimeCore.d.ts +3 -0
- package/dist/runtime/AgUiThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtime/AgUiThreadRuntimeCore.js +78 -16
- package/dist/runtime/AgUiThreadRuntimeCore.js.map +1 -1
- package/dist/runtime/adapter/conversions.d.ts.map +1 -1
- package/dist/runtime/adapter/conversions.js +63 -1
- package/dist/runtime/adapter/conversions.js.map +1 -1
- package/dist/runtime/adapter/run-aggregator.d.ts +6 -1
- package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
- package/dist/runtime/adapter/run-aggregator.js +57 -1
- package/dist/runtime/adapter/run-aggregator.js.map +1 -1
- package/dist/runtime/event-parser.js +5 -2
- package/dist/runtime/event-parser.js.map +1 -1
- package/dist/runtime/mcp-tool-result.d.ts.map +1 -1
- package/dist/runtime/mcp-tool-result.js +6 -3
- package/dist/runtime/mcp-tool-result.js.map +1 -1
- package/dist/runtime/types.d.ts +3 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/useAgUiRuntime.js +2 -1
- package/dist/useAgUiRuntime.js.map +1 -1
- package/package.json +5 -4
- package/src/agUiExtras.ts +1 -1
- package/src/hooks.ts +14 -0
- package/src/index.ts +1 -0
- package/src/runtime/AgUiThreadRuntimeCore.ts +132 -16
- package/src/runtime/adapter/conversions.ts +107 -1
- package/src/runtime/adapter/run-aggregator.ts +82 -0
- package/src/runtime/event-parser.ts +12 -5
- package/src/runtime/mcp-tool-result.ts +8 -6
- package/src/runtime/types.ts +3 -0
- package/src/useAgUiRuntime.ts +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-tool-result.js","names":[],"sources":["../../src/runtime/mcp-tool-result.ts"],"sourcesContent":["import { isRecord } from \"@assistant-ui/core/internal\";\nimport { isMcpAppUri } from \"@assistant-ui/core\";\n\nexport type McpToolCallResult = Record<string, unknown> & {\n content: unknown[];\n structuredContent?: Record<string, unknown>;\n _meta?: Record<string, unknown>;\n isError?: boolean;\n};\n\nexport const parseMcpToolCallResult = (\n source: Record<string, unknown>,\n modelContent: string,\n): McpToolCallResult | undefined => {\n const structuredContent = isRecord(source.structuredContent)\n ? source.structuredContent\n : undefined;\n const meta = isRecord(source._meta) ? source._meta : undefined;\n if (!structuredContent && !meta) return undefined;\n\n return {\n content: modelContent ? [{ type: \"text\", text: modelContent }] : [],\n ...(structuredContent ? { structuredContent } : {}),\n ...(meta ? { _meta: meta } : {}),\n ...(typeof source.isError === \"boolean\" ? { isError: source.isError } : {}),\n };\n};\n\n//
|
|
1
|
+
{"version":3,"file":"mcp-tool-result.js","names":[],"sources":["../../src/runtime/mcp-tool-result.ts"],"sourcesContent":["import { isRecord } from \"@assistant-ui/core/internal\";\nimport { isMcpAppUri } from \"@assistant-ui/core\";\n\nexport type McpToolCallResult = Record<string, unknown> & {\n content: unknown[];\n structuredContent?: Record<string, unknown>;\n _meta?: Record<string, unknown>;\n isError?: boolean;\n};\n\nexport const parseMcpToolCallResult = (\n source: Record<string, unknown>,\n modelContent: string,\n): McpToolCallResult | undefined => {\n const structuredContent = isRecord(source.structuredContent)\n ? source.structuredContent\n : undefined;\n const meta = isRecord(source._meta) ? source._meta : undefined;\n if (!structuredContent && !meta) return undefined;\n\n return {\n content: modelContent ? [{ type: \"text\", text: modelContent }] : [],\n ...(structuredContent ? { structuredContent } : {}),\n ...(meta ? { _meta: meta } : {}),\n ...(typeof source.isError === \"boolean\" ? { isError: source.isError } : {}),\n };\n};\n\n// The MCP Apps pointer on CallToolResult._meta: canonical nested `ui.resourceUri`\n// per the Apps spec (2026-01-26), with the deprecated flat `\"ui/resourceUri\"` kept\n// for legacy producers. Only ui:// URIs identify a widget.\nexport const readMcpAppResourceUri = (meta: unknown): string | undefined => {\n if (!isRecord(meta)) return undefined;\n const ui = meta[\"ui\"];\n const nested = isRecord(ui) ? ui[\"resourceUri\"] : undefined;\n if (typeof nested === \"string\" && isMcpAppUri(nested)) return nested;\n const flat = meta[\"ui/resourceUri\"];\n return typeof flat === \"string\" && isMcpAppUri(flat) ? flat : undefined;\n};\n"],"mappings":";;;AAUA,MAAa,0BACX,QACA,iBACkC;CAClC,MAAM,oBAAoB,SAAS,OAAO,iBAAiB,IACvD,OAAO,oBACP,KAAA;CACJ,MAAM,OAAO,SAAS,OAAO,KAAK,IAAI,OAAO,QAAQ,KAAA;CACrD,IAAI,CAAC,qBAAqB,CAAC,MAAM,OAAO,KAAA;CAExC,OAAO;EACL,SAAS,eAAe,CAAC;GAAE,MAAM;GAAQ,MAAM;EAAa,CAAC,IAAI,CAAC;EAClE,GAAI,oBAAoB,EAAE,kBAAkB,IAAI,CAAC;EACjD,GAAI,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;EAC9B,GAAI,OAAO,OAAO,YAAY,YAAY,EAAE,SAAS,OAAO,QAAQ,IAAI,CAAC;CAC3E;AACF;AAKA,MAAa,yBAAyB,SAAsC;CAC1E,IAAI,CAAC,SAAS,IAAI,GAAG,OAAO,KAAA;CAC5B,MAAM,KAAK,KAAK;CAChB,MAAM,SAAS,SAAS,EAAE,IAAI,GAAG,iBAAiB,KAAA;CAClD,IAAI,OAAO,WAAW,YAAY,YAAY,MAAM,GAAG,OAAO;CAC9D,MAAM,OAAO,KAAK;CAClB,OAAO,OAAO,SAAS,YAAY,YAAY,IAAI,IAAI,OAAO,KAAA;AAChE"}
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -71,6 +71,7 @@ type AgUiResumeEntry = {
|
|
|
71
71
|
};
|
|
72
72
|
type AgUiRuntimeExtras = {
|
|
73
73
|
interrupts: readonly AgUiInterrupt[];
|
|
74
|
+
sendA2uiAction: (action: Record<string, unknown>) => void;
|
|
74
75
|
submitInterruptResponses: (responses: readonly AgUiResumeEntry[]) => Promise<void>;
|
|
75
76
|
steerAway: (message: CreateAppendMessage, responses?: readonly AgUiResumeEntry[]) => Promise<void>;
|
|
76
77
|
state: ReadonlyJSONValue | undefined;
|
|
@@ -166,6 +167,8 @@ type AgUiEvent = {
|
|
|
166
167
|
type: "ACTIVITY_SNAPSHOT";
|
|
167
168
|
activityType: string;
|
|
168
169
|
content: Record<string, unknown>;
|
|
170
|
+
messageId?: string;
|
|
171
|
+
replace?: boolean;
|
|
169
172
|
} | {
|
|
170
173
|
type: "RAW";
|
|
171
174
|
event: any;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/runtime/types.ts"],"mappings":";;;;;;;;;;;;KAuBK;EACH,mBAAmB;EACnB,QAAQ;;;;;;EAMR;;KAGU,2BAA2B,KACrC;EAGA,qBAEM,qBACG,QAAQ,wBAAwB;;KAI/B;EACV,cAAc;EACd,SAAS;EACT,YAAY;EACZ,QAAQ;EACR,WAAW;EACX,UAAU;;;;EAIV,aAAa;;KAGH,wBAAwB;EAClC,OAAO;EACP,SAAS,QAAQ;EACjB;;;;;;;;;;;EAWA;EACA,WAAW,GAAG;EACd;EACA,WAAW;;KAGD;KAMA;EACV;EACA,QAAQ;EACR;EACA;EACA,iBAAiB;EACjB;EACA,WAAW;;KAGD;EACV;EACA;EACA;;KAGU;EACV,qBAAqB;EACrB,2BACE,oBAAoB,sBACjB;EACL,YACE,SAAS,qBACT,qBAAqB,sBAClB;EACL,OAAO;EACP,WACE,MACI,sBACE,MAAM,kCAAkC;;KAItC;EACN;;EACA;EAAmB,YAAY;;KAEzB;EACN;EAAqB;;EAErB;EACA;EACA,UAAU;;EAEV;EAAuB;;EACvB;EAAmB;EAAkB;;EACrC;EAA4B;;EAC5B;EAA8B;EAAoB;;EAClD;EAA0B;;EAC1B;EAA4B;;EAC5B;EAAwB;;EACxB;;EACA;EAAuC;;EACvC;;EACA;;EACA;EAAyB;;EACzB;EAAiC;;EACjC;EAAmC;EAAoB;;EACvD;EAA+B;;EAC/B;EAAuB;;EAEvB;EACA;EACA;EACA;;EAEA;EAAwB;EAAoB;;EAC5C;EAAuB;;EAEvB;EACA;EACA;EACA;EACA;;EAGA;EACA;EACA;EACA;EACA;EACA,YAAY;;EAGZ;EACA;EACA,SAAS;;
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/runtime/types.ts"],"mappings":";;;;;;;;;;;;KAuBK;EACH,mBAAmB;EACnB,QAAQ;;;;;;EAMR;;KAGU,2BAA2B,KACrC;EAGA,qBAEM,qBACG,QAAQ,wBAAwB;;KAI/B;EACV,cAAc;EACd,SAAS;EACT,YAAY;EACZ,QAAQ;EACR,WAAW;EACX,UAAU;;;;EAIV,aAAa;;KAGH,wBAAwB;EAClC,OAAO;EACP,SAAS,QAAQ;EACjB;;;;;;;;;;;EAWA;EACA,WAAW,GAAG;EACd;EACA,WAAW;;KAGD;KAMA;EACV;EACA,QAAQ;EACR;EACA;EACA,iBAAiB;EACjB;EACA,WAAW;;KAGD;EACV;EACA;EACA;;KAGU;EACV,qBAAqB;EACrB,iBAAiB,QAAQ;EACzB,2BACE,oBAAoB,sBACjB;EACL,YACE,SAAS,qBACT,qBAAqB,sBAClB;EACL,OAAO;EACP,WACE,MACI,sBACE,MAAM,kCAAkC;;KAItC;EACN;;EACA;EAAmB,YAAY;;KAEzB;EACN;EAAqB;;EAErB;EACA;EACA,UAAU;;EAEV;EAAuB;;EACvB;EAAmB;EAAkB;;EACrC;EAA4B;;EAC5B;EAA8B;EAAoB;;EAClD;EAA0B;;EAC1B;EAA4B;;EAC5B;EAAwB;;EACxB;;EACA;EAAuC;;EACvC;;EACA;;EACA;EAAyB;;EACzB;EAAiC;;EACjC;EAAmC;EAAoB;;EACvD;EAA+B;;EAC/B;EAAuB;;EAEvB;EACA;EACA;EACA;;EAEA;EAAwB;EAAoB;;EAC5C;EAAuB;;EAEvB;EACA;EACA;EACA;EACA;;EAGA;EACA;EACA;EACA;EACA;EACA,YAAY;;EAGZ;EACA;EACA,SAAS;EACT;EACA;;EAEA;EAAa;EAAY;;EACzB;EAAgB;EAAc;;EAC9B;EAAwB;;EACxB;EAAqB;;EACrB;EAA2B"}
|
package/dist/useAgUiRuntime.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { agUiExtras } from "./agUiExtras.js";
|
|
3
3
|
import { makeLogger } from "./runtime/logger.js";
|
|
4
4
|
import { AgUiThreadRuntimeCore } from "./runtime/AgUiThreadRuntimeCore.js";
|
|
5
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
6
5
|
import { useExternalStoreRuntime, useExternalStoreSharedOptions, useRuntimeAdapters } from "@assistant-ui/core/react";
|
|
6
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
7
7
|
//#region src/useAgUiRuntime.ts
|
|
8
8
|
const EMPTY_INTERRUPTS = [];
|
|
9
9
|
function useAgUiRuntime(options) {
|
|
@@ -79,6 +79,7 @@ function useAgUiRuntime(options) {
|
|
|
79
79
|
isRunning: core.isRunning() || hasExecutingTools,
|
|
80
80
|
extras: agUiExtras.provide({
|
|
81
81
|
interrupts: core.getPendingInterrupts()?.interrupts ?? EMPTY_INTERRUPTS,
|
|
82
|
+
sendA2uiAction: (action) => core.sendA2uiAction(action),
|
|
82
83
|
submitInterruptResponses: (responses) => core.submitInterruptResponses(responses),
|
|
83
84
|
steerAway: (message, responses) => core.steerAway(message, responses),
|
|
84
85
|
state: core.getState(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAgUiRuntime.js","names":[],"sources":["../src/useAgUiRuntime.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 { ToolExecutionStatus } from \"@assistant-ui/core\";\nimport type {\n AssistantRuntime,\n AppendMessage,\n ExternalStoreAdapter,\n ThreadMessage,\n} from \"@assistant-ui/core\";\nimport type { ReadonlyJSONValue } from \"assistant-stream/utils\";\nimport { makeLogger } from \"./runtime/logger\";\nimport type {\n AgUiInterrupt,\n AgUiResumeEntry,\n UseAgUiRuntimeOptions,\n} from \"./runtime/types\";\nimport { AgUiThreadRuntimeCore } from \"./runtime/AgUiThreadRuntimeCore\";\nimport { agUiExtras } from \"./agUiExtras\";\n\nconst EMPTY_INTERRUPTS: readonly AgUiInterrupt[] = [];\n\nexport type AgUiAssistantRuntime = AssistantRuntime & {\n /**\n * @deprecated Use the `useAgUiInterrupts()` hook instead. This method is kept\n * for backward compatibility and will be removed in a future major release.\n */\n unstable_getPendingInterrupts: () => readonly AgUiInterrupt[];\n /**\n * @deprecated Use the `useAgUiSubmitInterruptResponses()` hook instead. This\n * method is kept for backward compatibility and will be removed in a future\n * major release.\n */\n unstable_submitInterruptResponses: (\n responses: readonly AgUiResumeEntry[],\n ) => Promise<void>;\n};\n\nexport function useAgUiRuntime(\n options: UseAgUiRuntimeOptions,\n): AgUiAssistantRuntime {\n const logger = useMemo(() => makeLogger(options.logger), [options.logger]);\n const [_version, setVersion] = useState(0);\n const notifyUpdate = useCallback(() => setVersion((v) => v + 1), []);\n const coreRef = useRef<AgUiThreadRuntimeCore | null>(null);\n const runtimeAdapters = useRuntimeAdapters();\n\n const historyAdapter = options.adapters?.history ?? runtimeAdapters?.history;\n const threadListAdapter = options.adapters?.threadList;\n\n if (!coreRef.current) {\n coreRef.current = new AgUiThreadRuntimeCore({\n agent: options.agent,\n logger,\n showThinking: options.showThinking ?? true,\n autoCancelPendingToolCalls: options.autoCancelPendingToolCalls,\n ...(options.onError && { onError: options.onError }),\n ...(options.onCancel && { onCancel: options.onCancel }),\n ...(historyAdapter && { history: historyAdapter }),\n notifyUpdate,\n });\n }\n\n const core = coreRef.current;\n core.updateOptions({\n agent: options.agent,\n logger,\n showThinking: options.showThinking ?? true,\n autoCancelPendingToolCalls: options.autoCancelPendingToolCalls,\n ...(options.onError && { onError: options.onError }),\n ...(options.onCancel && { onCancel: options.onCancel }),\n ...(historyAdapter && { history: historyAdapter }),\n });\n\n const [toolStatuses, setToolStatuses] = useState<\n Record<string, ToolExecutionStatus>\n >({});\n\n const hasExecutingTools = Object.values(toolStatuses).some(\n (s) => s?.type === \"executing\",\n );\n\n const threadList = useMemo(() => {\n if (!threadListAdapter) return undefined;\n\n const { onSwitchToNewThread, onSwitchToThread, ...rest } =\n threadListAdapter;\n\n return {\n ...rest,\n onSwitchToNewThread: onSwitchToNewThread\n ? async () => {\n await onSwitchToNewThread();\n core.applyExternalMessages([]);\n core.resetState();\n }\n : undefined,\n onSwitchToThread: onSwitchToThread\n ? async (threadId: string) => {\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 const result = await onSwitchToThread(threadId);\n core.applyExternalMessages(result.messages);\n if (result.state !== undefined) {\n core.loadExternalState(result.state);\n } else {\n core.resetState();\n }\n if (result.unstable_resume) {\n void core.resumeInFlightRun(result.messages);\n }\n }\n : undefined,\n };\n }, [threadListAdapter, core]);\n\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 const shared = useExternalStoreSharedOptions(options);\n const store = useMemo(\n () => {\n void _version; // rerender on version change\n\n return {\n ...shared,\n isLoading: core.isLoading,\n messageRepository: core.getMessageRepository(),\n state: core.getState(),\n isRunning: core.isRunning() || hasExecutingTools,\n extras: agUiExtras.provide({\n interrupts:\n core.getPendingInterrupts()?.interrupts ?? EMPTY_INTERRUPTS,\n submitInterruptResponses: (responses) =>\n core.submitInterruptResponses(responses),\n steerAway: (message, responses) => core.steerAway(message, responses),\n state: core.getState(),\n setState: (next) => core.setState(next),\n }),\n unstable_enableToolInvocations: true,\n setToolStatuses,\n onNew: (message: AppendMessage) => core.append(message),\n onEdit: (message: AppendMessage) => core.edit(message),\n onReload: (parentId: string | null, config: { runConfig?: any }) =>\n core.reload(parentId, config),\n onCancel: async () => {\n core.cancel();\n },\n onAddToolResult: (options) => core.addToolResult(options),\n onResume: (config) => core.resume(config),\n setMessages: (messages: readonly ThreadMessage[]) =>\n core.applyExternalMessages(messages),\n onImport: (messages: readonly ThreadMessage[]) =>\n core.applyExternalMessages(messages),\n onLoadExternalState: (state: ReadonlyJSONValue) =>\n core.loadExternalState(state),\n adapters: adapterAdapters,\n } satisfies ExternalStoreAdapter<ThreadMessage>;\n },\n // _version is intentionally included to trigger re-computation when core state changes via notifyUpdate\n // toolInvocations intentionally excluded: abort/resume use refs internally and work with stale captures\n [adapterAdapters, core, _version, hasExecutingTools, shared],\n );\n\n const baseRuntime = useExternalStoreRuntime(store);\n\n const runtime = useMemo<AgUiAssistantRuntime>(() => {\n const wrapper = Object.create(baseRuntime) as AgUiAssistantRuntime;\n wrapper.unstable_getPendingInterrupts = () =>\n core.getPendingInterrupts()?.interrupts ?? [];\n wrapper.unstable_submitInterruptResponses = (responses) =>\n core.submitInterruptResponses(responses);\n return wrapper;\n }, [baseRuntime, core]);\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":"useAgUiRuntime.js","names":[],"sources":["../src/useAgUiRuntime.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 { ToolExecutionStatus } from \"@assistant-ui/core\";\nimport type {\n AssistantRuntime,\n AppendMessage,\n ExternalStoreAdapter,\n ThreadMessage,\n} from \"@assistant-ui/core\";\nimport type { ReadonlyJSONValue } from \"assistant-stream/utils\";\nimport { makeLogger } from \"./runtime/logger\";\nimport type {\n AgUiInterrupt,\n AgUiResumeEntry,\n UseAgUiRuntimeOptions,\n} from \"./runtime/types\";\nimport { AgUiThreadRuntimeCore } from \"./runtime/AgUiThreadRuntimeCore\";\nimport { agUiExtras } from \"./agUiExtras\";\n\nconst EMPTY_INTERRUPTS: readonly AgUiInterrupt[] = [];\n\nexport type AgUiAssistantRuntime = AssistantRuntime & {\n /**\n * @deprecated Use the `useAgUiInterrupts()` hook instead. This method is kept\n * for backward compatibility and will be removed in a future major release.\n */\n unstable_getPendingInterrupts: () => readonly AgUiInterrupt[];\n /**\n * @deprecated Use the `useAgUiSubmitInterruptResponses()` hook instead. This\n * method is kept for backward compatibility and will be removed in a future\n * major release.\n */\n unstable_submitInterruptResponses: (\n responses: readonly AgUiResumeEntry[],\n ) => Promise<void>;\n};\n\nexport function useAgUiRuntime(\n options: UseAgUiRuntimeOptions,\n): AgUiAssistantRuntime {\n const logger = useMemo(() => makeLogger(options.logger), [options.logger]);\n const [_version, setVersion] = useState(0);\n const notifyUpdate = useCallback(() => setVersion((v) => v + 1), []);\n const coreRef = useRef<AgUiThreadRuntimeCore | null>(null);\n const runtimeAdapters = useRuntimeAdapters();\n\n const historyAdapter = options.adapters?.history ?? runtimeAdapters?.history;\n const threadListAdapter = options.adapters?.threadList;\n\n if (!coreRef.current) {\n coreRef.current = new AgUiThreadRuntimeCore({\n agent: options.agent,\n logger,\n showThinking: options.showThinking ?? true,\n autoCancelPendingToolCalls: options.autoCancelPendingToolCalls,\n ...(options.onError && { onError: options.onError }),\n ...(options.onCancel && { onCancel: options.onCancel }),\n ...(historyAdapter && { history: historyAdapter }),\n notifyUpdate,\n });\n }\n\n const core = coreRef.current;\n core.updateOptions({\n agent: options.agent,\n logger,\n showThinking: options.showThinking ?? true,\n autoCancelPendingToolCalls: options.autoCancelPendingToolCalls,\n ...(options.onError && { onError: options.onError }),\n ...(options.onCancel && { onCancel: options.onCancel }),\n ...(historyAdapter && { history: historyAdapter }),\n });\n\n const [toolStatuses, setToolStatuses] = useState<\n Record<string, ToolExecutionStatus>\n >({});\n\n const hasExecutingTools = Object.values(toolStatuses).some(\n (s) => s?.type === \"executing\",\n );\n\n const threadList = useMemo(() => {\n if (!threadListAdapter) return undefined;\n\n const { onSwitchToNewThread, onSwitchToThread, ...rest } =\n threadListAdapter;\n\n return {\n ...rest,\n onSwitchToNewThread: onSwitchToNewThread\n ? async () => {\n await onSwitchToNewThread();\n core.applyExternalMessages([]);\n core.resetState();\n }\n : undefined,\n onSwitchToThread: onSwitchToThread\n ? async (threadId: string) => {\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 const result = await onSwitchToThread(threadId);\n core.applyExternalMessages(result.messages);\n if (result.state !== undefined) {\n core.loadExternalState(result.state);\n } else {\n core.resetState();\n }\n if (result.unstable_resume) {\n void core.resumeInFlightRun(result.messages);\n }\n }\n : undefined,\n };\n }, [threadListAdapter, core]);\n\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 const shared = useExternalStoreSharedOptions(options);\n const store = useMemo(\n () => {\n void _version; // rerender on version change\n\n return {\n ...shared,\n isLoading: core.isLoading,\n messageRepository: core.getMessageRepository(),\n state: core.getState(),\n isRunning: core.isRunning() || hasExecutingTools,\n extras: agUiExtras.provide({\n interrupts:\n core.getPendingInterrupts()?.interrupts ?? EMPTY_INTERRUPTS,\n sendA2uiAction: (action) => core.sendA2uiAction(action),\n submitInterruptResponses: (responses) =>\n core.submitInterruptResponses(responses),\n steerAway: (message, responses) => core.steerAway(message, responses),\n state: core.getState(),\n setState: (next) => core.setState(next),\n }),\n unstable_enableToolInvocations: true,\n setToolStatuses,\n onNew: (message: AppendMessage) => core.append(message),\n onEdit: (message: AppendMessage) => core.edit(message),\n onReload: (parentId: string | null, config: { runConfig?: any }) =>\n core.reload(parentId, config),\n onCancel: async () => {\n core.cancel();\n },\n onAddToolResult: (options) => core.addToolResult(options),\n onResume: (config) => core.resume(config),\n setMessages: (messages: readonly ThreadMessage[]) =>\n core.applyExternalMessages(messages),\n onImport: (messages: readonly ThreadMessage[]) =>\n core.applyExternalMessages(messages),\n onLoadExternalState: (state: ReadonlyJSONValue) =>\n core.loadExternalState(state),\n adapters: adapterAdapters,\n } satisfies ExternalStoreAdapter<ThreadMessage>;\n },\n // _version is intentionally included to trigger re-computation when core state changes via notifyUpdate\n // toolInvocations intentionally excluded: abort/resume use refs internally and work with stale captures\n [adapterAdapters, core, _version, hasExecutingTools, shared],\n );\n\n const baseRuntime = useExternalStoreRuntime(store);\n\n const runtime = useMemo<AgUiAssistantRuntime>(() => {\n const wrapper = Object.create(baseRuntime) as AgUiAssistantRuntime;\n wrapper.unstable_getPendingInterrupts = () =>\n core.getPendingInterrupts()?.interrupts ?? [];\n wrapper.unstable_submitInterruptResponses = (responses) =>\n core.submitInterruptResponses(responses);\n return wrapper;\n }, [baseRuntime, core]);\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-ag-ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.49",
|
|
4
4
|
"description": "AG-UI protocol adapter for assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ag-ui",
|
|
@@ -30,9 +30,10 @@
|
|
|
30
30
|
"sideEffects": false,
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@ag-ui/client": "^0.0.57",
|
|
33
|
-
"@assistant-ui/core": "^0.2
|
|
34
|
-
"@assistant-ui/
|
|
35
|
-
"assistant-
|
|
33
|
+
"@assistant-ui/core": "^0.3.2",
|
|
34
|
+
"@assistant-ui/react-generative-ui": "^0.0.11",
|
|
35
|
+
"@assistant-ui/store": "^0.3.2",
|
|
36
|
+
"assistant-stream": "^0.3.31",
|
|
36
37
|
"fast-json-patch": "^3.1.1"
|
|
37
38
|
},
|
|
38
39
|
"peerDependencies": {
|
package/src/agUiExtras.ts
CHANGED
package/src/hooks.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useCallback } from "react";
|
|
3
4
|
import { useAui } from "@assistant-ui/store";
|
|
4
5
|
import type { CreateAppendMessage } from "@assistant-ui/core";
|
|
5
6
|
import type { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
@@ -15,6 +16,19 @@ const EMPTY_INTERRUPTS: readonly AgUiInterrupt[] = [];
|
|
|
15
16
|
export const useAgUiInterrupts = (): readonly AgUiInterrupt[] =>
|
|
16
17
|
agUiExtras.use((e) => e.interrupts, EMPTY_INTERRUPTS);
|
|
17
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Returns a stable function that sends an A2UI action back to the agent and
|
|
21
|
+
* resumes the run.
|
|
22
|
+
*/
|
|
23
|
+
export const useAgUiSendA2uiAction = () => {
|
|
24
|
+
const aui = useAui();
|
|
25
|
+
return useCallback(
|
|
26
|
+
(action: Record<string, unknown>): void =>
|
|
27
|
+
agUiExtras.get(aui).sendA2uiAction(action),
|
|
28
|
+
[aui],
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
18
32
|
/**
|
|
19
33
|
* Returns a function that submits responses (each `resolved` or `cancelled`)
|
|
20
34
|
* for the pending AG-UI interrupts and resumes the run.
|
package/src/index.ts
CHANGED
|
@@ -99,6 +99,8 @@ export class AgUiThreadRuntimeCore {
|
|
|
99
99
|
private _isLoading = false;
|
|
100
100
|
private _loadPromise: Promise<void> | undefined;
|
|
101
101
|
private pendingResumeMessageId: string | null = null;
|
|
102
|
+
private pendingA2uiResume = false;
|
|
103
|
+
private pendingA2uiAction: Record<string, unknown> | undefined;
|
|
102
104
|
|
|
103
105
|
constructor(options: CoreOptions) {
|
|
104
106
|
this.agent = options.agent;
|
|
@@ -688,6 +690,31 @@ export class AgUiThreadRuntimeCore {
|
|
|
688
690
|
this.maybeResumeAfterToolResults(options.messageId);
|
|
689
691
|
}
|
|
690
692
|
|
|
693
|
+
sendA2uiAction(action: Record<string, unknown>): void {
|
|
694
|
+
this.assertNoPendingInterrupts();
|
|
695
|
+
this.maybeAutoCancelPendingToolCalls();
|
|
696
|
+
const parentId = this.repository.headId;
|
|
697
|
+
if (parentId === null) {
|
|
698
|
+
this.logger.debug(
|
|
699
|
+
"[agui] sendA2uiAction: no messages to resume, dropping action",
|
|
700
|
+
);
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const userAction = { ...action };
|
|
705
|
+
delete userAction.type;
|
|
706
|
+
if (!("timestamp" in userAction)) {
|
|
707
|
+
userAction.timestamp = new Date().toISOString();
|
|
708
|
+
}
|
|
709
|
+
this.pendingA2uiAction = userAction;
|
|
710
|
+
|
|
711
|
+
if (this.isRunningFlag) {
|
|
712
|
+
this.pendingA2uiResume = true;
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
this.startResumeRun(parentId);
|
|
716
|
+
}
|
|
717
|
+
|
|
691
718
|
// The continuation fires whether the frontend result lands before
|
|
692
719
|
// RUN_FINISHED (the status flips to requires-action only later, while the
|
|
693
720
|
// run is still draining) or after it.
|
|
@@ -739,6 +766,8 @@ export class AgUiThreadRuntimeCore {
|
|
|
739
766
|
}
|
|
740
767
|
|
|
741
768
|
applyExternalMessages(messages: readonly ThreadMessage[]): void {
|
|
769
|
+
this.pendingA2uiResume = false;
|
|
770
|
+
this.pendingA2uiAction = undefined;
|
|
742
771
|
this.assistantHistoryParents.clear();
|
|
743
772
|
|
|
744
773
|
if (messages.length === 0) {
|
|
@@ -892,20 +921,38 @@ export class AgUiThreadRuntimeCore {
|
|
|
892
921
|
this.pendingError = null;
|
|
893
922
|
const assistantParentId = parent ? parentId : this.repository.headId;
|
|
894
923
|
let assistantMessageId: string | undefined;
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
924
|
+
// A snapshot the preserve gate declines still evicts the in-flight
|
|
925
|
+
// assistant; recreating under the cached id on the next content-bearing
|
|
926
|
+
// emit keeps both the stream and the message identity. Status-only emits
|
|
927
|
+
// and server-id collisions must not recreate.
|
|
928
|
+
let assistantCollided = false;
|
|
929
|
+
const ensureAssistant = (allowRecreate = false): string => {
|
|
930
|
+
const cached = assistantMessageId;
|
|
931
|
+
if (cached !== undefined && this.tryGetMessage(cached)) return cached;
|
|
932
|
+
if (cached !== undefined && (assistantCollided || !allowRecreate)) {
|
|
933
|
+
return cached;
|
|
934
|
+
}
|
|
935
|
+
const parentId =
|
|
936
|
+
cached === undefined &&
|
|
937
|
+
assistantParentId &&
|
|
938
|
+
this.hasMessage(assistantParentId)
|
|
939
|
+
? assistantParentId
|
|
940
|
+
: this.repository.headId;
|
|
941
|
+
const created = this.insertAssistantPlaceholder(parentId, cached);
|
|
900
942
|
assistantMessageId = created;
|
|
901
|
-
this.markPendingAssistantHistory(created,
|
|
943
|
+
this.markPendingAssistantHistory(created, parentId);
|
|
902
944
|
return created;
|
|
903
945
|
};
|
|
904
946
|
|
|
905
947
|
if (shouldEagerlyInsertAssistant) ensureAssistant();
|
|
906
948
|
|
|
907
949
|
const applyUpdate = (update: ChatModelRunResult) => {
|
|
908
|
-
const
|
|
950
|
+
const hasContent =
|
|
951
|
+
Array.isArray(update.content) && update.content.length > 0;
|
|
952
|
+
const resolved = this.updateAssistantMessage(
|
|
953
|
+
ensureAssistant(hasContent),
|
|
954
|
+
update,
|
|
955
|
+
);
|
|
909
956
|
if (resolved !== assistantMessageId) {
|
|
910
957
|
assistantMessageId = resolved;
|
|
911
958
|
}
|
|
@@ -916,14 +963,17 @@ export class AgUiThreadRuntimeCore {
|
|
|
916
963
|
logger: this.logger,
|
|
917
964
|
emit: applyUpdate,
|
|
918
965
|
onServerMessageId: (serverId) => {
|
|
919
|
-
const placeholder = ensureAssistant();
|
|
966
|
+
const placeholder = ensureAssistant(true);
|
|
920
967
|
if (placeholder === serverId) return;
|
|
921
968
|
if (this.reassignAssistantId(placeholder, serverId)) {
|
|
922
969
|
assistantMessageId = serverId;
|
|
970
|
+
} else {
|
|
971
|
+
assistantCollided = true;
|
|
923
972
|
}
|
|
924
973
|
},
|
|
925
974
|
});
|
|
926
|
-
const dispatch = (event: AgUiEvent) =>
|
|
975
|
+
const dispatch = (event: AgUiEvent) =>
|
|
976
|
+
this.handleEvent(aggregator, event, assistantMessageId);
|
|
927
977
|
|
|
928
978
|
const abortController = new AbortController();
|
|
929
979
|
const abortSignal = abortController.signal;
|
|
@@ -1001,6 +1051,8 @@ export class AgUiThreadRuntimeCore {
|
|
|
1001
1051
|
const err = this.pendingError;
|
|
1002
1052
|
this.pendingError = null;
|
|
1003
1053
|
this.pendingResumeMessageId = null;
|
|
1054
|
+
this.pendingA2uiResume = false;
|
|
1055
|
+
this.pendingA2uiAction = undefined;
|
|
1004
1056
|
throw err;
|
|
1005
1057
|
}
|
|
1006
1058
|
|
|
@@ -1011,6 +1063,33 @@ export class AgUiThreadRuntimeCore {
|
|
|
1011
1063
|
this.pendingResumeMessageId = null;
|
|
1012
1064
|
if (!abortSignal.aborted) {
|
|
1013
1065
|
this.startResumeRun(resumeMessageId);
|
|
1066
|
+
} else {
|
|
1067
|
+
this.pendingA2uiAction = undefined;
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
if (this.pendingA2uiResume) {
|
|
1072
|
+
this.pendingA2uiResume = false;
|
|
1073
|
+
if (!abortSignal.aborted && this.pendingA2uiAction !== undefined) {
|
|
1074
|
+
if (this.getPendingInterrupts()) {
|
|
1075
|
+
this.pendingA2uiAction = undefined;
|
|
1076
|
+
this.logger.debug(
|
|
1077
|
+
"[agui] sendA2uiAction: pending interrupts, dropping action",
|
|
1078
|
+
);
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
this.maybeAutoCancelPendingToolCalls();
|
|
1082
|
+
const parentId = this.repository.headId;
|
|
1083
|
+
if (parentId !== null) {
|
|
1084
|
+
this.startResumeRun(parentId);
|
|
1085
|
+
} else {
|
|
1086
|
+
this.pendingA2uiAction = undefined;
|
|
1087
|
+
this.logger.debug(
|
|
1088
|
+
"[agui] sendA2uiAction: no messages to resume, dropping action",
|
|
1089
|
+
);
|
|
1090
|
+
}
|
|
1091
|
+
} else {
|
|
1092
|
+
this.pendingA2uiAction = undefined;
|
|
1014
1093
|
}
|
|
1015
1094
|
}
|
|
1016
1095
|
}
|
|
@@ -1029,6 +1108,8 @@ export class AgUiThreadRuntimeCore {
|
|
|
1029
1108
|
getAssistantMessageId: () => string | undefined;
|
|
1030
1109
|
},
|
|
1031
1110
|
): Promise<void> {
|
|
1111
|
+
this.pendingA2uiAction = undefined;
|
|
1112
|
+
this.pendingA2uiResume = false;
|
|
1032
1113
|
const assistantId = ctx.ensureAssistant();
|
|
1033
1114
|
const currentId = () => ctx.getAssistantMessageId() ?? assistantId;
|
|
1034
1115
|
const options: ChatModelRunOptions = {
|
|
@@ -1084,7 +1165,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1084
1165
|
historyMessages ?? this.repository.getMessages(),
|
|
1085
1166
|
);
|
|
1086
1167
|
const context = this.runtime?.thread.getModelContext();
|
|
1087
|
-
|
|
1168
|
+
const input = {
|
|
1088
1169
|
threadId,
|
|
1089
1170
|
runId,
|
|
1090
1171
|
state: this.stateSnapshot ?? null,
|
|
@@ -1097,9 +1178,14 @@ export class AgUiThreadRuntimeCore {
|
|
|
1097
1178
|
...(context?.callSettings ?? {}),
|
|
1098
1179
|
...(context?.config ?? {}),
|
|
1099
1180
|
...(runConfig?.custom ? { runConfig: runConfig.custom } : {}),
|
|
1181
|
+
...(this.pendingA2uiAction
|
|
1182
|
+
? { a2uiAction: { userAction: this.pendingA2uiAction } }
|
|
1183
|
+
: {}),
|
|
1100
1184
|
},
|
|
1101
1185
|
...(resume !== undefined ? { resume } : {}),
|
|
1102
1186
|
};
|
|
1187
|
+
this.pendingA2uiAction = undefined;
|
|
1188
|
+
return input;
|
|
1103
1189
|
}
|
|
1104
1190
|
|
|
1105
1191
|
private installResumeShim(): void {
|
|
@@ -1135,8 +1221,10 @@ export class AgUiThreadRuntimeCore {
|
|
|
1135
1221
|
this.setRunning(false);
|
|
1136
1222
|
}
|
|
1137
1223
|
|
|
1138
|
-
private insertAssistantPlaceholder(
|
|
1139
|
-
|
|
1224
|
+
private insertAssistantPlaceholder(
|
|
1225
|
+
parentId: string | null,
|
|
1226
|
+
id: string = generateOptimisticId(),
|
|
1227
|
+
): string {
|
|
1140
1228
|
const assistant: ThreadAssistantMessage = {
|
|
1141
1229
|
id,
|
|
1142
1230
|
role: "assistant",
|
|
@@ -1148,7 +1236,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1148
1236
|
unstable_annotations: [],
|
|
1149
1237
|
unstable_data: [],
|
|
1150
1238
|
steps: [],
|
|
1151
|
-
isOptimistic:
|
|
1239
|
+
isOptimistic: isOptimisticId(id),
|
|
1152
1240
|
custom: {},
|
|
1153
1241
|
},
|
|
1154
1242
|
};
|
|
@@ -1307,7 +1395,11 @@ export class AgUiThreadRuntimeCore {
|
|
|
1307
1395
|
};
|
|
1308
1396
|
}
|
|
1309
1397
|
|
|
1310
|
-
private handleEvent(
|
|
1398
|
+
private handleEvent(
|
|
1399
|
+
aggregator: RunAggregator,
|
|
1400
|
+
event: AgUiEvent,
|
|
1401
|
+
activeAssistantId: string | undefined,
|
|
1402
|
+
) {
|
|
1311
1403
|
switch (event.type) {
|
|
1312
1404
|
case "STATE_SNAPSHOT": {
|
|
1313
1405
|
this.stateSnapshot = event.snapshot as ReadonlyJSONValue;
|
|
@@ -1332,7 +1424,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1332
1424
|
return;
|
|
1333
1425
|
}
|
|
1334
1426
|
case "MESSAGES_SNAPSHOT": {
|
|
1335
|
-
this.importMessagesSnapshot(event.messages);
|
|
1427
|
+
this.importMessagesSnapshot(event.messages, activeAssistantId);
|
|
1336
1428
|
return;
|
|
1337
1429
|
}
|
|
1338
1430
|
case "TOOL_CALL_RESULT": {
|
|
@@ -1504,8 +1596,16 @@ export class AgUiThreadRuntimeCore {
|
|
|
1504
1596
|
this.maybeCompleteAfterToolResults(messageId);
|
|
1505
1597
|
}
|
|
1506
1598
|
|
|
1507
|
-
private importMessagesSnapshot(
|
|
1599
|
+
private importMessagesSnapshot(
|
|
1600
|
+
rawMessages: readonly unknown[],
|
|
1601
|
+
activeAssistantId: string | undefined,
|
|
1602
|
+
) {
|
|
1508
1603
|
try {
|
|
1604
|
+
const activeMessage = activeAssistantId
|
|
1605
|
+
? this.tryGetMessage(activeAssistantId)?.message
|
|
1606
|
+
: undefined;
|
|
1607
|
+
const activeAssistant =
|
|
1608
|
+
activeMessage?.role === "assistant" ? activeMessage : undefined;
|
|
1509
1609
|
const normalized = fromAgUiMessages(rawMessages, {
|
|
1510
1610
|
showThinking: this.showThinking,
|
|
1511
1611
|
});
|
|
@@ -1522,7 +1622,23 @@ export class AgUiThreadRuntimeCore {
|
|
|
1522
1622
|
);
|
|
1523
1623
|
}
|
|
1524
1624
|
}
|
|
1625
|
+
const snapshotHeadId = converted.at(-1)?.id ?? null;
|
|
1626
|
+
const snapshotContainsActiveAssistant = converted.some(
|
|
1627
|
+
(message) => message.id === activeAssistant?.id,
|
|
1628
|
+
);
|
|
1629
|
+
const preservesActiveAssistant =
|
|
1630
|
+
activeAssistant !== undefined &&
|
|
1631
|
+
!snapshotContainsActiveAssistant &&
|
|
1632
|
+
(activeAssistant.metadata.isOptimistic !== true ||
|
|
1633
|
+
converted.at(-1)?.role !== "assistant");
|
|
1634
|
+
if (preservesActiveAssistant) {
|
|
1635
|
+
converted.push(activeAssistant);
|
|
1636
|
+
}
|
|
1525
1637
|
this.applyExternalMessages(converted);
|
|
1638
|
+
if (preservesActiveAssistant) {
|
|
1639
|
+
this.recordedHistoryIds.delete(activeAssistant.id);
|
|
1640
|
+
this.markPendingAssistantHistory(activeAssistant.id, snapshotHeadId);
|
|
1641
|
+
}
|
|
1526
1642
|
} catch (error) {
|
|
1527
1643
|
this.logger.error?.("[agui] failed to import messages snapshot", error);
|
|
1528
1644
|
}
|
|
@@ -15,8 +15,15 @@ import { type Tool, toToolsJSONSchema } from "assistant-stream";
|
|
|
15
15
|
import type { ReadonlyJSONObject } from "assistant-stream/utils";
|
|
16
16
|
import {
|
|
17
17
|
AG_UI_METADATA_NAMESPACE,
|
|
18
|
+
A2UI_SURFACE_ACTIVITY_TYPE,
|
|
18
19
|
type AgUiCustomMetadata,
|
|
19
20
|
} from "./run-aggregator";
|
|
21
|
+
import {
|
|
22
|
+
applyA2uiOperations,
|
|
23
|
+
convertSurfaceToUISpec,
|
|
24
|
+
type A2uiState,
|
|
25
|
+
type A2uiSurfaceState,
|
|
26
|
+
} from "@assistant-ui/react-generative-ui/a2ui";
|
|
20
27
|
import type { AgUiInterrupt } from "../types";
|
|
21
28
|
import {
|
|
22
29
|
parseMcpToolCallResult,
|
|
@@ -211,6 +218,21 @@ function toInputContent(
|
|
|
211
218
|
}
|
|
212
219
|
}
|
|
213
220
|
|
|
221
|
+
if (type === "audio") {
|
|
222
|
+
const audio = part.audio;
|
|
223
|
+
if (!isObject(audio)) return null;
|
|
224
|
+
const data = getString(audio, "data");
|
|
225
|
+
const format = getString(audio, "format");
|
|
226
|
+
if (data === undefined || format === undefined) return null;
|
|
227
|
+
return {
|
|
228
|
+
type: "audio",
|
|
229
|
+
source: buildInputSource(
|
|
230
|
+
parseDataUrl(data)?.data ?? data,
|
|
231
|
+
`audio/${format}`,
|
|
232
|
+
),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
214
236
|
return null;
|
|
215
237
|
}
|
|
216
238
|
|
|
@@ -517,6 +539,42 @@ function toUserOrSystemSnapshotMessage(
|
|
|
517
539
|
};
|
|
518
540
|
}
|
|
519
541
|
|
|
542
|
+
// Rebuilds the a2ui:<surfaceId> "present" tool-call parts for one owning
|
|
543
|
+
// assistant message from a bucket of rebuilt surface state, mirroring the
|
|
544
|
+
// live RunAggregator.synthesizeA2uiToolCalls shape. Non-a2ui parts (text,
|
|
545
|
+
// other tool calls) are preserved; existing a2ui parts are replaced wholesale
|
|
546
|
+
// so create/update/delete within the bucket all converge on the rebuilt set.
|
|
547
|
+
function attachA2uiSurfaces(
|
|
548
|
+
message: CoreThreadMessageLike,
|
|
549
|
+
state: A2uiState,
|
|
550
|
+
): CoreThreadMessageLike {
|
|
551
|
+
const a2uiParts: ToolCallPart[] = [];
|
|
552
|
+
for (const [surfaceId, surface] of state) {
|
|
553
|
+
const { spec } = convertSurfaceToUISpec(surface);
|
|
554
|
+
if (!spec) continue;
|
|
555
|
+
a2uiParts.push({
|
|
556
|
+
type: "tool-call",
|
|
557
|
+
toolCallId: `a2ui:${surfaceId}`,
|
|
558
|
+
toolName: "present",
|
|
559
|
+
args: spec as unknown as ReadonlyJSONObject,
|
|
560
|
+
argsText: JSON.stringify(spec),
|
|
561
|
+
result: {},
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const content = Array.isArray(message.content) ? message.content : [];
|
|
566
|
+
const preserved = content.filter(
|
|
567
|
+
(part) =>
|
|
568
|
+
!(
|
|
569
|
+
isObject(part) &&
|
|
570
|
+
part.type === "tool-call" &&
|
|
571
|
+
typeof part.toolCallId === "string" &&
|
|
572
|
+
part.toolCallId.startsWith("a2ui:")
|
|
573
|
+
),
|
|
574
|
+
);
|
|
575
|
+
return { ...message, content: [...preserved, ...a2uiParts] };
|
|
576
|
+
}
|
|
577
|
+
|
|
520
578
|
export type FromAgUiMessagesOptions = {
|
|
521
579
|
/**
|
|
522
580
|
* Whether to convert `reasoning` messages into visible reasoning parts.
|
|
@@ -531,6 +589,8 @@ export function fromAgUiMessages(
|
|
|
531
589
|
): CoreThreadMessageLike[] {
|
|
532
590
|
const showThinking = options?.showThinking ?? true;
|
|
533
591
|
const converted: CoreThreadMessageLike[] = [];
|
|
592
|
+
const a2uiBuckets = new Map<string, A2uiState>();
|
|
593
|
+
const a2uiBucketOwnerIndices = new Map<string, number>();
|
|
534
594
|
|
|
535
595
|
for (const rawMessage of messages) {
|
|
536
596
|
if (!isObject(rawMessage)) continue;
|
|
@@ -638,6 +698,47 @@ export function fromAgUiMessages(
|
|
|
638
698
|
continue;
|
|
639
699
|
}
|
|
640
700
|
|
|
701
|
+
if (role === "activity") {
|
|
702
|
+
// Only a2ui-surface activity messages have an assistant-part equivalent
|
|
703
|
+
// to rehydrate; other activity types still have no surface to repaint.
|
|
704
|
+
const activityType = getString(rawMessage, "activityType");
|
|
705
|
+
if (activityType !== A2UI_SURFACE_ACTIVITY_TYPE) continue;
|
|
706
|
+
const activityContent = isObject(rawMessage.content)
|
|
707
|
+
? (rawMessage.content as Record<string, unknown>)
|
|
708
|
+
: null;
|
|
709
|
+
const operations = activityContent?.["a2ui_operations"];
|
|
710
|
+
if (!Array.isArray(operations)) continue;
|
|
711
|
+
|
|
712
|
+
let ownerIndex = -1;
|
|
713
|
+
for (let i = converted.length - 1; i >= 0; i--) {
|
|
714
|
+
const candidate = converted[i];
|
|
715
|
+
if (candidate && candidate.role === "assistant") {
|
|
716
|
+
ownerIndex = i;
|
|
717
|
+
break;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (ownerIndex === -1) continue;
|
|
721
|
+
|
|
722
|
+
const owner = converted[ownerIndex]!;
|
|
723
|
+
const bucketKey = getString(rawMessage, "id") ?? "a2ui:anonymous";
|
|
724
|
+
const { state } = applyA2uiOperations(new Map(), operations);
|
|
725
|
+
a2uiBuckets.delete(bucketKey);
|
|
726
|
+
a2uiBucketOwnerIndices.delete(bucketKey);
|
|
727
|
+
a2uiBuckets.set(bucketKey, state);
|
|
728
|
+
a2uiBucketOwnerIndices.set(bucketKey, ownerIndex);
|
|
729
|
+
|
|
730
|
+
const ownerState = new Map<string, A2uiSurfaceState>();
|
|
731
|
+
for (const [candidateBucketKey, candidateState] of a2uiBuckets) {
|
|
732
|
+
if (a2uiBucketOwnerIndices.get(candidateBucketKey) !== ownerIndex)
|
|
733
|
+
continue;
|
|
734
|
+
for (const [surfaceId, surface] of candidateState) {
|
|
735
|
+
ownerState.set(surfaceId, surface);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
converted[ownerIndex] = attachA2uiSurfaces(owner, ownerState);
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
|
|
641
742
|
if (role === "assistant") {
|
|
642
743
|
converted.push(toAssistantSnapshotMessage(rawMessage));
|
|
643
744
|
continue;
|
|
@@ -702,7 +803,12 @@ function convertAssistantMessage(
|
|
|
702
803
|
const contentArray = Array.isArray(message.content) ? message.content : [];
|
|
703
804
|
|
|
704
805
|
const toolCallParts = contentArray.filter(
|
|
705
|
-
(part): part is ToolCallPart =>
|
|
806
|
+
(part): part is ToolCallPart =>
|
|
807
|
+
part?.type === "tool-call" &&
|
|
808
|
+
!(
|
|
809
|
+
typeof part.toolCallId === "string" &&
|
|
810
|
+
part.toolCallId.startsWith("a2ui:")
|
|
811
|
+
),
|
|
706
812
|
);
|
|
707
813
|
|
|
708
814
|
const toolCalls = toolCallParts.map((part) => ({
|