@assistant-ui/react-ag-ui 0.0.59 → 0.0.61
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 +2 -5
- package/dist/agUiExtras.d.ts.map +1 -1
- package/dist/hooks.d.ts +9 -12
- package/dist/hooks.d.ts.map +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.d.ts.map +1 -0
- package/dist/runtime/AgUiThreadRuntimeCore.d.ts +153 -146
- package/dist/runtime/AgUiThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtime/AgUiThreadRuntimeCore.js +146 -26
- package/dist/runtime/AgUiThreadRuntimeCore.js.map +1 -1
- package/dist/runtime/adapter/conversions.d.ts +54 -56
- package/dist/runtime/adapter/conversions.d.ts.map +1 -1
- package/dist/runtime/adapter/conversions.js +164 -23
- package/dist/runtime/adapter/conversions.js.map +1 -1
- package/dist/runtime/adapter/run-aggregator.d.ts +98 -98
- package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
- package/dist/runtime/adapter/run-aggregator.js +35 -2
- package/dist/runtime/adapter/run-aggregator.js.map +1 -1
- package/dist/runtime/adapter/subscriber.d.ts +109 -111
- package/dist/runtime/adapter/subscriber.d.ts.map +1 -1
- package/dist/runtime/adapter/tool-approval.d.ts +7 -9
- package/dist/runtime/adapter/tool-approval.d.ts.map +1 -1
- package/dist/runtime/event-parser.d.ts +5 -8
- package/dist/runtime/event-parser.d.ts.map +1 -1
- package/dist/runtime/interrupt-internals.d.ts +4 -7
- package/dist/runtime/interrupt-internals.d.ts.map +1 -1
- package/dist/runtime/logger.d.ts +4 -7
- package/dist/runtime/logger.d.ts.map +1 -1
- package/dist/runtime/mcp-tool-result.d.ts +7 -10
- package/dist/runtime/mcp-tool-result.d.ts.map +1 -1
- package/dist/runtime/types.d.ts +248 -250
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/useAgUiRuntime.d.ts +15 -18
- package/dist/useAgUiRuntime.d.ts.map +1 -1
- package/dist/useAgUiRuntime.js +9 -2
- package/dist/useAgUiRuntime.js.map +1 -1
- package/package.json +12 -11
- package/src/runtime/AgUiThreadRuntimeCore.ts +246 -30
- package/src/runtime/adapter/conversions.content.test.ts +56 -0
- package/src/runtime/adapter/conversions.ts +278 -36
- package/src/runtime/adapter/run-aggregator.ts +53 -2
- package/src/useAgUiRuntime.history.test.tsx +93 -2
- package/src/useAgUiRuntime.thread-switch.test.tsx +84 -4
- package/src/useAgUiRuntime.ts +12 -3
- package/src/useAgUiRuntime.voice.test.tsx +402 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAgUiRuntime.js","names":[],"sources":["../src/useAgUiRuntime.ts"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport {\n useExternalStoreRuntime,\n useExternalStoreSharedOptions,\n useRuntimeAdapters,\n} from \"@assistant-ui/core/react\";\nimport { createMessageQueue } from \"@assistant-ui/core\";\nimport type {\n MessageQueueController,\n ToolExecutionStatus,\n} 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\";\nimport type { QueueItemState } from \"@assistant-ui/core/store\";\n\nconst EMPTY_INTERRUPTS: readonly AgUiInterrupt[] = [];\nconst EMPTY_QUEUE_ITEMS: readonly QueueItemState[] = Object.freeze([]);\nconst subscribeNoop = () => () => {};\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 threadSwitchGenerationRef = useRef(0);\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 resumeTranscript: options.resumeTranscript,\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 useEffect(() => {\n core.updateOptions({\n agent: options.agent,\n logger,\n showThinking: options.showThinking ?? true,\n resumeTranscript: options.resumeTranscript,\n autoCancelPendingToolCalls: options.autoCancelPendingToolCalls,\n ...(options.onError && { onError: options.onError }),\n ...(options.onCancel && { onCancel: options.onCancel }),\n ...(historyAdapter && { history: historyAdapter }),\n });\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 const isRunning = core.isRunning() || hasExecutingTools;\n\n // The driver sends through the agent core rather than the runtime, because\n // the runtime's append routes every tail append back into this queue.\n const queueRef = useRef<MessageQueueController | null>(null);\n // Counts rendered busy edges, whether a run start or an interrupt arriving.\n // A dispatch whose count never moves was never observed as busy, so no\n // falling edge is coming to release the queue.\n const busyEdgesRef = useRef(0);\n if (options.unstable_enableMessageQueue && !queueRef.current) {\n queueRef.current = createMessageQueue({\n run: (message) => {\n const controller = queueRef.current;\n const edgesAtDispatch = busyEdgesRef.current;\n // The queue drops the item before dispatching and stays busy until an\n // idle edge. An append observed as busy is released by that falling\n // edge, and releasing again here would advance the queue twice; one\n // that never runs (a pre-run rejection, or startRun false) has to be\n // released here or every later send buffers forever. An interrupt is\n // rechecked because a run that resolves without yielding to React\n // reaches here before the effect below has seen either edge.\n const releaseIfNoRun = () => {\n if (\n queueRef.current !== controller ||\n busyEdgesRef.current !== edgesAtDispatch ||\n core.getPendingInterrupts() !== null\n )\n return;\n controller?.notifyIdle();\n };\n void core.append(message).then(releaseIfNoRun, (e: unknown) => {\n releaseIfNoRun();\n logger.error?.(\"[agui] queued message failed to send\", e);\n });\n },\n });\n }\n const queueController = options.unstable_enableMessageQueue\n ? queueRef.current\n : null;\n useLayoutEffect(() => {\n if (options.unstable_enableMessageQueue || !queueRef.current) return;\n const controller = queueRef.current;\n queueRef.current = null;\n controller.clear();\n }, [options.unstable_enableMessageQueue]);\n\n // Feeds the store memo below: the runtime core skips an adapter whose\n // identity is unchanged, so queue items have to move the store reference or\n // subscribers never see composer.queue change.\n const queueItems = useSyncExternalStore(\n queueController?.subscribe ?? subscribeNoop,\n () => queueController?.adapter.items ?? EMPTY_QUEUE_ITEMS,\n () => EMPTY_QUEUE_ITEMS,\n );\n const steerQueueItems = useSyncExternalStore(\n queueController?.subscribe ?? subscribeNoop,\n () => queueController?.adapter.steerItems ?? EMPTY_QUEUE_ITEMS,\n () => EMPTY_QUEUE_ITEMS,\n );\n\n // Holds the queue while client-side tools are still executing, and while an\n // interrupt is unanswered: `append` refuses a new run until it is resolved,\n // and the queue drops an item before dispatching it, so draining into that\n // refusal would lose the message instead of keeping it visible.\n const queueBusy = isRunning || core.getPendingInterrupts() !== null;\n useEffect(() => {\n if (queueBusy) {\n busyEdgesRef.current++;\n queueController?.notifyBusy();\n } else {\n queueController?.notifyIdle();\n }\n }, [queueBusy, queueController]);\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 const generation = ++threadSwitchGenerationRef.current;\n await onSwitchToNewThread();\n if (generation !== threadSwitchGenerationRef.current) return;\n core.applyExternalMessages([]);\n core.resetState();\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 const result = await onSwitchToThread(threadId);\n if (generation !== threadSwitchGenerationRef.current) return;\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(),\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) => {\n queueController?.clear();\n return core.edit(message);\n },\n onReload: (parentId: string | null, config: { runConfig?: any }) => {\n queueController?.clear();\n return core.reload(parentId, config);\n },\n onCancel: async () => {\n queueController?.notifyCancelled();\n core.cancel();\n },\n onAddToolResult: (options) => core.addToolResult(options),\n onRespondToToolApproval: (options) =>\n core.respondToToolApproval(options).catch((error: unknown) => {\n core.reportError(error);\n throw error;\n }),\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 ...(queueController && { queue: queueController.adapter }),\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 [\n adapterAdapters,\n core,\n _version,\n isRunning,\n queueController,\n queueItems,\n steerQueueItems,\n shared,\n ],\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 {\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport {\n useExternalStoreRuntime,\n useExternalStoreSharedOptions,\n useRuntimeAdapters,\n} from \"@assistant-ui/core/react\";\nimport { createMessageQueue } from \"@assistant-ui/core\";\nimport type {\n MessageQueueController,\n ToolExecutionStatus,\n} 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\";\nimport type { QueueItemState } from \"@assistant-ui/core/store\";\n\nconst EMPTY_INTERRUPTS: readonly AgUiInterrupt[] = [];\nconst EMPTY_QUEUE_ITEMS: readonly QueueItemState[] = Object.freeze([]);\nconst subscribeNoop = () => () => {};\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 threadSwitchGenerationRef = useRef(0);\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 resumeTranscript: options.resumeTranscript,\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 useEffect(() => {\n core.updateOptions({\n agent: options.agent,\n logger,\n showThinking: options.showThinking ?? true,\n resumeTranscript: options.resumeTranscript,\n autoCancelPendingToolCalls: options.autoCancelPendingToolCalls,\n ...(options.onError && { onError: options.onError }),\n ...(options.onCancel && { onCancel: options.onCancel }),\n ...(historyAdapter && { history: historyAdapter }),\n });\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 const isRunning = core.isRunning() || hasExecutingTools;\n\n // The driver sends through the agent core rather than the runtime, because\n // the runtime's append routes every tail append back into this queue.\n const queueRef = useRef<MessageQueueController | null>(null);\n // Counts rendered busy edges, whether a run start or an interrupt arriving.\n // A dispatch whose count never moves was never observed as busy, so no\n // falling edge is coming to release the queue.\n const busyEdgesRef = useRef(0);\n if (options.unstable_enableMessageQueue && !queueRef.current) {\n queueRef.current = createMessageQueue({\n run: (message) => {\n const controller = queueRef.current;\n const edgesAtDispatch = busyEdgesRef.current;\n // The queue drops the item before dispatching and stays busy until an\n // idle edge. An append observed as busy is released by that falling\n // edge, and releasing again here would advance the queue twice; one\n // that never runs (a pre-run rejection, or startRun false) has to be\n // released here or every later send buffers forever. An interrupt is\n // rechecked because a run that resolves without yielding to React\n // reaches here before the effect below has seen either edge.\n const releaseIfNoRun = () => {\n if (\n queueRef.current !== controller ||\n busyEdgesRef.current !== edgesAtDispatch ||\n core.getPendingInterrupts() !== null\n )\n return;\n controller?.notifyIdle();\n };\n void core.append(message).then(releaseIfNoRun, (e: unknown) => {\n releaseIfNoRun();\n logger.error?.(\"[agui] queued message failed to send\", e);\n });\n },\n });\n }\n const queueController = options.unstable_enableMessageQueue\n ? queueRef.current\n : null;\n useLayoutEffect(() => {\n if (options.unstable_enableMessageQueue || !queueRef.current) return;\n const controller = queueRef.current;\n queueRef.current = null;\n controller.clear();\n }, [options.unstable_enableMessageQueue]);\n\n // Feeds the store memo below: the runtime core skips an adapter whose\n // identity is unchanged, so queue items have to move the store reference or\n // subscribers never see composer.queue change.\n const queueItems = useSyncExternalStore(\n queueController?.subscribe ?? subscribeNoop,\n () => queueController?.adapter.items ?? EMPTY_QUEUE_ITEMS,\n () => EMPTY_QUEUE_ITEMS,\n );\n const steerQueueItems = useSyncExternalStore(\n queueController?.subscribe ?? subscribeNoop,\n () => queueController?.adapter.steerItems ?? EMPTY_QUEUE_ITEMS,\n () => EMPTY_QUEUE_ITEMS,\n );\n\n // Holds the queue while client-side tools are still executing, and while an\n // interrupt is unanswered: `append` refuses a new run until it is resolved,\n // and the queue drops an item before dispatching it, so draining into that\n // refusal would lose the message instead of keeping it visible.\n const queueBusy = isRunning || core.getPendingInterrupts() !== null;\n useEffect(() => {\n if (queueBusy) {\n busyEdgesRef.current++;\n queueController?.notifyBusy();\n } else {\n queueController?.notifyIdle();\n }\n }, [queueBusy, queueController]);\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 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.resetThreadState();\n await onSwitchToNewThread();\n if (generation !== threadSwitchGenerationRef.current) return;\n core.applyExternalMessages([]);\n core.resetThreadState();\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.resetThreadState();\n const result = await onSwitchToThread(threadId);\n if (generation !== threadSwitchGenerationRef.current) return;\n core.applyExternalMessages([]);\n core.resetThreadState();\n core.applyExternalMessages(result.messages);\n if (result.state !== undefined) {\n core.loadExternalState(result.state);\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(),\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 unstable_persistsHistory: true,\n setToolStatuses,\n onNew: (message: AppendMessage) => core.append(message),\n onVoiceTranscript: (message) => core.appendVoiceTranscript(message),\n onEdit: (message: AppendMessage) => {\n queueController?.clear();\n return core.edit(message);\n },\n onReload: (parentId: string | null, config: { runConfig?: any }) => {\n queueController?.clear();\n return core.reload(parentId, config);\n },\n onCancel: async () => {\n queueController?.notifyCancelled();\n core.cancel();\n },\n onAddToolResult: (options) => core.addToolResult(options),\n unstable_onRecordToolInteraction: (options) =>\n core.recordToolInteraction(options),\n onRespondToToolApproval: (options) =>\n core.respondToToolApproval(options).catch((error: unknown) => {\n core.reportError(error);\n throw error;\n }),\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 ...(queueController && { queue: queueController.adapter }),\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 [\n adapterAdapters,\n core,\n _version,\n isRunning,\n queueController,\n queueItems,\n steerQueueItems,\n shared,\n ],\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.61",
|
|
4
4
|
"description": "AG-UI protocol adapter for assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ag-ui",
|
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
"sideEffects": false,
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@ag-ui/client": "^0.0.59",
|
|
33
|
-
"@assistant-ui/core": "^0.3.
|
|
34
|
-
"@assistant-ui/react-generative-ui": "^0.0.
|
|
35
|
-
"@assistant-ui/store": "^0.3.
|
|
36
|
-
"assistant-stream": "^0.3.
|
|
33
|
+
"@assistant-ui/core": "^0.3.21",
|
|
34
|
+
"@assistant-ui/react-generative-ui": "^0.0.20",
|
|
35
|
+
"@assistant-ui/store": "^0.3.15",
|
|
36
|
+
"assistant-stream": "^0.3.45",
|
|
37
37
|
"fast-json-patch": "^3.1.1"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
@@ -46,14 +46,14 @@
|
|
|
46
46
|
}
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
50
|
-
"@testing-library/dom": "^10.4.
|
|
49
|
+
"@assistant-ui/x-buildutils": "0.0.29",
|
|
50
|
+
"@testing-library/dom": "^10.4.2",
|
|
51
51
|
"@testing-library/react": "^16.3.3",
|
|
52
|
-
"@types/react": "^19.
|
|
52
|
+
"@types/react": "^19.3.0",
|
|
53
53
|
"jsdom": "^30.0.1",
|
|
54
|
-
"react": "^19.
|
|
55
|
-
"vitest": "^
|
|
56
|
-
"zod": "^4.5
|
|
54
|
+
"react": "^19.3.0",
|
|
55
|
+
"vitest": "^5.0.1",
|
|
56
|
+
"zod": "^4.6.5"
|
|
57
57
|
},
|
|
58
58
|
"publishConfig": {
|
|
59
59
|
"access": "public",
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"scripts": {
|
|
72
72
|
"build": "aui-build",
|
|
73
73
|
"test": "vitest run",
|
|
74
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
|
|
74
75
|
"test:watch": "vitest"
|
|
75
76
|
}
|
|
76
77
|
}
|
|
@@ -19,8 +19,10 @@ import type {
|
|
|
19
19
|
ThreadHistoryAdapter,
|
|
20
20
|
ThreadMessage,
|
|
21
21
|
ToolCallMessagePart,
|
|
22
|
+
Unstable_ToolInteraction,
|
|
22
23
|
} from "@assistant-ui/core";
|
|
23
24
|
import {
|
|
25
|
+
appendToolInteraction,
|
|
24
26
|
createMessageRepositorySession,
|
|
25
27
|
invokeUserCallback,
|
|
26
28
|
iterateToolCallParts,
|
|
@@ -329,6 +331,14 @@ export class AgUiThreadRuntimeCore {
|
|
|
329
331
|
await this.startRun(threadMessageId, message.runConfig);
|
|
330
332
|
}
|
|
331
333
|
|
|
334
|
+
appendVoiceTranscript(message: ThreadMessage): void {
|
|
335
|
+
const parentId = this.session.headId;
|
|
336
|
+
this.session.addOrUpdateMessage(parentId, message);
|
|
337
|
+
this.session.switchToBranch(message.id);
|
|
338
|
+
this.notifyUpdate();
|
|
339
|
+
this.recordHistoryEntry(parentId, message);
|
|
340
|
+
}
|
|
341
|
+
|
|
332
342
|
private maybeAutoCancelPendingToolCalls(): void {
|
|
333
343
|
if (this.autoCancelPendingToolCalls === false) return;
|
|
334
344
|
const pending = this.getPendingToolCalls();
|
|
@@ -871,11 +881,17 @@ export class AgUiThreadRuntimeCore {
|
|
|
871
881
|
const { content } = mapToolCallPartsDeep(assistant.content, (part) => {
|
|
872
882
|
if (part.toolCallId !== options.toolCallId) return part;
|
|
873
883
|
matchedToolCall = true;
|
|
884
|
+
// artifact and modelContent are optional; only override when supplied
|
|
885
|
+
// so a later result that omits them keeps a stored value, and
|
|
886
|
+
// emitToolResult can forward the model-facing content to the agent.
|
|
874
887
|
return {
|
|
875
888
|
...part,
|
|
876
889
|
result: options.result,
|
|
877
|
-
artifact: options.artifact,
|
|
878
890
|
isError: options.isError,
|
|
891
|
+
...(options.artifact !== undefined && { artifact: options.artifact }),
|
|
892
|
+
...(options.modelContent !== undefined && {
|
|
893
|
+
modelContent: options.modelContent,
|
|
894
|
+
}),
|
|
879
895
|
};
|
|
880
896
|
});
|
|
881
897
|
if (!matchedToolCall) return message;
|
|
@@ -887,6 +903,67 @@ export class AgUiThreadRuntimeCore {
|
|
|
887
903
|
this.maybeResumeAfterToolResults(sessionMessageId);
|
|
888
904
|
}
|
|
889
905
|
|
|
906
|
+
async recordToolInteraction(options: {
|
|
907
|
+
messageId: string;
|
|
908
|
+
toolCallId: string;
|
|
909
|
+
interaction: Unstable_ToolInteraction;
|
|
910
|
+
}): Promise<void> {
|
|
911
|
+
const sessionMessageId = this.session.tryGetMessage(options.messageId)
|
|
912
|
+
? options.messageId
|
|
913
|
+
: this.findMessageIdForToolCall(options.toolCallId);
|
|
914
|
+
if (sessionMessageId === undefined) {
|
|
915
|
+
throw new Error(
|
|
916
|
+
`[agui] recordToolInteraction: message "${options.messageId}" was not found`,
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const item = this.session.tryGetMessage(sessionMessageId);
|
|
921
|
+
if (!item) {
|
|
922
|
+
throw new Error(
|
|
923
|
+
`[agui] recordToolInteraction: message "${sessionMessageId}" was not found`,
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
let recorded = false;
|
|
928
|
+
const updated = this.session.updateMessage(sessionMessageId, (message) => {
|
|
929
|
+
if (message.role !== "assistant") return message;
|
|
930
|
+
const assistant = message as ThreadAssistantMessage;
|
|
931
|
+
const { content } = mapToolCallPartsDeep(assistant.content, (part) => {
|
|
932
|
+
if (part.toolCallId !== options.toolCallId) return part;
|
|
933
|
+
recorded = true;
|
|
934
|
+
return {
|
|
935
|
+
...part,
|
|
936
|
+
unstable_interactions: appendToolInteraction(
|
|
937
|
+
part.unstable_interactions,
|
|
938
|
+
options.interaction,
|
|
939
|
+
),
|
|
940
|
+
};
|
|
941
|
+
});
|
|
942
|
+
return recorded ? { ...assistant, content } : assistant;
|
|
943
|
+
});
|
|
944
|
+
if (!recorded || !updated) {
|
|
945
|
+
throw new Error(
|
|
946
|
+
`[agui] recordToolInteraction: tool call "${options.toolCallId}" was not found on message "${options.messageId}"`,
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
this.notifyUpdate();
|
|
951
|
+
|
|
952
|
+
const message = this.session.tryGetMessage(sessionMessageId)?.message;
|
|
953
|
+
const history = this.history;
|
|
954
|
+
if (
|
|
955
|
+
!message ||
|
|
956
|
+
!history?.update ||
|
|
957
|
+
!this.isPersistableStatus(message.status)
|
|
958
|
+
) {
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
await this.chainHistoryWrite(sessionMessageId, () =>
|
|
963
|
+
history.update!({ parentId: item.parentId, message }),
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
|
|
890
967
|
sendA2uiAction(action: Record<string, unknown>): void {
|
|
891
968
|
this.assertNoPendingInterrupts();
|
|
892
969
|
this.maybeAutoCancelPendingToolCalls();
|
|
@@ -973,6 +1050,17 @@ export class AgUiThreadRuntimeCore {
|
|
|
973
1050
|
}
|
|
974
1051
|
|
|
975
1052
|
applyExternalMessages(messages: readonly ThreadMessage[]): void {
|
|
1053
|
+
messages = messages.map((message) => {
|
|
1054
|
+
if (message.role === "system" || message.metadata.modality !== undefined)
|
|
1055
|
+
return message;
|
|
1056
|
+
const modality = this.session.tryGetMessage(message.id)?.message.metadata
|
|
1057
|
+
.modality;
|
|
1058
|
+
if (modality === undefined) return message;
|
|
1059
|
+
return {
|
|
1060
|
+
...message,
|
|
1061
|
+
metadata: { ...message.metadata, modality },
|
|
1062
|
+
} as ThreadMessage;
|
|
1063
|
+
});
|
|
976
1064
|
this.pendingA2uiResumeOwner = null;
|
|
977
1065
|
this.pendingA2uiAction = undefined;
|
|
978
1066
|
this.assistantHistoryParents.clear();
|
|
@@ -1053,6 +1141,31 @@ export class AgUiThreadRuntimeCore {
|
|
|
1053
1141
|
this.notifyUpdate();
|
|
1054
1142
|
}
|
|
1055
1143
|
|
|
1144
|
+
resetThreadState(): void {
|
|
1145
|
+
const controller = this.abortController;
|
|
1146
|
+
const activeRunAgent = this.activeRunAgent;
|
|
1147
|
+
|
|
1148
|
+
this.stateSnapshot = undefined;
|
|
1149
|
+
this.pendingResume = null;
|
|
1150
|
+
this.pendingA2uiResumeOwner = null;
|
|
1151
|
+
this.pendingA2uiAction = undefined;
|
|
1152
|
+
|
|
1153
|
+
if (controller) {
|
|
1154
|
+
this.abortController = null;
|
|
1155
|
+
this.activeRunAgent = null;
|
|
1156
|
+
this.setRunning(false);
|
|
1157
|
+
try {
|
|
1158
|
+
(activeRunAgent ?? this.agent).abortRun();
|
|
1159
|
+
} catch (error) {
|
|
1160
|
+
this.logger.error?.("[agui] agent abortRun failed", error);
|
|
1161
|
+
} finally {
|
|
1162
|
+
controller.abort();
|
|
1163
|
+
}
|
|
1164
|
+
} else {
|
|
1165
|
+
this.notifyUpdate();
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1056
1169
|
private async startRun(
|
|
1057
1170
|
parentId: string | null,
|
|
1058
1171
|
runConfig?: RunConfig,
|
|
@@ -1171,6 +1284,8 @@ export class AgUiThreadRuntimeCore {
|
|
|
1171
1284
|
}
|
|
1172
1285
|
};
|
|
1173
1286
|
|
|
1287
|
+
const abortController = new AbortController();
|
|
1288
|
+
const abortSignal = abortController.signal;
|
|
1174
1289
|
const aggregator = new RunAggregator({
|
|
1175
1290
|
showThinking: this.showThinking,
|
|
1176
1291
|
logger: this.logger,
|
|
@@ -1180,11 +1295,18 @@ export class AgUiThreadRuntimeCore {
|
|
|
1180
1295
|
},
|
|
1181
1296
|
onTextMessageStart: (serverId) => adoptServerMessageId(serverId, true),
|
|
1182
1297
|
});
|
|
1183
|
-
const dispatch = (event: AgUiEvent) =>
|
|
1184
|
-
this.
|
|
1298
|
+
const dispatch = (event: AgUiEvent) => {
|
|
1299
|
+
if (this.abortController !== abortController) return;
|
|
1300
|
+
const nextAssistantMessageId = this.handleEvent(
|
|
1301
|
+
aggregator,
|
|
1302
|
+
event,
|
|
1303
|
+
assistantMessageId,
|
|
1304
|
+
);
|
|
1305
|
+
if (event.type === "MESSAGES_SNAPSHOT") {
|
|
1306
|
+
assistantMessageId = nextAssistantMessageId;
|
|
1307
|
+
}
|
|
1308
|
+
};
|
|
1185
1309
|
|
|
1186
|
-
const abortController = new AbortController();
|
|
1187
|
-
const abortSignal = abortController.signal;
|
|
1188
1310
|
this.abortController = abortController;
|
|
1189
1311
|
const runAgentInstance = this.agent;
|
|
1190
1312
|
this.activeRunAgent = runAgentInstance;
|
|
@@ -1585,26 +1707,57 @@ export class AgUiThreadRuntimeCore {
|
|
|
1585
1707
|
previous: ThreadAssistantMessage["content"],
|
|
1586
1708
|
next: ThreadAssistantMessage["content"],
|
|
1587
1709
|
): ThreadAssistantMessage["content"] {
|
|
1588
|
-
const
|
|
1710
|
+
const preserved = new Map<string, ToolCallMessagePart>();
|
|
1589
1711
|
for (const part of iterateToolCallParts(previous)) {
|
|
1590
1712
|
if (isResolvedToolCall(part)) {
|
|
1591
|
-
|
|
1713
|
+
preserved.set(part.toolCallId, part);
|
|
1592
1714
|
}
|
|
1593
1715
|
}
|
|
1594
|
-
if (
|
|
1716
|
+
if (preserved.size === 0) {
|
|
1717
|
+
return this.preserveToolInteractions(previous, next);
|
|
1718
|
+
}
|
|
1595
1719
|
|
|
1596
1720
|
const { content: merged, changed } = mapToolCallPartsDeep(next, (part) => {
|
|
1597
|
-
|
|
1598
|
-
const prior = resolved.get(part.toolCallId);
|
|
1721
|
+
const prior = preserved.get(part.toolCallId);
|
|
1599
1722
|
if (!prior) return part;
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1723
|
+
if (!isResolvedToolCall(part) && isResolvedToolCall(prior)) {
|
|
1724
|
+
return {
|
|
1725
|
+
...part,
|
|
1726
|
+
result: prior.result,
|
|
1727
|
+
...(prior.artifact !== undefined ? { artifact: prior.artifact } : {}),
|
|
1728
|
+
...(prior.isError !== undefined ? { isError: prior.isError } : {}),
|
|
1729
|
+
...(prior.modelContent !== undefined
|
|
1730
|
+
? { modelContent: prior.modelContent }
|
|
1731
|
+
: {}),
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
return part;
|
|
1735
|
+
});
|
|
1736
|
+
return this.preserveToolInteractions(previous, changed ? merged : next);
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
private preserveToolInteractions(
|
|
1740
|
+
previous: ThreadAssistantMessage["content"],
|
|
1741
|
+
next: ThreadAssistantMessage["content"],
|
|
1742
|
+
): ThreadAssistantMessage["content"] {
|
|
1743
|
+
const interactions = new Map<
|
|
1744
|
+
string,
|
|
1745
|
+
NonNullable<ToolCallMessagePart["unstable_interactions"]>
|
|
1746
|
+
>();
|
|
1747
|
+
for (const part of iterateToolCallParts(previous)) {
|
|
1748
|
+
if (part.unstable_interactions !== undefined) {
|
|
1749
|
+
interactions.set(part.toolCallId, part.unstable_interactions);
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
if (interactions.size === 0) return next;
|
|
1753
|
+
|
|
1754
|
+
const { content, changed } = mapToolCallPartsDeep(next, (part) => {
|
|
1755
|
+
const interaction = interactions.get(part.toolCallId);
|
|
1756
|
+
if (interaction === undefined || part.unstable_interactions !== undefined)
|
|
1757
|
+
return part;
|
|
1758
|
+
return { ...part, unstable_interactions: interaction };
|
|
1606
1759
|
});
|
|
1607
|
-
return changed ?
|
|
1760
|
+
return changed ? content : next;
|
|
1608
1761
|
}
|
|
1609
1762
|
|
|
1610
1763
|
private mergeAssistantMetadata(
|
|
@@ -1640,15 +1793,15 @@ export class AgUiThreadRuntimeCore {
|
|
|
1640
1793
|
aggregator: RunAggregator,
|
|
1641
1794
|
event: AgUiEvent,
|
|
1642
1795
|
activeAssistantId: string | undefined,
|
|
1643
|
-
) {
|
|
1796
|
+
): string | undefined {
|
|
1644
1797
|
switch (event.type) {
|
|
1645
1798
|
case "STATE_SNAPSHOT": {
|
|
1646
1799
|
this.stateSnapshot = event.snapshot as ReadonlyJSONValue;
|
|
1647
|
-
this.
|
|
1648
|
-
|
|
1800
|
+
this.updateActiveAssistantState(activeAssistantId);
|
|
1801
|
+
break;
|
|
1649
1802
|
}
|
|
1650
1803
|
case "STATE_DELTA": {
|
|
1651
|
-
if (event.delta.length === 0)
|
|
1804
|
+
if (event.delta.length === 0) break;
|
|
1652
1805
|
try {
|
|
1653
1806
|
const state = this.stateSnapshot ?? {};
|
|
1654
1807
|
const result = jsonpatch.applyPatch(
|
|
@@ -1658,26 +1811,44 @@ export class AgUiThreadRuntimeCore {
|
|
|
1658
1811
|
/* mutateDocument */ false,
|
|
1659
1812
|
);
|
|
1660
1813
|
this.stateSnapshot = result.newDocument as ReadonlyJSONValue;
|
|
1661
|
-
this.
|
|
1814
|
+
this.updateActiveAssistantState(activeAssistantId);
|
|
1662
1815
|
} catch (error) {
|
|
1663
1816
|
this.logger.error?.("[agui] failed to apply state delta", error);
|
|
1664
1817
|
}
|
|
1665
|
-
|
|
1818
|
+
break;
|
|
1666
1819
|
}
|
|
1667
1820
|
case "MESSAGES_SNAPSHOT": {
|
|
1821
|
+
const previousHeadId = this.session.headId;
|
|
1822
|
+
const activeAssistant =
|
|
1823
|
+
activeAssistantId === undefined
|
|
1824
|
+
? undefined
|
|
1825
|
+
: this.session.tryGetMessage(activeAssistantId)?.message;
|
|
1826
|
+
const hasActiveText =
|
|
1827
|
+
activeAssistant?.role === "assistant" &&
|
|
1828
|
+
activeAssistant.content.some((part) => part.type === "text");
|
|
1668
1829
|
this.importMessagesSnapshot(event.messages, activeAssistantId);
|
|
1669
|
-
|
|
1830
|
+
const headId = this.session.headId;
|
|
1831
|
+
if (hasActiveText && headId !== previousHeadId) {
|
|
1832
|
+
const head =
|
|
1833
|
+
headId === null
|
|
1834
|
+
? undefined
|
|
1835
|
+
: this.session.tryGetMessage(headId)?.message;
|
|
1836
|
+
activeAssistantId =
|
|
1837
|
+
head?.role === "assistant" ? (headId ?? undefined) : undefined;
|
|
1838
|
+
}
|
|
1839
|
+
this.updateActiveAssistantState(activeAssistantId);
|
|
1840
|
+
break;
|
|
1670
1841
|
}
|
|
1671
1842
|
case "TOOL_CALL_RESULT": {
|
|
1672
1843
|
if (!aggregator.hasToolCall(event.toolCallId)) {
|
|
1673
1844
|
const messageId = this.findMessageIdForToolCall(event.toolCallId);
|
|
1674
1845
|
if (messageId !== undefined) {
|
|
1675
1846
|
this.applyCrossRunToolResult(messageId, event);
|
|
1676
|
-
|
|
1847
|
+
break;
|
|
1677
1848
|
}
|
|
1678
1849
|
}
|
|
1679
1850
|
aggregator.handle(event);
|
|
1680
|
-
|
|
1851
|
+
break;
|
|
1681
1852
|
}
|
|
1682
1853
|
case "ACTIVITY_SNAPSHOT": {
|
|
1683
1854
|
const toolCallId = event.content["toolCallId"];
|
|
@@ -1689,15 +1860,41 @@ export class AgUiThreadRuntimeCore {
|
|
|
1689
1860
|
const messageId = this.findMessageIdForToolCall(toolCallId);
|
|
1690
1861
|
if (messageId !== undefined) {
|
|
1691
1862
|
this.applyCrossRunActivitySnapshot(messageId, toolCallId, event);
|
|
1692
|
-
|
|
1863
|
+
break;
|
|
1693
1864
|
}
|
|
1694
1865
|
}
|
|
1695
1866
|
aggregator.handle(event);
|
|
1696
|
-
|
|
1867
|
+
break;
|
|
1697
1868
|
}
|
|
1698
1869
|
default:
|
|
1699
1870
|
aggregator.handle(event);
|
|
1700
1871
|
}
|
|
1872
|
+
return activeAssistantId;
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
private updateActiveAssistantState(messageId: string | undefined): void {
|
|
1876
|
+
const current =
|
|
1877
|
+
messageId === undefined
|
|
1878
|
+
? undefined
|
|
1879
|
+
: this.session.tryGetMessage(messageId)?.message;
|
|
1880
|
+
const activeAssistantId =
|
|
1881
|
+
current?.role === "assistant" && this.session.headId === messageId
|
|
1882
|
+
? messageId
|
|
1883
|
+
: this.session.headId;
|
|
1884
|
+
if (activeAssistantId !== null && activeAssistantId !== undefined) {
|
|
1885
|
+
this.session.updateMessage(activeAssistantId, (message) => {
|
|
1886
|
+
if (message.role !== "assistant") return message;
|
|
1887
|
+
const assistant = message as ThreadAssistantMessage;
|
|
1888
|
+
return {
|
|
1889
|
+
...assistant,
|
|
1890
|
+
metadata: {
|
|
1891
|
+
...assistant.metadata,
|
|
1892
|
+
unstable_state: this.stateSnapshot ?? null,
|
|
1893
|
+
},
|
|
1894
|
+
};
|
|
1895
|
+
});
|
|
1896
|
+
}
|
|
1897
|
+
this.notifyUpdate();
|
|
1701
1898
|
}
|
|
1702
1899
|
|
|
1703
1900
|
private applyCrossRunToolResult(
|
|
@@ -1851,9 +2048,28 @@ export class AgUiThreadRuntimeCore {
|
|
|
1851
2048
|
const converted: ThreadMessage[] = [];
|
|
1852
2049
|
for (const message of normalized) {
|
|
1853
2050
|
try {
|
|
1854
|
-
|
|
1855
|
-
|
|
2051
|
+
const convertedMessage = fromThreadMessageLike(
|
|
2052
|
+
message,
|
|
2053
|
+
generateId(),
|
|
2054
|
+
FALLBACK_USER_STATUS,
|
|
1856
2055
|
);
|
|
2056
|
+
const existing = this.session.tryGetMessage(
|
|
2057
|
+
convertedMessage.id,
|
|
2058
|
+
)?.message;
|
|
2059
|
+
if (
|
|
2060
|
+
convertedMessage.role === "assistant" &&
|
|
2061
|
+
existing?.role === "assistant"
|
|
2062
|
+
) {
|
|
2063
|
+
converted.push({
|
|
2064
|
+
...convertedMessage,
|
|
2065
|
+
content: this.preserveToolInteractions(
|
|
2066
|
+
existing.content,
|
|
2067
|
+
convertedMessage.content,
|
|
2068
|
+
),
|
|
2069
|
+
});
|
|
2070
|
+
} else {
|
|
2071
|
+
converted.push(convertedMessage);
|
|
2072
|
+
}
|
|
1857
2073
|
} catch (error) {
|
|
1858
2074
|
this.logger.error?.(
|
|
1859
2075
|
"[agui] failed to import message from snapshot",
|
|
@@ -276,3 +276,59 @@ describe("toAgUiMessages content metadata", () => {
|
|
|
276
276
|
expect(toAgUiMessages(rebuilt as never)).toEqual(sent);
|
|
277
277
|
});
|
|
278
278
|
});
|
|
279
|
+
|
|
280
|
+
describe("toAgUiMessages percent-encoded data URLs", () => {
|
|
281
|
+
it("sends a data URL that is not base64 as a url source", () => {
|
|
282
|
+
expect(
|
|
283
|
+
contentOf(
|
|
284
|
+
userMessage([
|
|
285
|
+
{ type: "image", image: "data:image/svg+xml,%3Csvg%2F%3E" },
|
|
286
|
+
{
|
|
287
|
+
type: "file",
|
|
288
|
+
data: "data:text/plain,hello",
|
|
289
|
+
mimeType: "text/plain",
|
|
290
|
+
filename: "f.txt",
|
|
291
|
+
},
|
|
292
|
+
]),
|
|
293
|
+
),
|
|
294
|
+
).toEqual([
|
|
295
|
+
{
|
|
296
|
+
type: "image",
|
|
297
|
+
source: { type: "url", value: "data:image/svg+xml,%3Csvg%2F%3E" },
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
type: "document",
|
|
301
|
+
source: {
|
|
302
|
+
type: "url",
|
|
303
|
+
value: "data:text/plain,hello",
|
|
304
|
+
mimeType: "text/plain",
|
|
305
|
+
},
|
|
306
|
+
metadata: { filename: "f.txt" },
|
|
307
|
+
},
|
|
308
|
+
]);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("keeps a data URL that is not base64 through a snapshot round trip", () => {
|
|
312
|
+
const sent = toAgUiMessages([
|
|
313
|
+
userMessage([
|
|
314
|
+
{
|
|
315
|
+
type: "file",
|
|
316
|
+
data: "data:text/plain,hello",
|
|
317
|
+
mimeType: "text/plain",
|
|
318
|
+
filename: "f.txt",
|
|
319
|
+
},
|
|
320
|
+
]),
|
|
321
|
+
]);
|
|
322
|
+
|
|
323
|
+
const rebuilt = fromAgUiMessages(sent as never);
|
|
324
|
+
const attachment = (rebuilt[0] as unknown as { attachments: unknown[] })
|
|
325
|
+
.attachments[0] as { content: unknown[] };
|
|
326
|
+
|
|
327
|
+
expect(attachment.content[0]).toMatchObject({
|
|
328
|
+
type: "file",
|
|
329
|
+
data: "data:text/plain,hello",
|
|
330
|
+
mimeType: "text/plain",
|
|
331
|
+
});
|
|
332
|
+
expect(toAgUiMessages(rebuilt as never)).toEqual(sent);
|
|
333
|
+
});
|
|
334
|
+
});
|