@athenaintel/react 0.55.0 → 0.57.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.
- package/README.md +84 -1
- package/dist/agent-runtime/src/answer-label.cjs +8 -2
- package/dist/agent-runtime/src/answer-label.cjs.map +1 -1
- package/dist/agent-runtime/src/answer-label.d.ts +10 -4
- package/dist/agent-runtime/src/answer-label.js +8 -2
- package/dist/agent-runtime/src/answer-label.js.map +1 -1
- package/dist/agent-runtime/src/deep-agent-transport-recipe.cjs +6 -3
- package/dist/agent-runtime/src/deep-agent-transport-recipe.cjs.map +1 -1
- package/dist/agent-runtime/src/deep-agent-transport-recipe.js +6 -3
- package/dist/agent-runtime/src/deep-agent-transport-recipe.js.map +1 -1
- package/dist/agent-runtime/src/edit-command.cjs +40 -19
- package/dist/agent-runtime/src/edit-command.cjs.map +1 -1
- package/dist/agent-runtime/src/edit-command.d.ts +2 -0
- package/dist/agent-runtime/src/edit-command.js +41 -20
- package/dist/agent-runtime/src/edit-command.js.map +1 -1
- package/dist/agent-runtime/src/generated/runconfig-contract.cjs +8 -0
- package/dist/agent-runtime/src/generated/runconfig-contract.cjs.map +1 -1
- package/dist/agent-runtime/src/generated/runconfig-contract.d.ts +9 -0
- package/dist/agent-runtime/src/generated/runconfig-contract.js +8 -0
- package/dist/agent-runtime/src/generated/runconfig-contract.js.map +1 -1
- package/dist/agent-runtime/src/history-store.cjs +426 -0
- package/dist/agent-runtime/src/history-store.cjs.map +1 -0
- package/dist/agent-runtime/src/history-store.d.ts +282 -0
- package/dist/agent-runtime/src/history-store.js +426 -0
- package/dist/agent-runtime/src/history-store.js.map +1 -0
- package/dist/agent-runtime/src/inbound-channel.cjs +96 -0
- package/dist/agent-runtime/src/inbound-channel.cjs.map +1 -0
- package/dist/agent-runtime/src/inbound-channel.d.ts +75 -0
- package/dist/agent-runtime/src/inbound-channel.js +96 -0
- package/dist/agent-runtime/src/inbound-channel.js.map +1 -0
- package/dist/agent-runtime/src/index.d.ts +6 -5
- package/dist/agent-runtime/src/interrupts.cjs +45 -3
- package/dist/agent-runtime/src/interrupts.cjs.map +1 -1
- package/dist/agent-runtime/src/interrupts.d.ts +51 -4
- package/dist/agent-runtime/src/interrupts.js +46 -4
- package/dist/agent-runtime/src/interrupts.js.map +1 -1
- package/dist/agent-runtime/src/message-window.cjs +43 -5
- package/dist/agent-runtime/src/message-window.cjs.map +1 -1
- package/dist/agent-runtime/src/message-window.d.ts +26 -2
- package/dist/agent-runtime/src/message-window.js +44 -6
- package/dist/agent-runtime/src/message-window.js.map +1 -1
- package/dist/agent-runtime/src/offline-status.cjs.map +1 -1
- package/dist/agent-runtime/src/offline-status.d.ts +110 -4
- package/dist/agent-runtime/src/offline-status.js.map +1 -1
- package/dist/agent-runtime/src/runtime.d.ts +1 -0
- package/dist/agent-runtime/src/thread-handoff-flow-machine.d.ts +8 -8
- package/dist/agent-runtime/src/tool-metadata.cjs +16 -0
- package/dist/agent-runtime/src/tool-metadata.cjs.map +1 -1
- package/dist/agent-runtime/src/tool-metadata.js +16 -0
- package/dist/agent-runtime/src/tool-metadata.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-thread.cjs +8 -16
- package/dist/agent-runtime/src/use-deep-agent-thread.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-thread.d.ts +1 -9
- package/dist/agent-runtime/src/use-deep-agent-thread.js +9 -17
- package/dist/agent-runtime/src/use-deep-agent-thread.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs +86 -79
- package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-window-sync.d.ts +2 -27
- package/dist/agent-runtime/src/use-deep-agent-window-sync.js +89 -82
- package/dist/agent-runtime/src/use-deep-agent-window-sync.js.map +1 -1
- package/dist/auth/AthenaSSOProvider.cjs +54 -12
- package/dist/auth/AthenaSSOProvider.cjs.map +1 -1
- package/dist/auth/AthenaSSOProvider.d.ts +23 -2
- package/dist/auth/AthenaSSOProvider.js +54 -12
- package/dist/auth/AthenaSSOProvider.js.map +1 -1
- package/dist/auth/index.cjs +2 -0
- package/dist/auth/index.cjs.map +1 -1
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/sign-back-in.cjs +141 -0
- package/dist/auth/sign-back-in.cjs.map +1 -0
- package/dist/auth/sign-back-in.d.ts +117 -0
- package/dist/auth/sign-back-in.js +141 -0
- package/dist/auth/sign-back-in.js.map +1 -0
- package/dist/chat/AthenaChat.cjs +7 -2
- package/dist/chat/AthenaChat.cjs.map +1 -1
- package/dist/chat/AthenaChat.js +7 -2
- package/dist/chat/AthenaChat.js.map +1 -1
- package/dist/chat/StatewireApprovalCard.cjs +6 -2
- package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
- package/dist/chat/StatewireApprovalCard.d.ts +8 -1
- package/dist/chat/StatewireApprovalCard.js +7 -3
- package/dist/chat/StatewireApprovalCard.js.map +1 -1
- package/dist/chat/super-grouping/CollapsibleGroup.cjs +1 -1
- package/dist/chat/super-grouping/CollapsibleGroup.cjs.map +1 -1
- package/dist/chat/super-grouping/CollapsibleGroup.d.ts +6 -5
- package/dist/chat/super-grouping/CollapsibleGroup.js +1 -1
- package/dist/chat/super-grouping/CollapsibleGroup.js.map +1 -1
- package/dist/chat/super-grouping/earlier-steps-button.cjs +5 -6
- package/dist/chat/super-grouping/earlier-steps-button.cjs.map +1 -1
- package/dist/chat/super-grouping/earlier-steps-button.d.ts +6 -6
- package/dist/chat/super-grouping/earlier-steps-button.js +5 -6
- package/dist/chat/super-grouping/earlier-steps-button.js.map +1 -1
- package/dist/chat/super-grouping/toolkit-map.cjs +1 -0
- package/dist/chat/super-grouping/toolkit-map.cjs.map +1 -1
- package/dist/chat/super-grouping/toolkit-map.js +1 -0
- package/dist/chat/super-grouping/toolkit-map.js.map +1 -1
- package/dist/chat/super-grouping/use-turn-steps.cjs +3 -3
- package/dist/chat/super-grouping/use-turn-steps.js +1 -1
- package/dist/chat-ui/index.cjs +4 -0
- package/dist/chat-ui/index.cjs.map +1 -1
- package/dist/chat-ui/index.d.ts +1 -0
- package/dist/chat-ui/index.js +5 -1
- package/dist/chat-ui/index.js.map +1 -1
- package/dist/chat-ui/messages/inbound-channel-message.cjs +219 -0
- package/dist/chat-ui/messages/inbound-channel-message.cjs.map +1 -0
- package/dist/chat-ui/messages/inbound-channel-message.d.ts +60 -0
- package/dist/chat-ui/messages/inbound-channel-message.js +219 -0
- package/dist/chat-ui/messages/inbound-channel-message.js.map +1 -0
- package/dist/chat-ui/messages/inbound-channel-reader.cjs +184 -0
- package/dist/chat-ui/messages/inbound-channel-reader.cjs.map +1 -0
- package/dist/chat-ui/messages/inbound-channel-reader.d.ts +65 -0
- package/dist/chat-ui/messages/inbound-channel-reader.js +184 -0
- package/dist/chat-ui/messages/inbound-channel-reader.js.map +1 -0
- package/dist/chat-ui/messages/message-components.cjs +18 -14
- package/dist/chat-ui/messages/message-components.cjs.map +1 -1
- package/dist/chat-ui/messages/message-components.d.ts +7 -0
- package/dist/chat-ui/messages/message-components.js +19 -15
- package/dist/chat-ui/messages/message-components.js.map +1 -1
- package/dist/chat-ui/thread/statewire-history-edge.cjs +0 -4
- package/dist/chat-ui/thread/statewire-history-edge.cjs.map +1 -1
- package/dist/chat-ui/thread/statewire-history-edge.d.ts +5 -7
- package/dist/chat-ui/thread/statewire-history-edge.js +1 -5
- package/dist/chat-ui/thread/statewire-history-edge.js.map +1 -1
- package/dist/composer/sign-back-in-draft.cjs +62 -0
- package/dist/composer/sign-back-in-draft.cjs.map +1 -0
- package/dist/composer/sign-back-in-draft.d.ts +34 -0
- package/dist/composer/sign-back-in-draft.js +62 -0
- package/dist/composer/sign-back-in-draft.js.map +1 -0
- package/dist/diagnostics/athena-diagnostics.cjs.map +1 -1
- package/dist/diagnostics/athena-diagnostics.d.ts +6 -1
- package/dist/diagnostics/athena-diagnostics.js.map +1 -1
- package/dist/index.cjs +8 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/provider/AthenaProvider.cjs +60 -14
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.js +60 -14
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/provider/statewire-window-context.cjs.map +1 -1
- package/dist/provider/statewire-window-context.d.ts +8 -8
- package/dist/provider/statewire-window-context.js.map +1 -1
- package/dist/react/package.json.cjs +1 -1
- package/dist/react/package.json.js +1 -1
- package/dist/runtime/auth.cjs +11 -0
- package/dist/runtime/auth.cjs.map +1 -1
- package/dist/runtime/auth.d.ts +18 -1
- package/dist/runtime/auth.js +11 -0
- package/dist/runtime/auth.js.map +1 -1
- package/dist/runtime/useAthenaStatewireRuntime.cjs +0 -2
- package/dist/runtime/useAthenaStatewireRuntime.cjs.map +1 -1
- package/dist/runtime/useAthenaStatewireRuntime.d.ts +5 -5
- package/dist/runtime/useAthenaStatewireRuntime.js +0 -2
- package/dist/runtime/useAthenaStatewireRuntime.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/dist/agent-runtime/src/history-pages.cjs +0 -279
- package/dist/agent-runtime/src/history-pages.cjs.map +0 -1
- package/dist/agent-runtime/src/history-pages.d.ts +0 -252
- package/dist/agent-runtime/src/history-pages.js +0 -279
- package/dist/agent-runtime/src/history-pages.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-deep-agent-window-sync.js","sources":["../../../../agent-runtime/src/use-deep-agent-window-sync.ts"],"sourcesContent":["import type { StatewireThread } from '@assistant-ui/react-statewire';\nimport { useAuiState } from '@assistant-ui/store';\nimport { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';\nimport {\n DEEP_AGENT_HISTORY_PAGE_PROTOCOL_VERSION,\n DEEP_AGENT_INTEREST_PROTOCOL,\n DEEP_AGENT_MAX_MESSAGE_WINDOW,\n DEEP_AGENT_OVERSIZED_INTEREST_CODE,\n} from './generated/runconfig-contract';\nimport {\n DEEP_AGENT_HISTORY_PAGE_TURNS,\n type DeepAgentHistorySnapshot,\n type DeepAgentHistoryStore,\n} from './history-pages';\nimport {\n buildToolOutputCommand,\n type DeepAgentInterestRequest,\n type DeepAgentMessageWindow,\n messageWindowCarries,\n pageInterestRequests,\n readDeepAgentMessageWindow,\n readToolOutputResult,\n readTruncatedToolOutputArtifact,\n restatedInterestRequests,\n} from './message-window';\nimport { readDeepAgentProtocol } from './protocol';\nimport { hasStatewireThreadExtras } from './thread-extras';\nimport type { DeepAgentToolOutputStore } from './tool-outputs';\n\n/**\n * Pages of older history chained per approach to the top. A renderer lowers\n * its near-top level once prepended content has pushed the first message out\n * of its band; this bounds the case where it cannot (no viewport to scroll,\n * a list too short to overflow) so a 2,000-message thread never streams whole.\n */\nexport const DEEP_AGENT_MAX_CHAINED_HISTORY_LOADS = 3;\n\n/**\n * Pages of older history that add no converted message — every message on\n * them folds into the first loaded turn, such as one agent run's hundreds of\n * tool steps — loaded on top of a request (or an approach's chain) before it\n * stops. Such a page changes nothing the user can see, so it neither answers\n * an ask nor counts against the per-approach chain.\n *\n * A page loaded while the oldest loaded message is a reply lands inside that\n * reply's turn, so it asks for the host's largest window\n * (`DEEP_AGENT_MAX_MESSAGE_WINDOW`) instead of the thread's: a run of 373\n * tool calls is about 750 messages, nineteen round trips at the default\n * window and four at the largest, and the turn shows nothing new until its\n * prompt does. A page above a prompt keeps the thread's window.\n */\nexport const DEEP_AGENT_MAX_UNSEEN_HISTORY_LOADS = 25;\n\n/** The converted thread messages the tool-output half scans (web and native `ThreadMessage` both satisfy it). */\nexport interface DeepAgentWindowSyncMessage {\n readonly id: string;\n /** The converted role; lets the opening fill tell a turn's prompt from its reply. */\n readonly role?: string;\n readonly content: readonly { readonly type: string; readonly artifact?: unknown }[];\n}\n\n/** One page interest round trip, for the host's telemetry. */\nexport interface DeepAgentHistoryLoadEvent {\n /** The page's ordinal (`1` extends the head). */\n page: number;\n request: DeepAgentInterestRequest;\n durationMs: number;\n}\n\n/** One `history/page` round trip, for the host's telemetry. */\nexport interface DeepAgentHistoryPageEvent {\n /** The message the page was asked before. */\n before: string;\n /** Turns asked for, and answered. */\n turns: number;\n answered: number;\n durationMs: number;\n}\n\nexport interface UseDeepAgentWindowSyncOptions {\n /**\n * The converted thread messages — read by the host through its platform\n * `useAuiState((s) => s.thread.messages)`, so this hook imports neither\n * `@assistant-ui/react` nor `@assistant-ui/react-native`.\n */\n messages: readonly DeepAgentWindowSyncMessage[];\n /** Messages per page; the thread's window size by default. */\n pageSize?: number;\n /**\n * Full tool outputs fetched back for cut tool messages; the same store the\n * thread converts with (`useDeepAgentThread({ toolOutputs })`). Omitted, the\n * tool-output half is inert (a surface that never shows a cut output whole).\n */\n toolOutputs?: DeepAgentToolOutputStore | undefined;\n /**\n * The renderer's report that the top of the LOADED history is near — a\n * level, not an edge: while it holds and older messages exist, pages load\n * one after another (bounded by `maxChainedLoads` per approach). Omitted for\n * hosts with only a manual affordance (`loadOlder`).\n */\n nearTop?: boolean;\n /** Bumped on every rise of `nearTop` (and every explicit request), so the chain bound is per approach. */\n nearTopEpoch?: number;\n /**\n * Whether a message's content is mounted right now, so lazy fetches (cut\n * tool outputs) target what the user can see rather than the whole loaded\n * history. Omitted: every message counts as mounted (the flat renderers).\n */\n isMessageMounted?: ((messageId: string) => boolean) | undefined;\n /**\n * Cut tool outputs this mount reads at once; the rest wait for one to\n * settle. One mounted message can hold a whole agent run — a hundred tool\n * steps — and the host rate-limits a client's frames, so a reader that sends\n * them all together gets refusals back for most of them. Omitted: every\n * mounted output is asked for together (renderers whose mounted set is\n * already small).\n */\n maxConcurrentToolOutputs?: number;\n maxChainedLoads?: number;\n /** Pages adding no converted message loaded per request before it stops; see {@link DEEP_AGENT_MAX_UNSEEN_HISTORY_LOADS}. */\n maxUnseenLoads?: number;\n /** A page interest resolved (telemetry). */\n onHistoryLoad?: ((event: DeepAgentHistoryLoadEvent) => void) | undefined;\n /**\n * Older turns paged by command (`history/page`, protocol 3) into this store:\n * the same one the thread joins in front of its live window\n * (`useDeepAgentThread({ historyStore })`). Used only against a host that\n * advertises `DEEP_AGENT_HISTORY_PAGE_PROTOCOL_VERSION`; there every older\n * page (the button, the near-top chain, the opening fill) is a `history/page`\n * of whole turns and no interest page is stated, so the attach's view stays\n * the head however far back the reader goes. Omitted, or an older host:\n * interest pages, as before.\n */\n historyPages?: DeepAgentHistoryStore | undefined;\n /** Turns one history page asks for. */\n historyPageTurns?: number;\n /** A history page resolved (telemetry). */\n onHistoryPage?: ((event: DeepAgentHistoryPageEvent) => void) | undefined;\n /** A read failed; the sync itself recovers (the pending marks are released). */\n onError?:\n | ((error: unknown, command: 'athena/interest' | 'history/page' | 'history/tool_output') => void)\n | undefined;\n now?: () => number;\n}\n\nexport interface DeepAgentWindowSyncState {\n /** The joined window, or `null` while the head interest has not resolved (both halves are inert). */\n window: DeepAgentMessageWindow | null;\n /** Whether the host holds messages older than the loaded window. */\n hasOlder: boolean;\n /**\n * Whether the next page can be asked for: older messages exist and the\n * view has room for them. `hasOlder && !canLoadOlder` is the depth limit —\n * an edge row says so instead of offering a dead button. The host bounds\n * one attach's view twice: `MAX_INTERESTS` requests, and an interest\n * document that lists every loaded message within a byte budget (about a\n * thousand messages). The second is found when the host refuses a page as\n * `oversized-interest` ({@link isOversizedInterestRejection}).\n */\n canLoadOlder: boolean;\n /** A page of older history is in flight. */\n loading: boolean;\n /** Ask for the next page of older history now (an edge row's button, a keypress). A no-op before the head resolved, at the beginning, or once `canLoadOlder` is false. */\n loadOlder: () => void;\n}\n\n/**\n * Whether an `athena/interest` `set` came back refused because the windows it\n * states resolve past what one attach's interest document carries: the view\n * is as deep as this connection can load, and asking again only gets the same\n * refusal.\n */\nexport function isOversizedInterestRejection(error: unknown): boolean {\n const failure = (error as { failure?: { fate?: unknown; code?: unknown } } | null | undefined)\n ?.failure;\n return failure?.fate === 'rejected' && failure.code === DEEP_AGENT_OVERSIZED_INTEREST_CODE;\n}\n\nconst everyMessageMounted = (): boolean => true;\nconst defaultNow = (): number => Date.now();\nconst noopSubscribe = () => () => {};\n\n/** The statewire extras of the nearest aui thread; throws on a non-statewire thread like the library's own hooks. */\nconst useStatewireExtras = (): StatewireThread.Extras =>\n useAuiState((s) => {\n if (!hasStatewireThreadExtras(s.thread.extras)) {\n throw new Error('useDeepAgentWindowSync requires a statewire thread');\n }\n return s.thread.extras as StatewireThread.Extras;\n });\n\n/** The joined interest window of the nearest statewire thread. */\nexport function useDeepAgentMessageWindow(): DeepAgentMessageWindow | null {\n const documents = useStatewireExtras().documents;\n const mounted = useSyncExternalStore(\n documents?.subscribe ?? noopSubscribe,\n () => documents?.list(),\n () => documents?.list()\n );\n return useMemo(() => readDeepAgentMessageWindow(mounted), [mounted]);\n}\n\n/**\n * The client half of the interest window, shared by every surface — mounted\n * once per runtime, inside its aui provider.\n *\n * - **Older history.** While the host renderer reports the top of the loaded\n * history near (`nearTop`) and the window says older messages exist, this\n * re-states the interests with one more page (anchored at the window's\n * first message) — the host mounts its documents for this client alone —\n * chaining at most `maxChainedLoads` pages per approach; `loadOlder` asks\n * for one page on demand (an edge row's button). Never two loads in flight.\n * A re-attach's hello carries the view the client declares on the attach\n * (`useDeepAgentThread`), every page in it however it was loaded (this\n * hook, a handover's or a search's reveal, an edit's parent). A hello that\n * does not carry the view the last connected session held — a host that\n * reads no declaration and has no memo of the client id — gets it stated\n * again ({@link restatedInterestRequests}).\n * - **Cut tool outputs.** Tool cards whose output the wire carried cut\n * (`artifact._athena_content_truncated`) get it fetched whole through\n * `history/tool_output` once their message is mounted, at most\n * `maxConcurrentToolOutputs` at a time; the converter puts it back\n * (`toolOutputs`), so the card re-renders complete. An output the host\n * replaced since (the marker's `chars` changed) is fetched again.\n */\n/**\n * Whether an older page put a new converted message ahead of the thread's\n * first one. Older history only ever merges into or lands before the first\n * converted message, so the second one is a stable landmark: it moved down iff\n * the page showed something. A new tail message does not move it. Without a\n * second message (or once it is gone) the converted count decides.\n */\nfunction pageShowedEarlierMessage(\n from: { count: number; secondId: string | undefined },\n messages: readonly DeepAgentWindowSyncMessage[]\n): boolean {\n if (from.secondId !== undefined) {\n const index = messages.findIndex((message) => message.id === from.secondId);\n if (index !== -1) return index > 1;\n }\n return messages.length > from.count;\n}\n\n/** Whether a converted message is a turn's reply rather than its prompt (unknown without a role). */\nfunction isReply(message: DeepAgentWindowSyncMessage | undefined): boolean {\n return message?.role !== undefined && message.role !== 'user';\n}\n\n/**\n * Whether the loaded thread still opens without context: it should open on a\n * prompt and hold at least one earlier turn, so the newest turn shows with the\n * one before it (a turn is a prompt followed by its reply). Queued and live\n * prompts land at the tail with no reply yet, so they never count; prompts\n * alone need nothing. Without roles, a lone converted message stands in for it.\n */\nfunction opensMidTurn(messages: readonly DeepAgentWindowSyncMessage[]): boolean {\n const first = messages[0];\n if (first === undefined) return false;\n if (messages.every((message) => message.role === undefined)) return messages.length < 2;\n if (first.role !== 'user') return true;\n if (messages.every((message) => message.role === 'user')) return false;\n let turns = 0;\n for (let index = 1; index < messages.length; index += 1) {\n if (messages[index - 1]!.role === 'user' && messages[index]!.role !== 'user') turns += 1;\n }\n return turns < 2;\n}\n\nexport function useDeepAgentWindowSync({\n messages,\n pageSize,\n toolOutputs,\n nearTop = false,\n nearTopEpoch = 0,\n isMessageMounted = everyMessageMounted,\n maxConcurrentToolOutputs = Number.POSITIVE_INFINITY,\n maxChainedLoads = DEEP_AGENT_MAX_CHAINED_HISTORY_LOADS,\n maxUnseenLoads = DEEP_AGENT_MAX_UNSEEN_HISTORY_LOADS,\n onHistoryLoad,\n historyPages,\n historyPageTurns = DEEP_AGENT_HISTORY_PAGE_TURNS,\n onHistoryPage,\n onError,\n now = defaultNow,\n}: UseDeepAgentWindowSyncOptions): DeepAgentWindowSyncState {\n const { sendCommand, invoke, connection } = useStatewireExtras();\n const window = useDeepAgentMessageWindow();\n const hostVersion = useAuiState((s) => {\n const extras = s.thread.extras as Partial<StatewireThread.Extras> | undefined;\n return readDeepAgentProtocol(extras?.state)?.version ?? 0;\n });\n // Older history comes by command when the surface keeps a store for it and\n // the host answers it; the view then stays the head.\n const pagesByCommand =\n historyPages !== undefined && hostVersion >= DEEP_AGENT_HISTORY_PAGE_PROTOCOL_VERSION;\n const historySnapshot = useSyncExternalStore(\n historyPages?.subscribe ?? noopSubscribe,\n () => historyPages?.getSnapshot() ?? null,\n () => historyPages?.getSnapshot() ?? null\n );\n const liveIds = useMemo(() => new Set(window?.chain.map((entry) => entry.id) ?? []), [window]);\n // The pages that still meet the live window; a detached store is cleared below.\n const history: DeepAgentHistorySnapshot | null =\n historySnapshot !== null &&\n window !== null &&\n historyPages !== undefined &&\n !historyPages.isDetached({ threadId: window.threadId, liveIds })\n ? historySnapshot\n : null;\n const onHistoryPageRef = useRef(onHistoryPage);\n onHistoryPageRef.current = onHistoryPage;\n const [loading, setLoading] = useState(false);\n // The view the host refused a page over as deeper than one attach holds\n // (`oversized-interest`): its conversation and how many messages it covered.\n // A view at least that large gets the same refusal (a run's appends only\n // grow it); one that shrank — an edit rewound the transcript, a re-attach\n // came back smaller — may fit a page again.\n const [spentDepth, setSpentDepth] = useState<{ threadId: string | undefined; loaded: number } | null>(\n null\n );\n const depthSpent =\n spentDepth !== null &&\n window !== null &&\n spentDepth.threadId === window.threadId &&\n window.chain.length >= spentDepth.loaded;\n const [requests, setRequests] = useState(0);\n const servedRequestsRef = useRef(0);\n const inflightRef = useRef(false);\n const chainRef = useRef({ epoch: 0, loads: 0, unseen: 0, requested: false });\n // Pages loaded to show the opening turn's prompt; `null` once it shows, the\n // user took over, or a page failed — never re-armed for this mount.\n const openingFillRef = useRef<number | null>(0);\n // The converted thread when the in-flight page was stated; `null` once its\n // settle was judged (or it failed).\n const pageFromRef = useRef<{ count: number; secondId: string | undefined } | null>(null);\n const aliveRef = useRef(true);\n // The window the last connected session held, whoever paged it in, and the\n // session whose hello it was last checked against (a hello resets the\n // documents to what the host seeded).\n const heldRef = useRef<DeepAgentMessageWindow | null>(null);\n const restatedConnectionRef = useRef<unknown>(null);\n // Outputs already asked for and answered by this mount — fetched, refused,\n // or failed — by the `chars` they were asked for. The converter re-runs on\n // every wire change, and the store is a bounded LRU: without this memory a\n // refused read would leave on every pass, and once a transcript holds more\n // cut outputs than the store retains, each fetch would evict a still-loaded\n // output and the next pass would fetch it again, forever. An evicted output\n // renders its cut text (the REST display limit) rather than re-entering the\n // loop; a marker whose `chars` moved (the host replaced the output) is asked\n // for again.\n const answeredRef = useRef(new Map<string, number>());\n // Reads this mount has in flight, and whether a pass left outputs waiting\n // for one to settle (`maxConcurrentToolOutputs`): only then does a settle\n // re-run the pass, so an unbounded mount renders no more than before.\n const toolOutputsInFlightRef = useRef(0);\n const toolOutputsWaitingRef = useRef(false);\n const [toolOutputSettles, setToolOutputSettles] = useState(0);\n const onHistoryLoadRef = useRef(onHistoryLoad);\n onHistoryLoadRef.current = onHistoryLoad;\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n const nowRef = useRef(now);\n nowRef.current = now;\n useEffect(() => {\n aliveRef.current = true;\n return () => {\n aliveRef.current = false;\n };\n }, []);\n\n // Pages that no longer meet the live window (another conversation, or an\n // edit rewound past their anchor) describe another transcript: drop them,\n // and the edge offers older history again from the window's first message.\n useEffect(() => {\n if (historyPages === undefined || historySnapshot === null || window === null) return;\n if (window.chain.length === 0) return;\n if (historyPages.isDetached({ threadId: window.threadId, liveIds })) historyPages.clear();\n }, [historyPages, historySnapshot, window, liveIds]);\n\n useEffect(() => {\n if (connection.status !== 'connected' || window === null) return;\n if (restatedConnectionRef.current === connection) {\n heldRef.current = window;\n return;\n }\n // Decided once per connected session, on the hello's interest document.\n restatedConnectionRef.current = connection;\n const held = heldRef.current;\n heldRef.current = window;\n // A page still in flight was stated over the held window: it restates it.\n if (held === null || inflightRef.current || messageWindowCarries({ hello: window, held })) return;\n // Another conversation's hello (the runtime switched threads under this mount).\n if (held.threadId !== undefined && window.threadId !== undefined && held.threadId !== window.threadId) {\n return;\n }\n // Held like a page, so no page is stated over it while it is in flight.\n inflightRef.current = true;\n setLoading(true);\n invoke(DEEP_AGENT_INTEREST_PROTOCOL, 'set', [restatedInterestRequests({ held, hello: window })])\n .catch((error: unknown) => {\n onErrorRef.current?.(error, 'athena/interest');\n })\n .finally(() => {\n inflightRef.current = false;\n if (aliveRef.current) setLoading(false);\n });\n }, [connection, invoke, window]);\n\n useEffect(() => {\n if (window === null || inflightRef.current) return;\n // Older turns by command when this surface keeps a store and the host\n // answers it (whole turns before the oldest message held, outside the\n // view); interest pages otherwise. The asks are the same either way.\n const store = pagesByCommand ? historyPages : undefined;\n if (depthSpent && store === undefined) {\n // An ask now would get the same refusal: answer it here, so it never\n // reaches another conversation this mount switches to.\n servedRequestsRef.current = requests;\n pageFromRef.current = null;\n return;\n }\n const from = pageFromRef.current;\n pageFromRef.current = null;\n // A page by command always shows a turn's prompt (it never sets `from`).\n const lastPageUnseen =\n from !== null && from.count > 0 && !pageShowedEarlierMessage(from, messages);\n const pageRequests = store === undefined ? pageInterestRequests(window, pageSize) : null;\n if (store === undefined ? pageRequests === null : !(history?.more ?? window.hasOlder)) return;\n const requested = requests !== servedRequestsRef.current;\n const chain = chainRef.current;\n let fillPage = false;\n if (\n openingFillRef.current !== null &&\n (requested || nearTop || (messages.length > 0 && !opensMidTurn(messages)))\n ) {\n openingFillRef.current = null;\n }\n if (requested) {\n // An explicit ask re-arms the chain for this approach.\n servedRequestsRef.current = requests;\n chainRef.current = { epoch: nearTopEpoch, loads: 1, unseen: 0, requested: true };\n } else if (lastPageUnseen && (chain.requested || (nearTop && chain.epoch === nearTopEpoch))) {\n // The page landed inside the first loaded turn: keep going until one shows.\n if (chain.unseen >= maxUnseenLoads) return;\n chain.unseen += 1;\n } else if (openingFillRef.current !== null && opensMidTurn(messages)) {\n if (openingFillRef.current >= maxUnseenLoads) return;\n openingFillRef.current += 1;\n fillPage = true;\n } else {\n if (!nearTop) return;\n if (chain.epoch !== nearTopEpoch) {\n chainRef.current = { epoch: nearTopEpoch, loads: 0, unseen: 0, requested: false };\n }\n if (chainRef.current.loads >= maxChainedLoads) return;\n chainRef.current.loads += 1;\n }\n const startedAt = nowRef.current();\n const durationMs = () => Math.max(0, Math.round(nowRef.current() - startedAt));\n const settle = () => {\n inflightRef.current = false;\n if (aliveRef.current) setLoading(false);\n };\n if (store !== undefined) {\n const before = history?.turns[0]?.id ?? window.chain[0]?.id;\n if (before === undefined) return;\n inflightRef.current = true;\n setLoading(true);\n store\n .requestOlder({\n send: sendCommand,\n threadId: window.threadId ?? '',\n before,\n beforeIsPrompt:\n history !== null || window.messages.find((message) => message.id === before)?.type === 'human',\n turns: historyPageTurns,\n })\n .then((answered) => {\n onHistoryPageRef.current?.({ before, turns: historyPageTurns, answered, durationMs: durationMs() });\n })\n .catch((error: unknown) => {\n if (fillPage) openingFillRef.current = null;\n onErrorRef.current?.(error, 'history/page');\n })\n .finally(settle);\n return;\n }\n if (pageRequests === null) return;\n // The page continues the oldest loaded turn (see DEEP_AGENT_MAX_UNSEEN_HISTORY_LOADS).\n const insideTurn = lastPageUnseen || isReply(messages[0]);\n const stated =\n (insideTurn && pageInterestRequests(window, DEEP_AGENT_MAX_MESSAGE_WINDOW)) || pageRequests;\n pageFromRef.current = { count: messages.length, secondId: messages[1]?.id };\n const page = window.pages + 1;\n const request = stated[stated.length - 1] as DeepAgentInterestRequest;\n inflightRef.current = true;\n setLoading(true);\n invoke(DEEP_AGENT_INTEREST_PROTOCOL, 'set', [stated])\n .then(() => {\n onHistoryLoadRef.current?.({ page, request, durationMs: durationMs() });\n })\n .catch((error: unknown) => {\n pageFromRef.current = null;\n if (fillPage) openingFillRef.current = null;\n if (isOversizedInterestRejection(error) && aliveRef.current) {\n setSpentDepth({ threadId: window.threadId, loaded: window.chain.length });\n }\n onErrorRef.current?.(error, 'athena/interest');\n })\n .finally(settle);\n // `loading` re-arms the chain once a page settled after its documents moved the window.\n }, [\n nearTop,\n nearTopEpoch,\n requests,\n window,\n loading,\n messages,\n invoke,\n maxChainedLoads,\n maxUnseenLoads,\n pageSize,\n depthSpent,\n pagesByCommand,\n historyPages,\n history,\n historyPageTurns,\n sendCommand,\n ]);\n\n useEffect(() => {\n if (window === null || toolOutputs === undefined) return;\n toolOutputsWaitingRef.current = false;\n for (const message of messages) {\n if (!isMessageMounted(message.id)) continue;\n for (const part of message.content) {\n if (part.type !== 'tool-call') continue;\n const marker = readTruncatedToolOutputArtifact(part.artifact);\n if (marker === null || toolOutputs.isPending(marker.messageId)) continue;\n // Fetched for this very length already; a different length means the\n // host replaced the output under the same id since.\n if (toolOutputs.entry(marker.messageId)?.chars === marker.chars) continue;\n if (answeredRef.current.get(marker.messageId) === marker.chars) continue;\n if (toolOutputsInFlightRef.current >= maxConcurrentToolOutputs) {\n toolOutputsWaitingRef.current = true;\n return;\n }\n const { messageId, chars } = marker;\n toolOutputsInFlightRef.current += 1;\n toolOutputs.markPending(messageId);\n Promise.resolve(sendCommand(buildToolOutputCommand({ messageId })))\n .then((payload) => {\n answeredRef.current.set(messageId, chars);\n const result = readToolOutputResult(payload);\n if (result === null) {\n toolOutputs.clearPending(messageId);\n return;\n }\n toolOutputs.set(result.messageId, { content: result.content, chars });\n })\n .catch((error: unknown) => {\n answeredRef.current.set(messageId, chars);\n toolOutputs.clearPending(messageId);\n onErrorRef.current?.(error, 'history/tool_output');\n })\n .finally(() => {\n toolOutputsInFlightRef.current -= 1;\n if (toolOutputsWaitingRef.current && aliveRef.current) {\n setToolOutputSettles((count) => count + 1);\n }\n });\n }\n }\n }, [\n messages,\n isMessageMounted,\n window,\n sendCommand,\n toolOutputs,\n maxConcurrentToolOutputs,\n toolOutputSettles,\n ]);\n\n const loadOlder = useCallback(() => {\n setRequests((count) => count + 1);\n }, []);\n\n // By command, the host bounds nothing but the transcript's start: older\n // turns exist while the pages say so (the window's own edge before any).\n const hasOlder = pagesByCommand\n ? history !== null\n ? history.more\n : (window?.hasOlder ?? false)\n : (window?.hasOlder ?? false);\n const canLoadOlder = useMemo(\n () =>\n pagesByCommand\n ? window !== null && hasOlder\n : window !== null && !depthSpent && pageInterestRequests(window, pageSize) !== null,\n [pagesByCommand, hasOlder, window, pageSize, depthSpent]\n );\n\n return { window, hasOlder, canLoadOlder, loading, loadOlder };\n}\n"],"names":["_a"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-deep-agent-window-sync.js","sources":["../../../../agent-runtime/src/use-deep-agent-window-sync.ts"],"sourcesContent":["import type { StatewireThread } from '@assistant-ui/react-statewire';\nimport { useAuiState } from '@assistant-ui/store';\nimport { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';\nimport {\n DEEP_AGENT_INTEREST_PROTOCOL,\n DEEP_AGENT_MAX_MESSAGE_WINDOW,\n DEEP_AGENT_OVERSIZED_INTEREST_CODE,\n} from './generated/runconfig-contract';\nimport {\n buildHistoryPageCommand,\n deepAgentHistoryStore,\n deepAgentHistoryVersion,\n type HistoryPageCommand,\n historyPageAsInterestRequest,\n isHistoryPageUnserved,\n peekDeepAgentHistoryStore,\n readHistoryPageResult,\n subscribeDeepAgentHistory,\n} from './history-store';\nimport {\n buildToolOutputCommand,\n type DeepAgentInterestRequest,\n type DeepAgentMessageWindow,\n messageWindowCarries,\n pageInterestRequests,\n readDeepAgentMessageWindow,\n readToolOutputResult,\n readTruncatedToolOutputArtifact,\n restatedInterestRequests,\n} from './message-window';\nimport { hasStatewireThreadExtras } from './thread-extras';\nimport type { DeepAgentToolOutputStore } from './tool-outputs';\n\n/**\n * Pages of older history chained per approach to the top. A renderer lowers\n * its near-top level once prepended content has pushed the first message out\n * of its band; this bounds the case where it cannot (no viewport to scroll,\n * a list too short to overflow) so a 2,000-message thread never streams whole.\n */\nexport const DEEP_AGENT_MAX_CHAINED_HISTORY_LOADS = 3;\n\n/**\n * Pages of older history that add no converted message — every message on\n * them folds into the first loaded turn, such as one agent run's hundreds of\n * tool steps — loaded on top of a request (or an approach's chain) before it\n * stops. Such a page changes nothing the user can see, so it neither answers\n * an ask nor counts against the per-approach chain.\n *\n * A page loaded while the oldest loaded message is a reply lands inside that\n * reply's turn, so it asks for the host's largest window\n * (`DEEP_AGENT_MAX_MESSAGE_WINDOW`) instead of the thread's: a run of 373\n * tool calls is about 750 messages, nineteen round trips at the default\n * window and four at the largest, and the turn shows nothing new until its\n * prompt does. A page above a prompt keeps the thread's window.\n */\nexport const DEEP_AGENT_MAX_UNSEEN_HISTORY_LOADS = 25;\n\n/** The converted thread messages the tool-output half scans (web and native `ThreadMessage` both satisfy it). */\nexport interface DeepAgentWindowSyncMessage {\n readonly id: string;\n /** The converted role; lets the opening fill tell a turn's prompt from its reply. */\n readonly role?: string;\n readonly content: readonly { readonly type: string; readonly artifact?: unknown }[];\n}\n\n/** One page interest round trip, for the host's telemetry. */\nexport interface DeepAgentHistoryLoadEvent {\n /** The page's ordinal (`1` extends the head). */\n page: number;\n request: DeepAgentInterestRequest;\n durationMs: number;\n}\n\nexport interface UseDeepAgentWindowSyncOptions {\n /**\n * The converted thread messages — read by the host through its platform\n * `useAuiState((s) => s.thread.messages)`, so this hook imports neither\n * `@assistant-ui/react` nor `@assistant-ui/react-native`.\n */\n messages: readonly DeepAgentWindowSyncMessage[];\n /** Messages per page; the thread's window size by default. */\n pageSize?: number;\n /**\n * Full tool outputs fetched back for cut tool messages; the same store the\n * thread converts with (`useDeepAgentThread({ toolOutputs })`). Omitted, the\n * tool-output half is inert (a surface that never shows a cut output whole).\n */\n toolOutputs?: DeepAgentToolOutputStore | undefined;\n /**\n * The renderer's report that the top of the LOADED history is near — a\n * level, not an edge: while it holds and older messages exist, pages load\n * one after another (bounded by `maxChainedLoads` per approach). Omitted for\n * hosts with only a manual affordance (`loadOlder`).\n */\n nearTop?: boolean;\n /** Bumped on every rise of `nearTop` (and every explicit request), so the chain bound is per approach. */\n nearTopEpoch?: number;\n /**\n * Whether a message's content is mounted right now, so lazy fetches (cut\n * tool outputs) target what the user can see rather than the whole loaded\n * history. Omitted: every message counts as mounted (the flat renderers).\n */\n isMessageMounted?: ((messageId: string) => boolean) | undefined;\n /**\n * Cut tool outputs this mount reads at once; the rest wait for one to\n * settle. One mounted message can hold a whole agent run — a hundred tool\n * steps — and the host rate-limits a client's frames, so a reader that sends\n * them all together gets refusals back for most of them. Omitted: every\n * mounted output is asked for together (renderers whose mounted set is\n * already small).\n */\n maxConcurrentToolOutputs?: number;\n maxChainedLoads?: number;\n /** Pages adding no converted message loaded per request before it stops; see {@link DEEP_AGENT_MAX_UNSEEN_HISTORY_LOADS}. */\n maxUnseenLoads?: number;\n /** A page interest resolved (telemetry). */\n onHistoryLoad?: ((event: DeepAgentHistoryLoadEvent) => void) | undefined;\n /** A read failed; the sync itself recovers (the pending marks are released). */\n onError?:\n | ((error: unknown, command: 'athena/interest' | 'history/page' | 'history/tool_output') => void)\n | undefined;\n now?: () => number;\n}\n\nexport interface DeepAgentWindowSyncState {\n /** The joined window, or `null` while the head interest has not resolved (both halves are inert). */\n window: DeepAgentMessageWindow | null;\n /** Whether the host holds messages older than the loaded window. */\n hasOlder: boolean;\n /**\n * Whether the next page can be asked for: older messages exist and the\n * view has room for them. `hasOlder && !canLoadOlder` is the depth limit —\n * an edge row says so instead of offering a dead button. The host bounds\n * one attach's view twice: `MAX_INTERESTS` requests, and an interest\n * document that lists every loaded message within a byte budget (about a\n * thousand messages). The second is found when the host refuses a page as\n * `oversized-interest` ({@link isOversizedInterestRejection}).\n */\n canLoadOlder: boolean;\n /** A page of older history is in flight. */\n loading: boolean;\n /** Ask for the next page of older history now (an edge row's button, a keypress). A no-op before the head resolved, at the beginning, or once `canLoadOlder` is false. */\n loadOlder: () => void;\n}\n\n/**\n * Whether an `athena/interest` `set` came back refused because the windows it\n * states resolve past what one attach's interest document carries: the view\n * is as deep as this connection can load, and asking again only gets the same\n * refusal.\n */\nexport function isOversizedInterestRejection(error: unknown): boolean {\n const failure = (error as { failure?: { fate?: unknown; code?: unknown } } | null | undefined)\n ?.failure;\n return failure?.fate === 'rejected' && failure.code === DEEP_AGENT_OVERSIZED_INTEREST_CODE;\n}\n\nconst everyMessageMounted = (): boolean => true;\nconst defaultNow = (): number => Date.now();\nconst noopSubscribe = () => () => {};\n\n/** The statewire extras of the nearest aui thread; throws on a non-statewire thread like the library's own hooks. */\nconst useStatewireExtras = (): StatewireThread.Extras =>\n useAuiState((s) => {\n if (!hasStatewireThreadExtras(s.thread.extras)) {\n throw new Error('useDeepAgentWindowSync requires a statewire thread');\n }\n return s.thread.extras as StatewireThread.Extras;\n });\n\n/** The joined interest window of the nearest statewire thread, older history from its store included. */\nexport function useDeepAgentMessageWindow(): DeepAgentMessageWindow | null {\n const documents = useStatewireExtras().documents;\n const mounted = useSyncExternalStore(\n documents?.subscribe ?? noopSubscribe,\n () => documents?.list(),\n () => documents?.list()\n );\n const history = useSyncExternalStore(\n subscribeDeepAgentHistory,\n deepAgentHistoryVersion,\n deepAgentHistoryVersion\n );\n // biome-ignore lint/correctness/useExhaustiveDependencies: `history` is the stores' change signal; the read consults the store itself.\n return useMemo(() => readDeepAgentMessageWindow(mounted), [mounted, history]);\n}\n\n/** One page of older history to load: a `history/page` read, or (older hosts) an interest restatement. */\ntype DeepAgentHistoryPageLoad =\n | { kind: 'command'; command: HistoryPageCommand; threadId: string }\n | { kind: 'interest'; requests: DeepAgentInterestRequest[] };\n\nfunction nextHistoryPage(\n window: DeepAgentMessageWindow,\n paged: boolean,\n size: number | undefined\n): DeepAgentHistoryPageLoad | null {\n if (!paged) {\n const requests = pageInterestRequests(window, size);\n return requests === null ? null : { kind: 'interest', requests };\n }\n const before = window.chain[0]?.id;\n if (before === undefined || !window.hasOlder || window.threadId === undefined) return null;\n return {\n kind: 'command',\n // Turns, not messages: a page always reaches a prompt, however long the run.\n command: buildHistoryPageCommand({ before }),\n threadId: window.threadId,\n };\n}\n\n/**\n * The client half of the interest window, shared by every surface — mounted\n * once per runtime, inside its aui provider.\n *\n * - **Older history.** While the host renderer reports the top of the loaded\n * history near (`nearTop`) and the window says older messages exist, this\n * loads one more page below the window's first message, chaining at most\n * `maxChainedLoads` pages per approach; `loadOlder` asks for one page on\n * demand (an edge row's button). Never two loads in flight. On a host that\n * serves `history/page` (`window.historyPaged`) the page is that read, into\n * the conversation's history store, and no interest is stated; a host that\n * refuses it pages by interest for the rest of that connection. On older\n * hosts the page re-states the interests with one more page anchored at\n * the window's first message (the host mounts its documents for this\n * client alone).\n * A re-attach's hello carries the view the client declares on the attach\n * (`useDeepAgentThread`), every page in it however it was loaded (this\n * hook, a handover's or a search's reveal, an edit's parent). A hello that\n * does not carry the view the last connected session held — a host that\n * reads no declaration and has no memo of the client id — gets it stated\n * again ({@link restatedInterestRequests}).\n * - **Cut tool outputs.** Tool cards whose output the wire carried cut\n * (`artifact._athena_content_truncated`) get it fetched whole through\n * `history/tool_output` once their message is mounted, at most\n * `maxConcurrentToolOutputs` at a time; the converter puts it back\n * (`toolOutputs`), so the card re-renders complete. An output the host\n * replaced since (the marker's `chars` changed) is fetched again.\n */\n/**\n * Whether an older page put a new converted message ahead of the thread's\n * first one. Older history only ever merges into or lands before the first\n * converted message, so the second one is a stable landmark: it moved down iff\n * the page showed something. A new tail message does not move it. Without a\n * second message (or once it is gone) the converted count decides.\n */\nfunction pageShowedEarlierMessage(\n from: { count: number; secondId: string | undefined },\n messages: readonly DeepAgentWindowSyncMessage[]\n): boolean {\n if (from.secondId !== undefined) {\n const index = messages.findIndex((message) => message.id === from.secondId);\n if (index !== -1) return index > 1;\n }\n return messages.length > from.count;\n}\n\n/** Whether a converted message is a turn's reply rather than its prompt (unknown without a role). */\nfunction isReply(message: DeepAgentWindowSyncMessage | undefined): boolean {\n return message?.role !== undefined && message.role !== 'user';\n}\n\n/**\n * Whether the loaded thread still opens without context: it should open on a\n * prompt and hold at least one earlier turn, so the newest turn shows with the\n * one before it (a turn is a prompt followed by its reply). Queued and live\n * prompts land at the tail with no reply yet, so they never count; prompts\n * alone need nothing. Without roles, a lone converted message stands in for it.\n */\nfunction opensMidTurn(messages: readonly DeepAgentWindowSyncMessage[]): boolean {\n const first = messages[0];\n if (first === undefined) return false;\n if (messages.every((message) => message.role === undefined)) return messages.length < 2;\n if (first.role !== 'user') return true;\n if (messages.every((message) => message.role === 'user')) return false;\n let turns = 0;\n for (let index = 1; index < messages.length; index += 1) {\n if (messages[index - 1]!.role === 'user' && messages[index]!.role !== 'user') turns += 1;\n }\n return turns < 2;\n}\n\nexport function useDeepAgentWindowSync({\n messages,\n pageSize,\n toolOutputs,\n nearTop = false,\n nearTopEpoch = 0,\n isMessageMounted = everyMessageMounted,\n maxConcurrentToolOutputs = Number.POSITIVE_INFINITY,\n maxChainedLoads = DEEP_AGENT_MAX_CHAINED_HISTORY_LOADS,\n maxUnseenLoads = DEEP_AGENT_MAX_UNSEEN_HISTORY_LOADS,\n onHistoryLoad,\n onError,\n now = defaultNow,\n}: UseDeepAgentWindowSyncOptions): DeepAgentWindowSyncState {\n const { sendCommand, invoke, connection } = useStatewireExtras();\n const window = useDeepAgentMessageWindow();\n const paged = window?.historyPaged === true;\n const pagesRef = useRef(0);\n const [loading, setLoading] = useState(false);\n // The view the host refused a page over as deeper than one attach holds\n // (`oversized-interest`): its conversation and how many messages it covered.\n // A view at least that large gets the same refusal (a run's appends only\n // grow it); one that shrank — an edit rewound the transcript, a re-attach\n // came back smaller — may fit a page again.\n const [spentDepth, setSpentDepth] = useState<{ threadId: string | undefined; loaded: number } | null>(\n null\n );\n const depthSpent =\n !paged &&\n spentDepth !== null &&\n window !== null &&\n spentDepth.threadId === window.threadId &&\n window.chain.length >= spentDepth.loaded;\n const [requests, setRequests] = useState(0);\n const servedRequestsRef = useRef(0);\n const inflightRef = useRef(false);\n const chainRef = useRef({ epoch: 0, loads: 0, unseen: 0, requested: false });\n // Pages loaded to show the opening turn's prompt; `null` once it shows, the\n // user took over, or a page failed — never re-armed for this mount.\n const openingFillRef = useRef<number | null>(0);\n // The converted thread when the in-flight page was stated; `null` once its\n // settle was judged (or it failed).\n const pageFromRef = useRef<{\n count: number;\n secondId: string | undefined;\n /** Whether a pass already waited for the converted thread to show the page. */\n waited: boolean;\n } | null>(null);\n const aliveRef = useRef(true);\n // The window the last connected session held, whoever paged it in, and the\n // session whose hello it was last checked against (a hello resets the\n // documents to what the host seeded).\n const heldRef = useRef<DeepAgentMessageWindow | null>(null);\n const restatedConnectionRef = useRef<unknown>(null);\n // Outputs already asked for and answered by this mount — fetched, refused,\n // or failed — by the `chars` they were asked for. The converter re-runs on\n // every wire change, and the store is a bounded LRU: without this memory a\n // refused read would leave on every pass, and once a transcript holds more\n // cut outputs than the store retains, each fetch would evict a still-loaded\n // output and the next pass would fetch it again, forever. An evicted output\n // renders its cut text (the REST display limit) rather than re-entering the\n // loop; a marker whose `chars` moved (the host replaced the output) is asked\n // for again.\n const answeredRef = useRef(new Map<string, number>());\n // Reads this mount has in flight, and whether a pass left outputs waiting\n // for one to settle (`maxConcurrentToolOutputs`): only then does a settle\n // re-run the pass, so an unbounded mount renders no more than before.\n const toolOutputsInFlightRef = useRef(0);\n const toolOutputsWaitingRef = useRef(false);\n const [toolOutputSettles, setToolOutputSettles] = useState(0);\n const onHistoryLoadRef = useRef(onHistoryLoad);\n onHistoryLoadRef.current = onHistoryLoad;\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n const nowRef = useRef(now);\n nowRef.current = now;\n useEffect(() => {\n aliveRef.current = true;\n return () => {\n aliveRef.current = false;\n };\n }, []);\n\n // A host that refused `history/page` although it advertises it leaves the\n // conversation's store reading the live view alone (and paging by\n // interest) for that connection only: a later one may land on a host that\n // serves it. The store keeps which connection refused, so a remounted edge\n // (or the edit path's refusal) is judged the same way.\n const storeThreadId = window?.threadId;\n useEffect(() => {\n if (storeThreadId === undefined) return;\n const store = peekDeepAgentHistoryStore(storeThreadId);\n if (store?.unserved && store.unservedBy !== connection) store.markServed();\n }, [connection, storeThreadId]);\n\n useEffect(() => {\n if (connection.status !== 'connected' || window === null) return;\n // Older history lives in the conversation's store, not the view: the\n // hello needs nothing restated to show it.\n if (paged) {\n restatedConnectionRef.current = connection;\n heldRef.current = window;\n return;\n }\n if (restatedConnectionRef.current === connection) {\n heldRef.current = window;\n return;\n }\n // Decided once per connected session, on the hello's interest document.\n restatedConnectionRef.current = connection;\n const held = heldRef.current;\n heldRef.current = window;\n // A page still in flight was stated over the held window: it restates it.\n if (held === null || inflightRef.current || messageWindowCarries({ hello: window, held })) return;\n // Another conversation's hello (the runtime switched threads under this mount).\n if (held.threadId !== undefined && window.threadId !== undefined && held.threadId !== window.threadId) {\n return;\n }\n // Held like a page, so no page is stated over it while it is in flight.\n inflightRef.current = true;\n setLoading(true);\n invoke(DEEP_AGENT_INTEREST_PROTOCOL, 'set', [restatedInterestRequests({ held, hello: window })])\n .catch((error: unknown) => {\n onErrorRef.current?.(error, 'athena/interest');\n })\n .finally(() => {\n inflightRef.current = false;\n if (aliveRef.current) setLoading(false);\n });\n }, [connection, invoke, window, paged]);\n\n useEffect(() => {\n if (window === null || inflightRef.current) return;\n if (depthSpent) {\n // An ask now would get the same refusal: answer it here, so it never\n // reaches another conversation this mount switches to.\n servedRequestsRef.current = requests;\n pageFromRef.current = null;\n return;\n }\n const from = pageFromRef.current;\n const requestedNow = requests !== servedRequestsRef.current;\n if (\n paged &&\n from !== null &&\n !from.waited &&\n !requestedNow &&\n from.count > 0 &&\n !pageShowedEarlierMessage(from, messages)\n ) {\n // A page read by command lands in the history store, which the\n // converter reads on a pass of its own: a thread that shows nothing of\n // it yet may simply not have converted it. Judge it on the next pass.\n from.waited = true;\n return;\n }\n pageFromRef.current = null;\n const lastPageUnseen =\n from !== null && from.count > 0 && !pageShowedEarlierMessage(from, messages);\n const nextPage = nextHistoryPage(window, paged, pageSize);\n if (nextPage === null) return;\n const requested = requests !== servedRequestsRef.current;\n const chain = chainRef.current;\n let fillPage = false;\n if (\n openingFillRef.current !== null &&\n (requested || nearTop || (messages.length > 0 && !opensMidTurn(messages)))\n ) {\n openingFillRef.current = null;\n }\n if (requested) {\n // An explicit ask re-arms the chain for this approach.\n servedRequestsRef.current = requests;\n chainRef.current = { epoch: nearTopEpoch, loads: 1, unseen: 0, requested: true };\n } else if (lastPageUnseen && (chain.requested || (nearTop && chain.epoch === nearTopEpoch))) {\n // The page landed inside the first loaded turn: keep going until one shows.\n if (chain.unseen >= maxUnseenLoads) return;\n chain.unseen += 1;\n } else if (openingFillRef.current !== null && opensMidTurn(messages)) {\n if (openingFillRef.current >= maxUnseenLoads) return;\n openingFillRef.current += 1;\n fillPage = true;\n } else {\n if (!nearTop) return;\n if (chain.epoch !== nearTopEpoch) {\n chainRef.current = { epoch: nearTopEpoch, loads: 0, unseen: 0, requested: false };\n }\n if (chainRef.current.loads >= maxChainedLoads) return;\n chainRef.current.loads += 1;\n }\n // The page continues the oldest loaded turn (see DEEP_AGENT_MAX_UNSEEN_HISTORY_LOADS).\n const insideTurn = lastPageUnseen || isReply(messages[0]);\n const stated =\n (insideTurn && nextHistoryPage(window, paged, DEEP_AGENT_MAX_MESSAGE_WINDOW)) || nextPage;\n pageFromRef.current = {\n count: messages.length,\n secondId: messages[1]?.id,\n waited: false,\n };\n const page = stated.kind === 'command' ? pagesRef.current + 1 : window.pages + 1;\n const request =\n stated.kind === 'command'\n ? historyPageAsInterestRequest(stated.command)\n : (stated.requests[stated.requests.length - 1] as DeepAgentInterestRequest);\n inflightRef.current = true;\n setLoading(true);\n const startedAt = nowRef.current();\n const load =\n stated.kind === 'command'\n ? Promise.resolve(sendCommand(stated.command)).then((payload) => {\n const result = readHistoryPageResult(payload, stated.command.before);\n if (result === null) throw new Error('history/page answered a malformed page');\n pagesRef.current += 1;\n deepAgentHistoryStore(stated.threadId).addPage(result);\n })\n : invoke(DEEP_AGENT_INTEREST_PROTOCOL, 'set', [stated.requests]);\n load\n .then(() => {\n onHistoryLoadRef.current?.({\n page,\n request,\n durationMs: Math.max(0, Math.round(nowRef.current() - startedAt)),\n });\n })\n .catch((error: unknown) => {\n pageFromRef.current = null;\n if (fillPage) openingFillRef.current = null;\n if (stated.kind === 'command') {\n // A host that does not serve the command pages by interest instead.\n if (isHistoryPageUnserved(error)) {\n deepAgentHistoryStore(stated.threadId).markUnserved(connection);\n }\n onErrorRef.current?.(error, 'history/page');\n return;\n }\n if (isOversizedInterestRejection(error) && aliveRef.current) {\n setSpentDepth({ threadId: window.threadId, loaded: window.chain.length });\n }\n onErrorRef.current?.(error, 'athena/interest');\n })\n .finally(() => {\n inflightRef.current = false;\n if (aliveRef.current) setLoading(false);\n });\n // `loading` re-arms the chain once a page settled after its documents moved the window.\n }, [\n nearTop,\n nearTopEpoch,\n requests,\n window,\n loading,\n messages,\n invoke,\n sendCommand,\n connection,\n paged,\n maxChainedLoads,\n maxUnseenLoads,\n pageSize,\n depthSpent,\n ]);\n\n useEffect(() => {\n if (window === null || toolOutputs === undefined) return;\n toolOutputsWaitingRef.current = false;\n for (const message of messages) {\n if (!isMessageMounted(message.id)) continue;\n for (const part of message.content) {\n if (part.type !== 'tool-call') continue;\n const marker = readTruncatedToolOutputArtifact(part.artifact);\n if (marker === null || toolOutputs.isPending(marker.messageId)) continue;\n // Fetched for this very length already; a different length means the\n // host replaced the output under the same id since.\n if (toolOutputs.entry(marker.messageId)?.chars === marker.chars) continue;\n if (answeredRef.current.get(marker.messageId) === marker.chars) continue;\n if (toolOutputsInFlightRef.current >= maxConcurrentToolOutputs) {\n toolOutputsWaitingRef.current = true;\n return;\n }\n const { messageId, chars } = marker;\n toolOutputsInFlightRef.current += 1;\n toolOutputs.markPending(messageId);\n Promise.resolve(sendCommand(buildToolOutputCommand({ messageId })))\n .then((payload) => {\n answeredRef.current.set(messageId, chars);\n const result = readToolOutputResult(payload);\n if (result === null) {\n toolOutputs.clearPending(messageId);\n return;\n }\n toolOutputs.set(result.messageId, { content: result.content, chars });\n })\n .catch((error: unknown) => {\n answeredRef.current.set(messageId, chars);\n toolOutputs.clearPending(messageId);\n onErrorRef.current?.(error, 'history/tool_output');\n })\n .finally(() => {\n toolOutputsInFlightRef.current -= 1;\n if (toolOutputsWaitingRef.current && aliveRef.current) {\n setToolOutputSettles((count) => count + 1);\n }\n });\n }\n }\n }, [\n messages,\n isMessageMounted,\n window,\n sendCommand,\n toolOutputs,\n maxConcurrentToolOutputs,\n toolOutputSettles,\n ]);\n\n const loadOlder = useCallback(() => {\n setRequests((count) => count + 1);\n }, []);\n\n const canLoadOlder = useMemo(\n () =>\n window !== null &&\n !depthSpent &&\n nextHistoryPage(window, paged, pageSize) !== null,\n [window, pageSize, depthSpent, paged]\n );\n\n return { window, hasOlder: window?.hasOlder ?? false, canLoadOlder, loading, loadOlder };\n}\n"],"names":["_a"],"mappings":"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"}
|
|
@@ -7,6 +7,7 @@ const athenaDiagnostics = require("../diagnostics/athena-diagnostics.cjs");
|
|
|
7
7
|
const errors = require("../diagnostics/errors.cjs");
|
|
8
8
|
const auth = require("../runtime/auth.cjs");
|
|
9
9
|
const AthenaAuthContext = require("./AthenaAuthContext.cjs");
|
|
10
|
+
const signBackIn = require("./sign-back-in.cjs");
|
|
10
11
|
const SSO_USERINFO_RETRY_POLICY = {
|
|
11
12
|
...auth.DEFAULT_MINT_RETRY_POLICY,
|
|
12
13
|
timeoutMs: 1e4
|
|
@@ -28,7 +29,9 @@ async function readUserInfoOnce({
|
|
|
28
29
|
}, timeoutMs);
|
|
29
30
|
try {
|
|
30
31
|
const response = await fetch(url, { credentials: "include", signal: request.signal });
|
|
31
|
-
if (response.status === 401 || response.status === 403)
|
|
32
|
+
if (response.status === 401 || response.status === 403) {
|
|
33
|
+
return { kind: "unauthenticated", status: response.status === 401 ? 401 : 403 };
|
|
34
|
+
}
|
|
32
35
|
if (!response.ok) {
|
|
33
36
|
return auth.isRetryableMintStatus(response.status) ? { kind: "transient", status: response.status, reason: "status" } : { kind: "failed", status: response.status };
|
|
34
37
|
}
|
|
@@ -57,7 +60,9 @@ async function readSession({
|
|
|
57
60
|
for (let attempts = 1; ; attempts += 1) {
|
|
58
61
|
const attempt = await readUserInfoOnce({ url, signal, timeoutMs: policy.timeoutMs });
|
|
59
62
|
if (attempt.kind === "session") return { kind: "session", data: attempt.data };
|
|
60
|
-
if (attempt.kind === "unauthenticated")
|
|
63
|
+
if (attempt.kind === "unauthenticated") {
|
|
64
|
+
return { kind: "no_session", reason: "unauthenticated", status: attempt.status };
|
|
65
|
+
}
|
|
61
66
|
if (attempt.kind === "invalid_response") {
|
|
62
67
|
console.error(
|
|
63
68
|
'[AthenaSDK] Invalid SSO userinfo response: expected "user.user_id", "user.email", "orgMemberInfos", and "accessToken"'
|
|
@@ -177,8 +182,10 @@ function AthenaSSOProvider({
|
|
|
177
182
|
displayOnError,
|
|
178
183
|
onError,
|
|
179
184
|
revalidateOnWindowFocus = false,
|
|
185
|
+
signBackInOnSessionEnd = true,
|
|
180
186
|
retryPolicy = SSO_USERINFO_RETRY_POLICY
|
|
181
187
|
}) {
|
|
188
|
+
var _a, _b;
|
|
182
189
|
const [queryClient] = require$$0.useState(() => new reactQuery.QueryClient());
|
|
183
190
|
const retryPolicyRef = require$$0.useRef(retryPolicy);
|
|
184
191
|
retryPolicyRef.current = retryPolicy;
|
|
@@ -207,14 +214,14 @@ function AthenaSSOProvider({
|
|
|
207
214
|
const queryKey = require$$0.useMemo(() => ["athena-sdk", "sso-session", ssoUrls.userInfo], [ssoUrls.userInfo]);
|
|
208
215
|
const queryFn = require$$0.useCallback(
|
|
209
216
|
async ({ signal }) => {
|
|
210
|
-
var
|
|
217
|
+
var _a2;
|
|
211
218
|
try {
|
|
212
219
|
return await readSession({ url: ssoUrls.userInfo, signal, policy: retryPolicyRef.current });
|
|
213
220
|
} catch (readError) {
|
|
214
221
|
if (readError instanceof errors.AthenaSdkError) {
|
|
215
222
|
athenaDiagnostics.athenaDiagnostics.emit("auth.sso.userinfo.error", { error: readError });
|
|
216
223
|
try {
|
|
217
|
-
(
|
|
224
|
+
(_a2 = onErrorRef.current) == null ? void 0 : _a2.call(onErrorRef, readError);
|
|
218
225
|
} catch (callbackError) {
|
|
219
226
|
console.error("[AthenaSDK] AthenaSSOProvider onError threw", callbackError);
|
|
220
227
|
}
|
|
@@ -244,6 +251,35 @@ function AthenaSSOProvider({
|
|
|
244
251
|
const loading = query.isPending;
|
|
245
252
|
const reason = session ? null : (answer == null ? void 0 : answer.kind) === "no_session" ? answer.reason : query.isError ? "request_failed" : null;
|
|
246
253
|
const signedOut = (answer == null ? void 0 : answer.kind) === "no_session" && answer.signedOut === true;
|
|
254
|
+
const answerStatus = (answer == null ? void 0 : answer.kind) === "no_session" ? answer.status : void 0;
|
|
255
|
+
const servedSessionRef = require$$0.useRef(null);
|
|
256
|
+
if (session !== null) servedSessionRef.current = session;
|
|
257
|
+
const servedSession = servedSessionRef.current;
|
|
258
|
+
const endedAt = !loading && servedSession !== null && reason === "unauthenticated" && !signedOut ? query.dataUpdatedAt : null;
|
|
259
|
+
const endingRef = require$$0.useRef(null);
|
|
260
|
+
if (endedAt !== null && ((_a = endingRef.current) == null ? void 0 : _a.at) !== endedAt) {
|
|
261
|
+
endingRef.current = {
|
|
262
|
+
at: endedAt,
|
|
263
|
+
signBackIn: signBackInOnSessionEnd && answerStatus === 401 && signBackIn.canSignBackIn()
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
const [unsentEnding, setUnsentEnding] = require$$0.useState(null);
|
|
267
|
+
const signingBackIn = endedAt !== null && ((_b = endingRef.current) == null ? void 0 : _b.at) === endedAt && endingRef.current.signBackIn && unsentEnding !== endedAt;
|
|
268
|
+
require$$0.useEffect(() => {
|
|
269
|
+
if (endedAt === null) return;
|
|
270
|
+
if (!signingBackIn) {
|
|
271
|
+
if (unsentEnding !== endedAt) {
|
|
272
|
+
signBackIn.reportSessionEnded({ source: "sso", status: answerStatus, signBackIn: false });
|
|
273
|
+
}
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (!signBackIn.signBackIn({ loginUrl: ssoUrls.login, source: "sso", status: answerStatus })) {
|
|
277
|
+
setUnsentEnding(endedAt);
|
|
278
|
+
}
|
|
279
|
+
}, [endedAt, signingBackIn, unsentEnding, ssoUrls.login, answerStatus]);
|
|
280
|
+
require$$0.useEffect(() => {
|
|
281
|
+
signBackIn.restoreSignBackInHash();
|
|
282
|
+
}, []);
|
|
247
283
|
require$$0.useEffect(() => {
|
|
248
284
|
if (!revalidateOnWindowFocus || typeof window === "undefined") {
|
|
249
285
|
return;
|
|
@@ -264,10 +300,15 @@ function AthenaSSOProvider({
|
|
|
264
300
|
};
|
|
265
301
|
}, [revalidateOnWindowFocus, queryClient, queryKey]);
|
|
266
302
|
require$$0.useEffect(() => {
|
|
267
|
-
if (
|
|
268
|
-
|
|
303
|
+
if (loading || reason !== "unauthenticated" || signedOut || signingBackIn || displayOnError || typeof window === "undefined") {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (!signBackIn.redirectToSignIn({ loginUrl: ssoUrls.login })) {
|
|
307
|
+
console.warn(
|
|
308
|
+
"[AthenaSDK] Not sending the page to sign-in again: this tab went through sign-in less than two minutes ago and still has no session. Pass `displayOnError` to show a sign-in screen instead."
|
|
309
|
+
);
|
|
269
310
|
}
|
|
270
|
-
}, [loading, reason, signedOut, displayOnError, ssoUrls.login]);
|
|
311
|
+
}, [loading, reason, signedOut, signingBackIn, displayOnError, ssoUrls.login]);
|
|
271
312
|
const retry = require$$0.useCallback(() => {
|
|
272
313
|
void queryClient.resetQueries({ queryKey });
|
|
273
314
|
}, [queryClient, queryKey]);
|
|
@@ -294,7 +335,7 @@ function AthenaSSOProvider({
|
|
|
294
335
|
sessionRef.current = session;
|
|
295
336
|
const getToken = require$$0.useCallback(
|
|
296
337
|
async ({ force = false } = {}) => {
|
|
297
|
-
var
|
|
338
|
+
var _a2;
|
|
298
339
|
if (force) {
|
|
299
340
|
try {
|
|
300
341
|
const renewed = await queryClient.fetchQuery({
|
|
@@ -308,10 +349,11 @@ function AthenaSSOProvider({
|
|
|
308
349
|
} catch {
|
|
309
350
|
}
|
|
310
351
|
}
|
|
311
|
-
return ((
|
|
352
|
+
return ((_a2 = sessionRef.current) == null ? void 0 : _a2.accessToken) ?? null;
|
|
312
353
|
},
|
|
313
354
|
[queryClient, queryKey, queryFn]
|
|
314
355
|
);
|
|
356
|
+
const shownSession = session ?? (signingBackIn ? servedSession : null);
|
|
315
357
|
const authState = require$$0.useMemo(() => {
|
|
316
358
|
if (loading) {
|
|
317
359
|
return {
|
|
@@ -325,7 +367,7 @@ function AthenaSSOProvider({
|
|
|
325
367
|
getToken
|
|
326
368
|
};
|
|
327
369
|
}
|
|
328
|
-
if (!
|
|
370
|
+
if (!shownSession) {
|
|
329
371
|
return {
|
|
330
372
|
isLoggedIn: false,
|
|
331
373
|
isLoading: false,
|
|
@@ -336,7 +378,7 @@ function AthenaSSOProvider({
|
|
|
336
378
|
getToken
|
|
337
379
|
};
|
|
338
380
|
}
|
|
339
|
-
const { user, orgs, accessToken } = mapSSOResponse(
|
|
381
|
+
const { user, orgs, accessToken } = mapSSOResponse(shownSession);
|
|
340
382
|
return {
|
|
341
383
|
isLoggedIn: true,
|
|
342
384
|
isLoading: false,
|
|
@@ -346,7 +388,7 @@ function AthenaSSOProvider({
|
|
|
346
388
|
logout,
|
|
347
389
|
getToken
|
|
348
390
|
};
|
|
349
|
-
}, [loading,
|
|
391
|
+
}, [loading, shownSession, logout, getToken]);
|
|
350
392
|
if (authState.isLoading) {
|
|
351
393
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: displayWhileLoading ?? null });
|
|
352
394
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AthenaSSOProvider.cjs","sources":["../../src/auth/AthenaSSOProvider.tsx"],"sourcesContent":["import { QueryClient, useQuery } from '@tanstack/react-query';\nimport type { ReactNode } from 'react';\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport { AthenaSdkError } from '../diagnostics/errors';\nimport {\n DEFAULT_MINT_RETRY_POLICY,\n getMintRetryDelay,\n isRetryableMintStatus,\n type MintRetryPolicy,\n} from '../runtime/auth';\nimport type { AthenaStatewireTokenGetter } from '../runtime/statewire-auth-headers';\nimport { AthenaAuthContext } from './AthenaAuthContext';\nimport type {\n AthenaAuthState,\n AthenaOrg,\n AthenaSSOUserInfo,\n AthenaUser,\n} from './types';\n\n// ─── Props ────────────────────────────────────────────────────────────\n\nexport interface AthenaSSOProviderProps {\n children: ReactNode;\n\n /**\n * Base URL used to resolve the Athena SSO endpoints.\n *\n * When omitted, the provider defaults to same-origin relative paths:\n * `/api/sso/userinfo`, `/api/sso/initiate`, and `/api/sso/logout`.\n *\n * Use this when your frontend and backend live on different origins\n * (for example `https://app.example.com` + `https://api.example.com`).\n */\n ssoBaseUrl?: string;\n\n /**\n * Optional override for the SSO user-info endpoint that returns the\n * authenticated user's data and access token.\n *\n * The endpoint must return JSON matching `AthenaSSOUserInfo`:\n * ```json\n * {\n * \"user\": { \"user_id\": \"…\", \"email\": \"…\", … },\n * \"orgMemberInfos\": [{ \"orgId\": \"…\", \"orgName\": \"…\", \"urlSafeOrgName\": \"…\" }],\n * \"accessToken\": \"…\"\n * }\n * ```\n *\n * The request is sent with `credentials: 'include'` so that\n * session cookies are forwarded automatically.\n */\n ssoUserInfoUrl?: string;\n\n /**\n * Optional override for the URL to redirect the user to when no valid\n * SSO session exists.\n * This is typically the SSO initiation endpoint on your backend\n * (e.g. `https://api.yourdomain.com/api/sso/initiate`).\n */\n ssoLoginUrl?: string;\n\n /**\n * Optional override for the backend SSO logout endpoint.\n * Defaults to `/api/sso/logout` (or `${ssoBaseUrl}/api/sso/logout`).\n */\n ssoLogoutUrl?: string;\n\n /**\n * Custom element displayed while the SSO session is being verified.\n * Defaults to `null` (renders nothing).\n */\n displayWhileLoading?: ReactNode;\n\n /**\n * Shown when there is no session to sign the app in with: an element, or a\n * function that receives why (`reason`) and a `retry`, so the app can offer\n * sign-in only to a person who is signed out and a retry when the endpoint\n * could not answer (see `AthenaSSOFailure`).\n * If not provided, the user is automatically redirected to `ssoLoginUrl`\n * when the endpoint answers that there is no session.\n */\n displayOnError?: ReactNode | ((failure: AthenaSSOFailure) => ReactNode);\n\n /**\n * Called with the typed error (`auth_sso_failed`) when a session read gives\n * up. A nested `AthenaProvider`'s `onError` is not mounted while the first\n * read runs, so observe sign-in failures here.\n */\n onError?: (error: AthenaSdkError) => void;\n\n /**\n * Re-validate the SSO session when the window regains focus.\n * Disabled by default. When enabled, token expiry and user switching\n * are picked up without requiring a full page reload.\n */\n revalidateOnWindowFocus?: boolean;\n\n /** @internal Overrides how a session read retries; used by tests. */\n retryPolicy?: MintRetryPolicy;\n}\n\n/**\n * Why the provider has no session.\n *\n * - `unauthenticated`: the endpoint answered 401 or 403, or the person signed\n * out. Offer sign-in.\n * - `request_failed`: the endpoint did not answer usably. A network error, a\n * timeout, or a 5xx / 408 / 429 on every attempt of one read, or another\n * status. The person may well be signed in. Offer `retry`.\n * - `invalid_response`: it answered without a usable session.\n */\nexport type AthenaSSOFailureReason = 'unauthenticated' | 'invalid_response' | 'request_failed';\n\n/** What `displayOnError` receives when it is a function. */\nexport interface AthenaSSOFailure {\n reason: AthenaSSOFailureReason;\n /** Read the session again. `displayWhileLoading` shows meanwhile. */\n retry: () => void;\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────\n\n/**\n * How one session read retries: the parent bridge's mint policy (Marathon's\n * `/api/sso/userinfo` and `/_athena/auth` mint the same credential), with a\n * longer wait per request, since every request mints and a slow answer is\n * still an answer.\n */\nconst SSO_USERINFO_RETRY_POLICY: MintRetryPolicy = {\n ...DEFAULT_MINT_RETRY_POLICY,\n timeoutMs: 10_000,\n};\n\n/** One userinfo request, classified. */\ntype UserInfoAttempt =\n | { kind: 'session'; data: AthenaSSOUserInfo }\n | { kind: 'unauthenticated' }\n | { kind: 'invalid_response' }\n /** A status it does not retry, or a 2xx whose body is not JSON. */\n | { kind: 'failed'; status: number }\n | {\n kind: 'transient';\n status: number | null;\n reason: 'status' | 'network' | 'timeout';\n cause?: unknown;\n };\n\nconst abortError = () => new DOMException('The session read was cancelled.', 'AbortError');\n\n/** One userinfo request. Throws an `AbortError` when `signal` cancels the read. */\nasync function readUserInfoOnce({\n url,\n signal,\n timeoutMs,\n}: {\n url: string;\n signal: AbortSignal;\n timeoutMs: number;\n}): Promise<UserInfoAttempt> {\n if (signal.aborted) throw abortError();\n const request = new AbortController();\n const cancel = () => request.abort();\n signal.addEventListener('abort', cancel, { once: true });\n let timedOut = false;\n const timer = setTimeout(() => {\n timedOut = true;\n request.abort();\n }, timeoutMs);\n\n try {\n const response = await fetch(url, { credentials: 'include', signal: request.signal });\n if (response.status === 401 || response.status === 403) return { kind: 'unauthenticated' };\n if (!response.ok) {\n return isRetryableMintStatus(response.status)\n ? { kind: 'transient', status: response.status, reason: 'status' }\n : { kind: 'failed', status: response.status };\n }\n\n let data: unknown;\n try {\n data = await response.json();\n } catch (error) {\n if (signal.aborted) throw abortError();\n return timedOut\n ? { kind: 'transient', status: null, reason: 'timeout', cause: error }\n : { kind: 'failed', status: response.status };\n }\n return isValidSSOUserInfo(data) ? { kind: 'session', data } : { kind: 'invalid_response' };\n } catch (error) {\n if (signal.aborted) throw abortError();\n return { kind: 'transient', status: null, reason: timedOut ? 'timeout' : 'network', cause: error };\n } finally {\n clearTimeout(timer);\n signal.removeEventListener('abort', cancel);\n }\n}\n\n/**\n * What a read concluded, as the query holds it. A read that could not answer\n * throws instead (`request_failed`), so an earlier answer stays.\n */\ntype SessionAnswer =\n | { kind: 'session'; data: AthenaSSOUserInfo }\n | {\n kind: 'no_session';\n reason: Exclude<AthenaSSOFailureReason, 'request_failed'>;\n /** The person signed out here (`logout`), rather than the endpoint answering no session. */\n signedOut?: true;\n };\n\n/**\n * One session read: one bounded cycle of userinfo requests. A transient\n * failure is retried with `policy`'s capped, jittered backoff; a read that\n * gives up throws the typed error it reports. Throws an `AbortError` when\n * `signal` cancels the read.\n */\nasync function readSession({\n url,\n signal,\n policy,\n}: {\n url: string;\n signal: AbortSignal;\n policy: MintRetryPolicy;\n}): Promise<SessionAnswer> {\n const maxAttempts = Math.max(1, Math.floor(policy.maxAttempts));\n for (let attempts = 1; ; attempts += 1) {\n const attempt = await readUserInfoOnce({ url, signal, timeoutMs: policy.timeoutMs });\n\n if (attempt.kind === 'session') return { kind: 'session', data: attempt.data };\n if (attempt.kind === 'unauthenticated') return { kind: 'no_session', reason: 'unauthenticated' };\n if (attempt.kind === 'invalid_response') {\n console.error(\n '[AthenaSDK] Invalid SSO userinfo response: expected \"user.user_id\", \"user.email\", \"orgMemberInfos\", and \"accessToken\"',\n );\n return { kind: 'no_session', reason: 'invalid_response' };\n }\n\n if (attempt.kind === 'transient' && attempts < maxAttempts) {\n const delayMs = getMintRetryDelay({ retry: attempts, policy });\n athenaDiagnostics.emit('auth.sso.userinfo.retry', {\n level: 'warn',\n data: {\n status: attempt.status,\n reason: attempt.reason,\n attempt: attempts,\n max_attempts: maxAttempts,\n delay_ms: delayMs,\n },\n });\n await waitBeforeRetry({ delayMs, signal });\n continue;\n }\n\n throw sessionReadError({ url, attempt, attempts });\n }\n}\n\n/** The backoff between two requests of one read; cancelling the read ends it at once. */\nfunction waitBeforeRetry({ delayMs, signal }: { delayMs: number; signal: AbortSignal }): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal.aborted) {\n reject(abortError());\n return;\n }\n const onAbort = () => {\n clearTimeout(timer);\n reject(abortError());\n };\n const timer = setTimeout(() => {\n signal.removeEventListener('abort', onAbort);\n resolve();\n }, delayMs);\n signal.addEventListener('abort', onAbort, { once: true });\n });\n}\n\n/** The typed error a read reports when it gives up without an answer about the session. */\nfunction sessionReadError({\n url,\n attempt,\n attempts,\n}: {\n url: string;\n attempt: Extract<UserInfoAttempt, { kind: 'failed' | 'transient' }>;\n attempts: number;\n}): AthenaSdkError {\n if (attempt.kind === 'failed') {\n const unreadable = attempt.status >= 200 && attempt.status < 300;\n return new AthenaSdkError({\n code: 'auth_sso_failed',\n message: unreadable\n ? `The SSO session read (${url}) answered HTTP ${attempt.status} without a JSON body.`\n : `The SSO session read (${url}) answered HTTP ${attempt.status}.`,\n hint: 'Check that ssoBaseUrl / ssoUserInfoUrl point at the SSO userinfo endpoint.',\n status: attempt.status,\n context: { attempts, reason: unreadable ? 'invalid_body' : 'status' },\n });\n }\n const answer =\n attempt.status !== null\n ? `HTTP ${attempt.status}`\n : attempt.reason === 'timeout'\n ? 'a timeout'\n : 'a network error';\n return new AthenaSdkError({\n code: 'auth_sso_failed',\n message: `The SSO session read (${url}) failed ${attempts} time${attempts === 1 ? '' : 's'} (last: ${answer}).`,\n hint: 'The sign-in service did not answer; the person may still be signed in. Offer a retry (displayOnError receives retry).',\n ...(attempt.status !== null && { status: attempt.status }),\n ...(attempt.cause !== undefined && { cause: attempt.cause }),\n context: { attempts, reason: attempt.reason },\n });\n}\n\nconst DEFAULT_SSO_PATHS = {\n userInfo: '/api/sso/userinfo',\n login: '/api/sso/initiate',\n logout: '/api/sso/logout',\n} as const;\n\nconst trimTrailingSlash = (value: string): string => value.replace(/\\/+$/, '');\n\nfunction resolveSSOUrl({\n baseUrl,\n overrideUrl,\n defaultPath,\n}: {\n baseUrl?: string;\n overrideUrl?: string;\n defaultPath: string;\n}): string {\n if (overrideUrl) {\n return overrideUrl;\n }\n\n if (!baseUrl) {\n return defaultPath;\n }\n\n return `${trimTrailingSlash(baseUrl)}${defaultPath}`;\n}\n\nfunction isValidSSOUserInfo(data: unknown): data is AthenaSSOUserInfo {\n if (!data || typeof data !== 'object') {\n return false;\n }\n\n const candidate = data as {\n accessToken?: unknown;\n orgMemberInfos?: unknown;\n user?: {\n user_id?: unknown;\n email?: unknown;\n };\n };\n\n return (\n typeof candidate.accessToken === 'string' &&\n Array.isArray(candidate.orgMemberInfos) &&\n !!candidate.user &&\n typeof candidate.user.user_id === 'string' &&\n typeof candidate.user.email === 'string'\n );\n}\n\n/** Map the backend SSO response to the SDK's user/org types. */\nfunction mapSSOResponse(data: AthenaSSOUserInfo): {\n user: AthenaUser;\n orgs: AthenaOrg[];\n accessToken: string;\n} {\n const user: AthenaUser = {\n userId: data.user.user_id,\n email: data.user.email,\n firstName: data.user.first_name || undefined,\n lastName: data.user.last_name || undefined,\n username: data.user.username || undefined,\n pictureUrl: data.user.picture_url || undefined,\n properties: data.user.properties,\n };\n\n const orgs: AthenaOrg[] = data.orgMemberInfos.map((org) => ({\n orgId: org.orgId,\n orgName: org.orgName,\n urlSafeOrgName: org.urlSafeOrgName,\n }));\n\n return { user, orgs, accessToken: data.accessToken };\n}\n\n// ─── Provider ─────────────────────────────────────────────────────────\n\n/**\n * Wraps your app with Athena authentication powered by an external SSO\n * identity provider (e.g. Microsoft Entra / Azure AD, Okta, etc.).\n *\n * The SSO login flow is handled entirely by your backend. This provider\n * verifies the session by fetching `ssoUserInfoUrl` and exposes the\n * same `AthenaAuthState` context that `AthenaAuthProvider` (PropelAuth)\n * provides, so downstream components like `<AthenaProvider>` and\n * `useAthenaAuth()` work identically.\n *\n * Place this **above** `<AthenaProvider>` in the component tree.\n *\n * @example\n * ```tsx\n * import { AthenaSSOProvider } from '@athenaintel/react/auth';\n * import { AthenaProvider, AthenaChat } from '@athenaintel/react';\n *\n * function App() {\n * return (\n * <AthenaSSOProvider\n * ssoUserInfoUrl=\"https://api.yourdomain.com/api/sso/userinfo\"\n * ssoLoginUrl=\"https://api.yourdomain.com/api/sso/initiate\"\n * >\n * <AthenaProvider>\n * <AthenaChat />\n * </AthenaProvider>\n * </AthenaSSOProvider>\n * );\n * }\n * ```\n */\nexport function AthenaSSOProvider({\n children,\n ssoBaseUrl,\n ssoUserInfoUrl,\n ssoLoginUrl,\n ssoLogoutUrl,\n displayWhileLoading,\n displayOnError,\n onError,\n revalidateOnWindowFocus = false,\n retryPolicy = SSO_USERINFO_RETRY_POLICY,\n}: AthenaSSOProviderProps) {\n // The provider's own client, never a host's: the session holds an access\n // token, and a host may persist its query cache.\n const [queryClient] = useState(() => new QueryClient());\n const retryPolicyRef = useRef(retryPolicy);\n retryPolicyRef.current = retryPolicy;\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n\n const ssoUrls = useMemo(\n () => ({\n userInfo: resolveSSOUrl({\n baseUrl: ssoBaseUrl,\n overrideUrl: ssoUserInfoUrl,\n defaultPath: DEFAULT_SSO_PATHS.userInfo,\n }),\n login: resolveSSOUrl({\n baseUrl: ssoBaseUrl,\n overrideUrl: ssoLoginUrl,\n defaultPath: DEFAULT_SSO_PATHS.login,\n }),\n logout: resolveSSOUrl({\n baseUrl: ssoBaseUrl,\n overrideUrl: ssoLogoutUrl,\n defaultPath: DEFAULT_SSO_PATHS.logout,\n }),\n }),\n [ssoBaseUrl, ssoUserInfoUrl, ssoLoginUrl, ssoLogoutUrl],\n );\n\n const queryKey = useMemo(() => ['athena-sdk', 'sso-session', ssoUrls.userInfo], [ssoUrls.userInfo]);\n\n // One read is one bounded cycle (`readSession`). A read that gives up\n // throws, so the query keeps the session an earlier read answered: a failed\n // background revalidation or renewal does not sign the person out.\n const queryFn = useCallback(\n async ({ signal }: { signal: AbortSignal }): Promise<SessionAnswer> => {\n try {\n return await readSession({ url: ssoUrls.userInfo, signal, policy: retryPolicyRef.current });\n } catch (readError) {\n if (readError instanceof AthenaSdkError) {\n athenaDiagnostics.emit('auth.sso.userinfo.error', { error: readError });\n try {\n onErrorRef.current?.(readError);\n } catch (callbackError) {\n console.error('[AthenaSDK] AthenaSSOProvider onError threw', callbackError);\n }\n }\n throw readError;\n }\n },\n [ssoUrls.userInfo],\n );\n\n const query = useQuery(\n {\n queryKey,\n queryFn,\n // `readSession` retries a transient failure itself, with the bridge's\n // policy; the query never retries on top of it, and reads while the\n // browser reports itself offline too, so that policy decides.\n retry: false,\n networkMode: 'always',\n staleTime: Number.POSITIVE_INFINITY,\n refetchOnWindowFocus: false,\n refetchOnReconnect: false,\n },\n queryClient,\n );\n\n const answer = query.data;\n const session = answer?.kind === 'session' ? answer.data : null;\n const loading = query.isPending;\n const reason: AthenaSSOFailureReason | null = session\n ? null\n : answer?.kind === 'no_session'\n ? answer.reason\n : query.isError\n ? 'request_failed'\n : null;\n const signedOut = answer?.kind === 'no_session' && answer.signedOut === true;\n\n useEffect(() => {\n if (!revalidateOnWindowFocus || typeof window === 'undefined') {\n return;\n }\n\n // A revalidation joins a read already in flight (a renewal, the first\n // read) instead of cancelling it.\n const revalidate = () => {\n void queryClient.refetchQueries({ queryKey }, { cancelRefetch: false });\n };\n const handleVisibilityChange = () => {\n if (document.visibilityState === 'visible') {\n revalidate();\n }\n };\n\n window.addEventListener('focus', revalidate);\n document.addEventListener('visibilitychange', handleVisibilityChange);\n\n return () => {\n window.removeEventListener('focus', revalidate);\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n };\n }, [revalidateOnWindowFocus, queryClient, queryKey]);\n\n // Redirect to SSO login when the endpoint answered no session and no error\n // UI was provided. Signing out redirects by itself, when asked to.\n useEffect(() => {\n if (\n !loading &&\n reason === 'unauthenticated' &&\n !signedOut &&\n !displayOnError &&\n typeof window !== 'undefined'\n ) {\n window.location.assign(ssoUrls.login);\n }\n }, [loading, reason, signedOut, displayOnError, ssoUrls.login]);\n\n const retry = useCallback(() => {\n void queryClient.resetQueries({ queryKey });\n }, [queryClient, queryKey]);\n\n const logout = useCallback(async (redirectOnLogout = true) => {\n await queryClient.cancelQueries({ queryKey });\n\n try {\n await fetch(ssoUrls.logout, {\n method: 'POST',\n credentials: 'include',\n });\n } catch (logoutError) {\n console.error('[AthenaSDK] Failed to invalidate SSO session during logout', logoutError);\n }\n\n queryClient.setQueryData<SessionAnswer>(queryKey, {\n kind: 'no_session',\n reason: 'unauthenticated',\n signedOut: true,\n });\n\n if (redirectOnLogout && typeof window !== 'undefined') {\n window.location.assign(ssoUrls.login);\n }\n }, [queryClient, queryKey, ssoUrls.login, ssoUrls.logout]);\n\n // The SSO side of the SDK's credential source: `force` (the previous token\n // was refused) reads the session again for a new token, joining a read\n // already in flight; otherwise, or when that read has no session to hand\n // back, the held one is served.\n const sessionRef = useRef(session);\n sessionRef.current = session;\n const getToken = useCallback<AthenaStatewireTokenGetter>(\n async ({ force = false } = {}) => {\n if (force) {\n try {\n const renewed = await queryClient.fetchQuery({\n queryKey,\n queryFn,\n staleTime: 0,\n retry: false,\n networkMode: 'always',\n });\n if (renewed.kind === 'session') return renewed.data.accessToken;\n } catch {\n // The read gave up (already reported) or was cancelled.\n }\n }\n return sessionRef.current?.accessToken ?? null;\n },\n [queryClient, queryKey, queryFn],\n );\n\n const authState: AthenaAuthState = useMemo(() => {\n if (loading) {\n return {\n isLoggedIn: false,\n isLoading: true,\n user: null,\n accessToken: null,\n orgs: [],\n logout: async () => {},\n getToken,\n };\n }\n\n if (!session) {\n return {\n isLoggedIn: false,\n isLoading: false,\n user: null,\n accessToken: null,\n orgs: [],\n logout,\n getToken,\n };\n }\n\n const { user, orgs, accessToken } = mapSSOResponse(session);\n\n return {\n isLoggedIn: true,\n isLoading: false,\n user,\n accessToken,\n orgs,\n logout,\n getToken,\n };\n }, [loading, session, logout, getToken]);\n\n if (authState.isLoading) {\n return <>{displayWhileLoading ?? null}</>;\n }\n\n if (!authState.isLoggedIn) {\n if (typeof displayOnError === 'function') {\n return <>{displayOnError({ reason: reason ?? 'unauthenticated', retry })}</>;\n }\n return displayOnError ? <>{displayOnError}</> : null;\n }\n\n return (\n <AthenaAuthContext.Provider value={authState}>\n {children}\n </AthenaAuthContext.Provider>\n );\n}\n"],"names":["DEFAULT_MINT_RETRY_POLICY","isRetryableMintStatus","getMintRetryDelay","athenaDiagnostics","AthenaSdkError","useState","QueryClient","useRef","useMemo","useCallback","useQuery","useEffect","jsx","Fragment","AthenaAuthContext"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AthenaSSOProvider.cjs","sources":["../../src/auth/AthenaSSOProvider.tsx"],"sourcesContent":["import { QueryClient, useQuery } from '@tanstack/react-query';\nimport type { ReactNode } from 'react';\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport { AthenaSdkError } from '../diagnostics/errors';\nimport {\n DEFAULT_MINT_RETRY_POLICY,\n getMintRetryDelay,\n isRetryableMintStatus,\n type MintRetryPolicy,\n} from '../runtime/auth';\nimport type { AthenaStatewireTokenGetter } from '../runtime/statewire-auth-headers';\nimport { AthenaAuthContext } from './AthenaAuthContext';\nimport {\n canSignBackIn,\n redirectToSignIn,\n reportSessionEnded,\n restoreSignBackInHash,\n signBackIn,\n} from './sign-back-in';\nimport type {\n AthenaAuthState,\n AthenaOrg,\n AthenaSSOUserInfo,\n AthenaUser,\n} from './types';\n\n// ─── Props ────────────────────────────────────────────────────────────\n\nexport interface AthenaSSOProviderProps {\n children: ReactNode;\n\n /**\n * Base URL used to resolve the Athena SSO endpoints.\n *\n * When omitted, the provider defaults to same-origin relative paths:\n * `/api/sso/userinfo`, `/api/sso/initiate`, and `/api/sso/logout`.\n *\n * Use this when your frontend and backend live on different origins\n * (for example `https://app.example.com` + `https://api.example.com`).\n */\n ssoBaseUrl?: string;\n\n /**\n * Optional override for the SSO user-info endpoint that returns the\n * authenticated user's data and access token.\n *\n * The endpoint must return JSON matching `AthenaSSOUserInfo`:\n * ```json\n * {\n * \"user\": { \"user_id\": \"…\", \"email\": \"…\", … },\n * \"orgMemberInfos\": [{ \"orgId\": \"…\", \"orgName\": \"…\", \"urlSafeOrgName\": \"…\" }],\n * \"accessToken\": \"…\"\n * }\n * ```\n *\n * The request is sent with `credentials: 'include'` so that\n * session cookies are forwarded automatically.\n */\n ssoUserInfoUrl?: string;\n\n /**\n * Optional override for the URL to redirect the user to when no valid\n * SSO session exists.\n * This is typically the SSO initiation endpoint on your backend\n * (e.g. `https://api.yourdomain.com/api/sso/initiate`).\n */\n ssoLoginUrl?: string;\n\n /**\n * Optional override for the backend SSO logout endpoint.\n * Defaults to `/api/sso/logout` (or `${ssoBaseUrl}/api/sso/logout`).\n */\n ssoLogoutUrl?: string;\n\n /**\n * Custom element displayed while the SSO session is being verified.\n * Defaults to `null` (renders nothing).\n */\n displayWhileLoading?: ReactNode;\n\n /**\n * Shown when there is no session to sign the app in with: an element, or a\n * function that receives why (`reason`) and a `retry`, so the app can offer\n * sign-in only to a person who is signed out and a retry when the endpoint\n * could not answer (see `AthenaSSOFailure`).\n * If not provided, the user is automatically redirected to `ssoLoginUrl`\n * (with `return_to` set to the page) when the endpoint answers that there is\n * no session, at most once per tab every two minutes: within that time after\n * a trip through sign-in, the provider renders nothing instead of looping.\n *\n * A session that ends while the page is open goes through sign-in on its own\n * instead (`signBackInOnSessionEnd`); this shows only when that trip is\n * off, did not help, or cannot help (the endpoint answered 403).\n */\n displayOnError?: ReactNode | ((failure: AthenaSSOFailure) => ReactNode);\n\n /**\n * When a session this provider served ends while the page is open (a later\n * read answers 401: the person signed out of Athena elsewhere, or the host's\n * session for this app expired), send the page through `ssoLoginUrl` once,\n * with `return_to` set to the page, instead of showing `displayOnError`. A\n * 403 is the host refusing this person the app (Marathon's `not_authorized`\n * or `unknown_asset`): signing in again cannot change that, so it shows\n * `displayOnError`. A person still signed in to Athena comes straight back to\n * the same page, signed in again; one who is not sees their sign-in page\n * first. The app stays on screen until the page leaves, and an\n * `AthenaProvider` inside keeps the composer's draft for the page that\n * returns. At most one trip per tab every two minutes: a second end within\n * that time shows `displayOnError`. Defaults to `true`.\n */\n signBackInOnSessionEnd?: boolean;\n\n /**\n * Called with the typed error (`auth_sso_failed`) when a session read gives\n * up. A nested `AthenaProvider`'s `onError` is not mounted while the first\n * read runs, so observe sign-in failures here.\n */\n onError?: (error: AthenaSdkError) => void;\n\n /**\n * Re-validate the SSO session when the window regains focus.\n * Disabled by default. When enabled, token expiry and user switching\n * are picked up without requiring a full page reload.\n */\n revalidateOnWindowFocus?: boolean;\n\n /** @internal Overrides how a session read retries; used by tests. */\n retryPolicy?: MintRetryPolicy;\n}\n\n/**\n * Why the provider has no session.\n *\n * - `unauthenticated`: the endpoint answered 401 or 403, or the person signed\n * out. Offer sign-in.\n * - `request_failed`: the endpoint did not answer usably. A network error, a\n * timeout, or a 5xx / 408 / 429 on every attempt of one read, or another\n * status. The person may well be signed in. Offer `retry`.\n * - `invalid_response`: it answered without a usable session.\n */\nexport type AthenaSSOFailureReason = 'unauthenticated' | 'invalid_response' | 'request_failed';\n\n/** What `displayOnError` receives when it is a function. */\nexport interface AthenaSSOFailure {\n reason: AthenaSSOFailureReason;\n /** Read the session again. `displayWhileLoading` shows meanwhile. */\n retry: () => void;\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────\n\n/**\n * How one session read retries: the parent bridge's mint policy (Marathon's\n * `/api/sso/userinfo` and `/_athena/auth` mint the same credential), with a\n * longer wait per request, since every request mints and a slow answer is\n * still an answer.\n */\nconst SSO_USERINFO_RETRY_POLICY: MintRetryPolicy = {\n ...DEFAULT_MINT_RETRY_POLICY,\n timeoutMs: 10_000,\n};\n\n/** One userinfo request, classified. */\ntype UserInfoAttempt =\n | { kind: 'session'; data: AthenaSSOUserInfo }\n | { kind: 'unauthenticated'; status: 401 | 403 }\n | { kind: 'invalid_response' }\n /** A status it does not retry, or a 2xx whose body is not JSON. */\n | { kind: 'failed'; status: number }\n | {\n kind: 'transient';\n status: number | null;\n reason: 'status' | 'network' | 'timeout';\n cause?: unknown;\n };\n\nconst abortError = () => new DOMException('The session read was cancelled.', 'AbortError');\n\n/** One userinfo request. Throws an `AbortError` when `signal` cancels the read. */\nasync function readUserInfoOnce({\n url,\n signal,\n timeoutMs,\n}: {\n url: string;\n signal: AbortSignal;\n timeoutMs: number;\n}): Promise<UserInfoAttempt> {\n if (signal.aborted) throw abortError();\n const request = new AbortController();\n const cancel = () => request.abort();\n signal.addEventListener('abort', cancel, { once: true });\n let timedOut = false;\n const timer = setTimeout(() => {\n timedOut = true;\n request.abort();\n }, timeoutMs);\n\n try {\n const response = await fetch(url, { credentials: 'include', signal: request.signal });\n if (response.status === 401 || response.status === 403) {\n return { kind: 'unauthenticated', status: response.status === 401 ? 401 : 403 };\n }\n if (!response.ok) {\n return isRetryableMintStatus(response.status)\n ? { kind: 'transient', status: response.status, reason: 'status' }\n : { kind: 'failed', status: response.status };\n }\n\n let data: unknown;\n try {\n data = await response.json();\n } catch (error) {\n if (signal.aborted) throw abortError();\n return timedOut\n ? { kind: 'transient', status: null, reason: 'timeout', cause: error }\n : { kind: 'failed', status: response.status };\n }\n return isValidSSOUserInfo(data) ? { kind: 'session', data } : { kind: 'invalid_response' };\n } catch (error) {\n if (signal.aborted) throw abortError();\n return { kind: 'transient', status: null, reason: timedOut ? 'timeout' : 'network', cause: error };\n } finally {\n clearTimeout(timer);\n signal.removeEventListener('abort', cancel);\n }\n}\n\n/**\n * What a read concluded, as the query holds it. A read that could not answer\n * throws instead (`request_failed`), so an earlier answer stays.\n */\ntype SessionAnswer =\n | { kind: 'session'; data: AthenaSSOUserInfo }\n | {\n kind: 'no_session';\n reason: Exclude<AthenaSSOFailureReason, 'request_failed'>;\n /**\n * What the endpoint answered when it said no session: 401, there is\n * none; 403, the host refused this person the app. Absent otherwise.\n */\n status?: 401 | 403;\n /** The person signed out here (`logout`), rather than the endpoint answering no session. */\n signedOut?: true;\n };\n\n/**\n * One session read: one bounded cycle of userinfo requests. A transient\n * failure is retried with `policy`'s capped, jittered backoff; a read that\n * gives up throws the typed error it reports. Throws an `AbortError` when\n * `signal` cancels the read.\n */\nasync function readSession({\n url,\n signal,\n policy,\n}: {\n url: string;\n signal: AbortSignal;\n policy: MintRetryPolicy;\n}): Promise<SessionAnswer> {\n const maxAttempts = Math.max(1, Math.floor(policy.maxAttempts));\n for (let attempts = 1; ; attempts += 1) {\n const attempt = await readUserInfoOnce({ url, signal, timeoutMs: policy.timeoutMs });\n\n if (attempt.kind === 'session') return { kind: 'session', data: attempt.data };\n if (attempt.kind === 'unauthenticated') {\n return { kind: 'no_session', reason: 'unauthenticated', status: attempt.status };\n }\n if (attempt.kind === 'invalid_response') {\n console.error(\n '[AthenaSDK] Invalid SSO userinfo response: expected \"user.user_id\", \"user.email\", \"orgMemberInfos\", and \"accessToken\"',\n );\n return { kind: 'no_session', reason: 'invalid_response' };\n }\n\n if (attempt.kind === 'transient' && attempts < maxAttempts) {\n const delayMs = getMintRetryDelay({ retry: attempts, policy });\n athenaDiagnostics.emit('auth.sso.userinfo.retry', {\n level: 'warn',\n data: {\n status: attempt.status,\n reason: attempt.reason,\n attempt: attempts,\n max_attempts: maxAttempts,\n delay_ms: delayMs,\n },\n });\n await waitBeforeRetry({ delayMs, signal });\n continue;\n }\n\n throw sessionReadError({ url, attempt, attempts });\n }\n}\n\n/** The backoff between two requests of one read; cancelling the read ends it at once. */\nfunction waitBeforeRetry({ delayMs, signal }: { delayMs: number; signal: AbortSignal }): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal.aborted) {\n reject(abortError());\n return;\n }\n const onAbort = () => {\n clearTimeout(timer);\n reject(abortError());\n };\n const timer = setTimeout(() => {\n signal.removeEventListener('abort', onAbort);\n resolve();\n }, delayMs);\n signal.addEventListener('abort', onAbort, { once: true });\n });\n}\n\n/** The typed error a read reports when it gives up without an answer about the session. */\nfunction sessionReadError({\n url,\n attempt,\n attempts,\n}: {\n url: string;\n attempt: Extract<UserInfoAttempt, { kind: 'failed' | 'transient' }>;\n attempts: number;\n}): AthenaSdkError {\n if (attempt.kind === 'failed') {\n const unreadable = attempt.status >= 200 && attempt.status < 300;\n return new AthenaSdkError({\n code: 'auth_sso_failed',\n message: unreadable\n ? `The SSO session read (${url}) answered HTTP ${attempt.status} without a JSON body.`\n : `The SSO session read (${url}) answered HTTP ${attempt.status}.`,\n hint: 'Check that ssoBaseUrl / ssoUserInfoUrl point at the SSO userinfo endpoint.',\n status: attempt.status,\n context: { attempts, reason: unreadable ? 'invalid_body' : 'status' },\n });\n }\n const answer =\n attempt.status !== null\n ? `HTTP ${attempt.status}`\n : attempt.reason === 'timeout'\n ? 'a timeout'\n : 'a network error';\n return new AthenaSdkError({\n code: 'auth_sso_failed',\n message: `The SSO session read (${url}) failed ${attempts} time${attempts === 1 ? '' : 's'} (last: ${answer}).`,\n hint: 'The sign-in service did not answer; the person may still be signed in. Offer a retry (displayOnError receives retry).',\n ...(attempt.status !== null && { status: attempt.status }),\n ...(attempt.cause !== undefined && { cause: attempt.cause }),\n context: { attempts, reason: attempt.reason },\n });\n}\n\nconst DEFAULT_SSO_PATHS = {\n userInfo: '/api/sso/userinfo',\n login: '/api/sso/initiate',\n logout: '/api/sso/logout',\n} as const;\n\nconst trimTrailingSlash = (value: string): string => value.replace(/\\/+$/, '');\n\nfunction resolveSSOUrl({\n baseUrl,\n overrideUrl,\n defaultPath,\n}: {\n baseUrl?: string;\n overrideUrl?: string;\n defaultPath: string;\n}): string {\n if (overrideUrl) {\n return overrideUrl;\n }\n\n if (!baseUrl) {\n return defaultPath;\n }\n\n return `${trimTrailingSlash(baseUrl)}${defaultPath}`;\n}\n\nfunction isValidSSOUserInfo(data: unknown): data is AthenaSSOUserInfo {\n if (!data || typeof data !== 'object') {\n return false;\n }\n\n const candidate = data as {\n accessToken?: unknown;\n orgMemberInfos?: unknown;\n user?: {\n user_id?: unknown;\n email?: unknown;\n };\n };\n\n return (\n typeof candidate.accessToken === 'string' &&\n Array.isArray(candidate.orgMemberInfos) &&\n !!candidate.user &&\n typeof candidate.user.user_id === 'string' &&\n typeof candidate.user.email === 'string'\n );\n}\n\n/** Map the backend SSO response to the SDK's user/org types. */\nfunction mapSSOResponse(data: AthenaSSOUserInfo): {\n user: AthenaUser;\n orgs: AthenaOrg[];\n accessToken: string;\n} {\n const user: AthenaUser = {\n userId: data.user.user_id,\n email: data.user.email,\n firstName: data.user.first_name || undefined,\n lastName: data.user.last_name || undefined,\n username: data.user.username || undefined,\n pictureUrl: data.user.picture_url || undefined,\n properties: data.user.properties,\n };\n\n const orgs: AthenaOrg[] = data.orgMemberInfos.map((org) => ({\n orgId: org.orgId,\n orgName: org.orgName,\n urlSafeOrgName: org.urlSafeOrgName,\n }));\n\n return { user, orgs, accessToken: data.accessToken };\n}\n\n// ─── Provider ─────────────────────────────────────────────────────────\n\n/**\n * Wraps your app with Athena authentication powered by an external SSO\n * identity provider (e.g. Microsoft Entra / Azure AD, Okta, etc.).\n *\n * The SSO login flow is handled entirely by your backend. This provider\n * verifies the session by fetching `ssoUserInfoUrl` and exposes the\n * same `AthenaAuthState` context that `AthenaAuthProvider` (PropelAuth)\n * provides, so downstream components like `<AthenaProvider>` and\n * `useAthenaAuth()` work identically.\n *\n * Place this **above** `<AthenaProvider>` in the component tree.\n *\n * @example\n * ```tsx\n * import { AthenaSSOProvider } from '@athenaintel/react/auth';\n * import { AthenaProvider, AthenaChat } from '@athenaintel/react';\n *\n * function App() {\n * return (\n * <AthenaSSOProvider\n * ssoUserInfoUrl=\"https://api.yourdomain.com/api/sso/userinfo\"\n * ssoLoginUrl=\"https://api.yourdomain.com/api/sso/initiate\"\n * >\n * <AthenaProvider>\n * <AthenaChat />\n * </AthenaProvider>\n * </AthenaSSOProvider>\n * );\n * }\n * ```\n */\nexport function AthenaSSOProvider({\n children,\n ssoBaseUrl,\n ssoUserInfoUrl,\n ssoLoginUrl,\n ssoLogoutUrl,\n displayWhileLoading,\n displayOnError,\n onError,\n revalidateOnWindowFocus = false,\n signBackInOnSessionEnd = true,\n retryPolicy = SSO_USERINFO_RETRY_POLICY,\n}: AthenaSSOProviderProps) {\n // The provider's own client, never a host's: the session holds an access\n // token, and a host may persist its query cache.\n const [queryClient] = useState(() => new QueryClient());\n const retryPolicyRef = useRef(retryPolicy);\n retryPolicyRef.current = retryPolicy;\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n\n const ssoUrls = useMemo(\n () => ({\n userInfo: resolveSSOUrl({\n baseUrl: ssoBaseUrl,\n overrideUrl: ssoUserInfoUrl,\n defaultPath: DEFAULT_SSO_PATHS.userInfo,\n }),\n login: resolveSSOUrl({\n baseUrl: ssoBaseUrl,\n overrideUrl: ssoLoginUrl,\n defaultPath: DEFAULT_SSO_PATHS.login,\n }),\n logout: resolveSSOUrl({\n baseUrl: ssoBaseUrl,\n overrideUrl: ssoLogoutUrl,\n defaultPath: DEFAULT_SSO_PATHS.logout,\n }),\n }),\n [ssoBaseUrl, ssoUserInfoUrl, ssoLoginUrl, ssoLogoutUrl],\n );\n\n const queryKey = useMemo(() => ['athena-sdk', 'sso-session', ssoUrls.userInfo], [ssoUrls.userInfo]);\n\n // One read is one bounded cycle (`readSession`). A read that gives up\n // throws, so the query keeps the session an earlier read answered: a failed\n // background revalidation or renewal does not sign the person out.\n const queryFn = useCallback(\n async ({ signal }: { signal: AbortSignal }): Promise<SessionAnswer> => {\n try {\n return await readSession({ url: ssoUrls.userInfo, signal, policy: retryPolicyRef.current });\n } catch (readError) {\n if (readError instanceof AthenaSdkError) {\n athenaDiagnostics.emit('auth.sso.userinfo.error', { error: readError });\n try {\n onErrorRef.current?.(readError);\n } catch (callbackError) {\n console.error('[AthenaSDK] AthenaSSOProvider onError threw', callbackError);\n }\n }\n throw readError;\n }\n },\n [ssoUrls.userInfo],\n );\n\n const query = useQuery(\n {\n queryKey,\n queryFn,\n // `readSession` retries a transient failure itself, with the bridge's\n // policy; the query never retries on top of it, and reads while the\n // browser reports itself offline too, so that policy decides.\n retry: false,\n networkMode: 'always',\n staleTime: Number.POSITIVE_INFINITY,\n refetchOnWindowFocus: false,\n refetchOnReconnect: false,\n },\n queryClient,\n );\n\n const answer = query.data;\n const session = answer?.kind === 'session' ? answer.data : null;\n const loading = query.isPending;\n const reason: AthenaSSOFailureReason | null = session\n ? null\n : answer?.kind === 'no_session'\n ? answer.reason\n : query.isError\n ? 'request_failed'\n : null;\n const signedOut = answer?.kind === 'no_session' && answer.signedOut === true;\n // What the endpoint answered when it said no session (401 or 403).\n const answerStatus = answer?.kind === 'no_session' ? answer.status : undefined;\n\n // The last session this provider served on this page. A no-session answer\n // after one means the session ended while the page was open. Refs, not\n // state: both follow the query's answer, and a state update here would\n // render the ending once with `displayOnError` before deciding.\n const servedSessionRef = useRef<AthenaSSOUserInfo | null>(null);\n if (session !== null) servedSessionRef.current = session;\n const servedSession = servedSessionRef.current;\n // When the read that ended the session answered. Every read moves it, even\n // one whose answer equals the last (the query keeps the equal answer's\n // object), so each ending is told apart from the one before.\n const endedAt: number | null =\n !loading && servedSession !== null && reason === 'unauthenticated' && !signedOut\n ? query.dataUpdatedAt\n : null;\n // Decided once per ending: a trip through sign-in, unless it is off, the\n // endpoint answered 403 (the host refused this person the app: signing in\n // again cannot change that), or one less than two minutes ago did not bring\n // a session back (`canSignBackIn`).\n const endingRef = useRef<{ at: number; signBackIn: boolean } | null>(null);\n if (endedAt !== null && endingRef.current?.at !== endedAt) {\n endingRef.current = {\n at: endedAt,\n signBackIn: signBackInOnSessionEnd && answerStatus === 401 && canSignBackIn(),\n };\n }\n // An ending whose trip could not start after all (storage refused the guard).\n const [unsentEnding, setUnsentEnding] = useState<number | null>(null);\n const signingBackIn =\n endedAt !== null &&\n endingRef.current?.at === endedAt &&\n endingRef.current.signBackIn &&\n unsentEnding !== endedAt;\n\n useEffect(() => {\n if (endedAt === null) return;\n if (!signingBackIn) {\n // An ending the page stays on (`displayOnError` shows it). One whose\n // trip could not start was reported by `signBackIn` already.\n if (unsentEnding !== endedAt) {\n reportSessionEnded({ source: 'sso', status: answerStatus, signBackIn: false });\n }\n return;\n }\n // The app is still mounted (it keeps the last session until the page\n // leaves), so what it keeps for the page that returns, the composer's\n // draft among it, is kept on the way out.\n if (!signBackIn({ loginUrl: ssoUrls.login, source: 'sso', status: answerStatus })) {\n setUnsentEnding(endedAt);\n }\n }, [endedAt, signingBackIn, unsentEnding, ssoUrls.login, answerStatus]);\n\n // The page a trip through sign-in returned to gets its URL fragment back\n // (a hash-routed app's route), which Marathon's return drops.\n useEffect(() => {\n restoreSignBackInHash();\n }, []);\n\n useEffect(() => {\n if (!revalidateOnWindowFocus || typeof window === 'undefined') {\n return;\n }\n\n // A revalidation joins a read already in flight (a renewal, the first\n // read) instead of cancelling it.\n const revalidate = () => {\n void queryClient.refetchQueries({ queryKey }, { cancelRefetch: false });\n };\n const handleVisibilityChange = () => {\n if (document.visibilityState === 'visible') {\n revalidate();\n }\n };\n\n window.addEventListener('focus', revalidate);\n document.addEventListener('visibilitychange', handleVisibilityChange);\n\n return () => {\n window.removeEventListener('focus', revalidate);\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n };\n }, [revalidateOnWindowFocus, queryClient, queryKey]);\n\n // Without error UI, send the page to SSO login, returning to it, whenever\n // the endpoint answered no session: a first read with none, or an ending\n // that does not go through `signBackIn` above. At most once per tab every\n // two minutes (`redirectToSignIn` keeps `signBackIn`'s guard): after a trip\n // that did not bring a session back, the page stays and renders nothing\n // instead of looping. Signing out redirects by itself, when asked to.\n useEffect(() => {\n if (\n loading ||\n reason !== 'unauthenticated' ||\n signedOut ||\n signingBackIn ||\n displayOnError ||\n typeof window === 'undefined'\n ) {\n return;\n }\n if (!redirectToSignIn({ loginUrl: ssoUrls.login })) {\n console.warn(\n '[AthenaSDK] Not sending the page to sign-in again: this tab went through sign-in less than two minutes ago and still has no session. Pass `displayOnError` to show a sign-in screen instead.',\n );\n }\n }, [loading, reason, signedOut, signingBackIn, displayOnError, ssoUrls.login]);\n\n const retry = useCallback(() => {\n void queryClient.resetQueries({ queryKey });\n }, [queryClient, queryKey]);\n\n const logout = useCallback(async (redirectOnLogout = true) => {\n await queryClient.cancelQueries({ queryKey });\n\n try {\n await fetch(ssoUrls.logout, {\n method: 'POST',\n credentials: 'include',\n });\n } catch (logoutError) {\n console.error('[AthenaSDK] Failed to invalidate SSO session during logout', logoutError);\n }\n\n queryClient.setQueryData<SessionAnswer>(queryKey, {\n kind: 'no_session',\n reason: 'unauthenticated',\n signedOut: true,\n });\n\n if (redirectOnLogout && typeof window !== 'undefined') {\n window.location.assign(ssoUrls.login);\n }\n }, [queryClient, queryKey, ssoUrls.login, ssoUrls.logout]);\n\n // The SSO side of the SDK's credential source: `force` (the previous token\n // was refused) reads the session again for a new token, joining a read\n // already in flight; otherwise, or when that read has no session to hand\n // back, the held one is served.\n const sessionRef = useRef(session);\n sessionRef.current = session;\n const getToken = useCallback<AthenaStatewireTokenGetter>(\n async ({ force = false } = {}) => {\n if (force) {\n try {\n const renewed = await queryClient.fetchQuery({\n queryKey,\n queryFn,\n staleTime: 0,\n retry: false,\n networkMode: 'always',\n });\n if (renewed.kind === 'session') return renewed.data.accessToken;\n } catch {\n // The read gave up (already reported) or was cancelled.\n }\n }\n return sessionRef.current?.accessToken ?? null;\n },\n [queryClient, queryKey, queryFn],\n );\n\n // While the page is on its way through sign-in, the app keeps the session it\n // had: it stays on screen instead of flashing `displayOnError`.\n const shownSession = session ?? (signingBackIn ? servedSession : null);\n const authState: AthenaAuthState = useMemo(() => {\n if (loading) {\n return {\n isLoggedIn: false,\n isLoading: true,\n user: null,\n accessToken: null,\n orgs: [],\n logout: async () => {},\n getToken,\n };\n }\n\n if (!shownSession) {\n return {\n isLoggedIn: false,\n isLoading: false,\n user: null,\n accessToken: null,\n orgs: [],\n logout,\n getToken,\n };\n }\n\n const { user, orgs, accessToken } = mapSSOResponse(shownSession);\n\n return {\n isLoggedIn: true,\n isLoading: false,\n user,\n accessToken,\n orgs,\n logout,\n getToken,\n };\n }, [loading, shownSession, logout, getToken]);\n\n if (authState.isLoading) {\n return <>{displayWhileLoading ?? null}</>;\n }\n\n if (!authState.isLoggedIn) {\n if (typeof displayOnError === 'function') {\n return <>{displayOnError({ reason: reason ?? 'unauthenticated', retry })}</>;\n }\n return displayOnError ? <>{displayOnError}</> : null;\n }\n\n return (\n <AthenaAuthContext.Provider value={authState}>\n {children}\n </AthenaAuthContext.Provider>\n );\n}\n"],"names":["DEFAULT_MINT_RETRY_POLICY","isRetryableMintStatus","getMintRetryDelay","athenaDiagnostics","AthenaSdkError","useState","QueryClient","useRef","useMemo","useCallback","_a","useQuery","canSignBackIn","useEffect","reportSessionEnded","signBackIn","restoreSignBackInHash","redirectToSignIn","jsx","Fragment","AthenaAuthContext"],"mappings":"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"}
|
|
@@ -53,9 +53,30 @@ export interface AthenaSSOProviderProps {
|
|
|
53
53
|
* sign-in only to a person who is signed out and a retry when the endpoint
|
|
54
54
|
* could not answer (see `AthenaSSOFailure`).
|
|
55
55
|
* If not provided, the user is automatically redirected to `ssoLoginUrl`
|
|
56
|
-
* when the endpoint answers that there is
|
|
56
|
+
* (with `return_to` set to the page) when the endpoint answers that there is
|
|
57
|
+
* no session, at most once per tab every two minutes: within that time after
|
|
58
|
+
* a trip through sign-in, the provider renders nothing instead of looping.
|
|
59
|
+
*
|
|
60
|
+
* A session that ends while the page is open goes through sign-in on its own
|
|
61
|
+
* instead (`signBackInOnSessionEnd`); this shows only when that trip is
|
|
62
|
+
* off, did not help, or cannot help (the endpoint answered 403).
|
|
57
63
|
*/
|
|
58
64
|
displayOnError?: ReactNode | ((failure: AthenaSSOFailure) => ReactNode);
|
|
65
|
+
/**
|
|
66
|
+
* When a session this provider served ends while the page is open (a later
|
|
67
|
+
* read answers 401: the person signed out of Athena elsewhere, or the host's
|
|
68
|
+
* session for this app expired), send the page through `ssoLoginUrl` once,
|
|
69
|
+
* with `return_to` set to the page, instead of showing `displayOnError`. A
|
|
70
|
+
* 403 is the host refusing this person the app (Marathon's `not_authorized`
|
|
71
|
+
* or `unknown_asset`): signing in again cannot change that, so it shows
|
|
72
|
+
* `displayOnError`. A person still signed in to Athena comes straight back to
|
|
73
|
+
* the same page, signed in again; one who is not sees their sign-in page
|
|
74
|
+
* first. The app stays on screen until the page leaves, and an
|
|
75
|
+
* `AthenaProvider` inside keeps the composer's draft for the page that
|
|
76
|
+
* returns. At most one trip per tab every two minutes: a second end within
|
|
77
|
+
* that time shows `displayOnError`. Defaults to `true`.
|
|
78
|
+
*/
|
|
79
|
+
signBackInOnSessionEnd?: boolean;
|
|
59
80
|
/**
|
|
60
81
|
* Called with the typed error (`auth_sso_failed`) when a session read gives
|
|
61
82
|
* up. A nested `AthenaProvider`'s `onError` is not mounted while the first
|
|
@@ -119,4 +140,4 @@ export interface AthenaSSOFailure {
|
|
|
119
140
|
* }
|
|
120
141
|
* ```
|
|
121
142
|
*/
|
|
122
|
-
export declare function AthenaSSOProvider({ children, ssoBaseUrl, ssoUserInfoUrl, ssoLoginUrl, ssoLogoutUrl, displayWhileLoading, displayOnError, onError, revalidateOnWindowFocus, retryPolicy, }: AthenaSSOProviderProps): import("react").JSX.Element | null;
|
|
143
|
+
export declare function AthenaSSOProvider({ children, ssoBaseUrl, ssoUserInfoUrl, ssoLoginUrl, ssoLogoutUrl, displayWhileLoading, displayOnError, onError, revalidateOnWindowFocus, signBackInOnSessionEnd, retryPolicy, }: AthenaSSOProviderProps): import("react").JSX.Element | null;
|