@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.
- package/README.md +45 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/react/client/Tools.d.ts +6 -1
- package/dist/react/client/Tools.d.ts.map +1 -1
- package/dist/react/client/Tools.js +16 -19
- package/dist/react/client/Tools.js.map +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/primitive-hooks/useComposerSend.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useComposerSend.js +2 -3
- package/dist/react/primitive-hooks/useComposerSend.js.map +1 -1
- package/dist/react/primitives/message/MessageAttachments.js +1 -1
- package/dist/react/primitives/message/MessageAttachments.js.map +1 -1
- package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageParts.js +14 -10
- package/dist/react/primitives/message/MessageParts.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +2 -0
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +2 -0
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/auiV0.d.ts +10 -1
- package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/auiV0.js +21 -3
- package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
- package/dist/react/runtimes/useToolInvocations.d.ts +2 -1
- package/dist/react/runtimes/useToolInvocations.d.ts.map +1 -1
- package/dist/react/runtimes/useToolInvocations.js +16 -1
- package/dist/react/runtimes/useToolInvocations.js.map +1 -1
- package/dist/react/types/scopes/tools.d.ts +4 -0
- package/dist/react/types/scopes/tools.d.ts.map +1 -1
- package/dist/runtime/api/composer-runtime.d.ts +1 -0
- package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
- package/dist/runtime/api/composer-runtime.js +2 -0
- package/dist/runtime/api/composer-runtime.js.map +1 -1
- package/dist/runtime/api/thread-runtime.d.ts +2 -0
- package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -0
- package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.js +1 -1
- package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.d.ts +1 -0
- package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
- package/dist/runtime/base/default-edit-composer-runtime-core.d.ts +1 -0
- package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/default-edit-composer-runtime-core.js +3 -0
- package/dist/runtime/base/default-edit-composer-runtime-core.js.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +1 -0
- package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.js +12 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
- package/dist/runtime/interfaces/composer-runtime-core.d.ts +1 -0
- package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/interfaces/thread-runtime-core.d.ts +6 -0
- package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-adapter.d.ts +15 -0
- package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +14 -12
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +1 -0
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js +2 -0
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts +1 -0
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +1 -0
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +2 -0
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +2 -0
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
- package/dist/runtimes/remote-thread-list/empty-thread-core.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/empty-thread-core.js +2 -0
- package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
- package/dist/store/clients/no-op-composer-client.d.ts.map +1 -1
- package/dist/store/clients/no-op-composer-client.js +1 -0
- package/dist/store/clients/no-op-composer-client.js.map +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.js +1 -0
- package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
- package/dist/store/scopes/composer.d.ts +9 -0
- package/dist/store/scopes/composer.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/message.d.ts +28 -1
- package/dist/types/message.d.ts.map +1 -1
- package/dist/types/message.js +2 -1
- package/dist/types/message.js.map +1 -1
- package/package.json +6 -7
- package/src/index.ts +6 -0
- package/src/react/client/Tools.ts +46 -22
- package/src/react/index.ts +1 -1
- package/src/react/primitive-hooks/useComposerSend.ts +2 -3
- package/src/react/primitives/message/MessageAttachments.test.tsx +50 -0
- package/src/react/primitives/message/MessageAttachments.tsx +1 -1
- package/src/react/primitives/message/MessageParts.tsx +20 -9
- package/src/react/runtimes/cloud/auiV0.ts +37 -4
- package/src/react/runtimes/useToolInvocations.ts +21 -1
- package/src/react/types/scopes/tools.ts +5 -0
- package/src/runtime/api/composer-runtime.ts +3 -0
- package/src/runtime/base/base-composer-runtime-core.ts +2 -1
- package/src/runtime/base/base-thread-runtime-core.ts +1 -0
- package/src/runtime/base/default-edit-composer-runtime-core.ts +4 -0
- package/src/runtime/base/default-thread-composer-runtime-core.ts +12 -1
- package/src/runtime/interfaces/composer-runtime-core.ts +1 -0
- package/src/runtime/interfaces/thread-runtime-core.ts +6 -0
- package/src/runtimes/external-store/external-store-adapter.ts +15 -0
- package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +15 -9
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +2 -0
- package/src/runtimes/local/local-thread-runtime-core.ts +1 -0
- package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +2 -0
- package/src/runtimes/remote-thread-list/empty-thread-core.ts +2 -0
- package/src/store/clients/no-op-composer-client.ts +1 -0
- package/src/store/runtime-clients/composer-runtime-client.ts +1 -0
- package/src/store/scopes/composer.ts +9 -0
- package/src/tests/auiV0Encode.test.ts +55 -0
- package/src/tests/composer-can-send.test.ts +112 -0
- package/src/tests/external-store-thread-list-runtime-core.test.ts +34 -0
- package/src/types/index.ts +2 -0
- 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
|
-
|
|
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;
|
|
@@ -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: [],
|
|
@@ -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", () => {
|
package/src/types/index.ts
CHANGED
|
@@ -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
|
package/src/types/message.ts
CHANGED
|
@@ -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
|
|
20
|
-
readonly
|
|
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[];
|