@athenaintel/react 0.32.0 → 0.36.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 (167) hide show
  1. package/README.md +148 -9
  2. package/dist/agent-runtime/src/composer-markdown.cjs.map +1 -1
  3. package/dist/agent-runtime/src/composer-markdown.js.map +1 -1
  4. package/dist/agent-runtime/src/deep-agent-transport-recipe.cjs +2 -3
  5. package/dist/agent-runtime/src/deep-agent-transport-recipe.cjs.map +1 -1
  6. package/dist/agent-runtime/src/deep-agent-transport-recipe.js +3 -4
  7. package/dist/agent-runtime/src/deep-agent-transport-recipe.js.map +1 -1
  8. package/dist/agent-runtime/src/edit-command.cjs.map +1 -1
  9. package/dist/agent-runtime/src/edit-command.js.map +1 -1
  10. package/dist/agent-runtime/src/generated/runconfig-contract.cjs +4 -0
  11. package/dist/agent-runtime/src/generated/runconfig-contract.cjs.map +1 -1
  12. package/dist/agent-runtime/src/generated/runconfig-contract.js +4 -0
  13. package/dist/agent-runtime/src/generated/runconfig-contract.js.map +1 -1
  14. package/dist/agent-runtime/src/messages.cjs +57 -2
  15. package/dist/agent-runtime/src/messages.cjs.map +1 -1
  16. package/dist/agent-runtime/src/messages.js +57 -2
  17. package/dist/agent-runtime/src/messages.js.map +1 -1
  18. package/dist/agent-runtime/src/quota-safe-local-storage-session.cjs +24 -3
  19. package/dist/agent-runtime/src/quota-safe-local-storage-session.cjs.map +1 -1
  20. package/dist/agent-runtime/src/quota-safe-local-storage-session.js +24 -3
  21. package/dist/agent-runtime/src/quota-safe-local-storage-session.js.map +1 -1
  22. package/dist/agent-runtime/src/statewire-transport.cjs +4 -13
  23. package/dist/agent-runtime/src/statewire-transport.cjs.map +1 -1
  24. package/dist/agent-runtime/src/statewire-transport.js +5 -14
  25. package/dist/agent-runtime/src/statewire-transport.js.map +1 -1
  26. package/dist/agent-runtime/src/tool-metadata.cjs +12 -1
  27. package/dist/agent-runtime/src/tool-metadata.cjs.map +1 -1
  28. package/dist/agent-runtime/src/tool-metadata.js +12 -1
  29. package/dist/agent-runtime/src/tool-metadata.js.map +1 -1
  30. package/dist/agent-runtime/src/use-deep-agent-thread.cjs.map +1 -1
  31. package/dist/agent-runtime/src/use-deep-agent-thread.js.map +1 -1
  32. package/dist/assets/AthenaAssetEmbed.cjs +3 -1
  33. package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
  34. package/dist/assets/AthenaAssetEmbed.d.ts +7 -0
  35. package/dist/assets/AthenaAssetEmbed.js +4 -2
  36. package/dist/assets/AthenaAssetEmbed.js.map +1 -1
  37. package/dist/assets/use-asset-embed.cjs +71 -71
  38. package/dist/assets/use-asset-embed.cjs.map +1 -1
  39. package/dist/assets/use-asset-embed.d.ts +20 -7
  40. package/dist/assets/use-asset-embed.js +72 -72
  41. package/dist/assets/use-asset-embed.js.map +1 -1
  42. package/dist/catalog/agent-mode-menu.cjs +115 -0
  43. package/dist/catalog/agent-mode-menu.cjs.map +1 -0
  44. package/dist/catalog/agent-mode-menu.d.ts +101 -0
  45. package/dist/catalog/agent-mode-menu.js +115 -0
  46. package/dist/catalog/agent-mode-menu.js.map +1 -0
  47. package/dist/catalog/agent-model-options.cjs +122 -0
  48. package/dist/catalog/agent-model-options.cjs.map +1 -0
  49. package/dist/catalog/agent-model-options.d.ts +115 -0
  50. package/dist/catalog/agent-model-options.js +122 -0
  51. package/dist/catalog/agent-model-options.js.map +1 -0
  52. package/dist/catalog/chat-pickers.cjs +235 -4
  53. package/dist/catalog/chat-pickers.cjs.map +1 -1
  54. package/dist/catalog/chat-pickers.d.ts +55 -3
  55. package/dist/catalog/chat-pickers.js +238 -7
  56. package/dist/catalog/chat-pickers.js.map +1 -1
  57. package/dist/catalog/runtime-catalog.cjs +121 -3
  58. package/dist/catalog/runtime-catalog.cjs.map +1 -1
  59. package/dist/catalog/runtime-catalog.d.ts +120 -0
  60. package/dist/catalog/runtime-catalog.js +124 -6
  61. package/dist/catalog/runtime-catalog.js.map +1 -1
  62. package/dist/catalog/use-athena-chat-mode.cjs +139 -0
  63. package/dist/catalog/use-athena-chat-mode.cjs.map +1 -0
  64. package/dist/catalog/use-athena-chat-mode.d.ts +43 -0
  65. package/dist/catalog/use-athena-chat-mode.js +139 -0
  66. package/dist/catalog/use-athena-chat-mode.js.map +1 -0
  67. package/dist/chat/AthenaChat.cjs +2 -0
  68. package/dist/chat/AthenaChat.cjs.map +1 -1
  69. package/dist/chat/AthenaChat.d.ts +8 -7
  70. package/dist/chat/AthenaChat.js +2 -0
  71. package/dist/chat/AthenaChat.js.map +1 -1
  72. package/dist/chat/super-grouping/SuperGroupingCard.cjs +30 -3
  73. package/dist/chat/super-grouping/SuperGroupingCard.cjs.map +1 -1
  74. package/dist/chat/super-grouping/SuperGroupingCard.d.ts +12 -9
  75. package/dist/chat/super-grouping/SuperGroupingCard.js +31 -4
  76. package/dist/chat/super-grouping/SuperGroupingCard.js.map +1 -1
  77. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs.map +1 -1
  78. package/dist/chat/super-grouping/SuperGroupingTranscript.d.ts +8 -8
  79. package/dist/chat/super-grouping/SuperGroupingTranscript.js.map +1 -1
  80. package/dist/chat/super-grouping/activity-timeline.cjs.map +1 -1
  81. package/dist/chat/super-grouping/activity-timeline.d.ts +4 -4
  82. package/dist/chat/super-grouping/activity-timeline.js.map +1 -1
  83. package/dist/chat/super-grouping/index.d.ts +6 -5
  84. package/dist/chat/super-grouping/toolkit-map.cjs +3 -1
  85. package/dist/chat/super-grouping/toolkit-map.cjs.map +1 -1
  86. package/dist/chat/super-grouping/toolkit-map.js +3 -1
  87. package/dist/chat/super-grouping/toolkit-map.js.map +1 -1
  88. package/dist/chat-ui/index.cjs +1 -0
  89. package/dist/chat-ui/index.cjs.map +1 -1
  90. package/dist/chat-ui/index.d.ts +1 -1
  91. package/dist/chat-ui/index.js +2 -1
  92. package/dist/chat-ui/mentions/suggestions/data/api-client.cjs +33 -2
  93. package/dist/chat-ui/mentions/suggestions/data/api-client.cjs.map +1 -1
  94. package/dist/chat-ui/mentions/suggestions/data/api-client.d.ts +14 -0
  95. package/dist/chat-ui/mentions/suggestions/data/api-client.js +33 -2
  96. package/dist/chat-ui/mentions/suggestions/data/api-client.js.map +1 -1
  97. package/dist/chat-ui/statewire.cjs +3 -0
  98. package/dist/chat-ui/statewire.cjs.map +1 -1
  99. package/dist/chat-ui/statewire.d.ts +4 -2
  100. package/dist/chat-ui/statewire.js +3 -0
  101. package/dist/chat-ui/statewire.js.map +1 -1
  102. package/dist/index.cjs +4 -0
  103. package/dist/index.cjs.map +1 -1
  104. package/dist/index.d.ts +7 -4
  105. package/dist/index.js +6 -2
  106. package/dist/index.js.map +1 -1
  107. package/dist/lib/use-athena-rest-query.cjs +4 -0
  108. package/dist/lib/use-athena-rest-query.cjs.map +1 -1
  109. package/dist/lib/use-athena-rest-query.d.ts +6 -0
  110. package/dist/lib/use-athena-rest-query.js +4 -0
  111. package/dist/lib/use-athena-rest-query.js.map +1 -1
  112. package/dist/provider/AthenaProvider.cjs +81 -9
  113. package/dist/provider/AthenaProvider.cjs.map +1 -1
  114. package/dist/provider/AthenaProvider.d.ts +25 -5
  115. package/dist/provider/AthenaProvider.js +83 -11
  116. package/dist/provider/AthenaProvider.js.map +1 -1
  117. package/dist/provider/agent-selection-context.cjs +6 -0
  118. package/dist/provider/agent-selection-context.cjs.map +1 -0
  119. package/dist/provider/agent-selection-context.d.ts +14 -0
  120. package/dist/provider/agent-selection-context.js +6 -0
  121. package/dist/provider/agent-selection-context.js.map +1 -0
  122. package/dist/provider/chat-mode-context.cjs +6 -0
  123. package/dist/provider/chat-mode-context.cjs.map +1 -0
  124. package/dist/provider/chat-mode-context.d.ts +35 -0
  125. package/dist/provider/chat-mode-context.js +6 -0
  126. package/dist/provider/chat-mode-context.js.map +1 -0
  127. package/dist/react/package.json.cjs +1 -1
  128. package/dist/react/package.json.js +1 -1
  129. package/dist/runtime/StatewireThreadConfigLaneSync.cjs +125 -0
  130. package/dist/runtime/StatewireThreadConfigLaneSync.cjs.map +1 -0
  131. package/dist/runtime/StatewireThreadConfigLaneSync.d.ts +54 -0
  132. package/dist/runtime/StatewireThreadConfigLaneSync.js +125 -0
  133. package/dist/runtime/StatewireThreadConfigLaneSync.js.map +1 -0
  134. package/dist/runtime/statewire-model-lane.cjs +0 -12
  135. package/dist/runtime/statewire-model-lane.cjs.map +1 -1
  136. package/dist/runtime/statewire-model-lane.d.ts +3 -1
  137. package/dist/runtime/statewire-model-lane.js +0 -12
  138. package/dist/runtime/statewire-model-lane.js.map +1 -1
  139. package/dist/runtime/statewire-run-config.cjs +10 -6
  140. package/dist/runtime/statewire-run-config.cjs.map +1 -1
  141. package/dist/runtime/statewire-run-config.d.ts +7 -0
  142. package/dist/runtime/statewire-run-config.js +10 -6
  143. package/dist/runtime/statewire-run-config.js.map +1 -1
  144. package/dist/runtime/statewire-server-lane-context.cjs.map +1 -1
  145. package/dist/runtime/statewire-server-lane-context.d.ts +2 -2
  146. package/dist/runtime/statewire-server-lane-context.js.map +1 -1
  147. package/dist/runtime/statewire-thread-config-lane.cjs +84 -0
  148. package/dist/runtime/statewire-thread-config-lane.cjs.map +1 -0
  149. package/dist/runtime/statewire-thread-config-lane.d.ts +94 -0
  150. package/dist/runtime/statewire-thread-config-lane.js +84 -0
  151. package/dist/runtime/statewire-thread-config-lane.js.map +1 -0
  152. package/dist/runtime/useAthenaStatewireRuntime.cjs +4 -2
  153. package/dist/runtime/useAthenaStatewireRuntime.cjs.map +1 -1
  154. package/dist/runtime/useAthenaStatewireRuntime.d.ts +2 -0
  155. package/dist/runtime/useAthenaStatewireRuntime.js +4 -2
  156. package/dist/runtime/useAthenaStatewireRuntime.js.map +1 -1
  157. package/dist/threads/use-athena-thread-model.cjs +11 -3
  158. package/dist/threads/use-athena-thread-model.cjs.map +1 -1
  159. package/dist/threads/use-athena-thread-model.d.ts +5 -0
  160. package/dist/threads/use-athena-thread-model.js +12 -4
  161. package/dist/threads/use-athena-thread-model.js.map +1 -1
  162. package/package.json +3 -3
  163. package/dist/runtime/StatewireModelLaneSync.cjs +0 -96
  164. package/dist/runtime/StatewireModelLaneSync.cjs.map +0 -1
  165. package/dist/runtime/StatewireModelLaneSync.d.ts +0 -45
  166. package/dist/runtime/StatewireModelLaneSync.js +0 -96
  167. package/dist/runtime/StatewireModelLaneSync.js.map +0 -1
@@ -1,22 +1,28 @@
1
- import { useState, useRef, useEffect } from "react";
1
+ import { useQuery, QueryClient, QueryClientContext } from "@tanstack/react-query";
2
+ import { useRef, useContext } from "react";
2
3
  import { deriveAthenaAppUrl } from "../lib/athena-urls.js";
4
+ import { keyPartFingerprint } from "../lib/use-athena-rest-query.js";
3
5
  import { sendWithCredentialRenewal } from "../runtime/credential-renewal.js";
4
6
  const DEFAULT_ASSET_EMBED_READ_ONLY = false;
7
+ const DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT = false;
5
8
  const DEFAULT_ASSET_EMBED_DISPLAY_MODE = "full";
6
9
  function buildAssetEmbedRequestBody({
7
10
  assetId,
8
11
  readOnly,
9
12
  expiresInSeconds,
10
- displayMode
13
+ displayMode,
14
+ allowQuickEdit = DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT
11
15
  }) {
12
16
  return {
13
17
  asset_id: assetId,
14
18
  read_only: readOnly,
15
19
  expires_in_seconds: expiresInSeconds,
16
- display_mode: displayMode
20
+ display_mode: displayMode,
21
+ // Sent only when asked: an agora that predates it is never handed an unknown field.
22
+ ...allowQuickEdit && { allow_quick_edit: true }
17
23
  };
18
24
  }
19
- const embedCache = /* @__PURE__ */ new Map();
25
+ const embedQueryClient = new QueryClient();
20
26
  const ATHENA_APP_HOSTNAMES = /* @__PURE__ */ new Set(["app.athenaintel.com", "staging-app.athenaintel.com"]);
21
27
  const ATHENA_PREVIEW_HOSTNAME_SUFFIX = ".previews.athenaintel.com";
22
28
  const ATHENA_WORKSPACE_HOSTNAME_PATTERN = /^.+--(\d+)\.(?:app\.athenaintel\.com|staging-app\.athenaintel\.com)$/;
@@ -292,92 +298,86 @@ function getAssetEmbedAuthContext({ token, apiKey }) {
292
298
  function useAssetEmbed(assetId, options = {
293
299
  backendUrl: ""
294
300
  }) {
301
+ var _a;
295
302
  const {
296
303
  readOnly = DEFAULT_ASSET_EMBED_READ_ONLY,
297
304
  expiresInSeconds = 60 * 60 * 24 * 30,
298
305
  displayMode = DEFAULT_ASSET_EMBED_DISPLAY_MODE,
306
+ allowQuickEdit = DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT,
299
307
  backendUrl,
300
308
  appUrl,
301
309
  apiKey,
302
310
  token,
303
311
  getToken
304
312
  } = options;
305
- const [embedUrl, setEmbedUrl] = useState(null);
306
- const [isLoading, setIsLoading] = useState(false);
307
- const [error, setError] = useState(null);
308
- const abortRef = useRef(null);
309
313
  const tokenRef = useRef(token);
310
314
  tokenRef.current = token;
311
315
  const getTokenRef = useRef(getToken);
312
316
  getTokenRef.current = getToken;
313
317
  const authContext = getAssetEmbedAuthContext({ token, apiKey });
314
- useEffect(() => {
315
- var _a;
316
- if (!assetId || !backendUrl) {
317
- setEmbedUrl(null);
318
- setIsLoading(false);
319
- setError(null);
320
- return;
321
- }
322
- const cacheKey = `${assetId}:${readOnly}:${displayMode}:${authContext}:${backendUrl}:${appUrl ?? ""}`;
323
- const cached = embedCache.get(cacheKey);
324
- if (cached && cached.expiresAt > Date.now() / 1e3) {
325
- setEmbedUrl(cached.url);
326
- setIsLoading(false);
327
- setError(null);
328
- return;
329
- }
330
- const apiBaseUrl = backendUrl.replace(/\/api\/assistant-ui\/?$/, "");
331
- const endpoint = `${apiBaseUrl}/api/embed/generate-token`;
332
- (_a = abortRef.current) == null ? void 0 : _a.abort();
333
- const controller = new AbortController();
334
- abortRef.current = controller;
335
- setIsLoading(true);
336
- setError(null);
337
- const heldToken = tokenRef.current;
338
- sendWithCredentialRenewal(
339
- { apiKey, token: heldToken, getToken: heldToken ? getTokenRef.current : void 0 },
340
- (headers) => fetch(endpoint, {
341
- method: "POST",
342
- headers: { "Content-Type": "application/json", ...headers },
343
- body: JSON.stringify(
344
- buildAssetEmbedRequestBody({
345
- assetId,
346
- readOnly,
347
- expiresInSeconds,
348
- displayMode
318
+ const enabled = Boolean(assetId && backendUrl);
319
+ const query = useQuery(
320
+ {
321
+ queryKey: [
322
+ "athena-sdk",
323
+ "asset-embed",
324
+ assetId,
325
+ readOnly,
326
+ displayMode,
327
+ allowQuickEdit,
328
+ // The identity, never the credential: an opaque token or a key is part of `authContext`.
329
+ keyPartFingerprint(authContext),
330
+ backendUrl,
331
+ appUrl ?? ""
332
+ ],
333
+ queryFn: async ({ signal }) => {
334
+ const apiBaseUrl = backendUrl.replace(/\/api\/assistant-ui\/?$/, "");
335
+ const heldToken = tokenRef.current;
336
+ const res = await sendWithCredentialRenewal(
337
+ { apiKey, token: heldToken, getToken: heldToken ? getTokenRef.current : void 0 },
338
+ (headers) => fetch(`${apiBaseUrl}/api/embed/generate-token`, {
339
+ method: "POST",
340
+ headers: { "Content-Type": "application/json", ...headers },
341
+ body: JSON.stringify(
342
+ buildAssetEmbedRequestBody({
343
+ assetId: assetId ?? "",
344
+ readOnly,
345
+ expiresInSeconds,
346
+ displayMode,
347
+ allowQuickEdit
348
+ })
349
+ ),
350
+ signal
349
351
  })
350
- ),
351
- signal: controller.signal
352
- })
353
- ).then(async (res) => {
354
- if (!res.ok) {
355
- const text = await res.text().catch(() => "");
356
- throw new Error(`Failed to generate embed token (${res.status}): ${text}`);
357
- }
358
- return res.json();
359
- }).then((data) => {
360
- const resolvedEmbedUrl = resolveAssetEmbedUrl({
361
- embedUrl: data.embed_url,
362
- appUrl,
363
- backendUrl
364
- });
365
- embedCache.set(cacheKey, {
366
- url: resolvedEmbedUrl,
367
- expiresAt: data.expires_at
368
- });
369
- setEmbedUrl(resolvedEmbedUrl);
370
- setIsLoading(false);
371
- }).catch((err) => {
372
- if (err.name === "AbortError") return;
373
- setError(err.message);
374
- setIsLoading(false);
375
- });
376
- return () => controller.abort();
377
- }, [assetId, readOnly, expiresInSeconds, displayMode, backendUrl, appUrl, authContext, apiKey]);
378
- return { embedUrl, isLoading, error };
352
+ );
353
+ if (!res.ok) {
354
+ const text = await res.text().catch(() => "");
355
+ throw new Error(`Failed to generate embed token (${res.status}): ${text}`);
356
+ }
357
+ const data = await res.json();
358
+ return {
359
+ url: resolveAssetEmbedUrl({ embedUrl: data.embed_url, appUrl, backendUrl }),
360
+ expiresAt: data.expires_at
361
+ };
362
+ },
363
+ enabled,
364
+ // A minted link is good until it expires; a new one would reload the frame, so it is never
365
+ // minted again while the embed shows (no refetch on focus or reconnect) and never retried.
366
+ staleTime: (cached) => cached.state.data ? Math.max(0, cached.state.data.expiresAt * 1e3 - Date.now()) : 0,
367
+ refetchOnWindowFocus: false,
368
+ refetchOnReconnect: false,
369
+ retry: false
370
+ },
371
+ useContext(QueryClientContext) ?? embedQueryClient
372
+ );
373
+ return {
374
+ embedUrl: enabled ? ((_a = query.data) == null ? void 0 : _a.url) ?? null : null,
375
+ isLoading: enabled && query.isLoading,
376
+ error: enabled && query.error ? query.error.message : null
377
+ };
379
378
  }
380
379
  export {
380
+ DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT,
381
381
  DEFAULT_ASSET_EMBED_DISPLAY_MODE,
382
382
  DEFAULT_ASSET_EMBED_READ_ONLY,
383
383
  appendCitationReferenceParams,
@@ -1 +1 @@
1
- {"version":3,"file":"use-asset-embed.js","sources":["../../src/assets/use-asset-embed.ts"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport { deriveAthenaAppUrl } from '../lib/athena-urls';\nimport { sendWithCredentialRenewal } from '../runtime/credential-renewal';\nimport type { AthenaStatewireTokenGetter } from '../runtime/statewire-auth-headers';\nimport type { AthenaAssetCitationReference } from './asset-embed-messages';\n\nexport interface UseAssetEmbedOptions {\n readOnly?: boolean;\n expiresInSeconds?: number;\n /** Native Olympus surface rendered by the iframe. Defaults to `full`. */\n displayMode?: 'minimal' | 'full';\n}\n\nexport const DEFAULT_ASSET_EMBED_READ_ONLY = false;\nexport const DEFAULT_ASSET_EMBED_DISPLAY_MODE = 'full' as const;\n\nexport interface UseAssetEmbedResult {\n embedUrl: string | null;\n isLoading: boolean;\n error: string | null;\n}\n\nexport function buildAssetEmbedRequestBody({\n assetId,\n readOnly,\n expiresInSeconds,\n displayMode,\n}: {\n assetId: string;\n readOnly: boolean;\n expiresInSeconds: number;\n displayMode: 'minimal' | 'full';\n}) {\n return {\n asset_id: assetId,\n read_only: readOnly,\n expires_in_seconds: expiresInSeconds,\n display_mode: displayMode,\n };\n}\n\n// Cache embed URLs to avoid re-generating tokens for the same asset\nconst embedCache = new Map<string, { url: string; expiresAt: number }>();\n\nconst ATHENA_APP_HOSTNAMES = new Set(['app.athenaintel.com', 'staging-app.athenaintel.com']);\nconst ATHENA_PREVIEW_HOSTNAME_SUFFIX = '.previews.athenaintel.com';\nconst ATHENA_WORKSPACE_HOSTNAME_PATTERN = /^.+--(\\d+)\\.(?:app\\.athenaintel\\.com|staging-app\\.athenaintel\\.com)$/;\nconst OLYMPUS_WORKSPACE_PORT = '8082';\n\nconst getUrl = (value?: string | null): URL | null => {\n if (!value) {\n return null;\n }\n\n try {\n return new URL(value);\n } catch {\n return null;\n }\n};\n\nconst getCurrentOrigin = (): string | null => {\n if (typeof window === 'undefined') {\n return null;\n }\n\n return window.location.origin;\n};\n\nconst getOrigin = (value?: string | null): string | null => getUrl(value)?.origin ?? null;\n\nconst getAthenaWorkspacePort = (url: URL): string | null => {\n const match = url.hostname.toLowerCase().match(ATHENA_WORKSPACE_HOSTNAME_PATTERN);\n return match?.[1] ?? null;\n};\n\nconst isEmbedCapableAthenaHost = (url: URL): boolean => {\n const hostname = url.hostname.toLowerCase();\n\n if (ATHENA_APP_HOSTNAMES.has(hostname) || hostname.endsWith(ATHENA_PREVIEW_HOSTNAME_SUFFIX)) {\n return true;\n }\n\n return getAthenaWorkspacePort(url) === OLYMPUS_WORKSPACE_PORT;\n};\n\nconst shouldPreferDerivedEmbedAppUrl = ({\n appUrl,\n derivedAppUrl,\n currentOrigin,\n}: {\n appUrl: string;\n derivedAppUrl: string | null;\n currentOrigin?: string | null;\n}): boolean => {\n if (!derivedAppUrl) {\n return false;\n }\n\n const resolvedAppUrl = getUrl(appUrl);\n if (!resolvedAppUrl) {\n return true;\n }\n\n const workspacePort = getAthenaWorkspacePort(resolvedAppUrl);\n if (workspacePort && workspacePort !== OLYMPUS_WORKSPACE_PORT) {\n return true;\n }\n\n if (isEmbedCapableAthenaHost(resolvedAppUrl)) {\n return false;\n }\n\n const resolvedCurrentOrigin = getOrigin(currentOrigin ?? getCurrentOrigin());\n return !!resolvedCurrentOrigin && resolvedAppUrl.origin === resolvedCurrentOrigin;\n};\n\nconst resolveEmbedAppUrl = ({\n appUrl,\n backendUrl,\n currentOrigin,\n}: {\n appUrl?: string;\n backendUrl?: string | null;\n currentOrigin?: string | null;\n}): string | null => {\n const derivedAppUrl = deriveAthenaAppUrl({ backendUrl });\n\n if (!appUrl) {\n return derivedAppUrl;\n }\n\n if (shouldPreferDerivedEmbedAppUrl({ appUrl, derivedAppUrl, currentOrigin })) {\n return derivedAppUrl;\n }\n\n return appUrl;\n};\n\nexport function resolveAssetEmbedUrl({\n embedUrl,\n appUrl,\n backendUrl,\n currentOrigin,\n}: {\n embedUrl: string;\n appUrl?: string;\n backendUrl?: string | null;\n currentOrigin?: string | null;\n}): string {\n try {\n const resolvedEmbedUrl = new URL(embedUrl);\n if (!resolvedEmbedUrl.pathname.startsWith('/embed/')) {\n return embedUrl;\n }\n\n const embedAppUrl = resolveEmbedAppUrl({\n appUrl,\n backendUrl,\n currentOrigin,\n });\n if (!embedAppUrl) {\n return embedUrl;\n }\n\n const resolvedAppUrl = new URL(embedAppUrl);\n resolvedEmbedUrl.protocol = resolvedAppUrl.protocol;\n resolvedEmbedUrl.host = resolvedAppUrl.host;\n return resolvedEmbedUrl.toString();\n } catch {\n return embedUrl;\n }\n}\n\nfunction setFiniteNumberParam({ params, key, value }: { params: URLSearchParams; key: string; value: unknown }): void {\n if (typeof value === 'number' && Number.isFinite(value)) {\n params.set(key, String(value));\n }\n}\n\nfunction setNonEmptyStringParam({ params, key, value }: { params: URLSearchParams; key: string; value: unknown }): void {\n if (typeof value === 'string' && value.length > 0) {\n params.set(key, value);\n }\n}\n\n/** The SDK's own embed parameter: it marks the bridge, never a place, so no anchor writes it. */\nconst SDK_BRIDGE_PARAM = 'athena_sdk_bridge';\n\n/**\n * The pairs of an anchor given as its raw query — the string an Athena link, citation or search\n * result carries (`anchor=slide&slide_index=3&slide_id=…`, `anchor=time&time=0:42`), alone or\n * after the `?` of an asset ref (`asset_x?anchor=…`) — read as URL parameters, or `null` when the\n * string holds none, or a parameter with no name. The SDK never interprets the anchor: the embed\n * page parses it with the references grammar, so every kind of place travels as written.\n */\nfunction rawAnchorQueryPairs(query: string): Array<[string, string]> | null {\n const queryIndex = query.indexOf('?');\n const search = queryIndex === -1 ? query : query.slice(queryIndex + 1);\n const pairs = [...new URLSearchParams(search)].filter(([key]) => key !== SDK_BRIDGE_PARAM);\n if (pairs.length === 0 || pairs.some(([key]) => key.length === 0)) return null;\n return pairs;\n}\n\nfunction appendRawAnchorQuery({ params, query }: { params: URLSearchParams; query: string }): void {\n const pairs = rawAnchorQueryPairs(query);\n if (!pairs) return;\n // Verbatim: a key the URL already has is replaced, and a repeated key keeps every value.\n for (const [key] of pairs) params.delete(key);\n for (const [key, value] of pairs) params.append(key, value);\n}\n\nfunction appendTypedAnchorParams({\n params,\n anchor,\n}: {\n params: URLSearchParams;\n anchor: object & { type: string };\n}): void {\n switch (anchor.type) {\n case 'page':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'page',\n value: 'page' in anchor ? anchor.page : undefined,\n });\n break;\n case 'page_range':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'start_page',\n value: 'startPage' in anchor ? anchor.startPage : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'end_page',\n value: 'endPage' in anchor ? anchor.endPage : undefined,\n });\n break;\n case 'page_rect':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'page',\n value: 'page' in anchor ? anchor.page : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'x',\n value: 'x' in anchor ? anchor.x : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'y',\n value: 'y' in anchor ? anchor.y : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'width',\n value: 'width' in anchor ? anchor.width : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'height',\n value: 'height' in anchor ? anchor.height : undefined,\n });\n if (\n 'unit' in anchor &&\n (anchor.unit === 'percent' || anchor.unit === 'point')\n ) {\n params.set('unit', anchor.unit);\n }\n break;\n case 'page_text':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'page',\n value: 'page' in anchor ? anchor.page : undefined,\n });\n if ('text' in anchor && typeof anchor.text === 'string') {\n params.set('text', anchor.text);\n }\n // Optional context that disambiguates a repeated phrase, and the hash of\n // the cited unit's normalized text; the embed parses both with the shared\n // `@athenaintel/references` parser.\n setNonEmptyStringParam({ params, key: 'prefix', value: 'prefix' in anchor ? anchor.prefix : undefined });\n setNonEmptyStringParam({ params, key: 'suffix', value: 'suffix' in anchor ? anchor.suffix : undefined });\n setNonEmptyStringParam({ params, key: 'hash', value: 'hash' in anchor ? anchor.hash : undefined });\n break;\n // Sheet anchors ride the same query contract the embed page parses with\n // the shared `@athenaintel/references` parser.\n case 'sheet_cell':\n case 'sheet_range':\n case 'sheet_tab':\n case 'sheet_table':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'sheet_id',\n value: 'sheetId' in anchor ? anchor.sheetId : undefined,\n });\n if ('cell' in anchor && typeof anchor.cell === 'string') {\n params.set('cell', anchor.cell);\n }\n if ('range' in anchor && typeof anchor.range === 'string') {\n params.set('range', anchor.range);\n }\n if ('tableName' in anchor && typeof anchor.tableName === 'string') {\n params.set('table_name', anchor.tableName);\n }\n if ('fallbackRange' in anchor && typeof anchor.fallbackRange === 'string') {\n params.set('fallback_range', anchor.fallbackRange);\n }\n break;\n }\n}\n\n/**\n * Writes a citation's place into an embed URL: its anchor and its excerpt (the passage the embed\n * finds by its words — alone, when the citation names no anchor). The anchor is either its raw\n * query string, forwarded as written whatever kind of place it names, or one of the typed page and\n * sheet anchors. Athena on the web's Spaces links read the same grammar, so a host that opens a\n * cited place there writes it the same way.\n */\nexport function appendCitationReferenceParams({\n params,\n reference,\n}: {\n params: URLSearchParams;\n reference?: AthenaAssetCitationReference;\n}): void {\n const anchor = reference?.anchor;\n if (typeof anchor === 'string') {\n appendRawAnchorQuery({ params, query: anchor });\n } else if (anchor && typeof anchor === 'object' && 'type' in anchor && typeof anchor.type === 'string') {\n appendTypedAnchorParams({ params, anchor: { ...anchor, type: anchor.type } });\n }\n\n const excerpt = reference?.meta?.excerpt;\n if (typeof excerpt === 'string' && excerpt) {\n params.set('excerpt', excerpt);\n }\n}\n\nexport function buildSdkAssetEmbedUrl(embedUrl: string, reference?: AthenaAssetCitationReference): string {\n try {\n const url = new URL(embedUrl);\n url.searchParams.set('athena_sdk_bridge', '1');\n appendCitationReferenceParams({ params: url.searchParams, reference });\n return url.toString();\n } catch {\n return embedUrl;\n }\n}\n\nconst getJwtPayload = (token: string): Record<string, unknown> | null => {\n try {\n const payloadSegment = token.split('.')[1];\n if (!payloadSegment || typeof atob !== 'function') return null;\n const normalized = payloadSegment.replace(/-/g, '+').replace(/_/g, '/');\n const payload: unknown = JSON.parse(\n atob(normalized.padEnd(normalized.length + ((4 - (normalized.length % 4)) % 4), '=')),\n );\n return payload && typeof payload === 'object' ? (payload as Record<string, unknown>) : null;\n } catch {\n return null;\n }\n};\n\nexport function getAssetEmbedAuthContext({ token, apiKey }: { token?: string | null; apiKey?: string }): string {\n if (!token) return apiKey ? `api-key:${apiKey}` : 'anonymous';\n\n const payload = getJwtPayload(token);\n const userId = payload?.user_id ?? payload?.sub;\n if (typeof userId !== 'string' || !userId) return `opaque-token:${token}`;\n\n return `jwt:${JSON.stringify({\n issuer: payload?.iss ?? null,\n userId,\n activeOrgId: payload?.active_org_id ?? payload?.org_id ?? null,\n workspaceId: payload?.workspace_id ?? null,\n orgMemberships: payload?.org_id_to_org_member_info ?? null,\n })}`;\n}\n\n/**\n * Hook to generate an embed URL for rendering an asset in an iframe.\n * Calls the Athena embed token endpoint.\n *\n * Note: The auth token is read via a ref and is intentionally NOT part\n * of the effect dependency array. PropelAuth and similar providers refresh\n * the token value periodically (e.g. on window focus), but the underlying\n * session is the same — re-running the effect on every token rotation\n * would force the iframe to reload unnecessarily. The effect only re-runs\n * when the auth _mode_ changes (auth vs anonymous) or the asset changes.\n */\nexport function useAssetEmbed(\n assetId: string | null,\n options: UseAssetEmbedOptions & {\n backendUrl: string;\n appUrl?: string;\n apiKey?: string;\n token?: string | null;\n /**\n * Live token resolver (the provider's `config.getToken`) that renews the\n * held `token`: read before the embed-token request, and asked for a\n * renewed token (`force: true`) when the backend refuses it. Ignored when\n * no token is held, so the request always authenticates as the identity\n * the embed cache is keyed on.\n */\n getToken?: AthenaStatewireTokenGetter;\n } = {\n backendUrl: '',\n },\n): UseAssetEmbedResult {\n const {\n readOnly = DEFAULT_ASSET_EMBED_READ_ONLY,\n expiresInSeconds = 60 * 60 * 24 * 30,\n displayMode = DEFAULT_ASSET_EMBED_DISPLAY_MODE,\n backendUrl,\n appUrl,\n apiKey,\n token,\n getToken,\n } = options;\n const [embedUrl, setEmbedUrl] = useState<string | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n\n // Keep the latest token in a ref so the effect can read it without\n // being re-triggered every time PropelAuth rotates the token value.\n const tokenRef = useRef(token);\n tokenRef.current = token;\n const getTokenRef = useRef(getToken);\n getTokenRef.current = getToken;\n\n // Stable across normal JWT rotations, but changes when the user or tenant\n // changes so a signed URL can never be reused across auth boundaries.\n const authContext = getAssetEmbedAuthContext({ token, apiKey });\n\n useEffect(() => {\n if (!assetId || !backendUrl) {\n setEmbedUrl(null);\n setIsLoading(false);\n setError(null);\n return;\n }\n\n const cacheKey = `${assetId}:${readOnly}:${displayMode}:${authContext}:${backendUrl}:${appUrl ?? ''}`;\n const cached = embedCache.get(cacheKey);\n if (cached && cached.expiresAt > Date.now() / 1000) {\n setEmbedUrl(cached.url);\n setIsLoading(false);\n setError(null);\n return;\n }\n\n // Derive the Athena API base from backendUrl before calling the embed endpoint.\n const apiBaseUrl = backendUrl.replace(/\\/api\\/assistant-ui\\/?$/, '');\n const endpoint = `${apiBaseUrl}/api/embed/generate-token`;\n\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n\n setIsLoading(true);\n setError(null);\n\n const heldToken = tokenRef.current;\n sendWithCredentialRenewal(\n { apiKey, token: heldToken, getToken: heldToken ? getTokenRef.current : undefined },\n (headers) =>\n fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...headers },\n body: JSON.stringify(\n buildAssetEmbedRequestBody({\n assetId,\n readOnly,\n expiresInSeconds,\n displayMode,\n }),\n ),\n signal: controller.signal,\n }),\n )\n .then(async (res) => {\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`Failed to generate embed token (${res.status}): ${text}`);\n }\n return res.json();\n })\n .then((data: { embed_url: string; expires_at: number }) => {\n const resolvedEmbedUrl = resolveAssetEmbedUrl({\n embedUrl: data.embed_url,\n appUrl,\n backendUrl,\n });\n embedCache.set(cacheKey, {\n url: resolvedEmbedUrl,\n expiresAt: data.expires_at,\n });\n setEmbedUrl(resolvedEmbedUrl);\n setIsLoading(false);\n })\n .catch((err) => {\n if (err.name === 'AbortError') return;\n setError(err.message);\n setIsLoading(false);\n });\n\n return () => controller.abort();\n // Raw token intentionally excluded; authContext tracks user/tenant identity\n // while remaining stable across ordinary JWT refreshes.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [assetId, readOnly, expiresInSeconds, displayMode, backendUrl, appUrl, authContext, apiKey]);\n\n return { embedUrl, isLoading, error };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"use-asset-embed.js","sources":["../../src/assets/use-asset-embed.ts"],"sourcesContent":["import { QueryClient, QueryClientContext, useQuery } from '@tanstack/react-query';\nimport { useContext, useRef } from 'react';\nimport { deriveAthenaAppUrl } from '../lib/athena-urls';\nimport { keyPartFingerprint } from '../lib/use-athena-rest-query';\nimport { sendWithCredentialRenewal } from '../runtime/credential-renewal';\nimport type { AthenaStatewireTokenGetter } from '../runtime/statewire-auth-headers';\nimport type { AthenaAssetCitationReference } from './asset-embed-messages';\n\nexport interface UseAssetEmbedOptions {\n readOnly?: boolean;\n expiresInSeconds?: number;\n /** Native Olympus surface rendered by the iframe. Defaults to `full`. */\n displayMode?: 'minimal' | 'full';\n /**\n * Offer Quick Edit (⌘J, an AI rewrite of the selection) in a minimal embed, where the viewer\n * does not sign in: spreadsheets and Word documents, on an editable link (`readOnly: false`)\n * only. Its runs are billed to the asset's workspace under the link's creator, and each link has\n * its own hourly cap. The full mode's editors offer it to the signed-in person already. Defaults\n * to `false`.\n */\n allowQuickEdit?: boolean;\n}\n\nexport const DEFAULT_ASSET_EMBED_READ_ONLY = false;\nexport const DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT = false;\nexport const DEFAULT_ASSET_EMBED_DISPLAY_MODE = 'full' as const;\n\nexport interface UseAssetEmbedResult {\n embedUrl: string | null;\n isLoading: boolean;\n error: string | null;\n}\n\nexport function buildAssetEmbedRequestBody({\n assetId,\n readOnly,\n expiresInSeconds,\n displayMode,\n allowQuickEdit = DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT,\n}: {\n assetId: string;\n readOnly: boolean;\n expiresInSeconds: number;\n displayMode: 'minimal' | 'full';\n allowQuickEdit?: boolean;\n}) {\n return {\n asset_id: assetId,\n read_only: readOnly,\n expires_in_seconds: expiresInSeconds,\n display_mode: displayMode,\n // Sent only when asked: an agora that predates it is never handed an unknown field.\n ...(allowQuickEdit && { allow_quick_edit: true }),\n };\n}\n\n/**\n * The embed links' query client when the host provides none: one for the page, so every embed of\n * one asset, mode and identity shares one minted link until it expires.\n */\nconst embedQueryClient = new QueryClient();\n\nconst ATHENA_APP_HOSTNAMES = new Set(['app.athenaintel.com', 'staging-app.athenaintel.com']);\nconst ATHENA_PREVIEW_HOSTNAME_SUFFIX = '.previews.athenaintel.com';\nconst ATHENA_WORKSPACE_HOSTNAME_PATTERN = /^.+--(\\d+)\\.(?:app\\.athenaintel\\.com|staging-app\\.athenaintel\\.com)$/;\nconst OLYMPUS_WORKSPACE_PORT = '8082';\n\nconst getUrl = (value?: string | null): URL | null => {\n if (!value) {\n return null;\n }\n\n try {\n return new URL(value);\n } catch {\n return null;\n }\n};\n\nconst getCurrentOrigin = (): string | null => {\n if (typeof window === 'undefined') {\n return null;\n }\n\n return window.location.origin;\n};\n\nconst getOrigin = (value?: string | null): string | null => getUrl(value)?.origin ?? null;\n\nconst getAthenaWorkspacePort = (url: URL): string | null => {\n const match = url.hostname.toLowerCase().match(ATHENA_WORKSPACE_HOSTNAME_PATTERN);\n return match?.[1] ?? null;\n};\n\nconst isEmbedCapableAthenaHost = (url: URL): boolean => {\n const hostname = url.hostname.toLowerCase();\n\n if (ATHENA_APP_HOSTNAMES.has(hostname) || hostname.endsWith(ATHENA_PREVIEW_HOSTNAME_SUFFIX)) {\n return true;\n }\n\n return getAthenaWorkspacePort(url) === OLYMPUS_WORKSPACE_PORT;\n};\n\nconst shouldPreferDerivedEmbedAppUrl = ({\n appUrl,\n derivedAppUrl,\n currentOrigin,\n}: {\n appUrl: string;\n derivedAppUrl: string | null;\n currentOrigin?: string | null;\n}): boolean => {\n if (!derivedAppUrl) {\n return false;\n }\n\n const resolvedAppUrl = getUrl(appUrl);\n if (!resolvedAppUrl) {\n return true;\n }\n\n const workspacePort = getAthenaWorkspacePort(resolvedAppUrl);\n if (workspacePort && workspacePort !== OLYMPUS_WORKSPACE_PORT) {\n return true;\n }\n\n if (isEmbedCapableAthenaHost(resolvedAppUrl)) {\n return false;\n }\n\n const resolvedCurrentOrigin = getOrigin(currentOrigin ?? getCurrentOrigin());\n return !!resolvedCurrentOrigin && resolvedAppUrl.origin === resolvedCurrentOrigin;\n};\n\nconst resolveEmbedAppUrl = ({\n appUrl,\n backendUrl,\n currentOrigin,\n}: {\n appUrl?: string;\n backendUrl?: string | null;\n currentOrigin?: string | null;\n}): string | null => {\n const derivedAppUrl = deriveAthenaAppUrl({ backendUrl });\n\n if (!appUrl) {\n return derivedAppUrl;\n }\n\n if (shouldPreferDerivedEmbedAppUrl({ appUrl, derivedAppUrl, currentOrigin })) {\n return derivedAppUrl;\n }\n\n return appUrl;\n};\n\nexport function resolveAssetEmbedUrl({\n embedUrl,\n appUrl,\n backendUrl,\n currentOrigin,\n}: {\n embedUrl: string;\n appUrl?: string;\n backendUrl?: string | null;\n currentOrigin?: string | null;\n}): string {\n try {\n const resolvedEmbedUrl = new URL(embedUrl);\n if (!resolvedEmbedUrl.pathname.startsWith('/embed/')) {\n return embedUrl;\n }\n\n const embedAppUrl = resolveEmbedAppUrl({\n appUrl,\n backendUrl,\n currentOrigin,\n });\n if (!embedAppUrl) {\n return embedUrl;\n }\n\n const resolvedAppUrl = new URL(embedAppUrl);\n resolvedEmbedUrl.protocol = resolvedAppUrl.protocol;\n resolvedEmbedUrl.host = resolvedAppUrl.host;\n return resolvedEmbedUrl.toString();\n } catch {\n return embedUrl;\n }\n}\n\nfunction setFiniteNumberParam({ params, key, value }: { params: URLSearchParams; key: string; value: unknown }): void {\n if (typeof value === 'number' && Number.isFinite(value)) {\n params.set(key, String(value));\n }\n}\n\nfunction setNonEmptyStringParam({ params, key, value }: { params: URLSearchParams; key: string; value: unknown }): void {\n if (typeof value === 'string' && value.length > 0) {\n params.set(key, value);\n }\n}\n\n/** The SDK's own embed parameter: it marks the bridge, never a place, so no anchor writes it. */\nconst SDK_BRIDGE_PARAM = 'athena_sdk_bridge';\n\n/**\n * The pairs of an anchor given as its raw query — the string an Athena link, citation or search\n * result carries (`anchor=slide&slide_index=3&slide_id=…`, `anchor=time&time=0:42`), alone or\n * after the `?` of an asset ref (`asset_x?anchor=…`) — read as URL parameters, or `null` when the\n * string holds none, or a parameter with no name. The SDK never interprets the anchor: the embed\n * page parses it with the references grammar, so every kind of place travels as written.\n */\nfunction rawAnchorQueryPairs(query: string): Array<[string, string]> | null {\n const queryIndex = query.indexOf('?');\n const search = queryIndex === -1 ? query : query.slice(queryIndex + 1);\n const pairs = [...new URLSearchParams(search)].filter(([key]) => key !== SDK_BRIDGE_PARAM);\n if (pairs.length === 0 || pairs.some(([key]) => key.length === 0)) return null;\n return pairs;\n}\n\nfunction appendRawAnchorQuery({ params, query }: { params: URLSearchParams; query: string }): void {\n const pairs = rawAnchorQueryPairs(query);\n if (!pairs) return;\n // Verbatim: a key the URL already has is replaced, and a repeated key keeps every value.\n for (const [key] of pairs) params.delete(key);\n for (const [key, value] of pairs) params.append(key, value);\n}\n\nfunction appendTypedAnchorParams({\n params,\n anchor,\n}: {\n params: URLSearchParams;\n anchor: object & { type: string };\n}): void {\n switch (anchor.type) {\n case 'page':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'page',\n value: 'page' in anchor ? anchor.page : undefined,\n });\n break;\n case 'page_range':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'start_page',\n value: 'startPage' in anchor ? anchor.startPage : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'end_page',\n value: 'endPage' in anchor ? anchor.endPage : undefined,\n });\n break;\n case 'page_rect':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'page',\n value: 'page' in anchor ? anchor.page : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'x',\n value: 'x' in anchor ? anchor.x : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'y',\n value: 'y' in anchor ? anchor.y : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'width',\n value: 'width' in anchor ? anchor.width : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'height',\n value: 'height' in anchor ? anchor.height : undefined,\n });\n if (\n 'unit' in anchor &&\n (anchor.unit === 'percent' || anchor.unit === 'point')\n ) {\n params.set('unit', anchor.unit);\n }\n break;\n case 'page_text':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'page',\n value: 'page' in anchor ? anchor.page : undefined,\n });\n if ('text' in anchor && typeof anchor.text === 'string') {\n params.set('text', anchor.text);\n }\n // Optional context that disambiguates a repeated phrase, and the hash of\n // the cited unit's normalized text; the embed parses both with the shared\n // `@athenaintel/references` parser.\n setNonEmptyStringParam({ params, key: 'prefix', value: 'prefix' in anchor ? anchor.prefix : undefined });\n setNonEmptyStringParam({ params, key: 'suffix', value: 'suffix' in anchor ? anchor.suffix : undefined });\n setNonEmptyStringParam({ params, key: 'hash', value: 'hash' in anchor ? anchor.hash : undefined });\n break;\n // Sheet anchors ride the same query contract the embed page parses with\n // the shared `@athenaintel/references` parser.\n case 'sheet_cell':\n case 'sheet_range':\n case 'sheet_tab':\n case 'sheet_table':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'sheet_id',\n value: 'sheetId' in anchor ? anchor.sheetId : undefined,\n });\n if ('cell' in anchor && typeof anchor.cell === 'string') {\n params.set('cell', anchor.cell);\n }\n if ('range' in anchor && typeof anchor.range === 'string') {\n params.set('range', anchor.range);\n }\n if ('tableName' in anchor && typeof anchor.tableName === 'string') {\n params.set('table_name', anchor.tableName);\n }\n if ('fallbackRange' in anchor && typeof anchor.fallbackRange === 'string') {\n params.set('fallback_range', anchor.fallbackRange);\n }\n break;\n }\n}\n\n/**\n * Writes a citation's place into an embed URL: its anchor and its excerpt (the passage the embed\n * finds by its words — alone, when the citation names no anchor). The anchor is either its raw\n * query string, forwarded as written whatever kind of place it names, or one of the typed page and\n * sheet anchors. Athena on the web's Spaces links read the same grammar, so a host that opens a\n * cited place there writes it the same way.\n */\nexport function appendCitationReferenceParams({\n params,\n reference,\n}: {\n params: URLSearchParams;\n reference?: AthenaAssetCitationReference;\n}): void {\n const anchor = reference?.anchor;\n if (typeof anchor === 'string') {\n appendRawAnchorQuery({ params, query: anchor });\n } else if (anchor && typeof anchor === 'object' && 'type' in anchor && typeof anchor.type === 'string') {\n appendTypedAnchorParams({ params, anchor: { ...anchor, type: anchor.type } });\n }\n\n const excerpt = reference?.meta?.excerpt;\n if (typeof excerpt === 'string' && excerpt) {\n params.set('excerpt', excerpt);\n }\n}\n\nexport function buildSdkAssetEmbedUrl(embedUrl: string, reference?: AthenaAssetCitationReference): string {\n try {\n const url = new URL(embedUrl);\n url.searchParams.set('athena_sdk_bridge', '1');\n appendCitationReferenceParams({ params: url.searchParams, reference });\n return url.toString();\n } catch {\n return embedUrl;\n }\n}\n\nconst getJwtPayload = (token: string): Record<string, unknown> | null => {\n try {\n const payloadSegment = token.split('.')[1];\n if (!payloadSegment || typeof atob !== 'function') return null;\n const normalized = payloadSegment.replace(/-/g, '+').replace(/_/g, '/');\n const payload: unknown = JSON.parse(\n atob(normalized.padEnd(normalized.length + ((4 - (normalized.length % 4)) % 4), '=')),\n );\n return payload && typeof payload === 'object' ? (payload as Record<string, unknown>) : null;\n } catch {\n return null;\n }\n};\n\nexport function getAssetEmbedAuthContext({ token, apiKey }: { token?: string | null; apiKey?: string }): string {\n if (!token) return apiKey ? `api-key:${apiKey}` : 'anonymous';\n\n const payload = getJwtPayload(token);\n const userId = payload?.user_id ?? payload?.sub;\n if (typeof userId !== 'string' || !userId) return `opaque-token:${token}`;\n\n return `jwt:${JSON.stringify({\n issuer: payload?.iss ?? null,\n userId,\n activeOrgId: payload?.active_org_id ?? payload?.org_id ?? null,\n workspaceId: payload?.workspace_id ?? null,\n orgMemberships: payload?.org_id_to_org_member_info ?? null,\n })}`;\n}\n\n/**\n * Hook to generate an embed URL for rendering an asset in an iframe.\n * Calls the Athena embed token endpoint through TanStack Query (the host's\n * QueryClient when there is one, else the page's embed client), keyed on the\n * asset, the mode and the identity.\n *\n * Note: The auth token is read via a ref and is intentionally NOT part\n * of the query key. PropelAuth and similar providers refresh the token value\n * periodically (e.g. on window focus), but the underlying session is the\n * same — minting again on every token rotation would force the iframe to\n * reload unnecessarily. The link is minted again only when the identity\n * (user or tenant), the asset or the mode changes.\n */\nexport function useAssetEmbed(\n assetId: string | null,\n options: UseAssetEmbedOptions & {\n backendUrl: string;\n appUrl?: string;\n apiKey?: string;\n token?: string | null;\n /**\n * Live token resolver (the provider's `config.getToken`) that renews the\n * held `token`: read before the embed-token request, and asked for a\n * renewed token (`force: true`) when the backend refuses it. Ignored when\n * no token is held, so the request always authenticates as the identity\n * the embed cache is keyed on.\n */\n getToken?: AthenaStatewireTokenGetter;\n } = {\n backendUrl: '',\n },\n): UseAssetEmbedResult {\n const {\n readOnly = DEFAULT_ASSET_EMBED_READ_ONLY,\n expiresInSeconds = 60 * 60 * 24 * 30,\n displayMode = DEFAULT_ASSET_EMBED_DISPLAY_MODE,\n allowQuickEdit = DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT,\n backendUrl,\n appUrl,\n apiKey,\n token,\n getToken,\n } = options;\n // Keep the latest token in a ref so the query can read it without being\n // keyed on it: PropelAuth and similar providers rotate the token value.\n const tokenRef = useRef(token);\n tokenRef.current = token;\n const getTokenRef = useRef(getToken);\n getTokenRef.current = getToken;\n\n // Stable across normal JWT rotations, but changes when the user or tenant\n // changes so a signed URL can never be reused across auth boundaries.\n const authContext = getAssetEmbedAuthContext({ token, apiKey });\n const enabled = Boolean(assetId && backendUrl);\n\n const query = useQuery(\n {\n queryKey: [\n 'athena-sdk',\n 'asset-embed',\n assetId,\n readOnly,\n displayMode,\n allowQuickEdit,\n // The identity, never the credential: an opaque token or a key is part of `authContext`.\n keyPartFingerprint(authContext),\n backendUrl,\n appUrl ?? '',\n ],\n queryFn: async ({ signal }): Promise<{ url: string; expiresAt: number }> => {\n // Derive the Athena API base from backendUrl before calling the embed endpoint.\n const apiBaseUrl = backendUrl.replace(/\\/api\\/assistant-ui\\/?$/, '');\n const heldToken = tokenRef.current;\n const res = await sendWithCredentialRenewal(\n { apiKey, token: heldToken, getToken: heldToken ? getTokenRef.current : undefined },\n (headers) =>\n fetch(`${apiBaseUrl}/api/embed/generate-token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...headers },\n body: JSON.stringify(\n buildAssetEmbedRequestBody({\n assetId: assetId ?? '',\n readOnly,\n expiresInSeconds,\n displayMode,\n allowQuickEdit,\n }),\n ),\n signal,\n }),\n );\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`Failed to generate embed token (${res.status}): ${text}`);\n }\n const data: { embed_url: string; expires_at: number } = await res.json();\n return {\n url: resolveAssetEmbedUrl({ embedUrl: data.embed_url, appUrl, backendUrl }),\n expiresAt: data.expires_at,\n };\n },\n enabled,\n // A minted link is good until it expires; a new one would reload the frame, so it is never\n // minted again while the embed shows (no refetch on focus or reconnect) and never retried.\n staleTime: (cached) =>\n cached.state.data ? Math.max(0, cached.state.data.expiresAt * 1000 - Date.now()) : 0,\n refetchOnWindowFocus: false,\n refetchOnReconnect: false,\n retry: false,\n },\n useContext(QueryClientContext) ?? embedQueryClient,\n );\n\n return {\n embedUrl: enabled ? (query.data?.url ?? null) : null,\n isLoading: enabled && query.isLoading,\n error: enabled && query.error ? query.error.message : null,\n };\n}\n"],"names":[],"mappings":";;;;;AAuBO,MAAM,gCAAgC;AACtC,MAAM,uCAAuC;AAC7C,MAAM,mCAAmC;AAQzC,SAAS,2BAA2B;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAiB;AACnB,GAMG;AACD,SAAO;AAAA,IACL,UAAU;AAAA,IACV,WAAW;AAAA,IACX,oBAAoB;AAAA,IACpB,cAAc;AAAA;AAAA,IAEd,GAAI,kBAAkB,EAAE,kBAAkB,KAAA;AAAA,EAAK;AAEnD;AAMA,MAAM,mBAAmB,IAAI,YAAA;AAE7B,MAAM,uBAAuB,oBAAI,IAAI,CAAC,uBAAuB,6BAA6B,CAAC;AAC3F,MAAM,iCAAiC;AACvC,MAAM,oCAAoC;AAC1C,MAAM,yBAAyB;AAE/B,MAAM,SAAS,CAAC,UAAsC;AACpD,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,EACT;AAEA,MAAI;AACF,WAAO,IAAI,IAAI,KAAK;AAAA,EACtB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,MAAM,mBAAmB,MAAqB;AAC5C,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO;AAAA,EACT;AAEA,SAAO,OAAO,SAAS;AACzB;AAEA,MAAM,YAAY,CAAC,UAAA;;AAAyC,uBAAO,KAAK,MAAZ,mBAAe,WAAU;AAAA;AAErF,MAAM,yBAAyB,CAAC,QAA4B;AAC1D,QAAM,QAAQ,IAAI,SAAS,YAAA,EAAc,MAAM,iCAAiC;AAChF,UAAO,+BAAQ,OAAM;AACvB;AAEA,MAAM,2BAA2B,CAAC,QAAsB;AACtD,QAAM,WAAW,IAAI,SAAS,YAAA;AAE9B,MAAI,qBAAqB,IAAI,QAAQ,KAAK,SAAS,SAAS,8BAA8B,GAAG;AAC3F,WAAO;AAAA,EACT;AAEA,SAAO,uBAAuB,GAAG,MAAM;AACzC;AAEA,MAAM,iCAAiC,CAAC;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AACF,MAIe;AACb,MAAI,CAAC,eAAe;AAClB,WAAO;AAAA,EACT;AAEA,QAAM,iBAAiB,OAAO,MAAM;AACpC,MAAI,CAAC,gBAAgB;AACnB,WAAO;AAAA,EACT;AAEA,QAAM,gBAAgB,uBAAuB,cAAc;AAC3D,MAAI,iBAAiB,kBAAkB,wBAAwB;AAC7D,WAAO;AAAA,EACT;AAEA,MAAI,yBAAyB,cAAc,GAAG;AAC5C,WAAO;AAAA,EACT;AAEA,QAAM,wBAAwB,UAAU,iBAAiB,iBAAA,CAAkB;AAC3E,SAAO,CAAC,CAAC,yBAAyB,eAAe,WAAW;AAC9D;AAEA,MAAM,qBAAqB,CAAC;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AACF,MAIqB;AACnB,QAAM,gBAAgB,mBAAmB,EAAE,YAAY;AAEvD,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,EACT;AAEA,MAAI,+BAA+B,EAAE,QAAQ,eAAe,cAAA,CAAe,GAAG;AAC5E,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKW;AACT,MAAI;AACF,UAAM,mBAAmB,IAAI,IAAI,QAAQ;AACzC,QAAI,CAAC,iBAAiB,SAAS,WAAW,SAAS,GAAG;AACpD,aAAO;AAAA,IACT;AAEA,UAAM,cAAc,mBAAmB;AAAA,MACrC;AAAA,MACA;AAAA,MACA;AAAA,IAAA,CACD;AACD,QAAI,CAAC,aAAa;AAChB,aAAO;AAAA,IACT;AAEA,UAAM,iBAAiB,IAAI,IAAI,WAAW;AAC1C,qBAAiB,WAAW,eAAe;AAC3C,qBAAiB,OAAO,eAAe;AACvC,WAAO,iBAAiB,SAAA;AAAA,EAC1B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,qBAAqB,EAAE,QAAQ,KAAK,SAAyE;AACpH,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,GAAG;AACvD,WAAO,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,EAC/B;AACF;AAEA,SAAS,uBAAuB,EAAE,QAAQ,KAAK,SAAyE;AACtH,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACjD,WAAO,IAAI,KAAK,KAAK;AAAA,EACvB;AACF;AAGA,MAAM,mBAAmB;AASzB,SAAS,oBAAoB,OAA+C;AAC1E,QAAM,aAAa,MAAM,QAAQ,GAAG;AACpC,QAAM,SAAS,eAAe,KAAK,QAAQ,MAAM,MAAM,aAAa,CAAC;AACrE,QAAM,QAAQ,CAAC,GAAG,IAAI,gBAAgB,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,QAAQ,gBAAgB;AACzF,MAAI,MAAM,WAAW,KAAK,MAAM,KAAK,CAAC,CAAC,GAAG,MAAM,IAAI,WAAW,CAAC,EAAG,QAAO;AAC1E,SAAO;AACT;AAEA,SAAS,qBAAqB,EAAE,QAAQ,SAA2D;AACjG,QAAM,QAAQ,oBAAoB,KAAK;AACvC,MAAI,CAAC,MAAO;AAEZ,aAAW,CAAC,GAAG,KAAK,MAAO,QAAO,OAAO,GAAG;AAC5C,aAAW,CAAC,KAAK,KAAK,KAAK,MAAO,QAAO,OAAO,KAAK,KAAK;AAC5D;AAEA,SAAS,wBAAwB;AAAA,EAC/B;AAAA,EACA;AACF,GAGS;AACP,UAAQ,OAAO,MAAA;AAAA,IACb,KAAK;AACH,aAAO,IAAI,UAAU,OAAO,IAAI;AAChC,2BAAqB;AAAA,QACnB;AAAA,QACA,KAAK;AAAA,QACL,OAAO,UAAU,SAAS,OAAO,OAAO;AAAA,MAAA,CACzC;AACD;AAAA,IACF,KAAK;AACH,aAAO,IAAI,UAAU,OAAO,IAAI;AAChC,2BAAqB;AAAA,QACnB;AAAA,QACA,KAAK;AAAA,QACL,OAAO,eAAe,SAAS,OAAO,YAAY;AAAA,MAAA,CACnD;AACD,2BAAqB;AAAA,QACnB;AAAA,QACA,KAAK;AAAA,QACL,OAAO,aAAa,SAAS,OAAO,UAAU;AAAA,MAAA,CAC/C;AACD;AAAA,IACF,KAAK;AACH,aAAO,IAAI,UAAU,OAAO,IAAI;AAChC,2BAAqB;AAAA,QACnB;AAAA,QACA,KAAK;AAAA,QACL,OAAO,UAAU,SAAS,OAAO,OAAO;AAAA,MAAA,CACzC;AACD,2BAAqB;AAAA,QACnB;AAAA,QACA,KAAK;AAAA,QACL,OAAO,OAAO,SAAS,OAAO,IAAI;AAAA,MAAA,CACnC;AACD,2BAAqB;AAAA,QACnB;AAAA,QACA,KAAK;AAAA,QACL,OAAO,OAAO,SAAS,OAAO,IAAI;AAAA,MAAA,CACnC;AACD,2BAAqB;AAAA,QACnB;AAAA,QACA,KAAK;AAAA,QACL,OAAO,WAAW,SAAS,OAAO,QAAQ;AAAA,MAAA,CAC3C;AACD,2BAAqB;AAAA,QACnB;AAAA,QACA,KAAK;AAAA,QACL,OAAO,YAAY,SAAS,OAAO,SAAS;AAAA,MAAA,CAC7C;AACD,UACE,UAAU,WACT,OAAO,SAAS,aAAa,OAAO,SAAS,UAC9C;AACA,eAAO,IAAI,QAAQ,OAAO,IAAI;AAAA,MAChC;AACA;AAAA,IACF,KAAK;AACH,aAAO,IAAI,UAAU,OAAO,IAAI;AAChC,2BAAqB;AAAA,QACnB;AAAA,QACA,KAAK;AAAA,QACL,OAAO,UAAU,SAAS,OAAO,OAAO;AAAA,MAAA,CACzC;AACD,UAAI,UAAU,UAAU,OAAO,OAAO,SAAS,UAAU;AACvD,eAAO,IAAI,QAAQ,OAAO,IAAI;AAAA,MAChC;AAIA,6BAAuB,EAAE,QAAQ,KAAK,UAAU,OAAO,YAAY,SAAS,OAAO,SAAS,OAAA,CAAW;AACvG,6BAAuB,EAAE,QAAQ,KAAK,UAAU,OAAO,YAAY,SAAS,OAAO,SAAS,OAAA,CAAW;AACvG,6BAAuB,EAAE,QAAQ,KAAK,QAAQ,OAAO,UAAU,SAAS,OAAO,OAAO,OAAA,CAAW;AACjG;AAAA;AAAA;AAAA,IAGF,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,IAAI,UAAU,OAAO,IAAI;AAChC,2BAAqB;AAAA,QACnB;AAAA,QACA,KAAK;AAAA,QACL,OAAO,aAAa,SAAS,OAAO,UAAU;AAAA,MAAA,CAC/C;AACD,UAAI,UAAU,UAAU,OAAO,OAAO,SAAS,UAAU;AACvD,eAAO,IAAI,QAAQ,OAAO,IAAI;AAAA,MAChC;AACA,UAAI,WAAW,UAAU,OAAO,OAAO,UAAU,UAAU;AACzD,eAAO,IAAI,SAAS,OAAO,KAAK;AAAA,MAClC;AACA,UAAI,eAAe,UAAU,OAAO,OAAO,cAAc,UAAU;AACjE,eAAO,IAAI,cAAc,OAAO,SAAS;AAAA,MAC3C;AACA,UAAI,mBAAmB,UAAU,OAAO,OAAO,kBAAkB,UAAU;AACzE,eAAO,IAAI,kBAAkB,OAAO,aAAa;AAAA,MACnD;AACA;AAAA,EAAA;AAEN;AASO,SAAS,8BAA8B;AAAA,EAC5C;AAAA,EACA;AACF,GAGS;;AACP,QAAM,SAAS,uCAAW;AAC1B,MAAI,OAAO,WAAW,UAAU;AAC9B,yBAAqB,EAAE,QAAQ,OAAO,OAAA,CAAQ;AAAA,EAChD,WAAW,UAAU,OAAO,WAAW,YAAY,UAAU,UAAU,OAAO,OAAO,SAAS,UAAU;AACtG,4BAAwB,EAAE,QAAQ,QAAQ,EAAE,GAAG,QAAQ,MAAM,OAAO,KAAA,GAAQ;AAAA,EAC9E;AAEA,QAAM,WAAU,4CAAW,SAAX,mBAAiB;AACjC,MAAI,OAAO,YAAY,YAAY,SAAS;AAC1C,WAAO,IAAI,WAAW,OAAO;AAAA,EAC/B;AACF;AAEO,SAAS,sBAAsB,UAAkB,WAAkD;AACxG,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,QAAQ;AAC5B,QAAI,aAAa,IAAI,qBAAqB,GAAG;AAC7C,kCAA8B,EAAE,QAAQ,IAAI,cAAc,WAAW;AACrE,WAAO,IAAI,SAAA;AAAA,EACb,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,MAAM,gBAAgB,CAAC,UAAkD;AACvE,MAAI;AACF,UAAM,iBAAiB,MAAM,MAAM,GAAG,EAAE,CAAC;AACzC,QAAI,CAAC,kBAAkB,OAAO,SAAS,WAAY,QAAO;AAC1D,UAAM,aAAa,eAAe,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AACtE,UAAM,UAAmB,KAAK;AAAA,MAC5B,KAAK,WAAW,OAAO,WAAW,UAAW,IAAK,WAAW,SAAS,KAAM,GAAI,GAAG,CAAC;AAAA,IAAA;AAEtF,WAAO,WAAW,OAAO,YAAY,WAAY,UAAsC;AAAA,EACzF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,yBAAyB,EAAE,OAAO,UAA8D;AAC9G,MAAI,CAAC,MAAO,QAAO,SAAS,WAAW,MAAM,KAAK;AAElD,QAAM,UAAU,cAAc,KAAK;AACnC,QAAM,UAAS,mCAAS,aAAW,mCAAS;AAC5C,MAAI,OAAO,WAAW,YAAY,CAAC,OAAQ,QAAO,gBAAgB,KAAK;AAEvE,SAAO,OAAO,KAAK,UAAU;AAAA,IAC3B,SAAQ,mCAAS,QAAO;AAAA,IACxB;AAAA,IACA,cAAa,mCAAS,mBAAiB,mCAAS,WAAU;AAAA,IAC1D,cAAa,mCAAS,iBAAgB;AAAA,IACtC,iBAAgB,mCAAS,8BAA6B;AAAA,EAAA,CACvD,CAAC;AACJ;AAeO,SAAS,cACd,SACA,UAaI;AAAA,EACF,YAAY;AACd,GACqB;;AACrB,QAAM;AAAA,IACJ,WAAW;AAAA,IACX,mBAAmB,KAAK,KAAK,KAAK;AAAA,IAClC,cAAc;AAAA,IACd,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,IACE;AAGJ,QAAM,WAAW,OAAO,KAAK;AAC7B,WAAS,UAAU;AACnB,QAAM,cAAc,OAAO,QAAQ;AACnC,cAAY,UAAU;AAItB,QAAM,cAAc,yBAAyB,EAAE,OAAO,QAAQ;AAC9D,QAAM,UAAU,QAAQ,WAAW,UAAU;AAE7C,QAAM,QAAQ;AAAA,IACZ;AAAA,MACE,UAAU;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,QAEA,mBAAmB,WAAW;AAAA,QAC9B;AAAA,QACA,UAAU;AAAA,MAAA;AAAA,MAEZ,SAAS,OAAO,EAAE,aAA0D;AAE1E,cAAM,aAAa,WAAW,QAAQ,2BAA2B,EAAE;AACnE,cAAM,YAAY,SAAS;AAC3B,cAAM,MAAM,MAAM;AAAA,UAChB,EAAE,QAAQ,OAAO,WAAW,UAAU,YAAY,YAAY,UAAU,OAAA;AAAA,UACxE,CAAC,YACC,MAAM,GAAG,UAAU,6BAA6B;AAAA,YAC9C,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,oBAAoB,GAAG,QAAA;AAAA,YAClD,MAAM,KAAK;AAAA,cACT,2BAA2B;AAAA,gBACzB,SAAS,WAAW;AAAA,gBACpB;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA;AAAA,cAAA,CACD;AAAA,YAAA;AAAA,YAEH;AAAA,UAAA,CACD;AAAA,QAAA;AAEL,YAAI,CAAC,IAAI,IAAI;AACX,gBAAM,OAAO,MAAM,IAAI,OAAO,MAAM,MAAM,EAAE;AAC5C,gBAAM,IAAI,MAAM,mCAAmC,IAAI,MAAM,MAAM,IAAI,EAAE;AAAA,QAC3E;AACA,cAAM,OAAkD,MAAM,IAAI,KAAA;AAClE,eAAO;AAAA,UACL,KAAK,qBAAqB,EAAE,UAAU,KAAK,WAAW,QAAQ,YAAY;AAAA,UAC1E,WAAW,KAAK;AAAA,QAAA;AAAA,MAEpB;AAAA,MACA;AAAA;AAAA;AAAA,MAGA,WAAW,CAAC,WACV,OAAO,MAAM,OAAO,KAAK,IAAI,GAAG,OAAO,MAAM,KAAK,YAAY,MAAO,KAAK,IAAA,CAAK,IAAI;AAAA,MACrF,sBAAsB;AAAA,MACtB,oBAAoB;AAAA,MACpB,OAAO;AAAA,IAAA;AAAA,IAET,WAAW,kBAAkB,KAAK;AAAA,EAAA;AAGpC,SAAO;AAAA,IACL,UAAU,YAAW,WAAM,SAAN,mBAAY,QAAO,OAAQ;AAAA,IAChD,WAAW,WAAW,MAAM;AAAA,IAC5B,OAAO,WAAW,MAAM,QAAQ,MAAM,MAAM,UAAU;AAAA,EAAA;AAE1D;"}
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const BASE_MODE_ID = "base";
4
+ const WEB_ENTRY = "web";
5
+ const MAX_MODE_KEY_LENGTH = 64;
6
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
7
+ const isModeKey = (value) => typeof value === "string" && value.length > 0 && value.length <= MAX_MODE_KEY_LENGTH;
8
+ const decoded = (value) => {
9
+ if (typeof value !== "string") return value;
10
+ try {
11
+ return JSON.parse(value);
12
+ } catch {
13
+ return value;
14
+ }
15
+ };
16
+ const uniqueKeys = (value) => {
17
+ const kept = [];
18
+ if (Array.isArray(value)) {
19
+ for (const entry of value) {
20
+ if (isModeKey(entry) && !kept.includes(entry)) kept.push(entry);
21
+ }
22
+ }
23
+ return kept;
24
+ };
25
+ function publishedAgentModeSource(athenaMetadata) {
26
+ if (!isRecord(athenaMetadata) || !athenaMetadata.published_version_id) return null;
27
+ const config = athenaMetadata.published_config;
28
+ if (!isRecord(config)) return null;
29
+ const agentModes = decoded(config.agentModes);
30
+ const stored = isRecord(agentModes) ? agentModes : {};
31
+ const startModes = {};
32
+ if (isRecord(stored.startModes)) {
33
+ for (const [channel, mode] of Object.entries(stored.startModes)) {
34
+ if (isModeKey(channel) && isModeKey(mode)) startModes[channel] = mode;
35
+ }
36
+ }
37
+ const switchModes = {};
38
+ if (isRecord(stored.switchModes)) {
39
+ for (const [channel, targets] of Object.entries(stored.switchModes)) {
40
+ const kept = uniqueKeys(targets);
41
+ if (isModeKey(channel) && kept.length > 0) switchModes[channel] = kept;
42
+ }
43
+ }
44
+ const channels = {};
45
+ const customChannels = decoded(config.customChannels);
46
+ if (Array.isArray(customChannels)) {
47
+ for (const entry of customChannels) {
48
+ if (!isRecord(entry) || !isModeKey(entry.id) || entry.id in channels) continue;
49
+ const name = typeof entry.name === "string" ? entry.name.trim() : "";
50
+ channels[entry.id] = { label: name || entry.id, enabled: entry.enabled !== false };
51
+ }
52
+ }
53
+ return {
54
+ modes: uniqueKeys(stored.modes).filter((id) => id !== BASE_MODE_ID),
55
+ startModes,
56
+ switchModes,
57
+ lockedChannels: uniqueKeys(stored.lockedChannels),
58
+ channels
59
+ };
60
+ }
61
+ function agentModeEntry({
62
+ source,
63
+ channel
64
+ }) {
65
+ return channel && isCustomEntry({ source, channel }) ? channel : WEB_ENTRY;
66
+ }
67
+ const isCustomEntry = ({ source, channel }) => channel !== BASE_MODE_ID && !source.modes.includes(channel) && channel in source.channels;
68
+ function agentModeOfChannel({
69
+ source,
70
+ channel
71
+ }) {
72
+ return isCustomEntry({ source, channel }) ? source.startModes[channel] ?? channel : channel;
73
+ }
74
+ const optionLabel = ({ source, id }) => {
75
+ var _a;
76
+ return id === BASE_MODE_ID ? "Default" : ((_a = source.channels[id]) == null ? void 0 : _a.label) ?? id;
77
+ };
78
+ function agentModeMenu({
79
+ source,
80
+ channel,
81
+ hostModes
82
+ }) {
83
+ var _a;
84
+ const entry = agentModeEntry({ source, channel });
85
+ const startModeId = source.startModes[entry] ?? (entry === WEB_ENTRY ? BASE_MODE_ID : entry);
86
+ const ownMode = channel && agentModeOfChannel({ source, channel }) === channel ? [channel] : [];
87
+ const offered = /* @__PURE__ */ new Set([startModeId, ...ownMode, ...source.switchModes[entry] ?? []]);
88
+ const options = [];
89
+ for (const id of hostModes) {
90
+ if (!offered.has(id) || options.some((option) => option.id === id)) continue;
91
+ if (id !== BASE_MODE_ID && ((_a = source.channels[id]) == null ? void 0 : _a.enabled) === false) continue;
92
+ options.push({ id, label: optionLabel({ source, id }) });
93
+ }
94
+ return {
95
+ entry,
96
+ startModeId,
97
+ firstSendModeId: channel ? agentModeOfChannel({ source, channel }) : startModeId,
98
+ options,
99
+ locked: source.lockedChannels.includes(entry)
100
+ };
101
+ }
102
+ function sessionChannelFromMetadata(athenaMetadata) {
103
+ if (!isRecord(athenaMetadata)) return null;
104
+ const current = athenaMetadata.collab_current_channel_id;
105
+ if (isModeKey(current)) return current;
106
+ const stamp = athenaMetadata.collab_channel_id;
107
+ return isModeKey(stamp) ? stamp : null;
108
+ }
109
+ exports.BASE_MODE_ID = BASE_MODE_ID;
110
+ exports.agentModeEntry = agentModeEntry;
111
+ exports.agentModeMenu = agentModeMenu;
112
+ exports.agentModeOfChannel = agentModeOfChannel;
113
+ exports.publishedAgentModeSource = publishedAgentModeSource;
114
+ exports.sessionChannelFromMetadata = sessionChannelFromMetadata;
115
+ //# sourceMappingURL=agent-mode-menu.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-mode-menu.cjs","sources":["../../src/catalog/agent-mode-menu.ts"],"sourcesContent":["/**\n * A collab agent's mode menu for an SDK chat, from its published definition.\n *\n * A chat offers the modes a session entered through its channel may run in,\n * by the rule agora checks every switch with (`resolve_mode_switch`): the\n * channel's start mode, the channel itself, and the modes the agent's author\n * lists for it (`agentModes.switchModes[<entry>]`). A channel the author\n * locks (`agentModes.lockedChannels`) keeps each session in the mode it\n * starts in: people pick the mode before a session's first message, and it\n * stays. A custom channel that is not itself a mode is its own entry; no\n * channel, a mode or a built-in channel is the Default (Text) entry (`web`).\n *\n * The host lists which of those modes its menu shows (`AthenaProvider`'s\n * `modes`); a mode the agent does not offer there is left out, never sent.\n *\n * Pure, for `useAthenaChatMode` and the tests.\n */\n\n/** The id of the agent as configured, with no mode's overrides (agora's `base`). */\nexport const BASE_MODE_ID = 'base';\n\n/** The Default (Text) entry: chats that name no custom channel of their own. */\nconst WEB_ENTRY = 'web';\n\n/** agora's limit on a mode key's length (`_MAX_MODE_KEY_LENGTH`). */\nconst MAX_MODE_KEY_LENGTH = 64;\n\n/** What an agent's published definition says about its modes. */\nexport interface AgentModeSource {\n /** Custom channels the author marked as modes (`agentModes.modes`). */\n modes: readonly string[];\n /** Channel key → the mode its sessions start in (`agentModes.startModes`). */\n startModes: Readonly<Record<string, string>>;\n /** Entry channel key → the modes its sessions may switch to (`agentModes.switchModes`). */\n switchModes: Readonly<Record<string, readonly string[]>>;\n /** Entry channels that keep each session in its start mode (`agentModes.lockedChannels`). */\n lockedChannels: readonly string[];\n /** The agent's custom channels by id (`customChannels`). */\n channels: Readonly<Record<string, { label: string; enabled: boolean }>>;\n}\n\n/** One mode a chat may run in. */\nexport interface AthenaChatModeOption {\n /** What a send names as `collab_channel_id`: `base` or a mode's id. */\n id: string;\n /** The author's name for it (`Default` for `base`). */\n label: string;\n}\n\n/** The mode menu of a chat on one channel of an agent. */\nexport interface AgentModeMenu {\n /** The entry channel its sessions are judged by (`web` for the Default (Text) entry). */\n entry: string;\n /** The mode a session of the entry starts in. */\n startModeId: string;\n /** The mode a new chat's first send runs in when nobody picks one: the\n * entry's start mode, or the channel itself when it names a mode. */\n firstSendModeId: string;\n /** The modes the host listed that the agent offers there, in the host's order. */\n options: readonly AthenaChatModeOption[];\n /** The channel keeps each session in the mode it starts in. */\n locked: boolean;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst isModeKey = (value: unknown): value is string =>\n typeof value === 'string' && value.length > 0 && value.length <= MAX_MODE_KEY_LENGTH;\n\n/** A stored JSON field: the live doc keeps it as a string, the caches decoded. */\nconst decoded = (value: unknown): unknown => {\n if (typeof value !== 'string') return value;\n try {\n return JSON.parse(value) as unknown;\n } catch {\n return value;\n }\n};\n\nconst uniqueKeys = (value: unknown): string[] => {\n const kept: string[] = [];\n if (Array.isArray(value)) {\n for (const entry of value) {\n if (isModeKey(entry) && !kept.includes(entry)) kept.push(entry);\n }\n }\n return kept;\n};\n\n/**\n * What an agent's published definition (`athenaMetadata.published_config`)\n * says about its modes, read as leniently as agora reads it\n * (`parse_agent_modes`, `parse_custom_channels`): malformed parts are dropped.\n * `null` for an agent never published.\n */\nexport function publishedAgentModeSource(athenaMetadata: unknown): AgentModeSource | null {\n if (!isRecord(athenaMetadata) || !athenaMetadata.published_version_id) return null;\n const config = athenaMetadata.published_config;\n if (!isRecord(config)) return null;\n const agentModes = decoded(config.agentModes);\n const stored = isRecord(agentModes) ? agentModes : {};\n const startModes: Record<string, string> = {};\n if (isRecord(stored.startModes)) {\n for (const [channel, mode] of Object.entries(stored.startModes)) {\n if (isModeKey(channel) && isModeKey(mode)) startModes[channel] = mode;\n }\n }\n const switchModes: Record<string, string[]> = {};\n if (isRecord(stored.switchModes)) {\n for (const [channel, targets] of Object.entries(stored.switchModes)) {\n const kept = uniqueKeys(targets);\n if (isModeKey(channel) && kept.length > 0) switchModes[channel] = kept;\n }\n }\n const channels: Record<string, { label: string; enabled: boolean }> = {};\n const customChannels = decoded(config.customChannels);\n if (Array.isArray(customChannels)) {\n for (const entry of customChannels) {\n if (!isRecord(entry) || !isModeKey(entry.id) || entry.id in channels) continue;\n const name = typeof entry.name === 'string' ? entry.name.trim() : '';\n channels[entry.id] = { label: name || entry.id, enabled: entry.enabled !== false };\n }\n }\n return {\n modes: uniqueKeys(stored.modes).filter((id) => id !== BASE_MODE_ID),\n startModes,\n switchModes,\n lockedChannels: uniqueKeys(stored.lockedChannels),\n channels,\n };\n}\n\n/**\n * The entry a session on `channel` is judged by (agora `session_entry_channel`):\n * one of the agent's custom channels that is not a mode, else the Default\n * (Text) entry.\n */\nexport function agentModeEntry({\n source,\n channel,\n}: {\n source: AgentModeSource;\n channel: string | null;\n}): string {\n return channel && isCustomEntry({ source, channel }) ? channel : WEB_ENTRY;\n}\n\nconst isCustomEntry = ({ source, channel }: { source: AgentModeSource; channel: string }) =>\n channel !== BASE_MODE_ID && !source.modes.includes(channel) && channel in source.channels;\n\n/**\n * The mode a run on `channel` runs in, as agora compiles it: a custom entry\n * channel follows its start mode even when a send names it, while `base`, a\n * mode or a built-in channel runs its own layer.\n */\nexport function agentModeOfChannel({\n source,\n channel,\n}: {\n source: AgentModeSource;\n channel: string;\n}): string {\n return isCustomEntry({ source, channel }) ? (source.startModes[channel] ?? channel) : channel;\n}\n\nconst optionLabel = ({ source, id }: { source: AgentModeSource; id: string }): string =>\n id === BASE_MODE_ID ? 'Default' : (source.channels[id]?.label ?? id);\n\n/**\n * The menu of a chat on `channel` (the provider's `channel`, `null` for none)\n * that lists `hostModes`: the modes the agent offers its entry — its start\n * mode, the channel itself (unless naming it runs that start mode), its switch\n * list — that the host listed, in the host's order, without a mode that is\n * switched off.\n */\nexport function agentModeMenu({\n source,\n channel,\n hostModes,\n}: {\n source: AgentModeSource;\n channel: string | null;\n hostModes: readonly string[];\n}): AgentModeMenu {\n const entry = agentModeEntry({ source, channel });\n const startModeId = source.startModes[entry] ?? (entry === WEB_ENTRY ? BASE_MODE_ID : entry);\n // A custom entry channel that starts in another mode runs that mode when\n // named, so it is no option of its own.\n const ownMode = channel && agentModeOfChannel({ source, channel }) === channel ? [channel] : [];\n const offered = new Set([startModeId, ...ownMode, ...(source.switchModes[entry] ?? [])]);\n const options: AthenaChatModeOption[] = [];\n for (const id of hostModes) {\n if (!offered.has(id) || options.some((option) => option.id === id)) continue;\n if (id !== BASE_MODE_ID && source.channels[id]?.enabled === false) continue;\n options.push({ id, label: optionLabel({ source, id }) });\n }\n return {\n entry,\n startModeId,\n firstSendModeId: channel ? agentModeOfChannel({ source, channel }) : startModeId,\n options,\n locked: source.lockedChannels.includes(entry),\n };\n}\n\n/**\n * The channel agora reads as a session's current one, from its session\n * asset's metadata: the mode it last switched to or started in\n * (`collab_current_channel_id`), else its channel stamp (`collab_channel_id`).\n * A send that names it continues the session without switching it; the mode\n * it runs in is {@link agentModeOfChannel} of it. `null` when neither is set.\n */\nexport function sessionChannelFromMetadata(athenaMetadata: unknown): string | null {\n if (!isRecord(athenaMetadata)) return null;\n const current = athenaMetadata.collab_current_channel_id;\n if (isModeKey(current)) return current;\n const stamp = athenaMetadata.collab_channel_id;\n return isModeKey(stamp) ? stamp : null;\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * A collab agent's mode menu for an SDK chat, from its published definition.
3
+ *
4
+ * A chat offers the modes a session entered through its channel may run in,
5
+ * by the rule agora checks every switch with (`resolve_mode_switch`): the
6
+ * channel's start mode, the channel itself, and the modes the agent's author
7
+ * lists for it (`agentModes.switchModes[<entry>]`). A channel the author
8
+ * locks (`agentModes.lockedChannels`) keeps each session in the mode it
9
+ * starts in: people pick the mode before a session's first message, and it
10
+ * stays. A custom channel that is not itself a mode is its own entry; no
11
+ * channel, a mode or a built-in channel is the Default (Text) entry (`web`).
12
+ *
13
+ * The host lists which of those modes its menu shows (`AthenaProvider`'s
14
+ * `modes`); a mode the agent does not offer there is left out, never sent.
15
+ *
16
+ * Pure, for `useAthenaChatMode` and the tests.
17
+ */
18
+ /** The id of the agent as configured, with no mode's overrides (agora's `base`). */
19
+ export declare const BASE_MODE_ID = "base";
20
+ /** What an agent's published definition says about its modes. */
21
+ export interface AgentModeSource {
22
+ /** Custom channels the author marked as modes (`agentModes.modes`). */
23
+ modes: readonly string[];
24
+ /** Channel key → the mode its sessions start in (`agentModes.startModes`). */
25
+ startModes: Readonly<Record<string, string>>;
26
+ /** Entry channel key → the modes its sessions may switch to (`agentModes.switchModes`). */
27
+ switchModes: Readonly<Record<string, readonly string[]>>;
28
+ /** Entry channels that keep each session in its start mode (`agentModes.lockedChannels`). */
29
+ lockedChannels: readonly string[];
30
+ /** The agent's custom channels by id (`customChannels`). */
31
+ channels: Readonly<Record<string, {
32
+ label: string;
33
+ enabled: boolean;
34
+ }>>;
35
+ }
36
+ /** One mode a chat may run in. */
37
+ export interface AthenaChatModeOption {
38
+ /** What a send names as `collab_channel_id`: `base` or a mode's id. */
39
+ id: string;
40
+ /** The author's name for it (`Default` for `base`). */
41
+ label: string;
42
+ }
43
+ /** The mode menu of a chat on one channel of an agent. */
44
+ export interface AgentModeMenu {
45
+ /** The entry channel its sessions are judged by (`web` for the Default (Text) entry). */
46
+ entry: string;
47
+ /** The mode a session of the entry starts in. */
48
+ startModeId: string;
49
+ /** The mode a new chat's first send runs in when nobody picks one: the
50
+ * entry's start mode, or the channel itself when it names a mode. */
51
+ firstSendModeId: string;
52
+ /** The modes the host listed that the agent offers there, in the host's order. */
53
+ options: readonly AthenaChatModeOption[];
54
+ /** The channel keeps each session in the mode it starts in. */
55
+ locked: boolean;
56
+ }
57
+ /**
58
+ * What an agent's published definition (`athenaMetadata.published_config`)
59
+ * says about its modes, read as leniently as agora reads it
60
+ * (`parse_agent_modes`, `parse_custom_channels`): malformed parts are dropped.
61
+ * `null` for an agent never published.
62
+ */
63
+ export declare function publishedAgentModeSource(athenaMetadata: unknown): AgentModeSource | null;
64
+ /**
65
+ * The entry a session on `channel` is judged by (agora `session_entry_channel`):
66
+ * one of the agent's custom channels that is not a mode, else the Default
67
+ * (Text) entry.
68
+ */
69
+ export declare function agentModeEntry({ source, channel, }: {
70
+ source: AgentModeSource;
71
+ channel: string | null;
72
+ }): string;
73
+ /**
74
+ * The mode a run on `channel` runs in, as agora compiles it: a custom entry
75
+ * channel follows its start mode even when a send names it, while `base`, a
76
+ * mode or a built-in channel runs its own layer.
77
+ */
78
+ export declare function agentModeOfChannel({ source, channel, }: {
79
+ source: AgentModeSource;
80
+ channel: string;
81
+ }): string;
82
+ /**
83
+ * The menu of a chat on `channel` (the provider's `channel`, `null` for none)
84
+ * that lists `hostModes`: the modes the agent offers its entry — its start
85
+ * mode, the channel itself (unless naming it runs that start mode), its switch
86
+ * list — that the host listed, in the host's order, without a mode that is
87
+ * switched off.
88
+ */
89
+ export declare function agentModeMenu({ source, channel, hostModes, }: {
90
+ source: AgentModeSource;
91
+ channel: string | null;
92
+ hostModes: readonly string[];
93
+ }): AgentModeMenu;
94
+ /**
95
+ * The channel agora reads as a session's current one, from its session
96
+ * asset's metadata: the mode it last switched to or started in
97
+ * (`collab_current_channel_id`), else its channel stamp (`collab_channel_id`).
98
+ * A send that names it continues the session without switching it; the mode
99
+ * it runs in is {@link agentModeOfChannel} of it. `null` when neither is set.
100
+ */
101
+ export declare function sessionChannelFromMetadata(athenaMetadata: unknown): string | null;