@assistant-ui/core 0.1.8 → 0.1.9

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 (53) hide show
  1. package/dist/adapters/attachment.d.ts +4 -0
  2. package/dist/adapters/attachment.d.ts.map +1 -1
  3. package/dist/adapters/attachment.js +1 -1
  4. package/dist/adapters/attachment.js.map +1 -1
  5. package/dist/react/client/Interactables.d.ts +3 -0
  6. package/dist/react/client/Interactables.d.ts.map +1 -0
  7. package/dist/react/client/Interactables.js +173 -0
  8. package/dist/react/client/Interactables.js.map +1 -0
  9. package/dist/react/index.d.ts +4 -0
  10. package/dist/react/index.d.ts.map +1 -1
  11. package/dist/react/index.js +3 -0
  12. package/dist/react/index.js.map +1 -1
  13. package/dist/react/model-context/makeInteractable.d.ts +10 -0
  14. package/dist/react/model-context/makeInteractable.d.ts.map +1 -0
  15. package/dist/react/model-context/makeInteractable.js +10 -0
  16. package/dist/react/model-context/makeInteractable.js.map +1 -0
  17. package/dist/react/model-context/useInteractable.d.ts +16 -0
  18. package/dist/react/model-context/useInteractable.d.ts.map +1 -0
  19. package/dist/react/model-context/useInteractable.js +36 -0
  20. package/dist/react/model-context/useInteractable.js.map +1 -0
  21. package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
  22. package/dist/react/primitives/message/MessageParts.js +2 -0
  23. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  24. package/dist/react/types/scopes/interactables.d.ts +39 -0
  25. package/dist/react/types/scopes/interactables.d.ts.map +1 -0
  26. package/dist/react/types/scopes/interactables.js +2 -0
  27. package/dist/react/types/scopes/interactables.js.map +1 -0
  28. package/dist/react/types/store-augmentation.d.ts +2 -0
  29. package/dist/react/types/store-augmentation.d.ts.map +1 -1
  30. package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -1
  31. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  32. package/dist/runtime/base/base-composer-runtime-core.js +33 -8
  33. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  34. package/dist/runtime/interfaces/composer-runtime-core.d.ts +1 -1
  35. package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
  36. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  37. package/dist/store/runtime-clients/composer-runtime-client.js +12 -5
  38. package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
  39. package/dist/store/scopes/composer.d.ts +5 -0
  40. package/dist/store/scopes/composer.d.ts.map +1 -1
  41. package/package.json +9 -9
  42. package/src/adapters/attachment.ts +1 -1
  43. package/src/react/client/Interactables.ts +233 -0
  44. package/src/react/index.ts +19 -0
  45. package/src/react/model-context/makeInteractable.ts +21 -0
  46. package/src/react/model-context/useInteractable.ts +73 -0
  47. package/src/react/primitives/message/MessageParts.tsx +2 -0
  48. package/src/react/types/scopes/interactables.ts +44 -0
  49. package/src/react/types/store-augmentation.ts +2 -0
  50. package/src/runtime/base/base-composer-runtime-core.ts +45 -9
  51. package/src/runtime/interfaces/composer-runtime-core.ts +4 -1
  52. package/src/store/runtime-clients/composer-runtime-client.ts +18 -7
  53. package/src/store/scopes/composer.ts +5 -0
@@ -440,6 +440,8 @@ const EmptyPartsImpl: FC<MessagePartComponentProps> = ({ components }) => {
440
440
 
441
441
  if (components?.Empty) return <components.Empty status={status} />;
442
442
 
443
+ if (status.type !== "running") return null;
444
+
443
445
  return (
444
446
  <EmptyPartFallback
445
447
  status={status}
@@ -0,0 +1,44 @@
1
+ import type { Tool } from "assistant-stream";
2
+ import type { Unsubscribe } from "../../..";
3
+
4
+ /**
5
+ * Schema type matching Tool["parameters"] from assistant-stream.
6
+ * Accepts both StandardSchemaV1 and JSONSchema7.
7
+ */
8
+ export type InteractableStateSchema = NonNullable<
9
+ Extract<Tool, { parameters: unknown }>["parameters"]
10
+ >;
11
+
12
+ export type InteractableDefinition = {
13
+ id: string;
14
+ name: string;
15
+ description: string;
16
+ stateSchema: InteractableStateSchema;
17
+ state: unknown;
18
+ selected?: boolean | undefined;
19
+ };
20
+
21
+ export type InteractableRegistration = {
22
+ id: string;
23
+ name: string;
24
+ description: string;
25
+ stateSchema: InteractableStateSchema;
26
+ initialState: unknown;
27
+ selected?: boolean | undefined;
28
+ };
29
+
30
+ export type InteractablesState = {
31
+ /** Keyed by instance id */
32
+ definitions: Record<string, InteractableDefinition>;
33
+ };
34
+
35
+ export type InteractablesMethods = {
36
+ getState(): InteractablesState;
37
+ register(def: InteractableRegistration): Unsubscribe;
38
+ setState(id: string, updater: (prev: unknown) => unknown): void;
39
+ setSelected(id: string, selected: boolean): void;
40
+ };
41
+
42
+ export type InteractablesClientSchema = {
43
+ methods: InteractablesMethods;
44
+ };
@@ -1,9 +1,11 @@
1
1
  import type { ToolsClientSchema } from "./scopes/tools";
2
2
  import type { DataRenderersClientSchema } from "./scopes/dataRenderers";
3
+ import type { InteractablesClientSchema } from "./scopes/interactables";
3
4
 
4
5
  declare module "@assistant-ui/store" {
5
6
  interface ScopeRegistry {
6
7
  tools: ToolsClientSchema;
7
8
  dataRenderers: DataRenderersClientSchema;
9
+ interactables: InteractablesClientSchema;
8
10
  }
9
11
  }
@@ -9,7 +9,10 @@ import type { QuoteInfo } from "../../types/quote";
9
9
  import type { Unsubscribe } from "../../types/unsubscribe";
10
10
  import type { RunConfig } from "../../types/message";
11
11
  import { BaseSubscribable } from "../../subscribable/subscribable";
12
- import type { AttachmentAdapter } from "../../adapters/attachment";
12
+ import {
13
+ type AttachmentAdapter,
14
+ fileMatchesAccept,
15
+ } from "../../adapters/attachment";
13
16
  import type {
14
17
  ComposerRuntimeCore,
15
18
  ComposerRuntimeEventType,
@@ -216,6 +219,17 @@ export abstract class BaseComposerRuntimeCore
216
219
  const adapter = this.getAttachmentAdapter();
217
220
  if (!adapter) throw new Error("Attachments are not supported");
218
221
 
222
+ if (
223
+ !fileMatchesAccept(
224
+ { name: fileOrAttachment.name, type: fileOrAttachment.type },
225
+ adapter.accept,
226
+ )
227
+ ) {
228
+ throw new Error(
229
+ `File type ${fileOrAttachment.type || "unknown"} is not accepted. Accepted types: ${adapter.accept}`,
230
+ );
231
+ }
232
+
219
233
  const upsertAttachment = (a: PendingAttachment) => {
220
234
  const idx = this._attachments.findIndex(
221
235
  (attachment) => attachment.id === a.id,
@@ -233,17 +247,39 @@ export abstract class BaseComposerRuntimeCore
233
247
  this._notifySubscribers();
234
248
  };
235
249
 
236
- const promiseOrGenerator = adapter.add({ file: fileOrAttachment });
237
- if (Symbol.asyncIterator in promiseOrGenerator) {
238
- for await (const r of promiseOrGenerator) {
239
- upsertAttachment(r);
250
+ let lastAttachment: PendingAttachment | undefined;
251
+ try {
252
+ const promiseOrGenerator = adapter.add({ file: fileOrAttachment });
253
+ if (Symbol.asyncIterator in promiseOrGenerator) {
254
+ for await (const r of promiseOrGenerator) {
255
+ lastAttachment = r;
256
+ upsertAttachment(r);
257
+ }
258
+ } else {
259
+ lastAttachment = await promiseOrGenerator;
260
+ upsertAttachment(lastAttachment);
240
261
  }
241
- } else {
242
- upsertAttachment(await promiseOrGenerator);
262
+ } catch (e) {
263
+ if (lastAttachment) {
264
+ upsertAttachment({
265
+ ...lastAttachment,
266
+ status: { type: "incomplete", reason: "error" },
267
+ });
268
+ }
269
+ try {
270
+ this._notifyEventSubscribers("attachmentAddError");
271
+ } catch {
272
+ // prevent subscriber errors from masking the adapter error
273
+ }
274
+ throw e;
243
275
  }
244
276
 
245
- this._notifyEventSubscribers("attachmentAdd");
246
- this._notifySubscribers();
277
+ const hasError =
278
+ lastAttachment?.status.type === "incomplete" &&
279
+ lastAttachment.status.reason === "error";
280
+ this._notifyEventSubscribers(
281
+ hasError ? "attachmentAddError" : "attachmentAdd",
282
+ );
247
283
  }
248
284
 
249
285
  async removeAttachment(attachmentId: string) {
@@ -5,7 +5,10 @@ import type { Unsubscribe } from "../../types/unsubscribe";
5
5
  import type { RunConfig } from "../../types/message";
6
6
  import type { DictationAdapter } from "../../adapters/speech";
7
7
 
8
- export type ComposerRuntimeEventType = "send" | "attachmentAdd";
8
+ export type ComposerRuntimeEventType =
9
+ | "send"
10
+ | "attachmentAdd"
11
+ | "attachmentAddError";
9
12
 
10
13
  export type DictationState = {
11
14
  readonly status: DictationAdapter.Status;
@@ -16,7 +16,6 @@ import {
16
16
  ComposerRuntime,
17
17
  EditComposerRuntime,
18
18
  } from "../../runtime/api/composer-runtime";
19
- import { ComposerRuntimeEventType } from "../../runtime/interfaces/composer-runtime-core";
20
19
  import { ComposerState } from "../scopes/composer";
21
20
  import { AttachmentRuntimeClient } from "./attachment-runtime-client";
22
21
  import { tapSubscribable } from "./tap-subscribable";
@@ -54,12 +53,7 @@ export const ComposerClient = resource(
54
53
  const unsubscribers: Unsubscribe[] = [];
55
54
 
56
55
  // Subscribe to composer events
57
- const composerEvents: ComposerRuntimeEventType[] = [
58
- "send",
59
- "attachmentAdd",
60
- ];
61
-
62
- for (const event of composerEvents) {
56
+ for (const event of ["send", "attachmentAdd"] as const) {
63
57
  const unsubscribe = runtime.unstable_on(event, () => {
64
58
  emit(`composer.${event}`, {
65
59
  threadId: threadIdRef.current,
@@ -69,6 +63,23 @@ export const ComposerClient = resource(
69
63
  unsubscribers.push(unsubscribe);
70
64
  }
71
65
 
66
+ // attachmentAddError carries the failed attachment ID
67
+ unsubscribers.push(
68
+ runtime.unstable_on("attachmentAddError", () => {
69
+ const errorAttachment = runtime
70
+ .getState()
71
+ .attachments.findLast(
72
+ (a) =>
73
+ a.status.type === "incomplete" && a.status.reason === "error",
74
+ );
75
+ emit("composer.attachmentAddError", {
76
+ threadId: threadIdRef.current,
77
+ ...(messageIdRef && { messageId: messageIdRef.current }),
78
+ ...(errorAttachment && { attachmentId: errorAttachment.id }),
79
+ });
80
+ }),
81
+ );
82
+
72
83
  return () => {
73
84
  for (const unsub of unsubscribers) unsub();
74
85
  };
@@ -90,6 +90,11 @@ export type ComposerMeta = {
90
90
  export type ComposerEvents = {
91
91
  "composer.send": { threadId: string; messageId?: string };
92
92
  "composer.attachmentAdd": { threadId: string; messageId?: string };
93
+ "composer.attachmentAddError": {
94
+ threadId: string;
95
+ messageId?: string;
96
+ attachmentId?: string;
97
+ };
93
98
  };
94
99
 
95
100
  export type ComposerClientSchema = {