@assistant-ui/react-ag-ui 0.0.48 → 0.0.50
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 +69 -5
- 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 +124 -13
- 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.50",
|
|
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.3.
|
|
34
|
-
"@assistant-ui/
|
|
35
|
-
"assistant-
|
|
33
|
+
"@assistant-ui/core": "^0.3.4",
|
|
34
|
+
"@assistant-ui/react-generative-ui": "^0.0.11",
|
|
35
|
+
"@assistant-ui/store": "^0.3.3",
|
|
36
|
+
"assistant-stream": "^0.3.32",
|
|
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,
|
|
@@ -154,12 +161,14 @@ function mediaTypeForMime(mimeType: string | undefined): MediaInputType {
|
|
|
154
161
|
|
|
155
162
|
// Build an AG-UI multimodal source from a data URL, raw base64 payload, or an
|
|
156
163
|
// http(s) URL. A `url` source may omit the mime type; a `data` source always
|
|
157
|
-
// resolves one (falling back to application/octet-stream).
|
|
164
|
+
// resolves one (falling back to application/octet-stream). An explicit
|
|
165
|
+
// `sourceType: "url"` on the part forces the url leg for non-http references.
|
|
158
166
|
function buildInputSource(
|
|
159
167
|
value: string,
|
|
160
168
|
declaredMimeType: string | undefined,
|
|
169
|
+
sourceType?: string,
|
|
161
170
|
): InputContentSource {
|
|
162
|
-
if (httpUrlPattern.test(value)) {
|
|
171
|
+
if (sourceType === "url" || httpUrlPattern.test(value)) {
|
|
163
172
|
return declaredMimeType !== undefined
|
|
164
173
|
? { type: "url", value, mimeType: declaredMimeType }
|
|
165
174
|
: { type: "url", value };
|
|
@@ -197,7 +206,11 @@ function toInputContent(
|
|
|
197
206
|
if (data === undefined) return null;
|
|
198
207
|
const declaredMimeType = getString(part, "mimeType") || fallbackMimeType;
|
|
199
208
|
const filename = getString(part, "filename");
|
|
200
|
-
const source = buildInputSource(
|
|
209
|
+
const source = buildInputSource(
|
|
210
|
+
data,
|
|
211
|
+
declaredMimeType,
|
|
212
|
+
getString(part, "sourceType"),
|
|
213
|
+
);
|
|
201
214
|
const metadata = filename !== undefined ? { filename } : undefined;
|
|
202
215
|
switch (mediaTypeForMime(source.mimeType)) {
|
|
203
216
|
case "image":
|
|
@@ -211,6 +224,21 @@ function toInputContent(
|
|
|
211
224
|
}
|
|
212
225
|
}
|
|
213
226
|
|
|
227
|
+
if (type === "audio") {
|
|
228
|
+
const audio = part.audio;
|
|
229
|
+
if (!isObject(audio)) return null;
|
|
230
|
+
const data = getString(audio, "data");
|
|
231
|
+
const format = getString(audio, "format");
|
|
232
|
+
if (data === undefined || format === undefined) return null;
|
|
233
|
+
return {
|
|
234
|
+
type: "audio",
|
|
235
|
+
source: buildInputSource(
|
|
236
|
+
parseDataUrl(data)?.data ?? data,
|
|
237
|
+
`audio/${format}`,
|
|
238
|
+
),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
214
242
|
return null;
|
|
215
243
|
}
|
|
216
244
|
|
|
@@ -223,14 +251,18 @@ const mediaInputTypes = new Set(["image", "audio", "video", "document"]);
|
|
|
223
251
|
// Inverse of buildInputSource.
|
|
224
252
|
function inputSourceToString(
|
|
225
253
|
value: unknown,
|
|
226
|
-
): { value: string; mimeType?: string } | null {
|
|
254
|
+
): { value: string; mimeType?: string; isUrl?: boolean } | null {
|
|
227
255
|
if (!isObject(value)) return null;
|
|
228
256
|
const sourceValue = getString(value, "value");
|
|
229
257
|
if (sourceValue === undefined) return null;
|
|
230
258
|
const mimeType = getString(value, "mimeType");
|
|
231
259
|
const type = getString(value, "type");
|
|
232
260
|
if (type === "url") {
|
|
233
|
-
return {
|
|
261
|
+
return {
|
|
262
|
+
value: sourceValue,
|
|
263
|
+
isUrl: true,
|
|
264
|
+
...(mimeType !== undefined && { mimeType }),
|
|
265
|
+
};
|
|
234
266
|
}
|
|
235
267
|
if (type === "data") {
|
|
236
268
|
const resolvedMimeType = mimeType ?? "application/octet-stream";
|
|
@@ -315,6 +347,7 @@ function toSnapshotAttachments(content: unknown): SnapshotAttachment[] {
|
|
|
315
347
|
type: "file",
|
|
316
348
|
data: source.value,
|
|
317
349
|
mimeType,
|
|
350
|
+
...(source.isUrl && { sourceType: "url" as const }),
|
|
318
351
|
...(filename !== undefined && { filename }),
|
|
319
352
|
},
|
|
320
353
|
],
|
|
@@ -324,13 +357,7 @@ function toSnapshotAttachments(content: unknown): SnapshotAttachment[] {
|
|
|
324
357
|
}
|
|
325
358
|
|
|
326
359
|
function buildUserContent(message: ThreadMessageLike): string | InputContent[] {
|
|
327
|
-
|
|
328
|
-
// binary payload for files always flows through message.attachments.
|
|
329
|
-
const contentParts = Array.isArray(message.content)
|
|
330
|
-
? message.content.filter(
|
|
331
|
-
(part) => !(isObject(part) && part.type === "file"),
|
|
332
|
-
)
|
|
333
|
-
: [];
|
|
360
|
+
const contentParts = Array.isArray(message.content) ? message.content : [];
|
|
334
361
|
|
|
335
362
|
const attachments = message.attachments ?? [];
|
|
336
363
|
|
|
@@ -517,6 +544,42 @@ function toUserOrSystemSnapshotMessage(
|
|
|
517
544
|
};
|
|
518
545
|
}
|
|
519
546
|
|
|
547
|
+
// Rebuilds the a2ui:<surfaceId> "present" tool-call parts for one owning
|
|
548
|
+
// assistant message from a bucket of rebuilt surface state, mirroring the
|
|
549
|
+
// live RunAggregator.synthesizeA2uiToolCalls shape. Non-a2ui parts (text,
|
|
550
|
+
// other tool calls) are preserved; existing a2ui parts are replaced wholesale
|
|
551
|
+
// so create/update/delete within the bucket all converge on the rebuilt set.
|
|
552
|
+
function attachA2uiSurfaces(
|
|
553
|
+
message: CoreThreadMessageLike,
|
|
554
|
+
state: A2uiState,
|
|
555
|
+
): CoreThreadMessageLike {
|
|
556
|
+
const a2uiParts: ToolCallPart[] = [];
|
|
557
|
+
for (const [surfaceId, surface] of state) {
|
|
558
|
+
const { spec } = convertSurfaceToUISpec(surface);
|
|
559
|
+
if (!spec) continue;
|
|
560
|
+
a2uiParts.push({
|
|
561
|
+
type: "tool-call",
|
|
562
|
+
toolCallId: `a2ui:${surfaceId}`,
|
|
563
|
+
toolName: "present",
|
|
564
|
+
args: spec as unknown as ReadonlyJSONObject,
|
|
565
|
+
argsText: JSON.stringify(spec),
|
|
566
|
+
result: {},
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const content = Array.isArray(message.content) ? message.content : [];
|
|
571
|
+
const preserved = content.filter(
|
|
572
|
+
(part) =>
|
|
573
|
+
!(
|
|
574
|
+
isObject(part) &&
|
|
575
|
+
part.type === "tool-call" &&
|
|
576
|
+
typeof part.toolCallId === "string" &&
|
|
577
|
+
part.toolCallId.startsWith("a2ui:")
|
|
578
|
+
),
|
|
579
|
+
);
|
|
580
|
+
return { ...message, content: [...preserved, ...a2uiParts] };
|
|
581
|
+
}
|
|
582
|
+
|
|
520
583
|
export type FromAgUiMessagesOptions = {
|
|
521
584
|
/**
|
|
522
585
|
* Whether to convert `reasoning` messages into visible reasoning parts.
|
|
@@ -531,6 +594,8 @@ export function fromAgUiMessages(
|
|
|
531
594
|
): CoreThreadMessageLike[] {
|
|
532
595
|
const showThinking = options?.showThinking ?? true;
|
|
533
596
|
const converted: CoreThreadMessageLike[] = [];
|
|
597
|
+
const a2uiBuckets = new Map<string, A2uiState>();
|
|
598
|
+
const a2uiBucketOwnerIndices = new Map<string, number>();
|
|
534
599
|
|
|
535
600
|
for (const rawMessage of messages) {
|
|
536
601
|
if (!isObject(rawMessage)) continue;
|
|
@@ -638,6 +703,47 @@ export function fromAgUiMessages(
|
|
|
638
703
|
continue;
|
|
639
704
|
}
|
|
640
705
|
|
|
706
|
+
if (role === "activity") {
|
|
707
|
+
// Only a2ui-surface activity messages have an assistant-part equivalent
|
|
708
|
+
// to rehydrate; other activity types still have no surface to repaint.
|
|
709
|
+
const activityType = getString(rawMessage, "activityType");
|
|
710
|
+
if (activityType !== A2UI_SURFACE_ACTIVITY_TYPE) continue;
|
|
711
|
+
const activityContent = isObject(rawMessage.content)
|
|
712
|
+
? (rawMessage.content as Record<string, unknown>)
|
|
713
|
+
: null;
|
|
714
|
+
const operations = activityContent?.["a2ui_operations"];
|
|
715
|
+
if (!Array.isArray(operations)) continue;
|
|
716
|
+
|
|
717
|
+
let ownerIndex = -1;
|
|
718
|
+
for (let i = converted.length - 1; i >= 0; i--) {
|
|
719
|
+
const candidate = converted[i];
|
|
720
|
+
if (candidate && candidate.role === "assistant") {
|
|
721
|
+
ownerIndex = i;
|
|
722
|
+
break;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
if (ownerIndex === -1) continue;
|
|
726
|
+
|
|
727
|
+
const owner = converted[ownerIndex]!;
|
|
728
|
+
const bucketKey = getString(rawMessage, "id") ?? "a2ui:anonymous";
|
|
729
|
+
const { state } = applyA2uiOperations(new Map(), operations);
|
|
730
|
+
a2uiBuckets.delete(bucketKey);
|
|
731
|
+
a2uiBucketOwnerIndices.delete(bucketKey);
|
|
732
|
+
a2uiBuckets.set(bucketKey, state);
|
|
733
|
+
a2uiBucketOwnerIndices.set(bucketKey, ownerIndex);
|
|
734
|
+
|
|
735
|
+
const ownerState = new Map<string, A2uiSurfaceState>();
|
|
736
|
+
for (const [candidateBucketKey, candidateState] of a2uiBuckets) {
|
|
737
|
+
if (a2uiBucketOwnerIndices.get(candidateBucketKey) !== ownerIndex)
|
|
738
|
+
continue;
|
|
739
|
+
for (const [surfaceId, surface] of candidateState) {
|
|
740
|
+
ownerState.set(surfaceId, surface);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
converted[ownerIndex] = attachA2uiSurfaces(owner, ownerState);
|
|
744
|
+
continue;
|
|
745
|
+
}
|
|
746
|
+
|
|
641
747
|
if (role === "assistant") {
|
|
642
748
|
converted.push(toAssistantSnapshotMessage(rawMessage));
|
|
643
749
|
continue;
|
|
@@ -702,7 +808,12 @@ function convertAssistantMessage(
|
|
|
702
808
|
const contentArray = Array.isArray(message.content) ? message.content : [];
|
|
703
809
|
|
|
704
810
|
const toolCallParts = contentArray.filter(
|
|
705
|
-
(part): part is ToolCallPart =>
|
|
811
|
+
(part): part is ToolCallPart =>
|
|
812
|
+
part?.type === "tool-call" &&
|
|
813
|
+
!(
|
|
814
|
+
typeof part.toolCallId === "string" &&
|
|
815
|
+
part.toolCallId.startsWith("a2ui:")
|
|
816
|
+
),
|
|
706
817
|
);
|
|
707
818
|
|
|
708
819
|
const toolCalls = toolCallParts.map((part) => ({
|