@assistant-ui/core 0.2.18 → 0.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +2 -1
- package/dist/internal.js +2 -1
- package/dist/model-context/interactable-composer-metadata.d.ts +105 -0
- package/dist/model-context/interactable-composer-metadata.d.ts.map +1 -0
- package/dist/model-context/interactable-composer-metadata.js +259 -0
- package/dist/model-context/interactable-composer-metadata.js.map +1 -0
- package/dist/model-context/registry.d.ts.map +1 -1
- package/dist/model-context/registry.js +1 -0
- package/dist/model-context/registry.js.map +1 -1
- package/dist/model-context/types.d.ts +6 -0
- package/dist/model-context/types.d.ts.map +1 -1
- package/dist/model-context/types.js +4 -0
- package/dist/model-context/types.js.map +1 -1
- package/dist/react/AssistantProvider.d.ts +1 -1
- package/dist/react/AssistantProvider.js +1 -1
- package/dist/react/AssistantRuntimeProvider.d.ts +1 -1
- package/dist/react/RuntimeAdapter.js +1 -1
- package/dist/react/client/Interactables.d.ts +8 -2
- package/dist/react/client/Interactables.d.ts.map +1 -1
- package/dist/react/client/Interactables.js +190 -94
- package/dist/react/client/Interactables.js.map +1 -1
- package/dist/react/client/Tools.js +1 -1
- package/dist/react/client/interactable-model-context.d.ts +9 -6
- package/dist/react/client/interactable-model-context.d.ts.map +1 -1
- package/dist/react/client/interactable-model-context.js +154 -32
- package/dist/react/client/interactable-model-context.js.map +1 -1
- package/dist/react/index.d.ts +15 -6
- package/dist/react/index.js +12 -5
- package/dist/react/interactables-legacy/Interactables.d.ts +12 -0
- package/dist/react/interactables-legacy/Interactables.d.ts.map +1 -0
- package/dist/react/interactables-legacy/Interactables.js +264 -0
- package/dist/react/interactables-legacy/Interactables.js.map +1 -0
- package/dist/react/interactables-legacy/interactable-model-context.d.ts +12 -0
- package/dist/react/interactables-legacy/interactable-model-context.d.ts.map +1 -0
- package/dist/react/interactables-legacy/interactable-model-context.js +54 -0
- package/dist/react/interactables-legacy/interactable-model-context.js.map +1 -0
- package/dist/react/interactables-legacy/scopes.d.ts +58 -0
- package/dist/react/interactables-legacy/scopes.d.ts.map +1 -0
- package/dist/react/interactables-legacy/scopes.js +0 -0
- package/dist/react/interactables-legacy/useAssistantInteractable.d.ts +31 -0
- package/dist/react/interactables-legacy/useAssistantInteractable.d.ts.map +1 -0
- package/dist/react/{model-context → interactables-legacy}/useAssistantInteractable.js +6 -2
- package/dist/react/interactables-legacy/useAssistantInteractable.js.map +1 -0
- package/dist/react/interactables-legacy/useInteractableState.d.ts +21 -0
- package/dist/react/interactables-legacy/useInteractableState.d.ts.map +1 -0
- package/dist/react/interactables-legacy/useInteractableState.js +84 -0
- package/dist/react/interactables-legacy/useInteractableState.js.map +1 -0
- package/dist/react/model-context/interactableTool.d.ts +53 -0
- package/dist/react/model-context/interactableTool.d.ts.map +1 -0
- package/dist/react/model-context/interactableTool.js +188 -0
- package/dist/react/model-context/interactableTool.js.map +1 -0
- package/dist/react/model-context/useAssistantContext.js +1 -1
- package/dist/react/model-context/useAssistantDataUI.js +1 -1
- package/dist/react/model-context/useAssistantInstructions.js +1 -1
- package/dist/react/model-context/useAssistantTool.js +1 -1
- package/dist/react/model-context/useAssistantToolUI.js +1 -1
- package/dist/react/model-context/useAuiToolOverrides.js +1 -1
- package/dist/react/model-context/useInteractable.d.ts +73 -0
- package/dist/react/model-context/useInteractable.d.ts.map +1 -0
- package/dist/react/model-context/useInteractable.js +100 -0
- package/dist/react/model-context/useInteractable.js.map +1 -0
- package/dist/react/model-context/useInteractableState.d.ts +8 -5
- package/dist/react/model-context/useInteractableState.d.ts.map +1 -1
- package/dist/react/model-context/useInteractableState.js +48 -48
- package/dist/react/model-context/useInteractableState.js.map +1 -1
- package/dist/react/model-context/useInteractableVersions.d.ts +19 -0
- package/dist/react/model-context/useInteractableVersions.d.ts.map +1 -0
- package/dist/react/model-context/useInteractableVersions.js +51 -0
- package/dist/react/model-context/useInteractableVersions.js.map +1 -0
- package/dist/react/primitive-hooks/useThreadMessageIds.d.ts +15 -0
- package/dist/react/primitive-hooks/useThreadMessageIds.d.ts.map +1 -0
- package/dist/react/primitive-hooks/useThreadMessageIds.js +26 -0
- package/dist/react/primitive-hooks/useThreadMessageIds.js.map +1 -0
- package/dist/react/primitive-hooks/useVoice.js +1 -1
- package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.js +1 -1
- package/dist/react/primitives/composer/ComposerAttachments.js +1 -1
- package/dist/react/primitives/composer/ComposerQueue.js +1 -1
- package/dist/react/primitives/generativeUI/GenerativeUI.d.ts.map +1 -1
- package/dist/react/primitives/generativeUI/GenerativeUI.js +2 -2
- package/dist/react/primitives/generativeUI/GenerativeUI.js.map +1 -1
- package/dist/react/primitives/message/MessageAttachments.js +1 -1
- package/dist/react/primitives/message/MessageGroupedParts.js +1 -1
- package/dist/react/primitives/message/MessageParts.js +1 -1
- package/dist/react/primitives/message/MessageQuote.js +1 -1
- package/dist/react/primitives/part/PartMessages.d.ts +5 -2
- package/dist/react/primitives/part/PartMessages.d.ts.map +1 -1
- package/dist/react/primitives/part/PartMessages.js +8 -8
- package/dist/react/primitives/part/PartMessages.js.map +1 -1
- package/dist/react/primitives/thread/ThreadMessages.d.ts +31 -1
- package/dist/react/primitives/thread/ThreadMessages.d.ts.map +1 -1
- package/dist/react/primitives/thread/ThreadMessages.js +64 -2
- package/dist/react/primitives/thread/ThreadMessages.js.map +1 -1
- package/dist/react/primitives/thread/ThreadSuggestions.js +1 -1
- package/dist/react/primitives/threadList/ThreadListItems.js +1 -1
- package/dist/react/providers/MessageByIdProvider.d.ts +9 -0
- package/dist/react/providers/MessageByIdProvider.d.ts.map +1 -0
- package/dist/react/providers/MessageByIdProvider.js +57 -0
- package/dist/react/providers/MessageByIdProvider.js.map +1 -0
- package/dist/react/providers/ReadonlyThreadProvider.js +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +3 -0
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +16 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +1 -1
- package/dist/react/runtimes/cloud/auiV0.js +1 -1
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +1 -1
- package/dist/react/runtimes/createRuntimeExtras.d.ts +20 -0
- package/dist/react/runtimes/createRuntimeExtras.d.ts.map +1 -0
- package/dist/react/runtimes/createRuntimeExtras.js +43 -0
- package/dist/react/runtimes/createRuntimeExtras.js.map +1 -0
- package/dist/react/runtimes/useLocalRuntime.js +1 -1
- package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
- package/dist/react/runtimes/useRemoteThreadListRuntime.js +6 -2
- package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
- package/dist/react/runtimes/useStreamingTiming.d.ts +26 -0
- package/dist/react/runtimes/useStreamingTiming.d.ts.map +1 -0
- package/dist/react/runtimes/useStreamingTiming.js +43 -0
- package/dist/react/runtimes/useStreamingTiming.js.map +1 -0
- package/dist/react/types/scopes/interactables.d.ts +40 -24
- package/dist/react/types/scopes/interactables.d.ts.map +1 -1
- package/dist/react/types/store-augmentation.d.ts +3 -1
- package/dist/react/types/store-augmentation.d.ts.map +1 -1
- package/dist/react/utils/useJSONEqual.d.ts +11 -0
- package/dist/react/utils/useJSONEqual.d.ts.map +1 -0
- package/dist/react/utils/useJSONEqual.js +30 -0
- package/dist/react/utils/useJSONEqual.js.map +1 -0
- package/dist/runtime/base/base-composer-runtime-core.d.ts +5 -0
- package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.js +13 -0
- package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
- package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/default-edit-composer-runtime-core.js +6 -1
- package/dist/runtime/base/default-edit-composer-runtime-core.js.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.js +4 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
- package/dist/runtime/utils/message-repository.d.ts +1 -0
- package/dist/runtime/utils/message-repository.d.ts.map +1 -1
- package/dist/runtime/utils/message-repository.js +6 -3
- package/dist/runtime/utils/message-repository.js.map +1 -1
- package/dist/runtime/utils/streaming-timing.d.ts +59 -0
- package/dist/runtime/utils/streaming-timing.d.ts.map +1 -0
- package/dist/runtime/utils/streaming-timing.js +76 -0
- package/dist/runtime/utils/streaming-timing.js.map +1 -0
- package/dist/runtime/utils/thread-message-like.js +2 -1
- package/dist/runtime/utils/thread-message-like.js.map +1 -1
- package/dist/runtimes/external-store/external-store-adapter.d.ts +26 -1
- 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 +9 -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 +19 -0
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/remote-thread-list/types.d.ts +12 -0
- package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
- package/dist/store/clients/suggestions.js +1 -1
- package/dist/store/clients/thread-message-client.js +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.js +1 -1
- package/dist/store/runtime-clients/message-runtime-client.js +1 -1
- package/dist/store/runtime-clients/thread-list-item-runtime-client.js +1 -1
- package/dist/store/runtime-clients/thread-runtime-client.js +1 -1
- package/dist/tests/remote-thread-list-test-helpers.d.ts +1 -1
- package/dist/tests/remote-thread-list-test-helpers.d.ts.map +1 -1
- package/dist/tests/remote-thread-list-test-helpers.js +3 -2
- package/dist/tests/remote-thread-list-test-helpers.js.map +1 -1
- package/package.json +9 -9
- package/src/index.ts +17 -0
- package/src/internal.ts +7 -0
- package/src/model-context/interactable-composer-metadata.test.ts +550 -0
- package/src/model-context/interactable-composer-metadata.ts +395 -0
- package/src/model-context/registry.ts +5 -0
- package/src/model-context/types.ts +12 -0
- package/src/react/client/Interactables.test.ts +358 -0
- package/src/react/client/Interactables.ts +339 -86
- package/src/react/client/interactable-model-context.test.ts +418 -0
- package/src/react/client/interactable-model-context.ts +252 -58
- package/src/react/index.ts +72 -3
- package/src/react/interactables-legacy/Interactables.ts +332 -0
- package/src/react/interactables-legacy/interactable-model-context.test.ts +54 -0
- package/src/react/interactables-legacy/interactable-model-context.ts +80 -0
- package/src/react/interactables-legacy/scopes.ts +66 -0
- package/src/react/{model-context → interactables-legacy}/useAssistantInteractable.ts +10 -1
- package/src/react/interactables-legacy/useInteractableState.ts +67 -0
- package/src/react/model-context/interactableTool.tsx +175 -0
- package/src/react/model-context/useInteractable.ts +233 -0
- package/src/react/model-context/useInteractableState.ts +31 -23
- package/src/react/model-context/useInteractableVersions.ts +48 -0
- package/src/react/primitive-hooks/useThreadMessageIds.ts +27 -0
- package/src/react/primitives/generativeUI/GenerativeUI.tsx +5 -1
- package/src/react/primitives/part/PartMessages.test.tsx +17 -0
- package/src/react/primitives/part/PartMessages.tsx +13 -8
- package/src/react/primitives/thread/ThreadMessages.tsx +67 -0
- package/src/react/providers/MessageByIdProvider.tsx +23 -0
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +21 -1
- package/src/react/runtimes/createRuntimeExtras.test.ts +58 -0
- package/src/react/runtimes/createRuntimeExtras.ts +68 -0
- package/src/react/runtimes/createRuntimeExtras.use.test.ts +50 -0
- package/src/react/runtimes/useRemoteThreadListRuntime.ts +13 -1
- package/src/react/runtimes/useStreamingTiming.ts +68 -0
- package/src/react/types/scopes/interactables.ts +45 -23
- package/src/react/types/store-augmentation.ts +4 -2
- package/src/react/utils/useJSONEqual.ts +20 -0
- package/src/runtime/base/base-composer-runtime-core.ts +13 -0
- package/src/runtime/base/default-edit-composer-runtime-core.ts +18 -1
- package/src/runtime/base/default-thread-composer-runtime-core.ts +11 -1
- package/src/runtime/utils/message-repository.ts +11 -7
- package/src/runtime/utils/streaming-timing.test.ts +301 -0
- package/src/runtime/utils/streaming-timing.ts +161 -0
- package/src/runtime/utils/thread-message-like.ts +2 -1
- package/src/runtimes/external-store/external-store-adapter.ts +28 -0
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +33 -1
- package/src/runtimes/local/local-thread-runtime-core.ts +4 -1
- package/src/runtimes/remote-thread-list/types.ts +13 -0
- package/src/tests/MessageRepository.test.ts +2 -0
- package/src/tests/composer-can-send.test.ts +1 -0
- package/src/tests/default-edit-composer-runtime-core.test.ts +78 -1
- package/src/tests/default-thread-composer-runtime-core.test.ts +82 -0
- package/src/tests/external-store-thread-runtime-core.test.ts +337 -0
- package/src/tests/groupParts.test.ts +32 -0
- package/src/tests/remote-thread-list-reactive-threadId.test.ts +90 -0
- package/src/tests/remote-thread-list-test-helpers.ts +2 -1
- package/src/tests/thread-message-like.test.ts +50 -0
- package/dist/react/model-context/useAssistantInteractable.d.ts +0 -22
- package/dist/react/model-context/useAssistantInteractable.d.ts.map +0 -1
- package/dist/react/model-context/useAssistantInteractable.js.map +0 -1
|
@@ -6,20 +6,70 @@ import {
|
|
|
6
6
|
attachTransformScopes,
|
|
7
7
|
} from "@assistant-ui/store";
|
|
8
8
|
import type {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
Unstable_InteractablesState,
|
|
10
|
+
Unstable_InteractableRegistration,
|
|
11
|
+
Unstable_InteractablePersistedState,
|
|
12
|
+
Unstable_InteractablePersistenceAdapter,
|
|
13
|
+
Unstable_InteractablesConfig,
|
|
14
14
|
} from "../types/scopes/interactables";
|
|
15
15
|
import { toJSONSchema, toPartialJSONSchema } from "assistant-stream";
|
|
16
16
|
import { ModelContext } from "../../store";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
buildInteractableModelContext,
|
|
19
|
+
type PartialJSONSchema,
|
|
20
|
+
} from "./interactable-model-context";
|
|
21
|
+
import {
|
|
22
|
+
findModelKnownState,
|
|
23
|
+
interactableToolName,
|
|
24
|
+
} from "../../model-context/interactable-composer-metadata";
|
|
18
25
|
|
|
19
26
|
const PERSISTENCE_DEBOUNCE_MS = 500;
|
|
20
27
|
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
type RestorePersistedStateOptions = {
|
|
29
|
+
stash: Map<string, unknown>;
|
|
30
|
+
shouldStash?: (id: string) => boolean;
|
|
31
|
+
shouldApply?: (
|
|
32
|
+
id: string,
|
|
33
|
+
def: Unstable_InteractablesState["definitions"][string],
|
|
34
|
+
) => boolean;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type ToolCallLikePart = {
|
|
38
|
+
type?: string;
|
|
39
|
+
toolCallId?: string;
|
|
40
|
+
toolName?: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type MessageLike = {
|
|
44
|
+
role?: string;
|
|
45
|
+
content?: readonly unknown[] | undefined;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type InternalInteractableRegistration = Unstable_InteractableRegistration & {
|
|
49
|
+
scope?: "thread" | undefined;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const hasInteractableCreateCall = (
|
|
53
|
+
messages: readonly MessageLike[],
|
|
54
|
+
id: string,
|
|
55
|
+
name: string,
|
|
56
|
+
) =>
|
|
57
|
+
messages.some(
|
|
58
|
+
(message) =>
|
|
59
|
+
message.role === "assistant" &&
|
|
60
|
+
message.content?.some((part) => {
|
|
61
|
+
if (!part || typeof part !== "object") return false;
|
|
62
|
+
const p = part as ToolCallLikePart;
|
|
63
|
+
return (
|
|
64
|
+
p.type === "tool-call" && p.toolCallId === id && p.toolName === name
|
|
65
|
+
);
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const useInteractablesResource = ({
|
|
70
|
+
persistence,
|
|
71
|
+
}: Unstable_InteractablesConfig = {}): ClientOutput<"unstable_interactables"> => {
|
|
72
|
+
const [state, setState] = useState<Unstable_InteractablesState>(() => ({
|
|
23
73
|
definitions: {},
|
|
24
74
|
persistence: {},
|
|
25
75
|
}));
|
|
@@ -27,19 +77,32 @@ const useInteractables = (): ClientOutput<"interactables"> => {
|
|
|
27
77
|
const clientRef = useAssistantClientRef();
|
|
28
78
|
|
|
29
79
|
const stateRef = useRef(state);
|
|
30
|
-
useEffect(() => {
|
|
31
|
-
stateRef.current = state;
|
|
32
|
-
}, [state]);
|
|
33
80
|
|
|
34
81
|
const subscribersRef = useRef(new Set<() => void>());
|
|
35
|
-
const partialSchemaCacheRef = useRef(
|
|
36
|
-
|
|
82
|
+
const partialSchemaCacheRef = useRef(new Map<string, PartialJSONSchema>());
|
|
83
|
+
const streamBaselinesRef = useRef(
|
|
84
|
+
new Map<string, { targetId: string; state: unknown }>(),
|
|
37
85
|
);
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
undefined,
|
|
86
|
+
const detachedAppStateRef = useRef(new Map<string, unknown>());
|
|
87
|
+
const detachedThreadStateRef = useRef(
|
|
88
|
+
new Map<string, Map<string, unknown>>(),
|
|
42
89
|
);
|
|
90
|
+
// An instance may be registered from several anchors (its creating tool
|
|
91
|
+
// call plus update_* calls); the definition lives until the last one leaves.
|
|
92
|
+
const registrationCountsRef = useRef(new Map<string, number>());
|
|
93
|
+
// One update-tool UI per interactable name, alive while any registrant
|
|
94
|
+
// that supplied an updateRender is mounted.
|
|
95
|
+
const updateToolUIsRef = useRef(
|
|
96
|
+
new Map<string, { count: number; unsubscribe: () => void }>(),
|
|
97
|
+
);
|
|
98
|
+
// App-scoped state restored via adapter.load(), consumed as components register.
|
|
99
|
+
const loadedStateRef = useRef(new Map<string, unknown>());
|
|
100
|
+
// Ids edited locally this session — a local edit always wins over a slow load.
|
|
101
|
+
const touchedIdsRef = useRef(new Set<string>());
|
|
102
|
+
|
|
103
|
+
const adapterRef = useRef<
|
|
104
|
+
Unstable_InteractablePersistenceAdapter | undefined
|
|
105
|
+
>(undefined);
|
|
43
106
|
const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(
|
|
44
107
|
undefined,
|
|
45
108
|
);
|
|
@@ -48,6 +111,28 @@ const useInteractables = (): ClientOutput<"interactables"> => {
|
|
|
48
111
|
const flushResolversRef = useRef<Array<() => void>>([]);
|
|
49
112
|
const dirtyIdsRef = useRef(new Set<string>());
|
|
50
113
|
|
|
114
|
+
const setStateAndRef = useCallback(
|
|
115
|
+
(
|
|
116
|
+
updater: (
|
|
117
|
+
prev: Unstable_InteractablesState,
|
|
118
|
+
) => Unstable_InteractablesState,
|
|
119
|
+
) => {
|
|
120
|
+
const next = updater(stateRef.current);
|
|
121
|
+
stateRef.current = next;
|
|
122
|
+
setState(next);
|
|
123
|
+
},
|
|
124
|
+
[],
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const exportState = useCallback((): Unstable_InteractablePersistedState => {
|
|
128
|
+
const result: Unstable_InteractablePersistedState = {};
|
|
129
|
+
for (const [id, def] of Object.entries(stateRef.current.definitions)) {
|
|
130
|
+
if (def.scope === "thread") continue; // thread items persist via snapshot, not the adapter
|
|
131
|
+
result[id] = { name: def.name, state: def.state };
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
134
|
+
}, []);
|
|
135
|
+
|
|
51
136
|
const runPersistence = useCallback(async () => {
|
|
52
137
|
const adapter = adapterRef.current;
|
|
53
138
|
if (!adapter) {
|
|
@@ -62,13 +147,9 @@ const useInteractables = (): ClientOutput<"interactables"> => {
|
|
|
62
147
|
hasPendingLocalChangeRef.current = true;
|
|
63
148
|
|
|
64
149
|
// Snapshot before any await so unregistered definitions are still included.
|
|
65
|
-
const
|
|
66
|
-
const payload: InteractablePersistedState = {};
|
|
67
|
-
for (const [id, def] of Object.entries(exported)) {
|
|
68
|
-
payload[id] = { name: def.name, state: def.state };
|
|
69
|
-
}
|
|
150
|
+
const payload = exportState();
|
|
70
151
|
|
|
71
|
-
|
|
152
|
+
setStateAndRef((prev) => ({
|
|
72
153
|
...prev,
|
|
73
154
|
persistence: {
|
|
74
155
|
...prev.persistence,
|
|
@@ -85,7 +166,7 @@ const useInteractables = (): ClientOutput<"interactables"> => {
|
|
|
85
166
|
await adapter.save(payload);
|
|
86
167
|
if (syncSeqRef.current === seq) {
|
|
87
168
|
hasPendingLocalChangeRef.current = false;
|
|
88
|
-
|
|
169
|
+
setStateAndRef((prev) => {
|
|
89
170
|
const persistence = { ...prev.persistence };
|
|
90
171
|
for (const id of dirtyIds) delete persistence[id];
|
|
91
172
|
return { ...prev, persistence };
|
|
@@ -94,7 +175,7 @@ const useInteractables = (): ClientOutput<"interactables"> => {
|
|
|
94
175
|
} catch (e) {
|
|
95
176
|
if (syncSeqRef.current === seq) {
|
|
96
177
|
hasPendingLocalChangeRef.current = false;
|
|
97
|
-
|
|
178
|
+
setStateAndRef((prev) => ({
|
|
98
179
|
...prev,
|
|
99
180
|
persistence: {
|
|
100
181
|
...prev.persistence,
|
|
@@ -112,7 +193,7 @@ const useInteractables = (): ClientOutput<"interactables"> => {
|
|
|
112
193
|
flushResolversRef.current = [];
|
|
113
194
|
}
|
|
114
195
|
}
|
|
115
|
-
}, []);
|
|
196
|
+
}, [exportState, setStateAndRef]);
|
|
116
197
|
|
|
117
198
|
const schedulePersistence = useCallback(
|
|
118
199
|
(id: string) => {
|
|
@@ -136,38 +217,103 @@ const useInteractables = (): ClientOutput<"interactables"> => {
|
|
|
136
217
|
[runPersistence],
|
|
137
218
|
);
|
|
138
219
|
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
220
|
+
const restorePersistedState = useCallback(
|
|
221
|
+
(
|
|
222
|
+
saved: Unstable_InteractablePersistedState,
|
|
223
|
+
options: RestorePersistedStateOptions,
|
|
224
|
+
) => {
|
|
225
|
+
const shouldStash = options.shouldStash ?? (() => true);
|
|
226
|
+
const shouldApply = options.shouldApply ?? (() => true);
|
|
146
227
|
|
|
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 };
|
|
154
228
|
for (const [id, entry] of Object.entries(saved)) {
|
|
155
|
-
if (
|
|
156
|
-
|
|
229
|
+
if (shouldStash(id)) options.stash.set(id, entry.state);
|
|
230
|
+
}
|
|
231
|
+
setStateAndRef((prev) => {
|
|
232
|
+
let changed = false;
|
|
233
|
+
const definitions = { ...prev.definitions };
|
|
234
|
+
for (const [id, entry] of Object.entries(saved)) {
|
|
235
|
+
const def = definitions[id];
|
|
236
|
+
if (!def || !shouldApply(id, def)) continue;
|
|
237
|
+
definitions[id] = { ...def, state: entry.state };
|
|
157
238
|
changed = true;
|
|
158
239
|
}
|
|
240
|
+
if (!changed) return prev;
|
|
241
|
+
return { ...prev, definitions };
|
|
242
|
+
});
|
|
243
|
+
},
|
|
244
|
+
[setStateAndRef],
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const importState = useCallback(
|
|
248
|
+
(saved: Unstable_InteractablePersistedState) => {
|
|
249
|
+
restorePersistedState(saved, { stash: detachedAppStateRef.current });
|
|
250
|
+
},
|
|
251
|
+
[restorePersistedState],
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
// Applies adapter.load() output: a local edit made while the load was in
|
|
255
|
+
// flight wins, and thread-scoped items never restore from the adapter.
|
|
256
|
+
const applyLoadedState = useCallback(
|
|
257
|
+
(saved: Unstable_InteractablePersistedState) => {
|
|
258
|
+
restorePersistedState(saved, {
|
|
259
|
+
stash: loadedStateRef.current,
|
|
260
|
+
shouldStash: (id) => !touchedIdsRef.current.has(id),
|
|
261
|
+
shouldApply: (id, def) =>
|
|
262
|
+
!touchedIdsRef.current.has(id) && def.scope !== "thread",
|
|
263
|
+
});
|
|
264
|
+
},
|
|
265
|
+
[restorePersistedState],
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
const loadFromAdapter = useCallback(
|
|
269
|
+
async (adapter: Unstable_InteractablePersistenceAdapter) => {
|
|
270
|
+
if (!adapter.load) return;
|
|
271
|
+
try {
|
|
272
|
+
const saved = await adapter.load();
|
|
273
|
+
if (!saved || adapterRef.current !== adapter) return;
|
|
274
|
+
applyLoadedState(saved);
|
|
275
|
+
} catch (e) {
|
|
276
|
+
console.warn("[Interactables] Persistence load failed.", e);
|
|
159
277
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
278
|
+
},
|
|
279
|
+
[applyLoadedState],
|
|
280
|
+
);
|
|
163
281
|
|
|
164
282
|
const setPersistenceAdapter = useCallback(
|
|
165
|
-
(adapter:
|
|
283
|
+
(adapter: Unstable_InteractablePersistenceAdapter | undefined) => {
|
|
166
284
|
adapterRef.current = adapter;
|
|
285
|
+
if (adapter) void loadFromAdapter(adapter);
|
|
167
286
|
},
|
|
168
|
-
[],
|
|
287
|
+
[loadFromAdapter],
|
|
169
288
|
);
|
|
170
289
|
|
|
290
|
+
const getCurrentThreadId = useCallback((): string | undefined => {
|
|
291
|
+
const client = clientRef.current;
|
|
292
|
+
if (!client) return undefined;
|
|
293
|
+
|
|
294
|
+
const threadListItem = client.threadListItem;
|
|
295
|
+
if (threadListItem.source != null) {
|
|
296
|
+
return threadListItem().getState().id;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const threads = client.threads;
|
|
300
|
+
if (threads.source != null) {
|
|
301
|
+
return threads().getState().mainThreadId;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return undefined;
|
|
305
|
+
}, [clientRef]);
|
|
306
|
+
|
|
307
|
+
useEffect(() => {
|
|
308
|
+
if (!persistence) return;
|
|
309
|
+
setPersistenceAdapter(persistence);
|
|
310
|
+
return () => {
|
|
311
|
+
if (adapterRef.current === persistence) {
|
|
312
|
+
adapterRef.current = undefined;
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
}, [persistence, setPersistenceAdapter]);
|
|
316
|
+
|
|
171
317
|
const flush = useCallback(async () => {
|
|
172
318
|
if (debounceTimerRef.current !== undefined) {
|
|
173
319
|
clearTimeout(debounceTimerRef.current);
|
|
@@ -195,7 +341,8 @@ const useInteractables = (): ClientOutput<"interactables"> => {
|
|
|
195
341
|
|
|
196
342
|
const setDefState = useCallback(
|
|
197
343
|
(id: string, updater: (prev: unknown) => unknown) => {
|
|
198
|
-
|
|
344
|
+
touchedIdsRef.current.add(id);
|
|
345
|
+
setStateAndRef((prev) => {
|
|
199
346
|
const existing = prev.definitions[id];
|
|
200
347
|
if (!existing) return prev;
|
|
201
348
|
return {
|
|
@@ -206,25 +353,13 @@ const useInteractables = (): ClientOutput<"interactables"> => {
|
|
|
206
353
|
},
|
|
207
354
|
};
|
|
208
355
|
});
|
|
209
|
-
if (stateRef.current.definitions[id])
|
|
356
|
+
if (stateRef.current.definitions[id]?.scope !== "thread") {
|
|
357
|
+
schedulePersistence(id);
|
|
358
|
+
}
|
|
210
359
|
},
|
|
211
|
-
[schedulePersistence],
|
|
360
|
+
[schedulePersistence, setStateAndRef],
|
|
212
361
|
);
|
|
213
362
|
|
|
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 },
|
|
223
|
-
},
|
|
224
|
-
};
|
|
225
|
-
});
|
|
226
|
-
}, []);
|
|
227
|
-
|
|
228
363
|
const provider = useMemo(
|
|
229
364
|
() => ({
|
|
230
365
|
getModelContext: () => {
|
|
@@ -234,6 +369,7 @@ const useInteractables = (): ClientOutput<"interactables"> => {
|
|
|
234
369
|
defs,
|
|
235
370
|
partialSchemaCacheRef.current,
|
|
236
371
|
setDefState,
|
|
372
|
+
streamBaselinesRef.current,
|
|
237
373
|
) ?? {}
|
|
238
374
|
);
|
|
239
375
|
},
|
|
@@ -256,24 +392,111 @@ const useInteractables = (): ClientOutput<"interactables"> => {
|
|
|
256
392
|
}, [clientRef, provider]);
|
|
257
393
|
|
|
258
394
|
const register = useCallback(
|
|
259
|
-
(def:
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
395
|
+
(def: InternalInteractableRegistration) => {
|
|
396
|
+
const threadAccessor = clientRef.current?.thread;
|
|
397
|
+
const threadMessages =
|
|
398
|
+
threadAccessor && threadAccessor.source != null
|
|
399
|
+
? (threadAccessor().getState().messages ?? [])
|
|
400
|
+
: [];
|
|
401
|
+
const scope =
|
|
402
|
+
def.scope ??
|
|
403
|
+
(hasInteractableCreateCall(threadMessages, def.id, def.name)
|
|
404
|
+
? "thread"
|
|
405
|
+
: "app");
|
|
406
|
+
|
|
407
|
+
if (
|
|
408
|
+
process.env.NODE_ENV !== "production" &&
|
|
409
|
+
stateRef.current.definitions[def.id] &&
|
|
410
|
+
scope !== "thread"
|
|
411
|
+
) {
|
|
267
412
|
console.warn(
|
|
268
|
-
`[Interactables]
|
|
269
|
-
|
|
413
|
+
`[Interactables] "${def.name}" (${def.id}) is already registered. ` +
|
|
414
|
+
`Register an app-scoped interactable once (unstable_useInteractable) and ` +
|
|
415
|
+
`read it from other components with unstable_useInteractableState.`,
|
|
270
416
|
);
|
|
271
417
|
}
|
|
272
418
|
|
|
273
|
-
|
|
274
|
-
|
|
419
|
+
registrationCountsRef.current.set(
|
|
420
|
+
def.id,
|
|
421
|
+
(registrationCountsRef.current.get(def.id) ?? 0) + 1,
|
|
422
|
+
);
|
|
275
423
|
|
|
276
|
-
|
|
424
|
+
let releaseUpdateToolUI: (() => void) | undefined;
|
|
425
|
+
if (def.updateRender) {
|
|
426
|
+
const toolsAccessor = clientRef.current?.tools;
|
|
427
|
+
if (toolsAccessor && toolsAccessor.source != null) {
|
|
428
|
+
const toolName = interactableToolName(def.name);
|
|
429
|
+
const existing = updateToolUIsRef.current.get(def.name);
|
|
430
|
+
if (existing) {
|
|
431
|
+
existing.count++;
|
|
432
|
+
} else {
|
|
433
|
+
updateToolUIsRef.current.set(def.name, {
|
|
434
|
+
count: 1,
|
|
435
|
+
unsubscribe: toolsAccessor().setToolUI(
|
|
436
|
+
toolName,
|
|
437
|
+
def.updateRender,
|
|
438
|
+
{ standalone: true },
|
|
439
|
+
),
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
releaseUpdateToolUI = () => {
|
|
443
|
+
const entry = updateToolUIsRef.current.get(def.name);
|
|
444
|
+
if (!entry) return;
|
|
445
|
+
if (--entry.count === 0) {
|
|
446
|
+
updateToolUIsRef.current.delete(def.name);
|
|
447
|
+
entry.unsubscribe();
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
} else if (process.env.NODE_ENV !== "production") {
|
|
451
|
+
console.warn(
|
|
452
|
+
`[Interactables] "${def.name}" supplied an updateRender, but no ` +
|
|
453
|
+
`tools scope is available to install it into.`,
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// The same id re-registers once per anchor (its create call + each update_*).
|
|
459
|
+
if (!partialSchemaCacheRef.current.has(def.id)) {
|
|
460
|
+
try {
|
|
461
|
+
const jsonSchema = toJSONSchema(def.stateSchema);
|
|
462
|
+
partialSchemaCacheRef.current.set(
|
|
463
|
+
def.id,
|
|
464
|
+
toPartialJSONSchema(jsonSchema),
|
|
465
|
+
);
|
|
466
|
+
} catch (e) {
|
|
467
|
+
console.warn(
|
|
468
|
+
`[Interactables] Failed to create partial schema for "${def.name}". The update tool will accept arbitrary fields without validation.`,
|
|
469
|
+
e,
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const threadId = scope === "thread" ? getCurrentThreadId() : undefined;
|
|
475
|
+
const detached =
|
|
476
|
+
scope === "thread"
|
|
477
|
+
? threadId
|
|
478
|
+
? detachedThreadStateRef.current.get(threadId)?.get(def.id)
|
|
479
|
+
: undefined
|
|
480
|
+
: detachedAppStateRef.current.get(def.id);
|
|
481
|
+
if (scope === "thread") {
|
|
482
|
+
if (threadId)
|
|
483
|
+
detachedThreadStateRef.current.get(threadId)?.delete(def.id);
|
|
484
|
+
} else {
|
|
485
|
+
detachedAppStateRef.current.delete(def.id);
|
|
486
|
+
}
|
|
487
|
+
const loaded =
|
|
488
|
+
scope === "thread" ? undefined : loadedStateRef.current.get(def.id);
|
|
489
|
+
|
|
490
|
+
// Tool-created items restore from what the model already knows in this
|
|
491
|
+
// thread (the creating call's args, sent snapshots, and the model's own
|
|
492
|
+
// update_* calls) on a fresh reload; detached (in-session remount) still
|
|
493
|
+
// wins so an unsent edit survives a scroll/virtualization cycle.
|
|
494
|
+
const known =
|
|
495
|
+
scope === "thread"
|
|
496
|
+
? findModelKnownState(threadMessages, def.id, def.name)
|
|
497
|
+
: undefined;
|
|
498
|
+
|
|
499
|
+
setStateAndRef((prev) => ({
|
|
277
500
|
...prev,
|
|
278
501
|
definitions: {
|
|
279
502
|
...prev.definitions,
|
|
@@ -282,19 +505,45 @@ const useInteractables = (): ClientOutput<"interactables"> => {
|
|
|
282
505
|
name: def.name,
|
|
283
506
|
description: def.description,
|
|
284
507
|
stateSchema: def.stateSchema,
|
|
508
|
+
initialState: def.initialState,
|
|
509
|
+
scope,
|
|
285
510
|
state:
|
|
286
|
-
prev.definitions[def.id]?.state ??
|
|
287
|
-
|
|
511
|
+
prev.definitions[def.id]?.state ??
|
|
512
|
+
detached ??
|
|
513
|
+
known?.state ??
|
|
514
|
+
loaded ??
|
|
515
|
+
def.initialState,
|
|
288
516
|
},
|
|
289
517
|
},
|
|
290
518
|
}));
|
|
291
519
|
|
|
292
520
|
return () => {
|
|
521
|
+
releaseUpdateToolUI?.();
|
|
522
|
+
|
|
523
|
+
const remaining = (registrationCountsRef.current.get(def.id) ?? 1) - 1;
|
|
524
|
+
if (remaining > 0) {
|
|
525
|
+
registrationCountsRef.current.set(def.id, remaining);
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
registrationCountsRef.current.delete(def.id);
|
|
529
|
+
|
|
293
530
|
flushIfPending();
|
|
294
|
-
|
|
531
|
+
setStateAndRef((prev) => {
|
|
295
532
|
const existing = prev.definitions[def.id];
|
|
296
533
|
if (existing) {
|
|
297
|
-
|
|
534
|
+
if (existing.scope === "thread") {
|
|
535
|
+
const threadId = getCurrentThreadId();
|
|
536
|
+
if (threadId) {
|
|
537
|
+
let stateById = detachedThreadStateRef.current.get(threadId);
|
|
538
|
+
if (!stateById) {
|
|
539
|
+
stateById = new Map();
|
|
540
|
+
detachedThreadStateRef.current.set(threadId, stateById);
|
|
541
|
+
}
|
|
542
|
+
stateById.set(def.id, existing.state);
|
|
543
|
+
}
|
|
544
|
+
} else {
|
|
545
|
+
detachedAppStateRef.current.set(def.id, existing.state);
|
|
546
|
+
}
|
|
298
547
|
}
|
|
299
548
|
partialSchemaCacheRef.current.delete(def.id);
|
|
300
549
|
const { [def.id]: _, ...rest } = prev.definitions;
|
|
@@ -303,14 +552,13 @@ const useInteractables = (): ClientOutput<"interactables"> => {
|
|
|
303
552
|
});
|
|
304
553
|
};
|
|
305
554
|
},
|
|
306
|
-
[flushIfPending],
|
|
555
|
+
[flushIfPending, clientRef, getCurrentThreadId, setStateAndRef],
|
|
307
556
|
);
|
|
308
557
|
|
|
309
558
|
return {
|
|
310
|
-
getState: () =>
|
|
559
|
+
getState: () => stateRef.current,
|
|
311
560
|
register,
|
|
312
561
|
setState: setDefState,
|
|
313
|
-
setSelected: setDefSelected,
|
|
314
562
|
exportState,
|
|
315
563
|
importState,
|
|
316
564
|
setPersistenceAdapter,
|
|
@@ -318,9 +566,14 @@ const useInteractables = (): ClientOutput<"interactables"> => {
|
|
|
318
566
|
};
|
|
319
567
|
};
|
|
320
568
|
|
|
321
|
-
|
|
569
|
+
/**
|
|
570
|
+
* Registers the unstable interactables store scope.
|
|
571
|
+
*
|
|
572
|
+
* @deprecated Unstable / Experimental (not actually removed).
|
|
573
|
+
*/
|
|
574
|
+
export const unstable_Interactables = resource(useInteractablesResource);
|
|
322
575
|
|
|
323
|
-
attachTransformScopes(
|
|
576
|
+
attachTransformScopes(useInteractablesResource, (scopes, parent) => {
|
|
324
577
|
if (!scopes.modelContext && parent.modelContext.source === null) {
|
|
325
578
|
scopes.modelContext = ModelContext();
|
|
326
579
|
}
|