@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
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { useAuiState } from "@assistant-ui/store";
|
|
3
|
+
import {
|
|
4
|
+
getPartialJsonObjectFieldState,
|
|
5
|
+
getPartialJsonObjectMeta,
|
|
6
|
+
} from "assistant-stream/utils";
|
|
7
|
+
|
|
8
|
+
type PropFieldStatus = "streaming" | "complete";
|
|
9
|
+
|
|
10
|
+
export type ToolArgsStatus<
|
|
11
|
+
TArgs extends Record<string, unknown> = Record<string, unknown>,
|
|
12
|
+
> = {
|
|
13
|
+
status: "running" | "complete" | "incomplete" | "requires-action";
|
|
14
|
+
propStatus: Partial<Record<keyof TArgs, PropFieldStatus>>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const useToolArgsStatus = <
|
|
18
|
+
TArgs extends Record<string, unknown> = Record<string, unknown>,
|
|
19
|
+
>(): ToolArgsStatus<TArgs> => {
|
|
20
|
+
const part = useAuiState((s) => s.part);
|
|
21
|
+
|
|
22
|
+
return useMemo(() => {
|
|
23
|
+
const statusType = part.status.type;
|
|
24
|
+
|
|
25
|
+
if (part.type !== "tool-call") {
|
|
26
|
+
throw new Error(
|
|
27
|
+
"useToolArgsStatus can only be used inside tool-call message parts",
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const isStreaming = statusType === "running";
|
|
32
|
+
const args = part.args as Record<string, unknown>;
|
|
33
|
+
const meta = getPartialJsonObjectMeta(args as Record<symbol, unknown>);
|
|
34
|
+
const propStatus: Partial<Record<string, PropFieldStatus>> = {};
|
|
35
|
+
|
|
36
|
+
for (const key of Object.keys(args)) {
|
|
37
|
+
if (meta) {
|
|
38
|
+
const fieldState = getPartialJsonObjectFieldState(args, [key]);
|
|
39
|
+
propStatus[key] =
|
|
40
|
+
fieldState === "complete" || !isStreaming ? "complete" : "streaming";
|
|
41
|
+
} else {
|
|
42
|
+
propStatus[key] = isStreaming ? "streaming" : "complete";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
status: statusType,
|
|
48
|
+
propStatus: propStatus as Partial<Record<keyof TArgs, PropFieldStatus>>,
|
|
49
|
+
};
|
|
50
|
+
}, [part]);
|
|
51
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useCallback, useSyncExternalStore } from "react";
|
|
2
|
+
import { useAui, useAuiState } from "@assistant-ui/store";
|
|
3
|
+
import type { VoiceSessionState } from "../../runtime/interfaces/thread-runtime-core";
|
|
4
|
+
|
|
5
|
+
export const useVoiceState = (): VoiceSessionState | undefined => {
|
|
6
|
+
return useAuiState((s) => s.thread.voice);
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const getServerVolume = () => 0;
|
|
10
|
+
|
|
11
|
+
export const useVoiceVolume = (): number => {
|
|
12
|
+
const aui = useAui();
|
|
13
|
+
const thread = aui.thread();
|
|
14
|
+
return useSyncExternalStore(
|
|
15
|
+
thread.subscribeVoiceVolume,
|
|
16
|
+
thread.getVoiceVolume,
|
|
17
|
+
getServerVolume,
|
|
18
|
+
);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const useVoiceControls = () => {
|
|
22
|
+
const aui = useAui();
|
|
23
|
+
|
|
24
|
+
const connect = useCallback(() => {
|
|
25
|
+
aui.thread().connectVoice();
|
|
26
|
+
}, [aui]);
|
|
27
|
+
|
|
28
|
+
const disconnect = useCallback(() => {
|
|
29
|
+
aui.thread().disconnectVoice();
|
|
30
|
+
}, [aui]);
|
|
31
|
+
|
|
32
|
+
const mute = useCallback(() => {
|
|
33
|
+
aui.thread().muteVoice();
|
|
34
|
+
}, [aui]);
|
|
35
|
+
|
|
36
|
+
const unmute = useCallback(() => {
|
|
37
|
+
aui.thread().unmuteVoice();
|
|
38
|
+
}, [aui]);
|
|
39
|
+
|
|
40
|
+
return { connect, disconnect, mute, unmute };
|
|
41
|
+
};
|
|
@@ -39,7 +39,7 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
39
39
|
|
|
40
40
|
private _mainThreadId!: string;
|
|
41
41
|
private readonly _state = new OptimisticState<RemoteThreadState>({
|
|
42
|
-
isLoading:
|
|
42
|
+
isLoading: true,
|
|
43
43
|
newThreadId: undefined,
|
|
44
44
|
threadIds: [],
|
|
45
45
|
archivedThreadIds: [],
|
|
@@ -103,6 +103,7 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
103
103
|
|
|
104
104
|
return {
|
|
105
105
|
...state,
|
|
106
|
+
isLoading: false,
|
|
106
107
|
threadIds: newThreadIds,
|
|
107
108
|
archivedThreadIds: newArchivedThreadIds,
|
|
108
109
|
threadIdMap: {
|
|
@@ -116,6 +117,13 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
116
117
|
};
|
|
117
118
|
},
|
|
118
119
|
})
|
|
120
|
+
.catch(() => {
|
|
121
|
+
this._loadThreadsPromise = undefined;
|
|
122
|
+
this._state.update({
|
|
123
|
+
...this._state.baseValue,
|
|
124
|
+
isLoading: false,
|
|
125
|
+
});
|
|
126
|
+
})
|
|
119
127
|
.then(() => {});
|
|
120
128
|
}
|
|
121
129
|
|
|
@@ -141,7 +149,12 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
141
149
|
Fragment) as ComponentType<PropsWithChildren>,
|
|
142
150
|
}));
|
|
143
151
|
this.__internal_setOptions(options);
|
|
144
|
-
|
|
152
|
+
const startThreadId = options.threadId ?? options.initialThreadId;
|
|
153
|
+
if (startThreadId) {
|
|
154
|
+
this.switchToThread(startThreadId);
|
|
155
|
+
} else {
|
|
156
|
+
this.switchToNewThread();
|
|
157
|
+
}
|
|
145
158
|
}
|
|
146
159
|
|
|
147
160
|
private useProvider;
|
|
@@ -34,6 +34,7 @@ const useRemoteThreadListRuntimeImpl = (
|
|
|
34
34
|
runtime.threads.__internal_setOptions(options);
|
|
35
35
|
runtime.threads.__internal_load();
|
|
36
36
|
}, [runtime, options]);
|
|
37
|
+
|
|
37
38
|
return useMemo(() => new AssistantRuntimeImpl(runtime), [runtime]);
|
|
38
39
|
};
|
|
39
40
|
|
|
@@ -43,6 +44,9 @@ export const useRemoteThreadListRuntime = (
|
|
|
43
44
|
const runtimeHookRef = useRef(options.runtimeHook);
|
|
44
45
|
runtimeHookRef.current = options.runtimeHook;
|
|
45
46
|
|
|
47
|
+
// threadId/initialThreadId only affect the constructor; capture once via ref
|
|
48
|
+
const startThreadIdRef = useRef(options.threadId ?? options.initialThreadId);
|
|
49
|
+
|
|
46
50
|
const stableRuntimeHook = useCallback(() => {
|
|
47
51
|
return runtimeHookRef.current();
|
|
48
52
|
}, []);
|
|
@@ -51,6 +55,7 @@ export const useRemoteThreadListRuntime = (
|
|
|
51
55
|
() => ({
|
|
52
56
|
adapter: options.adapter,
|
|
53
57
|
allowNesting: options.allowNesting,
|
|
58
|
+
initialThreadId: startThreadIdRef.current,
|
|
54
59
|
runtimeHook: stableRuntimeHook,
|
|
55
60
|
}),
|
|
56
61
|
[options.adapter, options.allowNesting, stableRuntimeHook],
|
|
@@ -72,5 +77,18 @@ export const useRemoteThreadListRuntime = (
|
|
|
72
77
|
return stableRuntimeHook();
|
|
73
78
|
}
|
|
74
79
|
|
|
75
|
-
|
|
80
|
+
const runtime = useRemoteThreadListRuntimeImpl(stableOptions);
|
|
81
|
+
|
|
82
|
+
const prevThreadIdRef = useRef(options.threadId);
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (options.threadId === prevThreadIdRef.current) return;
|
|
85
|
+
prevThreadIdRef.current = options.threadId;
|
|
86
|
+
if (options.threadId) {
|
|
87
|
+
runtime.threads.switchToThread(options.threadId).catch(() => {});
|
|
88
|
+
} else {
|
|
89
|
+
runtime.threads.switchToNewThread().catch(() => {});
|
|
90
|
+
}
|
|
91
|
+
}, [runtime, options.threadId]);
|
|
92
|
+
|
|
93
|
+
return runtime;
|
|
76
94
|
};
|
|
@@ -360,6 +360,13 @@ export function useToolInvocations({
|
|
|
360
360
|
}
|
|
361
361
|
let lastState = lastToolStates.current[content.toolCallId];
|
|
362
362
|
if (!lastState) {
|
|
363
|
+
if (content.result !== undefined) {
|
|
364
|
+
if (content.messages) {
|
|
365
|
+
processMessages(content.messages);
|
|
366
|
+
}
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
363
370
|
toolCallIdAliasesRef.current.set(
|
|
364
371
|
content.toolCallId,
|
|
365
372
|
content.toolCallId,
|
|
@@ -567,6 +574,8 @@ export function useToolInvocations({
|
|
|
567
574
|
return {
|
|
568
575
|
reset: () => {
|
|
569
576
|
isInitialState.current = true;
|
|
577
|
+
ignoredToolIds.current.clear();
|
|
578
|
+
lastToolStates.current = {};
|
|
570
579
|
void abort().finally(() => {
|
|
571
580
|
startedExecutionToolCallIdsRef.current.clear();
|
|
572
581
|
toolCallIdAliasesRef.current.clear();
|
|
@@ -27,9 +27,25 @@ export type InteractableRegistration = {
|
|
|
27
27
|
selected?: boolean | undefined;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
export type InteractablePersistenceStatus = {
|
|
31
|
+
isPending: boolean;
|
|
32
|
+
error: unknown;
|
|
33
|
+
};
|
|
34
|
+
|
|
30
35
|
export type InteractablesState = {
|
|
31
36
|
/** Keyed by instance id */
|
|
32
37
|
definitions: Record<string, InteractableDefinition>;
|
|
38
|
+
/** Per-id persistence sync status */
|
|
39
|
+
persistence: Record<string, InteractablePersistenceStatus>;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type InteractablePersistedState = Record<
|
|
43
|
+
string,
|
|
44
|
+
{ name: string; state: unknown }
|
|
45
|
+
>;
|
|
46
|
+
|
|
47
|
+
export type InteractablePersistenceAdapter = {
|
|
48
|
+
save(state: InteractablePersistedState): void | Promise<void>;
|
|
33
49
|
};
|
|
34
50
|
|
|
35
51
|
export type InteractablesMethods = {
|
|
@@ -37,6 +53,12 @@ export type InteractablesMethods = {
|
|
|
37
53
|
register(def: InteractableRegistration): Unsubscribe;
|
|
38
54
|
setState(id: string, updater: (prev: unknown) => unknown): void;
|
|
39
55
|
setSelected(id: string, selected: boolean): void;
|
|
56
|
+
exportState(): InteractablePersistedState;
|
|
57
|
+
importState(saved: InteractablePersistedState): void;
|
|
58
|
+
setPersistenceAdapter(
|
|
59
|
+
adapter: InteractablePersistenceAdapter | undefined,
|
|
60
|
+
): void;
|
|
61
|
+
flush(): Promise<void>;
|
|
40
62
|
};
|
|
41
63
|
|
|
42
64
|
export type InteractablesClientSchema = {
|
|
@@ -46,6 +46,8 @@ export type ThreadListRuntime = {
|
|
|
46
46
|
|
|
47
47
|
switchToThread(threadId: string): Promise<void>;
|
|
48
48
|
switchToNewThread(): Promise<void>;
|
|
49
|
+
|
|
50
|
+
getLoadThreadsPromise(): Promise<void>;
|
|
49
51
|
};
|
|
50
52
|
|
|
51
53
|
const getThreadListState = (
|
|
@@ -130,6 +132,7 @@ export class ThreadListRuntimeImpl implements ThreadListRuntime {
|
|
|
130
132
|
protected __internal_bindMethods() {
|
|
131
133
|
this.switchToThread = this.switchToThread.bind(this);
|
|
132
134
|
this.switchToNewThread = this.switchToNewThread.bind(this);
|
|
135
|
+
this.getLoadThreadsPromise = this.getLoadThreadsPromise.bind(this);
|
|
133
136
|
this.getState = this.getState.bind(this);
|
|
134
137
|
this.subscribe = this.subscribe.bind(this);
|
|
135
138
|
this.getById = this.getById.bind(this);
|
|
@@ -146,6 +149,10 @@ export class ThreadListRuntimeImpl implements ThreadListRuntime {
|
|
|
146
149
|
return this._core.switchToNewThread();
|
|
147
150
|
}
|
|
148
151
|
|
|
152
|
+
public getLoadThreadsPromise(): Promise<void> {
|
|
153
|
+
return this._core.getLoadThreadsPromise();
|
|
154
|
+
}
|
|
155
|
+
|
|
149
156
|
public getState(): ThreadListState {
|
|
150
157
|
return this._getState();
|
|
151
158
|
}
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
RuntimeCapabilities,
|
|
4
4
|
ThreadRuntimeCore,
|
|
5
5
|
SpeechState,
|
|
6
|
+
VoiceSessionState,
|
|
6
7
|
ThreadRuntimeEventType,
|
|
7
8
|
StartRunConfig,
|
|
8
9
|
ResumeRunConfig,
|
|
@@ -187,6 +188,8 @@ export type ThreadState = {
|
|
|
187
188
|
* @deprecated This API is still under active development and might change without notice.
|
|
188
189
|
*/
|
|
189
190
|
readonly speech: SpeechState | undefined;
|
|
191
|
+
|
|
192
|
+
readonly voice: VoiceSessionState | undefined;
|
|
190
193
|
};
|
|
191
194
|
|
|
192
195
|
export const getThreadState = (
|
|
@@ -209,6 +212,7 @@ export const getThreadState = (
|
|
|
209
212
|
suggestions: runtime.suggestions,
|
|
210
213
|
extras: runtime.extras,
|
|
211
214
|
speech: runtime.speech,
|
|
215
|
+
voice: runtime.voice,
|
|
212
216
|
});
|
|
213
217
|
};
|
|
214
218
|
|
|
@@ -317,6 +321,13 @@ export type ThreadRuntime = {
|
|
|
317
321
|
*/
|
|
318
322
|
stopSpeaking(): void;
|
|
319
323
|
|
|
324
|
+
connectVoice(): void;
|
|
325
|
+
disconnectVoice(): void;
|
|
326
|
+
getVoiceVolume(): number;
|
|
327
|
+
subscribeVoiceVolume(callback: () => void): Unsubscribe;
|
|
328
|
+
muteVoice(): void;
|
|
329
|
+
unmuteVoice(): void;
|
|
330
|
+
|
|
320
331
|
unstable_on(event: ThreadRuntimeEventType, callback: () => void): Unsubscribe;
|
|
321
332
|
};
|
|
322
333
|
|
|
@@ -388,6 +399,12 @@ export class ThreadRuntimeImpl implements ThreadRuntime {
|
|
|
388
399
|
this.startRun = this.startRun.bind(this);
|
|
389
400
|
this.cancelRun = this.cancelRun.bind(this);
|
|
390
401
|
this.stopSpeaking = this.stopSpeaking.bind(this);
|
|
402
|
+
this.connectVoice = this.connectVoice.bind(this);
|
|
403
|
+
this.disconnectVoice = this.disconnectVoice.bind(this);
|
|
404
|
+
this.muteVoice = this.muteVoice.bind(this);
|
|
405
|
+
this.unmuteVoice = this.unmuteVoice.bind(this);
|
|
406
|
+
this.getVoiceVolume = this.getVoiceVolume.bind(this);
|
|
407
|
+
this.subscribeVoiceVolume = this.subscribeVoiceVolume.bind(this);
|
|
391
408
|
this.export = this.export.bind(this);
|
|
392
409
|
this.import = this.import.bind(this);
|
|
393
410
|
this.reset = this.reset.bind(this);
|
|
@@ -463,6 +480,30 @@ export class ThreadRuntimeImpl implements ThreadRuntime {
|
|
|
463
480
|
return this._threadBinding.getState().stopSpeaking();
|
|
464
481
|
}
|
|
465
482
|
|
|
483
|
+
public connectVoice() {
|
|
484
|
+
this._threadBinding.getState().connectVoice();
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
public disconnectVoice() {
|
|
488
|
+
this._threadBinding.getState().disconnectVoice();
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
public getVoiceVolume() {
|
|
492
|
+
return this._threadBinding.getState().getVoiceVolume();
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
public subscribeVoiceVolume(callback: () => void) {
|
|
496
|
+
return this._threadBinding.getState().subscribeVoiceVolume(callback);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
public muteVoice() {
|
|
500
|
+
this._threadBinding.getState().muteVoice();
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
public unmuteVoice() {
|
|
504
|
+
this._threadBinding.getState().unmuteVoice();
|
|
505
|
+
}
|
|
506
|
+
|
|
466
507
|
public export() {
|
|
467
508
|
return this._threadBinding.getState().export();
|
|
468
509
|
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
AppendMessage,
|
|
3
|
+
ThreadAssistantMessage,
|
|
4
|
+
ThreadMessage,
|
|
5
|
+
} from "../../types/message";
|
|
2
6
|
import type { Unsubscribe } from "../../types/unsubscribe";
|
|
3
7
|
import type { ModelContextProvider } from "../../model-context/types";
|
|
4
8
|
import { getThreadMessageText } from "../../utils/text";
|
|
9
|
+
import { generateId } from "../../utils/id";
|
|
5
10
|
import {
|
|
6
11
|
ExportedMessageRepository,
|
|
7
12
|
MessageRepository,
|
|
@@ -14,6 +19,7 @@ import type {
|
|
|
14
19
|
SubmitFeedbackOptions,
|
|
15
20
|
ThreadRuntimeCore,
|
|
16
21
|
SpeechState,
|
|
22
|
+
VoiceSessionState,
|
|
17
23
|
RuntimeCapabilities,
|
|
18
24
|
ThreadRuntimeEventType,
|
|
19
25
|
StartRunConfig,
|
|
@@ -23,12 +29,14 @@ import { DefaultEditComposerRuntimeCore } from "./default-edit-composer-runtime-
|
|
|
23
29
|
import type { SpeechSynthesisAdapter } from "../../adapters/speech";
|
|
24
30
|
import type { FeedbackAdapter } from "../../adapters/feedback";
|
|
25
31
|
import type { AttachmentAdapter } from "../../adapters/attachment";
|
|
32
|
+
import type { RealtimeVoiceAdapter } from "../../adapters/voice";
|
|
26
33
|
import type { ThreadMessageLike } from "../utils/thread-message-like";
|
|
27
34
|
|
|
28
35
|
type BaseThreadAdapters = {
|
|
29
36
|
speech?: SpeechSynthesisAdapter | undefined;
|
|
30
37
|
feedback?: FeedbackAdapter | undefined;
|
|
31
38
|
attachments?: AttachmentAdapter | undefined;
|
|
39
|
+
voice?: RealtimeVoiceAdapter | undefined;
|
|
32
40
|
};
|
|
33
41
|
|
|
34
42
|
export abstract class BaseThreadRuntimeCore implements ThreadRuntimeCore {
|
|
@@ -53,10 +61,37 @@ export abstract class BaseThreadRuntimeCore implements ThreadRuntimeCore {
|
|
|
53
61
|
public abstract importExternalState(state: any): void;
|
|
54
62
|
public abstract unstable_loadExternalState(state: any): void;
|
|
55
63
|
|
|
56
|
-
|
|
64
|
+
protected _voiceMessages: ThreadMessage[] = [];
|
|
65
|
+
protected _voiceGeneration = 0;
|
|
66
|
+
private _cachedMergedMessages: readonly ThreadMessage[] | null = null;
|
|
67
|
+
private _cachedVoiceGeneration = -1;
|
|
68
|
+
private _cachedMergedBase: readonly ThreadMessage[] | null = null;
|
|
69
|
+
|
|
70
|
+
protected _markVoiceMessagesDirty() {
|
|
71
|
+
this._voiceGeneration++;
|
|
72
|
+
this._cachedMergedMessages = null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
protected _getBaseMessages(): readonly ThreadMessage[] {
|
|
57
76
|
return this.repository.getMessages();
|
|
58
77
|
}
|
|
59
78
|
|
|
79
|
+
public get messages(): readonly ThreadMessage[] {
|
|
80
|
+
if (this._voiceMessages.length === 0) {
|
|
81
|
+
return this._getBaseMessages();
|
|
82
|
+
}
|
|
83
|
+
const base = this._getBaseMessages();
|
|
84
|
+
if (
|
|
85
|
+
this._cachedVoiceGeneration !== this._voiceGeneration ||
|
|
86
|
+
this._cachedMergedBase !== base
|
|
87
|
+
) {
|
|
88
|
+
this._cachedMergedMessages = [...base, ...this._voiceMessages];
|
|
89
|
+
this._cachedVoiceGeneration = this._voiceGeneration;
|
|
90
|
+
this._cachedMergedBase = base;
|
|
91
|
+
}
|
|
92
|
+
return this._cachedMergedMessages!;
|
|
93
|
+
}
|
|
94
|
+
|
|
60
95
|
public get state() {
|
|
61
96
|
let mostRecentAssistantMessage;
|
|
62
97
|
for (const message of this.messages) {
|
|
@@ -99,11 +134,28 @@ export abstract class BaseThreadRuntimeCore implements ThreadRuntimeCore {
|
|
|
99
134
|
try {
|
|
100
135
|
return this.repository.getMessage(messageId);
|
|
101
136
|
} catch {
|
|
137
|
+
// Check voice messages
|
|
138
|
+
const baseMessages = this.repository.getMessages();
|
|
139
|
+
const voiceIdx = this._voiceMessages.findIndex((m) => m.id === messageId);
|
|
140
|
+
if (voiceIdx !== -1) {
|
|
141
|
+
const parentId =
|
|
142
|
+
voiceIdx > 0
|
|
143
|
+
? this._voiceMessages[voiceIdx - 1]!.id
|
|
144
|
+
: (baseMessages.at(-1)?.id ?? null);
|
|
145
|
+
return {
|
|
146
|
+
parentId,
|
|
147
|
+
message: this._voiceMessages[voiceIdx]!,
|
|
148
|
+
index: baseMessages.length + voiceIdx,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
102
151
|
return undefined;
|
|
103
152
|
}
|
|
104
153
|
}
|
|
105
154
|
|
|
106
155
|
public getBranches(messageId: string): string[] {
|
|
156
|
+
if (this._voiceMessages.some((m) => m.id === messageId)) {
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
107
159
|
return this.repository.getBranches(messageId);
|
|
108
160
|
}
|
|
109
161
|
|
|
@@ -188,6 +240,195 @@ export abstract class BaseThreadRuntimeCore implements ThreadRuntimeCore {
|
|
|
188
240
|
this._notifySubscribers();
|
|
189
241
|
}
|
|
190
242
|
|
|
243
|
+
private _voiceSession: RealtimeVoiceAdapter.Session | undefined;
|
|
244
|
+
private _voiceUnsubs: Array<() => void> = [];
|
|
245
|
+
public voice: VoiceSessionState | undefined;
|
|
246
|
+
|
|
247
|
+
private _voiceVolume = 0;
|
|
248
|
+
private _voiceVolumeSubscribers = new Set<() => void>();
|
|
249
|
+
|
|
250
|
+
public getVoiceVolume = () => this._voiceVolume;
|
|
251
|
+
|
|
252
|
+
public subscribeVoiceVolume = (callback: () => void): Unsubscribe => {
|
|
253
|
+
this._voiceVolumeSubscribers.add(callback);
|
|
254
|
+
return () => this._voiceVolumeSubscribers.delete(callback);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
public connectVoice() {
|
|
258
|
+
const adapter = this.adapters?.voice;
|
|
259
|
+
if (!adapter) throw new Error("Voice adapter not configured");
|
|
260
|
+
|
|
261
|
+
this.disconnectVoice();
|
|
262
|
+
|
|
263
|
+
const session = adapter.connect({});
|
|
264
|
+
this._voiceSession = session;
|
|
265
|
+
const unsubs: Array<() => void> = [];
|
|
266
|
+
|
|
267
|
+
let currentMode: RealtimeVoiceAdapter.Mode = "listening";
|
|
268
|
+
|
|
269
|
+
this.voice = {
|
|
270
|
+
status: session.status,
|
|
271
|
+
isMuted: session.isMuted,
|
|
272
|
+
mode: currentMode,
|
|
273
|
+
};
|
|
274
|
+
this._voiceVolume = 0;
|
|
275
|
+
this._notifySubscribers();
|
|
276
|
+
|
|
277
|
+
unsubs.push(
|
|
278
|
+
session.onStatusChange((status) => {
|
|
279
|
+
if (status.type === "ended") {
|
|
280
|
+
this._finishVoiceAssistantMessage();
|
|
281
|
+
this._voiceSession = undefined;
|
|
282
|
+
this.voice = undefined;
|
|
283
|
+
} else {
|
|
284
|
+
this.voice = {
|
|
285
|
+
status,
|
|
286
|
+
isMuted: session.isMuted,
|
|
287
|
+
mode: currentMode,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
this._notifySubscribers();
|
|
291
|
+
}),
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
unsubs.push(
|
|
295
|
+
session.onModeChange((mode) => {
|
|
296
|
+
currentMode = mode;
|
|
297
|
+
if (this.voice) {
|
|
298
|
+
this.voice = { ...this.voice, mode };
|
|
299
|
+
this._notifySubscribers();
|
|
300
|
+
}
|
|
301
|
+
}),
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
unsubs.push(
|
|
305
|
+
session.onVolumeChange((volume) => {
|
|
306
|
+
this._voiceVolume = volume;
|
|
307
|
+
for (const cb of this._voiceVolumeSubscribers) cb();
|
|
308
|
+
}),
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
unsubs.push(
|
|
312
|
+
session.onTranscript((transcript) => {
|
|
313
|
+
this._handleVoiceTranscript(transcript);
|
|
314
|
+
}),
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
this._voiceUnsubs = unsubs;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private _currentAssistantMsg: ThreadAssistantMessage | null = null;
|
|
321
|
+
|
|
322
|
+
private _handleVoiceTranscript(
|
|
323
|
+
transcript: RealtimeVoiceAdapter.TranscriptItem,
|
|
324
|
+
) {
|
|
325
|
+
this.ensureInitialized();
|
|
326
|
+
|
|
327
|
+
if (transcript.role === "user") {
|
|
328
|
+
this._finishVoiceAssistantMessage();
|
|
329
|
+
this._currentAssistantMsg = null;
|
|
330
|
+
|
|
331
|
+
if (transcript.isFinal) {
|
|
332
|
+
this._voiceMessages.push({
|
|
333
|
+
id: generateId(),
|
|
334
|
+
role: "user",
|
|
335
|
+
content: [{ type: "text", text: transcript.text }],
|
|
336
|
+
metadata: { custom: {} },
|
|
337
|
+
createdAt: new Date(),
|
|
338
|
+
status: { type: "complete", reason: "unknown" },
|
|
339
|
+
attachments: [],
|
|
340
|
+
});
|
|
341
|
+
this._markVoiceMessagesDirty();
|
|
342
|
+
this._notifySubscribers();
|
|
343
|
+
}
|
|
344
|
+
} else {
|
|
345
|
+
if (!this._currentAssistantMsg) {
|
|
346
|
+
this._currentAssistantMsg = {
|
|
347
|
+
id: generateId(),
|
|
348
|
+
role: "assistant",
|
|
349
|
+
content: [{ type: "text", text: transcript.text }],
|
|
350
|
+
metadata: {
|
|
351
|
+
unstable_state: this.state,
|
|
352
|
+
unstable_annotations: [],
|
|
353
|
+
unstable_data: [],
|
|
354
|
+
steps: [],
|
|
355
|
+
custom: {},
|
|
356
|
+
},
|
|
357
|
+
status: { type: "running" },
|
|
358
|
+
createdAt: new Date(),
|
|
359
|
+
};
|
|
360
|
+
this._voiceMessages.push(this._currentAssistantMsg);
|
|
361
|
+
} else {
|
|
362
|
+
const idx = this._voiceMessages.indexOf(this._currentAssistantMsg);
|
|
363
|
+
if (idx === -1) return;
|
|
364
|
+
const updated: ThreadAssistantMessage = {
|
|
365
|
+
...this._currentAssistantMsg,
|
|
366
|
+
content: [{ type: "text", text: transcript.text }],
|
|
367
|
+
...(transcript.isFinal
|
|
368
|
+
? { status: { type: "complete", reason: "stop" } }
|
|
369
|
+
: {}),
|
|
370
|
+
};
|
|
371
|
+
this._voiceMessages[idx] = updated;
|
|
372
|
+
this._currentAssistantMsg = updated;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (transcript.isFinal) {
|
|
376
|
+
this._currentAssistantMsg = null;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
this._markVoiceMessagesDirty();
|
|
380
|
+
this._notifySubscribers();
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
private _finishVoiceAssistantMessage() {
|
|
385
|
+
const last = this._voiceMessages.at(-1);
|
|
386
|
+
if (last?.role === "assistant" && last.status.type === "running") {
|
|
387
|
+
const idx = this._voiceMessages.length - 1;
|
|
388
|
+
this._voiceMessages[idx] = {
|
|
389
|
+
...(last as ThreadAssistantMessage),
|
|
390
|
+
status: { type: "complete", reason: "stop" },
|
|
391
|
+
};
|
|
392
|
+
this._markVoiceMessagesDirty();
|
|
393
|
+
this._notifySubscribers();
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
public disconnectVoice() {
|
|
398
|
+
this._finishVoiceAssistantMessage();
|
|
399
|
+
this._currentAssistantMsg = null;
|
|
400
|
+
for (const unsub of this._voiceUnsubs) unsub();
|
|
401
|
+
this._voiceUnsubs = [];
|
|
402
|
+
this._voiceSession?.disconnect();
|
|
403
|
+
this._voiceSession = undefined;
|
|
404
|
+
this.voice = undefined;
|
|
405
|
+
this._voiceVolume = 0;
|
|
406
|
+
for (const cb of this._voiceVolumeSubscribers) cb();
|
|
407
|
+
this._voiceMessages = [];
|
|
408
|
+
this._markVoiceMessagesDirty();
|
|
409
|
+
this._notifySubscribers();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
public muteVoice() {
|
|
413
|
+
if (!this._voiceSession) throw new Error("No active voice session");
|
|
414
|
+
this._voiceSession.mute();
|
|
415
|
+
this.voice = {
|
|
416
|
+
...this.voice!,
|
|
417
|
+
isMuted: true,
|
|
418
|
+
};
|
|
419
|
+
this._notifySubscribers();
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
public unmuteVoice() {
|
|
423
|
+
if (!this._voiceSession) throw new Error("No active voice session");
|
|
424
|
+
this._voiceSession.unmute();
|
|
425
|
+
this.voice = {
|
|
426
|
+
...this.voice!,
|
|
427
|
+
isMuted: false,
|
|
428
|
+
};
|
|
429
|
+
this._notifySubscribers();
|
|
430
|
+
}
|
|
431
|
+
|
|
191
432
|
protected ensureInitialized() {
|
|
192
433
|
if (!this._isInitialized) {
|
|
193
434
|
this._isInitialized = true;
|