@assistant-ui/core 0.2.12 → 0.2.14
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/thread-history.d.ts +3 -1
- package/dist/adapters/thread-history.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js.map +1 -1
- package/dist/react/AssistantProvider.js +6 -1
- package/dist/react/AssistantProvider.js.map +1 -1
- package/dist/react/RuntimeAdapter.d.ts +1 -1
- package/dist/react/RuntimeAdapter.d.ts.map +1 -1
- package/dist/react/RuntimeAdapter.js +16 -6
- package/dist/react/RuntimeAdapter.js.map +1 -1
- package/dist/react/client/DataRenderers.d.ts +1 -8
- package/dist/react/client/DataRenderers.d.ts.map +1 -1
- package/dist/react/client/DataRenderers.js +3 -2
- package/dist/react/client/DataRenderers.js.map +1 -1
- package/dist/react/client/Interactables.d.ts +1 -1
- package/dist/react/client/Interactables.d.ts.map +1 -1
- package/dist/react/client/Interactables.js +4 -3
- package/dist/react/client/Interactables.js.map +1 -1
- package/dist/react/client/Tools.d.ts +2 -13
- package/dist/react/client/Tools.d.ts.map +1 -1
- package/dist/react/client/Tools.js +4 -3
- package/dist/react/client/Tools.js.map +1 -1
- package/dist/react/primitives/message/MessageGroupedParts.d.ts +3 -2
- package/dist/react/primitives/message/MessageGroupedParts.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageGroupedParts.js +4 -4
- package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
- package/dist/react/primitives/message/MessageParts.d.ts +28 -1
- package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageParts.js +43 -9
- package/dist/react/primitives/message/MessageParts.js.map +1 -1
- package/dist/react/providers/TextMessagePartProvider.d.ts.map +1 -1
- package/dist/react/providers/TextMessagePartProvider.js +3 -2
- package/dist/react/providers/TextMessagePartProvider.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +2 -0
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +2 -0
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +1 -0
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +6 -0
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +2 -0
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
- package/dist/react/utils/groupParts.d.ts +13 -1
- package/dist/react/utils/groupParts.d.ts.map +1 -1
- package/dist/react/utils/groupParts.js +17 -5
- package/dist/react/utils/groupParts.js.map +1 -1
- package/dist/runtime/api/bindings.d.ts +1 -0
- package/dist/runtime/api/bindings.d.ts.map +1 -1
- package/dist/runtime/api/message-runtime.d.ts +2 -0
- package/dist/runtime/api/message-runtime.d.ts.map +1 -1
- package/dist/runtime/api/message-runtime.js +5 -0
- package/dist/runtime/api/message-runtime.js.map +1 -1
- package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-list-runtime.js +1 -0
- package/dist/runtime/api/thread-list-runtime.js.map +1 -1
- package/dist/runtime/api/thread-runtime.d.ts +3 -0
- package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-runtime.js +4 -0
- package/dist/runtime/api/thread-runtime.js.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.d.ts +1 -0
- package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
- package/dist/runtime/branch/external-thread-branch-adapter.d.ts +30 -0
- package/dist/runtime/branch/external-thread-branch-adapter.d.ts.map +1 -0
- package/dist/runtime/branch/external-thread-branch-adapter.js +0 -0
- package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +1 -0
- package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
- package/dist/runtime/interfaces/thread-runtime-core.d.ts +2 -0
- package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-adapter.d.ts +1 -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 +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 +13 -0
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/local/local-runtime-options.d.ts +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts +8 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +63 -5
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/local/should-continue.js +4 -2
- package/dist/runtimes/local/should-continue.js.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +2 -0
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +4 -0
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
- package/dist/runtimes/remote-thread-list/empty-thread-core.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/empty-thread-core.js +4 -0
- package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
- package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +1 -0
- package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/remote-thread-state.js +1 -0
- package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
- package/dist/runtimes/remote-thread-list/types.d.ts +1 -0
- package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
- package/dist/store/clients/chain-of-thought-client.d.ts +2 -7
- package/dist/store/clients/chain-of-thought-client.d.ts.map +1 -1
- package/dist/store/clients/chain-of-thought-client.js +3 -2
- package/dist/store/clients/chain-of-thought-client.js.map +1 -1
- package/dist/store/clients/model-context-client.d.ts +1 -1
- package/dist/store/clients/model-context-client.d.ts.map +1 -1
- package/dist/store/clients/model-context-client.js +3 -2
- package/dist/store/clients/model-context-client.js.map +1 -1
- package/dist/store/clients/no-op-composer-client.d.ts +2 -4
- package/dist/store/clients/no-op-composer-client.d.ts.map +1 -1
- package/dist/store/clients/no-op-composer-client.js +3 -2
- package/dist/store/clients/no-op-composer-client.js.map +1 -1
- package/dist/store/clients/runtime-adapter.d.ts +1 -3
- package/dist/store/clients/runtime-adapter.d.ts.map +1 -1
- package/dist/store/clients/runtime-adapter.js +2 -15
- package/dist/store/clients/runtime-adapter.js.map +1 -1
- package/dist/store/clients/suggestions.d.ts +1 -4
- package/dist/store/clients/suggestions.d.ts.map +1 -1
- package/dist/store/clients/suggestions.js +6 -4
- package/dist/store/clients/suggestions.js.map +1 -1
- package/dist/store/clients/thread-message-client.d.ts +1 -1
- package/dist/store/clients/thread-message-client.d.ts.map +1 -1
- package/dist/store/clients/thread-message-client.js +14 -10
- package/dist/store/clients/thread-message-client.js.map +1 -1
- package/dist/store/internal.d.ts +2 -2
- package/dist/store/internal.js +2 -2
- package/dist/store/runtime-clients/attachment-runtime-client.d.ts +2 -4
- package/dist/store/runtime-clients/attachment-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/attachment-runtime-client.js +3 -2
- package/dist/store/runtime-clients/attachment-runtime-client.js.map +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.d.ts +2 -10
- package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.js +9 -6
- package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
- package/dist/store/runtime-clients/message-part-runtime-client.d.ts +2 -4
- package/dist/store/runtime-clients/message-part-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/message-part-runtime-client.js +3 -2
- package/dist/store/runtime-clients/message-part-runtime-client.js.map +1 -1
- package/dist/store/runtime-clients/message-runtime-client.d.ts +2 -7
- package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/message-runtime-client.js +10 -6
- package/dist/store/runtime-clients/message-runtime-client.js.map +1 -1
- package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts +2 -4
- 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 +3 -2
- 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 +2 -5
- package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-list-runtime-client.js +6 -4
- package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
- package/dist/store/runtime-clients/thread-runtime-client.d.ts +2 -4
- package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-runtime-client.js +7 -4
- package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
- package/dist/store/scopes/message.d.ts +1 -0
- package/dist/store/scopes/message.d.ts.map +1 -1
- package/dist/store/scopes/thread-list-item.d.ts +1 -0
- package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
- package/dist/store/scopes/thread.d.ts +1 -0
- package/dist/store/scopes/thread.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/adapters/thread-history.ts +2 -0
- package/src/index.ts +1 -0
- package/src/react/AssistantProvider.tsx +3 -1
- package/src/react/RuntimeAdapter.ts +25 -8
- package/src/react/client/DataRenderers.ts +42 -45
- package/src/react/client/Interactables.ts +261 -261
- package/src/react/client/Tools.ts +6 -4
- package/src/react/primitives/message/MessageGroupedParts.tsx +19 -7
- package/src/react/primitives/message/MessageParts.tsx +64 -13
- package/src/react/providers/TextMessagePartProvider.tsx +5 -3
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +1 -0
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +11 -0
- package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +6 -0
- package/src/react/utils/groupParts.ts +27 -0
- package/src/runtime/api/bindings.ts +1 -0
- package/src/runtime/api/message-runtime.ts +7 -0
- package/src/runtime/api/thread-list-runtime.ts +1 -0
- package/src/runtime/api/thread-runtime.ts +7 -0
- package/src/runtime/base/base-thread-runtime-core.ts +1 -0
- package/src/runtime/branch/external-thread-branch-adapter.ts +26 -0
- package/src/runtime/interfaces/thread-list-runtime-core.ts +1 -0
- package/src/runtime/interfaces/thread-runtime-core.ts +2 -0
- package/src/runtimes/external-store/external-store-adapter.ts +1 -0
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +24 -0
- package/src/runtimes/local/local-runtime-options.ts +1 -1
- package/src/runtimes/local/local-thread-runtime-core.test.ts +311 -0
- package/src/runtimes/local/local-thread-runtime-core.ts +104 -7
- package/src/runtimes/local/should-continue.ts +23 -13
- package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +5 -0
- package/src/runtimes/remote-thread-list/empty-thread-core.ts +5 -0
- package/src/runtimes/remote-thread-list/remote-thread-state.ts +2 -0
- package/src/runtimes/remote-thread-list/types.ts +1 -0
- package/src/store/clients/chain-of-thought-client.ts +5 -3
- package/src/store/clients/model-context-client.test.ts +5 -4
- package/src/store/clients/model-context-client.ts +21 -21
- package/src/store/clients/no-op-composer-client.ts +5 -3
- package/src/store/clients/runtime-adapter.ts +0 -24
- package/src/store/clients/suggestions.ts +9 -18
- package/src/store/clients/thread-message-client.ts +29 -26
- package/src/store/internal.ts +1 -4
- package/src/store/runtime-clients/attachment-runtime-client.ts +14 -14
- package/src/store/runtime-clients/composer-runtime-client.ts +30 -24
- package/src/store/runtime-clients/message-part-runtime-client.ts +5 -3
- package/src/store/runtime-clients/message-runtime-client.ts +26 -19
- package/src/store/runtime-clients/thread-list-item-runtime-client.ts +5 -3
- package/src/store/runtime-clients/thread-list-runtime-client.ts +10 -6
- package/src/store/runtime-clients/thread-runtime-client.ts +11 -6
- package/src/store/scopes/message.ts +1 -0
- package/src/store/scopes/thread-list-item.ts +1 -0
- package/src/store/scopes/thread.ts +1 -0
- package/src/tests/external-store-thread-runtime-core.test.ts +57 -0
- package/src/tests/groupMessageParts.test.ts +84 -0
- package/src/tests/groupParts.test.ts +55 -0
|
@@ -18,201 +18,183 @@ import { buildInteractableModelContext } from "./interactable-model-context";
|
|
|
18
18
|
|
|
19
19
|
const PERSISTENCE_DEBOUNCE_MS = 500;
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
const useInteractables = (): ClientOutput<"interactables"> => {
|
|
22
|
+
const [state, setState] = useState<InteractablesState>(() => ({
|
|
23
|
+
definitions: {},
|
|
24
|
+
persistence: {},
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
const clientRef = useAssistantClientRef();
|
|
28
|
+
|
|
29
|
+
const stateRef = useRef(state);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
stateRef.current = state;
|
|
32
|
+
}, [state]);
|
|
27
33
|
|
|
28
|
-
|
|
34
|
+
const subscribersRef = useRef(new Set<() => void>());
|
|
35
|
+
const partialSchemaCacheRef = useRef(
|
|
36
|
+
new Map<string, InteractableStateSchema>(),
|
|
37
|
+
);
|
|
38
|
+
const detachedStateRef = useRef(new Map<string, unknown>());
|
|
29
39
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
40
|
+
const adapterRef = useRef<InteractablePersistenceAdapter | undefined>(
|
|
41
|
+
undefined,
|
|
42
|
+
);
|
|
43
|
+
const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(
|
|
44
|
+
undefined,
|
|
45
|
+
);
|
|
46
|
+
const syncSeqRef = useRef(0);
|
|
47
|
+
const hasPendingLocalChangeRef = useRef(false);
|
|
48
|
+
const flushResolversRef = useRef<Array<() => void>>([]);
|
|
49
|
+
const dirtyIdsRef = useRef(new Set<string>());
|
|
34
50
|
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
51
|
+
const runPersistence = useCallback(async () => {
|
|
52
|
+
const adapter = adapterRef.current;
|
|
53
|
+
if (!adapter) {
|
|
54
|
+
for (const resolve of flushResolversRef.current) resolve();
|
|
55
|
+
flushResolversRef.current = [];
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
40
58
|
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
undefined,
|
|
46
|
-
);
|
|
47
|
-
const syncSeqRef = useRef(0);
|
|
48
|
-
const hasPendingLocalChangeRef = useRef(false);
|
|
49
|
-
const flushResolversRef = useRef<Array<() => void>>([]);
|
|
50
|
-
const dirtyIdsRef = useRef(new Set<string>());
|
|
59
|
+
const seq = ++syncSeqRef.current;
|
|
60
|
+
const dirtyIds = new Set(dirtyIdsRef.current);
|
|
61
|
+
dirtyIdsRef.current.clear();
|
|
62
|
+
hasPendingLocalChangeRef.current = true;
|
|
51
63
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
64
|
+
// Snapshot before any await so unregistered definitions are still included.
|
|
65
|
+
const exported = stateRef.current.definitions;
|
|
66
|
+
const payload: InteractablePersistedState = {};
|
|
67
|
+
for (const [id, def] of Object.entries(exported)) {
|
|
68
|
+
payload[id] = { name: def.name, state: def.state };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
setState((prev) => ({
|
|
72
|
+
...prev,
|
|
73
|
+
persistence: {
|
|
74
|
+
...prev.persistence,
|
|
75
|
+
...Object.fromEntries(
|
|
76
|
+
[...dirtyIds].map((id) => [
|
|
77
|
+
id,
|
|
78
|
+
{ isPending: true, error: undefined },
|
|
79
|
+
]),
|
|
80
|
+
),
|
|
81
|
+
},
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
await adapter.save(payload);
|
|
86
|
+
if (syncSeqRef.current === seq) {
|
|
87
|
+
hasPendingLocalChangeRef.current = false;
|
|
88
|
+
setState((prev) => {
|
|
89
|
+
const persistence = { ...prev.persistence };
|
|
90
|
+
for (const id of dirtyIds) delete persistence[id];
|
|
91
|
+
return { ...prev, persistence };
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
} catch (e) {
|
|
95
|
+
if (syncSeqRef.current === seq) {
|
|
96
|
+
hasPendingLocalChangeRef.current = false;
|
|
97
|
+
setState((prev) => ({
|
|
98
|
+
...prev,
|
|
99
|
+
persistence: {
|
|
100
|
+
...prev.persistence,
|
|
101
|
+
...Object.fromEntries(
|
|
102
|
+
[...dirtyIds].map((id) => [id, { isPending: false, error: e }]),
|
|
103
|
+
),
|
|
104
|
+
},
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
} finally {
|
|
108
|
+
if (dirtyIdsRef.current.size > 0 && adapterRef.current) {
|
|
109
|
+
runPersistence();
|
|
110
|
+
} else {
|
|
55
111
|
for (const resolve of flushResolversRef.current) resolve();
|
|
56
112
|
flushResolversRef.current = [];
|
|
57
|
-
return;
|
|
58
113
|
}
|
|
114
|
+
}
|
|
115
|
+
}, []);
|
|
59
116
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const exported = stateRef.current.definitions;
|
|
67
|
-
const payload: InteractablePersistedState = {};
|
|
68
|
-
for (const [id, def] of Object.entries(exported)) {
|
|
69
|
-
payload[id] = { name: def.name, state: def.state };
|
|
117
|
+
const schedulePersistence = useCallback(
|
|
118
|
+
(id: string) => {
|
|
119
|
+
if (!adapterRef.current) return;
|
|
120
|
+
dirtyIdsRef.current.add(id);
|
|
121
|
+
if (debounceTimerRef.current !== undefined) {
|
|
122
|
+
clearTimeout(debounceTimerRef.current);
|
|
70
123
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
persistence: {
|
|
75
|
-
...prev.persistence,
|
|
76
|
-
...Object.fromEntries(
|
|
77
|
-
[...dirtyIds].map((id) => [
|
|
78
|
-
id,
|
|
79
|
-
{ isPending: true, error: undefined },
|
|
80
|
-
]),
|
|
81
|
-
),
|
|
82
|
-
},
|
|
83
|
-
}));
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
await adapter.save(payload);
|
|
87
|
-
if (syncSeqRef.current === seq) {
|
|
88
|
-
hasPendingLocalChangeRef.current = false;
|
|
89
|
-
setState((prev) => {
|
|
90
|
-
const persistence = { ...prev.persistence };
|
|
91
|
-
for (const id of dirtyIds) delete persistence[id];
|
|
92
|
-
return { ...prev, persistence };
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
} catch (e) {
|
|
96
|
-
if (syncSeqRef.current === seq) {
|
|
97
|
-
hasPendingLocalChangeRef.current = false;
|
|
98
|
-
setState((prev) => ({
|
|
99
|
-
...prev,
|
|
100
|
-
persistence: {
|
|
101
|
-
...prev.persistence,
|
|
102
|
-
...Object.fromEntries(
|
|
103
|
-
[...dirtyIds].map((id) => [id, { isPending: false, error: e }]),
|
|
104
|
-
),
|
|
105
|
-
},
|
|
106
|
-
}));
|
|
107
|
-
}
|
|
108
|
-
} finally {
|
|
109
|
-
if (dirtyIdsRef.current.size > 0 && adapterRef.current) {
|
|
124
|
+
debounceTimerRef.current = setTimeout(() => {
|
|
125
|
+
debounceTimerRef.current = undefined;
|
|
126
|
+
if (!hasPendingLocalChangeRef.current) {
|
|
110
127
|
runPersistence();
|
|
111
128
|
} else {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}, []);
|
|
117
|
-
|
|
118
|
-
const schedulePersistence = useCallback(
|
|
119
|
-
(id: string) => {
|
|
120
|
-
if (!adapterRef.current) return;
|
|
121
|
-
dirtyIdsRef.current.add(id);
|
|
122
|
-
if (debounceTimerRef.current !== undefined) {
|
|
123
|
-
clearTimeout(debounceTimerRef.current);
|
|
124
|
-
}
|
|
125
|
-
debounceTimerRef.current = setTimeout(() => {
|
|
126
|
-
debounceTimerRef.current = undefined;
|
|
127
|
-
if (!hasPendingLocalChangeRef.current) {
|
|
129
|
+
debounceTimerRef.current = setTimeout(() => {
|
|
130
|
+
debounceTimerRef.current = undefined;
|
|
128
131
|
runPersistence();
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}, PERSISTENCE_DEBOUNCE_MS);
|
|
136
|
-
},
|
|
137
|
-
[runPersistence],
|
|
138
|
-
);
|
|
132
|
+
}, PERSISTENCE_DEBOUNCE_MS);
|
|
133
|
+
}
|
|
134
|
+
}, PERSISTENCE_DEBOUNCE_MS);
|
|
135
|
+
},
|
|
136
|
+
[runPersistence],
|
|
137
|
+
);
|
|
139
138
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
139
|
+
const exportState = useCallback((): InteractablePersistedState => {
|
|
140
|
+
const result: InteractablePersistedState = {};
|
|
141
|
+
for (const [id, def] of Object.entries(stateRef.current.definitions)) {
|
|
142
|
+
result[id] = { name: def.name, state: def.state };
|
|
143
|
+
}
|
|
144
|
+
return result;
|
|
145
|
+
}, []);
|
|
147
146
|
|
|
148
|
-
|
|
147
|
+
const importState = useCallback((saved: InteractablePersistedState) => {
|
|
148
|
+
for (const [id, entry] of Object.entries(saved)) {
|
|
149
|
+
detachedStateRef.current.set(id, entry.state);
|
|
150
|
+
}
|
|
151
|
+
setState((prev) => {
|
|
152
|
+
let changed = false;
|
|
153
|
+
const definitions = { ...prev.definitions };
|
|
149
154
|
for (const [id, entry] of Object.entries(saved)) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
let changed = false;
|
|
154
|
-
const definitions = { ...prev.definitions };
|
|
155
|
-
for (const [id, entry] of Object.entries(saved)) {
|
|
156
|
-
if (definitions[id]) {
|
|
157
|
-
definitions[id] = { ...definitions[id], state: entry.state };
|
|
158
|
-
changed = true;
|
|
159
|
-
}
|
|
155
|
+
if (definitions[id]) {
|
|
156
|
+
definitions[id] = { ...definitions[id], state: entry.state };
|
|
157
|
+
changed = true;
|
|
160
158
|
}
|
|
161
|
-
return changed ? { ...prev, definitions } : prev;
|
|
162
|
-
});
|
|
163
|
-
}, []);
|
|
164
|
-
|
|
165
|
-
const setPersistenceAdapter = useCallback(
|
|
166
|
-
(adapter: InteractablePersistenceAdapter | undefined) => {
|
|
167
|
-
adapterRef.current = adapter;
|
|
168
|
-
},
|
|
169
|
-
[],
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
const flush = useCallback(async () => {
|
|
173
|
-
if (debounceTimerRef.current !== undefined) {
|
|
174
|
-
clearTimeout(debounceTimerRef.current);
|
|
175
|
-
debounceTimerRef.current = undefined;
|
|
176
159
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const p = new Promise<void>((resolve) => {
|
|
181
|
-
flushResolversRef.current.push(resolve);
|
|
182
|
-
});
|
|
183
|
-
if (!hasPendingLocalChangeRef.current) {
|
|
184
|
-
runPersistence();
|
|
185
|
-
}
|
|
186
|
-
return p;
|
|
187
|
-
}, [runPersistence]);
|
|
160
|
+
return changed ? { ...prev, definitions } : prev;
|
|
161
|
+
});
|
|
162
|
+
}, []);
|
|
188
163
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}, [runPersistence]);
|
|
164
|
+
const setPersistenceAdapter = useCallback(
|
|
165
|
+
(adapter: InteractablePersistenceAdapter | undefined) => {
|
|
166
|
+
adapterRef.current = adapter;
|
|
167
|
+
},
|
|
168
|
+
[],
|
|
169
|
+
);
|
|
196
170
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
171
|
+
const flush = useCallback(async () => {
|
|
172
|
+
if (debounceTimerRef.current !== undefined) {
|
|
173
|
+
clearTimeout(debounceTimerRef.current);
|
|
174
|
+
debounceTimerRef.current = undefined;
|
|
175
|
+
}
|
|
176
|
+
if (!adapterRef.current) return;
|
|
177
|
+
if (!hasPendingLocalChangeRef.current && dirtyIdsRef.current.size === 0)
|
|
178
|
+
return;
|
|
179
|
+
const p = new Promise<void>((resolve) => {
|
|
180
|
+
flushResolversRef.current.push(resolve);
|
|
181
|
+
});
|
|
182
|
+
if (!hasPendingLocalChangeRef.current) {
|
|
183
|
+
runPersistence();
|
|
184
|
+
}
|
|
185
|
+
return p;
|
|
186
|
+
}, [runPersistence]);
|
|
187
|
+
|
|
188
|
+
const flushIfPending = useCallback(() => {
|
|
189
|
+
if (adapterRef.current && debounceTimerRef.current !== undefined) {
|
|
190
|
+
clearTimeout(debounceTimerRef.current);
|
|
191
|
+
debounceTimerRef.current = undefined;
|
|
192
|
+
runPersistence();
|
|
193
|
+
}
|
|
194
|
+
}, [runPersistence]);
|
|
214
195
|
|
|
215
|
-
|
|
196
|
+
const setDefState = useCallback(
|
|
197
|
+
(id: string, updater: (prev: unknown) => unknown) => {
|
|
216
198
|
setState((prev) => {
|
|
217
199
|
const existing = prev.definitions[id];
|
|
218
200
|
if (!existing) return prev;
|
|
@@ -220,107 +202,125 @@ export const Interactables = resource(
|
|
|
220
202
|
...prev,
|
|
221
203
|
definitions: {
|
|
222
204
|
...prev.definitions,
|
|
223
|
-
[id]: { ...existing,
|
|
205
|
+
[id]: { ...existing, state: updater(existing.state) },
|
|
224
206
|
},
|
|
225
207
|
};
|
|
226
208
|
});
|
|
227
|
-
|
|
209
|
+
if (stateRef.current.definitions[id]) schedulePersistence(id);
|
|
210
|
+
},
|
|
211
|
+
[schedulePersistence],
|
|
212
|
+
);
|
|
228
213
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
) ?? {}
|
|
239
|
-
);
|
|
240
|
-
},
|
|
241
|
-
subscribe: (callback: () => void) => {
|
|
242
|
-
subscribersRef.current.add(callback);
|
|
243
|
-
return () => {
|
|
244
|
-
subscribersRef.current.delete(callback);
|
|
245
|
-
};
|
|
214
|
+
const setDefSelected = useCallback((id: string, selected: boolean) => {
|
|
215
|
+
setState((prev) => {
|
|
216
|
+
const existing = prev.definitions[id];
|
|
217
|
+
if (!existing) return prev;
|
|
218
|
+
return {
|
|
219
|
+
...prev,
|
|
220
|
+
definitions: {
|
|
221
|
+
...prev.definitions,
|
|
222
|
+
[id]: { ...existing, selected },
|
|
246
223
|
},
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
224
|
+
};
|
|
225
|
+
});
|
|
226
|
+
}, []);
|
|
227
|
+
|
|
228
|
+
const provider = useMemo(
|
|
229
|
+
() => ({
|
|
230
|
+
getModelContext: () => {
|
|
231
|
+
const defs = stateRef.current.definitions;
|
|
232
|
+
return (
|
|
233
|
+
buildInteractableModelContext(
|
|
234
|
+
defs,
|
|
235
|
+
partialSchemaCacheRef.current,
|
|
236
|
+
setDefState,
|
|
237
|
+
) ?? {}
|
|
238
|
+
);
|
|
239
|
+
},
|
|
240
|
+
subscribe: (callback: () => void) => {
|
|
241
|
+
subscribersRef.current.add(callback);
|
|
242
|
+
return () => {
|
|
243
|
+
subscribersRef.current.delete(callback);
|
|
244
|
+
};
|
|
245
|
+
},
|
|
246
|
+
}),
|
|
247
|
+
[setDefState],
|
|
248
|
+
);
|
|
250
249
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
250
|
+
useEffect(() => {
|
|
251
|
+
for (const cb of subscribersRef.current) cb();
|
|
252
|
+
}, [state]);
|
|
254
253
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
254
|
+
useEffect(() => {
|
|
255
|
+
return clientRef.current!.modelContext().register(provider);
|
|
256
|
+
}, [clientRef, provider]);
|
|
258
257
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
258
|
+
const register = useCallback(
|
|
259
|
+
(def: InteractableRegistration) => {
|
|
260
|
+
try {
|
|
261
|
+
const jsonSchema = toJSONSchema(def.stateSchema);
|
|
262
|
+
partialSchemaCacheRef.current.set(
|
|
263
|
+
def.id,
|
|
264
|
+
toPartialJSONSchema(jsonSchema),
|
|
265
|
+
);
|
|
266
|
+
} catch (e) {
|
|
267
|
+
console.warn(
|
|
268
|
+
`[Interactables] Failed to create partial schema for "${def.name}". The update tool will require all fields.`,
|
|
269
|
+
e,
|
|
270
|
+
);
|
|
271
|
+
}
|
|
273
272
|
|
|
274
|
-
|
|
275
|
-
|
|
273
|
+
const detached = detachedStateRef.current.get(def.id);
|
|
274
|
+
detachedStateRef.current.delete(def.id);
|
|
276
275
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
},
|
|
276
|
+
setState((prev) => ({
|
|
277
|
+
...prev,
|
|
278
|
+
definitions: {
|
|
279
|
+
...prev.definitions,
|
|
280
|
+
[def.id]: {
|
|
281
|
+
id: def.id,
|
|
282
|
+
name: def.name,
|
|
283
|
+
description: def.description,
|
|
284
|
+
stateSchema: def.stateSchema,
|
|
285
|
+
state:
|
|
286
|
+
prev.definitions[def.id]?.state ?? detached ?? def.initialState,
|
|
287
|
+
selected: def.selected,
|
|
290
288
|
},
|
|
291
|
-
}
|
|
289
|
+
},
|
|
290
|
+
}));
|
|
292
291
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
292
|
+
return () => {
|
|
293
|
+
flushIfPending();
|
|
294
|
+
setState((prev) => {
|
|
295
|
+
const existing = prev.definitions[def.id];
|
|
296
|
+
if (existing) {
|
|
297
|
+
detachedStateRef.current.set(def.id, existing.state);
|
|
298
|
+
}
|
|
299
|
+
partialSchemaCacheRef.current.delete(def.id);
|
|
300
|
+
const { [def.id]: _, ...rest } = prev.definitions;
|
|
301
|
+
const { [def.id]: __, ...restPersistence } = prev.persistence;
|
|
302
|
+
return { ...prev, definitions: rest, persistence: restPersistence };
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
},
|
|
306
|
+
[flushIfPending],
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
getState: () => state,
|
|
311
|
+
register,
|
|
312
|
+
setState: setDefState,
|
|
313
|
+
setSelected: setDefSelected,
|
|
314
|
+
exportState,
|
|
315
|
+
importState,
|
|
316
|
+
setPersistenceAdapter,
|
|
317
|
+
flush,
|
|
318
|
+
};
|
|
319
|
+
};
|
|
309
320
|
|
|
310
|
-
|
|
311
|
-
getState: () => state,
|
|
312
|
-
register,
|
|
313
|
-
setState: setDefState,
|
|
314
|
-
setSelected: setDefSelected,
|
|
315
|
-
exportState,
|
|
316
|
-
importState,
|
|
317
|
-
setPersistenceAdapter,
|
|
318
|
-
flush,
|
|
319
|
-
};
|
|
320
|
-
},
|
|
321
|
-
);
|
|
321
|
+
export const Interactables = resource(useInteractables);
|
|
322
322
|
|
|
323
|
-
attachTransformScopes(
|
|
323
|
+
attachTransformScopes(useInteractables, (scopes, parent) => {
|
|
324
324
|
if (!scopes.modelContext && parent.modelContext.source === null) {
|
|
325
325
|
scopes.modelContext = ModelContext();
|
|
326
326
|
}
|
|
@@ -30,7 +30,7 @@ export type { McpAppResourceOutput };
|
|
|
30
30
|
* context, while each tool renderer is registered with the tools scope for
|
|
31
31
|
* message rendering.
|
|
32
32
|
*/
|
|
33
|
-
|
|
33
|
+
const useTools = ({
|
|
34
34
|
toolkit,
|
|
35
35
|
mcpApp,
|
|
36
36
|
}: {
|
|
@@ -38,7 +38,7 @@ export const Tools = resource(function Tools({
|
|
|
38
38
|
toolkit?: Toolkit;
|
|
39
39
|
/** Optional MCP app resource whose tools should be merged into context. */
|
|
40
40
|
mcpApp?: ResourceElement<McpAppResourceOutput> | undefined;
|
|
41
|
-
}): ClientOutput<"tools"> {
|
|
41
|
+
}): ClientOutput<"tools"> => {
|
|
42
42
|
const mcpAppOutputs = useResources(
|
|
43
43
|
() => (mcpApp ? [withKey("mcpApp", mcpApp)] : []),
|
|
44
44
|
[mcpApp],
|
|
@@ -155,9 +155,11 @@ export const Tools = resource(function Tools({
|
|
|
155
155
|
getState: () => state,
|
|
156
156
|
setToolUI,
|
|
157
157
|
};
|
|
158
|
-
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export const Tools = resource(useTools);
|
|
159
161
|
|
|
160
|
-
attachTransformScopes(
|
|
162
|
+
attachTransformScopes(useTools, (scopes, parent) => {
|
|
161
163
|
if (!scopes.modelContext && parent.modelContext.source === null) {
|
|
162
164
|
scopes.modelContext = ModelContext();
|
|
163
165
|
}
|
|
@@ -49,8 +49,9 @@ export namespace MessagePrimitiveGroupedParts {
|
|
|
49
49
|
* which running states qualify:
|
|
50
50
|
* - `"never"` — never.
|
|
51
51
|
* - `"empty"` — only when the message has no parts yet.
|
|
52
|
-
* - `"no-text"` (default) — when the
|
|
53
|
-
* (e.g. it ended on a tool call, so the
|
|
52
|
+
* - `"no-text"` (default) — when the message has no parts yet or the last
|
|
53
|
+
* part isn't `text`/`reasoning` (e.g. it ended on a tool call, so the
|
|
54
|
+
* assistant likely isn't done).
|
|
54
55
|
* - `"always"` — whenever the message is running, regardless of parts.
|
|
55
56
|
*/
|
|
56
57
|
export type IndicatorMode = "never" | "empty" | "no-text" | "always";
|
|
@@ -153,7 +154,8 @@ const shouldShowIndicator = (
|
|
|
153
154
|
case "no-text": {
|
|
154
155
|
const last = parts[parts.length - 1];
|
|
155
156
|
return (
|
|
156
|
-
last
|
|
157
|
+
last === undefined ||
|
|
158
|
+
(last.type !== "text" && last.type !== "reasoning")
|
|
157
159
|
);
|
|
158
160
|
}
|
|
159
161
|
}
|
|
@@ -179,9 +181,14 @@ const renderNode = <TKey extends `group-${string}`>(
|
|
|
179
181
|
render: (info: MessagePrimitiveGroupedParts.RenderInfo<TKey>) => ReactNode,
|
|
180
182
|
): ReactNode => {
|
|
181
183
|
if (node.type === "part") {
|
|
182
|
-
// Key by
|
|
184
|
+
// Key by part identity when available, else absolute part index — never
|
|
185
|
+
// the structural nodeKey, which leaves zombie fiber subscriptions when
|
|
186
|
+
// parts reshape (#4051).
|
|
183
187
|
return (
|
|
184
|
-
<MessagePartChildren
|
|
188
|
+
<MessagePartChildren
|
|
189
|
+
key={node.idKey ? `part-${node.idKey}` : `part-${node.index}`}
|
|
190
|
+
index={node.index}
|
|
191
|
+
>
|
|
185
192
|
{({ part }) => render({ part, children: <PartChildrenSentinel /> })}
|
|
186
193
|
</MessagePartChildren>
|
|
187
194
|
);
|
|
@@ -195,7 +202,7 @@ const renderNode = <TKey extends `group-${string}`>(
|
|
|
195
202
|
};
|
|
196
203
|
|
|
197
204
|
return (
|
|
198
|
-
<Fragment key={node.nodeKey}>
|
|
205
|
+
<Fragment key={node.idKey ?? node.nodeKey}>
|
|
199
206
|
{render({
|
|
200
207
|
part: groupPart,
|
|
201
208
|
children: (
|
|
@@ -264,7 +271,12 @@ export const MessagePrimitiveGroupedParts = <TKey extends `group-${string}`>({
|
|
|
264
271
|
const memoDep = memoKey ?? groupBy;
|
|
265
272
|
const tree = useMemo(() => {
|
|
266
273
|
const context: GroupByContext = { toolUIs };
|
|
267
|
-
return buildGroupTree(
|
|
274
|
+
return buildGroupTree(
|
|
275
|
+
parts.map((part) => groupBy(part, context) ?? []),
|
|
276
|
+
parts.map((part) =>
|
|
277
|
+
part.type === "tool-call" ? part.toolCallId : undefined,
|
|
278
|
+
),
|
|
279
|
+
);
|
|
268
280
|
// oxlint-disable-next-line react/exhaustive-deps -- groupBy is captured via memoDep (either its identity or the helper's memoKey fingerprint); listing it directly would defeat the helper-tagged memo path
|
|
269
281
|
}, [parts, memoDep, toolUIs]);
|
|
270
282
|
|