@assistant-ui/ai-sdk 0.0.6 → 0.0.8

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.
Files changed (107) hide show
  1. package/README.md +1 -1
  2. package/dist/adapters/aiSDKFormatAdapter.d.ts +2 -2
  3. package/dist/adapters/aiSDKFormatAdapter.d.ts.map +1 -0
  4. package/dist/adapters/vercelAttachmentAdapter.d.ts +2 -5
  5. package/dist/adapters/vercelAttachmentAdapter.d.ts.map +1 -1
  6. package/dist/adapters/vercelAttachmentAdapter.js +3 -3
  7. package/dist/adapters/vercelAttachmentAdapter.js.map +1 -1
  8. package/dist/aiSDKExtras.d.ts +6 -9
  9. package/dist/aiSDKExtras.d.ts.map +1 -1
  10. package/dist/converters/convertMessage.d.ts +34 -25
  11. package/dist/converters/convertMessage.d.ts.map +1 -1
  12. package/dist/converters/convertMessage.js +78 -8
  13. package/dist/converters/convertMessage.js.map +1 -1
  14. package/dist/converters/modelContentEnvelope.d.ts +10 -12
  15. package/dist/converters/modelContentEnvelope.d.ts.map +1 -1
  16. package/dist/converters/toCreateMessage.d.ts +3 -6
  17. package/dist/converters/toCreateMessage.d.ts.map +1 -1
  18. package/dist/converters/toolOutputConversion.d.ts +22 -25
  19. package/dist/converters/toolOutputConversion.d.ts.map +1 -1
  20. package/dist/hooks.d.ts +4 -7
  21. package/dist/hooks.d.ts.map +1 -1
  22. package/dist/index.d.ts +17 -14
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.native.d.ts +15 -12
  25. package/dist/index.native.d.ts.map +1 -0
  26. package/dist/model-context/injectInteractableContext.d.ts +3 -6
  27. package/dist/model-context/injectInteractableContext.d.ts.map +1 -1
  28. package/dist/model-context/injectQuoteContext.d.ts +2 -5
  29. package/dist/model-context/injectQuoteContext.d.ts.map +1 -1
  30. package/dist/runtime/AISDKChat.d.ts +4 -7
  31. package/dist/runtime/AISDKChat.d.ts.map +1 -1
  32. package/dist/runtime/AISDKChat.js +1 -1
  33. package/dist/runtime/AISDKThreads.d.ts +29 -32
  34. package/dist/runtime/AISDKThreads.d.ts.map +1 -1
  35. package/dist/runtime/AISDKThreads.js +26 -33
  36. package/dist/runtime/AISDKThreads.js.map +1 -1
  37. package/dist/runtime/sdkIdentity.d.ts +2 -5
  38. package/dist/runtime/sdkIdentity.d.ts.map +1 -1
  39. package/dist/runtime/sdkIdentity.js +1 -1
  40. package/dist/runtime/useAISDKRuntime.d.ts +72 -61
  41. package/dist/runtime/useAISDKRuntime.d.ts.map +1 -1
  42. package/dist/runtime/useAISDKRuntime.js +170 -56
  43. package/dist/runtime/useAISDKRuntime.js.map +1 -1
  44. package/dist/runtime/useChatRuntime.d.ts +8 -11
  45. package/dist/runtime/useChatRuntime.d.ts.map +1 -1
  46. package/dist/runtime/useChatRuntime.js +14 -10
  47. package/dist/runtime/useChatRuntime.js.map +1 -1
  48. package/dist/runtime/useChatThread.d.ts +100 -81
  49. package/dist/runtime/useChatThread.d.ts.map +1 -1
  50. package/dist/runtime/useChatThread.js +33 -13
  51. package/dist/runtime/useChatThread.js.map +1 -1
  52. package/dist/runtime/useExternalHistory.d.ts +8 -9
  53. package/dist/runtime/useExternalHistory.d.ts.map +1 -1
  54. package/dist/runtime/useExternalHistory.js +236 -58
  55. package/dist/runtime/useExternalHistory.js.map +1 -1
  56. package/dist/runtime/useResourceCleanup.d.ts +1 -4
  57. package/dist/runtime/useResourceCleanup.d.ts.map +1 -1
  58. package/dist/runtime/useResourceCleanup.js +38 -14
  59. package/dist/runtime/useResourceCleanup.js.map +1 -1
  60. package/dist/runtime/useStreamingTiming.d.ts +5 -8
  61. package/dist/runtime/useStreamingTiming.d.ts.map +1 -1
  62. package/dist/runtime/useStreamingTiming.js +1 -1
  63. package/dist/tools/frontendTools.d.ts +24 -27
  64. package/dist/tools/frontendTools.d.ts.map +1 -1
  65. package/dist/tools/generativeTools.d.ts +29 -32
  66. package/dist/tools/generativeTools.d.ts.map +1 -1
  67. package/dist/tools/mcp-stdio.node.d.ts +2 -2
  68. package/dist/tools/mcp-stdio.node.d.ts.map +1 -0
  69. package/dist/tools/mcp-stdio.unsupported.d.ts +2 -5
  70. package/dist/tools/mcp-stdio.unsupported.d.ts.map +1 -1
  71. package/dist/transport/AssistantChatTransport.d.ts +17 -20
  72. package/dist/transport/AssistantChatTransport.d.ts.map +1 -1
  73. package/dist/transport/resumable.d.ts +29 -32
  74. package/dist/transport/resumable.d.ts.map +1 -1
  75. package/dist/usage.d.ts +12 -15
  76. package/dist/usage.d.ts.map +1 -1
  77. package/dist/usage.js.map +1 -1
  78. package/dist/utils/getVercelAIMessages.d.ts +3 -6
  79. package/dist/utils/getVercelAIMessages.d.ts.map +1 -1
  80. package/dist/utils/sliceMessagesUntil.d.ts +2 -5
  81. package/dist/utils/sliceMessagesUntil.d.ts.map +1 -1
  82. package/package.json +16 -14
  83. package/src/adapters/vercelAttachmentAdapter.test.ts +23 -0
  84. package/src/adapters/vercelAttachmentAdapter.ts +2 -2
  85. package/src/converters/convertMessage.test.ts +616 -3
  86. package/src/converters/convertMessage.ts +187 -12
  87. package/src/index.native.ts +1 -1
  88. package/src/index.ts +1 -1
  89. package/src/runtime/AISDKChat.test.ts +4 -5
  90. package/src/runtime/AISDKThreads.cloud.test.ts +60 -2
  91. package/src/runtime/AISDKThreads.test.ts +88 -51
  92. package/src/runtime/AISDKThreads.ts +4 -13
  93. package/src/runtime/useAISDKRuntime.approval.integration.test.tsx +338 -0
  94. package/src/runtime/useAISDKRuntime.approval.test.tsx +476 -4
  95. package/src/runtime/useAISDKRuntime.test.ts +282 -8
  96. package/src/runtime/useAISDKRuntime.ts +274 -46
  97. package/src/runtime/useAISDKRuntime.voice.test.tsx +319 -0
  98. package/src/runtime/useChatRuntime.integration.test.tsx +56 -0
  99. package/src/runtime/useChatRuntime.test.ts +77 -5
  100. package/src/runtime/useChatRuntime.ts +6 -2
  101. package/src/runtime/useChatThread.transport.test.tsx +149 -0
  102. package/src/runtime/useChatThread.ts +77 -10
  103. package/src/runtime/useExternalHistory.test.ts +989 -3
  104. package/src/runtime/useExternalHistory.ts +390 -24
  105. package/src/runtime/useResourceCleanup.ts +41 -15
  106. package/src/tools/generativeTools.test.ts +7 -1
  107. package/src/usage.ts +1 -1
@@ -2,9 +2,9 @@
2
2
  import { useAISDKRuntime } from "./useAISDKRuntime.js";
3
3
  import { AssistantChatTransport } from "../transport/AssistantChatTransport.js";
4
4
  import { useResourceCleanup } from "./useResourceCleanup.js";
5
- import { useCallback, useEffect, useMemo, useRef, useSyncExternalStore } from "@assistant-ui/tap/react-shim";
6
5
  import { pickExternalStoreSharedOptions } from "@assistant-ui/core";
7
- import { useChat } from "@ai-sdk/react";
6
+ import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "@assistant-ui/tap/react-shim";
7
+ import { Chat, useChat } from "@ai-sdk/react";
8
8
  //#region src/runtime/useChatThread.ts
9
9
  const useDynamicChatTransport = (transport) => {
10
10
  const transportRef = useRef(transport);
@@ -39,7 +39,7 @@ const getResumedStreamIds = (storage) => {
39
39
  * same fields `useChat` would.
40
40
  */
41
41
  const splitChatThreadOptions = (options) => {
42
- const { adapters, transport, throttle, toCreateMessage, isDisabled: _isDisabled, isSendDisabled: _isSendDisabled, unstable_capabilities: _unstable_capabilities, suggestions: _suggestions, onResume, onResumeToolCall, onResumeError, joinStrategy, messageRepository, unstable_onBranchChange, ...chatInit } = options ?? {};
42
+ const { adapters, transport, throttle, toCreateMessage, isDisabled: _isDisabled, isSendDisabled: _isSendDisabled, unstable_capabilities: _unstable_capabilities, suggestions: _suggestions, onResume, onResumeToolCall, onRespondToToolApproval, onResumeError, joinStrategy, messageRepository, unstable_onBranchChange, ...chatInit } = options ?? {};
43
43
  return {
44
44
  adapters,
45
45
  transport,
@@ -47,6 +47,7 @@ const splitChatThreadOptions = (options) => {
47
47
  toCreateMessage,
48
48
  onResume,
49
49
  onResumeToolCall,
50
+ onRespondToToolApproval,
50
51
  onResumeError,
51
52
  joinStrategy,
52
53
  messageRepository,
@@ -54,28 +55,47 @@ const splitChatThreadOptions = (options) => {
54
55
  chatInit
55
56
  };
56
57
  };
58
+ /**
59
+ * Constructs a `Chat` whose callbacks read the latest options through
60
+ * `callbacksRef`, the forwarding `useChat` applies only to a chat it
61
+ * constructs itself.
62
+ */
63
+ const createChat = (init, callbacksRef) => new Chat({
64
+ ...init,
65
+ onToolCall: (arg) => callbacksRef.current?.onToolCall?.(arg),
66
+ onData: (arg) => callbacksRef.current?.onData?.(arg),
67
+ onFinish: (arg) => callbacksRef.current?.onFinish?.(arg),
68
+ onError: (arg) => callbacksRef.current?.onError?.(arg),
69
+ sendAutomaticallyWhen: (arg) => callbacksRef.current?.sendAutomaticallyWhen?.(arg) ?? false
70
+ });
57
71
  const useChatThread = (options, env) => {
58
- const { adapters, transport: transportOptions, throttle, toCreateMessage, onResume, onResumeToolCall, onResumeError, joinStrategy, messageRepository, unstable_onBranchChange, chatInit: chatOptions } = splitChatThreadOptions(options);
59
- const { id, isMainThread, getThreadListItem, stopOnClientDestroy = true, chat: externalChat, messageRepositoryInstance } = env;
60
- const defaultTransport = useMemo(() => new AssistantChatTransport(), []);
61
- const sourceTransport = transportOptions ?? defaultTransport;
72
+ const { adapters, transport: transportOptions, throttle, toCreateMessage, onResume, onResumeToolCall, onRespondToToolApproval, onResumeError, joinStrategy, messageRepository, unstable_onBranchChange, chatInit: chatOptions } = splitChatThreadOptions(options);
73
+ const { id, isMainThread, getThreadListItem, stopOnClientDestroy = true, hostDestroySignal, chat: externalChat, messageRepositoryInstance } = env;
74
+ const sourceTransport = useMemo(() => transportOptions === void 0 ? new AssistantChatTransport() : externalChat === void 0 && transportOptions instanceof AssistantChatTransport ? transportOptions.__internal_clone() : transportOptions, [transportOptions, externalChat]);
62
75
  const transport = useDynamicChatTransport(sourceTransport);
63
- const chat = useChat({
76
+ const latestChatOptionsRef = useRef(chatOptions);
77
+ useEffect(() => {
78
+ latestChatOptionsRef.current = chatOptions;
79
+ });
80
+ const [ownedChat] = useState(() => externalChat ?? createChat({
64
81
  ...chatOptions,
65
82
  id,
66
- transport,
67
- ...throttle !== void 0 && { throttle },
68
- ...externalChat !== void 0 && { chat: externalChat }
83
+ transport
84
+ }, latestChatOptionsRef));
85
+ const chat = useChat({
86
+ chat: externalChat ?? ownedChat,
87
+ ...throttle !== void 0 && { throttle }
69
88
  });
70
89
  useResourceCleanup(stopOnClientDestroy, () => {
71
90
  chat.stop().catch(() => {});
72
- });
91
+ }, hostDestroySignal);
73
92
  const runtime = useAISDKRuntime(chat, {
74
93
  adapters,
75
94
  ...pickExternalStoreSharedOptions(options ?? {}),
76
95
  ...toCreateMessage && { toCreateMessage },
77
96
  ...onResume && { onResume },
78
97
  ...onResumeToolCall && { onResumeToolCall },
98
+ ...onRespondToToolApproval && { onRespondToToolApproval },
79
99
  ...joinStrategy && { joinStrategy },
80
100
  ...messageRepository && { messageRepository },
81
101
  ...messageRepositoryInstance && { unstable_messageRepositoryInstance: messageRepositoryInstance },
@@ -136,6 +156,6 @@ const useChatThread = (options, env) => {
136
156
  return runtime;
137
157
  };
138
158
  //#endregion
139
- export { splitChatThreadOptions, useChatThread };
159
+ export { createChat, splitChatThreadOptions, useChatThread };
140
160
 
141
161
  //# sourceMappingURL=useChatThread.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useChatThread.js","names":["useChat","Chat","UIMessage","MessageRepository","pickExternalStoreSharedOptions","AssistantRuntime","ExternalStoreSharedOptions","useAISDKRuntime","AISDKRuntimeAdapter","CustomToCreateMessageFunction","ChatInit","ChatTransport","AssistantChatTransport","InitializableThreadListItem","AssistantChatResumableOptions","ResumableClientStorage","useCallback","useEffect","useMemo","useRef","useSyncExternalStore","useResourceCleanup","ChatThreadOptions","UI_MESSAGE","throttle","adapters","toCreateMessage","onResume","onResumeToolCall","onResumeError","error","joinStrategy","messageRepository","unstable_onBranchChange","ChatThreadEnvironment","id","isMainThread","getThreadListItem","stopOnClientDestroy","chat","messageRepositoryInstance","useDynamicChatTransport","transport","transportRef","current","dynamicTransport","Proxy","get","_","prop","res","bind","getResumableAdapter","candidate","undefined","call","getNoPendingStreamId","resumedStreamIdsByStorage","WeakMap","Set","getResumedStreamIds","storage","resumedStreamIds","set","splitChatThreadOptions","options","isDisabled","_isDisabled","isSendDisabled","_isSendDisabled","unstable_capabilities","_unstable_capabilities","suggestions","_suggestions","chatInit","useChatThread","env","transportOptions","chatOptions","externalChat","defaultTransport","sourceTransport","stop","catch","runtime","unstable_messageRepositoryInstance","setRuntime","__internal_setGetThreadListItem","subscribeToRuntime","callback","thread","subscribe","getHistoryLoadingSnapshot","getState","isLoading","isLoadingHistory","resumableStorage","subscribeToResumableStorage","getPendingStreamId","getStreamId","pendingStreamId","isChatRunning","status","onResumeErrorRef","has","add","resumeStream","err","console","warn","callbackError","clear"],"sources":["../../src/runtime/useChatThread.ts"],"sourcesContent":["\"use client\";\n\nimport { useChat, type Chat, type UIMessage } from \"@ai-sdk/react\";\nimport type { MessageRepository } from \"@assistant-ui/core/internal\";\nimport {\n pickExternalStoreSharedOptions,\n type AssistantRuntime,\n type ExternalStoreSharedOptions,\n} from \"@assistant-ui/core\";\nimport {\n useAISDKRuntime,\n type AISDKRuntimeAdapter,\n type CustomToCreateMessageFunction,\n} from \"./useAISDKRuntime\";\nimport type { ChatInit, ChatTransport } from \"ai\";\nimport {\n AssistantChatTransport,\n type InitializableThreadListItem,\n} from \"../transport/AssistantChatTransport\";\nimport type {\n AssistantChatResumableOptions,\n ResumableClientStorage,\n} from \"../transport/resumable\";\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from \"react\";\nimport { useResourceCleanup } from \"./useResourceCleanup\";\n\nexport type ChatThreadOptions<UI_MESSAGE extends UIMessage = UIMessage> =\n ChatInit<UI_MESSAGE> &\n ExternalStoreSharedOptions & {\n throttle?: number | undefined;\n adapters?: AISDKRuntimeAdapter[\"adapters\"] | undefined;\n toCreateMessage?: CustomToCreateMessageFunction;\n onResume?: AISDKRuntimeAdapter[\"onResume\"];\n onResumeToolCall?: AISDKRuntimeAdapter[\"onResumeToolCall\"];\n /**\n * Called when an automatic resumable stream reconnect fails. Use this to\n * surface a toast, report telemetry, or mark the thread as needing a\n * retry. The failed stream id is cleared after the callback unless a\n * newer id has replaced it.\n */\n onResumeError?: ((error: unknown) => void) | undefined;\n joinStrategy?: AISDKRuntimeAdapter[\"joinStrategy\"];\n messageRepository?: AISDKRuntimeAdapter<UI_MESSAGE>[\"messageRepository\"];\n unstable_onBranchChange?: AISDKRuntimeAdapter[\"unstable_onBranchChange\"];\n };\n\nexport type ChatThreadEnvironment<UI_MESSAGE extends UIMessage = UIMessage> = {\n id: string;\n isMainThread: boolean;\n getThreadListItem: () => InitializableThreadListItem | undefined;\n stopOnClientDestroy?: boolean;\n /**\n * An externally owned chat instance. State lives on the instance, so it\n * survives the hosting resource unmounting; construction options are read\n * from the instance.\n */\n chat?: Chat<UI_MESSAGE> | undefined;\n /**\n * An externally owned per-thread message repository. Hosts that route\n * multiple threads through one mounting pass a distinct instance per\n * thread so histories and branches stay isolated.\n */\n messageRepositoryInstance?: MessageRepository | undefined;\n};\n\nconst useDynamicChatTransport = <UI_MESSAGE extends UIMessage = UIMessage>(\n transport: ChatTransport<UI_MESSAGE>,\n): ChatTransport<UI_MESSAGE> => {\n const transportRef = useRef<ChatTransport<UI_MESSAGE>>(transport);\n useEffect(() => {\n transportRef.current = transport;\n });\n const dynamicTransport = useMemo(\n () =>\n new Proxy(transportRef.current, {\n get(_, prop) {\n const res =\n transportRef.current[prop as keyof ChatTransport<UI_MESSAGE>];\n return typeof res === \"function\"\n ? res.bind(transportRef.current)\n : res;\n },\n }),\n [],\n );\n return dynamicTransport;\n};\n\nconst getResumableAdapter = <UI_MESSAGE extends UIMessage>(\n transport: ChatTransport<UI_MESSAGE>,\n): AssistantChatResumableOptions | undefined => {\n if (transport instanceof AssistantChatTransport) {\n return transport.getResumableAdapter();\n }\n const candidate = (transport as { getResumableAdapter?: () => unknown })\n .getResumableAdapter;\n if (typeof candidate !== \"function\") return undefined;\n return candidate.call(transport) as AssistantChatResumableOptions | undefined;\n};\n\nconst getNoPendingStreamId = () => null;\n\nconst resumedStreamIdsByStorage = new WeakMap<\n ResumableClientStorage,\n Set<string>\n>();\n\nconst getResumedStreamIds = (storage: ResumableClientStorage | undefined) => {\n if (!storage) return new Set<string>();\n let resumedStreamIds = resumedStreamIdsByStorage.get(storage);\n if (!resumedStreamIds) {\n resumedStreamIds = new Set();\n resumedStreamIdsByStorage.set(storage, resumedStreamIds);\n }\n return resumedStreamIds;\n};\n\n/**\n * Splits the combined options into the assistant-ui side and the `ChatInit`\n * remainder the AI SDK consumes, so external `Chat` construction forwards the\n * same fields `useChat` would.\n */\nexport const splitChatThreadOptions = <UI_MESSAGE extends UIMessage>(\n options: ChatThreadOptions<UI_MESSAGE> | undefined,\n) => {\n const {\n adapters,\n transport,\n throttle,\n toCreateMessage,\n isDisabled: _isDisabled,\n isSendDisabled: _isSendDisabled,\n unstable_capabilities: _unstable_capabilities,\n suggestions: _suggestions,\n onResume,\n onResumeToolCall,\n onResumeError,\n joinStrategy,\n messageRepository,\n unstable_onBranchChange,\n ...chatInit\n } = options ?? {};\n // peel guard: any shared key left in `chatInit` collapses this to `never`\n true satisfies keyof typeof chatInit &\n keyof ExternalStoreSharedOptions extends never\n ? true\n : never;\n return {\n adapters,\n transport,\n throttle,\n toCreateMessage,\n onResume,\n onResumeToolCall,\n onResumeError,\n joinStrategy,\n messageRepository,\n unstable_onBranchChange,\n chatInit,\n };\n};\n\nexport const useChatThread = <UI_MESSAGE extends UIMessage = UIMessage>(\n options: ChatThreadOptions<UI_MESSAGE> | undefined,\n env: ChatThreadEnvironment<UI_MESSAGE>,\n): AssistantRuntime => {\n const {\n adapters,\n transport: transportOptions,\n throttle,\n toCreateMessage,\n onResume,\n onResumeToolCall,\n onResumeError,\n joinStrategy,\n messageRepository,\n unstable_onBranchChange,\n chatInit: chatOptions,\n } = splitChatThreadOptions(options);\n\n const {\n id,\n isMainThread,\n getThreadListItem,\n stopOnClientDestroy = true,\n chat: externalChat,\n messageRepositoryInstance,\n } = env;\n\n const defaultTransport = useMemo(() => new AssistantChatTransport(), []);\n const sourceTransport = transportOptions ?? defaultTransport;\n const transport = useDynamicChatTransport(sourceTransport);\n\n const chat = useChat({\n ...chatOptions,\n id,\n transport,\n ...(throttle !== undefined && { throttle }),\n ...(externalChat !== undefined && { chat: externalChat }),\n });\n\n useResourceCleanup(stopOnClientDestroy, () => {\n void chat.stop().catch(() => {});\n });\n\n const runtime = useAISDKRuntime(chat, {\n adapters,\n ...pickExternalStoreSharedOptions(options ?? {}),\n ...(toCreateMessage && { toCreateMessage }),\n ...(onResume && { onResume }),\n ...(onResumeToolCall && { onResumeToolCall }),\n ...(joinStrategy && { joinStrategy }),\n ...(messageRepository && { messageRepository }),\n ...(messageRepositoryInstance && {\n unstable_messageRepositoryInstance: messageRepositoryInstance,\n }),\n ...(unstable_onBranchChange && { unstable_onBranchChange }),\n });\n\n if (sourceTransport instanceof AssistantChatTransport) {\n sourceTransport.setRuntime(runtime);\n sourceTransport.__internal_setGetThreadListItem(getThreadListItem);\n }\n\n const subscribeToRuntime = useCallback(\n (callback: () => void) => runtime.thread.subscribe(callback),\n [runtime],\n );\n const getHistoryLoadingSnapshot = useCallback(\n () => runtime.thread.getState().isLoading,\n [runtime],\n );\n const isLoadingHistory = useSyncExternalStore(\n subscribeToRuntime,\n getHistoryLoadingSnapshot,\n getHistoryLoadingSnapshot,\n );\n\n const resumableStorage = useMemo(\n () => getResumableAdapter(sourceTransport)?.storage,\n [sourceTransport],\n );\n const subscribeToResumableStorage = useCallback(\n (callback: () => void) =>\n isMainThread\n ? (resumableStorage?.subscribe?.(callback, id) ?? (() => {}))\n : () => {},\n [id, isMainThread, resumableStorage],\n );\n const getPendingStreamId = useCallback(\n () => (isMainThread ? (resumableStorage?.getStreamId(id) ?? null) : null),\n [id, isMainThread, resumableStorage],\n );\n const pendingStreamId = useSyncExternalStore(\n subscribeToResumableStorage,\n getPendingStreamId,\n getNoPendingStreamId,\n );\n const isChatRunning =\n chat.status === \"submitted\" || chat.status === \"streaming\";\n\n const resumedStreamIds = useMemo(\n () => getResumedStreamIds(resumableStorage),\n [resumableStorage],\n );\n const onResumeErrorRef = useRef(onResumeError);\n useEffect(() => {\n onResumeErrorRef.current = onResumeError;\n });\n useEffect(() => {\n if (!pendingStreamId || resumedStreamIds.has(pendingStreamId)) {\n return;\n }\n if (isChatRunning) {\n resumedStreamIds.add(pendingStreamId);\n return;\n }\n if (isLoadingHistory) return;\n resumedStreamIds.add(pendingStreamId);\n chat.resumeStream().catch((err: unknown) => {\n console.warn(\"[assistant-ui] resumable: resume failed\", err);\n try {\n onResumeErrorRef.current?.(err);\n } catch (callbackError) {\n console.error(\n \"[assistant-ui] resumable: onResumeError callback failed\",\n callbackError,\n );\n } finally {\n if (resumableStorage?.getStreamId(id) === pendingStreamId) {\n resumableStorage.clear(id);\n }\n }\n });\n }, [\n chat,\n id,\n isChatRunning,\n isLoadingHistory,\n pendingStreamId,\n resumableStorage,\n resumedStreamIds,\n ]);\n\n return runtime;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"useChatThread.js","names":["Chat","useChat","UIMessage","MessageRepository","pickExternalStoreSharedOptions","AssistantRuntime","ExternalStoreSharedOptions","useAISDKRuntime","AISDKRuntimeAdapter","CustomToCreateMessageFunction","ChatInit","ChatTransport","AssistantChatTransport","InitializableThreadListItem","AssistantChatResumableOptions","ResumableClientStorage","useCallback","useEffect","useMemo","useRef","useState","useSyncExternalStore","useResourceCleanup","ChatThreadOptions","UI_MESSAGE","throttle","adapters","toCreateMessage","onResume","onResumeToolCall","onRespondToToolApproval","onResumeError","error","joinStrategy","messageRepository","unstable_onBranchChange","ChatThreadEnvironment","id","isMainThread","getThreadListItem","stopOnClientDestroy","hostDestroySignal","AbortSignal","chat","messageRepositoryInstance","useDynamicChatTransport","transport","transportRef","current","dynamicTransport","Proxy","get","_","prop","res","bind","getResumableAdapter","candidate","undefined","call","getNoPendingStreamId","resumedStreamIdsByStorage","WeakMap","Set","getResumedStreamIds","storage","resumedStreamIds","set","splitChatThreadOptions","options","isDisabled","_isDisabled","isSendDisabled","_isSendDisabled","unstable_capabilities","_unstable_capabilities","suggestions","_suggestions","chatInit","ChatCallbacks","Pick","createChat","init","callbacksRef","onToolCall","arg","onData","onFinish","onError","sendAutomaticallyWhen","useChatThread","env","transportOptions","chatOptions","externalChat","sourceTransport","__internal_clone","latestChatOptionsRef","ownedChat","stop","catch","runtime","unstable_messageRepositoryInstance","setRuntime","__internal_setGetThreadListItem","subscribeToRuntime","callback","thread","subscribe","getHistoryLoadingSnapshot","getState","isLoading","isLoadingHistory","resumableStorage","subscribeToResumableStorage","getPendingStreamId","getStreamId","pendingStreamId","isChatRunning","status","onResumeErrorRef","has","add","resumeStream","err","console","warn","callbackError","clear"],"sources":["../../src/runtime/useChatThread.ts"],"sourcesContent":["\"use client\";\n\nimport { Chat, useChat, type UIMessage } from \"@ai-sdk/react\";\nimport type { MessageRepository } from \"@assistant-ui/core/internal\";\nimport {\n pickExternalStoreSharedOptions,\n type AssistantRuntime,\n type ExternalStoreSharedOptions,\n} from \"@assistant-ui/core\";\nimport {\n useAISDKRuntime,\n type AISDKRuntimeAdapter,\n type CustomToCreateMessageFunction,\n} from \"./useAISDKRuntime\";\nimport type { ChatInit, ChatTransport } from \"ai\";\nimport {\n AssistantChatTransport,\n type InitializableThreadListItem,\n} from \"../transport/AssistantChatTransport\";\nimport type {\n AssistantChatResumableOptions,\n ResumableClientStorage,\n} from \"../transport/resumable\";\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport { useResourceCleanup } from \"./useResourceCleanup\";\n\nexport type ChatThreadOptions<UI_MESSAGE extends UIMessage = UIMessage> =\n ChatInit<UI_MESSAGE> &\n ExternalStoreSharedOptions & {\n throttle?: number | undefined;\n adapters?: AISDKRuntimeAdapter[\"adapters\"] | undefined;\n toCreateMessage?: CustomToCreateMessageFunction;\n onResume?: AISDKRuntimeAdapter[\"onResume\"];\n onResumeToolCall?: AISDKRuntimeAdapter[\"onResumeToolCall\"];\n onRespondToToolApproval?: AISDKRuntimeAdapter[\"onRespondToToolApproval\"];\n /**\n * Called when an automatic resumable stream reconnect fails. Use this to\n * surface a toast, report telemetry, or mark the thread as needing a\n * retry. The failed stream id is cleared after the callback unless a\n * newer id has replaced it.\n */\n onResumeError?: ((error: unknown) => void) | undefined;\n joinStrategy?: AISDKRuntimeAdapter[\"joinStrategy\"];\n messageRepository?: AISDKRuntimeAdapter<UI_MESSAGE>[\"messageRepository\"];\n unstable_onBranchChange?: AISDKRuntimeAdapter[\"unstable_onBranchChange\"];\n };\n\nexport type ChatThreadEnvironment<UI_MESSAGE extends UIMessage = UIMessage> = {\n id: string;\n isMainThread: boolean;\n getThreadListItem: () => InitializableThreadListItem | undefined;\n stopOnClientDestroy?: boolean;\n /**\n * Aborts when the React component hosting the runtime is deleted. A nested\n * runtime resolves the destroy signal of the provider above it, which\n * outlives the nested component, so this stops the chat on its own unmount.\n */\n hostDestroySignal?: AbortSignal | undefined;\n /**\n * An externally owned chat instance. State lives on the instance, so it\n * survives the hosting resource unmounting; construction options are read\n * from the instance.\n */\n chat?: Chat<UI_MESSAGE> | undefined;\n /**\n * An externally owned per-thread message repository. Hosts that route\n * multiple threads through one mounting pass a distinct instance per\n * thread so histories and branches stay isolated.\n */\n messageRepositoryInstance?: MessageRepository | undefined;\n};\n\nconst useDynamicChatTransport = <UI_MESSAGE extends UIMessage = UIMessage>(\n transport: ChatTransport<UI_MESSAGE>,\n): ChatTransport<UI_MESSAGE> => {\n const transportRef = useRef<ChatTransport<UI_MESSAGE>>(transport);\n useEffect(() => {\n transportRef.current = transport;\n });\n const dynamicTransport = useMemo(\n () =>\n new Proxy(transportRef.current, {\n get(_, prop) {\n const res =\n transportRef.current[prop as keyof ChatTransport<UI_MESSAGE>];\n return typeof res === \"function\"\n ? res.bind(transportRef.current)\n : res;\n },\n }),\n [],\n );\n return dynamicTransport;\n};\n\nconst getResumableAdapter = <UI_MESSAGE extends UIMessage>(\n transport: ChatTransport<UI_MESSAGE>,\n): AssistantChatResumableOptions | undefined => {\n if (transport instanceof AssistantChatTransport) {\n return transport.getResumableAdapter();\n }\n const candidate = (transport as { getResumableAdapter?: () => unknown })\n .getResumableAdapter;\n if (typeof candidate !== \"function\") return undefined;\n return candidate.call(transport) as AssistantChatResumableOptions | undefined;\n};\n\nconst getNoPendingStreamId = () => null;\n\nconst resumedStreamIdsByStorage = new WeakMap<\n ResumableClientStorage,\n Set<string>\n>();\n\nconst getResumedStreamIds = (storage: ResumableClientStorage | undefined) => {\n if (!storage) return new Set<string>();\n let resumedStreamIds = resumedStreamIdsByStorage.get(storage);\n if (!resumedStreamIds) {\n resumedStreamIds = new Set();\n resumedStreamIdsByStorage.set(storage, resumedStreamIds);\n }\n return resumedStreamIds;\n};\n\n/**\n * Splits the combined options into the assistant-ui side and the `ChatInit`\n * remainder the AI SDK consumes, so external `Chat` construction forwards the\n * same fields `useChat` would.\n */\nexport const splitChatThreadOptions = <UI_MESSAGE extends UIMessage>(\n options: ChatThreadOptions<UI_MESSAGE> | undefined,\n) => {\n const {\n adapters,\n transport,\n throttle,\n toCreateMessage,\n isDisabled: _isDisabled,\n isSendDisabled: _isSendDisabled,\n unstable_capabilities: _unstable_capabilities,\n suggestions: _suggestions,\n onResume,\n onResumeToolCall,\n onRespondToToolApproval,\n onResumeError,\n joinStrategy,\n messageRepository,\n unstable_onBranchChange,\n ...chatInit\n } = options ?? {};\n // peel guard: any shared key left in `chatInit` collapses this to `never`\n true satisfies keyof typeof chatInit &\n keyof ExternalStoreSharedOptions extends never\n ? true\n : never;\n return {\n adapters,\n transport,\n throttle,\n toCreateMessage,\n onResume,\n onResumeToolCall,\n onRespondToToolApproval,\n onResumeError,\n joinStrategy,\n messageRepository,\n unstable_onBranchChange,\n chatInit,\n };\n};\n\ntype ChatCallbacks<UI_MESSAGE extends UIMessage> = Pick<\n ChatInit<UI_MESSAGE>,\n \"onToolCall\" | \"onData\" | \"onFinish\" | \"onError\" | \"sendAutomaticallyWhen\"\n>;\n\n/**\n * Constructs a `Chat` whose callbacks read the latest options through\n * `callbacksRef`, the forwarding `useChat` applies only to a chat it\n * constructs itself.\n */\nexport const createChat = <UI_MESSAGE extends UIMessage>(\n init: ChatInit<UI_MESSAGE>,\n callbacksRef: { readonly current: ChatCallbacks<UI_MESSAGE> | undefined },\n): Chat<UI_MESSAGE> =>\n new Chat<UI_MESSAGE>({\n ...init,\n onToolCall: (arg) => callbacksRef.current?.onToolCall?.(arg),\n onData: (arg) => callbacksRef.current?.onData?.(arg),\n onFinish: (arg) => callbacksRef.current?.onFinish?.(arg),\n onError: (arg) => callbacksRef.current?.onError?.(arg),\n sendAutomaticallyWhen: (arg) =>\n callbacksRef.current?.sendAutomaticallyWhen?.(arg) ?? false,\n });\n\nexport const useChatThread = <UI_MESSAGE extends UIMessage = UIMessage>(\n options: ChatThreadOptions<UI_MESSAGE> | undefined,\n env: ChatThreadEnvironment<UI_MESSAGE>,\n): AssistantRuntime => {\n const {\n adapters,\n transport: transportOptions,\n throttle,\n toCreateMessage,\n onResume,\n onResumeToolCall,\n onRespondToToolApproval,\n onResumeError,\n joinStrategy,\n messageRepository,\n unstable_onBranchChange,\n chatInit: chatOptions,\n } = splitChatThreadOptions(options);\n\n const {\n id,\n isMainThread,\n getThreadListItem,\n stopOnClientDestroy = true,\n hostDestroySignal,\n chat: externalChat,\n messageRepositoryInstance,\n } = env;\n\n // Wiring below is per thread and mutated on the instance, so a transport\n // shared across simultaneously mounted threads is last-writer-wins. A\n // caller-owned chat is already bound to its own clone, so cloning again\n // here would wire a copy the chat never sends through.\n const sourceTransport = useMemo(\n () =>\n transportOptions === undefined\n ? new AssistantChatTransport()\n : externalChat === undefined &&\n transportOptions instanceof AssistantChatTransport\n ? transportOptions.__internal_clone()\n : transportOptions,\n [transportOptions, externalChat],\n );\n const transport = useDynamicChatTransport(sourceTransport);\n\n const latestChatOptionsRef = useRef(chatOptions);\n useEffect(() => {\n latestChatOptionsRef.current = chatOptions;\n });\n // `useChat` stops a chat it constructs whenever it unmounts, and a\n // resource's soft unmount runs that cleanup, so the thread owns its chat.\n const [ownedChat] = useState(\n () =>\n externalChat ??\n createChat({ ...chatOptions, id, transport }, latestChatOptionsRef),\n );\n\n const chat = useChat({\n chat: externalChat ?? ownedChat,\n ...(throttle !== undefined && { throttle }),\n });\n\n useResourceCleanup(\n stopOnClientDestroy,\n () => {\n void chat.stop().catch(() => {});\n },\n hostDestroySignal,\n );\n\n const runtime = useAISDKRuntime(chat, {\n adapters,\n ...pickExternalStoreSharedOptions(options ?? {}),\n ...(toCreateMessage && { toCreateMessage }),\n ...(onResume && { onResume }),\n ...(onResumeToolCall && { onResumeToolCall }),\n ...(onRespondToToolApproval && { onRespondToToolApproval }),\n ...(joinStrategy && { joinStrategy }),\n ...(messageRepository && { messageRepository }),\n ...(messageRepositoryInstance && {\n unstable_messageRepositoryInstance: messageRepositoryInstance,\n }),\n ...(unstable_onBranchChange && { unstable_onBranchChange }),\n });\n\n // Wire in render, not an effect: a send from a descendant's mount effect\n // runs before this hook's effect would (effects fire child-first), and must\n // see a wired transport. The clone is per thread, so a discarded render's\n // wiring is discarded with it and the committed render re-wires the same\n // instance.\n if (sourceTransport instanceof AssistantChatTransport) {\n sourceTransport.setRuntime(runtime);\n sourceTransport.__internal_setGetThreadListItem(getThreadListItem);\n }\n\n const subscribeToRuntime = useCallback(\n (callback: () => void) => runtime.thread.subscribe(callback),\n [runtime],\n );\n const getHistoryLoadingSnapshot = useCallback(\n () => runtime.thread.getState().isLoading,\n [runtime],\n );\n const isLoadingHistory = useSyncExternalStore(\n subscribeToRuntime,\n getHistoryLoadingSnapshot,\n getHistoryLoadingSnapshot,\n );\n\n const resumableStorage = useMemo(\n () => getResumableAdapter(sourceTransport)?.storage,\n [sourceTransport],\n );\n const subscribeToResumableStorage = useCallback(\n (callback: () => void) =>\n isMainThread\n ? (resumableStorage?.subscribe?.(callback, id) ?? (() => {}))\n : () => {},\n [id, isMainThread, resumableStorage],\n );\n const getPendingStreamId = useCallback(\n () => (isMainThread ? (resumableStorage?.getStreamId(id) ?? null) : null),\n [id, isMainThread, resumableStorage],\n );\n const pendingStreamId = useSyncExternalStore(\n subscribeToResumableStorage,\n getPendingStreamId,\n getNoPendingStreamId,\n );\n const isChatRunning =\n chat.status === \"submitted\" || chat.status === \"streaming\";\n\n const resumedStreamIds = useMemo(\n () => getResumedStreamIds(resumableStorage),\n [resumableStorage],\n );\n const onResumeErrorRef = useRef(onResumeError);\n useEffect(() => {\n onResumeErrorRef.current = onResumeError;\n });\n useEffect(() => {\n if (!pendingStreamId || resumedStreamIds.has(pendingStreamId)) {\n return;\n }\n if (isChatRunning) {\n resumedStreamIds.add(pendingStreamId);\n return;\n }\n if (isLoadingHistory) return;\n resumedStreamIds.add(pendingStreamId);\n chat.resumeStream().catch((err: unknown) => {\n console.warn(\"[assistant-ui] resumable: resume failed\", err);\n try {\n onResumeErrorRef.current?.(err);\n } catch (callbackError) {\n console.error(\n \"[assistant-ui] resumable: onResumeError callback failed\",\n callbackError,\n );\n } finally {\n if (resumableStorage?.getStreamId(id) === pendingStreamId) {\n resumableStorage.clear(id);\n }\n }\n });\n }, [\n chat,\n id,\n isChatRunning,\n isLoadingHistory,\n pendingStreamId,\n resumableStorage,\n resumedStreamIds,\n ]);\n\n return runtime;\n};\n"],"mappings":"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"}
@@ -1,11 +1,10 @@
1
- import { RefObject } from "react";
2
- import { AssistantRuntime, ExportedMessageRepository, MessageFormatAdapter, MessageFormatRepository, ThreadHistoryAdapter, ThreadMessage } from "@assistant-ui/core";
3
- //#region src/runtime/useExternalHistory.d.ts
4
- declare const toExportedMessageRepository: <TMessage>(toThreadMessages: (messages: TMessage[]) => ThreadMessage[], messages: MessageFormatRepository<TMessage>) => ExportedMessageRepository;
5
- declare const useExternalHistory: <TMessage>(runtimeRef: RefObject<AssistantRuntime>, historyAdapter: ThreadHistoryAdapter | undefined, toThreadMessages: (messages: TMessage[]) => ThreadMessage[], storageFormatAdapter: MessageFormatAdapter<TMessage, any>, onSetMessages: (messages: TMessage[]) => void) => {
6
- isLoading: boolean;
7
- deleteMessage: (messageId: string) => Promise<void>;
1
+ import type { AssistantRuntime, ThreadHistoryAdapter, ThreadMessage, MessageFormatAdapter, MessageFormatRepository, ExportedMessageRepository, RespondToToolApprovalOptions, Unstable_ToolInteractionLog } from "@assistant-ui/core";
2
+ import { type RefObject } from "react";
3
+ export declare const toExportedMessageRepository: <TMessage>(toThreadMessages: (messages: TMessage[]) => ThreadMessage[], messages: MessageFormatRepository<TMessage>) => ExportedMessageRepository;
4
+ export declare const useExternalHistory: <TMessage>(runtimeRef: RefObject<AssistantRuntime>, historyAdapter: ThreadHistoryAdapter | undefined, toThreadMessages: (messages: TMessage[]) => ThreadMessage[], storageFormatAdapter: MessageFormatAdapter<TMessage, any>, onSetMessages: (messages: TMessage[]) => void, toolArtifacts?: Map<string, unknown>, onToolArtifactsRestored?: () => void, toolInteractions?: Map<string, Unstable_ToolInteractionLog>, onToolInteractionsRestored?: () => void, toolApprovalResponses?: Map<string, RespondToToolApprovalOptions>, onToolApprovalResponsesRestored?: () => void) => {
5
+ isLoading: boolean;
6
+ deleteMessage: (messageId: string) => Promise<void>;
7
+ persistToolInteractions: (messageId: string) => Promise<void>;
8
+ persistToolApprovalResponses: (messageId: string) => Promise<void>;
8
9
  };
9
- //#endregion
10
- export { toExportedMessageRepository, useExternalHistory };
11
10
  //# sourceMappingURL=useExternalHistory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useExternalHistory.d.ts","names":[],"sources":["../../src/runtime/useExternalHistory.ts"],"mappings":";;;cAuBa,8BAA+B,UAC1C,mBAAmB,UAAU,eAAe,iBAC5C,UAAU,wBAAwB,cACjC;cA+BU,qBAAsB,UACjC,YAAY,UAAU,mBACtB,gBAAgB,kCAChB,mBAAmB,UAAU,eAAe,iBAC5C,sBAAsB,qBAAqB,gBAC3C,gBAAgB,UAAU;;EA8UN,gBAAA,sBAAM"}
1
+ {"version":3,"file":"useExternalHistory.d.ts","sourceRoot":"","sources":["../../src/runtime/useExternalHistory.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,oBAAoB,EAEpB,uBAAuB,EACvB,yBAAyB,EACzB,4BAA4B,EAC5B,2BAA2B,EAC5B,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EAIL,KAAK,SAAS,EAGf,MAAM,OAAO,CAAC;AAEf,eAAO,MAAM,2BAA2B,GAAI,QAAQ,EAClD,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,EAC3D,UAAU,uBAAuB,CAAC,QAAQ,CAAC,KAC1C,yBAoBF,CAAC;AAwNF,eAAO,MAAM,kBAAkB,GAAI,QAAQ,EACzC,YAAY,SAAS,CAAC,gBAAgB,CAAC,EACvC,gBAAgB,oBAAoB,GAAG,SAAS,EAChD,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,EAC3D,sBAAsB,oBAAoB,CAAC,QAAQ,EAAE,GAAG,CAAC,EACzD,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,IAAI,EAC7C,gBAAgB,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,0BAA0B,MAAM,IAAI,EACpC,mBAAmB,GAAG,CAAC,MAAM,EAAE,2BAA2B,CAAC,EAC3D,6BAA6B,MAAM,IAAI,EACvC,wBAAwB,GAAG,CAAC,MAAM,EAAE,4BAA4B,CAAC,EACjE,kCAAkC,MAAM,IAAI;;+BA+dxB,MAAM;yCA3UZ,MAAM;8CAAN,MAAM;CAyXrB,CAAC"}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
- import { useCallback, useEffect, useMemo, useRef, useState } from "@assistant-ui/tap/react-shim";
2
+ import { MessageRepository, readToolInteractionLog } from "@assistant-ui/core/internal";
3
3
  import { getExternalStoreMessages } from "@assistant-ui/core";
4
- import { MessageRepository } from "@assistant-ui/core/internal";
4
+ import { useCallback, useEffect, useMemo, useRef, useState } from "@assistant-ui/tap/react-shim";
5
5
  import { useAui } from "@assistant-ui/store";
6
6
  //#region src/runtime/useExternalHistory.ts
7
7
  const toExportedMessageRepository = (toThreadMessages, messages) => {
@@ -25,8 +25,107 @@ const toExportedMessageRepository = (toThreadMessages, messages) => {
25
25
  };
26
26
  };
27
27
  const isAwaitingToolApproval = (message) => message.status?.type === "requires-action" && message.status.reason === "tool-calls";
28
+ const isTerminalMessage = (message) => message.status === void 0 || message.status.type === "complete" || message.status.type === "incomplete";
29
+ const TOOL_ARTIFACTS_METADATA_KEY = "__aui_toolArtifacts";
30
+ const TOOL_INTERACTIONS_METADATA_KEY = "__aui_toolInteractions";
31
+ const TOOL_APPROVAL_RESPONSES_METADATA_KEY = "__aui_toolApprovalResponses";
32
+ const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
33
+ const collectToolArtifacts = (message, toolArtifacts) => {
34
+ if (!toolArtifacts) return void 0;
35
+ const entries = message.content.flatMap((part) => {
36
+ if (part.type !== "tool-call") return [];
37
+ const artifact = toolArtifacts.get(part.toolCallId);
38
+ return artifact === void 0 ? [] : [[part.toolCallId, artifact]];
39
+ });
40
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
41
+ };
42
+ const collectToolInteractions = (message, toolInteractions) => {
43
+ if (!toolInteractions) return void 0;
44
+ const entries = message.content.flatMap((part) => {
45
+ if (part.type !== "tool-call") return [];
46
+ const interactions = toolInteractions.get(part.toolCallId);
47
+ return interactions === void 0 ? [] : [[part.toolCallId, interactions]];
48
+ });
49
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
50
+ };
51
+ const collectToolApprovalResponses = (message, toolApprovalResponses) => {
52
+ if (!toolApprovalResponses) return void 0;
53
+ const entries = message.content.flatMap((part) => {
54
+ if (part.type !== "tool-call" || !part.approval) return [];
55
+ const response = toolApprovalResponses.get(part.approval.id);
56
+ if (!response) return [];
57
+ return [[part.approval.id, {
58
+ approved: response.approved,
59
+ ...response.optionId != null && { optionId: response.optionId },
60
+ ...response.text != null && { text: response.text },
61
+ ...response.reason != null && { reason: response.reason }
62
+ }]];
63
+ });
64
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
65
+ };
66
+ const addToolData = (message, toolArtifacts, toolInteractions, toolApprovalResponses) => {
67
+ if (!toolArtifacts && !toolInteractions && !toolApprovalResponses || !isRecord(message) || !Array.isArray(message.parts)) return message;
68
+ const toolCallIds = message.parts.flatMap((part) => {
69
+ if (!isRecord(part) || typeof part.toolCallId !== "string") return [];
70
+ return [part.toolCallId];
71
+ });
72
+ const artifacts = toolArtifacts ? Object.fromEntries(toolCallIds.flatMap((toolCallId) => Object.hasOwn(toolArtifacts, toolCallId) ? [[toolCallId, toolArtifacts[toolCallId]]] : [])) : void 0;
73
+ const interactions = toolInteractions ? Object.fromEntries(toolCallIds.flatMap((toolCallId) => Object.hasOwn(toolInteractions, toolCallId) ? [[toolCallId, toolInteractions[toolCallId]]] : [])) : void 0;
74
+ const approvalIds = message.parts.flatMap((part) => {
75
+ if (!isRecord(part) || !isRecord(part.approval)) return [];
76
+ const approvalId = part.approval.id;
77
+ return typeof approvalId === "string" ? [approvalId] : [];
78
+ });
79
+ const approvalResponses = toolApprovalResponses ? Object.fromEntries(approvalIds.flatMap((approvalId) => Object.hasOwn(toolApprovalResponses, approvalId) ? [[approvalId, toolApprovalResponses[approvalId]]] : [])) : void 0;
80
+ const hasArtifacts = !!artifacts && Object.keys(artifacts).length > 0;
81
+ const hasInteractions = !!interactions && Object.keys(interactions).length > 0;
82
+ const hasApprovalResponses = !!approvalResponses && Object.keys(approvalResponses).length > 0;
83
+ if (!hasArtifacts && !hasInteractions && !hasApprovalResponses) return message;
84
+ const metadata = isRecord(message.metadata) ? message.metadata : {};
85
+ return {
86
+ ...message,
87
+ metadata: {
88
+ ...metadata,
89
+ ...hasArtifacts && { [TOOL_ARTIFACTS_METADATA_KEY]: artifacts },
90
+ ...hasInteractions && { [TOOL_INTERACTIONS_METADATA_KEY]: interactions },
91
+ ...hasApprovalResponses && { [TOOL_APPROVAL_RESPONSES_METADATA_KEY]: approvalResponses }
92
+ }
93
+ };
94
+ };
95
+ const restoreToolData = (message, toolArtifacts, toolInteractions, toolApprovalResponses) => {
96
+ if (!isRecord(message) || !isRecord(message.metadata)) return message;
97
+ const metadata = message.metadata;
98
+ const hasArtifacts = Object.hasOwn(metadata, TOOL_ARTIFACTS_METADATA_KEY);
99
+ const hasInteractions = Object.hasOwn(metadata, TOOL_INTERACTIONS_METADATA_KEY);
100
+ const hasApprovalResponses = Object.hasOwn(metadata, TOOL_APPROVAL_RESPONSES_METADATA_KEY);
101
+ if (!hasArtifacts && !hasInteractions && !hasApprovalResponses) return message;
102
+ const artifacts = metadata[TOOL_ARTIFACTS_METADATA_KEY];
103
+ if (toolArtifacts && isRecord(artifacts)) for (const [toolCallId, artifact] of Object.entries(artifacts)) toolArtifacts.set(toolCallId, artifact);
104
+ const interactions = metadata[TOOL_INTERACTIONS_METADATA_KEY];
105
+ if (toolInteractions && isRecord(interactions)) for (const [toolCallId, value] of Object.entries(interactions)) {
106
+ const log = readToolInteractionLog(value);
107
+ if (log) toolInteractions.set(toolCallId, log);
108
+ }
109
+ const approvalResponses = metadata[TOOL_APPROVAL_RESPONSES_METADATA_KEY];
110
+ if (toolApprovalResponses && isRecord(approvalResponses)) for (const [approvalId, value] of Object.entries(approvalResponses)) {
111
+ if (!isRecord(value) || typeof value.approved !== "boolean") continue;
112
+ toolApprovalResponses.set(approvalId, {
113
+ approvalId,
114
+ approved: value.approved,
115
+ ...typeof value.optionId === "string" && { optionId: value.optionId },
116
+ ...typeof value.text === "string" && { text: value.text },
117
+ ...typeof value.reason === "string" && { reason: value.reason }
118
+ });
119
+ }
120
+ const { [TOOL_ARTIFACTS_METADATA_KEY]: _, [TOOL_INTERACTIONS_METADATA_KEY]: __, [TOOL_APPROVAL_RESPONSES_METADATA_KEY]: ___, ...restMetadata } = metadata;
121
+ const { metadata: _metadata, ...restMessage } = message;
122
+ return Object.keys(restMetadata).length === 0 ? restMessage : {
123
+ ...restMessage,
124
+ metadata: restMetadata
125
+ };
126
+ };
28
127
  const encodeContent = (storageFormatAdapter, item) => JSON.stringify(storageFormatAdapter.encode(item));
29
- const useExternalHistory = (runtimeRef, historyAdapter, toThreadMessages, storageFormatAdapter, onSetMessages) => {
128
+ const useExternalHistory = (runtimeRef, historyAdapter, toThreadMessages, storageFormatAdapter, onSetMessages, toolArtifacts, onToolArtifactsRestored, toolInteractions, onToolInteractionsRestored, toolApprovalResponses, onToolApprovalResponsesRestored) => {
30
129
  const loadedRef = useRef(false);
31
130
  const [itemEpoch, setItemEpoch] = useState(0);
32
131
  const aui = useAui();
@@ -50,12 +149,26 @@ const useExternalHistory = (runtimeRef, historyAdapter, toThreadMessages, storag
50
149
  const loadHistory = async () => {
51
150
  try {
52
151
  const repo = await formatAdapter.load();
53
- if (repo && repo.messages.length > 0) {
54
- for (const m of repo.messages) persistedInnerMessages.current.set(storageFormatAdapter.getId(m.message), {
55
- source: m.message,
152
+ toolArtifacts?.clear();
153
+ toolInteractions?.clear();
154
+ toolApprovalResponses?.clear();
155
+ const restoredMessages = repo?.messages.map((item) => ({
156
+ ...item,
157
+ message: restoreToolData(item.message, toolArtifacts, toolInteractions, toolApprovalResponses)
158
+ })) ?? [];
159
+ onToolArtifactsRestored?.();
160
+ onToolInteractionsRestored?.();
161
+ onToolApprovalResponsesRestored?.();
162
+ if (repo && restoredMessages.length > 0) {
163
+ const restoredRepo = {
164
+ ...repo,
165
+ messages: restoredMessages
166
+ };
167
+ for (const [index, m] of repo.messages.entries()) persistedInnerMessages.current.set(storageFormatAdapter.getId(m.message), {
168
+ source: restoredMessages[index].message,
56
169
  content: encodeContent(storageFormatAdapter, m)
57
170
  });
58
- const converted = toExportedMessageRepository(toThreadMessages, repo);
171
+ const converted = toExportedMessageRepository(toThreadMessages, restoredRepo);
59
172
  runtimeRef.current.thread.import(converted);
60
173
  const tempRepo = new MessageRepository();
61
174
  tempRepo.import(converted);
@@ -95,7 +208,13 @@ const useExternalHistory = (runtimeRef, historyAdapter, toThreadMessages, storag
95
208
  optionalThreadListItem,
96
209
  aui,
97
210
  itemEpoch,
98
- storageFormatAdapter
211
+ storageFormatAdapter,
212
+ toolArtifacts,
213
+ onToolArtifactsRestored,
214
+ toolInteractions,
215
+ onToolInteractionsRestored,
216
+ toolApprovalResponses,
217
+ onToolApprovalResponsesRestored
99
218
  ]);
100
219
  const runStartRef = useRef(null);
101
220
  const persistTimerRef = useRef(null);
@@ -103,9 +222,55 @@ const useExternalHistory = (runtimeRef, historyAdapter, toThreadMessages, storag
103
222
  const stepBoundariesRef = useRef([]);
104
223
  const wasRunningRef = useRef(false);
105
224
  const toolCallCountRef = useRef(0);
225
+ const persistToolInteractions = useCallback((messageId) => {
226
+ const persistence = persistInFlightRef.current.then(async () => {
227
+ if (!formatAdapter?.update) return;
228
+ const messages_0 = runtimeRef.current.thread.getState().messages;
229
+ const message = messages_0.find((item_0) => item_0.id === messageId || getExternalStoreMessages(item_0).some((innerMessage) => storageFormatAdapter.getId(innerMessage) === messageId));
230
+ if (!message) return;
231
+ const previousMessages = messages_0.slice(0, messages_0.indexOf(message)).flatMap(getExternalStoreMessages);
232
+ let parentId = previousMessages.at(-1) ? storageFormatAdapter.getId(previousMessages.at(-1)) : null;
233
+ const storedToolArtifacts = collectToolArtifacts(message, toolArtifacts);
234
+ const storedToolInteractions = collectToolInteractions(message, toolInteractions);
235
+ const storedToolApprovalResponses = collectToolApprovalResponses(message, toolApprovalResponses);
236
+ for (const innerMessage_0 of getExternalStoreMessages(message)) {
237
+ const item_1 = {
238
+ parentId,
239
+ message: addToolData(innerMessage_0, storedToolArtifacts, storedToolInteractions, storedToolApprovalResponses)
240
+ };
241
+ const innerId = storageFormatAdapter.getId(item_1.message);
242
+ const persisted = persistedInnerMessages.current.get(innerId);
243
+ if (persisted) {
244
+ const content = encodeContent(storageFormatAdapter, item_1);
245
+ if (content === persisted.content) persisted.source = item_1.message;
246
+ else {
247
+ await formatAdapter.update(item_1, innerId);
248
+ persistedInnerMessages.current.set(innerId, {
249
+ source: item_1.message,
250
+ content
251
+ });
252
+ }
253
+ }
254
+ parentId = innerId;
255
+ }
256
+ });
257
+ persistInFlightRef.current = persistence.catch(() => {});
258
+ return persistence.catch((error_0) => {
259
+ console.error("Failed to persist tool data:", error_0);
260
+ });
261
+ }, [
262
+ formatAdapter,
263
+ runtimeRef,
264
+ storageFormatAdapter,
265
+ toolArtifacts,
266
+ toolInteractions,
267
+ toolApprovalResponses
268
+ ]);
106
269
  useEffect(() => {
107
270
  if (!formatAdapter) return;
108
271
  const adapter = formatAdapter;
272
+ const idleScheduledInnerIds = /* @__PURE__ */ new Set();
273
+ const unpersistedInnerIds = (message_0) => isTerminalMessage(message_0) ? getExternalStoreMessages(message_0).map((innerMessage_1) => storageFormatAdapter.getId(innerMessage_1)).filter((innerId_0) => !persistedInnerMessages.current.has(innerId_0)) : [];
109
274
  const unsubscribe = runtimeRef.current.thread.subscribe(() => {
110
275
  const threadState_0 = runtimeRef.current.thread.getState();
111
276
  const { isRunning } = threadState_0;
@@ -134,8 +299,13 @@ const useExternalHistory = (runtimeRef, historyAdapter, toThreadMessages, storag
134
299
  }
135
300
  return;
136
301
  }
137
- if (!wasRunning) return;
138
- if (runStartRef.current != null) stepBoundariesRef.current.push(Date.now() - runStartRef.current);
302
+ if (wasRunning) {
303
+ if (runStartRef.current != null) stepBoundariesRef.current.push(Date.now() - runStartRef.current);
304
+ } else {
305
+ const pending = threadState_0.messages.flatMap(unpersistedInnerIds);
306
+ if (pending.every((innerId_1) => idleScheduledInnerIds.has(innerId_1))) return;
307
+ for (const innerId_2 of pending) idleScheduledInnerIds.add(innerId_2);
308
+ }
139
309
  if (persistTimerRef.current) clearTimeout(persistTimerRef.current);
140
310
  persistTimerRef.current = setTimeout(() => {
141
311
  persistSettled(false);
@@ -180,58 +350,61 @@ const useExternalHistory = (runtimeRef, historyAdapter, toThreadMessages, storag
180
350
  ...stepTimestamps != null ? { stepTimestamps } : void 0
181
351
  };
182
352
  persistInFlightRef.current = persistInFlightRef.current.then(async () => {
183
- const { messages: messages_0 } = latest;
353
+ const { messages: messages_1 } = latest;
184
354
  let lastInnerMessageId = null;
185
355
  const getLastInnerId = (msgs) => msgs.length > 0 ? storageFormatAdapter.getId(msgs.at(-1)) : null;
186
- const toBatchItems = (msgs_0) => msgs_0.map((msg, idx) => ({
356
+ const toBatchItems = (msgs_0, toolArtifacts_0, toolInteractions_0, toolApprovalResponses_0) => msgs_0.map((msg, idx) => ({
187
357
  parentId: idx === 0 ? lastInnerMessageId : storageFormatAdapter.getId(msgs_0[idx - 1]),
188
- message: msg
358
+ message: addToolData(msg, toolArtifacts_0, toolInteractions_0, toolApprovalResponses_0)
189
359
  }));
190
- for (const message of messages_0) {
191
- const innerMessages = getExternalStoreMessages(message);
192
- const isTerminal = message.status === void 0 || message.status.type === "complete" || message.status.type === "incomplete";
193
- const isAwaitingToolCalls = isAwaitingToolApproval(message);
360
+ for (const message_1 of messages_1) {
361
+ const innerMessages = getExternalStoreMessages(message_1);
362
+ const isTerminal = isTerminalMessage(message_1);
363
+ const isAwaitingToolCalls = isAwaitingToolApproval(message_1);
194
364
  if (!(isTerminal || isAwaitingToolCalls && adapter.update !== void 0)) {
195
365
  lastInnerMessageId = getLastInnerId(innerMessages) ?? lastInnerMessageId;
196
366
  continue;
197
367
  }
198
- if (!historyIds.current.has(message.id)) {
199
- historyIds.current.add(message.id);
200
- deferredTelemetryIds.current.add(message.id);
368
+ if (!historyIds.current.has(message_1.id)) {
369
+ historyIds.current.add(message_1.id);
370
+ deferredTelemetryIds.current.add(message_1.id);
201
371
  }
202
- const batchItems = toBatchItems(innerMessages);
203
- for (const item of batchItems) {
204
- const innerId = storageFormatAdapter.getId(item.message);
205
- const persisted = persistedInnerMessages.current.get(innerId);
206
- if (!persisted) {
207
- await adapter.append(item);
208
- persistedInnerMessages.current.set(innerId, {
209
- source: item.message,
210
- content: encodeContent(storageFormatAdapter, item)
372
+ const batchItems = toBatchItems(innerMessages, collectToolArtifacts(message_1, toolArtifacts), collectToolInteractions(message_1, toolInteractions), collectToolApprovalResponses(message_1, toolApprovalResponses));
373
+ for (const item_2 of batchItems) {
374
+ const innerId_3 = storageFormatAdapter.getId(item_2.message);
375
+ const persisted_0 = persistedInnerMessages.current.get(innerId_3);
376
+ if (!persisted_0) {
377
+ await adapter.append(item_2);
378
+ persistedInnerMessages.current.set(innerId_3, {
379
+ source: item_2.message,
380
+ content: encodeContent(storageFormatAdapter, item_2)
211
381
  });
212
- } else if (persisted.source !== item.message && durationMs !== void 0 && adapter.update) {
213
- const content = encodeContent(storageFormatAdapter, item);
214
- if (content === persisted.content) persisted.source = item.message;
382
+ } else if (persisted_0.source !== item_2.message && durationMs !== void 0 && adapter.update) {
383
+ const content_0 = encodeContent(storageFormatAdapter, item_2);
384
+ if (content_0 === persisted_0.content) persisted_0.source = item_2.message;
215
385
  else try {
216
- await adapter.update(item, innerId);
217
- persistedInnerMessages.current.set(innerId, {
218
- source: item.message,
219
- content
386
+ await adapter.update(item_2, innerId_3);
387
+ persistedInnerMessages.current.set(innerId_3, {
388
+ source: item_2.message,
389
+ content: content_0
220
390
  });
221
391
  } catch {}
222
392
  }
223
393
  }
224
394
  lastInnerMessageId = getLastInnerId(innerMessages) ?? lastInnerMessageId;
225
- if (deferredTelemetryIds.current.has(message.id) && isTerminal) {
226
- deferredTelemetryIds.current.delete(message.id);
227
- adapter.reportTelemetry?.(batchItems, {
395
+ if (deferredTelemetryIds.current.has(message_1.id) && isTerminal) {
396
+ deferredTelemetryIds.current.delete(message_1.id);
397
+ adapter.reportTelemetry?.(batchItems.map((item_3, index_0) => ({
398
+ ...item_3,
399
+ message: innerMessages[index_0]
400
+ })), {
228
401
  ...telemetryOptions,
229
- message
402
+ message: message_1
230
403
  });
231
404
  }
232
405
  }
233
- }).catch((error_0) => {
234
- console.error("Failed to persist message history:", error_0);
406
+ }).catch((error_1) => {
407
+ console.error("Failed to persist message history:", error_1);
235
408
  });
236
409
  }
237
410
  return () => {
@@ -245,31 +418,34 @@ const useExternalHistory = (runtimeRef, historyAdapter, toThreadMessages, storag
245
418
  }, [
246
419
  formatAdapter,
247
420
  storageFormatAdapter,
248
- runtimeRef
421
+ runtimeRef,
422
+ toolArtifacts,
423
+ toolInteractions,
424
+ toolApprovalResponses
249
425
  ]);
250
426
  return {
251
427
  isLoading,
252
- deleteMessage: useCallback(async (messageId) => {
428
+ deleteMessage: useCallback(async (messageId_0) => {
253
429
  const deleteMessages = formatAdapter?.delete?.bind(formatAdapter);
254
430
  if (!deleteMessages) return;
255
- const messages_1 = runtimeRef.current.thread.getState().messages;
256
- const messageIndex = messages_1.findIndex((m_2) => m_2.id === messageId);
431
+ const messages_2 = runtimeRef.current.thread.getState().messages;
432
+ const messageIndex = messages_2.findIndex((m_2) => m_2.id === messageId_0);
257
433
  if (messageIndex === -1) return;
258
- const previousInnerMessages = messages_1.slice(0, messageIndex).flatMap(getExternalStoreMessages);
259
- let parentId = previousInnerMessages.at(-1) ? storageFormatAdapter.getId(previousInnerMessages.at(-1)) : null;
260
- const itemsToDelete = getExternalStoreMessages(messages_1[messageIndex]).map((message_0) => {
261
- const item_0 = {
262
- parentId,
263
- message: message_0
434
+ const previousInnerMessages = messages_2.slice(0, messageIndex).flatMap(getExternalStoreMessages);
435
+ let parentId_0 = previousInnerMessages.at(-1) ? storageFormatAdapter.getId(previousInnerMessages.at(-1)) : null;
436
+ const itemsToDelete = getExternalStoreMessages(messages_2[messageIndex]).map((message_2) => {
437
+ const item_4 = {
438
+ parentId: parentId_0,
439
+ message: message_2
264
440
  };
265
- parentId = storageFormatAdapter.getId(message_0);
266
- return item_0;
441
+ parentId_0 = storageFormatAdapter.getId(message_2);
442
+ return item_4;
267
443
  });
268
444
  const deletion = persistInFlightRef.current.then(async () => {
269
445
  await deleteMessages(itemsToDelete);
270
- historyIds.current.delete(messageId);
271
- deferredTelemetryIds.current.delete(messageId);
272
- for (const item_1 of itemsToDelete) persistedInnerMessages.current.delete(storageFormatAdapter.getId(item_1.message));
446
+ historyIds.current.delete(messageId_0);
447
+ deferredTelemetryIds.current.delete(messageId_0);
448
+ for (const item_5 of itemsToDelete) persistedInnerMessages.current.delete(storageFormatAdapter.getId(item_5.message));
273
449
  });
274
450
  persistInFlightRef.current = deletion.catch(() => {});
275
451
  await deletion;
@@ -277,7 +453,9 @@ const useExternalHistory = (runtimeRef, historyAdapter, toThreadMessages, storag
277
453
  formatAdapter,
278
454
  runtimeRef,
279
455
  storageFormatAdapter
280
- ])
456
+ ]),
457
+ persistToolInteractions,
458
+ persistToolApprovalResponses: persistToolInteractions
281
459
  };
282
460
  };
283
461
  //#endregion