@assistant-ui/core 0.1.9 → 0.1.11
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/adapters/index.d.ts +3 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +1 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/voice.d.ts +49 -0
- package/dist/adapters/voice.d.ts.map +1 -0
- package/dist/adapters/voice.js +109 -0
- package/dist/adapters/voice.js.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/model-context/types.d.ts +4 -0
- package/dist/model-context/types.d.ts.map +1 -1
- package/dist/model-context/types.js.map +1 -1
- package/dist/react/client/Interactables.d.ts.map +1 -1
- package/dist/react/client/Interactables.js +155 -65
- package/dist/react/client/Interactables.js.map +1 -1
- package/dist/react/client/interactable-model-context.d.ts +8 -0
- package/dist/react/client/interactable-model-context.d.ts.map +1 -0
- package/dist/react/client/interactable-model-context.js +62 -0
- package/dist/react/client/interactable-model-context.js.map +1 -0
- package/dist/react/index.d.ts +6 -3
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +5 -2
- package/dist/react/index.js.map +1 -1
- package/dist/react/model-context/useAssistantContext.d.ts +4 -0
- package/dist/react/model-context/useAssistantContext.d.ts.map +1 -0
- package/dist/react/model-context/useAssistantContext.js +18 -0
- package/dist/react/model-context/useAssistantContext.js.map +1 -0
- package/dist/react/model-context/useAssistantInteractable.d.ts +18 -0
- package/dist/react/model-context/useAssistantInteractable.d.ts.map +1 -0
- package/dist/react/model-context/useAssistantInteractable.js +31 -0
- package/dist/react/model-context/useAssistantInteractable.js.map +1 -0
- package/dist/react/model-context/useInteractableState.d.ts +15 -0
- package/dist/react/model-context/useInteractableState.d.ts.map +1 -0
- package/dist/react/model-context/useInteractableState.js +36 -0
- package/dist/react/model-context/useInteractableState.js.map +1 -0
- package/dist/react/model-context/useToolArgsStatus.d.ts +8 -0
- package/dist/react/model-context/useToolArgsStatus.d.ts.map +1 -0
- package/dist/react/model-context/useToolArgsStatus.js +31 -0
- package/dist/react/model-context/useToolArgsStatus.js.map +1 -0
- package/dist/react/primitive-hooks/useVoice.d.ts +10 -0
- package/dist/react/primitive-hooks/useVoice.d.ts.map +1 -0
- package/dist/react/primitive-hooks/useVoice.js +28 -0
- package/dist/react/primitive-hooks/useVoice.js.map +1 -0
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +14 -0
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +14 -0
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +16 -2
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/useLocalRuntime.d.ts +1 -0
- package/dist/react/runtimes/useLocalRuntime.d.ts.map +1 -1
- package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
- package/dist/react/runtimes/useRemoteThreadListRuntime.js +17 -1
- package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
- package/dist/react/runtimes/useToolInvocations.d.ts.map +1 -1
- package/dist/react/runtimes/useToolInvocations.js +8 -0
- package/dist/react/runtimes/useToolInvocations.js.map +1 -1
- package/dist/react/types/scopes/interactables.d.ts +17 -0
- package/dist/react/types/scopes/interactables.d.ts.map +1 -1
- package/dist/runtime/api/thread-list-runtime.d.ts +2 -0
- package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-list-runtime.js +4 -0
- package/dist/runtime/api/thread-list-runtime.js.map +1 -1
- package/dist/runtime/api/thread-runtime.d.ts +21 -1
- package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-runtime.js +25 -0
- package/dist/runtime/api/thread-runtime.js.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.d.ts +24 -1
- package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.js +205 -1
- package/dist/runtime/base/base-thread-runtime-core.js.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/runtimes/external-store/external-store-adapter.d.ts +2 -0
- package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +2 -1
- 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 +3 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/local/local-runtime-options.d.ts +2 -0
- package/dist/runtimes/local/local-runtime-options.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts +2 -0
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +6 -0
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +8 -0
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +15 -6
- 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 +18 -2
- package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
- package/dist/runtimes/remote-thread-list/types.d.ts +14 -0
- package/dist/runtimes/remote-thread-list/types.d.ts.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 +1 -0
- package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
- package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-runtime-client.js +7 -8
- package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
- package/dist/store/scopes/thread.d.ts +9 -9
- package/dist/store/scopes/thread.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/message.d.ts +1 -0
- package/dist/types/message.d.ts.map +1 -1
- package/package.json +9 -15
- package/src/adapters/index.ts +5 -0
- package/src/adapters/voice.ts +166 -0
- package/src/index.ts +10 -0
- package/src/model-context/types.ts +5 -0
- package/src/react/client/Interactables.ts +221 -122
- package/src/react/client/interactable-model-context.ts +83 -0
- package/src/react/index.ts +19 -8
- package/src/react/model-context/useAssistantContext.ts +22 -0
- package/src/react/model-context/useAssistantInteractable.ts +47 -0
- package/src/react/model-context/useInteractableState.ts +63 -0
- package/src/react/model-context/useToolArgsStatus.ts +51 -0
- package/src/react/primitive-hooks/useVoice.ts +41 -0
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +15 -2
- package/src/react/runtimes/useRemoteThreadListRuntime.ts +19 -1
- package/src/react/runtimes/useToolInvocations.ts +9 -0
- package/src/react/types/scopes/interactables.ts +22 -0
- package/src/runtime/api/thread-list-runtime.ts +7 -0
- package/src/runtime/api/thread-runtime.ts +41 -0
- package/src/runtime/base/base-thread-runtime-core.ts +243 -2
- package/src/runtime/interfaces/thread-runtime-core.ts +17 -0
- package/src/runtimes/external-store/external-store-adapter.ts +2 -0
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +3 -1
- package/src/runtimes/local/local-runtime-options.ts +2 -0
- package/src/runtimes/local/local-thread-runtime-core.ts +7 -0
- package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +20 -6
- package/src/runtimes/remote-thread-list/empty-thread-core.ts +23 -2
- package/src/runtimes/remote-thread-list/types.ts +16 -0
- package/src/store/runtime-clients/thread-list-runtime-client.ts +1 -0
- package/src/store/runtime-clients/thread-runtime-client.ts +7 -8
- package/src/store/scopes/thread.ts +9 -8
- package/src/store/scopes/threads.ts +1 -0
- package/src/tests/empty-thread-core.test.ts +33 -0
- package/src/tests/remote-thread-list-isLoading.test.ts +192 -0
- package/src/tests/remote-thread-list-reactive-threadId.test.ts +93 -0
- package/src/tests/thread-list-runtime-getLoadThreadsPromise.test.ts +75 -0
- package/src/types/message.ts +1 -0
- package/dist/react/model-context/makeInteractable.d.ts +0 -10
- package/dist/react/model-context/makeInteractable.d.ts.map +0 -1
- package/dist/react/model-context/makeInteractable.js +0 -10
- package/dist/react/model-context/makeInteractable.js.map +0 -1
- package/dist/react/model-context/useInteractable.d.ts +0 -16
- package/dist/react/model-context/useInteractable.d.ts.map +0 -1
- package/dist/react/model-context/useInteractable.js +0 -36
- package/dist/react/model-context/useInteractable.js.map +0 -1
- package/src/react/model-context/makeInteractable.ts +0 -21
- package/src/react/model-context/useInteractable.ts +0 -73
|
@@ -4,6 +4,7 @@ import type { Unsubscribe } from "../../types/unsubscribe";
|
|
|
4
4
|
import type { AppendMessage, ThreadMessage } from "../../types/message";
|
|
5
5
|
import type { RunConfig } from "../../types/message";
|
|
6
6
|
import type { SpeechSynthesisAdapter } from "../../adapters/speech";
|
|
7
|
+
import type { RealtimeVoiceAdapter } from "../../adapters/voice";
|
|
7
8
|
import type {
|
|
8
9
|
ChatModelRunOptions,
|
|
9
10
|
ChatModelRunResult,
|
|
@@ -24,6 +25,7 @@ export type RuntimeCapabilities = {
|
|
|
24
25
|
readonly unstable_copy: boolean;
|
|
25
26
|
readonly speech: boolean;
|
|
26
27
|
readonly dictation: boolean;
|
|
28
|
+
readonly voice: boolean;
|
|
27
29
|
readonly attachments: boolean;
|
|
28
30
|
readonly feedback: boolean;
|
|
29
31
|
readonly queue: boolean;
|
|
@@ -57,6 +59,12 @@ export type SpeechState = {
|
|
|
57
59
|
readonly status: SpeechSynthesisAdapter.Status;
|
|
58
60
|
};
|
|
59
61
|
|
|
62
|
+
export type VoiceSessionState = {
|
|
63
|
+
readonly status: RealtimeVoiceAdapter.Status;
|
|
64
|
+
readonly isMuted: boolean;
|
|
65
|
+
readonly mode: RealtimeVoiceAdapter.Mode;
|
|
66
|
+
};
|
|
67
|
+
|
|
60
68
|
export type SubmittedFeedback = {
|
|
61
69
|
readonly type: "negative" | "positive";
|
|
62
70
|
};
|
|
@@ -102,6 +110,11 @@ export type ThreadRuntimeCore = Readonly<{
|
|
|
102
110
|
speak: (messageId: string) => void;
|
|
103
111
|
stopSpeaking: () => void;
|
|
104
112
|
|
|
113
|
+
connectVoice: () => void;
|
|
114
|
+
disconnectVoice: () => void;
|
|
115
|
+
muteVoice: () => void;
|
|
116
|
+
unmuteVoice: () => void;
|
|
117
|
+
|
|
105
118
|
submitFeedback: (feedback: SubmitFeedbackOptions) => void;
|
|
106
119
|
|
|
107
120
|
getModelContext: () => ModelContext;
|
|
@@ -111,6 +124,7 @@ export type ThreadRuntimeCore = Readonly<{
|
|
|
111
124
|
beginEdit: (messageId: string) => void;
|
|
112
125
|
|
|
113
126
|
speech: SpeechState | undefined;
|
|
127
|
+
voice: VoiceSessionState | undefined;
|
|
114
128
|
|
|
115
129
|
capabilities: Readonly<RuntimeCapabilities>;
|
|
116
130
|
isDisabled: boolean;
|
|
@@ -123,6 +137,9 @@ export type ThreadRuntimeCore = Readonly<{
|
|
|
123
137
|
|
|
124
138
|
subscribe: (callback: () => void) => Unsubscribe;
|
|
125
139
|
|
|
140
|
+
getVoiceVolume: () => number;
|
|
141
|
+
subscribeVoiceVolume: (callback: () => void) => Unsubscribe;
|
|
142
|
+
|
|
126
143
|
import(repository: ExportedMessageRepository): void;
|
|
127
144
|
export(): ExportedMessageRepository;
|
|
128
145
|
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
SpeechSynthesisAdapter,
|
|
6
6
|
DictationAdapter,
|
|
7
7
|
} from "../../adapters/speech";
|
|
8
|
+
import type { RealtimeVoiceAdapter } from "../../adapters/voice";
|
|
8
9
|
import type { FeedbackAdapter } from "../../adapters/feedback";
|
|
9
10
|
import type {
|
|
10
11
|
AddToolResultOptions,
|
|
@@ -90,6 +91,7 @@ type ExternalStoreAdapterBase<T> = {
|
|
|
90
91
|
attachments?: AttachmentAdapter | undefined;
|
|
91
92
|
speech?: SpeechSynthesisAdapter | undefined;
|
|
92
93
|
dictation?: DictationAdapter | undefined;
|
|
94
|
+
voice?: RealtimeVoiceAdapter | undefined;
|
|
93
95
|
feedback?: FeedbackAdapter | undefined;
|
|
94
96
|
/**
|
|
95
97
|
* @deprecated This API is still under active development and might change without notice.
|
|
@@ -63,6 +63,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
63
63
|
unstable_copy: false,
|
|
64
64
|
speech: false,
|
|
65
65
|
dictation: false,
|
|
66
|
+
voice: false,
|
|
66
67
|
attachments: false,
|
|
67
68
|
feedback: false,
|
|
68
69
|
queue: false,
|
|
@@ -78,7 +79,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
78
79
|
return this._store.isLoading ?? false;
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
|
|
82
|
+
protected override _getBaseMessages(): readonly ThreadMessage[] {
|
|
82
83
|
return this._messages;
|
|
83
84
|
}
|
|
84
85
|
|
|
@@ -137,6 +138,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
137
138
|
cancel: this._store.onCancel !== undefined,
|
|
138
139
|
speech: this._store.adapters?.speech !== undefined,
|
|
139
140
|
dictation: this._store.adapters?.dictation !== undefined,
|
|
141
|
+
voice: this._store.adapters?.voice !== undefined,
|
|
140
142
|
unstable_copy: this._store.unstable_capabilities?.copy !== false,
|
|
141
143
|
attachments: !!this._store.adapters?.attachments,
|
|
142
144
|
feedback: !!this._store.adapters?.feedback,
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
SpeechSynthesisAdapter,
|
|
6
6
|
DictationAdapter,
|
|
7
7
|
} from "../../adapters/speech";
|
|
8
|
+
import type { RealtimeVoiceAdapter } from "../../adapters/voice";
|
|
8
9
|
import type { SuggestionAdapter } from "../../adapters/suggestion";
|
|
9
10
|
import type { ChatModelAdapter } from "../../runtime/utils/chat-model-adapter";
|
|
10
11
|
|
|
@@ -16,6 +17,7 @@ export type LocalRuntimeOptionsBase = {
|
|
|
16
17
|
attachments?: AttachmentAdapter | undefined;
|
|
17
18
|
speech?: SpeechSynthesisAdapter | undefined;
|
|
18
19
|
dictation?: DictationAdapter | undefined;
|
|
20
|
+
voice?: RealtimeVoiceAdapter | undefined;
|
|
19
21
|
feedback?: FeedbackAdapter | undefined;
|
|
20
22
|
suggestion?: SuggestionAdapter | undefined;
|
|
21
23
|
};
|
|
@@ -45,6 +45,7 @@ export class LocalThreadRuntimeCore
|
|
|
45
45
|
unstable_copy: true,
|
|
46
46
|
speech: false,
|
|
47
47
|
dictation: false,
|
|
48
|
+
voice: false,
|
|
48
49
|
attachments: false,
|
|
49
50
|
feedback: false,
|
|
50
51
|
queue: false,
|
|
@@ -118,6 +119,12 @@ export class LocalThreadRuntimeCore
|
|
|
118
119
|
hasUpdates = true;
|
|
119
120
|
}
|
|
120
121
|
|
|
122
|
+
const canVoice = options.adapters?.voice !== undefined;
|
|
123
|
+
if (this.capabilities.voice !== canVoice) {
|
|
124
|
+
this.capabilities.voice = canVoice;
|
|
125
|
+
hasUpdates = true;
|
|
126
|
+
}
|
|
127
|
+
|
|
121
128
|
const canAttach = options.adapters?.attachments !== undefined;
|
|
122
129
|
if (this.capabilities.attachments !== canAttach) {
|
|
123
130
|
this.capabilities.attachments = canAttach;
|
|
@@ -34,7 +34,6 @@ export class ReadonlyThreadRuntimeCore
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
getBranches(messageId: string) {
|
|
37
|
-
// no branching in readonly threads
|
|
38
37
|
const idx = this._messages.findIndex((m) => m.id === messageId);
|
|
39
38
|
if (idx === -1) return [];
|
|
40
39
|
return [messageId];
|
|
@@ -56,9 +55,7 @@ export class ReadonlyThreadRuntimeCore
|
|
|
56
55
|
throw READONLY_THREAD_ERROR;
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
cancelRun(): void {
|
|
60
|
-
// noop - nothing to cancel
|
|
61
|
-
}
|
|
58
|
+
cancelRun(): void {}
|
|
62
59
|
|
|
63
60
|
addToolResult(): void {
|
|
64
61
|
throw READONLY_THREAD_ERROR;
|
|
@@ -72,8 +69,23 @@ export class ReadonlyThreadRuntimeCore
|
|
|
72
69
|
throw READONLY_THREAD_ERROR;
|
|
73
70
|
}
|
|
74
71
|
|
|
75
|
-
stopSpeaking(): void {
|
|
76
|
-
|
|
72
|
+
stopSpeaking(): void {}
|
|
73
|
+
|
|
74
|
+
connectVoice(): void {
|
|
75
|
+
throw READONLY_THREAD_ERROR;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
disconnectVoice(): void {}
|
|
79
|
+
|
|
80
|
+
getVoiceVolume = () => 0;
|
|
81
|
+
subscribeVoiceVolume = (): Unsubscribe => () => {};
|
|
82
|
+
|
|
83
|
+
muteVoice(): void {
|
|
84
|
+
throw READONLY_THREAD_ERROR;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
unmuteVoice(): void {
|
|
88
|
+
throw READONLY_THREAD_ERROR;
|
|
77
89
|
}
|
|
78
90
|
|
|
79
91
|
submitFeedback(): void {
|
|
@@ -179,6 +191,7 @@ export class ReadonlyThreadRuntimeCore
|
|
|
179
191
|
}
|
|
180
192
|
|
|
181
193
|
speech = undefined;
|
|
194
|
+
voice = undefined;
|
|
182
195
|
|
|
183
196
|
capabilities = {
|
|
184
197
|
switchToBranch: false,
|
|
@@ -189,6 +202,7 @@ export class ReadonlyThreadRuntimeCore
|
|
|
189
202
|
unstable_copy: false,
|
|
190
203
|
speech: false,
|
|
191
204
|
dictation: false,
|
|
205
|
+
voice: false,
|
|
192
206
|
attachments: false,
|
|
193
207
|
feedback: false,
|
|
194
208
|
queue: false,
|
|
@@ -48,6 +48,25 @@ export const EMPTY_THREAD_CORE: ThreadRuntimeCore = {
|
|
|
48
48
|
throw EMPTY_THREAD_ERROR;
|
|
49
49
|
},
|
|
50
50
|
|
|
51
|
+
connectVoice() {
|
|
52
|
+
throw EMPTY_THREAD_ERROR;
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
disconnectVoice() {
|
|
56
|
+
throw EMPTY_THREAD_ERROR;
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
getVoiceVolume: () => 0,
|
|
60
|
+
subscribeVoiceVolume: () => () => {},
|
|
61
|
+
|
|
62
|
+
muteVoice() {
|
|
63
|
+
throw EMPTY_THREAD_ERROR;
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
unmuteVoice() {
|
|
67
|
+
throw EMPTY_THREAD_ERROR;
|
|
68
|
+
},
|
|
69
|
+
|
|
51
70
|
submitFeedback() {
|
|
52
71
|
throw EMPTY_THREAD_ERROR;
|
|
53
72
|
},
|
|
@@ -72,7 +91,7 @@ export const EMPTY_THREAD_CORE: ThreadRuntimeCore = {
|
|
|
72
91
|
attachments: [],
|
|
73
92
|
attachmentAccept: "*",
|
|
74
93
|
|
|
75
|
-
async addAttachment(
|
|
94
|
+
async addAttachment() {
|
|
76
95
|
throw EMPTY_THREAD_ERROR;
|
|
77
96
|
},
|
|
78
97
|
|
|
@@ -153,6 +172,7 @@ export const EMPTY_THREAD_CORE: ThreadRuntimeCore = {
|
|
|
153
172
|
},
|
|
154
173
|
|
|
155
174
|
speech: undefined,
|
|
175
|
+
voice: undefined,
|
|
156
176
|
|
|
157
177
|
capabilities: {
|
|
158
178
|
switchToBranch: false,
|
|
@@ -163,13 +183,14 @@ export const EMPTY_THREAD_CORE: ThreadRuntimeCore = {
|
|
|
163
183
|
unstable_copy: false,
|
|
164
184
|
speech: false,
|
|
165
185
|
dictation: false,
|
|
186
|
+
voice: false,
|
|
166
187
|
attachments: false,
|
|
167
188
|
feedback: false,
|
|
168
189
|
queue: false,
|
|
169
190
|
},
|
|
170
191
|
|
|
171
192
|
isDisabled: false,
|
|
172
|
-
isLoading:
|
|
193
|
+
isLoading: true,
|
|
173
194
|
|
|
174
195
|
messages: [],
|
|
175
196
|
|
|
@@ -39,6 +39,22 @@ export type RemoteThreadListOptions = {
|
|
|
39
39
|
runtimeHook: () => AssistantRuntime;
|
|
40
40
|
adapter: RemoteThreadListAdapter;
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* When provided, the runtime starts on this thread instead of creating a
|
|
44
|
+
* new empty thread. Useful for URL-based routing (e.g. `/chat/[threadId]`)
|
|
45
|
+
* where the initial thread is known at mount time.
|
|
46
|
+
*
|
|
47
|
+
* @deprecated Use `threadId` instead, which also reacts to subsequent changes.
|
|
48
|
+
*/
|
|
49
|
+
initialThreadId?: string | undefined;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The current thread ID to display. When this value changes, the runtime
|
|
53
|
+
* automatically switches to the specified thread. Set to `undefined` to
|
|
54
|
+
* switch to a new thread.
|
|
55
|
+
*/
|
|
56
|
+
threadId?: string | undefined;
|
|
57
|
+
|
|
42
58
|
/**
|
|
43
59
|
* When true, if this runtime is used inside another RemoteThreadListRuntime,
|
|
44
60
|
* it becomes a no-op and simply calls the runtimeHook directly.
|
|
@@ -85,6 +85,7 @@ export const ThreadListClient = resource(
|
|
|
85
85
|
switchToNewThread: async () => {
|
|
86
86
|
await runtime.switchToNewThread();
|
|
87
87
|
},
|
|
88
|
+
getLoadThreadsPromise: () => runtime.getLoadThreadsPromise(),
|
|
88
89
|
__internal_getAssistantRuntime: () => __internal_assistantRuntime,
|
|
89
90
|
};
|
|
90
91
|
},
|
|
@@ -44,11 +44,9 @@ export const ThreadClient = resource(
|
|
|
44
44
|
const runtimeState = tapSubscribable(runtime);
|
|
45
45
|
const emit = tapAssistantEmit();
|
|
46
46
|
|
|
47
|
-
// Bind thread events to event manager
|
|
48
47
|
tapEffect(() => {
|
|
49
48
|
const unsubscribers: Unsubscribe[] = [];
|
|
50
49
|
|
|
51
|
-
// Subscribe to thread events
|
|
52
50
|
const threadEvents: ThreadRuntimeEventType[] = [
|
|
53
51
|
"runStart",
|
|
54
52
|
"runEnd",
|
|
@@ -105,6 +103,7 @@ export const ThreadClient = resource(
|
|
|
105
103
|
suggestions: runtimeState.suggestions,
|
|
106
104
|
extras: runtimeState.extras,
|
|
107
105
|
speech: runtimeState.speech,
|
|
106
|
+
voice: runtimeState.voice,
|
|
108
107
|
|
|
109
108
|
composer: composer.state,
|
|
110
109
|
messages: messages.state,
|
|
@@ -124,12 +123,12 @@ export const ThreadClient = resource(
|
|
|
124
123
|
import: runtime.import,
|
|
125
124
|
reset: runtime.reset,
|
|
126
125
|
stopSpeaking: runtime.stopSpeaking,
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
126
|
+
connectVoice: runtime.connectVoice,
|
|
127
|
+
disconnectVoice: runtime.disconnectVoice,
|
|
128
|
+
getVoiceVolume: runtime.getVoiceVolume,
|
|
129
|
+
subscribeVoiceVolume: runtime.subscribeVoiceVolume,
|
|
130
|
+
muteVoice: runtime.muteVoice,
|
|
131
|
+
unmuteVoice: runtime.unmuteVoice,
|
|
133
132
|
message: (selector) => {
|
|
134
133
|
if ("id" in selector) {
|
|
135
134
|
return messages.get({ key: selector.id });
|
|
@@ -2,9 +2,11 @@ import type { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
|
2
2
|
import type {
|
|
3
3
|
RuntimeCapabilities,
|
|
4
4
|
SpeechState,
|
|
5
|
+
VoiceSessionState,
|
|
5
6
|
ThreadSuggestion,
|
|
6
7
|
} from "../../runtime/interfaces/thread-runtime-core";
|
|
7
8
|
import type { ExportedMessageRepository } from "../../runtime/utils/message-repository";
|
|
9
|
+
import type { Unsubscribe } from "../../types/unsubscribe";
|
|
8
10
|
import type { ThreadMessageLike } from "../../runtime/utils/thread-message-like";
|
|
9
11
|
import type {
|
|
10
12
|
CreateAppendMessage,
|
|
@@ -56,6 +58,7 @@ export type ThreadState = {
|
|
|
56
58
|
readonly extras: unknown;
|
|
57
59
|
/** @deprecated This API is still under active development and might change without notice. */
|
|
58
60
|
readonly speech: SpeechState | undefined;
|
|
61
|
+
readonly voice: VoiceSessionState | undefined;
|
|
59
62
|
readonly composer: ComposerState;
|
|
60
63
|
};
|
|
61
64
|
|
|
@@ -112,14 +115,12 @@ export type ThreadMethods = {
|
|
|
112
115
|
message(selector: { id: string } | { index: number }): MessageMethods;
|
|
113
116
|
/** @deprecated This API is still under active development and might change without notice. */
|
|
114
117
|
stopSpeaking(): void;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
*/
|
|
122
|
-
stopVoice(): Promise<void>;
|
|
118
|
+
connectVoice(): void;
|
|
119
|
+
disconnectVoice(): void;
|
|
120
|
+
getVoiceVolume(): number;
|
|
121
|
+
subscribeVoiceVolume(callback: () => void): Unsubscribe;
|
|
122
|
+
muteVoice(): void;
|
|
123
|
+
unmuteVoice(): void;
|
|
123
124
|
__internal_getRuntime?(): ThreadRuntime;
|
|
124
125
|
};
|
|
125
126
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { EMPTY_THREAD_CORE } from "../runtimes/remote-thread-list/empty-thread-core";
|
|
3
|
+
|
|
4
|
+
describe("EMPTY_THREAD_CORE", () => {
|
|
5
|
+
it("has isLoading=true so it is not mistaken for an empty conversation", () => {
|
|
6
|
+
expect(EMPTY_THREAD_CORE.isLoading).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("has empty messages", () => {
|
|
10
|
+
expect(EMPTY_THREAD_CORE.messages).toEqual([]);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("does not trigger isEmpty (messages.length === 0 && !isLoading)", () => {
|
|
14
|
+
const isEmpty =
|
|
15
|
+
EMPTY_THREAD_CORE.messages.length === 0 && !EMPTY_THREAD_CORE.isLoading;
|
|
16
|
+
expect(isEmpty).toBe(false);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("all capabilities are disabled", () => {
|
|
20
|
+
const caps = EMPTY_THREAD_CORE.capabilities;
|
|
21
|
+
expect(caps.edit).toBe(false);
|
|
22
|
+
expect(caps.reload).toBe(false);
|
|
23
|
+
expect(caps.cancel).toBe(false);
|
|
24
|
+
expect(caps.speech).toBe(false);
|
|
25
|
+
expect(caps.attachments).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("mutating methods throw", () => {
|
|
29
|
+
expect(() => EMPTY_THREAD_CORE.append({} as any)).toThrow();
|
|
30
|
+
expect(() => EMPTY_THREAD_CORE.startRun({} as any)).toThrow();
|
|
31
|
+
expect(() => EMPTY_THREAD_CORE.cancelRun()).toThrow();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { OptimisticState } from "../runtimes/remote-thread-list/optimistic-state";
|
|
3
|
+
import {
|
|
4
|
+
type RemoteThreadState,
|
|
5
|
+
type RemoteThreadData,
|
|
6
|
+
type THREAD_MAPPING_ID,
|
|
7
|
+
createThreadMappingId,
|
|
8
|
+
} from "../runtimes/remote-thread-list/remote-thread-state";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Tests for the isLoading lifecycle of RemoteThreadListThreadListRuntimeCore.
|
|
12
|
+
*
|
|
13
|
+
* The initial isLoading must be `true` so consumers can distinguish
|
|
14
|
+
* "not yet loaded" from "loaded with zero threads".
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
type ListResult = {
|
|
18
|
+
threads: {
|
|
19
|
+
remoteId: string;
|
|
20
|
+
status: "regular" | "archived";
|
|
21
|
+
title?: string;
|
|
22
|
+
externalId?: string | undefined;
|
|
23
|
+
}[];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const INITIAL_STATE: RemoteThreadState = {
|
|
27
|
+
isLoading: true,
|
|
28
|
+
newThreadId: undefined,
|
|
29
|
+
threadIds: [],
|
|
30
|
+
archivedThreadIds: [],
|
|
31
|
+
threadIdMap: {},
|
|
32
|
+
threadData: {},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const applyListResult = (
|
|
36
|
+
state: RemoteThreadState,
|
|
37
|
+
l: ListResult,
|
|
38
|
+
): RemoteThreadState => {
|
|
39
|
+
const newThreadIds: string[] = [];
|
|
40
|
+
const newArchivedThreadIds: string[] = [];
|
|
41
|
+
const newThreadIdMap = {} as Record<string, THREAD_MAPPING_ID>;
|
|
42
|
+
const newThreadData = {} as Record<THREAD_MAPPING_ID, RemoteThreadData>;
|
|
43
|
+
|
|
44
|
+
for (const thread of l.threads) {
|
|
45
|
+
if (thread.status === "regular") newThreadIds.push(thread.remoteId);
|
|
46
|
+
else newArchivedThreadIds.push(thread.remoteId);
|
|
47
|
+
|
|
48
|
+
const mappingId = createThreadMappingId(thread.remoteId);
|
|
49
|
+
newThreadIdMap[thread.remoteId] = mappingId;
|
|
50
|
+
newThreadData[mappingId] = {
|
|
51
|
+
id: thread.remoteId,
|
|
52
|
+
remoteId: thread.remoteId,
|
|
53
|
+
externalId: thread.externalId,
|
|
54
|
+
status: thread.status,
|
|
55
|
+
title: thread.title,
|
|
56
|
+
initializeTask: Promise.resolve({
|
|
57
|
+
remoteId: thread.remoteId,
|
|
58
|
+
externalId: thread.externalId,
|
|
59
|
+
}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
...state,
|
|
65
|
+
isLoading: false,
|
|
66
|
+
threadIds: newThreadIds,
|
|
67
|
+
archivedThreadIds: newArchivedThreadIds,
|
|
68
|
+
threadIdMap: { ...state.threadIdMap, ...newThreadIdMap },
|
|
69
|
+
threadData: { ...state.threadData, ...newThreadData },
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
function deferred<T>() {
|
|
74
|
+
let resolve!: (v: T) => void;
|
|
75
|
+
let reject!: (e: unknown) => void;
|
|
76
|
+
const promise = new Promise<T>((res, rej) => {
|
|
77
|
+
resolve = res;
|
|
78
|
+
reject = rej;
|
|
79
|
+
});
|
|
80
|
+
return { promise, resolve, reject };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
describe("RemoteThreadList isLoading lifecycle", () => {
|
|
84
|
+
it("starts as true before any loading begins", () => {
|
|
85
|
+
const state = new OptimisticState<RemoteThreadState>(INITIAL_STATE);
|
|
86
|
+
expect(state.value.isLoading).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("remains true while loading is in progress", () => {
|
|
90
|
+
const state = new OptimisticState<RemoteThreadState>(INITIAL_STATE);
|
|
91
|
+
const d = deferred<ListResult>();
|
|
92
|
+
|
|
93
|
+
state.optimisticUpdate({
|
|
94
|
+
execute: () => d.promise,
|
|
95
|
+
loading: (s) => ({ ...s, isLoading: true }),
|
|
96
|
+
then: applyListResult,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(state.value.isLoading).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("becomes false after loading completes with empty list", async () => {
|
|
103
|
+
const state = new OptimisticState<RemoteThreadState>(INITIAL_STATE);
|
|
104
|
+
const d = deferred<ListResult>();
|
|
105
|
+
|
|
106
|
+
const promise = state.optimisticUpdate({
|
|
107
|
+
execute: () => d.promise,
|
|
108
|
+
loading: (s) => ({ ...s, isLoading: true }),
|
|
109
|
+
then: applyListResult,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
d.resolve({ threads: [] });
|
|
113
|
+
await promise;
|
|
114
|
+
|
|
115
|
+
expect(state.value.isLoading).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("becomes false after loading completes with threads", async () => {
|
|
119
|
+
const state = new OptimisticState<RemoteThreadState>(INITIAL_STATE);
|
|
120
|
+
const d = deferred<ListResult>();
|
|
121
|
+
|
|
122
|
+
const promise = state.optimisticUpdate({
|
|
123
|
+
execute: () => d.promise,
|
|
124
|
+
loading: (s) => ({ ...s, isLoading: true }),
|
|
125
|
+
then: applyListResult,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
d.resolve({
|
|
129
|
+
threads: [
|
|
130
|
+
{ remoteId: "t-1", status: "regular", title: "Thread 1" },
|
|
131
|
+
{ remoteId: "t-2", status: "archived", title: "Thread 2" },
|
|
132
|
+
],
|
|
133
|
+
});
|
|
134
|
+
await promise;
|
|
135
|
+
|
|
136
|
+
expect(state.value.isLoading).toBe(false);
|
|
137
|
+
expect(state.value.threadIds).toEqual(["t-1"]);
|
|
138
|
+
expect(state.value.archivedThreadIds).toEqual(["t-2"]);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("initial true and final false are distinguishable", async () => {
|
|
142
|
+
const state = new OptimisticState<RemoteThreadState>(INITIAL_STATE);
|
|
143
|
+
const before = state.value.isLoading;
|
|
144
|
+
|
|
145
|
+
const d = deferred<ListResult>();
|
|
146
|
+
const promise = state.optimisticUpdate({
|
|
147
|
+
execute: () => d.promise,
|
|
148
|
+
loading: (s) => ({ ...s, isLoading: true }),
|
|
149
|
+
then: applyListResult,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
d.resolve({ threads: [] });
|
|
153
|
+
await promise;
|
|
154
|
+
|
|
155
|
+
const after = state.value.isLoading;
|
|
156
|
+
expect(before).toBe(true);
|
|
157
|
+
expect(after).toBe(false);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("RemoteThreadList isLoading error path", () => {
|
|
162
|
+
it("isLoading resets to false after adapter.list() rejects", async () => {
|
|
163
|
+
const state = new OptimisticState<RemoteThreadState>(INITIAL_STATE);
|
|
164
|
+
const d = deferred<ListResult>();
|
|
165
|
+
|
|
166
|
+
// Simulate the getLoadThreadsPromise pattern with .catch() fix
|
|
167
|
+
let loadPromise: Promise<void> | undefined;
|
|
168
|
+
loadPromise = state
|
|
169
|
+
.optimisticUpdate({
|
|
170
|
+
execute: () => d.promise,
|
|
171
|
+
loading: (s) => ({ ...s, isLoading: true }),
|
|
172
|
+
then: applyListResult,
|
|
173
|
+
})
|
|
174
|
+
.catch(() => {
|
|
175
|
+
loadPromise = undefined;
|
|
176
|
+
state.update({ ...state.baseValue, isLoading: false });
|
|
177
|
+
})
|
|
178
|
+
.then(() => {});
|
|
179
|
+
|
|
180
|
+
// While loading, isLoading is true
|
|
181
|
+
expect(state.value.isLoading).toBe(true);
|
|
182
|
+
|
|
183
|
+
d.reject(new Error("network error"));
|
|
184
|
+
await loadPromise;
|
|
185
|
+
|
|
186
|
+
// After failure, isLoading resets to false
|
|
187
|
+
expect(state.value.isLoading).toBe(false);
|
|
188
|
+
|
|
189
|
+
// Cache is cleared, allowing retry
|
|
190
|
+
expect(loadPromise).toBeUndefined();
|
|
191
|
+
});
|
|
192
|
+
});
|