@assistant-ui/core 0.2.0 → 0.2.2

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 (126) hide show
  1. package/README.md +45 -0
  2. package/dist/index.d.ts +2 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/react/client/Tools.d.ts +6 -1
  7. package/dist/react/client/Tools.d.ts.map +1 -1
  8. package/dist/react/client/Tools.js +16 -19
  9. package/dist/react/client/Tools.js.map +1 -1
  10. package/dist/react/index.d.ts +1 -1
  11. package/dist/react/index.d.ts.map +1 -1
  12. package/dist/react/index.js.map +1 -1
  13. package/dist/react/primitive-hooks/useComposerSend.d.ts.map +1 -1
  14. package/dist/react/primitive-hooks/useComposerSend.js +2 -3
  15. package/dist/react/primitive-hooks/useComposerSend.js.map +1 -1
  16. package/dist/react/primitives/message/MessageAttachments.js +1 -1
  17. package/dist/react/primitives/message/MessageAttachments.js.map +1 -1
  18. package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
  19. package/dist/react/primitives/message/MessageParts.js +14 -10
  20. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  21. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +2 -0
  22. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  23. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +2 -0
  24. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  25. package/dist/react/runtimes/cloud/auiV0.d.ts +10 -1
  26. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  27. package/dist/react/runtimes/cloud/auiV0.js +21 -3
  28. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  29. package/dist/react/runtimes/useToolInvocations.d.ts +2 -1
  30. package/dist/react/runtimes/useToolInvocations.d.ts.map +1 -1
  31. package/dist/react/runtimes/useToolInvocations.js +16 -1
  32. package/dist/react/runtimes/useToolInvocations.js.map +1 -1
  33. package/dist/react/types/scopes/tools.d.ts +4 -0
  34. package/dist/react/types/scopes/tools.d.ts.map +1 -1
  35. package/dist/runtime/api/composer-runtime.d.ts +1 -0
  36. package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
  37. package/dist/runtime/api/composer-runtime.js +2 -0
  38. package/dist/runtime/api/composer-runtime.js.map +1 -1
  39. package/dist/runtime/api/thread-runtime.d.ts +2 -0
  40. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  41. package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -0
  42. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  43. package/dist/runtime/base/base-composer-runtime-core.js +1 -1
  44. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  45. package/dist/runtime/base/base-thread-runtime-core.d.ts +1 -0
  46. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  47. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  48. package/dist/runtime/base/default-edit-composer-runtime-core.d.ts +1 -0
  49. package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
  50. package/dist/runtime/base/default-edit-composer-runtime-core.js +3 -0
  51. package/dist/runtime/base/default-edit-composer-runtime-core.js.map +1 -1
  52. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +1 -0
  53. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  54. package/dist/runtime/base/default-thread-composer-runtime-core.js +12 -1
  55. package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
  56. package/dist/runtime/interfaces/composer-runtime-core.d.ts +1 -0
  57. package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
  58. package/dist/runtime/interfaces/thread-runtime-core.d.ts +6 -0
  59. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  60. package/dist/runtimes/external-store/external-store-adapter.d.ts +15 -0
  61. package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
  62. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +1 -1
  63. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  64. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +14 -12
  65. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  66. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +1 -0
  67. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  68. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +2 -0
  69. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  70. package/dist/runtimes/local/local-thread-runtime-core.d.ts +1 -0
  71. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  72. package/dist/runtimes/local/local-thread-runtime-core.js +1 -0
  73. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  74. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +2 -0
  75. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
  76. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +2 -0
  77. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
  78. package/dist/runtimes/remote-thread-list/empty-thread-core.d.ts.map +1 -1
  79. package/dist/runtimes/remote-thread-list/empty-thread-core.js +2 -0
  80. package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
  81. package/dist/store/clients/no-op-composer-client.d.ts.map +1 -1
  82. package/dist/store/clients/no-op-composer-client.js +1 -0
  83. package/dist/store/clients/no-op-composer-client.js.map +1 -1
  84. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  85. package/dist/store/runtime-clients/composer-runtime-client.js +1 -0
  86. package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
  87. package/dist/store/scopes/composer.d.ts +9 -0
  88. package/dist/store/scopes/composer.d.ts.map +1 -1
  89. package/dist/types/index.d.ts +1 -1
  90. package/dist/types/index.d.ts.map +1 -1
  91. package/dist/types/message.d.ts +28 -1
  92. package/dist/types/message.d.ts.map +1 -1
  93. package/dist/types/message.js +2 -1
  94. package/dist/types/message.js.map +1 -1
  95. package/package.json +6 -7
  96. package/src/index.ts +6 -0
  97. package/src/react/client/Tools.ts +46 -22
  98. package/src/react/index.ts +1 -1
  99. package/src/react/primitive-hooks/useComposerSend.ts +2 -3
  100. package/src/react/primitives/message/MessageAttachments.test.tsx +50 -0
  101. package/src/react/primitives/message/MessageAttachments.tsx +1 -1
  102. package/src/react/primitives/message/MessageParts.tsx +20 -9
  103. package/src/react/runtimes/cloud/auiV0.ts +37 -4
  104. package/src/react/runtimes/useToolInvocations.ts +21 -1
  105. package/src/react/types/scopes/tools.ts +5 -0
  106. package/src/runtime/api/composer-runtime.ts +3 -0
  107. package/src/runtime/base/base-composer-runtime-core.ts +2 -1
  108. package/src/runtime/base/base-thread-runtime-core.ts +1 -0
  109. package/src/runtime/base/default-edit-composer-runtime-core.ts +4 -0
  110. package/src/runtime/base/default-thread-composer-runtime-core.ts +12 -1
  111. package/src/runtime/interfaces/composer-runtime-core.ts +1 -0
  112. package/src/runtime/interfaces/thread-runtime-core.ts +6 -0
  113. package/src/runtimes/external-store/external-store-adapter.ts +15 -0
  114. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +15 -9
  115. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +2 -0
  116. package/src/runtimes/local/local-thread-runtime-core.ts +1 -0
  117. package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +2 -0
  118. package/src/runtimes/remote-thread-list/empty-thread-core.ts +2 -0
  119. package/src/store/clients/no-op-composer-client.ts +1 -0
  120. package/src/store/runtime-clients/composer-runtime-client.ts +1 -0
  121. package/src/store/scopes/composer.ts +9 -0
  122. package/src/tests/auiV0Encode.test.ts +55 -0
  123. package/src/tests/composer-can-send.test.ts +112 -0
  124. package/src/tests/external-store-thread-list-runtime-core.test.ts +34 -0
  125. package/src/types/index.ts +2 -0
  126. package/src/types/message.ts +44 -7
@@ -59,7 +59,22 @@ type ExternalStoreMessageConverterAdapter<T> = {
59
59
  };
60
60
 
61
61
  type ExternalStoreAdapterBase<T> = {
62
+ /**
63
+ * Whether the entire thread is disabled. When `true`, the composer's input
64
+ * is also disabled (the user cannot type, attach files, or submit). For a
65
+ * narrower gate that keeps the input usable but blocks only sending, use
66
+ * `isSendDisabled`.
67
+ */
62
68
  isDisabled?: boolean | undefined;
69
+ /**
70
+ * Whether sending new messages is currently disabled. When `true`, the
71
+ * thread composer's input remains usable but `send()` becomes a no-op
72
+ * and the thread composer's `canSend` is `false`. Use this to gate
73
+ * sending on external React state (e.g. while tool config is loading)
74
+ * without disabling the input itself the way `isDisabled` does. Edit
75
+ * composers (saving message edits) intentionally ignore this flag.
76
+ */
77
+ isSendDisabled?: boolean | undefined;
63
78
  /**
64
79
  * Whether the thread is running. When provided, this value flows directly
65
80
  * to `thread.isRunning`, letting the application keep the thread in a
@@ -78,14 +78,7 @@ export class ExternalStoreThreadListRuntimeCore
78
78
  }
79
79
 
80
80
  public getItemById(threadId: string) {
81
- for (const thread of this.adapter.threads ?? []) {
82
- if (thread.id === threadId) return thread as any;
83
- }
84
- for (const thread of this.adapter.archivedThreads ?? []) {
85
- if (thread.id === threadId) return thread as any;
86
- }
87
- if (threadId === DEFAULT_THREAD_ID) return DEFAULT_THREAD;
88
- return undefined;
81
+ return this._threadData[threadId];
89
82
  }
90
83
 
91
84
  public __internal_setAdapter(
@@ -115,7 +108,8 @@ export class ExternalStoreThreadListRuntimeCore
115
108
 
116
109
  if (
117
110
  previousThreads !== newThreads ||
118
- previousArchivedThreads !== newArchivedThreads
111
+ previousArchivedThreads !== newArchivedThreads ||
112
+ previousThreadId !== newThreadId
119
113
  ) {
120
114
  this._threadData = {
121
115
  ...DEFAULT_THREAD_DATA,
@@ -159,6 +153,18 @@ export class ExternalStoreThreadListRuntimeCore
159
153
  this._mainThread = this.threadFactory();
160
154
  }
161
155
 
156
+ if (!this._threadData[this._mainThreadId]) {
157
+ this._threadData = {
158
+ ...this._threadData,
159
+ [this._mainThreadId]: {
160
+ id: this._mainThreadId,
161
+ remoteId: undefined,
162
+ externalId: undefined,
163
+ status: "regular",
164
+ },
165
+ };
166
+ }
167
+
162
168
  this._notifySubscribers();
163
169
  }
164
170
 
@@ -75,6 +75,7 @@ export class ExternalStoreThreadRuntimeCore
75
75
 
76
76
  private _messages!: readonly ThreadMessage[];
77
77
  public isDisabled!: boolean;
78
+ public isSendDisabled!: boolean;
78
79
  public get isLoading() {
79
80
  return this._store.isLoading ?? false;
80
81
  }
@@ -122,6 +123,7 @@ export class ExternalStoreThreadRuntimeCore
122
123
 
123
124
  const isRunning = store.isRunning ?? false;
124
125
  this.isDisabled = store.isDisabled ?? false;
126
+ this.isSendDisabled = store.isSendDisabled ?? false;
125
127
 
126
128
  const oldStore = this._store as ExternalStoreAdapter<any> | undefined;
127
129
  this._store = store;
@@ -54,6 +54,7 @@ export class LocalThreadRuntimeCore
54
54
  private abortController: AbortController | null = null;
55
55
 
56
56
  public readonly isDisabled = false;
57
+ public readonly isSendDisabled = false;
57
58
 
58
59
  private _isLoading = false;
59
60
  public get isLoading() {
@@ -118,6 +118,7 @@ export class ReadonlyThreadRuntimeCore
118
118
 
119
119
  isEditing: false as const,
120
120
  canCancel: false,
121
+ canSend: false,
121
122
  isEmpty: true,
122
123
  text: "",
123
124
 
@@ -205,6 +206,7 @@ export class ReadonlyThreadRuntimeCore
205
206
  } as const;
206
207
 
207
208
  isDisabled = false;
209
+ isSendDisabled = false;
208
210
  isLoading = false;
209
211
 
210
212
  state = null;
@@ -98,6 +98,7 @@ export const EMPTY_THREAD_CORE: ThreadRuntimeCore = {
98
98
  isEditing: true,
99
99
 
100
100
  canCancel: false,
101
+ canSend: false,
101
102
  isEmpty: true,
102
103
 
103
104
  text: "",
@@ -186,6 +187,7 @@ export const EMPTY_THREAD_CORE: ThreadRuntimeCore = {
186
187
  },
187
188
 
188
189
  isDisabled: false,
190
+ isSendDisabled: false,
189
191
  isLoading: true,
190
192
 
191
193
  messages: [],
@@ -14,6 +14,7 @@ export const NoOpComposerClient = resource(
14
14
  role: "user",
15
15
  runConfig: {},
16
16
  canCancel: false,
17
+ canSend: false,
17
18
  type: type,
18
19
  dictation: undefined,
19
20
  quote: undefined,
@@ -103,6 +103,7 @@ export const ComposerClient = resource(
103
103
  runConfig: runtimeState.runConfig,
104
104
  isEditing: runtimeState.isEditing,
105
105
  canCancel: runtimeState.canCancel,
106
+ canSend: runtimeState.canSend,
106
107
  attachmentAccept: runtimeState.attachmentAccept,
107
108
  isEmpty: runtimeState.isEmpty,
108
109
  type: runtimeState.type ?? "thread",
@@ -26,6 +26,15 @@ export type ComposerState = {
26
26
  readonly runConfig: RunConfig;
27
27
  readonly isEditing: boolean;
28
28
  readonly canCancel: boolean;
29
+ /**
30
+ * Whether the composer is currently willing to send. `true` when the
31
+ * composer is in editing mode and has non-empty content; for thread
32
+ * composers also requires the thread's `isSendDisabled` flag to be unset.
33
+ * Edit composers (saving message edits) ignore `isSendDisabled` since it
34
+ * is a thread-scoped gate. Cross-thread gating (running, queue capability)
35
+ * is layered on top by `useComposerSend`.
36
+ */
37
+ readonly canSend: boolean;
29
38
  readonly attachmentAccept: string;
30
39
  readonly isEmpty: boolean;
31
40
  readonly type: "thread" | "edit";
@@ -0,0 +1,55 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { auiV0Encode } from "../react/runtimes/cloud/auiV0";
3
+
4
+ describe("auiV0Encode", () => {
5
+ it("preserves document source parts in the core cloud encoder", () => {
6
+ const encoded = auiV0Encode({
7
+ id: "m1",
8
+ createdAt: new Date("2026-03-15T00:00:00.000Z"),
9
+ role: "assistant",
10
+ status: { type: "complete", reason: "stop" },
11
+ metadata: {
12
+ unstable_state: undefined,
13
+ unstable_annotations: [],
14
+ unstable_data: [],
15
+ steps: [],
16
+ custom: {},
17
+ },
18
+ content: [
19
+ {
20
+ type: "source",
21
+ sourceType: "document",
22
+ id: "doc_123",
23
+ title: "proposal.pdf",
24
+ mediaType: "application/pdf",
25
+ filename: "proposal.pdf",
26
+ providerMetadata: {
27
+ openai: {
28
+ type: "file_citation",
29
+ fileId: "file_123",
30
+ index: 0,
31
+ },
32
+ },
33
+ },
34
+ ],
35
+ });
36
+
37
+ expect(encoded.content).toEqual([
38
+ {
39
+ type: "source",
40
+ sourceType: "document",
41
+ id: "doc_123",
42
+ title: "proposal.pdf",
43
+ mediaType: "application/pdf",
44
+ filename: "proposal.pdf",
45
+ providerMetadata: {
46
+ openai: {
47
+ type: "file_citation",
48
+ fileId: "file_123",
49
+ index: 0,
50
+ },
51
+ },
52
+ },
53
+ ]);
54
+ });
55
+ });
@@ -0,0 +1,112 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { DefaultThreadComposerRuntimeCore } from "../runtime/base/default-thread-composer-runtime-core";
3
+ import { DefaultEditComposerRuntimeCore } from "../runtime/base/default-edit-composer-runtime-core";
4
+ import type { ThreadRuntimeCore } from "../runtime/interfaces/thread-runtime-core";
5
+ import type { ThreadMessage } from "../types/message";
6
+
7
+ type ThreadRuntimeStub = Omit<ThreadRuntimeCore, "composer"> & {
8
+ notify: () => void;
9
+ };
10
+
11
+ const makeRuntimeStub = (
12
+ overrides: Partial<ThreadRuntimeCore> = {},
13
+ ): ThreadRuntimeStub => {
14
+ const subscribers = new Set<() => void>();
15
+ const stub = {
16
+ append: vi.fn(),
17
+ cancelRun: vi.fn(),
18
+ subscribe: (cb: () => void) => {
19
+ subscribers.add(cb);
20
+ return () => subscribers.delete(cb);
21
+ },
22
+ capabilities: { cancel: false },
23
+ messages: [],
24
+ isDisabled: false,
25
+ isSendDisabled: false,
26
+ isLoading: false,
27
+ composer: { runConfig: {} },
28
+ notify: () => {
29
+ for (const cb of subscribers) cb();
30
+ },
31
+ ...overrides,
32
+ } as unknown as ThreadRuntimeStub;
33
+ return stub;
34
+ };
35
+
36
+ const makeUserMessage = (text = "old"): ThreadMessage =>
37
+ ({
38
+ id: "msg-1",
39
+ role: "user",
40
+ createdAt: new Date(),
41
+ content: [{ type: "text", text }],
42
+ attachments: [],
43
+ metadata: { custom: {} },
44
+ }) as ThreadMessage;
45
+
46
+ describe("DefaultThreadComposerRuntimeCore.canSend", () => {
47
+ it("is false when the composer is empty", () => {
48
+ const composer = new DefaultThreadComposerRuntimeCore(makeRuntimeStub());
49
+ expect(composer.canSend).toBe(false);
50
+ });
51
+
52
+ it("is true when in editing mode with non-empty text", () => {
53
+ const composer = new DefaultThreadComposerRuntimeCore(makeRuntimeStub());
54
+ composer.setText("hi");
55
+ expect(composer.canSend).toBe(true);
56
+ });
57
+
58
+ it("is false when the runtime reports isSendDisabled", () => {
59
+ const composer = new DefaultThreadComposerRuntimeCore(
60
+ makeRuntimeStub({ isSendDisabled: true }),
61
+ );
62
+ composer.setText("hi");
63
+ expect(composer.canSend).toBe(false);
64
+ });
65
+
66
+ it("notifies subscribers when isSendDisabled flips", () => {
67
+ const stub = makeRuntimeStub();
68
+ const composer = new DefaultThreadComposerRuntimeCore(stub);
69
+ composer.setText("hi");
70
+ const onChange = vi.fn();
71
+ composer.subscribe(onChange);
72
+
73
+ (stub as { isSendDisabled: boolean }).isSendDisabled = true;
74
+ stub.notify();
75
+ expect(onChange).toHaveBeenCalled();
76
+ expect(composer.canSend).toBe(false);
77
+ });
78
+ });
79
+
80
+ describe("BaseComposerRuntimeCore.send", () => {
81
+ it("is a no-op when canSend is false because of isSendDisabled", async () => {
82
+ const stub = makeRuntimeStub({ isSendDisabled: true });
83
+ const composer = new DefaultThreadComposerRuntimeCore(stub);
84
+ composer.setText("hi");
85
+
86
+ await composer.send();
87
+
88
+ expect(stub.append).not.toHaveBeenCalled();
89
+ });
90
+
91
+ it("dispatches when canSend is true", async () => {
92
+ const stub = makeRuntimeStub();
93
+ const composer = new DefaultThreadComposerRuntimeCore(stub);
94
+ composer.setText("hi");
95
+
96
+ await composer.send();
97
+
98
+ expect(stub.append).toHaveBeenCalledTimes(1);
99
+ });
100
+ });
101
+
102
+ describe("DefaultEditComposerRuntimeCore.canSend", () => {
103
+ it("ignores runtime.isSendDisabled (thread-scoped flag does not block edits)", () => {
104
+ const stub = makeRuntimeStub({ isSendDisabled: true });
105
+ const composer = new DefaultEditComposerRuntimeCore(stub, () => {}, {
106
+ parentId: null,
107
+ message: makeUserMessage("seed"),
108
+ });
109
+
110
+ expect(composer.canSend).toBe(true);
111
+ });
112
+ });
@@ -146,6 +146,33 @@ describe("ExternalStoreThreadListRuntimeCore - __internal_setAdapter", () => {
146
146
  core.__internal_setAdapter(makeAdapter({ threadId: "thread-beta" }));
147
147
  expect(callback).toHaveBeenCalled();
148
148
  });
149
+
150
+ it("synthesizes mainThreadId entry after a switch to a threadId not in the threads list (regression: #3971)", () => {
151
+ const core = new ExternalStoreThreadListRuntimeCore(
152
+ makeAdapter({ threadId: "thread-alpha" }),
153
+ makeFactory(),
154
+ );
155
+ core.__internal_setAdapter(makeAdapter({ threadId: "thread-beta" }));
156
+ const item = core.getItemById("thread-beta");
157
+ expect(item).toBeDefined();
158
+ expect(item?.id).toBe("thread-beta");
159
+ });
160
+
161
+ it("does not retain stale synthesized entries across mainThreadId switches (regression: #3971)", () => {
162
+ const core = new ExternalStoreThreadListRuntimeCore(
163
+ makeAdapter({ threadId: "thread-alpha" }),
164
+ makeFactory(),
165
+ );
166
+ core.__internal_setAdapter(makeAdapter({ threadId: "thread-beta" }));
167
+ core.__internal_setAdapter(makeAdapter({ threadId: "thread-gamma" }));
168
+ expect(core.getItemById("thread-alpha")).toBeUndefined();
169
+ expect(core.getItemById("thread-beta")).toBeUndefined();
170
+ expect(core.getItemById("thread-gamma")).toBeDefined();
171
+ expect(Object.keys(core.threadItems).sort()).toEqual([
172
+ "DEFAULT_THREAD_ID",
173
+ "thread-gamma",
174
+ ]);
175
+ });
149
176
  });
150
177
 
151
178
  describe("ExternalStoreThreadListRuntimeCore - isMain via ThreadListRuntimeImpl", () => {
@@ -187,6 +214,13 @@ describe("ExternalStoreThreadListRuntimeCore - isMain via ThreadListRuntimeImpl"
187
214
  expect(state.mainThreadId).toBe("thread-alpha");
188
215
  expect(state.threadIds).toEqual(["thread-alpha", "thread-beta"]);
189
216
  });
217
+
218
+ it("does not throw when adapter.threadId has no matching threads entry (regression: #3971)", () => {
219
+ const impl = buildImpl({ threadId: "thread-alpha" });
220
+ expect(impl.mainItem.getState().id).toBe("thread-alpha");
221
+ expect(impl.mainItem.getState().isMain).toBe(true);
222
+ expect(impl.mainItem.getState().status).toBe("regular");
223
+ });
190
224
  });
191
225
 
192
226
  describe("ExternalStoreThreadListRuntimeCore - switchToThread", () => {
@@ -2,12 +2,14 @@ export type {
2
2
  // Message parts
3
3
  TextMessagePart,
4
4
  ReasoningMessagePart,
5
+ SourceProviderMetadata,
5
6
  SourceMessagePart,
6
7
  ImageMessagePart,
7
8
  FileMessagePart,
8
9
  DataMessagePart,
9
10
  Unstable_AudioMessagePart,
10
11
  ToolCallMessagePart,
12
+ ToolModelContentPart,
11
13
  ThreadUserMessagePart,
12
14
  ThreadAssistantMessagePart,
13
15
  // Message status
@@ -2,8 +2,11 @@ import type {
2
2
  ReadonlyJSONObject,
3
3
  ReadonlyJSONValue,
4
4
  } from "assistant-stream/utils";
5
+ import type { ToolModelContentPart } from "assistant-stream";
5
6
  import type { CompleteAttachment } from "./attachment";
6
7
 
8
+ export type { ToolModelContentPart };
9
+
7
10
  export type TextMessagePart = {
8
11
  readonly type: "text";
9
12
  readonly text: string;
@@ -16,15 +19,32 @@ export type ReasoningMessagePart = {
16
19
  readonly parentId?: string;
17
20
  };
18
21
 
19
- export type SourceMessagePart = {
20
- readonly type: "source";
21
- readonly sourceType: "url";
22
- readonly id: string;
23
- readonly url: string;
24
- readonly title?: string;
25
- readonly parentId?: string;
22
+ export type SourceProviderMetadata = {
23
+ readonly [providerName: string]: ReadonlyJSONObject;
26
24
  };
27
25
 
26
+ export type SourceMessagePart =
27
+ | {
28
+ readonly type: "source";
29
+ readonly sourceType: "url";
30
+ readonly id: string;
31
+ readonly url: string;
32
+ readonly title?: string;
33
+ readonly providerMetadata?: SourceProviderMetadata;
34
+ readonly parentId?: string;
35
+ }
36
+ | {
37
+ readonly type: "source";
38
+ readonly sourceType: "document";
39
+ readonly id: string;
40
+ readonly url?: undefined;
41
+ readonly title: string;
42
+ readonly mediaType: string;
43
+ readonly filename?: string;
44
+ readonly providerMetadata?: SourceProviderMetadata;
45
+ readonly parentId?: string;
46
+ };
47
+
28
48
  export type ImageMessagePart = {
29
49
  readonly type: "image";
30
50
  readonly image: string;
@@ -53,6 +73,21 @@ export type DataMessagePart<T = any> = {
53
73
  readonly data: T;
54
74
  };
55
75
 
76
+ export type McpAppMetadata = {
77
+ readonly resourceUri: string;
78
+ readonly mimeType?: string;
79
+ readonly visibility?: readonly ("model" | "app")[];
80
+ };
81
+
82
+ export const MCP_APP_URI_SCHEME = "ui://";
83
+
84
+ export const isMcpAppUri = (uri: string | undefined): boolean =>
85
+ !!uri?.startsWith(MCP_APP_URI_SCHEME);
86
+
87
+ export type ToolCallMessagePartMcpMetadata = {
88
+ readonly app?: McpAppMetadata;
89
+ };
90
+
56
91
  export type ToolCallMessagePart<
57
92
  TArgs = ReadonlyJSONObject,
58
93
  TResult = unknown,
@@ -65,6 +100,8 @@ export type ToolCallMessagePart<
65
100
  readonly isError?: boolean | undefined;
66
101
  readonly argsText: string;
67
102
  readonly artifact?: unknown;
103
+ readonly mcp?: ToolCallMessagePartMcpMetadata;
104
+ readonly modelContent?: readonly ToolModelContentPart[] | undefined;
68
105
  readonly interrupt?: { type: "human"; payload: unknown };
69
106
  readonly parentId?: string;
70
107
  readonly messages?: readonly ThreadMessage[];