@assistant-ui/core 0.1.7 → 0.1.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 (112) hide show
  1. package/dist/adapters/index.d.ts +10 -0
  2. package/dist/adapters/index.d.ts.map +1 -0
  3. package/dist/adapters/index.js +4 -0
  4. package/dist/adapters/index.js.map +1 -0
  5. package/dist/adapters/mention.d.ts +24 -0
  6. package/dist/adapters/mention.d.ts.map +1 -0
  7. package/dist/adapters/mention.js +42 -0
  8. package/dist/adapters/mention.js.map +1 -0
  9. package/dist/index.d.ts +3 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +1 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/react/RuntimeAdapter.js +5 -6
  14. package/dist/react/RuntimeAdapter.js.map +1 -1
  15. package/dist/react/client/Tools.js +5 -6
  16. package/dist/react/client/Tools.js.map +1 -1
  17. package/dist/react/index.d.ts +2 -0
  18. package/dist/react/index.d.ts.map +1 -1
  19. package/dist/react/index.js +2 -0
  20. package/dist/react/index.js.map +1 -1
  21. package/dist/react/primitive-hooks/useComposerSend.d.ts +2 -1
  22. package/dist/react/primitive-hooks/useComposerSend.d.ts.map +1 -1
  23. package/dist/react/primitive-hooks/useComposerSend.js +5 -3
  24. package/dist/react/primitive-hooks/useComposerSend.js.map +1 -1
  25. package/dist/react/primitives/composer/ComposerQueue.d.ts +31 -0
  26. package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -0
  27. package/dist/react/primitives/composer/ComposerQueue.js +30 -0
  28. package/dist/react/primitives/composer/ComposerQueue.js.map +1 -0
  29. package/dist/react/providers/QueueItemByIndexProvider.d.ts +6 -0
  30. package/dist/react/providers/QueueItemByIndexProvider.d.ts.map +1 -0
  31. package/dist/react/providers/QueueItemByIndexProvider.js +13 -0
  32. package/dist/react/providers/QueueItemByIndexProvider.js.map +1 -0
  33. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  34. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +1 -0
  35. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  36. package/dist/runtime/interfaces/thread-runtime-core.d.ts +1 -0
  37. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  38. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  39. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +2 -0
  40. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  41. package/dist/runtimes/local/local-thread-runtime-core.d.ts +1 -0
  42. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  43. package/dist/runtimes/local/local-thread-runtime-core.js +1 -0
  44. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  45. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +1 -0
  46. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
  47. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +1 -0
  48. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
  49. package/dist/runtimes/remote-thread-list/empty-thread-core.d.ts.map +1 -1
  50. package/dist/runtimes/remote-thread-list/empty-thread-core.js +1 -0
  51. package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
  52. package/dist/runtimes/remote-thread-list/optimistic-state.d.ts +9 -0
  53. package/dist/runtimes/remote-thread-list/optimistic-state.d.ts.map +1 -1
  54. package/dist/runtimes/remote-thread-list/optimistic-state.js +20 -0
  55. package/dist/runtimes/remote-thread-list/optimistic-state.js.map +1 -1
  56. package/dist/store/clients/no-op-composer-client.d.ts.map +1 -1
  57. package/dist/store/clients/no-op-composer-client.js +4 -0
  58. package/dist/store/clients/no-op-composer-client.js.map +1 -1
  59. package/dist/store/clients/runtime-adapter.d.ts +1 -1
  60. package/dist/store/clients/runtime-adapter.d.ts.map +1 -1
  61. package/dist/store/clients/runtime-adapter.js +19 -26
  62. package/dist/store/clients/runtime-adapter.js.map +1 -1
  63. package/dist/store/index.d.ts +2 -1
  64. package/dist/store/index.d.ts.map +1 -1
  65. package/dist/store/index.js.map +1 -1
  66. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  67. package/dist/store/runtime-clients/composer-runtime-client.js +4 -0
  68. package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
  69. package/dist/store/scope-registration.d.ts +2 -0
  70. package/dist/store/scope-registration.d.ts.map +1 -1
  71. package/dist/store/scopes/composer.d.ts +20 -1
  72. package/dist/store/scopes/composer.d.ts.map +1 -1
  73. package/dist/store/scopes/queue-item.d.ts +20 -0
  74. package/dist/store/scopes/queue-item.d.ts.map +1 -0
  75. package/dist/store/scopes/queue-item.js +2 -0
  76. package/dist/store/scopes/queue-item.js.map +1 -0
  77. package/dist/types/index.d.ts +6 -0
  78. package/dist/types/index.d.ts.map +1 -0
  79. package/dist/types/index.js +2 -0
  80. package/dist/types/index.js.map +1 -0
  81. package/dist/types/mention.d.ts +32 -0
  82. package/dist/types/mention.d.ts.map +1 -0
  83. package/dist/types/mention.js +2 -0
  84. package/dist/types/mention.js.map +1 -0
  85. package/package.json +8 -8
  86. package/src/adapters/index.ts +34 -0
  87. package/src/adapters/mention.ts +77 -0
  88. package/src/index.ts +11 -0
  89. package/src/react/RuntimeAdapter.ts +5 -7
  90. package/src/react/client/Tools.ts +5 -6
  91. package/src/react/index.ts +5 -0
  92. package/src/react/primitive-hooks/useComposerSend.ts +11 -4
  93. package/src/react/primitives/composer/ComposerQueue.tsx +58 -0
  94. package/src/react/providers/QueueItemByIndexProvider.tsx +21 -0
  95. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +1 -0
  96. package/src/runtime/interfaces/thread-runtime-core.ts +1 -0
  97. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +2 -0
  98. package/src/runtimes/local/local-thread-runtime-core.ts +1 -0
  99. package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +1 -0
  100. package/src/runtimes/remote-thread-list/empty-thread-core.ts +1 -0
  101. package/src/runtimes/remote-thread-list/optimistic-state.ts +27 -0
  102. package/src/store/clients/no-op-composer-client.ts +4 -0
  103. package/src/store/clients/runtime-adapter.ts +20 -31
  104. package/src/store/index.ts +7 -0
  105. package/src/store/runtime-clients/composer-runtime-client.ts +4 -0
  106. package/src/store/scope-registration.ts +2 -0
  107. package/src/store/scopes/composer.ts +21 -1
  108. package/src/store/scopes/queue-item.ts +20 -0
  109. package/src/tests/OptimisticState-list-race.test.ts +256 -0
  110. package/src/tests/mention-formatter.test.ts +112 -0
  111. package/src/types/index.ts +47 -0
  112. package/src/types/mention.ts +50 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue-item.js","sourceRoot":"","sources":["../../../src/store/scopes/queue-item.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ export type { TextMessagePart, ReasoningMessagePart, SourceMessagePart, ImageMessagePart, FileMessagePart, DataMessagePart, Unstable_AudioMessagePart, ToolCallMessagePart, ThreadUserMessagePart, ThreadAssistantMessagePart, MessagePartStatus, ToolCallMessagePartStatus, MessageStatus, MessageTiming, ThreadStep, ThreadSystemMessage, ThreadUserMessage, ThreadAssistantMessage, ThreadMessage, MessageRole, RunConfig, AppendMessage, } from "./message.js";
2
+ export type { Attachment, PendingAttachment, CompleteAttachment, AttachmentStatus, CreateAttachment, } from "./attachment.js";
3
+ export type { Unsubscribe } from "./unsubscribe.js";
4
+ export type { QuoteInfo } from "./quote.js";
5
+ export type { Unstable_MentionItem, Unstable_MentionCategory, Unstable_DirectiveSegment, Unstable_DirectiveFormatter, } from "./mention.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAEV,eAAe,EACf,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,yBAAyB,EACzB,mBAAmB,EACnB,qBAAqB,EACrB,0BAA0B,EAE1B,iBAAiB,EACjB,yBAAyB,EACzB,aAAa,EAEb,aAAa,EACb,UAAU,EACV,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,aAAa,EACb,WAAW,EAEX,SAAS,EACT,aAAa,GACd,qBAAkB;AAEnB,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,GACjB,wBAAqB;AAEtB,YAAY,EAAE,WAAW,EAAE,yBAAsB;AAEjD,YAAY,EAAE,SAAS,EAAE,mBAAgB;AAEzC,YAAY,EACV,oBAAoB,EACpB,wBAAwB,EACxB,yBAAyB,EACzB,2BAA2B,GAC5B,qBAAkB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,32 @@
1
+ import type { ReadonlyJSONObject } from "assistant-stream/utils";
2
+ export type Unstable_MentionItem = {
3
+ readonly id: string;
4
+ readonly type: string;
5
+ readonly label: string;
6
+ readonly icon?: string | undefined;
7
+ readonly description?: string | undefined;
8
+ readonly metadata?: ReadonlyJSONObject | undefined;
9
+ };
10
+ export type Unstable_MentionCategory = {
11
+ readonly id: string;
12
+ readonly label: string;
13
+ readonly icon?: string | undefined;
14
+ };
15
+ /** Parsed segment from directive text */
16
+ export type Unstable_DirectiveSegment = {
17
+ readonly kind: "text";
18
+ readonly text: string;
19
+ } | {
20
+ readonly kind: "mention";
21
+ readonly type: string;
22
+ readonly label: string;
23
+ readonly id: string;
24
+ };
25
+ /** Configurable formatter for mention directive serialization/parsing */
26
+ export type Unstable_DirectiveFormatter = {
27
+ /** Serialize a mention item to directive text */
28
+ serialize(item: Unstable_MentionItem): string;
29
+ /** Parse text into alternating text and mention segments */
30
+ parse(text: string): readonly Unstable_DirectiveSegment[];
31
+ };
32
+ //# sourceMappingURL=mention.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mention.d.ts","sourceRoot":"","sources":["../../src/types/mention.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAMjE,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;CACpD,CAAC;AAMF,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC,CAAC;AAMF,yCAAyC;AACzC,MAAM,MAAM,yBAAyB,GACjC;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChD;IACE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB,CAAC;AAMN,yEAAyE;AACzE,MAAM,MAAM,2BAA2B,GAAG;IACxC,iDAAiD;IACjD,SAAS,CAAC,IAAI,EAAE,oBAAoB,GAAG,MAAM,CAAC;IAC9C,4DAA4D;IAC5D,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,yBAAyB,EAAE,CAAC;CAC3D,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=mention.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mention.js","sourceRoot":"","sources":["../../src/types/mention.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/core",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Framework-agnostic core runtime for assistant-ui",
5
5
  "keywords": [
6
6
  "assistant",
@@ -53,12 +53,12 @@
53
53
  ],
54
54
  "sideEffects": false,
55
55
  "dependencies": {
56
- "assistant-stream": "^0.3.6",
57
- "nanoid": "^5.1.6"
56
+ "assistant-stream": "^0.3.7",
57
+ "nanoid": "^5.1.7"
58
58
  },
59
59
  "peerDependencies": {
60
- "@assistant-ui/store": "^0.2.3",
61
- "@assistant-ui/tap": "^0.5.3",
60
+ "@assistant-ui/store": "^0.2.4",
61
+ "@assistant-ui/tap": "^0.5.4",
62
62
  "@types/react": "*",
63
63
  "react": "^18 || ^19",
64
64
  "assistant-cloud": "^0.1.22",
@@ -82,9 +82,9 @@
82
82
  "@types/react": "^19.2.14",
83
83
  "react": "^19.2.4",
84
84
  "vitest": "^4.1.0",
85
- "zustand": "^5.0.11",
86
- "@assistant-ui/store": "0.2.3",
87
- "@assistant-ui/tap": "0.5.3",
85
+ "zustand": "^5.0.12",
86
+ "@assistant-ui/store": "0.2.4",
87
+ "@assistant-ui/tap": "0.5.4",
88
88
  "@assistant-ui/x-buildutils": "0.0.3",
89
89
  "assistant-cloud": "0.1.22"
90
90
  },
@@ -0,0 +1,34 @@
1
+ // Attachment adapters
2
+ export type { AttachmentAdapter } from "./attachment";
3
+ export {
4
+ SimpleImageAttachmentAdapter,
5
+ SimpleTextAttachmentAdapter,
6
+ CompositeAttachmentAdapter,
7
+ } from "./attachment";
8
+
9
+ // Speech adapters
10
+ export type { SpeechSynthesisAdapter, DictationAdapter } from "./speech";
11
+ export {
12
+ WebSpeechSynthesisAdapter,
13
+ WebSpeechDictationAdapter,
14
+ } from "./speech";
15
+
16
+ // Feedback adapter
17
+ export type { FeedbackAdapter } from "./feedback";
18
+
19
+ // Suggestion adapter
20
+ export type { SuggestionAdapter } from "./suggestion";
21
+
22
+ // Mention adapter
23
+ export type { Unstable_MentionAdapter } from "./mention";
24
+ export { unstable_defaultDirectiveFormatter } from "./mention";
25
+
26
+ // Thread history adapters
27
+ export type {
28
+ ThreadHistoryAdapter,
29
+ GenericThreadHistoryAdapter,
30
+ MessageFormatAdapter,
31
+ MessageFormatItem,
32
+ MessageFormatRepository,
33
+ MessageStorageEntry,
34
+ } from "./thread-history";
@@ -0,0 +1,77 @@
1
+ import type {
2
+ Unstable_MentionCategory,
3
+ Unstable_MentionItem,
4
+ Unstable_DirectiveSegment,
5
+ Unstable_DirectiveFormatter,
6
+ } from "../types/mention";
7
+
8
+ // =============================================================================
9
+ // Mention Adapter
10
+ // =============================================================================
11
+
12
+ /**
13
+ * Adapter for providing mention categories and items to the mention picker.
14
+ *
15
+ * All methods are synchronous by design — the adapter drives UI display and
16
+ * must return data immediately. Use external state management (e.g. React
17
+ * Query, SWR, or local state) to handle async data fetching, then expose
18
+ * the loaded results synchronously through this adapter.
19
+ */
20
+ export type Unstable_MentionAdapter = {
21
+ /** Return the top-level categories for the mention picker. */
22
+ categories(): readonly Unstable_MentionCategory[];
23
+
24
+ /** Return items within a category. */
25
+ categoryItems(categoryId: string): readonly Unstable_MentionItem[];
26
+
27
+ /** Global search across all categories (optional). */
28
+ search?(query: string): readonly Unstable_MentionItem[];
29
+ };
30
+
31
+ // =============================================================================
32
+ // Default Directive Formatter
33
+ // =============================================================================
34
+
35
+ const DIRECTIVE_RE = /:([\w-]+)\[([^\]]+)\](?:\{name=([^}]+)\})?/g;
36
+
37
+ /**
38
+ * Default directive formatter using the `:type[label]{name=id}` syntax.
39
+ *
40
+ * When `id` equals `label`, the `{name=…}` attribute is omitted for brevity.
41
+ */
42
+ export const unstable_defaultDirectiveFormatter: Unstable_DirectiveFormatter = {
43
+ serialize(item: Unstable_MentionItem): string {
44
+ const attrs = item.id !== item.label ? `{name=${item.id}}` : "";
45
+ return `:${item.type}[${item.label}]${attrs}`;
46
+ },
47
+
48
+ parse(text: string): Unstable_DirectiveSegment[] {
49
+ const segments: Unstable_DirectiveSegment[] = [];
50
+ let lastIndex = 0;
51
+
52
+ DIRECTIVE_RE.lastIndex = 0;
53
+ let match: RegExpExecArray | null;
54
+ while ((match = DIRECTIVE_RE.exec(text)) !== null) {
55
+ if (match.index > lastIndex) {
56
+ segments.push({
57
+ kind: "text",
58
+ text: text.slice(lastIndex, match.index),
59
+ });
60
+ }
61
+ const label = match[2]!;
62
+ segments.push({
63
+ kind: "mention",
64
+ type: match[1]!,
65
+ label,
66
+ id: match[3] ?? label,
67
+ });
68
+ lastIndex = DIRECTIVE_RE.lastIndex;
69
+ }
70
+
71
+ if (lastIndex < text.length) {
72
+ segments.push({ kind: "text", text: text.slice(lastIndex) });
73
+ }
74
+
75
+ return segments;
76
+ },
77
+ };
package/src/index.ts CHANGED
@@ -43,6 +43,13 @@ export type { Unsubscribe } from "./types/unsubscribe";
43
43
 
44
44
  export type { QuoteInfo } from "./types/quote";
45
45
 
46
+ export type {
47
+ Unstable_MentionItem,
48
+ Unstable_MentionCategory,
49
+ Unstable_DirectiveSegment,
50
+ Unstable_DirectiveFormatter,
51
+ } from "./types/mention";
52
+
46
53
  // === model-context ===
47
54
 
48
55
  export type {
@@ -103,6 +110,10 @@ export type { FeedbackAdapter } from "./adapters/feedback";
103
110
  // Suggestion adapter
104
111
  export type { SuggestionAdapter } from "./adapters/suggestion";
105
112
 
113
+ // Mention adapter
114
+ export type { Unstable_MentionAdapter } from "./adapters/mention";
115
+ export { unstable_defaultDirectiveFormatter } from "./adapters/mention";
116
+
106
117
  // Thread history adapters
107
118
  export type {
108
119
  ThreadHistoryAdapter,
@@ -13,15 +13,13 @@ export const RuntimeAdapter = resource((runtime: AssistantRuntime) =>
13
13
  );
14
14
 
15
15
  attachTransformScopes(RuntimeAdapter, (scopes, parent) => {
16
- const result = baseRuntimeAdapterTransformScopes(scopes, parent);
16
+ baseRuntimeAdapterTransformScopes(scopes, parent);
17
17
 
18
- if (!result.tools && parent.tools.source === null) {
19
- result.tools = Tools({});
18
+ if (!scopes.tools && parent.tools.source === null) {
19
+ scopes.tools = Tools({});
20
20
  }
21
21
 
22
- if (!result.dataRenderers && parent.dataRenderers.source === null) {
23
- result.dataRenderers = DataRenderers();
22
+ if (!scopes.dataRenderers && parent.dataRenderers.source === null) {
23
+ scopes.dataRenderers = DataRenderers();
24
24
  }
25
-
26
- return result;
27
25
  });
@@ -89,9 +89,8 @@ export const Tools = resource(
89
89
  },
90
90
  );
91
91
 
92
- attachTransformScopes(Tools, (scopes, parent) => ({
93
- ...scopes,
94
- ...(scopes.modelContext || parent.modelContext.source !== null
95
- ? {}
96
- : { modelContext: ModelContext() }),
97
- }));
92
+ attachTransformScopes(Tools, (scopes, parent) => {
93
+ if (!scopes.modelContext && parent.modelContext.source === null) {
94
+ scopes.modelContext = ModelContext();
95
+ }
96
+ });
@@ -85,6 +85,10 @@ export {
85
85
  SuggestionByIndexProvider,
86
86
  type SuggestionByIndexProviderProps,
87
87
  } from "./providers/SuggestionByIndexProvider";
88
+ export {
89
+ QueueItemByIndexProvider,
90
+ type QueueItemByIndexProviderProps,
91
+ } from "./providers/QueueItemByIndexProvider";
88
92
  export { ReadonlyThreadProvider } from "./providers/ReadonlyThreadProvider";
89
93
 
90
94
  // RuntimeAdapter
@@ -144,6 +148,7 @@ export {
144
148
  ComposerPrimitiveAttachments,
145
149
  ComposerPrimitiveAttachmentByIndex,
146
150
  } from "./primitives/composer/ComposerAttachments";
151
+ export { ComposerPrimitiveQueue } from "./primitives/composer/ComposerQueue";
147
152
  export {
148
153
  ThreadListPrimitiveItems,
149
154
  ThreadListPrimitiveItemByIndex,
@@ -1,15 +1,22 @@
1
1
  import { useCallback } from "react";
2
2
  import { useAui, useAuiState } from "@assistant-ui/store";
3
+ import type { ComposerSendOptions } from "../../store/scopes/composer";
3
4
 
4
5
  export const useComposerSend = () => {
5
6
  const aui = useAui();
6
7
  const disabled = useAuiState(
7
- (s) => s.thread.isRunning || !s.composer.isEditing || s.composer.isEmpty,
8
+ (s) =>
9
+ (s.thread.isRunning && !s.thread.capabilities.queue) ||
10
+ !s.composer.isEditing ||
11
+ s.composer.isEmpty,
8
12
  );
9
13
 
10
- const send = useCallback(() => {
11
- aui.composer().send();
12
- }, [aui]);
14
+ const send = useCallback(
15
+ (opts?: ComposerSendOptions) => {
16
+ aui.composer().send(opts);
17
+ },
18
+ [aui],
19
+ );
13
20
 
14
21
  return { send, disabled };
15
22
  };
@@ -0,0 +1,58 @@
1
+ import { type FC, type ReactNode, memo, useMemo } from "react";
2
+ import { RenderChildrenWithAccessor, useAuiState } from "@assistant-ui/store";
3
+ import type { QueueItemState } from "../../../store/scopes/queue-item";
4
+ import { QueueItemByIndexProvider } from "../../providers/QueueItemByIndexProvider";
5
+
6
+ export namespace ComposerPrimitiveQueue {
7
+ export type Props = {
8
+ /** Render function called for each queue item. Receives the queue item state. */
9
+ children: (value: { queueItem: QueueItemState }) => ReactNode;
10
+ };
11
+ }
12
+
13
+ const ComposerPrimitiveQueueInner: FC<{
14
+ children: (value: { queueItem: QueueItemState }) => ReactNode;
15
+ }> = ({ children }) => {
16
+ const queue = useAuiState((s) => s.composer.queue.length);
17
+
18
+ return useMemo(
19
+ () =>
20
+ Array.from({ length: queue }, (_, index) => (
21
+ <QueueItemByIndexProvider key={index} index={index}>
22
+ <RenderChildrenWithAccessor
23
+ getItemState={(aui) =>
24
+ aui.composer().queueItem({ index }).getState()
25
+ }
26
+ >
27
+ {(getItem) =>
28
+ children({
29
+ get queueItem() {
30
+ return getItem();
31
+ },
32
+ })
33
+ }
34
+ </RenderChildrenWithAccessor>
35
+ </QueueItemByIndexProvider>
36
+ )),
37
+ [queue, children],
38
+ );
39
+ };
40
+
41
+ /**
42
+ * Renders all queue items in the composer.
43
+ *
44
+ * @example
45
+ * ```tsx
46
+ * <ComposerPrimitive.Queue>
47
+ * {({ queueItem }) => (
48
+ * <div>
49
+ * <QueueItemPrimitive.Text />
50
+ * <QueueItemPrimitive.Steer>Run Now</QueueItemPrimitive.Steer>
51
+ * </div>
52
+ * )}
53
+ * </ComposerPrimitive.Queue>
54
+ * ```
55
+ */
56
+ export const ComposerPrimitiveQueue = memo(ComposerPrimitiveQueueInner);
57
+
58
+ ComposerPrimitiveQueue.displayName = "ComposerPrimitive.Queue";
@@ -0,0 +1,21 @@
1
+ import { type FC, type PropsWithChildren } from "react";
2
+ import { AuiProvider, Derived, useAui } from "@assistant-ui/store";
3
+
4
+ export type QueueItemByIndexProviderProps = PropsWithChildren<{
5
+ index: number;
6
+ }>;
7
+
8
+ export const QueueItemByIndexProvider: FC<QueueItemByIndexProviderProps> = ({
9
+ index,
10
+ children,
11
+ }) => {
12
+ const aui = useAui({
13
+ queueItem: Derived({
14
+ source: "composer",
15
+ query: { index },
16
+ get: (aui) => aui.composer().queueItem({ index }),
17
+ }),
18
+ });
19
+
20
+ return <AuiProvider value={aui}>{children}</AuiProvider>;
21
+ };
@@ -480,6 +480,7 @@ export class RemoteThreadListThreadListRuntimeCore
480
480
  throw new Error("Thread is not yet initialized");
481
481
 
482
482
  await this._ensureThreadIsNotMain(data.id);
483
+ this._hookManager.stopThreadRuntime(data.id);
483
484
 
484
485
  return this._state.optimisticUpdate({
485
486
  execute: async () => {
@@ -26,6 +26,7 @@ export type RuntimeCapabilities = {
26
26
  readonly dictation: boolean;
27
27
  readonly attachments: boolean;
28
28
  readonly feedback: boolean;
29
+ readonly queue: boolean;
29
30
  };
30
31
 
31
32
  export type AddToolResultOptions = {
@@ -65,6 +65,7 @@ export class ExternalStoreThreadRuntimeCore
65
65
  dictation: false,
66
66
  attachments: false,
67
67
  feedback: false,
68
+ queue: false,
68
69
  };
69
70
 
70
71
  public get capabilities() {
@@ -139,6 +140,7 @@ export class ExternalStoreThreadRuntimeCore
139
140
  unstable_copy: this._store.unstable_capabilities?.copy !== false,
140
141
  attachments: !!this._store.adapters?.attachments,
141
142
  feedback: !!this._store.adapters?.feedback,
143
+ queue: false,
142
144
  };
143
145
  if (!shallowEqual(this._capabilities, newCapabilities)) {
144
146
  this._capabilities = newCapabilities;
@@ -47,6 +47,7 @@ export class LocalThreadRuntimeCore
47
47
  dictation: false,
48
48
  attachments: false,
49
49
  feedback: false,
50
+ queue: false,
50
51
  };
51
52
 
52
53
  private abortController: AbortController | null = null;
@@ -191,6 +191,7 @@ export class ReadonlyThreadRuntimeCore
191
191
  dictation: false,
192
192
  attachments: false,
193
193
  feedback: false,
194
+ queue: false,
194
195
  } as const;
195
196
 
196
197
  isDisabled = false;
@@ -165,6 +165,7 @@ export const EMPTY_THREAD_CORE: ThreadRuntimeCore = {
165
165
  dictation: false,
166
166
  attachments: false,
167
167
  feedback: false,
168
+ queue: false,
168
169
  },
169
170
 
170
171
  isDisabled: false,
@@ -30,6 +30,17 @@ const pipeTransforms = <TState, TExtra>(
30
30
  export class OptimisticState<TState> extends BaseSubscribable {
31
31
  private readonly _pendingTransforms: Array<PendingTransform<TState, any>> =
32
32
  [];
33
+
34
+ /**
35
+ * `optimistic` callbacks from transforms that have already resolved.
36
+ * Re-applied after every `then` callback so that a wholesale state
37
+ * replacement (e.g. list()) cannot erase earlier completed effects
38
+ * (e.g. delete). Cleared when no pending transforms remain.
39
+ *
40
+ * Correctness requirement: `optimistic` callbacks must be idempotent.
41
+ */
42
+ private readonly _completedOptimistics: Array<(state: TState) => TState> = [];
43
+
33
44
  private _baseValue: TState;
34
45
  private _cachedValue: TState;
35
46
 
@@ -77,12 +88,28 @@ export class OptimisticState<TState> extends BaseSubscribable {
77
88
  transform.optimistic,
78
89
  transform.then,
79
90
  ]);
91
+
92
+ // Re-apply previously completed optimistic callbacks so that a
93
+ // then() that does wholesale replacement cannot erase their effects.
94
+ for (const fn of this._completedOptimistics) {
95
+ this._baseValue = fn(this._baseValue);
96
+ }
97
+
98
+ if (transform.optimistic) {
99
+ this._completedOptimistics.push(transform.optimistic);
100
+ }
101
+
80
102
  return result;
81
103
  } finally {
82
104
  const index = this._pendingTransforms.indexOf(pendingTransform);
83
105
  if (index > -1) {
84
106
  this._pendingTransforms.splice(index, 1);
85
107
  }
108
+
109
+ if (this._pendingTransforms.length === 0) {
110
+ this._completedOptimistics.length = 0;
111
+ }
112
+
86
113
  this._updateState();
87
114
  }
88
115
  }
@@ -17,6 +17,7 @@ export const NoOpComposerClient = resource(
17
17
  type: type,
18
18
  dictation: undefined,
19
19
  quote: undefined,
20
+ queue: [],
20
21
  };
21
22
  }, [type]);
22
23
 
@@ -61,6 +62,9 @@ export const NoOpComposerClient = resource(
61
62
  setQuote: () => {
62
63
  throw new Error("Not supported");
63
64
  },
65
+ queueItem: () => {
66
+ throw new Error("Not supported");
67
+ },
64
68
  };
65
69
  },
66
70
  );
@@ -30,38 +30,27 @@ export const RuntimeAdapterResource = resource((runtime: AssistantRuntime) => {
30
30
  export const baseRuntimeAdapterTransformScopes = (
31
31
  scopes: ScopesConfig,
32
32
  parent: AssistantClient,
33
- ): ScopesConfig => {
34
- const result = {
35
- ...scopes,
36
- thread:
37
- scopes.thread ??
38
- Derived({
39
- source: "threads",
40
- query: { type: "main" },
41
- get: (aui) => aui.threads().thread("main"),
42
- }),
43
- threadListItem:
44
- scopes.threadListItem ??
45
- Derived({
46
- source: "threads",
47
- query: { type: "main" },
48
- get: (aui) => aui.threads().item("main"),
49
- }),
50
- composer:
51
- scopes.composer ??
52
- Derived({
53
- source: "thread",
54
- query: {},
55
- get: (aui) => aui.threads().thread("main").composer(),
56
- }),
57
- };
33
+ ): void => {
34
+ scopes.thread ??= Derived({
35
+ source: "threads",
36
+ query: { type: "main" },
37
+ get: (aui) => aui.threads().thread("main"),
38
+ });
39
+ scopes.threadListItem ??= Derived({
40
+ source: "threads",
41
+ query: { type: "main" },
42
+ get: (aui) => aui.threads().item("main"),
43
+ });
44
+ scopes.composer ??= Derived({
45
+ source: "thread",
46
+ query: {},
47
+ get: (aui) => aui.threads().thread("main").composer(),
48
+ });
58
49
 
59
- if (!result.modelContext && parent.modelContext.source === null) {
60
- result.modelContext = ModelContext();
50
+ if (!scopes.modelContext && parent.modelContext.source === null) {
51
+ scopes.modelContext = ModelContext();
61
52
  }
62
- if (!result.suggestions && parent.suggestions.source === null) {
63
- result.suggestions = Suggestions();
53
+ if (!scopes.suggestions && parent.suggestions.source === null) {
54
+ scopes.suggestions = Suggestions();
64
55
  }
65
-
66
- return result;
67
56
  };
@@ -35,10 +35,17 @@ export type {
35
35
  export type {
36
36
  ComposerState,
37
37
  ComposerMethods,
38
+ ComposerSendOptions,
38
39
  ComposerMeta,
39
40
  ComposerEvents,
40
41
  ComposerClientSchema,
41
42
  } from "./scopes/composer";
43
+ export type {
44
+ QueueItemState,
45
+ QueueItemMethods,
46
+ QueueItemMeta,
47
+ QueueItemClientSchema,
48
+ } from "./scopes/queue-item";
42
49
  export type {
43
50
  AttachmentState,
44
51
  AttachmentMethods,
@@ -101,6 +101,7 @@ export const ComposerClient = resource(
101
101
  type: runtimeState.type ?? "thread",
102
102
  dictation: runtimeState.dictation,
103
103
  quote: runtimeState.quote,
104
+ queue: [],
104
105
  };
105
106
  }, [runtimeState, attachments.state]);
106
107
 
@@ -129,6 +130,9 @@ export const ComposerClient = resource(
129
130
  return attachments.get(selector);
130
131
  }
131
132
  },
133
+ queueItem: () => {
134
+ throw new Error("Queue is not supported in this runtime");
135
+ },
132
136
  __internal_getRuntime: () => runtime,
133
137
  };
134
138
  },
@@ -9,6 +9,7 @@ import type { ModelContextClientSchema } from "./scopes/model-context";
9
9
  import type { SuggestionsClientSchema } from "./scopes/suggestions";
10
10
  import type { SuggestionClientSchema } from "./scopes/suggestion";
11
11
  import type { ChainOfThoughtClientSchema } from "./scopes/chain-of-thought";
12
+ import type { QueueItemClientSchema } from "./scopes/queue-item";
12
13
 
13
14
  declare module "@assistant-ui/store" {
14
15
  interface ScopeRegistry {
@@ -23,5 +24,6 @@ declare module "@assistant-ui/store" {
23
24
  suggestions: SuggestionsClientSchema;
24
25
  suggestion: SuggestionClientSchema;
25
26
  chainOfThought: ChainOfThoughtClientSchema;
27
+ queueItem: QueueItemClientSchema;
26
28
  }
27
29
  }