@assistant-ui/core 0.2.0 → 0.2.3
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/README.md +45 -0
- package/dist/index.d.ts +2 -1
- 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/tool.d.ts +25 -0
- package/dist/model-context/tool.d.ts.map +1 -1
- package/dist/model-context/tool.js +25 -0
- package/dist/model-context/tool.js.map +1 -1
- package/dist/react/AssistantRuntimeProvider.d.ts +33 -0
- package/dist/react/AssistantRuntimeProvider.d.ts.map +1 -1
- package/dist/react/AssistantRuntimeProvider.js +22 -0
- package/dist/react/AssistantRuntimeProvider.js.map +1 -1
- package/dist/react/client/DataRenderers.d.ts +7 -0
- package/dist/react/client/DataRenderers.d.ts.map +1 -1
- package/dist/react/client/DataRenderers.js +7 -0
- package/dist/react/client/DataRenderers.js.map +1 -1
- package/dist/react/client/Tools.d.ts +18 -1
- package/dist/react/client/Tools.d.ts.map +1 -1
- package/dist/react/client/Tools.js +24 -19
- package/dist/react/client/Tools.js.map +1 -1
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/model-context/makeAssistantDataUI.d.ts +13 -0
- package/dist/react/model-context/makeAssistantDataUI.d.ts.map +1 -1
- package/dist/react/model-context/makeAssistantDataUI.js +6 -0
- package/dist/react/model-context/makeAssistantDataUI.js.map +1 -1
- package/dist/react/model-context/makeAssistantTool.d.ts +15 -0
- package/dist/react/model-context/makeAssistantTool.d.ts.map +1 -1
- package/dist/react/model-context/makeAssistantTool.js +8 -0
- package/dist/react/model-context/makeAssistantTool.js.map +1 -1
- package/dist/react/model-context/makeAssistantToolUI.d.ts +15 -0
- package/dist/react/model-context/makeAssistantToolUI.d.ts.map +1 -1
- package/dist/react/model-context/makeAssistantToolUI.js +8 -0
- package/dist/react/model-context/makeAssistantToolUI.js.map +1 -1
- package/dist/react/model-context/toolbox.d.ts +29 -0
- package/dist/react/model-context/toolbox.d.ts.map +1 -1
- package/dist/react/model-context/useAssistantDataUI.d.ts +9 -0
- package/dist/react/model-context/useAssistantDataUI.d.ts.map +1 -1
- package/dist/react/model-context/useAssistantDataUI.js +6 -0
- package/dist/react/model-context/useAssistantDataUI.js.map +1 -1
- package/dist/react/model-context/useAssistantTool.d.ts +34 -0
- package/dist/react/model-context/useAssistantTool.d.ts.map +1 -1
- package/dist/react/model-context/useAssistantTool.js +30 -0
- package/dist/react/model-context/useAssistantTool.js.map +1 -1
- package/dist/react/model-context/useAssistantToolUI.d.ts +12 -0
- package/dist/react/model-context/useAssistantToolUI.d.ts.map +1 -1
- package/dist/react/model-context/useAssistantToolUI.js +9 -0
- package/dist/react/model-context/useAssistantToolUI.js.map +1 -1
- package/dist/react/model-context/useToolArgsStatus.d.ts +29 -0
- package/dist/react/model-context/useToolArgsStatus.d.ts.map +1 -1
- package/dist/react/model-context/useToolArgsStatus.js +24 -0
- package/dist/react/model-context/useToolArgsStatus.js.map +1 -1
- package/dist/react/primitive-hooks/useActionBarCopy.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useActionBarCopy.js +4 -3
- package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
- package/dist/react/primitive-hooks/useComposerSend.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useComposerSend.js +2 -3
- package/dist/react/primitive-hooks/useComposerSend.js.map +1 -1
- package/dist/react/primitives/message/MessageAttachments.js +1 -1
- package/dist/react/primitives/message/MessageAttachments.js.map +1 -1
- package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageParts.js +14 -10
- package/dist/react/primitives/message/MessageParts.js.map +1 -1
- package/dist/react/primitives/messagePart/MessagePartInProgress.d.ts +6 -0
- package/dist/react/primitives/messagePart/MessagePartInProgress.d.ts.map +1 -0
- package/dist/react/primitives/messagePart/MessagePartInProgress.js +7 -0
- package/dist/react/primitives/messagePart/MessagePartInProgress.js.map +1 -0
- 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/cloud/auiV0.d.ts +10 -1
- package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/auiV0.js +21 -3
- package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
- package/dist/react/runtimes/useToolInvocations.d.ts +11 -1
- package/dist/react/runtimes/useToolInvocations.d.ts.map +1 -1
- package/dist/react/runtimes/useToolInvocations.js +325 -256
- package/dist/react/runtimes/useToolInvocations.js.map +1 -1
- package/dist/react/types/MessagePartComponentTypes.d.ts +11 -0
- package/dist/react/types/MessagePartComponentTypes.d.ts.map +1 -1
- package/dist/react/types/scopes/tools.d.ts +4 -0
- package/dist/react/types/scopes/tools.d.ts.map +1 -1
- package/dist/runtime/api/composer-runtime.d.ts +1 -0
- package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
- package/dist/runtime/api/composer-runtime.js +2 -0
- package/dist/runtime/api/composer-runtime.js.map +1 -1
- package/dist/runtime/api/thread-runtime.d.ts +2 -0
- package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -0
- package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.js +1 -1
- package/dist/runtime/base/base-composer-runtime-core.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/base/default-edit-composer-runtime-core.d.ts +1 -0
- package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/default-edit-composer-runtime-core.js +3 -0
- package/dist/runtime/base/default-edit-composer-runtime-core.js.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +1 -0
- package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.js +12 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
- package/dist/runtime/interfaces/composer-runtime-core.d.ts +1 -0
- package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/interfaces/thread-runtime-core.d.ts +6 -0
- package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-adapter.d.ts +15 -0
- package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +14 -12
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +2 -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-thread-runtime-core.d.ts +1 -0
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +1 -0
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +2 -0
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +2 -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 +2 -0
- package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
- package/dist/store/clients/model-context-client.d.ts.map +1 -1
- package/dist/store/clients/model-context-client.js +24 -4
- package/dist/store/clients/model-context-client.js.map +1 -1
- package/dist/store/clients/no-op-composer-client.d.ts.map +1 -1
- package/dist/store/clients/no-op-composer-client.js +1 -0
- package/dist/store/clients/no-op-composer-client.js.map +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.js +1 -0
- package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
- package/dist/store/scopes/composer.d.ts +9 -0
- package/dist/store/scopes/composer.d.ts.map +1 -1
- package/dist/store/scopes/model-context.d.ts +4 -1
- package/dist/store/scopes/model-context.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/message.d.ts +50 -1
- package/dist/types/message.d.ts.map +1 -1
- package/dist/types/message.js +2 -1
- package/dist/types/message.js.map +1 -1
- package/package.json +7 -7
- package/src/index.ts +6 -0
- package/src/model-context/tool.ts +25 -0
- package/src/react/AssistantRuntimeProvider.tsx +33 -0
- package/src/react/client/DataRenderers.ts +7 -0
- package/src/react/client/Tools.ts +56 -22
- package/src/react/index.ts +2 -1
- package/src/react/model-context/makeAssistantDataUI.ts +13 -0
- package/src/react/model-context/makeAssistantTool.ts +15 -0
- package/src/react/model-context/makeAssistantToolUI.ts +15 -0
- package/src/react/model-context/toolbox.ts +32 -1
- package/src/react/model-context/useAssistantDataUI.ts +9 -0
- package/src/react/model-context/useAssistantTool.ts +34 -0
- package/src/react/model-context/useAssistantToolUI.ts +12 -0
- package/src/react/model-context/useToolArgsStatus.ts +29 -0
- package/src/react/primitive-hooks/useActionBarCopy.ts +9 -5
- package/src/react/primitive-hooks/useComposerSend.ts +2 -3
- package/src/react/primitives/message/MessageAttachments.test.tsx +50 -0
- package/src/react/primitives/message/MessageAttachments.tsx +1 -1
- package/src/react/primitives/message/MessageParts.tsx +20 -9
- package/src/react/primitives/messagePart/MessagePartInProgress.ts +15 -0
- package/src/react/runtimes/cloud/auiV0.ts +37 -4
- package/src/react/runtimes/useToolInvocations.ts +422 -333
- package/src/react/types/MessagePartComponentTypes.ts +11 -0
- package/src/react/types/scopes/tools.ts +5 -0
- package/src/runtime/api/composer-runtime.ts +3 -0
- package/src/runtime/base/base-composer-runtime-core.ts +2 -1
- package/src/runtime/base/base-thread-runtime-core.ts +1 -0
- package/src/runtime/base/default-edit-composer-runtime-core.ts +4 -0
- package/src/runtime/base/default-thread-composer-runtime-core.ts +12 -1
- package/src/runtime/interfaces/composer-runtime-core.ts +1 -0
- package/src/runtime/interfaces/thread-runtime-core.ts +6 -0
- package/src/runtimes/external-store/external-store-adapter.ts +15 -0
- package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +15 -9
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +13 -0
- package/src/runtimes/local/local-thread-runtime-core.ts +1 -0
- package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +2 -0
- package/src/runtimes/remote-thread-list/empty-thread-core.ts +2 -0
- package/src/store/clients/model-context-client.test.ts +108 -0
- package/src/store/clients/model-context-client.ts +36 -6
- package/src/store/clients/no-op-composer-client.ts +1 -0
- package/src/store/runtime-clients/composer-runtime-client.ts +1 -0
- package/src/store/scopes/composer.ts +9 -0
- package/src/store/scopes/model-context.ts +4 -1
- package/src/tests/auiV0Encode.test.ts +55 -0
- package/src/tests/composer-can-send.test.ts +112 -0
- package/src/tests/external-store-thread-list-runtime-core.test.ts +34 -0
- package/src/tests/external-store-thread-runtime-core.test.ts +113 -0
- package/src/types/index.ts +2 -0
- package/src/types/message.ts +66 -7
|
@@ -57,10 +57,21 @@ export type ToolCallMessagePartProps<
|
|
|
57
57
|
TResult = unknown,
|
|
58
58
|
> = MessagePartState &
|
|
59
59
|
ToolCallMessagePart<TArgs, TResult> & {
|
|
60
|
+
/**
|
|
61
|
+
* Sets the result for this tool-call message part.
|
|
62
|
+
*
|
|
63
|
+
* Use when the renderer, rather than a tool `execute` function, is the
|
|
64
|
+
* source of the result.
|
|
65
|
+
*/
|
|
60
66
|
addResult: (result: TResult | ToolResponse<TResult>) => void;
|
|
67
|
+
/**
|
|
68
|
+
* Supplies the payload requested by `context.human(...)` and resumes the
|
|
69
|
+
* paused frontend tool execution.
|
|
70
|
+
*/
|
|
61
71
|
resume: (payload: unknown) => void;
|
|
62
72
|
};
|
|
63
73
|
|
|
74
|
+
/** Component used to render a tool-call message part. */
|
|
64
75
|
export type ToolCallMessagePartComponent<
|
|
65
76
|
TArgs = any,
|
|
66
77
|
TResult = any,
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import type { ToolCallMessagePartComponent } from "../MessagePartComponentTypes";
|
|
2
2
|
import type { Unsubscribe } from "../../..";
|
|
3
3
|
|
|
4
|
+
export type McpAppResourceOutput = {
|
|
5
|
+
readonly render: ToolCallMessagePartComponent;
|
|
6
|
+
};
|
|
7
|
+
|
|
4
8
|
export type ToolsState = {
|
|
5
9
|
tools: Record<string, ToolCallMessagePartComponent[]>;
|
|
10
|
+
mcpApp?: McpAppResourceOutput | undefined;
|
|
6
11
|
};
|
|
7
12
|
|
|
8
13
|
export type ToolsMethods = {
|
|
@@ -41,6 +41,7 @@ export type {
|
|
|
41
41
|
|
|
42
42
|
type BaseComposerState = {
|
|
43
43
|
readonly canCancel: boolean;
|
|
44
|
+
readonly canSend: boolean;
|
|
44
45
|
readonly isEditing: boolean;
|
|
45
46
|
readonly isEmpty: boolean;
|
|
46
47
|
|
|
@@ -86,6 +87,7 @@ const getThreadComposerState = (
|
|
|
86
87
|
|
|
87
88
|
isEditing: runtime?.isEditing ?? false,
|
|
88
89
|
canCancel: runtime?.canCancel ?? false,
|
|
90
|
+
canSend: runtime?.canSend ?? false,
|
|
89
91
|
isEmpty: runtime?.isEmpty ?? true,
|
|
90
92
|
|
|
91
93
|
attachments: runtime?.attachments ?? EMPTY_ARRAY,
|
|
@@ -108,6 +110,7 @@ const getEditComposerState = (
|
|
|
108
110
|
|
|
109
111
|
isEditing: runtime?.isEditing ?? false,
|
|
110
112
|
canCancel: runtime?.canCancel ?? false,
|
|
113
|
+
canSend: runtime?.canSend ?? false,
|
|
111
114
|
isEmpty: runtime?.isEmpty ?? true,
|
|
112
115
|
|
|
113
116
|
text: runtime?.text ?? "",
|
|
@@ -52,6 +52,7 @@ export abstract class BaseComposerRuntimeCore
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
public abstract get canCancel(): boolean;
|
|
55
|
+
public abstract get canSend(): boolean;
|
|
55
56
|
|
|
56
57
|
public get isEmpty() {
|
|
57
58
|
return !this.text.trim() && !this.attachments.length;
|
|
@@ -157,7 +158,7 @@ export abstract class BaseComposerRuntimeCore
|
|
|
157
158
|
}
|
|
158
159
|
|
|
159
160
|
public async send(options?: SendOptions) {
|
|
160
|
-
if (this.
|
|
161
|
+
if (!this.canSend) return;
|
|
161
162
|
|
|
162
163
|
if (this._dictationSession) {
|
|
163
164
|
this._dictationSession.cancel();
|
|
@@ -48,6 +48,7 @@ export abstract class BaseThreadRuntimeCore implements ThreadRuntimeCore {
|
|
|
48
48
|
protected readonly repository = new MessageRepository();
|
|
49
49
|
public abstract get adapters(): BaseThreadAdapters | undefined;
|
|
50
50
|
public abstract get isDisabled(): boolean;
|
|
51
|
+
public abstract get isSendDisabled(): boolean;
|
|
51
52
|
public abstract get isLoading(): boolean;
|
|
52
53
|
public abstract get suggestions(): readonly ThreadSuggestion[];
|
|
53
54
|
public abstract get extras(): unknown;
|
|
@@ -17,6 +17,10 @@ export class DefaultThreadComposerRuntimeCore
|
|
|
17
17
|
return this._canCancel;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
public get canSend() {
|
|
21
|
+
return !this.isEmpty && !this.runtime.isSendDisabled;
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
protected getAttachmentAdapter() {
|
|
21
25
|
return this.runtime.adapters?.attachments;
|
|
22
26
|
}
|
|
@@ -40,11 +44,18 @@ export class DefaultThreadComposerRuntimeCore
|
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
public connect() {
|
|
47
|
+
let lastIsSendDisabled = this.runtime.isSendDisabled;
|
|
43
48
|
return this.runtime.subscribe(() => {
|
|
49
|
+
let changed = false;
|
|
44
50
|
if (this.canCancel !== this.runtime.capabilities.cancel) {
|
|
45
51
|
this._canCancel = this.runtime.capabilities.cancel;
|
|
46
|
-
|
|
52
|
+
changed = true;
|
|
53
|
+
}
|
|
54
|
+
if (lastIsSendDisabled !== this.runtime.isSendDisabled) {
|
|
55
|
+
lastIsSendDisabled = this.runtime.isSendDisabled;
|
|
56
|
+
changed = true;
|
|
47
57
|
}
|
|
58
|
+
if (changed) this._notifySubscribers();
|
|
48
59
|
});
|
|
49
60
|
}
|
|
50
61
|
|
|
@@ -158,6 +158,12 @@ export type ThreadRuntimeCore = Readonly<{
|
|
|
158
158
|
|
|
159
159
|
capabilities: Readonly<RuntimeCapabilities>;
|
|
160
160
|
isDisabled: boolean;
|
|
161
|
+
/**
|
|
162
|
+
* Whether sending from this thread's composer is disabled. Surfaces the
|
|
163
|
+
* `isSendDisabled` flag from external-store adapters; internal runtimes
|
|
164
|
+
* default to `false`. Composer state derives `canSend` from this.
|
|
165
|
+
*/
|
|
166
|
+
isSendDisabled: boolean;
|
|
161
167
|
isLoading: boolean;
|
|
162
168
|
/**
|
|
163
169
|
* Optional explicit thread-level running flag. When provided, takes
|
|
@@ -59,7 +59,22 @@ type ExternalStoreMessageConverterAdapter<T> = {
|
|
|
59
59
|
};
|
|
60
60
|
|
|
61
61
|
type ExternalStoreAdapterBase<T> = {
|
|
62
|
+
/**
|
|
63
|
+
* Whether the entire thread is disabled. When `true`, the composer's input
|
|
64
|
+
* is also disabled (the user cannot type, attach files, or submit). For a
|
|
65
|
+
* narrower gate that keeps the input usable but blocks only sending, use
|
|
66
|
+
* `isSendDisabled`.
|
|
67
|
+
*/
|
|
62
68
|
isDisabled?: boolean | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* Whether sending new messages is currently disabled. When `true`, the
|
|
71
|
+
* thread composer's input remains usable but `send()` becomes a no-op
|
|
72
|
+
* and the thread composer's `canSend` is `false`. Use this to gate
|
|
73
|
+
* sending on external React state (e.g. while tool config is loading)
|
|
74
|
+
* without disabling the input itself the way `isDisabled` does. Edit
|
|
75
|
+
* composers (saving message edits) intentionally ignore this flag.
|
|
76
|
+
*/
|
|
77
|
+
isSendDisabled?: boolean | undefined;
|
|
63
78
|
/**
|
|
64
79
|
* Whether the thread is running. When provided, this value flows directly
|
|
65
80
|
* to `thread.isRunning`, letting the application keep the thread in a
|
|
@@ -78,14 +78,7 @@ export class ExternalStoreThreadListRuntimeCore
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
public getItemById(threadId: string) {
|
|
81
|
-
|
|
82
|
-
if (thread.id === threadId) return thread as any;
|
|
83
|
-
}
|
|
84
|
-
for (const thread of this.adapter.archivedThreads ?? []) {
|
|
85
|
-
if (thread.id === threadId) return thread as any;
|
|
86
|
-
}
|
|
87
|
-
if (threadId === DEFAULT_THREAD_ID) return DEFAULT_THREAD;
|
|
88
|
-
return undefined;
|
|
81
|
+
return this._threadData[threadId];
|
|
89
82
|
}
|
|
90
83
|
|
|
91
84
|
public __internal_setAdapter(
|
|
@@ -115,7 +108,8 @@ export class ExternalStoreThreadListRuntimeCore
|
|
|
115
108
|
|
|
116
109
|
if (
|
|
117
110
|
previousThreads !== newThreads ||
|
|
118
|
-
previousArchivedThreads !== newArchivedThreads
|
|
111
|
+
previousArchivedThreads !== newArchivedThreads ||
|
|
112
|
+
previousThreadId !== newThreadId
|
|
119
113
|
) {
|
|
120
114
|
this._threadData = {
|
|
121
115
|
...DEFAULT_THREAD_DATA,
|
|
@@ -159,6 +153,18 @@ export class ExternalStoreThreadListRuntimeCore
|
|
|
159
153
|
this._mainThread = this.threadFactory();
|
|
160
154
|
}
|
|
161
155
|
|
|
156
|
+
if (!this._threadData[this._mainThreadId]) {
|
|
157
|
+
this._threadData = {
|
|
158
|
+
...this._threadData,
|
|
159
|
+
[this._mainThreadId]: {
|
|
160
|
+
id: this._mainThreadId,
|
|
161
|
+
remoteId: undefined,
|
|
162
|
+
externalId: undefined,
|
|
163
|
+
status: "regular",
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
162
168
|
this._notifySubscribers();
|
|
163
169
|
}
|
|
164
170
|
|
|
@@ -53,6 +53,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
53
53
|
implements ThreadRuntimeCore
|
|
54
54
|
{
|
|
55
55
|
private _assistantOptimisticId: string | null = null;
|
|
56
|
+
private _lastSyncedMessageIds = new Set<string>();
|
|
56
57
|
|
|
57
58
|
private _capabilities: RuntimeCapabilities = {
|
|
58
59
|
switchToBranch: false,
|
|
@@ -75,6 +76,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
75
76
|
|
|
76
77
|
private _messages!: readonly ThreadMessage[];
|
|
77
78
|
public isDisabled!: boolean;
|
|
79
|
+
public isSendDisabled!: boolean;
|
|
78
80
|
public get isLoading() {
|
|
79
81
|
return this._store.isLoading ?? false;
|
|
80
82
|
}
|
|
@@ -122,6 +124,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
122
124
|
|
|
123
125
|
const isRunning = store.isRunning ?? false;
|
|
124
126
|
this.isDisabled = store.isDisabled ?? false;
|
|
127
|
+
this.isSendDisabled = store.isSendDisabled ?? false;
|
|
125
128
|
|
|
126
129
|
const oldStore = this._store as ExternalStoreAdapter<any> | undefined;
|
|
127
130
|
this._store = store;
|
|
@@ -168,6 +171,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
168
171
|
// Clear and import the message repository
|
|
169
172
|
this.repository.clear();
|
|
170
173
|
this._assistantOptimisticId = null;
|
|
174
|
+
this._lastSyncedMessageIds = new Set();
|
|
171
175
|
this.repository.import(store.messageRepository);
|
|
172
176
|
|
|
173
177
|
messages = this.repository.getMessages();
|
|
@@ -220,6 +224,12 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
220
224
|
return newMessage;
|
|
221
225
|
});
|
|
222
226
|
|
|
227
|
+
const nextIds = new Set(messages.map((m) => m.id));
|
|
228
|
+
for (const prevId of this._lastSyncedMessageIds) {
|
|
229
|
+
if (!nextIds.has(prevId)) this.repository.deleteMessage(prevId);
|
|
230
|
+
}
|
|
231
|
+
this._lastSyncedMessageIds = nextIds;
|
|
232
|
+
|
|
223
233
|
for (let i = 0; i < messages.length; i++) {
|
|
224
234
|
const message = messages[i]!;
|
|
225
235
|
const parent = messages[i - 1];
|
|
@@ -334,6 +344,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
334
344
|
previousMessage.id === messages.at(-1)?.id // ensure the previous message is a leaf node
|
|
335
345
|
) {
|
|
336
346
|
this.repository.deleteMessage(previousMessage.id);
|
|
347
|
+
this._lastSyncedMessageIds.delete(previousMessage.id);
|
|
337
348
|
if (!this.composer.text.trim()) {
|
|
338
349
|
this.composer.setText(getThreadMessageText(previousMessage));
|
|
339
350
|
}
|
|
@@ -362,6 +373,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
362
373
|
}
|
|
363
374
|
|
|
364
375
|
public override reset(initialMessages?: readonly ThreadMessageLike[]) {
|
|
376
|
+
this._lastSyncedMessageIds = new Set();
|
|
365
377
|
const repo = new MessageRepository();
|
|
366
378
|
repo.import(ExportedMessageRepository.fromArray(initialMessages ?? []));
|
|
367
379
|
this.updateMessages(repo.getMessages());
|
|
@@ -369,6 +381,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
369
381
|
|
|
370
382
|
public override import(data: ExportedMessageRepository) {
|
|
371
383
|
this._assistantOptimisticId = null;
|
|
384
|
+
this._lastSyncedMessageIds = new Set();
|
|
372
385
|
|
|
373
386
|
super.import(data);
|
|
374
387
|
|
|
@@ -118,6 +118,7 @@ export class ReadonlyThreadRuntimeCore
|
|
|
118
118
|
|
|
119
119
|
isEditing: false as const,
|
|
120
120
|
canCancel: false,
|
|
121
|
+
canSend: false,
|
|
121
122
|
isEmpty: true,
|
|
122
123
|
text: "",
|
|
123
124
|
|
|
@@ -205,6 +206,7 @@ export class ReadonlyThreadRuntimeCore
|
|
|
205
206
|
} as const;
|
|
206
207
|
|
|
207
208
|
isDisabled = false;
|
|
209
|
+
isSendDisabled = false;
|
|
208
210
|
isLoading = false;
|
|
209
211
|
|
|
210
212
|
state = null;
|
|
@@ -98,6 +98,7 @@ export const EMPTY_THREAD_CORE: ThreadRuntimeCore = {
|
|
|
98
98
|
isEditing: true,
|
|
99
99
|
|
|
100
100
|
canCancel: false,
|
|
101
|
+
canSend: false,
|
|
101
102
|
isEmpty: true,
|
|
102
103
|
|
|
103
104
|
text: "",
|
|
@@ -186,6 +187,7 @@ export const EMPTY_THREAD_CORE: ThreadRuntimeCore = {
|
|
|
186
187
|
},
|
|
187
188
|
|
|
188
189
|
isDisabled: false,
|
|
190
|
+
isSendDisabled: false,
|
|
189
191
|
isLoading: true,
|
|
190
192
|
|
|
191
193
|
messages: [],
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { createResourceRoot } from "@assistant-ui/tap";
|
|
3
|
+
import type { Tool } from "assistant-stream";
|
|
4
|
+
import { ModelContext } from "./model-context-client";
|
|
5
|
+
import type {
|
|
6
|
+
ModelContext as ModelContextValue,
|
|
7
|
+
ModelContextProvider,
|
|
8
|
+
} from "../../model-context/types";
|
|
9
|
+
|
|
10
|
+
const tick = () => new Promise<void>((resolve) => setTimeout(resolve, 0));
|
|
11
|
+
|
|
12
|
+
const provider = (ctx: ModelContextValue): ModelContextProvider => ({
|
|
13
|
+
getModelContext: () => ctx,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const stubTool = (): Tool<any, any> =>
|
|
17
|
+
({ description: "", parameters: {} as any }) as unknown as Tool<any, any>;
|
|
18
|
+
|
|
19
|
+
const render = () => {
|
|
20
|
+
const root = createResourceRoot();
|
|
21
|
+
const sub = root.render(ModelContext());
|
|
22
|
+
return { sub, unmount: () => root.unmount() };
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
describe("ModelContext", () => {
|
|
26
|
+
it("starts with undefined modelName and an empty toolNames array", () => {
|
|
27
|
+
const { sub, unmount } = render();
|
|
28
|
+
try {
|
|
29
|
+
const state = sub.getValue().getState();
|
|
30
|
+
expect(state.modelName).toBeUndefined();
|
|
31
|
+
expect(state.toolNames).toEqual([]);
|
|
32
|
+
} finally {
|
|
33
|
+
unmount();
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("reflects modelName from a registered provider", async () => {
|
|
38
|
+
const { sub, unmount } = render();
|
|
39
|
+
try {
|
|
40
|
+
sub.getValue().register(provider({ config: { modelName: "gpt-4" } }));
|
|
41
|
+
await tick();
|
|
42
|
+
|
|
43
|
+
expect(sub.getValue().getState().modelName).toBe("gpt-4");
|
|
44
|
+
} finally {
|
|
45
|
+
unmount();
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("reflects tool names from a registered provider", async () => {
|
|
50
|
+
const { sub, unmount } = render();
|
|
51
|
+
try {
|
|
52
|
+
sub
|
|
53
|
+
.getValue()
|
|
54
|
+
.register(provider({ tools: { foo: stubTool(), bar: stubTool() } }));
|
|
55
|
+
await tick();
|
|
56
|
+
|
|
57
|
+
expect(sub.getValue().getState().toolNames).toEqual(["bar", "foo"]);
|
|
58
|
+
} finally {
|
|
59
|
+
unmount();
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("keeps the same state reference when an extra provider does not change the merged values", async () => {
|
|
64
|
+
const { sub, unmount } = render();
|
|
65
|
+
try {
|
|
66
|
+
sub.getValue().register(provider({ config: { modelName: "gpt-4" } }));
|
|
67
|
+
await tick();
|
|
68
|
+
const before = sub.getValue().getState();
|
|
69
|
+
|
|
70
|
+
sub.getValue().register(provider({ config: { modelName: "gpt-4" } }));
|
|
71
|
+
await tick();
|
|
72
|
+
const after = sub.getValue().getState();
|
|
73
|
+
|
|
74
|
+
expect(after).toBe(before);
|
|
75
|
+
} finally {
|
|
76
|
+
unmount();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("clears modelName when the last contributing provider unsubscribes", async () => {
|
|
81
|
+
const { sub, unmount } = render();
|
|
82
|
+
try {
|
|
83
|
+
const unsubscribe = sub
|
|
84
|
+
.getValue()
|
|
85
|
+
.register(provider({ config: { modelName: "gpt-4" } }));
|
|
86
|
+
await tick();
|
|
87
|
+
expect(sub.getValue().getState().modelName).toBe("gpt-4");
|
|
88
|
+
|
|
89
|
+
unsubscribe();
|
|
90
|
+
await tick();
|
|
91
|
+
expect(sub.getValue().getState().modelName).toBeUndefined();
|
|
92
|
+
expect(sub.getValue().getState().toolNames).toEqual([]);
|
|
93
|
+
} finally {
|
|
94
|
+
unmount();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("reflects modelName synchronously after register without awaiting", () => {
|
|
99
|
+
const { sub, unmount } = render();
|
|
100
|
+
try {
|
|
101
|
+
sub.getValue().register(provider({ config: { modelName: "gpt-4" } }));
|
|
102
|
+
|
|
103
|
+
expect(sub.getValue().getState().modelName).toBe("gpt-4");
|
|
104
|
+
} finally {
|
|
105
|
+
unmount();
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -1,18 +1,48 @@
|
|
|
1
|
-
import { resource, tapMemo, tapState } from "@assistant-ui/tap";
|
|
1
|
+
import { resource, tapEffect, tapMemo, tapState } from "@assistant-ui/tap";
|
|
2
2
|
import type { ClientOutput } from "@assistant-ui/store";
|
|
3
3
|
import { CompositeContextProvider } from "../../utils/composite-context-provider";
|
|
4
4
|
import type { ModelContextState } from "../scopes/model-context";
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const EMPTY_TOOL_NAMES: readonly string[] = [];
|
|
7
|
+
|
|
8
|
+
const INITIAL_STATE: ModelContextState = {
|
|
9
|
+
modelName: undefined,
|
|
10
|
+
toolNames: EMPTY_TOOL_NAMES,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const toolNamesEqual = (a: readonly string[], b: readonly string[]): boolean =>
|
|
14
|
+
a === b || (a.length === b.length && a.every((v, i) => v === b[i]));
|
|
15
|
+
|
|
16
|
+
const deriveState = (
|
|
17
|
+
composite: CompositeContextProvider,
|
|
18
|
+
prev: ModelContextState,
|
|
19
|
+
): ModelContextState => {
|
|
20
|
+
const ctx = composite.getModelContext();
|
|
21
|
+
const modelName = ctx.config?.modelName;
|
|
22
|
+
const keys = ctx.tools ? Object.keys(ctx.tools).sort() : EMPTY_TOOL_NAMES;
|
|
23
|
+
const toolNames = keys.length ? keys : EMPTY_TOOL_NAMES;
|
|
24
|
+
|
|
25
|
+
if (modelName === prev.modelName && toolNamesEqual(toolNames, prev.toolNames))
|
|
26
|
+
return prev;
|
|
27
|
+
|
|
28
|
+
return { modelName, toolNames };
|
|
29
|
+
};
|
|
7
30
|
|
|
8
31
|
export const ModelContext = resource((): ClientOutput<"modelContext"> => {
|
|
9
|
-
const [state] = tapState<ModelContextState>(
|
|
10
|
-
() => ({ version: version + 1 }) as unknown as ModelContextState,
|
|
11
|
-
);
|
|
12
32
|
const composite = tapMemo(() => new CompositeContextProvider(), []);
|
|
33
|
+
const [state, setState] = tapState<ModelContextState>(() =>
|
|
34
|
+
deriveState(composite, INITIAL_STATE),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
tapEffect(() => {
|
|
38
|
+
setState((prev) => deriveState(composite, prev));
|
|
39
|
+
return composite.subscribe(() => {
|
|
40
|
+
setState((prev) => deriveState(composite, prev));
|
|
41
|
+
});
|
|
42
|
+
}, [composite]);
|
|
13
43
|
|
|
14
44
|
return {
|
|
15
|
-
getState: () => state,
|
|
45
|
+
getState: () => deriveState(composite, state),
|
|
16
46
|
getModelContext: () => composite.getModelContext(),
|
|
17
47
|
subscribe: (callback) => composite.subscribe(callback),
|
|
18
48
|
register: (provider) => composite.registerModelContextProvider(provider),
|
|
@@ -103,6 +103,7 @@ export const ComposerClient = resource(
|
|
|
103
103
|
runConfig: runtimeState.runConfig,
|
|
104
104
|
isEditing: runtimeState.isEditing,
|
|
105
105
|
canCancel: runtimeState.canCancel,
|
|
106
|
+
canSend: runtimeState.canSend,
|
|
106
107
|
attachmentAccept: runtimeState.attachmentAccept,
|
|
107
108
|
isEmpty: runtimeState.isEmpty,
|
|
108
109
|
type: runtimeState.type ?? "thread",
|
|
@@ -26,6 +26,15 @@ export type ComposerState = {
|
|
|
26
26
|
readonly runConfig: RunConfig;
|
|
27
27
|
readonly isEditing: boolean;
|
|
28
28
|
readonly canCancel: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Whether the composer is currently willing to send. `true` when the
|
|
31
|
+
* composer is in editing mode and has non-empty content; for thread
|
|
32
|
+
* composers also requires the thread's `isSendDisabled` flag to be unset.
|
|
33
|
+
* Edit composers (saving message edits) ignore `isSendDisabled` since it
|
|
34
|
+
* is a thread-scoped gate. Cross-thread gating (running, queue capability)
|
|
35
|
+
* is layered on top by `useComposerSend`.
|
|
36
|
+
*/
|
|
37
|
+
readonly canSend: boolean;
|
|
29
38
|
readonly attachmentAccept: string;
|
|
30
39
|
readonly isEmpty: boolean;
|
|
31
40
|
readonly type: "thread" | "edit";
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { Unsubscribe } from "../../types/unsubscribe";
|
|
2
2
|
import type { ModelContextProvider } from "../../model-context/types";
|
|
3
3
|
|
|
4
|
-
export type ModelContextState =
|
|
4
|
+
export type ModelContextState = {
|
|
5
|
+
readonly modelName?: string | undefined;
|
|
6
|
+
readonly toolNames: readonly string[];
|
|
7
|
+
};
|
|
5
8
|
|
|
6
9
|
export type ModelContextMethods = ModelContextProvider & {
|
|
7
10
|
getState(): ModelContextState;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { auiV0Encode } from "../react/runtimes/cloud/auiV0";
|
|
3
|
+
|
|
4
|
+
describe("auiV0Encode", () => {
|
|
5
|
+
it("preserves document source parts in the core cloud encoder", () => {
|
|
6
|
+
const encoded = auiV0Encode({
|
|
7
|
+
id: "m1",
|
|
8
|
+
createdAt: new Date("2026-03-15T00:00:00.000Z"),
|
|
9
|
+
role: "assistant",
|
|
10
|
+
status: { type: "complete", reason: "stop" },
|
|
11
|
+
metadata: {
|
|
12
|
+
unstable_state: undefined,
|
|
13
|
+
unstable_annotations: [],
|
|
14
|
+
unstable_data: [],
|
|
15
|
+
steps: [],
|
|
16
|
+
custom: {},
|
|
17
|
+
},
|
|
18
|
+
content: [
|
|
19
|
+
{
|
|
20
|
+
type: "source",
|
|
21
|
+
sourceType: "document",
|
|
22
|
+
id: "doc_123",
|
|
23
|
+
title: "proposal.pdf",
|
|
24
|
+
mediaType: "application/pdf",
|
|
25
|
+
filename: "proposal.pdf",
|
|
26
|
+
providerMetadata: {
|
|
27
|
+
openai: {
|
|
28
|
+
type: "file_citation",
|
|
29
|
+
fileId: "file_123",
|
|
30
|
+
index: 0,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
expect(encoded.content).toEqual([
|
|
38
|
+
{
|
|
39
|
+
type: "source",
|
|
40
|
+
sourceType: "document",
|
|
41
|
+
id: "doc_123",
|
|
42
|
+
title: "proposal.pdf",
|
|
43
|
+
mediaType: "application/pdf",
|
|
44
|
+
filename: "proposal.pdf",
|
|
45
|
+
providerMetadata: {
|
|
46
|
+
openai: {
|
|
47
|
+
type: "file_citation",
|
|
48
|
+
fileId: "file_123",
|
|
49
|
+
index: 0,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
55
|
+
});
|