@athenaintel/react 0.48.0 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/README.md +48 -10
  2. package/dist/agent-runtime/src/call-recap.cjs +319 -0
  3. package/dist/agent-runtime/src/call-recap.cjs.map +1 -0
  4. package/dist/agent-runtime/src/call-recap.d.ts +142 -0
  5. package/dist/agent-runtime/src/call-recap.js +319 -0
  6. package/dist/agent-runtime/src/call-recap.js.map +1 -0
  7. package/dist/agent-runtime/src/chat-outbox-store.cjs +1 -0
  8. package/dist/agent-runtime/src/chat-outbox-store.cjs.map +1 -1
  9. package/dist/agent-runtime/src/chat-outbox-store.d.ts +7 -0
  10. package/dist/agent-runtime/src/chat-outbox-store.js +1 -0
  11. package/dist/agent-runtime/src/chat-outbox-store.js.map +1 -1
  12. package/dist/agent-runtime/src/chat-outbox.cjs +46 -5
  13. package/dist/agent-runtime/src/chat-outbox.cjs.map +1 -1
  14. package/dist/agent-runtime/src/chat-outbox.d.ts +29 -0
  15. package/dist/agent-runtime/src/chat-outbox.js +46 -5
  16. package/dist/agent-runtime/src/chat-outbox.js.map +1 -1
  17. package/dist/agent-runtime/src/conversation-index.cjs.map +1 -1
  18. package/dist/agent-runtime/src/conversation-index.d.ts +7 -0
  19. package/dist/agent-runtime/src/conversation-index.js.map +1 -1
  20. package/dist/agent-runtime/src/conversation-list.cjs +2 -1
  21. package/dist/agent-runtime/src/conversation-list.cjs.map +1 -1
  22. package/dist/agent-runtime/src/conversation-list.d.ts +19 -3
  23. package/dist/agent-runtime/src/conversation-list.js +2 -1
  24. package/dist/agent-runtime/src/conversation-list.js.map +1 -1
  25. package/dist/agent-runtime/src/index.d.ts +6 -5
  26. package/dist/agent-runtime/src/interrupts.cjs +28 -1
  27. package/dist/agent-runtime/src/interrupts.cjs.map +1 -1
  28. package/dist/agent-runtime/src/interrupts.d.ts +21 -1
  29. package/dist/agent-runtime/src/interrupts.js +28 -1
  30. package/dist/agent-runtime/src/interrupts.js.map +1 -1
  31. package/dist/agent-runtime/src/model-context.cjs +3 -2
  32. package/dist/agent-runtime/src/model-context.cjs.map +1 -1
  33. package/dist/agent-runtime/src/model-context.d.ts +6 -1
  34. package/dist/agent-runtime/src/model-context.js +3 -2
  35. package/dist/agent-runtime/src/model-context.js.map +1 -1
  36. package/dist/agent-runtime/src/runtime.d.ts +3 -2
  37. package/dist/agent-runtime/src/send-recovery.cjs +37 -3
  38. package/dist/agent-runtime/src/send-recovery.cjs.map +1 -1
  39. package/dist/agent-runtime/src/send-recovery.d.ts +19 -0
  40. package/dist/agent-runtime/src/send-recovery.js +37 -3
  41. package/dist/agent-runtime/src/send-recovery.js.map +1 -1
  42. package/dist/agent-runtime/src/thread-handoff-flow-machine.d.ts +8 -8
  43. package/dist/agent-runtime/src/thread-handoff-protocol.d.ts +113 -1
  44. package/dist/agent-runtime/src/thread-handoff.d.ts +1 -1
  45. package/dist/agent-runtime/src/use-conversation-list.cjs +3 -2
  46. package/dist/agent-runtime/src/use-conversation-list.cjs.map +1 -1
  47. package/dist/agent-runtime/src/use-conversation-list.d.ts +6 -0
  48. package/dist/agent-runtime/src/use-conversation-list.js +3 -2
  49. package/dist/agent-runtime/src/use-conversation-list.js.map +1 -1
  50. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs +21 -1
  51. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs.map +1 -1
  52. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.d.ts +8 -2
  53. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js +21 -1
  54. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js.map +1 -1
  55. package/dist/agent-runtime/src/use-deep-agent-runtime.cjs +24 -6
  56. package/dist/agent-runtime/src/use-deep-agent-runtime.cjs.map +1 -1
  57. package/dist/agent-runtime/src/use-deep-agent-runtime.d.ts +17 -0
  58. package/dist/agent-runtime/src/use-deep-agent-runtime.js +24 -6
  59. package/dist/agent-runtime/src/use-deep-agent-runtime.js.map +1 -1
  60. package/dist/agent-runtime/src/use-deep-agent-voice.cjs +44 -9
  61. package/dist/agent-runtime/src/use-deep-agent-voice.cjs.map +1 -1
  62. package/dist/agent-runtime/src/use-deep-agent-voice.d.ts +34 -1
  63. package/dist/agent-runtime/src/use-deep-agent-voice.js +44 -9
  64. package/dist/agent-runtime/src/use-deep-agent-voice.js.map +1 -1
  65. package/dist/agent-runtime/src/use-thread-handoff-flow.d.ts +8 -2
  66. package/dist/agent-runtime/src/voice-earcon.cjs +139 -0
  67. package/dist/agent-runtime/src/voice-earcon.cjs.map +1 -0
  68. package/dist/agent-runtime/src/voice-earcon.d.ts +38 -0
  69. package/dist/agent-runtime/src/voice-earcon.js +139 -0
  70. package/dist/agent-runtime/src/voice-earcon.js.map +1 -0
  71. package/dist/agent-runtime/src/voice-media.cjs.map +1 -1
  72. package/dist/agent-runtime/src/voice-media.d.ts +14 -0
  73. package/dist/agent-runtime/src/voice-media.js.map +1 -1
  74. package/dist/agent-runtime/src/voice-presentation.cjs +2 -0
  75. package/dist/agent-runtime/src/voice-presentation.cjs.map +1 -1
  76. package/dist/agent-runtime/src/voice-presentation.js +2 -0
  77. package/dist/agent-runtime/src/voice-presentation.js.map +1 -1
  78. package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
  79. package/dist/assets/AthenaAssetEmbed.d.ts +11 -5
  80. package/dist/assets/AthenaAssetEmbed.js.map +1 -1
  81. package/dist/assets/use-asset-embed.cjs.map +1 -1
  82. package/dist/assets/use-asset-embed.d.ts +12 -5
  83. package/dist/assets/use-asset-embed.js.map +1 -1
  84. package/dist/chat/StatewireApprovalCard.cjs +41 -6
  85. package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
  86. package/dist/chat/StatewireApprovalCard.d.ts +18 -1
  87. package/dist/chat/StatewireApprovalCard.js +42 -7
  88. package/dist/chat/StatewireApprovalCard.js.map +1 -1
  89. package/dist/chat/StatewireQueuedMessages.cjs +56 -6
  90. package/dist/chat/StatewireQueuedMessages.cjs.map +1 -1
  91. package/dist/chat/StatewireQueuedMessages.d.ts +14 -0
  92. package/dist/chat/StatewireQueuedMessages.js +58 -8
  93. package/dist/chat/StatewireQueuedMessages.js.map +1 -1
  94. package/dist/chat/WaitingOnYouBanner.cjs +6 -2
  95. package/dist/chat/WaitingOnYouBanner.cjs.map +1 -1
  96. package/dist/chat/WaitingOnYouBanner.js +7 -3
  97. package/dist/chat/WaitingOnYouBanner.js.map +1 -1
  98. package/dist/chat/chat-voice-controls.cjs +3 -1
  99. package/dist/chat/chat-voice-controls.cjs.map +1 -1
  100. package/dist/chat/chat-voice-controls.d.ts +6 -1
  101. package/dist/chat/chat-voice-controls.js +3 -1
  102. package/dist/chat/chat-voice-controls.js.map +1 -1
  103. package/dist/chat-ui/index.cjs +3 -0
  104. package/dist/chat-ui/index.cjs.map +1 -1
  105. package/dist/chat-ui/index.d.ts +1 -0
  106. package/dist/chat-ui/index.js +3 -0
  107. package/dist/chat-ui/index.js.map +1 -1
  108. package/dist/chat-ui/messages/from-the-call-card.cjs +124 -0
  109. package/dist/chat-ui/messages/from-the-call-card.cjs.map +1 -0
  110. package/dist/chat-ui/messages/from-the-call-card.d.ts +17 -0
  111. package/dist/chat-ui/messages/from-the-call-card.js +124 -0
  112. package/dist/chat-ui/messages/from-the-call-card.js.map +1 -0
  113. package/dist/chat-ui/rich-text/chat-link.cjs +1 -0
  114. package/dist/chat-ui/rich-text/chat-link.cjs.map +1 -1
  115. package/dist/chat-ui/rich-text/chat-link.d.ts +4 -0
  116. package/dist/chat-ui/rich-text/chat-link.js +4 -3
  117. package/dist/chat-ui/rich-text/chat-link.js.map +1 -1
  118. package/dist/chat-ui/rich-text/chat-message-body.cjs +1 -0
  119. package/dist/chat-ui/rich-text/chat-message-body.cjs.map +1 -1
  120. package/dist/chat-ui/rich-text/chat-message-body.d.ts +12 -0
  121. package/dist/chat-ui/rich-text/chat-message-body.js +2 -1
  122. package/dist/chat-ui/rich-text/chat-message-body.js.map +1 -1
  123. package/dist/chat-ui/thread/chat-thread-shell.cjs +2 -0
  124. package/dist/chat-ui/thread/chat-thread-shell.cjs.map +1 -1
  125. package/dist/chat-ui/thread/chat-thread-shell.js +2 -0
  126. package/dist/chat-ui/thread/chat-thread-shell.js.map +1 -1
  127. package/dist/collab/client.cjs +15 -3
  128. package/dist/collab/client.cjs.map +1 -1
  129. package/dist/collab/client.d.ts +4 -2
  130. package/dist/collab/client.js +16 -4
  131. package/dist/collab/client.js.map +1 -1
  132. package/dist/composer/index.d.ts +1 -1
  133. package/dist/composer/useReadComposerDraft.cjs +26 -7
  134. package/dist/composer/useReadComposerDraft.cjs.map +1 -1
  135. package/dist/composer/useReadComposerDraft.d.ts +23 -1
  136. package/dist/composer/useReadComposerDraft.js +27 -8
  137. package/dist/composer/useReadComposerDraft.js.map +1 -1
  138. package/dist/index.cjs +3 -0
  139. package/dist/index.cjs.map +1 -1
  140. package/dist/index.d.ts +3 -1
  141. package/dist/index.js +4 -1
  142. package/dist/index.js.map +1 -1
  143. package/dist/keryx-http/src/unconfirmed-update-attributions.cjs +190 -0
  144. package/dist/keryx-http/src/unconfirmed-update-attributions.cjs.map +1 -0
  145. package/dist/keryx-http/src/unconfirmed-update-attributions.d.ts +132 -0
  146. package/dist/keryx-http/src/unconfirmed-update-attributions.js +190 -0
  147. package/dist/keryx-http/src/unconfirmed-update-attributions.js.map +1 -0
  148. package/dist/keryx-http/src/update-attributions.cjs +34 -4
  149. package/dist/keryx-http/src/update-attributions.cjs.map +1 -1
  150. package/dist/keryx-http/src/update-attributions.d.ts +36 -4
  151. package/dist/keryx-http/src/update-attributions.js +35 -5
  152. package/dist/keryx-http/src/update-attributions.js.map +1 -1
  153. package/dist/provider/AthenaProvider.cjs +4 -0
  154. package/dist/provider/AthenaProvider.cjs.map +1 -1
  155. package/dist/provider/AthenaProvider.d.ts +11 -1
  156. package/dist/provider/AthenaProvider.js +4 -0
  157. package/dist/provider/AthenaProvider.js.map +1 -1
  158. package/dist/react/package.json.cjs +1 -1
  159. package/dist/react/package.json.js +1 -1
  160. package/dist/threads/api.cjs.map +1 -1
  161. package/dist/threads/api.d.ts +15 -0
  162. package/dist/threads/api.js.map +1 -1
  163. package/dist/threads/conversation-list-fetcher.cjs +13 -5
  164. package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
  165. package/dist/threads/conversation-list-fetcher.d.ts +4 -1
  166. package/dist/threads/conversation-list-fetcher.js +13 -5
  167. package/dist/threads/conversation-list-fetcher.js.map +1 -1
  168. package/dist/threads/statewire-thread-list.cjs +28 -6
  169. package/dist/threads/statewire-thread-list.cjs.map +1 -1
  170. package/dist/threads/statewire-thread-list.d.ts +20 -8
  171. package/dist/threads/statewire-thread-list.js +28 -6
  172. package/dist/threads/statewire-thread-list.js.map +1 -1
  173. package/dist/threads/use-athena-sessions.cjs +27 -5
  174. package/dist/threads/use-athena-sessions.cjs.map +1 -1
  175. package/dist/threads/use-athena-sessions.d.ts +10 -5
  176. package/dist/threads/use-athena-sessions.js +28 -6
  177. package/dist/threads/use-athena-sessions.js.map +1 -1
  178. package/dist/threads/use-rest-conversation-history.d.ts +1 -0
  179. package/package.json +1 -1
@@ -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 /** Collab agent and channel the session started on; older servers omit them. */\n collab_agent_id?: string | null;\n collab_channel_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 /** Whether the calling user started the thread (`false`: shared with them); older servers omit it. */\n created_by_me?: boolean;\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 * Every request field the server applies beyond paging. The server ignores\n * fields it does not know, so a filter missing from this list came back\n * unapplied; servers that predate the list omit it (none of the\n * `ThreadListFilterBody` fields are applied there).\n */\n supported_filters?: string[];\n}\n\n/**\n * The server-side filters and order of `POST /api/conversations/threads/list`\n * beyond the original `app_id` / `start_channel` / `search` /\n * `exclude_triggered` set. Each is a predicate of the server's one page\n * statement, so a page is exact; a server that predates a field ignores it\n * (check `ThreadListResponse.supported_filters`).\n */\nexport interface ThreadListFilterBody {\n /** Original fields, accepted here too so one body can carry a whole query. */\n search?: string;\n exclude_triggered?: boolean;\n app_ids?: string[];\n start_channels?: string[];\n created_after?: string;\n created_before?: string;\n updated_after?: string;\n updated_before?: string;\n collab_agent_ids?: string[];\n /** `'default'` matches the agent's default channel (no channel stamp); needs `collab_agent_ids`. */\n collab_channel_ids?: string[];\n favorites_only?: boolean;\n unread_only?: boolean;\n run_statuses?: string[];\n session_statuses?: string[];\n has_pull_requests?: boolean;\n created_by_me?: boolean;\n exclude_dismissed?: boolean;\n sort?: 'updated' | 'created';\n direction?: 'desc' | 'asc';\n}\n\nexport interface ThreadCreateResponse {\n thread_id: string;\n}\n\ninterface AuthConfig {\n apiKey?: string;\n token?: string | null;\n credentials?: RequestCredentials;\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 /** Server-side filters and order beyond the original set (see `ThreadListFilterBody`). */\n filters?: ThreadListFilterBody;\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 ...opts.filters,\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 credentials: auth.credentials,\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 ThreadInboxDismissResponse {\n success: boolean;\n dismissed: boolean;\n}\n\n/**\n * Dismiss a session from the inbox, or bring it back\n * (`POST /api/conversations/threads/notifications/dismiss`). A dismissed\n * session returns on its own with its next message. The state lives on the\n * session for its creator's inbox, so the route takes EDIT on the session\n * (403 for a VIEW share).\n */\nexport async function dismissThreadFromInbox(\n backendUrl: string,\n auth: AuthConfig,\n opts: { threadId: string; dismissed: boolean },\n): Promise<ThreadInboxDismissResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/conversations/threads/notifications/dismiss`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({ thread_id: opts.threadId, dismissed: opts.dismissed }),\n });\n if (!res.ok) {\n throw new AthenaSdkError({\n code: res.status === 401 || res.status === 403 ? 'auth_rejected' : 'unknown',\n message: `Failed to ${opts.dismissed ? 'dismiss' : 'restore'} the session: ${res.status}`,\n status: res.status,\n hint:\n res.status === 403\n ? 'Only someone who can edit the session (its creator, usually) can dismiss it from the inbox.'\n : undefined,\n context: { thread_id: opts.threadId, auth_mode: getAuthMode(auth) },\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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"}
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 /** Collab agent and channel the session started on; older servers omit them. */\n collab_agent_id?: string | null;\n collab_channel_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 /** Whether the calling user started the thread (`false`: shared with them); older servers omit it. */\n created_by_me?: boolean;\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 * Every request field the server applies beyond paging. The server ignores\n * fields it does not know, so a filter missing from this list came back\n * unapplied; servers that predate the list omit it (none of the\n * `ThreadListFilterBody` fields are applied there).\n */\n supported_filters?: string[];\n /**\n * Whether the page also holds the conversations with the `home_agent_id`\n * agent: true only when the request named one, the agent has a home app set\n * and the caller can open the agent. Servers that predate the field omit it,\n * which reads as false.\n */\n home_agent_applied?: boolean;\n}\n\n/**\n * The server-side filters and order of `POST /api/conversations/threads/list`\n * beyond the original `app_id` / `start_channel` / `search` /\n * `exclude_triggered` set. Each is a predicate of the server's one page\n * statement, so a page is exact; a server that predates a field ignores it\n * (check `ThreadListResponse.supported_filters`).\n */\nexport interface ThreadListFilterBody {\n /** Original fields, accepted here too so one body can carry a whole query. */\n search?: string;\n exclude_triggered?: boolean;\n app_ids?: string[];\n /**\n * A collab agent (`asset_<uuid>`) whose home this list's app is. When the\n * agent has a home app set and the caller can open the agent, the app scope\n * (`app_id` / `app_ids`) widens to every conversation with the agent too,\n * ANDed with the other filters as usual; `home_agent_applied` on the answer\n * says whether it did.\n */\n home_agent_id?: string;\n start_channels?: string[];\n created_after?: string;\n created_before?: string;\n updated_after?: string;\n updated_before?: string;\n collab_agent_ids?: string[];\n /** `'default'` matches the agent's default channel (no channel stamp); needs `collab_agent_ids`. */\n collab_channel_ids?: string[];\n favorites_only?: boolean;\n unread_only?: boolean;\n run_statuses?: string[];\n session_statuses?: string[];\n has_pull_requests?: boolean;\n created_by_me?: boolean;\n exclude_dismissed?: boolean;\n sort?: 'updated' | 'created';\n direction?: 'desc' | 'asc';\n}\n\nexport interface ThreadCreateResponse {\n thread_id: string;\n}\n\ninterface AuthConfig {\n apiKey?: string;\n token?: string | null;\n credentials?: RequestCredentials;\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 /** Server-side filters and order beyond the original set (see `ThreadListFilterBody`). */\n filters?: ThreadListFilterBody;\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 ...opts.filters,\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 credentials: auth.credentials,\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 ThreadInboxDismissResponse {\n success: boolean;\n dismissed: boolean;\n}\n\n/**\n * Dismiss a session from the inbox, or bring it back\n * (`POST /api/conversations/threads/notifications/dismiss`). A dismissed\n * session returns on its own with its next message. The state lives on the\n * session for its creator's inbox, so the route takes EDIT on the session\n * (403 for a VIEW share).\n */\nexport async function dismissThreadFromInbox(\n backendUrl: string,\n auth: AuthConfig,\n opts: { threadId: string; dismissed: boolean },\n): Promise<ThreadInboxDismissResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/conversations/threads/notifications/dismiss`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({ thread_id: opts.threadId, dismissed: opts.dismissed }),\n });\n if (!res.ok) {\n throw new AthenaSdkError({\n code: res.status === 401 || res.status === 403 ? 'auth_rejected' : 'unknown',\n message: `Failed to ${opts.dismissed ? 'dismiss' : 'restore'} the session: ${res.status}`,\n status: res.status,\n hint:\n res.status === 403\n ? 'Only someone who can edit the session (its creator, usually) can dismiss it from the inbox.'\n : undefined,\n context: { thread_id: opts.threadId, auth_mode: getAuthMode(auth) },\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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"}
@@ -86,6 +86,13 @@ export interface ThreadListResponse {
86
86
  * `ThreadListFilterBody` fields are applied there).
87
87
  */
88
88
  supported_filters?: string[];
89
+ /**
90
+ * Whether the page also holds the conversations with the `home_agent_id`
91
+ * agent: true only when the request named one, the agent has a home app set
92
+ * and the caller can open the agent. Servers that predate the field omit it,
93
+ * which reads as false.
94
+ */
95
+ home_agent_applied?: boolean;
89
96
  }
90
97
  /**
91
98
  * The server-side filters and order of `POST /api/conversations/threads/list`
@@ -99,6 +106,14 @@ export interface ThreadListFilterBody {
99
106
  search?: string;
100
107
  exclude_triggered?: boolean;
101
108
  app_ids?: string[];
109
+ /**
110
+ * A collab agent (`asset_<uuid>`) whose home this list's app is. When the
111
+ * agent has a home app set and the caller can open the agent, the app scope
112
+ * (`app_id` / `app_ids`) widens to every conversation with the agent too,
113
+ * ANDed with the other filters as usual; `home_agent_applied` on the answer
114
+ * says whether it did.
115
+ */
116
+ home_agent_id?: string;
102
117
  start_channels?: string[];
103
118
  created_after?: string;
104
119
  created_before?: string;
@@ -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 /** Collab agent and channel the session started on; older servers omit them. */\n collab_agent_id?: string | null;\n collab_channel_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 /** Whether the calling user started the thread (`false`: shared with them); older servers omit it. */\n created_by_me?: boolean;\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 * Every request field the server applies beyond paging. The server ignores\n * fields it does not know, so a filter missing from this list came back\n * unapplied; servers that predate the list omit it (none of the\n * `ThreadListFilterBody` fields are applied there).\n */\n supported_filters?: string[];\n}\n\n/**\n * The server-side filters and order of `POST /api/conversations/threads/list`\n * beyond the original `app_id` / `start_channel` / `search` /\n * `exclude_triggered` set. Each is a predicate of the server's one page\n * statement, so a page is exact; a server that predates a field ignores it\n * (check `ThreadListResponse.supported_filters`).\n */\nexport interface ThreadListFilterBody {\n /** Original fields, accepted here too so one body can carry a whole query. */\n search?: string;\n exclude_triggered?: boolean;\n app_ids?: string[];\n start_channels?: string[];\n created_after?: string;\n created_before?: string;\n updated_after?: string;\n updated_before?: string;\n collab_agent_ids?: string[];\n /** `'default'` matches the agent's default channel (no channel stamp); needs `collab_agent_ids`. */\n collab_channel_ids?: string[];\n favorites_only?: boolean;\n unread_only?: boolean;\n run_statuses?: string[];\n session_statuses?: string[];\n has_pull_requests?: boolean;\n created_by_me?: boolean;\n exclude_dismissed?: boolean;\n sort?: 'updated' | 'created';\n direction?: 'desc' | 'asc';\n}\n\nexport interface ThreadCreateResponse {\n thread_id: string;\n}\n\ninterface AuthConfig {\n apiKey?: string;\n token?: string | null;\n credentials?: RequestCredentials;\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 /** Server-side filters and order beyond the original set (see `ThreadListFilterBody`). */\n filters?: ThreadListFilterBody;\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 ...opts.filters,\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 credentials: auth.credentials,\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 ThreadInboxDismissResponse {\n success: boolean;\n dismissed: boolean;\n}\n\n/**\n * Dismiss a session from the inbox, or bring it back\n * (`POST /api/conversations/threads/notifications/dismiss`). A dismissed\n * session returns on its own with its next message. The state lives on the\n * session for its creator's inbox, so the route takes EDIT on the session\n * (403 for a VIEW share).\n */\nexport async function dismissThreadFromInbox(\n backendUrl: string,\n auth: AuthConfig,\n opts: { threadId: string; dismissed: boolean },\n): Promise<ThreadInboxDismissResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/conversations/threads/notifications/dismiss`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({ thread_id: opts.threadId, dismissed: opts.dismissed }),\n });\n if (!res.ok) {\n throw new AthenaSdkError({\n code: res.status === 401 || res.status === 403 ? 'auth_rejected' : 'unknown',\n message: `Failed to ${opts.dismissed ? 'dismiss' : 'restore'} the session: ${res.status}`,\n status: res.status,\n hint:\n res.status === 403\n ? 'Only someone who can edit the session (its creator, usually) can dismiss it from the inbox.'\n : undefined,\n context: { thread_id: opts.threadId, auth_mode: getAuthMode(auth) },\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":"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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 /** Collab agent and channel the session started on; older servers omit them. */\n collab_agent_id?: string | null;\n collab_channel_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 /** Whether the calling user started the thread (`false`: shared with them); older servers omit it. */\n created_by_me?: boolean;\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 * Every request field the server applies beyond paging. The server ignores\n * fields it does not know, so a filter missing from this list came back\n * unapplied; servers that predate the list omit it (none of the\n * `ThreadListFilterBody` fields are applied there).\n */\n supported_filters?: string[];\n /**\n * Whether the page also holds the conversations with the `home_agent_id`\n * agent: true only when the request named one, the agent has a home app set\n * and the caller can open the agent. Servers that predate the field omit it,\n * which reads as false.\n */\n home_agent_applied?: boolean;\n}\n\n/**\n * The server-side filters and order of `POST /api/conversations/threads/list`\n * beyond the original `app_id` / `start_channel` / `search` /\n * `exclude_triggered` set. Each is a predicate of the server's one page\n * statement, so a page is exact; a server that predates a field ignores it\n * (check `ThreadListResponse.supported_filters`).\n */\nexport interface ThreadListFilterBody {\n /** Original fields, accepted here too so one body can carry a whole query. */\n search?: string;\n exclude_triggered?: boolean;\n app_ids?: string[];\n /**\n * A collab agent (`asset_<uuid>`) whose home this list's app is. When the\n * agent has a home app set and the caller can open the agent, the app scope\n * (`app_id` / `app_ids`) widens to every conversation with the agent too,\n * ANDed with the other filters as usual; `home_agent_applied` on the answer\n * says whether it did.\n */\n home_agent_id?: string;\n start_channels?: string[];\n created_after?: string;\n created_before?: string;\n updated_after?: string;\n updated_before?: string;\n collab_agent_ids?: string[];\n /** `'default'` matches the agent's default channel (no channel stamp); needs `collab_agent_ids`. */\n collab_channel_ids?: string[];\n favorites_only?: boolean;\n unread_only?: boolean;\n run_statuses?: string[];\n session_statuses?: string[];\n has_pull_requests?: boolean;\n created_by_me?: boolean;\n exclude_dismissed?: boolean;\n sort?: 'updated' | 'created';\n direction?: 'desc' | 'asc';\n}\n\nexport interface ThreadCreateResponse {\n thread_id: string;\n}\n\ninterface AuthConfig {\n apiKey?: string;\n token?: string | null;\n credentials?: RequestCredentials;\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 /** Server-side filters and order beyond the original set (see `ThreadListFilterBody`). */\n filters?: ThreadListFilterBody;\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 ...opts.filters,\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 credentials: auth.credentials,\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 ThreadInboxDismissResponse {\n success: boolean;\n dismissed: boolean;\n}\n\n/**\n * Dismiss a session from the inbox, or bring it back\n * (`POST /api/conversations/threads/notifications/dismiss`). A dismissed\n * session returns on its own with its next message. The state lives on the\n * session for its creator's inbox, so the route takes EDIT on the session\n * (403 for a VIEW share).\n */\nexport async function dismissThreadFromInbox(\n backendUrl: string,\n auth: AuthConfig,\n opts: { threadId: string; dismissed: boolean },\n): Promise<ThreadInboxDismissResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/conversations/threads/notifications/dismiss`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({ thread_id: opts.threadId, dismissed: opts.dismissed }),\n });\n if (!res.ok) {\n throw new AthenaSdkError({\n code: res.status === 401 || res.status === 403 ? 'auth_rejected' : 'unknown',\n message: `Failed to ${opts.dismissed ? 'dismiss' : 'restore'} the session: ${res.status}`,\n status: res.status,\n hint:\n res.status === 403\n ? 'Only someone who can edit the session (its creator, usually) can dismiss it from the inbox.'\n : undefined,\n context: { thread_id: opts.threadId, auth_mode: getAuthMode(auth) },\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":"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"}
@@ -26,6 +26,7 @@ function threadListRequestBody(params) {
26
26
  ...filter.excludeTriggered !== void 0 ? { exclude_triggered: filter.excludeTriggered } : {},
27
27
  ...filter.appId ? { app_id: filter.appId } : {},
28
28
  ...filter.appIds && filter.appIds.length > 0 ? { app_ids: [...filter.appIds] } : {},
29
+ ...filter.homeAgentId ? { home_agent_id: filter.homeAgentId } : {},
29
30
  ...((_a = filter.sourceChannels) == null ? void 0 : _a[0]) ? { start_channel: filter.sourceChannels[0] } : {},
30
31
  ...filter.search ? { search: filter.search } : {}
31
32
  };
@@ -47,6 +48,7 @@ function conversationWakeFromThreadWake(wake) {
47
48
  return { source: wake.source ?? null, reason: wake.reason ?? null, at: wake.at ?? null };
48
49
  }
49
50
  function conversationSummaryFromThreadSummary(thread, filter) {
51
+ const requestedOnlyApp = filter.appIds && filter.appIds.length > 0 || filter.homeAgentId ? void 0 : filter.appId;
50
52
  return {
51
53
  threadId: thread.thread_id,
52
54
  title: thread.title,
@@ -58,7 +60,7 @@ function conversationSummaryFromThreadSummary(thread, filter) {
58
60
  sourceChannel: thread.start_channel ?? null,
59
61
  assetType: null,
60
62
  assetId: null,
61
- appId: thread.app_id ?? filter.appId ?? null,
63
+ appId: thread.app_id ?? requestedOnlyApp ?? null,
62
64
  collabAgentId: thread.collab_agent_id,
63
65
  collabChannelId: thread.collab_channel_id,
64
66
  triggerType: thread.trigger_type ?? null,
@@ -79,9 +81,13 @@ function conversationSummaryFromThreadSummary(thread, filter) {
79
81
  }
80
82
  function createRestConversationListFetcher(options) {
81
83
  return async (params) => {
82
- const { app_ids: appIds, ...body } = threadListRequestBody(params);
83
- const filters = appIds || options.extraFilters ? { ...appIds ? { app_ids: appIds } : {}, ...options.extraFilters } : void 0;
84
- const { threads, has_more: hasMore } = await credentialRenewal.withCredentialRenewal(
84
+ const { app_ids: appIds, home_agent_id: homeAgentId, ...body } = threadListRequestBody(params);
85
+ const filters = appIds || homeAgentId || options.extraFilters ? {
86
+ ...appIds ? { app_ids: appIds } : {},
87
+ ...homeAgentId ? { home_agent_id: homeAgentId } : {},
88
+ ...options.extraFilters
89
+ } : void 0;
90
+ const response = await credentialRenewal.withCredentialRenewal(
85
91
  options.getAuth,
86
92
  (auth) => api.listThreads(options.backendUrl, auth, {
87
93
  ...body,
@@ -90,11 +96,13 @@ function createRestConversationListFetcher(options) {
90
96
  signal: params.signal
91
97
  })
92
98
  );
99
+ const { threads, has_more: hasMore } = response;
93
100
  const items = threads.map((thread) => conversationSummaryFromThreadSummary(thread, params.filter));
94
101
  return {
95
102
  items: options.keepRow ? items.filter(options.keepRow) : items,
96
103
  hasMore: hasMore ?? threads.length >= params.limit,
97
- totalCount: null
104
+ totalCount: null,
105
+ ...homeAgentId && response.home_agent_applied === true ? { homeAgentApplied: true } : {}
98
106
  };
99
107
  };
100
108
  }
@@ -1 +1 @@
1
- {"version":3,"file":"conversation-list-fetcher.cjs","sources":["../../src/threads/conversation-list-fetcher.ts"],"sourcesContent":["/**\n * REST adapter for the shared conversation-list contract\n * (`@athenaintel/agent-runtime` — thread-list-contract-convergence).\n *\n * Translates the canonical {@link ConversationListFetchParams} onto Agora's\n * `POST /api/conversations/threads/list` and maps its `ThreadSummary` rows\n * into the canonical {@link ConversationSummary} shape. Filter fields this\n * transport cannot express throw instead of being ignored — a dropped filter\n * would return wrong rows under a cache key that claims otherwise.\n */\n\nimport type {\n ConversationListFetcher,\n ConversationListFetchParams,\n ConversationListFilter,\n ConversationListPage,\n ConversationPullRequest,\n ConversationSummary,\n ConversationWake,\n} from '@athenaintel/agent-runtime';\nimport { type CredentialResolver, withCredentialRenewal } from '../runtime/credential-renewal';\nimport {\n listThreads,\n type ThreadListFilterBody,\n type ThreadPullRequest,\n type ThreadSummary,\n type ThreadWake,\n} from './api';\n\nexport interface ThreadListRequestBody {\n limit: number;\n offset: number;\n exclude_triggered?: boolean;\n app_id?: string;\n app_ids?: string[];\n start_channel?: string;\n search?: string;\n}\n\n/**\n * Map the canonical filter onto the REST thread-list request body.\n * Exported for tests; throws on filter fields the endpoint cannot express.\n */\nexport function threadListRequestBody(\n params: Pick<ConversationListFetchParams, 'limit' | 'offset' | 'filter'>,\n): ThreadListRequestBody {\n const { filter } = params;\n const unsupported: string[] = [];\n if (filter.assetTypes && filter.assetTypes.length > 0) unsupported.push('assetTypes');\n if (filter.states && filter.states.length > 0) unsupported.push('states');\n if (filter.sourceChannels && filter.sourceChannels.length > 1) {\n unsupported.push('sourceChannels (more than one)');\n }\n if (filter.excludeSourceChannels && filter.excludeSourceChannels.length > 0) {\n unsupported.push('excludeSourceChannels');\n }\n if (filter.computerAssetId) unsupported.push('computerAssetId');\n if (unsupported.length > 0) {\n throw new Error(\n `[AthenaSDK] The REST thread list cannot express filter field(s): ${unsupported.join(', ')}`,\n );\n }\n return {\n limit: params.limit,\n offset: params.offset,\n ...(filter.excludeTriggered !== undefined\n ? { exclude_triggered: filter.excludeTriggered }\n : {}),\n ...(filter.appId ? { app_id: filter.appId } : {}),\n ...(filter.appIds && filter.appIds.length > 0 ? { app_ids: [...filter.appIds] } : {}),\n ...(filter.sourceChannels?.[0] ? { start_channel: filter.sourceChannels[0] } : {}),\n ...(filter.search ? { search: filter.search } : {}),\n };\n}\n\n/** Map one REST pull-request record onto the canonical camelCase shape. */\nexport function conversationPullRequestFromThreadPullRequest(\n record: ThreadPullRequest,\n): ConversationPullRequest {\n return {\n repoFullName: record.repo_full_name,\n number: record.number,\n url: record.url,\n title: record.title,\n state: record.state,\n isDraft: record.is_draft,\n reviewDecision: record.review_decision ?? null,\n checksState: record.checks_state,\n updatedAt: record.updated_at,\n };\n}\n\nfunction conversationWakeFromThreadWake(wake: ThreadWake): ConversationWake {\n return { source: wake.source ?? null, reason: wake.reason ?? null, at: wake.at ?? null };\n}\n\n/**\n * Map a REST `ThreadSummary` row into the canonical contract shape. Fields\n * the server did not send stay `undefined` (an older server, or a list that\n * did not opt into pull requests), so a consumer can tell \"not reported\"\n * from \"reported as none\".\n */\nexport function conversationSummaryFromThreadSummary(\n thread: ThreadSummary,\n filter: ConversationListFilter,\n): ConversationSummary {\n return {\n threadId: thread.thread_id,\n title: thread.title,\n updatedAt: thread.updated_at,\n createdAt: thread.created_at ?? null,\n state: thread.state,\n // Not reported by this endpoint.\n runtime: null,\n sourceChannel: thread.start_channel ?? null,\n assetType: null,\n assetId: null,\n appId: thread.app_id ?? filter.appId ?? null,\n collabAgentId: thread.collab_agent_id,\n collabChannelId: thread.collab_channel_id,\n triggerType: thread.trigger_type ?? null,\n preview: thread.last_message ?? null,\n messageCount: thread.message_count ?? null,\n isUnread: thread.is_unread ?? false,\n // Older servers omit the status planes; `isConversationRunning` then falls back to `state`.\n sessionStatus: thread.session_status ?? null,\n runStatus: thread.run_status ?? null,\n attention: thread.attention,\n nextAction: thread.next_action,\n attentionSource: thread.attention_source,\n isFavorited: thread.is_favorited,\n createdByMe: thread.created_by_me,\n lastWake:\n thread.last_wake === undefined || thread.last_wake === null\n ? thread.last_wake\n : conversationWakeFromThreadWake(thread.last_wake),\n pullRequests:\n thread.pull_requests === undefined || thread.pull_requests === null\n ? thread.pull_requests\n : thread.pull_requests.map(conversationPullRequestFromThreadPullRequest),\n };\n}\n\nexport interface RestConversationListFetcherOptions {\n backendUrl: string;\n /**\n * Resolved per request so rotating tokens never go stale in the fetcher;\n * called again with `{ force: true }` when the host refuses the token.\n */\n getAuth: CredentialResolver<{ apiKey?: string; token?: string | null }>;\n /**\n * Ask the server for live GitHub state on each row's `pullRequests`\n * (`include_pull_requests`). Off by default: it costs the server a lookup,\n * so only a list that renders pull-request chips opts in.\n */\n includePullRequests?: boolean;\n /**\n * Server-side filters and order beyond the shared filter contract\n * (`SessionsPage` / `Inbox`). The caller keys its cache on them (the\n * pager's `scopeKey`), since the contract's filter key does not carry them.\n */\n extraFilters?: ThreadListFilterBody;\n /**\n * Rows a page keeps — the filters this server cannot apply, checked on the\n * rows it returned (`planSessionsQuery`'s `local`). Such a page can come\n * back short while `hasMore` stays true.\n */\n keepRow?: (row: ConversationSummary) => boolean;\n}\n\n/**\n * Build a {@link ConversationListFetcher} over the REST thread list. The\n * endpoint reports no `hasMore`/total, so `hasMore` is the standard offset\n * heuristic (a full page implies more may exist) and `totalCount` is null.\n */\nexport function createRestConversationListFetcher(\n options: RestConversationListFetcherOptions,\n): ConversationListFetcher {\n return async (params): Promise<ConversationListPage> => {\n const { app_ids: appIds, ...body } = threadListRequestBody(params);\n const filters: ThreadListFilterBody | undefined =\n appIds || options.extraFilters\n ? { ...(appIds ? { app_ids: appIds } : {}), ...options.extraFilters }\n : undefined;\n const { threads, has_more: hasMore } = await withCredentialRenewal(options.getAuth, (auth) =>\n listThreads(options.backendUrl, auth, {\n ...body,\n ...(options.includePullRequests ? { include_pull_requests: true } : {}),\n ...(filters ? { filters } : {}),\n signal: params.signal,\n }),\n );\n const items = threads.map((thread) => conversationSummaryFromThreadSummary(thread, params.filter));\n return {\n items: options.keepRow ? items.filter(options.keepRow) : items,\n hasMore: hasMore ?? threads.length >= params.limit,\n totalCount: null,\n };\n };\n}\n"],"names":["withCredentialRenewal","listThreads"],"mappings":"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"}
1
+ {"version":3,"file":"conversation-list-fetcher.cjs","sources":["../../src/threads/conversation-list-fetcher.ts"],"sourcesContent":["/**\n * REST adapter for the shared conversation-list contract\n * (`@athenaintel/agent-runtime` — thread-list-contract-convergence).\n *\n * Translates the canonical {@link ConversationListFetchParams} onto Agora's\n * `POST /api/conversations/threads/list` and maps its `ThreadSummary` rows\n * into the canonical {@link ConversationSummary} shape. Filter fields this\n * transport cannot express throw instead of being ignored — a dropped filter\n * would return wrong rows under a cache key that claims otherwise.\n */\n\nimport type {\n ConversationListFetcher,\n ConversationListFetchParams,\n ConversationListFilter,\n ConversationListPage,\n ConversationPullRequest,\n ConversationSummary,\n ConversationWake,\n} from '@athenaintel/agent-runtime';\nimport { type CredentialResolver, withCredentialRenewal } from '../runtime/credential-renewal';\nimport {\n listThreads,\n type ThreadListFilterBody,\n type ThreadPullRequest,\n type ThreadSummary,\n type ThreadWake,\n} from './api';\n\nexport interface ThreadListRequestBody {\n limit: number;\n offset: number;\n exclude_triggered?: boolean;\n app_id?: string;\n app_ids?: string[];\n home_agent_id?: string;\n start_channel?: string;\n search?: string;\n}\n\n/**\n * Map the canonical filter onto the REST thread-list request body.\n * Exported for tests; throws on filter fields the endpoint cannot express.\n */\nexport function threadListRequestBody(\n params: Pick<ConversationListFetchParams, 'limit' | 'offset' | 'filter'>,\n): ThreadListRequestBody {\n const { filter } = params;\n const unsupported: string[] = [];\n if (filter.assetTypes && filter.assetTypes.length > 0) unsupported.push('assetTypes');\n if (filter.states && filter.states.length > 0) unsupported.push('states');\n if (filter.sourceChannels && filter.sourceChannels.length > 1) {\n unsupported.push('sourceChannels (more than one)');\n }\n if (filter.excludeSourceChannels && filter.excludeSourceChannels.length > 0) {\n unsupported.push('excludeSourceChannels');\n }\n if (filter.computerAssetId) unsupported.push('computerAssetId');\n if (unsupported.length > 0) {\n throw new Error(\n `[AthenaSDK] The REST thread list cannot express filter field(s): ${unsupported.join(', ')}`,\n );\n }\n return {\n limit: params.limit,\n offset: params.offset,\n ...(filter.excludeTriggered !== undefined\n ? { exclude_triggered: filter.excludeTriggered }\n : {}),\n ...(filter.appId ? { app_id: filter.appId } : {}),\n ...(filter.appIds && filter.appIds.length > 0 ? { app_ids: [...filter.appIds] } : {}),\n ...(filter.homeAgentId ? { home_agent_id: filter.homeAgentId } : {}),\n ...(filter.sourceChannels?.[0] ? { start_channel: filter.sourceChannels[0] } : {}),\n ...(filter.search ? { search: filter.search } : {}),\n };\n}\n\n/** Map one REST pull-request record onto the canonical camelCase shape. */\nexport function conversationPullRequestFromThreadPullRequest(\n record: ThreadPullRequest,\n): ConversationPullRequest {\n return {\n repoFullName: record.repo_full_name,\n number: record.number,\n url: record.url,\n title: record.title,\n state: record.state,\n isDraft: record.is_draft,\n reviewDecision: record.review_decision ?? null,\n checksState: record.checks_state,\n updatedAt: record.updated_at,\n };\n}\n\nfunction conversationWakeFromThreadWake(wake: ThreadWake): ConversationWake {\n return { source: wake.source ?? null, reason: wake.reason ?? null, at: wake.at ?? null };\n}\n\n/**\n * Map a REST `ThreadSummary` row into the canonical contract shape. Fields\n * the server did not send stay `undefined` (an older server, or a list that\n * did not opt into pull requests), so a consumer can tell \"not reported\"\n * from \"reported as none\".\n */\nexport function conversationSummaryFromThreadSummary(\n thread: ThreadSummary,\n filter: ConversationListFilter,\n): ConversationSummary {\n // A row that reports no app is the requested app's only when the request named exactly that\n // app: a list widened to other apps (`appIds`) or to an agent's conversations (`homeAgentId`)\n // also holds rows of other apps, and of none (a Slack conversation).\n const requestedOnlyApp =\n (filter.appIds && filter.appIds.length > 0) || filter.homeAgentId ? undefined : filter.appId;\n return {\n threadId: thread.thread_id,\n title: thread.title,\n updatedAt: thread.updated_at,\n createdAt: thread.created_at ?? null,\n state: thread.state,\n // Not reported by this endpoint.\n runtime: null,\n sourceChannel: thread.start_channel ?? null,\n assetType: null,\n assetId: null,\n appId: thread.app_id ?? requestedOnlyApp ?? null,\n collabAgentId: thread.collab_agent_id,\n collabChannelId: thread.collab_channel_id,\n triggerType: thread.trigger_type ?? null,\n preview: thread.last_message ?? null,\n messageCount: thread.message_count ?? null,\n isUnread: thread.is_unread ?? false,\n // Older servers omit the status planes; `isConversationRunning` then falls back to `state`.\n sessionStatus: thread.session_status ?? null,\n runStatus: thread.run_status ?? null,\n attention: thread.attention,\n nextAction: thread.next_action,\n attentionSource: thread.attention_source,\n isFavorited: thread.is_favorited,\n createdByMe: thread.created_by_me,\n lastWake:\n thread.last_wake === undefined || thread.last_wake === null\n ? thread.last_wake\n : conversationWakeFromThreadWake(thread.last_wake),\n pullRequests:\n thread.pull_requests === undefined || thread.pull_requests === null\n ? thread.pull_requests\n : thread.pull_requests.map(conversationPullRequestFromThreadPullRequest),\n };\n}\n\nexport interface RestConversationListFetcherOptions {\n backendUrl: string;\n /**\n * Resolved per request so rotating tokens never go stale in the fetcher;\n * called again with `{ force: true }` when the host refuses the token.\n */\n getAuth: CredentialResolver<{ apiKey?: string; token?: string | null }>;\n /**\n * Ask the server for live GitHub state on each row's `pullRequests`\n * (`include_pull_requests`). Off by default: it costs the server a lookup,\n * so only a list that renders pull-request chips opts in.\n */\n includePullRequests?: boolean;\n /**\n * Server-side filters and order beyond the shared filter contract\n * (`SessionsPage` / `Inbox`). The caller keys its cache on them (the\n * pager's `scopeKey`), since the contract's filter key does not carry them.\n */\n extraFilters?: ThreadListFilterBody;\n /**\n * Rows a page keeps — the filters this server cannot apply, checked on the\n * rows it returned (`planSessionsQuery`'s `local`). Such a page can come\n * back short while `hasMore` stays true.\n */\n keepRow?: (row: ConversationSummary) => boolean;\n}\n\n/**\n * Build a {@link ConversationListFetcher} over the REST thread list. The\n * endpoint reports no `hasMore`/total, so `hasMore` is the standard offset\n * heuristic (a full page implies more may exist) and `totalCount` is null. A\n * page asked for a `homeAgentId` carries `homeAgentApplied: true` only when the\n * server answered `home_agent_applied: true`.\n */\nexport function createRestConversationListFetcher(\n options: RestConversationListFetcherOptions,\n): ConversationListFetcher {\n return async (params): Promise<ConversationListPage> => {\n const { app_ids: appIds, home_agent_id: homeAgentId, ...body } = threadListRequestBody(params);\n const filters: ThreadListFilterBody | undefined =\n appIds || homeAgentId || options.extraFilters\n ? {\n ...(appIds ? { app_ids: appIds } : {}),\n ...(homeAgentId ? { home_agent_id: homeAgentId } : {}),\n ...options.extraFilters,\n }\n : undefined;\n const response = await withCredentialRenewal(options.getAuth, (auth) =>\n listThreads(options.backendUrl, auth, {\n ...body,\n ...(options.includePullRequests ? { include_pull_requests: true } : {}),\n ...(filters ? { filters } : {}),\n signal: params.signal,\n }),\n );\n const { threads, has_more: hasMore } = response;\n const items = threads.map((thread) => conversationSummaryFromThreadSummary(thread, params.filter));\n return {\n items: options.keepRow ? items.filter(options.keepRow) : items,\n hasMore: hasMore ?? threads.length >= params.limit,\n totalCount: null,\n ...(homeAgentId && response.home_agent_applied === true ? { homeAgentApplied: true } : {}),\n };\n };\n}\n"],"names":["withCredentialRenewal","listThreads"],"mappings":";;;;AA4CO,SAAS,sBACd,QACuB;;AACvB,QAAM,EAAE,WAAW;AACnB,QAAM,cAAwB,CAAA;AAC9B,MAAI,OAAO,cAAc,OAAO,WAAW,SAAS,EAAG,aAAY,KAAK,YAAY;AACpF,MAAI,OAAO,UAAU,OAAO,OAAO,SAAS,EAAG,aAAY,KAAK,QAAQ;AACxE,MAAI,OAAO,kBAAkB,OAAO,eAAe,SAAS,GAAG;AAC7D,gBAAY,KAAK,gCAAgC;AAAA,EACnD;AACA,MAAI,OAAO,yBAAyB,OAAO,sBAAsB,SAAS,GAAG;AAC3E,gBAAY,KAAK,uBAAuB;AAAA,EAC1C;AACA,MAAI,OAAO,gBAAiB,aAAY,KAAK,iBAAiB;AAC9D,MAAI,YAAY,SAAS,GAAG;AAC1B,UAAM,IAAI;AAAA,MACR,oEAAoE,YAAY,KAAK,IAAI,CAAC;AAAA,IAAA;AAAA,EAE9F;AACA,SAAO;AAAA,IACL,OAAO,OAAO;AAAA,IACd,QAAQ,OAAO;AAAA,IACf,GAAI,OAAO,qBAAqB,SAC5B,EAAE,mBAAmB,OAAO,iBAAA,IAC5B,CAAA;AAAA,IACJ,GAAI,OAAO,QAAQ,EAAE,QAAQ,OAAO,MAAA,IAAU,CAAA;AAAA,IAC9C,GAAI,OAAO,UAAU,OAAO,OAAO,SAAS,IAAI,EAAE,SAAS,CAAC,GAAG,OAAO,MAAM,EAAA,IAAM,CAAA;AAAA,IAClF,GAAI,OAAO,cAAc,EAAE,eAAe,OAAO,YAAA,IAAgB,CAAA;AAAA,IACjE,KAAI,YAAO,mBAAP,mBAAwB,MAAK,EAAE,eAAe,OAAO,eAAe,CAAC,EAAA,IAAM,CAAA;AAAA,IAC/E,GAAI,OAAO,SAAS,EAAE,QAAQ,OAAO,OAAA,IAAW,CAAA;AAAA,EAAC;AAErD;AAGO,SAAS,6CACd,QACyB;AACzB,SAAO;AAAA,IACL,cAAc,OAAO;AAAA,IACrB,QAAQ,OAAO;AAAA,IACf,KAAK,OAAO;AAAA,IACZ,OAAO,OAAO;AAAA,IACd,OAAO,OAAO;AAAA,IACd,SAAS,OAAO;AAAA,IAChB,gBAAgB,OAAO,mBAAmB;AAAA,IAC1C,aAAa,OAAO;AAAA,IACpB,WAAW,OAAO;AAAA,EAAA;AAEtB;AAEA,SAAS,+BAA+B,MAAoC;AAC1E,SAAO,EAAE,QAAQ,KAAK,UAAU,MAAM,QAAQ,KAAK,UAAU,MAAM,IAAI,KAAK,MAAM,KAAA;AACpF;AAQO,SAAS,qCACd,QACA,QACqB;AAIrB,QAAM,mBACH,OAAO,UAAU,OAAO,OAAO,SAAS,KAAM,OAAO,cAAc,SAAY,OAAO;AACzF,SAAO;AAAA,IACL,UAAU,OAAO;AAAA,IACjB,OAAO,OAAO;AAAA,IACd,WAAW,OAAO;AAAA,IAClB,WAAW,OAAO,cAAc;AAAA,IAChC,OAAO,OAAO;AAAA;AAAA,IAEd,SAAS;AAAA,IACT,eAAe,OAAO,iBAAiB;AAAA,IACvC,WAAW;AAAA,IACX,SAAS;AAAA,IACT,OAAO,OAAO,UAAU,oBAAoB;AAAA,IAC5C,eAAe,OAAO;AAAA,IACtB,iBAAiB,OAAO;AAAA,IACxB,aAAa,OAAO,gBAAgB;AAAA,IACpC,SAAS,OAAO,gBAAgB;AAAA,IAChC,cAAc,OAAO,iBAAiB;AAAA,IACtC,UAAU,OAAO,aAAa;AAAA;AAAA,IAE9B,eAAe,OAAO,kBAAkB;AAAA,IACxC,WAAW,OAAO,cAAc;AAAA,IAChC,WAAW,OAAO;AAAA,IAClB,YAAY,OAAO;AAAA,IACnB,iBAAiB,OAAO;AAAA,IACxB,aAAa,OAAO;AAAA,IACpB,aAAa,OAAO;AAAA,IACpB,UACE,OAAO,cAAc,UAAa,OAAO,cAAc,OACnD,OAAO,YACP,+BAA+B,OAAO,SAAS;AAAA,IACrD,cACE,OAAO,kBAAkB,UAAa,OAAO,kBAAkB,OAC3D,OAAO,gBACP,OAAO,cAAc,IAAI,4CAA4C;AAAA,EAAA;AAE/E;AAoCO,SAAS,kCACd,SACyB;AACzB,SAAO,OAAO,WAA0C;AACtD,UAAM,EAAE,SAAS,QAAQ,eAAe,aAAa,GAAG,KAAA,IAAS,sBAAsB,MAAM;AAC7F,UAAM,UACJ,UAAU,eAAe,QAAQ,eAC7B;AAAA,MACE,GAAI,SAAS,EAAE,SAAS,OAAA,IAAW,CAAA;AAAA,MACnC,GAAI,cAAc,EAAE,eAAe,YAAA,IAAgB,CAAA;AAAA,MACnD,GAAG,QAAQ;AAAA,IAAA,IAEb;AACN,UAAM,WAAW,MAAMA,kBAAAA;AAAAA,MAAsB,QAAQ;AAAA,MAAS,CAAC,SAC7DC,IAAAA,YAAY,QAAQ,YAAY,MAAM;AAAA,QACpC,GAAG;AAAA,QACH,GAAI,QAAQ,sBAAsB,EAAE,uBAAuB,KAAA,IAAS,CAAA;AAAA,QACpE,GAAI,UAAU,EAAE,QAAA,IAAY,CAAA;AAAA,QAC5B,QAAQ,OAAO;AAAA,MAAA,CAChB;AAAA,IAAA;AAEH,UAAM,EAAE,SAAS,UAAU,QAAA,IAAY;AACvC,UAAM,QAAQ,QAAQ,IAAI,CAAC,WAAW,qCAAqC,QAAQ,OAAO,MAAM,CAAC;AACjG,WAAO;AAAA,MACL,OAAO,QAAQ,UAAU,MAAM,OAAO,QAAQ,OAAO,IAAI;AAAA,MACzD,SAAS,WAAW,QAAQ,UAAU,OAAO;AAAA,MAC7C,YAAY;AAAA,MACZ,GAAI,eAAe,SAAS,uBAAuB,OAAO,EAAE,kBAAkB,SAAS,CAAA;AAAA,IAAC;AAAA,EAE5F;AACF;;;;;"}
@@ -17,6 +17,7 @@ export interface ThreadListRequestBody {
17
17
  exclude_triggered?: boolean;
18
18
  app_id?: string;
19
19
  app_ids?: string[];
20
+ home_agent_id?: string;
20
21
  start_channel?: string;
21
22
  search?: string;
22
23
  }
@@ -66,6 +67,8 @@ export interface RestConversationListFetcherOptions {
66
67
  /**
67
68
  * Build a {@link ConversationListFetcher} over the REST thread list. The
68
69
  * endpoint reports no `hasMore`/total, so `hasMore` is the standard offset
69
- * heuristic (a full page implies more may exist) and `totalCount` is null.
70
+ * heuristic (a full page implies more may exist) and `totalCount` is null. A
71
+ * page asked for a `homeAgentId` carries `homeAgentApplied: true` only when the
72
+ * server answered `home_agent_applied: true`.
70
73
  */
71
74
  export declare function createRestConversationListFetcher(options: RestConversationListFetcherOptions): ConversationListFetcher;
@@ -24,6 +24,7 @@ function threadListRequestBody(params) {
24
24
  ...filter.excludeTriggered !== void 0 ? { exclude_triggered: filter.excludeTriggered } : {},
25
25
  ...filter.appId ? { app_id: filter.appId } : {},
26
26
  ...filter.appIds && filter.appIds.length > 0 ? { app_ids: [...filter.appIds] } : {},
27
+ ...filter.homeAgentId ? { home_agent_id: filter.homeAgentId } : {},
27
28
  ...((_a = filter.sourceChannels) == null ? void 0 : _a[0]) ? { start_channel: filter.sourceChannels[0] } : {},
28
29
  ...filter.search ? { search: filter.search } : {}
29
30
  };
@@ -45,6 +46,7 @@ function conversationWakeFromThreadWake(wake) {
45
46
  return { source: wake.source ?? null, reason: wake.reason ?? null, at: wake.at ?? null };
46
47
  }
47
48
  function conversationSummaryFromThreadSummary(thread, filter) {
49
+ const requestedOnlyApp = filter.appIds && filter.appIds.length > 0 || filter.homeAgentId ? void 0 : filter.appId;
48
50
  return {
49
51
  threadId: thread.thread_id,
50
52
  title: thread.title,
@@ -56,7 +58,7 @@ function conversationSummaryFromThreadSummary(thread, filter) {
56
58
  sourceChannel: thread.start_channel ?? null,
57
59
  assetType: null,
58
60
  assetId: null,
59
- appId: thread.app_id ?? filter.appId ?? null,
61
+ appId: thread.app_id ?? requestedOnlyApp ?? null,
60
62
  collabAgentId: thread.collab_agent_id,
61
63
  collabChannelId: thread.collab_channel_id,
62
64
  triggerType: thread.trigger_type ?? null,
@@ -77,9 +79,13 @@ function conversationSummaryFromThreadSummary(thread, filter) {
77
79
  }
78
80
  function createRestConversationListFetcher(options) {
79
81
  return async (params) => {
80
- const { app_ids: appIds, ...body } = threadListRequestBody(params);
81
- const filters = appIds || options.extraFilters ? { ...appIds ? { app_ids: appIds } : {}, ...options.extraFilters } : void 0;
82
- const { threads, has_more: hasMore } = await withCredentialRenewal(
82
+ const { app_ids: appIds, home_agent_id: homeAgentId, ...body } = threadListRequestBody(params);
83
+ const filters = appIds || homeAgentId || options.extraFilters ? {
84
+ ...appIds ? { app_ids: appIds } : {},
85
+ ...homeAgentId ? { home_agent_id: homeAgentId } : {},
86
+ ...options.extraFilters
87
+ } : void 0;
88
+ const response = await withCredentialRenewal(
83
89
  options.getAuth,
84
90
  (auth) => listThreads(options.backendUrl, auth, {
85
91
  ...body,
@@ -88,11 +94,13 @@ function createRestConversationListFetcher(options) {
88
94
  signal: params.signal
89
95
  })
90
96
  );
97
+ const { threads, has_more: hasMore } = response;
91
98
  const items = threads.map((thread) => conversationSummaryFromThreadSummary(thread, params.filter));
92
99
  return {
93
100
  items: options.keepRow ? items.filter(options.keepRow) : items,
94
101
  hasMore: hasMore ?? threads.length >= params.limit,
95
- totalCount: null
102
+ totalCount: null,
103
+ ...homeAgentId && response.home_agent_applied === true ? { homeAgentApplied: true } : {}
96
104
  };
97
105
  };
98
106
  }