@assistant-ui/react-ag-ui 0.0.70 → 0.0.71
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/runtime/AgUiThreadRuntimeCore.d.ts +2 -0
- package/dist/runtime/AgUiThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtime/AgUiThreadRuntimeCore.js +57 -4
- package/dist/runtime/AgUiThreadRuntimeCore.js.map +1 -1
- package/dist/runtime/adapter/run-aggregator.d.ts +3 -0
- package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
- package/dist/runtime/adapter/run-aggregator.js +3 -1
- package/dist/runtime/adapter/run-aggregator.js.map +1 -1
- package/dist/runtime/types.d.ts +2 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/types.js.map +1 -1
- package/dist/useAgUiRuntime.d.ts.map +1 -1
- package/dist/useAgUiRuntime.js +2 -0
- package/dist/useAgUiRuntime.js.map +1 -1
- package/package.json +5 -5
- package/src/runtime/AgUiThreadRuntimeCore.ts +118 -1
- package/src/runtime/adapter/run-aggregator.ts +5 -0
- package/src/runtime/types.ts +2 -0
- package/src/useAgUiRuntime.external-store.test.tsx +39 -0
- package/src/useAgUiRuntime.ts +2 -0
package/dist/useAgUiRuntime.js
CHANGED
|
@@ -22,6 +22,7 @@ function useAgUiRuntime(options) {
|
|
|
22
22
|
agent: options.agent,
|
|
23
23
|
logger,
|
|
24
24
|
showThinking: options.showThinking ?? true,
|
|
25
|
+
joinStrategy: options.joinStrategy,
|
|
25
26
|
getRunContext: options.getRunContext,
|
|
26
27
|
resumeTranscript: options.resumeTranscript,
|
|
27
28
|
autoCancelPendingToolCalls: options.autoCancelPendingToolCalls,
|
|
@@ -37,6 +38,7 @@ function useAgUiRuntime(options) {
|
|
|
37
38
|
agent: options.agent,
|
|
38
39
|
logger,
|
|
39
40
|
showThinking: options.showThinking ?? true,
|
|
41
|
+
joinStrategy: options.joinStrategy,
|
|
40
42
|
getRunContext: options.getRunContext,
|
|
41
43
|
resumeTranscript: options.resumeTranscript,
|
|
42
44
|
autoCancelPendingToolCalls: options.autoCancelPendingToolCalls,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAgUiRuntime.js","names":[],"sources":["../src/useAgUiRuntime.ts"],"sourcesContent":["\"use client\";\n\nimport {\n useEffect,\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 {\n useIsomorphicLayoutEffect,\n useReplaySafeEffect,\n} from \"@assistant-ui/store/internal\";\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 coreRef = useRef<AgUiThreadRuntimeCore | null>(null);\n const threadSwitchGenerationRef = useRef(0);\n const switchingGenerationRef = useRef<number | 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 getRunContext: options.getRunContext,\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 isThreadSwitching: () => switchingGenerationRef.current !== null,\n });\n }\n\n const core = coreRef.current;\n const snapshot = useSyncExternalStore(\n core.subscribe,\n core.getSnapshot,\n core.getSnapshot,\n );\n useEffect(() => {\n core.updateOptions({\n agent: options.agent,\n logger,\n showThinking: options.showThinking ?? true,\n getRunContext: options.getRunContext,\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 = snapshot.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 const lastQueueEdgeRef = useRef<{\n controller: MessageQueueController;\n busy: boolean;\n } | null>(null);\n if (options.unstable_enableMessageQueue && !queueRef.current) {\n queueRef.current = createMessageQueue({\n run: (message) => {\n const controller = queueRef.current;\n // A send dispatched mid-switch is dropped, and the queue released, or\n // it would wait for an idle edge that never comes.\n if (switchingGenerationRef.current !== null) {\n controller?.notifyIdle();\n return;\n }\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 useIsomorphicLayoutEffect(() => {\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 (options.isSendDisabled === true) {\n queueController?.hold();\n } else {\n queueController?.release();\n }\n }, [options.isSendDisabled, queueController]);\n\n useEffect(() => {\n if (!queueController) return;\n if (\n lastQueueEdgeRef.current?.controller === queueController &&\n lastQueueEdgeRef.current.busy === queueBusy\n )\n return;\n lastQueueEdgeRef.current = { controller: queueController, busy: queueBusy };\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 const abandonPreviousThread = () => {\n queueRef.current?.notifyCancelled();\n if (core.isRunning()) {\n void core.cancel().catch((error: unknown) => {\n logger.error?.(\n \"[agui] cancelling the run on thread switch failed\",\n error,\n );\n });\n }\n queueRef.current?.clear();\n };\n\n const releaseSwitch = (generation: number) => {\n if (switchingGenerationRef.current === generation) {\n switchingGenerationRef.current = null;\n }\n };\n\n return {\n ...rest,\n onSwitchToNewThread: onSwitchToNewThread\n ? async () => {\n const generation = ++threadSwitchGenerationRef.current;\n switchingGenerationRef.current = generation;\n try {\n abandonPreviousThread();\n if (generation !== threadSwitchGenerationRef.current) return;\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 abandonPreviousThread();\n if (generation !== threadSwitchGenerationRef.current) return;\n core.applyExternalMessages([]);\n core.resetThreadState();\n } finally {\n releaseSwitch(generation);\n }\n }\n : undefined,\n onSwitchToThread: onSwitchToThread\n ? async (threadId: string) => {\n const generation = ++threadSwitchGenerationRef.current;\n switchingGenerationRef.current = generation;\n try {\n abandonPreviousThread();\n if (generation !== threadSwitchGenerationRef.current) return;\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 abandonPreviousThread();\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 (generation !== threadSwitchGenerationRef.current) return;\n releaseSwitch(generation);\n if (result.unstable_resume) {\n void core.resumeInFlightRun(result.messages);\n }\n } finally {\n releaseSwitch(generation);\n }\n }\n : undefined,\n };\n }, [threadListAdapter, core, logger]);\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 return {\n ...shared,\n isLoading: snapshot.isLoading,\n messageRepository: snapshot.messageRepository,\n state: snapshot.state,\n isRunning: snapshot.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: snapshot.state,\n setState: (next) => core.setState(next),\n }),\n unstable_enableToolInvocations: true,\n unstable_persistsHistory: true,\n setToolStatuses,\n onNew: (message: AppendMessage) =>\n switchingGenerationRef.current !== null\n ? Promise.resolve()\n : 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 // toolInvocations intentionally excluded: abort/resume use refs internally and work with stale captures\n [\n adapterAdapters,\n core,\n isRunning,\n queueController,\n queueItems,\n steerQueueItems,\n shared,\n snapshot,\n ],\n );\n\n const baseRuntime = useExternalStoreRuntime(store);\n\n const createRuntime = (): AgUiAssistantRuntime => {\n return {\n threads: baseRuntime.threads,\n get thread() {\n return baseRuntime.thread;\n },\n registerModelContextProvider: (provider) =>\n baseRuntime.registerModelContextProvider(provider),\n unstable_getPendingInterrupts: () =>\n core.getPendingInterrupts()?.interrupts ?? [],\n unstable_submitInterruptResponses: (responses) =>\n core.submitInterruptResponses(responses),\n };\n };\n const [pinnedRuntime, setPinnedRuntime] = useState(() => ({\n baseRuntime,\n runtime: createRuntime(),\n }));\n let currentRuntime = pinnedRuntime;\n if (pinnedRuntime.baseRuntime !== baseRuntime) {\n currentRuntime = { baseRuntime, runtime: createRuntime() };\n setPinnedRuntime(currentRuntime);\n }\n const runtime = currentRuntime.runtime;\n\n useReplaySafeEffect(() => {\n core.attachRuntime(runtime);\n return () => {\n core.detachRuntime();\n };\n }, [core, runtime]);\n\n useEffect(() => {\n core.__internal_load();\n }, [core]);\n\n return runtime;\n}\n"],"mappings":";;;;;;;;;AAwCA,MAAM,mBAA6C,CAAC;AACpD,MAAM,oBAA+C,OAAO,OAAO,CAAC,CAAC;AACrE,MAAM,4BAA4B,CAAC;AAkBnC,SAAgB,eACd,SACsB;CACtB,MAAM,SAAS,cAAc,WAAW,QAAQ,MAAM,GAAG,CAAC,QAAQ,MAAM,CAAC;CACzE,MAAM,UAAU,OAAqC,IAAI;CACzD,MAAM,4BAA4B,OAAO,CAAC;CAC1C,MAAM,yBAAyB,OAAsB,IAAI;CACzD,MAAM,kBAAkB,mBAAmB;CAE3C,MAAM,iBAAiB,QAAQ,UAAU,WAAW,iBAAiB;CACrE,MAAM,oBAAoB,QAAQ,UAAU;CAE5C,IAAI,CAAC,QAAQ,SACX,QAAQ,UAAU,IAAI,sBAAsB;EAC1C,OAAO,QAAQ;EACf;EACA,cAAc,QAAQ,gBAAgB;EACtC,eAAe,QAAQ;EACvB,kBAAkB,QAAQ;EAC1B,4BAA4B,QAAQ;EACpC,GAAI,QAAQ,WAAW,EAAE,SAAS,QAAQ,QAAQ;EAClD,GAAI,QAAQ,YAAY,EAAE,UAAU,QAAQ,SAAS;EACrD,GAAI,kBAAkB,EAAE,SAAS,eAAe;EAChD,yBAAyB,uBAAuB,YAAY;CAC9D,CAAC;CAGH,MAAM,OAAO,QAAQ;CACrB,MAAM,WAAW,qBACf,KAAK,WACL,KAAK,aACL,KAAK,WACP;CACA,gBAAgB;EACd,KAAK,cAAc;GACjB,OAAO,QAAQ;GACf;GACA,cAAc,QAAQ,gBAAgB;GACtC,eAAe,QAAQ;GACvB,kBAAkB,QAAQ;GAC1B,4BAA4B,QAAQ;GACpC,GAAI,QAAQ,WAAW,EAAE,SAAS,QAAQ,QAAQ;GAClD,GAAI,QAAQ,YAAY,EAAE,UAAU,QAAQ,SAAS;GACrD,GAAI,kBAAkB,EAAE,SAAS,eAAe;EAClD,CAAC;CACH,CAAC;CAED,MAAM,CAAC,cAAc,mBAAmB,SAEtC,CAAC,CAAC;CAEJ,MAAM,oBAAoB,OAAO,OAAO,YAAY,CAAC,CAAC,MACnD,MAAM,GAAG,SAAS,WACrB;CACA,MAAM,YAAY,SAAS,aAAa;CAIxC,MAAM,WAAW,OAAsC,IAAI;CAI3D,MAAM,eAAe,OAAO,CAAC;CAC7B,MAAM,mBAAmB,OAGf,IAAI;CACd,IAAI,QAAQ,+BAA+B,CAAC,SAAS,SACnD,SAAS,UAAU,mBAAmB,EACpC,MAAM,YAAY;EAChB,MAAM,aAAa,SAAS;EAG5B,IAAI,uBAAuB,YAAY,MAAM;GAC3C,YAAY,WAAW;GACvB;EACF;EACA,MAAM,kBAAkB,aAAa;EAQrC,MAAM,uBAAuB;GAC3B,IACE,SAAS,YAAY,cACrB,aAAa,YAAY,mBACzB,KAAK,qBAAqB,MAAM,MAEhC;GACF,YAAY,WAAW;EACzB;EACA,KAAU,OAAO,OAAO,CAAC,CAAC,KAAK,iBAAiB,MAAe;GAC7D,eAAe;GACf,OAAO,QAAQ,wCAAwC,CAAC;EAC1D,CAAC;CACH,EACF,CAAC;CAEH,MAAM,kBAAkB,QAAQ,8BAC5B,SAAS,UACT;CACJ,gCAAgC;EAC9B,IAAI,QAAQ,+BAA+B,CAAC,SAAS,SAAS;EAC9D,MAAM,aAAa,SAAS;EAC5B,SAAS,UAAU;EACnB,WAAW,MAAM;CACnB,GAAG,CAAC,QAAQ,2BAA2B,CAAC;CAKxC,MAAM,aAAa,qBACjB,iBAAiB,aAAa,qBACxB,iBAAiB,QAAQ,SAAS,yBAClC,iBACR;CACA,MAAM,kBAAkB,qBACtB,iBAAiB,aAAa,qBACxB,iBAAiB,QAAQ,cAAc,yBACvC,iBACR;CAMA,MAAM,YAAY,aAAa,KAAK,qBAAqB,MAAM;CAC/D,gBAAgB;EACd,IAAI,QAAQ,mBAAmB,MAC7B,iBAAiB,KAAK;OAEtB,iBAAiB,QAAQ;CAE7B,GAAG,CAAC,QAAQ,gBAAgB,eAAe,CAAC;CAE5C,gBAAgB;EACd,IAAI,CAAC,iBAAiB;EACtB,IACE,iBAAiB,SAAS,eAAe,mBACzC,iBAAiB,QAAQ,SAAS,WAElC;EACF,iBAAiB,UAAU;GAAE,YAAY;GAAiB,MAAM;EAAU;EAC1E,IAAI,WAAW;GACb,aAAa;GACb,gBAAgB,WAAW;EAC7B,OACE,gBAAgB,WAAW;CAE/B,GAAG,CAAC,WAAW,eAAe,CAAC;CAE/B,MAAM,aAAa,cAAc;EAC/B,IAAI,CAAC,mBAAmB,OAAO,KAAA;EAE/B,MAAM,EAAE,qBAAqB,kBAAkB,GAAG,SAChD;EAEF,MAAM,8BAA8B;GAClC,SAAS,SAAS,gBAAgB;GAClC,IAAI,KAAK,UAAU,GACjB,KAAU,OAAO,CAAC,CAAC,OAAO,UAAmB;IAC3C,OAAO,QACL,qDACA,KACF;GACF,CAAC;GAEH,SAAS,SAAS,MAAM;EAC1B;EAEA,MAAM,iBAAiB,eAAuB;GAC5C,IAAI,uBAAuB,YAAY,YACrC,uBAAuB,UAAU;EAErC;EAEA,OAAO;GACL,GAAG;GACH,qBAAqB,sBACjB,YAAY;IACV,MAAM,aAAa,EAAE,0BAA0B;IAC/C,uBAAuB,UAAU;IACjC,IAAI;KACF,sBAAsB;KACtB,IAAI,eAAe,0BAA0B,SAAS;KAGtD,KAAK,sBAAsB,CAAC,CAAC;KAC7B,KAAK,iBAAiB;KACtB,MAAM,oBAAoB;KAC1B,IAAI,eAAe,0BAA0B,SAAS;KACtD,sBAAsB;KACtB,IAAI,eAAe,0BAA0B,SAAS;KACtD,KAAK,sBAAsB,CAAC,CAAC;KAC7B,KAAK,iBAAiB;IACxB,UAAU;KACR,cAAc,UAAU;IAC1B;GACF,IACA,KAAA;GACJ,kBAAkB,mBACd,OAAO,aAAqB;IAC1B,MAAM,aAAa,EAAE,0BAA0B;IAC/C,uBAAuB,UAAU;IACjC,IAAI;KACF,sBAAsB;KACtB,IAAI,eAAe,0BAA0B,SAAS;KAGtD,KAAK,sBAAsB,CAAC,CAAC;KAC7B,KAAK,iBAAiB;KACtB,MAAM,SAAS,MAAM,iBAAiB,QAAQ;KAC9C,IAAI,eAAe,0BAA0B,SAAS;KACtD,sBAAsB;KACtB,IAAI,eAAe,0BAA0B,SAAS;KACtD,KAAK,sBAAsB,CAAC,CAAC;KAC7B,KAAK,iBAAiB;KACtB,KAAK,sBAAsB,OAAO,QAAQ;KAC1C,IAAI,OAAO,UAAU,KAAA,GACnB,KAAK,kBAAkB,OAAO,KAAK;KAErC,IAAI,eAAe,0BAA0B,SAAS;KACtD,cAAc,UAAU;KACxB,IAAI,OAAO,iBACT,KAAU,kBAAkB,OAAO,QAAQ;IAE/C,UAAU;KACR,cAAc,UAAU;IAC1B;GACF,IACA,KAAA;EACN;CACF,GAAG;EAAC;EAAmB;EAAM;CAAM,CAAC;CAEpC,MAAM,WAAW,QAAQ;CACzB,MAAM,kBAAkB,eACf;EACL,aAAa,UAAU,eAAe,iBAAiB;EACvD,QAAQ,UAAU;EAClB,WAAW,UAAU;EACrB,OAAO,UAAU;EACjB,UAAU,UAAU;EACpB;CACF,IACA;EAAC;EAAU;EAAiB;CAAU,CACxC;CAEA,MAAM,SAAS,8BAA8B,OAAO;CACpD,MAAM,QAAQ,cACN;EACJ,OAAO;GACL,GAAG;GACH,WAAW,SAAS;GACpB,mBAAmB,SAAS;GAC5B,OAAO,SAAS;GAChB,WAAW,SAAS;GACpB,QAAQ,WAAW,QAAQ;IACzB,YACE,KAAK,qBAAqB,CAAC,EAAE,cAAc;IAC7C,iBAAiB,WAAW,KAAK,eAAe,MAAM;IACtD,2BAA2B,cACzB,KAAK,yBAAyB,SAAS;IACzC,YAAY,SAAS,cAAc,KAAK,UAAU,SAAS,SAAS;IACpE,OAAO,SAAS;IAChB,WAAW,SAAS,KAAK,SAAS,IAAI;GACxC,CAAC;GACD,gCAAgC;GAChC,0BAA0B;GAC1B;GACA,QAAQ,YACN,uBAAuB,YAAY,OAC/B,QAAQ,QAAQ,IAChB,KAAK,OAAO,OAAO;GACzB,oBAAoB,YAAY,KAAK,sBAAsB,OAAO;GAClE,SAAS,YAA2B;IAClC,iBAAiB,MAAM;IACvB,OAAO,KAAK,KAAK,OAAO;GAC1B;GACA,WAAW,UAAyB,WAAgC;IAClE,iBAAiB,MAAM;IACvB,OAAO,KAAK,OAAO,UAAU,MAAM;GACrC;GACA,UAAU,YAAY;IACpB,iBAAiB,gBAAgB;IACjC,KAAK,OAAO;GACd;GACA,kBAAkB,YAAY,KAAK,cAAc,OAAO;GACxD,mCAAmC,YACjC,KAAK,sBAAsB,OAAO;GACpC,0BAA0B,YACxB,KAAK,sBAAsB,OAAO,CAAC,CAAC,OAAO,UAAmB;IAC5D,KAAK,YAAY,KAAK;IACtB,MAAM;GACR,CAAC;GACH,WAAW,WAAW,KAAK,OAAO,MAAM;GACxC,cAAc,aACZ,KAAK,sBAAsB,QAAQ;GACrC,WAAW,aACT,KAAK,sBAAsB,QAAQ;GACrC,sBAAsB,UACpB,KAAK,kBAAkB,KAAK;GAC9B,UAAU;GACV,GAAI,mBAAmB,EAAE,OAAO,gBAAgB,QAAQ;EAC1D;CACF,GAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CACF;CAEA,MAAM,cAAc,wBAAwB,KAAK;CAEjD,MAAM,sBAA4C;EAChD,OAAO;GACL,SAAS,YAAY;GACrB,IAAI,SAAS;IACX,OAAO,YAAY;GACrB;GACA,+BAA+B,aAC7B,YAAY,6BAA6B,QAAQ;GACnD,qCACE,KAAK,qBAAqB,CAAC,EAAE,cAAc,CAAC;GAC9C,oCAAoC,cAClC,KAAK,yBAAyB,SAAS;EAC3C;CACF;CACA,MAAM,CAAC,eAAe,oBAAoB,gBAAgB;EACxD;EACA,SAAS,cAAc;CACzB,EAAE;CACF,IAAI,iBAAiB;CACrB,IAAI,cAAc,gBAAgB,aAAa;EAC7C,iBAAiB;GAAE;GAAa,SAAS,cAAc;EAAE;EACzD,iBAAiB,cAAc;CACjC;CACA,MAAM,UAAU,eAAe;CAE/B,0BAA0B;EACxB,KAAK,cAAc,OAAO;EAC1B,aAAa;GACX,KAAK,cAAc;EACrB;CACF,GAAG,CAAC,MAAM,OAAO,CAAC;CAElB,gBAAgB;EACd,KAAK,gBAAgB;CACvB,GAAG,CAAC,IAAI,CAAC;CAET,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"useAgUiRuntime.js","names":[],"sources":["../src/useAgUiRuntime.ts"],"sourcesContent":["\"use client\";\n\nimport {\n useEffect,\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 {\n useIsomorphicLayoutEffect,\n useReplaySafeEffect,\n} from \"@assistant-ui/store/internal\";\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 coreRef = useRef<AgUiThreadRuntimeCore | null>(null);\n const threadSwitchGenerationRef = useRef(0);\n const switchingGenerationRef = useRef<number | 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 joinStrategy: options.joinStrategy,\n getRunContext: options.getRunContext,\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 isThreadSwitching: () => switchingGenerationRef.current !== null,\n });\n }\n\n const core = coreRef.current;\n const snapshot = useSyncExternalStore(\n core.subscribe,\n core.getSnapshot,\n core.getSnapshot,\n );\n useEffect(() => {\n core.updateOptions({\n agent: options.agent,\n logger,\n showThinking: options.showThinking ?? true,\n joinStrategy: options.joinStrategy,\n getRunContext: options.getRunContext,\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 = snapshot.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 const lastQueueEdgeRef = useRef<{\n controller: MessageQueueController;\n busy: boolean;\n } | null>(null);\n if (options.unstable_enableMessageQueue && !queueRef.current) {\n queueRef.current = createMessageQueue({\n run: (message) => {\n const controller = queueRef.current;\n // A send dispatched mid-switch is dropped, and the queue released, or\n // it would wait for an idle edge that never comes.\n if (switchingGenerationRef.current !== null) {\n controller?.notifyIdle();\n return;\n }\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 useIsomorphicLayoutEffect(() => {\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 (options.isSendDisabled === true) {\n queueController?.hold();\n } else {\n queueController?.release();\n }\n }, [options.isSendDisabled, queueController]);\n\n useEffect(() => {\n if (!queueController) return;\n if (\n lastQueueEdgeRef.current?.controller === queueController &&\n lastQueueEdgeRef.current.busy === queueBusy\n )\n return;\n lastQueueEdgeRef.current = { controller: queueController, busy: queueBusy };\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 const abandonPreviousThread = () => {\n queueRef.current?.notifyCancelled();\n if (core.isRunning()) {\n void core.cancel().catch((error: unknown) => {\n logger.error?.(\n \"[agui] cancelling the run on thread switch failed\",\n error,\n );\n });\n }\n queueRef.current?.clear();\n };\n\n const releaseSwitch = (generation: number) => {\n if (switchingGenerationRef.current === generation) {\n switchingGenerationRef.current = null;\n }\n };\n\n return {\n ...rest,\n onSwitchToNewThread: onSwitchToNewThread\n ? async () => {\n const generation = ++threadSwitchGenerationRef.current;\n switchingGenerationRef.current = generation;\n try {\n abandonPreviousThread();\n if (generation !== threadSwitchGenerationRef.current) return;\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 abandonPreviousThread();\n if (generation !== threadSwitchGenerationRef.current) return;\n core.applyExternalMessages([]);\n core.resetThreadState();\n } finally {\n releaseSwitch(generation);\n }\n }\n : undefined,\n onSwitchToThread: onSwitchToThread\n ? async (threadId: string) => {\n const generation = ++threadSwitchGenerationRef.current;\n switchingGenerationRef.current = generation;\n try {\n abandonPreviousThread();\n if (generation !== threadSwitchGenerationRef.current) return;\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 abandonPreviousThread();\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 (generation !== threadSwitchGenerationRef.current) return;\n releaseSwitch(generation);\n if (result.unstable_resume) {\n void core.resumeInFlightRun(result.messages);\n }\n } finally {\n releaseSwitch(generation);\n }\n }\n : undefined,\n };\n }, [threadListAdapter, core, logger]);\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 return {\n ...shared,\n isLoading: snapshot.isLoading,\n messageRepository: snapshot.messageRepository,\n state: snapshot.state,\n isRunning: snapshot.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: snapshot.state,\n setState: (next) => core.setState(next),\n }),\n unstable_enableToolInvocations: true,\n unstable_persistsHistory: true,\n setToolStatuses,\n onNew: (message: AppendMessage) =>\n switchingGenerationRef.current !== null\n ? Promise.resolve()\n : 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 // toolInvocations intentionally excluded: abort/resume use refs internally and work with stale captures\n [\n adapterAdapters,\n core,\n isRunning,\n queueController,\n queueItems,\n steerQueueItems,\n shared,\n snapshot,\n ],\n );\n\n const baseRuntime = useExternalStoreRuntime(store);\n\n const createRuntime = (): AgUiAssistantRuntime => {\n return {\n threads: baseRuntime.threads,\n get thread() {\n return baseRuntime.thread;\n },\n registerModelContextProvider: (provider) =>\n baseRuntime.registerModelContextProvider(provider),\n unstable_getPendingInterrupts: () =>\n core.getPendingInterrupts()?.interrupts ?? [],\n unstable_submitInterruptResponses: (responses) =>\n core.submitInterruptResponses(responses),\n };\n };\n const [pinnedRuntime, setPinnedRuntime] = useState(() => ({\n baseRuntime,\n runtime: createRuntime(),\n }));\n let currentRuntime = pinnedRuntime;\n if (pinnedRuntime.baseRuntime !== baseRuntime) {\n currentRuntime = { baseRuntime, runtime: createRuntime() };\n setPinnedRuntime(currentRuntime);\n }\n const runtime = currentRuntime.runtime;\n\n useReplaySafeEffect(() => {\n core.attachRuntime(runtime);\n return () => {\n core.detachRuntime();\n };\n }, [core, runtime]);\n\n useEffect(() => {\n core.__internal_load();\n }, [core]);\n\n return runtime;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/react-ag-ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.71",
|
|
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/generative-ui": "^0.0.
|
|
35
|
-
"@assistant-ui/store": "^0.3.
|
|
36
|
-
"assistant-stream": "^0.3.
|
|
33
|
+
"@assistant-ui/core": "^0.3.30",
|
|
34
|
+
"@assistant-ui/generative-ui": "^0.0.6",
|
|
35
|
+
"@assistant-ui/store": "^0.3.22",
|
|
36
|
+
"assistant-stream": "^0.3.53",
|
|
37
37
|
"fast-json-patch": "^3.1.1"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
@@ -112,6 +112,7 @@ type CoreOptions = {
|
|
|
112
112
|
agent: AbstractAgent;
|
|
113
113
|
logger: Logger;
|
|
114
114
|
showThinking: boolean;
|
|
115
|
+
joinStrategy?: UseAgUiRuntimeOptions["joinStrategy"];
|
|
115
116
|
getRunContext?: UseAgUiRuntimeOptions["getRunContext"];
|
|
116
117
|
resumeTranscript?: AgUiResumeTranscript | undefined;
|
|
117
118
|
autoCancelPendingToolCalls?: boolean | undefined;
|
|
@@ -158,6 +159,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
158
159
|
private agent: AbstractAgent;
|
|
159
160
|
private logger: Logger;
|
|
160
161
|
private showThinking: boolean;
|
|
162
|
+
private joinStrategy: UseAgUiRuntimeOptions["joinStrategy"];
|
|
161
163
|
private getRunContext: UseAgUiRuntimeOptions["getRunContext"];
|
|
162
164
|
private resumeTranscript: AgUiResumeTranscript;
|
|
163
165
|
private autoCancelPendingToolCalls: boolean | undefined;
|
|
@@ -218,6 +220,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
218
220
|
this.agent = options.agent;
|
|
219
221
|
this.logger = options.logger;
|
|
220
222
|
this.showThinking = options.showThinking;
|
|
223
|
+
this.joinStrategy = options.joinStrategy;
|
|
221
224
|
this.getRunContext = options.getRunContext;
|
|
222
225
|
this.resumeTranscript = options.resumeTranscript ?? "full";
|
|
223
226
|
this.autoCancelPendingToolCalls = options.autoCancelPendingToolCalls;
|
|
@@ -249,6 +252,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
249
252
|
this.agent = options.agent;
|
|
250
253
|
this.logger = options.logger;
|
|
251
254
|
this.showThinking = options.showThinking;
|
|
255
|
+
this.joinStrategy = options.joinStrategy;
|
|
252
256
|
this.getRunContext = options.getRunContext;
|
|
253
257
|
this.resumeTranscript = options.resumeTranscript ?? "full";
|
|
254
258
|
this.autoCancelPendingToolCalls = options.autoCancelPendingToolCalls;
|
|
@@ -1297,6 +1301,8 @@ export class AgUiThreadRuntimeCore {
|
|
|
1297
1301
|
const runStartMessageIds = new Set(
|
|
1298
1302
|
this.getMessageRepository().messages.map(({ message }) => message.id),
|
|
1299
1303
|
);
|
|
1304
|
+
const joinStrategy = this.joinStrategy;
|
|
1305
|
+
const hasRunUserBoundary = parent?.message.role === "user" && !resume;
|
|
1300
1306
|
|
|
1301
1307
|
let pendingError: Error | null = null;
|
|
1302
1308
|
const assistantParentId = parent ? parentId : this.session.headId;
|
|
@@ -1392,6 +1398,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1392
1398
|
const abortSignal = abortController.signal;
|
|
1393
1399
|
const aggregator = new RunAggregator({
|
|
1394
1400
|
showThinking: this.showThinking,
|
|
1401
|
+
joinStrategy,
|
|
1395
1402
|
logger: this.logger,
|
|
1396
1403
|
emit: applyUpdate,
|
|
1397
1404
|
onServerMessageId: (serverId) => {
|
|
@@ -1407,6 +1414,9 @@ export class AgUiThreadRuntimeCore {
|
|
|
1407
1414
|
aggregator,
|
|
1408
1415
|
event,
|
|
1409
1416
|
assistantMessageId,
|
|
1417
|
+
joinStrategy,
|
|
1418
|
+
runStartMessageIds,
|
|
1419
|
+
hasRunUserBoundary,
|
|
1410
1420
|
);
|
|
1411
1421
|
if (event.type === "MESSAGES_SNAPSHOT") {
|
|
1412
1422
|
assistantMessageId = nextAssistantMessageId;
|
|
@@ -2041,6 +2051,9 @@ export class AgUiThreadRuntimeCore {
|
|
|
2041
2051
|
aggregator: RunAggregator,
|
|
2042
2052
|
event: AgUiEvent,
|
|
2043
2053
|
activeAssistantId: string | undefined,
|
|
2054
|
+
joinStrategy: UseAgUiRuntimeOptions["joinStrategy"],
|
|
2055
|
+
runStartMessageIds: ReadonlySet<string>,
|
|
2056
|
+
hasRunUserBoundary: boolean,
|
|
2044
2057
|
): string | undefined {
|
|
2045
2058
|
switch (event.type) {
|
|
2046
2059
|
case "STATE_SNAPSHOT": {
|
|
@@ -2074,7 +2087,13 @@ export class AgUiThreadRuntimeCore {
|
|
|
2074
2087
|
const hasActiveText =
|
|
2075
2088
|
activeAssistant?.role === "assistant" &&
|
|
2076
2089
|
activeAssistant.content.some((part) => part.type === "text");
|
|
2077
|
-
this.importMessagesSnapshot(
|
|
2090
|
+
this.importMessagesSnapshot(
|
|
2091
|
+
event.messages,
|
|
2092
|
+
activeAssistantId,
|
|
2093
|
+
joinStrategy,
|
|
2094
|
+
runStartMessageIds,
|
|
2095
|
+
hasRunUserBoundary,
|
|
2096
|
+
);
|
|
2078
2097
|
const headId = this.session.headId;
|
|
2079
2098
|
if (hasActiveText && headId !== previousHeadId) {
|
|
2080
2099
|
const head =
|
|
@@ -2283,6 +2302,9 @@ export class AgUiThreadRuntimeCore {
|
|
|
2283
2302
|
private importMessagesSnapshot(
|
|
2284
2303
|
rawMessages: readonly unknown[],
|
|
2285
2304
|
activeAssistantId: string | undefined,
|
|
2305
|
+
joinStrategy: UseAgUiRuntimeOptions["joinStrategy"],
|
|
2306
|
+
runStartMessageIds: ReadonlySet<string>,
|
|
2307
|
+
hasRunUserBoundary: boolean,
|
|
2286
2308
|
) {
|
|
2287
2309
|
try {
|
|
2288
2310
|
const activeAssistantItem = activeAssistantId
|
|
@@ -2337,6 +2359,101 @@ export class AgUiThreadRuntimeCore {
|
|
|
2337
2359
|
};
|
|
2338
2360
|
}
|
|
2339
2361
|
}
|
|
2362
|
+
if (joinStrategy === "concat-content") {
|
|
2363
|
+
const activeIndex = converted.findIndex(
|
|
2364
|
+
(message) => message.id === activeAssistantId,
|
|
2365
|
+
);
|
|
2366
|
+
const lastKnownIndex = converted.findLastIndex((message) =>
|
|
2367
|
+
runStartMessageIds.has(message.id),
|
|
2368
|
+
);
|
|
2369
|
+
const lastUserIndex = hasRunUserBoundary
|
|
2370
|
+
? converted.findLastIndex((message) => message.role === "user")
|
|
2371
|
+
: -1;
|
|
2372
|
+
const startIndex =
|
|
2373
|
+
activeIndex >= 0
|
|
2374
|
+
? activeIndex
|
|
2375
|
+
: lastKnownIndex >= 0 || lastUserIndex >= 0
|
|
2376
|
+
? Math.max(lastKnownIndex, lastUserIndex) + 1
|
|
2377
|
+
: -1;
|
|
2378
|
+
if (startIndex >= 0 && converted[startIndex]?.role === "assistant") {
|
|
2379
|
+
let endIndex = startIndex + 1;
|
|
2380
|
+
while (converted[endIndex]?.role === "assistant") endIndex++;
|
|
2381
|
+
const runMessages = converted.slice(
|
|
2382
|
+
startIndex,
|
|
2383
|
+
endIndex,
|
|
2384
|
+
) as ThreadAssistantMessage[];
|
|
2385
|
+
const includesStreamedPrefix =
|
|
2386
|
+
activeIndex < 0 &&
|
|
2387
|
+
activeAssistant?.content.some((part) => part.type !== "data");
|
|
2388
|
+
if (includesStreamedPrefix && activeAssistant) {
|
|
2389
|
+
runMessages.unshift(activeAssistant);
|
|
2390
|
+
}
|
|
2391
|
+
if (runMessages.length > 1) {
|
|
2392
|
+
const [first, ...rest] = runMessages;
|
|
2393
|
+
let content = runMessages.flatMap((message) => message.content);
|
|
2394
|
+
if (includesStreamedPrefix) {
|
|
2395
|
+
content = [...first!.content];
|
|
2396
|
+
let cursor = 0;
|
|
2397
|
+
const keyOf = (part: (typeof content)[number]) =>
|
|
2398
|
+
part.type === "tool-call"
|
|
2399
|
+
? `tool:${part.toolCallId}`
|
|
2400
|
+
: part.type === "text" || part.type === "reasoning"
|
|
2401
|
+
? `${part.type}:${part.text}`
|
|
2402
|
+
: undefined;
|
|
2403
|
+
for (const message of rest) {
|
|
2404
|
+
for (const part of message.content) {
|
|
2405
|
+
const key = keyOf(part);
|
|
2406
|
+
const match =
|
|
2407
|
+
key === undefined
|
|
2408
|
+
? -1
|
|
2409
|
+
: content.findIndex(
|
|
2410
|
+
(previous, index) =>
|
|
2411
|
+
index >= cursor && keyOf(previous) === key,
|
|
2412
|
+
);
|
|
2413
|
+
if (match < 0) {
|
|
2414
|
+
content.push(part);
|
|
2415
|
+
cursor = content.length;
|
|
2416
|
+
} else {
|
|
2417
|
+
if (part.type === "tool-call") {
|
|
2418
|
+
content[match] = this.preserveToolResults(
|
|
2419
|
+
[content[match]!],
|
|
2420
|
+
[part],
|
|
2421
|
+
)[0]!;
|
|
2422
|
+
}
|
|
2423
|
+
cursor = match + 1;
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
converted.splice(startIndex, endIndex - startIndex, {
|
|
2429
|
+
...first!,
|
|
2430
|
+
content,
|
|
2431
|
+
status: rest.at(-1)!.status,
|
|
2432
|
+
metadata: rest.reduce(
|
|
2433
|
+
(metadata, message) => ({
|
|
2434
|
+
...metadata,
|
|
2435
|
+
...message.metadata,
|
|
2436
|
+
unstable_state: message.metadata.unstable_state,
|
|
2437
|
+
unstable_annotations: [
|
|
2438
|
+
...metadata.unstable_annotations,
|
|
2439
|
+
...message.metadata.unstable_annotations,
|
|
2440
|
+
],
|
|
2441
|
+
unstable_data: [
|
|
2442
|
+
...metadata.unstable_data,
|
|
2443
|
+
...message.metadata.unstable_data,
|
|
2444
|
+
],
|
|
2445
|
+
steps: [...metadata.steps, ...message.metadata.steps],
|
|
2446
|
+
custom: mergeAgUiNamespace(
|
|
2447
|
+
metadata.custom,
|
|
2448
|
+
message.metadata.custom,
|
|
2449
|
+
),
|
|
2450
|
+
}),
|
|
2451
|
+
first!.metadata,
|
|
2452
|
+
),
|
|
2453
|
+
});
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2340
2457
|
const snapshotContainsActiveAssistant = converted.some(
|
|
2341
2458
|
(message) => message.id === activeAssistant?.id,
|
|
2342
2459
|
);
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type ToolCallMessagePart,
|
|
12
12
|
type ToolModelContentPart,
|
|
13
13
|
} from "@assistant-ui/core";
|
|
14
|
+
import type { JoinStrategy } from "@assistant-ui/core/react";
|
|
14
15
|
import {
|
|
15
16
|
applyA2uiOperations,
|
|
16
17
|
surfaceToPresentToolCall,
|
|
@@ -176,6 +177,7 @@ export const isPlainObject = (
|
|
|
176
177
|
|
|
177
178
|
export type RunAggregatorOptions = {
|
|
178
179
|
showThinking: boolean;
|
|
180
|
+
joinStrategy?: JoinStrategy | undefined;
|
|
179
181
|
logger: Logger;
|
|
180
182
|
emit: Emit;
|
|
181
183
|
onServerMessageId?: (messageId: string) => void;
|
|
@@ -195,6 +197,7 @@ export type RunAggregatorOptions = {
|
|
|
195
197
|
export class RunAggregator {
|
|
196
198
|
private readonly emitUpdate: Emit;
|
|
197
199
|
private readonly showThinking: boolean;
|
|
200
|
+
private readonly joinStrategy: JoinStrategy | undefined;
|
|
198
201
|
private readonly logger: Logger;
|
|
199
202
|
private readonly onServerMessageId: ((messageId: string) => void) | undefined;
|
|
200
203
|
private readonly onTextMessageStart:
|
|
@@ -259,6 +262,7 @@ export class RunAggregator {
|
|
|
259
262
|
constructor(options: RunAggregatorOptions) {
|
|
260
263
|
this.emitUpdate = options.emit;
|
|
261
264
|
this.showThinking = options.showThinking;
|
|
265
|
+
this.joinStrategy = options.joinStrategy;
|
|
262
266
|
this.logger = options.logger;
|
|
263
267
|
this.onServerMessageId = options.onServerMessageId;
|
|
264
268
|
this.onTextMessageStart = options.onTextMessageStart;
|
|
@@ -851,6 +855,7 @@ export class RunAggregator {
|
|
|
851
855
|
|
|
852
856
|
private beginDistinctTextMessage(messageId: string | undefined): void {
|
|
853
857
|
if (
|
|
858
|
+
this.joinStrategy === "concat-content" ||
|
|
854
859
|
!messageId ||
|
|
855
860
|
this.lastTextMessageId === undefined ||
|
|
856
861
|
this.lastTextMessageId === messageId ||
|
package/src/runtime/types.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
ThreadHistoryAdapter,
|
|
12
12
|
ThreadMessage,
|
|
13
13
|
} from "@assistant-ui/core";
|
|
14
|
+
import type { JoinStrategy } from "@assistant-ui/core/react";
|
|
14
15
|
import type { AbstractAgent, Context, Message } from "@ag-ui/client";
|
|
15
16
|
import type { Logger } from "./logger";
|
|
16
17
|
import type { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
@@ -82,6 +83,7 @@ export type UseAgUiRuntimeOptions = ExternalStoreSharedOptions & {
|
|
|
82
83
|
agent: AbstractAgent;
|
|
83
84
|
logger?: Partial<Logger>;
|
|
84
85
|
showThinking?: boolean;
|
|
86
|
+
joinStrategy?: JoinStrategy;
|
|
85
87
|
/**
|
|
86
88
|
* Return context entries for each run. `messages` is the exact AG-UI
|
|
87
89
|
* transcript sent in the run input, after transcript selection and message
|
|
@@ -49,3 +49,42 @@ it("re-renders when the core publishes a state change", () => {
|
|
|
49
49
|
expect(renders).toBeGreaterThan(initialRenders);
|
|
50
50
|
expect(result.current.thread.getState().messages).toHaveLength(1);
|
|
51
51
|
});
|
|
52
|
+
|
|
53
|
+
it("passes joinStrategy to the active run", async () => {
|
|
54
|
+
const agent = {
|
|
55
|
+
runAgent: vi.fn(async (_input: unknown, subscriber: any) => {
|
|
56
|
+
for (const [messageId, delta] of [
|
|
57
|
+
["a", "A"],
|
|
58
|
+
["b", "B"],
|
|
59
|
+
]) {
|
|
60
|
+
subscriber.onTextMessageStartEvent?.({
|
|
61
|
+
event: { type: "TEXT_MESSAGE_START", messageId },
|
|
62
|
+
});
|
|
63
|
+
subscriber.onTextMessageContentEvent?.({
|
|
64
|
+
event: { type: "TEXT_MESSAGE_CONTENT", messageId, delta },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
subscriber.onRunFinalized?.();
|
|
68
|
+
}),
|
|
69
|
+
abortRun: vi.fn(),
|
|
70
|
+
} as unknown as HttpAgent;
|
|
71
|
+
const { result } = renderHook(() =>
|
|
72
|
+
useAgUiRuntime({ agent, joinStrategy: "concat-content" }),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
await act(async () => {
|
|
76
|
+
await result.current.thread.append({
|
|
77
|
+
role: "user",
|
|
78
|
+
content: [{ type: "text", text: "hi" }],
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const assistants = result.current.thread
|
|
83
|
+
.getState()
|
|
84
|
+
.messages.filter((message) => message.role === "assistant");
|
|
85
|
+
expect(assistants).toHaveLength(1);
|
|
86
|
+
expect(assistants[0]?.content).toMatchObject([
|
|
87
|
+
{ type: "text", text: "A" },
|
|
88
|
+
{ type: "text", text: "B" },
|
|
89
|
+
]);
|
|
90
|
+
});
|
package/src/useAgUiRuntime.ts
CHANGED
|
@@ -75,6 +75,7 @@ export function useAgUiRuntime(
|
|
|
75
75
|
agent: options.agent,
|
|
76
76
|
logger,
|
|
77
77
|
showThinking: options.showThinking ?? true,
|
|
78
|
+
joinStrategy: options.joinStrategy,
|
|
78
79
|
getRunContext: options.getRunContext,
|
|
79
80
|
resumeTranscript: options.resumeTranscript,
|
|
80
81
|
autoCancelPendingToolCalls: options.autoCancelPendingToolCalls,
|
|
@@ -96,6 +97,7 @@ export function useAgUiRuntime(
|
|
|
96
97
|
agent: options.agent,
|
|
97
98
|
logger,
|
|
98
99
|
showThinking: options.showThinking ?? true,
|
|
100
|
+
joinStrategy: options.joinStrategy,
|
|
99
101
|
getRunContext: options.getRunContext,
|
|
100
102
|
resumeTranscript: options.resumeTranscript,
|
|
101
103
|
autoCancelPendingToolCalls: options.autoCancelPendingToolCalls,
|