@assistant-ui/core 0.3.3 → 0.3.4

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 (127) hide show
  1. package/dist/internal.d.ts +4 -2
  2. package/dist/internal.js +4 -2
  3. package/dist/react/primitive-hooks/useSuggestionTrigger.js +29 -25
  4. package/dist/react/primitive-hooks/useSuggestionTrigger.js.map +1 -1
  5. package/dist/react/primitives/message/MessageParts.d.ts +6 -1
  6. package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
  7. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  8. package/dist/react/providers/ReadonlyThreadProvider.d.ts.map +1 -1
  9. package/dist/react/providers/ReadonlyThreadProvider.js +1 -0
  10. package/dist/react/providers/ReadonlyThreadProvider.js.map +1 -1
  11. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +67 -0
  12. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  13. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +76 -15
  14. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  15. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +4 -0
  16. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  17. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +21 -0
  18. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  19. package/dist/react/runtimes/cloud/auiV0.d.ts +2 -0
  20. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  21. package/dist/react/runtimes/cloud/auiV0.js +6 -4
  22. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  23. package/dist/react/types/MessagePartComponentTypes.d.ts +8 -0
  24. package/dist/react/types/MessagePartComponentTypes.d.ts.map +1 -1
  25. package/dist/runtime/api/bindings.d.ts +5 -0
  26. package/dist/runtime/api/bindings.d.ts.map +1 -1
  27. package/dist/runtime/api/thread-list-runtime.d.ts +14 -1
  28. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  29. package/dist/runtime/api/thread-list-runtime.js +6 -1
  30. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  31. package/dist/runtime/api/thread-runtime.d.ts +7 -1
  32. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  33. package/dist/runtime/api/thread-runtime.js +11 -3
  34. package/dist/runtime/api/thread-runtime.js.map +1 -1
  35. package/dist/runtime/base/base-composer-runtime-core.d.ts +3 -0
  36. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  37. package/dist/runtime/base/base-composer-runtime-core.js +54 -12
  38. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  39. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +10 -0
  40. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
  41. package/dist/runtime/interfaces/thread-runtime-core.d.ts +14 -0
  42. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  43. package/dist/runtimes/external-store/external-store-adapter.d.ts +9 -0
  44. package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
  45. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +1 -0
  46. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  47. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +4 -0
  48. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  49. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +1 -0
  50. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  51. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +6 -0
  52. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  53. package/dist/runtimes/local/local-thread-runtime-core.d.ts +1 -0
  54. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  55. package/dist/runtimes/local/local-thread-runtime-core.js +1 -0
  56. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  57. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +1 -0
  58. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
  59. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +1 -0
  60. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
  61. package/dist/runtimes/remote-thread-list/empty-thread-core.js +1 -0
  62. package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
  63. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts +1 -0
  64. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  65. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +66 -45
  66. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  67. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  68. package/dist/store/runtime-clients/thread-list-runtime-client.js +91 -75
  69. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  70. package/dist/store/scopes/thread-list-item.d.ts +7 -0
  71. package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
  72. package/dist/store/scopes/threads.d.ts +1 -0
  73. package/dist/store/scopes/threads.d.ts.map +1 -1
  74. package/dist/types/message.d.ts +11 -0
  75. package/dist/types/message.d.ts.map +1 -1
  76. package/dist/types/message.js.map +1 -1
  77. package/dist/utils/data-url.d.ts +14 -1
  78. package/dist/utils/data-url.d.ts.map +1 -1
  79. package/dist/utils/data-url.js +23 -1
  80. package/dist/utils/data-url.js.map +1 -1
  81. package/dist/utils/image-media-type.d.ts +13 -0
  82. package/dist/utils/image-media-type.d.ts.map +1 -0
  83. package/dist/utils/image-media-type.js +141 -0
  84. package/dist/utils/image-media-type.js.map +1 -0
  85. package/dist/utils/wire-media.d.ts +35 -0
  86. package/dist/utils/wire-media.d.ts.map +1 -0
  87. package/dist/utils/wire-media.js +55 -0
  88. package/dist/utils/wire-media.js.map +1 -0
  89. package/package.json +3 -3
  90. package/src/internal.ts +12 -1
  91. package/src/react/primitive-hooks/useSuggestionTrigger.test.ts +148 -0
  92. package/src/react/primitive-hooks/useSuggestionTrigger.ts +10 -4
  93. package/src/react/primitives/message/MessageParts.tsx +6 -1
  94. package/src/react/providers/ReadonlyThreadProvider.tsx +1 -0
  95. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +157 -0
  96. package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +160 -0
  97. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +135 -32
  98. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +37 -0
  99. package/src/react/runtimes/cloud/auiV0.ts +8 -2
  100. package/src/react/types/MessagePartComponentTypes.ts +8 -0
  101. package/src/runtime/api/bindings.ts +5 -0
  102. package/src/runtime/api/thread-list-runtime.ts +22 -1
  103. package/src/runtime/api/thread-runtime.ts +15 -6
  104. package/src/runtime/base/base-composer-runtime-core.ts +72 -19
  105. package/src/runtime/interfaces/thread-list-runtime-core.ts +11 -0
  106. package/src/runtime/interfaces/thread-runtime-core.ts +15 -0
  107. package/src/runtimes/external-store/external-store-adapter.ts +9 -0
  108. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +9 -0
  109. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +8 -0
  110. package/src/runtimes/local/local-thread-runtime-core.ts +1 -0
  111. package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +1 -0
  112. package/src/runtimes/remote-thread-list/empty-thread-core.ts +1 -0
  113. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +13 -2
  114. package/src/store/runtime-clients/thread-list-runtime-client.ts +13 -1
  115. package/src/store/scopes/thread-list-item.ts +7 -0
  116. package/src/store/scopes/threads.ts +1 -0
  117. package/src/tests/RemoteThreadListThreadListRuntimeCore-reloadMainThread.test.ts +257 -0
  118. package/src/tests/auiV0Encode.test.ts +125 -1
  119. package/src/tests/base-composer-runtime-core-addAttachment.test.ts +296 -0
  120. package/src/tests/event-subscription-listener-errors.test.ts +1 -0
  121. package/src/tests/external-store-thread-list-runtime-core.test.ts +39 -1
  122. package/src/types/message.ts +11 -0
  123. package/src/utils/data-url.ts +24 -0
  124. package/src/utils/image-media-type.test.ts +96 -0
  125. package/src/utils/image-media-type.ts +118 -0
  126. package/src/utils/wire-media.test.ts +96 -0
  127. package/src/utils/wire-media.ts +70 -0
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Leading-byte signatures for the image formats a browser or attachment
3
+ * adapter realistically produces. `null` matches any byte, which WebP needs
4
+ * because its four size bytes sit between the two markers, and AVIF and HEIC
5
+ * need because their leading `ftyp` box size varies by encoder.
6
+ *
7
+ * These are file format constants rather than an evolving API, so the table
8
+ * mirrors the one the AI SDK applies on the far side of several adapters
9
+ * without taking a dependency to reach it.
10
+ */
11
+ const IMAGE_SIGNATURES: readonly {
12
+ readonly mediaType: string;
13
+ readonly prefix: readonly (number | null)[];
14
+ }[] = [
15
+ { mediaType: "image/gif", prefix: [0x47, 0x49, 0x46] },
16
+ { mediaType: "image/png", prefix: [0x89, 0x50, 0x4e, 0x47] },
17
+ { mediaType: "image/jpeg", prefix: [0xff, 0xd8] },
18
+ {
19
+ mediaType: "image/webp",
20
+ prefix: [
21
+ 0x52,
22
+ 0x49,
23
+ 0x46,
24
+ 0x46,
25
+ null,
26
+ null,
27
+ null,
28
+ null,
29
+ 0x57,
30
+ 0x45,
31
+ 0x42,
32
+ 0x50,
33
+ ],
34
+ },
35
+ { mediaType: "image/bmp", prefix: [0x42, 0x4d] },
36
+ { mediaType: "image/tiff", prefix: [0x49, 0x49, 0x2a, 0x00] },
37
+ { mediaType: "image/tiff", prefix: [0x4d, 0x4d, 0x00, 0x2a] },
38
+ {
39
+ mediaType: "image/avif",
40
+ prefix: [
41
+ null,
42
+ null,
43
+ null,
44
+ null,
45
+ 0x66,
46
+ 0x74,
47
+ 0x79,
48
+ 0x70,
49
+ 0x61,
50
+ 0x76,
51
+ 0x69,
52
+ 0x66,
53
+ ],
54
+ },
55
+ {
56
+ mediaType: "image/heic",
57
+ prefix: [
58
+ null,
59
+ null,
60
+ null,
61
+ null,
62
+ 0x66,
63
+ 0x74,
64
+ 0x79,
65
+ 0x70,
66
+ 0x68,
67
+ 0x65,
68
+ 0x69,
69
+ 0x63,
70
+ ],
71
+ },
72
+ ];
73
+
74
+ /** 16 base64 characters decode to the 12 bytes the longest signature needs. */
75
+ const PREFIX_CHARS = 16;
76
+
77
+ const decodePrefix = (base64: string): Uint8Array | undefined => {
78
+ const prefix = base64.slice(0, PREFIX_CHARS);
79
+ const aligned = prefix.slice(0, prefix.length - (prefix.length % 4));
80
+ if (aligned.length === 0) return undefined;
81
+
82
+ const nodeBuffer = (
83
+ globalThis as {
84
+ Buffer?: {
85
+ from(data: string, encoding: string): Uint8Array;
86
+ };
87
+ }
88
+ ).Buffer;
89
+ if (nodeBuffer) return new Uint8Array(nodeBuffer.from(aligned, "base64"));
90
+
91
+ try {
92
+ const binary = atob(aligned);
93
+ const bytes = new Uint8Array(binary.length);
94
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
95
+ return bytes;
96
+ } catch {
97
+ return undefined;
98
+ }
99
+ };
100
+
101
+ /**
102
+ * The image media type a base64 payload's leading bytes identify, or
103
+ * `undefined` when they match no known signature or the input is not base64.
104
+ *
105
+ * An `ImageMessagePart` carries no media type, so an adapter that must declare
106
+ * one on the wire reads it from the bytes rather than assuming a format. Never
107
+ * throws: the input is whatever a caller put on the part.
108
+ */
109
+ export function detectImageMediaType(base64: string): string | undefined {
110
+ const bytes = decodePrefix(base64);
111
+ if (!bytes) return undefined;
112
+
113
+ return IMAGE_SIGNATURES.find(
114
+ ({ prefix }) =>
115
+ bytes.length >= prefix.length &&
116
+ prefix.every((byte, index) => byte === null || bytes[index] === byte),
117
+ )?.mediaType;
118
+ }
@@ -0,0 +1,96 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ resolveFileMediaType,
4
+ resolveImageMediaType,
5
+ toMediaWireUrl,
6
+ } from "./wire-media";
7
+
8
+ const JPEG = "/9j/4AAQSkZJRg==";
9
+
10
+ describe("resolveImageMediaType", () => {
11
+ it("prefers an image content type over everything else", () => {
12
+ expect(
13
+ resolveImageMediaType(`data:image/png;base64,${JPEG}`, "image/webp"),
14
+ ).toBe("image/webp");
15
+ });
16
+
17
+ it("ignores a content type that is not an image", () => {
18
+ expect(resolveImageMediaType(JPEG, "application/octet-stream")).toBe(
19
+ "image/jpeg",
20
+ );
21
+ });
22
+
23
+ it("takes the data url declaration when it is an image type", () => {
24
+ expect(
25
+ resolveImageMediaType("data:image/svg+xml,%3Csvg%3E%3C/svg%3E"),
26
+ ).toBe("image/svg+xml");
27
+ });
28
+
29
+ it("sniffs through a declaration that is not an image type", () => {
30
+ expect(
31
+ resolveImageMediaType(`data:application/octet-stream;base64,${JPEG}`),
32
+ ).toBe("image/jpeg");
33
+ });
34
+
35
+ it("sniffs a bare base64 payload", () => {
36
+ expect(resolveImageMediaType(JPEG)).toBe("image/jpeg");
37
+ });
38
+
39
+ it("floors to image/png when there are no bytes to read", () => {
40
+ expect(resolveImageMediaType("https://cdn.example.com/photo")).toBe(
41
+ "image/png",
42
+ );
43
+ });
44
+
45
+ it("floors to image/png when the bytes match no signature", () => {
46
+ expect(resolveImageMediaType("QUJD")).toBe("image/png");
47
+ });
48
+ });
49
+
50
+ describe("resolveFileMediaType", () => {
51
+ it("prefers the declared type", () => {
52
+ expect(
53
+ resolveFileMediaType(
54
+ "data:application/octet-stream;base64,QUJD",
55
+ "application/pdf",
56
+ ),
57
+ ).toBe("application/pdf");
58
+ });
59
+
60
+ it("falls back to the data url declaration when none is declared", () => {
61
+ expect(resolveFileMediaType("data:text/plain,hello", "")).toBe(
62
+ "text/plain",
63
+ );
64
+ });
65
+
66
+ it("floors to application/octet-stream", () => {
67
+ expect(resolveFileMediaType("QUJD", "")).toBe("application/octet-stream");
68
+ });
69
+ });
70
+
71
+ describe("toMediaWireUrl", () => {
72
+ it("forwards an envelope that agrees byte for byte", () => {
73
+ const payload = "data:image/jpeg;base64,QUJD";
74
+ expect(toMediaWireUrl(payload, "image/jpeg")).toBe(payload);
75
+ });
76
+
77
+ it("rebuilds an envelope that disagrees", () => {
78
+ expect(
79
+ toMediaWireUrl("data:application/octet-stream;base64,QUJD", "image/jpeg"),
80
+ ).toBe("data:image/jpeg;base64,QUJD");
81
+ });
82
+
83
+ it("wraps a bare base64 payload", () => {
84
+ expect(toMediaWireUrl("QUJD", "application/pdf")).toBe(
85
+ "data:application/pdf;base64,QUJD",
86
+ );
87
+ });
88
+
89
+ it.each([
90
+ "https://cdn.example.com/a.pdf",
91
+ "blob:https://app.example/1-2-3",
92
+ "data:text/plain,hello",
93
+ ])("forwards %s untouched", (payload) => {
94
+ expect(toMediaWireUrl(payload, "application/pdf")).toBe(payload);
95
+ });
96
+ });
@@ -0,0 +1,70 @@
1
+ import { dataUrlMediaType, isParsableUrl, parseDataUrl } from "./data-url";
2
+ import { detectImageMediaType } from "./image-media-type";
3
+
4
+ /**
5
+ * The media type to declare for an `ImageMessagePart`, which carries none of
6
+ * its own: an attachment's content type, then the payload's data URL
7
+ * declaration, then its leading bytes, then `image/png`.
8
+ *
9
+ * The floor cannot be a wildcard. `image/*` is rejected outright by the AI SDK
10
+ * for a url source and whenever inline bytes fail to sniff, so it fails exactly
11
+ * where a fallback is needed.
12
+ */
13
+ export function resolveImageMediaType(
14
+ image: string,
15
+ contentType?: string | undefined,
16
+ ): string {
17
+ if (contentType?.startsWith("image/")) return contentType;
18
+
19
+ const declared = dataUrlMediaType(image);
20
+ if (declared?.startsWith("image/")) return declared;
21
+
22
+ // Read through a data URL envelope too, so a generic one such as
23
+ // `application/octet-stream` does not mask the format. A url of any other
24
+ // scheme has no local bytes to read.
25
+ const parsed = parseDataUrl(image);
26
+ const payload = parsed?.data ?? (isParsableUrl(image) ? undefined : image);
27
+ if (payload !== undefined) {
28
+ const sniffed = detectImageMediaType(payload);
29
+ if (sniffed) return sniffed;
30
+ }
31
+
32
+ return "image/png";
33
+ }
34
+
35
+ /**
36
+ * The media type to declare for a `FileMessagePart`: its own `mimeType`, then
37
+ * the payload's data URL declaration, then `application/octet-stream`.
38
+ *
39
+ * `mimeType` is a plain string, and an adapter reading `file.type` on a file
40
+ * the OS cannot type yields `""`, so the declared value is not always present.
41
+ */
42
+ export function resolveFileMediaType(
43
+ data: string,
44
+ mimeType?: string | undefined,
45
+ ): string {
46
+ return mimeType || dataUrlMediaType(data) || "application/octet-stream";
47
+ }
48
+
49
+ /**
50
+ * A payload placed into a wire field that is contractually a url.
51
+ *
52
+ * Two hazards drive this. A consumer may hand the value to an unguarded
53
+ * `new URL()`, so a payload that is not a url has to be wrapped; and a data
54
+ * URL's own media type wins over a separately declared one downstream, so a
55
+ * base64 envelope that disagrees with `mediaType` is rebuilt around the same
56
+ * bytes. Everything else passes through byte for byte: an envelope that agrees,
57
+ * a url of any other scheme, and a percent-encoded data URL, whose declaration
58
+ * cannot be corrected without transcoding the payload and is authoritative for
59
+ * the bytes it carries anyway.
60
+ */
61
+ export function toMediaWireUrl(payload: string, mediaType: string): string {
62
+ const parsed = parseDataUrl(payload);
63
+ if (parsed) {
64
+ return parsed.mimeType === mediaType
65
+ ? payload
66
+ : `data:${mediaType};base64,${parsed.data}`;
67
+ }
68
+ if (isParsableUrl(payload)) return payload;
69
+ return `data:${mediaType};base64,${payload}`;
70
+ }