@assistant-ui/core 0.3.6 → 0.3.8

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.
Files changed (89) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/react/RuntimeAdapter.js +1 -1
  3. package/dist/react/RuntimeAdapter.js.map +1 -1
  4. package/dist/react/client/Interactables.js +1 -1
  5. package/dist/react/client/Interactables.js.map +1 -1
  6. package/dist/react/client/Tools.d.ts +1 -1
  7. package/dist/react/client/Tools.js +1 -1
  8. package/dist/react/client/Tools.js.map +1 -1
  9. package/dist/react/index.d.ts +9 -9
  10. package/dist/react/index.js +4 -4
  11. package/dist/react/model-context/interactableTool.d.ts +1 -1
  12. package/dist/react/model-context/useAssistantTool.d.ts +1 -1
  13. package/dist/react/runtimes/cloud/auiV0.d.ts +1 -0
  14. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  15. package/dist/react/runtimes/cloud/auiV0.js +2 -1
  16. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  17. package/dist/react/runtimes/createMessageConverter.d.ts +1 -1
  18. package/dist/react/runtimes/createRuntimeExtras.d.ts +4 -1
  19. package/dist/react/runtimes/createRuntimeExtras.d.ts.map +1 -1
  20. package/dist/react/runtimes/createRuntimeExtras.js +6 -17
  21. package/dist/react/runtimes/createRuntimeExtras.js.map +1 -1
  22. package/dist/react/types/scopes/dataRenderers.d.ts +1 -1
  23. package/dist/react/types/scopes/interactables.d.ts +1 -1
  24. package/dist/react/types/scopes/tools.d.ts +1 -1
  25. package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -1
  26. package/dist/runtime/base/default-edit-composer-runtime-core.d.ts +0 -2
  27. package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
  28. package/dist/runtime/base/default-edit-composer-runtime-core.js +18 -25
  29. package/dist/runtime/base/default-edit-composer-runtime-core.js.map +1 -1
  30. package/dist/runtime/utils/runtime-extras-brand.d.ts +21 -0
  31. package/dist/runtime/utils/runtime-extras-brand.d.ts.map +1 -0
  32. package/dist/runtime/utils/runtime-extras-brand.js +37 -0
  33. package/dist/runtime/utils/runtime-extras-brand.js.map +1 -0
  34. package/dist/runtime/utils/thread-message-like.js +3 -1
  35. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  36. package/dist/store/clients/runtime-adapter.d.ts.map +1 -1
  37. package/dist/store/clients/runtime-adapter.js +1 -1
  38. package/dist/store/clients/runtime-adapter.js.map +1 -1
  39. package/dist/store/clients/suggestions.js +1 -1
  40. package/dist/store/clients/suggestions.js.map +1 -1
  41. package/dist/store/clients/thread-message-client.js +1 -1
  42. package/dist/store/clients/thread-message-client.js.map +1 -1
  43. package/dist/store/index.d.ts +10 -1
  44. package/dist/store/index.js +7 -1
  45. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  46. package/dist/store/runtime-clients/composer-runtime-client.js +1 -1
  47. package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
  48. package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
  49. package/dist/store/runtime-clients/message-runtime-client.js +1 -1
  50. package/dist/store/runtime-clients/message-runtime-client.js.map +1 -1
  51. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  52. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +1 -1
  53. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  54. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  55. package/dist/store/runtime-clients/thread-list-runtime-client.js +1 -1
  56. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  57. package/dist/store/runtime-clients/thread-runtime-client.js +1 -1
  58. package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
  59. package/dist/types/message.d.ts +1 -0
  60. package/dist/types/message.d.ts.map +1 -1
  61. package/dist/types/message.js.map +1 -1
  62. package/package.json +3 -3
  63. package/src/react/RuntimeAdapter.test.ts +85 -0
  64. package/src/react/RuntimeAdapter.ts +1 -1
  65. package/src/react/client/Interactables.test.ts +3 -2
  66. package/src/react/client/Interactables.ts +4 -4
  67. package/src/react/client/Tools.ts +4 -4
  68. package/src/react/index.ts +1 -0
  69. package/src/react/runtimes/cloud/auiV0.ts +8 -1
  70. package/src/react/runtimes/createRuntimeExtras.test.ts +14 -1
  71. package/src/react/runtimes/createRuntimeExtras.ts +15 -26
  72. package/src/runtime/base/default-edit-composer-runtime-core.ts +32 -50
  73. package/src/runtime/utils/runtime-extras-brand.ts +47 -0
  74. package/src/runtime/utils/thread-message-like.ts +5 -1
  75. package/src/store/clients/runtime-adapter.ts +2 -5
  76. package/src/store/clients/suggestions.ts +1 -1
  77. package/src/store/clients/thread-message-client.ts +1 -1
  78. package/src/store/index.ts +31 -0
  79. package/src/store/runtime-clients/composer-runtime-client.ts +2 -5
  80. package/src/store/runtime-clients/lookup-domains.test.ts +109 -0
  81. package/src/store/runtime-clients/message-runtime-client.ts +2 -5
  82. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +2 -1
  83. package/src/store/runtime-clients/thread-list-runtime-client.ts +2 -5
  84. package/src/store/runtime-clients/thread-runtime-client.ts +2 -2
  85. package/src/tests/auiV0Encode.test.ts +97 -0
  86. package/src/tests/default-edit-composer-runtime-core.test.ts +6 -2
  87. package/src/tests/framework-neutral-boundary.test.ts +60 -24
  88. package/src/tests/thread-message-like.test.ts +43 -0
  89. package/src/types/message.ts +1 -0
@@ -5,11 +5,11 @@ import {
5
5
  withKey,
6
6
  type ResourceElement,
7
7
  } from "@assistant-ui/tap";
8
+ import type { ClientOutput } from "@assistant-ui/store";
8
9
  import {
9
- useAssistantClientRef,
10
- type ClientOutput,
11
10
  attachTransformScopes,
12
- } from "@assistant-ui/store";
11
+ useAssistantClientRef,
12
+ } from "@assistant-ui/store/client";
13
13
  import type { McpAppResourceOutput, ToolsState } from "../types/scopes/tools";
14
14
  import type { Tool } from "assistant-stream";
15
15
  import {
@@ -18,7 +18,7 @@ import {
18
18
  type Toolkit,
19
19
  } from "../model-context/toolbox";
20
20
  import type { ToolCallMessagePartComponent } from "../types/MessagePartComponentTypes";
21
- import { ModelContext } from "../../store";
21
+ import { ModelContext } from "../../store/clients/model-context-client";
22
22
 
23
23
  export type { McpAppResourceOutput };
24
24
 
@@ -228,6 +228,7 @@ export {
228
228
  } from "./runtimes/useStreamingTiming";
229
229
  export {
230
230
  createRuntimeExtras,
231
+ unstable_createRuntimeExtrasFromBrand,
231
232
  type RuntimeExtras,
232
233
  } from "./runtimes/createRuntimeExtras";
233
234
  export { RemoteThreadListHookInstanceManager } from "./runtimes/RemoteThreadListHookInstanceManager";
@@ -32,6 +32,7 @@ type AuiV0MessagePart =
32
32
  | {
33
33
  readonly type: "reasoning";
34
34
  readonly text: string;
35
+ readonly unstable_summary?: string;
35
36
  }
36
37
  | {
37
38
  readonly type: "source";
@@ -226,7 +227,13 @@ export function auiV0Encode(message: ThreadMessage): AuiV0Message {
226
227
  return { type: "text", text: part.text };
227
228
 
228
229
  case "reasoning":
229
- return { type: "reasoning", text: part.text };
230
+ return {
231
+ type: "reasoning",
232
+ text: part.text,
233
+ ...(part.unstable_summary !== undefined
234
+ ? { unstable_summary: part.unstable_summary }
235
+ : undefined),
236
+ };
230
237
 
231
238
  case "source":
232
239
  if (part.sourceType === "url") {
@@ -1,6 +1,10 @@
1
1
  import { describe, it, expect } from "vitest";
2
2
  import type { AssistantClient } from "@assistant-ui/store";
3
- import { createRuntimeExtras } from "./createRuntimeExtras";
3
+ import {
4
+ createRuntimeExtras,
5
+ unstable_createRuntimeExtrasFromBrand,
6
+ } from "./createRuntimeExtras";
7
+ import { createRuntimeExtrasBrand } from "../../runtime/utils/runtime-extras-brand";
4
8
 
5
9
  type Extras = { value: number; greet: () => string };
6
10
 
@@ -55,4 +59,13 @@ describe("createRuntimeExtras", () => {
55
59
 
56
60
  expect(() => channel.get(clientWith(undefined))).toThrow("useTestRuntime");
57
61
  });
62
+
63
+ it("reads extras provided through a standalone brand instance", () => {
64
+ const brand = createRuntimeExtrasBrand<Extras>("useTestRuntime");
65
+ const channel = unstable_createRuntimeExtrasFromBrand(brand);
66
+ const branded = brand.provide({ value: 7, greet: () => "hi" });
67
+
68
+ expect(channel.is(branded)).toBe(true);
69
+ expect(channel.get(clientWith(branded)).value).toBe(7);
70
+ });
58
71
  });
@@ -2,6 +2,10 @@
2
2
 
3
3
  import { useAuiState } from "@assistant-ui/store";
4
4
  import type { AssistantClient } from "@assistant-ui/store";
5
+ import {
6
+ createRuntimeExtrasBrand,
7
+ type RuntimeExtrasBrand,
8
+ } from "../../runtime/utils/runtime-extras-brand";
5
9
 
6
10
  /** @deprecated Internal API for external-store adapter authors. Not part of the public API; may change or be removed without notice. */
7
11
  export type RuntimeExtras<T extends object> = {
@@ -17,33 +21,10 @@ export type RuntimeExtras<T extends object> = {
17
21
  };
18
22
 
19
23
  /** @deprecated Internal API for external-store adapter authors. Not part of the public API; may change or be removed without notice. */
20
- export const createRuntimeExtras = <T extends object>(
21
- runtimeName: string,
24
+ export const unstable_createRuntimeExtrasFromBrand = <T extends object>(
25
+ brand: RuntimeExtrasBrand<T>,
22
26
  ): RuntimeExtras<T> => {
23
- const brand = Symbol(`${runtimeName} extras`);
24
-
25
- const is = (extras: unknown): extras is T =>
26
- typeof extras === "object" && extras !== null && brand in extras;
27
-
28
- const assert = (extras: unknown): T => {
29
- if (!is(extras))
30
- throw new Error(
31
- `The current thread is not backed by the ${runtimeName} runtime.`,
32
- );
33
- return extras;
34
- };
35
-
36
- const provide = (value: T): T => {
37
- Object.defineProperty(value, brand, {
38
- value: true,
39
- enumerable: false,
40
- configurable: true,
41
- });
42
- return value;
43
- };
44
-
45
- const tryGet = (extras: unknown): T | undefined =>
46
- is(extras) ? extras : undefined;
27
+ const { provide, is, tryGet, assert } = brand;
47
28
 
48
29
  const get = (client: AssistantClient): T =>
49
30
  assert(client.thread.getState().extras);
@@ -66,3 +47,11 @@ export const createRuntimeExtras = <T extends object>(
66
47
 
67
48
  return { provide, is, tryGet, get, use: use as RuntimeExtras<T>["use"] };
68
49
  };
50
+
51
+ /** @deprecated Internal API for external-store adapter authors. Not part of the public API; may change or be removed without notice. */
52
+ export const createRuntimeExtras = <T extends object>(
53
+ runtimeName: string,
54
+ ): RuntimeExtras<T> =>
55
+ unstable_createRuntimeExtrasFromBrand(
56
+ createRuntimeExtrasBrand<T>(runtimeName),
57
+ );
@@ -1,7 +1,7 @@
1
1
  import type { AppendMessage, ThreadMessage } from "../../types/message";
2
2
  import type { CompleteAttachment } from "../../types/attachment";
3
3
  import { getThreadMessageText } from "../../utils/text";
4
- import { attachmentsEqual, liftNonTextParts } from "../../adapters/attachment";
4
+ import { liftNonTextParts } from "../../adapters/attachment";
5
5
  import type { AttachmentAdapter } from "../../adapters/attachment";
6
6
  import type { DictationAdapter } from "../../adapters/speech";
7
7
  import type { SendOptions } from "../interfaces/composer-runtime-core";
@@ -26,8 +26,6 @@ export class DefaultEditComposerRuntimeCore extends BaseComposerRuntimeCore {
26
26
  return this.runtime.adapters?.dictation;
27
27
  }
28
28
 
29
- private _previousText;
30
- private _previousAttachments: readonly CompleteAttachment[];
31
29
  private _nonTextPassthrough: readonly ThreadMessage["content"][number][];
32
30
  private _parentId: string | null;
33
31
  private _sourceId: string | null;
@@ -58,24 +56,24 @@ export class DefaultEditComposerRuntimeCore extends BaseComposerRuntimeCore {
58
56
  this.endEditCallback = endEditCallback;
59
57
  this._parentId = parentId;
60
58
  this._sourceId = message.id;
61
- this._previousText = getThreadMessageText(message);
62
- this.setText(this._previousText);
59
+ this.setText(getThreadMessageText(message));
63
60
 
64
61
  this.setRole(message.role);
65
62
 
63
+ let attachments: readonly CompleteAttachment[];
66
64
  if (message.role === "user") {
67
- this._previousAttachments = [
65
+ attachments = [
68
66
  ...(message.attachments ?? []),
69
67
  ...liftNonTextParts(message.content),
70
68
  ];
71
69
  this._nonTextPassthrough = [];
72
70
  } else {
73
- this._previousAttachments = message.attachments ?? [];
71
+ attachments = message.attachments ?? [];
74
72
  this._nonTextPassthrough = message.content.filter(
75
73
  (p) => p.type !== "text",
76
74
  );
77
75
  }
78
- this.setAttachments(this._previousAttachments);
76
+ this.setAttachments(attachments);
79
77
 
80
78
  this.setRunConfig({ ...runtime.composer.runConfig });
81
79
  }
@@ -92,49 +90,33 @@ export class DefaultEditComposerRuntimeCore extends BaseComposerRuntimeCore {
92
90
  message: Omit<AppendMessage, "parentId" | "sourceId">,
93
91
  options?: SendOptions,
94
92
  ) {
95
- let appendTask: void | Promise<void> = undefined;
96
- const text = getThreadMessageText(message as AppendMessage);
97
- const attachmentsChanged = !attachmentsEqual(
98
- message.attachments ?? [],
99
- this._previousAttachments,
93
+ const content =
94
+ this._nonTextPassthrough.length > 0
95
+ ? ([
96
+ ...message.content,
97
+ ...this._nonTextPassthrough,
98
+ ] as AppendMessage["content"])
99
+ : message.content;
100
+ // Gate live state against the new branch's prefix (messages up to the
101
+ // parent): an unchanged interactable re-stamps the prior baseline, an
102
+ // interactable edited since the original message stamps its newest state.
103
+ const messages = this.runtime.messages;
104
+ const parentIndex =
105
+ this._parentId === null
106
+ ? -1
107
+ : messages.findIndex((m) => m.id === this._parentId);
108
+ const composerMetadata = gateInteractableComposerMetadata(
109
+ this.runtime.getModelContext().unstable_composerMetadata,
110
+ messages.slice(0, parentIndex + 1),
100
111
  );
101
-
102
- if (
103
- text !== this._previousText ||
104
- attachmentsChanged ||
105
- options?.startRun
106
- ) {
107
- const content =
108
- this._nonTextPassthrough.length > 0
109
- ? ([
110
- ...message.content,
111
- ...this._nonTextPassthrough,
112
- ] as AppendMessage["content"])
113
- : message.content;
114
- // Gate live state against the new branch's prefix (messages up to the
115
- // parent): an unchanged interactable re-stamps the prior baseline, an
116
- // interactable edited since the original message stamps its newest state.
117
- const messages = this.runtime.messages;
118
- const parentIndex =
119
- this._parentId === null
120
- ? -1
121
- : messages.findIndex((m) => m.id === this._parentId);
122
- const composerMetadata = gateInteractableComposerMetadata(
123
- this.runtime.getModelContext().unstable_composerMetadata,
124
- messages.slice(0, parentIndex + 1),
125
- );
126
- const enriched = this.enrichWithComposerMetadata(
127
- message,
128
- composerMetadata,
129
- );
130
- appendTask = this.runtime.append({
131
- ...enriched,
132
- content,
133
- parentId: this._parentId,
134
- sourceId: this._sourceId,
135
- startRun: options?.startRun,
136
- });
137
- }
112
+ const enriched = this.enrichWithComposerMetadata(message, composerMetadata);
113
+ const appendTask = this.runtime.append({
114
+ ...enriched,
115
+ content,
116
+ parentId: this._parentId,
117
+ sourceId: this._sourceId,
118
+ startRun: options?.startRun,
119
+ });
138
120
 
139
121
  this.handleCancel();
140
122
  return appendTask;
@@ -0,0 +1,47 @@
1
+ /** @deprecated Internal API for external-store adapter authors. Not part of the public API; may change or be removed without notice. */
2
+ export type RuntimeExtrasBrand<T extends object> = {
3
+ provide: (value: T) => T;
4
+ is: (extras: unknown) => extras is T;
5
+ tryGet: (extras: unknown) => T | undefined;
6
+ assert: (extras: unknown) => T;
7
+ };
8
+
9
+ /**
10
+ * Creates the framework-neutral half of a runtime extras channel: the brand
11
+ * symbol with its provide/read primitives. A runtime factory provides extras
12
+ * through the brand on any framework; the React entry wraps the same brand
13
+ * with a `use()` hook. The symbol is per-instance, so the adapter package
14
+ * creates the brand once and every binding imports that instance.
15
+ *
16
+ * @deprecated Internal API for external-store adapter authors. Not part of the public API; may change or be removed without notice.
17
+ */
18
+ export const createRuntimeExtrasBrand = <T extends object>(
19
+ runtimeName: string,
20
+ ): RuntimeExtrasBrand<T> => {
21
+ const brand = Symbol(`${runtimeName} extras`);
22
+
23
+ const is = (extras: unknown): extras is T =>
24
+ typeof extras === "object" && extras !== null && brand in extras;
25
+
26
+ const assert = (extras: unknown): T => {
27
+ if (!is(extras))
28
+ throw new Error(
29
+ `The current thread is not backed by the ${runtimeName} runtime.`,
30
+ );
31
+ return extras;
32
+ };
33
+
34
+ const provide = (value: T): T => {
35
+ Object.defineProperty(value, brand, {
36
+ value: true,
37
+ enumerable: false,
38
+ configurable: true,
39
+ });
40
+ return value;
41
+ };
42
+
43
+ const tryGet = (extras: unknown): T | undefined =>
44
+ is(extras) ? extras : undefined;
45
+
46
+ return { provide, is, tryGet, assert };
47
+ };
@@ -160,10 +160,14 @@ export const fromThreadMessageLike = (
160
160
  const type = part.type;
161
161
  switch (type) {
162
162
  case "text":
163
- case "reasoning":
164
163
  if (!part.text?.trim()) return null;
165
164
  return part;
166
165
 
166
+ case "reasoning":
167
+ if (!part.text?.trim() && !part.unstable_summary?.trim())
168
+ return null;
169
+ return part;
170
+
167
171
  case "file":
168
172
  case "source":
169
173
  return part;
@@ -1,8 +1,5 @@
1
- import {
2
- Derived,
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";
@@ -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,11 +1,8 @@
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
- type ClientOutput,
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,
@@ -0,0 +1,109 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { AuiConfig, createAssistantClient } from "@assistant-ui/store/client";
3
+ import { RuntimeAdapter, Suggestions } from "../index";
4
+ import { ExternalStoreRuntimeCore } from "../../runtimes/internal";
5
+ import { AssistantRuntimeImpl } from "../../runtime/internal";
6
+ import type { ExternalStoreAdapter } from "../../runtimes/external-store/external-store-adapter";
7
+
8
+ type DemoMessage = { id: string; role: "user" | "assistant"; text: string };
9
+
10
+ const createClient = () => {
11
+ const adapter: ExternalStoreAdapter<DemoMessage> = {
12
+ messages: [
13
+ { id: "u1", role: "user", text: "hi" },
14
+ { id: "a1", role: "assistant", text: "hello" },
15
+ ],
16
+ convertMessage: (message) => ({
17
+ id: message.id,
18
+ role: message.role,
19
+ content: [{ type: "text", text: message.text }],
20
+ }),
21
+ onNew: async () => {},
22
+ adapters: {
23
+ threadList: {
24
+ threadId: "t1",
25
+ threads: [{ status: "regular", id: "t1", title: "one" }],
26
+ archivedThreads: [{ status: "archived", id: "t2", title: "two" }],
27
+ },
28
+ },
29
+ };
30
+ const runtime = new AssistantRuntimeImpl(
31
+ new ExternalStoreRuntimeCore(adapter),
32
+ );
33
+ return createAssistantClient(
34
+ AuiConfig({
35
+ threads: RuntimeAdapter(runtime),
36
+ suggestions: Suggestions([
37
+ { title: "a", label: "a", prompt: "a" },
38
+ { title: "b", label: "b", prompt: "b" },
39
+ ]),
40
+ }),
41
+ );
42
+ };
43
+
44
+ /**
45
+ * Pins each by-index lookup's valid domain to the state array that backs it.
46
+ * Consumers (the vue by-index providers) guard with `index < array.length`
47
+ * against these arrays; a lookup changing its backing array must fail here.
48
+ */
49
+ describe("by-index lookup domains", () => {
50
+ it("thread.message is indexed by thread.getState().messages", () => {
51
+ const handle = createClient();
52
+ const aui = handle.getClient();
53
+ const length = aui.thread.getState().messages.length;
54
+ expect(length).toBe(2);
55
+ expect(aui.thread.message({ index: length - 1 }).getState().id).toBe("a1");
56
+ expect(() => aui.thread.message({ index: length }).getState()).toThrow(
57
+ /out of bounds/,
58
+ );
59
+ handle.destroy();
60
+ });
61
+
62
+ it("message.part is indexed by message.getState().parts", () => {
63
+ const handle = createClient();
64
+ const aui = handle.getClient();
65
+ const message = aui.thread.message({ index: 1 });
66
+ const length = message.getState().parts.length;
67
+ expect(length).toBe(1);
68
+ expect(message.part({ index: length - 1 }).getState().type).toBe("text");
69
+ expect(() => message.part({ index: length }).getState()).toThrow(
70
+ /out of bounds/,
71
+ );
72
+ handle.destroy();
73
+ });
74
+
75
+ it("suggestions.suggestion is indexed by suggestions.getState().suggestions", () => {
76
+ const handle = createClient();
77
+ const aui = handle.getClient();
78
+ const length = aui.suggestions.getState().suggestions.length;
79
+ expect(length).toBe(2);
80
+ expect(
81
+ aui.suggestions.suggestion({ index: length - 1 }).getState().title,
82
+ ).toBe("b");
83
+ expect(() =>
84
+ aui.suggestions.suggestion({ index: length }).getState(),
85
+ ).toThrow(/out of bounds/);
86
+ handle.destroy();
87
+ });
88
+
89
+ it("threads.item is indexed by threadIds and archivedThreadIds", () => {
90
+ const handle = createClient();
91
+ const aui = handle.getClient();
92
+ const state = aui.threads.getState();
93
+ expect(state.threadIds).toHaveLength(1);
94
+ expect(state.archivedThreadIds).toHaveLength(1);
95
+ expect(aui.threads.item({ index: 0, archived: false }).getState().id).toBe(
96
+ "t1",
97
+ );
98
+ expect(aui.threads.item({ index: 0, archived: true }).getState().id).toBe(
99
+ "t2",
100
+ );
101
+ expect(() =>
102
+ aui.threads.item({ index: 1, archived: false }).getState(),
103
+ ).toThrow(/not found|out of bounds/);
104
+ expect(() =>
105
+ aui.threads.item({ index: 1, archived: true }).getState(),
106
+ ).toThrow(/not found|out of bounds/);
107
+ handle.destroy();
108
+ });
109
+ });
@@ -1,10 +1,7 @@
1
1
  import { useMemo, useState } from "react";
2
2
  import { useResource, withKey, resource } from "@assistant-ui/tap";
3
- import {
4
- type ClientOutput,
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 { type ClientOutput, useAssistantEmit } from "@assistant-ui/store";
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
- type ClientOutput,
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";