@assistant-ui/core 0.3.5 → 0.3.7
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 -4
- package/dist/index.js.map +1 -1
- package/dist/internal.js +1 -1
- package/dist/react/AssistantProvider.d.ts +3 -2
- package/dist/react/AssistantProvider.d.ts.map +1 -1
- package/dist/react/AssistantProvider.js +19 -26
- package/dist/react/AssistantProvider.js.map +1 -1
- package/dist/react/AssistantRuntimeProvider.d.ts +7 -2
- package/dist/react/AssistantRuntimeProvider.d.ts.map +1 -1
- package/dist/react/AssistantRuntimeProvider.js +9 -7
- package/dist/react/AssistantRuntimeProvider.js.map +1 -1
- package/dist/react/RuntimeAdapter.js +1 -1
- package/dist/react/RuntimeAdapter.js.map +1 -1
- package/dist/react/client/Interactables.js +1 -1
- package/dist/react/client/Interactables.js.map +1 -1
- package/dist/react/client/Tools.d.ts +1 -1
- package/dist/react/client/Tools.js +1 -1
- package/dist/react/client/Tools.js.map +1 -1
- package/dist/react/index.d.ts +9 -9
- package/dist/react/index.js +4 -4
- package/dist/react/model-context/interactableTool.d.ts +1 -1
- package/dist/react/model-context/useAssistantTool.d.ts +1 -1
- package/dist/react/providers/AttachmentByIndexProvider.d.ts.map +1 -1
- package/dist/react/providers/AttachmentByIndexProvider.js +33 -39
- package/dist/react/providers/AttachmentByIndexProvider.js.map +1 -1
- package/dist/react/providers/ChainOfThoughtByIndicesProvider.d.ts.map +1 -1
- package/dist/react/providers/ChainOfThoughtByIndicesProvider.js +15 -20
- package/dist/react/providers/ChainOfThoughtByIndicesProvider.js.map +1 -1
- package/dist/react/providers/ChainOfThoughtPartByIndexProvider.js +17 -20
- package/dist/react/providers/ChainOfThoughtPartByIndexProvider.js.map +1 -1
- package/dist/react/providers/MessageByIdProvider.js +28 -38
- package/dist/react/providers/MessageByIdProvider.js.map +1 -1
- package/dist/react/providers/MessageByIndexProvider.js +28 -38
- package/dist/react/providers/MessageByIndexProvider.js.map +1 -1
- package/dist/react/providers/PartByIndexProvider.js +17 -20
- package/dist/react/providers/PartByIndexProvider.js.map +1 -1
- package/dist/react/providers/QueueItemByIndexProvider.js +21 -21
- package/dist/react/providers/QueueItemByIndexProvider.js.map +1 -1
- package/dist/react/providers/ReadonlyThreadProvider.js +24 -32
- package/dist/react/providers/ReadonlyThreadProvider.js.map +1 -1
- package/dist/react/providers/SuggestionByIndexProvider.js +17 -20
- package/dist/react/providers/SuggestionByIndexProvider.js.map +1 -1
- package/dist/react/providers/TextMessagePartProvider.d.ts.map +1 -1
- package/dist/react/providers/TextMessagePartProvider.js +16 -19
- package/dist/react/providers/TextMessagePartProvider.js.map +1 -1
- package/dist/react/providers/ThreadListItemByIndexProvider.js +17 -20
- package/dist/react/providers/ThreadListItemByIndexProvider.js.map +1 -1
- package/dist/react/providers/ThreadListItemRuntimeProvider.js +15 -18
- package/dist/react/providers/ThreadListItemRuntimeProvider.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +7 -3
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +5 -2
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +3 -2
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/createMessageConverter.d.ts +1 -1
- package/dist/react/runtimes/createRuntimeExtras.d.ts +4 -1
- package/dist/react/runtimes/createRuntimeExtras.d.ts.map +1 -1
- package/dist/react/runtimes/createRuntimeExtras.js +6 -17
- package/dist/react/runtimes/createRuntimeExtras.js.map +1 -1
- package/dist/react/runtimes/useLocalRuntime.d.ts +3 -1
- package/dist/react/runtimes/useLocalRuntime.d.ts.map +1 -1
- package/dist/react/runtimes/useLocalRuntime.js +4 -2
- package/dist/react/runtimes/useLocalRuntime.js.map +1 -1
- package/dist/react/types/scopes/dataRenderers.d.ts +1 -1
- package/dist/react/types/scopes/interactables.d.ts +1 -1
- package/dist/react/types/scopes/tools.d.ts +1 -1
- package/dist/runtime/api/composer-runtime.d.ts +5 -1
- package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
- package/dist/runtime/api/composer-runtime.js +8 -1
- package/dist/runtime/api/composer-runtime.js.map +1 -1
- package/dist/runtime/api/message-runtime.js +1 -1
- package/dist/runtime/api/message-runtime.js.map +1 -1
- package/dist/runtime/api/thread-runtime.d.ts +4 -2
- package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts +2 -1
- 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/default-thread-composer-runtime-core.d.ts +3 -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 +14 -3
- package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
- package/dist/runtime/interfaces/composer-runtime-core.d.ts +2 -1
- package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/interfaces/thread-runtime-core.d.ts +3 -1
- package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/queue/external-thread-queue-adapter.d.ts +19 -6
- package/dist/runtime/queue/external-thread-queue-adapter.d.ts.map +1 -1
- package/dist/runtime/queue/message-queue.d.ts +10 -1
- package/dist/runtime/queue/message-queue.d.ts.map +1 -1
- package/dist/runtime/queue/message-queue.js +138 -36
- package/dist/runtime/queue/message-queue.js.map +1 -1
- package/dist/runtime/utils/runtime-extras-brand.d.ts +21 -0
- package/dist/runtime/utils/runtime-extras-brand.d.ts.map +1 -0
- package/dist/runtime/utils/runtime-extras-brand.js +37 -0
- package/dist/runtime/utils/runtime-extras-brand.js.map +1 -0
- package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
- 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 +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +3 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js +7 -6
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/local/local-runtime-options.d.ts +13 -0
- package/dist/runtimes/local/local-runtime-options.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts +5 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +30 -14
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
- package/dist/runtimes/remote-thread-list/empty-thread-core.js +1 -1
- package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
- package/dist/runtimes/remote-thread-list/types.d.ts +6 -3
- package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
- package/dist/store/clients/runtime-adapter.d.ts.map +1 -1
- package/dist/store/clients/runtime-adapter.js +1 -1
- package/dist/store/clients/runtime-adapter.js.map +1 -1
- package/dist/store/clients/suggestions.js +1 -1
- package/dist/store/clients/suggestions.js.map +1 -1
- package/dist/store/clients/thread-message-client.js +1 -1
- package/dist/store/clients/thread-message-client.js.map +1 -1
- package/dist/store/index.d.ts +10 -1
- package/dist/store/index.js +7 -1
- package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.js +12 -5
- package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
- package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/message-runtime-client.js +1 -1
- package/dist/store/runtime-clients/message-runtime-client.js.map +1 -1
- 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 +1 -1
- 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.map +1 -1
- package/dist/store/runtime-clients/thread-list-runtime-client.js +1 -1
- package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
- package/dist/store/runtime-clients/thread-runtime-client.js +1 -1
- package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
- package/dist/store/scopes/composer.d.ts +7 -3
- package/dist/store/scopes/composer.d.ts.map +1 -1
- package/dist/store/scopes/queue-item.d.ts +10 -0
- package/dist/store/scopes/queue-item.d.ts.map +1 -1
- package/dist/store/scopes/queue-item.js.map +1 -1
- package/package.json +15 -15
- package/src/index.ts +5 -1
- package/src/react/AssistantProvider.tsx +20 -18
- package/src/react/AssistantRuntimeProvider.test.tsx +55 -1
- package/src/react/AssistantRuntimeProvider.tsx +12 -2
- package/src/react/RuntimeAdapter.test.ts +85 -0
- package/src/react/RuntimeAdapter.ts +1 -1
- package/src/react/client/Interactables.test.ts +3 -2
- package/src/react/client/Interactables.ts +4 -4
- package/src/react/client/Tools.ts +4 -4
- package/src/react/index.ts +1 -0
- package/src/react/providers/AttachmentByIndexProvider.tsx +15 -7
- package/src/react/providers/ChainOfThoughtByIndicesProvider.tsx +12 -4
- package/src/react/providers/ChainOfThoughtPartByIndexProvider.tsx +8 -4
- package/src/react/providers/MessageByIdProvider.tsx +8 -4
- package/src/react/providers/MessageByIndexProvider.tsx +8 -4
- package/src/react/providers/PartByIndexProvider.tsx +8 -4
- package/src/react/providers/QueueItemByIndexProvider.tsx +9 -5
- package/src/react/providers/ReadonlyThreadProvider.tsx +9 -4
- package/src/react/providers/SuggestionByIndexProvider.tsx +8 -4
- package/src/react/providers/TextMessagePartProvider.tsx +13 -4
- package/src/react/providers/ThreadListItemByIndexProvider.tsx +8 -4
- package/src/react/providers/ThreadListItemRuntimeProvider.tsx +8 -4
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +19 -4
- package/src/react/runtimes/createRuntimeExtras.test.ts +14 -1
- package/src/react/runtimes/createRuntimeExtras.ts +15 -26
- package/src/react/runtimes/useLocalRuntime.ts +4 -0
- package/src/runtime/api/composer-runtime.ts +11 -2
- package/src/runtime/api/message-runtime.test.ts +25 -0
- package/src/runtime/api/message-runtime.ts +1 -1
- package/src/runtime/base/base-composer-runtime-core.ts +5 -1
- package/src/runtime/base/default-thread-composer-runtime-core.ts +27 -3
- package/src/runtime/interfaces/composer-runtime-core.ts +2 -1
- package/src/runtime/interfaces/thread-runtime-core.ts +3 -1
- package/src/runtime/queue/external-thread-queue-adapter.ts +19 -3
- package/src/runtime/queue/message-queue.ts +200 -38
- package/src/runtime/utils/runtime-extras-brand.ts +47 -0
- package/src/runtime/utils/thread-message-like.ts +2 -4
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +10 -8
- package/src/runtimes/local/local-runtime-options.ts +15 -0
- package/src/runtimes/local/local-thread-runtime-core.test.ts +43 -0
- package/src/runtimes/local/local-thread-runtime-core.ts +48 -13
- package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +1 -1
- package/src/runtimes/remote-thread-list/empty-thread-core.ts +1 -1
- package/src/runtimes/remote-thread-list/types.ts +9 -2
- package/src/store/clients/runtime-adapter.ts +2 -5
- package/src/store/clients/suggestions.ts +1 -1
- package/src/store/clients/thread-message-client.ts +1 -1
- package/src/store/index.ts +31 -0
- package/src/store/runtime-clients/composer-runtime-client.ts +15 -10
- package/src/store/runtime-clients/message-runtime-client.ts +2 -5
- package/src/store/runtime-clients/thread-list-item-runtime-client.ts +2 -1
- package/src/store/runtime-clients/thread-list-runtime-client.ts +2 -5
- package/src/store/runtime-clients/thread-runtime-client.ts +2 -2
- package/src/store/scopes/composer.ts +6 -4
- package/src/store/scopes/queue-item.ts +8 -1
- package/src/tests/external-store-thread-runtime-core.test.ts +56 -13
- package/src/tests/framework-neutral-boundary.test.ts +257 -0
- package/src/tests/message-queue.test.ts +494 -34
- package/src/tests/thread-message-like.test.ts +66 -0
- package/LICENSE +0 -21
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import type { ComponentType, PropsWithChildren } from "react";
|
|
2
1
|
import type { ThreadMessage } from "../../types/message";
|
|
3
2
|
import type { AssistantRuntime } from "../../runtime/api/assistant-runtime";
|
|
4
3
|
import type { AssistantStream } from "assistant-stream";
|
|
5
4
|
|
|
5
|
+
/* oxlint-disable typescript/no-explicit-any -- structural stand-in for ComponentType without depending on react types */
|
|
6
|
+
type RemoteThreadListProviderProps = { children?: any };
|
|
7
|
+
|
|
8
|
+
export type RemoteThreadListProviderComponent =
|
|
9
|
+
| ((props: RemoteThreadListProviderProps) => any)
|
|
10
|
+
| (new (props: RemoteThreadListProviderProps) => any);
|
|
11
|
+
/* oxlint-enable typescript/no-explicit-any */
|
|
12
|
+
|
|
6
13
|
export type RemoteThreadInitializeResponse = {
|
|
7
14
|
remoteId: string;
|
|
8
15
|
externalId?: string | undefined;
|
|
@@ -54,7 +61,7 @@ export type RemoteThreadListAdapter = {
|
|
|
54
61
|
* is unsupported and leaves thread context unavailable to downstream
|
|
55
62
|
* consumers. Load data inside an always-mounted child instead.
|
|
56
63
|
*/
|
|
57
|
-
unstable_Provider?:
|
|
64
|
+
unstable_Provider?: RemoteThreadListProviderComponent | undefined;
|
|
58
65
|
};
|
|
59
66
|
|
|
60
67
|
export type RemoteThreadListOptions = {
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
type ScopesConfig,
|
|
4
|
-
type AssistantClient,
|
|
5
|
-
} from "@assistant-ui/store";
|
|
1
|
+
import type { AssistantClient, ScopesConfig } from "@assistant-ui/store";
|
|
2
|
+
import { Derived } from "@assistant-ui/store/client";
|
|
6
3
|
import { ModelContext } from "./model-context-client";
|
|
7
4
|
import { Suggestions } from "./suggestions";
|
|
8
5
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
import { resource, withKey } from "@assistant-ui/tap";
|
|
3
3
|
import type { ClientOutput } from "@assistant-ui/store";
|
|
4
|
-
import { useClientLookup } from "@assistant-ui/store";
|
|
4
|
+
import { useClientLookup } from "@assistant-ui/store/client";
|
|
5
5
|
import type { SuggestionsState } from "../scopes/suggestions";
|
|
6
6
|
import type { SuggestionState } from "../scopes/suggestion";
|
|
7
7
|
|
|
@@ -7,7 +7,7 @@ import type { Attachment } from "../../types/attachment";
|
|
|
7
7
|
import { useMemo, useState } from "react";
|
|
8
8
|
import { useResource, resource, withKey } from "@assistant-ui/tap";
|
|
9
9
|
import type { ClientOutput } from "@assistant-ui/store";
|
|
10
|
-
import { useClientLookup } from "@assistant-ui/store";
|
|
10
|
+
import { useClientLookup } from "@assistant-ui/store/client";
|
|
11
11
|
import type { MessageState } from "../scopes/message";
|
|
12
12
|
import type { PartState } from "../scopes/part";
|
|
13
13
|
import { NoOpComposerClient } from "./no-op-composer-client";
|
package/src/store/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/// <reference path="./scope-registration.ts" />
|
|
2
|
+
/// <reference path="../react/types/store-augmentation.ts" />
|
|
2
3
|
|
|
3
4
|
// scopes
|
|
4
5
|
export type {
|
|
@@ -77,6 +78,36 @@ export type {
|
|
|
77
78
|
ChainOfThoughtPart,
|
|
78
79
|
} from "./scopes/chain-of-thought";
|
|
79
80
|
|
|
81
|
+
// runtime wiring
|
|
82
|
+
export { RuntimeAdapter } from "../react/RuntimeAdapter";
|
|
83
|
+
export {
|
|
84
|
+
useExternalMessageConverter,
|
|
85
|
+
convertExternalMessages,
|
|
86
|
+
type JoinStrategy,
|
|
87
|
+
} from "../react/runtimes/external-message-converter";
|
|
88
|
+
export {
|
|
89
|
+
useStreamingTiming,
|
|
90
|
+
type StreamingTimingAccessors,
|
|
91
|
+
type StreamingTimingOptions,
|
|
92
|
+
type StreamingTimingState,
|
|
93
|
+
} from "../react/runtimes/useStreamingTiming";
|
|
94
|
+
export {
|
|
95
|
+
createRuntimeExtrasBrand,
|
|
96
|
+
type RuntimeExtrasBrand,
|
|
97
|
+
} from "../runtime/utils/runtime-extras-brand";
|
|
98
|
+
export { defineToolkit } from "../react/model-context/define-toolkit";
|
|
99
|
+
export {
|
|
100
|
+
defineMcpToolkit,
|
|
101
|
+
type McpToolkitDefinition,
|
|
102
|
+
type McpToolkitEntry,
|
|
103
|
+
type McpToolkitToolConfig,
|
|
104
|
+
} from "../react/model-context/define-mcp-toolkit";
|
|
105
|
+
export type {
|
|
106
|
+
Toolkit,
|
|
107
|
+
ToolkitDefinition,
|
|
108
|
+
ToolkitDefinitionEntry,
|
|
109
|
+
} from "../react/model-context/toolbox";
|
|
110
|
+
|
|
80
111
|
// clients
|
|
81
112
|
export { NoOpComposerClient } from "./clients/no-op-composer-client";
|
|
82
113
|
export { Suggestions, type SuggestionConfig } from "./clients/suggestions";
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import type { Unsubscribe } from "../../types/unsubscribe";
|
|
2
2
|
import { useMemo, useEffect } from "react";
|
|
3
3
|
import { useResource, resource, withKey } from "@assistant-ui/tap";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
useAssistantEmit,
|
|
7
|
-
useClientLookup,
|
|
8
|
-
} from "@assistant-ui/store";
|
|
4
|
+
import type { ClientOutput } from "@assistant-ui/store";
|
|
5
|
+
import { useAssistantEmit, useClientLookup } from "@assistant-ui/store/client";
|
|
9
6
|
import type {
|
|
10
7
|
ComposerRuntime,
|
|
11
8
|
EditComposerRuntime,
|
|
12
9
|
} from "../../runtime/api/composer-runtime";
|
|
13
10
|
import type { ComposerState } from "../scopes/composer";
|
|
14
11
|
import type { QueueItemState } from "../scopes/queue-item";
|
|
12
|
+
import type { QueuePlacement } from "../../runtime/queue/external-thread-queue-adapter";
|
|
15
13
|
import { AttachmentRuntimeClient } from "./attachment-runtime-client";
|
|
16
14
|
import { useSubscribable } from "./useSubscribable";
|
|
17
15
|
|
|
@@ -40,16 +38,17 @@ const ComposerAttachmentClientByIndex = resource(
|
|
|
40
38
|
|
|
41
39
|
const useQueueItemClient = ({
|
|
42
40
|
item,
|
|
43
|
-
|
|
41
|
+
onMove,
|
|
44
42
|
onRemove,
|
|
45
43
|
}: {
|
|
46
44
|
item: QueueItemState;
|
|
47
|
-
|
|
45
|
+
onMove: (placement: QueuePlacement) => void;
|
|
48
46
|
onRemove: () => void;
|
|
49
47
|
}): ClientOutput<"queueItem"> => {
|
|
50
48
|
return {
|
|
51
49
|
getState: () => item,
|
|
52
|
-
steer:
|
|
50
|
+
steer: () => onMove({ lane: "steer", insertAfter: null }),
|
|
51
|
+
move: onMove,
|
|
53
52
|
remove: onRemove,
|
|
54
53
|
};
|
|
55
54
|
};
|
|
@@ -121,7 +120,7 @@ const useComposerClient = ({
|
|
|
121
120
|
item.id,
|
|
122
121
|
QueueItemClient({
|
|
123
122
|
item,
|
|
124
|
-
|
|
123
|
+
onMove: (placement) => runtime.moveQueueItem(item.id, placement),
|
|
125
124
|
onRemove: () => runtime.removeQueueItem(item.id),
|
|
126
125
|
}),
|
|
127
126
|
),
|
|
@@ -171,7 +170,13 @@ const useComposerClient = ({
|
|
|
171
170
|
return attachments.get(selector);
|
|
172
171
|
}
|
|
173
172
|
},
|
|
174
|
-
queueItem: (selector) =>
|
|
173
|
+
queueItem: (selector) => {
|
|
174
|
+
if ("id" in selector) {
|
|
175
|
+
return queueItems.get({ key: selector.id });
|
|
176
|
+
} else {
|
|
177
|
+
return queueItems.get(selector);
|
|
178
|
+
}
|
|
179
|
+
},
|
|
175
180
|
__internal_getRuntime: () => runtime,
|
|
176
181
|
};
|
|
177
182
|
};
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { useMemo, useState } from "react";
|
|
2
2
|
import { useResource, withKey, resource } from "@assistant-ui/tap";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
useClientLookup,
|
|
6
|
-
useClientResource,
|
|
7
|
-
} from "@assistant-ui/store";
|
|
3
|
+
import type { ClientOutput } from "@assistant-ui/store";
|
|
4
|
+
import { useClientLookup, useClientResource } from "@assistant-ui/store/client";
|
|
8
5
|
import type { MessageRuntime } from "../../runtime/api/message-runtime";
|
|
9
6
|
import { useSubscribable } from "./useSubscribable";
|
|
10
7
|
import { liveRef } from "./liveRef";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Unsubscribe } from "../../types/unsubscribe";
|
|
2
2
|
import { useEffect, useMemo } from "react";
|
|
3
3
|
import { resource } from "@assistant-ui/tap";
|
|
4
|
-
import {
|
|
4
|
+
import type { ClientOutput } from "@assistant-ui/store";
|
|
5
|
+
import { useAssistantEmit } from "@assistant-ui/store/client";
|
|
5
6
|
import type {
|
|
6
7
|
ThreadListItemEventType,
|
|
7
8
|
ThreadListItemRuntime,
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
2
|
import { useResource, withKey, resource } from "@assistant-ui/tap";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
useClientLookup,
|
|
6
|
-
useClientResource,
|
|
7
|
-
} from "@assistant-ui/store";
|
|
3
|
+
import type { ClientOutput } from "@assistant-ui/store";
|
|
4
|
+
import { useClientLookup, useClientResource } from "@assistant-ui/store/client";
|
|
8
5
|
import type { ThreadListRuntime } from "../../runtime/api/thread-list-runtime";
|
|
9
6
|
import type { AssistantRuntime } from "../../runtime/api/assistant-runtime";
|
|
10
7
|
import { useSubscribable } from "./useSubscribable";
|
|
@@ -4,12 +4,12 @@ import type { ThreadRuntime } from "../../runtime/api/thread-runtime";
|
|
|
4
4
|
import { useMemo, useEffect, type RefObject } from "react";
|
|
5
5
|
import { useResource, resource, withKey } from "@assistant-ui/tap";
|
|
6
6
|
import { liveRef } from "./liveRef";
|
|
7
|
+
import type { ClientOutput } from "@assistant-ui/store";
|
|
7
8
|
import {
|
|
8
|
-
type ClientOutput,
|
|
9
9
|
useAssistantEmit,
|
|
10
10
|
useClientLookup,
|
|
11
11
|
useClientResource,
|
|
12
|
-
} from "@assistant-ui/store";
|
|
12
|
+
} from "@assistant-ui/store/client";
|
|
13
13
|
import { ComposerClient } from "./composer-runtime-client";
|
|
14
14
|
import { MessageClient } from "./message-runtime-client";
|
|
15
15
|
import { useSubscribable } from "./useSubscribable";
|
|
@@ -13,8 +13,10 @@ import type { QueueItemState, QueueItemMethods } from "./queue-item";
|
|
|
13
13
|
|
|
14
14
|
export type ComposerSendOptions = SendOptions & {
|
|
15
15
|
/**
|
|
16
|
-
* Whether to steer (
|
|
17
|
-
*
|
|
16
|
+
* Whether to steer (process this message next, interrupting the current run
|
|
17
|
+
* when the runtime supports it). Defaults to true while a queued run is in
|
|
18
|
+
* flight and false when idle. Pass false to queue behind the pending
|
|
19
|
+
* messages.
|
|
18
20
|
*/
|
|
19
21
|
steer?: boolean;
|
|
20
22
|
};
|
|
@@ -88,9 +90,9 @@ export type ComposerMethods = {
|
|
|
88
90
|
setQuote(quote: QuoteInfo | undefined): void;
|
|
89
91
|
|
|
90
92
|
/**
|
|
91
|
-
* Access a queue item by index.
|
|
93
|
+
* Access a queue item by index or id.
|
|
92
94
|
*/
|
|
93
|
-
queueItem(selector: { index: number }): QueueItemMethods;
|
|
95
|
+
queueItem(selector: { index: number } | { id: string }): QueueItemMethods;
|
|
94
96
|
|
|
95
97
|
__internal_getRuntime?(): ComposerRuntime;
|
|
96
98
|
};
|
|
@@ -1,19 +1,26 @@
|
|
|
1
|
+
import type { FileMessagePart, TextMessagePart } from "../../types/message";
|
|
2
|
+
import type { QueuePlacement } from "../../runtime/queue/external-thread-queue-adapter";
|
|
3
|
+
|
|
1
4
|
export type QueueItemState = {
|
|
2
5
|
readonly id: string;
|
|
6
|
+
/** @deprecated Derive from the text parts of `parts` instead. Removal after 2026-11-05. */
|
|
3
7
|
readonly prompt: string;
|
|
8
|
+
readonly parts: readonly (FileMessagePart | TextMessagePart)[];
|
|
4
9
|
};
|
|
5
10
|
|
|
6
11
|
export const EMPTY_QUEUE_ITEMS: readonly QueueItemState[] = Object.freeze([]);
|
|
7
12
|
|
|
8
13
|
export type QueueItemMethods = {
|
|
9
14
|
getState(): QueueItemState;
|
|
15
|
+
/** @deprecated Use `move({ lane: "steer", insertAfter: null })` instead. Removal after 2026-11-05. */
|
|
10
16
|
steer(): void;
|
|
17
|
+
move(placement: QueuePlacement): void;
|
|
11
18
|
remove(): void;
|
|
12
19
|
};
|
|
13
20
|
|
|
14
21
|
export type QueueItemMeta = {
|
|
15
22
|
source: "composer";
|
|
16
|
-
query: { index: number };
|
|
23
|
+
query: { type: "index"; index: number } | { type: "id"; id: string };
|
|
17
24
|
};
|
|
18
25
|
|
|
19
26
|
export type QueueItemClientSchema = {
|
|
@@ -832,10 +832,12 @@ describe("ExternalStoreThreadRuntimeCore - initialize event replay", () => {
|
|
|
832
832
|
describe("ExternalStoreThreadRuntimeCore - message queue", () => {
|
|
833
833
|
const makeQueue = () => ({
|
|
834
834
|
items: [] as never[],
|
|
835
|
+
steerItems: [] as never[],
|
|
835
836
|
enqueue: vi.fn(),
|
|
836
837
|
steer: vi.fn(),
|
|
838
|
+
move: vi.fn(),
|
|
839
|
+
edit: vi.fn(),
|
|
837
840
|
remove: vi.fn(),
|
|
838
|
-
clear: vi.fn(),
|
|
839
841
|
});
|
|
840
842
|
|
|
841
843
|
const appendMessage = (
|
|
@@ -866,7 +868,7 @@ describe("ExternalStoreThreadRuntimeCore - message queue", () => {
|
|
|
866
868
|
expect(withoutQueue.capabilities.queue).toBe(false);
|
|
867
869
|
});
|
|
868
870
|
|
|
869
|
-
it("routes a tail append through queue
|
|
871
|
+
it("routes a tail append through the queue adapter instead of onNew", async () => {
|
|
870
872
|
const queue = makeQueue();
|
|
871
873
|
const onNew = vi.fn();
|
|
872
874
|
const runtime = new ExternalStoreThreadRuntimeCore(
|
|
@@ -877,8 +879,48 @@ describe("ExternalStoreThreadRuntimeCore - message queue", () => {
|
|
|
877
879
|
await runtime.append(appendMessage({ steer: true }));
|
|
878
880
|
|
|
879
881
|
expect(onNew).not.toHaveBeenCalled();
|
|
882
|
+
expect(queue.enqueue).not.toHaveBeenCalled();
|
|
883
|
+
expect(queue.steer).toHaveBeenCalledTimes(1);
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
it("defaults a mid-run send to steer and an idle send to enqueue", async () => {
|
|
887
|
+
const queue = makeQueue();
|
|
888
|
+
const running = new ExternalStoreThreadRuntimeCore(
|
|
889
|
+
mockContextProvider,
|
|
890
|
+
makeStore({ queue, onNew: vi.fn(), isRunning: true }),
|
|
891
|
+
);
|
|
892
|
+
await running.append(
|
|
893
|
+
appendMessage({ parentId: running.messages.at(-1)?.id ?? null }),
|
|
894
|
+
);
|
|
895
|
+
expect(queue.steer).toHaveBeenCalledTimes(1);
|
|
896
|
+
expect(queue.enqueue).not.toHaveBeenCalled();
|
|
897
|
+
|
|
898
|
+
const idleQueue = makeQueue();
|
|
899
|
+
const idle = new ExternalStoreThreadRuntimeCore(
|
|
900
|
+
mockContextProvider,
|
|
901
|
+
makeStore({ queue: idleQueue, onNew: vi.fn(), isRunning: false }),
|
|
902
|
+
);
|
|
903
|
+
await idle.append(
|
|
904
|
+
appendMessage({ parentId: idle.messages.at(-1)?.id ?? null }),
|
|
905
|
+
);
|
|
906
|
+
expect(idleQueue.enqueue).toHaveBeenCalledTimes(1);
|
|
907
|
+
expect(idleQueue.steer).not.toHaveBeenCalled();
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
it("queues behind pending items when steer is explicitly false mid-run", async () => {
|
|
911
|
+
const queue = makeQueue();
|
|
912
|
+
const runtime = new ExternalStoreThreadRuntimeCore(
|
|
913
|
+
mockContextProvider,
|
|
914
|
+
makeStore({ queue, onNew: vi.fn(), isRunning: true }),
|
|
915
|
+
);
|
|
916
|
+
await runtime.append(
|
|
917
|
+
appendMessage({
|
|
918
|
+
steer: false,
|
|
919
|
+
parentId: runtime.messages.at(-1)?.id ?? null,
|
|
920
|
+
}),
|
|
921
|
+
);
|
|
880
922
|
expect(queue.enqueue).toHaveBeenCalledTimes(1);
|
|
881
|
-
expect(queue.
|
|
923
|
+
expect(queue.steer).not.toHaveBeenCalled();
|
|
882
924
|
});
|
|
883
925
|
|
|
884
926
|
it("does not abort in-flight tools when buffering a queued send", async () => {
|
|
@@ -913,7 +955,7 @@ describe("ExternalStoreThreadRuntimeCore - message queue", () => {
|
|
|
913
955
|
expect(abort).toHaveBeenCalledTimes(1);
|
|
914
956
|
});
|
|
915
957
|
|
|
916
|
-
it("
|
|
958
|
+
it("keeps the queue on cancel, reload, and edit", async () => {
|
|
917
959
|
const queue = makeQueue();
|
|
918
960
|
const runtime = new ExternalStoreThreadRuntimeCore(
|
|
919
961
|
mockContextProvider,
|
|
@@ -926,29 +968,30 @@ describe("ExternalStoreThreadRuntimeCore - message queue", () => {
|
|
|
926
968
|
);
|
|
927
969
|
|
|
928
970
|
runtime.cancelRun();
|
|
929
|
-
expect(queue.clear).toHaveBeenCalledWith("cancel-run");
|
|
930
|
-
|
|
931
971
|
await runtime.startRun({ parentId: null, sourceId: null, runConfig: {} });
|
|
932
|
-
expect(queue.clear).toHaveBeenCalledWith("reload");
|
|
933
|
-
|
|
934
972
|
// a non-tail parentId routes to the edit branch
|
|
935
973
|
await runtime.append(appendMessage({ parentId: "not-the-tail" }));
|
|
936
|
-
|
|
974
|
+
|
|
975
|
+
expect(queue.remove).not.toHaveBeenCalled();
|
|
976
|
+
expect(queue.move).not.toHaveBeenCalled();
|
|
937
977
|
});
|
|
938
978
|
|
|
939
|
-
it("delegates
|
|
979
|
+
it("delegates queue reads / move / remove to the adapter", () => {
|
|
940
980
|
const queue = makeQueue();
|
|
941
|
-
const items = [{ id: "q1", prompt: "queued" }];
|
|
981
|
+
const items = [{ id: "q1", prompt: "queued", parts: [] }];
|
|
982
|
+
const steerItems = [{ id: "s1", prompt: "steered", parts: [] }];
|
|
942
983
|
queue.items = items as never;
|
|
984
|
+
queue.steerItems = steerItems as never;
|
|
943
985
|
const runtime = new ExternalStoreThreadRuntimeCore(
|
|
944
986
|
mockContextProvider,
|
|
945
987
|
makeStore({ queue }),
|
|
946
988
|
);
|
|
947
989
|
|
|
948
990
|
expect(runtime.getQueueItems()).toBe(items);
|
|
949
|
-
runtime.
|
|
991
|
+
expect(runtime.getSteerQueueItems()).toBe(steerItems);
|
|
992
|
+
runtime.moveQueueItem("q1", { lane: "steer" });
|
|
950
993
|
runtime.removeQueueItem("q1");
|
|
951
|
-
expect(queue.
|
|
994
|
+
expect(queue.move).toHaveBeenCalledWith("q1", { lane: "steer" });
|
|
952
995
|
expect(queue.remove).toHaveBeenCalledWith("q1");
|
|
953
996
|
});
|
|
954
997
|
});
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
4
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
5
|
+
|
|
6
|
+
const SRC_DIR = resolve(__dirname, "..");
|
|
7
|
+
|
|
8
|
+
const FORBIDDEN_MODULES = new Set([
|
|
9
|
+
"react",
|
|
10
|
+
"react/jsx-runtime",
|
|
11
|
+
"@assistant-ui/store",
|
|
12
|
+
"@assistant-ui/tap",
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
const STORE_ALLOWED_REACT_NAMES = new Set([
|
|
16
|
+
"useState",
|
|
17
|
+
"useReducer",
|
|
18
|
+
"useRef",
|
|
19
|
+
"useMemo",
|
|
20
|
+
"useCallback",
|
|
21
|
+
"useEffect",
|
|
22
|
+
"useLayoutEffect",
|
|
23
|
+
"useInsertionEffect",
|
|
24
|
+
"useEffectEvent",
|
|
25
|
+
"useSyncExternalStore",
|
|
26
|
+
"useDebugValue",
|
|
27
|
+
"use",
|
|
28
|
+
"useContext",
|
|
29
|
+
"createContext",
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
const IMPORT_RE =
|
|
33
|
+
/(?:import|export)\s+(?:type\s+)?(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)(?:\s*,\s*(?:\{[^}]*\}|\*\s+as\s+\w+|\w+))*\s+from\s+)?["']([^"']+)["']/g;
|
|
34
|
+
|
|
35
|
+
const SIDE_EFFECT_IMPORT_RE = /import\s+["']([^"']+)["']/g;
|
|
36
|
+
|
|
37
|
+
const EXPORT_STAR_RE = /export\s+\*\s+from\s+["']([^"']+)["']/g;
|
|
38
|
+
|
|
39
|
+
const DYNAMIC_IMPORT_RE = /\b(?:import|require)\s*\(\s*["']([^"']+)["']\s*\)/g;
|
|
40
|
+
|
|
41
|
+
const FROM_REACT_RE =
|
|
42
|
+
/(?:import|export)\s+(?:type\s+)?([\s\S]*?)\s+from\s+["']react["']/g;
|
|
43
|
+
|
|
44
|
+
function resolveRelative(fromFile: string, spec: string): string | null {
|
|
45
|
+
if (!spec.startsWith(".")) return null;
|
|
46
|
+
const base = resolve(dirname(fromFile), spec);
|
|
47
|
+
const candidates = [
|
|
48
|
+
base,
|
|
49
|
+
`${base}.ts`,
|
|
50
|
+
`${base}.tsx`,
|
|
51
|
+
join(base, "index.ts"),
|
|
52
|
+
join(base, "index.tsx"),
|
|
53
|
+
];
|
|
54
|
+
for (const candidate of candidates) {
|
|
55
|
+
if (existsSync(candidate) && statSync(candidate).isFile()) {
|
|
56
|
+
return candidate;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function collectImports(content: string): string[] {
|
|
63
|
+
const specs: string[] = [];
|
|
64
|
+
for (const re of [
|
|
65
|
+
IMPORT_RE,
|
|
66
|
+
SIDE_EFFECT_IMPORT_RE,
|
|
67
|
+
EXPORT_STAR_RE,
|
|
68
|
+
DYNAMIC_IMPORT_RE,
|
|
69
|
+
]) {
|
|
70
|
+
re.lastIndex = 0;
|
|
71
|
+
let match: RegExpExecArray | null;
|
|
72
|
+
while ((match = re.exec(content)) !== null) {
|
|
73
|
+
specs.push(match[1]!);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return specs;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function walkFrom(entry: string): { forbidden: string[]; jsxFiles: string[] } {
|
|
80
|
+
const visited = new Set<string>();
|
|
81
|
+
const queue = [entry];
|
|
82
|
+
const forbidden: string[] = [];
|
|
83
|
+
|
|
84
|
+
while (queue.length > 0) {
|
|
85
|
+
const file = queue.pop()!;
|
|
86
|
+
if (visited.has(file)) continue;
|
|
87
|
+
visited.add(file);
|
|
88
|
+
|
|
89
|
+
const content = readFileSync(file, "utf-8");
|
|
90
|
+
const specs = collectImports(content);
|
|
91
|
+
|
|
92
|
+
for (const spec of specs) {
|
|
93
|
+
if (FORBIDDEN_MODULES.has(spec)) {
|
|
94
|
+
forbidden.push(`${relative(SRC_DIR, file)} imports "${spec}"`);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const resolved = resolveRelative(file, spec);
|
|
98
|
+
if (resolved && !visited.has(resolved)) {
|
|
99
|
+
queue.push(resolved);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const jsxFiles = [...visited]
|
|
105
|
+
.filter((file) => file.endsWith(".tsx"))
|
|
106
|
+
.map((file) => relative(SRC_DIR, file));
|
|
107
|
+
|
|
108
|
+
return { forbidden, jsxFiles };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function nonTypeImportNames(clause: string): string[] {
|
|
112
|
+
const names: string[] = [];
|
|
113
|
+
const trimmed = clause.trim();
|
|
114
|
+
if (trimmed.startsWith("type ")) return names;
|
|
115
|
+
if (/^\*\s+as\s+\w+$/.test(trimmed)) return names;
|
|
116
|
+
|
|
117
|
+
const defaultAndNamed = trimmed.match(/^(\w+)\s*,\s*\{([\s\S]*)\}$/);
|
|
118
|
+
const namedOnly = trimmed.match(/^\{([\s\S]*)\}$/);
|
|
119
|
+
const defaultOnly = trimmed.match(/^(\w+)$/);
|
|
120
|
+
|
|
121
|
+
let namedBody = "";
|
|
122
|
+
if (defaultAndNamed) {
|
|
123
|
+
names.push(defaultAndNamed[1]!);
|
|
124
|
+
namedBody = defaultAndNamed[2]!;
|
|
125
|
+
} else if (namedOnly) {
|
|
126
|
+
namedBody = namedOnly[1]!;
|
|
127
|
+
} else if (defaultOnly) {
|
|
128
|
+
names.push(defaultOnly[1]!);
|
|
129
|
+
return names;
|
|
130
|
+
} else {
|
|
131
|
+
return names;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const part of namedBody.split(",")) {
|
|
135
|
+
const p = part.trim();
|
|
136
|
+
if (!p || p.startsWith("type ")) continue;
|
|
137
|
+
const asMatch = p.match(/^(\w+)(?:\s+as\s+\w+)?$/);
|
|
138
|
+
if (asMatch) names.push(asMatch[1]!);
|
|
139
|
+
}
|
|
140
|
+
return names;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
describe("framework neutral boundary", () => {
|
|
144
|
+
it("the default entry graph must not import react, react/jsx-runtime, @assistant-ui/tap, or @assistant-ui/store", () => {
|
|
145
|
+
const { forbidden, jsxFiles } = walkFrom(resolve(SRC_DIR, "index.ts"));
|
|
146
|
+
expect(
|
|
147
|
+
forbidden,
|
|
148
|
+
`The default entry must stay loadable without react, @assistant-ui/tap, or @assistant-ui/store installed.\n` +
|
|
149
|
+
`These modules in the src/index.ts graph import a forbidden package:\n` +
|
|
150
|
+
forbidden.map((f) => ` - ${f}`).join("\n"),
|
|
151
|
+
).toEqual([]);
|
|
152
|
+
expect(
|
|
153
|
+
jsxFiles,
|
|
154
|
+
`The JSX transform injects react/jsx-runtime at build time, so the default entry graph must not contain .tsx files:\n` +
|
|
155
|
+
jsxFiles.map((f) => ` - ${f}`).join("\n"),
|
|
156
|
+
).toEqual([]);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("the internal entry graph must not import react, react/jsx-runtime, @assistant-ui/tap, or @assistant-ui/store", () => {
|
|
160
|
+
const { forbidden, jsxFiles } = walkFrom(resolve(SRC_DIR, "internal.ts"));
|
|
161
|
+
expect(
|
|
162
|
+
forbidden,
|
|
163
|
+
"The internal entry must stay loadable without react, react/jsx-runtime, @assistant-ui/tap, or @assistant-ui/store installed.\nViolations:\n" +
|
|
164
|
+
forbidden.map((f) => ` - ${f}`).join("\n"),
|
|
165
|
+
).toEqual([]);
|
|
166
|
+
expect(
|
|
167
|
+
jsxFiles,
|
|
168
|
+
`The JSX transform injects react/jsx-runtime at build time, so the internal entry graph must not contain .tsx files:\n` +
|
|
169
|
+
jsxFiles.map((f) => ` - ${f}`).join("\n"),
|
|
170
|
+
).toEqual([]);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("the store entry graphs stay hostable inside a tap root", () => {
|
|
174
|
+
const violations: string[] = [];
|
|
175
|
+
const jsxFiles: string[] = [];
|
|
176
|
+
const visited = new Set<string>();
|
|
177
|
+
const pending = [
|
|
178
|
+
resolve(SRC_DIR, "store/index.ts"),
|
|
179
|
+
resolve(SRC_DIR, "store/internal.ts"),
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
// Walks the runtime graph only: type-only imports are erased at build, so
|
|
183
|
+
// they neither carry runtime dependencies nor get traversed. This is also
|
|
184
|
+
// why type imports may keep using the @assistant-ui/store barrel: the
|
|
185
|
+
// augmentations target that specifier, and splitting types across entries
|
|
186
|
+
// would fork the ScopeRegistry seen by src-mapped consumers.
|
|
187
|
+
const RUNTIME_IMPORT_RE =
|
|
188
|
+
/(?:^|\n)\s*(?:import|export)\s+(?!type\b)(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\*|\w+(?:\s*,\s*(?:\{[^}]*\}|\*\s+as\s+\w+))?)\s+from\s+)?["']([^"']+)["']/g;
|
|
189
|
+
|
|
190
|
+
const collectRuntimeImports = (content: string): string[] => {
|
|
191
|
+
const specs: string[] = [];
|
|
192
|
+
for (const re of [RUNTIME_IMPORT_RE, DYNAMIC_IMPORT_RE]) {
|
|
193
|
+
re.lastIndex = 0;
|
|
194
|
+
let match: RegExpExecArray | null;
|
|
195
|
+
while ((match = re.exec(content)) !== null) {
|
|
196
|
+
specs.push(match[1]!);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return specs;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
while (pending.length > 0) {
|
|
203
|
+
const file = pending.pop()!;
|
|
204
|
+
if (visited.has(file)) continue;
|
|
205
|
+
visited.add(file);
|
|
206
|
+
if (file.endsWith(".tsx")) jsxFiles.push(relative(SRC_DIR, file));
|
|
207
|
+
|
|
208
|
+
const content = readFileSync(file, "utf-8");
|
|
209
|
+
|
|
210
|
+
for (const spec of collectRuntimeImports(content)) {
|
|
211
|
+
if (spec === "react/jsx-runtime") {
|
|
212
|
+
violations.push(
|
|
213
|
+
`${relative(SRC_DIR, file)} imports "react/jsx-runtime"`,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
if (spec === "@assistant-ui/store") {
|
|
217
|
+
violations.push(
|
|
218
|
+
`${relative(SRC_DIR, file)} value-imports the "@assistant-ui/store" barrel; use "@assistant-ui/store/client"`,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
const resolved = resolveRelative(file, spec);
|
|
222
|
+
if (resolved && !visited.has(resolved)) {
|
|
223
|
+
pending.push(resolved);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
FROM_REACT_RE.lastIndex = 0;
|
|
228
|
+
let match: RegExpExecArray | null;
|
|
229
|
+
while ((match = FROM_REACT_RE.exec(content)) !== null) {
|
|
230
|
+
if (/^(?:import|export)\s+type\b/.test(match[0]!)) continue;
|
|
231
|
+
const clause = match[1]!;
|
|
232
|
+
if (clause.trimStart().startsWith("type ")) continue;
|
|
233
|
+
for (const name of nonTypeImportNames(clause)) {
|
|
234
|
+
if (!STORE_ALLOWED_REACT_NAMES.has(name)) {
|
|
235
|
+
violations.push(
|
|
236
|
+
`${relative(SRC_DIR, file)} imports "${name}" from "react"`,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
expect(
|
|
244
|
+
violations,
|
|
245
|
+
`The store entry graphs run inside tap roots on every framework, so they may ` +
|
|
246
|
+
`only use react names the tap shim dispatches, never react/jsx-runtime, and ` +
|
|
247
|
+
`never the @assistant-ui/store barrel (whose module scope requires React).\n` +
|
|
248
|
+
`Violations:\n` +
|
|
249
|
+
violations.map((f) => ` - ${f}`).join("\n"),
|
|
250
|
+
).toEqual([]);
|
|
251
|
+
expect(
|
|
252
|
+
jsxFiles,
|
|
253
|
+
`The JSX transform injects react/jsx-runtime at build time, so the store entry graphs must not contain .tsx files:\n` +
|
|
254
|
+
jsxFiles.map((f) => ` - ${f}`).join("\n"),
|
|
255
|
+
).toEqual([]);
|
|
256
|
+
});
|
|
257
|
+
});
|