@assistant-ui/react-streamdown 0.0.0

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 (49) hide show
  1. package/README.md +163 -0
  2. package/dist/adapters/PreOverride.d.ts +27 -0
  3. package/dist/adapters/PreOverride.d.ts.map +1 -0
  4. package/dist/adapters/PreOverride.js +31 -0
  5. package/dist/adapters/PreOverride.js.map +1 -0
  6. package/dist/adapters/code-adapter.d.ts +22 -0
  7. package/dist/adapters/code-adapter.d.ts.map +1 -0
  8. package/dist/adapters/code-adapter.js +75 -0
  9. package/dist/adapters/code-adapter.js.map +1 -0
  10. package/dist/adapters/components-adapter.d.ts +18 -0
  11. package/dist/adapters/components-adapter.d.ts.map +1 -0
  12. package/dist/adapters/components-adapter.js +34 -0
  13. package/dist/adapters/components-adapter.js.map +1 -0
  14. package/dist/defaults.d.ts +18 -0
  15. package/dist/defaults.d.ts.map +1 -0
  16. package/dist/defaults.js +37 -0
  17. package/dist/defaults.js.map +1 -0
  18. package/dist/index.d.ts +9 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +7 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/memoization.d.ts +10 -0
  23. package/dist/memoization.d.ts.map +1 -0
  24. package/dist/memoization.js +30 -0
  25. package/dist/memoization.js.map +1 -0
  26. package/dist/primitives/StreamdownText.d.ts +60 -0
  27. package/dist/primitives/StreamdownText.d.ts.map +1 -0
  28. package/dist/primitives/StreamdownText.js +124 -0
  29. package/dist/primitives/StreamdownText.js.map +1 -0
  30. package/dist/types.d.ts +356 -0
  31. package/dist/types.d.ts.map +1 -0
  32. package/dist/types.js +2 -0
  33. package/dist/types.js.map +1 -0
  34. package/package.json +93 -0
  35. package/src/__tests__/PreOverride.test.tsx +132 -0
  36. package/src/__tests__/code-adapter.integration.test.tsx +325 -0
  37. package/src/__tests__/code-adapter.test.tsx +46 -0
  38. package/src/__tests__/components-adapter.test.tsx +152 -0
  39. package/src/__tests__/defaults.test.ts +96 -0
  40. package/src/__tests__/index.test.ts +40 -0
  41. package/src/__tests__/memoization.test.ts +71 -0
  42. package/src/adapters/PreOverride.tsx +52 -0
  43. package/src/adapters/code-adapter.tsx +148 -0
  44. package/src/adapters/components-adapter.tsx +51 -0
  45. package/src/defaults.ts +46 -0
  46. package/src/index.ts +45 -0
  47. package/src/memoization.ts +38 -0
  48. package/src/primitives/StreamdownText.tsx +201 -0
  49. package/src/types.ts +416 -0
@@ -0,0 +1,124 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { INTERNAL, useMessagePartText } from "@assistant-ui/react";
4
+ import { harden } from "rehype-harden";
5
+ import rehypeRaw from "rehype-raw";
6
+ import rehypeSanitize from "rehype-sanitize";
7
+ import { Streamdown } from "streamdown";
8
+ import { forwardRef, useMemo } from "react";
9
+ import { useAdaptedComponents } from "../adapters/components-adapter.js";
10
+ import { DEFAULT_SHIKI_THEME, mergePlugins } from "../defaults.js";
11
+ const { useSmoothStatus } = INTERNAL;
12
+ /**
13
+ * Builds rehypePlugins array with security configuration.
14
+ */
15
+ function buildSecurityRehypePlugins(security) {
16
+ return [
17
+ rehypeRaw,
18
+ [rehypeSanitize, {}],
19
+ [
20
+ harden,
21
+ {
22
+ allowedImagePrefixes: security.allowedImagePrefixes ?? ["*"],
23
+ allowedLinkPrefixes: security.allowedLinkPrefixes ?? ["*"],
24
+ allowedProtocols: security.allowedProtocols ?? ["*"],
25
+ allowDataImages: security.allowDataImages ?? true,
26
+ defaultOrigin: security.defaultOrigin,
27
+ blockedLinkClass: security.blockedLinkClass,
28
+ blockedImageClass: security.blockedImageClass,
29
+ },
30
+ ],
31
+ ];
32
+ }
33
+ /**
34
+ * A primitive component for rendering markdown text using Streamdown.
35
+ *
36
+ * Streamdown is optimized for AI-powered streaming with features like:
37
+ * - Block-based rendering for better streaming performance
38
+ * - Incomplete markdown handling via remend
39
+ * - Built-in syntax highlighting via Shiki
40
+ * - Math, Mermaid, and CJK support via plugins
41
+ *
42
+ * @example
43
+ * ```tsx
44
+ * // Basic usage
45
+ * <StreamdownTextPrimitive />
46
+ *
47
+ * // With plugins
48
+ * import { code } from "@streamdown/code";
49
+ * import { math } from "@streamdown/math";
50
+ *
51
+ * <StreamdownTextPrimitive
52
+ * plugins={{ code, math }}
53
+ * shikiTheme={["github-light", "github-dark"]}
54
+ * />
55
+ *
56
+ * // Disable a specific plugin
57
+ * <StreamdownTextPrimitive plugins={{ code: false }} />
58
+ *
59
+ * // Migration from react-markdown (compatibility mode)
60
+ * <StreamdownTextPrimitive
61
+ * components={{
62
+ * SyntaxHighlighter: MySyntaxHighlighter,
63
+ * CodeHeader: MyCodeHeader,
64
+ * }}
65
+ * componentsByLanguage={{
66
+ * mermaid: { SyntaxHighlighter: MermaidRenderer }
67
+ * }}
68
+ * />
69
+ * ```
70
+ */
71
+ export const StreamdownTextPrimitive = forwardRef(({
72
+ // assistant-ui compatibility props
73
+ components, componentsByLanguage, preprocess,
74
+ // plugin configuration
75
+ plugins: userPlugins,
76
+ // container props
77
+ containerProps, containerClassName,
78
+ // streamdown native props (explicitly listed for documentation)
79
+ caret, controls, linkSafety, remend, mermaid, parseIncompleteMarkdown, allowedTags, remarkRehypeOptions, security, BlockComponent, parseMarkdownIntoBlocksFn,
80
+ // streamdown props
81
+ mode = "streaming", className, shikiTheme, ...streamdownProps }, ref) => {
82
+ const { text } = useMessagePartText();
83
+ const status = useSmoothStatus();
84
+ const processedText = useMemo(() => (preprocess ? preprocess(text) : text), [text, preprocess]);
85
+ const resolvedPlugins = useMemo(() => {
86
+ const merged = mergePlugins(userPlugins, {});
87
+ return Object.keys(merged).length > 0 ? merged : undefined;
88
+ }, [userPlugins]);
89
+ const resolvedShikiTheme = useMemo(() => shikiTheme ?? (resolvedPlugins?.code ? DEFAULT_SHIKI_THEME : undefined), [shikiTheme, resolvedPlugins?.code]);
90
+ const adaptedComponents = useAdaptedComponents({
91
+ components,
92
+ componentsByLanguage,
93
+ });
94
+ const mergedComponents = useMemo(() => {
95
+ const { SyntaxHighlighter: _, CodeHeader: __, ...userHtmlComponents } = components ?? {};
96
+ return { ...userHtmlComponents, ...adaptedComponents };
97
+ }, [components, adaptedComponents]);
98
+ const containerClass = useMemo(() => {
99
+ const classes = [containerClassName, containerProps?.className]
100
+ .filter(Boolean)
101
+ .join(" ");
102
+ return classes || undefined;
103
+ }, [containerClassName, containerProps?.className]);
104
+ const rehypePlugins = useMemo(() => (security ? buildSecurityRehypePlugins(security) : undefined), [security]);
105
+ const optionalProps = {
106
+ ...(className && { className }),
107
+ ...(caret && { caret }),
108
+ ...(controls !== undefined && { controls }),
109
+ ...(linkSafety && { linkSafety }),
110
+ ...(remend && { remend }),
111
+ ...(mermaid && { mermaid }),
112
+ ...(parseIncompleteMarkdown !== undefined && { parseIncompleteMarkdown }),
113
+ ...(allowedTags && { allowedTags }),
114
+ ...(resolvedPlugins && { plugins: resolvedPlugins }),
115
+ ...(resolvedShikiTheme && { shikiTheme: resolvedShikiTheme }),
116
+ ...(remarkRehypeOptions && { remarkRehypeOptions }),
117
+ ...(rehypePlugins && { rehypePlugins }),
118
+ ...(BlockComponent && { BlockComponent }),
119
+ ...(parseMarkdownIntoBlocksFn && { parseMarkdownIntoBlocksFn }),
120
+ };
121
+ return (_jsx("div", { ref: ref, "data-status": status.type, ...containerProps, className: containerClass, children: _jsx(Streamdown, { mode: mode, isAnimating: status.type === "running", components: mergedComponents, ...optionalProps, ...streamdownProps, children: processedText }) }));
122
+ });
123
+ StreamdownTextPrimitive.displayName = "StreamdownTextPrimitive";
124
+ //# sourceMappingURL=StreamdownText.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StreamdownText.js","sourceRoot":"","sources":["../../src/primitives/StreamdownText.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,356 @@
1
+ import type { Element } from "hast";
2
+ import type { ComponentPropsWithoutRef, ComponentType, ReactNode } from "react";
3
+ import type { Options as RemarkRehypeOptions } from "remark-rehype";
4
+ import type { StreamdownProps, MermaidOptions, MermaidErrorComponentProps } from "streamdown";
5
+ /**
6
+ * Caret style for streaming indicator.
7
+ */
8
+ export type CaretStyle = "block" | "circle";
9
+ /**
10
+ * Controls configuration for interactive elements.
11
+ */
12
+ export type ControlsConfig = boolean | {
13
+ table?: boolean;
14
+ code?: boolean;
15
+ mermaid?: boolean | {
16
+ download?: boolean;
17
+ copy?: boolean;
18
+ fullscreen?: boolean;
19
+ panZoom?: boolean;
20
+ };
21
+ };
22
+ /**
23
+ * Props passed to the link safety modal component.
24
+ */
25
+ export type LinkSafetyModalProps = {
26
+ url: string;
27
+ isOpen: boolean;
28
+ onClose: () => void;
29
+ onConfirm: () => void;
30
+ };
31
+ /**
32
+ * Configuration for link safety confirmation.
33
+ */
34
+ export type LinkSafetyConfig = {
35
+ /** Whether link safety is enabled. */
36
+ enabled: boolean;
37
+ /** Custom function to check if a link is safe. */
38
+ onLinkCheck?: (url: string) => Promise<boolean> | boolean;
39
+ /** Custom modal component for link confirmation. */
40
+ renderModal?: (props: LinkSafetyModalProps) => ReactNode;
41
+ };
42
+ /**
43
+ * Custom handler for incomplete markdown completion.
44
+ */
45
+ export type RemendHandler = {
46
+ /** Handler name for identification */
47
+ name: string;
48
+ /** Function to transform text */
49
+ handle: (text: string) => string;
50
+ /** Priority for handler execution order (lower runs first, default: 100) */
51
+ priority?: number;
52
+ };
53
+ /**
54
+ * Configuration for incomplete markdown auto-completion.
55
+ */
56
+ export type RemendConfig = {
57
+ /** Complete links (e.g., `[text](url` → `[text](streamdown:incomplete-link)`) */
58
+ links?: boolean;
59
+ /** Complete images (e.g., `![alt](url` → removed) */
60
+ images?: boolean;
61
+ /** How to handle incomplete links: 'protocol' or 'text-only' */
62
+ linkMode?: "protocol" | "text-only";
63
+ /** Complete bold formatting (e.g., `**text` → `**text**`) */
64
+ bold?: boolean;
65
+ /** Complete italic formatting (e.g., `*text` → `*text*`) */
66
+ italic?: boolean;
67
+ /** Complete bold-italic formatting (e.g., `***text` → `***text***`) */
68
+ boldItalic?: boolean;
69
+ /** Complete inline code formatting (e.g., `` `code `` → `` `code` ``) */
70
+ inlineCode?: boolean;
71
+ /** Complete strikethrough formatting (e.g., `~~text` → `~~text~~`) */
72
+ strikethrough?: boolean;
73
+ /** Complete block KaTeX math (e.g., `$$equation` → `$$equation$$`) */
74
+ katex?: boolean;
75
+ /** Handle incomplete setext headings to prevent misinterpretation */
76
+ setextHeadings?: boolean;
77
+ /** Custom handlers for incomplete markdown completion */
78
+ handlers?: RemendHandler[];
79
+ };
80
+ /**
81
+ * Props for the SyntaxHighlighter component.
82
+ * Compatible with @assistant-ui/react-markdown API.
83
+ */
84
+ export type SyntaxHighlighterProps = {
85
+ node?: Element | undefined;
86
+ components: {
87
+ Pre: ComponentType<ComponentPropsWithoutRef<"pre"> & {
88
+ node?: Element | undefined;
89
+ }>;
90
+ Code: ComponentType<ComponentPropsWithoutRef<"code"> & {
91
+ node?: Element | undefined;
92
+ }>;
93
+ };
94
+ language: string;
95
+ code: string;
96
+ };
97
+ /**
98
+ * Props for the CodeHeader component.
99
+ * Compatible with @assistant-ui/react-markdown API.
100
+ */
101
+ export type CodeHeaderProps = {
102
+ node?: Element | undefined;
103
+ language: string | undefined;
104
+ code: string;
105
+ };
106
+ /**
107
+ * Language-specific component overrides.
108
+ */
109
+ export type ComponentsByLanguage = Record<string, {
110
+ CodeHeader?: ComponentType<CodeHeaderProps> | undefined;
111
+ SyntaxHighlighter?: ComponentType<SyntaxHighlighterProps> | undefined;
112
+ }>;
113
+ /**
114
+ * Extended components prop that includes SyntaxHighlighter and CodeHeader.
115
+ */
116
+ export type StreamdownTextComponents = NonNullable<StreamdownProps["components"]> & {
117
+ SyntaxHighlighter?: ComponentType<SyntaxHighlighterProps> | undefined;
118
+ CodeHeader?: ComponentType<CodeHeaderProps> | undefined;
119
+ };
120
+ /**
121
+ * Plugin configuration type.
122
+ * Set to `false` to explicitly disable a plugin.
123
+ * Set to a plugin instance to use that plugin.
124
+ *
125
+ * NOTE: Plugins are NOT auto-detected for tree-shaking optimization.
126
+ * You must explicitly import and provide them.
127
+ *
128
+ * @example
129
+ * import { code } from "@streamdown/code";
130
+ * import { math } from "@streamdown/math";
131
+ * <StreamdownTextPrimitive plugins={{ code, math }} />
132
+ */
133
+ export type PluginConfig = {
134
+ /** Code syntax highlighting plugin. Must be explicitly provided. */
135
+ code?: unknown | false | undefined;
136
+ /** Math/LaTeX rendering plugin. Must be explicitly provided. */
137
+ math?: unknown | false | undefined;
138
+ /** CJK text optimization plugin. Must be explicitly provided. */
139
+ cjk?: unknown | false | undefined;
140
+ /** Mermaid diagram plugin. Must be explicitly provided. */
141
+ mermaid?: unknown | false | undefined;
142
+ };
143
+ /**
144
+ * Resolved plugin configuration (without false values).
145
+ * This is the type passed to streamdown after processing.
146
+ */
147
+ export type ResolvedPluginConfig = NonNullable<StreamdownProps["plugins"]>;
148
+ /**
149
+ * Allowed HTML tags whitelist.
150
+ * Maps tag names to allowed attribute names.
151
+ */
152
+ export type AllowedTags = Record<string, string[]>;
153
+ /**
154
+ * Security configuration for URL validation via rehype-harden.
155
+ * Overrides streamdown's permissive defaults (allow-all policy).
156
+ *
157
+ * @example
158
+ * // Restrict to specific domains
159
+ * security={{
160
+ * allowedLinkPrefixes: ["https://example.com", "https://docs.example.com"],
161
+ * allowedImagePrefixes: ["https://cdn.example.com"],
162
+ * allowedProtocols: ["https", "mailto"],
163
+ * }}
164
+ */
165
+ export type SecurityConfig = {
166
+ /** URL prefixes allowed for links. Default: ["*"] (all) */
167
+ allowedLinkPrefixes?: string[];
168
+ /** URL prefixes allowed for images. Default: ["*"] (all) */
169
+ allowedImagePrefixes?: string[];
170
+ /** Allowed protocols (e.g., ["http", "https", "mailto"]). Default: ["*"] */
171
+ allowedProtocols?: string[];
172
+ /** Allow base64 data images. Default: true */
173
+ allowDataImages?: boolean;
174
+ /** Default origin for relative URLs */
175
+ defaultOrigin?: string;
176
+ /** CSS class for blocked links */
177
+ blockedLinkClass?: string;
178
+ /** CSS class for blocked images */
179
+ blockedImageClass?: string;
180
+ };
181
+ export type { MermaidOptions, MermaidErrorComponentProps, RemarkRehypeOptions };
182
+ /**
183
+ * Props for the BlockComponent override.
184
+ * Used to customize how individual markdown blocks are rendered.
185
+ *
186
+ * Note: This is a documentation type. The actual BlockComponent prop
187
+ * uses StreamdownProps["BlockComponent"] for type compatibility.
188
+ */
189
+ export type BlockProps = {
190
+ content: string;
191
+ shouldParseIncompleteMarkdown: boolean;
192
+ index: number;
193
+ components?: StreamdownProps["components"];
194
+ rehypePlugins?: StreamdownProps["rehypePlugins"];
195
+ remarkPlugins?: StreamdownProps["remarkPlugins"];
196
+ remarkRehypeOptions?: RemarkRehypeOptions;
197
+ };
198
+ /**
199
+ * Props for StreamdownTextPrimitive.
200
+ */
201
+ export type StreamdownTextPrimitiveProps = Omit<StreamdownProps, "children" | "components" | "plugins" | "caret" | "controls" | "linkSafety" | "remend" | "mermaid" | "BlockComponent" | "parseMarkdownIntoBlocksFn"> & {
202
+ /**
203
+ * Custom components for rendering markdown elements.
204
+ * Includes SyntaxHighlighter and CodeHeader for code block customization.
205
+ */
206
+ components?: StreamdownTextComponents | undefined;
207
+ /**
208
+ * Language-specific component overrides.
209
+ * @example { mermaid: { SyntaxHighlighter: MermaidDiagram } }
210
+ */
211
+ componentsByLanguage?: ComponentsByLanguage | undefined;
212
+ /**
213
+ * Plugin configuration.
214
+ * Set to `false` to disable a specific plugin when using merged configs.
215
+ *
216
+ * @example
217
+ * // With plugins
218
+ * import { code } from "@streamdown/code";
219
+ * import { math } from "@streamdown/math";
220
+ * plugins={{ code, math }}
221
+ *
222
+ * @example
223
+ * // Disable a plugin in merged config
224
+ * plugins={{ code: false }}
225
+ */
226
+ plugins?: PluginConfig | undefined;
227
+ /**
228
+ * Function to transform text before markdown processing.
229
+ */
230
+ preprocess?: ((text: string) => string) | undefined;
231
+ /**
232
+ * Container element props.
233
+ */
234
+ containerProps?: Omit<ComponentPropsWithoutRef<"div">, "children"> | undefined;
235
+ /**
236
+ * Additional class name for the container.
237
+ */
238
+ containerClassName?: string | undefined;
239
+ /**
240
+ * Streaming caret style.
241
+ * - "block": Block cursor (▋)
242
+ * - "circle": Circle cursor (●)
243
+ */
244
+ caret?: CaretStyle | undefined;
245
+ /**
246
+ * Interactive controls configuration.
247
+ * Set to `true` to enable all controls, `false` to disable all,
248
+ * or provide an object to configure specific controls.
249
+ *
250
+ * @example
251
+ * // Enable all controls
252
+ * controls={true}
253
+ *
254
+ * @example
255
+ * // Configure specific controls
256
+ * controls={{ code: true, table: false, mermaid: { fullscreen: true } }}
257
+ */
258
+ controls?: ControlsConfig | undefined;
259
+ /**
260
+ * Link safety configuration.
261
+ * Shows a confirmation dialog before opening external links.
262
+ *
263
+ * @example
264
+ * // Disable link safety
265
+ * linkSafety={{ enabled: false }}
266
+ *
267
+ * @example
268
+ * // Custom link check
269
+ * linkSafety={{
270
+ * enabled: true,
271
+ * onLinkCheck: (url) => url.startsWith('https://trusted.com')
272
+ * }}
273
+ */
274
+ linkSafety?: LinkSafetyConfig | undefined;
275
+ /**
276
+ * Incomplete markdown auto-completion configuration.
277
+ * Controls how streaming markdown with incomplete syntax is handled.
278
+ *
279
+ * @example
280
+ * // Disable link completion
281
+ * remend={{ links: false }}
282
+ *
283
+ * @example
284
+ * // Use text-only mode for incomplete links
285
+ * remend={{ linkMode: "text-only" }}
286
+ */
287
+ remend?: RemendConfig | undefined;
288
+ /**
289
+ * Mermaid diagram configuration.
290
+ * Allows customization of Mermaid rendering.
291
+ *
292
+ * @example
293
+ * // Custom Mermaid config
294
+ * mermaid={{ config: { theme: 'dark' } }}
295
+ *
296
+ * @example
297
+ * // Custom error component
298
+ * mermaid={{ errorComponent: MyMermaidError }}
299
+ */
300
+ mermaid?: MermaidOptions | undefined;
301
+ /**
302
+ * Whether to parse incomplete markdown during streaming.
303
+ * When true, incomplete markdown syntax will be processed as-is.
304
+ * When false (default), remend will complete the syntax first.
305
+ */
306
+ parseIncompleteMarkdown?: boolean | undefined;
307
+ /**
308
+ * Allowed HTML tags whitelist.
309
+ * Maps tag names to their allowed attribute names.
310
+ * Use this to allow specific HTML tags in markdown content.
311
+ *
312
+ * @example
313
+ * allowedTags={{ div: ['class', 'id'], span: ['class'] }}
314
+ */
315
+ allowedTags?: AllowedTags | undefined;
316
+ /**
317
+ * Options passed to remark-rehype during markdown processing.
318
+ * Allows customization of the remark to rehype conversion.
319
+ *
320
+ * @example
321
+ * remarkRehypeOptions={{ allowDangerousHtml: true }}
322
+ */
323
+ remarkRehypeOptions?: RemarkRehypeOptions | undefined;
324
+ /**
325
+ * Security configuration for URL/image validation.
326
+ * Overrides streamdown's default (allow-all) policy via rehype-harden.
327
+ *
328
+ * @example
329
+ * // Restrict links to trusted domains only
330
+ * security={{
331
+ * allowedLinkPrefixes: ["https://trusted.com"],
332
+ * allowedImagePrefixes: ["https://cdn.trusted.com"],
333
+ * allowedProtocols: ["https"],
334
+ * }}
335
+ */
336
+ security?: SecurityConfig | undefined;
337
+ /**
338
+ * Custom component for rendering individual markdown blocks.
339
+ * Use this for advanced block-level customization.
340
+ *
341
+ * @example
342
+ * BlockComponent={({ content, index }) => <div key={index}>{content}</div>}
343
+ */
344
+ BlockComponent?: StreamdownProps["BlockComponent"] | undefined;
345
+ /**
346
+ * Custom function to parse markdown into blocks.
347
+ * By default, streamdown splits on double newlines.
348
+ * Use this to implement custom block splitting logic.
349
+ *
350
+ * @example
351
+ * parseMarkdownIntoBlocksFn={(md) => md.split(/\n{2,}/)}
352
+ */
353
+ parseMarkdownIntoBlocksFn?: ((markdown: string) => string[]) | undefined;
354
+ };
355
+ export type { StreamdownProps };
356
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,93 @@
1
+ {
2
+ "name": "@assistant-ui/react-streamdown",
3
+ "version": "0.0.0",
4
+ "description": "Streamdown-based markdown rendering for assistant-ui",
5
+ "keywords": [
6
+ "markdown",
7
+ "streamdown",
8
+ "assistant-ui",
9
+ "react",
10
+ "ai",
11
+ "chat",
12
+ "streaming"
13
+ ],
14
+ "author": "AgentbaseAI Inc.",
15
+ "license": "MIT",
16
+ "type": "module",
17
+ "exports": {
18
+ ".": {
19
+ "aui-source": "./src/index.ts",
20
+ "types": "./dist/index.d.ts",
21
+ "default": "./dist/index.js"
22
+ }
23
+ },
24
+ "main": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "files": [
27
+ "dist",
28
+ "src",
29
+ "README.md"
30
+ ],
31
+ "sideEffects": false,
32
+ "scripts": {
33
+ "build": "aui-build",
34
+ "test": "vitest run",
35
+ "test:watch": "vitest"
36
+ },
37
+ "dependencies": {
38
+ "rehype-harden": "^1.1.7",
39
+ "rehype-raw": "^7.0.0",
40
+ "rehype-sanitize": "^6.0.0",
41
+ "streamdown": "^2.0.0"
42
+ },
43
+ "peerDependencies": {
44
+ "@assistant-ui/react": "^0.12.3",
45
+ "@streamdown/code": "^1.0.0",
46
+ "@streamdown/cjk": "^1.0.0",
47
+ "@streamdown/math": "^1.0.0",
48
+ "@streamdown/mermaid": "^1.0.0",
49
+ "@types/react": "*",
50
+ "react": "^18 || ^19"
51
+ },
52
+ "peerDependenciesMeta": {
53
+ "@types/react": {
54
+ "optional": true
55
+ },
56
+ "@streamdown/code": {
57
+ "optional": true
58
+ },
59
+ "@streamdown/cjk": {
60
+ "optional": true
61
+ },
62
+ "@streamdown/math": {
63
+ "optional": true
64
+ },
65
+ "@streamdown/mermaid": {
66
+ "optional": true
67
+ }
68
+ },
69
+ "devDependencies": {
70
+ "@assistant-ui/react": "workspace:*",
71
+ "@assistant-ui/x-buildutils": "workspace:*",
72
+ "@streamdown/code": "^1.0.0",
73
+ "@testing-library/react": "^16.3.0",
74
+ "@types/hast": "^3.0.4",
75
+ "@types/react": "^19.2.7",
76
+ "react": "^19.2.3",
77
+ "react-dom": "^19.2.3",
78
+ "remark-rehype": "^11.1.2",
79
+ "vitest": "^4.0.16"
80
+ },
81
+ "publishConfig": {
82
+ "access": "public"
83
+ },
84
+ "homepage": "https://www.assistant-ui.com/",
85
+ "repository": {
86
+ "type": "git",
87
+ "url": "git+https://github.com/assistant-ui/assistant-ui.git",
88
+ "directory": "packages/react-streamdown"
89
+ },
90
+ "bugs": {
91
+ "url": "https://github.com/assistant-ui/assistant-ui/issues"
92
+ }
93
+ }