@assistant-ui/core 0.3.5 → 0.3.7

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 (207) hide show
  1. package/dist/index.d.ts +4 -4
  2. package/dist/index.js.map +1 -1
  3. package/dist/internal.js +1 -1
  4. package/dist/react/AssistantProvider.d.ts +3 -2
  5. package/dist/react/AssistantProvider.d.ts.map +1 -1
  6. package/dist/react/AssistantProvider.js +19 -26
  7. package/dist/react/AssistantProvider.js.map +1 -1
  8. package/dist/react/AssistantRuntimeProvider.d.ts +7 -2
  9. package/dist/react/AssistantRuntimeProvider.d.ts.map +1 -1
  10. package/dist/react/AssistantRuntimeProvider.js +9 -7
  11. package/dist/react/AssistantRuntimeProvider.js.map +1 -1
  12. package/dist/react/RuntimeAdapter.js +1 -1
  13. package/dist/react/RuntimeAdapter.js.map +1 -1
  14. package/dist/react/client/Interactables.js +1 -1
  15. package/dist/react/client/Interactables.js.map +1 -1
  16. package/dist/react/client/Tools.d.ts +1 -1
  17. package/dist/react/client/Tools.js +1 -1
  18. package/dist/react/client/Tools.js.map +1 -1
  19. package/dist/react/index.d.ts +9 -9
  20. package/dist/react/index.js +4 -4
  21. package/dist/react/model-context/interactableTool.d.ts +1 -1
  22. package/dist/react/model-context/useAssistantTool.d.ts +1 -1
  23. package/dist/react/providers/AttachmentByIndexProvider.d.ts.map +1 -1
  24. package/dist/react/providers/AttachmentByIndexProvider.js +33 -39
  25. package/dist/react/providers/AttachmentByIndexProvider.js.map +1 -1
  26. package/dist/react/providers/ChainOfThoughtByIndicesProvider.d.ts.map +1 -1
  27. package/dist/react/providers/ChainOfThoughtByIndicesProvider.js +15 -20
  28. package/dist/react/providers/ChainOfThoughtByIndicesProvider.js.map +1 -1
  29. package/dist/react/providers/ChainOfThoughtPartByIndexProvider.js +17 -20
  30. package/dist/react/providers/ChainOfThoughtPartByIndexProvider.js.map +1 -1
  31. package/dist/react/providers/MessageByIdProvider.js +28 -38
  32. package/dist/react/providers/MessageByIdProvider.js.map +1 -1
  33. package/dist/react/providers/MessageByIndexProvider.js +28 -38
  34. package/dist/react/providers/MessageByIndexProvider.js.map +1 -1
  35. package/dist/react/providers/PartByIndexProvider.js +17 -20
  36. package/dist/react/providers/PartByIndexProvider.js.map +1 -1
  37. package/dist/react/providers/QueueItemByIndexProvider.js +21 -21
  38. package/dist/react/providers/QueueItemByIndexProvider.js.map +1 -1
  39. package/dist/react/providers/ReadonlyThreadProvider.js +24 -32
  40. package/dist/react/providers/ReadonlyThreadProvider.js.map +1 -1
  41. package/dist/react/providers/SuggestionByIndexProvider.js +17 -20
  42. package/dist/react/providers/SuggestionByIndexProvider.js.map +1 -1
  43. package/dist/react/providers/TextMessagePartProvider.d.ts.map +1 -1
  44. package/dist/react/providers/TextMessagePartProvider.js +16 -19
  45. package/dist/react/providers/TextMessagePartProvider.js.map +1 -1
  46. package/dist/react/providers/ThreadListItemByIndexProvider.js +17 -20
  47. package/dist/react/providers/ThreadListItemByIndexProvider.js.map +1 -1
  48. package/dist/react/providers/ThreadListItemRuntimeProvider.js +15 -18
  49. package/dist/react/providers/ThreadListItemRuntimeProvider.js.map +1 -1
  50. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +7 -3
  51. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  52. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +5 -2
  53. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  54. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +3 -2
  55. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  56. package/dist/react/runtimes/createMessageConverter.d.ts +1 -1
  57. package/dist/react/runtimes/createRuntimeExtras.d.ts +4 -1
  58. package/dist/react/runtimes/createRuntimeExtras.d.ts.map +1 -1
  59. package/dist/react/runtimes/createRuntimeExtras.js +6 -17
  60. package/dist/react/runtimes/createRuntimeExtras.js.map +1 -1
  61. package/dist/react/runtimes/useLocalRuntime.d.ts +3 -1
  62. package/dist/react/runtimes/useLocalRuntime.d.ts.map +1 -1
  63. package/dist/react/runtimes/useLocalRuntime.js +4 -2
  64. package/dist/react/runtimes/useLocalRuntime.js.map +1 -1
  65. package/dist/react/types/scopes/dataRenderers.d.ts +1 -1
  66. package/dist/react/types/scopes/interactables.d.ts +1 -1
  67. package/dist/react/types/scopes/tools.d.ts +1 -1
  68. package/dist/runtime/api/composer-runtime.d.ts +5 -1
  69. package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
  70. package/dist/runtime/api/composer-runtime.js +8 -1
  71. package/dist/runtime/api/composer-runtime.js.map +1 -1
  72. package/dist/runtime/api/message-runtime.js +1 -1
  73. package/dist/runtime/api/message-runtime.js.map +1 -1
  74. package/dist/runtime/api/thread-runtime.d.ts +4 -2
  75. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  76. package/dist/runtime/base/base-composer-runtime-core.d.ts +2 -1
  77. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  78. package/dist/runtime/base/base-composer-runtime-core.js +1 -1
  79. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  80. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +3 -1
  81. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  82. package/dist/runtime/base/default-thread-composer-runtime-core.js +14 -3
  83. package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
  84. package/dist/runtime/interfaces/composer-runtime-core.d.ts +2 -1
  85. package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
  86. package/dist/runtime/interfaces/thread-runtime-core.d.ts +3 -1
  87. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  88. package/dist/runtime/queue/external-thread-queue-adapter.d.ts +19 -6
  89. package/dist/runtime/queue/external-thread-queue-adapter.d.ts.map +1 -1
  90. package/dist/runtime/queue/message-queue.d.ts +10 -1
  91. package/dist/runtime/queue/message-queue.d.ts.map +1 -1
  92. package/dist/runtime/queue/message-queue.js +138 -36
  93. package/dist/runtime/queue/message-queue.js.map +1 -1
  94. package/dist/runtime/utils/runtime-extras-brand.d.ts +21 -0
  95. package/dist/runtime/utils/runtime-extras-brand.d.ts.map +1 -0
  96. package/dist/runtime/utils/runtime-extras-brand.js +37 -0
  97. package/dist/runtime/utils/runtime-extras-brand.js.map +1 -0
  98. package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
  99. package/dist/runtime/utils/thread-message-like.js +2 -1
  100. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  101. package/dist/runtimes/external-store/external-store-adapter.d.ts +1 -1
  102. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +3 -1
  103. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  104. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +7 -6
  105. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  106. package/dist/runtimes/local/local-runtime-options.d.ts +13 -0
  107. package/dist/runtimes/local/local-runtime-options.d.ts.map +1 -1
  108. package/dist/runtimes/local/local-thread-runtime-core.d.ts +5 -1
  109. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  110. package/dist/runtimes/local/local-thread-runtime-core.js +30 -14
  111. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  112. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +1 -1
  113. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +1 -1
  114. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
  115. package/dist/runtimes/remote-thread-list/empty-thread-core.js +1 -1
  116. package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
  117. package/dist/runtimes/remote-thread-list/types.d.ts +6 -3
  118. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  119. package/dist/store/clients/runtime-adapter.d.ts.map +1 -1
  120. package/dist/store/clients/runtime-adapter.js +1 -1
  121. package/dist/store/clients/runtime-adapter.js.map +1 -1
  122. package/dist/store/clients/suggestions.js +1 -1
  123. package/dist/store/clients/suggestions.js.map +1 -1
  124. package/dist/store/clients/thread-message-client.js +1 -1
  125. package/dist/store/clients/thread-message-client.js.map +1 -1
  126. package/dist/store/index.d.ts +10 -1
  127. package/dist/store/index.js +7 -1
  128. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  129. package/dist/store/runtime-clients/composer-runtime-client.js +12 -5
  130. package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
  131. package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
  132. package/dist/store/runtime-clients/message-runtime-client.js +1 -1
  133. package/dist/store/runtime-clients/message-runtime-client.js.map +1 -1
  134. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  135. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +1 -1
  136. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  137. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  138. package/dist/store/runtime-clients/thread-list-runtime-client.js +1 -1
  139. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  140. package/dist/store/runtime-clients/thread-runtime-client.js +1 -1
  141. package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
  142. package/dist/store/scopes/composer.d.ts +7 -3
  143. package/dist/store/scopes/composer.d.ts.map +1 -1
  144. package/dist/store/scopes/queue-item.d.ts +10 -0
  145. package/dist/store/scopes/queue-item.d.ts.map +1 -1
  146. package/dist/store/scopes/queue-item.js.map +1 -1
  147. package/package.json +15 -15
  148. package/src/index.ts +5 -1
  149. package/src/react/AssistantProvider.tsx +20 -18
  150. package/src/react/AssistantRuntimeProvider.test.tsx +55 -1
  151. package/src/react/AssistantRuntimeProvider.tsx +12 -2
  152. package/src/react/RuntimeAdapter.test.ts +85 -0
  153. package/src/react/RuntimeAdapter.ts +1 -1
  154. package/src/react/client/Interactables.test.ts +3 -2
  155. package/src/react/client/Interactables.ts +4 -4
  156. package/src/react/client/Tools.ts +4 -4
  157. package/src/react/index.ts +1 -0
  158. package/src/react/providers/AttachmentByIndexProvider.tsx +15 -7
  159. package/src/react/providers/ChainOfThoughtByIndicesProvider.tsx +12 -4
  160. package/src/react/providers/ChainOfThoughtPartByIndexProvider.tsx +8 -4
  161. package/src/react/providers/MessageByIdProvider.tsx +8 -4
  162. package/src/react/providers/MessageByIndexProvider.tsx +8 -4
  163. package/src/react/providers/PartByIndexProvider.tsx +8 -4
  164. package/src/react/providers/QueueItemByIndexProvider.tsx +9 -5
  165. package/src/react/providers/ReadonlyThreadProvider.tsx +9 -4
  166. package/src/react/providers/SuggestionByIndexProvider.tsx +8 -4
  167. package/src/react/providers/TextMessagePartProvider.tsx +13 -4
  168. package/src/react/providers/ThreadListItemByIndexProvider.tsx +8 -4
  169. package/src/react/providers/ThreadListItemRuntimeProvider.tsx +8 -4
  170. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +19 -4
  171. package/src/react/runtimes/createRuntimeExtras.test.ts +14 -1
  172. package/src/react/runtimes/createRuntimeExtras.ts +15 -26
  173. package/src/react/runtimes/useLocalRuntime.ts +4 -0
  174. package/src/runtime/api/composer-runtime.ts +11 -2
  175. package/src/runtime/api/message-runtime.test.ts +25 -0
  176. package/src/runtime/api/message-runtime.ts +1 -1
  177. package/src/runtime/base/base-composer-runtime-core.ts +5 -1
  178. package/src/runtime/base/default-thread-composer-runtime-core.ts +27 -3
  179. package/src/runtime/interfaces/composer-runtime-core.ts +2 -1
  180. package/src/runtime/interfaces/thread-runtime-core.ts +3 -1
  181. package/src/runtime/queue/external-thread-queue-adapter.ts +19 -3
  182. package/src/runtime/queue/message-queue.ts +200 -38
  183. package/src/runtime/utils/runtime-extras-brand.ts +47 -0
  184. package/src/runtime/utils/thread-message-like.ts +2 -4
  185. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +10 -8
  186. package/src/runtimes/local/local-runtime-options.ts +15 -0
  187. package/src/runtimes/local/local-thread-runtime-core.test.ts +43 -0
  188. package/src/runtimes/local/local-thread-runtime-core.ts +48 -13
  189. package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +1 -1
  190. package/src/runtimes/remote-thread-list/empty-thread-core.ts +1 -1
  191. package/src/runtimes/remote-thread-list/types.ts +9 -2
  192. package/src/store/clients/runtime-adapter.ts +2 -5
  193. package/src/store/clients/suggestions.ts +1 -1
  194. package/src/store/clients/thread-message-client.ts +1 -1
  195. package/src/store/index.ts +31 -0
  196. package/src/store/runtime-clients/composer-runtime-client.ts +15 -10
  197. package/src/store/runtime-clients/message-runtime-client.ts +2 -5
  198. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +2 -1
  199. package/src/store/runtime-clients/thread-list-runtime-client.ts +2 -5
  200. package/src/store/runtime-clients/thread-runtime-client.ts +2 -2
  201. package/src/store/scopes/composer.ts +6 -4
  202. package/src/store/scopes/queue-item.ts +8 -1
  203. package/src/tests/external-store-thread-runtime-core.test.ts +56 -13
  204. package/src/tests/framework-neutral-boundary.test.ts +257 -0
  205. package/src/tests/message-queue.test.ts +494 -34
  206. package/src/tests/thread-message-like.test.ts +66 -0
  207. package/LICENSE +0 -21
@@ -14,14 +14,29 @@ import {
14
14
  normalizeCursor,
15
15
  updateStatusReducer,
16
16
  } from "../../runtimes/remote-thread-list/remote-thread-state";
17
- import type { RemoteThreadListOptions } from "../../runtimes/remote-thread-list/types";
17
+ import type {
18
+ RemoteThreadListOptions,
19
+ RemoteThreadListProviderComponent,
20
+ } from "../../runtimes/remote-thread-list/types";
18
21
  import { RemoteThreadListHookInstanceManager } from "./RemoteThreadListHookInstanceManager";
19
- import { type FC, Fragment, useEffect, useId } from "react";
22
+ import {
23
+ type ComponentType,
24
+ type FC,
25
+ Fragment,
26
+ type PropsWithChildren,
27
+ useEffect,
28
+ useId,
29
+ } from "react";
20
30
  import { create } from "zustand";
21
31
  import { AssistantMessageStream } from "assistant-stream";
22
32
  import type { ModelContextProvider } from "../../model-context/types";
23
33
  import { RuntimeAdapterProvider } from "./RuntimeAdapterProvider";
24
34
 
35
+ const asProviderComponent = (
36
+ provider: RemoteThreadListProviderComponent | undefined,
37
+ ): ComponentType<PropsWithChildren> =>
38
+ (provider ?? Fragment) as ComponentType<PropsWithChildren>;
39
+
25
40
  const threadNotFoundError = (threadIdOrRemoteId: string, action: string) =>
26
41
  new Error(`Thread "${threadIdOrRemoteId}" not found while ${action}.`);
27
42
 
@@ -189,7 +204,7 @@ export class RemoteThreadListThreadListRuntimeCore
189
204
  this._notifySubscribers(),
190
205
  );
191
206
  this.useProvider = create(() => ({
192
- Provider: options.adapter.unstable_Provider ?? Fragment,
207
+ Provider: asProviderComponent(options.adapter.unstable_Provider),
193
208
  }));
194
209
  this.__internal_setOptions(options);
195
210
  this.switchToNewThread();
@@ -210,7 +225,7 @@ export class RemoteThreadListThreadListRuntimeCore
210
225
 
211
226
  this._options = options;
212
227
 
213
- const Provider = options.adapter.unstable_Provider ?? Fragment;
228
+ const Provider = asProviderComponent(options.adapter.unstable_Provider);
214
229
  if (Provider !== this.useProvider.getState().Provider) {
215
230
  this.useProvider.setState({ Provider }, true);
216
231
  }
@@ -1,6 +1,10 @@
1
1
  import { describe, it, expect } from "vitest";
2
2
  import type { AssistantClient } from "@assistant-ui/store";
3
- import { createRuntimeExtras } from "./createRuntimeExtras";
3
+ import {
4
+ createRuntimeExtras,
5
+ unstable_createRuntimeExtrasFromBrand,
6
+ } from "./createRuntimeExtras";
7
+ import { createRuntimeExtrasBrand } from "../../runtime/utils/runtime-extras-brand";
4
8
 
5
9
  type Extras = { value: number; greet: () => string };
6
10
 
@@ -55,4 +59,13 @@ describe("createRuntimeExtras", () => {
55
59
 
56
60
  expect(() => channel.get(clientWith(undefined))).toThrow("useTestRuntime");
57
61
  });
62
+
63
+ it("reads extras provided through a standalone brand instance", () => {
64
+ const brand = createRuntimeExtrasBrand<Extras>("useTestRuntime");
65
+ const channel = unstable_createRuntimeExtrasFromBrand(brand);
66
+ const branded = brand.provide({ value: 7, greet: () => "hi" });
67
+
68
+ expect(channel.is(branded)).toBe(true);
69
+ expect(channel.get(clientWith(branded)).value).toBe(7);
70
+ });
58
71
  });
@@ -2,6 +2,10 @@
2
2
 
3
3
  import { useAuiState } from "@assistant-ui/store";
4
4
  import type { AssistantClient } from "@assistant-ui/store";
5
+ import {
6
+ createRuntimeExtrasBrand,
7
+ type RuntimeExtrasBrand,
8
+ } from "../../runtime/utils/runtime-extras-brand";
5
9
 
6
10
  /** @deprecated Internal API for external-store adapter authors. Not part of the public API; may change or be removed without notice. */
7
11
  export type RuntimeExtras<T extends object> = {
@@ -17,33 +21,10 @@ export type RuntimeExtras<T extends object> = {
17
21
  };
18
22
 
19
23
  /** @deprecated Internal API for external-store adapter authors. Not part of the public API; may change or be removed without notice. */
20
- export const createRuntimeExtras = <T extends object>(
21
- runtimeName: string,
24
+ export const unstable_createRuntimeExtrasFromBrand = <T extends object>(
25
+ brand: RuntimeExtrasBrand<T>,
22
26
  ): RuntimeExtras<T> => {
23
- const brand = Symbol(`${runtimeName} extras`);
24
-
25
- const is = (extras: unknown): extras is T =>
26
- typeof extras === "object" && extras !== null && brand in extras;
27
-
28
- const assert = (extras: unknown): T => {
29
- if (!is(extras))
30
- throw new Error(
31
- `The current thread is not backed by the ${runtimeName} runtime.`,
32
- );
33
- return extras;
34
- };
35
-
36
- const provide = (value: T): T => {
37
- Object.defineProperty(value, brand, {
38
- value: true,
39
- enumerable: false,
40
- configurable: true,
41
- });
42
- return value;
43
- };
44
-
45
- const tryGet = (extras: unknown): T | undefined =>
46
- is(extras) ? extras : undefined;
27
+ const { provide, is, tryGet, assert } = brand;
47
28
 
48
29
  const get = (client: AssistantClient): T =>
49
30
  assert(client.thread.getState().extras);
@@ -66,3 +47,11 @@ export const createRuntimeExtras = <T extends object>(
66
47
 
67
48
  return { provide, is, tryGet, get, use: use as RuntimeExtras<T>["use"] };
68
49
  };
50
+
51
+ /** @deprecated Internal API for external-store adapter authors. Not part of the public API; may change or be removed without notice. */
52
+ export const createRuntimeExtras = <T extends object>(
53
+ runtimeName: string,
54
+ ): RuntimeExtras<T> =>
55
+ unstable_createRuntimeExtrasFromBrand(
56
+ createRuntimeExtrasBrand<T>(runtimeName),
57
+ );
@@ -84,6 +84,8 @@ export const splitLocalRuntimeOptions = <T extends LocalRuntimeOptions>(
84
84
  adapters,
85
85
  unstable_humanToolNames,
86
86
  unstable_enableMessageQueue,
87
+ unstable_queueClearOnRewind,
88
+ unstable_queueClearOnCancel,
87
89
  ...rest
88
90
  } = options;
89
91
 
@@ -95,6 +97,8 @@ export const splitLocalRuntimeOptions = <T extends LocalRuntimeOptions>(
95
97
  adapters,
96
98
  unstable_humanToolNames,
97
99
  unstable_enableMessageQueue,
100
+ unstable_queueClearOnRewind,
101
+ unstable_queueClearOnCancel,
98
102
  },
99
103
  otherOptions: rest,
100
104
  };
@@ -4,6 +4,7 @@ import type { QuoteInfo } from "../../types/quote";
4
4
  import type { Unsubscribe } from "../../types/unsubscribe";
5
5
  import type { RunConfig } from "../../types/message";
6
6
  import type { QueueItemState } from "../../store/scopes/queue-item";
7
+ import type { QueuePlacement } from "../queue/external-thread-queue-adapter";
7
8
  import {
8
9
  LazyMemoizeSubject,
9
10
  EventSubscriptionSubject,
@@ -200,9 +201,12 @@ export type ComposerRuntime = {
200
201
  */
201
202
  cancel(): void;
202
203
 
203
- /** Promote a queued message so it processes next. */
204
+ /** @deprecated Use `moveQueueItem(queueItemId, { lane: "steer", insertAfter: null })` instead. Removal after 2026-11-05. */
204
205
  steerQueueItem(queueItemId: string): void;
205
206
 
207
+ /** Move a queued message between lanes or within a lane. */
208
+ moveQueueItem(queueItemId: string, placement: QueuePlacement): void;
209
+
206
210
  /** Remove a queued message. */
207
211
  removeQueueItem(queueItemId: string): void;
208
212
 
@@ -268,6 +272,7 @@ export abstract class ComposerRuntimeImpl implements ComposerRuntime {
268
272
  this.send = this.send.bind(this);
269
273
  this.cancel = this.cancel.bind(this);
270
274
  this.steerQueueItem = this.steerQueueItem.bind(this);
275
+ this.moveQueueItem = this.moveQueueItem.bind(this);
271
276
  this.removeQueueItem = this.removeQueueItem.bind(this);
272
277
  this.setRole = this.setRole.bind(this);
273
278
  this.getAttachmentByIndex = this.getAttachmentByIndex.bind(this);
@@ -322,9 +327,13 @@ export abstract class ComposerRuntimeImpl implements ComposerRuntime {
322
327
  }
323
328
 
324
329
  public steerQueueItem(queueItemId: string) {
330
+ this.moveQueueItem(queueItemId, { lane: "steer", insertAfter: null });
331
+ }
332
+
333
+ public moveQueueItem(queueItemId: string, placement: QueuePlacement) {
325
334
  const core = this._core.getState();
326
335
  if (!core) throw new Error("Composer is not available");
327
- core.steerQueueItem(queueItemId);
336
+ core.moveQueueItem(queueItemId, placement);
328
337
  }
329
338
 
330
339
  public removeQueueItem(queueItemId: string) {
@@ -173,6 +173,31 @@ describe("toMessagePartStatus", () => {
173
173
  });
174
174
  });
175
175
 
176
+ it.each([
177
+ ["false", false],
178
+ ["zero", 0],
179
+ ["an empty string", ""],
180
+ ["null", null],
181
+ ])("treats %s tool results as complete", (_label, result) => {
182
+ const message = createAssistantMessage(
183
+ [
184
+ {
185
+ type: "tool-call",
186
+ toolCallId: "call-1",
187
+ toolName: "weather",
188
+ args: {},
189
+ argsText: "{}",
190
+ result,
191
+ },
192
+ ],
193
+ { type: "running" },
194
+ );
195
+
196
+ expect(toMessagePartStatus(message, 0, message.content[0]!)).toEqual({
197
+ type: "complete",
198
+ });
199
+ });
200
+
176
201
  it("normalizes supplied upstream statuses", () => {
177
202
  const upstreamComplete = {
178
203
  type: "text",
@@ -44,7 +44,7 @@ export const toMessagePartStatus = (
44
44
  if (message.role !== "assistant") return COMPLETE_STATUS;
45
45
 
46
46
  if (part.type === "tool-call") {
47
- if (!part.result) {
47
+ if (part.result === undefined) {
48
48
  return message.status as ToolCallMessagePartStatus;
49
49
  } else {
50
50
  return COMPLETE_STATUS;
@@ -24,6 +24,7 @@ import type {
24
24
  SendOptions,
25
25
  } from "../interfaces/composer-runtime-core";
26
26
  import type { DictationAdapter } from "../../adapters/speech";
27
+ import type { QueuePlacement } from "../queue/external-thread-queue-adapter";
27
28
  import {
28
29
  EMPTY_QUEUE_ITEMS,
29
30
  type QueueItemState,
@@ -301,7 +302,10 @@ export abstract class BaseComposerRuntimeCore
301
302
  return EMPTY_QUEUE_ITEMS;
302
303
  }
303
304
 
304
- public steerQueueItem(_queueItemId: string): void {}
305
+ public moveQueueItem(
306
+ _queueItemId: string,
307
+ _placement: QueuePlacement,
308
+ ): void {}
305
309
  public removeQueueItem(_queueItemId: string): void {}
306
310
 
307
311
  protected abstract handleSend(
@@ -6,6 +6,7 @@ import type {
6
6
  ThreadComposerRuntimeCore,
7
7
  } from "../interfaces/composer-runtime-core";
8
8
  import type { ThreadRuntimeCore } from "../interfaces/thread-runtime-core";
9
+ import type { QueuePlacement } from "../queue/external-thread-queue-adapter";
9
10
  import {
10
11
  EMPTY_QUEUE_ITEMS,
11
12
  type QueueItemState,
@@ -26,12 +27,35 @@ export class DefaultThreadComposerRuntimeCore
26
27
  return !this.isEmpty && !this.runtime.isSendDisabled && !this._isSending;
27
28
  }
28
29
 
30
+ private _queueCache:
31
+ | {
32
+ steer: readonly QueueItemState[];
33
+ queue: readonly QueueItemState[];
34
+ flat: readonly QueueItemState[];
35
+ }
36
+ | undefined;
37
+
29
38
  public override get queue(): readonly QueueItemState[] {
30
- return this.runtime.getQueueItems?.() ?? EMPTY_QUEUE_ITEMS;
39
+ const steer = this.runtime.getSteerQueueItems?.() ?? EMPTY_QUEUE_ITEMS;
40
+ const queue = this.runtime.getQueueItems?.() ?? EMPTY_QUEUE_ITEMS;
41
+ const cache = this._queueCache;
42
+ if (cache && cache.steer === steer && cache.queue === queue)
43
+ return cache.flat;
44
+ const flat =
45
+ steer.length === 0
46
+ ? queue
47
+ : queue.length === 0
48
+ ? steer
49
+ : [...steer, ...queue];
50
+ this._queueCache = { steer, queue, flat };
51
+ return flat;
31
52
  }
32
53
 
33
- public override steerQueueItem(queueItemId: string): void {
34
- this.runtime.steerQueueItem?.(queueItemId);
54
+ public override moveQueueItem(
55
+ queueItemId: string,
56
+ placement: QueuePlacement,
57
+ ): void {
58
+ this.runtime.moveQueueItem?.(queueItemId, placement);
35
59
  }
36
60
 
37
61
  public override removeQueueItem(queueItemId: string): void {
@@ -5,6 +5,7 @@ import type { Unsubscribe } from "../../types/unsubscribe";
5
5
  import type { RunConfig } from "../../types/message";
6
6
  import type { DictationAdapter } from "../../adapters/speech";
7
7
  import type { QueueItemState } from "../../store/scopes/queue-item";
8
+ import type { QueuePlacement } from "../queue/external-thread-queue-adapter";
8
9
 
9
10
  export type AttachmentAddErrorReason =
10
11
  | "no-adapter"
@@ -82,7 +83,7 @@ export type ComposerRuntimeCore = Readonly<{
82
83
  cancel: () => void;
83
84
 
84
85
  queue: readonly QueueItemState[];
85
- steerQueueItem: (queueItemId: string) => void;
86
+ moveQueueItem: (queueItemId: string, placement: QueuePlacement) => void;
86
87
  removeQueueItem: (queueItemId: string) => void;
87
88
 
88
89
  dictation: DictationState | undefined;
@@ -17,6 +17,7 @@ import type {
17
17
  ThreadComposerRuntimeCore,
18
18
  } from "./composer-runtime-core";
19
19
  import type { QueueItemState } from "../../store/scopes/queue-item";
20
+ import type { QueuePlacement } from "../queue/external-thread-queue-adapter";
20
21
 
21
22
  export type RuntimeCapabilities = {
22
23
  readonly switchToBranch: boolean;
@@ -177,7 +178,8 @@ export type ThreadRuntimeCore = Readonly<{
177
178
  beginEdit: (messageId: string) => void;
178
179
 
179
180
  getQueueItems?: () => readonly QueueItemState[];
180
- steerQueueItem?: (queueItemId: string) => void;
181
+ getSteerQueueItems?: () => readonly QueueItemState[];
182
+ moveQueueItem?: (queueItemId: string, placement: QueuePlacement) => void;
181
183
  removeQueueItem?: (queueItemId: string) => void;
182
184
 
183
185
  speech: SpeechState | undefined;
@@ -1,14 +1,30 @@
1
1
  import type { AppendMessage } from "../../types/message";
2
2
  import type { QueueItemState } from "../../store/scopes/queue-item";
3
3
 
4
+ export type QueuePlacement = {
5
+ readonly lane?: "queue" | "steer";
6
+ readonly insertAfter?: string | null;
7
+ readonly insertBefore?: string | null;
8
+ };
9
+
4
10
  /**
5
11
  * The queue surface a runtime exposes so the composer can stay usable during a
6
12
  * run and render the pending messages.
7
13
  */
8
14
  export type ExternalThreadQueueAdapter = {
9
15
  items: readonly QueueItemState[];
10
- enqueue: (message: AppendMessage, options: { steer: boolean }) => void;
11
- steer: (queueItemId: string) => void;
16
+ steerItems: readonly QueueItemState[];
17
+ /** Send a message into the queue lane, processed in order. */
18
+ enqueue: (message: AppendMessage) => void;
19
+ /** Send a message into the steer lane, processed next. */
20
+ steer: (message: AppendMessage) => void;
21
+ /**
22
+ * Move a queued message between lanes or within a lane. An unanchored move
23
+ * into the steer lane mid-run interrupts — cancels the live run and
24
+ * dispatches the item — when the runtime supports cancellation; a move with
25
+ * `insertAfter`/`insertBefore` only places the item and never interrupts.
26
+ */
27
+ move: (queueItemId: string, placement: QueuePlacement) => void;
28
+ edit: (queueItemId: string, message: AppendMessage) => void;
12
29
  remove: (queueItemId: string) => void;
13
- clear: (reason: "edit" | "reload" | "cancel-run") => void;
14
30
  };