@assistant-ui/react 0.11.36 → 0.11.38

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 (53) hide show
  1. package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  2. package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListThreadListRuntimeCore.js +34 -1
  3. package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  4. package/dist/legacy-runtime/runtime-cores/remote-thread-list/adapter/cloud.d.ts.map +1 -1
  5. package/dist/legacy-runtime/runtime-cores/remote-thread-list/adapter/cloud.js +9 -0
  6. package/dist/legacy-runtime/runtime-cores/remote-thread-list/adapter/cloud.js.map +1 -1
  7. package/dist/legacy-runtime/runtime-cores/remote-thread-list/adapter/in-memory.d.ts +2 -1
  8. package/dist/legacy-runtime/runtime-cores/remote-thread-list/adapter/in-memory.d.ts.map +1 -1
  9. package/dist/legacy-runtime/runtime-cores/remote-thread-list/adapter/in-memory.js +3 -0
  10. package/dist/legacy-runtime/runtime-cores/remote-thread-list/adapter/in-memory.js.map +1 -1
  11. package/dist/legacy-runtime/runtime-cores/remote-thread-list/types.d.ts +1 -0
  12. package/dist/legacy-runtime/runtime-cores/remote-thread-list/types.d.ts.map +1 -1
  13. package/dist/primitives/assistantModal/scope.d.ts +2 -3
  14. package/dist/primitives/assistantModal/scope.d.ts.map +1 -1
  15. package/dist/primitives/assistantModal/scope.js.map +1 -1
  16. package/dist/primitives/index.d.ts +1 -0
  17. package/dist/primitives/index.d.ts.map +1 -1
  18. package/dist/primitives/index.js +2 -0
  19. package/dist/primitives/index.js.map +1 -1
  20. package/dist/primitives/message/MessageParts.d.ts +31 -1
  21. package/dist/primitives/message/MessageParts.d.ts.map +1 -1
  22. package/dist/primitives/message/MessageParts.js +66 -23
  23. package/dist/primitives/message/MessageParts.js.map +1 -1
  24. package/dist/primitives/reasoning/index.d.ts +2 -0
  25. package/dist/primitives/reasoning/index.d.ts.map +1 -0
  26. package/dist/primitives/reasoning/index.js +6 -0
  27. package/dist/primitives/reasoning/index.js.map +1 -0
  28. package/dist/primitives/reasoning/useScrollLock.d.ts +29 -0
  29. package/dist/primitives/reasoning/useScrollLock.d.ts.map +1 -0
  30. package/dist/primitives/reasoning/useScrollLock.js +50 -0
  31. package/dist/primitives/reasoning/useScrollLock.js.map +1 -0
  32. package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
  33. package/dist/primitives/thread/useThreadViewportAutoScroll.js +11 -12
  34. package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
  35. package/dist/tests/setup.js +287 -125
  36. package/dist/tests/setup.js.map +1 -1
  37. package/dist/types/MessagePartComponentTypes.d.ts +6 -1
  38. package/dist/types/MessagePartComponentTypes.d.ts.map +1 -1
  39. package/dist/types/index.d.ts +1 -1
  40. package/dist/types/index.d.ts.map +1 -1
  41. package/package.json +8 -8
  42. package/src/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListThreadListRuntimeCore.tsx +49 -2
  43. package/src/legacy-runtime/runtime-cores/remote-thread-list/adapter/cloud.tsx +10 -0
  44. package/src/legacy-runtime/runtime-cores/remote-thread-list/adapter/in-memory.tsx +5 -0
  45. package/src/legacy-runtime/runtime-cores/remote-thread-list/types.tsx +1 -0
  46. package/src/primitives/assistantModal/scope.tsx +3 -1
  47. package/src/primitives/index.ts +1 -0
  48. package/src/primitives/message/MessageParts.tsx +111 -30
  49. package/src/primitives/reasoning/index.ts +1 -0
  50. package/src/primitives/reasoning/useScrollLock.tsx +86 -0
  51. package/src/primitives/thread/useThreadViewportAutoScroll.tsx +14 -13
  52. package/src/types/MessagePartComponentTypes.tsx +7 -1
  53. package/src/types/index.ts +2 -0
@@ -1,4 +1,4 @@
1
- import type { ComponentType } from "react";
1
+ import type { ComponentType, PropsWithChildren } from "react";
2
2
  import type { MessagePartStatus, FileMessagePart, ImageMessagePart, ReasoningMessagePart, SourceMessagePart, TextMessagePart, ToolCallMessagePart, Unstable_AudioMessagePart } from "./AssistantTypes";
3
3
  import { MessagePartState } from "../legacy-runtime/runtime/MessagePartRuntime";
4
4
  import { ToolResponse } from "assistant-stream";
@@ -10,6 +10,11 @@ export type TextMessagePartProps = MessagePartState & TextMessagePart;
10
10
  export type TextMessagePartComponent = ComponentType<TextMessagePartProps>;
11
11
  export type ReasoningMessagePartProps = MessagePartState & ReasoningMessagePart;
12
12
  export type ReasoningMessagePartComponent = ComponentType<ReasoningMessagePartProps>;
13
+ export type ReasoningGroupProps = PropsWithChildren<{
14
+ startIndex: number;
15
+ endIndex: number;
16
+ }>;
17
+ export type ReasoningGroupComponent = ComponentType<ReasoningGroupProps>;
13
18
  export type SourceMessagePartProps = MessagePartState & SourceMessagePart;
14
19
  export type SourceMessagePartComponent = ComponentType<SourceMessagePartProps>;
15
20
  export type ImageMessagePartProps = MessagePartState & ImageMessagePart;
@@ -1 +1 @@
1
- {"version":3,"file":"MessagePartComponentTypes.d.ts","sourceRoot":"","sources":["../../src/types/MessagePartComponentTypes.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,yBAAyB,EAC1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AAChF,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhD,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,iBAAiB,CAAC;CAC3B,CAAC;AACF,MAAM,MAAM,yBAAyB,GAAG,aAAa,CAAC,qBAAqB,CAAC,CAAC;AAE7E,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG,eAAe,CAAC;AACtE,MAAM,MAAM,wBAAwB,GAAG,aAAa,CAAC,oBAAoB,CAAC,CAAC;AAE3E,MAAM,MAAM,yBAAyB,GAAG,gBAAgB,GAAG,oBAAoB,CAAC;AAChF,MAAM,MAAM,6BAA6B,GACvC,aAAa,CAAC,yBAAyB,CAAC,CAAC;AAE3C,MAAM,MAAM,sBAAsB,GAAG,gBAAgB,GAAG,iBAAiB,CAAC;AAC1E,MAAM,MAAM,0BAA0B,GAAG,aAAa,CAAC,sBAAsB,CAAC,CAAC;AAE/E,MAAM,MAAM,qBAAqB,GAAG,gBAAgB,GAAG,gBAAgB,CAAC;AACxE,MAAM,MAAM,yBAAyB,GAAG,aAAa,CAAC,qBAAqB,CAAC,CAAC;AAE7E,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG,eAAe,CAAC;AACtE,MAAM,MAAM,wBAAwB,GAAG,aAAa,CAAC,oBAAoB,CAAC,CAAC;AAE3E,MAAM,MAAM,8BAA8B,GAAG,gBAAgB,GAC3D,yBAAyB,CAAC;AAC5B,MAAM,MAAM,kCAAkC,GAC5C,aAAa,CAAC,8BAA8B,CAAC,CAAC;AAEhD,MAAM,MAAM,wBAAwB,CAClC,KAAK,GAAG,GAAG,EACX,OAAO,GAAG,OAAO,IACf,gBAAgB,GAClB,mBAAmB,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG;IACpC,SAAS,EAAE,CAAC,MAAM,EAAE,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7D,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC,CAAC;AAEJ,MAAM,MAAM,4BAA4B,CACtC,KAAK,GAAG,GAAG,EACX,OAAO,GAAG,GAAG,IACX,aAAa,CAAC,wBAAwB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"MessagePartComponentTypes.d.ts","sourceRoot":"","sources":["../../src/types/MessagePartComponentTypes.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAC9D,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,yBAAyB,EAC1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AAChF,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhD,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,iBAAiB,CAAC;CAC3B,CAAC;AACF,MAAM,MAAM,yBAAyB,GAAG,aAAa,CAAC,qBAAqB,CAAC,CAAC;AAE7E,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG,eAAe,CAAC;AACtE,MAAM,MAAM,wBAAwB,GAAG,aAAa,CAAC,oBAAoB,CAAC,CAAC;AAE3E,MAAM,MAAM,yBAAyB,GAAG,gBAAgB,GAAG,oBAAoB,CAAC;AAChF,MAAM,MAAM,6BAA6B,GACvC,aAAa,CAAC,yBAAyB,CAAC,CAAC;AAE3C,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,aAAa,CAAC,mBAAmB,CAAC,CAAC;AAEzE,MAAM,MAAM,sBAAsB,GAAG,gBAAgB,GAAG,iBAAiB,CAAC;AAC1E,MAAM,MAAM,0BAA0B,GAAG,aAAa,CAAC,sBAAsB,CAAC,CAAC;AAE/E,MAAM,MAAM,qBAAqB,GAAG,gBAAgB,GAAG,gBAAgB,CAAC;AACxE,MAAM,MAAM,yBAAyB,GAAG,aAAa,CAAC,qBAAqB,CAAC,CAAC;AAE7E,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG,eAAe,CAAC;AACtE,MAAM,MAAM,wBAAwB,GAAG,aAAa,CAAC,oBAAoB,CAAC,CAAC;AAE3E,MAAM,MAAM,8BAA8B,GAAG,gBAAgB,GAC3D,yBAAyB,CAAC;AAC5B,MAAM,MAAM,kCAAkC,GAC5C,aAAa,CAAC,8BAA8B,CAAC,CAAC;AAEhD,MAAM,MAAM,wBAAwB,CAClC,KAAK,GAAG,GAAG,EACX,OAAO,GAAG,OAAO,IACf,gBAAgB,GAClB,mBAAmB,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG;IACpC,SAAS,EAAE,CAAC,MAAM,EAAE,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7D,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC,CAAC;AAEJ,MAAM,MAAM,4BAA4B,CACtC,KAAK,GAAG,GAAG,EACX,OAAO,GAAG,GAAG,IACX,aAAa,CAAC,wBAAwB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC"}
@@ -1,6 +1,6 @@
1
1
  export type { Attachment, PendingAttachment, CompleteAttachment, AttachmentStatus, } from "./AttachmentTypes";
2
2
  export type { AppendMessage, TextMessagePart, ReasoningMessagePart, SourceMessagePart, ImageMessagePart, FileMessagePart, Unstable_AudioMessagePart, ToolCallMessagePart, MessageStatus, MessagePartStatus, ToolCallMessagePartStatus, ThreadUserMessagePart, ThreadAssistantMessagePart, ThreadSystemMessage, ThreadAssistantMessage, ThreadUserMessage, ThreadMessage, } from "./AssistantTypes";
3
- export type { EmptyMessagePartComponent, EmptyMessagePartProps, TextMessagePartComponent, TextMessagePartProps, ReasoningMessagePartComponent, ReasoningMessagePartProps, SourceMessagePartComponent, SourceMessagePartProps, ImageMessagePartComponent, ImageMessagePartProps, FileMessagePartComponent, FileMessagePartProps, Unstable_AudioMessagePartComponent, Unstable_AudioMessagePartProps, ToolCallMessagePartComponent, ToolCallMessagePartProps, } from "./MessagePartComponentTypes";
3
+ export type { EmptyMessagePartComponent, EmptyMessagePartProps, TextMessagePartComponent, TextMessagePartProps, ReasoningMessagePartComponent, ReasoningMessagePartProps, SourceMessagePartComponent, SourceMessagePartProps, ImageMessagePartComponent, ImageMessagePartProps, FileMessagePartComponent, FileMessagePartProps, Unstable_AudioMessagePartComponent, Unstable_AudioMessagePartProps, ToolCallMessagePartComponent, ToolCallMessagePartProps, ReasoningGroupProps, ReasoningGroupComponent, } from "./MessagePartComponentTypes";
4
4
  export type { ThreadListItemStatus } from "../legacy-runtime/runtime/ThreadListItemRuntime";
5
5
  export type { Unsubscribe } from "./Unsubscribe";
6
6
  export type { AssistantEventScope, AssistantEventSelector, AssistantEvent, AssistantEventMap, AssistantEventCallback, } from "./EventTypes";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EACV,aAAa,EACb,eAAe,EACf,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,yBAAyB,EACzB,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,yBAAyB,EAGzB,qBAAqB,EACrB,0BAA0B,EAC1B,mBAAmB,EACnB,sBAAsB,EACtB,iBAAiB,EACjB,aAAa,GACd,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EACV,yBAAyB,EACzB,qBAAqB,EACrB,wBAAwB,EACxB,oBAAoB,EACpB,6BAA6B,EAC7B,yBAAyB,EACzB,0BAA0B,EAC1B,sBAAsB,EACtB,yBAAyB,EACzB,qBAAqB,EACrB,wBAAwB,EACxB,oBAAoB,EACpB,kCAAkC,EAClC,8BAA8B,EAC9B,4BAA4B,EAC5B,wBAAwB,GACzB,MAAM,6BAA6B,CAAC;AAGrC,YAAY,EAAE,oBAAoB,EAAE,MAAM,iDAAiD,CAAC;AAE5F,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,YAAY,EACV,mBAAmB,EACnB,sBAAsB,EACtB,cAAc,EACd,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EACV,aAAa,EACb,eAAe,EACf,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,yBAAyB,EACzB,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,yBAAyB,EAGzB,qBAAqB,EACrB,0BAA0B,EAC1B,mBAAmB,EACnB,sBAAsB,EACtB,iBAAiB,EACjB,aAAa,GACd,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EACV,yBAAyB,EACzB,qBAAqB,EACrB,wBAAwB,EACxB,oBAAoB,EACpB,6BAA6B,EAC7B,yBAAyB,EACzB,0BAA0B,EAC1B,sBAAsB,EACtB,yBAAyB,EACzB,qBAAqB,EACrB,wBAAwB,EACxB,oBAAoB,EACpB,kCAAkC,EAClC,8BAA8B,EAC9B,4BAA4B,EAC5B,wBAAwB,EACxB,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,6BAA6B,CAAC;AAGrC,YAAY,EAAE,oBAAoB,EAAE,MAAM,iDAAiD,CAAC;AAE5F,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,YAAY,EACV,mBAAmB,EACnB,sBAAsB,EACtB,cAAc,EACd,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,cAAc,CAAC"}
package/package.json CHANGED
@@ -28,7 +28,7 @@
28
28
  "conversational-ui",
29
29
  "conversational-ai"
30
30
  ],
31
- "version": "0.11.36",
31
+ "version": "0.11.38",
32
32
  "license": "MIT",
33
33
  "type": "module",
34
34
  "exports": {
@@ -48,14 +48,14 @@
48
48
  ],
49
49
  "sideEffects": false,
50
50
  "dependencies": {
51
- "assistant-cloud": "^0.1.6",
51
+ "assistant-cloud": "^0.1.7",
52
52
  "@assistant-ui/tap": "^0.1.5",
53
53
  "@radix-ui/primitive": "^1.1.3",
54
54
  "@radix-ui/react-compose-refs": "^1.1.2",
55
- "@radix-ui/react-context": "^1.1.2",
55
+ "@radix-ui/react-context": "^1.1.3",
56
56
  "@radix-ui/react-popover": "^1.1.15",
57
- "@radix-ui/react-primitive": "^2.1.3",
58
- "@radix-ui/react-slot": "^1.2.3",
57
+ "@radix-ui/react-primitive": "^2.1.4",
58
+ "@radix-ui/react-slot": "^1.2.4",
59
59
  "@radix-ui/react-use-callback-ref": "^1.1.1",
60
60
  "@radix-ui/react-use-escape-keydown": "^1.1.1",
61
61
  "@standard-schema/spec": "^1.0.0",
@@ -84,11 +84,11 @@
84
84
  "@stryker-mutator/core": "^9.3.0",
85
85
  "@stryker-mutator/vitest-runner": "^9.3.0",
86
86
  "@types/json-schema": "^7.0.15",
87
- "@types/node": "^24.10.0",
87
+ "@types/node": "^24.10.1",
88
88
  "eslint": "^9",
89
- "eslint-config-next": "16.0.1",
89
+ "eslint-config-next": "16.0.3",
90
90
  "tsx": "^4.20.6",
91
- "vitest": "^4.0.6",
91
+ "vitest": "^4.0.8",
92
92
  "@assistant-ui/x-buildutils": "0.0.1"
93
93
  },
94
94
  "publishConfig": {
@@ -308,9 +308,56 @@ export class RemoteThreadListThreadListRuntimeCore
308
308
  }
309
309
 
310
310
  public async switchToThread(threadIdOrRemoteId: string): Promise<void> {
311
- const data = this.getItemById(threadIdOrRemoteId);
312
- if (!data) throw new Error("Thread not found");
311
+ let data = this.getItemById(threadIdOrRemoteId);
312
+
313
+ if (!data) {
314
+ const remoteMetadata =
315
+ await this._options.adapter.fetch(threadIdOrRemoteId);
316
+ const state = this._state.value;
317
+ const mappingId = createThreadMappingId(remoteMetadata.remoteId);
318
+
319
+ const newThreadData = {
320
+ ...state.threadData,
321
+ [mappingId]: {
322
+ id: mappingId,
323
+ initializeTask: Promise.resolve({
324
+ remoteId: remoteMetadata.remoteId,
325
+ externalId: remoteMetadata.externalId,
326
+ }),
327
+ remoteId: remoteMetadata.remoteId,
328
+ externalId: remoteMetadata.externalId,
329
+ status: remoteMetadata.status,
330
+ title: remoteMetadata.title,
331
+ } as RemoteThreadData,
332
+ };
333
+
334
+ const newThreadIdMap = {
335
+ ...state.threadIdMap,
336
+ [remoteMetadata.remoteId]: mappingId,
337
+ };
338
+
339
+ const newThreadIds =
340
+ remoteMetadata.status === "regular"
341
+ ? [...state.threadIds, remoteMetadata.remoteId]
342
+ : state.threadIds;
343
+
344
+ const newArchivedThreadIds =
345
+ remoteMetadata.status === "archived"
346
+ ? [...state.archivedThreadIds, remoteMetadata.remoteId]
347
+ : state.archivedThreadIds;
313
348
 
349
+ this._state.update({
350
+ ...state,
351
+ threadIds: newThreadIds,
352
+ archivedThreadIds: newArchivedThreadIds,
353
+ threadIdMap: newThreadIdMap,
354
+ threadData: newThreadData,
355
+ });
356
+
357
+ data = this.getItemById(threadIdOrRemoteId);
358
+ }
359
+
360
+ if (!data) throw new Error("Thread not found");
314
361
  if (this._mainThreadId === data.id) return;
315
362
 
316
363
  const task = this._hookManager.startThreadRuntime(data.id);
@@ -121,6 +121,16 @@ export const useCloudThreadListAdapter = (
121
121
  });
122
122
  },
123
123
 
124
+ fetch: async (threadId: string) => {
125
+ const thread = await cloud.threads.get(threadId);
126
+ return {
127
+ status: thread.is_archived ? "archived" : "regular",
128
+ remoteId: thread.id,
129
+ title: thread.title,
130
+ externalId: thread.external_id ?? undefined,
131
+ };
132
+ },
133
+
124
134
  unstable_Provider,
125
135
  };
126
136
  };
@@ -3,6 +3,7 @@ import {
3
3
  RemoteThreadInitializeResponse,
4
4
  RemoteThreadListAdapter,
5
5
  RemoteThreadListResponse,
6
+ RemoteThreadMetadata,
6
7
  } from "../types";
7
8
 
8
9
  export class InMemoryThreadListAdapter implements RemoteThreadListAdapter {
@@ -35,4 +36,8 @@ export class InMemoryThreadListAdapter implements RemoteThreadListAdapter {
35
36
  generateTitle(): Promise<AssistantStream> {
36
37
  return Promise.resolve(new ReadableStream<AssistantStreamChunk>());
37
38
  }
39
+
40
+ fetch(_threadId: string): Promise<RemoteThreadMetadata> {
41
+ return Promise.reject(new Error("Thread not found"));
42
+ }
38
43
  }
@@ -31,6 +31,7 @@ export type RemoteThreadListAdapter = {
31
31
  remoteId: string,
32
32
  unstable_messages: readonly ThreadMessage[],
33
33
  ): Promise<AssistantStream>;
34
+ fetch(threadId: string): Promise<RemoteThreadMetadata>;
34
35
 
35
36
  unstable_Provider?: ComponentType<PropsWithChildren>;
36
37
  };
@@ -1,5 +1,7 @@
1
1
  import * as PopoverPrimitive from "@radix-ui/react-popover";
2
2
  import type { Scope } from "@radix-ui/react-context";
3
3
 
4
- export const usePopoverScope = PopoverPrimitive.createPopoverScope();
4
+ export const usePopoverScope: ReturnType<
5
+ typeof PopoverPrimitive.createPopoverScope
6
+ > = PopoverPrimitive.createPopoverScope();
5
7
  export type ScopedProps<P> = P & { __scopeAssistantModal?: Scope };
@@ -16,3 +16,4 @@ export { useMessagePartSource } from "./messagePart/useMessagePartSource";
16
16
  export { useMessagePartFile } from "./messagePart/useMessagePartFile";
17
17
  export { useMessagePartImage } from "./messagePart/useMessagePartImage";
18
18
  export { useThreadViewportAutoScroll } from "./thread/useThreadViewportAutoScroll";
19
+ export { useScrollLock } from "./reasoning";
@@ -25,6 +25,7 @@ import type {
25
25
  ToolCallMessagePartProps,
26
26
  FileMessagePartComponent,
27
27
  ReasoningMessagePartComponent,
28
+ ReasoningGroupComponent,
28
29
  } from "../../types/MessagePartComponentTypes";
29
30
  import { MessagePartPrimitiveInProgress } from "../messagePart/MessagePartInProgress";
30
31
  import { MessagePartStatus } from "../../types/AssistantTypes";
@@ -32,50 +33,75 @@ import { useShallow } from "zustand/shallow";
32
33
 
33
34
  type MessagePartRange =
34
35
  | { type: "single"; index: number }
35
- | { type: "toolGroup"; startIndex: number; endIndex: number };
36
+ | { type: "toolGroup"; startIndex: number; endIndex: number }
37
+ | { type: "reasoningGroup"; startIndex: number; endIndex: number };
36
38
 
37
39
  /**
38
- * Groups consecutive tool-call message parts into ranges.
39
- * Always groups tool calls, even if there's only one.
40
+ * Creates a group state manager for a specific part type.
41
+ * Returns functions to start, end, and finalize groups.
42
+ */
43
+ const createGroupState = <T extends "toolGroup" | "reasoningGroup">(
44
+ groupType: T,
45
+ ) => {
46
+ let start = -1;
47
+
48
+ return {
49
+ startGroup: (index: number) => {
50
+ if (start === -1) {
51
+ start = index;
52
+ }
53
+ },
54
+ endGroup: (endIndex: number, ranges: MessagePartRange[]) => {
55
+ if (start !== -1) {
56
+ ranges.push({
57
+ type: groupType,
58
+ startIndex: start,
59
+ endIndex,
60
+ } as MessagePartRange);
61
+ start = -1;
62
+ }
63
+ },
64
+ finalize: (endIndex: number, ranges: MessagePartRange[]) => {
65
+ if (start !== -1) {
66
+ ranges.push({
67
+ type: groupType,
68
+ startIndex: start,
69
+ endIndex,
70
+ } as MessagePartRange);
71
+ }
72
+ },
73
+ };
74
+ };
75
+
76
+ /**
77
+ * Groups consecutive tool-call and reasoning message parts into ranges.
78
+ * Always groups tool calls and reasoning parts, even if there's only one.
40
79
  */
41
80
  const groupMessageParts = (
42
81
  messageTypes: readonly string[],
43
82
  ): MessagePartRange[] => {
44
83
  const ranges: MessagePartRange[] = [];
45
- let currentToolGroupStart = -1;
84
+ const toolGroup = createGroupState("toolGroup");
85
+ const reasoningGroup = createGroupState("reasoningGroup");
46
86
 
47
87
  for (let i = 0; i < messageTypes.length; i++) {
48
88
  const type = messageTypes[i];
49
89
 
50
90
  if (type === "tool-call") {
51
- // Start a new tool group if we haven't started one
52
- if (currentToolGroupStart === -1) {
53
- currentToolGroupStart = i;
54
- }
91
+ reasoningGroup.endGroup(i - 1, ranges);
92
+ toolGroup.startGroup(i);
93
+ } else if (type === "reasoning") {
94
+ toolGroup.endGroup(i - 1, ranges);
95
+ reasoningGroup.startGroup(i);
55
96
  } else {
56
- // End current tool group if it exists
57
- if (currentToolGroupStart !== -1) {
58
- ranges.push({
59
- type: "toolGroup",
60
- startIndex: currentToolGroupStart,
61
- endIndex: i - 1,
62
- });
63
- currentToolGroupStart = -1;
64
- }
65
-
66
- // Add non-tool-call part individually
97
+ toolGroup.endGroup(i - 1, ranges);
98
+ reasoningGroup.endGroup(i - 1, ranges);
67
99
  ranges.push({ type: "single", index: i });
68
100
  }
69
101
  }
70
102
 
71
- // Handle any remaining tool group at the end
72
- if (currentToolGroupStart !== -1) {
73
- ranges.push({
74
- type: "toolGroup",
75
- startIndex: currentToolGroupStart,
76
- endIndex: messageTypes.length - 1,
77
- });
78
- }
103
+ toolGroup.finalize(messageTypes.length - 1, ranges);
104
+ reasoningGroup.finalize(messageTypes.length - 1, ranges);
79
105
 
80
106
  return ranges;
81
107
  };
@@ -184,6 +210,37 @@ export namespace MessagePrimitiveParts {
184
210
  ToolGroup?: ComponentType<
185
211
  PropsWithChildren<{ startIndex: number; endIndex: number }>
186
212
  >;
213
+
214
+ /**
215
+ * Component for rendering grouped reasoning parts.
216
+ *
217
+ * When provided, this component will automatically wrap reasoning message parts
218
+ * (one or more consecutive) in a group container. Each reasoning part inside
219
+ * renders its own text independently - no text merging occurs.
220
+ *
221
+ * The component receives:
222
+ * - `startIndex`: The index of the first reasoning part in the group
223
+ * - `endIndex`: The index of the last reasoning part in the group
224
+ * - `children`: The rendered Reasoning components (one per part)
225
+ *
226
+ * @example
227
+ * ```tsx
228
+ * // Collapsible reasoning group
229
+ * ReasoningGroup: ({ children }) => (
230
+ * <details className="reasoning-group">
231
+ * <summary>Reasoning</summary>
232
+ * <div className="reasoning-content">
233
+ * {children}
234
+ * </div>
235
+ * </details>
236
+ * )
237
+ * ```
238
+ *
239
+ * @param startIndex - Index of the first reasoning part in the group
240
+ * @param endIndex - Index of the last reasoning part in the group
241
+ * @param children - Rendered reasoning part components
242
+ */
243
+ ReasoningGroup?: ReasoningGroupComponent;
187
244
  }
188
245
  | undefined;
189
246
  };
@@ -220,6 +277,7 @@ const defaultComponents = {
220
277
  File: () => null,
221
278
  Unstable_Audio: () => null,
222
279
  ToolGroup: ({ children }) => children,
280
+ ReasoningGroup: ({ children }) => children,
223
281
  } satisfies MessagePrimitiveParts.Props["components"];
224
282
 
225
283
  type MessagePartComponentProps = {
@@ -328,7 +386,8 @@ export const MessagePrimitivePartByIndex: FC<MessagePrimitivePartByIndex.Props>
328
386
  prev.components?.File === next.components?.File &&
329
387
  prev.components?.Unstable_Audio === next.components?.Unstable_Audio &&
330
388
  prev.components?.tools === next.components?.tools &&
331
- prev.components?.ToolGroup === next.components?.ToolGroup,
389
+ prev.components?.ToolGroup === next.components?.ToolGroup &&
390
+ prev.components?.ReasoningGroup === next.components?.ReasoningGroup,
332
391
  );
333
392
 
334
393
  MessagePrimitivePartByIndex.displayName = "MessagePrimitive.PartByIndex";
@@ -416,12 +475,12 @@ export const MessagePrimitiveParts: FC<MessagePrimitiveParts.Props> = ({
416
475
  components={components}
417
476
  />
418
477
  );
419
- } else {
478
+ } else if (range.type === "toolGroup") {
420
479
  const ToolGroupComponent =
421
480
  components!.ToolGroup ?? defaultComponents.ToolGroup;
422
481
  return (
423
482
  <ToolGroupComponent
424
- key={range.startIndex}
483
+ key={`tool-${range.startIndex}`}
425
484
  startIndex={range.startIndex}
426
485
  endIndex={range.endIndex}
427
486
  >
@@ -437,6 +496,28 @@ export const MessagePrimitiveParts: FC<MessagePrimitiveParts.Props> = ({
437
496
  )}
438
497
  </ToolGroupComponent>
439
498
  );
499
+ } else {
500
+ // reasoningGroup
501
+ const ReasoningGroupComponent =
502
+ components!.ReasoningGroup ?? defaultComponents.ReasoningGroup;
503
+ return (
504
+ <ReasoningGroupComponent
505
+ key={`reasoning-${range.startIndex}`}
506
+ startIndex={range.startIndex}
507
+ endIndex={range.endIndex}
508
+ >
509
+ {Array.from(
510
+ { length: range.endIndex - range.startIndex + 1 },
511
+ (_, i) => (
512
+ <MessagePrimitivePartByIndex
513
+ key={i}
514
+ index={range.startIndex + i}
515
+ components={components}
516
+ />
517
+ ),
518
+ )}
519
+ </ReasoningGroupComponent>
520
+ );
440
521
  }
441
522
  });
442
523
  }, [messageRanges, components, contentLength]);
@@ -0,0 +1 @@
1
+ export { useScrollLock } from "./useScrollLock";
@@ -0,0 +1,86 @@
1
+ "use client";
2
+
3
+ import { type RefObject, useCallback, useEffect, useRef } from "react";
4
+
5
+ /**
6
+ * Locks scroll position during collapsible/height animations and hides scrollbar.
7
+ *
8
+ * This utility prevents page jumps when content height changes during animations,
9
+ * providing a smooth user experience. It finds the nearest scrollable ancestor and
10
+ * temporarily locks its scroll position while the animation completes.
11
+ *
12
+ * - Prevents forced reflows: no layout reads, mutations scoped to scrollable parent only
13
+ * - Reactive: only intercepts scroll events when browser actually adjusts
14
+ * - Cleans up automatically after animation duration
15
+ *
16
+ * @param animatedElementRef - Ref to the animated element
17
+ * @param animationDuration - Lock duration in milliseconds
18
+ * @returns Function to activate the scroll lock
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * const collapsibleRef = useRef<HTMLDivElement>(null);
23
+ * const lockScroll = useScrollLock(collapsibleRef, 200);
24
+ *
25
+ * const handleCollapse = () => {
26
+ * lockScroll(); // Lock scroll before collapsing
27
+ * setIsOpen(false);
28
+ * };
29
+ * ```
30
+ */
31
+ export const useScrollLock = <T extends HTMLElement = HTMLElement>(
32
+ animatedElementRef: RefObject<T | null>,
33
+ animationDuration: number,
34
+ ) => {
35
+ const scrollContainerRef = useRef<HTMLElement | null>(null);
36
+ const cleanupRef = useRef<(() => void) | null>(null);
37
+
38
+ useEffect(() => {
39
+ return () => {
40
+ cleanupRef.current?.();
41
+ };
42
+ }, []);
43
+
44
+ const lockScroll = useCallback(() => {
45
+ cleanupRef.current?.();
46
+
47
+ (function findScrollableAncestor() {
48
+ if (scrollContainerRef.current || !animatedElementRef.current) return;
49
+
50
+ let el: HTMLElement | null = animatedElementRef.current;
51
+ while (el) {
52
+ const { overflowY } = getComputedStyle(el);
53
+ if (overflowY === "scroll" || overflowY === "auto") {
54
+ scrollContainerRef.current = el;
55
+ break;
56
+ }
57
+ el = el.parentElement;
58
+ }
59
+ })();
60
+
61
+ const scrollContainer = scrollContainerRef.current;
62
+ if (!scrollContainer) return;
63
+
64
+ const scrollPosition = scrollContainer.scrollTop;
65
+ const scrollbarWidth = scrollContainer.style.scrollbarWidth;
66
+
67
+ scrollContainer.style.scrollbarWidth = "none";
68
+
69
+ const resetPosition = () => (scrollContainer.scrollTop = scrollPosition);
70
+ scrollContainer.addEventListener("scroll", resetPosition);
71
+
72
+ const timeoutId = setTimeout(() => {
73
+ scrollContainer.removeEventListener("scroll", resetPosition);
74
+ scrollContainer.style.scrollbarWidth = scrollbarWidth;
75
+ cleanupRef.current = null;
76
+ }, animationDuration);
77
+
78
+ cleanupRef.current = () => {
79
+ clearTimeout(timeoutId);
80
+ scrollContainer.removeEventListener("scroll", resetPosition);
81
+ scrollContainer.style.scrollbarWidth = scrollbarWidth;
82
+ };
83
+ }, [animationDuration, animatedElementRef]);
84
+
85
+ return lockScroll;
86
+ };
@@ -28,16 +28,13 @@ export const useThreadViewportAutoScroll = <TElement extends HTMLElement>({
28
28
  // fix: delay the state change until the scroll is done
29
29
  const isScrollingToBottomRef = useRef(false);
30
30
 
31
- const scrollToBottom = useCallback(
32
- (behavior: ScrollBehavior) => {
33
- const div = divRef.current;
34
- if (!div || !autoScroll) return;
31
+ const scrollToBottom = useCallback((behavior: ScrollBehavior) => {
32
+ const div = divRef.current;
33
+ if (!div) return;
35
34
 
36
- isScrollingToBottomRef.current = true;
37
- div.scrollTo({ top: div.scrollHeight, behavior });
38
- },
39
- [autoScroll],
40
- );
35
+ isScrollingToBottomRef.current = true;
36
+ div.scrollTo({ top: div.scrollHeight, behavior });
37
+ }, []);
41
38
 
42
39
  const handleScroll = () => {
43
40
  const div = divRef.current;
@@ -45,7 +42,8 @@ export const useThreadViewportAutoScroll = <TElement extends HTMLElement>({
45
42
 
46
43
  const isAtBottom = threadViewportStore.getState().isAtBottom;
47
44
  const newIsAtBottom =
48
- div.scrollHeight - div.scrollTop <= div.clientHeight + 1; // TODO figure out why +1 is needed
45
+ Math.abs(div.scrollHeight - div.scrollTop - div.clientHeight) < 1 ||
46
+ div.scrollHeight <= div.clientHeight;
49
47
 
50
48
  if (!newIsAtBottom && lastScrollTop.current < div.scrollTop) {
51
49
  // ignore scroll down
@@ -66,8 +64,9 @@ export const useThreadViewportAutoScroll = <TElement extends HTMLElement>({
66
64
 
67
65
  const resizeRef = useOnResizeContent(() => {
68
66
  if (
69
- isScrollingToBottomRef.current ||
70
- threadViewportStore.getState().isAtBottom
67
+ autoScroll &&
68
+ (isScrollingToBottomRef.current ||
69
+ threadViewportStore.getState().isAtBottom)
71
70
  ) {
72
71
  scrollToBottom("instant");
73
72
  }
@@ -87,7 +86,9 @@ export const useThreadViewportAutoScroll = <TElement extends HTMLElement>({
87
86
  });
88
87
 
89
88
  // autoscroll on run start
90
- useAssistantEvent("thread.run-start", () => scrollToBottom("auto"));
89
+ useAssistantEvent("thread.run-start", () => {
90
+ if (autoScroll) scrollToBottom("auto");
91
+ });
91
92
 
92
93
  const autoScrollRef = useComposedRefs<TElement>(resizeRef, scrollRef, divRef);
93
94
  return autoScrollRef as RefCallback<TElement>;
@@ -1,4 +1,4 @@
1
- import type { ComponentType } from "react";
1
+ import type { ComponentType, PropsWithChildren } from "react";
2
2
  import type {
3
3
  MessagePartStatus,
4
4
  FileMessagePart,
@@ -24,6 +24,12 @@ export type ReasoningMessagePartProps = MessagePartState & ReasoningMessagePart;
24
24
  export type ReasoningMessagePartComponent =
25
25
  ComponentType<ReasoningMessagePartProps>;
26
26
 
27
+ export type ReasoningGroupProps = PropsWithChildren<{
28
+ startIndex: number;
29
+ endIndex: number;
30
+ }>;
31
+ export type ReasoningGroupComponent = ComponentType<ReasoningGroupProps>;
32
+
27
33
  export type SourceMessagePartProps = MessagePartState & SourceMessagePart;
28
34
  export type SourceMessagePartComponent = ComponentType<SourceMessagePartProps>;
29
35
 
@@ -44,6 +44,8 @@ export type {
44
44
  Unstable_AudioMessagePartProps,
45
45
  ToolCallMessagePartComponent,
46
46
  ToolCallMessagePartProps,
47
+ ReasoningGroupProps,
48
+ ReasoningGroupComponent,
47
49
  } from "./MessagePartComponentTypes";
48
50
 
49
51
  // Thread list item types