@athenaintel/react 0.38.1 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/README.md +23 -3
  2. package/dist/agent-runtime/src/errors.cjs +8 -0
  3. package/dist/agent-runtime/src/errors.cjs.map +1 -1
  4. package/dist/agent-runtime/src/errors.js +8 -0
  5. package/dist/agent-runtime/src/errors.js.map +1 -1
  6. package/dist/agent-runtime/src/messages.cjs +76 -16
  7. package/dist/agent-runtime/src/messages.cjs.map +1 -1
  8. package/dist/agent-runtime/src/messages.js +76 -16
  9. package/dist/agent-runtime/src/messages.js.map +1 -1
  10. package/dist/agent-runtime/src/tool-metadata.cjs +15 -0
  11. package/dist/agent-runtime/src/tool-metadata.cjs.map +1 -1
  12. package/dist/agent-runtime/src/tool-metadata.js +15 -0
  13. package/dist/agent-runtime/src/tool-metadata.js.map +1 -1
  14. package/dist/assets/use-asset-content.cjs +127 -0
  15. package/dist/assets/use-asset-content.cjs.map +1 -0
  16. package/dist/assets/use-asset-content.d.ts +60 -0
  17. package/dist/assets/use-asset-content.js +127 -0
  18. package/dist/assets/use-asset-content.js.map +1 -0
  19. package/dist/chat/super-grouping/CollapsibleGroup.cjs +16 -8
  20. package/dist/chat/super-grouping/CollapsibleGroup.cjs.map +1 -1
  21. package/dist/chat/super-grouping/CollapsibleGroup.d.ts +26 -1
  22. package/dist/chat/super-grouping/CollapsibleGroup.js +16 -8
  23. package/dist/chat/super-grouping/CollapsibleGroup.js.map +1 -1
  24. package/dist/chat/super-grouping/GroupedMessageParts.cjs +59 -0
  25. package/dist/chat/super-grouping/GroupedMessageParts.cjs.map +1 -0
  26. package/dist/chat/super-grouping/GroupedMessageParts.d.ts +25 -0
  27. package/dist/chat/super-grouping/GroupedMessageParts.js +59 -0
  28. package/dist/chat/super-grouping/GroupedMessageParts.js.map +1 -0
  29. package/dist/chat/super-grouping/SuperGroupingCard.cjs +9 -5
  30. package/dist/chat/super-grouping/SuperGroupingCard.cjs.map +1 -1
  31. package/dist/chat/super-grouping/SuperGroupingCard.d.ts +2 -1
  32. package/dist/chat/super-grouping/SuperGroupingCard.js +13 -9
  33. package/dist/chat/super-grouping/SuperGroupingCard.js.map +1 -1
  34. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs +10 -6
  35. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs.map +1 -1
  36. package/dist/chat/super-grouping/SuperGroupingTranscript.js +14 -10
  37. package/dist/chat/super-grouping/SuperGroupingTranscript.js.map +1 -1
  38. package/dist/chat/super-grouping/grouped-part-keys.cjs +104 -0
  39. package/dist/chat/super-grouping/grouped-part-keys.cjs.map +1 -0
  40. package/dist/chat/super-grouping/grouped-part-keys.d.ts +37 -0
  41. package/dist/chat/super-grouping/grouped-part-keys.js +104 -0
  42. package/dist/chat/super-grouping/grouped-part-keys.js.map +1 -0
  43. package/dist/chat/super-grouping/index.cjs +2 -0
  44. package/dist/chat/super-grouping/index.cjs.map +1 -1
  45. package/dist/chat/super-grouping/index.d.ts +3 -1
  46. package/dist/chat/super-grouping/index.js +2 -0
  47. package/dist/chat/super-grouping/index.js.map +1 -1
  48. package/dist/chat-ui/composer/tiptap-composer.cjs +25 -19
  49. package/dist/chat-ui/composer/tiptap-composer.cjs.map +1 -1
  50. package/dist/chat-ui/composer/tiptap-composer.d.ts +5 -0
  51. package/dist/chat-ui/composer/tiptap-composer.js +26 -20
  52. package/dist/chat-ui/composer/tiptap-composer.js.map +1 -1
  53. package/dist/chat-ui/thread/statewire-history-edge.cjs +13 -0
  54. package/dist/chat-ui/thread/statewire-history-edge.cjs.map +1 -1
  55. package/dist/chat-ui/thread/statewire-history-edge.js +13 -0
  56. package/dist/chat-ui/thread/statewire-history-edge.js.map +1 -1
  57. package/dist/composer/clipboard.cjs +16 -0
  58. package/dist/composer/clipboard.cjs.map +1 -1
  59. package/dist/composer/clipboard.d.ts +9 -0
  60. package/dist/composer/clipboard.js +17 -1
  61. package/dist/composer/clipboard.js.map +1 -1
  62. package/dist/diagnostics/errors.cjs +11 -0
  63. package/dist/diagnostics/errors.cjs.map +1 -1
  64. package/dist/diagnostics/errors.d.ts +11 -0
  65. package/dist/diagnostics/errors.js +11 -0
  66. package/dist/diagnostics/errors.js.map +1 -1
  67. package/dist/index.cjs +37 -0
  68. package/dist/index.cjs.map +1 -1
  69. package/dist/index.d.ts +14 -1
  70. package/dist/index.js +37 -0
  71. package/dist/index.js.map +1 -1
  72. package/dist/provider/AthenaProvider.cjs +5 -2
  73. package/dist/provider/AthenaProvider.cjs.map +1 -1
  74. package/dist/provider/AthenaProvider.js +5 -2
  75. package/dist/provider/AthenaProvider.js.map +1 -1
  76. package/dist/provider/statewire-history-context.cjs +48 -0
  77. package/dist/provider/statewire-history-context.cjs.map +1 -0
  78. package/dist/provider/statewire-history-context.d.ts +36 -0
  79. package/dist/provider/statewire-history-context.js +48 -0
  80. package/dist/provider/statewire-history-context.js.map +1 -0
  81. package/dist/react/package.json.cjs +1 -1
  82. package/dist/react/package.json.js +1 -1
  83. package/dist/runtime/statewire-lifecycle.cjs +8 -0
  84. package/dist/runtime/statewire-lifecycle.cjs.map +1 -1
  85. package/dist/runtime/statewire-lifecycle.js +9 -1
  86. package/dist/runtime/statewire-lifecycle.js.map +1 -1
  87. package/dist/styles.css +1 -1
  88. package/dist/tools/index.cjs +7 -0
  89. package/dist/tools/index.cjs.map +1 -1
  90. package/dist/tools/index.d.ts +2 -0
  91. package/dist/tools/index.js +7 -0
  92. package/dist/tools/index.js.map +1 -1
  93. package/dist/tools/inline-tools.cjs +10 -0
  94. package/dist/tools/inline-tools.cjs.map +1 -0
  95. package/dist/tools/inline-tools.d.ts +2 -0
  96. package/dist/tools/inline-tools.js +10 -0
  97. package/dist/tools/inline-tools.js.map +1 -0
  98. package/dist/tools/test-recording-report.cjs +175 -0
  99. package/dist/tools/test-recording-report.cjs.map +1 -0
  100. package/dist/tools/test-recording-report.d.ts +82 -0
  101. package/dist/tools/test-recording-report.js +175 -0
  102. package/dist/tools/test-recording-report.js.map +1 -0
  103. package/dist/tools/test-recording-tool-uis.cjs +498 -0
  104. package/dist/tools/test-recording-tool-uis.cjs.map +1 -0
  105. package/dist/tools/test-recording-tool-uis.d.ts +32 -0
  106. package/dist/tools/test-recording-tool-uis.js +498 -0
  107. package/dist/tools/test-recording-tool-uis.js.map +1 -0
  108. package/dist/tools/tool-summary.cjs +49 -1
  109. package/dist/tools/tool-summary.cjs.map +1 -1
  110. package/dist/tools/tool-summary.js +49 -1
  111. package/dist/tools/tool-summary.js.map +1 -1
  112. package/dist/tools/tool-uis.cjs +6 -1
  113. package/dist/tools/tool-uis.cjs.map +1 -1
  114. package/dist/tools/tool-uis.js +6 -1
  115. package/dist/tools/tool-uis.js.map +1 -1
  116. package/dist/workbench/ComputerLiveDesktop.cjs +287 -0
  117. package/dist/workbench/ComputerLiveDesktop.cjs.map +1 -0
  118. package/dist/workbench/ComputerLiveDesktop.d.ts +27 -0
  119. package/dist/workbench/ComputerLiveDesktop.js +287 -0
  120. package/dist/workbench/ComputerLiveDesktop.js.map +1 -0
  121. package/dist/workbench/SessionWorkbench.cjs +2 -2
  122. package/dist/workbench/SessionWorkbench.cjs.map +1 -1
  123. package/dist/workbench/SessionWorkbench.js +2 -2
  124. package/dist/workbench/SessionWorkbench.js.map +1 -1
  125. package/dist/workbench/live-desktop-api.cjs +139 -0
  126. package/dist/workbench/live-desktop-api.cjs.map +1 -0
  127. package/dist/workbench/live-desktop-api.d.ts +69 -0
  128. package/dist/workbench/live-desktop-api.js +139 -0
  129. package/dist/workbench/live-desktop-api.js.map +1 -0
  130. package/dist/workbench/session-workbench-context.cjs.map +1 -1
  131. package/dist/workbench/session-workbench-context.d.ts +7 -0
  132. package/dist/workbench/session-workbench-context.js.map +1 -1
  133. package/dist/workbench/session-workbench-panes.cjs +38 -2
  134. package/dist/workbench/session-workbench-panes.cjs.map +1 -1
  135. package/dist/workbench/session-workbench-panes.js +38 -2
  136. package/dist/workbench/session-workbench-panes.js.map +1 -1
  137. package/package.json +1 -1
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const reactQuery = require("@tanstack/react-query");
4
+ const react = require("react");
5
+ const mint = require("../collab/mint.cjs");
6
+ const errors = require("../diagnostics/errors.cjs");
7
+ const useAthenaRestQuery = require("../lib/use-athena-rest-query.cjs");
8
+ const AthenaContext = require("../provider/AthenaContext.cjs");
9
+ const credentialRenewal = require("../runtime/credential-renewal.cjs");
10
+ function athenaAssetContentPath(assetId) {
11
+ return `/api/asset/content/${encodeURIComponent(assetId)}`;
12
+ }
13
+ function isAbortError(error) {
14
+ return (error instanceof DOMException || error instanceof Error) && error.name === "AbortError";
15
+ }
16
+ function assetContentError({
17
+ error,
18
+ assetId,
19
+ message,
20
+ hint
21
+ }) {
22
+ if (isAbortError(error) || errors.isAthenaSdkError(error)) return error;
23
+ return new errors.AthenaSdkError({
24
+ code: "asset_content_failed",
25
+ message,
26
+ cause: error,
27
+ hint,
28
+ context: { assetId }
29
+ });
30
+ }
31
+ async function fetchAthenaAssetContent({
32
+ backendUrl,
33
+ auth,
34
+ assetId,
35
+ signal
36
+ }) {
37
+ const url = `${mint.getAthenaApiBaseUrl(backendUrl)}${athenaAssetContentPath(assetId)}`;
38
+ let response;
39
+ try {
40
+ response = await credentialRenewal.sendWithCredentialRenewal(
41
+ auth,
42
+ (headers) => fetch(url, { method: "GET", headers, signal: signal ?? null })
43
+ );
44
+ } catch (error) {
45
+ throw assetContentError({
46
+ error,
47
+ assetId,
48
+ message: "Reading the asset could not reach the Agora API, or its credential could not be resolved.",
49
+ hint: "Check the network, that backendUrl points at the Agora API base, and that getToken resolves."
50
+ });
51
+ }
52
+ if (!response.ok) {
53
+ const refused = response.status === 401 || response.status === 403;
54
+ throw new errors.AthenaSdkError({
55
+ code: refused ? "auth_rejected" : "asset_content_failed",
56
+ message: `Reading the asset failed: ${response.status}`,
57
+ status: response.status,
58
+ detail: await response.json().catch(() => void 0),
59
+ hint: refused ? "The signed-in user cannot read this asset." : response.status === 404 ? "The asset has no stored file, or it no longer exists." : "Check that backendUrl points at the Agora API base.",
60
+ context: { assetId }
61
+ });
62
+ }
63
+ try {
64
+ return await response.blob();
65
+ } catch (error) {
66
+ throw assetContentError({
67
+ error,
68
+ assetId,
69
+ message: "Reading the asset failed while its file downloaded.",
70
+ hint: "Check the network and try again."
71
+ });
72
+ }
73
+ }
74
+ const NO_RETRY_STATUSES = /* @__PURE__ */ new Set([400, 401, 403, 404, 409]);
75
+ function retryTransient(failureCount, error) {
76
+ const status = error.status;
77
+ if (typeof status === "number" && NO_RETRY_STATUSES.has(status)) return false;
78
+ return failureCount < 1;
79
+ }
80
+ function useAthenaAssetContent({
81
+ assetId,
82
+ enabled = true
83
+ }) {
84
+ const config = AthenaContext.useAthenaConfig();
85
+ const { backendUrl, apiKey, getToken } = config;
86
+ const identity = useAthenaRestQuery.useAthenaQueryIdentity({ apiKey, token: config.token, getToken });
87
+ const queryClient = useAthenaRestQuery.useAthenaQueryClient();
88
+ const reader = react.useId();
89
+ return reactQuery.useQuery(
90
+ {
91
+ queryKey: ["athena-sdk", "asset-content", backendUrl, ...identity, assetId, reader],
92
+ // `config.token` is read at fetch time: the provider serves the latest
93
+ // token through a getter, so rotations reach the request.
94
+ queryFn: ({ signal }) => fetchAthenaAssetContent({
95
+ backendUrl,
96
+ auth: { apiKey, token: config.token, getToken },
97
+ assetId: assetId ?? "",
98
+ signal
99
+ }),
100
+ enabled: enabled && assetId !== null && assetId.length > 0 && backendUrl.length > 0,
101
+ staleTime: Number.POSITIVE_INFINITY,
102
+ gcTime: 0,
103
+ refetchOnWindowFocus: false,
104
+ refetchOnReconnect: false,
105
+ retry: retryTransient
106
+ },
107
+ queryClient
108
+ );
109
+ }
110
+ function useObjectUrl(blob) {
111
+ const [url, setUrl] = react.useState(null);
112
+ react.useEffect(() => {
113
+ if (!blob) {
114
+ setUrl(null);
115
+ return void 0;
116
+ }
117
+ const objectUrl = URL.createObjectURL(blob);
118
+ setUrl(objectUrl);
119
+ return () => URL.revokeObjectURL(objectUrl);
120
+ }, [blob]);
121
+ return url;
122
+ }
123
+ exports.athenaAssetContentPath = athenaAssetContentPath;
124
+ exports.fetchAthenaAssetContent = fetchAthenaAssetContent;
125
+ exports.useAthenaAssetContent = useAthenaAssetContent;
126
+ exports.useObjectUrl = useObjectUrl;
127
+ //# sourceMappingURL=use-asset-content.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-asset-content.cjs","sources":["../../src/assets/use-asset-content.ts"],"sourcesContent":["/**\n * An asset's stored file, read with the signed-in user's own credential:\n * `GET /api/asset/content/{asset_id}` (agora `web/api/asset/views.py`\n * `serve_asset_content`), which passes the asset's permission check\n * (`get_asset_with_permission_check`) and streams the bytes through Agora.\n * No link is minted — no presigned storage URL, no embed token — so nothing\n * that could be opened without the credential ever exists: the bytes land in\n * a `Blob` that only this page holds, and a media element plays it through an\n * object URL (`useObjectUrl`).\n *\n * This is how the SDK plays an asset's video: a `<video src>` cannot carry\n * the `Authorization` / `X-API-KEY` header the SDK authenticates with, and the\n * alternatives (`/api/asset/fetch-presigned-url`, an `AthenaAssetEmbed` link)\n * each hand out a URL that works for whoever holds it. The whole file is read\n * before playback starts, so read it when the person asks to play, not on\n * mount.\n */\nimport { useQuery, type UseQueryResult } from '@tanstack/react-query';\nimport { useEffect, useId, useState } from 'react';\nimport { getAthenaApiBaseUrl } from '../collab/mint';\nimport { AthenaSdkError, isAthenaSdkError } from '../diagnostics/errors';\nimport { useAthenaQueryClient, useAthenaQueryIdentity } from '../lib/use-athena-rest-query';\nimport { useAthenaConfig } from '../provider/AthenaContext';\nimport { sendWithCredentialRenewal } from '../runtime/credential-renewal';\nimport type { StatewireAuthSource } from '../runtime/statewire-auth-headers';\n\n/** `/api/asset/content/{asset_id}`: the asset's stored file, permission-checked. */\nexport function athenaAssetContentPath(assetId: string): string {\n return `/api/asset/content/${encodeURIComponent(assetId)}`;\n}\n\n/** A `fetch` the caller cancelled (unmount, a newer query): lifecycle, not failure. */\nfunction isAbortError(error: unknown): boolean {\n return (error instanceof DOMException || error instanceof Error) && error.name === 'AbortError';\n}\n\n/** `error` as an `asset_content_failed` SDK error; an abort or an SDK error passes through. */\nfunction assetContentError({\n error,\n assetId,\n message,\n hint,\n}: {\n error: unknown;\n assetId: string;\n message: string;\n hint: string;\n}): unknown {\n if (isAbortError(error) || isAthenaSdkError(error)) return error;\n return new AthenaSdkError({\n code: 'asset_content_failed',\n message,\n cause: error,\n hint,\n context: { assetId },\n });\n}\n\n/**\n * Read an asset's stored file as a `Blob`. The credential rides\n * `Authorization: Bearer` (resolved live through `getToken` when the host gave\n * one) or `X-API-KEY`; a refused bearer (`401`) is renewed once\n * (`sendWithCredentialRenewal`). Every failure is an `AthenaSdkError` —\n * `auth_rejected` for a 401 / 403, else `asset_content_failed` (with the HTTP\n * `status` when the server answered), a credential that could not be\n * resolved and a download cut off mid-body included; only an abort keeps its\n * own identity.\n */\nexport async function fetchAthenaAssetContent({\n backendUrl,\n auth,\n assetId,\n signal,\n}: {\n backendUrl: string;\n auth: StatewireAuthSource;\n assetId: string;\n signal?: AbortSignal;\n}): Promise<Blob> {\n const url = `${getAthenaApiBaseUrl(backendUrl)}${athenaAssetContentPath(assetId)}`;\n let response: Response;\n try {\n response = await sendWithCredentialRenewal(auth, (headers) =>\n fetch(url, { method: 'GET', headers, signal: signal ?? null }),\n );\n } catch (error) {\n throw assetContentError({\n error,\n assetId,\n message: 'Reading the asset could not reach the Agora API, or its credential could not be resolved.',\n hint: 'Check the network, that backendUrl points at the Agora API base, and that getToken resolves.',\n });\n }\n if (!response.ok) {\n const refused = response.status === 401 || response.status === 403;\n throw new AthenaSdkError({\n code: refused ? 'auth_rejected' : 'asset_content_failed',\n message: `Reading the asset failed: ${response.status}`,\n status: response.status,\n detail: await response.json().catch(() => undefined),\n hint: refused\n ? 'The signed-in user cannot read this asset.'\n : response.status === 404\n ? 'The asset has no stored file, or it no longer exists.'\n : 'Check that backendUrl points at the Agora API base.',\n context: { assetId },\n });\n }\n try {\n return await response.blob();\n } catch (error) {\n throw assetContentError({\n error,\n assetId,\n message: 'Reading the asset failed while its file downloaded.',\n hint: 'Check the network and try again.',\n });\n }\n}\n\nconst NO_RETRY_STATUSES: ReadonlySet<number> = new Set([400, 401, 403, 404, 409]);\n\n/** Retry a transient failure once; a refusal or a missing file answers the same way again. */\nfunction retryTransient(failureCount: number, error: Error): boolean {\n const status = (error as { status?: unknown }).status;\n if (typeof status === 'number' && NO_RETRY_STATUSES.has(status)) return false;\n return failureCount < 1;\n}\n\nexport interface UseAthenaAssetContentOptions {\n /** The asset to read; `null` holds the query idle. */\n assetId: string | null;\n /** `false` holds the query idle (read on demand: the whole file is downloaded). Default `true`. */\n enabled?: boolean;\n}\n\n/**\n * An asset's stored file through TanStack Query, with the provider's\n * `backendUrl` and credentials. Each mounted hook reads its own copy: the key\n * carries the hook instance as well as the asset, the signed-in principal and\n * a fingerprint of the credential (`useAthenaQueryIdentity`, never the\n * credential itself), and the bytes are dropped as soon as it unmounts. So no\n * reader is ever handed bytes another mount read — not even behind a host\n * `getToken`, whose account the key cannot see — and every new reader passes\n * the permission check again. While mounted the file is read once (a stored\n * file does not change under its asset id); `refetch()` reads it again.\n */\nexport function useAthenaAssetContent({\n assetId,\n enabled = true,\n}: UseAthenaAssetContentOptions): UseQueryResult<Blob, Error> {\n const config = useAthenaConfig();\n const { backendUrl, apiKey, getToken } = config;\n const identity = useAthenaQueryIdentity({ apiKey, token: config.token, getToken });\n const queryClient = useAthenaQueryClient();\n const reader = useId();\n return useQuery(\n {\n queryKey: ['athena-sdk', 'asset-content', backendUrl, ...identity, assetId, reader],\n // `config.token` is read at fetch time: the provider serves the latest\n // token through a getter, so rotations reach the request.\n queryFn: ({ signal }) =>\n fetchAthenaAssetContent({\n backendUrl,\n auth: { apiKey, token: config.token, getToken },\n assetId: assetId ?? '',\n signal,\n }),\n enabled: enabled && assetId !== null && assetId.length > 0 && backendUrl.length > 0,\n staleTime: Number.POSITIVE_INFINITY,\n gcTime: 0,\n refetchOnWindowFocus: false,\n refetchOnReconnect: false,\n retry: retryTransient,\n },\n queryClient,\n );\n}\n\n/**\n * An object URL for `blob` while it is shown, revoked when the blob changes\n * or the component unmounts; `null` without a blob.\n */\nexport function useObjectUrl(blob: Blob | null | undefined): string | null {\n const [url, setUrl] = useState<string | null>(null);\n useEffect(() => {\n if (!blob) {\n setUrl(null);\n return undefined;\n }\n const objectUrl = URL.createObjectURL(blob);\n setUrl(objectUrl);\n return () => URL.revokeObjectURL(objectUrl);\n }, [blob]);\n return url;\n}\n"],"names":["isAthenaSdkError","AthenaSdkError","getAthenaApiBaseUrl","sendWithCredentialRenewal","useAthenaConfig","useAthenaQueryIdentity","useAthenaQueryClient","useId","useQuery","useState","useEffect"],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * An asset's stored file, read with the signed-in user's own credential:
3
+ * `GET /api/asset/content/{asset_id}` (agora `web/api/asset/views.py`
4
+ * `serve_asset_content`), which passes the asset's permission check
5
+ * (`get_asset_with_permission_check`) and streams the bytes through Agora.
6
+ * No link is minted — no presigned storage URL, no embed token — so nothing
7
+ * that could be opened without the credential ever exists: the bytes land in
8
+ * a `Blob` that only this page holds, and a media element plays it through an
9
+ * object URL (`useObjectUrl`).
10
+ *
11
+ * This is how the SDK plays an asset's video: a `<video src>` cannot carry
12
+ * the `Authorization` / `X-API-KEY` header the SDK authenticates with, and the
13
+ * alternatives (`/api/asset/fetch-presigned-url`, an `AthenaAssetEmbed` link)
14
+ * each hand out a URL that works for whoever holds it. The whole file is read
15
+ * before playback starts, so read it when the person asks to play, not on
16
+ * mount.
17
+ */
18
+ import { type UseQueryResult } from '@tanstack/react-query';
19
+ import type { StatewireAuthSource } from '../runtime/statewire-auth-headers';
20
+ /** `/api/asset/content/{asset_id}`: the asset's stored file, permission-checked. */
21
+ export declare function athenaAssetContentPath(assetId: string): string;
22
+ /**
23
+ * Read an asset's stored file as a `Blob`. The credential rides
24
+ * `Authorization: Bearer` (resolved live through `getToken` when the host gave
25
+ * one) or `X-API-KEY`; a refused bearer (`401`) is renewed once
26
+ * (`sendWithCredentialRenewal`). Every failure is an `AthenaSdkError` —
27
+ * `auth_rejected` for a 401 / 403, else `asset_content_failed` (with the HTTP
28
+ * `status` when the server answered), a credential that could not be
29
+ * resolved and a download cut off mid-body included; only an abort keeps its
30
+ * own identity.
31
+ */
32
+ export declare function fetchAthenaAssetContent({ backendUrl, auth, assetId, signal, }: {
33
+ backendUrl: string;
34
+ auth: StatewireAuthSource;
35
+ assetId: string;
36
+ signal?: AbortSignal;
37
+ }): Promise<Blob>;
38
+ export interface UseAthenaAssetContentOptions {
39
+ /** The asset to read; `null` holds the query idle. */
40
+ assetId: string | null;
41
+ /** `false` holds the query idle (read on demand: the whole file is downloaded). Default `true`. */
42
+ enabled?: boolean;
43
+ }
44
+ /**
45
+ * An asset's stored file through TanStack Query, with the provider's
46
+ * `backendUrl` and credentials. Each mounted hook reads its own copy: the key
47
+ * carries the hook instance as well as the asset, the signed-in principal and
48
+ * a fingerprint of the credential (`useAthenaQueryIdentity`, never the
49
+ * credential itself), and the bytes are dropped as soon as it unmounts. So no
50
+ * reader is ever handed bytes another mount read — not even behind a host
51
+ * `getToken`, whose account the key cannot see — and every new reader passes
52
+ * the permission check again. While mounted the file is read once (a stored
53
+ * file does not change under its asset id); `refetch()` reads it again.
54
+ */
55
+ export declare function useAthenaAssetContent({ assetId, enabled, }: UseAthenaAssetContentOptions): UseQueryResult<Blob, Error>;
56
+ /**
57
+ * An object URL for `blob` while it is shown, revoked when the blob changes
58
+ * or the component unmounts; `null` without a blob.
59
+ */
60
+ export declare function useObjectUrl(blob: Blob | null | undefined): string | null;
@@ -0,0 +1,127 @@
1
+ import { useQuery } from "@tanstack/react-query";
2
+ import { useId, useState, useEffect } from "react";
3
+ import { getAthenaApiBaseUrl } from "../collab/mint.js";
4
+ import { AthenaSdkError, isAthenaSdkError } from "../diagnostics/errors.js";
5
+ import { useAthenaQueryIdentity, useAthenaQueryClient } from "../lib/use-athena-rest-query.js";
6
+ import { useAthenaConfig } from "../provider/AthenaContext.js";
7
+ import { sendWithCredentialRenewal } from "../runtime/credential-renewal.js";
8
+ function athenaAssetContentPath(assetId) {
9
+ return `/api/asset/content/${encodeURIComponent(assetId)}`;
10
+ }
11
+ function isAbortError(error) {
12
+ return (error instanceof DOMException || error instanceof Error) && error.name === "AbortError";
13
+ }
14
+ function assetContentError({
15
+ error,
16
+ assetId,
17
+ message,
18
+ hint
19
+ }) {
20
+ if (isAbortError(error) || isAthenaSdkError(error)) return error;
21
+ return new AthenaSdkError({
22
+ code: "asset_content_failed",
23
+ message,
24
+ cause: error,
25
+ hint,
26
+ context: { assetId }
27
+ });
28
+ }
29
+ async function fetchAthenaAssetContent({
30
+ backendUrl,
31
+ auth,
32
+ assetId,
33
+ signal
34
+ }) {
35
+ const url = `${getAthenaApiBaseUrl(backendUrl)}${athenaAssetContentPath(assetId)}`;
36
+ let response;
37
+ try {
38
+ response = await sendWithCredentialRenewal(
39
+ auth,
40
+ (headers) => fetch(url, { method: "GET", headers, signal: signal ?? null })
41
+ );
42
+ } catch (error) {
43
+ throw assetContentError({
44
+ error,
45
+ assetId,
46
+ message: "Reading the asset could not reach the Agora API, or its credential could not be resolved.",
47
+ hint: "Check the network, that backendUrl points at the Agora API base, and that getToken resolves."
48
+ });
49
+ }
50
+ if (!response.ok) {
51
+ const refused = response.status === 401 || response.status === 403;
52
+ throw new AthenaSdkError({
53
+ code: refused ? "auth_rejected" : "asset_content_failed",
54
+ message: `Reading the asset failed: ${response.status}`,
55
+ status: response.status,
56
+ detail: await response.json().catch(() => void 0),
57
+ hint: refused ? "The signed-in user cannot read this asset." : response.status === 404 ? "The asset has no stored file, or it no longer exists." : "Check that backendUrl points at the Agora API base.",
58
+ context: { assetId }
59
+ });
60
+ }
61
+ try {
62
+ return await response.blob();
63
+ } catch (error) {
64
+ throw assetContentError({
65
+ error,
66
+ assetId,
67
+ message: "Reading the asset failed while its file downloaded.",
68
+ hint: "Check the network and try again."
69
+ });
70
+ }
71
+ }
72
+ const NO_RETRY_STATUSES = /* @__PURE__ */ new Set([400, 401, 403, 404, 409]);
73
+ function retryTransient(failureCount, error) {
74
+ const status = error.status;
75
+ if (typeof status === "number" && NO_RETRY_STATUSES.has(status)) return false;
76
+ return failureCount < 1;
77
+ }
78
+ function useAthenaAssetContent({
79
+ assetId,
80
+ enabled = true
81
+ }) {
82
+ const config = useAthenaConfig();
83
+ const { backendUrl, apiKey, getToken } = config;
84
+ const identity = useAthenaQueryIdentity({ apiKey, token: config.token, getToken });
85
+ const queryClient = useAthenaQueryClient();
86
+ const reader = useId();
87
+ return useQuery(
88
+ {
89
+ queryKey: ["athena-sdk", "asset-content", backendUrl, ...identity, assetId, reader],
90
+ // `config.token` is read at fetch time: the provider serves the latest
91
+ // token through a getter, so rotations reach the request.
92
+ queryFn: ({ signal }) => fetchAthenaAssetContent({
93
+ backendUrl,
94
+ auth: { apiKey, token: config.token, getToken },
95
+ assetId: assetId ?? "",
96
+ signal
97
+ }),
98
+ enabled: enabled && assetId !== null && assetId.length > 0 && backendUrl.length > 0,
99
+ staleTime: Number.POSITIVE_INFINITY,
100
+ gcTime: 0,
101
+ refetchOnWindowFocus: false,
102
+ refetchOnReconnect: false,
103
+ retry: retryTransient
104
+ },
105
+ queryClient
106
+ );
107
+ }
108
+ function useObjectUrl(blob) {
109
+ const [url, setUrl] = useState(null);
110
+ useEffect(() => {
111
+ if (!blob) {
112
+ setUrl(null);
113
+ return void 0;
114
+ }
115
+ const objectUrl = URL.createObjectURL(blob);
116
+ setUrl(objectUrl);
117
+ return () => URL.revokeObjectURL(objectUrl);
118
+ }, [blob]);
119
+ return url;
120
+ }
121
+ export {
122
+ athenaAssetContentPath,
123
+ fetchAthenaAssetContent,
124
+ useAthenaAssetContent,
125
+ useObjectUrl
126
+ };
127
+ //# sourceMappingURL=use-asset-content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-asset-content.js","sources":["../../src/assets/use-asset-content.ts"],"sourcesContent":["/**\n * An asset's stored file, read with the signed-in user's own credential:\n * `GET /api/asset/content/{asset_id}` (agora `web/api/asset/views.py`\n * `serve_asset_content`), which passes the asset's permission check\n * (`get_asset_with_permission_check`) and streams the bytes through Agora.\n * No link is minted — no presigned storage URL, no embed token — so nothing\n * that could be opened without the credential ever exists: the bytes land in\n * a `Blob` that only this page holds, and a media element plays it through an\n * object URL (`useObjectUrl`).\n *\n * This is how the SDK plays an asset's video: a `<video src>` cannot carry\n * the `Authorization` / `X-API-KEY` header the SDK authenticates with, and the\n * alternatives (`/api/asset/fetch-presigned-url`, an `AthenaAssetEmbed` link)\n * each hand out a URL that works for whoever holds it. The whole file is read\n * before playback starts, so read it when the person asks to play, not on\n * mount.\n */\nimport { useQuery, type UseQueryResult } from '@tanstack/react-query';\nimport { useEffect, useId, useState } from 'react';\nimport { getAthenaApiBaseUrl } from '../collab/mint';\nimport { AthenaSdkError, isAthenaSdkError } from '../diagnostics/errors';\nimport { useAthenaQueryClient, useAthenaQueryIdentity } from '../lib/use-athena-rest-query';\nimport { useAthenaConfig } from '../provider/AthenaContext';\nimport { sendWithCredentialRenewal } from '../runtime/credential-renewal';\nimport type { StatewireAuthSource } from '../runtime/statewire-auth-headers';\n\n/** `/api/asset/content/{asset_id}`: the asset's stored file, permission-checked. */\nexport function athenaAssetContentPath(assetId: string): string {\n return `/api/asset/content/${encodeURIComponent(assetId)}`;\n}\n\n/** A `fetch` the caller cancelled (unmount, a newer query): lifecycle, not failure. */\nfunction isAbortError(error: unknown): boolean {\n return (error instanceof DOMException || error instanceof Error) && error.name === 'AbortError';\n}\n\n/** `error` as an `asset_content_failed` SDK error; an abort or an SDK error passes through. */\nfunction assetContentError({\n error,\n assetId,\n message,\n hint,\n}: {\n error: unknown;\n assetId: string;\n message: string;\n hint: string;\n}): unknown {\n if (isAbortError(error) || isAthenaSdkError(error)) return error;\n return new AthenaSdkError({\n code: 'asset_content_failed',\n message,\n cause: error,\n hint,\n context: { assetId },\n });\n}\n\n/**\n * Read an asset's stored file as a `Blob`. The credential rides\n * `Authorization: Bearer` (resolved live through `getToken` when the host gave\n * one) or `X-API-KEY`; a refused bearer (`401`) is renewed once\n * (`sendWithCredentialRenewal`). Every failure is an `AthenaSdkError` —\n * `auth_rejected` for a 401 / 403, else `asset_content_failed` (with the HTTP\n * `status` when the server answered), a credential that could not be\n * resolved and a download cut off mid-body included; only an abort keeps its\n * own identity.\n */\nexport async function fetchAthenaAssetContent({\n backendUrl,\n auth,\n assetId,\n signal,\n}: {\n backendUrl: string;\n auth: StatewireAuthSource;\n assetId: string;\n signal?: AbortSignal;\n}): Promise<Blob> {\n const url = `${getAthenaApiBaseUrl(backendUrl)}${athenaAssetContentPath(assetId)}`;\n let response: Response;\n try {\n response = await sendWithCredentialRenewal(auth, (headers) =>\n fetch(url, { method: 'GET', headers, signal: signal ?? null }),\n );\n } catch (error) {\n throw assetContentError({\n error,\n assetId,\n message: 'Reading the asset could not reach the Agora API, or its credential could not be resolved.',\n hint: 'Check the network, that backendUrl points at the Agora API base, and that getToken resolves.',\n });\n }\n if (!response.ok) {\n const refused = response.status === 401 || response.status === 403;\n throw new AthenaSdkError({\n code: refused ? 'auth_rejected' : 'asset_content_failed',\n message: `Reading the asset failed: ${response.status}`,\n status: response.status,\n detail: await response.json().catch(() => undefined),\n hint: refused\n ? 'The signed-in user cannot read this asset.'\n : response.status === 404\n ? 'The asset has no stored file, or it no longer exists.'\n : 'Check that backendUrl points at the Agora API base.',\n context: { assetId },\n });\n }\n try {\n return await response.blob();\n } catch (error) {\n throw assetContentError({\n error,\n assetId,\n message: 'Reading the asset failed while its file downloaded.',\n hint: 'Check the network and try again.',\n });\n }\n}\n\nconst NO_RETRY_STATUSES: ReadonlySet<number> = new Set([400, 401, 403, 404, 409]);\n\n/** Retry a transient failure once; a refusal or a missing file answers the same way again. */\nfunction retryTransient(failureCount: number, error: Error): boolean {\n const status = (error as { status?: unknown }).status;\n if (typeof status === 'number' && NO_RETRY_STATUSES.has(status)) return false;\n return failureCount < 1;\n}\n\nexport interface UseAthenaAssetContentOptions {\n /** The asset to read; `null` holds the query idle. */\n assetId: string | null;\n /** `false` holds the query idle (read on demand: the whole file is downloaded). Default `true`. */\n enabled?: boolean;\n}\n\n/**\n * An asset's stored file through TanStack Query, with the provider's\n * `backendUrl` and credentials. Each mounted hook reads its own copy: the key\n * carries the hook instance as well as the asset, the signed-in principal and\n * a fingerprint of the credential (`useAthenaQueryIdentity`, never the\n * credential itself), and the bytes are dropped as soon as it unmounts. So no\n * reader is ever handed bytes another mount read — not even behind a host\n * `getToken`, whose account the key cannot see — and every new reader passes\n * the permission check again. While mounted the file is read once (a stored\n * file does not change under its asset id); `refetch()` reads it again.\n */\nexport function useAthenaAssetContent({\n assetId,\n enabled = true,\n}: UseAthenaAssetContentOptions): UseQueryResult<Blob, Error> {\n const config = useAthenaConfig();\n const { backendUrl, apiKey, getToken } = config;\n const identity = useAthenaQueryIdentity({ apiKey, token: config.token, getToken });\n const queryClient = useAthenaQueryClient();\n const reader = useId();\n return useQuery(\n {\n queryKey: ['athena-sdk', 'asset-content', backendUrl, ...identity, assetId, reader],\n // `config.token` is read at fetch time: the provider serves the latest\n // token through a getter, so rotations reach the request.\n queryFn: ({ signal }) =>\n fetchAthenaAssetContent({\n backendUrl,\n auth: { apiKey, token: config.token, getToken },\n assetId: assetId ?? '',\n signal,\n }),\n enabled: enabled && assetId !== null && assetId.length > 0 && backendUrl.length > 0,\n staleTime: Number.POSITIVE_INFINITY,\n gcTime: 0,\n refetchOnWindowFocus: false,\n refetchOnReconnect: false,\n retry: retryTransient,\n },\n queryClient,\n );\n}\n\n/**\n * An object URL for `blob` while it is shown, revoked when the blob changes\n * or the component unmounts; `null` without a blob.\n */\nexport function useObjectUrl(blob: Blob | null | undefined): string | null {\n const [url, setUrl] = useState<string | null>(null);\n useEffect(() => {\n if (!blob) {\n setUrl(null);\n return undefined;\n }\n const objectUrl = URL.createObjectURL(blob);\n setUrl(objectUrl);\n return () => URL.revokeObjectURL(objectUrl);\n }, [blob]);\n return url;\n}\n"],"names":[],"mappings":"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"}
@@ -28,6 +28,10 @@ const DEFAULT_TOOLKIT_THEME = { color: "#919EAB" };
28
28
  const DEFAULT_GROUPING = groupingFunction.createToolBasedGroupingFunction(toolkitMap.defaultGetToolGroupKey);
29
29
  const SuperGroupingContext = react.createContext(null);
30
30
  const SuperGroupingProvider = SuperGroupingContext.Provider;
31
+ const GroupToolUIsMountedContext = react.createContext(true);
32
+ function GroupToolUI({ children }) {
33
+ return react.useContext(GroupToolUIsMountedContext) ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children }) : null;
34
+ }
31
35
  const CollapsibleGroup = ({
32
36
  groupKey,
33
37
  indices,
@@ -35,21 +39,21 @@ const CollapsibleGroup = ({
35
39
  themes,
36
40
  forceOpen = false,
37
41
  compactWhileRunning = false,
42
+ mountHidden = false,
38
43
  headerActions,
39
44
  groupingFunction: groupingFunction2,
40
45
  className
41
46
  }) => {
42
- var _a;
43
47
  const panelId = react.useId();
44
48
  const isMessageRunning = react$1.useAuiState((s) => {
45
- var _a2;
46
- return ((_a2 = s.message.status) == null ? void 0 : _a2.type) === "running";
49
+ var _a;
50
+ return ((_a = s.message.status) == null ? void 0 : _a.type) === "running";
47
51
  });
48
52
  const messageContent = react$1.useAuiState(
49
53
  (s) => s.message.role === "assistant" ? s.message.content : void 0
50
54
  );
51
- const contextGrouping = (_a = react.useContext(SuperGroupingContext)) == null ? void 0 : _a.groupingFunction;
52
- const grouping = groupingFunction2 ?? contextGrouping ?? DEFAULT_GROUPING;
55
+ const superGrouping = react.useContext(SuperGroupingContext);
56
+ const grouping = groupingFunction2 ?? (superGrouping == null ? void 0 : superGrouping.groupingFunction) ?? DEFAULT_GROUPING;
53
57
  const lastIndex = indices.at(-1);
54
58
  const isLastGroup = react.useMemo(() => {
55
59
  if (!messageContent) return false;
@@ -59,8 +63,8 @@ const CollapsibleGroup = ({
59
63
  const toolCallCount = react.useMemo(
60
64
  () => messageContent ? indices.reduce(
61
65
  (count, index) => {
62
- var _a2;
63
- return ((_a2 = messageContent[index]) == null ? void 0 : _a2.type) === "tool-call" ? count + 1 : count;
66
+ var _a;
67
+ return ((_a = messageContent[index]) == null ? void 0 : _a.type) === "tool-call" ? count + 1 : count;
64
68
  },
65
69
  0
66
70
  ) : 0,
@@ -73,6 +77,9 @@ const CollapsibleGroup = ({
73
77
  const [userOpened, setOpen] = react.useState();
74
78
  const autoOpen = isLastGroup && isMessageRunning && !compactWhileRunning;
75
79
  const open = userOpened ?? (autoOpen || forceOpen);
80
+ const mountToolUIs = open || isMessageRunning || mountHidden || (superGrouping == null ? void 0 : superGrouping.mountHidden) === true;
81
+ const [toolUIsMounted, setToolUIsMounted] = react.useState(mountToolUIs);
82
+ if (mountToolUIs && !toolUIsMounted) setToolUIsMounted(true);
76
83
  if (!groupKey || indices.length <= 1) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
77
84
  const toggle = () => setOpen(!open);
78
85
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -144,7 +151,7 @@ const CollapsibleGroup = ({
144
151
  open ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
145
152
  ),
146
153
  style: { borderColor: `${theme.color}33` },
147
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-2 px-5 py-4", children }) })
154
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(GroupToolUIsMountedContext.Provider, { value: toolUIsMounted, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-2 px-5 py-4", children }) }) })
148
155
  }
149
156
  )
150
157
  ]
@@ -153,6 +160,7 @@ const CollapsibleGroup = ({
153
160
  };
154
161
  exports.CollapsibleGroup = CollapsibleGroup;
155
162
  exports.DEFAULT_TOOLKIT_THEME = DEFAULT_TOOLKIT_THEME;
163
+ exports.GroupToolUI = GroupToolUI;
156
164
  exports.SuperGroupingProvider = SuperGroupingProvider;
157
165
  exports.TOOLKIT_THEMES = TOOLKIT_THEMES;
158
166
  //# sourceMappingURL=CollapsibleGroup.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"CollapsibleGroup.cjs","sources":["../../../src/chat/super-grouping/CollapsibleGroup.tsx"],"sourcesContent":["import { useAuiState } from '@assistant-ui/react';\nimport { ChevronDown, ChevronUp } from 'lucide-react';\nimport type { ReactNode } from 'react';\nimport { createContext, useContext, useId, useMemo, useState } from 'react';\nimport { cn } from '../../lib/utils';\nimport { createToolBasedGroupingFunction, type GroupingFunction } from './grouping-function';\nimport { defaultGetToolGroupKey } from './toolkit-map';\n\n/**\n * Per-toolkit theme: the brand color that tints the group header, and an\n * optional icon URL a host serves (Olympus ships toolkit SVGs; the SDK does\n * not bundle images, so it tints only).\n */\nexport type ToolkitTheme = {\n /** Hex brand color for this toolkit. */\n color: string;\n /** Absolute or root-relative URL of the toolkit icon, when the host has one. */\n icon?: string;\n};\n\n/** Brand colors keyed by toolkit display name — the group keys the grouping function emits. */\nexport const TOOLKIT_THEMES: Record<string, ToolkitTheme> = {\n Web: { color: '#7BCCFA' },\n Document: { color: '#4586F9' },\n 'Document (Word)': { color: '#4586F9' },\n Notebook: { color: '#FFAB00' },\n Python: { color: '#00A76F' },\n Spreadsheet: { color: '#00A76F' },\n Email: { color: '#B584FF' },\n AOP: { color: '#336DFF' },\n App: { color: '#7336F5' },\n Presentation: { color: '#F56B36' },\n 'User Interface': { color: '#7336F5' },\n Canvas: { color: '#002542' },\n Computer: { color: '#3E0042' },\n Database: { color: '#7336F5' },\n 'Chrome Browser': { color: '#4285F4' },\n};\n\nexport const DEFAULT_TOOLKIT_THEME: ToolkitTheme = { color: '#919EAB' };\n\nconst DEFAULT_GROUPING = createToolBasedGroupingFunction(defaultGetToolGroupKey);\n\n/**\n * The grouping in force for the message being rendered. `SuperGroupingCard`\n * provides it so each `CollapsibleGroup` can tell whether it is the last\n * grouped block (the one that opens by default while the run is live) without\n * being handed the function as a prop through `Unstable_PartsGrouped`.\n */\nconst SuperGroupingContext = createContext<{ groupingFunction: GroupingFunction } | null>(null);\n\nexport const SuperGroupingProvider = SuperGroupingContext.Provider;\n\nexport interface CollapsibleGroupProps {\n /** Group key from the grouping function (toolkit display name); `undefined` for ungrouped parts. */\n groupKey: string | undefined;\n /** Indices of the parts belonging to this group. */\n indices: number[];\n children?: ReactNode;\n /** Host toolkit themes (colors, icons), merged over the built-in colors. */\n themes?: Record<string, ToolkitTheme>;\n /**\n * Keep the group open regardless of the run state — a host rule such as\n * \"an unsubmitted email draft stays visible\". The user's own toggle still\n * wins once used.\n */\n forceOpen?: boolean;\n /**\n * Do not open the live group by default (screenshot-heavy browser-control\n * runs hold hundreds of actions); the user can still expand it.\n */\n compactWhileRunning?: boolean;\n /** Extra header controls, rendered as siblings of the toggle (never nested buttons). */\n headerActions?: ReactNode;\n /** The grouping used to find the last grouped block; defaults to the card's, then the SDK's. */\n groupingFunction?: GroupingFunction;\n className?: string;\n}\n\n/**\n * Per-toolkit collapsible block inside the grouped details panel — \"Using\n * Spreadsheet Toolkit · 4 tool calls\" — used by the SDK, Olympus, and the\n * Chrome extension. The last grouped block opens by default while the message\n * is running and settles closed; the user's toggle is remembered for the\n * mounted block.\n *\n * Selector discipline: one raw `s.message.content` read; every per-group\n * derivation (grouping, counts) runs in `useMemo` off it, so the grouping\n * pass happens once per content change instead of once per selector pass.\n */\nexport const CollapsibleGroup = ({\n groupKey,\n indices,\n children,\n themes,\n forceOpen = false,\n compactWhileRunning = false,\n headerActions,\n groupingFunction,\n className,\n}: CollapsibleGroupProps) => {\n const panelId = useId();\n const isMessageRunning = useAuiState((s) => s.message.status?.type === 'running');\n const messageContent = useAuiState((s) =>\n s.message.role === 'assistant' ? s.message.content : undefined,\n );\n const contextGrouping = useContext(SuperGroupingContext)?.groupingFunction;\n const grouping = groupingFunction ?? contextGrouping ?? DEFAULT_GROUPING;\n\n const lastIndex = indices.at(-1);\n const isLastGroup = useMemo(() => {\n if (!messageContent) return false;\n const lastGroup = grouping(messageContent)\n .filter((group) => group.indices.length > 1)\n .at(-1);\n return lastGroup?.indices.at(-1) === lastIndex;\n }, [messageContent, grouping, lastIndex]);\n\n const toolCallCount = useMemo(\n () =>\n messageContent\n ? indices.reduce(\n (count, index) => (messageContent[index]?.type === 'tool-call' ? count + 1 : count),\n 0,\n )\n : 0,\n [messageContent, indices],\n );\n\n const theme = useMemo<ToolkitTheme>(\n () => (groupKey ? { ...TOOLKIT_THEMES, ...themes }[groupKey] ?? DEFAULT_TOOLKIT_THEME : DEFAULT_TOOLKIT_THEME),\n [groupKey, themes],\n );\n\n const [userOpened, setOpen] = useState<boolean>();\n const autoOpen = isLastGroup && isMessageRunning && !compactWhileRunning;\n const open = userOpened ?? (autoOpen || forceOpen);\n\n if (!groupKey || indices.length <= 1) return <>{children}</>;\n\n const toggle = () => setOpen(!open);\n\n return (\n <div\n data-slot=\"collapsible-group\"\n data-group-key={groupKey}\n className={cn('my-4 rounded-2xl border shadow-sm', className)}\n style={{\n borderColor: `${theme.color}33`,\n background: `linear-gradient(to right, ${theme.color}0d, transparent, ${theme.color}0d)`,\n }}\n >\n <div className=\"flex w-full items-center rounded-2xl\">\n <button\n type=\"button\"\n onClick={toggle}\n aria-expanded={open}\n aria-controls={panelId}\n className=\"flex min-w-0 flex-1 items-center justify-between gap-3 rounded-2xl px-5 py-3 text-left transition-colors hover:bg-black/5 dark:hover:bg-white/5\"\n >\n <span className=\"flex min-w-0 items-center gap-3 text-foreground/90\">\n {theme.icon ? (\n <img src={theme.icon} alt=\"\" aria-hidden=\"true\" className=\"size-5 shrink-0\" />\n ) : (\n <span\n className=\"inline-block size-2 shrink-0 rounded-full\"\n style={{ backgroundColor: theme.color }}\n aria-hidden=\"true\"\n />\n )}\n <span className=\"truncate text-sm font-semibold\">\n {isMessageRunning ? 'Using ' : 'Used '}\n {groupKey} Toolkit\n </span>\n </span>\n <span className=\"shrink-0 text-xs font-medium text-muted-foreground\">\n {toolCallCount} tool {toolCallCount === 1 ? 'call' : 'calls'}\n </span>\n </button>\n <div className=\"flex shrink-0 items-center gap-1 pr-3 pl-1\">\n {headerActions}\n <button\n type=\"button\"\n onClick={toggle}\n aria-label={open ? 'Collapse toolkit group' : 'Expand toolkit group'}\n aria-expanded={open}\n aria-controls={panelId}\n className=\"flex items-center justify-center rounded-md p-1 text-muted-foreground transition-colors hover:bg-black/5 dark:hover:bg-white/5\"\n >\n {open ? <ChevronUp size={16} /> : <ChevronDown size={16} />}\n </button>\n </div>\n </div>\n\n <div\n id={panelId}\n className={cn(\n 'grid overflow-hidden border-t transition-[grid-template-rows] duration-200 ease-in-out motion-reduce:transition-none',\n open ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]',\n )}\n style={{ borderColor: `${theme.color}33` }}\n >\n <div className=\"overflow-hidden\">\n <div className=\"flex flex-col gap-2 px-5 py-4\">{children}</div>\n </div>\n </div>\n </div>\n );\n};\n"],"names":["createToolBasedGroupingFunction","defaultGetToolGroupKey","createContext","groupingFunction","useId","useAuiState","_a","useContext","useMemo","useState","jsxs","cn","jsx","ChevronUp","ChevronDown"],"mappings":"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"}
1
+ {"version":3,"file":"CollapsibleGroup.cjs","sources":["../../../src/chat/super-grouping/CollapsibleGroup.tsx"],"sourcesContent":["import { useAuiState } from '@assistant-ui/react';\nimport { ChevronDown, ChevronUp } from 'lucide-react';\nimport type { ReactNode } from 'react';\nimport { createContext, useContext, useId, useMemo, useState } from 'react';\nimport { cn } from '../../lib/utils';\nimport { createToolBasedGroupingFunction, type GroupingFunction } from './grouping-function';\nimport { defaultGetToolGroupKey } from './toolkit-map';\n\n/**\n * Per-toolkit theme: the brand color that tints the group header, and an\n * optional icon URL a host serves (Olympus ships toolkit SVGs; the SDK does\n * not bundle images, so it tints only).\n */\nexport type ToolkitTheme = {\n /** Hex brand color for this toolkit. */\n color: string;\n /** Absolute or root-relative URL of the toolkit icon, when the host has one. */\n icon?: string;\n};\n\n/** Brand colors keyed by toolkit display name — the group keys the grouping function emits. */\nexport const TOOLKIT_THEMES: Record<string, ToolkitTheme> = {\n Web: { color: '#7BCCFA' },\n Document: { color: '#4586F9' },\n 'Document (Word)': { color: '#4586F9' },\n Notebook: { color: '#FFAB00' },\n Python: { color: '#00A76F' },\n Spreadsheet: { color: '#00A76F' },\n Email: { color: '#B584FF' },\n AOP: { color: '#336DFF' },\n App: { color: '#7336F5' },\n Presentation: { color: '#F56B36' },\n 'User Interface': { color: '#7336F5' },\n Canvas: { color: '#002542' },\n Computer: { color: '#3E0042' },\n Database: { color: '#7336F5' },\n 'Chrome Browser': { color: '#4285F4' },\n};\n\nexport const DEFAULT_TOOLKIT_THEME: ToolkitTheme = { color: '#919EAB' };\n\nconst DEFAULT_GROUPING = createToolBasedGroupingFunction(defaultGetToolGroupKey);\n\n/**\n * The grouping in force for the message being rendered. `SuperGroupingCard`\n * provides it so each `CollapsibleGroup` can tell whether it is the last\n * grouped block (the one that opens by default while the run is live) without\n * being handed the function as a prop through `GroupedMessageParts`, and\n * whether the card keeps its tool UIs mounted while hidden (`mountHidden`:\n * its turn is active, or a deep link targets it).\n */\nconst SuperGroupingContext = createContext<{\n groupingFunction: GroupingFunction;\n mountHidden?: boolean;\n} | null>(null);\n\nexport const SuperGroupingProvider = SuperGroupingContext.Provider;\n\n/** Whether the group a tool UI renders in has mounted its tool UIs; outside a group, always. */\nconst GroupToolUIsMountedContext = createContext(true);\n\n/**\n * A tool call's UI inside a `CollapsibleGroup`: rendered once the group first\n * needs its tool UIs (see `CollapsibleGroup`). `GroupedMessageParts` renders\n * every tool call through it.\n */\nexport function GroupToolUI({ children }: { children: ReactNode }) {\n return useContext(GroupToolUIsMountedContext) ? <>{children}</> : null;\n}\n\nexport interface CollapsibleGroupProps {\n /** Group key from the grouping function (toolkit display name); `undefined` for ungrouped parts. */\n groupKey: string | undefined;\n /** Indices of the parts belonging to this group. */\n indices: number[];\n children?: ReactNode;\n /** Host toolkit themes (colors, icons), merged over the built-in colors. */\n themes?: Record<string, ToolkitTheme>;\n /**\n * Keep the group open regardless of the run state — a host rule such as\n * \"an unsubmitted email draft stays visible\". The user's own toggle still\n * wins once used.\n */\n forceOpen?: boolean;\n /**\n * Do not open the live group by default (screenshot-heavy browser-control\n * runs hold hundreds of actions); the user can still expand it.\n */\n compactWhileRunning?: boolean;\n /**\n * Mount the tool UIs while collapsed (hidden), so a host that needs to find\n * one — a deep link into one of its tool calls — can. The card a group\n * renders in asks the same while its turn is active or its `mountDetails` is\n * set.\n */\n mountHidden?: boolean;\n /** Extra header controls, rendered as siblings of the toggle (never nested buttons). */\n headerActions?: ReactNode;\n /** The grouping used to find the last grouped block; defaults to the card's, then the SDK's. */\n groupingFunction?: GroupingFunction;\n className?: string;\n}\n\n/**\n * Per-toolkit collapsible block inside the grouped details panel — \"Using\n * Spreadsheet Toolkit · 4 tool calls\" — used by the SDK, Olympus, and the\n * Chrome extension. The last grouped block opens by default while the message\n * is running and settles closed; the user's toggle is remembered for the\n * mounted block.\n *\n * The group's tool UIs (the tool calls `GroupedMessageParts` renders through\n * `GroupToolUI`) mount the first time they are needed — the group opens, its\n * turn is running (live tool UIs run their effects), or a host asks\n * (`mountHidden`) — and stay mounted after. A finished turn's collapsed groups\n * mount none of their tool UIs, which a long run holds hundreds of. The rest\n * of the body renders from the start: the agent's text between the calls is\n * part of the message that selections, in-page find and links into the\n * message count.\n *\n * Selector discipline: one raw `s.message.content` read; every per-group\n * derivation (grouping, counts) runs in `useMemo` off it, so the grouping\n * pass happens once per content change instead of once per selector pass.\n */\nexport const CollapsibleGroup = ({\n groupKey,\n indices,\n children,\n themes,\n forceOpen = false,\n compactWhileRunning = false,\n mountHidden = false,\n headerActions,\n groupingFunction,\n className,\n}: CollapsibleGroupProps) => {\n const panelId = useId();\n const isMessageRunning = useAuiState((s) => s.message.status?.type === 'running');\n const messageContent = useAuiState((s) =>\n s.message.role === 'assistant' ? s.message.content : undefined,\n );\n const superGrouping = useContext(SuperGroupingContext);\n const grouping = groupingFunction ?? superGrouping?.groupingFunction ?? DEFAULT_GROUPING;\n\n const lastIndex = indices.at(-1);\n const isLastGroup = useMemo(() => {\n if (!messageContent) return false;\n const lastGroup = grouping(messageContent)\n .filter((group) => group.indices.length > 1)\n .at(-1);\n return lastGroup?.indices.at(-1) === lastIndex;\n }, [messageContent, grouping, lastIndex]);\n\n const toolCallCount = useMemo(\n () =>\n messageContent\n ? indices.reduce(\n (count, index) => (messageContent[index]?.type === 'tool-call' ? count + 1 : count),\n 0,\n )\n : 0,\n [messageContent, indices],\n );\n\n const theme = useMemo<ToolkitTheme>(\n () => (groupKey ? { ...TOOLKIT_THEMES, ...themes }[groupKey] ?? DEFAULT_TOOLKIT_THEME : DEFAULT_TOOLKIT_THEME),\n [groupKey, themes],\n );\n\n const [userOpened, setOpen] = useState<boolean>();\n const autoOpen = isLastGroup && isMessageRunning && !compactWhileRunning;\n const open = userOpened ?? (autoOpen || forceOpen);\n\n const mountToolUIs =\n open || isMessageRunning || mountHidden || superGrouping?.mountHidden === true;\n const [toolUIsMounted, setToolUIsMounted] = useState(mountToolUIs);\n // Adjusting state during render is React's sanctioned derived-state pattern.\n if (mountToolUIs && !toolUIsMounted) setToolUIsMounted(true);\n\n if (!groupKey || indices.length <= 1) return <>{children}</>;\n\n const toggle = () => setOpen(!open);\n\n return (\n <div\n data-slot=\"collapsible-group\"\n data-group-key={groupKey}\n className={cn('my-4 rounded-2xl border shadow-sm', className)}\n style={{\n borderColor: `${theme.color}33`,\n background: `linear-gradient(to right, ${theme.color}0d, transparent, ${theme.color}0d)`,\n }}\n >\n <div className=\"flex w-full items-center rounded-2xl\">\n <button\n type=\"button\"\n onClick={toggle}\n aria-expanded={open}\n aria-controls={panelId}\n className=\"flex min-w-0 flex-1 items-center justify-between gap-3 rounded-2xl px-5 py-3 text-left transition-colors hover:bg-black/5 dark:hover:bg-white/5\"\n >\n <span className=\"flex min-w-0 items-center gap-3 text-foreground/90\">\n {theme.icon ? (\n <img src={theme.icon} alt=\"\" aria-hidden=\"true\" className=\"size-5 shrink-0\" />\n ) : (\n <span\n className=\"inline-block size-2 shrink-0 rounded-full\"\n style={{ backgroundColor: theme.color }}\n aria-hidden=\"true\"\n />\n )}\n <span className=\"truncate text-sm font-semibold\">\n {isMessageRunning ? 'Using ' : 'Used '}\n {groupKey} Toolkit\n </span>\n </span>\n <span className=\"shrink-0 text-xs font-medium text-muted-foreground\">\n {toolCallCount} tool {toolCallCount === 1 ? 'call' : 'calls'}\n </span>\n </button>\n <div className=\"flex shrink-0 items-center gap-1 pr-3 pl-1\">\n {headerActions}\n <button\n type=\"button\"\n onClick={toggle}\n aria-label={open ? 'Collapse toolkit group' : 'Expand toolkit group'}\n aria-expanded={open}\n aria-controls={panelId}\n className=\"flex items-center justify-center rounded-md p-1 text-muted-foreground transition-colors hover:bg-black/5 dark:hover:bg-white/5\"\n >\n {open ? <ChevronUp size={16} /> : <ChevronDown size={16} />}\n </button>\n </div>\n </div>\n\n <div\n id={panelId}\n className={cn(\n 'grid overflow-hidden border-t transition-[grid-template-rows] duration-200 ease-in-out motion-reduce:transition-none',\n open ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]',\n )}\n style={{ borderColor: `${theme.color}33` }}\n >\n <div className=\"overflow-hidden\">\n <GroupToolUIsMountedContext.Provider value={toolUIsMounted}>\n <div className=\"flex flex-col gap-2 px-5 py-4\">{children}</div>\n </GroupToolUIsMountedContext.Provider>\n </div>\n </div>\n </div>\n );\n};\n"],"names":["createToolBasedGroupingFunction","defaultGetToolGroupKey","createContext","useContext","jsx","Fragment","groupingFunction","useId","useAuiState","useMemo","useState","jsxs","cn","ChevronUp","ChevronDown"],"mappings":";;;;;;;;;AAqBO,MAAM,iBAA+C;AAAA,EAC1D,KAAK,EAAE,OAAO,UAAA;AAAA,EACd,UAAU,EAAE,OAAO,UAAA;AAAA,EACnB,mBAAmB,EAAE,OAAO,UAAA;AAAA,EAC5B,UAAU,EAAE,OAAO,UAAA;AAAA,EACnB,QAAQ,EAAE,OAAO,UAAA;AAAA,EACjB,aAAa,EAAE,OAAO,UAAA;AAAA,EACtB,OAAO,EAAE,OAAO,UAAA;AAAA,EAChB,KAAK,EAAE,OAAO,UAAA;AAAA,EACd,KAAK,EAAE,OAAO,UAAA;AAAA,EACd,cAAc,EAAE,OAAO,UAAA;AAAA,EACvB,kBAAkB,EAAE,OAAO,UAAA;AAAA,EAC3B,QAAQ,EAAE,OAAO,UAAA;AAAA,EACjB,UAAU,EAAE,OAAO,UAAA;AAAA,EACnB,UAAU,EAAE,OAAO,UAAA;AAAA,EACnB,kBAAkB,EAAE,OAAO,UAAA;AAC7B;AAEO,MAAM,wBAAsC,EAAE,OAAO,UAAA;AAE5D,MAAM,mBAAmBA,iBAAAA,gCAAgCC,iCAAsB;AAU/E,MAAM,uBAAuBC,MAAAA,cAGnB,IAAI;AAEP,MAAM,wBAAwB,qBAAqB;AAG1D,MAAM,6BAA6BA,MAAAA,cAAc,IAAI;AAO9C,SAAS,YAAY,EAAE,YAAqC;AACjE,SAAOC,MAAAA,WAAW,0BAA0B,IAAIC,2BAAAA,IAAAC,WAAAA,UAAA,EAAG,UAAS,IAAM;AACpE;AAuDO,MAAM,mBAAmB,CAAC;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,sBAAsB;AAAA,EACtB,cAAc;AAAA,EACd;AAAA,EACA,kBAAAC;AAAA,EACA;AACF,MAA6B;AAC3B,QAAM,UAAUC,MAAAA,MAAA;AAChB,QAAM,mBAAmBC,QAAAA,YAAY,CAAC,MAAA;;AAAM,oBAAE,QAAQ,WAAV,mBAAkB,UAAS;AAAA,GAAS;AAChF,QAAM,iBAAiBA,QAAAA;AAAAA,IAAY,CAAC,MAClC,EAAE,QAAQ,SAAS,cAAc,EAAE,QAAQ,UAAU;AAAA,EAAA;AAEvD,QAAM,gBAAgBL,MAAAA,WAAW,oBAAoB;AACrD,QAAM,WAAWG,sBAAoB,+CAAe,qBAAoB;AAExE,QAAM,YAAY,QAAQ,GAAG,EAAE;AAC/B,QAAM,cAAcG,MAAAA,QAAQ,MAAM;AAChC,QAAI,CAAC,eAAgB,QAAO;AAC5B,UAAM,YAAY,SAAS,cAAc,EACtC,OAAO,CAAC,UAAU,MAAM,QAAQ,SAAS,CAAC,EAC1C,GAAG,EAAE;AACR,YAAO,uCAAW,QAAQ,GAAG,SAAQ;AAAA,EACvC,GAAG,CAAC,gBAAgB,UAAU,SAAS,CAAC;AAExC,QAAM,gBAAgBA,MAAAA;AAAAA,IACpB,MACE,iBACI,QAAQ;AAAA,MACN,CAAC,OAAO,UAAA;;AAAW,qCAAe,KAAK,MAApB,mBAAuB,UAAS,cAAc,QAAQ,IAAI;AAAA;AAAA,MAC7E;AAAA,IAAA,IAEF;AAAA,IACN,CAAC,gBAAgB,OAAO;AAAA,EAAA;AAG1B,QAAM,QAAQA,MAAAA;AAAAA,IACZ,MAAO,WAAW,EAAE,GAAG,gBAAgB,GAAG,OAAA,EAAS,QAAQ,KAAK,wBAAwB;AAAA,IACxF,CAAC,UAAU,MAAM;AAAA,EAAA;AAGnB,QAAM,CAAC,YAAY,OAAO,IAAIC,eAAA;AAC9B,QAAM,WAAW,eAAe,oBAAoB,CAAC;AACrD,QAAM,OAAO,eAAe,YAAY;AAExC,QAAM,eACJ,QAAQ,oBAAoB,gBAAe,+CAAe,iBAAgB;AAC5E,QAAM,CAAC,gBAAgB,iBAAiB,IAAIA,MAAAA,SAAS,YAAY;AAEjE,MAAI,gBAAgB,CAAC,eAAgB,mBAAkB,IAAI;AAE3D,MAAI,CAAC,YAAY,QAAQ,UAAU,EAAG,8DAAU,UAAS;AAEzD,QAAM,SAAS,MAAM,QAAQ,CAAC,IAAI;AAElC,SACEC,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,kBAAgB;AAAA,MAChB,WAAWC,OAAAA,GAAG,qCAAqC,SAAS;AAAA,MAC5D,OAAO;AAAA,QACL,aAAa,GAAG,MAAM,KAAK;AAAA,QAC3B,YAAY,6BAA6B,MAAM,KAAK,oBAAoB,MAAM,KAAK;AAAA,MAAA;AAAA,MAGrF,UAAA;AAAA,QAAAD,2BAAAA,KAAC,OAAA,EAAI,WAAU,wCACb,UAAA;AAAA,UAAAA,2BAAAA;AAAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS;AAAA,cACT,iBAAe;AAAA,cACf,iBAAe;AAAA,cACf,WAAU;AAAA,cAEV,UAAA;AAAA,gBAAAA,2BAAAA,KAAC,QAAA,EAAK,WAAU,sDACb,UAAA;AAAA,kBAAA,MAAM,OACLP,2BAAAA,IAAC,OAAA,EAAI,KAAK,MAAM,MAAM,KAAI,IAAG,eAAY,QAAO,WAAU,kBAAA,CAAkB,IAE5EA,2BAAAA;AAAAA,oBAAC;AAAA,oBAAA;AAAA,sBACC,WAAU;AAAA,sBACV,OAAO,EAAE,iBAAiB,MAAM,MAAA;AAAA,sBAChC,eAAY;AAAA,oBAAA;AAAA,kBAAA;AAAA,kBAGhBO,2BAAAA,KAAC,QAAA,EAAK,WAAU,kCACb,UAAA;AAAA,oBAAA,mBAAmB,WAAW;AAAA,oBAC9B;AAAA,oBAAS;AAAA,kBAAA,EAAA,CACZ;AAAA,gBAAA,GACF;AAAA,gBACAA,2BAAAA,KAAC,QAAA,EAAK,WAAU,sDACb,UAAA;AAAA,kBAAA;AAAA,kBAAc;AAAA,kBAAO,kBAAkB,IAAI,SAAS;AAAA,gBAAA,EAAA,CACvD;AAAA,cAAA;AAAA,YAAA;AAAA,UAAA;AAAA,UAEFA,2BAAAA,KAAC,OAAA,EAAI,WAAU,8CACZ,UAAA;AAAA,YAAA;AAAA,YACDP,2BAAAA;AAAAA,cAAC;AAAA,cAAA;AAAA,gBACC,MAAK;AAAA,gBACL,SAAS;AAAA,gBACT,cAAY,OAAO,2BAA2B;AAAA,gBAC9C,iBAAe;AAAA,gBACf,iBAAe;AAAA,gBACf,WAAU;AAAA,gBAET,UAAA,sCAAQS,YAAAA,WAAA,EAAU,MAAM,IAAI,IAAKT,2BAAAA,IAACU,YAAAA,aAAA,EAAY,MAAM,GAAA,CAAI;AAAA,cAAA;AAAA,YAAA;AAAA,UAC3D,EAAA,CACF;AAAA,QAAA,GACF;AAAA,QAEAV,2BAAAA;AAAAA,UAAC;AAAA,UAAA;AAAA,YACC,IAAI;AAAA,YACJ,WAAWQ,OAAAA;AAAAA,cACT;AAAA,cACA,OAAO,oBAAoB;AAAA,YAAA;AAAA,YAE7B,OAAO,EAAE,aAAa,GAAG,MAAM,KAAK,KAAA;AAAA,YAEpC,yCAAC,OAAA,EAAI,WAAU,mBACb,UAAAR,2BAAAA,IAAC,2BAA2B,UAA3B,EAAoC,OAAO,gBAC1C,yCAAC,OAAA,EAAI,WAAU,iCAAiC,UAAS,GAC3D,EAAA,CACF;AAAA,UAAA;AAAA,QAAA;AAAA,MACF;AAAA,IAAA;AAAA,EAAA;AAGN;;;;;;"}
@@ -16,7 +16,16 @@ export declare const TOOLKIT_THEMES: Record<string, ToolkitTheme>;
16
16
  export declare const DEFAULT_TOOLKIT_THEME: ToolkitTheme;
17
17
  export declare const SuperGroupingProvider: import("react").Provider<{
18
18
  groupingFunction: GroupingFunction;
19
+ mountHidden?: boolean;
19
20
  } | null>;
21
+ /**
22
+ * A tool call's UI inside a `CollapsibleGroup`: rendered once the group first
23
+ * needs its tool UIs (see `CollapsibleGroup`). `GroupedMessageParts` renders
24
+ * every tool call through it.
25
+ */
26
+ export declare function GroupToolUI({ children }: {
27
+ children: ReactNode;
28
+ }): import("react").JSX.Element | null;
20
29
  export interface CollapsibleGroupProps {
21
30
  /** Group key from the grouping function (toolkit display name); `undefined` for ungrouped parts. */
22
31
  groupKey: string | undefined;
@@ -36,6 +45,13 @@ export interface CollapsibleGroupProps {
36
45
  * runs hold hundreds of actions); the user can still expand it.
37
46
  */
38
47
  compactWhileRunning?: boolean;
48
+ /**
49
+ * Mount the tool UIs while collapsed (hidden), so a host that needs to find
50
+ * one — a deep link into one of its tool calls — can. The card a group
51
+ * renders in asks the same while its turn is active or its `mountDetails` is
52
+ * set.
53
+ */
54
+ mountHidden?: boolean;
39
55
  /** Extra header controls, rendered as siblings of the toggle (never nested buttons). */
40
56
  headerActions?: ReactNode;
41
57
  /** The grouping used to find the last grouped block; defaults to the card's, then the SDK's. */
@@ -49,8 +65,17 @@ export interface CollapsibleGroupProps {
49
65
  * is running and settles closed; the user's toggle is remembered for the
50
66
  * mounted block.
51
67
  *
68
+ * The group's tool UIs (the tool calls `GroupedMessageParts` renders through
69
+ * `GroupToolUI`) mount the first time they are needed — the group opens, its
70
+ * turn is running (live tool UIs run their effects), or a host asks
71
+ * (`mountHidden`) — and stay mounted after. A finished turn's collapsed groups
72
+ * mount none of their tool UIs, which a long run holds hundreds of. The rest
73
+ * of the body renders from the start: the agent's text between the calls is
74
+ * part of the message that selections, in-page find and links into the
75
+ * message count.
76
+ *
52
77
  * Selector discipline: one raw `s.message.content` read; every per-group
53
78
  * derivation (grouping, counts) runs in `useMemo` off it, so the grouping
54
79
  * pass happens once per content change instead of once per selector pass.
55
80
  */
56
- export declare const CollapsibleGroup: ({ groupKey, indices, children, themes, forceOpen, compactWhileRunning, headerActions, groupingFunction, className, }: CollapsibleGroupProps) => import("react").JSX.Element;
81
+ export declare const CollapsibleGroup: ({ groupKey, indices, children, themes, forceOpen, compactWhileRunning, mountHidden, headerActions, groupingFunction, className, }: CollapsibleGroupProps) => import("react").JSX.Element;
@@ -26,6 +26,10 @@ const DEFAULT_TOOLKIT_THEME = { color: "#919EAB" };
26
26
  const DEFAULT_GROUPING = createToolBasedGroupingFunction(defaultGetToolGroupKey);
27
27
  const SuperGroupingContext = createContext(null);
28
28
  const SuperGroupingProvider = SuperGroupingContext.Provider;
29
+ const GroupToolUIsMountedContext = createContext(true);
30
+ function GroupToolUI({ children }) {
31
+ return useContext(GroupToolUIsMountedContext) ? /* @__PURE__ */ jsx(Fragment, { children }) : null;
32
+ }
29
33
  const CollapsibleGroup = ({
30
34
  groupKey,
31
35
  indices,
@@ -33,21 +37,21 @@ const CollapsibleGroup = ({
33
37
  themes,
34
38
  forceOpen = false,
35
39
  compactWhileRunning = false,
40
+ mountHidden = false,
36
41
  headerActions,
37
42
  groupingFunction,
38
43
  className
39
44
  }) => {
40
- var _a;
41
45
  const panelId = useId();
42
46
  const isMessageRunning = useAuiState((s) => {
43
- var _a2;
44
- return ((_a2 = s.message.status) == null ? void 0 : _a2.type) === "running";
47
+ var _a;
48
+ return ((_a = s.message.status) == null ? void 0 : _a.type) === "running";
45
49
  });
46
50
  const messageContent = useAuiState(
47
51
  (s) => s.message.role === "assistant" ? s.message.content : void 0
48
52
  );
49
- const contextGrouping = (_a = useContext(SuperGroupingContext)) == null ? void 0 : _a.groupingFunction;
50
- const grouping = groupingFunction ?? contextGrouping ?? DEFAULT_GROUPING;
53
+ const superGrouping = useContext(SuperGroupingContext);
54
+ const grouping = groupingFunction ?? (superGrouping == null ? void 0 : superGrouping.groupingFunction) ?? DEFAULT_GROUPING;
51
55
  const lastIndex = indices.at(-1);
52
56
  const isLastGroup = useMemo(() => {
53
57
  if (!messageContent) return false;
@@ -57,8 +61,8 @@ const CollapsibleGroup = ({
57
61
  const toolCallCount = useMemo(
58
62
  () => messageContent ? indices.reduce(
59
63
  (count, index) => {
60
- var _a2;
61
- return ((_a2 = messageContent[index]) == null ? void 0 : _a2.type) === "tool-call" ? count + 1 : count;
64
+ var _a;
65
+ return ((_a = messageContent[index]) == null ? void 0 : _a.type) === "tool-call" ? count + 1 : count;
62
66
  },
63
67
  0
64
68
  ) : 0,
@@ -71,6 +75,9 @@ const CollapsibleGroup = ({
71
75
  const [userOpened, setOpen] = useState();
72
76
  const autoOpen = isLastGroup && isMessageRunning && !compactWhileRunning;
73
77
  const open = userOpened ?? (autoOpen || forceOpen);
78
+ const mountToolUIs = open || isMessageRunning || mountHidden || (superGrouping == null ? void 0 : superGrouping.mountHidden) === true;
79
+ const [toolUIsMounted, setToolUIsMounted] = useState(mountToolUIs);
80
+ if (mountToolUIs && !toolUIsMounted) setToolUIsMounted(true);
74
81
  if (!groupKey || indices.length <= 1) return /* @__PURE__ */ jsx(Fragment, { children });
75
82
  const toggle = () => setOpen(!open);
76
83
  return /* @__PURE__ */ jsxs(
@@ -142,7 +149,7 @@ const CollapsibleGroup = ({
142
149
  open ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
143
150
  ),
144
151
  style: { borderColor: `${theme.color}33` },
145
- children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 px-5 py-4", children }) })
152
+ children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsx(GroupToolUIsMountedContext.Provider, { value: toolUIsMounted, children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 px-5 py-4", children }) }) })
146
153
  }
147
154
  )
148
155
  ]
@@ -152,6 +159,7 @@ const CollapsibleGroup = ({
152
159
  export {
153
160
  CollapsibleGroup,
154
161
  DEFAULT_TOOLKIT_THEME,
162
+ GroupToolUI,
155
163
  SuperGroupingProvider,
156
164
  TOOLKIT_THEMES
157
165
  };