@assistant-ui/core 0.3.2 → 0.3.4
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/dist/index.d.ts +4 -4
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +9 -7
- package/dist/internal.js +8 -6
- package/dist/react/primitive-hooks/useActionBarCopy.js +51 -24
- package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
- package/dist/react/primitive-hooks/useSuggestionTrigger.js +29 -25
- package/dist/react/primitive-hooks/useSuggestionTrigger.js.map +1 -1
- package/dist/react/primitives/message/MessageGroupedParts.d.ts +2 -1
- package/dist/react/primitives/message/MessageGroupedParts.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageGroupedParts.js +2 -2
- package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
- package/dist/react/primitives/message/MessageParts.d.ts +6 -1
- package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageParts.js.map +1 -1
- package/dist/react/providers/ReadonlyThreadProvider.d.ts.map +1 -1
- package/dist/react/providers/ReadonlyThreadProvider.js +1 -0
- package/dist/react/providers/ReadonlyThreadProvider.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +67 -0
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +76 -15
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +4 -0
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +21 -0
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/cloud/auiV0.d.ts +26 -2
- package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/auiV0.js +29 -8
- package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
- package/dist/react/types/MessagePartComponentTypes.d.ts +8 -0
- package/dist/react/types/MessagePartComponentTypes.d.ts.map +1 -1
- package/dist/runtime/api/bindings.d.ts +5 -0
- package/dist/runtime/api/bindings.d.ts.map +1 -1
- package/dist/runtime/api/message-runtime.d.ts.map +1 -1
- package/dist/runtime/api/message-runtime.js +5 -1
- package/dist/runtime/api/message-runtime.js.map +1 -1
- package/dist/runtime/api/thread-list-runtime.d.ts +14 -1
- package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-list-runtime.js +6 -1
- package/dist/runtime/api/thread-list-runtime.js.map +1 -1
- package/dist/runtime/api/thread-runtime.d.ts +7 -1
- package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-runtime.js +11 -3
- package/dist/runtime/api/thread-runtime.js.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts +3 -0
- package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.js +54 -12
- package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
- package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +10 -0
- package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
- package/dist/runtime/interfaces/thread-runtime-core.d.ts +14 -0
- package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/internal.d.ts +4 -4
- package/dist/runtime/internal.js +4 -4
- package/dist/runtimes/external-store/external-store-adapter.d.ts +9 -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 -0
- 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 +4 -0
- 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 +6 -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 +1 -0
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +1 -0
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
- package/dist/runtimes/remote-thread-list/empty-thread-core.js +1 -0
- package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
- package/dist/store/clients/chain-of-thought-client.d.ts.map +1 -1
- package/dist/store/clients/chain-of-thought-client.js +29 -23
- package/dist/store/clients/chain-of-thought-client.js.map +1 -1
- package/dist/store/clients/thread-message-client.d.ts.map +1 -1
- package/dist/store/clients/thread-message-client.js +101 -89
- package/dist/store/clients/thread-message-client.js.map +1 -1
- package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts +1 -0
- package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-list-item-runtime-client.js +66 -45
- package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
- package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-list-runtime-client.js +91 -75
- package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
- package/dist/store/scopes/thread-list-item.d.ts +7 -0
- package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
- package/dist/store/scopes/threads.d.ts +1 -0
- package/dist/store/scopes/threads.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/message.d.ts +22 -1
- package/dist/types/message.d.ts.map +1 -1
- package/dist/types/message.js.map +1 -1
- package/dist/utils/data-url.d.ts +14 -1
- package/dist/utils/data-url.d.ts.map +1 -1
- package/dist/utils/data-url.js +23 -1
- package/dist/utils/data-url.js.map +1 -1
- package/dist/utils/getGroupStatus.d.ts +9 -0
- package/dist/utils/getGroupStatus.d.ts.map +1 -0
- package/dist/utils/getGroupStatus.js +15 -0
- package/dist/utils/getGroupStatus.js.map +1 -0
- package/dist/utils/image-media-type.d.ts +13 -0
- package/dist/utils/image-media-type.d.ts.map +1 -0
- package/dist/utils/image-media-type.js +141 -0
- package/dist/utils/image-media-type.js.map +1 -0
- package/dist/utils/normalizePartStatus.d.ts +8 -0
- package/dist/utils/normalizePartStatus.d.ts.map +1 -0
- package/dist/utils/normalizePartStatus.js +39 -0
- package/dist/utils/normalizePartStatus.js.map +1 -0
- package/dist/utils/wire-media.d.ts +35 -0
- package/dist/utils/wire-media.d.ts.map +1 -0
- package/dist/utils/wire-media.js +55 -0
- package/dist/utils/wire-media.js.map +1 -0
- package/package.json +4 -4
- package/src/index.ts +1 -0
- package/src/internal.ts +13 -1
- package/src/react/primitive-hooks/useActionBarCopy.test.ts +109 -14
- package/src/react/primitive-hooks/useActionBarCopy.ts +27 -2
- package/src/react/primitive-hooks/useSuggestionTrigger.test.ts +148 -0
- package/src/react/primitive-hooks/useSuggestionTrigger.ts +10 -4
- package/src/react/primitives/message/MessageGroupedParts.tsx +4 -4
- package/src/react/primitives/message/MessageParts.tsx +6 -1
- package/src/react/providers/ReadonlyThreadProvider.tsx +1 -0
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +157 -0
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +160 -0
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +135 -32
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +37 -0
- package/src/react/runtimes/cloud/auiV0.ts +59 -14
- package/src/react/types/MessagePartComponentTypes.ts +8 -0
- package/src/runtime/api/bindings.ts +5 -0
- package/src/runtime/api/message-runtime.test.ts +140 -0
- package/src/runtime/api/message-runtime.ts +10 -5
- package/src/runtime/api/thread-list-runtime.ts +22 -1
- package/src/runtime/api/thread-runtime.ts +15 -6
- package/src/runtime/base/base-composer-runtime-core.ts +72 -19
- package/src/runtime/interfaces/thread-list-runtime-core.ts +11 -0
- package/src/runtime/interfaces/thread-runtime-core.ts +15 -0
- package/src/runtimes/external-store/external-store-adapter.ts +9 -0
- package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +9 -0
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +8 -0
- package/src/runtimes/local/local-thread-runtime-core.ts +1 -0
- package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +1 -0
- package/src/runtimes/remote-thread-list/empty-thread-core.ts +1 -0
- package/src/store/clients/chain-of-thought-client.ts +2 -7
- package/src/store/clients/thread-message-client.test.ts +84 -0
- package/src/store/clients/thread-message-client.ts +14 -4
- package/src/store/runtime-clients/thread-list-item-runtime-client.ts +13 -2
- package/src/store/runtime-clients/thread-list-runtime-client.ts +13 -1
- package/src/store/scopes/thread-list-item.ts +7 -0
- package/src/store/scopes/threads.ts +1 -0
- package/src/tests/RemoteThreadListThreadListRuntimeCore-reloadMainThread.test.ts +257 -0
- package/src/tests/auiV0Encode.test.ts +218 -1
- package/src/tests/base-composer-runtime-core-addAttachment.test.ts +296 -0
- package/src/tests/event-subscription-listener-errors.test.ts +1 -0
- package/src/tests/external-store-thread-list-runtime-core.test.ts +39 -1
- package/src/types/index.ts +1 -0
- package/src/types/message.ts +30 -0
- package/src/utils/data-url.ts +24 -0
- package/src/utils/getGroupStatus.test.ts +36 -0
- package/src/utils/getGroupStatus.ts +31 -0
- package/src/utils/image-media-type.test.ts +96 -0
- package/src/utils/image-media-type.ts +118 -0
- package/src/utils/normalizePartStatus.test.ts +59 -0
- package/src/utils/normalizePartStatus.ts +55 -0
- package/src/utils/wire-media.test.ts +96 -0
- package/src/utils/wire-media.ts +70 -0
|
@@ -18,14 +18,25 @@ import { ThreadListItemRuntimeProvider } from "../providers/ThreadListItemRuntim
|
|
|
18
18
|
import type { ThreadRuntimeCore } from "../../runtime/interfaces/thread-runtime-core";
|
|
19
19
|
import type { ThreadListRuntimeCore } from "../../runtime/interfaces/thread-list-runtime-core";
|
|
20
20
|
import type { AssistantRuntime } from "../../runtime/api/assistant-runtime";
|
|
21
|
+
import type { Unsubscribe } from "../../types/unsubscribe";
|
|
21
22
|
import { BaseSubscribable } from "../../subscribable/subscribable";
|
|
22
|
-
import
|
|
23
|
+
import {
|
|
24
|
+
getThreadRuntimeCoreIsRunning,
|
|
25
|
+
type ThreadRuntimeImpl,
|
|
26
|
+
} from "../../runtime/api/thread-runtime";
|
|
23
27
|
import { ThreadListRuntimeImpl } from "../../runtime/api/thread-list-runtime";
|
|
24
28
|
|
|
25
29
|
type RemoteThreadListHook = () => AssistantRuntime;
|
|
26
30
|
|
|
27
31
|
type RemoteThreadListHookInstance = {
|
|
28
|
-
runtime?: ThreadRuntimeCore;
|
|
32
|
+
runtime?: ThreadRuntimeCore | undefined;
|
|
33
|
+
// A runtime riding across a restart stays readable, but only counts as
|
|
34
|
+
// attached once a binder of the current generation re-publishes it.
|
|
35
|
+
publishedGeneration?: number | undefined;
|
|
36
|
+
// Part of the binder's React key, so only a bump remounts the hook.
|
|
37
|
+
generation: number;
|
|
38
|
+
isRunning: boolean;
|
|
39
|
+
unsubscribeRunning?: Unsubscribe | undefined;
|
|
29
40
|
};
|
|
30
41
|
|
|
31
42
|
const ProviderRenderDetector: FC<{
|
|
@@ -41,6 +52,9 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
|
|
|
41
52
|
StoreApi<{ useRuntime: RemoteThreadListHook }>
|
|
42
53
|
>;
|
|
43
54
|
private instances = new Map<string, RemoteThreadListHookInstance>();
|
|
55
|
+
// Manager-wide so it survives instance deletion: a stop and start within one
|
|
56
|
+
// React commit must not reuse a binder key.
|
|
57
|
+
private nextGeneration = 0;
|
|
44
58
|
private useAliveThreadsKeysChanged = create(() => ({}));
|
|
45
59
|
private parent: ThreadListRuntimeCore;
|
|
46
60
|
|
|
@@ -53,20 +67,18 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
|
|
|
53
67
|
this.useRuntimeHook = create(() => ({ useRuntime: runtimeHook }));
|
|
54
68
|
}
|
|
55
69
|
|
|
56
|
-
|
|
57
|
-
if (!this.instances.has(threadId)) {
|
|
58
|
-
this.instances.set(threadId, {});
|
|
59
|
-
this.useAliveThreadsKeysChanged.setState({}, true);
|
|
60
|
-
}
|
|
61
|
-
|
|
70
|
+
private _whenRuntimeAttached(threadId: string) {
|
|
62
71
|
return new Promise<ThreadRuntimeCore>((resolve, reject) => {
|
|
63
72
|
const callback = () => {
|
|
64
73
|
const instance = this.instances.get(threadId);
|
|
65
74
|
if (!instance) {
|
|
66
75
|
dispose();
|
|
67
76
|
reject(new Error("Thread was deleted before runtime was started"));
|
|
68
|
-
} else if (
|
|
69
|
-
|
|
77
|
+
} else if (
|
|
78
|
+
!instance.runtime ||
|
|
79
|
+
instance.publishedGeneration !== instance.generation
|
|
80
|
+
) {
|
|
81
|
+
return; // not yet published by the current generation's binder
|
|
70
82
|
} else {
|
|
71
83
|
dispose();
|
|
72
84
|
resolve(instance.runtime);
|
|
@@ -77,13 +89,104 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
|
|
|
77
89
|
});
|
|
78
90
|
}
|
|
79
91
|
|
|
92
|
+
public startThreadRuntime(threadId: string) {
|
|
93
|
+
if (!this.instances.has(threadId)) {
|
|
94
|
+
this.instances.set(threadId, {
|
|
95
|
+
generation: this.nextGeneration++,
|
|
96
|
+
isRunning: false,
|
|
97
|
+
});
|
|
98
|
+
this.useAliveThreadsKeysChanged.setState({}, true);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return this._whenRuntimeAttached(threadId);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public __internal_restartThreadRuntime(threadId: string) {
|
|
105
|
+
const instance = this.instances.get(threadId);
|
|
106
|
+
if (!instance) return this.startThreadRuntime(threadId);
|
|
107
|
+
|
|
108
|
+
instance.generation = this.nextGeneration++;
|
|
109
|
+
this.useAliveThreadsKeysChanged.setState({}, true);
|
|
110
|
+
this._notifySubscribers();
|
|
111
|
+
|
|
112
|
+
return this._whenRuntimeAttached(threadId);
|
|
113
|
+
}
|
|
114
|
+
|
|
80
115
|
public getThreadRuntimeCore(threadId: string) {
|
|
81
116
|
const instance = this.instances.get(threadId);
|
|
82
117
|
if (!instance) return undefined;
|
|
83
118
|
return instance.runtime;
|
|
84
119
|
}
|
|
85
120
|
|
|
121
|
+
public __internal_isThreadRunning(threadId: string) {
|
|
122
|
+
return this.instances.get(threadId)?.isRunning ?? false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private runningSubscribers = new Set<() => void>();
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Fires when any thread crosses the running boundary. Separate from the
|
|
129
|
+
* general subscription so a run does not push the thread list through the
|
|
130
|
+
* channel that resolves pending runtime attachments.
|
|
131
|
+
*/
|
|
132
|
+
public __internal_subscribeRunningChanged(callback: () => void): Unsubscribe {
|
|
133
|
+
this.runningSubscribers.add(callback);
|
|
134
|
+
return () => this.runningSubscribers.delete(callback);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private _publishThreadRuntime(
|
|
138
|
+
threadId: string,
|
|
139
|
+
runtime: ThreadRuntimeCore,
|
|
140
|
+
generation: number,
|
|
141
|
+
) {
|
|
142
|
+
const instance = this.instances.get(threadId);
|
|
143
|
+
if (!instance)
|
|
144
|
+
throw new Error(
|
|
145
|
+
`Thread "${threadId}" runtime binding not found. This is a bug in assistant-ui.`,
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
// An outgoing binder outlives its generation until React commits the key
|
|
149
|
+
// change, and must not publish over the incoming one.
|
|
150
|
+
if (instance.generation !== generation) return;
|
|
151
|
+
|
|
152
|
+
const previousRuntime = instance.runtime;
|
|
153
|
+
instance.runtime = runtime;
|
|
154
|
+
instance.publishedGeneration = generation;
|
|
155
|
+
if (previousRuntime !== runtime) {
|
|
156
|
+
this._trackRunning(instance);
|
|
157
|
+
}
|
|
158
|
+
this._notifySubscribers();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Run state changes far more often than the thread list does, so the list is
|
|
162
|
+
// only notified when a thread crosses the running boundary.
|
|
163
|
+
private _trackRunning(instance: RemoteThreadListHookInstance) {
|
|
164
|
+
instance.unsubscribeRunning?.();
|
|
165
|
+
|
|
166
|
+
const runtime = instance.runtime;
|
|
167
|
+
if (!runtime) {
|
|
168
|
+
instance.unsubscribeRunning = undefined;
|
|
169
|
+
this._setRunning(instance, false);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
this._setRunning(instance, getThreadRuntimeCoreIsRunning(runtime));
|
|
174
|
+
instance.unsubscribeRunning = runtime.subscribe(() => {
|
|
175
|
+
this._setRunning(instance, getThreadRuntimeCoreIsRunning(runtime));
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private _setRunning(
|
|
180
|
+
instance: RemoteThreadListHookInstance,
|
|
181
|
+
isRunning: boolean,
|
|
182
|
+
) {
|
|
183
|
+
if (instance.isRunning === isRunning) return;
|
|
184
|
+
instance.isRunning = isRunning;
|
|
185
|
+
for (const callback of this.runningSubscribers) callback();
|
|
186
|
+
}
|
|
187
|
+
|
|
86
188
|
public stopThreadRuntime(threadId: string) {
|
|
189
|
+
this.instances.get(threadId)?.unsubscribeRunning?.();
|
|
87
190
|
this.instances.delete(threadId);
|
|
88
191
|
this.useAliveThreadsKeysChanged.setState({}, true);
|
|
89
192
|
this._notifySubscribers();
|
|
@@ -98,10 +201,9 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
|
|
|
98
201
|
|
|
99
202
|
// Rendered as a child of the user's Provider so the runtime hook can
|
|
100
203
|
// read context the Provider injects (e.g. RuntimeAdapterProvider).
|
|
101
|
-
private _RuntimeBinder: FC<
|
|
102
|
-
threadId
|
|
103
|
-
|
|
104
|
-
}) => {
|
|
204
|
+
private _RuntimeBinder: FC<
|
|
205
|
+
PropsWithChildren<{ threadId: string; generation: number }>
|
|
206
|
+
> = ({ threadId, generation, children }) => {
|
|
105
207
|
const { useRuntime } = this.useRuntimeHook();
|
|
106
208
|
const runtime = useRuntime();
|
|
107
209
|
|
|
@@ -109,15 +211,12 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
|
|
|
109
211
|
.__internal_threadBinding;
|
|
110
212
|
|
|
111
213
|
const updateRuntime = useCallback(() => {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
aliveThread.runtime = threadBinding.getState();
|
|
119
|
-
this._notifySubscribers();
|
|
120
|
-
}, [threadId, threadBinding]);
|
|
214
|
+
this._publishThreadRuntime(
|
|
215
|
+
threadId,
|
|
216
|
+
threadBinding.getState(),
|
|
217
|
+
generation,
|
|
218
|
+
);
|
|
219
|
+
}, [threadId, generation, threadBinding]);
|
|
121
220
|
|
|
122
221
|
const isMounted = useRef(false);
|
|
123
222
|
if (!isMounted.current) {
|
|
@@ -168,8 +267,9 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
|
|
|
168
267
|
|
|
169
268
|
private _OuterActiveThreadProvider: FC<{
|
|
170
269
|
threadId: string;
|
|
270
|
+
generation: number;
|
|
171
271
|
provider: ComponentType<PropsWithChildren>;
|
|
172
|
-
}> = memo(({ threadId, provider: Provider }) => {
|
|
272
|
+
}> = memo(({ threadId, generation, provider: Provider }) => {
|
|
173
273
|
const runtime = useMemo(
|
|
174
274
|
() => new ThreadListRuntimeImpl(this.parent).getItemById(threadId),
|
|
175
275
|
[threadId],
|
|
@@ -195,7 +295,7 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
|
|
|
195
295
|
return (
|
|
196
296
|
<ThreadListItemRuntimeProvider runtime={runtime}>
|
|
197
297
|
<Provider>
|
|
198
|
-
<this._RuntimeBinder threadId={threadId}>
|
|
298
|
+
<this._RuntimeBinder threadId={threadId} generation={generation}>
|
|
199
299
|
<ProviderRenderDetector detectorRef={detectorRef} />
|
|
200
300
|
</this._RuntimeBinder>
|
|
201
301
|
</Provider>
|
|
@@ -208,12 +308,15 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
|
|
|
208
308
|
}> = ({ provider }) => {
|
|
209
309
|
this.useAliveThreadsKeysChanged(); // trigger re-render on alive threads change
|
|
210
310
|
|
|
211
|
-
return Array.from(this.instances.
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
311
|
+
return Array.from(this.instances.entries()).map(
|
|
312
|
+
([threadId, { generation }]) => (
|
|
313
|
+
<this._OuterActiveThreadProvider
|
|
314
|
+
key={`${threadId}:${generation}`}
|
|
315
|
+
threadId={threadId}
|
|
316
|
+
generation={generation}
|
|
317
|
+
provider={provider}
|
|
318
|
+
/>
|
|
319
|
+
),
|
|
320
|
+
);
|
|
218
321
|
};
|
|
219
322
|
}
|
|
@@ -185,6 +185,9 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
185
185
|
options.runtimeHook,
|
|
186
186
|
this,
|
|
187
187
|
);
|
|
188
|
+
this._hookManager.__internal_subscribeRunningChanged(() =>
|
|
189
|
+
this._notifySubscribers(),
|
|
190
|
+
);
|
|
188
191
|
this.useProvider = create(() => ({
|
|
189
192
|
Provider: options.adapter.unstable_Provider ?? Fragment,
|
|
190
193
|
}));
|
|
@@ -245,6 +248,34 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
245
248
|
}
|
|
246
249
|
}
|
|
247
250
|
|
|
251
|
+
public async reloadMainThread(): Promise<void> {
|
|
252
|
+
const threadId = this._mainThreadId;
|
|
253
|
+
if (threadId === undefined) return;
|
|
254
|
+
|
|
255
|
+
// An unsent thread holds no remote state, so a refetch would only discard
|
|
256
|
+
// what the user has typed.
|
|
257
|
+
if (this.getItemById(threadId)?.status === "new") return;
|
|
258
|
+
|
|
259
|
+
const runtimeCore = this._hookManager.getThreadRuntimeCore(threadId);
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
if (runtimeCore?.unstable_refetchThread) {
|
|
263
|
+
// Called on the core so class-method implementations keep `this`.
|
|
264
|
+
await runtimeCore.unstable_refetchThread();
|
|
265
|
+
} else {
|
|
266
|
+
await this._hookManager.__internal_restartThreadRuntime(threadId);
|
|
267
|
+
}
|
|
268
|
+
} catch (error) {
|
|
269
|
+
// delete and detach switch the main thread away before stopping the
|
|
270
|
+
// runtime, so a rejection once that has happened belongs to them.
|
|
271
|
+
if (threadId !== this._mainThreadId) return;
|
|
272
|
+
throw error;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (threadId !== this._mainThreadId) return;
|
|
276
|
+
this._notifySubscribers();
|
|
277
|
+
}
|
|
278
|
+
|
|
248
279
|
public reload() {
|
|
249
280
|
this._loadGeneration++;
|
|
250
281
|
this._loadThreadsPromise = undefined;
|
|
@@ -321,6 +352,12 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
321
352
|
return result;
|
|
322
353
|
}
|
|
323
354
|
|
|
355
|
+
public unstable_isThreadRunning(threadIdOrRemoteId: string) {
|
|
356
|
+
const data = this.getItemById(threadIdOrRemoteId);
|
|
357
|
+
if (!data) return false;
|
|
358
|
+
return this._hookManager.__internal_isThreadRunning(data.id);
|
|
359
|
+
}
|
|
360
|
+
|
|
324
361
|
public getItemById(threadIdOrRemoteId: string) {
|
|
325
362
|
return getThreadData(this._state.value, threadIdOrRemoteId);
|
|
326
363
|
}
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
DataMessagePart,
|
|
3
|
-
FileMessagePart,
|
|
4
|
-
ImageMessagePart,
|
|
5
2
|
MessageStatus,
|
|
6
3
|
SourceProviderMetadata,
|
|
7
4
|
ThreadMessage,
|
|
8
|
-
TextMessagePart,
|
|
9
5
|
ToolApprovalOption,
|
|
10
|
-
Unstable_AudioMessagePart,
|
|
11
6
|
} from "../../../types/message";
|
|
12
7
|
import type { CompleteAttachment } from "../../../types/attachment";
|
|
13
8
|
import { fromThreadMessageLike } from "../../../runtime/utils/thread-message-like";
|
|
@@ -82,14 +77,38 @@ type AuiV0MessagePart =
|
|
|
82
77
|
readonly data: string;
|
|
83
78
|
readonly mimeType: string;
|
|
84
79
|
readonly filename?: string;
|
|
80
|
+
readonly sourceType?: "url" | "id";
|
|
85
81
|
};
|
|
86
82
|
|
|
87
83
|
type AuiV0AttachmentPart =
|
|
88
|
-
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
|
84
|
+
| {
|
|
85
|
+
readonly type: "text";
|
|
86
|
+
readonly text: string;
|
|
87
|
+
}
|
|
88
|
+
| {
|
|
89
|
+
readonly type: "image";
|
|
90
|
+
readonly image: string;
|
|
91
|
+
readonly filename?: string;
|
|
92
|
+
}
|
|
93
|
+
| {
|
|
94
|
+
readonly type: "file";
|
|
95
|
+
readonly data: string;
|
|
96
|
+
readonly mimeType: string;
|
|
97
|
+
readonly filename?: string;
|
|
98
|
+
readonly sourceType?: "url" | "id";
|
|
99
|
+
}
|
|
100
|
+
| {
|
|
101
|
+
readonly type: "audio";
|
|
102
|
+
readonly audio: {
|
|
103
|
+
readonly data: string;
|
|
104
|
+
readonly format: "mp3" | "wav";
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
| {
|
|
108
|
+
readonly type: "data";
|
|
109
|
+
readonly name: string;
|
|
110
|
+
readonly data: ReadonlyJSONValue;
|
|
111
|
+
};
|
|
93
112
|
|
|
94
113
|
type AuiV0Attachment = {
|
|
95
114
|
readonly id: string;
|
|
@@ -125,16 +144,41 @@ const encodeAttachmentPart = (
|
|
|
125
144
|
const type = part.type;
|
|
126
145
|
switch (type) {
|
|
127
146
|
case "text":
|
|
147
|
+
return { type: "text", text: part.text };
|
|
148
|
+
|
|
128
149
|
case "image":
|
|
150
|
+
return {
|
|
151
|
+
type: "image",
|
|
152
|
+
image: part.image,
|
|
153
|
+
...(part.filename != null ? { filename: part.filename } : undefined),
|
|
154
|
+
};
|
|
155
|
+
|
|
129
156
|
case "file":
|
|
157
|
+
return {
|
|
158
|
+
type: "file",
|
|
159
|
+
data: part.data,
|
|
160
|
+
mimeType: part.mimeType,
|
|
161
|
+
...(part.filename != null ? { filename: part.filename } : undefined),
|
|
162
|
+
...(part.sourceType != null
|
|
163
|
+
? { sourceType: part.sourceType }
|
|
164
|
+
: undefined),
|
|
165
|
+
};
|
|
166
|
+
|
|
130
167
|
case "audio":
|
|
131
|
-
return
|
|
168
|
+
return {
|
|
169
|
+
type: "audio",
|
|
170
|
+
audio: { data: part.audio.data, format: part.audio.format },
|
|
171
|
+
};
|
|
132
172
|
|
|
133
173
|
case "data": {
|
|
134
174
|
if (!isJSONValue(part.data)) {
|
|
135
175
|
console.warn(`attachment data is not JSON! ${JSON.stringify(part)}`);
|
|
136
176
|
}
|
|
137
|
-
return {
|
|
177
|
+
return {
|
|
178
|
+
type: "data",
|
|
179
|
+
name: part.name,
|
|
180
|
+
data: part.data as ReadonlyJSONValue,
|
|
181
|
+
};
|
|
138
182
|
}
|
|
139
183
|
|
|
140
184
|
default: {
|
|
@@ -213,7 +257,7 @@ export function auiV0Encode(message: ThreadMessage): AuiV0Message {
|
|
|
213
257
|
};
|
|
214
258
|
|
|
215
259
|
case "tool-call": {
|
|
216
|
-
if (!isJSONValue(part.result)) {
|
|
260
|
+
if (part.result !== undefined && !isJSONValue(part.result)) {
|
|
217
261
|
console.warn(
|
|
218
262
|
`tool-call result is not JSON! ${JSON.stringify(part)}`,
|
|
219
263
|
);
|
|
@@ -225,7 +269,7 @@ export function auiV0Encode(message: ThreadMessage): AuiV0Message {
|
|
|
225
269
|
...(JSON.stringify(part.args) === part.argsText
|
|
226
270
|
? { args: part.args }
|
|
227
271
|
: { argsText: part.argsText }),
|
|
228
|
-
...(part.result
|
|
272
|
+
...(part.result !== undefined
|
|
229
273
|
? { result: part.result as ReadonlyJSONValue }
|
|
230
274
|
: undefined),
|
|
231
275
|
...(part.isError ? { isError: true } : undefined),
|
|
@@ -242,6 +286,7 @@ export function auiV0Encode(message: ThreadMessage): AuiV0Message {
|
|
|
242
286
|
data: part.data,
|
|
243
287
|
mimeType: part.mimeType,
|
|
244
288
|
...(part.filename ? { filename: part.filename } : undefined),
|
|
289
|
+
...(part.sourceType ? { sourceType: part.sourceType } : undefined),
|
|
245
290
|
};
|
|
246
291
|
|
|
247
292
|
default: {
|
|
@@ -44,8 +44,16 @@ export type ImageMessagePartComponent = ComponentType<ImageMessagePartProps>;
|
|
|
44
44
|
export type FileMessagePartProps = MessagePartState & FileMessagePart;
|
|
45
45
|
export type FileMessagePartComponent = ComponentType<FileMessagePartProps>;
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* @deprecated Use {@link FileMessagePartProps} and render `audio/*` mime types
|
|
49
|
+
* from the `File` slot.
|
|
50
|
+
*/
|
|
47
51
|
export type Unstable_AudioMessagePartProps = MessagePartState &
|
|
48
52
|
Unstable_AudioMessagePart;
|
|
53
|
+
/**
|
|
54
|
+
* @deprecated Use {@link FileMessagePartComponent} and render `audio/*` mime
|
|
55
|
+
* types from the `File` slot.
|
|
56
|
+
*/
|
|
49
57
|
export type Unstable_AudioMessagePartComponent =
|
|
50
58
|
ComponentType<Unstable_AudioMessagePartProps>;
|
|
51
59
|
|
|
@@ -37,6 +37,11 @@ export type MessageStateBinding = SubscribableWithState<
|
|
|
37
37
|
|
|
38
38
|
export type ThreadListItemState = {
|
|
39
39
|
readonly isMain: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Whether this thread has a run in progress, including a run that continues
|
|
42
|
+
* after the user switches to another thread.
|
|
43
|
+
*/
|
|
44
|
+
readonly isRunning: boolean;
|
|
40
45
|
readonly id: string;
|
|
41
46
|
readonly remoteId: string | undefined;
|
|
42
47
|
readonly externalId: string | undefined;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import type { CompleteAttachment } from "../../types/attachment";
|
|
3
|
+
import type { ThreadAssistantMessage } from "../../types/message";
|
|
3
4
|
import type { ThreadRuntimeCoreBinding } from "./thread-runtime";
|
|
4
5
|
import {
|
|
5
6
|
MessageRuntimeImpl,
|
|
7
|
+
toMessagePartStatus,
|
|
6
8
|
type MessageState,
|
|
7
9
|
type MessageStateBinding,
|
|
8
10
|
} from "./message-runtime";
|
|
@@ -63,6 +65,144 @@ const threadBinding = {
|
|
|
63
65
|
subscribe: () => () => {},
|
|
64
66
|
} as unknown as ThreadRuntimeCoreBinding;
|
|
65
67
|
|
|
68
|
+
const createAssistantMessage = (
|
|
69
|
+
content: ThreadAssistantMessage["content"],
|
|
70
|
+
status: ThreadAssistantMessage["status"],
|
|
71
|
+
): ThreadAssistantMessage => ({
|
|
72
|
+
id: "message-1",
|
|
73
|
+
role: "assistant",
|
|
74
|
+
createdAt: new Date(0),
|
|
75
|
+
content,
|
|
76
|
+
status,
|
|
77
|
+
metadata: {
|
|
78
|
+
unstable_state: null,
|
|
79
|
+
unstable_annotations: [],
|
|
80
|
+
unstable_data: [],
|
|
81
|
+
steps: [],
|
|
82
|
+
custom: {},
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("toMessagePartStatus", () => {
|
|
87
|
+
it("honours a supplied running status on a non-last part", () => {
|
|
88
|
+
const message = createAssistantMessage(
|
|
89
|
+
[
|
|
90
|
+
{ type: "text", text: "first", status: { type: "running" } },
|
|
91
|
+
{ type: "text", text: "last" },
|
|
92
|
+
],
|
|
93
|
+
{ type: "running" },
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
expect(toMessagePartStatus(message, 0, message.content[0]!)).toEqual({
|
|
97
|
+
type: "running",
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("honours a supplied complete status on the last part", () => {
|
|
102
|
+
const message = createAssistantMessage(
|
|
103
|
+
[{ type: "reasoning", text: "done", status: { type: "complete" } }],
|
|
104
|
+
{ type: "running" },
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
expect(toMessagePartStatus(message, 0, message.content[0]!)).toEqual({
|
|
108
|
+
type: "complete",
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("ignores supplied statuses after the message completes", () => {
|
|
113
|
+
const message = createAssistantMessage(
|
|
114
|
+
[{ type: "text", text: "truncated", status: { type: "running" } }],
|
|
115
|
+
{ type: "complete", reason: "stop" },
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
expect(toMessagePartStatus(message, 0, message.content[0]!)).toEqual({
|
|
119
|
+
type: "complete",
|
|
120
|
+
reason: "stop",
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("falls back to positional statuses for statusless running parts", () => {
|
|
125
|
+
const message = createAssistantMessage(
|
|
126
|
+
[
|
|
127
|
+
{ type: "text", text: "first" },
|
|
128
|
+
{ type: "reasoning", text: "last" },
|
|
129
|
+
],
|
|
130
|
+
{ type: "running" },
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
expect(toMessagePartStatus(message, 0, message.content[0]!)).toEqual({
|
|
134
|
+
type: "complete",
|
|
135
|
+
});
|
|
136
|
+
expect(toMessagePartStatus(message, 1, message.content[1]!)).toEqual({
|
|
137
|
+
type: "running",
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("preserves tool-call status derivation", () => {
|
|
142
|
+
const unresolved = createAssistantMessage(
|
|
143
|
+
[
|
|
144
|
+
{
|
|
145
|
+
type: "tool-call",
|
|
146
|
+
toolCallId: "call-1",
|
|
147
|
+
toolName: "weather",
|
|
148
|
+
args: {},
|
|
149
|
+
argsText: "{}",
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
{ type: "running" },
|
|
153
|
+
);
|
|
154
|
+
const resolved = createAssistantMessage(
|
|
155
|
+
[
|
|
156
|
+
{
|
|
157
|
+
type: "tool-call",
|
|
158
|
+
toolCallId: "call-1",
|
|
159
|
+
toolName: "weather",
|
|
160
|
+
args: {},
|
|
161
|
+
argsText: "{}",
|
|
162
|
+
result: "sunny",
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
{ type: "running" },
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
expect(toMessagePartStatus(unresolved, 0, unresolved.content[0]!)).toEqual({
|
|
169
|
+
type: "running",
|
|
170
|
+
});
|
|
171
|
+
expect(toMessagePartStatus(resolved, 0, resolved.content[0]!)).toEqual({
|
|
172
|
+
type: "complete",
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("normalizes supplied upstream statuses", () => {
|
|
177
|
+
const upstreamComplete = {
|
|
178
|
+
type: "text",
|
|
179
|
+
text: "done",
|
|
180
|
+
status: { type: "complete", reason: "unknown" },
|
|
181
|
+
} as unknown as ThreadAssistantMessage["content"][number];
|
|
182
|
+
const upstreamIncomplete = {
|
|
183
|
+
type: "reasoning",
|
|
184
|
+
text: "interrupted",
|
|
185
|
+
status: {
|
|
186
|
+
type: "incomplete",
|
|
187
|
+
reason: "unknown",
|
|
188
|
+
error: "upstream error",
|
|
189
|
+
},
|
|
190
|
+
} as unknown as ThreadAssistantMessage["content"][number];
|
|
191
|
+
const message = createAssistantMessage(
|
|
192
|
+
[upstreamComplete, upstreamIncomplete],
|
|
193
|
+
{ type: "running" },
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
expect(toMessagePartStatus(message, 0, message.content[0]!)).toEqual({
|
|
197
|
+
type: "complete",
|
|
198
|
+
});
|
|
199
|
+
expect(toMessagePartStatus(message, 1, message.content[1]!)).toEqual({
|
|
200
|
+
type: "incomplete",
|
|
201
|
+
reason: "other",
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
66
206
|
describe("MessageRuntimeImpl paths", () => {
|
|
67
207
|
it("appends nested selectors to the message path", () => {
|
|
68
208
|
const runtime = new MessageRuntimeImpl(messageBinding, threadBinding);
|
|
@@ -8,6 +8,10 @@ import type {
|
|
|
8
8
|
} from "../../types/message";
|
|
9
9
|
import type { Unsubscribe } from "../../types/unsubscribe";
|
|
10
10
|
import type { MessagePartStatus, RunConfig } from "../../types/message";
|
|
11
|
+
import {
|
|
12
|
+
COMPLETE_STATUS,
|
|
13
|
+
normalizePartStatus,
|
|
14
|
+
} from "../../utils/normalizePartStatus";
|
|
11
15
|
import { getThreadMessageText } from "../../utils/text";
|
|
12
16
|
import { NestedSubscriptionSubject } from "../../subscribable/subscribable";
|
|
13
17
|
import {
|
|
@@ -32,10 +36,6 @@ import type { MessageRuntimePath } from "./paths";
|
|
|
32
36
|
import type { ThreadRuntimeCoreBinding } from "./thread-runtime";
|
|
33
37
|
import type { MessageStateBinding } from "./bindings";
|
|
34
38
|
|
|
35
|
-
const COMPLETE_STATUS: MessagePartStatus = Object.freeze({
|
|
36
|
-
type: "complete",
|
|
37
|
-
});
|
|
38
|
-
|
|
39
39
|
export const toMessagePartStatus = (
|
|
40
40
|
message: ThreadMessage,
|
|
41
41
|
partIndex: number,
|
|
@@ -51,6 +51,11 @@ export const toMessagePartStatus = (
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
if (message.status.type === "running") {
|
|
55
|
+
const status = normalizePartStatus(part);
|
|
56
|
+
if (status) return status;
|
|
57
|
+
}
|
|
58
|
+
|
|
54
59
|
const isLastPart = partIndex === Math.max(0, message.content.length - 1);
|
|
55
60
|
if (message.status.type === "requires-action") return COMPLETE_STATUS;
|
|
56
61
|
return isLastPart ? (message.status as MessagePartStatus) : COMPLETE_STATUS;
|
|
@@ -70,7 +75,7 @@ const getMessagePartState = (
|
|
|
70
75
|
return Object.freeze({
|
|
71
76
|
...part,
|
|
72
77
|
...{ [symbolInnerMessage]: (part as any)[symbolInnerMessage] },
|
|
73
|
-
status,
|
|
78
|
+
status: status as MessagePartStatus,
|
|
74
79
|
});
|
|
75
80
|
};
|
|
76
81
|
|