@assistant-ui/react 0.15.25 → 0.15.26
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/dist/hooks/useToolCallElapsed.d.ts.map +1 -1
- package/dist/hooks/useToolCallElapsed.js +3 -2
- package/dist/hooks/useToolCallElapsed.js.map +1 -1
- package/dist/index.d.ts +30 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppRenderer.js +3 -2
- package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/mcp-apps/app-frame.d.ts.map +1 -1
- package/dist/mcp-apps/app-frame.js +56 -46
- package/dist/mcp-apps/app-frame.js.map +1 -1
- package/dist/mcp-apps/csp.d.ts +4 -0
- package/dist/mcp-apps/csp.d.ts.map +1 -0
- package/dist/mcp-apps/csp.js +93 -0
- package/dist/mcp-apps/csp.js.map +1 -0
- package/dist/mcp-apps/types.d.ts +1 -0
- package/dist/mcp-apps/types.d.ts.map +1 -1
- package/dist/mcp-apps/types.js.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalRoot.d.ts +1 -0
- package/dist/primitives/assistantModal/AssistantModalRoot.d.ts.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalRoot.js.map +1 -1
- package/dist/primitives/attachment.d.ts +3 -1
- package/dist/primitives/attachment.d.ts.map +1 -1
- package/dist/primitives/attachment.js.map +1 -1
- package/dist/primitives/composer/ComposerCancel.d.ts +5 -1
- package/dist/primitives/composer/ComposerCancel.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerCancel.js +14 -1
- package/dist/primitives/composer/ComposerCancel.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.d.ts +14 -0
- package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +4 -3
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/ComposerRoot.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerRoot.js +24 -19
- package/dist/primitives/composer/ComposerRoot.js.map +1 -1
- package/dist/primitives/composer/composerInputSelector.d.ts +3 -0
- package/dist/primitives/composer/composerInputSelector.d.ts.map +1 -0
- package/dist/primitives/composer/composerInputSelector.js +7 -0
- package/dist/primitives/composer/composerInputSelector.js.map +1 -0
- package/dist/primitives/composer/useComposerCancelWithFocus.d.ts +2 -0
- package/dist/primitives/composer/useComposerCancelWithFocus.d.ts.map +1 -0
- package/dist/primitives/composer/useComposerCancelWithFocus.js +42 -0
- package/dist/primitives/composer/useComposerCancelWithFocus.js.map +1 -0
- package/dist/primitives/composer.d.ts +35 -14
- package/dist/primitives/composer.d.ts.map +1 -1
- package/dist/primitives/composer.js.map +1 -1
- package/dist/primitives/message/MessagePartsGrouped.d.ts +8 -1
- package/dist/primitives/message/MessagePartsGrouped.d.ts.map +1 -1
- package/dist/primitives/message/MessagePartsGrouped.js +58 -278
- package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.d.ts.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js +5 -2
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js.map +1 -1
- package/dist/primitives/thread/ThreadViewport.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadViewport.js +4 -3
- package/dist/primitives/thread/ThreadViewport.js.map +1 -1
- package/dist/primitives/thread/topAnchor/useTopAnchorReserve.js +2 -2
- package/dist/primitives/thread/topAnchor/useTopAnchorReserve.js.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js +90 -21
- package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
- package/dist/sandbox-host/SandboxHost.d.ts.map +1 -1
- package/dist/sandbox-host/SandboxHost.js +3 -2
- package/dist/sandbox-host/SandboxHost.js.map +1 -1
- package/dist/unstable/useComposerInput.d.ts +7 -4
- package/dist/unstable/useComposerInput.d.ts.map +1 -1
- package/dist/unstable/useComposerInput.js +4 -4
- package/dist/unstable/useComposerInput.js.map +1 -1
- package/dist/unstable/useComposerInputHistory.d.ts +3 -2
- package/dist/unstable/useComposerInputHistory.d.ts.map +1 -1
- package/dist/unstable/useComposerInputHistory.js +2 -2
- package/dist/unstable/useComposerInputHistory.js.map +1 -1
- package/dist/unstable/useLiveCompletionAdapter.d.ts +3 -2
- package/dist/unstable/useLiveCompletionAdapter.d.ts.map +1 -1
- package/dist/unstable/useLiveCompletionAdapter.js +8 -7
- package/dist/unstable/useLiveCompletionAdapter.js.map +1 -1
- package/dist/unstable/useMentionAdapter.d.ts +12 -3
- package/dist/unstable/useMentionAdapter.d.ts.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 -2
- package/dist/unstable/useMessageStallDetection.d.ts.map +1 -1
- package/dist/unstable/useMessageStallDetection.js +2 -2
- package/dist/unstable/useMessageStallDetection.js.map +1 -1
- package/dist/unstable/useSlashCommandAdapter.d.ts +5 -2
- package/dist/unstable/useSlashCommandAdapter.d.ts.map +1 -1
- package/dist/unstable/useSlashCommandAdapter.js +5 -4
- package/dist/unstable/useSlashCommandAdapter.js.map +1 -1
- package/dist/unstable/webmcp/useWebMcpProvider.d.ts +4 -0
- package/dist/unstable/webmcp/useWebMcpProvider.d.ts.map +1 -1
- package/dist/unstable/webmcp/useWebMcpProvider.js +2 -0
- package/dist/unstable/webmcp/useWebMcpProvider.js.map +1 -1
- package/dist/utils/getSelectionMessageId.d.ts.map +1 -1
- package/dist/utils/getSelectionMessageId.js +31 -8
- package/dist/utils/getSelectionMessageId.js.map +1 -1
- package/dist/utils/hooks/useOnResizeContent.d.ts.map +1 -1
- package/dist/utils/hooks/useOnResizeContent.js +2 -22
- package/dist/utils/hooks/useOnResizeContent.js.map +1 -1
- package/dist/utils/useIsomorphicLayoutEffect.d.ts +3 -0
- package/dist/utils/useIsomorphicLayoutEffect.d.ts.map +1 -0
- package/dist/utils/useIsomorphicLayoutEffect.js +6 -0
- package/dist/utils/useIsomorphicLayoutEffect.js.map +1 -0
- package/package.json +10 -10
- package/src/hooks/useToolCallElapsed.test.tsx +4 -2
- package/src/hooks/useToolCallElapsed.ts +3 -2
- package/src/index.ts +19 -2
- package/src/mcp-apps/McpAppRenderer.tsx +3 -9
- package/src/mcp-apps/app-frame.test.tsx +38 -0
- package/src/mcp-apps/app-frame.tsx +5 -8
- package/src/mcp-apps/csp.test.ts +112 -0
- package/src/mcp-apps/csp.ts +124 -0
- package/src/mcp-apps/types.ts +1 -0
- package/src/model-context/frame/AssistantFrame.test.ts +45 -0
- package/src/model-context/frame/SPEC_AssistantFrame.md +1 -0
- package/src/primitives/assistantModal/AssistantModalRoot.tsx +1 -0
- package/src/primitives/attachment.ts +4 -1
- package/src/primitives/composer/ComposerCancel.focus.test.tsx +307 -0
- package/src/primitives/composer/ComposerCancel.ts +8 -1
- package/src/primitives/composer/ComposerInput.tsx +19 -1
- package/src/primitives/composer/ComposerRoot.tsx +6 -5
- package/src/primitives/composer/composerInputSelector.ts +4 -0
- package/src/primitives/composer/useComposerCancelWithFocus.ts +57 -0
- package/src/primitives/composer.ts +56 -21
- package/src/primitives/message/MessagePartsGrouped.test.tsx +106 -2
- package/src/primitives/message/MessagePartsGrouped.tsx +36 -134
- package/src/primitives/selectionToolbar/SelectionToolbarRoot.test.tsx +20 -12
- package/src/primitives/selectionToolbar/SelectionToolbarRoot.tsx +6 -2
- package/src/primitives/thread/ThreadViewport.tsx +3 -3
- package/src/primitives/thread/topAnchor/useTopAnchorReserve.ts +2 -2
- package/src/primitives/thread/useThreadViewportAutoScroll.test.tsx +666 -63
- package/src/primitives/thread/useThreadViewportAutoScroll.ts +201 -73
- package/src/sandbox-host/SandboxHost.tsx +3 -8
- package/src/unstable/useComposerInput.ts +7 -4
- package/src/unstable/useComposerInputHistory.ts +3 -2
- package/src/unstable/useLiveCompletionAdapter.ts +9 -14
- package/src/unstable/useMentionAdapter.ts +12 -3
- package/src/unstable/useMessageStallDetection.ts +4 -2
- package/src/unstable/useSlashCommandAdapter.ts +8 -4
- package/src/unstable/webmcp/useWebMcpProvider.ts +4 -0
- package/src/utils/getSelectionMessageId.test.ts +231 -0
- package/src/utils/getSelectionMessageId.ts +45 -7
- package/src/utils/hooks/useOnResizeContent.ts +2 -29
- package/src/utils/useIsomorphicLayoutEffect.test.tsx +21 -0
- package/src/utils/useIsomorphicLayoutEffect.ts +9 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** @deprecated Experimental since 2026-06-13. Not scheduled for removal; the API may change in any release. */
|
|
1
2
|
export type Unstable_MessageStallDetectionOptions = {
|
|
2
3
|
/**
|
|
3
4
|
* Milliseconds of unchanged message content before the message counts as
|
|
@@ -6,6 +7,7 @@ export type Unstable_MessageStallDetectionOptions = {
|
|
|
6
7
|
*/
|
|
7
8
|
thresholdMs?: number | undefined;
|
|
8
9
|
};
|
|
10
|
+
/** @deprecated Experimental since 2026-06-13. Not scheduled for removal; the API may change in any release. */
|
|
9
11
|
export type Unstable_MessageStallDetection = {
|
|
10
12
|
/** True while the message is running and its content has not changed for at least `thresholdMs`. */
|
|
11
13
|
stalled: boolean;
|
|
@@ -13,8 +15,6 @@ export type Unstable_MessageStallDetection = {
|
|
|
13
15
|
stalledForMs: number;
|
|
14
16
|
};
|
|
15
17
|
/**
|
|
16
|
-
* @deprecated Under active development and might change without notice.
|
|
17
|
-
*
|
|
18
18
|
* Detects mid-run output stalls on the current message: while the message is
|
|
19
19
|
* running, watches its text, reasoning, and tool-argument values plus tool-result
|
|
20
20
|
* availability and reports a stall once they stop changing for `thresholdMs`.
|
|
@@ -22,6 +22,8 @@ export type Unstable_MessageStallDetection = {
|
|
|
22
22
|
* provider stalls, after the first tokens have already streamed.
|
|
23
23
|
*
|
|
24
24
|
* Must be used inside a message scope.
|
|
25
|
+
*
|
|
26
|
+
* @deprecated Experimental since 2026-06-13. Not scheduled for removal; the API may change in any release.
|
|
25
27
|
*/
|
|
26
28
|
export declare function unstable_useMessageStallDetection(options?: Unstable_MessageStallDetectionOptions): Unstable_MessageStallDetection;
|
|
27
29
|
//# sourceMappingURL=useMessageStallDetection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMessageStallDetection.d.ts","sourceRoot":"","sources":["../../src/unstable/useMessageStallDetection.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,qCAAqC,GAAG;IAClD;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,8BAA8B,GAAG;IAC3C,oGAAoG;IACpG,OAAO,EAAE,OAAO,CAAC;IACjB,kFAAkF;IAClF,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,iCAAiC,CAC/C,OAAO,CAAC,EAAE,qCAAqC,GAC9C,8BAA8B,CAkEhC"}
|
|
1
|
+
{"version":3,"file":"useMessageStallDetection.d.ts","sourceRoot":"","sources":["../../src/unstable/useMessageStallDetection.ts"],"names":[],"mappings":"AAMA,+GAA+G;AAC/G,MAAM,MAAM,qCAAqC,GAAG;IAClD;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,CAAC;AAEF,+GAA+G;AAC/G,MAAM,MAAM,8BAA8B,GAAG;IAC3C,oGAAoG;IACpG,OAAO,EAAE,OAAO,CAAC;IACjB,kFAAkF;IAClF,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,iCAAiC,CAC/C,OAAO,CAAC,EAAE,qCAAqC,GAC9C,8BAA8B,CAkEhC"}
|
|
@@ -4,8 +4,6 @@ import { useAuiState } from "@assistant-ui/store";
|
|
|
4
4
|
import { useShallowSelector } from "@assistant-ui/store/internal";
|
|
5
5
|
//#region src/unstable/useMessageStallDetection.ts
|
|
6
6
|
/**
|
|
7
|
-
* @deprecated Under active development and might change without notice.
|
|
8
|
-
*
|
|
9
7
|
* Detects mid-run output stalls on the current message: while the message is
|
|
10
8
|
* running, watches its text, reasoning, and tool-argument values plus tool-result
|
|
11
9
|
* availability and reports a stall once they stop changing for `thresholdMs`.
|
|
@@ -13,6 +11,8 @@ import { useShallowSelector } from "@assistant-ui/store/internal";
|
|
|
13
11
|
* provider stalls, after the first tokens have already streamed.
|
|
14
12
|
*
|
|
15
13
|
* Must be used inside a message scope.
|
|
14
|
+
*
|
|
15
|
+
* @deprecated Experimental since 2026-06-13. Not scheduled for removal; the API may change in any release.
|
|
16
16
|
*/
|
|
17
17
|
function unstable_useMessageStallDetection(options) {
|
|
18
18
|
const thresholdMs = options?.thresholdMs ?? 2e3;
|
|
@@ -1 +1 @@
|
|
|
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","setStalled","now","setNow","current","Date","stall","sinceActivity","id","setTimeout","clearTimeout","setInterval","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 *
|
|
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","setStalled","now","setNow","current","Date","stall","sinceActivity","id","setTimeout","clearTimeout","setInterval","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\n/** @deprecated Experimental since 2026-06-13. Not scheduled for removal; the API may change in any release. */\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\n/** @deprecated Experimental since 2026-06-13. Not scheduled for removal; the API may change in any release. */\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 * 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 *\n * @deprecated Experimental since 2026-06-13. Not scheduled for removal; the API may change in any release.\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(0);\n const [stalled, setStalled] = useState(false);\n const [now, setNow] = 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 stall = () => {\n setNow(Date.now());\n setStalled(true);\n };\n const sinceActivity = Date.now() - lastActivityRef.current;\n if (sinceActivity >= thresholdMs) {\n stall();\n return undefined;\n }\n\n setStalled(false);\n const id = setTimeout(stall, thresholdMs - sinceActivity);\n return () => clearTimeout(id);\n }, [running, activity, thresholdMs]);\n\n useEffect(() => {\n if (!stalled) return undefined;\n const id = setInterval(() => setNow(Date.now()), 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, now - lastActivityRef.current),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAmCA,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,OAAO,CAAC;CAChC,MAAM,CAACO,SAASoB,cAAc1B,SAAS,KAAK;CAC5C,MAAM,CAAC2B,KAAKC,UAAU5B,SAAS,CAAC;CAEhCF,gBAAgB;EACd,IAAI,CAACc,SAAS,OAAOY,KAAAA;EACrBC,gBAAgBI,UAAUC,KAAKH,IAAI;CAErC,GAAG,CAACf,SAASF,QAAQ,CAAC;CAEtBZ,gBAAgB;EACd,IAAI,CAACc,SAAS;GACZc,WAAW,KAAK;GAChB;EACF;EAEA,MAAMK,cAAc;GAClBH,OAAOE,KAAKH,IAAI,CAAC;GACjBD,WAAW,IAAI;EACjB;EACA,MAAMM,gBAAgBF,KAAKH,IAAI,IAAIF,gBAAgBI;EACnD,IAAIG,iBAAiB5B,aAAa;GAChC2B,MAAM;GACN;EACF;EAEAL,WAAW,KAAK;EAChB,MAAMO,KAAKC,WAAWH,OAAO3B,cAAc4B,aAAa;EACxD,aAAaG,aAAaF,EAAE;CAC9B,GAAG;EAACrB;EAASF;EAAUN;CAAW,CAAC;CAEnCN,gBAAgB;EACd,IAAI,CAACQ,SAAS,OAAOkB,KAAAA;EACrB,MAAMS,KAAKG,kBAAkBR,OAAOE,KAAKH,IAAI,CAAC,GAAG,GAAI;EACrD,aAAaU,cAAcJ,EAAE;CAC/B,GAAG,CAAC3B,OAAO,CAAC;CAEZ,IAAI,CAACA,SAAS,OAAO;EAAEA,SAAS;EAAOC,cAAc;CAAE;CACvD,OAAO;EACLD,SAAS;EACTC,cAAc+B,KAAKC,IAAI,GAAGZ,MAAMF,gBAAgBI,OAAO;CACzD;AACF"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Unstable_TriggerAdapter, Unstable_TriggerItem } from "@assistant-ui/core";
|
|
2
2
|
import type { Unstable_IconComponent } from "./useMentionAdapter.js";
|
|
3
|
+
/** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */
|
|
3
4
|
export type Unstable_SlashCommand = {
|
|
4
5
|
readonly id: string;
|
|
5
6
|
readonly label?: string | undefined;
|
|
@@ -7,6 +8,7 @@ export type Unstable_SlashCommand = {
|
|
|
7
8
|
readonly icon?: string | undefined;
|
|
8
9
|
readonly execute: () => void;
|
|
9
10
|
};
|
|
11
|
+
/** @deprecated Experimental since 2026-04-06. Not scheduled for removal; the API may change in any release. */
|
|
10
12
|
export type Unstable_UseSlashCommandAdapterOptions = {
|
|
11
13
|
readonly commands: readonly Unstable_SlashCommand[];
|
|
12
14
|
/** Strip the trigger text from the composer after executing. @default false */
|
|
@@ -16,18 +18,19 @@ export type Unstable_UseSlashCommandAdapterOptions = {
|
|
|
16
18
|
/** Fallback icon when no entry in `iconMap` matches. */
|
|
17
19
|
readonly fallbackIcon?: Unstable_IconComponent;
|
|
18
20
|
};
|
|
21
|
+
/** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */
|
|
19
22
|
export type Unstable_SlashCommandAction = {
|
|
20
23
|
readonly onExecute: (item: Unstable_TriggerItem) => void;
|
|
21
24
|
readonly removeOnExecute?: boolean | undefined;
|
|
22
25
|
};
|
|
23
26
|
/**
|
|
24
|
-
* @deprecated Under active development and may change without notice.
|
|
25
|
-
*
|
|
26
27
|
* Bundles slash command definitions (with inline `execute` callbacks) into
|
|
27
28
|
* `{adapter, action}` that plug directly into `ComposerTriggerPopover`.
|
|
28
29
|
* `execute` stays in the hook closure and is never attached to the returned
|
|
29
30
|
* `TriggerItem`, keeping items serializable.
|
|
30
31
|
*
|
|
32
|
+
* @deprecated Experimental since 2026-04-06. Not scheduled for removal; the API may change in any release.
|
|
33
|
+
*
|
|
31
34
|
* @example
|
|
32
35
|
* ```tsx
|
|
33
36
|
* const slash = unstable_useSlashCommandAdapter({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSlashCommandAdapter.d.ts","sourceRoot":"","sources":["../../src/unstable/useSlashCommandAdapter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useSlashCommandAdapter.d.ts","sourceRoot":"","sources":["../../src/unstable/useSlashCommandAdapter.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,uBAAuB,EACvB,oBAAoB,EACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,sBAAsB,EAAE,+BAA4B;AAGlE,+GAA+G;AAC/G,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC9B,CAAC;AAEF,+GAA+G;AAC/G,MAAM,MAAM,sCAAsC,GAAG;IACnD,QAAQ,CAAC,QAAQ,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACpD,+EAA+E;IAC/E,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/C,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;IAC1D,wDAAwD;IACxD,QAAQ,CAAC,YAAY,CAAC,EAAE,sBAAsB,CAAC;CAChD,CAAC;AAEF,+GAA+G;AAC/G,MAAM,MAAM,2BAA2B,GAAG;IACxC,QAAQ,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACzD,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAChD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,sCAAsC,GAC9C;IACD,OAAO,EAAE,uBAAuB,CAAC;IACjC,MAAM,EAAE,2BAA2B,CAAC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;IACjD,YAAY,CAAC,EAAE,sBAAsB,CAAC;CACvC,CAiDA"}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { useIsomorphicLayoutEffect } from "../utils/useIsomorphicLayoutEffect.js";
|
|
2
3
|
import { matchesTriggerItemQuery } from "../primitives/composer/trigger/matchesTriggerItemQuery.js";
|
|
3
|
-
import {
|
|
4
|
+
import { useMemo, useRef } from "@assistant-ui/tap/react-shim";
|
|
4
5
|
//#region src/unstable/useSlashCommandAdapter.ts
|
|
5
6
|
/**
|
|
6
|
-
* @deprecated Under active development and may change without notice.
|
|
7
|
-
*
|
|
8
7
|
* Bundles slash command definitions (with inline `execute` callbacks) into
|
|
9
8
|
* `{adapter, action}` that plug directly into `ComposerTriggerPopover`.
|
|
10
9
|
* `execute` stays in the hook closure and is never attached to the returned
|
|
11
10
|
* `TriggerItem`, keeping items serializable.
|
|
12
11
|
*
|
|
12
|
+
* @deprecated Experimental since 2026-04-06. Not scheduled for removal; the API may change in any release.
|
|
13
|
+
*
|
|
13
14
|
* @example
|
|
14
15
|
* ```tsx
|
|
15
16
|
* const slash = unstable_useSlashCommandAdapter({
|
|
@@ -28,7 +29,7 @@ function unstable_useSlashCommandAdapter(options) {
|
|
|
28
29
|
const committedItemsRef = useRef(void 0);
|
|
29
30
|
const nextItems = commands.map(toItem);
|
|
30
31
|
const items = areTriggerItemsEqual(committedItemsRef.current, nextItems) ? committedItemsRef.current : nextItems;
|
|
31
|
-
|
|
32
|
+
useIsomorphicLayoutEffect(() => {
|
|
32
33
|
commandsRef.current = commands;
|
|
33
34
|
committedItemsRef.current = items;
|
|
34
35
|
}, [commands, items]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSlashCommandAdapter.js","names":["
|
|
1
|
+
{"version":3,"file":"useSlashCommandAdapter.js","names":["useMemo","useRef","useIsomorphicLayoutEffect","Unstable_TriggerAdapter","Unstable_TriggerItem","Unstable_IconComponent","matchesTriggerItemQuery","Unstable_SlashCommand","id","label","description","icon","execute","Unstable_UseSlashCommandAdapterOptions","commands","removeOnExecute","iconMap","Record","fallbackIcon","Unstable_SlashCommandAction","onExecute","item","unstable_useSlashCommandAdapter","options","adapter","action","commandsRef","committedItemsRef","undefined","nextItems","map","toItem","items","areTriggerItemsEqual","current","categories","categoryItems","search","query","lower","toLowerCase","filter","find","c","cmd","type","metadata","previous","next","length","every","index","nextItem"],"sources":["../../src/unstable/useSlashCommandAdapter.ts"],"sourcesContent":["\"use client\";\n\nimport { useMemo, useRef } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"../utils/useIsomorphicLayoutEffect\";\nimport type {\n Unstable_TriggerAdapter,\n Unstable_TriggerItem,\n} from \"@assistant-ui/core\";\nimport type { Unstable_IconComponent } from \"./useMentionAdapter\";\nimport { matchesTriggerItemQuery } from \"../primitives/composer/trigger/matchesTriggerItemQuery\";\n\n/** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */\nexport type Unstable_SlashCommand = {\n readonly id: string;\n readonly label?: string | undefined;\n readonly description?: string | undefined;\n readonly icon?: string | undefined;\n readonly execute: () => void;\n};\n\n/** @deprecated Experimental since 2026-04-06. Not scheduled for removal; the API may change in any release. */\nexport type Unstable_UseSlashCommandAdapterOptions = {\n readonly commands: readonly Unstable_SlashCommand[];\n /** Strip the trigger text from the composer after executing. @default false */\n readonly removeOnExecute?: boolean | undefined;\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\n/** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */\nexport type Unstable_SlashCommandAction = {\n readonly onExecute: (item: Unstable_TriggerItem) => void;\n readonly removeOnExecute?: boolean | undefined;\n};\n\n/**\n * Bundles slash command definitions (with inline `execute` callbacks) into\n * `{adapter, action}` that plug directly into `ComposerTriggerPopover`.\n * `execute` stays in the hook closure and is never attached to the returned\n * `TriggerItem`, keeping items serializable.\n *\n * @deprecated Experimental since 2026-04-06. Not scheduled for removal; the API may change in any release.\n *\n * @example\n * ```tsx\n * const slash = unstable_useSlashCommandAdapter({\n * commands: [\n * { id: \"summarize\", execute: () => runSummarize(), icon: \"FileText\" },\n * { id: \"translate\", execute: () => runTranslate(), icon: \"Languages\" },\n * ],\n * });\n *\n * <ComposerTriggerPopover char=\"/\" {...slash} />\n * ```\n */\nexport function unstable_useSlashCommandAdapter(\n options: Unstable_UseSlashCommandAdapterOptions,\n): {\n adapter: Unstable_TriggerAdapter;\n action: Unstable_SlashCommandAction;\n iconMap?: Record<string, Unstable_IconComponent>;\n fallbackIcon?: Unstable_IconComponent;\n} {\n const { commands, removeOnExecute } = options;\n\n const commandsRef = useRef(commands);\n const committedItemsRef = useRef<readonly Unstable_TriggerItem[]>(undefined);\n const nextItems = commands.map(toItem);\n // A referential cache, never state: `items` is always structurally equal to\n // `nextItems`, so the render-time read cannot change what the adapter shows.\n const items = areTriggerItemsEqual(committedItemsRef.current, nextItems)\n ? committedItemsRef.current\n : nextItems;\n\n // The adapter's callbacks must only observe commands from committed renders.\n useIsomorphicLayoutEffect(() => {\n commandsRef.current = commands;\n committedItemsRef.current = items;\n }, [commands, items]);\n\n const adapter = useMemo<Unstable_TriggerAdapter>(\n () => ({\n categories: () => [],\n categoryItems: () => [],\n search: (query: string) => {\n const lower = query.toLowerCase();\n return items.filter((item) => matchesTriggerItemQuery(item, lower));\n },\n }),\n [items],\n );\n\n const action = useMemo<Unstable_SlashCommandAction>(\n () => ({\n onExecute: (item) => {\n commandsRef.current.find((c) => c.id === item.id)?.execute();\n },\n ...(removeOnExecute !== undefined ? { removeOnExecute } : {}),\n }),\n [removeOnExecute],\n );\n\n return useMemo(\n () => ({\n adapter,\n action,\n ...(options.iconMap ? { iconMap: options.iconMap } : {}),\n ...(options.fallbackIcon ? { fallbackIcon: options.fallbackIcon } : {}),\n }),\n [adapter, action, options.iconMap, options.fallbackIcon],\n );\n}\n\nfunction toItem(cmd: Unstable_SlashCommand): Unstable_TriggerItem {\n return {\n id: cmd.id,\n type: \"command\",\n label: cmd.label ?? `/${cmd.id}`,\n ...(cmd.description !== undefined ? { description: cmd.description } : {}),\n ...(cmd.icon !== undefined ? { metadata: { icon: cmd.icon } } : {}),\n };\n}\n\nfunction areTriggerItemsEqual(\n previous: readonly Unstable_TriggerItem[] | undefined,\n next: readonly Unstable_TriggerItem[],\n): previous is readonly Unstable_TriggerItem[] {\n if (!previous || previous.length !== next.length) return false;\n\n return previous.every((item, index) => {\n const nextItem = next[index];\n return (\n nextItem !== undefined &&\n item.id === nextItem.id &&\n item.label === nextItem.label &&\n item.description === nextItem.description &&\n item.metadata?.icon === nextItem.metadata?.icon\n );\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,SAAgBsB,gCACdC,SAMA;CACA,MAAM,EAAET,UAAUC,oBAAoBQ;CAEtC,MAAMG,cAAczB,OAAOa,QAAQ;CACnC,MAAMa,oBAAoB1B,OAAwC2B,KAAAA,CAAS;CAC3E,MAAMC,YAAYf,SAASgB,IAAIC,MAAM;CAGrC,MAAMC,QAAQC,qBAAqBN,kBAAkBO,SAASL,SAAS,IACnEF,kBAAkBO,UAClBL;CAGJ3B,gCAAgC;EAC9BwB,YAAYQ,UAAUpB;EACtBa,kBAAkBO,UAAUF;CAC9B,GAAG,CAAClB,UAAUkB,KAAK,CAAC;CAEpB,MAAMR,UAAUxB,eACP;EACLmC,kBAAkB,CAAA;EAClBC,qBAAqB,CAAA;EACrBC,SAASC,UAAkB;GACzB,MAAMC,QAAQD,MAAME,YAAY;GAChC,OAAOR,MAAMS,QAAQpB,SAASf,wBAAwBe,MAAMkB,KAAK,CAAC;EACpE;CACF,IACA,CAACP,KAAK,CACR;CAEA,MAAMP,SAASzB,eACN;EACLoB,YAAYC,SAAS;GACnBK,YAAYQ,QAAQQ,MAAMC,MAAMA,EAAEnC,OAAOa,KAAKb,EAAE,CAAC,EAAEI,QAAQ;EAC7D;EACA,GAAIG,oBAAoBa,KAAAA,IAAY,EAAEb,gBAAgB,IAAI,CAAC;CAC7D,IACA,CAACA,eAAe,CAClB;CAEA,OAAOf,eACE;EACLwB;EACAC;EACA,GAAIF,QAAQP,UAAU,EAAEA,SAASO,QAAQP,QAAQ,IAAI,CAAC;EACtD,GAAIO,QAAQL,eAAe,EAAEA,cAAcK,QAAQL,aAAa,IAAI,CAAC;CACvE,IACA;EAACM;EAASC;EAAQF,QAAQP;EAASO,QAAQL;CAAY,CACzD;AACF;AAEA,SAASa,OAAOa,KAAkD;CAChE,OAAO;EACLpC,IAAIoC,IAAIpC;EACRqC,MAAM;EACNpC,OAAOmC,IAAInC,SAAS,IAAImC,IAAIpC;EAC5B,GAAIoC,IAAIlC,gBAAgBkB,KAAAA,IAAY,EAAElB,aAAakC,IAAIlC,YAAY,IAAI,CAAC;EACxE,GAAIkC,IAAIjC,SAASiB,KAAAA,IAAY,EAAEkB,UAAU,EAAEnC,MAAMiC,IAAIjC,KAAK,EAAE,IAAI,CAAC;CACnE;AACF;AAEA,SAASsB,qBACPc,UACAC,MAC6C;CAC7C,IAAI,CAACD,YAAYA,SAASE,WAAWD,KAAKC,QAAQ,OAAO;CAEzD,OAAOF,SAASG,OAAO7B,MAAM8B,UAAU;EACrC,MAAMC,WAAWJ,KAAKG;EACtB,OACEC,aAAaxB,KAAAA,KACbP,KAAKb,OAAO4C,SAAS5C,MACrBa,KAAKZ,UAAU2C,SAAS3C,SACxBY,KAAKX,gBAAgB0C,SAAS1C,eAC9BW,KAAKyB,UAAUnC,SAASyC,SAASN,UAAUnC;CAE/C,CAAC;AACH"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { Tool } from "assistant-stream";
|
|
2
|
+
/** @deprecated Experimental since 2026-08-29. Not scheduled for removal; the API may change in any release. */
|
|
2
3
|
export type Unstable_WebMcpProviderOptions = {
|
|
3
4
|
filter?: (name: string, tool: Tool<any, any>) => boolean;
|
|
4
5
|
};
|
|
6
|
+
/** @deprecated Experimental since 2026-08-29. Not scheduled for removal; the API may change in any release. */
|
|
5
7
|
export type Unstable_WebMcpProviderResult = {
|
|
6
8
|
status: "unsupported" | "active";
|
|
7
9
|
registeredToolNames: readonly string[];
|
|
@@ -13,6 +15,8 @@ export type Unstable_WebMcpProviderResult = {
|
|
|
13
15
|
* Returns `status: "unsupported"` when the page exposes no
|
|
14
16
|
* `document.modelContext` (or `navigator.modelContext`), and the sorted names
|
|
15
17
|
* of the tools currently registered with the host.
|
|
18
|
+
*
|
|
19
|
+
* @deprecated Experimental since 2026-08-29. Not scheduled for removal; the API may change in any release.
|
|
16
20
|
*/
|
|
17
21
|
export declare const unstable_useWebMcpProvider: (options?: Unstable_WebMcpProviderOptions) => Unstable_WebMcpProviderResult;
|
|
18
22
|
//# sourceMappingURL=useWebMcpProvider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWebMcpProvider.d.ts","sourceRoot":"","sources":["../../../src/unstable/webmcp/useWebMcpProvider.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAU7C,MAAM,MAAM,8BAA8B,GAAG;IAC3C,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC;CAC1D,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,MAAM,EAAE,aAAa,GAAG,QAAQ,CAAC;IACjC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC,CAAC;AA8FF
|
|
1
|
+
{"version":3,"file":"useWebMcpProvider.d.ts","sourceRoot":"","sources":["../../../src/unstable/webmcp/useWebMcpProvider.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAU7C,+GAA+G;AAC/G,MAAM,MAAM,8BAA8B,GAAG;IAC3C,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC;CAC1D,CAAC;AAEF,+GAA+G;AAC/G,MAAM,MAAM,6BAA6B,GAAG;IAC1C,MAAM,EAAE,aAAa,GAAG,QAAQ,CAAC;IACjC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC,CAAC;AA8FF;;;;;;;;;GASG;AACH,eAAO,MAAM,0BAA0B,GACrC,UAAS,8BAAmC,KAC3C,6BAyCF,CAAC"}
|
|
@@ -80,6 +80,8 @@ const useWebMcpRegistry = (t0) => {
|
|
|
80
80
|
* Returns `status: "unsupported"` when the page exposes no
|
|
81
81
|
* `document.modelContext` (or `navigator.modelContext`), and the sorted names
|
|
82
82
|
* of the tools currently registered with the host.
|
|
83
|
+
*
|
|
84
|
+
* @deprecated Experimental since 2026-08-29. Not scheduled for removal; the API may change in any release.
|
|
83
85
|
*/
|
|
84
86
|
const unstable_useWebMcpProvider = (options = {}) => {
|
|
85
87
|
const aui = useAui();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWebMcpProvider.js","names":["c","_c","useCallback","useEffect","useInsertionEffect","useRef","useState","useSyncExternalStore","useResources","useTapRoot","withKey","useAui","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","getCurrentTool","elements","entries","push","t1","error","has","add","console","warn","unstable_useWebMcpProvider","options","available","currentToolsRef","current","root","WebMcpProviderRoot","getValue","published","setPublished","_c"],"sources":["../../../src/unstable/webmcp/useWebMcpProvider.ts"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useInsertionEffect,\n useRef,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport { useResources, useTapRoot, withKey } from \"@assistant-ui/tap\";\nimport { useAui } 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 host,\n filter,\n tools,\n getCurrentTool,\n}: {\n host: WebMcpHost;\n filter: (name: string, tool: Tool<any, any>) => boolean;\n tools: Record<string, Tool<any, any>>;\n getCurrentTool: (name: string) => Tool<any, any> | undefined;\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({\n host,\n name,\n signature,\n tool,\n getCurrentTool,\n }),\n [host, name, signature, tool, getCurrentTool],\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 const tools = useModelContextSnapshot(\n aui,\n host.available,\n modelContextToolSource,\n );\n const currentToolsRef = useRef(tools);\n useInsertionEffect(() => {\n currentToolsRef.current = tools;\n }, [tools]);\n const getCurrentTool = useCallback(\n (name: string) => currentToolsRef.current[name],\n [],\n );\n\n const root = useTapRoot(function WebMcpProviderRoot() {\n return useWebMcpRegistry({\n host,\n filter,\n tools,\n getCurrentTool,\n });\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":";;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"useWebMcpProvider.js","names":["c","_c","useCallback","useEffect","useInsertionEffect","useRef","useState","useSyncExternalStore","useResources","useTapRoot","withKey","useAui","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","getCurrentTool","elements","entries","push","t1","error","has","add","console","warn","unstable_useWebMcpProvider","options","available","currentToolsRef","current","root","WebMcpProviderRoot","getValue","published","setPublished","_c"],"sources":["../../../src/unstable/webmcp/useWebMcpProvider.ts"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useInsertionEffect,\n useRef,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport { useResources, useTapRoot, withKey } from \"@assistant-ui/tap\";\nimport { useAui } 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\n/** @deprecated Experimental since 2026-08-29. Not scheduled for removal; the API may change in any release. */\nexport type Unstable_WebMcpProviderOptions = {\n filter?: (name: string, tool: Tool<any, any>) => boolean;\n};\n\n/** @deprecated Experimental since 2026-08-29. Not scheduled for removal; the API may change in any release. */\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 host,\n filter,\n tools,\n getCurrentTool,\n}: {\n host: WebMcpHost;\n filter: (name: string, tool: Tool<any, any>) => boolean;\n tools: Record<string, Tool<any, any>>;\n getCurrentTool: (name: string) => Tool<any, any> | undefined;\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({\n host,\n name,\n signature,\n tool,\n getCurrentTool,\n }),\n [host, name, signature, tool, getCurrentTool],\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 *\n * @deprecated Experimental since 2026-08-29. Not scheduled for removal; the API may change in any release.\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 const tools = useModelContextSnapshot(\n aui,\n host.available,\n modelContextToolSource,\n );\n const currentToolsRef = useRef(tools);\n useInsertionEffect(() => {\n currentToolsRef.current = tools;\n }, [tools]);\n const getCurrentTool = useCallback(\n (name: string) => currentToolsRef.current[name],\n [],\n );\n\n const root = useTapRoot(function WebMcpProviderRoot() {\n return useWebMcpRegistry({\n host,\n filter,\n tools,\n getCurrentTool,\n });\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getSelectionMessageId.d.ts","sourceRoot":"","sources":["../../src/utils/getSelectionMessageId.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"getSelectionMessageId.d.ts","sourceRoot":"","sources":["../../src/utils/getSelectionMessageId.ts"],"names":[],"mappings":"AAmFA,eAAO,MAAM,qBAAqB,GAChC,WAAW,SAAS,EACpB,OAAO,OAAO,GAAG,IAAI,KACpB,MAAM,GAAG,IA8CX,CAAC"}
|
|
@@ -25,8 +25,25 @@ const findQuoteMarker = (node, messageElement) => {
|
|
|
25
25
|
if (!messageElement.contains(marker)) return null;
|
|
26
26
|
return marker;
|
|
27
27
|
};
|
|
28
|
-
const
|
|
29
|
-
|
|
28
|
+
const normalizeRangeEnd = (range) => {
|
|
29
|
+
if (range.endOffset !== 0 || range.collapsed) return range;
|
|
30
|
+
const walker = range.endContainer.ownerDocument?.createTreeWalker(range.commonAncestorContainer, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
|
|
31
|
+
if (!walker) return range;
|
|
32
|
+
walker.currentNode = range.endContainer;
|
|
33
|
+
while (walker.previousNode()) {
|
|
34
|
+
const node = walker.currentNode;
|
|
35
|
+
const offset = node instanceof Text ? node.length : node.childNodes.length;
|
|
36
|
+
if (range.comparePoint(node, offset) < 0) return range;
|
|
37
|
+
const marker = (node instanceof Element ? node : node.parentElement)?.closest(QUOTE_SELECTABLE_SELECTOR);
|
|
38
|
+
if (marker && isExcluded(marker) && !node.contains(range.endContainer)) return range;
|
|
39
|
+
if (!(node instanceof Text) || !node.data.trim()) continue;
|
|
40
|
+
const normalized = range.cloneRange();
|
|
41
|
+
normalized.setEnd(node, offset);
|
|
42
|
+
return normalized.collapsed ? range : normalized;
|
|
43
|
+
}
|
|
44
|
+
return range;
|
|
45
|
+
};
|
|
46
|
+
const intersectsExcluded = (scope, ranges) => {
|
|
30
47
|
for (const marker of scope.querySelectorAll(QUOTE_SELECTABLE_SELECTOR)) {
|
|
31
48
|
if (!isExcluded(marker)) continue;
|
|
32
49
|
if (ranges.some((range) => range.intersectsNode(marker))) return true;
|
|
@@ -34,8 +51,17 @@ const intersectsExcluded = (scope, selection) => {
|
|
|
34
51
|
return false;
|
|
35
52
|
};
|
|
36
53
|
const getSelectionMessageId = (selection, root) => {
|
|
37
|
-
|
|
54
|
+
let { anchorNode, focusNode } = selection;
|
|
38
55
|
if (!anchorNode || !focusNode) return null;
|
|
56
|
+
const ranges = Array.from({ length: selection.rangeCount }, (_, i) => selection.getRangeAt(i));
|
|
57
|
+
if (ranges.length === 1) {
|
|
58
|
+
const range = normalizeRangeEnd(ranges[0]);
|
|
59
|
+
if (range !== ranges[0]) {
|
|
60
|
+
ranges[0] = range;
|
|
61
|
+
anchorNode = range.startContainer;
|
|
62
|
+
focusNode = range.endContainer;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
39
65
|
const anchorMessageElement = findMessageElement(anchorNode);
|
|
40
66
|
const focusMessageElement = findMessageElement(focusNode);
|
|
41
67
|
if (!anchorMessageElement || anchorMessageElement !== focusMessageElement) return null;
|
|
@@ -50,11 +76,8 @@ const getSelectionMessageId = (selection, root) => {
|
|
|
50
76
|
if (!anchorMarker || anchorMarker !== focusMarker) return null;
|
|
51
77
|
}
|
|
52
78
|
const scope = anchorMarker ?? anchorMessageElement;
|
|
53
|
-
for (
|
|
54
|
-
|
|
55
|
-
if (!scope.contains(commonAncestorContainer)) return null;
|
|
56
|
-
}
|
|
57
|
-
return intersectsExcluded(scope, selection) ? null : messageId;
|
|
79
|
+
for (const { commonAncestorContainer } of ranges) if (!scope.contains(commonAncestorContainer)) return null;
|
|
80
|
+
return intersectsExcluded(scope, ranges) ? null : messageId;
|
|
58
81
|
};
|
|
59
82
|
//#endregion
|
|
60
83
|
export { getSelectionMessageId };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getSelectionMessageId.js","names":["QUOTE_SELECTABLE_SELECTOR","getElement","node","Node","HTMLElement","parentElement","findMessageElement","el","id","getAttribute","isExcluded","marker","Element","hasQuoteSelectableRegion","messageElement","matches","querySelectorAll","findQuoteMarker","closest","contains","
|
|
1
|
+
{"version":3,"file":"getSelectionMessageId.js","names":["QUOTE_SELECTABLE_SELECTOR","getElement","node","Node","HTMLElement","parentElement","findMessageElement","el","id","getAttribute","isExcluded","marker","Element","hasQuoteSelectableRegion","messageElement","matches","querySelectorAll","findQuoteMarker","closest","contains","normalizeRangeEnd","range","Range","endOffset","collapsed","walker","endContainer","ownerDocument","createTreeWalker","commonAncestorContainer","NodeFilter","SHOW_ELEMENT","SHOW_TEXT","currentNode","previousNode","offset","Text","length","childNodes","comparePoint","element","data","trim","normalized","cloneRange","setEnd","intersectsExcluded","scope","ranges","some","intersectsNode","getSelectionMessageId","selection","Selection","root","anchorNode","focusNode","Array","from","rangeCount","_","i","getRangeAt","startContainer","anchorMessageElement","focusMessageElement","messageId","anchorMarker","focusMarker"],"sources":["../../src/utils/getSelectionMessageId.ts"],"sourcesContent":["const QUOTE_SELECTABLE_SELECTOR = \"[data-aui-quote-selectable]\";\n\nconst getElement = (node: Node | null): HTMLElement | null => {\n return node instanceof HTMLElement ? node : (node?.parentElement ?? null);\n};\n\nconst findMessageElement = (node: Node | null): HTMLElement | null => {\n let el = node instanceof HTMLElement ? node : (node?.parentElement ?? null);\n while (el) {\n const id = el.getAttribute(\"data-message-id\");\n if (id) return el;\n el = el.parentElement;\n }\n return null;\n};\n\nconst isExcluded = (marker: Element): boolean => {\n return marker.getAttribute(\"data-aui-quote-selectable\") === \"false\";\n};\n\nconst hasQuoteSelectableRegion = (messageElement: HTMLElement) => {\n if (\n messageElement.matches(QUOTE_SELECTABLE_SELECTOR) &&\n !isExcluded(messageElement)\n ) {\n return true;\n }\n for (const marker of messageElement.querySelectorAll(\n QUOTE_SELECTABLE_SELECTOR,\n )) {\n if (!isExcluded(marker)) return true;\n }\n return false;\n};\n\nconst findQuoteMarker = (\n node: Node | null,\n messageElement: HTMLElement,\n): HTMLElement | null => {\n const marker = getElement(node)?.closest(QUOTE_SELECTABLE_SELECTOR);\n if (!(marker instanceof HTMLElement)) return null;\n if (!messageElement.contains(marker)) return null;\n return marker;\n};\n\nconst normalizeRangeEnd = (range: Range): Range => {\n if (range.endOffset !== 0 || range.collapsed) return range;\n\n const walker = range.endContainer.ownerDocument?.createTreeWalker(\n range.commonAncestorContainer,\n NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,\n );\n if (!walker) return range;\n walker.currentNode = range.endContainer;\n\n while (walker.previousNode()) {\n const node = walker.currentNode;\n const offset = node instanceof Text ? node.length : node.childNodes.length;\n if (range.comparePoint(node, offset) < 0) return range;\n const element = node instanceof Element ? node : node.parentElement;\n const marker = element?.closest(QUOTE_SELECTABLE_SELECTOR);\n if (marker && isExcluded(marker) && !node.contains(range.endContainer))\n return range;\n if (!(node instanceof Text) || !node.data.trim()) continue;\n\n const normalized = range.cloneRange();\n normalized.setEnd(node, offset);\n return normalized.collapsed ? range : normalized;\n }\n return range;\n};\n\nconst intersectsExcluded = (\n scope: Element,\n ranges: readonly Range[],\n): boolean => {\n for (const marker of scope.querySelectorAll(QUOTE_SELECTABLE_SELECTOR)) {\n if (!isExcluded(marker)) continue;\n if (ranges.some((range) => range.intersectsNode(marker))) return true;\n }\n return false;\n};\n\nexport const getSelectionMessageId = (\n selection: Selection,\n root?: Element | null,\n): string | null => {\n let { anchorNode, focusNode } = selection;\n if (!anchorNode || !focusNode) return null;\n\n const ranges = Array.from({ length: selection.rangeCount }, (_, i) =>\n selection.getRangeAt(i),\n );\n if (ranges.length === 1) {\n const range = normalizeRangeEnd(ranges[0]!);\n if (range !== ranges[0]) {\n ranges[0] = range;\n anchorNode = range.startContainer;\n focusNode = range.endContainer;\n }\n }\n\n const anchorMessageElement = findMessageElement(anchorNode);\n const focusMessageElement = findMessageElement(focusNode);\n\n if (!anchorMessageElement || anchorMessageElement !== focusMessageElement) {\n return null;\n }\n if (root && !root.contains(anchorMessageElement)) {\n return null;\n }\n\n const messageId = anchorMessageElement.getAttribute(\"data-message-id\");\n if (!messageId) return null;\n\n const anchorMarker = findQuoteMarker(anchorNode, anchorMessageElement);\n const focusMarker = findQuoteMarker(focusNode, anchorMessageElement);\n\n if (anchorMarker && isExcluded(anchorMarker)) return null;\n if (focusMarker && isExcluded(focusMarker)) return null;\n\n if (hasQuoteSelectableRegion(anchorMessageElement)) {\n if (!anchorMarker || anchorMarker !== focusMarker) return null;\n }\n\n const scope = anchorMarker ?? anchorMessageElement;\n\n for (const { commonAncestorContainer } of ranges) {\n if (!scope.contains(commonAncestorContainer)) return null;\n }\n\n return intersectsExcluded(scope, ranges) ? null : messageId;\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useOnResizeContent.d.ts","sourceRoot":"","sources":["../../../src/utils/hooks/useOnResizeContent.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useOnResizeContent.d.ts","sourceRoot":"","sources":["../../../src/utils/hooks/useOnResizeContent.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,IAAI,qCAStD,CAAC"}
|
|
@@ -2,39 +2,19 @@ import { useManagedRef } from "./useManagedRef.js";
|
|
|
2
2
|
import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
|
|
3
3
|
import "@assistant-ui/tap/react-shim";
|
|
4
4
|
import { useCallbackRef } from "radix-ui/internal";
|
|
5
|
+
import { observeContentResize } from "@assistant-ui/store/client";
|
|
5
6
|
//#region src/utils/hooks/useOnResizeContent.ts
|
|
6
7
|
const useOnResizeContent = (callback) => {
|
|
7
8
|
const $ = c(2);
|
|
8
9
|
const callbackRef = useCallbackRef(callback);
|
|
9
10
|
let t0;
|
|
10
11
|
if ($[0] !== callbackRef) {
|
|
11
|
-
t0 = (el) =>
|
|
12
|
-
const resizeObserver = new ResizeObserver(() => {
|
|
13
|
-
callbackRef();
|
|
14
|
-
});
|
|
15
|
-
const mutationObserver = new MutationObserver((mutations) => {
|
|
16
|
-
if (mutations.some(_temp)) callbackRef();
|
|
17
|
-
});
|
|
18
|
-
resizeObserver.observe(el);
|
|
19
|
-
mutationObserver.observe(el, {
|
|
20
|
-
childList: true,
|
|
21
|
-
subtree: true,
|
|
22
|
-
attributes: true,
|
|
23
|
-
characterData: true
|
|
24
|
-
});
|
|
25
|
-
return () => {
|
|
26
|
-
resizeObserver.disconnect();
|
|
27
|
-
mutationObserver.disconnect();
|
|
28
|
-
};
|
|
29
|
-
};
|
|
12
|
+
t0 = (el) => observeContentResize(el, callbackRef);
|
|
30
13
|
$[0] = callbackRef;
|
|
31
14
|
$[1] = t0;
|
|
32
15
|
} else t0 = $[1];
|
|
33
16
|
return useManagedRef(t0);
|
|
34
17
|
};
|
|
35
|
-
function _temp(m) {
|
|
36
|
-
return m.type !== "attributes" || m.attributeName !== "style";
|
|
37
|
-
}
|
|
38
18
|
//#endregion
|
|
39
19
|
export { useOnResizeContent };
|
|
40
20
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useOnResizeContent.js","names":["useCallbackRef","useCallback","useManagedRef","useOnResizeContent","callback","$","_c","callbackRef","t0","el","
|
|
1
|
+
{"version":3,"file":"useOnResizeContent.js","names":["observeContentResize","useCallbackRef","useCallback","useManagedRef","useOnResizeContent","callback","$","_c","callbackRef","t0","el","refCallback","_c","refCallback"],"sources":["../../../src/utils/hooks/useOnResizeContent.ts"],"sourcesContent":["import { observeContentResize } from \"@assistant-ui/store/client\";\nimport { useCallbackRef } from \"radix-ui/internal\";\nimport { useCallback } from \"react\";\nimport { useManagedRef } from \"./useManagedRef\";\n\nexport const useOnResizeContent = (callback: () => void) => {\n const callbackRef = useCallbackRef(callback);\n\n const refCallback = useCallback(\n (el: HTMLElement) => observeContentResize(el, callbackRef),\n [callbackRef],\n );\n\n return useManagedRef(refCallback);\n};\n"],"mappings":";;;;;;AAKA,MAAaI,sBAAqBC,aAAA;CAAA,MAAAC,IAAAC,EAAA,CAAA;CAChC,MAAAC,cAAoBP,eAAeI,QAAQ;CAAE,IAAAI;CAAA,IAAAH,EAAA,OAAAE,aAAA;EAG3CC,MAAAC,OAAqBV,qBAAqBU,IAAIF,WAAW;EAACF,EAAA,KAAAE;EAAAF,EAAA,KAAAG;CAAA,OAAAA,KAAAH,EAAA;CAE1D,OAEKH,cAAcQ,EAAW;AAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useIsomorphicLayoutEffect.d.ts","sourceRoot":"","sources":["../../src/utils/useIsomorphicLayoutEffect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAmB,MAAM,OAAO,CAAC;AAMnD,eAAO,MAAM,yBAAyB,kBAEzB,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { useEffect, useLayoutEffect } from "@assistant-ui/tap/react-shim";
|
|
2
|
+
const useIsomorphicLayoutEffect = typeof globalThis.window !== "undefined" ? useLayoutEffect : useEffect;
|
|
3
|
+
//#endregion
|
|
4
|
+
export { useIsomorphicLayoutEffect };
|
|
5
|
+
|
|
6
|
+
//# sourceMappingURL=useIsomorphicLayoutEffect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useIsomorphicLayoutEffect.js","names":["useEffect","useLayoutEffect","hasWindow","globalThis","window","useIsomorphicLayoutEffect"],"sources":["../../src/utils/useIsomorphicLayoutEffect.ts"],"sourcesContent":["import { useEffect, useLayoutEffect } from \"react\";\n\nconst hasWindow =\n typeof (globalThis as typeof globalThis & { window?: unknown }).window !==\n \"undefined\";\n\nexport const useIsomorphicLayoutEffect = hasWindow\n ? useLayoutEffect\n : useEffect;\n"],"mappings":";AAMA,MAAaK,4BAHX,OAAQF,WAAwDC,WAChE,cAGEH,kBACAD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/react",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.26",
|
|
4
4
|
"description": "Open-source TypeScript/React library for building production-grade AI chat experiences",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -55,12 +55,12 @@
|
|
|
55
55
|
],
|
|
56
56
|
"sideEffects": false,
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@assistant-ui/core": "^0.3.
|
|
59
|
-
"@assistant-ui/store": "^0.3.
|
|
60
|
-
"@assistant-ui/tap": "^0.9.
|
|
61
|
-
"assistant-cloud": "^0.2.
|
|
62
|
-
"assistant-stream": "^0.3.
|
|
63
|
-
"radix-ui": "^1.
|
|
58
|
+
"@assistant-ui/core": "^0.3.25",
|
|
59
|
+
"@assistant-ui/store": "^0.3.18",
|
|
60
|
+
"@assistant-ui/tap": "^0.9.22",
|
|
61
|
+
"assistant-cloud": "^0.2.6",
|
|
62
|
+
"assistant-stream": "^0.3.49",
|
|
63
|
+
"radix-ui": "^1.7.0",
|
|
64
64
|
"react-textarea-autosize": "^8.5.9",
|
|
65
65
|
"safe-content-frame": "^0.0.33",
|
|
66
66
|
"zod": "^4.6.5",
|
|
@@ -81,15 +81,15 @@
|
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
|
-
"@assistant-ui/vite": "0.0.
|
|
84
|
+
"@assistant-ui/vite": "0.0.21",
|
|
85
85
|
"@assistant-ui/x-buildutils": "0.0.30",
|
|
86
86
|
"@assistant-ui/x-react18": "0.0.0",
|
|
87
87
|
"@testing-library/react": "^16.3.3",
|
|
88
88
|
"@types/json-schema": "^7.0.15",
|
|
89
|
-
"@types/node": "^26.6.
|
|
89
|
+
"@types/node": "^26.6.4",
|
|
90
90
|
"@types/react": "^19.3.0",
|
|
91
91
|
"@types/react-dom": "^19.3.0",
|
|
92
|
-
"jsdom": "^30.
|
|
92
|
+
"jsdom": "^30.1.2",
|
|
93
93
|
"react": "^19.3.0",
|
|
94
94
|
"react-dom": "^19.3.0",
|
|
95
95
|
"vitest": "^5.0.3"
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
|
|
3
1
|
import { renderToString } from "react-dom/server";
|
|
4
2
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
3
|
|
|
@@ -27,9 +25,13 @@ describe("useToolCallElapsed", () => {
|
|
|
27
25
|
|
|
28
26
|
it("does not read the wall clock during server render", () => {
|
|
29
27
|
const nowSpy = vi.spyOn(Date, "now");
|
|
28
|
+
const errors = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
30
29
|
const Probe = () => <span>{useToolCallElapsed() ?? "none"}</span>;
|
|
31
30
|
|
|
32
31
|
expect(renderToString(<Probe />)).toBe("<span>none</span>");
|
|
33
32
|
expect(nowSpy).not.toHaveBeenCalled();
|
|
33
|
+
expect(errors.mock.calls.flat().join(" ")).not.toMatch(
|
|
34
|
+
/useLayoutEffect does nothing on the server/,
|
|
35
|
+
);
|
|
34
36
|
});
|
|
35
37
|
});
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useState } from "react";
|
|
4
4
|
import { useAuiState } from "@assistant-ui/store";
|
|
5
|
+
import { useIsomorphicLayoutEffect } from "../utils/useIsomorphicLayoutEffect";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Hook that returns the elapsed wall-clock time of the current tool call in
|
|
@@ -36,7 +37,7 @@ export const useToolCallElapsed = (): number | undefined => {
|
|
|
36
37
|
timing !== undefined && timing.completedAt === undefined && partRunning;
|
|
37
38
|
const [now, setNow] = useState<number | undefined>(undefined);
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
useIsomorphicLayoutEffect(() => {
|
|
40
41
|
if (!running) return undefined;
|
|
41
42
|
// The clock is read only after mount so a prerender never reads it; the
|
|
42
43
|
// layout effect still catches the elapsed value up before the first paint.
|