@assistant-ui/core 0.2.22 → 0.2.23

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 (214) hide show
  1. package/dist/adapters/thread-history.d.ts +8 -0
  2. package/dist/adapters/thread-history.d.ts.map +1 -1
  3. package/dist/internal.d.ts +3 -2
  4. package/dist/internal.js +3 -2
  5. package/dist/model-context/frame/host.d.ts.map +1 -1
  6. package/dist/model-context/frame/host.js +36 -21
  7. package/dist/model-context/frame/host.js.map +1 -1
  8. package/dist/model-context/types.d.ts.map +1 -1
  9. package/dist/model-context/types.js +13 -4
  10. package/dist/model-context/types.js.map +1 -1
  11. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  12. package/dist/react/adapters/LocalStorageThreadListAdapter.js +1 -0
  13. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  14. package/dist/react/client/interactable-model-context.js +1 -0
  15. package/dist/react/client/interactable-model-context.js.map +1 -1
  16. package/dist/react/interactables-legacy/useInteractableState.js +1 -0
  17. package/dist/react/interactables-legacy/useInteractableState.js.map +1 -1
  18. package/dist/react/model-context/useInteractableState.js +1 -0
  19. package/dist/react/model-context/useInteractableState.js.map +1 -1
  20. package/dist/react/model-context/useInteractableVersions.js +1 -0
  21. package/dist/react/model-context/useInteractableVersions.js.map +1 -1
  22. package/dist/react/model-context/useToolArgsStatus.js +1 -0
  23. package/dist/react/model-context/useToolArgsStatus.js.map +1 -1
  24. package/dist/react/primitive-hooks/useActionBarCopy.js +1 -0
  25. package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
  26. package/dist/react/primitive-hooks/useActionBarEdit.js +1 -0
  27. package/dist/react/primitive-hooks/useActionBarEdit.js.map +1 -1
  28. package/dist/react/primitive-hooks/useActionBarFeedback.js +1 -0
  29. package/dist/react/primitive-hooks/useActionBarFeedback.js.map +1 -1
  30. package/dist/react/primitive-hooks/useActionBarReload.js +1 -0
  31. package/dist/react/primitive-hooks/useActionBarReload.js.map +1 -1
  32. package/dist/react/primitive-hooks/useActionBarSpeak.js +1 -0
  33. package/dist/react/primitive-hooks/useActionBarSpeak.js.map +1 -1
  34. package/dist/react/primitive-hooks/useActionBarStopSpeaking.js +1 -0
  35. package/dist/react/primitive-hooks/useActionBarStopSpeaking.js.map +1 -1
  36. package/dist/react/primitive-hooks/useBranchPickerNext.js +1 -0
  37. package/dist/react/primitive-hooks/useBranchPickerNext.js.map +1 -1
  38. package/dist/react/primitive-hooks/useBranchPickerPrevious.js +1 -0
  39. package/dist/react/primitive-hooks/useBranchPickerPrevious.js.map +1 -1
  40. package/dist/react/primitive-hooks/useComposerAddAttachment.js +1 -0
  41. package/dist/react/primitive-hooks/useComposerAddAttachment.js.map +1 -1
  42. package/dist/react/primitive-hooks/useComposerCancel.js +1 -0
  43. package/dist/react/primitive-hooks/useComposerCancel.js.map +1 -1
  44. package/dist/react/primitive-hooks/useComposerDictate.js +1 -0
  45. package/dist/react/primitive-hooks/useComposerDictate.js.map +1 -1
  46. package/dist/react/primitive-hooks/useComposerSend.js +1 -0
  47. package/dist/react/primitive-hooks/useComposerSend.js.map +1 -1
  48. package/dist/react/primitive-hooks/useEditComposerCancel.js +1 -0
  49. package/dist/react/primitive-hooks/useEditComposerCancel.js.map +1 -1
  50. package/dist/react/primitive-hooks/useEditComposerSend.js +1 -0
  51. package/dist/react/primitive-hooks/useEditComposerSend.js.map +1 -1
  52. package/dist/react/primitive-hooks/useMessageBranching.js +1 -0
  53. package/dist/react/primitive-hooks/useMessageBranching.js.map +1 -1
  54. package/dist/react/primitive-hooks/useMessageReload.js +1 -0
  55. package/dist/react/primitive-hooks/useMessageReload.js.map +1 -1
  56. package/dist/react/primitive-hooks/useSuggestionTrigger.js +1 -0
  57. package/dist/react/primitive-hooks/useSuggestionTrigger.js.map +1 -1
  58. package/dist/react/primitive-hooks/useThreadListItemArchive.js +1 -0
  59. package/dist/react/primitive-hooks/useThreadListItemArchive.js.map +1 -1
  60. package/dist/react/primitive-hooks/useThreadListItemDelete.js +1 -0
  61. package/dist/react/primitive-hooks/useThreadListItemDelete.js.map +1 -1
  62. package/dist/react/primitive-hooks/useThreadListItemTrigger.js +1 -0
  63. package/dist/react/primitive-hooks/useThreadListItemTrigger.js.map +1 -1
  64. package/dist/react/primitive-hooks/useThreadListItemUnarchive.js +1 -0
  65. package/dist/react/primitive-hooks/useThreadListItemUnarchive.js.map +1 -1
  66. package/dist/react/primitive-hooks/useThreadListLoadMore.js +1 -0
  67. package/dist/react/primitive-hooks/useThreadListLoadMore.js.map +1 -1
  68. package/dist/react/primitive-hooks/useThreadListNew.js +1 -0
  69. package/dist/react/primitive-hooks/useThreadListNew.js.map +1 -1
  70. package/dist/react/providers/TextMessagePartProvider.js +1 -0
  71. package/dist/react/providers/TextMessagePartProvider.js.map +1 -1
  72. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  73. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +6 -4
  74. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  75. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +31 -1
  76. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  77. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +11 -1
  78. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  79. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts +1 -1
  80. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts.map +1 -1
  81. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js +1 -1
  82. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js.map +1 -1
  83. package/dist/react/runtimes/cloud/auiV0.d.ts +12 -1
  84. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  85. package/dist/react/runtimes/cloud/auiV0.js +2 -1
  86. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  87. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +75 -73
  88. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  89. package/dist/react/runtimes/external-message-converter.js +1 -1
  90. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  91. package/dist/react/runtimes/useExternalStoreSharedOptions.js +1 -0
  92. package/dist/react/runtimes/useExternalStoreSharedOptions.js.map +1 -1
  93. package/dist/react/runtimes/useLocalRuntime.d.ts +0 -1
  94. package/dist/react/runtimes/useLocalRuntime.d.ts.map +1 -1
  95. package/dist/runtime/api/assistant-runtime.d.ts +1 -1
  96. package/dist/runtime/api/assistant-runtime.d.ts.map +1 -1
  97. package/dist/runtime/api/assistant-runtime.js +1 -1
  98. package/dist/runtime/api/assistant-runtime.js.map +1 -1
  99. package/dist/runtime/api/attachment-runtime.d.ts +1 -1
  100. package/dist/runtime/api/attachment-runtime.d.ts.map +1 -1
  101. package/dist/runtime/api/attachment-runtime.js +1 -1
  102. package/dist/runtime/api/attachment-runtime.js.map +1 -1
  103. package/dist/runtime/api/composer-runtime.d.ts +2 -2
  104. package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
  105. package/dist/runtime/api/composer-runtime.js +2 -2
  106. package/dist/runtime/api/composer-runtime.js.map +1 -1
  107. package/dist/runtime/api/message-part-runtime.d.ts +4 -4
  108. package/dist/runtime/api/message-part-runtime.d.ts.map +1 -1
  109. package/dist/runtime/api/message-part-runtime.js +3 -3
  110. package/dist/runtime/api/message-part-runtime.js.map +1 -1
  111. package/dist/runtime/api/message-runtime.d.ts +1 -1
  112. package/dist/runtime/api/message-runtime.d.ts.map +1 -1
  113. package/dist/runtime/api/message-runtime.js +2 -2
  114. package/dist/runtime/api/message-runtime.js.map +1 -1
  115. package/dist/runtime/api/thread-list-item-runtime.d.ts +1 -1
  116. package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
  117. package/dist/runtime/api/thread-list-item-runtime.js +2 -2
  118. package/dist/runtime/api/thread-list-item-runtime.js.map +1 -1
  119. package/dist/runtime/api/thread-list-runtime.d.ts +1 -1
  120. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  121. package/dist/runtime/api/thread-list-runtime.js +1 -1
  122. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  123. package/dist/runtime/base/base-thread-runtime-core.d.ts +1 -1
  124. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  125. package/dist/runtime/base/base-thread-runtime-core.js +1 -1
  126. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  127. package/dist/runtime/base/default-edit-composer-runtime-core.d.ts +2 -2
  128. package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
  129. package/dist/runtime/base/default-edit-composer-runtime-core.js +2 -2
  130. package/dist/runtime/base/default-edit-composer-runtime-core.js.map +1 -1
  131. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +1 -1
  132. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  133. package/dist/runtime/base/default-thread-composer-runtime-core.js +1 -1
  134. package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
  135. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  136. package/dist/runtime/utils/message-repository.js +4 -2
  137. package/dist/runtime/utils/message-repository.js.map +1 -1
  138. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +1 -1
  139. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  140. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +1 -1
  141. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  142. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  143. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +12 -0
  144. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  145. package/dist/runtimes/internal.d.ts +2 -1
  146. package/dist/runtimes/internal.js +2 -1
  147. package/dist/runtimes/local/local-thread-runtime-core.d.ts +3 -0
  148. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  149. package/dist/runtimes/local/local-thread-runtime-core.js +44 -13
  150. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  151. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +32 -27
  152. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  153. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +2 -0
  154. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  155. package/dist/store/clients/no-op-composer-client.js +1 -0
  156. package/dist/store/clients/no-op-composer-client.js.map +1 -1
  157. package/dist/store/runtime-clients/thread-list-runtime-client.js +1 -0
  158. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  159. package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
  160. package/dist/store/runtime-clients/thread-runtime-client.js +17 -15
  161. package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
  162. package/dist/store/scopes/thread.d.ts +1 -0
  163. package/dist/store/scopes/thread.d.ts.map +1 -1
  164. package/dist/subscribable/subscribable.d.ts +3 -3
  165. package/dist/subscribable/subscribable.d.ts.map +1 -1
  166. package/dist/subscribable/subscribable.js +3 -3
  167. package/dist/subscribable/subscribable.js.map +1 -1
  168. package/package.json +7 -7
  169. package/src/adapters/thread-history.ts +8 -0
  170. package/src/internal.ts +1 -1
  171. package/src/model-context/frame/host.test.ts +47 -0
  172. package/src/model-context/frame/host.ts +46 -19
  173. package/src/model-context/types.ts +16 -6
  174. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +15 -5
  175. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +20 -6
  176. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.ts +34 -0
  177. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +26 -5
  178. package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.ts +5 -1
  179. package/src/react/runtimes/cloud/auiV0.ts +14 -0
  180. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +90 -88
  181. package/src/react/runtimes/external-message-converter.test.ts +346 -0
  182. package/src/react/runtimes/external-message-converter.ts +1 -1
  183. package/src/react/runtimes/useLocalRuntime.test.tsx +43 -0
  184. package/src/runtime/api/assistant-runtime.ts +4 -1
  185. package/src/runtime/api/attachment-runtime.ts +8 -2
  186. package/src/runtime/api/composer-runtime.ts +9 -5
  187. package/src/runtime/api/message-part-runtime.ts +10 -3
  188. package/src/runtime/api/message-runtime.ts +7 -2
  189. package/src/runtime/api/thread-list-item-runtime.ts +7 -2
  190. package/src/runtime/api/thread-list-runtime.ts +10 -2
  191. package/src/runtime/base/base-thread-runtime-core.ts +5 -1
  192. package/src/runtime/base/default-edit-composer-runtime-core.ts +14 -2
  193. package/src/runtime/base/default-thread-composer-runtime-core.ts +11 -1
  194. package/src/runtime/utils/message-repository.ts +19 -13
  195. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +4 -1
  196. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +15 -0
  197. package/src/runtimes/internal.ts +3 -0
  198. package/src/runtimes/local/local-thread-runtime-core.test.ts +596 -0
  199. package/src/runtimes/local/local-thread-runtime-core.ts +85 -21
  200. package/src/runtimes/local/should-continue.test.ts +122 -0
  201. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +9 -10
  202. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +38 -32
  203. package/src/store/clients/model-context-client.test.ts +21 -1
  204. package/src/store/runtime-clients/thread-runtime-client.ts +2 -1
  205. package/src/store/scopes/thread.ts +1 -0
  206. package/src/subscribable/subscribable.ts +16 -8
  207. package/src/tests/MessageRepository.test.ts +12 -0
  208. package/src/tests/RemoteThreadListThreadListRuntimeCore-switchToThread-position.test.ts +121 -0
  209. package/src/tests/auiV0Encode.test.ts +74 -0
  210. package/src/tests/base-composer-runtime-core.test.ts +422 -0
  211. package/src/tests/external-store-thread-list-runtime-core.test.ts +6 -6
  212. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +575 -0
  213. package/src/tests/external-store-thread-runtime-core.test.ts +55 -1
  214. package/src/tests/OptimisticState-delete-crash.test.ts +0 -208
@@ -1 +1 @@
1
- {"version":3,"file":"subscribable.js","names":["Unsubscribe","notifyEventListeners","SKIP_UPDATE","Symbol","Subscribable","subscribe","callback","SubscribableWithState","path","TPath","getState","TState","NestedSubscribable","EventSubscribable","event","TEvent","binding","unstable_on","payload","shallowEqual","objA","T","objB","undefined","keysA","Object","keys","length","key","valueA","valueB","is","BaseSubscribable","_subscribers","Set","add","delete","waitForUpdate","Promise","resolve","unsubscribe","_notifySubscribers","errors","error","push","console","AggregateError","BaseSubject","_subscriptions","_connection","isConnected","_connect","notifySubscribers","errorContext","_updateConnection","size","ShallowMemoizeSubject","constructor","state","Error","_previousState","_syncState","LazyMemoizeSubject","_previousStateDirty","newState","NestedSubscriptionSubject","outerSubscribe","lastState","innerUnsubscribe","onRuntimeUpdate","outerUnsubscribe","EventSubscriptionSubject","config"],"sources":["../../src/subscribable/subscribable.ts"],"sourcesContent":["import type { Unsubscribe } from \"../types/unsubscribe\";\nimport { notifyEventListeners } from \"../utils/notify-event-listeners\";\n\nexport const SKIP_UPDATE = Symbol(\"skip-update\");\nexport type SKIP_UPDATE = typeof SKIP_UPDATE;\n\nexport type Subscribable = {\n subscribe: (callback: () => void) => Unsubscribe;\n};\n\nexport type SubscribableWithState<TState, TPath> = Subscribable & {\n path: TPath;\n getState: () => TState;\n};\n\nexport type NestedSubscribable<\n TState extends Subscribable | undefined,\n TPath,\n> = SubscribableWithState<TState, TPath>;\n\nexport type EventSubscribable<TEvent extends string> = {\n event: TEvent;\n binding: SubscribableWithState<\n | {\n unstable_on: (\n event: TEvent,\n callback: (payload?: unknown) => void,\n ) => Unsubscribe;\n }\n | undefined,\n unknown\n >;\n};\n\nfunction shallowEqual<T extends object>(\n objA: T | undefined,\n objB: T | undefined,\n) {\n if (objA === undefined && objB === undefined) return true;\n if (objA === undefined) return false;\n if (objB === undefined) return false;\n\n const keysA = Object.keys(objA);\n if (keysA.length !== Object.keys(objB).length) return false;\n\n for (const key of keysA) {\n const valueA = objA[key as keyof T];\n const valueB = objB[key as keyof T];\n if (!Object.is(valueA, valueB)) return false;\n }\n\n return true;\n}\n\nexport class BaseSubscribable {\n private _subscribers = new Set<() => void>();\n\n public subscribe(callback: () => void): Unsubscribe {\n this._subscribers.add(callback);\n return () => this._subscribers.delete(callback);\n }\n\n public waitForUpdate() {\n return new Promise<void>((resolve) => {\n const unsubscribe = this.subscribe(() => {\n unsubscribe();\n resolve();\n });\n });\n }\n\n protected _notifySubscribers() {\n const errors = [];\n for (const callback of this._subscribers) {\n try {\n callback();\n } catch (error) {\n errors.push(error);\n }\n }\n\n if (errors.length > 0) {\n if (errors.length === 1) {\n throw errors[0];\n } else {\n for (const error of errors) {\n console.error(error);\n }\n throw new AggregateError(errors);\n }\n }\n }\n}\n\n// lazy connect/disconnect: only opens upstream subscription while it has subscribers\nexport abstract class BaseSubject {\n private _subscriptions = new Set<(payload?: unknown) => void>();\n private _connection: Unsubscribe | undefined;\n\n protected get isConnected() {\n return !!this._connection;\n }\n\n protected abstract _connect(): Unsubscribe;\n\n protected notifySubscribers(payload?: unknown, errorContext?: string) {\n if (errorContext) {\n notifyEventListeners(this._subscriptions, payload, errorContext);\n return;\n }\n\n for (const callback of this._subscriptions) callback(payload);\n }\n\n private _updateConnection() {\n if (this._subscriptions.size > 0) {\n if (this._connection) return;\n this._connection = this._connect();\n } else {\n this._connection?.();\n this._connection = undefined;\n }\n }\n\n public subscribe(callback: (payload?: unknown) => void) {\n this._subscriptions.add(callback);\n this._updateConnection();\n\n return () => {\n this._subscriptions.delete(callback);\n this._updateConnection();\n };\n }\n}\n\nexport class ShallowMemoizeSubject<TState extends object, TPath>\n extends BaseSubject\n implements SubscribableWithState<TState, TPath>\n{\n public get path() {\n return this.binding.path;\n }\n\n constructor(\n private binding: SubscribableWithState<TState | SKIP_UPDATE, TPath>,\n ) {\n super();\n const state = binding.getState();\n if (state === SKIP_UPDATE)\n throw new Error(\"Entry not available in the store\");\n this._previousState = state;\n }\n\n private _previousState: TState;\n public getState = () => {\n if (!this.isConnected) this._syncState();\n return this._previousState;\n };\n\n private _syncState() {\n const state = this.binding.getState();\n if (state === SKIP_UPDATE) return false;\n if (shallowEqual(state, this._previousState)) return false;\n this._previousState = state;\n return true;\n }\n\n protected _connect() {\n const callback = () => {\n if (this._syncState()) {\n this.notifySubscribers();\n }\n };\n\n return this.binding.subscribe(callback);\n }\n}\n\nexport class LazyMemoizeSubject<TState extends object, TPath>\n extends BaseSubject\n implements SubscribableWithState<TState, TPath>\n{\n public get path() {\n return this.binding.path;\n }\n\n constructor(\n private binding: SubscribableWithState<TState | SKIP_UPDATE, TPath>,\n ) {\n super();\n }\n\n private _previousStateDirty = true;\n private _previousState: TState | undefined;\n public getState = () => {\n if (!this.isConnected || this._previousStateDirty) {\n const newState = this.binding.getState();\n if (newState !== SKIP_UPDATE) {\n this._previousState = newState;\n }\n this._previousStateDirty = false;\n }\n if (this._previousState === undefined)\n throw new Error(\"Entry not available in the store\");\n return this._previousState;\n };\n\n protected _connect() {\n const callback = () => {\n this._previousStateDirty = true;\n this.notifySubscribers();\n };\n\n return this.binding.subscribe(callback);\n }\n}\n\nexport class NestedSubscriptionSubject<\n TState extends Subscribable | undefined,\n TPath,\n>\n extends BaseSubject\n implements\n SubscribableWithState<TState, TPath>,\n NestedSubscribable<TState, TPath>\n{\n public get path() {\n return this.binding.path;\n }\n\n constructor(private binding: NestedSubscribable<TState, TPath>) {\n super();\n }\n\n public getState() {\n return this.binding.getState();\n }\n\n public outerSubscribe(callback: () => void) {\n return this.binding.subscribe(callback);\n }\n\n protected _connect(): Unsubscribe {\n const callback = () => {\n this.notifySubscribers();\n };\n\n let lastState = this.binding.getState();\n let innerUnsubscribe = lastState?.subscribe(callback);\n const onRuntimeUpdate = () => {\n const newState = this.binding.getState();\n if (newState === lastState) return;\n lastState = newState;\n\n innerUnsubscribe?.();\n innerUnsubscribe = newState?.subscribe(callback);\n\n callback();\n };\n\n const outerUnsubscribe = this.outerSubscribe(onRuntimeUpdate);\n return () => {\n outerUnsubscribe?.();\n innerUnsubscribe?.();\n };\n }\n}\n\nexport class EventSubscriptionSubject<\n TEvent extends string,\n> extends BaseSubject {\n constructor(private config: EventSubscribable<TEvent>) {\n super();\n }\n\n public getState() {\n return this.config.binding.getState();\n }\n\n public outerSubscribe(callback: () => void) {\n return this.config.binding.subscribe(callback);\n }\n\n protected _connect(): Unsubscribe {\n const errorContext = `Runtime event \"${this.config.event}\"`;\n const callback = (payload?: unknown) => {\n this.notifySubscribers(payload, errorContext);\n };\n\n let lastState = this.config.binding.getState();\n let innerUnsubscribe = lastState?.unstable_on(this.config.event, callback);\n const onRuntimeUpdate = () => {\n const newState = this.config.binding.getState();\n if (newState === lastState) return;\n lastState = newState;\n\n innerUnsubscribe?.();\n innerUnsubscribe = newState?.unstable_on(this.config.event, callback);\n };\n\n const outerUnsubscribe = this.outerSubscribe(onRuntimeUpdate);\n return () => {\n outerUnsubscribe?.();\n innerUnsubscribe?.();\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"subscribable.js","names":["Unsubscribe","notifyEventListeners","SKIP_UPDATE","Symbol","Subscribable","subscribe","callback","SubscribableWithState","path","TPath","getState","TState","NestedSubscribable","EventSubscribable","event","TEvent","binding","unstable_on","payload","shallowEqual","objA","T","objB","undefined","keysA","Object","keys","length","key","valueA","valueB","is","BaseSubscribable","_subscribers","Set","add","delete","waitForUpdate","Promise","resolve","unsubscribe","_notifySubscribers","errors","error","push","console","AggregateError","BaseSubject","_subscriptions","_connection","isConnected","_connect","notifySubscribers","errorContext","_updateConnection","size","ShallowMemoizeSubject","constructor","state","Error","_previousState","_syncState","LazyMemoizeSubject","_previousStateDirty","newState","NestedSubscriptionSubject","outerSubscribe","lastState","innerUnsubscribe","onRuntimeUpdate","outerUnsubscribe","EventSubscriptionSubject","config"],"sources":["../../src/subscribable/subscribable.ts"],"sourcesContent":["import type { Unsubscribe } from \"../types/unsubscribe\";\nimport { notifyEventListeners } from \"../utils/notify-event-listeners\";\n\nexport const SKIP_UPDATE = Symbol(\"skip-update\");\nexport type SKIP_UPDATE = typeof SKIP_UPDATE;\n\nexport type Subscribable = {\n subscribe: (callback: () => void) => Unsubscribe;\n};\n\nexport type SubscribableWithState<TState, TPath> = Subscribable & {\n path: TPath;\n getState: () => TState;\n};\n\nexport type NestedSubscribable<\n TState extends Subscribable | undefined,\n TPath,\n> = SubscribableWithState<TState, TPath>;\n\nexport type EventSubscribable<TEvent extends string> = {\n event: TEvent;\n binding: SubscribableWithState<\n | {\n unstable_on: (\n event: TEvent,\n callback: (payload?: unknown) => void,\n ) => Unsubscribe;\n }\n | undefined,\n unknown\n >;\n};\n\nfunction shallowEqual<T extends object>(\n objA: T | undefined,\n objB: T | undefined,\n) {\n if (objA === undefined && objB === undefined) return true;\n if (objA === undefined) return false;\n if (objB === undefined) return false;\n\n const keysA = Object.keys(objA);\n if (keysA.length !== Object.keys(objB).length) return false;\n\n for (const key of keysA) {\n const valueA = objA[key as keyof T];\n const valueB = objB[key as keyof T];\n if (!Object.is(valueA, valueB)) return false;\n }\n\n return true;\n}\n\nexport class BaseSubscribable {\n private _subscribers = new Set<() => void>();\n\n public subscribe(callback: () => void): Unsubscribe {\n this._subscribers.add(callback);\n return () => this._subscribers.delete(callback);\n }\n\n public waitForUpdate() {\n return new Promise<void>((resolve) => {\n const unsubscribe = this.subscribe(() => {\n unsubscribe();\n resolve();\n });\n });\n }\n\n protected _notifySubscribers() {\n const errors = [];\n for (const callback of this._subscribers) {\n try {\n callback();\n } catch (error) {\n errors.push(error);\n }\n }\n\n if (errors.length > 0) {\n if (errors.length === 1) {\n throw errors[0];\n } else {\n for (const error of errors) {\n console.error(error);\n }\n throw new AggregateError(errors);\n }\n }\n }\n}\n\n// lazy connect/disconnect: only opens upstream subscription while it has subscribers\nexport abstract class BaseSubject {\n private _subscriptions = new Set<(payload?: unknown) => void>();\n private _connection: Unsubscribe | undefined;\n\n protected get isConnected() {\n return !!this._connection;\n }\n\n protected abstract _connect(): Unsubscribe;\n\n protected notifySubscribers(payload?: unknown, errorContext?: string) {\n if (errorContext) {\n notifyEventListeners(this._subscriptions, payload, errorContext);\n return;\n }\n\n for (const callback of this._subscriptions) callback(payload);\n }\n\n private _updateConnection() {\n if (this._subscriptions.size > 0) {\n if (this._connection) return;\n this._connection = this._connect();\n } else {\n this._connection?.();\n this._connection = undefined;\n }\n }\n\n public subscribe(callback: (payload?: unknown) => void) {\n this._subscriptions.add(callback);\n this._updateConnection();\n\n return () => {\n this._subscriptions.delete(callback);\n this._updateConnection();\n };\n }\n}\n\nexport class ShallowMemoizeSubject<TState extends object, TPath>\n extends BaseSubject\n implements SubscribableWithState<TState, TPath>\n{\n public get path() {\n return this.binding.path;\n }\n\n private binding: SubscribableWithState<TState | SKIP_UPDATE, TPath>;\n\n constructor(binding: SubscribableWithState<TState | SKIP_UPDATE, TPath>) {\n super();\n this.binding = binding;\n const state = binding.getState();\n if (state === SKIP_UPDATE)\n throw new Error(\"Entry not available in the store\");\n this._previousState = state;\n }\n\n private _previousState: TState;\n public getState = () => {\n if (!this.isConnected) this._syncState();\n return this._previousState;\n };\n\n private _syncState() {\n const state = this.binding.getState();\n if (state === SKIP_UPDATE) return false;\n if (shallowEqual(state, this._previousState)) return false;\n this._previousState = state;\n return true;\n }\n\n protected _connect() {\n const callback = () => {\n if (this._syncState()) {\n this.notifySubscribers();\n }\n };\n\n return this.binding.subscribe(callback);\n }\n}\n\nexport class LazyMemoizeSubject<TState extends object, TPath>\n extends BaseSubject\n implements SubscribableWithState<TState, TPath>\n{\n public get path() {\n return this.binding.path;\n }\n\n private binding: SubscribableWithState<TState | SKIP_UPDATE, TPath>;\n\n constructor(binding: SubscribableWithState<TState | SKIP_UPDATE, TPath>) {\n super();\n this.binding = binding;\n }\n\n private _previousStateDirty = true;\n private _previousState: TState | undefined;\n public getState = () => {\n if (!this.isConnected || this._previousStateDirty) {\n const newState = this.binding.getState();\n if (newState !== SKIP_UPDATE) {\n this._previousState = newState;\n }\n this._previousStateDirty = false;\n }\n if (this._previousState === undefined)\n throw new Error(\"Entry not available in the store\");\n return this._previousState;\n };\n\n protected _connect() {\n const callback = () => {\n this._previousStateDirty = true;\n this.notifySubscribers();\n };\n\n return this.binding.subscribe(callback);\n }\n}\n\nexport class NestedSubscriptionSubject<\n TState extends Subscribable | undefined,\n TPath,\n>\n extends BaseSubject\n implements\n SubscribableWithState<TState, TPath>,\n NestedSubscribable<TState, TPath>\n{\n public get path() {\n return this.binding.path;\n }\n\n private binding: NestedSubscribable<TState, TPath>;\n\n constructor(binding: NestedSubscribable<TState, TPath>) {\n super();\n this.binding = binding;\n }\n\n public getState() {\n return this.binding.getState();\n }\n\n public outerSubscribe(callback: () => void) {\n return this.binding.subscribe(callback);\n }\n\n protected _connect(): Unsubscribe {\n const callback = () => {\n this.notifySubscribers();\n };\n\n let lastState = this.binding.getState();\n let innerUnsubscribe = lastState?.subscribe(callback);\n const onRuntimeUpdate = () => {\n const newState = this.binding.getState();\n if (newState === lastState) return;\n lastState = newState;\n\n innerUnsubscribe?.();\n innerUnsubscribe = newState?.subscribe(callback);\n\n callback();\n };\n\n const outerUnsubscribe = this.outerSubscribe(onRuntimeUpdate);\n return () => {\n outerUnsubscribe?.();\n innerUnsubscribe?.();\n };\n }\n}\n\nexport class EventSubscriptionSubject<\n TEvent extends string,\n> extends BaseSubject {\n private config: EventSubscribable<TEvent>;\n\n constructor(config: EventSubscribable<TEvent>) {\n super();\n this.config = config;\n }\n\n public getState() {\n return this.config.binding.getState();\n }\n\n public outerSubscribe(callback: () => void) {\n return this.config.binding.subscribe(callback);\n }\n\n protected _connect(): Unsubscribe {\n const errorContext = `Runtime event \"${this.config.event}\"`;\n const callback = (payload?: unknown) => {\n this.notifySubscribers(payload, errorContext);\n };\n\n let lastState = this.config.binding.getState();\n let innerUnsubscribe = lastState?.unstable_on(this.config.event, callback);\n const onRuntimeUpdate = () => {\n const newState = this.config.binding.getState();\n if (newState === lastState) return;\n lastState = newState;\n\n innerUnsubscribe?.();\n innerUnsubscribe = newState?.unstable_on(this.config.event, callback);\n };\n\n const outerUnsubscribe = this.outerSubscribe(onRuntimeUpdate);\n return () => {\n outerUnsubscribe?.();\n innerUnsubscribe?.();\n };\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/core",
3
- "version": "0.2.22",
3
+ "version": "0.2.23",
4
4
  "description": "Framework-agnostic core runtime for assistant-ui",
5
5
  "keywords": [
6
6
  "assistant",
@@ -59,7 +59,7 @@
59
59
  ],
60
60
  "sideEffects": false,
61
61
  "dependencies": {
62
- "assistant-stream": "^0.3.27",
62
+ "assistant-stream": "^0.3.28",
63
63
  "nanoid": "^6.0.0"
64
64
  },
65
65
  "optionalDevDependencies": {
@@ -95,11 +95,11 @@
95
95
  "react-dom": "^19.2.8",
96
96
  "vitest": "^4.1.10",
97
97
  "zustand": "^5.0.14",
98
- "@assistant-ui/store": "0.2.21",
99
- "@assistant-ui/tap": "0.9.5",
100
- "@assistant-ui/vite": "0.0.10",
101
- "@assistant-ui/x-buildutils": "0.0.19",
102
- "assistant-cloud": "0.1.36"
98
+ "@assistant-ui/store": "0.2.22",
99
+ "@assistant-ui/tap": "0.9.6",
100
+ "@assistant-ui/vite": "0.0.11",
101
+ "@assistant-ui/x-buildutils": "0.0.20",
102
+ "assistant-cloud": "0.1.37"
103
103
  },
104
104
  "publishConfig": {
105
105
  "access": "public",
@@ -65,6 +65,14 @@ export type ThreadHistoryAdapter = {
65
65
  options: ChatModelRunOptions,
66
66
  ): AsyncGenerator<ChatModelRunResult, void, unknown>;
67
67
  append(item: ExportedMessageRepositoryItem): Promise<void>;
68
+ /**
69
+ * Rewrites a previously appended message in place, keyed by its message id.
70
+ * Adapters that implement this let a runtime persist a run paused for tool
71
+ * approval and finalize the same message once the run resumes. An update may
72
+ * arrive for an id whose earlier write failed; treat it as an upsert keyed
73
+ * on the message id rather than assuming the entry exists.
74
+ */
75
+ update?(item: ExportedMessageRepositoryItem): Promise<void>;
68
76
  delete?(items: ExportedMessageRepositoryItem[]): Promise<void>;
69
77
  /** Required when used with `useAISDKRuntime` / `useChatRuntime`. */
70
78
  withFormat?<TMessage, TStorageFormat extends Record<string, unknown>>(
package/src/internal.ts CHANGED
@@ -40,7 +40,7 @@ export { CompositeContextProvider } from "./utils/composite-context-provider";
40
40
 
41
41
  // Shared attachment data-URL encoder, reused by framework adapters so the
42
42
  // FileReader fallback lives in one place.
43
- export { getFileDataURL } from "./adapters/attachment";
43
+ export { getFileDataURL, fileMatchesAccept } from "./adapters/attachment";
44
44
  export { isCreateAttachment } from "./types/attachment";
45
45
 
46
46
  // Streaming-stable tool-args stringifier, reused by framework adapters so the
@@ -88,6 +88,53 @@ describe("AssistantFrameHost", () => {
88
88
  expect(vi.getTimerCount()).toBe(0);
89
89
  });
90
90
 
91
+ it("rejects pending tool calls when execution is aborted", async () => {
92
+ const { execute, host } = createHost();
93
+ const abortController = new AbortController();
94
+ const abortError = new Error("Run cancelled");
95
+ abortError.name = "AbortError";
96
+ const result = Promise.resolve(
97
+ execute(
98
+ {},
99
+ {
100
+ ...executionContext,
101
+ abortSignal: abortController.signal,
102
+ },
103
+ ),
104
+ );
105
+ const onRejected = vi.fn();
106
+ void result.catch(onRejected);
107
+
108
+ abortController.abort(abortError);
109
+ await Promise.resolve();
110
+
111
+ expect(onRejected).toHaveBeenCalledWith(abortError);
112
+ expect(vi.getTimerCount()).toBe(0);
113
+ host.dispose();
114
+ });
115
+
116
+ it("does not post tool calls when execution is already aborted", async () => {
117
+ const { execute, host, postMessage } = createHost();
118
+ const abortController = new AbortController();
119
+ const abortError = new Error("Run cancelled");
120
+ abortError.name = "AbortError";
121
+ abortController.abort(abortError);
122
+
123
+ await expect(
124
+ execute(
125
+ {},
126
+ {
127
+ ...executionContext,
128
+ abortSignal: abortController.signal,
129
+ },
130
+ ),
131
+ ).rejects.toBe(abortError);
132
+
133
+ expect(postMessage).toHaveBeenCalledOnce();
134
+ expect(vi.getTimerCount()).toBe(0);
135
+ host.dispose();
136
+ });
137
+
91
138
  it("rejects tool calls made after disposal without posting a request", async () => {
92
139
  const { execute, host, postMessage } = createHost();
93
140
  host.dispose();
@@ -40,6 +40,13 @@ const deserializeModelContext = (
40
40
  }),
41
41
  });
42
42
 
43
+ const getAbortReason = (signal: AbortSignal): unknown => {
44
+ if (signal.reason !== undefined) return signal.reason;
45
+ const error = new Error("Tool call was aborted");
46
+ error.name = "AbortError";
47
+ return error;
48
+ };
49
+
43
50
  export class AssistantFrameHost implements ModelContextProvider {
44
51
  private _context: ModelContext = {};
45
52
  private _subscribers = new Set<() => void>();
@@ -105,7 +112,8 @@ export class AssistantFrameHost implements ModelContextProvider {
105
112
  name,
106
113
  {
107
114
  ...tool,
108
- execute: (args: any) => this.callTool(name, args),
115
+ execute: (args: any, context: { abortSignal: AbortSignal }) =>
116
+ this.callTool(name, args, context.abortSignal),
109
117
  } as Tool<any, any>,
110
118
  ]),
111
119
  ),
@@ -113,7 +121,11 @@ export class AssistantFrameHost implements ModelContextProvider {
113
121
  this.notifySubscribers();
114
122
  }
115
123
 
116
- private callTool(toolName: string, args: any): Promise<any> {
124
+ private callTool(
125
+ toolName: string,
126
+ args: any,
127
+ abortSignal: AbortSignal,
128
+ ): Promise<any> {
117
129
  return this.sendRequest(
118
130
  {
119
131
  type: "tool-call",
@@ -123,6 +135,7 @@ export class AssistantFrameHost implements ModelContextProvider {
123
135
  },
124
136
  30000,
125
137
  `Tool call "${toolName}" timed out`,
138
+ abortSignal,
126
139
  );
127
140
  }
128
141
 
@@ -130,40 +143,54 @@ export class AssistantFrameHost implements ModelContextProvider {
130
143
  message: T,
131
144
  timeout = 30000,
132
145
  timeoutMessage = "Request timed out",
146
+ abortSignal?: AbortSignal,
133
147
  ): Promise<any> {
134
148
  if (this._disposed) {
135
149
  return Promise.reject(new Error("AssistantFrameHost has been disposed"));
136
150
  }
151
+ if (abortSignal?.aborted) {
152
+ return Promise.reject(getAbortReason(abortSignal));
153
+ }
137
154
 
138
155
  return new Promise((resolve, reject) => {
139
- this._pendingRequests.set(message.id, { resolve, reject });
140
-
141
- this._iframeWindow.postMessage(
142
- { channel: FRAME_MESSAGE_CHANNEL, message },
143
- this._targetOrigin,
144
- );
145
-
146
- const timeoutId = setTimeout(() => {
156
+ let timeoutId: ReturnType<typeof setTimeout> | undefined;
157
+ const onAbort = () => {
158
+ if (!abortSignal) return;
147
159
  const pending = this._pendingRequests.get(message.id);
148
160
  if (pending) {
149
- pending.reject(new Error(timeoutMessage));
161
+ pending.reject(getAbortReason(abortSignal));
150
162
  this._pendingRequests.delete(message.id);
151
163
  }
152
- }, timeout);
153
-
154
- const originalResolve = this._pendingRequests.get(message.id)!.resolve;
155
- const originalReject = this._pendingRequests.get(message.id)!.reject;
164
+ };
165
+ const cleanup = () => {
166
+ if (timeoutId !== undefined) clearTimeout(timeoutId);
167
+ abortSignal?.removeEventListener("abort", onAbort);
168
+ };
156
169
 
157
170
  this._pendingRequests.set(message.id, {
158
171
  resolve: (value: any) => {
159
- clearTimeout(timeoutId);
160
- originalResolve(value);
172
+ cleanup();
173
+ resolve(value);
161
174
  },
162
175
  reject: (error: any) => {
163
- clearTimeout(timeoutId);
164
- originalReject(error);
176
+ cleanup();
177
+ reject(error);
165
178
  },
166
179
  });
180
+
181
+ timeoutId = setTimeout(() => {
182
+ const pending = this._pendingRequests.get(message.id);
183
+ if (pending) {
184
+ pending.reject(new Error(timeoutMessage));
185
+ this._pendingRequests.delete(message.id);
186
+ }
187
+ }, timeout);
188
+ abortSignal?.addEventListener("abort", onAbort, { once: true });
189
+
190
+ this._iframeWindow.postMessage(
191
+ { channel: FRAME_MESSAGE_CHANNEL, message },
192
+ this._targetOrigin,
193
+ );
167
194
  });
168
195
  }
169
196
 
@@ -55,6 +55,12 @@ export type AssistantContextConfig = {
55
55
  disabled?: boolean | undefined;
56
56
  };
57
57
 
58
+ const stripOverwrite = (tool: Tool<any, any>): Tool<any, any> => {
59
+ if (!tool.overwrite) return tool;
60
+ const { overwrite: _, ...rest } = tool;
61
+ return rest as Tool<any, any>;
62
+ };
63
+
58
64
  export const mergeModelContexts = (
59
65
  configSet: Set<ModelContextProvider>,
60
66
  ): ModelContext => {
@@ -79,25 +85,29 @@ export const mergeModelContexts = (
79
85
  if (existing && existing !== tool) {
80
86
  const existingPriority = toolPriorities[name]!;
81
87
  if (existingPriority === priority) {
82
- throw new Error(
83
- `You tried to define a tool with the name ${name}, but it already exists.`,
84
- );
88
+ if (!tool.overwrite) {
89
+ throw new Error(
90
+ `You tried to define a tool with the name ${name}, but it already exists.`,
91
+ );
92
+ }
93
+ acc.tools![name] = stripOverwrite(tool);
94
+ continue;
85
95
  }
86
96
 
87
97
  const higherPriorityTool =
88
98
  existingPriority > priority ? existing : tool;
89
99
  const lowerPriorityTool =
90
100
  existingPriority > priority ? tool : existing;
91
- acc.tools![name] = {
101
+ acc.tools![name] = stripOverwrite({
92
102
  ...lowerPriorityTool,
93
103
  ...higherPriorityTool,
94
- } as Tool<any, any>;
104
+ } as Tool<any, any>);
95
105
  toolPriorities[name] = Math.max(existingPriority, priority);
96
106
  continue;
97
107
  }
98
108
 
99
109
  if (!acc.tools) acc.tools = {};
100
- acc.tools[name] = tool;
110
+ acc.tools[name] = stripOverwrite(tool);
101
111
  toolPriorities[name] ??= priority;
102
112
  }
103
113
  }
@@ -275,12 +275,22 @@ export const parseStoredMessageRepository = (
275
275
  };
276
276
 
277
277
  class AsyncStorageHistoryAdapter implements ThreadHistoryAdapter {
278
+ private storage: AsyncStorageLike;
279
+ private aui: ReturnType<typeof useAui>;
280
+ private prefix: string;
281
+ private mutationQueue: KeyedMutationQueue;
282
+
278
283
  constructor(
279
- private storage: AsyncStorageLike,
280
- private aui: ReturnType<typeof useAui>,
281
- private prefix: string,
282
- private mutationQueue: KeyedMutationQueue,
283
- ) {}
284
+ storage: AsyncStorageLike,
285
+ aui: ReturnType<typeof useAui>,
286
+ prefix: string,
287
+ mutationQueue: KeyedMutationQueue,
288
+ ) {
289
+ this.storage = storage;
290
+ this.aui = aui;
291
+ this.prefix = prefix;
292
+ this.mutationQueue = mutationQueue;
293
+ }
284
294
 
285
295
  private _messagesKey(remoteId: string) {
286
296
  return `${this.prefix}messages:${remoteId}`;
@@ -356,22 +356,36 @@ export class RemoteThreadListThreadListRuntimeCore
356
356
  [remoteMetadata.remoteId]: mappingId,
357
357
  };
358
358
 
359
- // Filter both arrays first so a concurrent `list()` can't leave the id
360
- // duplicated or under the wrong status.
359
+ // A concurrent `list()` may already have placed this thread; keep that
360
+ // position and only merge metadata. A genuinely absent thread stays
361
+ // appended: it may live on an unloaded page, and a prepend would pin it
362
+ // above newer threads permanently since `classifyThreads` skips ids it
363
+ // has already seen. Filtering both arrays first still prevents
364
+ // duplication or a wrong-status entry from `list()`.
365
+ const remoteId = remoteMetadata.remoteId;
366
+ const wasInTarget =
367
+ remoteMetadata.status === "regular"
368
+ ? state.threadIds.includes(remoteId)
369
+ : state.archivedThreadIds.includes(remoteId);
370
+
361
371
  const threadIdsWithoutRemote = state.threadIds.filter(
362
- (id) => id !== remoteMetadata.remoteId,
372
+ (id) => id !== remoteId,
363
373
  );
364
374
  const archivedThreadIdsWithoutRemote = state.archivedThreadIds.filter(
365
- (id) => id !== remoteMetadata.remoteId,
375
+ (id) => id !== remoteId,
366
376
  );
367
377
 
368
378
  const newThreadIds =
369
379
  remoteMetadata.status === "regular"
370
- ? [...threadIdsWithoutRemote, remoteMetadata.remoteId]
380
+ ? wasInTarget
381
+ ? state.threadIds
382
+ : [...threadIdsWithoutRemote, remoteId]
371
383
  : threadIdsWithoutRemote;
372
384
  const newArchivedThreadIds =
373
385
  remoteMetadata.status === "archived"
374
- ? [...archivedThreadIdsWithoutRemote, remoteMetadata.remoteId]
386
+ ? wasInTarget
387
+ ? state.archivedThreadIds
388
+ : [...archivedThreadIdsWithoutRemote, remoteId]
375
389
  : archivedThreadIdsWithoutRemote;
376
390
 
377
391
  this._state.update({
@@ -0,0 +1,34 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { extractAuiV0 } from "./AssistantCloudThreadHistoryAdapter";
3
+
4
+ const auiV0Message = (status: { type: string; reason?: string }) => ({
5
+ role: "assistant",
6
+ status,
7
+ content: [
8
+ {
9
+ type: "tool-call",
10
+ toolCallId: "call-1",
11
+ toolName: "send_email",
12
+ args: {},
13
+ argsText: "{}",
14
+ },
15
+ ],
16
+ metadata: { steps: [{ usage: { inputTokens: 1, outputTokens: 2 } }] },
17
+ });
18
+
19
+ describe("extractAuiV0", () => {
20
+ it("returns null for a requires-action message so paused writes never report", () => {
21
+ expect(
22
+ extractAuiV0(
23
+ auiV0Message({ type: "requires-action", reason: "tool-calls" }),
24
+ ),
25
+ ).toBeNull();
26
+ });
27
+
28
+ it("reports a terminal message once with its usage", () => {
29
+ const result = extractAuiV0(auiV0Message({ type: "complete" }));
30
+ expect(result?.status).toBe("completed");
31
+ expect(result?.inputTokens).toBe(1);
32
+ expect(result?.outputTokens).toBe(2);
33
+ });
34
+ });
@@ -22,10 +22,13 @@ const globalPersistence = new WeakMap<
22
22
  >();
23
23
 
24
24
  class AssistantCloudThreadHistoryAdapter implements ThreadHistoryAdapter {
25
- constructor(
26
- private cloudRef: RefObject<AssistantCloud>,
27
- private aui: AssistantClient,
28
- ) {}
25
+ private cloudRef: RefObject<AssistantCloud>;
26
+ private aui: AssistantClient;
27
+
28
+ constructor(cloudRef: RefObject<AssistantCloud>, aui: AssistantClient) {
29
+ this.cloudRef = cloudRef;
30
+ this.aui = aui;
31
+ }
29
32
 
30
33
  private get _persistence(): CloudMessagePersistence {
31
34
  const key = this.aui.threadListItem();
@@ -102,6 +105,21 @@ class AssistantCloudThreadHistoryAdapter implements ThreadHistoryAdapter {
102
105
  }
103
106
  }
104
107
 
108
+ async update(item: ExportedMessageRepositoryItem) {
109
+ if (!this._persistence.isPersisted(item.message.id)) {
110
+ return this.append(item);
111
+ }
112
+ const { message } = item;
113
+ const remoteId = this.aui.threadListItem().getState().remoteId;
114
+ if (!remoteId) return;
115
+ const encoded = auiV0Encode(message);
116
+ await this._persistence.update(remoteId, message.id, "aui/v0", encoded);
117
+
118
+ if (this.cloudRef.current.telemetry.enabled) {
119
+ this._maybeReportRun(remoteId, "aui/v0", encoded);
120
+ }
121
+ }
122
+
105
123
  async delete() {
106
124
  throw new Error(
107
125
  "Assistant Cloud does not support deleting thread messages yet.",
@@ -341,7 +359,7 @@ const AUI_STATUS_MAP: Record<string, TelemetryData["status"]> = {
341
359
  incomplete: "incomplete",
342
360
  };
343
361
 
344
- function extractAuiV0<T>(content: T): TelemetryData | null {
362
+ export function extractAuiV0<T>(content: T): TelemetryData | null {
345
363
  const msg = content as {
346
364
  role?: string;
347
365
  status?: { type: string };
@@ -369,6 +387,9 @@ function extractAuiV0<T>(content: T): TelemetryData | null {
369
387
  };
370
388
 
371
389
  if (msg.role !== "assistant") return null;
390
+ // A paused (requires-action) write is not a finished run; reporting it would
391
+ // mislabel it "completed" and double-count steps once the terminal write reports.
392
+ if (msg.status?.type === "requires-action") return null;
372
393
 
373
394
  const toolCalls = msg.content
374
395
  ?.filter((p) => p.type === "tool-call" && p.toolName && p.toolCallId)
@@ -19,7 +19,11 @@ const guessAttachmentType = (
19
19
  export class CloudFileAttachmentAdapter implements AttachmentAdapter {
20
20
  public accept = "*";
21
21
 
22
- constructor(private cloud: AssistantCloud) {}
22
+ private cloud: AssistantCloud;
23
+
24
+ constructor(cloud: AssistantCloud) {
25
+ this.cloud = cloud;
26
+ }
23
27
 
24
28
  private uploadedUrls = new Map<string, string>();
25
29
 
@@ -6,6 +6,7 @@ import type {
6
6
  SourceProviderMetadata,
7
7
  ThreadMessage,
8
8
  TextMessagePart,
9
+ ToolApprovalOption,
9
10
  Unstable_AudioMessagePart,
10
11
  } from "../../../types/message";
11
12
  import type { CompleteAttachment } from "../../../types/attachment";
@@ -18,6 +19,16 @@ import type {
18
19
  } from "assistant-stream/utils";
19
20
  import type { ExportedMessageRepositoryItem } from "../../../runtime/utils/message-repository";
20
21
 
22
+ type AuiV0ToolApproval = {
23
+ readonly id: string;
24
+ readonly approved?: boolean;
25
+ readonly reason?: string;
26
+ readonly isAutomatic?: boolean;
27
+ readonly options?: readonly ToolApprovalOption[];
28
+ readonly optionId?: string;
29
+ readonly resolution?: "cancelled" | "expired";
30
+ };
31
+
21
32
  type AuiV0MessagePart =
22
33
  | {
23
34
  readonly type: "text";
@@ -51,6 +62,7 @@ type AuiV0MessagePart =
51
62
  readonly args: ReadonlyJSONObject;
52
63
  readonly result?: ReadonlyJSONValue;
53
64
  readonly isError?: true;
65
+ readonly approval?: AuiV0ToolApproval;
54
66
  }
55
67
  | {
56
68
  readonly type: "tool-call";
@@ -59,6 +71,7 @@ type AuiV0MessagePart =
59
71
  readonly argsText: string;
60
72
  readonly result?: ReadonlyJSONValue;
61
73
  readonly isError?: true;
74
+ readonly approval?: AuiV0ToolApproval;
62
75
  }
63
76
  | {
64
77
  readonly type: "image";
@@ -216,6 +229,7 @@ export function auiV0Encode(message: ThreadMessage): AuiV0Message {
216
229
  ? { result: part.result as ReadonlyJSONValue }
217
230
  : undefined),
218
231
  ...(part.isError ? { isError: true } : undefined),
232
+ ...(part.approval ? { approval: part.approval } : undefined),
219
233
  };
220
234
  }
221
235