@athenaintel/react 0.24.0 → 0.25.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 +29 -4
- package/dist/assets/AssetPanel.cjs +3 -2
- package/dist/assets/AssetPanel.cjs.map +1 -1
- package/dist/assets/AssetPanel.js +3 -2
- package/dist/assets/AssetPanel.js.map +1 -1
- package/dist/assets/AthenaAssetEmbed.cjs +1 -0
- package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
- package/dist/assets/AthenaAssetEmbed.js +1 -0
- package/dist/assets/AthenaAssetEmbed.js.map +1 -1
- package/dist/assets/use-asset-embed.cjs +22 -23
- package/dist/assets/use-asset-embed.cjs.map +1 -1
- package/dist/assets/use-asset-embed.d.ts +9 -0
- package/dist/assets/use-asset-embed.js +22 -23
- package/dist/assets/use-asset-embed.js.map +1 -1
- package/dist/auth/AthenaAuthProvider.cjs +32 -4
- package/dist/auth/AthenaAuthProvider.cjs.map +1 -1
- package/dist/auth/AthenaAuthProvider.d.ts +21 -0
- package/dist/auth/AthenaAuthProvider.js +33 -5
- package/dist/auth/AthenaAuthProvider.js.map +1 -1
- package/dist/auth/AthenaCredentialSourceContext.cjs +21 -0
- package/dist/auth/AthenaCredentialSourceContext.cjs.map +1 -0
- package/dist/auth/AthenaCredentialSourceContext.d.ts +14 -0
- package/dist/auth/AthenaCredentialSourceContext.js +21 -0
- package/dist/auth/AthenaCredentialSourceContext.js.map +1 -0
- package/dist/auth/AthenaSSOProvider.cjs +71 -47
- package/dist/auth/AthenaSSOProvider.cjs.map +1 -1
- package/dist/auth/AthenaSSOProvider.js +71 -47
- package/dist/auth/AthenaSSOProvider.js.map +1 -1
- package/dist/auth/credential-source.cjs +120 -0
- package/dist/auth/credential-source.cjs.map +1 -0
- package/dist/auth/credential-source.d.ts +68 -0
- package/dist/auth/credential-source.js +120 -0
- package/dist/auth/credential-source.js.map +1 -0
- package/dist/auth/types.d.ts +9 -0
- package/dist/chat/AthenaChat.cjs +8 -0
- package/dist/chat/AthenaChat.cjs.map +1 -1
- package/dist/chat/AthenaChat.js +8 -0
- package/dist/chat/AthenaChat.js.map +1 -1
- package/dist/chat/StatewireApprovalCard.cjs +31 -6
- package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
- package/dist/chat/StatewireApprovalCard.d.ts +34 -4
- package/dist/chat/StatewireApprovalCard.js +33 -8
- package/dist/chat/StatewireApprovalCard.js.map +1 -1
- package/dist/chat-ui/index.d.ts +1 -1
- package/dist/chat-ui/mentions/suggestions/data/api-client.cjs +20 -11
- package/dist/chat-ui/mentions/suggestions/data/api-client.cjs.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/api-client.d.ts +11 -2
- package/dist/chat-ui/mentions/suggestions/data/api-client.js +20 -11
- package/dist/chat-ui/mentions/suggestions/data/api-client.js.map +1 -1
- package/dist/chat-ui/statewire.d.ts +1 -1
- package/dist/collab/mint.cjs +6 -15
- package/dist/collab/mint.cjs.map +1 -1
- package/dist/collab/mint.d.ts +8 -0
- package/dist/collab/mint.js +6 -15
- package/dist/collab/mint.js.map +1 -1
- package/dist/collab/presence-mint.cjs +20 -16
- package/dist/collab/presence-mint.cjs.map +1 -1
- package/dist/collab/presence-mint.js +21 -17
- package/dist/collab/presence-mint.js.map +1 -1
- package/dist/collab/presence-react.cjs +12 -5
- package/dist/collab/presence-react.cjs.map +1 -1
- package/dist/collab/presence-react.d.ts +3 -1
- package/dist/collab/presence-react.js +12 -5
- package/dist/collab/presence-react.js.map +1 -1
- package/dist/collab/react.cjs +5 -2
- package/dist/collab/react.cjs.map +1 -1
- package/dist/collab/react.js +5 -2
- package/dist/collab/react.js.map +1 -1
- package/dist/dictation/use-athena-dictation-engine.cjs +12 -11
- package/dist/dictation/use-athena-dictation-engine.cjs.map +1 -1
- package/dist/dictation/use-athena-dictation-engine.d.ts +4 -3
- package/dist/dictation/use-athena-dictation-engine.js +12 -11
- package/dist/dictation/use-athena-dictation-engine.js.map +1 -1
- package/dist/index.cjs +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -1
- package/dist/packages/agent-runtime/src/conversation-list.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/conversation-list.js.map +1 -1
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs +1 -0
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.js +1 -0
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.js.map +1 -1
- package/dist/packages/agent-runtime/src/interrupts.cjs +22 -2
- package/dist/packages/agent-runtime/src/interrupts.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/interrupts.js +22 -2
- package/dist/packages/agent-runtime/src/interrupts.js.map +1 -1
- package/dist/packages/agent-runtime/src/messages.cjs +2 -1
- package/dist/packages/agent-runtime/src/messages.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/messages.js +2 -1
- package/dist/packages/agent-runtime/src/messages.js.map +1 -1
- package/dist/packages/agent-runtime/src/voice-presentation.cjs +6 -0
- package/dist/packages/agent-runtime/src/voice-presentation.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/voice-presentation.js +6 -0
- package/dist/packages/agent-runtime/src/voice-presentation.js.map +1 -1
- package/dist/packages/agent-runtime/src/voice-turn-grouping.cjs +68 -0
- package/dist/packages/agent-runtime/src/voice-turn-grouping.cjs.map +1 -0
- package/dist/packages/agent-runtime/src/voice-turn-grouping.js +68 -0
- package/dist/packages/agent-runtime/src/voice-turn-grouping.js.map +1 -0
- package/dist/packages/react/package.json.cjs +1 -1
- package/dist/packages/react/package.json.js +1 -1
- package/dist/provider/AthenaContext.cjs.map +1 -1
- package/dist/provider/AthenaContext.d.ts +7 -1
- package/dist/provider/AthenaContext.js.map +1 -1
- package/dist/provider/AthenaProvider.cjs +24 -2
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.js +24 -2
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/provider/theme-style-context.cjs +12 -0
- package/dist/provider/theme-style-context.cjs.map +1 -0
- package/dist/provider/theme-style-context.d.ts +8 -0
- package/dist/provider/theme-style-context.js +12 -0
- package/dist/provider/theme-style-context.js.map +1 -0
- package/dist/runtime/auth-refusal-restart.cjs +66 -0
- package/dist/runtime/auth-refusal-restart.cjs.map +1 -0
- package/dist/runtime/auth-refusal-restart.d.ts +35 -0
- package/dist/runtime/auth-refusal-restart.js +66 -0
- package/dist/runtime/auth-refusal-restart.js.map +1 -0
- package/dist/runtime/auth.cjs +58 -21
- package/dist/runtime/auth.cjs.map +1 -1
- package/dist/runtime/auth.d.ts +11 -1
- package/dist/runtime/auth.js +59 -22
- package/dist/runtime/auth.js.map +1 -1
- package/dist/runtime/credential-renewal.cjs +29 -0
- package/dist/runtime/credential-renewal.cjs.map +1 -0
- package/dist/runtime/credential-renewal.d.ts +35 -0
- package/dist/runtime/credential-renewal.js +29 -0
- package/dist/runtime/credential-renewal.js.map +1 -0
- package/dist/search/useAthenaRepoSearch.cjs +3 -5
- package/dist/search/useAthenaRepoSearch.cjs.map +1 -1
- package/dist/search/useAthenaRepoSearch.d.ts +7 -7
- package/dist/search/useAthenaRepoSearch.js +3 -5
- package/dist/search/useAthenaRepoSearch.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/threads/SessionList.cjs +87 -16
- package/dist/threads/SessionList.cjs.map +1 -1
- package/dist/threads/SessionList.js +88 -17
- package/dist/threads/SessionList.js.map +1 -1
- package/dist/threads/api.cjs +5 -1
- package/dist/threads/api.cjs.map +1 -1
- package/dist/threads/api.js +5 -1
- package/dist/threads/api.js.map +1 -1
- package/dist/threads/conversation-list-fetcher.cjs +5 -5
- package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
- package/dist/threads/conversation-list-fetcher.d.ts +6 -5
- package/dist/threads/conversation-list-fetcher.js +5 -5
- package/dist/threads/conversation-list-fetcher.js.map +1 -1
- package/dist/threads/session-list-model.cjs +13 -5
- package/dist/threads/session-list-model.cjs.map +1 -1
- package/dist/threads/session-list-model.d.ts +10 -3
- package/dist/threads/session-list-model.js +13 -5
- package/dist/threads/session-list-model.js.map +1 -1
- package/dist/threads/statewire-thread-list.cjs +13 -14
- package/dist/threads/statewire-thread-list.cjs.map +1 -1
- package/dist/threads/statewire-thread-list.js +13 -14
- package/dist/threads/statewire-thread-list.js.map +1 -1
- package/dist/threads/use-rest-conversation-history.cjs +6 -2
- package/dist/threads/use-rest-conversation-history.cjs.map +1 -1
- package/dist/threads/use-rest-conversation-history.d.ts +3 -1
- package/dist/threads/use-rest-conversation-history.js +7 -3
- package/dist/threads/use-rest-conversation-history.js.map +1 -1
- package/dist/threads/use-statewire-conversation-history.cjs +9 -2
- package/dist/threads/use-statewire-conversation-history.cjs.map +1 -1
- package/dist/threads/use-statewire-conversation-history.d.ts +3 -1
- package/dist/threads/use-statewire-conversation-history.js +10 -3
- package/dist/threads/use-statewire-conversation-history.js.map +1 -1
- package/dist/threads/use-thread-archive.cjs +2 -1
- package/dist/threads/use-thread-archive.cjs.map +1 -1
- package/dist/threads/use-thread-archive.d.ts +3 -1
- package/dist/threads/use-thread-archive.js +2 -1
- package/dist/threads/use-thread-archive.js.map +1 -1
- package/dist/threads/use-thread-favorite.cjs.map +1 -1
- package/dist/threads/use-thread-favorite.d.ts +3 -1
- package/dist/threads/use-thread-favorite.js.map +1 -1
- package/dist/threads/use-thread-field-override.cjs +2 -1
- package/dist/threads/use-thread-field-override.cjs.map +1 -1
- package/dist/threads/use-thread-field-override.d.ts +3 -1
- package/dist/threads/use-thread-field-override.js +2 -1
- package/dist/threads/use-thread-field-override.js.map +1 -1
- package/dist/threads/use-thread-rename.cjs.map +1 -1
- package/dist/threads/use-thread-rename.d.ts +3 -1
- package/dist/threads/use-thread-rename.js.map +1 -1
- package/dist/tools/tool-uis.cjs +11 -15
- package/dist/tools/tool-uis.cjs.map +1 -1
- package/dist/tools/tool-uis.js +11 -15
- package/dist/tools/tool-uis.js.map +1 -1
- package/dist/ui/popover.cjs +46 -0
- package/dist/ui/popover.cjs.map +1 -0
- package/dist/ui/popover.d.ts +6 -0
- package/dist/ui/popover.js +46 -0
- package/dist/ui/popover.js.map +1 -0
- package/dist/voice/use-athena-voice-session.cjs +17 -5
- package/dist/voice/use-athena-voice-session.cjs.map +1 -1
- package/dist/voice/use-athena-voice-session.d.ts +2 -2
- package/dist/voice/use-athena-voice-session.js +17 -5
- package/dist/voice/use-athena-voice-session.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionList.js","sources":["../../src/threads/SessionList.tsx"],"sourcesContent":["import {\n ArchiveIcon,\n ClockIcon,\n GitMergeIcon,\n GitPullRequestClosedIcon,\n GitPullRequestDraftIcon,\n GitPullRequestIcon,\n LoaderCircleIcon,\n PencilIcon,\n PinIcon,\n PlusIcon,\n RefreshCwIcon,\n SearchIcon,\n} from 'lucide-react';\nimport type { ChangeEvent, FC, KeyboardEvent, MouseEvent, ReactNode } from 'react';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { cn } from '../lib/utils';\nimport { THREAD_TITLE_MAX_LENGTH } from './api';\nimport {\n groupSessions,\n type PullRequestChip as PullRequestChipModel,\n type PullRequestChipState,\n pullRequestChip,\n type SessionIndicator as SessionIndicatorKind,\n type SessionLineTone,\n sessionIndicator,\n sessionOriginLabel,\n sessionSecondaryLine,\n} from './session-list-model';\nimport {\n type AthenaStatewireThreadSummary,\n useAthenaStatewireThreadList,\n} from './statewire-thread-list';\nimport { THREAD_PREFETCH_HOVER_DELAY_MS } from './thread-prefetcher';\nimport type { ThreadFavoriteTarget } from './use-thread-favorite';\nimport type { ThreadRenameTarget } from './use-thread-rename';\n\nexport interface SessionListProps {\n className?: string;\n /**\n * Group rows under \"Needs you\" / \"Working\" / \"Pinned\" / \"Recent\" (default\n * `true`); a session appears once, in the first group that applies. `false`\n * renders one newest-first list.\n */\n grouped?: boolean;\n /** Show the title search box, wired to the server-side `search` (default `true`). */\n showSearch?: boolean;\n /**\n * Offer the pin toggle on each row (default `true`). `false` — or a backend\n * without the favorite route — shows a pinned row's pin read-only instead.\n */\n showPinToggle?: boolean;\n /**\n * Offer the inline rename on each row (default `true`). Withdrawn on a\n * backend without the rename route (`renameAvailable`).\n */\n showRename?: boolean;\n /**\n * Offer the archive action on each row (default `true`). Withdrawn on a\n * backend without the archive route (`archiveAvailable`).\n */\n showArchive?: boolean;\n /**\n * Warm the provider's snapshot cache for a row after a 120 ms hover dwell\n * and on row focus (default `true`), so selecting it paints at once.\n */\n prefetchOnHover?: boolean;\n}\n\nconst INDICATOR_LABEL: Record<NonNullable<SessionIndicatorKind>, string> = {\n needs_you: 'Needs you',\n working: 'Working',\n unread: 'Unread',\n watching: 'Watching',\n};\n\n/**\n * The row's status indicator; an empty spacer keeps titles aligned when a\n * row has none.\n */\nconst SessionStatusIndicator: FC<{ kind: SessionIndicatorKind }> = ({ kind }) => {\n if (kind === null) return <span aria-hidden=\"true\" className=\"mt-0.5 size-4 shrink-0\" />;\n const label = INDICATOR_LABEL[kind];\n if (kind === 'working') {\n return (\n <LoaderCircleIcon\n role=\"img\"\n aria-label={label}\n data-indicator={kind}\n className=\"mt-0.5 size-4 shrink-0 animate-spin text-primary\"\n />\n );\n }\n if (kind === 'watching') {\n return (\n <ClockIcon\n role=\"img\"\n aria-label={label}\n data-indicator={kind}\n className=\"mt-0.5 size-4 shrink-0 text-muted-foreground\"\n />\n );\n }\n return (\n <span\n role=\"img\"\n aria-label={label}\n data-indicator={kind}\n className=\"mt-0.5 flex size-4 shrink-0 items-center justify-center\"\n >\n <span\n className={cn(\n 'size-2 rounded-full',\n kind === 'needs_you' ? 'bg-orange-500' : 'bg-primary',\n )}\n />\n </span>\n );\n};\n\nconst LINE_TONE_CLASS: Record<SessionLineTone, string> = {\n needs_you: 'text-orange-600',\n ready: 'text-green-600',\n error: 'text-red-600',\n muted: 'text-muted-foreground',\n};\n\nconst PR_STATE_ICON: Record<PullRequestChipState, FC<{ className?: string; 'aria-hidden'?: boolean }>> = {\n open: GitPullRequestIcon,\n draft: GitPullRequestDraftIcon,\n merged: GitMergeIcon,\n closed: GitPullRequestClosedIcon,\n};\n\nconst PR_STATE_CLASS: Record<PullRequestChipState, string> = {\n open: 'text-green-600',\n draft: 'text-muted-foreground',\n merged: 'text-violet-600',\n closed: 'text-red-600',\n};\n\n/** The focus treatment every row control and action shares. */\nconst FOCUS_RING_CLASS =\n 'outline-none focus-visible:ring-2 focus-visible:ring-ring/60 focus-visible:ring-offset-0';\n\nconst stopPropagation = (event: MouseEvent) => event.stopPropagation();\n\n/**\n * One chip per row linking to the session's primary pull request; a sibling\n * of the row button (never nested inside it) so the markup stays valid and a\n * click on it never selects the row. Sits on the row's second line, beside\n * the meta text.\n */\nconst PullRequestChip: FC<{ chip: PullRequestChipModel }> = ({ chip }) => {\n const Icon = PR_STATE_ICON[chip.state];\n return (\n <a\n href={chip.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={stopPropagation}\n title={chip.title}\n aria-label={`Pull request ${chip.label}, ${chip.state}`}\n data-pr-state={chip.state}\n data-pr-count={chip.count}\n className={cn(\n 'inline-flex h-4 shrink-0 items-center gap-1 rounded-full border border-border/60 px-1.5 font-medium text-[11px] leading-none transition-colors hover:bg-muted',\n FOCUS_RING_CLASS,\n PR_STATE_CLASS[chip.state],\n )}\n >\n <Icon className=\"size-3\" aria-hidden />\n {chip.label}\n </a>\n );\n};\n\n/**\n * One action in the row's trailing cluster: a sibling of the row control\n * (never nested in it — a click must not select the row), labelled for\n * assistive technology, revealed on hover or focus unless `alwaysVisible`,\n * and disabled while its write is in flight so one row never has two writes\n * racing.\n */\nconst RowActionButton: FC<{\n label: string;\n onClick: (event: MouseEvent<HTMLButtonElement>) => void;\n pending: boolean;\n alwaysVisible: boolean;\n action: string;\n pressed?: boolean;\n children: ReactNode;\n}> = ({ label, onClick, pending, alwaysVisible, action, pressed, children }) => (\n <button\n type=\"button\"\n onClick={onClick}\n disabled={pending}\n aria-busy={pending || undefined}\n aria-pressed={pressed}\n aria-label={label}\n title={label}\n data-action={action}\n data-pending={pending || undefined}\n className={cn(\n 'rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:cursor-wait disabled:opacity-60',\n FOCUS_RING_CLASS,\n !alwaysVisible &&\n !pending &&\n 'opacity-0 focus-visible:opacity-100 group-focus-within:opacity-100 group-hover:opacity-100',\n )}\n >\n {children}\n </button>\n);\n\n/**\n * The pin toggle: pressed while the session is pinned (and then always\n * visible); otherwise an outline pin revealed on hover or focus so an\n * unpinned row stays quiet.\n */\nconst PinToggle: FC<{\n thread: Pick<AthenaStatewireThreadSummary, 'id' | 'isFavorited'>;\n pending: boolean;\n onToggle: (target: ThreadFavoriteTarget) => void;\n}> = ({ thread, pending, onToggle }) => {\n const label = thread.isFavorited ? 'Unpin session' : 'Pin session';\n const handleClick = (event: MouseEvent) => {\n event.stopPropagation();\n if (pending) return;\n onToggle({ threadId: thread.id, isFavorited: !thread.isFavorited });\n };\n return (\n <span data-pin-toggle={thread.isFavorited ? 'pinned' : 'unpinned'} className=\"contents\">\n <RowActionButton\n label={label}\n onClick={handleClick}\n pending={pending}\n alwaysVisible={thread.isFavorited}\n action=\"pin\"\n pressed={thread.isFavorited}\n >\n <PinIcon className={cn('size-3.5', thread.isFavorited && 'fill-current')} aria-hidden />\n </RowActionButton>\n </span>\n );\n};\n\nconst SKELETON_TITLE_WIDTHS = ['w-3/5', 'w-4/5', 'w-1/2', 'w-2/3'] as const;\n\n/**\n * Placeholder rows shaped like session rows (indicator, title line, meta\n * line) for the first page and for a refresh that has no rows to keep yet.\n */\nexport const SessionListSkeleton: FC<{ rows?: number }> = ({ rows = 4 }) => (\n <div\n role=\"status\"\n aria-busy=\"true\"\n aria-label=\"Loading sessions\"\n data-testid=\"session-list-skeleton\"\n className=\"flex flex-col gap-0.5\"\n >\n {Array.from({ length: rows }, (_, index) => (\n <div key={index} aria-hidden=\"true\" className=\"flex items-start gap-2 px-3 py-2\">\n <span className=\"mt-0.5 size-4 shrink-0 animate-pulse rounded-full bg-muted\" />\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex h-5 items-center\">\n <span\n className={cn(\n 'h-3.5 animate-pulse rounded bg-muted',\n SKELETON_TITLE_WIDTHS[index % SKELETON_TITLE_WIDTHS.length],\n )}\n />\n </span>\n <span className=\"mt-0.5 flex h-4 items-center\">\n <span className=\"h-3 w-1/3 animate-pulse rounded bg-muted/70\" />\n </span>\n </span>\n </div>\n ))}\n </div>\n);\n\nconst SessionRow: FC<{\n thread: AthenaStatewireThreadSummary;\n isActive: boolean;\n onSelect: (threadId: string) => void;\n /** Absent when hover prefetching is off or the provider has no snapshot cache to warm. */\n onPrefetch: ((threadId: string) => void) | undefined;\n /** Absent when the toggle is not offered: a pinned row then shows a read-only pin. */\n onToggleFavorite: ((target: ThreadFavoriteTarget) => void) | undefined;\n /** True while this session's pin write is in flight. */\n favoritePending: boolean;\n /** Absent when the rename is not offered (host opt-out, or a backend without the route). */\n onRename: ((target: ThreadRenameTarget) => void) | undefined;\n renamePending: boolean;\n /** Absent when the archive is not offered (host opt-out, or a backend without the route). */\n onArchive: ((threadId: string) => void) | undefined;\n archivePending: boolean;\n}> = ({\n thread,\n isActive,\n onSelect,\n onPrefetch,\n onToggleFavorite,\n favoritePending,\n onRename,\n renamePending,\n onArchive,\n archivePending,\n}) => {\n const indicator = sessionIndicator(thread);\n const line = sessionSecondaryLine(thread);\n const chip = pullRequestChip(thread.pullRequests);\n const origin = sessionOriginLabel(thread);\n const title = thread.title ?? 'Untitled';\n\n // Hover prefetch: the pointer must rest on the row for the dwell before\n // the fetch starts, so sweeping the pointer down the list fetches nothing.\n const hoverTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const cancelHover = useCallback(() => {\n if (hoverTimerRef.current === null) return;\n clearTimeout(hoverTimerRef.current);\n hoverTimerRef.current = null;\n }, []);\n useEffect(() => cancelHover, [cancelHover]);\n const handlePointerEnter = () => {\n if (!onPrefetch) return;\n cancelHover();\n hoverTimerRef.current = setTimeout(() => {\n hoverTimerRef.current = null;\n onPrefetch(thread.id);\n }, THREAD_PREFETCH_HOVER_DELAY_MS);\n };\n const handleFocus = () => {\n cancelHover();\n onPrefetch?.(thread.id);\n };\n\n // Inline rename: the title becomes an input; Enter commits, Escape cancels,\n // blur commits. An Enter or Escape settles the edit before the blur its\n // unmount may raise, so the blur never commits a second time.\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState('');\n const settledRef = useRef(false);\n const inputRef = useRef<HTMLInputElement | null>(null);\n useEffect(() => {\n if (!editing) return;\n inputRef.current?.focus();\n inputRef.current?.select();\n }, [editing]);\n const beginRename = (event: MouseEvent) => {\n event.stopPropagation();\n if (renamePending) return;\n settledRef.current = false;\n setDraft(thread.title ?? '');\n setEditing(true);\n };\n const commitRename = () => {\n if (settledRef.current) return;\n settledRef.current = true;\n setEditing(false);\n const next = draft.trim();\n if (!next || next === (thread.title ?? '').trim()) return;\n onRename?.({ threadId: thread.id, title: next });\n };\n const cancelRename = () => {\n settledRef.current = true;\n setEditing(false);\n };\n const handleEditorKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'Enter') {\n event.preventDefault();\n commitRename();\n } else if (event.key === 'Escape') {\n event.preventDefault();\n event.stopPropagation();\n cancelRename();\n }\n };\n\n const handleArchive = (event: MouseEvent) => {\n event.stopPropagation();\n if (archivePending) return;\n onArchive?.(thread.id);\n };\n\n const hasActions = Boolean(onRename || onToggleFavorite || onArchive);\n const body = (\n <>\n <SessionStatusIndicator kind={indicator} />\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex h-5 items-center gap-1.5\">\n {editing ? (\n <input\n ref={inputRef}\n type=\"text\"\n value={draft}\n maxLength={THREAD_TITLE_MAX_LENGTH}\n aria-label=\"Session title\"\n data-rename-input\n onChange={(event: ChangeEvent<HTMLInputElement>) => setDraft(event.target.value)}\n onKeyDown={handleEditorKeyDown}\n onBlur={commitRename}\n onClick={stopPropagation}\n className={cn(\n 'h-5 min-w-0 flex-1 rounded border border-primary/60 bg-background px-1 font-medium text-foreground text-sm',\n FOCUS_RING_CLASS,\n )}\n />\n ) : (\n <span data-title className=\"min-w-0 truncate font-medium text-sm\">\n {title}\n </span>\n )}\n {thread.isFavorited && !onToggleFavorite ? (\n <PinIcon\n role=\"img\"\n aria-label=\"Pinned\"\n className=\"size-3 shrink-0 fill-current text-muted-foreground\"\n />\n ) : null}\n {origin ? (\n <span\n data-origin={origin}\n className=\"shrink-0 rounded-full border border-border/60 px-1.5 py-px text-[10px] text-muted-foreground\"\n >\n {origin}\n </span>\n ) : null}\n </span>\n <span\n data-line={line.kind}\n className={cn('mt-0.5 block h-4 truncate font-normal text-xs leading-4', LINE_TONE_CLASS[line.tone])}\n >\n {line.text}\n </span>\n </span>\n </>\n );\n\n return (\n <li\n data-active={isActive || undefined}\n data-thread-id={thread.id}\n data-editing={editing || undefined}\n onPointerEnter={handlePointerEnter}\n onPointerLeave={cancelHover}\n className={cn(\n 'group relative flex items-start gap-1 rounded-lg text-muted-foreground transition-colors hover:bg-muted/40 hover:text-foreground',\n isActive && 'bg-muted text-foreground',\n )}\n >\n {isActive ? (\n <span\n aria-hidden=\"true\"\n data-active-accent\n className=\"absolute inset-y-2 left-0 w-0.5 rounded-full bg-primary\"\n />\n ) : null}\n {editing ? (\n <div className=\"flex min-w-0 flex-1 items-start gap-2 p-2 pl-3 text-left\">{body}</div>\n ) : (\n <button\n type=\"button\"\n onClick={() => onSelect(thread.id)}\n onFocus={handleFocus}\n className={cn(\n 'flex min-w-0 flex-1 cursor-pointer items-start gap-2 rounded-lg p-2 pl-3 text-left',\n FOCUS_RING_CLASS,\n )}\n >\n {body}\n </button>\n )}\n {chip || hasActions ? (\n <div className=\"flex shrink-0 flex-col items-end justify-between gap-0.5 py-2 pr-2\">\n {hasActions ? (\n <div data-row-actions className=\"flex h-5 items-center gap-0.5\">\n {onRename ? (\n <RowActionButton\n label=\"Rename session\"\n onClick={beginRename}\n pending={renamePending}\n alwaysVisible={false}\n action=\"rename\"\n >\n <PencilIcon className=\"size-3.5\" aria-hidden />\n </RowActionButton>\n ) : null}\n {onToggleFavorite ? (\n <PinToggle thread={thread} pending={favoritePending} onToggle={onToggleFavorite} />\n ) : null}\n {onArchive ? (\n <RowActionButton\n label=\"Archive session\"\n onClick={handleArchive}\n pending={archivePending}\n alwaysVisible={false}\n action=\"archive\"\n >\n <ArchiveIcon className=\"size-3.5\" aria-hidden />\n </RowActionButton>\n ) : null}\n </div>\n ) : (\n <span aria-hidden=\"true\" className=\"h-5\" />\n )}\n {chip ? <PullRequestChip chip={chip} /> : null}\n </div>\n ) : null}\n </li>\n );\n};\n\n/**\n * Devin-style session list: the `ThreadList` rows with an attention\n * indicator, the server's next action, pull-request chips, an origin badge\n * and a trailing action cluster (rename inline, pin, archive) revealed on\n * hover or focus, grouped by what each session asks of the viewer. Reads the\n * thread-list state that `<AthenaProvider enableThreadList>` provides and\n * renders nothing outside that mode.\n *\n * Every write is optimistic and rolls back on failure: the pin flips the row\n * and moves it between `Pinned` and `Recent`; the rename shows the new title\n * on commit; the archive removes the row. On a backend that predates a route\n * the matching action is withdrawn (`favoriteToggleAvailable` falls back to a\n * read-only pin; `renameAvailable` / `archiveAvailable` hide the action).\n *\n * Rows warm the provider's snapshot cache after a 120 ms hover dwell (and on\n * focus) so a selection paints its transcript at once. Skeleton rows stand in\n * while the first page loads or a refresh has nothing to keep; the empty\n * state appears only after a settled empty answer, and a background refresh\n * never blanks the rows it already has.\n */\nexport function SessionList({\n className,\n grouped = true,\n showSearch = true,\n showPinToggle = true,\n showRename = true,\n showArchive = true,\n prefetchOnHover = true,\n}: SessionListProps) {\n const list = useAthenaStatewireThreadList();\n if (!list) return null;\n\n const sections = groupSessions(list.threads, grouped);\n const hasSearch = list.search.trim().length > 0;\n const handleSearch = (event: ChangeEvent<HTMLInputElement>) =>\n list.setSearch(event.target.value);\n const onToggleFavorite =\n showPinToggle && list.favoriteToggleAvailable ? list.setFavorited : undefined;\n const onRename = showRename && list.renameAvailable ? list.renameThread : undefined;\n const onArchive = showArchive && list.archiveAvailable ? list.archiveThread : undefined;\n const onPrefetch = prefetchOnHover ? list.prefetchThread : undefined;\n // Skeleton until the first page has answered (no credential yet, or the\n // page in flight) and for a refresh with no rows to keep; a refresh that\n // has rows keeps showing them.\n const showSkeleton =\n !list.hasResolvedFirstPage || (list.isFetching && list.threads.length === 0);\n\n return (\n <div\n className={cn('athena-sdk-chrome flex flex-col gap-2', className)}\n data-testid=\"session-list\"\n >\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={list.newThread}\n className={cn(\n 'flex h-9 flex-1 items-center gap-2 rounded-lg border border-border/60 px-3 font-medium text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground',\n FOCUS_RING_CLASS,\n )}\n >\n <PlusIcon className=\"size-4\" />\n New Chat\n </button>\n <button\n type=\"button\"\n onClick={list.refresh}\n disabled={list.isFetching}\n className={cn(\n 'flex size-9 items-center justify-center rounded-lg border border-border/60 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50',\n FOCUS_RING_CLASS,\n )}\n title=\"Refresh sessions\"\n aria-label=\"Refresh sessions\"\n >\n <RefreshCwIcon className={cn('size-4', list.isFetching && 'animate-spin')} />\n </button>\n </div>\n\n {showSearch ? (\n <label className=\"relative block\">\n <SearchIcon className=\"pointer-events-none absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <span className=\"sr-only\">Search sessions</span>\n <input\n type=\"search\"\n value={list.search}\n onChange={handleSearch}\n placeholder=\"Search sessions…\"\n className=\"h-8 w-full rounded-lg border border-border/60 bg-muted/40 pr-2 pl-8 text-sm outline-none focus:border-primary focus:bg-background\"\n />\n </label>\n ) : null}\n\n {showSkeleton ? (\n <SessionListSkeleton />\n ) : sections.length === 0 ? (\n <p data-testid=\"session-list-empty\" className=\"px-3 py-4 text-muted-foreground text-xs\">\n {hasSearch ? 'No matching sessions.' : 'No sessions yet.'}\n </p>\n ) : (\n <div className=\"flex flex-col gap-2\" aria-busy={list.isFetching || undefined}>\n {sections.map(({ group, items }) => (\n <section\n key={group ?? 'all'}\n aria-label={group ?? 'Sessions'}\n data-group={group ?? undefined}\n >\n {group ? (\n <h3 className=\"px-3 pb-1 font-medium text-[11px] text-muted-foreground/70 uppercase tracking-wide\">\n {group}\n </h3>\n ) : null}\n <ul className=\"flex flex-col gap-0.5\">\n {items.map((thread) => (\n <SessionRow\n key={thread.id}\n thread={thread}\n isActive={thread.id === list.activeThreadId}\n onSelect={list.selectThread}\n onPrefetch={onPrefetch}\n onToggleFavorite={onToggleFavorite}\n favoritePending={list.favoritePendingThreadIds.has(thread.id)}\n onRename={onRename}\n renamePending={list.renamePendingThreadIds.has(thread.id)}\n onArchive={onArchive}\n archivePending={list.archivePendingThreadIds.has(thread.id)}\n />\n ))}\n </ul>\n </section>\n ))}\n </div>\n )}\n\n {list.hasNextPage && !showSkeleton ? (\n <button\n type=\"button\"\n disabled={list.isFetchingNextPage}\n onClick={list.fetchNextPage}\n className={cn(\n 'w-full rounded-lg py-2 text-muted-foreground text-xs transition-colors hover:bg-muted disabled:opacity-50',\n FOCUS_RING_CLASS,\n )}\n >\n {list.isFetchingNextPage ? 'Loading…' : 'Load more'}\n </button>\n ) : null}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;;;AAqEA,MAAM,kBAAqE;AAAA,EACzE,WAAW;AAAA,EACX,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,UAAU;AACZ;AAMA,MAAM,yBAA6D,CAAC,EAAE,WAAW;AAC/E,MAAI,SAAS,KAAM,QAAO,oBAAC,UAAK,eAAY,QAAO,WAAU,0BAAyB;AACtF,QAAM,QAAQ,gBAAgB,IAAI;AAClC,MAAI,SAAS,WAAW;AACtB,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAY;AAAA,QACZ,kBAAgB;AAAA,QAChB,WAAU;AAAA,MAAA;AAAA,IAAA;AAAA,EAGhB;AACA,MAAI,SAAS,YAAY;AACvB,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAY;AAAA,QACZ,kBAAgB;AAAA,QAChB,WAAU;AAAA,MAAA;AAAA,IAAA;AAAA,EAGhB;AACA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,kBAAgB;AAAA,MAChB,WAAU;AAAA,MAEV,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,WAAW;AAAA,YACT;AAAA,YACA,SAAS,cAAc,kBAAkB;AAAA,UAAA;AAAA,QAC3C;AAAA,MAAA;AAAA,IACF;AAAA,EAAA;AAGN;AAEA,MAAM,kBAAmD;AAAA,EACvD,WAAW;AAAA,EACX,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACT;AAEA,MAAM,gBAAmG;AAAA,EACvG,MAAM;AAAA,EACN,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ;AACV;AAEA,MAAM,iBAAuD;AAAA,EAC3D,MAAM;AAAA,EACN,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ;AACV;AAGA,MAAM,mBACJ;AAEF,MAAM,kBAAkB,CAAC,UAAsB,MAAM,gBAAA;AAQrD,MAAM,kBAAsD,CAAC,EAAE,WAAW;AACxE,QAAM,OAAO,cAAc,KAAK,KAAK;AACrC,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAM,KAAK;AAAA,MACX,QAAO;AAAA,MACP,KAAI;AAAA,MACJ,SAAS;AAAA,MACT,OAAO,KAAK;AAAA,MACZ,cAAY,gBAAgB,KAAK,KAAK,KAAK,KAAK,KAAK;AAAA,MACrD,iBAAe,KAAK;AAAA,MACpB,iBAAe,KAAK;AAAA,MACpB,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA,eAAe,KAAK,KAAK;AAAA,MAAA;AAAA,MAG3B,UAAA;AAAA,QAAA,oBAAC,MAAA,EAAK,WAAU,UAAS,eAAW,MAAC;AAAA,QACpC,KAAK;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGZ;AASA,MAAM,kBAQD,CAAC,EAAE,OAAO,SAAS,SAAS,eAAe,QAAQ,SAAS,SAAA,MAC/D;AAAA,EAAC;AAAA,EAAA;AAAA,IACC,MAAK;AAAA,IACL;AAAA,IACA,UAAU;AAAA,IACV,aAAW,WAAW;AAAA,IACtB,gBAAc;AAAA,IACd,cAAY;AAAA,IACZ,OAAO;AAAA,IACP,eAAa;AAAA,IACb,gBAAc,WAAW;AAAA,IACzB,WAAW;AAAA,MACT;AAAA,MACA;AAAA,MACA,CAAC,iBACC,CAAC,WACD;AAAA,IAAA;AAAA,IAGH;AAAA,EAAA;AACH;AAQF,MAAM,YAID,CAAC,EAAE,QAAQ,SAAS,eAAe;AACtC,QAAM,QAAQ,OAAO,cAAc,kBAAkB;AACrD,QAAM,cAAc,CAAC,UAAsB;AACzC,UAAM,gBAAA;AACN,QAAI,QAAS;AACb,aAAS,EAAE,UAAU,OAAO,IAAI,aAAa,CAAC,OAAO,aAAa;AAAA,EACpE;AACA,SACE,oBAAC,UAAK,mBAAiB,OAAO,cAAc,WAAW,YAAY,WAAU,YAC3E,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA,eAAe,OAAO;AAAA,MACtB,QAAO;AAAA,MACP,SAAS,OAAO;AAAA,MAEhB,UAAA,oBAAC,SAAA,EAAQ,WAAW,GAAG,YAAY,OAAO,eAAe,cAAc,GAAG,eAAW,KAAA,CAAC;AAAA,IAAA;AAAA,EAAA,GAE1F;AAEJ;AAEA,MAAM,wBAAwB,CAAC,SAAS,SAAS,SAAS,OAAO;AAM1D,MAAM,sBAA6C,CAAC,EAAE,OAAO,QAClE;AAAA,EAAC;AAAA,EAAA;AAAA,IACC,MAAK;AAAA,IACL,aAAU;AAAA,IACV,cAAW;AAAA,IACX,eAAY;AAAA,IACZ,WAAU;AAAA,IAET,UAAA,MAAM,KAAK,EAAE,QAAQ,QAAQ,CAAC,GAAG,UAChC,qBAAC,OAAA,EAAgB,eAAY,QAAO,WAAU,oCAC5C,UAAA;AAAA,MAAA,oBAAC,QAAA,EAAK,WAAU,6DAAA,CAA6D;AAAA,MAC7E,qBAAC,QAAA,EAAK,WAAU,kBACd,UAAA;AAAA,QAAA,oBAAC,QAAA,EAAK,WAAU,yBACd,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA,sBAAsB,QAAQ,sBAAsB,MAAM;AAAA,YAAA;AAAA,UAC5D;AAAA,QAAA,GAEJ;AAAA,QACA,oBAAC,UAAK,WAAU,gCACd,8BAAC,QAAA,EAAK,WAAU,+CAA8C,EAAA,CAChE;AAAA,MAAA,EAAA,CACF;AAAA,IAAA,EAAA,GAdQ,KAeV,CACD;AAAA,EAAA;AACH;AAGF,MAAM,aAgBD,CAAC;AAAA,EACJ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,YAAY,iBAAiB,MAAM;AACzC,QAAM,OAAO,qBAAqB,MAAM;AACxC,QAAM,OAAO,gBAAgB,OAAO,YAAY;AAChD,QAAM,SAAS,mBAAmB,MAAM;AACxC,QAAM,QAAQ,OAAO,SAAS;AAI9B,QAAM,gBAAgB,OAA6C,IAAI;AACvE,QAAM,cAAc,YAAY,MAAM;AACpC,QAAI,cAAc,YAAY,KAAM;AACpC,iBAAa,cAAc,OAAO;AAClC,kBAAc,UAAU;AAAA,EAC1B,GAAG,CAAA,CAAE;AACL,YAAU,MAAM,aAAa,CAAC,WAAW,CAAC;AAC1C,QAAM,qBAAqB,MAAM;AAC/B,QAAI,CAAC,WAAY;AACjB,gBAAA;AACA,kBAAc,UAAU,WAAW,MAAM;AACvC,oBAAc,UAAU;AACxB,iBAAW,OAAO,EAAE;AAAA,IACtB,GAAG,8BAA8B;AAAA,EACnC;AACA,QAAM,cAAc,MAAM;AACxB,gBAAA;AACA,6CAAa,OAAO;AAAA,EACtB;AAKA,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AACrC,QAAM,aAAa,OAAO,KAAK;AAC/B,QAAM,WAAW,OAAgC,IAAI;AACrD,YAAU,MAAM;;AACd,QAAI,CAAC,QAAS;AACd,mBAAS,YAAT,mBAAkB;AAClB,mBAAS,YAAT,mBAAkB;AAAA,EACpB,GAAG,CAAC,OAAO,CAAC;AACZ,QAAM,cAAc,CAAC,UAAsB;AACzC,UAAM,gBAAA;AACN,QAAI,cAAe;AACnB,eAAW,UAAU;AACrB,aAAS,OAAO,SAAS,EAAE;AAC3B,eAAW,IAAI;AAAA,EACjB;AACA,QAAM,eAAe,MAAM;AACzB,QAAI,WAAW,QAAS;AACxB,eAAW,UAAU;AACrB,eAAW,KAAK;AAChB,UAAM,OAAO,MAAM,KAAA;AACnB,QAAI,CAAC,QAAQ,UAAU,OAAO,SAAS,IAAI,OAAQ;AACnD,yCAAW,EAAE,UAAU,OAAO,IAAI,OAAO;EAC3C;AACA,QAAM,eAAe,MAAM;AACzB,eAAW,UAAU;AACrB,eAAW,KAAK;AAAA,EAClB;AACA,QAAM,sBAAsB,CAAC,UAA2C;AACtE,QAAI,MAAM,QAAQ,SAAS;AACzB,YAAM,eAAA;AACN,mBAAA;AAAA,IACF,WAAW,MAAM,QAAQ,UAAU;AACjC,YAAM,eAAA;AACN,YAAM,gBAAA;AACN,mBAAA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,gBAAgB,CAAC,UAAsB;AAC3C,UAAM,gBAAA;AACN,QAAI,eAAgB;AACpB,2CAAY,OAAO;AAAA,EACrB;AAEA,QAAM,aAAa,QAAQ,YAAY,oBAAoB,SAAS;AACpE,QAAM,OACJ,qBAAA,UAAA,EACE,UAAA;AAAA,IAAA,oBAAC,wBAAA,EAAuB,MAAM,UAAA,CAAW;AAAA,IACzC,qBAAC,QAAA,EAAK,WAAU,kBACd,UAAA;AAAA,MAAA,qBAAC,QAAA,EAAK,WAAU,iCACb,UAAA;AAAA,QAAA,UACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,KAAK;AAAA,YACL,MAAK;AAAA,YACL,OAAO;AAAA,YACP,WAAW;AAAA,YACX,cAAW;AAAA,YACX,qBAAiB;AAAA,YACjB,UAAU,CAAC,UAAyC,SAAS,MAAM,OAAO,KAAK;AAAA,YAC/E,WAAW;AAAA,YACX,QAAQ;AAAA,YACR,SAAS;AAAA,YACT,WAAW;AAAA,cACT;AAAA,cACA;AAAA,YAAA;AAAA,UACF;AAAA,QAAA,IAGF,oBAAC,QAAA,EAAK,cAAU,MAAC,WAAU,wCACxB,UAAA,OACH;AAAA,QAED,OAAO,eAAe,CAAC,mBACtB;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,cAAW;AAAA,YACX,WAAU;AAAA,UAAA;AAAA,QAAA,IAEV;AAAA,QACH,SACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,eAAa;AAAA,YACb,WAAU;AAAA,YAET,UAAA;AAAA,UAAA;AAAA,QAAA,IAED;AAAA,MAAA,GACN;AAAA,MACA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,aAAW,KAAK;AAAA,UAChB,WAAW,GAAG,2DAA2D,gBAAgB,KAAK,IAAI,CAAC;AAAA,UAElG,UAAA,KAAK;AAAA,QAAA;AAAA,MAAA;AAAA,IACR,EAAA,CACF;AAAA,EAAA,GACF;AAGF,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,eAAa,YAAY;AAAA,MACzB,kBAAgB,OAAO;AAAA,MACvB,gBAAc,WAAW;AAAA,MACzB,gBAAgB;AAAA,MAChB,gBAAgB;AAAA,MAChB,WAAW;AAAA,QACT;AAAA,QACA,YAAY;AAAA,MAAA;AAAA,MAGb,UAAA;AAAA,QAAA,WACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,eAAY;AAAA,YACZ,sBAAkB;AAAA,YAClB,WAAU;AAAA,UAAA;AAAA,QAAA,IAEV;AAAA,QACH,UACC,oBAAC,OAAA,EAAI,WAAU,4DAA4D,gBAAK,IAEhF;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM,SAAS,OAAO,EAAE;AAAA,YACjC,SAAS;AAAA,YACT,WAAW;AAAA,cACT;AAAA,cACA;AAAA,YAAA;AAAA,YAGD,UAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAGJ,QAAQ,aACP,qBAAC,OAAA,EAAI,WAAU,sEACZ,UAAA;AAAA,UAAA,aACC,qBAAC,OAAA,EAAI,oBAAgB,MAAC,WAAU,iCAC7B,UAAA;AAAA,YAAA,WACC;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,OAAM;AAAA,gBACN,SAAS;AAAA,gBACT,SAAS;AAAA,gBACT,eAAe;AAAA,gBACf,QAAO;AAAA,gBAEP,UAAA,oBAAC,YAAA,EAAW,WAAU,YAAW,eAAW,KAAA,CAAC;AAAA,cAAA;AAAA,YAAA,IAE7C;AAAA,YACH,uCACE,WAAA,EAAU,QAAgB,SAAS,iBAAiB,UAAU,kBAAkB,IAC/E;AAAA,YACH,YACC;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,OAAM;AAAA,gBACN,SAAS;AAAA,gBACT,SAAS;AAAA,gBACT,eAAe;AAAA,gBACf,QAAO;AAAA,gBAEP,UAAA,oBAAC,aAAA,EAAY,WAAU,YAAW,eAAW,KAAA,CAAC;AAAA,cAAA;AAAA,YAAA,IAE9C;AAAA,UAAA,GACN,IAEA,oBAAC,QAAA,EAAK,eAAY,QAAO,WAAU,OAAM;AAAA,UAE1C,OAAO,oBAAC,iBAAA,EAAgB,KAAA,CAAY,IAAK;AAAA,QAAA,EAAA,CAC5C,IACE;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGV;AAsBO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA,UAAU;AAAA,EACV,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb,cAAc;AAAA,EACd,kBAAkB;AACpB,GAAqB;AACnB,QAAM,OAAO,6BAAA;AACb,MAAI,CAAC,KAAM,QAAO;AAElB,QAAM,WAAW,cAAc,KAAK,SAAS,OAAO;AACpD,QAAM,YAAY,KAAK,OAAO,KAAA,EAAO,SAAS;AAC9C,QAAM,eAAe,CAAC,UACpB,KAAK,UAAU,MAAM,OAAO,KAAK;AACnC,QAAM,mBACJ,iBAAiB,KAAK,0BAA0B,KAAK,eAAe;AACtE,QAAM,WAAW,cAAc,KAAK,kBAAkB,KAAK,eAAe;AAC1E,QAAM,YAAY,eAAe,KAAK,mBAAmB,KAAK,gBAAgB;AAC9E,QAAM,aAAa,kBAAkB,KAAK,iBAAiB;AAI3D,QAAM,eACJ,CAAC,KAAK,wBAAyB,KAAK,cAAc,KAAK,QAAQ,WAAW;AAE5E,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,GAAG,yCAAyC,SAAS;AAAA,MAChE,eAAY;AAAA,MAEZ,UAAA;AAAA,QAAA,qBAAC,OAAA,EAAI,WAAU,2BACb,UAAA;AAAA,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS,KAAK;AAAA,cACd,WAAW;AAAA,gBACT;AAAA,gBACA;AAAA,cAAA;AAAA,cAGF,UAAA;AAAA,gBAAA,oBAAC,UAAA,EAAS,WAAU,SAAA,CAAS;AAAA,gBAAE;AAAA,cAAA;AAAA,YAAA;AAAA,UAAA;AAAA,UAGjC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS,KAAK;AAAA,cACd,UAAU,KAAK;AAAA,cACf,WAAW;AAAA,gBACT;AAAA,gBACA;AAAA,cAAA;AAAA,cAEF,OAAM;AAAA,cACN,cAAW;AAAA,cAEX,UAAA,oBAAC,iBAAc,WAAW,GAAG,UAAU,KAAK,cAAc,cAAc,EAAA,CAAG;AAAA,YAAA;AAAA,UAAA;AAAA,QAC7E,GACF;AAAA,QAEC,aACC,qBAAC,SAAA,EAAM,WAAU,kBACf,UAAA;AAAA,UAAA,oBAAC,YAAA,EAAW,WAAU,gGAAA,CAAgG;AAAA,UACtH,oBAAC,QAAA,EAAK,WAAU,WAAU,UAAA,mBAAe;AAAA,UACzC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAK;AAAA,cACL,OAAO,KAAK;AAAA,cACZ,UAAU;AAAA,cACV,aAAY;AAAA,cACZ,WAAU;AAAA,YAAA;AAAA,UAAA;AAAA,QACZ,EAAA,CACF,IACE;AAAA,QAEH,eACC,oBAAC,qBAAA,CAAA,CAAoB,IACnB,SAAS,WAAW,IACtB,oBAAC,KAAA,EAAE,eAAY,sBAAqB,WAAU,2CAC3C,UAAA,YAAY,0BAA0B,mBAAA,CACzC,IAEA,oBAAC,OAAA,EAAI,WAAU,uBAAsB,aAAW,KAAK,cAAc,QAChE,UAAA,SAAS,IAAI,CAAC,EAAE,OAAO,YACtB;AAAA,UAAC;AAAA,UAAA;AAAA,YAEC,cAAY,SAAS;AAAA,YACrB,cAAY,SAAS;AAAA,YAEpB,UAAA;AAAA,cAAA,QACC,oBAAC,MAAA,EAAG,WAAU,sFACX,iBACH,IACE;AAAA,kCACH,MAAA,EAAG,WAAU,yBACX,UAAA,MAAM,IAAI,CAAC,WACV;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBAEC;AAAA,kBACA,UAAU,OAAO,OAAO,KAAK;AAAA,kBAC7B,UAAU,KAAK;AAAA,kBACf;AAAA,kBACA;AAAA,kBACA,iBAAiB,KAAK,yBAAyB,IAAI,OAAO,EAAE;AAAA,kBAC5D;AAAA,kBACA,eAAe,KAAK,uBAAuB,IAAI,OAAO,EAAE;AAAA,kBACxD;AAAA,kBACA,gBAAgB,KAAK,wBAAwB,IAAI,OAAO,EAAE;AAAA,gBAAA;AAAA,gBAVrD,OAAO;AAAA,cAAA,CAYf,EAAA,CACH;AAAA,YAAA;AAAA,UAAA;AAAA,UAzBK,SAAS;AAAA,QAAA,CA2BjB,GACH;AAAA,QAGD,KAAK,eAAe,CAAC,eACpB;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,UAAU,KAAK;AAAA,YACf,SAAS,KAAK;AAAA,YACd,WAAW;AAAA,cACT;AAAA,cACA;AAAA,YAAA;AAAA,YAGD,UAAA,KAAK,qBAAqB,aAAa;AAAA,UAAA;AAAA,QAAA,IAExC;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGV;"}
|
|
1
|
+
{"version":3,"file":"SessionList.js","sources":["../../src/threads/SessionList.tsx"],"sourcesContent":["import {\n ArchiveIcon,\n ClockIcon,\n GitMergeIcon,\n GitPullRequestClosedIcon,\n GitPullRequestDraftIcon,\n GitPullRequestIcon,\n LoaderCircleIcon,\n PencilIcon,\n PinIcon,\n PlusIcon,\n RefreshCwIcon,\n SearchIcon,\n} from 'lucide-react';\nimport type { ConversationPullRequest } from '@athenaintel/agent-runtime';\nimport type { ChangeEvent, FC, KeyboardEvent, MouseEvent, ReactNode } from 'react';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { cn } from '../lib/utils';\nimport { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';\nimport { THREAD_TITLE_MAX_LENGTH } from './api';\nimport {\n groupSessions,\n orderedPullRequests,\n type PullRequestChip as PullRequestChipModel,\n type PullRequestChipState,\n pullRequestChip,\n pullRequestChipState,\n type SessionIndicator as SessionIndicatorKind,\n type SessionLineTone,\n sessionIndicator,\n sessionOriginLabel,\n sessionSecondaryLine,\n} from './session-list-model';\nimport {\n type AthenaStatewireThreadSummary,\n useAthenaStatewireThreadList,\n} from './statewire-thread-list';\nimport { THREAD_PREFETCH_HOVER_DELAY_MS } from './thread-prefetcher';\nimport type { ThreadFavoriteTarget } from './use-thread-favorite';\nimport type { ThreadRenameTarget } from './use-thread-rename';\n\nexport interface SessionListProps {\n className?: string;\n /**\n * Group rows under \"Needs you\" / \"Working\" / \"Pinned\" / \"Recent\" (default\n * `true`); a session appears once, in the first group that applies. `false`\n * renders one newest-first list.\n */\n grouped?: boolean;\n /** Show the title search box, wired to the server-side `search` (default `true`). */\n showSearch?: boolean;\n /**\n * Offer the pin toggle on each row (default `true`). `false` — or a backend\n * without the favorite route — shows a pinned row's pin read-only instead.\n */\n showPinToggle?: boolean;\n /**\n * Offer the inline rename on each row (default `true`). Withdrawn on a\n * backend without the rename route (`renameAvailable`).\n */\n showRename?: boolean;\n /**\n * Offer the archive action on each row (default `true`). Withdrawn on a\n * backend without the archive route (`archiveAvailable`).\n */\n showArchive?: boolean;\n /**\n * Warm the provider's snapshot cache for a row after a 120 ms hover dwell\n * and on row focus (default `true`), so selecting it paints at once.\n */\n prefetchOnHover?: boolean;\n}\n\nconst INDICATOR_LABEL: Record<NonNullable<SessionIndicatorKind>, string> = {\n needs_you: 'Needs you',\n working: 'Working',\n unread: 'Unread',\n watching: 'Watching',\n};\n\n/**\n * The row's status indicator; an empty spacer keeps titles aligned when a\n * row has none.\n */\nconst SessionStatusIndicator: FC<{ kind: SessionIndicatorKind }> = ({ kind }) => {\n if (kind === null) return <span aria-hidden=\"true\" className=\"mt-0.5 size-4 shrink-0\" />;\n const label = INDICATOR_LABEL[kind];\n if (kind === 'working') {\n return (\n <LoaderCircleIcon\n role=\"img\"\n aria-label={label}\n data-indicator={kind}\n className=\"mt-0.5 size-4 shrink-0 animate-spin text-primary\"\n />\n );\n }\n if (kind === 'watching') {\n return (\n <ClockIcon\n role=\"img\"\n aria-label={label}\n data-indicator={kind}\n className=\"mt-0.5 size-4 shrink-0 text-muted-foreground\"\n />\n );\n }\n return (\n <span\n role=\"img\"\n aria-label={label}\n data-indicator={kind}\n className=\"mt-0.5 flex size-4 shrink-0 items-center justify-center\"\n >\n <span\n className={cn(\n 'size-2 rounded-full',\n kind === 'needs_you' ? 'bg-orange-500' : 'bg-primary',\n )}\n />\n </span>\n );\n};\n\nconst LINE_TONE_CLASS: Record<SessionLineTone, string> = {\n needs_you: 'text-orange-600',\n ready: 'text-green-600',\n error: 'text-red-600',\n muted: 'text-muted-foreground',\n};\n\nconst PR_STATE_ICON: Record<PullRequestChipState, FC<{ className?: string; 'aria-hidden'?: boolean }>> = {\n open: GitPullRequestIcon,\n draft: GitPullRequestDraftIcon,\n merged: GitMergeIcon,\n closed: GitPullRequestClosedIcon,\n};\n\nconst PR_STATE_CLASS: Record<PullRequestChipState, string> = {\n open: 'text-green-600',\n draft: 'text-muted-foreground',\n merged: 'text-violet-600',\n closed: 'text-red-600',\n};\n\n/** The focus treatment every row control and action shares. */\nconst FOCUS_RING_CLASS =\n 'outline-none focus-visible:ring-2 focus-visible:ring-ring/60 focus-visible:ring-offset-0';\n\nconst stopPropagation = (event: MouseEvent) => event.stopPropagation();\n\nconst PR_CHECKS_LABEL: Record<ConversationPullRequest['checksState'], string> = {\n SUCCESS: 'checks passed',\n FAILURE: 'checks failing',\n PENDING: 'checks running',\n NONE: '',\n};\n\nconst PR_CHIP_CLASS =\n 'inline-flex h-4 shrink-0 items-center gap-1 rounded-full border border-border/60 px-1.5 font-medium text-[11px] leading-none transition-colors hover:bg-muted';\n\n/**\n * One row of the pull-request list: the state icon, the title, and the\n * number, state and checks beneath it (the repository too when the list\n * spans several), as a link to GitHub in a new tab.\n */\nconst PullRequestListRow: FC<{ pullRequest: ConversationPullRequest; showRepo: boolean }> = ({\n pullRequest,\n showRepo,\n}) => {\n const state = pullRequestChipState(pullRequest);\n const Icon = PR_STATE_ICON[state];\n const meta = [\n showRepo ? `${pullRequest.repoFullName} #${pullRequest.number}` : `#${pullRequest.number}`,\n state,\n PR_CHECKS_LABEL[pullRequest.checksState],\n ]\n .filter(Boolean)\n .join(' · ');\n return (\n <li>\n <a\n href={pullRequest.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n data-pr-state={state}\n className={cn(\n 'flex items-start gap-2 rounded-sm px-2 py-1.5 text-xs transition-colors hover:bg-muted',\n FOCUS_RING_CLASS,\n )}\n >\n <Icon className={cn('mt-0.5 size-3.5 shrink-0', PR_STATE_CLASS[state])} aria-hidden />\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-foreground\">\n {pullRequest.title || `#${pullRequest.number}`}\n </span>\n <span className=\"block truncate text-muted-foreground\">{meta}</span>\n </span>\n </a>\n </li>\n );\n};\n\n/**\n * The session's pull-request chip; a sibling of the row button (never nested\n * inside it) so the markup stays valid and a click on it never selects the\n * row. Sits on the row's second line, beside the meta text. One pull request:\n * a link to it. Several: a button that opens all of them — in flight first,\n * then the most recently updated — each a link to GitHub.\n */\nconst PullRequestChip: FC<{\n chip: PullRequestChipModel;\n pullRequests: readonly ConversationPullRequest[];\n}> = ({ chip, pullRequests }) => {\n const Icon = PR_STATE_ICON[chip.state];\n if (chip.count === 1) {\n return (\n <a\n href={chip.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={stopPropagation}\n title={chip.title}\n aria-label={`Pull request ${chip.label}, ${chip.state}`}\n data-pr-state={chip.state}\n data-pr-count={chip.count}\n className={cn(PR_CHIP_CLASS, FOCUS_RING_CLASS, PR_STATE_CLASS[chip.state])}\n >\n <Icon className=\"size-3\" aria-hidden />\n {chip.label}\n </a>\n );\n }\n const listLabel = `${chip.count} pull requests`;\n const ordered = orderedPullRequests(pullRequests);\n // One repository (the usual case) is named once in the header, so each\n // row's meta line has room for its state and checks.\n const repositories = new Set(ordered.map((pullRequest) => pullRequest.repoFullName));\n const sharedRepository = repositories.size === 1 ? (ordered[0]?.repoFullName ?? null) : null;\n return (\n <Popover>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n onClick={stopPropagation}\n aria-label={`${chip.label}: show all ${listLabel}`}\n data-pr-state={chip.state}\n data-pr-count={chip.count}\n className={cn(PR_CHIP_CLASS, FOCUS_RING_CLASS, PR_STATE_CLASS[chip.state])}\n >\n <Icon className=\"size-3\" aria-hidden />\n {chip.label}\n </button>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className=\"w-80 p-0\" aria-label={listLabel} data-pr-list=\"\">\n <p className=\"truncate border-b border-border px-3 py-2 font-medium text-muted-foreground text-xs\">\n {sharedRepository ? `${listLabel} in ${sharedRepository}` : listLabel}\n </p>\n <ul className=\"max-h-72 overflow-y-auto p-1\">\n {ordered.map((pullRequest) => (\n <PullRequestListRow\n key={`${pullRequest.repoFullName}#${pullRequest.number}`}\n pullRequest={pullRequest}\n showRepo={sharedRepository === null}\n />\n ))}\n </ul>\n </PopoverContent>\n </Popover>\n );\n};\n\n/**\n * One action in the row's trailing cluster: a sibling of the row control\n * (never nested in it — a click must not select the row), labelled for\n * assistive technology, revealed on hover or focus unless `alwaysVisible`,\n * and disabled while its write is in flight so one row never has two writes\n * racing.\n */\nconst RowActionButton: FC<{\n label: string;\n onClick: (event: MouseEvent<HTMLButtonElement>) => void;\n pending: boolean;\n alwaysVisible: boolean;\n action: string;\n pressed?: boolean;\n children: ReactNode;\n}> = ({ label, onClick, pending, alwaysVisible, action, pressed, children }) => (\n <button\n type=\"button\"\n onClick={onClick}\n disabled={pending}\n aria-busy={pending || undefined}\n aria-pressed={pressed}\n aria-label={label}\n title={label}\n data-action={action}\n data-pending={pending || undefined}\n className={cn(\n 'rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:cursor-wait disabled:opacity-60',\n FOCUS_RING_CLASS,\n !alwaysVisible &&\n !pending &&\n 'opacity-0 focus-visible:opacity-100 group-focus-within:opacity-100 group-hover:opacity-100',\n )}\n >\n {children}\n </button>\n);\n\n/**\n * The pin toggle: pressed while the session is pinned (and then always\n * visible); otherwise an outline pin revealed on hover or focus so an\n * unpinned row stays quiet.\n */\nconst PinToggle: FC<{\n thread: Pick<AthenaStatewireThreadSummary, 'id' | 'isFavorited'>;\n pending: boolean;\n onToggle: (target: ThreadFavoriteTarget) => void;\n}> = ({ thread, pending, onToggle }) => {\n const label = thread.isFavorited ? 'Unpin session' : 'Pin session';\n const handleClick = (event: MouseEvent) => {\n event.stopPropagation();\n if (pending) return;\n onToggle({ threadId: thread.id, isFavorited: !thread.isFavorited });\n };\n return (\n <span data-pin-toggle={thread.isFavorited ? 'pinned' : 'unpinned'} className=\"contents\">\n <RowActionButton\n label={label}\n onClick={handleClick}\n pending={pending}\n alwaysVisible={thread.isFavorited}\n action=\"pin\"\n pressed={thread.isFavorited}\n >\n <PinIcon className={cn('size-3.5', thread.isFavorited && 'fill-current')} aria-hidden />\n </RowActionButton>\n </span>\n );\n};\n\nconst SKELETON_TITLE_WIDTHS = ['w-3/5', 'w-4/5', 'w-1/2', 'w-2/3'] as const;\n\n/**\n * Placeholder rows shaped like session rows (indicator, title line, meta\n * line) for the first page and for a refresh that has no rows to keep yet.\n */\nexport const SessionListSkeleton: FC<{ rows?: number }> = ({ rows = 4 }) => (\n <div\n role=\"status\"\n aria-busy=\"true\"\n aria-label=\"Loading sessions\"\n data-testid=\"session-list-skeleton\"\n className=\"flex flex-col gap-0.5\"\n >\n {Array.from({ length: rows }, (_, index) => (\n <div key={index} aria-hidden=\"true\" className=\"flex items-start gap-2 px-3 py-2\">\n <span className=\"mt-0.5 size-4 shrink-0 animate-pulse rounded-full bg-muted\" />\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex h-5 items-center\">\n <span\n className={cn(\n 'h-3.5 animate-pulse rounded bg-muted',\n SKELETON_TITLE_WIDTHS[index % SKELETON_TITLE_WIDTHS.length],\n )}\n />\n </span>\n <span className=\"mt-0.5 flex h-4 items-center\">\n <span className=\"h-3 w-1/3 animate-pulse rounded bg-muted/70\" />\n </span>\n </span>\n </div>\n ))}\n </div>\n);\n\nconst SessionRow: FC<{\n thread: AthenaStatewireThreadSummary;\n isActive: boolean;\n onSelect: (threadId: string) => void;\n /** Absent when hover prefetching is off or the provider has no snapshot cache to warm. */\n onPrefetch: ((threadId: string) => void) | undefined;\n /** Absent when the toggle is not offered: a pinned row then shows a read-only pin. */\n onToggleFavorite: ((target: ThreadFavoriteTarget) => void) | undefined;\n /** True while this session's pin write is in flight. */\n favoritePending: boolean;\n /** Absent when the rename is not offered (host opt-out, or a backend without the route). */\n onRename: ((target: ThreadRenameTarget) => void) | undefined;\n renamePending: boolean;\n /** Absent when the archive is not offered (host opt-out, or a backend without the route). */\n onArchive: ((threadId: string) => void) | undefined;\n archivePending: boolean;\n}> = ({\n thread,\n isActive,\n onSelect,\n onPrefetch,\n onToggleFavorite,\n favoritePending,\n onRename,\n renamePending,\n onArchive,\n archivePending,\n}) => {\n const indicator = sessionIndicator(thread);\n const line = sessionSecondaryLine(thread);\n const chip = pullRequestChip(thread.pullRequests);\n const origin = sessionOriginLabel(thread);\n const title = thread.title ?? 'Untitled';\n\n // Hover prefetch: the pointer must rest on the row for the dwell before\n // the fetch starts, so sweeping the pointer down the list fetches nothing.\n const hoverTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const cancelHover = useCallback(() => {\n if (hoverTimerRef.current === null) return;\n clearTimeout(hoverTimerRef.current);\n hoverTimerRef.current = null;\n }, []);\n useEffect(() => cancelHover, [cancelHover]);\n const handlePointerEnter = () => {\n if (!onPrefetch) return;\n cancelHover();\n hoverTimerRef.current = setTimeout(() => {\n hoverTimerRef.current = null;\n onPrefetch(thread.id);\n }, THREAD_PREFETCH_HOVER_DELAY_MS);\n };\n const handleFocus = () => {\n cancelHover();\n onPrefetch?.(thread.id);\n };\n\n // Inline rename: the title becomes an input; Enter commits, Escape cancels,\n // blur commits. An Enter or Escape settles the edit before the blur its\n // unmount may raise, so the blur never commits a second time.\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState('');\n const settledRef = useRef(false);\n const inputRef = useRef<HTMLInputElement | null>(null);\n useEffect(() => {\n if (!editing) return;\n inputRef.current?.focus();\n inputRef.current?.select();\n }, [editing]);\n const beginRename = (event: MouseEvent) => {\n event.stopPropagation();\n if (renamePending) return;\n settledRef.current = false;\n setDraft(thread.title ?? '');\n setEditing(true);\n };\n const commitRename = () => {\n if (settledRef.current) return;\n settledRef.current = true;\n setEditing(false);\n const next = draft.trim();\n if (!next || next === (thread.title ?? '').trim()) return;\n onRename?.({ threadId: thread.id, title: next });\n };\n const cancelRename = () => {\n settledRef.current = true;\n setEditing(false);\n };\n const handleEditorKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'Enter') {\n event.preventDefault();\n commitRename();\n } else if (event.key === 'Escape') {\n event.preventDefault();\n event.stopPropagation();\n cancelRename();\n }\n };\n\n const handleArchive = (event: MouseEvent) => {\n event.stopPropagation();\n if (archivePending) return;\n onArchive?.(thread.id);\n };\n\n const hasActions = Boolean(onRename || onToggleFavorite || onArchive);\n const body = (\n <>\n <SessionStatusIndicator kind={indicator} />\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex h-5 items-center gap-1.5\">\n {editing ? (\n <input\n ref={inputRef}\n type=\"text\"\n value={draft}\n maxLength={THREAD_TITLE_MAX_LENGTH}\n aria-label=\"Session title\"\n data-rename-input\n onChange={(event: ChangeEvent<HTMLInputElement>) => setDraft(event.target.value)}\n onKeyDown={handleEditorKeyDown}\n onBlur={commitRename}\n onClick={stopPropagation}\n className={cn(\n 'h-5 min-w-0 flex-1 rounded border border-primary/60 bg-background px-1 font-medium text-foreground text-sm',\n FOCUS_RING_CLASS,\n )}\n />\n ) : (\n <span data-title className=\"min-w-0 truncate font-medium text-sm\">\n {title}\n </span>\n )}\n {thread.isFavorited && !onToggleFavorite ? (\n <PinIcon\n role=\"img\"\n aria-label=\"Pinned\"\n className=\"size-3 shrink-0 fill-current text-muted-foreground\"\n />\n ) : null}\n {origin ? (\n <span\n data-origin={origin}\n className=\"shrink-0 rounded-full border border-border/60 px-1.5 py-px text-[10px] text-muted-foreground\"\n >\n {origin}\n </span>\n ) : null}\n </span>\n <span\n data-line={line.kind}\n className={cn('mt-0.5 block h-4 truncate font-normal text-xs leading-4', LINE_TONE_CLASS[line.tone])}\n >\n {line.text}\n </span>\n </span>\n </>\n );\n\n return (\n <li\n data-active={isActive || undefined}\n data-thread-id={thread.id}\n data-editing={editing || undefined}\n onPointerEnter={handlePointerEnter}\n onPointerLeave={cancelHover}\n className={cn(\n 'group relative flex items-start gap-1 rounded-lg text-muted-foreground transition-colors hover:bg-muted/40 hover:text-foreground',\n isActive && 'bg-muted text-foreground',\n )}\n >\n {isActive ? (\n <span\n aria-hidden=\"true\"\n data-active-accent\n className=\"absolute inset-y-2 left-0 w-0.5 rounded-full bg-primary\"\n />\n ) : null}\n {editing ? (\n <div className=\"flex min-w-0 flex-1 items-start gap-2 p-2 pl-3 text-left\">{body}</div>\n ) : (\n <button\n type=\"button\"\n onClick={() => onSelect(thread.id)}\n onFocus={handleFocus}\n className={cn(\n 'flex min-w-0 flex-1 cursor-pointer items-start gap-2 rounded-lg p-2 pl-3 text-left',\n FOCUS_RING_CLASS,\n )}\n >\n {body}\n </button>\n )}\n {chip || hasActions ? (\n <div className=\"flex shrink-0 flex-col items-end justify-between gap-0.5 py-2 pr-2\">\n {hasActions ? (\n <div data-row-actions className=\"flex h-5 items-center gap-0.5\">\n {onRename ? (\n <RowActionButton\n label=\"Rename session\"\n onClick={beginRename}\n pending={renamePending}\n alwaysVisible={false}\n action=\"rename\"\n >\n <PencilIcon className=\"size-3.5\" aria-hidden />\n </RowActionButton>\n ) : null}\n {onToggleFavorite ? (\n <PinToggle thread={thread} pending={favoritePending} onToggle={onToggleFavorite} />\n ) : null}\n {onArchive ? (\n <RowActionButton\n label=\"Archive session\"\n onClick={handleArchive}\n pending={archivePending}\n alwaysVisible={false}\n action=\"archive\"\n >\n <ArchiveIcon className=\"size-3.5\" aria-hidden />\n </RowActionButton>\n ) : null}\n </div>\n ) : (\n <span aria-hidden=\"true\" className=\"h-5\" />\n )}\n {chip ? <PullRequestChip chip={chip} pullRequests={thread.pullRequests ?? []} /> : null}\n </div>\n ) : null}\n </li>\n );\n};\n\n/**\n * Devin-style session list: the `ThreadList` rows with an attention\n * indicator, the server's next action, pull-request chips, an origin badge\n * and a trailing action cluster (rename inline, pin, archive) revealed on\n * hover or focus, grouped by what each session asks of the viewer. Reads the\n * thread-list state that `<AthenaProvider enableThreadList>` provides and\n * renders nothing outside that mode.\n *\n * Every write is optimistic and rolls back on failure: the pin flips the row\n * and moves it between `Pinned` and `Recent`; the rename shows the new title\n * on commit; the archive removes the row. On a backend that predates a route\n * the matching action is withdrawn (`favoriteToggleAvailable` falls back to a\n * read-only pin; `renameAvailable` / `archiveAvailable` hide the action).\n *\n * Rows warm the provider's snapshot cache after a 120 ms hover dwell (and on\n * focus) so a selection paints its transcript at once. Skeleton rows stand in\n * while the first page loads or a refresh has nothing to keep; the empty\n * state appears only after a settled empty answer, and a background refresh\n * never blanks the rows it already has.\n */\nexport function SessionList({\n className,\n grouped = true,\n showSearch = true,\n showPinToggle = true,\n showRename = true,\n showArchive = true,\n prefetchOnHover = true,\n}: SessionListProps) {\n const list = useAthenaStatewireThreadList();\n if (!list) return null;\n\n const sections = groupSessions(list.threads, grouped);\n const hasSearch = list.search.trim().length > 0;\n const handleSearch = (event: ChangeEvent<HTMLInputElement>) =>\n list.setSearch(event.target.value);\n const onToggleFavorite =\n showPinToggle && list.favoriteToggleAvailable ? list.setFavorited : undefined;\n const onRename = showRename && list.renameAvailable ? list.renameThread : undefined;\n const onArchive = showArchive && list.archiveAvailable ? list.archiveThread : undefined;\n const onPrefetch = prefetchOnHover ? list.prefetchThread : undefined;\n // Skeleton until the first page has answered (no credential yet, or the\n // page in flight) and for a refresh with no rows to keep; a refresh that\n // has rows keeps showing them.\n const showSkeleton =\n !list.hasResolvedFirstPage || (list.isFetching && list.threads.length === 0);\n\n return (\n <div\n className={cn('athena-sdk-chrome flex flex-col gap-2', className)}\n data-testid=\"session-list\"\n >\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={list.newThread}\n className={cn(\n 'flex h-9 flex-1 items-center gap-2 rounded-lg border border-border/60 px-3 font-medium text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground',\n FOCUS_RING_CLASS,\n )}\n >\n <PlusIcon className=\"size-4\" />\n New Chat\n </button>\n <button\n type=\"button\"\n onClick={list.refresh}\n disabled={list.isFetching}\n className={cn(\n 'flex size-9 items-center justify-center rounded-lg border border-border/60 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50',\n FOCUS_RING_CLASS,\n )}\n title=\"Refresh sessions\"\n aria-label=\"Refresh sessions\"\n >\n <RefreshCwIcon className={cn('size-4', list.isFetching && 'animate-spin')} />\n </button>\n </div>\n\n {showSearch ? (\n <label className=\"relative block\">\n <SearchIcon className=\"pointer-events-none absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <span className=\"sr-only\">Search sessions</span>\n <input\n type=\"search\"\n value={list.search}\n onChange={handleSearch}\n placeholder=\"Search sessions…\"\n className=\"h-8 w-full rounded-lg border border-border/60 bg-muted/40 pr-2 pl-8 text-sm outline-none focus:border-primary focus:bg-background\"\n />\n </label>\n ) : null}\n\n {showSkeleton ? (\n <SessionListSkeleton />\n ) : sections.length === 0 ? (\n <p data-testid=\"session-list-empty\" className=\"px-3 py-4 text-muted-foreground text-xs\">\n {hasSearch ? 'No matching sessions.' : 'No sessions yet.'}\n </p>\n ) : (\n <div className=\"flex flex-col gap-2\" aria-busy={list.isFetching || undefined}>\n {sections.map(({ group, items }) => (\n <section\n key={group ?? 'all'}\n aria-label={group ?? 'Sessions'}\n data-group={group ?? undefined}\n >\n {group ? (\n <h3 className=\"px-3 pb-1 font-medium text-[11px] text-muted-foreground/70 uppercase tracking-wide\">\n {group}\n </h3>\n ) : null}\n <ul className=\"flex flex-col gap-0.5\">\n {items.map((thread) => (\n <SessionRow\n key={thread.id}\n thread={thread}\n isActive={thread.id === list.activeThreadId}\n onSelect={list.selectThread}\n onPrefetch={onPrefetch}\n onToggleFavorite={onToggleFavorite}\n favoritePending={list.favoritePendingThreadIds.has(thread.id)}\n onRename={onRename}\n renamePending={list.renamePendingThreadIds.has(thread.id)}\n onArchive={onArchive}\n archivePending={list.archivePendingThreadIds.has(thread.id)}\n />\n ))}\n </ul>\n </section>\n ))}\n </div>\n )}\n\n {list.hasNextPage && !showSkeleton ? (\n <button\n type=\"button\"\n disabled={list.isFetchingNextPage}\n onClick={list.fetchNextPage}\n className={cn(\n 'w-full rounded-lg py-2 text-muted-foreground text-xs transition-colors hover:bg-muted disabled:opacity-50',\n FOCUS_RING_CLASS,\n )}\n >\n {list.isFetchingNextPage ? 'Loading…' : 'Load more'}\n </button>\n ) : null}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
package/dist/threads/api.cjs
CHANGED
|
@@ -105,7 +105,11 @@ async function setThreadReadState(backendUrl, auth, opts) {
|
|
|
105
105
|
})
|
|
106
106
|
});
|
|
107
107
|
if (!res.ok) {
|
|
108
|
-
throw new
|
|
108
|
+
throw new errors.AthenaSdkError({
|
|
109
|
+
code: res.status === 401 || res.status === 403 ? "auth_rejected" : "unknown",
|
|
110
|
+
message: `Failed to set thread read state: ${res.status}`,
|
|
111
|
+
status: res.status
|
|
112
|
+
});
|
|
109
113
|
}
|
|
110
114
|
return res.json();
|
|
111
115
|
}
|
package/dist/threads/api.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.cjs","sources":["../../src/threads/api.ts"],"sourcesContent":["/**\n * Athena thread API client.\n * Calls existing REST endpoints for thread CRUD.\n */\n\nimport type {\n ConversationAttention,\n ConversationAttentionSource,\n} from '@athenaintel/agent-runtime';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport { AthenaSdkError, type AthenaSdkErrorCode, isAthenaSdkError } from '../diagnostics/errors';\n\n/**\n * Live GitHub state of a pull request the thread opened, read by the server\n * when the list renders (agora `agent_pr_threads` is the only stored link).\n */\nexport interface ThreadPullRequest {\n /** `owner/repo` the pull request lives in. */\n repo_full_name: string;\n number: number;\n url: string;\n title: string;\n state: 'OPEN' | 'CLOSED' | 'MERGED';\n is_draft: boolean;\n /** GitHub's review decision (APPROVED, CHANGES_REQUESTED, REVIEW_REQUIRED) or null. */\n review_decision: string | null;\n /** Combined state of the checks on the head commit. */\n checks_state: 'SUCCESS' | 'FAILURE' | 'PENDING' | 'NONE';\n /** ISO 8601 timestamp of the pull request's last update on GitHub. */\n updated_at: string;\n}\n\n/** The last event that woke a resting thread (a GitHub webhook, a schedule, a handoff). */\nexport interface ThreadWake {\n source: string | null;\n reason: string | null;\n /** ISO 8601 timestamp of the wake. */\n at: string | null;\n}\n\nexport interface ThreadSummary {\n thread_id: string;\n title: string;\n created_at: string;\n updated_at: string;\n last_message: string | null;\n message_count: number;\n state: string | null;\n start_channel?: string | null;\n last_channel?: string | null;\n /** Client app that created the session (SESSION_APP_ID_GUIDE). */\n app_id?: string | null;\n /** Trigger marker for automated/background sessions. */\n trigger_type?: string | null;\n /** Whether the calling user has seen this thread since it last changed. */\n is_unread?: boolean;\n /**\n * Canonical status_v2 planes (`idle | active | needs_input | error` and\n * `scheduled | queued | running | needs_input | completed | failed |\n * canceled`); older servers omit them.\n */\n session_status?: string | null;\n run_status?: string | null;\n /**\n * The viewer's attention state, the one-line next action (≤ 80 chars) and\n * the signal that produced them, derived server-side; older servers omit them.\n */\n attention?: ConversationAttention | null;\n next_action?: string | null;\n attention_source?: ConversationAttentionSource | null;\n /** Whether the calling user favorited (pinned) the thread; older servers omit it. */\n is_favorited?: boolean;\n /** The last wake of a resting thread; older servers omit it. */\n last_wake?: ThreadWake | null;\n /**\n * Pull requests the thread opened with their live GitHub state. `[]` when\n * it opened none, `null` when the state could not be read right now;\n * older servers omit the field.\n */\n pull_requests?: ThreadPullRequest[] | null;\n}\n\n/**\n * True for fetch cancellations (unmount/remount aborts, StrictMode double\n * effects). These are lifecycle noise, not failures: emitting them as\n * `*.error` diagnostics floods the console with\n * \"signal is aborted without reason\" for requests the caller itself aborted.\n */\nfunction isAbortError(error: unknown): boolean {\n return (\n (error instanceof DOMException && error.name === 'AbortError') ||\n (error instanceof Error && error.name === 'AbortError')\n );\n}\n\nexport interface ThreadListResponse {\n threads: ThreadSummary[];\n /** Server lookahead result. Older servers may omit this field. */\n has_more?: boolean;\n}\n\nexport interface ThreadCreateResponse {\n thread_id: string;\n}\n\ninterface AuthConfig {\n apiKey?: string;\n token?: string | null;\n}\n\nfunction getAuthHeaders(auth: AuthConfig): Record<string, string> {\n if (auth.token) {\n return { Authorization: `Bearer ${auth.token}` };\n }\n if (auth.apiKey) {\n return { 'X-API-KEY': auth.apiKey };\n }\n return {};\n}\n\n/**\n * Resolves the Athena API base URL from backendUrl.\n * Thread endpoints live under `/api/conversations/threads/*` and `/api/unstable/threads/`.\n * If backendUrl points at a nested Athena endpoint, trim it back to the API base first.\n */\nfunction getAthenaApiBaseUrl(backendUrl: string): string {\n const stripped = backendUrl.replace(/\\/api\\/assistant-ui\\/?$/, '');\n // If the suffix wasn't found, assume the URL already points to the API base\n if (stripped === backendUrl) {\n return backendUrl.replace(/\\/$/, '');\n }\n return stripped;\n}\n\nexport async function listThreads(\n backendUrl: string,\n auth: AuthConfig,\n opts: {\n limit?: number;\n offset?: number;\n exclude_triggered?: boolean;\n app_id?: string;\n start_channel?: string;\n search?: string;\n /** Ask the server for live GitHub state on `pull_requests` (off by default: it costs a lookup). */\n include_pull_requests?: boolean;\n signal?: AbortSignal;\n } = {},\n): Promise<ThreadListResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const endSpan = athenaDiagnostics.span('threads.list', 'threads.list.start', {\n auth_mode: getAuthMode(auth),\n app_id: opts.app_id,\n });\n try {\n const res = await fetch(`${base}/api/conversations/threads/list`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n signal: opts.signal ?? null,\n body: JSON.stringify({\n limit: opts.limit ?? 50,\n offset: opts.offset ?? 0,\n // Default to the full recent-conversations set unless a caller\n // explicitly asks to hide triggered/background sessions.\n exclude_triggered: opts.exclude_triggered ?? false,\n ...(opts.app_id ? { app_id: opts.app_id } : {}),\n ...(opts.start_channel ? { start_channel: opts.start_channel } : {}),\n ...(opts.search ? { search: opts.search } : {}),\n ...(opts.include_pull_requests ? { include_pull_requests: true } : {}),\n }),\n });\n if (!res.ok) {\n throw new AthenaSdkError({\n code: res.status === 401 || res.status === 403 ? 'auth_rejected' : 'threads_list_failed',\n message: `Failed to list threads: ${res.status}`,\n status: res.status,\n hint:\n res.status === 401 || res.status === 403\n ? 'The credential was refused. Check the token/API key and that the user belongs to the workspace.'\n : 'Check backendUrl points at the Agora API base.',\n context: { auth_mode: getAuthMode(auth) },\n });\n }\n const data = (await res.json()) as ThreadListResponse;\n endSpan('threads.list.done', { data: { count: data.threads?.length ?? 0 } });\n return data;\n } catch (error) {\n // Cancellation is lifecycle noise (unmount/remount aborts): log it as an\n // informational abort, not an error — passing `error` would force the\n // span end to error level and spam the console.\n if (isAbortError(error)) {\n endSpan('threads.list.aborted', { data: { reason: 'request aborted' } });\n } else {\n endSpan('threads.list.error', { error });\n }\n throw error;\n }\n}\n\n/** The `(workspace, user)` pair the credential resolves to on the backend. */\nexport interface AccountIdentity {\n userId: string;\n /** `null` for a user with no current workspace. */\n workspaceId: string | null;\n}\n\n/**\n * Resolve the credential's agora identity. The conversation index is one\n * statewire instance per `(workspace, user)`, and neither half is knowable\n * from the credential alone: the SDK's auth context carries an identity\n * provider user id, not agora's, and never a workspace.\n */\nexport async function fetchAccountIdentity(\n backendUrl: string,\n auth: AuthConfig,\n opts: { signal?: AbortSignal } = {},\n): Promise<AccountIdentity> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/v0/me`, {\n headers: getAuthHeaders(auth),\n signal: opts.signal ?? null,\n });\n if (!res.ok) {\n throw new AthenaSdkError({\n code: res.status === 401 || res.status === 403 ? 'auth_rejected' : 'threads_list_failed',\n message: `Failed to resolve account identity: ${res.status}`,\n status: res.status,\n hint:\n res.status === 401 || res.status === 403\n ? 'The credential was refused. Check the token/API key and that the user belongs to the workspace.'\n : 'Check backendUrl points at the Agora API base.',\n context: { auth_mode: getAuthMode(auth) },\n });\n }\n const body = (await res.json()) as { user_id?: unknown; workspace_id?: unknown };\n if (typeof body.user_id !== 'string' || body.user_id.length === 0) {\n throw new AthenaSdkError({\n code: 'threads_list_failed',\n message: 'Account identity lookup returned no user id.',\n context: { auth_mode: getAuthMode(auth) },\n });\n }\n return {\n userId: body.user_id,\n workspaceId: typeof body.workspace_id === 'string' ? body.workspace_id : null,\n };\n}\n\nexport interface ThreadReadStateResponse {\n thread_id: string;\n is_unread: boolean;\n}\n\n/** Store the calling user's read receipt for a thread. */\nexport async function setThreadReadState(\n backendUrl: string,\n auth: AuthConfig,\n opts: { threadId: string; isUnread: boolean },\n): Promise<ThreadReadStateResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/conversations/threads/read-state`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({\n thread_id: opts.threadId,\n is_unread: opts.isUnread,\n }),\n });\n if (!res.ok) {\n throw new Error(`[AthenaSDK] Failed to set thread read state: ${res.status}`);\n }\n return res.json();\n}\n\nexport interface ThreadFavoriteResponse {\n thread_id: string;\n /** The server's state after the write. */\n is_favorited: boolean;\n}\n\n/**\n * Classify a 4xx from one of the per-thread write routes (`favorite`,\n * `rename`, `archive`). A 404 there is ambiguous: FastAPI answers\n * `{\"detail\":\"Not Found\"}` for a route an older agora does not have, the\n * archive route answers `\"Endpoint not available\"` on an agora without the\n * checkpointer — and each route answers 404 for a thread that no longer\n * exists (`\"Thread <id> not found\"`, `\"Conversation with langchain thread id\n * <id> not found\"`). The routes' own not-found answers name the thread, so a\n * body that does not is the ROUTE missing, and only that withdraws the\n * feature; a stale row must not disable the action for every other row.\n */\nfunction classifyThreadRouteRejection(\n status: number,\n body: string,\n threadId: string,\n): { routeMissing: boolean; detail: unknown } {\n let detail: unknown = body || undefined;\n try {\n const parsed: unknown = JSON.parse(body);\n if (parsed && typeof parsed === 'object') {\n const record = parsed as { detail?: unknown; message?: unknown };\n detail = record.detail ?? record.message ?? parsed;\n }\n } catch {\n // A non-JSON body (a proxy page) is kept as text.\n }\n const routeMissing = status === 405 || (status === 404 && !mentionsThread(body, threadId));\n return { routeMissing, detail };\n}\n\n/**\n * Whether an error body names the thread as a whole token (the routes embed\n * the id between spaces: `Thread <id> not found`); a substring match would\n * let a short id match a letter inside `\"detail\"`.\n */\nfunction mentionsThread(body: string, threadId: string): boolean {\n if (!threadId) return false;\n const escaped = threadId.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n return new RegExp(`(^|[^A-Za-z0-9_-])${escaped}($|[^A-Za-z0-9_-])`).test(body);\n}\n\n/** True when a per-thread write was rejected because the backend has no such route. */\nfunction isThreadRouteUnavailableError(error: unknown, code: AthenaSdkErrorCode): boolean {\n return isAthenaSdkError(error) && error.code === code && error.context.route_missing === true;\n}\n\n/**\n * Pin (favorite) or unpin a thread for the calling user. The response is the\n * server's truth after the write. Rejections: 401 / 403 when the caller\n * cannot view the thread (`auth_rejected`), otherwise `thread_favorite_failed`\n * with the HTTP status — 404 / 405 from an agora that predates the route,\n * which `isThreadFavoriteUnavailableError` recognizes so callers can fall\n * back to a read-only pin.\n */\nexport async function setThreadFavorite(\n backendUrl: string,\n auth: AuthConfig,\n opts: { threadId: string; isFavorited: boolean },\n): Promise<ThreadFavoriteResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/conversations/threads/favorite`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({\n thread_id: opts.threadId,\n is_favorited: opts.isFavorited,\n }),\n });\n if (!res.ok) {\n const refused = res.status === 401 || res.status === 403;\n const { routeMissing, detail } = classifyThreadRouteRejection(\n res.status,\n await getResponseErrorBody(res),\n opts.threadId,\n );\n throw new AthenaSdkError({\n code: refused ? 'auth_rejected' : 'thread_favorite_failed',\n message: `Failed to set thread favorite: ${res.status}`,\n status: res.status,\n detail,\n hint: refused\n ? 'The credential cannot view this thread.'\n : routeMissing\n ? 'The backend has no favorite route yet (older agora); the pin stays read-only until it deploys.'\n : res.status === 404\n ? 'The thread no longer exists on the server (archived or deleted elsewhere); refresh the list.'\n : 'Check backendUrl points at the Agora API base.',\n context: { thread_id: opts.threadId, auth_mode: getAuthMode(auth), route_missing: routeMissing },\n });\n }\n return res.json();\n}\n\n/**\n * True when `setThreadFavorite` failed because the backend has no favorite\n * route (a 405, or a 404 whose body does not name the thread — see\n * `classifyThreadRouteRejection`); a 404 for a thread that is gone is a\n * per-row failure and leaves the toggle available.\n */\nexport function isThreadFavoriteUnavailableError(error: unknown): boolean {\n return isThreadRouteUnavailableError(error, 'thread_favorite_failed');\n}\n\nexport interface ThreadRenameResponse {\n thread_id: string;\n /** The stored title after the write (trimmed by the server). */\n title: string;\n}\n\n/** The longest title the rename route accepts; the server answers 422 past it. */\nexport const THREAD_TITLE_MAX_LENGTH = 200;\n\n/**\n * Rename a thread for every viewer (`POST /api/conversations/threads/rename`).\n * The server trims the title and stores 1–200 characters; the response is the\n * row's title after the write. Rejections: 401 / 403 when the caller cannot\n * edit the thread (`auth_rejected` — a viewer can pin a shared session but not\n * retitle it), otherwise `thread_rename_failed` with the HTTP status — 404 /\n * 405 from an agora that predates the route, which\n * `isThreadRenameUnavailableError` recognizes so callers can drop the action.\n */\nexport async function renameThread(\n backendUrl: string,\n auth: AuthConfig,\n opts: { threadId: string; title: string },\n): Promise<ThreadRenameResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/conversations/threads/rename`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({\n thread_id: opts.threadId,\n title: opts.title,\n }),\n });\n if (!res.ok) {\n const refused = res.status === 401 || res.status === 403;\n const { routeMissing, detail } = classifyThreadRouteRejection(\n res.status,\n await getResponseErrorBody(res),\n opts.threadId,\n );\n throw new AthenaSdkError({\n code: refused ? 'auth_rejected' : 'thread_rename_failed',\n message: `Failed to rename thread: ${res.status}`,\n status: res.status,\n detail,\n hint: refused\n ? 'The credential cannot edit this thread (a viewer can pin a shared session, not retitle it).'\n : routeMissing\n ? 'The backend has no rename route yet (older agora); sessions cannot be renamed from here until it deploys.'\n : res.status === 404\n ? 'The thread no longer exists on the server (archived or deleted elsewhere); refresh the list.'\n : res.status === 422\n ? `The title must be 1–${THREAD_TITLE_MAX_LENGTH} characters after trimming.`\n : 'Check backendUrl points at the Agora API base.',\n context: { thread_id: opts.threadId, auth_mode: getAuthMode(auth), route_missing: routeMissing },\n });\n }\n return res.json();\n}\n\n/**\n * True when `renameThread` failed because the backend has no rename route\n * (a 405, or a 404 whose body does not name the thread); a 404 for a thread\n * that is gone is a per-row failure and leaves the action offered.\n */\nexport function isThreadRenameUnavailableError(error: unknown): boolean {\n return isThreadRouteUnavailableError(error, 'thread_rename_failed');\n}\n\n/**\n * True when `archiveThread` failed because the backend offers no archive\n * route: a 405, FastAPI's bare 404 on an agora without the route, or the\n * route's own `\"Endpoint not available\"` 404 on an agora without the\n * checkpointer. The route's `\"Thread <id> not found\"` 404 for a stale row is\n * a per-row failure and leaves the action offered on every other row.\n */\nexport function isThreadArchiveUnavailableError(error: unknown): boolean {\n return isThreadRouteUnavailableError(error, 'thread_archive_failed');\n}\n\nexport async function createThread(\n backendUrl: string,\n auth: AuthConfig,\n opts: { systemPrompt?: string; enabledTools?: string[]; appId?: string } = {},\n): Promise<ThreadCreateResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/unstable/threads/`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({\n config: {\n system_prompt: opts.systemPrompt ?? '',\n enabled_tools: opts.enabledTools,\n ...(opts.appId ? { app_id: opts.appId } : {}),\n },\n }),\n });\n if (!res.ok) {\n throw new Error(`[AthenaSDK] Failed to create thread: ${res.status}`);\n }\n return res.json();\n}\n\nexport interface ThreadStateResponse {\n thread_id: string;\n messages: unknown[];\n status?: string;\n [key: string]: unknown;\n}\n\ninterface ConversationThreadResponse {\n thread?: {\n thread_id?: string;\n state?: string | null;\n channel_values?: Record<string, unknown> | null;\n messages?: unknown[] | null;\n } | null;\n thread_found?: boolean;\n}\n\n/**\n * Deserialize a LangChain serialized message.\n * The API returns messages in `{lc, type: \"constructor\", id, kwargs}` format.\n * We need the plain format: `{type: \"human\", content: \"...\", id: \"...\"}`.\n */\nfunction deserializeMessage(msg: unknown): unknown {\n if (\n msg &&\n typeof msg === 'object' &&\n 'lc' in msg &&\n 'kwargs' in msg &&\n (msg as Record<string, unknown>).type === 'constructor'\n ) {\n const kwargs = (msg as { kwargs: Record<string, unknown> }).kwargs;\n\n // Recursively deserialize nested content without mutating the original\n const deserializedToolCalls = Array.isArray(kwargs.tool_calls)\n ? kwargs.tool_calls.map((tc: unknown) => {\n if (tc && typeof tc === 'object' && 'lc' in tc && 'kwargs' in tc) {\n return (tc as { kwargs: unknown }).kwargs;\n }\n return tc;\n })\n : kwargs.tool_calls;\n\n return { ...kwargs, tool_calls: deserializedToolCalls };\n }\n return msg;\n}\n\n/** Return a non-sensitive auth label for request diagnostics. */\nfunction getAuthMode(auth: AuthConfig): 'bearer' | 'api-key' | 'none' {\n if (auth.token) {\n return 'bearer';\n }\n if (auth.apiKey) {\n return 'api-key';\n }\n return 'none';\n}\n\n/** Safely read a response body for error messages without masking the root failure. */\nfunction getResponseErrorBody(response: Response): Promise<string> {\n return response.text().catch(() => '');\n}\n\n/**\n * Fetch persisted thread state from the conversations API.\n *\n * This endpoint is the same source used by Athena's conversation sidebar and\n * supports API-key auth plus BYO SSO session tokens sent as Bearer tokens.\n */\nexport async function getThreadState(\n backendUrl: string,\n auth: AuthConfig,\n threadId: string,\n opts: {\n /**\n * Bypass the server's Redis thread cache and read the checkpoint (default\n * `true`, the history load's contract). A warm-up read that only seeds a\n * first paint passes `false`: the cached copy is cheaper and the attach\n * snapshot replaces it anyway.\n */\n skipCache?: boolean;\n signal?: AbortSignal;\n } = {},\n): Promise<ThreadStateResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const authMode = getAuthMode(auth);\n const endpoint = `${base}/api/conversations/threads/get`;\n\n const endSpan = athenaDiagnostics.span('threads.history', 'threads.history.start', {\n thread_id: threadId,\n auth_mode: authMode,\n });\n\n let res: Response;\n try {\n res = await fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n signal: opts.signal ?? null,\n body: JSON.stringify({\n thread_id: threadId,\n skip_cache: opts.skipCache ?? true,\n }),\n });\n } catch (error) {\n if (isAbortError(error)) {\n endSpan('threads.history.aborted', {\n data: { thread_id: threadId, reason: 'request aborted' },\n });\n } else {\n endSpan('threads.history.error', { error, data: { thread_id: threadId } });\n }\n throw error;\n }\n if (!res.ok) {\n const body = await getResponseErrorBody(res);\n const error = new AthenaSdkError({\n code: res.status === 401 || res.status === 403 ? 'auth_rejected' : 'thread_load_failed',\n message: `Failed to get thread state: ${res.status} (${authMode})${\n body ? `: ${body.slice(0, 300)}` : ''\n }`,\n status: res.status,\n hint: 'A brand-new thread may not be persisted yet; anything else means the credential or backendUrl is wrong.',\n context: { thread_id: threadId, auth_mode: authMode },\n });\n endSpan('threads.history.error', { error, data: { thread_id: threadId } });\n throw error;\n }\n const data = (await res.json()) as ConversationThreadResponse;\n const channelMessages = data.thread?.channel_values?.messages;\n endSpan('threads.history.done', {\n data: {\n thread_id: threadId,\n found: data.thread_found !== false,\n message_count: Array.isArray(channelMessages)\n ? channelMessages.length\n : (data.thread?.messages?.length ?? 0),\n },\n });\n const thread = data.thread;\n\n if (data.thread_found === false || !thread) {\n return {\n thread_id: threadId,\n messages: [],\n };\n }\n\n const channelValues = thread.channel_values ?? {};\n const rawMessages = Array.isArray(channelValues.messages)\n ? channelValues.messages\n : Array.isArray(thread.messages)\n ? thread.messages\n : [];\n\n return {\n ...channelValues,\n thread_id: thread.thread_id ?? threadId,\n messages: rawMessages.map(deserializeMessage),\n ...(thread.state ? { status: thread.state } : {}),\n };\n}\n\n/**\n * Archive a thread (`POST /api/conversations/threads/archive`): it leaves\n * every list but keeps its state. Rejections: 401 / 403 when the caller\n * cannot access the thread (`auth_rejected`), otherwise\n * `thread_archive_failed` with the HTTP status — 404 / 405 when the backend\n * has no archive route, which `isThreadArchiveUnavailableError` recognizes.\n */\nexport async function archiveThread(\n backendUrl: string,\n auth: AuthConfig,\n threadId: string,\n): Promise<void> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/conversations/threads/archive`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({ thread_id: threadId }),\n });\n if (!res.ok) {\n const refused = res.status === 401 || res.status === 403;\n const { routeMissing, detail } = classifyThreadRouteRejection(\n res.status,\n await getResponseErrorBody(res),\n threadId,\n );\n throw new AthenaSdkError({\n code: refused ? 'auth_rejected' : 'thread_archive_failed',\n message: `Failed to archive thread: ${res.status}`,\n status: res.status,\n detail,\n hint: refused\n ? 'The credential cannot access this thread.'\n : routeMissing\n ? 'The backend has no archive route (no checkpointer, or an older agora).'\n : res.status === 404\n ? 'The thread no longer exists on the server (archived or deleted elsewhere); refresh the list.'\n : 'Check backendUrl points at the Agora API base.',\n context: { thread_id: threadId, auth_mode: getAuthMode(auth), route_missing: routeMissing },\n });\n }\n}\n"],"names":["athenaDiagnostics","AthenaSdkError","isAthenaSdkError"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"api.cjs","sources":["../../src/threads/api.ts"],"sourcesContent":["/**\n * Athena thread API client.\n * Calls existing REST endpoints for thread CRUD.\n */\n\nimport type {\n ConversationAttention,\n ConversationAttentionSource,\n} from '@athenaintel/agent-runtime';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport { AthenaSdkError, type AthenaSdkErrorCode, isAthenaSdkError } from '../diagnostics/errors';\n\n/**\n * Live GitHub state of a pull request the thread opened, read by the server\n * when the list renders (agora `agent_pr_threads` is the only stored link).\n */\nexport interface ThreadPullRequest {\n /** `owner/repo` the pull request lives in. */\n repo_full_name: string;\n number: number;\n url: string;\n title: string;\n state: 'OPEN' | 'CLOSED' | 'MERGED';\n is_draft: boolean;\n /** GitHub's review decision (APPROVED, CHANGES_REQUESTED, REVIEW_REQUIRED) or null. */\n review_decision: string | null;\n /** Combined state of the checks on the head commit. */\n checks_state: 'SUCCESS' | 'FAILURE' | 'PENDING' | 'NONE';\n /** ISO 8601 timestamp of the pull request's last update on GitHub. */\n updated_at: string;\n}\n\n/** The last event that woke a resting thread (a GitHub webhook, a schedule, a handoff). */\nexport interface ThreadWake {\n source: string | null;\n reason: string | null;\n /** ISO 8601 timestamp of the wake. */\n at: string | null;\n}\n\nexport interface ThreadSummary {\n thread_id: string;\n title: string;\n created_at: string;\n updated_at: string;\n last_message: string | null;\n message_count: number;\n state: string | null;\n start_channel?: string | null;\n last_channel?: string | null;\n /** Client app that created the session (SESSION_APP_ID_GUIDE). */\n app_id?: string | null;\n /** Trigger marker for automated/background sessions. */\n trigger_type?: string | null;\n /** Whether the calling user has seen this thread since it last changed. */\n is_unread?: boolean;\n /**\n * Canonical status_v2 planes (`idle | active | needs_input | error` and\n * `scheduled | queued | running | needs_input | completed | failed |\n * canceled`); older servers omit them.\n */\n session_status?: string | null;\n run_status?: string | null;\n /**\n * The viewer's attention state, the one-line next action (≤ 80 chars) and\n * the signal that produced them, derived server-side; older servers omit them.\n */\n attention?: ConversationAttention | null;\n next_action?: string | null;\n attention_source?: ConversationAttentionSource | null;\n /** Whether the calling user favorited (pinned) the thread; older servers omit it. */\n is_favorited?: boolean;\n /** The last wake of a resting thread; older servers omit it. */\n last_wake?: ThreadWake | null;\n /**\n * Pull requests the thread opened with their live GitHub state. `[]` when\n * it opened none, `null` when the state could not be read right now;\n * older servers omit the field.\n */\n pull_requests?: ThreadPullRequest[] | null;\n}\n\n/**\n * True for fetch cancellations (unmount/remount aborts, StrictMode double\n * effects). These are lifecycle noise, not failures: emitting them as\n * `*.error` diagnostics floods the console with\n * \"signal is aborted without reason\" for requests the caller itself aborted.\n */\nfunction isAbortError(error: unknown): boolean {\n return (\n (error instanceof DOMException && error.name === 'AbortError') ||\n (error instanceof Error && error.name === 'AbortError')\n );\n}\n\nexport interface ThreadListResponse {\n threads: ThreadSummary[];\n /** Server lookahead result. Older servers may omit this field. */\n has_more?: boolean;\n}\n\nexport interface ThreadCreateResponse {\n thread_id: string;\n}\n\ninterface AuthConfig {\n apiKey?: string;\n token?: string | null;\n}\n\nfunction getAuthHeaders(auth: AuthConfig): Record<string, string> {\n if (auth.token) {\n return { Authorization: `Bearer ${auth.token}` };\n }\n if (auth.apiKey) {\n return { 'X-API-KEY': auth.apiKey };\n }\n return {};\n}\n\n/**\n * Resolves the Athena API base URL from backendUrl.\n * Thread endpoints live under `/api/conversations/threads/*` and `/api/unstable/threads/`.\n * If backendUrl points at a nested Athena endpoint, trim it back to the API base first.\n */\nfunction getAthenaApiBaseUrl(backendUrl: string): string {\n const stripped = backendUrl.replace(/\\/api\\/assistant-ui\\/?$/, '');\n // If the suffix wasn't found, assume the URL already points to the API base\n if (stripped === backendUrl) {\n return backendUrl.replace(/\\/$/, '');\n }\n return stripped;\n}\n\nexport async function listThreads(\n backendUrl: string,\n auth: AuthConfig,\n opts: {\n limit?: number;\n offset?: number;\n exclude_triggered?: boolean;\n app_id?: string;\n start_channel?: string;\n search?: string;\n /** Ask the server for live GitHub state on `pull_requests` (off by default: it costs a lookup). */\n include_pull_requests?: boolean;\n signal?: AbortSignal;\n } = {},\n): Promise<ThreadListResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const endSpan = athenaDiagnostics.span('threads.list', 'threads.list.start', {\n auth_mode: getAuthMode(auth),\n app_id: opts.app_id,\n });\n try {\n const res = await fetch(`${base}/api/conversations/threads/list`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n signal: opts.signal ?? null,\n body: JSON.stringify({\n limit: opts.limit ?? 50,\n offset: opts.offset ?? 0,\n // Default to the full recent-conversations set unless a caller\n // explicitly asks to hide triggered/background sessions.\n exclude_triggered: opts.exclude_triggered ?? false,\n ...(opts.app_id ? { app_id: opts.app_id } : {}),\n ...(opts.start_channel ? { start_channel: opts.start_channel } : {}),\n ...(opts.search ? { search: opts.search } : {}),\n ...(opts.include_pull_requests ? { include_pull_requests: true } : {}),\n }),\n });\n if (!res.ok) {\n throw new AthenaSdkError({\n code: res.status === 401 || res.status === 403 ? 'auth_rejected' : 'threads_list_failed',\n message: `Failed to list threads: ${res.status}`,\n status: res.status,\n hint:\n res.status === 401 || res.status === 403\n ? 'The credential was refused. Check the token/API key and that the user belongs to the workspace.'\n : 'Check backendUrl points at the Agora API base.',\n context: { auth_mode: getAuthMode(auth) },\n });\n }\n const data = (await res.json()) as ThreadListResponse;\n endSpan('threads.list.done', { data: { count: data.threads?.length ?? 0 } });\n return data;\n } catch (error) {\n // Cancellation is lifecycle noise (unmount/remount aborts): log it as an\n // informational abort, not an error — passing `error` would force the\n // span end to error level and spam the console.\n if (isAbortError(error)) {\n endSpan('threads.list.aborted', { data: { reason: 'request aborted' } });\n } else {\n endSpan('threads.list.error', { error });\n }\n throw error;\n }\n}\n\n/** The `(workspace, user)` pair the credential resolves to on the backend. */\nexport interface AccountIdentity {\n userId: string;\n /** `null` for a user with no current workspace. */\n workspaceId: string | null;\n}\n\n/**\n * Resolve the credential's agora identity. The conversation index is one\n * statewire instance per `(workspace, user)`, and neither half is knowable\n * from the credential alone: the SDK's auth context carries an identity\n * provider user id, not agora's, and never a workspace.\n */\nexport async function fetchAccountIdentity(\n backendUrl: string,\n auth: AuthConfig,\n opts: { signal?: AbortSignal } = {},\n): Promise<AccountIdentity> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/v0/me`, {\n headers: getAuthHeaders(auth),\n signal: opts.signal ?? null,\n });\n if (!res.ok) {\n throw new AthenaSdkError({\n code: res.status === 401 || res.status === 403 ? 'auth_rejected' : 'threads_list_failed',\n message: `Failed to resolve account identity: ${res.status}`,\n status: res.status,\n hint:\n res.status === 401 || res.status === 403\n ? 'The credential was refused. Check the token/API key and that the user belongs to the workspace.'\n : 'Check backendUrl points at the Agora API base.',\n context: { auth_mode: getAuthMode(auth) },\n });\n }\n const body = (await res.json()) as { user_id?: unknown; workspace_id?: unknown };\n if (typeof body.user_id !== 'string' || body.user_id.length === 0) {\n throw new AthenaSdkError({\n code: 'threads_list_failed',\n message: 'Account identity lookup returned no user id.',\n context: { auth_mode: getAuthMode(auth) },\n });\n }\n return {\n userId: body.user_id,\n workspaceId: typeof body.workspace_id === 'string' ? body.workspace_id : null,\n };\n}\n\nexport interface ThreadReadStateResponse {\n thread_id: string;\n is_unread: boolean;\n}\n\n/** Store the calling user's read receipt for a thread. */\nexport async function setThreadReadState(\n backendUrl: string,\n auth: AuthConfig,\n opts: { threadId: string; isUnread: boolean },\n): Promise<ThreadReadStateResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/conversations/threads/read-state`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({\n thread_id: opts.threadId,\n is_unread: opts.isUnread,\n }),\n });\n if (!res.ok) {\n throw new AthenaSdkError({\n code: res.status === 401 || res.status === 403 ? 'auth_rejected' : 'unknown',\n message: `Failed to set thread read state: ${res.status}`,\n status: res.status,\n });\n }\n return res.json();\n}\n\nexport interface ThreadFavoriteResponse {\n thread_id: string;\n /** The server's state after the write. */\n is_favorited: boolean;\n}\n\n/**\n * Classify a 4xx from one of the per-thread write routes (`favorite`,\n * `rename`, `archive`). A 404 there is ambiguous: FastAPI answers\n * `{\"detail\":\"Not Found\"}` for a route an older agora does not have, the\n * archive route answers `\"Endpoint not available\"` on an agora without the\n * checkpointer — and each route answers 404 for a thread that no longer\n * exists (`\"Thread <id> not found\"`, `\"Conversation with langchain thread id\n * <id> not found\"`). The routes' own not-found answers name the thread, so a\n * body that does not is the ROUTE missing, and only that withdraws the\n * feature; a stale row must not disable the action for every other row.\n */\nfunction classifyThreadRouteRejection(\n status: number,\n body: string,\n threadId: string,\n): { routeMissing: boolean; detail: unknown } {\n let detail: unknown = body || undefined;\n try {\n const parsed: unknown = JSON.parse(body);\n if (parsed && typeof parsed === 'object') {\n const record = parsed as { detail?: unknown; message?: unknown };\n detail = record.detail ?? record.message ?? parsed;\n }\n } catch {\n // A non-JSON body (a proxy page) is kept as text.\n }\n const routeMissing = status === 405 || (status === 404 && !mentionsThread(body, threadId));\n return { routeMissing, detail };\n}\n\n/**\n * Whether an error body names the thread as a whole token (the routes embed\n * the id between spaces: `Thread <id> not found`); a substring match would\n * let a short id match a letter inside `\"detail\"`.\n */\nfunction mentionsThread(body: string, threadId: string): boolean {\n if (!threadId) return false;\n const escaped = threadId.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n return new RegExp(`(^|[^A-Za-z0-9_-])${escaped}($|[^A-Za-z0-9_-])`).test(body);\n}\n\n/** True when a per-thread write was rejected because the backend has no such route. */\nfunction isThreadRouteUnavailableError(error: unknown, code: AthenaSdkErrorCode): boolean {\n return isAthenaSdkError(error) && error.code === code && error.context.route_missing === true;\n}\n\n/**\n * Pin (favorite) or unpin a thread for the calling user. The response is the\n * server's truth after the write. Rejections: 401 / 403 when the caller\n * cannot view the thread (`auth_rejected`), otherwise `thread_favorite_failed`\n * with the HTTP status — 404 / 405 from an agora that predates the route,\n * which `isThreadFavoriteUnavailableError` recognizes so callers can fall\n * back to a read-only pin.\n */\nexport async function setThreadFavorite(\n backendUrl: string,\n auth: AuthConfig,\n opts: { threadId: string; isFavorited: boolean },\n): Promise<ThreadFavoriteResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/conversations/threads/favorite`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({\n thread_id: opts.threadId,\n is_favorited: opts.isFavorited,\n }),\n });\n if (!res.ok) {\n const refused = res.status === 401 || res.status === 403;\n const { routeMissing, detail } = classifyThreadRouteRejection(\n res.status,\n await getResponseErrorBody(res),\n opts.threadId,\n );\n throw new AthenaSdkError({\n code: refused ? 'auth_rejected' : 'thread_favorite_failed',\n message: `Failed to set thread favorite: ${res.status}`,\n status: res.status,\n detail,\n hint: refused\n ? 'The credential cannot view this thread.'\n : routeMissing\n ? 'The backend has no favorite route yet (older agora); the pin stays read-only until it deploys.'\n : res.status === 404\n ? 'The thread no longer exists on the server (archived or deleted elsewhere); refresh the list.'\n : 'Check backendUrl points at the Agora API base.',\n context: { thread_id: opts.threadId, auth_mode: getAuthMode(auth), route_missing: routeMissing },\n });\n }\n return res.json();\n}\n\n/**\n * True when `setThreadFavorite` failed because the backend has no favorite\n * route (a 405, or a 404 whose body does not name the thread — see\n * `classifyThreadRouteRejection`); a 404 for a thread that is gone is a\n * per-row failure and leaves the toggle available.\n */\nexport function isThreadFavoriteUnavailableError(error: unknown): boolean {\n return isThreadRouteUnavailableError(error, 'thread_favorite_failed');\n}\n\nexport interface ThreadRenameResponse {\n thread_id: string;\n /** The stored title after the write (trimmed by the server). */\n title: string;\n}\n\n/** The longest title the rename route accepts; the server answers 422 past it. */\nexport const THREAD_TITLE_MAX_LENGTH = 200;\n\n/**\n * Rename a thread for every viewer (`POST /api/conversations/threads/rename`).\n * The server trims the title and stores 1–200 characters; the response is the\n * row's title after the write. Rejections: 401 / 403 when the caller cannot\n * edit the thread (`auth_rejected` — a viewer can pin a shared session but not\n * retitle it), otherwise `thread_rename_failed` with the HTTP status — 404 /\n * 405 from an agora that predates the route, which\n * `isThreadRenameUnavailableError` recognizes so callers can drop the action.\n */\nexport async function renameThread(\n backendUrl: string,\n auth: AuthConfig,\n opts: { threadId: string; title: string },\n): Promise<ThreadRenameResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/conversations/threads/rename`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({\n thread_id: opts.threadId,\n title: opts.title,\n }),\n });\n if (!res.ok) {\n const refused = res.status === 401 || res.status === 403;\n const { routeMissing, detail } = classifyThreadRouteRejection(\n res.status,\n await getResponseErrorBody(res),\n opts.threadId,\n );\n throw new AthenaSdkError({\n code: refused ? 'auth_rejected' : 'thread_rename_failed',\n message: `Failed to rename thread: ${res.status}`,\n status: res.status,\n detail,\n hint: refused\n ? 'The credential cannot edit this thread (a viewer can pin a shared session, not retitle it).'\n : routeMissing\n ? 'The backend has no rename route yet (older agora); sessions cannot be renamed from here until it deploys.'\n : res.status === 404\n ? 'The thread no longer exists on the server (archived or deleted elsewhere); refresh the list.'\n : res.status === 422\n ? `The title must be 1–${THREAD_TITLE_MAX_LENGTH} characters after trimming.`\n : 'Check backendUrl points at the Agora API base.',\n context: { thread_id: opts.threadId, auth_mode: getAuthMode(auth), route_missing: routeMissing },\n });\n }\n return res.json();\n}\n\n/**\n * True when `renameThread` failed because the backend has no rename route\n * (a 405, or a 404 whose body does not name the thread); a 404 for a thread\n * that is gone is a per-row failure and leaves the action offered.\n */\nexport function isThreadRenameUnavailableError(error: unknown): boolean {\n return isThreadRouteUnavailableError(error, 'thread_rename_failed');\n}\n\n/**\n * True when `archiveThread` failed because the backend offers no archive\n * route: a 405, FastAPI's bare 404 on an agora without the route, or the\n * route's own `\"Endpoint not available\"` 404 on an agora without the\n * checkpointer. The route's `\"Thread <id> not found\"` 404 for a stale row is\n * a per-row failure and leaves the action offered on every other row.\n */\nexport function isThreadArchiveUnavailableError(error: unknown): boolean {\n return isThreadRouteUnavailableError(error, 'thread_archive_failed');\n}\n\nexport async function createThread(\n backendUrl: string,\n auth: AuthConfig,\n opts: { systemPrompt?: string; enabledTools?: string[]; appId?: string } = {},\n): Promise<ThreadCreateResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/unstable/threads/`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({\n config: {\n system_prompt: opts.systemPrompt ?? '',\n enabled_tools: opts.enabledTools,\n ...(opts.appId ? { app_id: opts.appId } : {}),\n },\n }),\n });\n if (!res.ok) {\n throw new Error(`[AthenaSDK] Failed to create thread: ${res.status}`);\n }\n return res.json();\n}\n\nexport interface ThreadStateResponse {\n thread_id: string;\n messages: unknown[];\n status?: string;\n [key: string]: unknown;\n}\n\ninterface ConversationThreadResponse {\n thread?: {\n thread_id?: string;\n state?: string | null;\n channel_values?: Record<string, unknown> | null;\n messages?: unknown[] | null;\n } | null;\n thread_found?: boolean;\n}\n\n/**\n * Deserialize a LangChain serialized message.\n * The API returns messages in `{lc, type: \"constructor\", id, kwargs}` format.\n * We need the plain format: `{type: \"human\", content: \"...\", id: \"...\"}`.\n */\nfunction deserializeMessage(msg: unknown): unknown {\n if (\n msg &&\n typeof msg === 'object' &&\n 'lc' in msg &&\n 'kwargs' in msg &&\n (msg as Record<string, unknown>).type === 'constructor'\n ) {\n const kwargs = (msg as { kwargs: Record<string, unknown> }).kwargs;\n\n // Recursively deserialize nested content without mutating the original\n const deserializedToolCalls = Array.isArray(kwargs.tool_calls)\n ? kwargs.tool_calls.map((tc: unknown) => {\n if (tc && typeof tc === 'object' && 'lc' in tc && 'kwargs' in tc) {\n return (tc as { kwargs: unknown }).kwargs;\n }\n return tc;\n })\n : kwargs.tool_calls;\n\n return { ...kwargs, tool_calls: deserializedToolCalls };\n }\n return msg;\n}\n\n/** Return a non-sensitive auth label for request diagnostics. */\nfunction getAuthMode(auth: AuthConfig): 'bearer' | 'api-key' | 'none' {\n if (auth.token) {\n return 'bearer';\n }\n if (auth.apiKey) {\n return 'api-key';\n }\n return 'none';\n}\n\n/** Safely read a response body for error messages without masking the root failure. */\nfunction getResponseErrorBody(response: Response): Promise<string> {\n return response.text().catch(() => '');\n}\n\n/**\n * Fetch persisted thread state from the conversations API.\n *\n * This endpoint is the same source used by Athena's conversation sidebar and\n * supports API-key auth plus BYO SSO session tokens sent as Bearer tokens.\n */\nexport async function getThreadState(\n backendUrl: string,\n auth: AuthConfig,\n threadId: string,\n opts: {\n /**\n * Bypass the server's Redis thread cache and read the checkpoint (default\n * `true`, the history load's contract). A warm-up read that only seeds a\n * first paint passes `false`: the cached copy is cheaper and the attach\n * snapshot replaces it anyway.\n */\n skipCache?: boolean;\n signal?: AbortSignal;\n } = {},\n): Promise<ThreadStateResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const authMode = getAuthMode(auth);\n const endpoint = `${base}/api/conversations/threads/get`;\n\n const endSpan = athenaDiagnostics.span('threads.history', 'threads.history.start', {\n thread_id: threadId,\n auth_mode: authMode,\n });\n\n let res: Response;\n try {\n res = await fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n signal: opts.signal ?? null,\n body: JSON.stringify({\n thread_id: threadId,\n skip_cache: opts.skipCache ?? true,\n }),\n });\n } catch (error) {\n if (isAbortError(error)) {\n endSpan('threads.history.aborted', {\n data: { thread_id: threadId, reason: 'request aborted' },\n });\n } else {\n endSpan('threads.history.error', { error, data: { thread_id: threadId } });\n }\n throw error;\n }\n if (!res.ok) {\n const body = await getResponseErrorBody(res);\n const error = new AthenaSdkError({\n code: res.status === 401 || res.status === 403 ? 'auth_rejected' : 'thread_load_failed',\n message: `Failed to get thread state: ${res.status} (${authMode})${\n body ? `: ${body.slice(0, 300)}` : ''\n }`,\n status: res.status,\n hint: 'A brand-new thread may not be persisted yet; anything else means the credential or backendUrl is wrong.',\n context: { thread_id: threadId, auth_mode: authMode },\n });\n endSpan('threads.history.error', { error, data: { thread_id: threadId } });\n throw error;\n }\n const data = (await res.json()) as ConversationThreadResponse;\n const channelMessages = data.thread?.channel_values?.messages;\n endSpan('threads.history.done', {\n data: {\n thread_id: threadId,\n found: data.thread_found !== false,\n message_count: Array.isArray(channelMessages)\n ? channelMessages.length\n : (data.thread?.messages?.length ?? 0),\n },\n });\n const thread = data.thread;\n\n if (data.thread_found === false || !thread) {\n return {\n thread_id: threadId,\n messages: [],\n };\n }\n\n const channelValues = thread.channel_values ?? {};\n const rawMessages = Array.isArray(channelValues.messages)\n ? channelValues.messages\n : Array.isArray(thread.messages)\n ? thread.messages\n : [];\n\n return {\n ...channelValues,\n thread_id: thread.thread_id ?? threadId,\n messages: rawMessages.map(deserializeMessage),\n ...(thread.state ? { status: thread.state } : {}),\n };\n}\n\n/**\n * Archive a thread (`POST /api/conversations/threads/archive`): it leaves\n * every list but keeps its state. Rejections: 401 / 403 when the caller\n * cannot access the thread (`auth_rejected`), otherwise\n * `thread_archive_failed` with the HTTP status — 404 / 405 when the backend\n * has no archive route, which `isThreadArchiveUnavailableError` recognizes.\n */\nexport async function archiveThread(\n backendUrl: string,\n auth: AuthConfig,\n threadId: string,\n): Promise<void> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/conversations/threads/archive`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({ thread_id: threadId }),\n });\n if (!res.ok) {\n const refused = res.status === 401 || res.status === 403;\n const { routeMissing, detail } = classifyThreadRouteRejection(\n res.status,\n await getResponseErrorBody(res),\n threadId,\n );\n throw new AthenaSdkError({\n code: refused ? 'auth_rejected' : 'thread_archive_failed',\n message: `Failed to archive thread: ${res.status}`,\n status: res.status,\n detail,\n hint: refused\n ? 'The credential cannot access this thread.'\n : routeMissing\n ? 'The backend has no archive route (no checkpointer, or an older agora).'\n : res.status === 404\n ? 'The thread no longer exists on the server (archived or deleted elsewhere); refresh the list.'\n : 'Check backendUrl points at the Agora API base.',\n context: { thread_id: threadId, auth_mode: getAuthMode(auth), route_missing: routeMissing },\n });\n }\n}\n"],"names":["athenaDiagnostics","AthenaSdkError","isAthenaSdkError"],"mappings":"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"}
|
package/dist/threads/api.js
CHANGED
|
@@ -103,7 +103,11 @@ async function setThreadReadState(backendUrl, auth, opts) {
|
|
|
103
103
|
})
|
|
104
104
|
});
|
|
105
105
|
if (!res.ok) {
|
|
106
|
-
throw new
|
|
106
|
+
throw new AthenaSdkError({
|
|
107
|
+
code: res.status === 401 || res.status === 403 ? "auth_rejected" : "unknown",
|
|
108
|
+
message: `Failed to set thread read state: ${res.status}`,
|
|
109
|
+
status: res.status
|
|
110
|
+
});
|
|
107
111
|
}
|
|
108
112
|
return res.json();
|
|
109
113
|
}
|
package/dist/threads/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sources":["../../src/threads/api.ts"],"sourcesContent":["/**\n * Athena thread API client.\n * Calls existing REST endpoints for thread CRUD.\n */\n\nimport type {\n ConversationAttention,\n ConversationAttentionSource,\n} from '@athenaintel/agent-runtime';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport { AthenaSdkError, type AthenaSdkErrorCode, isAthenaSdkError } from '../diagnostics/errors';\n\n/**\n * Live GitHub state of a pull request the thread opened, read by the server\n * when the list renders (agora `agent_pr_threads` is the only stored link).\n */\nexport interface ThreadPullRequest {\n /** `owner/repo` the pull request lives in. */\n repo_full_name: string;\n number: number;\n url: string;\n title: string;\n state: 'OPEN' | 'CLOSED' | 'MERGED';\n is_draft: boolean;\n /** GitHub's review decision (APPROVED, CHANGES_REQUESTED, REVIEW_REQUIRED) or null. */\n review_decision: string | null;\n /** Combined state of the checks on the head commit. */\n checks_state: 'SUCCESS' | 'FAILURE' | 'PENDING' | 'NONE';\n /** ISO 8601 timestamp of the pull request's last update on GitHub. */\n updated_at: string;\n}\n\n/** The last event that woke a resting thread (a GitHub webhook, a schedule, a handoff). */\nexport interface ThreadWake {\n source: string | null;\n reason: string | null;\n /** ISO 8601 timestamp of the wake. */\n at: string | null;\n}\n\nexport interface ThreadSummary {\n thread_id: string;\n title: string;\n created_at: string;\n updated_at: string;\n last_message: string | null;\n message_count: number;\n state: string | null;\n start_channel?: string | null;\n last_channel?: string | null;\n /** Client app that created the session (SESSION_APP_ID_GUIDE). */\n app_id?: string | null;\n /** Trigger marker for automated/background sessions. */\n trigger_type?: string | null;\n /** Whether the calling user has seen this thread since it last changed. */\n is_unread?: boolean;\n /**\n * Canonical status_v2 planes (`idle | active | needs_input | error` and\n * `scheduled | queued | running | needs_input | completed | failed |\n * canceled`); older servers omit them.\n */\n session_status?: string | null;\n run_status?: string | null;\n /**\n * The viewer's attention state, the one-line next action (≤ 80 chars) and\n * the signal that produced them, derived server-side; older servers omit them.\n */\n attention?: ConversationAttention | null;\n next_action?: string | null;\n attention_source?: ConversationAttentionSource | null;\n /** Whether the calling user favorited (pinned) the thread; older servers omit it. */\n is_favorited?: boolean;\n /** The last wake of a resting thread; older servers omit it. */\n last_wake?: ThreadWake | null;\n /**\n * Pull requests the thread opened with their live GitHub state. `[]` when\n * it opened none, `null` when the state could not be read right now;\n * older servers omit the field.\n */\n pull_requests?: ThreadPullRequest[] | null;\n}\n\n/**\n * True for fetch cancellations (unmount/remount aborts, StrictMode double\n * effects). These are lifecycle noise, not failures: emitting them as\n * `*.error` diagnostics floods the console with\n * \"signal is aborted without reason\" for requests the caller itself aborted.\n */\nfunction isAbortError(error: unknown): boolean {\n return (\n (error instanceof DOMException && error.name === 'AbortError') ||\n (error instanceof Error && error.name === 'AbortError')\n );\n}\n\nexport interface ThreadListResponse {\n threads: ThreadSummary[];\n /** Server lookahead result. Older servers may omit this field. */\n has_more?: boolean;\n}\n\nexport interface ThreadCreateResponse {\n thread_id: string;\n}\n\ninterface AuthConfig {\n apiKey?: string;\n token?: string | null;\n}\n\nfunction getAuthHeaders(auth: AuthConfig): Record<string, string> {\n if (auth.token) {\n return { Authorization: `Bearer ${auth.token}` };\n }\n if (auth.apiKey) {\n return { 'X-API-KEY': auth.apiKey };\n }\n return {};\n}\n\n/**\n * Resolves the Athena API base URL from backendUrl.\n * Thread endpoints live under `/api/conversations/threads/*` and `/api/unstable/threads/`.\n * If backendUrl points at a nested Athena endpoint, trim it back to the API base first.\n */\nfunction getAthenaApiBaseUrl(backendUrl: string): string {\n const stripped = backendUrl.replace(/\\/api\\/assistant-ui\\/?$/, '');\n // If the suffix wasn't found, assume the URL already points to the API base\n if (stripped === backendUrl) {\n return backendUrl.replace(/\\/$/, '');\n }\n return stripped;\n}\n\nexport async function listThreads(\n backendUrl: string,\n auth: AuthConfig,\n opts: {\n limit?: number;\n offset?: number;\n exclude_triggered?: boolean;\n app_id?: string;\n start_channel?: string;\n search?: string;\n /** Ask the server for live GitHub state on `pull_requests` (off by default: it costs a lookup). */\n include_pull_requests?: boolean;\n signal?: AbortSignal;\n } = {},\n): Promise<ThreadListResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const endSpan = athenaDiagnostics.span('threads.list', 'threads.list.start', {\n auth_mode: getAuthMode(auth),\n app_id: opts.app_id,\n });\n try {\n const res = await fetch(`${base}/api/conversations/threads/list`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n signal: opts.signal ?? null,\n body: JSON.stringify({\n limit: opts.limit ?? 50,\n offset: opts.offset ?? 0,\n // Default to the full recent-conversations set unless a caller\n // explicitly asks to hide triggered/background sessions.\n exclude_triggered: opts.exclude_triggered ?? false,\n ...(opts.app_id ? { app_id: opts.app_id } : {}),\n ...(opts.start_channel ? { start_channel: opts.start_channel } : {}),\n ...(opts.search ? { search: opts.search } : {}),\n ...(opts.include_pull_requests ? { include_pull_requests: true } : {}),\n }),\n });\n if (!res.ok) {\n throw new AthenaSdkError({\n code: res.status === 401 || res.status === 403 ? 'auth_rejected' : 'threads_list_failed',\n message: `Failed to list threads: ${res.status}`,\n status: res.status,\n hint:\n res.status === 401 || res.status === 403\n ? 'The credential was refused. Check the token/API key and that the user belongs to the workspace.'\n : 'Check backendUrl points at the Agora API base.',\n context: { auth_mode: getAuthMode(auth) },\n });\n }\n const data = (await res.json()) as ThreadListResponse;\n endSpan('threads.list.done', { data: { count: data.threads?.length ?? 0 } });\n return data;\n } catch (error) {\n // Cancellation is lifecycle noise (unmount/remount aborts): log it as an\n // informational abort, not an error — passing `error` would force the\n // span end to error level and spam the console.\n if (isAbortError(error)) {\n endSpan('threads.list.aborted', { data: { reason: 'request aborted' } });\n } else {\n endSpan('threads.list.error', { error });\n }\n throw error;\n }\n}\n\n/** The `(workspace, user)` pair the credential resolves to on the backend. */\nexport interface AccountIdentity {\n userId: string;\n /** `null` for a user with no current workspace. */\n workspaceId: string | null;\n}\n\n/**\n * Resolve the credential's agora identity. The conversation index is one\n * statewire instance per `(workspace, user)`, and neither half is knowable\n * from the credential alone: the SDK's auth context carries an identity\n * provider user id, not agora's, and never a workspace.\n */\nexport async function fetchAccountIdentity(\n backendUrl: string,\n auth: AuthConfig,\n opts: { signal?: AbortSignal } = {},\n): Promise<AccountIdentity> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/v0/me`, {\n headers: getAuthHeaders(auth),\n signal: opts.signal ?? null,\n });\n if (!res.ok) {\n throw new AthenaSdkError({\n code: res.status === 401 || res.status === 403 ? 'auth_rejected' : 'threads_list_failed',\n message: `Failed to resolve account identity: ${res.status}`,\n status: res.status,\n hint:\n res.status === 401 || res.status === 403\n ? 'The credential was refused. Check the token/API key and that the user belongs to the workspace.'\n : 'Check backendUrl points at the Agora API base.',\n context: { auth_mode: getAuthMode(auth) },\n });\n }\n const body = (await res.json()) as { user_id?: unknown; workspace_id?: unknown };\n if (typeof body.user_id !== 'string' || body.user_id.length === 0) {\n throw new AthenaSdkError({\n code: 'threads_list_failed',\n message: 'Account identity lookup returned no user id.',\n context: { auth_mode: getAuthMode(auth) },\n });\n }\n return {\n userId: body.user_id,\n workspaceId: typeof body.workspace_id === 'string' ? body.workspace_id : null,\n };\n}\n\nexport interface ThreadReadStateResponse {\n thread_id: string;\n is_unread: boolean;\n}\n\n/** Store the calling user's read receipt for a thread. */\nexport async function setThreadReadState(\n backendUrl: string,\n auth: AuthConfig,\n opts: { threadId: string; isUnread: boolean },\n): Promise<ThreadReadStateResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/conversations/threads/read-state`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({\n thread_id: opts.threadId,\n is_unread: opts.isUnread,\n }),\n });\n if (!res.ok) {\n throw new Error(`[AthenaSDK] Failed to set thread read state: ${res.status}`);\n }\n return res.json();\n}\n\nexport interface ThreadFavoriteResponse {\n thread_id: string;\n /** The server's state after the write. */\n is_favorited: boolean;\n}\n\n/**\n * Classify a 4xx from one of the per-thread write routes (`favorite`,\n * `rename`, `archive`). A 404 there is ambiguous: FastAPI answers\n * `{\"detail\":\"Not Found\"}` for a route an older agora does not have, the\n * archive route answers `\"Endpoint not available\"` on an agora without the\n * checkpointer — and each route answers 404 for a thread that no longer\n * exists (`\"Thread <id> not found\"`, `\"Conversation with langchain thread id\n * <id> not found\"`). The routes' own not-found answers name the thread, so a\n * body that does not is the ROUTE missing, and only that withdraws the\n * feature; a stale row must not disable the action for every other row.\n */\nfunction classifyThreadRouteRejection(\n status: number,\n body: string,\n threadId: string,\n): { routeMissing: boolean; detail: unknown } {\n let detail: unknown = body || undefined;\n try {\n const parsed: unknown = JSON.parse(body);\n if (parsed && typeof parsed === 'object') {\n const record = parsed as { detail?: unknown; message?: unknown };\n detail = record.detail ?? record.message ?? parsed;\n }\n } catch {\n // A non-JSON body (a proxy page) is kept as text.\n }\n const routeMissing = status === 405 || (status === 404 && !mentionsThread(body, threadId));\n return { routeMissing, detail };\n}\n\n/**\n * Whether an error body names the thread as a whole token (the routes embed\n * the id between spaces: `Thread <id> not found`); a substring match would\n * let a short id match a letter inside `\"detail\"`.\n */\nfunction mentionsThread(body: string, threadId: string): boolean {\n if (!threadId) return false;\n const escaped = threadId.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n return new RegExp(`(^|[^A-Za-z0-9_-])${escaped}($|[^A-Za-z0-9_-])`).test(body);\n}\n\n/** True when a per-thread write was rejected because the backend has no such route. */\nfunction isThreadRouteUnavailableError(error: unknown, code: AthenaSdkErrorCode): boolean {\n return isAthenaSdkError(error) && error.code === code && error.context.route_missing === true;\n}\n\n/**\n * Pin (favorite) or unpin a thread for the calling user. The response is the\n * server's truth after the write. Rejections: 401 / 403 when the caller\n * cannot view the thread (`auth_rejected`), otherwise `thread_favorite_failed`\n * with the HTTP status — 404 / 405 from an agora that predates the route,\n * which `isThreadFavoriteUnavailableError` recognizes so callers can fall\n * back to a read-only pin.\n */\nexport async function setThreadFavorite(\n backendUrl: string,\n auth: AuthConfig,\n opts: { threadId: string; isFavorited: boolean },\n): Promise<ThreadFavoriteResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/conversations/threads/favorite`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({\n thread_id: opts.threadId,\n is_favorited: opts.isFavorited,\n }),\n });\n if (!res.ok) {\n const refused = res.status === 401 || res.status === 403;\n const { routeMissing, detail } = classifyThreadRouteRejection(\n res.status,\n await getResponseErrorBody(res),\n opts.threadId,\n );\n throw new AthenaSdkError({\n code: refused ? 'auth_rejected' : 'thread_favorite_failed',\n message: `Failed to set thread favorite: ${res.status}`,\n status: res.status,\n detail,\n hint: refused\n ? 'The credential cannot view this thread.'\n : routeMissing\n ? 'The backend has no favorite route yet (older agora); the pin stays read-only until it deploys.'\n : res.status === 404\n ? 'The thread no longer exists on the server (archived or deleted elsewhere); refresh the list.'\n : 'Check backendUrl points at the Agora API base.',\n context: { thread_id: opts.threadId, auth_mode: getAuthMode(auth), route_missing: routeMissing },\n });\n }\n return res.json();\n}\n\n/**\n * True when `setThreadFavorite` failed because the backend has no favorite\n * route (a 405, or a 404 whose body does not name the thread — see\n * `classifyThreadRouteRejection`); a 404 for a thread that is gone is a\n * per-row failure and leaves the toggle available.\n */\nexport function isThreadFavoriteUnavailableError(error: unknown): boolean {\n return isThreadRouteUnavailableError(error, 'thread_favorite_failed');\n}\n\nexport interface ThreadRenameResponse {\n thread_id: string;\n /** The stored title after the write (trimmed by the server). */\n title: string;\n}\n\n/** The longest title the rename route accepts; the server answers 422 past it. */\nexport const THREAD_TITLE_MAX_LENGTH = 200;\n\n/**\n * Rename a thread for every viewer (`POST /api/conversations/threads/rename`).\n * The server trims the title and stores 1–200 characters; the response is the\n * row's title after the write. Rejections: 401 / 403 when the caller cannot\n * edit the thread (`auth_rejected` — a viewer can pin a shared session but not\n * retitle it), otherwise `thread_rename_failed` with the HTTP status — 404 /\n * 405 from an agora that predates the route, which\n * `isThreadRenameUnavailableError` recognizes so callers can drop the action.\n */\nexport async function renameThread(\n backendUrl: string,\n auth: AuthConfig,\n opts: { threadId: string; title: string },\n): Promise<ThreadRenameResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/conversations/threads/rename`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({\n thread_id: opts.threadId,\n title: opts.title,\n }),\n });\n if (!res.ok) {\n const refused = res.status === 401 || res.status === 403;\n const { routeMissing, detail } = classifyThreadRouteRejection(\n res.status,\n await getResponseErrorBody(res),\n opts.threadId,\n );\n throw new AthenaSdkError({\n code: refused ? 'auth_rejected' : 'thread_rename_failed',\n message: `Failed to rename thread: ${res.status}`,\n status: res.status,\n detail,\n hint: refused\n ? 'The credential cannot edit this thread (a viewer can pin a shared session, not retitle it).'\n : routeMissing\n ? 'The backend has no rename route yet (older agora); sessions cannot be renamed from here until it deploys.'\n : res.status === 404\n ? 'The thread no longer exists on the server (archived or deleted elsewhere); refresh the list.'\n : res.status === 422\n ? `The title must be 1–${THREAD_TITLE_MAX_LENGTH} characters after trimming.`\n : 'Check backendUrl points at the Agora API base.',\n context: { thread_id: opts.threadId, auth_mode: getAuthMode(auth), route_missing: routeMissing },\n });\n }\n return res.json();\n}\n\n/**\n * True when `renameThread` failed because the backend has no rename route\n * (a 405, or a 404 whose body does not name the thread); a 404 for a thread\n * that is gone is a per-row failure and leaves the action offered.\n */\nexport function isThreadRenameUnavailableError(error: unknown): boolean {\n return isThreadRouteUnavailableError(error, 'thread_rename_failed');\n}\n\n/**\n * True when `archiveThread` failed because the backend offers no archive\n * route: a 405, FastAPI's bare 404 on an agora without the route, or the\n * route's own `\"Endpoint not available\"` 404 on an agora without the\n * checkpointer. The route's `\"Thread <id> not found\"` 404 for a stale row is\n * a per-row failure and leaves the action offered on every other row.\n */\nexport function isThreadArchiveUnavailableError(error: unknown): boolean {\n return isThreadRouteUnavailableError(error, 'thread_archive_failed');\n}\n\nexport async function createThread(\n backendUrl: string,\n auth: AuthConfig,\n opts: { systemPrompt?: string; enabledTools?: string[]; appId?: string } = {},\n): Promise<ThreadCreateResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/unstable/threads/`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({\n config: {\n system_prompt: opts.systemPrompt ?? '',\n enabled_tools: opts.enabledTools,\n ...(opts.appId ? { app_id: opts.appId } : {}),\n },\n }),\n });\n if (!res.ok) {\n throw new Error(`[AthenaSDK] Failed to create thread: ${res.status}`);\n }\n return res.json();\n}\n\nexport interface ThreadStateResponse {\n thread_id: string;\n messages: unknown[];\n status?: string;\n [key: string]: unknown;\n}\n\ninterface ConversationThreadResponse {\n thread?: {\n thread_id?: string;\n state?: string | null;\n channel_values?: Record<string, unknown> | null;\n messages?: unknown[] | null;\n } | null;\n thread_found?: boolean;\n}\n\n/**\n * Deserialize a LangChain serialized message.\n * The API returns messages in `{lc, type: \"constructor\", id, kwargs}` format.\n * We need the plain format: `{type: \"human\", content: \"...\", id: \"...\"}`.\n */\nfunction deserializeMessage(msg: unknown): unknown {\n if (\n msg &&\n typeof msg === 'object' &&\n 'lc' in msg &&\n 'kwargs' in msg &&\n (msg as Record<string, unknown>).type === 'constructor'\n ) {\n const kwargs = (msg as { kwargs: Record<string, unknown> }).kwargs;\n\n // Recursively deserialize nested content without mutating the original\n const deserializedToolCalls = Array.isArray(kwargs.tool_calls)\n ? kwargs.tool_calls.map((tc: unknown) => {\n if (tc && typeof tc === 'object' && 'lc' in tc && 'kwargs' in tc) {\n return (tc as { kwargs: unknown }).kwargs;\n }\n return tc;\n })\n : kwargs.tool_calls;\n\n return { ...kwargs, tool_calls: deserializedToolCalls };\n }\n return msg;\n}\n\n/** Return a non-sensitive auth label for request diagnostics. */\nfunction getAuthMode(auth: AuthConfig): 'bearer' | 'api-key' | 'none' {\n if (auth.token) {\n return 'bearer';\n }\n if (auth.apiKey) {\n return 'api-key';\n }\n return 'none';\n}\n\n/** Safely read a response body for error messages without masking the root failure. */\nfunction getResponseErrorBody(response: Response): Promise<string> {\n return response.text().catch(() => '');\n}\n\n/**\n * Fetch persisted thread state from the conversations API.\n *\n * This endpoint is the same source used by Athena's conversation sidebar and\n * supports API-key auth plus BYO SSO session tokens sent as Bearer tokens.\n */\nexport async function getThreadState(\n backendUrl: string,\n auth: AuthConfig,\n threadId: string,\n opts: {\n /**\n * Bypass the server's Redis thread cache and read the checkpoint (default\n * `true`, the history load's contract). A warm-up read that only seeds a\n * first paint passes `false`: the cached copy is cheaper and the attach\n * snapshot replaces it anyway.\n */\n skipCache?: boolean;\n signal?: AbortSignal;\n } = {},\n): Promise<ThreadStateResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const authMode = getAuthMode(auth);\n const endpoint = `${base}/api/conversations/threads/get`;\n\n const endSpan = athenaDiagnostics.span('threads.history', 'threads.history.start', {\n thread_id: threadId,\n auth_mode: authMode,\n });\n\n let res: Response;\n try {\n res = await fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n signal: opts.signal ?? null,\n body: JSON.stringify({\n thread_id: threadId,\n skip_cache: opts.skipCache ?? true,\n }),\n });\n } catch (error) {\n if (isAbortError(error)) {\n endSpan('threads.history.aborted', {\n data: { thread_id: threadId, reason: 'request aborted' },\n });\n } else {\n endSpan('threads.history.error', { error, data: { thread_id: threadId } });\n }\n throw error;\n }\n if (!res.ok) {\n const body = await getResponseErrorBody(res);\n const error = new AthenaSdkError({\n code: res.status === 401 || res.status === 403 ? 'auth_rejected' : 'thread_load_failed',\n message: `Failed to get thread state: ${res.status} (${authMode})${\n body ? `: ${body.slice(0, 300)}` : ''\n }`,\n status: res.status,\n hint: 'A brand-new thread may not be persisted yet; anything else means the credential or backendUrl is wrong.',\n context: { thread_id: threadId, auth_mode: authMode },\n });\n endSpan('threads.history.error', { error, data: { thread_id: threadId } });\n throw error;\n }\n const data = (await res.json()) as ConversationThreadResponse;\n const channelMessages = data.thread?.channel_values?.messages;\n endSpan('threads.history.done', {\n data: {\n thread_id: threadId,\n found: data.thread_found !== false,\n message_count: Array.isArray(channelMessages)\n ? channelMessages.length\n : (data.thread?.messages?.length ?? 0),\n },\n });\n const thread = data.thread;\n\n if (data.thread_found === false || !thread) {\n return {\n thread_id: threadId,\n messages: [],\n };\n }\n\n const channelValues = thread.channel_values ?? {};\n const rawMessages = Array.isArray(channelValues.messages)\n ? channelValues.messages\n : Array.isArray(thread.messages)\n ? thread.messages\n : [];\n\n return {\n ...channelValues,\n thread_id: thread.thread_id ?? threadId,\n messages: rawMessages.map(deserializeMessage),\n ...(thread.state ? { status: thread.state } : {}),\n };\n}\n\n/**\n * Archive a thread (`POST /api/conversations/threads/archive`): it leaves\n * every list but keeps its state. Rejections: 401 / 403 when the caller\n * cannot access the thread (`auth_rejected`), otherwise\n * `thread_archive_failed` with the HTTP status — 404 / 405 when the backend\n * has no archive route, which `isThreadArchiveUnavailableError` recognizes.\n */\nexport async function archiveThread(\n backendUrl: string,\n auth: AuthConfig,\n threadId: string,\n): Promise<void> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/conversations/threads/archive`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({ thread_id: threadId }),\n });\n if (!res.ok) {\n const refused = res.status === 401 || res.status === 403;\n const { routeMissing, detail } = classifyThreadRouteRejection(\n res.status,\n await getResponseErrorBody(res),\n threadId,\n );\n throw new AthenaSdkError({\n code: refused ? 'auth_rejected' : 'thread_archive_failed',\n message: `Failed to archive thread: ${res.status}`,\n status: res.status,\n detail,\n hint: refused\n ? 'The credential cannot access this thread.'\n : routeMissing\n ? 'The backend has no archive route (no checkpointer, or an older agora).'\n : res.status === 404\n ? 'The thread no longer exists on the server (archived or deleted elsewhere); refresh the list.'\n : 'Check backendUrl points at the Agora API base.',\n context: { thread_id: threadId, auth_mode: getAuthMode(auth), route_missing: routeMissing },\n });\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"api.js","sources":["../../src/threads/api.ts"],"sourcesContent":["/**\n * Athena thread API client.\n * Calls existing REST endpoints for thread CRUD.\n */\n\nimport type {\n ConversationAttention,\n ConversationAttentionSource,\n} from '@athenaintel/agent-runtime';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport { AthenaSdkError, type AthenaSdkErrorCode, isAthenaSdkError } from '../diagnostics/errors';\n\n/**\n * Live GitHub state of a pull request the thread opened, read by the server\n * when the list renders (agora `agent_pr_threads` is the only stored link).\n */\nexport interface ThreadPullRequest {\n /** `owner/repo` the pull request lives in. */\n repo_full_name: string;\n number: number;\n url: string;\n title: string;\n state: 'OPEN' | 'CLOSED' | 'MERGED';\n is_draft: boolean;\n /** GitHub's review decision (APPROVED, CHANGES_REQUESTED, REVIEW_REQUIRED) or null. */\n review_decision: string | null;\n /** Combined state of the checks on the head commit. */\n checks_state: 'SUCCESS' | 'FAILURE' | 'PENDING' | 'NONE';\n /** ISO 8601 timestamp of the pull request's last update on GitHub. */\n updated_at: string;\n}\n\n/** The last event that woke a resting thread (a GitHub webhook, a schedule, a handoff). */\nexport interface ThreadWake {\n source: string | null;\n reason: string | null;\n /** ISO 8601 timestamp of the wake. */\n at: string | null;\n}\n\nexport interface ThreadSummary {\n thread_id: string;\n title: string;\n created_at: string;\n updated_at: string;\n last_message: string | null;\n message_count: number;\n state: string | null;\n start_channel?: string | null;\n last_channel?: string | null;\n /** Client app that created the session (SESSION_APP_ID_GUIDE). */\n app_id?: string | null;\n /** Trigger marker for automated/background sessions. */\n trigger_type?: string | null;\n /** Whether the calling user has seen this thread since it last changed. */\n is_unread?: boolean;\n /**\n * Canonical status_v2 planes (`idle | active | needs_input | error` and\n * `scheduled | queued | running | needs_input | completed | failed |\n * canceled`); older servers omit them.\n */\n session_status?: string | null;\n run_status?: string | null;\n /**\n * The viewer's attention state, the one-line next action (≤ 80 chars) and\n * the signal that produced them, derived server-side; older servers omit them.\n */\n attention?: ConversationAttention | null;\n next_action?: string | null;\n attention_source?: ConversationAttentionSource | null;\n /** Whether the calling user favorited (pinned) the thread; older servers omit it. */\n is_favorited?: boolean;\n /** The last wake of a resting thread; older servers omit it. */\n last_wake?: ThreadWake | null;\n /**\n * Pull requests the thread opened with their live GitHub state. `[]` when\n * it opened none, `null` when the state could not be read right now;\n * older servers omit the field.\n */\n pull_requests?: ThreadPullRequest[] | null;\n}\n\n/**\n * True for fetch cancellations (unmount/remount aborts, StrictMode double\n * effects). These are lifecycle noise, not failures: emitting them as\n * `*.error` diagnostics floods the console with\n * \"signal is aborted without reason\" for requests the caller itself aborted.\n */\nfunction isAbortError(error: unknown): boolean {\n return (\n (error instanceof DOMException && error.name === 'AbortError') ||\n (error instanceof Error && error.name === 'AbortError')\n );\n}\n\nexport interface ThreadListResponse {\n threads: ThreadSummary[];\n /** Server lookahead result. Older servers may omit this field. */\n has_more?: boolean;\n}\n\nexport interface ThreadCreateResponse {\n thread_id: string;\n}\n\ninterface AuthConfig {\n apiKey?: string;\n token?: string | null;\n}\n\nfunction getAuthHeaders(auth: AuthConfig): Record<string, string> {\n if (auth.token) {\n return { Authorization: `Bearer ${auth.token}` };\n }\n if (auth.apiKey) {\n return { 'X-API-KEY': auth.apiKey };\n }\n return {};\n}\n\n/**\n * Resolves the Athena API base URL from backendUrl.\n * Thread endpoints live under `/api/conversations/threads/*` and `/api/unstable/threads/`.\n * If backendUrl points at a nested Athena endpoint, trim it back to the API base first.\n */\nfunction getAthenaApiBaseUrl(backendUrl: string): string {\n const stripped = backendUrl.replace(/\\/api\\/assistant-ui\\/?$/, '');\n // If the suffix wasn't found, assume the URL already points to the API base\n if (stripped === backendUrl) {\n return backendUrl.replace(/\\/$/, '');\n }\n return stripped;\n}\n\nexport async function listThreads(\n backendUrl: string,\n auth: AuthConfig,\n opts: {\n limit?: number;\n offset?: number;\n exclude_triggered?: boolean;\n app_id?: string;\n start_channel?: string;\n search?: string;\n /** Ask the server for live GitHub state on `pull_requests` (off by default: it costs a lookup). */\n include_pull_requests?: boolean;\n signal?: AbortSignal;\n } = {},\n): Promise<ThreadListResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const endSpan = athenaDiagnostics.span('threads.list', 'threads.list.start', {\n auth_mode: getAuthMode(auth),\n app_id: opts.app_id,\n });\n try {\n const res = await fetch(`${base}/api/conversations/threads/list`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n signal: opts.signal ?? null,\n body: JSON.stringify({\n limit: opts.limit ?? 50,\n offset: opts.offset ?? 0,\n // Default to the full recent-conversations set unless a caller\n // explicitly asks to hide triggered/background sessions.\n exclude_triggered: opts.exclude_triggered ?? false,\n ...(opts.app_id ? { app_id: opts.app_id } : {}),\n ...(opts.start_channel ? { start_channel: opts.start_channel } : {}),\n ...(opts.search ? { search: opts.search } : {}),\n ...(opts.include_pull_requests ? { include_pull_requests: true } : {}),\n }),\n });\n if (!res.ok) {\n throw new AthenaSdkError({\n code: res.status === 401 || res.status === 403 ? 'auth_rejected' : 'threads_list_failed',\n message: `Failed to list threads: ${res.status}`,\n status: res.status,\n hint:\n res.status === 401 || res.status === 403\n ? 'The credential was refused. Check the token/API key and that the user belongs to the workspace.'\n : 'Check backendUrl points at the Agora API base.',\n context: { auth_mode: getAuthMode(auth) },\n });\n }\n const data = (await res.json()) as ThreadListResponse;\n endSpan('threads.list.done', { data: { count: data.threads?.length ?? 0 } });\n return data;\n } catch (error) {\n // Cancellation is lifecycle noise (unmount/remount aborts): log it as an\n // informational abort, not an error — passing `error` would force the\n // span end to error level and spam the console.\n if (isAbortError(error)) {\n endSpan('threads.list.aborted', { data: { reason: 'request aborted' } });\n } else {\n endSpan('threads.list.error', { error });\n }\n throw error;\n }\n}\n\n/** The `(workspace, user)` pair the credential resolves to on the backend. */\nexport interface AccountIdentity {\n userId: string;\n /** `null` for a user with no current workspace. */\n workspaceId: string | null;\n}\n\n/**\n * Resolve the credential's agora identity. The conversation index is one\n * statewire instance per `(workspace, user)`, and neither half is knowable\n * from the credential alone: the SDK's auth context carries an identity\n * provider user id, not agora's, and never a workspace.\n */\nexport async function fetchAccountIdentity(\n backendUrl: string,\n auth: AuthConfig,\n opts: { signal?: AbortSignal } = {},\n): Promise<AccountIdentity> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/v0/me`, {\n headers: getAuthHeaders(auth),\n signal: opts.signal ?? null,\n });\n if (!res.ok) {\n throw new AthenaSdkError({\n code: res.status === 401 || res.status === 403 ? 'auth_rejected' : 'threads_list_failed',\n message: `Failed to resolve account identity: ${res.status}`,\n status: res.status,\n hint:\n res.status === 401 || res.status === 403\n ? 'The credential was refused. Check the token/API key and that the user belongs to the workspace.'\n : 'Check backendUrl points at the Agora API base.',\n context: { auth_mode: getAuthMode(auth) },\n });\n }\n const body = (await res.json()) as { user_id?: unknown; workspace_id?: unknown };\n if (typeof body.user_id !== 'string' || body.user_id.length === 0) {\n throw new AthenaSdkError({\n code: 'threads_list_failed',\n message: 'Account identity lookup returned no user id.',\n context: { auth_mode: getAuthMode(auth) },\n });\n }\n return {\n userId: body.user_id,\n workspaceId: typeof body.workspace_id === 'string' ? body.workspace_id : null,\n };\n}\n\nexport interface ThreadReadStateResponse {\n thread_id: string;\n is_unread: boolean;\n}\n\n/** Store the calling user's read receipt for a thread. */\nexport async function setThreadReadState(\n backendUrl: string,\n auth: AuthConfig,\n opts: { threadId: string; isUnread: boolean },\n): Promise<ThreadReadStateResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/conversations/threads/read-state`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({\n thread_id: opts.threadId,\n is_unread: opts.isUnread,\n }),\n });\n if (!res.ok) {\n throw new AthenaSdkError({\n code: res.status === 401 || res.status === 403 ? 'auth_rejected' : 'unknown',\n message: `Failed to set thread read state: ${res.status}`,\n status: res.status,\n });\n }\n return res.json();\n}\n\nexport interface ThreadFavoriteResponse {\n thread_id: string;\n /** The server's state after the write. */\n is_favorited: boolean;\n}\n\n/**\n * Classify a 4xx from one of the per-thread write routes (`favorite`,\n * `rename`, `archive`). A 404 there is ambiguous: FastAPI answers\n * `{\"detail\":\"Not Found\"}` for a route an older agora does not have, the\n * archive route answers `\"Endpoint not available\"` on an agora without the\n * checkpointer — and each route answers 404 for a thread that no longer\n * exists (`\"Thread <id> not found\"`, `\"Conversation with langchain thread id\n * <id> not found\"`). The routes' own not-found answers name the thread, so a\n * body that does not is the ROUTE missing, and only that withdraws the\n * feature; a stale row must not disable the action for every other row.\n */\nfunction classifyThreadRouteRejection(\n status: number,\n body: string,\n threadId: string,\n): { routeMissing: boolean; detail: unknown } {\n let detail: unknown = body || undefined;\n try {\n const parsed: unknown = JSON.parse(body);\n if (parsed && typeof parsed === 'object') {\n const record = parsed as { detail?: unknown; message?: unknown };\n detail = record.detail ?? record.message ?? parsed;\n }\n } catch {\n // A non-JSON body (a proxy page) is kept as text.\n }\n const routeMissing = status === 405 || (status === 404 && !mentionsThread(body, threadId));\n return { routeMissing, detail };\n}\n\n/**\n * Whether an error body names the thread as a whole token (the routes embed\n * the id between spaces: `Thread <id> not found`); a substring match would\n * let a short id match a letter inside `\"detail\"`.\n */\nfunction mentionsThread(body: string, threadId: string): boolean {\n if (!threadId) return false;\n const escaped = threadId.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n return new RegExp(`(^|[^A-Za-z0-9_-])${escaped}($|[^A-Za-z0-9_-])`).test(body);\n}\n\n/** True when a per-thread write was rejected because the backend has no such route. */\nfunction isThreadRouteUnavailableError(error: unknown, code: AthenaSdkErrorCode): boolean {\n return isAthenaSdkError(error) && error.code === code && error.context.route_missing === true;\n}\n\n/**\n * Pin (favorite) or unpin a thread for the calling user. The response is the\n * server's truth after the write. Rejections: 401 / 403 when the caller\n * cannot view the thread (`auth_rejected`), otherwise `thread_favorite_failed`\n * with the HTTP status — 404 / 405 from an agora that predates the route,\n * which `isThreadFavoriteUnavailableError` recognizes so callers can fall\n * back to a read-only pin.\n */\nexport async function setThreadFavorite(\n backendUrl: string,\n auth: AuthConfig,\n opts: { threadId: string; isFavorited: boolean },\n): Promise<ThreadFavoriteResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/conversations/threads/favorite`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({\n thread_id: opts.threadId,\n is_favorited: opts.isFavorited,\n }),\n });\n if (!res.ok) {\n const refused = res.status === 401 || res.status === 403;\n const { routeMissing, detail } = classifyThreadRouteRejection(\n res.status,\n await getResponseErrorBody(res),\n opts.threadId,\n );\n throw new AthenaSdkError({\n code: refused ? 'auth_rejected' : 'thread_favorite_failed',\n message: `Failed to set thread favorite: ${res.status}`,\n status: res.status,\n detail,\n hint: refused\n ? 'The credential cannot view this thread.'\n : routeMissing\n ? 'The backend has no favorite route yet (older agora); the pin stays read-only until it deploys.'\n : res.status === 404\n ? 'The thread no longer exists on the server (archived or deleted elsewhere); refresh the list.'\n : 'Check backendUrl points at the Agora API base.',\n context: { thread_id: opts.threadId, auth_mode: getAuthMode(auth), route_missing: routeMissing },\n });\n }\n return res.json();\n}\n\n/**\n * True when `setThreadFavorite` failed because the backend has no favorite\n * route (a 405, or a 404 whose body does not name the thread — see\n * `classifyThreadRouteRejection`); a 404 for a thread that is gone is a\n * per-row failure and leaves the toggle available.\n */\nexport function isThreadFavoriteUnavailableError(error: unknown): boolean {\n return isThreadRouteUnavailableError(error, 'thread_favorite_failed');\n}\n\nexport interface ThreadRenameResponse {\n thread_id: string;\n /** The stored title after the write (trimmed by the server). */\n title: string;\n}\n\n/** The longest title the rename route accepts; the server answers 422 past it. */\nexport const THREAD_TITLE_MAX_LENGTH = 200;\n\n/**\n * Rename a thread for every viewer (`POST /api/conversations/threads/rename`).\n * The server trims the title and stores 1–200 characters; the response is the\n * row's title after the write. Rejections: 401 / 403 when the caller cannot\n * edit the thread (`auth_rejected` — a viewer can pin a shared session but not\n * retitle it), otherwise `thread_rename_failed` with the HTTP status — 404 /\n * 405 from an agora that predates the route, which\n * `isThreadRenameUnavailableError` recognizes so callers can drop the action.\n */\nexport async function renameThread(\n backendUrl: string,\n auth: AuthConfig,\n opts: { threadId: string; title: string },\n): Promise<ThreadRenameResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/conversations/threads/rename`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({\n thread_id: opts.threadId,\n title: opts.title,\n }),\n });\n if (!res.ok) {\n const refused = res.status === 401 || res.status === 403;\n const { routeMissing, detail } = classifyThreadRouteRejection(\n res.status,\n await getResponseErrorBody(res),\n opts.threadId,\n );\n throw new AthenaSdkError({\n code: refused ? 'auth_rejected' : 'thread_rename_failed',\n message: `Failed to rename thread: ${res.status}`,\n status: res.status,\n detail,\n hint: refused\n ? 'The credential cannot edit this thread (a viewer can pin a shared session, not retitle it).'\n : routeMissing\n ? 'The backend has no rename route yet (older agora); sessions cannot be renamed from here until it deploys.'\n : res.status === 404\n ? 'The thread no longer exists on the server (archived or deleted elsewhere); refresh the list.'\n : res.status === 422\n ? `The title must be 1–${THREAD_TITLE_MAX_LENGTH} characters after trimming.`\n : 'Check backendUrl points at the Agora API base.',\n context: { thread_id: opts.threadId, auth_mode: getAuthMode(auth), route_missing: routeMissing },\n });\n }\n return res.json();\n}\n\n/**\n * True when `renameThread` failed because the backend has no rename route\n * (a 405, or a 404 whose body does not name the thread); a 404 for a thread\n * that is gone is a per-row failure and leaves the action offered.\n */\nexport function isThreadRenameUnavailableError(error: unknown): boolean {\n return isThreadRouteUnavailableError(error, 'thread_rename_failed');\n}\n\n/**\n * True when `archiveThread` failed because the backend offers no archive\n * route: a 405, FastAPI's bare 404 on an agora without the route, or the\n * route's own `\"Endpoint not available\"` 404 on an agora without the\n * checkpointer. The route's `\"Thread <id> not found\"` 404 for a stale row is\n * a per-row failure and leaves the action offered on every other row.\n */\nexport function isThreadArchiveUnavailableError(error: unknown): boolean {\n return isThreadRouteUnavailableError(error, 'thread_archive_failed');\n}\n\nexport async function createThread(\n backendUrl: string,\n auth: AuthConfig,\n opts: { systemPrompt?: string; enabledTools?: string[]; appId?: string } = {},\n): Promise<ThreadCreateResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/unstable/threads/`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({\n config: {\n system_prompt: opts.systemPrompt ?? '',\n enabled_tools: opts.enabledTools,\n ...(opts.appId ? { app_id: opts.appId } : {}),\n },\n }),\n });\n if (!res.ok) {\n throw new Error(`[AthenaSDK] Failed to create thread: ${res.status}`);\n }\n return res.json();\n}\n\nexport interface ThreadStateResponse {\n thread_id: string;\n messages: unknown[];\n status?: string;\n [key: string]: unknown;\n}\n\ninterface ConversationThreadResponse {\n thread?: {\n thread_id?: string;\n state?: string | null;\n channel_values?: Record<string, unknown> | null;\n messages?: unknown[] | null;\n } | null;\n thread_found?: boolean;\n}\n\n/**\n * Deserialize a LangChain serialized message.\n * The API returns messages in `{lc, type: \"constructor\", id, kwargs}` format.\n * We need the plain format: `{type: \"human\", content: \"...\", id: \"...\"}`.\n */\nfunction deserializeMessage(msg: unknown): unknown {\n if (\n msg &&\n typeof msg === 'object' &&\n 'lc' in msg &&\n 'kwargs' in msg &&\n (msg as Record<string, unknown>).type === 'constructor'\n ) {\n const kwargs = (msg as { kwargs: Record<string, unknown> }).kwargs;\n\n // Recursively deserialize nested content without mutating the original\n const deserializedToolCalls = Array.isArray(kwargs.tool_calls)\n ? kwargs.tool_calls.map((tc: unknown) => {\n if (tc && typeof tc === 'object' && 'lc' in tc && 'kwargs' in tc) {\n return (tc as { kwargs: unknown }).kwargs;\n }\n return tc;\n })\n : kwargs.tool_calls;\n\n return { ...kwargs, tool_calls: deserializedToolCalls };\n }\n return msg;\n}\n\n/** Return a non-sensitive auth label for request diagnostics. */\nfunction getAuthMode(auth: AuthConfig): 'bearer' | 'api-key' | 'none' {\n if (auth.token) {\n return 'bearer';\n }\n if (auth.apiKey) {\n return 'api-key';\n }\n return 'none';\n}\n\n/** Safely read a response body for error messages without masking the root failure. */\nfunction getResponseErrorBody(response: Response): Promise<string> {\n return response.text().catch(() => '');\n}\n\n/**\n * Fetch persisted thread state from the conversations API.\n *\n * This endpoint is the same source used by Athena's conversation sidebar and\n * supports API-key auth plus BYO SSO session tokens sent as Bearer tokens.\n */\nexport async function getThreadState(\n backendUrl: string,\n auth: AuthConfig,\n threadId: string,\n opts: {\n /**\n * Bypass the server's Redis thread cache and read the checkpoint (default\n * `true`, the history load's contract). A warm-up read that only seeds a\n * first paint passes `false`: the cached copy is cheaper and the attach\n * snapshot replaces it anyway.\n */\n skipCache?: boolean;\n signal?: AbortSignal;\n } = {},\n): Promise<ThreadStateResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const authMode = getAuthMode(auth);\n const endpoint = `${base}/api/conversations/threads/get`;\n\n const endSpan = athenaDiagnostics.span('threads.history', 'threads.history.start', {\n thread_id: threadId,\n auth_mode: authMode,\n });\n\n let res: Response;\n try {\n res = await fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n signal: opts.signal ?? null,\n body: JSON.stringify({\n thread_id: threadId,\n skip_cache: opts.skipCache ?? true,\n }),\n });\n } catch (error) {\n if (isAbortError(error)) {\n endSpan('threads.history.aborted', {\n data: { thread_id: threadId, reason: 'request aborted' },\n });\n } else {\n endSpan('threads.history.error', { error, data: { thread_id: threadId } });\n }\n throw error;\n }\n if (!res.ok) {\n const body = await getResponseErrorBody(res);\n const error = new AthenaSdkError({\n code: res.status === 401 || res.status === 403 ? 'auth_rejected' : 'thread_load_failed',\n message: `Failed to get thread state: ${res.status} (${authMode})${\n body ? `: ${body.slice(0, 300)}` : ''\n }`,\n status: res.status,\n hint: 'A brand-new thread may not be persisted yet; anything else means the credential or backendUrl is wrong.',\n context: { thread_id: threadId, auth_mode: authMode },\n });\n endSpan('threads.history.error', { error, data: { thread_id: threadId } });\n throw error;\n }\n const data = (await res.json()) as ConversationThreadResponse;\n const channelMessages = data.thread?.channel_values?.messages;\n endSpan('threads.history.done', {\n data: {\n thread_id: threadId,\n found: data.thread_found !== false,\n message_count: Array.isArray(channelMessages)\n ? channelMessages.length\n : (data.thread?.messages?.length ?? 0),\n },\n });\n const thread = data.thread;\n\n if (data.thread_found === false || !thread) {\n return {\n thread_id: threadId,\n messages: [],\n };\n }\n\n const channelValues = thread.channel_values ?? {};\n const rawMessages = Array.isArray(channelValues.messages)\n ? channelValues.messages\n : Array.isArray(thread.messages)\n ? thread.messages\n : [];\n\n return {\n ...channelValues,\n thread_id: thread.thread_id ?? threadId,\n messages: rawMessages.map(deserializeMessage),\n ...(thread.state ? { status: thread.state } : {}),\n };\n}\n\n/**\n * Archive a thread (`POST /api/conversations/threads/archive`): it leaves\n * every list but keeps its state. Rejections: 401 / 403 when the caller\n * cannot access the thread (`auth_rejected`), otherwise\n * `thread_archive_failed` with the HTTP status — 404 / 405 when the backend\n * has no archive route, which `isThreadArchiveUnavailableError` recognizes.\n */\nexport async function archiveThread(\n backendUrl: string,\n auth: AuthConfig,\n threadId: string,\n): Promise<void> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/conversations/threads/archive`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({ thread_id: threadId }),\n });\n if (!res.ok) {\n const refused = res.status === 401 || res.status === 403;\n const { routeMissing, detail } = classifyThreadRouteRejection(\n res.status,\n await getResponseErrorBody(res),\n threadId,\n );\n throw new AthenaSdkError({\n code: refused ? 'auth_rejected' : 'thread_archive_failed',\n message: `Failed to archive thread: ${res.status}`,\n status: res.status,\n detail,\n hint: refused\n ? 'The credential cannot access this thread.'\n : routeMissing\n ? 'The backend has no archive route (no checkpointer, or an older agora).'\n : res.status === 404\n ? 'The thread no longer exists on the server (archived or deleted elsewhere); refresh the list.'\n : 'Check backendUrl points at the Agora API base.',\n context: { thread_id: threadId, auth_mode: getAuthMode(auth), route_missing: routeMissing },\n });\n }\n}\n"],"names":[],"mappings":";;AAwFA,SAAS,aAAa,OAAyB;AAC7C,SACG,iBAAiB,gBAAgB,MAAM,SAAS,gBAChD,iBAAiB,SAAS,MAAM,SAAS;AAE9C;AAiBA,SAAS,eAAe,MAA0C;AAChE,MAAI,KAAK,OAAO;AACd,WAAO,EAAE,eAAe,UAAU,KAAK,KAAK,GAAA;AAAA,EAC9C;AACA,MAAI,KAAK,QAAQ;AACf,WAAO,EAAE,aAAa,KAAK,OAAA;AAAA,EAC7B;AACA,SAAO,CAAA;AACT;AAOA,SAAS,oBAAoB,YAA4B;AACvD,QAAM,WAAW,WAAW,QAAQ,2BAA2B,EAAE;AAEjE,MAAI,aAAa,YAAY;AAC3B,WAAO,WAAW,QAAQ,OAAO,EAAE;AAAA,EACrC;AACA,SAAO;AACT;AAEA,eAAsB,YACpB,YACA,MACA,OAUI,CAAA,GACyB;;AAC7B,QAAM,OAAO,oBAAoB,UAAU;AAC3C,QAAM,UAAU,kBAAkB,KAAK,gBAAgB,sBAAsB;AAAA,IAC3E,WAAW,YAAY,IAAI;AAAA,IAC3B,QAAQ,KAAK;AAAA,EAAA,CACd;AACD,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,GAAG,IAAI,mCAAmC;AAAA,MAChE,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,oBAAoB,GAAG,eAAe,IAAI,EAAA;AAAA,MACrE,QAAQ,KAAK,UAAU;AAAA,MACvB,MAAM,KAAK,UAAU;AAAA,QACnB,OAAO,KAAK,SAAS;AAAA,QACrB,QAAQ,KAAK,UAAU;AAAA;AAAA;AAAA,QAGvB,mBAAmB,KAAK,qBAAqB;AAAA,QAC7C,GAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAA,IAAW,CAAA;AAAA,QAC5C,GAAI,KAAK,gBAAgB,EAAE,eAAe,KAAK,cAAA,IAAkB,CAAA;AAAA,QACjE,GAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAA,IAAW,CAAA;AAAA,QAC5C,GAAI,KAAK,wBAAwB,EAAE,uBAAuB,KAAA,IAAS,CAAA;AAAA,MAAC,CACrE;AAAA,IAAA,CACF;AACD,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,eAAe;AAAA,QACvB,MAAM,IAAI,WAAW,OAAO,IAAI,WAAW,MAAM,kBAAkB;AAAA,QACnE,SAAS,2BAA2B,IAAI,MAAM;AAAA,QAC9C,QAAQ,IAAI;AAAA,QACZ,MACE,IAAI,WAAW,OAAO,IAAI,WAAW,MACjC,oGACA;AAAA,QACN,SAAS,EAAE,WAAW,YAAY,IAAI,EAAA;AAAA,MAAE,CACzC;AAAA,IACH;AACA,UAAM,OAAQ,MAAM,IAAI,KAAA;AACxB,YAAQ,qBAAqB,EAAE,MAAM,EAAE,SAAO,UAAK,YAAL,mBAAc,WAAU,EAAA,GAAK;AAC3E,WAAO;AAAA,EACT,SAAS,OAAO;AAId,QAAI,aAAa,KAAK,GAAG;AACvB,cAAQ,wBAAwB,EAAE,MAAM,EAAE,QAAQ,kBAAA,GAAqB;AAAA,IACzE,OAAO;AACL,cAAQ,sBAAsB,EAAE,OAAO;AAAA,IACzC;AACA,UAAM;AAAA,EACR;AACF;AAeA,eAAsB,qBACpB,YACA,MACA,OAAiC,CAAA,GACP;AAC1B,QAAM,OAAO,oBAAoB,UAAU;AAC3C,QAAM,MAAM,MAAM,MAAM,GAAG,IAAI,cAAc;AAAA,IAC3C,SAAS,eAAe,IAAI;AAAA,IAC5B,QAAQ,KAAK,UAAU;AAAA,EAAA,CACxB;AACD,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,eAAe;AAAA,MACvB,MAAM,IAAI,WAAW,OAAO,IAAI,WAAW,MAAM,kBAAkB;AAAA,MACnE,SAAS,uCAAuC,IAAI,MAAM;AAAA,MAC1D,QAAQ,IAAI;AAAA,MACZ,MACE,IAAI,WAAW,OAAO,IAAI,WAAW,MACjC,oGACA;AAAA,MACN,SAAS,EAAE,WAAW,YAAY,IAAI,EAAA;AAAA,IAAE,CACzC;AAAA,EACH;AACA,QAAM,OAAQ,MAAM,IAAI,KAAA;AACxB,MAAI,OAAO,KAAK,YAAY,YAAY,KAAK,QAAQ,WAAW,GAAG;AACjE,UAAM,IAAI,eAAe;AAAA,MACvB,MAAM;AAAA,MACN,SAAS;AAAA,MACT,SAAS,EAAE,WAAW,YAAY,IAAI,EAAA;AAAA,IAAE,CACzC;AAAA,EACH;AACA,SAAO;AAAA,IACL,QAAQ,KAAK;AAAA,IACb,aAAa,OAAO,KAAK,iBAAiB,WAAW,KAAK,eAAe;AAAA,EAAA;AAE7E;AAQA,eAAsB,mBACpB,YACA,MACA,MACkC;AAClC,QAAM,OAAO,oBAAoB,UAAU;AAC3C,QAAM,MAAM,MAAM,MAAM,GAAG,IAAI,yCAAyC;AAAA,IACtE,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,oBAAoB,GAAG,eAAe,IAAI,EAAA;AAAA,IACrE,MAAM,KAAK,UAAU;AAAA,MACnB,WAAW,KAAK;AAAA,MAChB,WAAW,KAAK;AAAA,IAAA,CACjB;AAAA,EAAA,CACF;AACD,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,eAAe;AAAA,MACvB,MAAM,IAAI,WAAW,OAAO,IAAI,WAAW,MAAM,kBAAkB;AAAA,MACnE,SAAS,oCAAoC,IAAI,MAAM;AAAA,MACvD,QAAQ,IAAI;AAAA,IAAA,CACb;AAAA,EACH;AACA,SAAO,IAAI,KAAA;AACb;AAmBA,SAAS,6BACP,QACA,MACA,UAC4C;AAC5C,MAAI,SAAkB,QAAQ;AAC9B,MAAI;AACF,UAAM,SAAkB,KAAK,MAAM,IAAI;AACvC,QAAI,UAAU,OAAO,WAAW,UAAU;AACxC,YAAM,SAAS;AACf,eAAS,OAAO,UAAU,OAAO,WAAW;AAAA,IAC9C;AAAA,EACF,QAAQ;AAAA,EAER;AACA,QAAM,eAAe,WAAW,OAAQ,WAAW,OAAO,CAAC,eAAe,MAAM,QAAQ;AACxF,SAAO,EAAE,cAAc,OAAA;AACzB;AAOA,SAAS,eAAe,MAAc,UAA2B;AAC/D,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,UAAU,SAAS,QAAQ,uBAAuB,MAAM;AAC9D,SAAO,IAAI,OAAO,qBAAqB,OAAO,oBAAoB,EAAE,KAAK,IAAI;AAC/E;AAGA,SAAS,8BAA8B,OAAgB,MAAmC;AACxF,SAAO,iBAAiB,KAAK,KAAK,MAAM,SAAS,QAAQ,MAAM,QAAQ,kBAAkB;AAC3F;AAUA,eAAsB,kBACpB,YACA,MACA,MACiC;AACjC,QAAM,OAAO,oBAAoB,UAAU;AAC3C,QAAM,MAAM,MAAM,MAAM,GAAG,IAAI,uCAAuC;AAAA,IACpE,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,oBAAoB,GAAG,eAAe,IAAI,EAAA;AAAA,IACrE,MAAM,KAAK,UAAU;AAAA,MACnB,WAAW,KAAK;AAAA,MAChB,cAAc,KAAK;AAAA,IAAA,CACpB;AAAA,EAAA,CACF;AACD,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,UAAU,IAAI,WAAW,OAAO,IAAI,WAAW;AACrD,UAAM,EAAE,cAAc,OAAA,IAAW;AAAA,MAC/B,IAAI;AAAA,MACJ,MAAM,qBAAqB,GAAG;AAAA,MAC9B,KAAK;AAAA,IAAA;AAEP,UAAM,IAAI,eAAe;AAAA,MACvB,MAAM,UAAU,kBAAkB;AAAA,MAClC,SAAS,kCAAkC,IAAI,MAAM;AAAA,MACrD,QAAQ,IAAI;AAAA,MACZ;AAAA,MACA,MAAM,UACF,4CACA,eACE,mGACA,IAAI,WAAW,MACb,iGACA;AAAA,MACR,SAAS,EAAE,WAAW,KAAK,UAAU,WAAW,YAAY,IAAI,GAAG,eAAe,aAAA;AAAA,IAAa,CAChG;AAAA,EACH;AACA,SAAO,IAAI,KAAA;AACb;AAQO,SAAS,iCAAiC,OAAyB;AACxE,SAAO,8BAA8B,OAAO,wBAAwB;AACtE;AASO,MAAM,0BAA0B;AAWvC,eAAsB,aACpB,YACA,MACA,MAC+B;AAC/B,QAAM,OAAO,oBAAoB,UAAU;AAC3C,QAAM,MAAM,MAAM,MAAM,GAAG,IAAI,qCAAqC;AAAA,IAClE,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,oBAAoB,GAAG,eAAe,IAAI,EAAA;AAAA,IACrE,MAAM,KAAK,UAAU;AAAA,MACnB,WAAW,KAAK;AAAA,MAChB,OAAO,KAAK;AAAA,IAAA,CACb;AAAA,EAAA,CACF;AACD,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,UAAU,IAAI,WAAW,OAAO,IAAI,WAAW;AACrD,UAAM,EAAE,cAAc,OAAA,IAAW;AAAA,MAC/B,IAAI;AAAA,MACJ,MAAM,qBAAqB,GAAG;AAAA,MAC9B,KAAK;AAAA,IAAA;AAEP,UAAM,IAAI,eAAe;AAAA,MACvB,MAAM,UAAU,kBAAkB;AAAA,MAClC,SAAS,4BAA4B,IAAI,MAAM;AAAA,MAC/C,QAAQ,IAAI;AAAA,MACZ;AAAA,MACA,MAAM,UACF,gGACA,eACE,8GACA,IAAI,WAAW,MACb,iGACA,IAAI,WAAW,MACb,uBAAuB,uBAAuB,gCAC9C;AAAA,MACV,SAAS,EAAE,WAAW,KAAK,UAAU,WAAW,YAAY,IAAI,GAAG,eAAe,aAAA;AAAA,IAAa,CAChG;AAAA,EACH;AACA,SAAO,IAAI,KAAA;AACb;AAOO,SAAS,+BAA+B,OAAyB;AACtE,SAAO,8BAA8B,OAAO,sBAAsB;AACpE;AASO,SAAS,gCAAgC,OAAyB;AACvE,SAAO,8BAA8B,OAAO,uBAAuB;AACrE;AAEA,eAAsB,aACpB,YACA,MACA,OAA2E,CAAA,GAC5C;AAC/B,QAAM,OAAO,oBAAoB,UAAU;AAC3C,QAAM,MAAM,MAAM,MAAM,GAAG,IAAI,0BAA0B;AAAA,IACvD,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,oBAAoB,GAAG,eAAe,IAAI,EAAA;AAAA,IACrE,MAAM,KAAK,UAAU;AAAA,MACnB,QAAQ;AAAA,QACN,eAAe,KAAK,gBAAgB;AAAA,QACpC,eAAe,KAAK;AAAA,QACpB,GAAI,KAAK,QAAQ,EAAE,QAAQ,KAAK,MAAA,IAAU,CAAA;AAAA,MAAC;AAAA,IAC7C,CACD;AAAA,EAAA,CACF;AACD,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,MAAM,wCAAwC,IAAI,MAAM,EAAE;AAAA,EACtE;AACA,SAAO,IAAI,KAAA;AACb;AAwBA,SAAS,mBAAmB,KAAuB;AACjD,MACE,OACA,OAAO,QAAQ,YACf,QAAQ,OACR,YAAY,OACX,IAAgC,SAAS,eAC1C;AACA,UAAM,SAAU,IAA4C;AAG5D,UAAM,wBAAwB,MAAM,QAAQ,OAAO,UAAU,IACzD,OAAO,WAAW,IAAI,CAAC,OAAgB;AACrC,UAAI,MAAM,OAAO,OAAO,YAAY,QAAQ,MAAM,YAAY,IAAI;AAChE,eAAQ,GAA2B;AAAA,MACrC;AACA,aAAO;AAAA,IACT,CAAC,IACD,OAAO;AAEX,WAAO,EAAE,GAAG,QAAQ,YAAY,sBAAA;AAAA,EAClC;AACA,SAAO;AACT;AAGA,SAAS,YAAY,MAAiD;AACpE,MAAI,KAAK,OAAO;AACd,WAAO;AAAA,EACT;AACA,MAAI,KAAK,QAAQ;AACf,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGA,SAAS,qBAAqB,UAAqC;AACjE,SAAO,SAAS,KAAA,EAAO,MAAM,MAAM,EAAE;AACvC;AAQA,eAAsB,eACpB,YACA,MACA,UACA,OASI,CAAA,GAC0B;;AAC9B,QAAM,OAAO,oBAAoB,UAAU;AAC3C,QAAM,WAAW,YAAY,IAAI;AACjC,QAAM,WAAW,GAAG,IAAI;AAExB,QAAM,UAAU,kBAAkB,KAAK,mBAAmB,yBAAyB;AAAA,IACjF,WAAW;AAAA,IACX,WAAW;AAAA,EAAA,CACZ;AAED,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,MAAM,UAAU;AAAA,MAC1B,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,oBAAoB,GAAG,eAAe,IAAI,EAAA;AAAA,MACrE,QAAQ,KAAK,UAAU;AAAA,MACvB,MAAM,KAAK,UAAU;AAAA,QACnB,WAAW;AAAA,QACX,YAAY,KAAK,aAAa;AAAA,MAAA,CAC/B;AAAA,IAAA,CACF;AAAA,EACH,SAAS,OAAO;AACd,QAAI,aAAa,KAAK,GAAG;AACvB,cAAQ,2BAA2B;AAAA,QACjC,MAAM,EAAE,WAAW,UAAU,QAAQ,kBAAA;AAAA,MAAkB,CACxD;AAAA,IACH,OAAO;AACL,cAAQ,yBAAyB,EAAE,OAAO,MAAM,EAAE,WAAW,SAAA,GAAY;AAAA,IAC3E;AACA,UAAM;AAAA,EACR;AACA,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,OAAO,MAAM,qBAAqB,GAAG;AAC3C,UAAM,QAAQ,IAAI,eAAe;AAAA,MAC/B,MAAM,IAAI,WAAW,OAAO,IAAI,WAAW,MAAM,kBAAkB;AAAA,MACnE,SAAS,+BAA+B,IAAI,MAAM,KAAK,QAAQ,IAC7D,OAAO,KAAK,KAAK,MAAM,GAAG,GAAG,CAAC,KAAK,EACrC;AAAA,MACA,QAAQ,IAAI;AAAA,MACZ,MAAM;AAAA,MACN,SAAS,EAAE,WAAW,UAAU,WAAW,SAAA;AAAA,IAAS,CACrD;AACD,YAAQ,yBAAyB,EAAE,OAAO,MAAM,EAAE,WAAW,SAAA,GAAY;AACzE,UAAM;AAAA,EACR;AACA,QAAM,OAAQ,MAAM,IAAI,KAAA;AACxB,QAAM,mBAAkB,gBAAK,WAAL,mBAAa,mBAAb,mBAA6B;AACrD,UAAQ,wBAAwB;AAAA,IAC9B,MAAM;AAAA,MACJ,WAAW;AAAA,MACX,OAAO,KAAK,iBAAiB;AAAA,MAC7B,eAAe,MAAM,QAAQ,eAAe,IACxC,gBAAgB,WACf,gBAAK,WAAL,mBAAa,aAAb,mBAAuB,WAAU;AAAA,IAAA;AAAA,EACxC,CACD;AACD,QAAM,SAAS,KAAK;AAEpB,MAAI,KAAK,iBAAiB,SAAS,CAAC,QAAQ;AAC1C,WAAO;AAAA,MACL,WAAW;AAAA,MACX,UAAU,CAAA;AAAA,IAAC;AAAA,EAEf;AAEA,QAAM,gBAAgB,OAAO,kBAAkB,CAAA;AAC/C,QAAM,cAAc,MAAM,QAAQ,cAAc,QAAQ,IACpD,cAAc,WACd,MAAM,QAAQ,OAAO,QAAQ,IAC3B,OAAO,WACP,CAAA;AAEN,SAAO;AAAA,IACL,GAAG;AAAA,IACH,WAAW,OAAO,aAAa;AAAA,IAC/B,UAAU,YAAY,IAAI,kBAAkB;AAAA,IAC5C,GAAI,OAAO,QAAQ,EAAE,QAAQ,OAAO,MAAA,IAAU,CAAA;AAAA,EAAC;AAEnD;AASA,eAAsB,cACpB,YACA,MACA,UACe;AACf,QAAM,OAAO,oBAAoB,UAAU;AAC3C,QAAM,MAAM,MAAM,MAAM,GAAG,IAAI,sCAAsC;AAAA,IACnE,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,oBAAoB,GAAG,eAAe,IAAI,EAAA;AAAA,IACrE,MAAM,KAAK,UAAU,EAAE,WAAW,UAAU;AAAA,EAAA,CAC7C;AACD,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,UAAU,IAAI,WAAW,OAAO,IAAI,WAAW;AACrD,UAAM,EAAE,cAAc,OAAA,IAAW;AAAA,MAC/B,IAAI;AAAA,MACJ,MAAM,qBAAqB,GAAG;AAAA,MAC9B;AAAA,IAAA;AAEF,UAAM,IAAI,eAAe;AAAA,MACvB,MAAM,UAAU,kBAAkB;AAAA,MAClC,SAAS,6BAA6B,IAAI,MAAM;AAAA,MAChD,QAAQ,IAAI;AAAA,MACZ;AAAA,MACA,MAAM,UACF,8CACA,eACE,2EACA,IAAI,WAAW,MACb,iGACA;AAAA,MACR,SAAS,EAAE,WAAW,UAAU,WAAW,YAAY,IAAI,GAAG,eAAe,aAAA;AAAA,IAAa,CAC3F;AAAA,EACH;AACF;"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const credentialRenewal = require("../runtime/credential-renewal.cjs");
|
|
3
4
|
const api = require("./api.cjs");
|
|
4
5
|
function threadListRequestBody(params) {
|
|
5
6
|
var _a;
|
|
@@ -75,14 +76,13 @@ function conversationSummaryFromThreadSummary(thread, filter) {
|
|
|
75
76
|
function createRestConversationListFetcher(options) {
|
|
76
77
|
return async (params) => {
|
|
77
78
|
const body = threadListRequestBody(params);
|
|
78
|
-
const { threads, has_more: hasMore } = await
|
|
79
|
-
options.
|
|
80
|
-
|
|
81
|
-
{
|
|
79
|
+
const { threads, has_more: hasMore } = await credentialRenewal.withCredentialRenewal(
|
|
80
|
+
options.getAuth,
|
|
81
|
+
(auth) => api.listThreads(options.backendUrl, auth, {
|
|
82
82
|
...body,
|
|
83
83
|
...options.includePullRequests ? { include_pull_requests: true } : {},
|
|
84
84
|
signal: params.signal
|
|
85
|
-
}
|
|
85
|
+
})
|
|
86
86
|
);
|
|
87
87
|
return {
|
|
88
88
|
items: threads.map((thread) => conversationSummaryFromThreadSummary(thread, params.filter)),
|