@assistant-ui/react 0.15.18 → 0.15.19
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.
- package/README.md +7 -1
- package/dist/context/providers/ThreadViewportProvider.js +2 -2
- package/dist/context/providers/ThreadViewportProvider.js.map +1 -1
- package/dist/hooks/useToolCallElapsed.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/mcp-apps/app-frame.d.ts.map +1 -1
- package/dist/mcp-apps/app-frame.js +17 -12
- package/dist/mcp-apps/app-frame.js.map +1 -1
- package/dist/mcp-apps/bridge.js +7 -1
- package/dist/mcp-apps/bridge.js.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalRoot.d.ts.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalRoot.js +47 -79
- package/dist/primitives/assistantModal/AssistantModalRoot.js.map +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.js +61 -138
- package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +3 -8
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerKeyboardResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerKeyboardResource.js +53 -61
- package/dist/primitives/composer/trigger/triggerKeyboardResource.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerNavigationResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerNavigationResource.js +127 -137
- package/dist/primitives/composer/trigger/triggerNavigationResource.js.map +1 -1
- package/dist/primitives/message/MessageParts.js +2 -14
- package/dist/primitives/message/MessageParts.js.map +1 -1
- package/dist/primitives/message/MessagePartsGrouped.d.ts +3 -3
- package/dist/primitives/message/MessagePartsGrouped.js +2 -2
- package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
- package/dist/primitives/messagePart/MessagePartText.d.ts +3 -1
- package/dist/primitives/messagePart/MessagePartText.d.ts.map +1 -1
- package/dist/primitives/messagePart/MessagePartText.js +13 -36
- package/dist/primitives/messagePart/MessagePartText.js.map +1 -1
- package/dist/primitives/reasoning/useScrollLock.js +3 -1
- package/dist/primitives/reasoning/useScrollLock.js.map +1 -1
- package/dist/sandbox-host/SandboxHost.js +22 -4
- package/dist/sandbox-host/SandboxHost.js.map +1 -1
- package/dist/unstable/useLiveCompletionAdapter.js +24 -2
- package/dist/unstable/useLiveCompletionAdapter.js.map +1 -1
- package/dist/unstable/useMentionAdapter.js +2 -2
- package/dist/unstable/useMentionAdapter.js.map +1 -1
- package/dist/unstable/useMessageStallDetection.d.ts +4 -5
- package/dist/unstable/useMessageStallDetection.d.ts.map +1 -1
- package/dist/unstable/useMessageStallDetection.js +16 -15
- package/dist/unstable/useMessageStallDetection.js.map +1 -1
- package/dist/unstable/webmcp/convertTools.d.ts.map +1 -1
- package/dist/unstable/webmcp/convertTools.js +54 -3
- package/dist/unstable/webmcp/convertTools.js.map +1 -1
- package/dist/unstable/webmcp/useWebMcpProvider.d.ts.map +1 -1
- package/dist/unstable/webmcp/useWebMcpProvider.js +2 -1
- package/dist/unstable/webmcp/useWebMcpProvider.js.map +1 -1
- package/dist/utils/Primitive.d.ts +14 -6
- package/dist/utils/Primitive.d.ts.map +1 -1
- package/dist/utils/Primitive.js +25 -6
- package/dist/utils/Primitive.js.map +1 -1
- package/dist/utils/getSelectionMessageId.d.ts.map +1 -1
- package/dist/utils/getSelectionMessageId.js +17 -3
- package/dist/utils/getSelectionMessageId.js.map +1 -1
- package/package.json +11 -11
- package/src/context/providers/ThreadViewportProvider.tsx +2 -2
- package/src/hooks/useToolCallElapsed.ts +3 -0
- package/src/index.ts +1 -1
- package/src/mcp-apps/app-frame.test.tsx +189 -0
- package/src/mcp-apps/app-frame.tsx +31 -22
- package/src/mcp-apps/bridge.test.ts +98 -0
- package/src/mcp-apps/bridge.ts +7 -1
- package/src/primitives/assistantModal/AssistantModalRoot.test.tsx +123 -0
- package/src/primitives/assistantModal/AssistantModalRoot.tsx +11 -31
- package/src/primitives/assistantModal/AssistantModalTrigger.test.tsx +1 -16
- package/src/primitives/composer/ComposerAttachmentDropzone.test.tsx +43 -0
- package/src/primitives/composer/ComposerAttachmentDropzone.tsx +2 -10
- package/src/primitives/composer/ComposerInput.test.tsx +22 -0
- package/src/primitives/composer/ComposerInput.tsx +5 -9
- package/src/primitives/composer/trigger/triggerKeyboardResource.ts +13 -7
- package/src/primitives/composer/trigger/triggerNavigationResource.ts +5 -3
- package/src/primitives/message/MessageParts.tsx +1 -21
- package/src/primitives/message/MessagePartsGrouped.test.tsx +93 -0
- package/src/primitives/message/MessagePartsGrouped.tsx +6 -6
- package/src/primitives/messagePart/MessagePartText.test.tsx +69 -0
- package/src/primitives/messagePart/MessagePartText.tsx +22 -9
- package/src/primitives/reasoning/useScrollLock.test.tsx +129 -0
- package/src/primitives/reasoning/useScrollLock.ts +15 -1
- package/src/primitives/thread/useThreadViewportAutoScroll.test.tsx +38 -1
- package/src/sandbox-host/SandboxHost.test.tsx +40 -0
- package/src/sandbox-host/SandboxHost.tsx +24 -4
- package/src/tests/MessageParts.rendererOptions.test.tsx +117 -0
- package/src/tests/toolCallTiming.test.tsx +55 -1
- package/src/unstable/useLiveCompletionAdapter.test.tsx +202 -1
- package/src/unstable/useLiveCompletionAdapter.ts +38 -8
- package/src/unstable/useMentionAdapter.test.tsx +47 -1
- package/src/unstable/useMentionAdapter.ts +2 -2
- package/src/unstable/useMessageStallDetection.ts +26 -19
- package/src/unstable/webmcp/convertTools.test.ts +181 -21
- package/src/unstable/webmcp/convertTools.ts +87 -7
- package/src/unstable/webmcp/useWebMcpProvider.ts +2 -4
- package/src/utils/Primitive.test.tsx +46 -1
- package/src/utils/Primitive.tsx +37 -7
- package/src/utils/getSelectionMessageId.test.ts +176 -1
- package/src/utils/getSelectionMessageId.ts +21 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMentionAdapter.js","names":["useMemo","FC","useAui","Unstable_DirectiveFormatter","Unstable_TriggerAdapter","Unstable_TriggerCategory","Unstable_TriggerItem","unstable_defaultDirectiveFormatter","ReadonlyJSONObject","matchesTriggerItemQuery","shallowEqualRecords","useModelContextSnapshot","ModelContextSnapshotSource","Unstable_IconComponent","className","Unstable_Mention","id","type","label","description","icon","metadata","Unstable_MentionCategory","items","Unstable_ModelContextToolsOptions","category","formatLabel","toolName","Unstable_UseMentionAdapterOptions","categories","includeModelContextTools","formatter","onInserted","item","iconMap","Record","fallbackIcon","Unstable_MentionDirective","EMPTY_TOOL_MENTIONS","Readonly","Object","freeze","toolMentionSource","empty","read","aui","tools","thread","getModelContext","mentions","name","tool","entries","subscribe","onChange","unsubscribeContext","on","unsubscribeSelection","scope","event","isEqual","unstable_useMentionAdapter","options","adapter","directive","includeTools","toolsConfig","undefined","wantsTools","isCategorized","length","toolMentions","defaultIcon","toolItems","map","toTriggerItem","groups","cat","allGroups","categoryItems","find","g","search","query","lower","toLowerCase","flatMap","filter","flatItems","seen","Set","i","flatPool","t","has","m"],"sources":["../../src/unstable/useMentionAdapter.ts"],"sourcesContent":["\"use client\";\n\nimport { useMemo, type FC } from \"react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport type {\n Unstable_DirectiveFormatter,\n Unstable_TriggerAdapter,\n Unstable_TriggerCategory,\n Unstable_TriggerItem,\n} from \"@assistant-ui/core\";\nimport { unstable_defaultDirectiveFormatter } from \"@assistant-ui/core\";\nimport type { ReadonlyJSONObject } from \"assistant-stream/utils\";\nimport { matchesTriggerItemQuery } from \"../primitives/composer/trigger/matchesTriggerItemQuery\";\nimport {\n shallowEqualRecords,\n useModelContextSnapshot,\n type ModelContextSnapshotSource,\n} from \"./useModelContextSnapshot\";\n\n/** Icon component shape consumed by `ComposerTriggerPopover`'s `iconMap`. */\nexport type Unstable_IconComponent = FC<{ className?: string }>;\n\nexport type Unstable_Mention = {\n readonly id: string;\n readonly type: string;\n readonly label: string;\n readonly description?: string | undefined;\n /** Shortcut for `metadata.icon`; merged with `metadata` if both are given. */\n readonly icon?: string | undefined;\n readonly metadata?: ReadonlyJSONObject | undefined;\n};\n\nexport type Unstable_MentionCategory = {\n readonly id: string;\n readonly label: string;\n readonly items: readonly Unstable_Mention[];\n};\n\nexport type Unstable_ModelContextToolsOptions = {\n /**\n * Wrap tools in a dedicated category. Selects drill-down mode on its own\n * when `categories` is unset and `items` is omitted.\n */\n readonly category?: { readonly id: string; readonly label: string };\n /** Format tool name for display. */\n readonly formatLabel?: (toolName: string) => string;\n /** Default icon key for each tool. */\n readonly icon?: string;\n};\n\nexport type Unstable_UseMentionAdapterOptions = {\n /**\n * Flat mention list. Ignored when `categories` is set, and keeps the\n * adapter flat when a tool `category` is configured.\n */\n readonly items?: readonly Unstable_Mention[];\n /** Categorized mentions for drill-down navigation. */\n readonly categories?: readonly Unstable_MentionCategory[];\n /**\n * How tools registered in model context integrate.\n * - `false`: exclude.\n * - `true`: include (default when no `items`/`categories`; as a category\n * if `categories` is set, flat otherwise).\n * - object: explicit config; `category` also selects drill-down mode.\n *\n * Omitted → defaults to `true` iff neither `items` nor `categories`.\n */\n readonly includeModelContextTools?:\n | boolean\n | Unstable_ModelContextToolsOptions;\n /** Directive formatter. @default unstable_defaultDirectiveFormatter */\n readonly formatter?: Unstable_DirectiveFormatter;\n /** Fires after an item is inserted into the composer. */\n readonly onInserted?: (item: Unstable_TriggerItem) => void;\n /** Maps `metadata.icon` / `category.id` string keys to React components. */\n readonly iconMap?: Record<string, Unstable_IconComponent>;\n /** Fallback icon when no entry in `iconMap` matches. */\n readonly fallbackIcon?: Unstable_IconComponent;\n};\n\nexport type Unstable_MentionDirective = {\n readonly formatter: Unstable_DirectiveFormatter;\n readonly onInserted?: ((item: Unstable_TriggerItem) => void) | undefined;\n};\n\nconst EMPTY_TOOL_MENTIONS: Readonly<Record<string, string | undefined>> =\n Object.freeze({});\n\n// The description is copied out rather than reached through the tool, so a\n// provider that returns a stable tool object and edits it in place is observed.\nconst toolMentionSource: ModelContextSnapshotSource<\n Readonly<Record<string, string | undefined>>\n> = {\n empty: EMPTY_TOOL_MENTIONS,\n read: (aui) => {\n const tools = aui.thread.getModelContext().tools;\n if (!tools) return EMPTY_TOOL_MENTIONS;\n const mentions: Record<string, string | undefined> = {};\n for (const [name, tool] of Object.entries(tools)) {\n mentions[name] = tool.description;\n }\n return mentions;\n },\n subscribe: (aui, onChange) => {\n const unsubscribeContext = aui.on(\"thread.modelContextUpdate\", onChange);\n // Rebinding the thread event subject to a new thread does not replay it,\n // so a switch between threads carrying different providers is its own\n // refresh trigger. Subscribed globally because a composer can render\n // without a thread list scope.\n const unsubscribeSelection = aui.on(\n { scope: \"*\", event: \"threads.selectionChanged\" },\n onChange,\n );\n return () => {\n unsubscribeContext();\n unsubscribeSelection();\n };\n },\n isEqual: shallowEqualRecords,\n};\n\n/**\n * @deprecated Under active development and might change without notice.\n *\n * Creates a spreadable `{ adapter, directive }` bundle for `@` mentions.\n * Supports tools registered in model context, explicit items, or both —\n * flat or categorized.\n *\n * @example\n * ```tsx\n * const mention = unstable_useMentionAdapter();\n * <ComposerTriggerPopover char=\"@\" {...mention} />\n * ```\n */\nexport function unstable_useMentionAdapter(\n options?: Unstable_UseMentionAdapterOptions,\n): {\n adapter: Unstable_TriggerAdapter;\n directive: Unstable_MentionDirective;\n iconMap?: Record<string, Unstable_IconComponent>;\n fallbackIcon?: Unstable_IconComponent;\n} {\n const aui = useAui();\n\n const items = options?.items;\n const categories = options?.categories;\n const includeTools =\n options?.includeModelContextTools ?? (!items && !categories);\n const toolsConfig =\n typeof includeTools === \"object\" ? includeTools : undefined;\n const wantsTools = includeTools !== false;\n const formatter = options?.formatter;\n const onInserted = options?.onInserted;\n const isCategorized =\n (categories !== undefined && categories.length > 0) ||\n (toolsConfig?.category !== undefined && items === undefined);\n const toolMentions = useModelContextSnapshot(\n aui,\n wantsTools,\n toolMentionSource,\n );\n\n const adapter = useMemo<Unstable_TriggerAdapter>(() => {\n const formatLabel = toolsConfig?.formatLabel;\n const defaultIcon = toolsConfig?.icon;\n const toolItems = wantsTools\n ? Object.entries(toolMentions).map(([name, description]) =>\n toTriggerItem({\n id: name,\n type: \"tool\",\n label: formatLabel ? formatLabel(name) : name,\n description,\n icon: defaultIcon,\n }),\n )\n : [];\n\n // Categorized: drill-down mode\n if (isCategorized) {\n const groups = (categories ?? []).map((cat) => ({\n id: cat.id,\n label: cat.label,\n items: cat.items.map(toTriggerItem),\n }));\n const allGroups =\n toolItems.length > 0\n ? [\n ...groups,\n {\n id: toolsConfig?.category?.id ?? \"tools\",\n label: toolsConfig?.category?.label ?? \"Tools\",\n items: toolItems,\n },\n ]\n : groups;\n\n return {\n categories: () => allGroups.map(({ id, label }) => ({ id, label })),\n categoryItems: (id) => allGroups.find((g) => g.id === id)?.items ?? [],\n search: (query) => {\n const lower = query.toLowerCase();\n return allGroups\n .flatMap((g) => g.items)\n .filter((item) => matchesTriggerItemQuery(item, lower));\n },\n };\n }\n\n // Flat: items + (optionally) tools, all in one search pool\n const flatItems = (items ?? []).map(toTriggerItem);\n // Dedupe by id — explicit items win.\n const seen = new Set(flatItems.map((i) => i.id));\n const flatPool = [\n ...flatItems,\n ...toolItems.filter((t) => !seen.has(t.id)),\n ];\n\n return {\n categories: (): readonly Unstable_TriggerCategory[] => [],\n categoryItems: () => [],\n search: (query) => {\n const lower = query.toLowerCase();\n return flatPool.filter((item) => matchesTriggerItemQuery(item, lower));\n },\n };\n }, [items, categories, wantsTools, toolsConfig, isCategorized, toolMentions]);\n\n const directive = useMemo<Unstable_MentionDirective>(\n () => ({\n formatter: formatter ?? unstable_defaultDirectiveFormatter,\n ...(onInserted ? { onInserted } : {}),\n }),\n [formatter, onInserted],\n );\n\n return {\n adapter,\n directive,\n ...(options?.iconMap ? { iconMap: options.iconMap } : {}),\n ...(options?.fallbackIcon ? { fallbackIcon: options.fallbackIcon } : {}),\n };\n}\n\nfunction toTriggerItem(m: Unstable_Mention): Unstable_TriggerItem {\n const metadata =\n m.icon !== undefined ? { ...(m.metadata ?? {}), icon: m.icon } : m.metadata;\n return {\n id: m.id,\n type: m.type,\n label: m.label,\n ...(m.description !== undefined ? { description: m.description } : {}),\n ...(metadata !== undefined ? { metadata } : {}),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useMentionAdapter.js","names":["useMemo","FC","useAui","Unstable_DirectiveFormatter","Unstable_TriggerAdapter","Unstable_TriggerCategory","Unstable_TriggerItem","unstable_defaultDirectiveFormatter","ReadonlyJSONObject","matchesTriggerItemQuery","shallowEqualRecords","useModelContextSnapshot","ModelContextSnapshotSource","Unstable_IconComponent","className","Unstable_Mention","id","type","label","description","icon","metadata","Unstable_MentionCategory","items","Unstable_ModelContextToolsOptions","category","formatLabel","toolName","Unstable_UseMentionAdapterOptions","categories","includeModelContextTools","formatter","onInserted","item","iconMap","Record","fallbackIcon","Unstable_MentionDirective","EMPTY_TOOL_MENTIONS","Readonly","Object","freeze","toolMentionSource","empty","read","aui","tools","thread","getModelContext","mentions","create","name","tool","entries","subscribe","onChange","unsubscribeContext","on","unsubscribeSelection","scope","event","isEqual","unstable_useMentionAdapter","options","adapter","directive","includeTools","toolsConfig","undefined","wantsTools","isCategorized","toolMentions","defaultIcon","toolItems","map","toTriggerItem","groups","cat","allGroups","length","categoryItems","find","g","search","query","lower","toLowerCase","flatMap","filter","flatItems","seen","Set","i","flatPool","t","has","m"],"sources":["../../src/unstable/useMentionAdapter.ts"],"sourcesContent":["\"use client\";\n\nimport { useMemo, type FC } from \"react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport type {\n Unstable_DirectiveFormatter,\n Unstable_TriggerAdapter,\n Unstable_TriggerCategory,\n Unstable_TriggerItem,\n} from \"@assistant-ui/core\";\nimport { unstable_defaultDirectiveFormatter } from \"@assistant-ui/core\";\nimport type { ReadonlyJSONObject } from \"assistant-stream/utils\";\nimport { matchesTriggerItemQuery } from \"../primitives/composer/trigger/matchesTriggerItemQuery\";\nimport {\n shallowEqualRecords,\n useModelContextSnapshot,\n type ModelContextSnapshotSource,\n} from \"./useModelContextSnapshot\";\n\n/** Icon component shape consumed by `ComposerTriggerPopover`'s `iconMap`. */\nexport type Unstable_IconComponent = FC<{ className?: string }>;\n\nexport type Unstable_Mention = {\n readonly id: string;\n readonly type: string;\n readonly label: string;\n readonly description?: string | undefined;\n /** Shortcut for `metadata.icon`; merged with `metadata` if both are given. */\n readonly icon?: string | undefined;\n readonly metadata?: ReadonlyJSONObject | undefined;\n};\n\nexport type Unstable_MentionCategory = {\n readonly id: string;\n readonly label: string;\n readonly items: readonly Unstable_Mention[];\n};\n\nexport type Unstable_ModelContextToolsOptions = {\n /**\n * Wrap tools in a dedicated category. Selects drill-down mode on its own\n * when `categories` is unset and `items` is omitted.\n */\n readonly category?: { readonly id: string; readonly label: string };\n /** Format tool name for display. */\n readonly formatLabel?: (toolName: string) => string;\n /** Default icon key for each tool. */\n readonly icon?: string;\n};\n\nexport type Unstable_UseMentionAdapterOptions = {\n /**\n * Flat mention list. Ignored when `categories` is set, and keeps the\n * adapter flat when a tool `category` is configured.\n */\n readonly items?: readonly Unstable_Mention[];\n /** Categorized mentions for drill-down navigation. */\n readonly categories?: readonly Unstable_MentionCategory[];\n /**\n * How tools registered in model context integrate.\n * - `false`: exclude.\n * - `true`: include (default when no `items`/`categories`; as a category\n * if `categories` is set, flat otherwise).\n * - object: explicit config; `category` also selects drill-down mode.\n *\n * Omitted → defaults to `true` iff neither `items` nor `categories`.\n */\n readonly includeModelContextTools?:\n | boolean\n | Unstable_ModelContextToolsOptions;\n /** Directive formatter. @default unstable_defaultDirectiveFormatter */\n readonly formatter?: Unstable_DirectiveFormatter;\n /** Fires after an item is inserted into the composer. */\n readonly onInserted?: (item: Unstable_TriggerItem) => void;\n /** Maps `metadata.icon` / `category.id` string keys to React components. */\n readonly iconMap?: Record<string, Unstable_IconComponent>;\n /** Fallback icon when no entry in `iconMap` matches. */\n readonly fallbackIcon?: Unstable_IconComponent;\n};\n\nexport type Unstable_MentionDirective = {\n readonly formatter: Unstable_DirectiveFormatter;\n readonly onInserted?: ((item: Unstable_TriggerItem) => void) | undefined;\n};\n\nconst EMPTY_TOOL_MENTIONS: Readonly<Record<string, string | undefined>> =\n Object.freeze({});\n\n// The description is copied out rather than reached through the tool, so a\n// provider that returns a stable tool object and edits it in place is observed.\nconst toolMentionSource: ModelContextSnapshotSource<\n Readonly<Record<string, string | undefined>>\n> = {\n empty: EMPTY_TOOL_MENTIONS,\n read: (aui) => {\n const tools = aui.thread.getModelContext().tools;\n if (!tools) return EMPTY_TOOL_MENTIONS;\n const mentions = Object.create(null) as Record<string, string | undefined>;\n for (const [name, tool] of Object.entries(tools)) {\n mentions[name] = tool.description;\n }\n return mentions;\n },\n subscribe: (aui, onChange) => {\n const unsubscribeContext = aui.on(\"thread.modelContextUpdate\", onChange);\n // Rebinding the thread event subject to a new thread does not replay it,\n // so a switch between threads carrying different providers is its own\n // refresh trigger. Subscribed globally because a composer can render\n // without a thread list scope.\n const unsubscribeSelection = aui.on(\n { scope: \"*\", event: \"threads.selectionChanged\" },\n onChange,\n );\n return () => {\n unsubscribeContext();\n unsubscribeSelection();\n };\n },\n isEqual: shallowEqualRecords,\n};\n\n/**\n * @deprecated Under active development and might change without notice.\n *\n * Creates a spreadable `{ adapter, directive }` bundle for `@` mentions.\n * Supports tools registered in model context, explicit items, or both —\n * flat or categorized.\n *\n * @example\n * ```tsx\n * const mention = unstable_useMentionAdapter();\n * <ComposerTriggerPopover char=\"@\" {...mention} />\n * ```\n */\nexport function unstable_useMentionAdapter(\n options?: Unstable_UseMentionAdapterOptions,\n): {\n adapter: Unstable_TriggerAdapter;\n directive: Unstable_MentionDirective;\n iconMap?: Record<string, Unstable_IconComponent>;\n fallbackIcon?: Unstable_IconComponent;\n} {\n const aui = useAui();\n\n const items = options?.items;\n const categories = options?.categories;\n const includeTools =\n options?.includeModelContextTools ?? (!items && !categories);\n const toolsConfig =\n typeof includeTools === \"object\" ? includeTools : undefined;\n const wantsTools = includeTools !== false;\n const formatter = options?.formatter;\n const onInserted = options?.onInserted;\n const isCategorized =\n categories !== undefined ||\n (toolsConfig?.category !== undefined && items === undefined);\n const toolMentions = useModelContextSnapshot(\n aui,\n wantsTools,\n toolMentionSource,\n );\n\n const adapter = useMemo<Unstable_TriggerAdapter>(() => {\n const formatLabel = toolsConfig?.formatLabel;\n const defaultIcon = toolsConfig?.icon;\n const toolItems = wantsTools\n ? Object.entries(toolMentions).map(([name, description]) =>\n toTriggerItem({\n id: name,\n type: \"tool\",\n label: formatLabel ? formatLabel(name) : name,\n description,\n icon: defaultIcon,\n }),\n )\n : [];\n\n // Categorized: drill-down mode\n if (isCategorized) {\n const groups = (categories ?? []).map((cat) => ({\n id: cat.id,\n label: cat.label,\n items: cat.items.map(toTriggerItem),\n }));\n const allGroups =\n toolItems.length > 0\n ? [\n ...groups,\n {\n id: toolsConfig?.category?.id ?? \"tools\",\n label: toolsConfig?.category?.label ?? \"Tools\",\n items: toolItems,\n },\n ]\n : groups;\n\n return {\n categories: () => allGroups.map(({ id, label }) => ({ id, label })),\n categoryItems: (id) => allGroups.find((g) => g.id === id)?.items ?? [],\n search: (query) => {\n const lower = query.toLowerCase();\n return allGroups\n .flatMap((g) => g.items)\n .filter((item) => matchesTriggerItemQuery(item, lower));\n },\n };\n }\n\n // Flat: items + (optionally) tools, all in one search pool\n const flatItems = (items ?? []).map(toTriggerItem);\n // Dedupe by id — explicit items win.\n const seen = new Set(flatItems.map((i) => i.id));\n const flatPool = [\n ...flatItems,\n ...toolItems.filter((t) => !seen.has(t.id)),\n ];\n\n return {\n categories: (): readonly Unstable_TriggerCategory[] => [],\n categoryItems: () => [],\n search: (query) => {\n const lower = query.toLowerCase();\n return flatPool.filter((item) => matchesTriggerItemQuery(item, lower));\n },\n };\n }, [items, categories, wantsTools, toolsConfig, isCategorized, toolMentions]);\n\n const directive = useMemo<Unstable_MentionDirective>(\n () => ({\n formatter: formatter ?? unstable_defaultDirectiveFormatter,\n ...(onInserted ? { onInserted } : {}),\n }),\n [formatter, onInserted],\n );\n\n return {\n adapter,\n directive,\n ...(options?.iconMap ? { iconMap: options.iconMap } : {}),\n ...(options?.fallbackIcon ? { fallbackIcon: options.fallbackIcon } : {}),\n };\n}\n\nfunction toTriggerItem(m: Unstable_Mention): Unstable_TriggerItem {\n const metadata =\n m.icon !== undefined ? { ...(m.metadata ?? {}), icon: m.icon } : m.metadata;\n return {\n id: m.id,\n type: m.type,\n label: m.label,\n ...(m.description !== undefined ? { description: m.description } : {}),\n ...(metadata !== undefined ? { metadata } : {}),\n };\n}\n"],"mappings":"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"}
|
|
@@ -17,11 +17,10 @@ type Unstable_MessageStallDetection = {
|
|
|
17
17
|
* @deprecated Under active development and might change without notice.
|
|
18
18
|
*
|
|
19
19
|
* Detects mid-run output stalls on the current message: while the message is
|
|
20
|
-
* running, watches
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* tokens have already streamed.
|
|
20
|
+
* running, watches its text, reasoning, and tool-argument values plus tool-result
|
|
21
|
+
* availability and reports a stall once they stop changing for `thresholdMs`.
|
|
22
|
+
* Useful for re-surfacing a "still working" indicator during tool think-time or
|
|
23
|
+
* provider stalls, after the first tokens have already streamed.
|
|
25
24
|
*
|
|
26
25
|
* Must be used inside a message scope.
|
|
27
26
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMessageStallDetection.d.ts","names":[],"sources":["../../src/unstable/useMessageStallDetection.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"useMessageStallDetection.d.ts","names":[],"sources":["../../src/unstable/useMessageStallDetection.ts"],"mappings":";KAMY;;;;;;EAMV;;KAGU;;EAEV;;EAEA;;;;;;;;;;;;;iBAcc,kCACd,UAAU,wCACT"}
|
|
@@ -1,36 +1,37 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useAuiState } from "@assistant-ui/store";
|
|
3
3
|
import { useEffect, useRef, useState } from "@assistant-ui/tap/react-shim";
|
|
4
|
+
import { useShallowSelector } from "@assistant-ui/store/internal";
|
|
4
5
|
//#region src/unstable/useMessageStallDetection.ts
|
|
5
6
|
/**
|
|
6
7
|
* @deprecated Under active development and might change without notice.
|
|
7
8
|
*
|
|
8
9
|
* Detects mid-run output stalls on the current message: while the message is
|
|
9
|
-
* running, watches
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* tokens have already streamed.
|
|
10
|
+
* running, watches its text, reasoning, and tool-argument values plus tool-result
|
|
11
|
+
* availability and reports a stall once they stop changing for `thresholdMs`.
|
|
12
|
+
* Useful for re-surfacing a "still working" indicator during tool think-time or
|
|
13
|
+
* provider stalls, after the first tokens have already streamed.
|
|
14
14
|
*
|
|
15
15
|
* Must be used inside a message scope.
|
|
16
16
|
*/
|
|
17
17
|
function unstable_useMessageStallDetection(options) {
|
|
18
18
|
const thresholdMs = options?.thresholdMs ?? 2e3;
|
|
19
|
-
const
|
|
20
|
-
if (s.message.status?.type
|
|
21
|
-
|
|
22
|
-
for (const part of s.message.content) if (part.type === "text" || part.type === "reasoning")
|
|
23
|
-
else if (part.type === "tool-call")
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
const activity = useAuiState(useShallowSelector((s) => {
|
|
20
|
+
if (!(s.message.status?.type === "running")) return [false];
|
|
21
|
+
const values = [true, s.message.content.length];
|
|
22
|
+
for (const part of s.message.content) if (part.type === "text" || part.type === "reasoning") values.push(part.type, part.text);
|
|
23
|
+
else if (part.type === "tool-call") values.push(part.type, part.argsText, part.result !== void 0);
|
|
24
|
+
else values.push(part.type);
|
|
25
|
+
return values;
|
|
26
|
+
}));
|
|
27
|
+
const running = activity[0] === true;
|
|
27
28
|
const lastActivityRef = useRef(Date.now());
|
|
28
29
|
const [stalled, setStalled] = useState(false);
|
|
29
30
|
const [, setTick] = useState(0);
|
|
30
31
|
useEffect(() => {
|
|
31
32
|
if (!running) return void 0;
|
|
32
33
|
lastActivityRef.current = Date.now();
|
|
33
|
-
}, [running,
|
|
34
|
+
}, [running, activity]);
|
|
34
35
|
useEffect(() => {
|
|
35
36
|
if (!running) {
|
|
36
37
|
setStalled(false);
|
|
@@ -46,7 +47,7 @@ function unstable_useMessageStallDetection(options) {
|
|
|
46
47
|
return () => clearTimeout(id);
|
|
47
48
|
}, [
|
|
48
49
|
running,
|
|
49
|
-
|
|
50
|
+
activity,
|
|
50
51
|
thresholdMs
|
|
51
52
|
]);
|
|
52
53
|
useEffect(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMessageStallDetection.js","names":["useEffect","useRef","useState","useAuiState","Unstable_MessageStallDetectionOptions","thresholdMs","Unstable_MessageStallDetection","stalled","stalledForMs","unstable_useMessageStallDetection","options","
|
|
1
|
+
{"version":3,"file":"useMessageStallDetection.js","names":["useEffect","useRef","useState","useAuiState","useShallowSelector","Unstable_MessageStallDetectionOptions","thresholdMs","Unstable_MessageStallDetection","stalled","stalledForMs","unstable_useMessageStallDetection","options","activity","s","running","message","status","type","values","content","length","part","push","text","argsText","result","undefined","lastActivityRef","Date","now","setStalled","setTick","current","sinceActivity","id","setTimeout","clearTimeout","setInterval","t","clearInterval","Math","max"],"sources":["../../src/unstable/useMessageStallDetection.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { useAuiState } from \"@assistant-ui/store\";\nimport { useShallowSelector } from \"@assistant-ui/store/internal\";\n\nexport type Unstable_MessageStallDetectionOptions = {\n /**\n * Milliseconds of unchanged message content before the message counts as\n * stalled.\n * @default 2000\n */\n thresholdMs?: number | undefined;\n};\n\nexport type Unstable_MessageStallDetection = {\n /** True while the message is running and its content has not changed for at least `thresholdMs`. */\n stalled: boolean;\n /** Milliseconds since the last observed content change. `0` while not stalled. */\n stalledForMs: number;\n};\n\n/**\n * @deprecated Under active development and might change without notice.\n *\n * Detects mid-run output stalls on the current message: while the message is\n * running, watches its text, reasoning, and tool-argument values plus tool-result\n * availability and reports a stall once they stop changing for `thresholdMs`.\n * Useful for re-surfacing a \"still working\" indicator during tool think-time or\n * provider stalls, after the first tokens have already streamed.\n *\n * Must be used inside a message scope.\n */\nexport function unstable_useMessageStallDetection(\n options?: Unstable_MessageStallDetectionOptions,\n): Unstable_MessageStallDetection {\n const thresholdMs = options?.thresholdMs ?? 2000;\n\n const activity = useAuiState(\n useShallowSelector((s) => {\n const running = s.message.status?.type === \"running\";\n if (!running) return [false];\n\n const values: unknown[] = [true, s.message.content.length];\n\n for (const part of s.message.content) {\n if (part.type === \"text\" || part.type === \"reasoning\") {\n values.push(part.type, part.text);\n } else if (part.type === \"tool-call\") {\n values.push(part.type, part.argsText, part.result !== undefined);\n } else {\n values.push(part.type);\n }\n }\n return values;\n }),\n );\n\n const running = activity[0] === true;\n const lastActivityRef = useRef(Date.now());\n const [stalled, setStalled] = useState(false);\n const [, setTick] = useState(0);\n\n useEffect(() => {\n if (!running) return undefined;\n lastActivityRef.current = Date.now();\n return undefined;\n }, [running, activity]);\n\n useEffect(() => {\n if (!running) {\n setStalled(false);\n return undefined;\n }\n\n const sinceActivity = Date.now() - lastActivityRef.current;\n if (sinceActivity >= thresholdMs) {\n setStalled(true);\n return undefined;\n }\n\n setStalled(false);\n const id = setTimeout(() => setStalled(true), thresholdMs - sinceActivity);\n return () => clearTimeout(id);\n }, [running, activity, thresholdMs]);\n\n useEffect(() => {\n if (!stalled) return undefined;\n const id = setInterval(() => setTick((t) => t + 1), 1000);\n return () => clearInterval(id);\n }, [stalled]);\n\n if (!stalled) return { stalled: false, stalledForMs: 0 };\n return {\n stalled: true,\n stalledForMs: Math.max(0, Date.now() - lastActivityRef.current),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiCA,SAAgBU,kCACdC,SACgC;CAChC,MAAML,cAAcK,SAASL,eAAe;CAE5C,MAAMM,WAAWT,YACfC,oBAAoBS,MAAM;EAExB,IAAI,EADYA,EAAEE,QAAQC,QAAQC,SAAS,YAC7B,OAAO,CAAC,KAAK;EAE3B,MAAMC,SAAoB,CAAC,MAAML,EAAEE,QAAQI,QAAQC,MAAM;EAEzD,KAAK,MAAMC,QAAQR,EAAEE,QAAQI,SAC3B,IAAIE,KAAKJ,SAAS,UAAUI,KAAKJ,SAAS,aACxCC,OAAOI,KAAKD,KAAKJ,MAAMI,KAAKE,IAAI;OAC3B,IAAIF,KAAKJ,SAAS,aACvBC,OAAOI,KAAKD,KAAKJ,MAAMI,KAAKG,UAAUH,KAAKI,WAAWC,KAAAA,CAAS;OAE/DR,OAAOI,KAAKD,KAAKJ,IAAI;EAGzB,OAAOC;CACT,CAAC,CACH;CAEA,MAAMJ,UAAUF,SAAS,OAAO;CAChC,MAAMe,kBAAkB1B,OAAO2B,KAAKC,IAAI,CAAC;CACzC,MAAM,CAACrB,SAASsB,cAAc5B,SAAS,KAAK;CAC5C,MAAM,GAAG6B,WAAW7B,SAAS,CAAC;CAE9BF,gBAAgB;EACd,IAAI,CAACc,SAAS,OAAOY,KAAAA;EACrBC,gBAAgBK,UAAUJ,KAAKC,IAAI;CAErC,GAAG,CAACf,SAASF,QAAQ,CAAC;CAEtBZ,gBAAgB;EACd,IAAI,CAACc,SAAS;GACZgB,WAAW,KAAK;GAChB;EACF;EAEA,MAAMG,gBAAgBL,KAAKC,IAAI,IAAIF,gBAAgBK;EACnD,IAAIC,iBAAiB3B,aAAa;GAChCwB,WAAW,IAAI;GACf;EACF;EAEAA,WAAW,KAAK;EAChB,MAAMI,KAAKC,iBAAiBL,WAAW,IAAI,GAAGxB,cAAc2B,aAAa;EACzE,aAAaG,aAAaF,EAAE;CAC9B,GAAG;EAACpB;EAASF;EAAUN;CAAW,CAAC;CAEnCN,gBAAgB;EACd,IAAI,CAACQ,SAAS,OAAOkB,KAAAA;EACrB,MAAMQ,KAAKG,kBAAkBN,SAASO,MAAMA,IAAI,CAAC,GAAG,GAAI;EACxD,aAAaC,cAAcL,EAAE;CAC/B,GAAG,CAAC1B,OAAO,CAAC;CAEZ,IAAI,CAACA,SAAS,OAAO;EAAEA,SAAS;EAAOC,cAAc;CAAE;CACvD,OAAO;EACLD,SAAS;EACTC,cAAc+B,KAAKC,IAAI,GAAGb,KAAKC,IAAI,IAAIF,gBAAgBK,OAAO;CAChE;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"convertTools.d.ts","names":[],"sources":["../../../src/unstable/webmcp/convertTools.ts"],"mappings":";;;;;;;;;;cAoBa,sBACX,eACA,MAAM;cAMK,sBAAuB,MAAM;cA2C7B,eACX,iBACA;EACE,MAAM;EACN;EACA,MAAM;MAEP,QAAQ;
|
|
1
|
+
{"version":3,"file":"convertTools.d.ts","names":[],"sources":["../../../src/unstable/webmcp/convertTools.ts"],"mappings":";;;;;;;;;;cAoBa,sBACX,eACA,MAAM;cAMK,sBAAuB,MAAM;cA2C7B,eACX,iBACA;EACE,MAAM;EACN;EACA,MAAM;MAEP,QAAQ;cAwGE,eACX,cACA,eAAe,gBACf,kBAAkB,gBACjB"}
|
|
@@ -64,6 +64,47 @@ const toMcpContent = async (result, options) => {
|
|
|
64
64
|
} : { content };
|
|
65
65
|
};
|
|
66
66
|
const isStandardSchema = (schema) => typeof schema === "object" && schema !== null && "~standard" in schema && schema["~standard"].version === 1;
|
|
67
|
+
const TOOL_ABORTED = Symbol("assistant-ui.webmcp-tool-aborted");
|
|
68
|
+
const isThenable = (value) => typeof value?.then === "function";
|
|
69
|
+
const raceWithAbort = async (value, abortSignal) => {
|
|
70
|
+
let onAbort;
|
|
71
|
+
const abortPromise = new Promise((resolve) => {
|
|
72
|
+
onAbort = () => resolve(TOOL_ABORTED);
|
|
73
|
+
if (abortSignal.aborted) onAbort();
|
|
74
|
+
else abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
75
|
+
});
|
|
76
|
+
try {
|
|
77
|
+
return await Promise.race([abortPromise, value]);
|
|
78
|
+
} finally {
|
|
79
|
+
abortSignal.removeEventListener("abort", onAbort);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const combineAbortSignals = (callerSignal, lifecycleSignal) => {
|
|
83
|
+
const controller = new AbortController();
|
|
84
|
+
const teardown = [];
|
|
85
|
+
const cleanup = () => {
|
|
86
|
+
while (teardown.length) teardown.pop()();
|
|
87
|
+
};
|
|
88
|
+
const abort = (reason) => {
|
|
89
|
+
cleanup();
|
|
90
|
+
controller.abort(reason);
|
|
91
|
+
};
|
|
92
|
+
const listen = (signal) => {
|
|
93
|
+
const onAbort = () => abort(signal.reason);
|
|
94
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
95
|
+
teardown.push(() => signal.removeEventListener("abort", onAbort));
|
|
96
|
+
};
|
|
97
|
+
if (callerSignal.aborted) abort(callerSignal.reason);
|
|
98
|
+
else if (lifecycleSignal.aborted) abort(lifecycleSignal.reason);
|
|
99
|
+
else {
|
|
100
|
+
listen(callerSignal);
|
|
101
|
+
listen(lifecycleSignal);
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
signal: controller.signal,
|
|
105
|
+
cleanup
|
|
106
|
+
};
|
|
107
|
+
};
|
|
67
108
|
const toWebMcpTool = (name, getTool, lifecycleSignal) => ({
|
|
68
109
|
name,
|
|
69
110
|
description: getTool().description ?? "",
|
|
@@ -73,13 +114,21 @@ const toWebMcpTool = (name, getTool, lifecycleSignal) => ({
|
|
|
73
114
|
const tool = getTool();
|
|
74
115
|
const args = rawArgs ?? {};
|
|
75
116
|
const toolCallId = generateId();
|
|
117
|
+
let cleanup;
|
|
76
118
|
try {
|
|
77
119
|
const callerSignal = context?.signal;
|
|
78
|
-
|
|
120
|
+
let abortSignal;
|
|
121
|
+
if (callerSignal && lifecycleSignal) {
|
|
122
|
+
const combined = combineAbortSignals(callerSignal, lifecycleSignal);
|
|
123
|
+
abortSignal = combined.signal;
|
|
124
|
+
cleanup = combined.cleanup;
|
|
125
|
+
} else abortSignal = callerSignal ?? lifecycleSignal;
|
|
126
|
+
if (abortSignal?.aborted) return errorResult("Tool execution was cancelled.");
|
|
79
127
|
let executeFn = tool.execute;
|
|
80
128
|
if (isStandardSchema(tool.parameters)) {
|
|
81
|
-
|
|
82
|
-
validation = await
|
|
129
|
+
const result = tool.parameters["~standard"].validate(args);
|
|
130
|
+
const validation = isThenable(result) ? abortSignal ? await raceWithAbort(result, abortSignal) : await result : result;
|
|
131
|
+
if (validation === TOOL_ABORTED) return errorResult("Tool execution was cancelled.");
|
|
83
132
|
if (validation.issues) {
|
|
84
133
|
const issues = validation.issues;
|
|
85
134
|
executeFn = tool.experimental_onSchemaValidationError ?? (() => {
|
|
@@ -101,6 +150,8 @@ const toWebMcpTool = (name, getTool, lifecycleSignal) => ({
|
|
|
101
150
|
});
|
|
102
151
|
} catch (e) {
|
|
103
152
|
return errorResult(e instanceof Error ? e.message : String(e));
|
|
153
|
+
} finally {
|
|
154
|
+
cleanup?.();
|
|
104
155
|
}
|
|
105
156
|
}
|
|
106
157
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"convertTools.js","names":["ToolResponse","toJSONSchema","Tool","ToolModelContentPart","generateId","WebMcpCallToolResult","WebMcpContent","WebMcpToolDescriptor","defaultWebMcpFilter","_name","tool","type","undefined","execute","disabled","toWebMcpInputSchema","parameters","properties","textContent","text","errorResult","message","isError","content","toText","value","toString","JSON","stringify","String","primitiveContent","mapModelContentPart","part","mediaType","startsWith","data","mimeType","toMcpContent","result","options","toolCallId","args","Record","Promise","response","toResponse","modelContent","map","toModelOutput","parts","input","output","e","console","warn","StandardSchemaLike","version","validate","issues","isStandardSchema","schema","toWebMcpTool","name","getTool","lifecycleSignal","AbortSignal","description","inputSchema","rawArgs","context","aborted","callerSignal","signal","abortSignal","any","executeFn","validation","experimental_onSchemaValidationError","Error","AbortController","human","reject"],"sources":["../../../src/unstable/webmcp/convertTools.ts"],"sourcesContent":["import {\n ToolResponse,\n toJSONSchema,\n type Tool,\n type ToolModelContentPart,\n} from \"assistant-stream\";\nimport { generateId } from \"@assistant-ui/core/internal\";\nimport type {\n WebMcpCallToolResult,\n WebMcpContent,\n WebMcpToolDescriptor,\n} from \"./webmcp-host\";\n\n/**\n * The predicate the WebMCP provider uses when no `filter` is passed: an enabled\n * frontend tool with a client-side `execute`. A tool authored without a `type`\n * is included, because `execute` is what distinguishes the deprecated\n * type-less form from a backend or human tool. A `filter` replaces this, so\n * pass it through to narrow the default set rather than widen it.\n */\nexport const defaultWebMcpFilter = (\n _name: string,\n tool: Tool<any, any>,\n): boolean =>\n (tool.type === \"frontend\" || tool.type === undefined) &&\n !!tool.execute &&\n !tool.disabled;\n\nexport const toWebMcpInputSchema = (tool: Tool<any, any>): unknown =>\n tool.parameters\n ? toJSONSchema(tool.parameters)\n : { type: \"object\", properties: {} };\n\nconst textContent = (text: string): WebMcpContent => ({ type: \"text\", text });\n\nconst errorResult = (message: string): WebMcpCallToolResult => ({\n isError: true,\n content: [textContent(message)],\n});\n\n// bigint throws in JSON.stringify and symbol serializes to undefined, but both\n// have a faithful string form. A value that cannot be serialized at all still\n// throws through to the error result.\nconst toText = (value: unknown): string => {\n if (typeof value === \"string\") return value;\n if (typeof value === \"bigint\" || typeof value === \"symbol\") {\n return value.toString();\n }\n return JSON.stringify(value) ?? String(value);\n};\n\nconst primitiveContent = (value: unknown): WebMcpContent[] => [\n textContent(toText(value)),\n];\n\nconst mapModelContentPart = (part: ToolModelContentPart): WebMcpContent => {\n if (part.type === \"text\") {\n return textContent(part.text ?? \"\");\n }\n if (part.type === \"file\") {\n if (\n typeof part.mediaType === \"string\" &&\n part.mediaType.startsWith(\"image/\")\n ) {\n return { type: \"image\", data: part.data ?? \"\", mimeType: part.mediaType };\n }\n return textContent(part.data ?? \"\");\n }\n return textContent(toText(part));\n};\n\nexport const toMcpContent = async (\n result: unknown,\n options: {\n tool: Tool<any, any>;\n toolCallId: string;\n args: Record<string, unknown>;\n },\n): Promise<WebMcpCallToolResult> => {\n const response = ToolResponse.toResponse(result);\n if (response.modelContent) {\n const content = response.modelContent.map(mapModelContentPart);\n return response.isError ? { isError: true, content } : { content };\n }\n if (!response.isError && options.tool.toModelOutput) {\n try {\n const parts = await options.tool.toModelOutput({\n toolCallId: options.toolCallId,\n input: options.args,\n output: response.result,\n });\n return { content: parts.map(mapModelContentPart) };\n } catch (e) {\n console.warn(\n \"[assistant-ui] toModelOutput threw; falling back to default projection.\",\n e,\n );\n }\n }\n const content = primitiveContent(response.result);\n return response.isError ? { isError: true, content } : { content };\n};\n\ntype StandardSchemaLike = {\n \"~standard\": {\n version: number;\n validate: (\n value: unknown,\n ) =>\n | { issues?: readonly unknown[] | undefined }\n | Promise<{ issues?: readonly unknown[] | undefined }>;\n };\n};\n\nconst isStandardSchema = (schema: unknown): schema is StandardSchemaLike =>\n typeof schema === \"object\" &&\n schema !== null &&\n \"~standard\" in schema &&\n (schema as StandardSchemaLike)[\"~standard\"].version === 1;\n\nexport const toWebMcpTool = (\n name: string,\n getTool: () => Tool<any, any>,\n lifecycleSignal?: AbortSignal,\n): WebMcpToolDescriptor => ({\n name,\n description: getTool().description ?? \"\",\n inputSchema: toWebMcpInputSchema(getTool()),\n execute: async (rawArgs, context) => {\n if (lifecycleSignal?.aborted) {\n return errorResult(`Tool \"${name}\" is no longer registered`);\n }\n const tool = getTool();\n const args = (rawArgs ?? {}) as Record<string, unknown>;\n const toolCallId = generateId();\n try {\n const callerSignal = context?.signal;\n const abortSignal = !callerSignal\n ? lifecycleSignal\n : !lifecycleSignal\n ? callerSignal\n : AbortSignal.any([callerSignal, lifecycleSignal]);\n let executeFn = tool.execute;\n if (isStandardSchema(tool.parameters)) {\n let validation = tool.parameters[\"~standard\"].validate(args);\n validation = await validation;\n if (validation.issues) {\n const issues = validation.issues;\n executeFn =\n tool.experimental_onSchemaValidationError ??\n (() => {\n throw new Error(\n `Function parameter validation failed. ${JSON.stringify(issues)}`,\n );\n });\n }\n }\n\n if (abortSignal?.aborted) {\n return errorResult(\"Tool execution was cancelled.\");\n }\n\n if (!executeFn) {\n return errorResult(`Tool \"${name}\" has no client-side implementation.`);\n }\n\n const result = await executeFn(args, {\n toolCallId,\n abortSignal: abortSignal ?? new AbortController().signal,\n human: () =>\n Promise.reject(\n new Error(\"human input not supported in WebMCP context\"),\n ),\n });\n return await toMcpContent(result, { tool, toolCallId, args });\n } catch (e) {\n return errorResult(e instanceof Error ? e.message : String(e));\n }\n },\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"convertTools.js","names":["ToolResponse","toJSONSchema","Tool","ToolModelContentPart","generateId","WebMcpCallToolResult","WebMcpContent","WebMcpToolDescriptor","defaultWebMcpFilter","_name","tool","type","undefined","execute","disabled","toWebMcpInputSchema","parameters","properties","textContent","text","errorResult","message","isError","content","toText","value","toString","JSON","stringify","String","primitiveContent","mapModelContentPart","part","mediaType","startsWith","data","mimeType","toMcpContent","result","options","toolCallId","args","Record","Promise","response","toResponse","modelContent","map","toModelOutput","parts","input","output","e","console","warn","StandardSchemaLike","version","validate","issues","isStandardSchema","schema","TOOL_ABORTED","Symbol","isThenable","T","PromiseLike","then","raceWithAbort","abortSignal","AbortSignal","onAbort","abortPromise","resolve","aborted","addEventListener","once","race","removeEventListener","combineAbortSignals","callerSignal","lifecycleSignal","signal","cleanup","controller","AbortController","teardown","length","pop","abort","reason","listen","push","toWebMcpTool","name","getTool","description","inputSchema","rawArgs","context","combined","executeFn","validation","experimental_onSchemaValidationError","Error","human","reject"],"sources":["../../../src/unstable/webmcp/convertTools.ts"],"sourcesContent":["import {\n ToolResponse,\n toJSONSchema,\n type Tool,\n type ToolModelContentPart,\n} from \"assistant-stream\";\nimport { generateId } from \"@assistant-ui/core/internal\";\nimport type {\n WebMcpCallToolResult,\n WebMcpContent,\n WebMcpToolDescriptor,\n} from \"./webmcp-host\";\n\n/**\n * The predicate the WebMCP provider uses when no `filter` is passed: an enabled\n * frontend tool with a client-side `execute`. A tool authored without a `type`\n * is included, because `execute` is what distinguishes the deprecated\n * type-less form from a backend or human tool. A `filter` replaces this, so\n * pass it through to narrow the default set rather than widen it.\n */\nexport const defaultWebMcpFilter = (\n _name: string,\n tool: Tool<any, any>,\n): boolean =>\n (tool.type === \"frontend\" || tool.type === undefined) &&\n !!tool.execute &&\n !tool.disabled;\n\nexport const toWebMcpInputSchema = (tool: Tool<any, any>): unknown =>\n tool.parameters\n ? toJSONSchema(tool.parameters)\n : { type: \"object\", properties: {} };\n\nconst textContent = (text: string): WebMcpContent => ({ type: \"text\", text });\n\nconst errorResult = (message: string): WebMcpCallToolResult => ({\n isError: true,\n content: [textContent(message)],\n});\n\n// bigint throws in JSON.stringify and symbol serializes to undefined, but both\n// have a faithful string form. A value that cannot be serialized at all still\n// throws through to the error result.\nconst toText = (value: unknown): string => {\n if (typeof value === \"string\") return value;\n if (typeof value === \"bigint\" || typeof value === \"symbol\") {\n return value.toString();\n }\n return JSON.stringify(value) ?? String(value);\n};\n\nconst primitiveContent = (value: unknown): WebMcpContent[] => [\n textContent(toText(value)),\n];\n\nconst mapModelContentPart = (part: ToolModelContentPart): WebMcpContent => {\n if (part.type === \"text\") {\n return textContent(part.text ?? \"\");\n }\n if (part.type === \"file\") {\n if (\n typeof part.mediaType === \"string\" &&\n part.mediaType.startsWith(\"image/\")\n ) {\n return { type: \"image\", data: part.data ?? \"\", mimeType: part.mediaType };\n }\n return textContent(part.data ?? \"\");\n }\n return textContent(toText(part));\n};\n\nexport const toMcpContent = async (\n result: unknown,\n options: {\n tool: Tool<any, any>;\n toolCallId: string;\n args: Record<string, unknown>;\n },\n): Promise<WebMcpCallToolResult> => {\n const response = ToolResponse.toResponse(result);\n if (response.modelContent) {\n const content = response.modelContent.map(mapModelContentPart);\n return response.isError ? { isError: true, content } : { content };\n }\n if (!response.isError && options.tool.toModelOutput) {\n try {\n const parts = await options.tool.toModelOutput({\n toolCallId: options.toolCallId,\n input: options.args,\n output: response.result,\n });\n return { content: parts.map(mapModelContentPart) };\n } catch (e) {\n console.warn(\n \"[assistant-ui] toModelOutput threw; falling back to default projection.\",\n e,\n );\n }\n }\n const content = primitiveContent(response.result);\n return response.isError ? { isError: true, content } : { content };\n};\n\ntype StandardSchemaLike = {\n \"~standard\": {\n version: number;\n validate: (\n value: unknown,\n ) =>\n | { issues?: readonly unknown[] | undefined }\n | Promise<{ issues?: readonly unknown[] | undefined }>;\n };\n};\n\nconst isStandardSchema = (schema: unknown): schema is StandardSchemaLike =>\n typeof schema === \"object\" &&\n schema !== null &&\n \"~standard\" in schema &&\n (schema as StandardSchemaLike)[\"~standard\"].version === 1;\n\nconst TOOL_ABORTED = Symbol(\"assistant-ui.webmcp-tool-aborted\");\n\nconst isThenable = <T>(value: T | PromiseLike<T>): value is PromiseLike<T> =>\n typeof (value as PromiseLike<T> | null | undefined)?.then === \"function\";\n\nconst raceWithAbort = async <T>(\n value: PromiseLike<T>,\n abortSignal: AbortSignal,\n): Promise<T | typeof TOOL_ABORTED> => {\n let onAbort!: () => void;\n const abortPromise = new Promise<typeof TOOL_ABORTED>((resolve) => {\n onAbort = () => resolve(TOOL_ABORTED);\n if (abortSignal.aborted) {\n onAbort();\n } else {\n abortSignal.addEventListener(\"abort\", onAbort, { once: true });\n }\n });\n\n try {\n // Unlike assistant-stream's helper, cancellation wins when validation aborts and rejects synchronously.\n return await Promise.race([abortPromise, value]);\n } finally {\n abortSignal.removeEventListener(\"abort\", onAbort);\n }\n};\n\n// AbortSignal.any sits above the browserslist floor and rejects any input that\n// is not a native AbortSignal, which a navigator.modelContext polyfill's signal\n// is not. The merged signal tracks its inputs only until cleanup runs, where\n// the single-signal path hands the caller the lifecycle signal itself.\nconst combineAbortSignals = (\n callerSignal: AbortSignal,\n lifecycleSignal: AbortSignal,\n): { signal: AbortSignal; cleanup: () => void } => {\n const controller = new AbortController();\n const teardown: (() => void)[] = [];\n const cleanup = () => {\n while (teardown.length) teardown.pop()!();\n };\n const abort = (reason: unknown) => {\n cleanup();\n controller.abort(reason);\n };\n const listen = (signal: AbortSignal) => {\n const onAbort = () => abort(signal.reason);\n signal.addEventListener(\"abort\", onAbort, { once: true });\n teardown.push(() => signal.removeEventListener(\"abort\", onAbort));\n };\n\n if (callerSignal.aborted) {\n abort(callerSignal.reason);\n } else if (lifecycleSignal.aborted) {\n abort(lifecycleSignal.reason);\n } else {\n listen(callerSignal);\n listen(lifecycleSignal);\n }\n\n return { signal: controller.signal, cleanup };\n};\n\nexport const toWebMcpTool = (\n name: string,\n getTool: () => Tool<any, any>,\n lifecycleSignal?: AbortSignal,\n): WebMcpToolDescriptor => ({\n name,\n description: getTool().description ?? \"\",\n inputSchema: toWebMcpInputSchema(getTool()),\n execute: async (rawArgs, context) => {\n if (lifecycleSignal?.aborted) {\n return errorResult(`Tool \"${name}\" is no longer registered`);\n }\n const tool = getTool();\n const args = (rawArgs ?? {}) as Record<string, unknown>;\n const toolCallId = generateId();\n let cleanup: (() => void) | undefined;\n try {\n const callerSignal = context?.signal;\n let abortSignal: AbortSignal | undefined;\n if (callerSignal && lifecycleSignal) {\n const combined = combineAbortSignals(callerSignal, lifecycleSignal);\n abortSignal = combined.signal;\n cleanup = combined.cleanup;\n } else {\n abortSignal = callerSignal ?? lifecycleSignal;\n }\n\n if (abortSignal?.aborted) {\n return errorResult(\"Tool execution was cancelled.\");\n }\n\n let executeFn = tool.execute;\n if (isStandardSchema(tool.parameters)) {\n const result = tool.parameters[\"~standard\"].validate(args);\n const validation = isThenable(result)\n ? abortSignal\n ? await raceWithAbort(result, abortSignal)\n : await result\n : result;\n if (validation === TOOL_ABORTED) {\n return errorResult(\"Tool execution was cancelled.\");\n }\n if (validation.issues) {\n const issues = validation.issues;\n executeFn =\n tool.experimental_onSchemaValidationError ??\n (() => {\n throw new Error(\n `Function parameter validation failed. ${JSON.stringify(issues)}`,\n );\n });\n }\n }\n\n if (abortSignal?.aborted) {\n return errorResult(\"Tool execution was cancelled.\");\n }\n\n if (!executeFn) {\n return errorResult(`Tool \"${name}\" has no client-side implementation.`);\n }\n\n const result = await executeFn(args, {\n toolCallId,\n abortSignal: abortSignal ?? new AbortController().signal,\n human: () =>\n Promise.reject(\n new Error(\"human input not supported in WebMCP context\"),\n ),\n });\n return await toMcpContent(result, { tool, toolCallId, args });\n } catch (e) {\n return errorResult(e instanceof Error ? e.message : String(e));\n } finally {\n cleanup?.();\n }\n },\n});\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWebMcpProvider.d.ts","names":[],"sources":["../../../src/unstable/webmcp/useWebMcpProvider.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"useWebMcpProvider.d.ts","names":[],"sources":["../../../src/unstable/webmcp/useWebMcpProvider.ts"],"mappings":";;KAeY;EACV,UAAU,cAAc,MAAM;;KAGpB;EACV;EACA;;;;;;;;;;cAqGW,6BACX,UAAS,mCACR"}
|
|
@@ -7,6 +7,7 @@ import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
|
|
|
7
7
|
import { useAui } from "@assistant-ui/store";
|
|
8
8
|
import { useEffect, useState, useSyncExternalStore } from "@assistant-ui/tap/react-shim";
|
|
9
9
|
import { useResources, useTapRoot, withKey } from "@assistant-ui/tap";
|
|
10
|
+
import { shallowEqual } from "@assistant-ui/store/internal";
|
|
10
11
|
//#region src/unstable/webmcp/useWebMcpProvider.ts
|
|
11
12
|
const EMPTY_NAMES = Object.freeze([]);
|
|
12
13
|
const EMPTY_TOOLS = Object.freeze({});
|
|
@@ -32,7 +33,7 @@ const useStableNames = (names) => {
|
|
|
32
33
|
const [cell] = useState(() => ({ names: EMPTY_NAMES }));
|
|
33
34
|
const next = names.filter((name) => name !== null).sort();
|
|
34
35
|
const previous = cell.names;
|
|
35
|
-
if (previous
|
|
36
|
+
if (shallowEqual(previous, next)) return previous;
|
|
36
37
|
cell.names = next;
|
|
37
38
|
return next;
|
|
38
39
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWebMcpProvider.js","names":["c","_c","useEffect","useState","useSyncExternalStore","useResources","useTapRoot","withKey","useAui","AssistantClient","Tool","getDefaultWebMcpHost","WebMcpHost","defaultWebMcpFilter","toWebMcpInputSchema","WebMcpRegistrationResource","useModelContextSnapshot","ModelContextSnapshotSource","Unstable_WebMcpProviderOptions","filter","name","tool","Unstable_WebMcpProviderResult","status","registeredToolNames","EMPTY_NAMES","Object","freeze","EMPTY_TOOLS","Record","warned","WeakSet","signatures","WeakMap","description","signature","signatureOf","cached","get","JSON","stringify","set","NO_SUBSCRIPTION","modelContextToolSource","empty","read","aui","modelContext","getModelContext","tools","subscribe","onChange","useStableNames","names","cell","next","sort","previous","
|
|
1
|
+
{"version":3,"file":"useWebMcpProvider.js","names":["c","_c","useEffect","useState","useSyncExternalStore","useResources","useTapRoot","withKey","useAui","AssistantClient","Tool","getDefaultWebMcpHost","WebMcpHost","defaultWebMcpFilter","toWebMcpInputSchema","WebMcpRegistrationResource","shallowEqual","useModelContextSnapshot","ModelContextSnapshotSource","Unstable_WebMcpProviderOptions","filter","name","tool","Unstable_WebMcpProviderResult","status","registeredToolNames","EMPTY_NAMES","Object","freeze","EMPTY_TOOLS","Record","warned","WeakSet","signatures","WeakMap","description","signature","signatureOf","cached","get","JSON","stringify","set","NO_SUBSCRIPTION","modelContextToolSource","empty","read","aui","modelContext","getModelContext","tools","subscribe","onChange","useStableNames","names","cell","next","sort","previous","useWebMcpRegistry","t0","$","host","available","elements","entries","push","t1","error","has","add","console","warn","unstable_useWebMcpProvider","options","root","WebMcpProviderRoot","getValue","published","setPublished"],"sources":["../../../src/unstable/webmcp/useWebMcpProvider.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState, useSyncExternalStore } from \"react\";\nimport { useResources, useTapRoot, withKey } from \"@assistant-ui/tap\";\nimport { useAui, type AssistantClient } from \"@assistant-ui/store\";\nimport type { Tool } from \"assistant-stream\";\nimport { getDefaultWebMcpHost, type WebMcpHost } from \"./webmcp-host\";\nimport { defaultWebMcpFilter, toWebMcpInputSchema } from \"./convertTools\";\nimport { WebMcpRegistrationResource } from \"./WebMcpRegistrationResource\";\nimport { shallowEqual } from \"@assistant-ui/store/internal\";\nimport {\n useModelContextSnapshot,\n type ModelContextSnapshotSource,\n} from \"../useModelContextSnapshot\";\n\nexport type Unstable_WebMcpProviderOptions = {\n filter?: (name: string, tool: Tool<any, any>) => boolean;\n};\n\nexport type Unstable_WebMcpProviderResult = {\n status: \"unsupported\" | \"active\";\n registeredToolNames: readonly string[];\n};\n\nconst EMPTY_NAMES: readonly string[] = Object.freeze([]);\nconst EMPTY_TOOLS: Record<string, Tool<any, any>> = Object.freeze({});\n\n// The description is re-read on every sync so mutating it in place is\n// observed; the schema is converted only when the tool object itself changes.\n// Keyed on the tool so a schema or filter that throws warns once instead of\n// on every model-context notify.\nconst warned = new WeakSet<Tool<any, any>>();\n\nconst signatures = new WeakMap<\n Tool<any, any>,\n { description: string | undefined; signature: string }\n>();\n\nconst signatureOf = (tool: Tool<any, any>) => {\n const cached = signatures.get(tool);\n if (cached && cached.description === tool.description)\n return cached.signature;\n const signature = `${tool.description ?? \"\"}\\u0000${JSON.stringify(\n toWebMcpInputSchema(tool),\n )}`;\n signatures.set(tool, { description: tool.description, signature });\n return signature;\n};\n\nconst NO_SUBSCRIPTION = () => {};\n\n// No `isEqual`: a caller republishing an unchanged tool set is how it asks for\n// a re-sync, which is what re-attempts a tool whose filter has stopped throwing\n// and what picks up a description edited in place on a stable tool object.\nconst modelContextToolSource: ModelContextSnapshotSource<\n Record<string, Tool<any, any>>\n> = {\n empty: EMPTY_TOOLS,\n read: (aui) => aui.modelContext.getModelContext().tools ?? EMPTY_TOOLS,\n subscribe: (aui, onChange) =>\n aui.modelContext.subscribe?.(onChange) ?? NO_SUBSCRIPTION,\n};\n\nconst useStableNames = (names: readonly (string | null)[]) => {\n const [cell] = useState(() => ({ names: EMPTY_NAMES }));\n const next = names.filter((name): name is string => name !== null).sort();\n const previous = cell.names;\n if (shallowEqual(previous, next)) {\n return previous;\n }\n cell.names = next;\n return next;\n};\n\nconst useWebMcpRegistry = ({\n aui,\n host,\n filter,\n}: {\n aui: AssistantClient;\n host: WebMcpHost;\n filter: (name: string, tool: Tool<any, any>) => boolean;\n}) => {\n const tools = useModelContextSnapshot(\n aui,\n host.available,\n modelContextToolSource,\n );\n\n const elements = [];\n for (const [name, tool] of Object.entries(tools)) {\n try {\n if (!filter(name, tool)) continue;\n const signature = signatureOf(tool);\n elements.push(\n withKey(\n name,\n WebMcpRegistrationResource({ host, name, signature, tool }),\n [host, name, signature, tool],\n ),\n );\n } catch (error) {\n if (warned.has(tool)) continue;\n warned.add(tool);\n console.warn(\n `[assistant-ui] Skipping WebMCP registration for tool \"${name}\": filter or schema conversion failed.`,\n error,\n );\n }\n }\n\n return useStableNames(useResources(elements));\n};\n\n/**\n * Publishes the frontend tools in the model context to a WebMCP-capable\n * browser, so the user's own browser agent can call them.\n *\n * Returns `status: \"unsupported\"` when the page exposes no\n * `document.modelContext` (or `navigator.modelContext`), and the sorted names\n * of the tools currently registered with the host.\n */\nexport const unstable_useWebMcpProvider = (\n options: Unstable_WebMcpProviderOptions = {},\n): Unstable_WebMcpProviderResult => {\n const aui = useAui();\n const [host] = useState(getDefaultWebMcpHost);\n const filter = options.filter ?? defaultWebMcpFilter;\n\n const root = useTapRoot(function WebMcpProviderRoot() {\n return useWebMcpRegistry({ aui, host, filter });\n });\n const registeredToolNames = useSyncExternalStore(\n root.subscribe,\n root.getValue,\n () => EMPTY_NAMES,\n );\n\n const [published, setPublished] = useState(false);\n useEffect(() => {\n if (host.available) setPublished(true);\n }, [host]);\n\n return {\n status: published ? \"active\" : \"unsupported\",\n registeredToolNames,\n };\n};\n"],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { ComponentPropsWithoutRef, ComponentRef, ElementType, ForwardRefExoticComponent, ReactElement, RefAttributes } from "react";
|
|
1
|
+
import { ComponentProps, ComponentPropsWithoutRef, ComponentRef, ElementType, ForwardRefExoticComponent, ReactElement, ReactNode, RefAttributes } from "react";
|
|
2
2
|
import { Primitive as Primitive$1 } from "radix-ui/internal";
|
|
3
|
+
import { Slot } from "radix-ui";
|
|
3
4
|
//#region src/utils/Primitive.d.ts
|
|
4
5
|
/**
|
|
5
6
|
* Thin wrapper around Radix `Primitive` that adds `render` prop support.
|
|
@@ -16,6 +17,13 @@ type WithRenderPropProps<T extends ElementType> = ComponentPropsWithoutRef<T> &
|
|
|
16
17
|
render?: ReactElement | undefined;
|
|
17
18
|
};
|
|
18
19
|
type PrimitiveProps<E extends PrimitiveNode> = WithRenderPropProps<(typeof Primitive$1)[E]>;
|
|
20
|
+
/**
|
|
21
|
+
* Composes a `render` element at a call site that has already computed its props.
|
|
22
|
+
*
|
|
23
|
+
* `withRenderProp` wraps a component; this covers the case where the props are
|
|
24
|
+
* already in hand and the composition happens inline.
|
|
25
|
+
*/
|
|
26
|
+
declare function renderSlot(render: ReactElement, children: ReactNode, props: ComponentProps<typeof Slot.Root>): ReactElement;
|
|
19
27
|
declare function withRenderProp<T extends ElementType>(Component: T): ForwardRefExoticComponent<WithRenderPropProps<T> & RefAttributes<ComponentRef<T>>>;
|
|
20
28
|
declare const Primitive: {
|
|
21
29
|
a: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLAnchorElement> & import("react").AnchorHTMLAttributes<HTMLAnchorElement> & {
|
|
@@ -93,17 +101,17 @@ declare const Primitive: {
|
|
|
93
101
|
}, "ref"> & {
|
|
94
102
|
render?: ReactElement | undefined;
|
|
95
103
|
} & RefAttributes<HTMLSpanElement>>;
|
|
96
|
-
|
|
104
|
+
svg: ForwardRefExoticComponent<Omit<import("react").SVGProps<SVGSVGElement> & {
|
|
97
105
|
asChild?: boolean;
|
|
98
106
|
}, "ref"> & {
|
|
99
107
|
render?: ReactElement | undefined;
|
|
100
|
-
} & RefAttributes<
|
|
101
|
-
|
|
108
|
+
} & RefAttributes<SVGSVGElement>>;
|
|
109
|
+
ul: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLUListElement> & import("react").HTMLAttributes<HTMLUListElement> & {
|
|
102
110
|
asChild?: boolean;
|
|
103
111
|
}, "ref"> & {
|
|
104
112
|
render?: ReactElement | undefined;
|
|
105
|
-
} & RefAttributes<
|
|
113
|
+
} & RefAttributes<HTMLUListElement>>;
|
|
106
114
|
};
|
|
107
115
|
//#endregion
|
|
108
|
-
export { Primitive, type PrimitiveProps, type WithRenderPropProps, withRenderProp };
|
|
116
|
+
export { Primitive, type PrimitiveProps, type WithRenderPropProps, renderSlot, withRenderProp };
|
|
109
117
|
//# sourceMappingURL=Primitive.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Primitive.d.ts","names":[],"sources":["../../src/utils/Primitive.tsx"],"mappings":"
|
|
1
|
+
{"version":3,"file":"Primitive.d.ts","names":[],"sources":["../../src/utils/Primitive.tsx"],"mappings":";;;;;;;;;;;;;cA4BM;KAmBD,wBAAwB;KAExB,oBAAoB,UAAU,eACjC,yBAAyB;EACvB,SAAS;;KAGR,eAAe,UAAU,iBAAiB,4BACrC,aAAgB;;;;;;;iBAoCjB,WACP,QAAQ,cACR,UAAU,WACV,OAAO,sBAAsB,KAAK,QACjC;iBAMM,eAAe,UAAU,aAAa,WAAW,IAmCtC,0BAChB,oBAAoB,KAAK,cAAc,aAAa;cAclD;;;;IApGO,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA"}
|
package/dist/utils/Primitive.js
CHANGED
|
@@ -2,6 +2,7 @@ import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
|
|
|
2
2
|
import { cloneElement, forwardRef, isValidElement } from "@assistant-ui/tap/react-shim";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
import { Primitive as Primitive$1 } from "radix-ui/internal";
|
|
5
|
+
import { Slot } from "radix-ui";
|
|
5
6
|
//#region src/utils/Primitive.tsx
|
|
6
7
|
/**
|
|
7
8
|
* Thin wrapper around Radix `Primitive` that adds `render` prop support.
|
|
@@ -31,6 +32,25 @@ const NODES = [
|
|
|
31
32
|
"svg",
|
|
32
33
|
"ul"
|
|
33
34
|
];
|
|
35
|
+
/**
|
|
36
|
+
* Composes the children of a `render` element. Outer children win when supplied;
|
|
37
|
+
* the render element's own children are the fallback.
|
|
38
|
+
*/
|
|
39
|
+
function composeRenderElement(render, children) {
|
|
40
|
+
return cloneElement(render, void 0, children !== void 0 ? children : render.props.children);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Composes a `render` element at a call site that has already computed its props.
|
|
44
|
+
*
|
|
45
|
+
* `withRenderProp` wraps a component; this covers the case where the props are
|
|
46
|
+
* already in hand and the composition happens inline.
|
|
47
|
+
*/
|
|
48
|
+
function renderSlot(render, children, props) {
|
|
49
|
+
return /* @__PURE__ */ jsx(Slot.Root, {
|
|
50
|
+
...props,
|
|
51
|
+
children: composeRenderElement(render, children)
|
|
52
|
+
});
|
|
53
|
+
}
|
|
34
54
|
function withRenderProp(Component) {
|
|
35
55
|
const Wrapped = forwardRef((t0, ref) => {
|
|
36
56
|
const $ = c(17);
|
|
@@ -53,13 +73,12 @@ function withRenderProp(Component) {
|
|
|
53
73
|
}
|
|
54
74
|
const Comp = Component;
|
|
55
75
|
if (render && isValidElement(render)) {
|
|
56
|
-
const renderChildren = children !== void 0 ? children : render.props.children;
|
|
57
76
|
const t1 = rest;
|
|
58
77
|
let t2;
|
|
59
|
-
if ($[5] !==
|
|
60
|
-
t2 =
|
|
61
|
-
$[5] =
|
|
62
|
-
$[6] =
|
|
78
|
+
if ($[5] !== children || $[6] !== render) {
|
|
79
|
+
t2 = composeRenderElement(render, children);
|
|
80
|
+
$[5] = children;
|
|
81
|
+
$[6] = render;
|
|
63
82
|
$[7] = t2;
|
|
64
83
|
} else t2 = $[7];
|
|
65
84
|
let t3;
|
|
@@ -108,6 +127,6 @@ const Primitive = NODES.reduce((acc, node) => {
|
|
|
108
127
|
return acc;
|
|
109
128
|
}, {});
|
|
110
129
|
//#endregion
|
|
111
|
-
export { Primitive, withRenderProp };
|
|
130
|
+
export { Primitive, renderSlot, withRenderProp };
|
|
112
131
|
|
|
113
132
|
//# sourceMappingURL=Primitive.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Primitive.js","names":["ComponentPropsWithoutRef","ComponentRef","ElementType","ForwardRefExoticComponent","PropsWithoutRef","ReactElement","ReactNode","RefAttributes","cloneElement","forwardRef","isValidElement","Primitive","RadixPrimitive","NODES","const","PrimitiveNode","WithRenderPropProps","T","render","PrimitiveProps","E","WithRenderPropRuntimeProps","asChild","children","PrimitiveRef","
|
|
1
|
+
{"version":3,"file":"Primitive.js","names":["ComponentProps","ComponentPropsWithoutRef","ComponentRef","ElementType","ForwardRefExoticComponent","PropsWithoutRef","ReactElement","ReactNode","RefAttributes","cloneElement","forwardRef","isValidElement","Primitive","RadixPrimitive","Slot","NODES","const","PrimitiveNode","WithRenderPropProps","T","render","PrimitiveProps","E","WithRenderPropRuntimeProps","asChild","children","PrimitiveRef","composeRenderElement","undefined","props","renderSlot","Root","withRenderProp","Component","Wrapped","t0","ref","$","_c","rest","Comp","t1","t2","t3","componentName","displayName","name","createPrimitive","node","RadixComp","reduce","acc","ReturnType","K"],"sources":["../../src/utils/Primitive.tsx"],"sourcesContent":["import {\n type ComponentProps,\n type ComponentPropsWithoutRef,\n type ComponentRef,\n type ElementType,\n type ForwardRefExoticComponent,\n type PropsWithoutRef,\n type ReactElement,\n type ReactNode,\n type RefAttributes,\n cloneElement,\n forwardRef,\n isValidElement,\n} from \"react\";\nimport { Primitive as RadixPrimitive } from \"radix-ui/internal\";\nimport { Slot } from \"radix-ui\";\n\n/**\n * Thin wrapper around Radix `Primitive` that adds `render` prop support.\n *\n * When `render` is provided, it is converted to the equivalent `asChild` pattern:\n * render={<Comp props />} + children → asChild + <Comp props>{children}</Comp>\n *\n * All prop merging, ref composition, and event handler chaining remain handled\n * by Radix's battle-tested Slot implementation — we add zero custom logic for that.\n */\n\n// Match @radix-ui/react-primitive's full element set\nconst NODES = [\n \"a\",\n \"button\",\n \"div\",\n \"form\",\n \"h2\",\n \"h3\",\n \"img\",\n \"input\",\n \"label\",\n \"li\",\n \"nav\",\n \"ol\",\n \"p\",\n \"select\",\n \"span\",\n \"svg\",\n \"ul\",\n] as const;\ntype PrimitiveNode = (typeof NODES)[number];\n\ntype WithRenderPropProps<T extends ElementType> =\n ComponentPropsWithoutRef<T> & {\n render?: ReactElement | undefined;\n };\n\ntype PrimitiveProps<E extends PrimitiveNode> = WithRenderPropProps<\n (typeof RadixPrimitive)[E]\n>;\n\ntype WithRenderPropRuntimeProps<T extends ElementType> =\n WithRenderPropProps<T> & {\n asChild?: boolean | undefined;\n children?: ReactNode | undefined;\n };\n\ntype PrimitiveRef<E extends PrimitiveNode> = ComponentRef<\n (typeof RadixPrimitive)[E]\n>;\n\n/**\n * Composes the children of a `render` element. Outer children win when supplied;\n * the render element's own children are the fallback.\n */\nfunction composeRenderElement(\n render: ReactElement,\n children: ReactNode,\n): ReactElement {\n return cloneElement(\n render,\n undefined,\n children !== undefined\n ? children\n : (render.props as { children?: ReactNode }).children,\n );\n}\n\n/**\n * Composes a `render` element at a call site that has already computed its props.\n *\n * `withRenderProp` wraps a component; this covers the case where the props are\n * already in hand and the composition happens inline.\n */\nfunction renderSlot(\n render: ReactElement,\n children: ReactNode,\n props: ComponentProps<typeof Slot.Root>,\n): ReactElement {\n return (\n <Slot.Root {...props}>{composeRenderElement(render, children)}</Slot.Root>\n );\n}\n\nfunction withRenderProp<T extends ElementType>(Component: T) {\n const Wrapped = forwardRef<ComponentRef<T>, WithRenderPropRuntimeProps<T>>(\n (\n {\n render,\n asChild,\n children,\n ...rest\n }: PropsWithoutRef<WithRenderPropRuntimeProps<T>>,\n ref,\n ) => {\n const Comp = Component as any;\n\n if (render && isValidElement(render)) {\n return (\n <Comp {...(rest as any)} asChild ref={ref}>\n {composeRenderElement(render, children)}\n </Comp>\n );\n }\n\n return (\n <Comp {...(rest as any)} asChild={asChild} ref={ref}>\n {children}\n </Comp>\n );\n },\n );\n\n const componentName =\n typeof Component === \"string\"\n ? Component\n : (Component.displayName ?? Component.name ?? \"Component\");\n Wrapped.displayName = componentName;\n\n return Wrapped as ForwardRefExoticComponent<\n WithRenderPropProps<T> & RefAttributes<ComponentRef<T>>\n >;\n}\n\nfunction createPrimitive<E extends PrimitiveNode>(node: E) {\n const RadixComp = RadixPrimitive[node];\n const Component = withRenderProp(RadixComp);\n\n Component.displayName = `Primitive.${node}`;\n return Component as ForwardRefExoticComponent<\n PrimitiveProps<E> & RefAttributes<PrimitiveRef<E>>\n >;\n}\n\nconst Primitive = NODES.reduce(\n (acc, node) => {\n acc[node] = createPrimitive(node);\n return acc;\n },\n {} as {\n [K in PrimitiveNode]: ReturnType<typeof createPrimitive<K>>;\n },\n);\n\nexport { Primitive, renderSlot, withRenderProp };\nexport type { PrimitiveProps, WithRenderPropProps };\n"],"mappings":";;;;;;;;;;;;;;;AA4BA,MAAMe,QAAQ;CACZ;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AAAI;;;;;AA2BN,SAASY,qBACPP,QACAK,UACc;CACd,OAAOhB,aACLW,QACAQ,KAAAA,GACAH,aAAaG,KAAAA,IACTH,WACCL,OAAOS,MAAmCJ,QACjD;AACF;;;;;;;AAQA,SAASK,WACPV,QACAK,UACAI,OACc;CACd,OACE,oBAAC,KAAK,MAAN;EAAW,GAAIA;EAAQF,UAAAA,qBAAqBP,QAAQK,QAAQ;CAAa,CAAA;AAE7E;AAEA,SAASO,eAAsCC,WAAc;CAC3D,MAAMC,UAAUxB,YACdyB,IAAAC,QAAA;EAAA,MAAAC,IAAAC,EAAA,EAAA;EAAA,IAAAd;EAAA,IAAAC;EAAA,IAAAL;EAAA,IAAAmB;EAAA,IAAAF,EAAA,OAAAF,IAAA;GACE,CAAA,CAAAf,QAAAI,SAAAC,UAAA,GAAAc,QAAAJ;GAKiDE,EAAA,KAAAF;GAAAE,EAAA,KAAAb;GAAAa,EAAA,KAAAZ;GAAAY,EAAA,KAAAjB;GAAAiB,EAAA,KAAAE;EAAA,OAAA;GAAAf,UAAAa,EAAA;GAAAZ,WAAAY,EAAA;GAAAjB,SAAAiB,EAAA;GAAAE,OAAAF,EAAA;EAAA;EAGjD,MAAAG,OAAaP;EAEb,IAAIb,UAAUT,eAAeS,MAAM,GAAC;GAErB,MAAAqB,KAAAF;GAAW,IAAAG;GAAA,IAAAL,EAAA,OAAAZ,YAAAY,EAAA,OAAAjB,QAAA;IACnBsB,KAAAf,qBAAqBP,QAAQK,QAAQ;IAACY,EAAA,KAAAZ;IAAAY,EAAA,KAAAjB;IAAAiB,EAAA,KAAAK;GAAA,OAAAA,KAAAL,EAAA;GAAA,IAAAM;GAAA,IAAAN,EAAA,OAAAD,OAAAC,EAAA,OAAAI,MAAAJ,EAAA,QAAAK,IAAA;IADzCC,KAAA,oBAAC,MAAD;KAAK,GAAMF;KAAc,SAAA;KAAaL;KACnCM,UAAAA;IADE,CAAA;IAEEL,EAAA,KAAAD;IAAAC,EAAA,KAAAI;IAAAJ,EAAA,MAAAK;IAAAL,EAAA,MAAAM;GAAA,OAAAA,KAAAN,EAAA;GAAA,OAFPM;EAEO;EAKE,MAAAF,KAAAF;EAAW,IAAAG;EAAA,IAAAL,EAAA,QAAAb,WAAAa,EAAA,QAAAZ,YAAAY,EAAA,QAAAD,OAAAC,EAAA,QAAAI,IAAA;GAAtBC,KAAA,oBAAC,MAAD;IAAK,GAAMD;IAAuBjB;IAAcY;IAC7CX;GADE,CAAA;GAEEY,EAAA,MAAAb;GAAAa,EAAA,MAAAZ;GAAAY,EAAA,MAAAD;GAAAC,EAAA,MAAAI;GAAAJ,EAAA,MAAAK;EAAA,OAAAA,KAAAL,EAAA;EAAA,OAFPK;CAEO,CAGb;CAMAR,QAAQW,cAHN,OAAOZ,cAAc,WACjBA,YACCA,UAAUY,eAAeZ,UAAUa,QAAQ;CAGlD,OAAOZ;AAGT;AAEA,SAASa,gBAAyCC,MAAS;CACzD,MAAMC,YAAYpC,YAAemC;CACjC,MAAMf,YAAYD,eAAeiB,SAAS;CAE1ChB,UAAUY,cAAc,aAAaG;CACrC,OAAOf;AAGT;AAEA,MAAMrB,YAAYG,MAAMmC,QACrBC,KAAKH,SAAS;CACbG,IAAIH,QAAQD,gBAAgBC,IAAI;CAChC,OAAOG;AACT,GACA,CAAC,CAGH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getSelectionMessageId.d.ts","names":[],"sources":["../../src/utils/getSelectionMessageId.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"getSelectionMessageId.d.ts","names":[],"sources":["../../src/utils/getSelectionMessageId.ts"],"mappings":";cAwDa,wBAAyB,WAAW"}
|
|
@@ -25,6 +25,14 @@ const findQuoteMarker = (node, messageElement) => {
|
|
|
25
25
|
if (!messageElement.contains(marker)) return null;
|
|
26
26
|
return marker;
|
|
27
27
|
};
|
|
28
|
+
const intersectsExcluded = (scope, selection) => {
|
|
29
|
+
const ranges = Array.from({ length: selection.rangeCount }, (_, i) => selection.getRangeAt(i));
|
|
30
|
+
for (const marker of scope.querySelectorAll(QUOTE_SELECTABLE_SELECTOR)) {
|
|
31
|
+
if (!isExcluded(marker)) continue;
|
|
32
|
+
if (ranges.some((range) => range.intersectsNode(marker))) return true;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
};
|
|
28
36
|
const getSelectionMessageId = (selection) => {
|
|
29
37
|
const { anchorNode, focusNode } = selection;
|
|
30
38
|
if (!anchorNode || !focusNode) return null;
|
|
@@ -37,9 +45,15 @@ const getSelectionMessageId = (selection) => {
|
|
|
37
45
|
const focusMarker = findQuoteMarker(focusNode, anchorMessageElement);
|
|
38
46
|
if (anchorMarker && isExcluded(anchorMarker)) return null;
|
|
39
47
|
if (focusMarker && isExcluded(focusMarker)) return null;
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
if (hasQuoteSelectableRegion(anchorMessageElement)) {
|
|
49
|
+
if (!anchorMarker || anchorMarker !== focusMarker) return null;
|
|
50
|
+
}
|
|
51
|
+
const scope = anchorMarker ?? anchorMessageElement;
|
|
52
|
+
for (let i = 0; i < selection.rangeCount; i++) {
|
|
53
|
+
const { commonAncestorContainer } = selection.getRangeAt(i);
|
|
54
|
+
if (!scope.contains(commonAncestorContainer)) return null;
|
|
55
|
+
}
|
|
56
|
+
return intersectsExcluded(scope, selection) ? null : messageId;
|
|
43
57
|
};
|
|
44
58
|
//#endregion
|
|
45
59
|
export { getSelectionMessageId };
|