@assistant-ui/ai-sdk 0.0.2 → 0.0.4

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 (54) hide show
  1. package/README.md +1 -1
  2. package/dist/converters/convertMessage.d.ts.map +1 -1
  3. package/dist/converters/convertMessage.js +23 -2
  4. package/dist/converters/convertMessage.js.map +1 -1
  5. package/dist/converters/toCreateMessage.d.ts.map +1 -1
  6. package/dist/converters/toCreateMessage.js +6 -2
  7. package/dist/converters/toCreateMessage.js.map +1 -1
  8. package/dist/model-context/injectInteractableContext.d.ts +1 -1
  9. package/dist/model-context/injectInteractableContext.js +1 -1
  10. package/dist/model-context/injectInteractableContext.js.map +1 -1
  11. package/dist/model-context/injectQuoteContext.d.ts +1 -1
  12. package/dist/model-context/injectQuoteContext.js +1 -1
  13. package/dist/model-context/injectQuoteContext.js.map +1 -1
  14. package/dist/runtime/AISDKChat.js +1 -1
  15. package/dist/runtime/AISDKThreads.d.ts +10 -8
  16. package/dist/runtime/AISDKThreads.d.ts.map +1 -1
  17. package/dist/runtime/AISDKThreads.js +34 -27
  18. package/dist/runtime/AISDKThreads.js.map +1 -1
  19. package/dist/runtime/useAISDKRuntime.d.ts +18 -3
  20. package/dist/runtime/useAISDKRuntime.d.ts.map +1 -1
  21. package/dist/runtime/useAISDKRuntime.js +199 -73
  22. package/dist/runtime/useAISDKRuntime.js.map +1 -1
  23. package/dist/runtime/useChatRuntime.js +1 -1
  24. package/dist/runtime/useChatThread.d.ts +14 -1
  25. package/dist/runtime/useChatThread.d.ts.map +1 -1
  26. package/dist/runtime/useChatThread.js +11 -4
  27. package/dist/runtime/useChatThread.js.map +1 -1
  28. package/dist/runtime/useExternalHistory.d.ts.map +1 -1
  29. package/dist/runtime/useExternalHistory.js +52 -47
  30. package/dist/runtime/useExternalHistory.js.map +1 -1
  31. package/dist/runtime/useResourceCleanup.js +1 -1
  32. package/dist/usage.d.ts +1 -2
  33. package/dist/usage.d.ts.map +1 -1
  34. package/dist/usage.js +5 -7
  35. package/dist/usage.js.map +1 -1
  36. package/package.json +12 -12
  37. package/src/converters/convertMessage.test.ts +22 -0
  38. package/src/converters/convertMessage.ts +26 -2
  39. package/src/converters/toCreateMessage.ts +6 -5
  40. package/src/model-context/injectInteractableContext.ts +1 -1
  41. package/src/model-context/injectQuoteContext.ts +1 -1
  42. package/src/runtime/AISDKThreads.cloud.test.ts +11 -1
  43. package/src/runtime/AISDKThreads.test.ts +102 -0
  44. package/src/runtime/AISDKThreads.ts +16 -9
  45. package/src/runtime/useAISDKRuntime.denied-tool.test.tsx +122 -0
  46. package/src/runtime/useAISDKRuntime.test.ts +479 -10
  47. package/src/runtime/useAISDKRuntime.ts +199 -55
  48. package/src/runtime/useChatRuntime.integration.test.tsx +46 -0
  49. package/src/runtime/useChatRuntime.test.ts +16 -0
  50. package/src/runtime/useChatThread.ts +26 -0
  51. package/src/runtime/useExternalHistory.test.ts +143 -1
  52. package/src/runtime/useExternalHistory.ts +53 -56
  53. package/src/usage.test.ts +26 -8
  54. package/src/usage.ts +4 -9
@@ -2,9 +2,11 @@ import { AISDKRuntimeAdapter, CustomToCreateMessageFunction } from "./useAISDKRu
2
2
  import { InitializableThreadListItem } from "../transport/AssistantChatTransport.js";
3
3
  import { ChatInit, ChatTransport } from "ai";
4
4
  import { AssistantRuntime, ExternalStoreSharedOptions } from "@assistant-ui/core";
5
+ import { MessageRepository } from "@assistant-ui/core/internal";
5
6
  import { Chat, UIMessage as UIMessage$1 } from "@ai-sdk/react";
6
7
  //#region src/runtime/useChatThread.d.ts
7
8
  type ChatThreadOptions<UI_MESSAGE extends UIMessage$1 = UIMessage$1> = ChatInit<UI_MESSAGE> & ExternalStoreSharedOptions & {
9
+ throttle?: number | undefined;
8
10
  adapters?: AISDKRuntimeAdapter["adapters"] | undefined;
9
11
  toCreateMessage?: CustomToCreateMessageFunction;
10
12
  onResume?: AISDKRuntimeAdapter["onResume"];
@@ -17,6 +19,8 @@ type ChatThreadOptions<UI_MESSAGE extends UIMessage$1 = UIMessage$1> = ChatInit<
17
19
  */
18
20
  onResumeError?: ((error: unknown) => void) | undefined;
19
21
  joinStrategy?: AISDKRuntimeAdapter["joinStrategy"];
22
+ messageRepository?: AISDKRuntimeAdapter<UI_MESSAGE>["messageRepository"];
23
+ unstable_onBranchChange?: AISDKRuntimeAdapter["unstable_onBranchChange"];
20
24
  };
21
25
  type ChatThreadEnvironment<UI_MESSAGE extends UIMessage$1 = UIMessage$1> = {
22
26
  id: string;
@@ -29,6 +33,12 @@ type ChatThreadEnvironment<UI_MESSAGE extends UIMessage$1 = UIMessage$1> = {
29
33
  * from the instance.
30
34
  */
31
35
  chat?: Chat<UI_MESSAGE> | undefined;
36
+ /**
37
+ * An externally owned per-thread message repository. Hosts that route
38
+ * multiple threads through one mounting pass a distinct instance per
39
+ * thread so histories and branches stay isolated.
40
+ */
41
+ messageRepositoryInstance?: MessageRepository | undefined;
32
42
  };
33
43
  /**
34
44
  * Splits the combined options into the assistant-ui side and the `ChatInit`
@@ -48,6 +58,7 @@ declare const splitChatThreadOptions: <UI_MESSAGE extends UIMessage$1>(options:
48
58
  suggestion?: import("@assistant-ui/core").SuggestionAdapter | undefined;
49
59
  }) | undefined;
50
60
  transport: ChatTransport<UI_MESSAGE> | undefined;
61
+ throttle: number | undefined;
51
62
  toCreateMessage: CustomToCreateMessageFunction | undefined;
52
63
  onResume: ((config: import("@assistant-ui/core").ResumeRunConfig) => Promise<void>) | undefined;
53
64
  onResumeToolCall: ((options: {
@@ -56,9 +67,11 @@ declare const splitChatThreadOptions: <UI_MESSAGE extends UIMessage$1>(options:
56
67
  }) => void) | undefined;
57
68
  onResumeError: ((error: unknown) => void) | undefined;
58
69
  joinStrategy: import("@assistant-ui/core/store").JoinStrategy | undefined;
70
+ messageRepository: import("@assistant-ui/core").MessageFormatRepository<UI_MESSAGE> | undefined;
71
+ unstable_onBranchChange: ((event: import("@assistant-ui/core").ExternalStoreBranchChange) => void) | undefined;
59
72
  chatInit: {
60
73
  id?: string;
61
- messageMetadataSchema?: import("ai").FlexibleSchema<UI_MESSAGE extends UIMessage$1<infer METADATA, import("ai").UIDataTypes, import("ai").UITools> ? METADATA : unknown>;
74
+ messageMetadataSchema?: import("ai").FlexibleSchema<UI_MESSAGE["metadata"]>;
62
75
  dataPartSchemas?: (UI_MESSAGE extends UIMessage$1<unknown, infer DATA_TYPES extends import("ai").UIDataTypes, import("ai").UITools> ? DATA_TYPES : import("ai").UIDataTypes) extends (infer T extends import("ai").UIDataTypes) ? { [K in keyof T]: import("ai").FlexibleSchema<T[K]>; } : never;
63
76
  messages?: UI_MESSAGE[];
64
77
  generateId?: import("ai").IdGenerator;
@@ -1 +1 @@
1
- {"version":3,"file":"useChatThread.d.ts","names":[],"sources":["../../src/runtime/useChatThread.ts"],"mappings":";;;;;;KA+BY,kBAAkB,mBAAmB,cAAY,eAC3D,SAAS,cACP;EACE,WAAW;EACX,kBAAkB;EAClB,WAAW;EACX,mBAAmB;;;;;;;EAOnB,kBAAkB;EAClB,eAAe;;KAGT,sBAAsB,mBAAmB,cAAY;EAC/D;EACA;EACA,yBAAyB;EACzB;;;;;;EAMA,OAAO,KAAK;;;;;;;cA4DD,yBAA0B,mBAAmB,aACxD,SAAS,kBAAkB;;;;;;;;;;;;;;;;;;;EA3EE,iBAAA;;;;;;;;;;;;;;;;;cA4GlB,gBAAiB,mBAAmB,cAAY,aAC3D,SAAS,kBAAkB,yBAC3B,KAAK,sBAAsB,gBAC1B"}
1
+ {"version":3,"file":"useChatThread.d.ts","names":[],"sources":["../../src/runtime/useChatThread.ts"],"mappings":";;;;;;;KAgCY,kBAAkB,mBAAmB,cAAY,eAC3D,SAAS,cACP;EACE;EACA,WAAW;EACX,kBAAkB;EAClB,WAAW;EACX,mBAAmB;;;;;;;EAOnB,kBAAkB;EAClB,eAAe;EACf,oBAAoB,oBAAoB;EACxC,0BAA0B;;KAGpB,sBAAsB,mBAAmB,cAAY;EAC/D;EACA;EACA,yBAAyB;EACzB;;;;;;EAMA,OAAO,KAAK;;;;;;EAMZ,4BAA4B;;;;;;;cA4DjB,yBAA0B,mBAAmB,aACxD,SAAS,kBAAkB;;;;;;;;;;;;;;;;;;;;EAnFE,iBAAA;;;;;;;;;;;;;;;;;;;cA0HlB,gBAAiB,mBAAmB,cAAY,aAC3D,SAAS,kBAAkB,yBAC3B,KAAK,sBAAsB,gBAC1B"}
@@ -39,21 +39,24 @@ const getResumedStreamIds = (storage) => {
39
39
  * same fields `useChat` would.
40
40
  */
41
41
  const splitChatThreadOptions = (options) => {
42
- const { adapters, transport, toCreateMessage, isDisabled: _isDisabled, isSendDisabled: _isSendDisabled, unstable_capabilities: _unstable_capabilities, suggestions: _suggestions, onResume, onResumeToolCall, onResumeError, joinStrategy, ...chatInit } = 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 ?? {};
43
43
  return {
44
44
  adapters,
45
45
  transport,
46
+ throttle,
46
47
  toCreateMessage,
47
48
  onResume,
48
49
  onResumeToolCall,
49
50
  onResumeError,
50
51
  joinStrategy,
52
+ messageRepository,
53
+ unstable_onBranchChange,
51
54
  chatInit
52
55
  };
53
56
  };
54
57
  const useChatThread = (options, env) => {
55
- const { adapters, transport: transportOptions, toCreateMessage, onResume, onResumeToolCall, onResumeError, joinStrategy, chatInit: chatOptions } = splitChatThreadOptions(options);
56
- const { id, isMainThread, getThreadListItem, stopOnClientDestroy = false, chat: externalChat } = 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 = false, chat: externalChat, messageRepositoryInstance } = env;
57
60
  const defaultTransport = useMemo(() => new AssistantChatTransport(), []);
58
61
  const sourceTransport = transportOptions ?? defaultTransport;
59
62
  const transport = useDynamicChatTransport(sourceTransport);
@@ -61,6 +64,7 @@ const useChatThread = (options, env) => {
61
64
  ...chatOptions,
62
65
  id,
63
66
  transport,
67
+ ...throttle !== void 0 && { throttle },
64
68
  ...externalChat !== void 0 && { chat: externalChat }
65
69
  });
66
70
  useResourceCleanup(stopOnClientDestroy, () => {
@@ -72,7 +76,10 @@ const useChatThread = (options, env) => {
72
76
  ...toCreateMessage && { toCreateMessage },
73
77
  ...onResume && { onResume },
74
78
  ...onResumeToolCall && { onResumeToolCall },
75
- ...joinStrategy && { joinStrategy }
79
+ ...joinStrategy && { joinStrategy },
80
+ ...messageRepository && { messageRepository },
81
+ ...messageRepositoryInstance && { unstable_messageRepositoryInstance: messageRepositoryInstance },
82
+ ...unstable_onBranchChange && { unstable_onBranchChange }
76
83
  });
77
84
  if (sourceTransport instanceof AssistantChatTransport) {
78
85
  sourceTransport.setRuntime(runtime);
@@ -1 +1 @@
1
- {"version":3,"file":"useChatThread.js","names":["useChat","Chat","UIMessage","pickExternalStoreSharedOptions","AssistantRuntime","ExternalStoreSharedOptions","useAISDKRuntime","AISDKRuntimeAdapter","CustomToCreateMessageFunction","ChatInit","ChatTransport","AssistantChatTransport","InitializableThreadListItem","AssistantChatResumableOptions","ResumableClientStorage","useCallback","useEffect","useMemo","useRef","useSyncExternalStore","useResourceCleanup","ChatThreadOptions","UI_MESSAGE","adapters","toCreateMessage","onResume","onResumeToolCall","onResumeError","error","joinStrategy","ChatThreadEnvironment","id","isMainThread","getThreadListItem","stopOnClientDestroy","chat","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","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 {\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 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 };\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\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 toCreateMessage,\n isDisabled: _isDisabled,\n isSendDisabled: _isSendDisabled,\n unstable_capabilities: _unstable_capabilities,\n suggestions: _suggestions,\n onResume,\n onResumeToolCall,\n onResumeError,\n joinStrategy,\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 toCreateMessage,\n onResume,\n onResumeToolCall,\n onResumeError,\n joinStrategy,\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 toCreateMessage,\n onResume,\n onResumeToolCall,\n onResumeError,\n joinStrategy,\n chatInit: chatOptions,\n } = splitChatThreadOptions(options);\n\n const {\n id,\n isMainThread,\n getThreadListItem,\n stopOnClientDestroy = false,\n chat: externalChat,\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 ...(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 });\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":["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 = false,\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 +1 @@
1
- {"version":3,"file":"useExternalHistory.d.ts","names":[],"sources":["../../src/runtime/useExternalHistory.ts"],"mappings":";;;cAsBa,8BAA+B,UAC1C,mBAAmB,UAAU,eAAe,iBAC5C,UAAU,wBAAwB,cACjC;cAoCU,qBAAsB,UACjC,YAAY,UAAU,mBACtB,gBAAgB,kCAChB,mBAAmB,UAAU,eAAe,iBAC5C,sBAAsB,qBAAqB,gBAC3C,gBAAgB,UAAU;;EAsUN,gBAAA,sBAAM"}
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;;EAwUN,gBAAA,sBAAM"}
@@ -25,7 +25,7 @@ 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 snapshotExternalMessages = (messages) => new Map(messages.map((message) => [message.id, [...getExternalStoreMessages(message)]]));
28
+ const encodeContent = (storageFormatAdapter, item) => JSON.stringify(storageFormatAdapter.encode(item));
29
29
  const useExternalHistory = (runtimeRef, historyAdapter, toThreadMessages, storageFormatAdapter, onSetMessages) => {
30
30
  const loadedRef = useRef(false);
31
31
  const [itemEpoch, setItemEpoch] = useState(0);
@@ -33,9 +33,8 @@ const useExternalHistory = (runtimeRef, historyAdapter, toThreadMessages, storag
33
33
  const optionalThreadListItem = useCallback(() => aui.threadListItem.source ? aui.threadListItem : null, [aui]);
34
34
  const [hasLoaded, setHasLoaded] = useState(false);
35
35
  const historyIds = useRef(/* @__PURE__ */ new Set());
36
- const persistedInnerIds = useRef(/* @__PURE__ */ new Set());
37
36
  const deferredTelemetryIds = useRef(/* @__PURE__ */ new Set());
38
- const persistedExternalMessages = useRef(/* @__PURE__ */ new Map());
37
+ const persistedInnerMessages = useRef(/* @__PURE__ */ new Map());
39
38
  const onSetMessagesRef = useRef(onSetMessages);
40
39
  useEffect(() => {
41
40
  onSetMessagesRef.current = onSetMessages;
@@ -52,7 +51,10 @@ const useExternalHistory = (runtimeRef, historyAdapter, toThreadMessages, storag
52
51
  try {
53
52
  const repo = await formatAdapter.load();
54
53
  if (repo && repo.messages.length > 0) {
55
- for (const m of repo.messages) persistedInnerIds.current.add(storageFormatAdapter.getId(m.message));
54
+ for (const m of repo.messages) persistedInnerMessages.current.set(storageFormatAdapter.getId(m.message), {
55
+ source: m.message,
56
+ content: encodeContent(storageFormatAdapter, m)
57
+ });
56
58
  const converted = toExportedMessageRepository(toThreadMessages, repo);
57
59
  runtimeRef.current.thread.import(converted);
58
60
  const tempRepo = new MessageRepository();
@@ -64,7 +66,6 @@ const useExternalHistory = (runtimeRef, historyAdapter, toThreadMessages, storag
64
66
  historyIds.current.add(m_0.message.id);
65
67
  if (isAwaitingToolApproval(m_0.message)) deferredTelemetryIds.current.add(m_0.message.id);
66
68
  }
67
- persistedExternalMessages.current = snapshotExternalMessages(converted.messages.map((m_1) => m_1.message));
68
69
  }
69
70
  } catch (error) {
70
71
  console.error("Failed to load message history:", error);
@@ -140,6 +141,16 @@ const useExternalHistory = (runtimeRef, historyAdapter, toThreadMessages, storag
140
141
  persistSettled(false);
141
142
  }, 0);
142
143
  });
144
+ const initialThreadState = runtimeRef.current.thread.getState();
145
+ wasRunningRef.current = initialThreadState.isRunning;
146
+ if (initialThreadState.isRunning) {
147
+ if (runStartRef.current == null) {
148
+ runStartRef.current = Date.now();
149
+ stepBoundariesRef.current = [];
150
+ toolCallCountRef.current = 0;
151
+ adapter.pin?.();
152
+ }
153
+ } else if (initialThreadState.messages.length > 0) persistSettled(true);
143
154
  function persistSettled(ignoreRunning) {
144
155
  persistTimerRef.current = null;
145
156
  const latest = runtimeRef.current.thread.getState();
@@ -147,7 +158,7 @@ const useExternalHistory = (runtimeRef, historyAdapter, toThreadMessages, storag
147
158
  const boundaries = stepBoundariesRef.current;
148
159
  const durationMs = boundaries.length > 0 ? boundaries.at(-1) : void 0;
149
160
  if (boundaries.length === 1 && durationMs != null) {
150
- const lastAssistant = latest.messages.findLast((m_2) => m_2.role === "assistant");
161
+ const lastAssistant = latest.messages.findLast((m_1) => m_1.role === "assistant");
151
162
  if (lastAssistant) {
152
163
  const tcCount = lastAssistant.content.filter((p_0) => p_0.type === "tool-call").length;
153
164
  if (tcCount > 0) {
@@ -169,58 +180,53 @@ const useExternalHistory = (runtimeRef, historyAdapter, toThreadMessages, storag
169
180
  ...stepTimestamps != null ? { stepTimestamps } : void 0
170
181
  };
171
182
  persistInFlightRef.current = persistInFlightRef.current.then(async () => {
172
- const changedRunMessageIds = /* @__PURE__ */ new Set();
173
- for (const message of latest.messages) {
174
- const externalMessages = getExternalStoreMessages(message);
175
- const previous = persistedExternalMessages.current.get(message.id);
176
- if (previous === void 0 || previous.length !== externalMessages.length || externalMessages.some((item, index) => item !== previous[index])) changedRunMessageIds.add(message.id);
177
- }
178
183
  const { messages: messages_0 } = latest;
179
184
  let lastInnerMessageId = null;
180
- const failedUpdateIds = /* @__PURE__ */ new Set();
181
185
  const getLastInnerId = (msgs) => msgs.length > 0 ? storageFormatAdapter.getId(msgs.at(-1)) : null;
182
186
  const toBatchItems = (msgs_0) => msgs_0.map((msg, idx) => ({
183
187
  parentId: idx === 0 ? lastInnerMessageId : storageFormatAdapter.getId(msgs_0[idx - 1]),
184
188
  message: msg
185
189
  }));
186
- for (const message_0 of messages_0) {
187
- const innerMessages = getExternalStoreMessages(message_0);
188
- const isTerminal = message_0.status === void 0 || message_0.status.type === "complete" || message_0.status.type === "incomplete";
189
- const isAwaitingToolCalls = isAwaitingToolApproval(message_0);
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);
190
194
  if (!(isTerminal || isAwaitingToolCalls && adapter.update !== void 0)) {
191
195
  lastInnerMessageId = getLastInnerId(innerMessages) ?? lastInnerMessageId;
192
196
  continue;
193
197
  }
194
- const isPersistedMessage = historyIds.current.has(message_0.id);
195
- if (isPersistedMessage && !changedRunMessageIds.has(message_0.id)) {
196
- lastInnerMessageId = getLastInnerId(innerMessages) ?? lastInnerMessageId;
197
- continue;
198
- }
199
- if (!isPersistedMessage) {
200
- historyIds.current.add(message_0.id);
201
- deferredTelemetryIds.current.add(message_0.id);
198
+ if (!historyIds.current.has(message.id)) {
199
+ historyIds.current.add(message.id);
200
+ deferredTelemetryIds.current.add(message.id);
202
201
  }
203
202
  const batchItems = toBatchItems(innerMessages);
204
- for (const item_0 of batchItems) {
205
- const innerId = storageFormatAdapter.getId(item_0.message);
206
- if (!persistedInnerIds.current.has(innerId)) {
207
- await adapter.append(item_0);
208
- persistedInnerIds.current.add(innerId);
209
- } else if (durationMs !== void 0) try {
210
- await adapter.update?.(item_0, innerId);
211
- } catch {
212
- failedUpdateIds.add(message_0.id);
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)
211
+ });
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;
215
+ else try {
216
+ await adapter.update(item, innerId);
217
+ persistedInnerMessages.current.set(innerId, {
218
+ source: item.message,
219
+ content
220
+ });
221
+ } catch {}
213
222
  }
214
223
  }
215
224
  lastInnerMessageId = getLastInnerId(innerMessages) ?? lastInnerMessageId;
216
- if (deferredTelemetryIds.current.has(message_0.id) && isTerminal) {
217
- deferredTelemetryIds.current.delete(message_0.id);
225
+ if (deferredTelemetryIds.current.has(message.id) && isTerminal) {
226
+ deferredTelemetryIds.current.delete(message.id);
218
227
  adapter.reportTelemetry?.(batchItems, telemetryOptions);
219
228
  }
220
229
  }
221
- const nextSnapshot = snapshotExternalMessages(latest.messages);
222
- for (const id of failedUpdateIds) nextSnapshot.delete(id);
223
- persistedExternalMessages.current = nextSnapshot;
224
230
  }).catch((error_0) => {
225
231
  console.error("Failed to persist message history:", error_0);
226
232
  });
@@ -244,24 +250,23 @@ const useExternalHistory = (runtimeRef, historyAdapter, toThreadMessages, storag
244
250
  const deleteMessages = formatAdapter?.delete?.bind(formatAdapter);
245
251
  if (!deleteMessages) return;
246
252
  const messages_1 = runtimeRef.current.thread.getState().messages;
247
- const messageIndex = messages_1.findIndex((m_3) => m_3.id === messageId);
253
+ const messageIndex = messages_1.findIndex((m_2) => m_2.id === messageId);
248
254
  if (messageIndex === -1) return;
249
255
  const previousInnerMessages = messages_1.slice(0, messageIndex).flatMap(getExternalStoreMessages);
250
256
  let parentId = previousInnerMessages.at(-1) ? storageFormatAdapter.getId(previousInnerMessages.at(-1)) : null;
251
- const itemsToDelete = getExternalStoreMessages(messages_1[messageIndex]).map((message_1) => {
252
- const item_1 = {
257
+ const itemsToDelete = getExternalStoreMessages(messages_1[messageIndex]).map((message_0) => {
258
+ const item_0 = {
253
259
  parentId,
254
- message: message_1
260
+ message: message_0
255
261
  };
256
- parentId = storageFormatAdapter.getId(message_1);
257
- return item_1;
262
+ parentId = storageFormatAdapter.getId(message_0);
263
+ return item_0;
258
264
  });
259
265
  const deletion = persistInFlightRef.current.then(async () => {
260
266
  await deleteMessages(itemsToDelete);
261
267
  historyIds.current.delete(messageId);
262
268
  deferredTelemetryIds.current.delete(messageId);
263
- persistedExternalMessages.current.delete(messageId);
264
- for (const item_2 of itemsToDelete) persistedInnerIds.current.delete(storageFormatAdapter.getId(item_2.message));
269
+ for (const item_1 of itemsToDelete) persistedInnerMessages.current.delete(storageFormatAdapter.getId(item_1.message));
265
270
  });
266
271
  persistInFlightRef.current = deletion.catch(() => {});
267
272
  await deletion;
@@ -1 +1 @@
1
- {"version":3,"file":"useExternalHistory.js","names":["AssistantRuntime","ThreadHistoryAdapter","ThreadMessage","MessageFormatAdapter","MessageFormatRepository","ExportedMessageRepository","getExternalStoreMessages","MessageRepository","useAui","useRef","useEffect","useState","RefObject","useCallback","useMemo","toExportedMessageRepository","toThreadMessages","messages","TMessage","survivingIds","Set","survivors","flatMap","m","message","console","warn","parentId","has","add","id","headId","isAwaitingToolApproval","status","type","reason","snapshotExternalMessages","Map","map","useExternalHistory","runtimeRef","historyAdapter","storageFormatAdapter","onSetMessages","loadedRef","itemEpoch","setItemEpoch","aui","optionalThreadListItem","threadListItem","source","hasLoaded","setHasLoaded","historyIds","persistedInnerIds","deferredTelemetryIds","persistedExternalMessages","onSetMessagesRef","current","formatAdapter","undefined","withFormat","Error","isLoading","loadHistory","repo","load","length","getId","converted","thread","import","tempRepo","getMessages","error","pin","remoteId","getState","subscribe","n","threadState","isRunning","runStartRef","persistTimerRef","ReturnType","setTimeout","persistInFlightRef","Promise","resolve","stepBoundariesRef","wasRunningRef","toolCallCountRef","adapter","unsubscribe","wasRunning","lastMsg","at","role","currentToolCallCount","content","filter","p","push","Date","now","clearTimeout","persistSettled","ignoreRunning","latest","boundaries","durationMs","lastAssistant","findLast","tcCount","totalSteps","stepDur","i","Math","round","stepTimestamps","endMs","start_ms","end_ms","telemetryOptions","then","changedRunMessageIds","externalMessages","previous","get","some","item","index","lastInnerMessageId","failedUpdateIds","getLastInnerId","msgs","toBatchItems","msg","idx","innerMessages","isTerminal","isAwaitingToolCalls","isReady","update","isPersistedMessage","batchItems","innerId","append","delete","reportTelemetry","nextSnapshot","catch","deleteMessage","messageId","deleteMessages","bind","messageIndex","findIndex","previousInnerMessages","slice","itemsToDelete","deletion"],"sources":["../../src/runtime/useExternalHistory.ts"],"sourcesContent":["\"use client\";\n\nimport type {\n AssistantRuntime,\n ThreadHistoryAdapter,\n ThreadMessage,\n MessageFormatAdapter,\n MessageFormatRepository,\n ExportedMessageRepository,\n} from \"@assistant-ui/core\";\nimport { getExternalStoreMessages } from \"@assistant-ui/core\";\nimport { MessageRepository } from \"@assistant-ui/core/internal\";\nimport { useAui } from \"@assistant-ui/store\";\nimport {\n useRef,\n useEffect,\n useState,\n type RefObject,\n useCallback,\n useMemo,\n} from \"react\";\n\nexport const toExportedMessageRepository = <TMessage>(\n toThreadMessages: (messages: TMessage[]) => ThreadMessage[],\n messages: MessageFormatRepository<TMessage>,\n): ExportedMessageRepository => {\n const survivingIds = new Set<string>();\n const survivors = messages.messages.flatMap((m) => {\n const message = toThreadMessages([m.message])[0];\n if (!message) {\n console.warn(\"Skipping a stored message that could not be loaded.\");\n return [];\n }\n if (m.parentId && !survivingIds.has(m.parentId)) return [];\n survivingIds.add(message.id);\n return [{ ...m, message }];\n });\n\n return {\n headId:\n messages.headId && survivingIds.has(messages.headId)\n ? messages.headId\n : null,\n messages: survivors,\n };\n};\n\nconst isAwaitingToolApproval = (message: ThreadMessage) =>\n message.status?.type === \"requires-action\" &&\n message.status.reason === \"tool-calls\";\n\nconst snapshotExternalMessages = <TMessage>(\n messages: readonly ThreadMessage[],\n) =>\n new Map<string, TMessage[]>(\n messages.map((message) => [\n message.id,\n [...getExternalStoreMessages<TMessage>(message)],\n ]),\n );\n\nexport const useExternalHistory = <TMessage>(\n runtimeRef: RefObject<AssistantRuntime>,\n historyAdapter: ThreadHistoryAdapter | undefined,\n toThreadMessages: (messages: TMessage[]) => ThreadMessage[],\n storageFormatAdapter: MessageFormatAdapter<TMessage, any>,\n onSetMessages: (messages: TMessage[]) => void,\n) => {\n const loadedRef = useRef(false);\n const [itemEpoch, setItemEpoch] = useState(0);\n\n const aui = useAui();\n const optionalThreadListItem = useCallback(\n () => (aui.threadListItem.source ? aui.threadListItem : null),\n [aui],\n );\n\n const [hasLoaded, setHasLoaded] = useState(false);\n\n const historyIds = useRef(new Set<string>());\n const persistedInnerIds = useRef(new Set<string>());\n const deferredTelemetryIds = useRef(new Set<string>());\n const persistedExternalMessages = useRef(new Map<string, TMessage[]>());\n\n const onSetMessagesRef = useRef(onSetMessages);\n useEffect(() => {\n onSetMessagesRef.current = onSetMessages;\n });\n\n const formatAdapter = useMemo(() => {\n if (!historyAdapter) return undefined;\n if (!historyAdapter.withFormat) {\n throw new Error(\n \"useAISDKRuntime: ThreadHistoryAdapter is missing the required `withFormat` method.\",\n );\n }\n return historyAdapter.withFormat<TMessage, any>(storageFormatAdapter);\n }, [historyAdapter, storageFormatAdapter]);\n\n const isLoading = formatAdapter != null && !hasLoaded;\n\n useEffect(() => {\n if (!formatAdapter || loadedRef.current) return undefined;\n\n const loadHistory = async () => {\n try {\n const repo = await formatAdapter.load();\n if (repo && repo.messages.length > 0) {\n for (const m of repo.messages) {\n persistedInnerIds.current.add(\n storageFormatAdapter.getId(m.message),\n );\n }\n const converted = toExportedMessageRepository(toThreadMessages, repo);\n runtimeRef.current.thread.import(converted);\n\n const tempRepo = new MessageRepository();\n tempRepo.import(converted);\n const messages = tempRepo.getMessages();\n\n onSetMessagesRef.current(\n messages.flatMap(getExternalStoreMessages<TMessage>),\n );\n\n historyIds.current = new Set();\n for (const m of converted.messages) {\n historyIds.current.add(m.message.id);\n if (isAwaitingToolApproval(m.message)) {\n deferredTelemetryIds.current.add(m.message.id);\n }\n }\n persistedExternalMessages.current =\n snapshotExternalMessages<TMessage>(\n converted.messages.map((m) => m.message),\n );\n }\n } catch (error) {\n console.error(\"Failed to load message history:\", error);\n } finally {\n setHasLoaded(true);\n }\n };\n\n formatAdapter.pin?.();\n\n const remoteId = optionalThreadListItem()?.getState().remoteId;\n if (!remoteId) {\n setHasLoaded(true);\n return aui.subscribe(() => {\n if (optionalThreadListItem()?.getState().remoteId) {\n setItemEpoch((n) => n + 1);\n }\n });\n }\n\n const threadState = runtimeRef.current.thread.getState();\n if (threadState.isRunning || threadState.messages.length > 0) {\n loadedRef.current = true;\n setHasLoaded(true);\n return undefined;\n }\n\n loadedRef.current = true;\n void loadHistory();\n return undefined;\n }, [\n formatAdapter,\n toThreadMessages,\n runtimeRef,\n optionalThreadListItem,\n aui,\n itemEpoch,\n storageFormatAdapter,\n ]);\n\n const runStartRef = useRef<number | null>(null);\n const persistTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const persistInFlightRef = useRef<Promise<void>>(Promise.resolve());\n const stepBoundariesRef = useRef<number[]>([]);\n const wasRunningRef = useRef(false);\n const toolCallCountRef = useRef(0);\n\n useEffect(() => {\n if (!formatAdapter) return;\n const adapter = formatAdapter;\n\n const unsubscribe = runtimeRef.current.thread.subscribe(() => {\n const threadState = runtimeRef.current.thread.getState();\n const { isRunning } = threadState;\n const wasRunning = wasRunningRef.current;\n wasRunningRef.current = isRunning;\n\n // Track step boundaries by content changes (more reliable than isRunning)\n if (runStartRef.current != null) {\n const lastMsg = threadState.messages.at(-1);\n if (lastMsg?.role === \"assistant\") {\n const currentToolCallCount = lastMsg.content.filter(\n (p) => p.type === \"tool-call\",\n ).length;\n while (toolCallCountRef.current < currentToolCallCount) {\n stepBoundariesRef.current.push(Date.now() - runStartRef.current);\n toolCallCountRef.current++;\n }\n }\n }\n\n if (isRunning) {\n if (runStartRef.current == null) {\n runStartRef.current = Date.now();\n stepBoundariesRef.current = [];\n toolCallCountRef.current = 0;\n adapter.pin?.();\n }\n // Cancel any pending persist — isRunning went back to true\n if (persistTimerRef.current) {\n clearTimeout(persistTimerRef.current);\n persistTimerRef.current = null;\n }\n return;\n }\n\n // Only act on the true→false transition\n if (!wasRunning) return;\n\n // Record step boundary offset (synchronous for accuracy)\n if (runStartRef.current != null) {\n stepBoundariesRef.current.push(Date.now() - runStartRef.current);\n }\n\n // Debounce: wait one macrotask so agentic step flickers are absorbed\n if (persistTimerRef.current) clearTimeout(persistTimerRef.current);\n persistTimerRef.current = setTimeout(() => {\n persistSettled(false);\n }, 0);\n });\n\n function persistSettled(ignoreRunning: boolean) {\n persistTimerRef.current = null;\n const latest = runtimeRef.current.thread.getState();\n if (!ignoreRunning && latest.isRunning) return;\n\n const boundaries = stepBoundariesRef.current;\n const durationMs = boundaries.length > 0 ? boundaries.at(-1) : undefined;\n\n // Fallback: if only 1 boundary but message has multiple steps, distribute evenly\n if (boundaries.length === 1 && durationMs != null) {\n const lastAssistant = latest.messages.findLast(\n (m) => m.role === \"assistant\",\n );\n if (lastAssistant) {\n const tcCount = lastAssistant.content.filter(\n (p) => p.type === \"tool-call\",\n ).length;\n if (tcCount > 0) {\n const totalSteps = tcCount + 1;\n const stepDur = durationMs / totalSteps;\n boundaries.length = 0;\n for (let i = 0; i < totalSteps; i++) {\n boundaries.push(Math.round((i + 1) * stepDur));\n }\n }\n }\n }\n\n // Build per-step timestamps when there are multiple steps\n const stepTimestamps =\n boundaries.length > 1\n ? boundaries.map((endMs, i) => ({\n start_ms: i === 0 ? 0 : boundaries[i - 1]!,\n end_ms: endMs,\n }))\n : undefined;\n\n runStartRef.current = null;\n stepBoundariesRef.current = [];\n\n const telemetryOptions = {\n ...(durationMs != null ? { durationMs } : undefined),\n ...(stepTimestamps != null ? { stepTimestamps } : undefined),\n };\n\n persistInFlightRef.current = persistInFlightRef.current\n .then(async () => {\n const changedRunMessageIds = new Set<string>();\n for (const message of latest.messages) {\n const externalMessages =\n getExternalStoreMessages<TMessage>(message);\n const previous = persistedExternalMessages.current.get(message.id);\n if (\n previous === undefined ||\n previous.length !== externalMessages.length ||\n externalMessages.some((item, index) => item !== previous[index])\n ) {\n changedRunMessageIds.add(message.id);\n }\n }\n\n const { messages } = latest;\n let lastInnerMessageId: string | null = null;\n const failedUpdateIds = new Set<string>();\n\n const getLastInnerId = (msgs: TMessage[]): string | null =>\n msgs.length > 0 ? storageFormatAdapter.getId(msgs.at(-1)!) : null;\n\n const toBatchItems = (msgs: TMessage[]) =>\n msgs.map((msg, idx) => ({\n parentId:\n idx === 0\n ? lastInnerMessageId\n : storageFormatAdapter.getId(msgs[idx - 1]!),\n message: msg,\n }));\n\n for (const message of messages) {\n const innerMessages = getExternalStoreMessages<TMessage>(message);\n\n const isTerminal =\n message.status === undefined ||\n message.status.type === \"complete\" ||\n message.status.type === \"incomplete\";\n const isAwaitingToolCalls = isAwaitingToolApproval(message);\n // A paused message's later content can only reach storage via update, so it is persisted early only when the adapter supports update.\n const isReady =\n isTerminal ||\n (isAwaitingToolCalls && adapter.update !== undefined);\n\n if (!isReady) {\n lastInnerMessageId =\n getLastInnerId(innerMessages) ?? lastInnerMessageId;\n continue;\n }\n\n const isPersistedMessage = historyIds.current.has(message.id);\n if (isPersistedMessage && !changedRunMessageIds.has(message.id)) {\n lastInnerMessageId =\n getLastInnerId(innerMessages) ?? lastInnerMessageId;\n continue;\n }\n if (!isPersistedMessage) {\n historyIds.current.add(message.id);\n deferredTelemetryIds.current.add(message.id);\n }\n\n const batchItems = toBatchItems(innerMessages);\n for (const item of batchItems) {\n const innerId = storageFormatAdapter.getId(item.message);\n if (!persistedInnerIds.current.has(innerId)) {\n await adapter.append(item);\n persistedInnerIds.current.add(innerId);\n } else if (durationMs !== undefined) {\n try {\n await adapter.update?.(item, innerId);\n } catch {\n // A failed update drops the message from the refreshed baseline so it retries on the next run stop.\n failedUpdateIds.add(message.id);\n }\n }\n }\n\n lastInnerMessageId =\n getLastInnerId(innerMessages) ?? lastInnerMessageId;\n\n if (deferredTelemetryIds.current.has(message.id) && isTerminal) {\n deferredTelemetryIds.current.delete(message.id);\n adapter.reportTelemetry?.(batchItems, telemetryOptions);\n }\n }\n\n const nextSnapshot = snapshotExternalMessages<TMessage>(\n latest.messages,\n );\n for (const id of failedUpdateIds) {\n nextSnapshot.delete(id);\n }\n persistedExternalMessages.current = nextSnapshot;\n })\n .catch((error) => {\n console.error(\"Failed to persist message history:\", error);\n });\n }\n\n return () => {\n unsubscribe();\n if (persistTimerRef.current) {\n clearTimeout(persistTimerRef.current);\n persistTimerRef.current = null;\n persistSettled(false);\n }\n };\n }, [formatAdapter, storageFormatAdapter, runtimeRef]);\n\n const deleteMessage = useCallback(\n async (messageId: string) => {\n const deleteMessages = formatAdapter?.delete?.bind(formatAdapter);\n if (!deleteMessages) return;\n\n const messages = runtimeRef.current.thread.getState().messages;\n const messageIndex = messages.findIndex((m) => m.id === messageId);\n if (messageIndex === -1) return;\n\n const previousInnerMessages = messages\n .slice(0, messageIndex)\n .flatMap(getExternalStoreMessages<TMessage>);\n let parentId = previousInnerMessages.at(-1)\n ? storageFormatAdapter.getId(previousInnerMessages.at(-1)!)\n : null;\n const itemsToDelete = getExternalStoreMessages<TMessage>(\n messages[messageIndex]!,\n ).map((message) => {\n const item = { parentId, message };\n parentId = storageFormatAdapter.getId(message);\n return item;\n });\n\n const deletion = persistInFlightRef.current.then(async () => {\n await deleteMessages(itemsToDelete);\n\n historyIds.current.delete(messageId);\n deferredTelemetryIds.current.delete(messageId);\n persistedExternalMessages.current.delete(messageId);\n for (const item of itemsToDelete) {\n persistedInnerIds.current.delete(\n storageFormatAdapter.getId(item.message),\n );\n }\n });\n\n persistInFlightRef.current = deletion.catch(() => {});\n await deletion;\n },\n [formatAdapter, runtimeRef, storageFormatAdapter],\n );\n\n return { isLoading, deleteMessage };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"useExternalHistory.js","names":["AssistantRuntime","ThreadHistoryAdapter","ThreadMessage","MessageFormatAdapter","MessageFormatItem","MessageFormatRepository","ExportedMessageRepository","getExternalStoreMessages","MessageRepository","useAui","useRef","useEffect","useState","RefObject","useCallback","useMemo","toExportedMessageRepository","toThreadMessages","messages","TMessage","survivingIds","Set","survivors","flatMap","m","message","console","warn","parentId","has","add","id","headId","isAwaitingToolApproval","status","type","reason","encodeContent","storageFormatAdapter","item","JSON","stringify","encode","useExternalHistory","runtimeRef","historyAdapter","onSetMessages","loadedRef","itemEpoch","setItemEpoch","aui","optionalThreadListItem","threadListItem","source","hasLoaded","setHasLoaded","historyIds","deferredTelemetryIds","persistedInnerMessages","Map","content","onSetMessagesRef","current","formatAdapter","undefined","withFormat","Error","isLoading","loadHistory","repo","load","length","set","getId","converted","thread","import","tempRepo","getMessages","error","pin","remoteId","getState","subscribe","n","threadState","isRunning","runStartRef","persistTimerRef","ReturnType","setTimeout","persistInFlightRef","Promise","resolve","stepBoundariesRef","wasRunningRef","toolCallCountRef","adapter","unsubscribe","wasRunning","lastMsg","at","role","currentToolCallCount","filter","p","push","Date","now","clearTimeout","persistSettled","initialThreadState","ignoreRunning","latest","boundaries","durationMs","lastAssistant","findLast","tcCount","totalSteps","stepDur","i","Math","round","stepTimestamps","map","endMs","start_ms","end_ms","telemetryOptions","then","lastInnerMessageId","getLastInnerId","msgs","toBatchItems","msg","idx","innerMessages","isTerminal","isAwaitingToolCalls","isReady","update","batchItems","innerId","persisted","get","append","delete","reportTelemetry","catch","deleteMessage","messageId","deleteMessages","bind","messageIndex","findIndex","previousInnerMessages","slice","itemsToDelete","deletion"],"sources":["../../src/runtime/useExternalHistory.ts"],"sourcesContent":["\"use client\";\n\nimport type {\n AssistantRuntime,\n ThreadHistoryAdapter,\n ThreadMessage,\n MessageFormatAdapter,\n MessageFormatItem,\n MessageFormatRepository,\n ExportedMessageRepository,\n} from \"@assistant-ui/core\";\nimport { getExternalStoreMessages } from \"@assistant-ui/core\";\nimport { MessageRepository } from \"@assistant-ui/core/internal\";\nimport { useAui } from \"@assistant-ui/store\";\nimport {\n useRef,\n useEffect,\n useState,\n type RefObject,\n useCallback,\n useMemo,\n} from \"react\";\n\nexport const toExportedMessageRepository = <TMessage>(\n toThreadMessages: (messages: TMessage[]) => ThreadMessage[],\n messages: MessageFormatRepository<TMessage>,\n): ExportedMessageRepository => {\n const survivingIds = new Set<string>();\n const survivors = messages.messages.flatMap((m) => {\n const message = toThreadMessages([m.message])[0];\n if (!message) {\n console.warn(\"Skipping a stored message that could not be loaded.\");\n return [];\n }\n if (m.parentId && !survivingIds.has(m.parentId)) return [];\n survivingIds.add(message.id);\n return [{ ...m, message }];\n });\n\n return {\n headId:\n messages.headId && survivingIds.has(messages.headId)\n ? messages.headId\n : null,\n messages: survivors,\n };\n};\n\nconst isAwaitingToolApproval = (message: ThreadMessage) =>\n message.status?.type === \"requires-action\" &&\n message.status.reason === \"tool-calls\";\n\nconst encodeContent = <TMessage>(\n storageFormatAdapter: MessageFormatAdapter<TMessage, any>,\n item: MessageFormatItem<TMessage>,\n) => JSON.stringify(storageFormatAdapter.encode(item));\n\nexport const useExternalHistory = <TMessage>(\n runtimeRef: RefObject<AssistantRuntime>,\n historyAdapter: ThreadHistoryAdapter | undefined,\n toThreadMessages: (messages: TMessage[]) => ThreadMessage[],\n storageFormatAdapter: MessageFormatAdapter<TMessage, any>,\n onSetMessages: (messages: TMessage[]) => void,\n) => {\n const loadedRef = useRef(false);\n const [itemEpoch, setItemEpoch] = useState(0);\n\n const aui = useAui();\n const optionalThreadListItem = useCallback(\n () => (aui.threadListItem.source ? aui.threadListItem : null),\n [aui],\n );\n\n const [hasLoaded, setHasLoaded] = useState(false);\n\n const historyIds = useRef(new Set<string>());\n const deferredTelemetryIds = useRef(new Set<string>());\n // `content` is a snapshot taken at write time rather than a re-encode of `source`, because a retained message object can be mutated in place by the runtime that produced it.\n const persistedInnerMessages = useRef(\n new Map<string, { source: TMessage; content: string }>(),\n );\n\n const onSetMessagesRef = useRef(onSetMessages);\n useEffect(() => {\n onSetMessagesRef.current = onSetMessages;\n });\n\n const formatAdapter = useMemo(() => {\n if (!historyAdapter) return undefined;\n if (!historyAdapter.withFormat) {\n throw new Error(\n \"useAISDKRuntime: ThreadHistoryAdapter is missing the required `withFormat` method.\",\n );\n }\n return historyAdapter.withFormat<TMessage, any>(storageFormatAdapter);\n }, [historyAdapter, storageFormatAdapter]);\n\n const isLoading = formatAdapter != null && !hasLoaded;\n\n useEffect(() => {\n if (!formatAdapter || loadedRef.current) return undefined;\n\n const loadHistory = async () => {\n try {\n const repo = await formatAdapter.load();\n if (repo && repo.messages.length > 0) {\n for (const m of repo.messages) {\n persistedInnerMessages.current.set(\n storageFormatAdapter.getId(m.message),\n {\n source: m.message,\n content: encodeContent(storageFormatAdapter, m),\n },\n );\n }\n const converted = toExportedMessageRepository(toThreadMessages, repo);\n runtimeRef.current.thread.import(converted);\n\n const tempRepo = new MessageRepository();\n tempRepo.import(converted);\n const messages = tempRepo.getMessages();\n\n onSetMessagesRef.current(\n messages.flatMap(getExternalStoreMessages<TMessage>),\n );\n\n historyIds.current = new Set();\n for (const m of converted.messages) {\n historyIds.current.add(m.message.id);\n if (isAwaitingToolApproval(m.message)) {\n deferredTelemetryIds.current.add(m.message.id);\n }\n }\n }\n } catch (error) {\n console.error(\"Failed to load message history:\", error);\n } finally {\n setHasLoaded(true);\n }\n };\n\n formatAdapter.pin?.();\n\n const remoteId = optionalThreadListItem()?.getState().remoteId;\n if (!remoteId) {\n setHasLoaded(true);\n return aui.subscribe(() => {\n if (optionalThreadListItem()?.getState().remoteId) {\n setItemEpoch((n) => n + 1);\n }\n });\n }\n\n const threadState = runtimeRef.current.thread.getState();\n if (threadState.isRunning || threadState.messages.length > 0) {\n loadedRef.current = true;\n setHasLoaded(true);\n return undefined;\n }\n\n loadedRef.current = true;\n void loadHistory();\n return undefined;\n }, [\n formatAdapter,\n toThreadMessages,\n runtimeRef,\n optionalThreadListItem,\n aui,\n itemEpoch,\n storageFormatAdapter,\n ]);\n\n const runStartRef = useRef<number | null>(null);\n const persistTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const persistInFlightRef = useRef<Promise<void>>(Promise.resolve());\n const stepBoundariesRef = useRef<number[]>([]);\n const wasRunningRef = useRef(false);\n const toolCallCountRef = useRef(0);\n\n useEffect(() => {\n if (!formatAdapter) return;\n const adapter = formatAdapter;\n\n const unsubscribe = runtimeRef.current.thread.subscribe(() => {\n const threadState = runtimeRef.current.thread.getState();\n const { isRunning } = threadState;\n const wasRunning = wasRunningRef.current;\n wasRunningRef.current = isRunning;\n\n // Track step boundaries by content changes (more reliable than isRunning)\n if (runStartRef.current != null) {\n const lastMsg = threadState.messages.at(-1);\n if (lastMsg?.role === \"assistant\") {\n const currentToolCallCount = lastMsg.content.filter(\n (p) => p.type === \"tool-call\",\n ).length;\n while (toolCallCountRef.current < currentToolCallCount) {\n stepBoundariesRef.current.push(Date.now() - runStartRef.current);\n toolCallCountRef.current++;\n }\n }\n }\n\n if (isRunning) {\n if (runStartRef.current == null) {\n runStartRef.current = Date.now();\n stepBoundariesRef.current = [];\n toolCallCountRef.current = 0;\n adapter.pin?.();\n }\n // Cancel any pending persist — isRunning went back to true\n if (persistTimerRef.current) {\n clearTimeout(persistTimerRef.current);\n persistTimerRef.current = null;\n }\n return;\n }\n\n // Only act on the true→false transition\n if (!wasRunning) return;\n\n // Record step boundary offset (synchronous for accuracy)\n if (runStartRef.current != null) {\n stepBoundariesRef.current.push(Date.now() - runStartRef.current);\n }\n\n // Debounce: wait one macrotask so agentic step flickers are absorbed\n if (persistTimerRef.current) clearTimeout(persistTimerRef.current);\n persistTimerRef.current = setTimeout(() => {\n persistSettled(false);\n }, 0);\n });\n\n const initialThreadState = runtimeRef.current.thread.getState();\n wasRunningRef.current = initialThreadState.isRunning;\n if (initialThreadState.isRunning) {\n if (runStartRef.current == null) {\n runStartRef.current = Date.now();\n stepBoundariesRef.current = [];\n toolCallCountRef.current = 0;\n adapter.pin?.();\n }\n } else if (initialThreadState.messages.length > 0) {\n persistSettled(true);\n }\n\n function persistSettled(ignoreRunning: boolean) {\n persistTimerRef.current = null;\n const latest = runtimeRef.current.thread.getState();\n if (!ignoreRunning && latest.isRunning) return;\n\n const boundaries = stepBoundariesRef.current;\n const durationMs = boundaries.length > 0 ? boundaries.at(-1) : undefined;\n\n // Fallback: if only 1 boundary but message has multiple steps, distribute evenly\n if (boundaries.length === 1 && durationMs != null) {\n const lastAssistant = latest.messages.findLast(\n (m) => m.role === \"assistant\",\n );\n if (lastAssistant) {\n const tcCount = lastAssistant.content.filter(\n (p) => p.type === \"tool-call\",\n ).length;\n if (tcCount > 0) {\n const totalSteps = tcCount + 1;\n const stepDur = durationMs / totalSteps;\n boundaries.length = 0;\n for (let i = 0; i < totalSteps; i++) {\n boundaries.push(Math.round((i + 1) * stepDur));\n }\n }\n }\n }\n\n // Build per-step timestamps when there are multiple steps\n const stepTimestamps =\n boundaries.length > 1\n ? boundaries.map((endMs, i) => ({\n start_ms: i === 0 ? 0 : boundaries[i - 1]!,\n end_ms: endMs,\n }))\n : undefined;\n\n runStartRef.current = null;\n stepBoundariesRef.current = [];\n\n const telemetryOptions = {\n ...(durationMs != null ? { durationMs } : undefined),\n ...(stepTimestamps != null ? { stepTimestamps } : undefined),\n };\n\n persistInFlightRef.current = persistInFlightRef.current\n .then(async () => {\n const { messages } = latest;\n let lastInnerMessageId: string | null = null;\n\n const getLastInnerId = (msgs: TMessage[]): string | null =>\n msgs.length > 0 ? storageFormatAdapter.getId(msgs.at(-1)!) : null;\n\n const toBatchItems = (msgs: TMessage[]) =>\n msgs.map((msg, idx) => ({\n parentId:\n idx === 0\n ? lastInnerMessageId\n : storageFormatAdapter.getId(msgs[idx - 1]!),\n message: msg,\n }));\n\n for (const message of messages) {\n const innerMessages = getExternalStoreMessages<TMessage>(message);\n\n const isTerminal =\n message.status === undefined ||\n message.status.type === \"complete\" ||\n message.status.type === \"incomplete\";\n const isAwaitingToolCalls = isAwaitingToolApproval(message);\n // A paused message's later content can only reach storage via update, so it is persisted early only when the adapter supports update.\n const isReady =\n isTerminal ||\n (isAwaitingToolCalls && adapter.update !== undefined);\n\n if (!isReady) {\n lastInnerMessageId =\n getLastInnerId(innerMessages) ?? lastInnerMessageId;\n continue;\n }\n\n if (!historyIds.current.has(message.id)) {\n historyIds.current.add(message.id);\n deferredTelemetryIds.current.add(message.id);\n }\n\n const batchItems = toBatchItems(innerMessages);\n for (const item of batchItems) {\n const innerId = storageFormatAdapter.getId(item.message);\n const persisted = persistedInnerMessages.current.get(innerId);\n if (!persisted) {\n await adapter.append(item);\n persistedInnerMessages.current.set(innerId, {\n source: item.message,\n content: encodeContent(storageFormatAdapter, item),\n });\n } else if (\n persisted.source !== item.message &&\n durationMs !== undefined &&\n adapter.update\n ) {\n const content = encodeContent(storageFormatAdapter, item);\n if (content === persisted.content) {\n persisted.source = item.message;\n } else {\n try {\n await adapter.update(item, innerId);\n persistedInnerMessages.current.set(innerId, {\n source: item.message,\n content,\n });\n } catch {\n // A failed update leaves the stale baseline behind so the next run stop retries it.\n }\n }\n }\n }\n\n lastInnerMessageId =\n getLastInnerId(innerMessages) ?? lastInnerMessageId;\n\n if (deferredTelemetryIds.current.has(message.id) && isTerminal) {\n deferredTelemetryIds.current.delete(message.id);\n adapter.reportTelemetry?.(batchItems, telemetryOptions);\n }\n }\n })\n .catch((error) => {\n console.error(\"Failed to persist message history:\", error);\n });\n }\n\n return () => {\n unsubscribe();\n if (persistTimerRef.current) {\n clearTimeout(persistTimerRef.current);\n persistTimerRef.current = null;\n persistSettled(false);\n }\n };\n }, [formatAdapter, storageFormatAdapter, runtimeRef]);\n\n const deleteMessage = useCallback(\n async (messageId: string) => {\n const deleteMessages = formatAdapter?.delete?.bind(formatAdapter);\n if (!deleteMessages) return;\n\n const messages = runtimeRef.current.thread.getState().messages;\n const messageIndex = messages.findIndex((m) => m.id === messageId);\n if (messageIndex === -1) return;\n\n const previousInnerMessages = messages\n .slice(0, messageIndex)\n .flatMap(getExternalStoreMessages<TMessage>);\n let parentId = previousInnerMessages.at(-1)\n ? storageFormatAdapter.getId(previousInnerMessages.at(-1)!)\n : null;\n const itemsToDelete = getExternalStoreMessages<TMessage>(\n messages[messageIndex]!,\n ).map((message) => {\n const item = { parentId, message };\n parentId = storageFormatAdapter.getId(message);\n return item;\n });\n\n const deletion = persistInFlightRef.current.then(async () => {\n await deleteMessages(itemsToDelete);\n\n historyIds.current.delete(messageId);\n deferredTelemetryIds.current.delete(messageId);\n for (const item of itemsToDelete) {\n persistedInnerMessages.current.delete(\n storageFormatAdapter.getId(item.message),\n );\n }\n });\n\n persistInFlightRef.current = deletion.catch(() => {});\n await deletion;\n },\n [formatAdapter, runtimeRef, storageFormatAdapter],\n );\n\n return { isLoading, deleteMessage };\n};\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { useEffect, useRef } from "@assistant-ui/tap/react-shim";
2
1
  import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
2
+ import { useEffect, useRef } from "@assistant-ui/tap/react-shim";
3
3
  import { useAssistantClientDestroySignal } from "@assistant-ui/store/internal";
4
4
  //#region src/runtime/useResourceCleanup.ts
5
5
  const useResourceCleanup = (enabled, cleanup) => {
package/dist/usage.d.ts CHANGED
@@ -12,8 +12,7 @@ interface TokenUsageExtractableMessage {
12
12
  metadata?: unknown;
13
13
  }
14
14
  declare function getThreadMessageTokenUsage(message: TokenUsageExtractableMessage | undefined): ThreadTokenUsage | undefined;
15
- declare function getLatestThreadTokenUsage(messages: readonly TokenUsageExtractableMessage[] | undefined): ThreadTokenUsage | undefined;
16
15
  declare function useThreadTokenUsage(): ThreadTokenUsage | undefined;
17
16
  //#endregion
18
- export { ThreadTokenUsage, TokenUsageExtractableMessage, getLatestThreadTokenUsage, getThreadMessageTokenUsage, useThreadTokenUsage };
17
+ export { ThreadTokenUsage, TokenUsageExtractableMessage, getThreadMessageTokenUsage, useThreadTokenUsage };
19
18
  //# sourceMappingURL=usage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"usage.d.ts","names":[],"sources":["../src/usage.ts"],"mappings":";KAGY;EACV;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;;iBAuHc,2BACd,SAAS,2CACR;iBAea,0BACd,mBAAmB,6CAClB;iBAmBa,uBAAuB"}
1
+ {"version":3,"file":"usage.d.ts","names":[],"sources":["../src/usage.ts"],"mappings":";KAIY;EACV;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;;iBAuHc,2BACd,SAAS,2CACR;iBA8Ba,uBAAuB"}
package/dist/usage.js CHANGED
@@ -1,5 +1,6 @@
1
- import { useAuiState } from "@assistant-ui/store";
2
1
  import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
2
+ import "@assistant-ui/tap/react-shim";
3
+ import { useAuiState } from "@assistant-ui/store";
3
4
  //#region src/usage.ts
4
5
  const USAGE_KEYS = [
5
6
  "inputTokens",
@@ -93,13 +94,10 @@ function getThreadMessageTokenUsage(message) {
93
94
  if (!metadata) return void 0;
94
95
  const topLevelUsage = normalizeUsage(metadata.usage);
95
96
  if (topLevelUsage) return withComputedTotal(topLevelUsage);
96
- const legacyUsage = normalizeUsage(asRecord(metadata.custom)?.usage);
97
- if (legacyUsage) return withComputedTotal(legacyUsage);
97
+ const customUsage = normalizeUsage(asRecord(metadata.custom)?.usage);
98
+ if (customUsage) return withComputedTotal(customUsage);
98
99
  return usageFromSteps(metadata.steps);
99
100
  }
100
- function getLatestThreadTokenUsage(messages) {
101
- return getThreadMessageTokenUsage(findLatestMessageWithUsage(messages));
102
- }
103
101
  function findLatestMessageWithUsage(messages) {
104
102
  if (!messages) return void 0;
105
103
  for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
@@ -122,6 +120,6 @@ function _temp(s) {
122
120
  return findLatestMessageWithUsage(s.thread.messages);
123
121
  }
124
122
  //#endregion
125
- export { getLatestThreadTokenUsage, getThreadMessageTokenUsage, useThreadTokenUsage };
123
+ export { getThreadMessageTokenUsage, useThreadTokenUsage };
126
124
 
127
125
  //# sourceMappingURL=usage.js.map
package/dist/usage.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"usage.js","names":["useAuiState","ThreadTokenUsage","totalTokens","inputTokens","outputTokens","reasoningTokens","cachedInputTokens","TokenUsageExtractableMessage","role","metadata","UsageRecord","Record","USAGE_KEYS","const","asRecord","value","Array","isArray","undefined","asPositiveTokenCount","Number","isFinite","computeTotalTokens","usage","normalizeUsage","record","result","hasFields","key","count","outputTokenDetails","inputTokenDetails","cacheReadTokens","withComputedTotal","usageFromSteps","steps","sums","present","stepsWithUsage","stepsWithComputableTotal","step","stepTotal","getThreadMessageTokenUsage","message","topLevelUsage","legacyUsage","custom","getLatestThreadTokenUsage","messages","findLatestMessageWithUsage","idx","length","useThreadTokenUsage","$","_c","msg","_temp","t0","s","thread"],"sources":["../src/usage.ts"],"sourcesContent":["/// <reference types=\"@assistant-ui/core/react\" />\nimport { useAuiState } from \"@assistant-ui/store\";\n\nexport type ThreadTokenUsage = {\n totalTokens?: number;\n inputTokens?: number;\n outputTokens?: number;\n reasoningTokens?: number;\n cachedInputTokens?: number;\n};\n\nexport interface TokenUsageExtractableMessage {\n role?: string;\n metadata?: unknown;\n}\n\ntype UsageRecord = Record<string, unknown>;\n\nconst USAGE_KEYS = [\n \"inputTokens\",\n \"outputTokens\",\n \"reasoningTokens\",\n \"cachedInputTokens\",\n \"totalTokens\",\n] as const satisfies (keyof ThreadTokenUsage)[];\n\nfunction asRecord(value: unknown): UsageRecord | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value))\n return undefined;\n return value as UsageRecord;\n}\n\nfunction asPositiveTokenCount(value: unknown): number | undefined {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value < 0) {\n return undefined;\n }\n return value;\n}\n\nfunction computeTotalTokens(usage: ThreadTokenUsage): number | undefined {\n if (usage.totalTokens !== undefined) return usage.totalTokens;\n if (usage.inputTokens !== undefined && usage.outputTokens !== undefined) {\n return usage.inputTokens + usage.outputTokens;\n }\n return undefined;\n}\n\nfunction normalizeUsage(value: unknown): ThreadTokenUsage | undefined {\n const record = asRecord(value);\n if (!record) return undefined;\n\n const result: ThreadTokenUsage = {};\n let hasFields = false;\n for (const key of USAGE_KEYS) {\n const count = asPositiveTokenCount(record[key]);\n if (count !== undefined) {\n result[key] = count;\n hasFields = true;\n }\n }\n // AI SDK v7 moved these under token detail objects; v6 kept them top-level.\n if (result.reasoningTokens === undefined) {\n const count = asPositiveTokenCount(\n asRecord(record.outputTokenDetails)?.reasoningTokens,\n );\n if (count !== undefined) {\n result.reasoningTokens = count;\n hasFields = true;\n }\n }\n if (result.cachedInputTokens === undefined) {\n const count = asPositiveTokenCount(\n asRecord(record.inputTokenDetails)?.cacheReadTokens,\n );\n if (count !== undefined) {\n result.cachedInputTokens = count;\n hasFields = true;\n }\n }\n return hasFields ? result : undefined;\n}\n\nfunction withComputedTotal(\n usage: ThreadTokenUsage,\n): ThreadTokenUsage | undefined {\n const totalTokens = computeTotalTokens(usage);\n return { ...usage, ...(totalTokens !== undefined && { totalTokens }) };\n}\n\nfunction usageFromSteps(value: unknown): ThreadTokenUsage | undefined {\n const steps = Array.isArray(value) ? value : [];\n\n const sums: Record<string, number> = {};\n const present: Record<string, boolean> = {};\n let stepsWithUsage = 0;\n let stepsWithComputableTotal = 0;\n\n for (const step of steps) {\n const usage = normalizeUsage(asRecord(step)?.usage);\n if (!usage) continue;\n stepsWithUsage++;\n\n const stepTotal = computeTotalTokens(usage);\n if (stepTotal !== undefined) {\n sums.totalTokens = (sums.totalTokens ?? 0) + stepTotal;\n stepsWithComputableTotal++;\n }\n\n for (const key of USAGE_KEYS) {\n if (key === \"totalTokens\") continue;\n if (usage[key] !== undefined) {\n sums[key] = (sums[key] ?? 0) + usage[key];\n present[key] = true;\n }\n }\n }\n\n if (stepsWithUsage === 0) return undefined;\n\n const result: ThreadTokenUsage = {};\n if (stepsWithComputableTotal === stepsWithUsage) {\n result.totalTokens = sums.totalTokens!;\n }\n for (const key of USAGE_KEYS) {\n if (key === \"totalTokens\") continue;\n if (present[key]) {\n result[key] = sums[key]!;\n }\n }\n return result;\n}\n\nexport function getThreadMessageTokenUsage(\n message: TokenUsageExtractableMessage | undefined,\n): ThreadTokenUsage | undefined {\n if (!message || message.role !== \"assistant\") return undefined;\n\n const metadata = asRecord(message.metadata);\n if (!metadata) return undefined;\n\n const topLevelUsage = normalizeUsage(metadata.usage);\n if (topLevelUsage) return withComputedTotal(topLevelUsage);\n\n const legacyUsage = normalizeUsage(asRecord(metadata.custom)?.usage);\n if (legacyUsage) return withComputedTotal(legacyUsage);\n\n return usageFromSteps(metadata.steps);\n}\n\nexport function getLatestThreadTokenUsage(\n messages: readonly TokenUsageExtractableMessage[] | undefined,\n): ThreadTokenUsage | undefined {\n return getThreadMessageTokenUsage(findLatestMessageWithUsage(messages));\n}\n\nfunction findLatestMessageWithUsage(\n messages: readonly TokenUsageExtractableMessage[] | undefined,\n): TokenUsageExtractableMessage | undefined {\n if (!messages) return undefined;\n\n for (let idx = messages.length - 1; idx >= 0; idx -= 1) {\n const message = messages[idx];\n if (getThreadMessageTokenUsage(message)) {\n return message;\n }\n }\n\n return undefined;\n}\n\nexport function useThreadTokenUsage(): ThreadTokenUsage | undefined {\n const msg = useAuiState((s) => findLatestMessageWithUsage(s.thread.messages));\n return getThreadMessageTokenUsage(msg);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"usage.js","names":["useMemo","useAuiState","ThreadTokenUsage","totalTokens","inputTokens","outputTokens","reasoningTokens","cachedInputTokens","TokenUsageExtractableMessage","role","metadata","UsageRecord","Record","USAGE_KEYS","const","asRecord","value","Array","isArray","undefined","asPositiveTokenCount","Number","isFinite","computeTotalTokens","usage","normalizeUsage","record","result","hasFields","key","count","outputTokenDetails","inputTokenDetails","cacheReadTokens","withComputedTotal","usageFromSteps","steps","sums","present","stepsWithUsage","stepsWithComputableTotal","step","stepTotal","getThreadMessageTokenUsage","message","topLevelUsage","customUsage","custom","findLatestMessageWithUsage","messages","idx","length","useThreadTokenUsage","$","_c","msg","_temp","t0","s","thread"],"sources":["../src/usage.ts"],"sourcesContent":["/// <reference types=\"@assistant-ui/core/react\" />\nimport { useMemo } from \"react\";\nimport { useAuiState } from \"@assistant-ui/store\";\n\nexport type ThreadTokenUsage = {\n totalTokens?: number;\n inputTokens?: number;\n outputTokens?: number;\n reasoningTokens?: number;\n cachedInputTokens?: number;\n};\n\nexport interface TokenUsageExtractableMessage {\n role?: string;\n metadata?: unknown;\n}\n\ntype UsageRecord = Record<string, unknown>;\n\nconst USAGE_KEYS = [\n \"inputTokens\",\n \"outputTokens\",\n \"reasoningTokens\",\n \"cachedInputTokens\",\n \"totalTokens\",\n] as const satisfies (keyof ThreadTokenUsage)[];\n\nfunction asRecord(value: unknown): UsageRecord | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value))\n return undefined;\n return value as UsageRecord;\n}\n\nfunction asPositiveTokenCount(value: unknown): number | undefined {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value < 0) {\n return undefined;\n }\n return value;\n}\n\nfunction computeTotalTokens(usage: ThreadTokenUsage): number | undefined {\n if (usage.totalTokens !== undefined) return usage.totalTokens;\n if (usage.inputTokens !== undefined && usage.outputTokens !== undefined) {\n return usage.inputTokens + usage.outputTokens;\n }\n return undefined;\n}\n\nfunction normalizeUsage(value: unknown): ThreadTokenUsage | undefined {\n const record = asRecord(value);\n if (!record) return undefined;\n\n const result: ThreadTokenUsage = {};\n let hasFields = false;\n for (const key of USAGE_KEYS) {\n const count = asPositiveTokenCount(record[key]);\n if (count !== undefined) {\n result[key] = count;\n hasFields = true;\n }\n }\n // AI SDK v7 moved these under token detail objects; v6 kept them top-level.\n if (result.reasoningTokens === undefined) {\n const count = asPositiveTokenCount(\n asRecord(record.outputTokenDetails)?.reasoningTokens,\n );\n if (count !== undefined) {\n result.reasoningTokens = count;\n hasFields = true;\n }\n }\n if (result.cachedInputTokens === undefined) {\n const count = asPositiveTokenCount(\n asRecord(record.inputTokenDetails)?.cacheReadTokens,\n );\n if (count !== undefined) {\n result.cachedInputTokens = count;\n hasFields = true;\n }\n }\n return hasFields ? result : undefined;\n}\n\nfunction withComputedTotal(\n usage: ThreadTokenUsage,\n): ThreadTokenUsage | undefined {\n const totalTokens = computeTotalTokens(usage);\n return { ...usage, ...(totalTokens !== undefined && { totalTokens }) };\n}\n\nfunction usageFromSteps(value: unknown): ThreadTokenUsage | undefined {\n const steps = Array.isArray(value) ? value : [];\n\n const sums: Record<string, number> = {};\n const present: Record<string, boolean> = {};\n let stepsWithUsage = 0;\n let stepsWithComputableTotal = 0;\n\n for (const step of steps) {\n const usage = normalizeUsage(asRecord(step)?.usage);\n if (!usage) continue;\n stepsWithUsage++;\n\n const stepTotal = computeTotalTokens(usage);\n if (stepTotal !== undefined) {\n sums.totalTokens = (sums.totalTokens ?? 0) + stepTotal;\n stepsWithComputableTotal++;\n }\n\n for (const key of USAGE_KEYS) {\n if (key === \"totalTokens\") continue;\n if (usage[key] !== undefined) {\n sums[key] = (sums[key] ?? 0) + usage[key];\n present[key] = true;\n }\n }\n }\n\n if (stepsWithUsage === 0) return undefined;\n\n const result: ThreadTokenUsage = {};\n if (stepsWithComputableTotal === stepsWithUsage) {\n result.totalTokens = sums.totalTokens!;\n }\n for (const key of USAGE_KEYS) {\n if (key === \"totalTokens\") continue;\n if (present[key]) {\n result[key] = sums[key]!;\n }\n }\n return result;\n}\n\nexport function getThreadMessageTokenUsage(\n message: TokenUsageExtractableMessage | undefined,\n): ThreadTokenUsage | undefined {\n if (!message || message.role !== \"assistant\") return undefined;\n\n const metadata = asRecord(message.metadata);\n if (!metadata) return undefined;\n\n const topLevelUsage = normalizeUsage(metadata.usage);\n if (topLevelUsage) return withComputedTotal(topLevelUsage);\n\n const customUsage = normalizeUsage(asRecord(metadata.custom)?.usage);\n if (customUsage) return withComputedTotal(customUsage);\n\n return usageFromSteps(metadata.steps);\n}\n\nfunction findLatestMessageWithUsage(\n messages: readonly TokenUsageExtractableMessage[] | undefined,\n): TokenUsageExtractableMessage | undefined {\n if (!messages) return undefined;\n\n for (let idx = messages.length - 1; idx >= 0; idx -= 1) {\n const message = messages[idx];\n if (getThreadMessageTokenUsage(message)) {\n return message;\n }\n }\n\n return undefined;\n}\n\nexport function useThreadTokenUsage(): ThreadTokenUsage | undefined {\n const msg = useAuiState((s) => findLatestMessageWithUsage(s.thread.messages));\n return useMemo(() => getThreadMessageTokenUsage(msg), [msg]);\n}\n"],"mappings":"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"}