@athenaintel/react 0.21.0 → 0.21.1

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 (40) hide show
  1. package/README.md +13 -1
  2. package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.cjs +6 -6
  3. package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.cjs.map +1 -1
  4. package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.js +6 -6
  5. package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.js.map +1 -1
  6. package/dist/packages/agent-runtime/src/conversation-index.cjs +19 -10
  7. package/dist/packages/agent-runtime/src/conversation-index.cjs.map +1 -1
  8. package/dist/packages/agent-runtime/src/conversation-index.js +19 -10
  9. package/dist/packages/agent-runtime/src/conversation-index.js.map +1 -1
  10. package/dist/packages/react/package.json.cjs +1 -1
  11. package/dist/packages/react/package.json.js +1 -1
  12. package/dist/threads/api.cjs +29 -0
  13. package/dist/threads/api.cjs.map +1 -1
  14. package/dist/threads/api.d.ts +15 -0
  15. package/dist/threads/api.js +29 -0
  16. package/dist/threads/api.js.map +1 -1
  17. package/dist/threads/credential-shape.cjs +15 -0
  18. package/dist/threads/credential-shape.cjs.map +1 -0
  19. package/dist/threads/credential-shape.d.ts +17 -0
  20. package/dist/threads/credential-shape.js +15 -0
  21. package/dist/threads/credential-shape.js.map +1 -0
  22. package/dist/threads/statewire-thread-list.cjs +35 -3
  23. package/dist/threads/statewire-thread-list.cjs.map +1 -1
  24. package/dist/threads/statewire-thread-list.js +36 -4
  25. package/dist/threads/statewire-thread-list.js.map +1 -1
  26. package/dist/threads/use-statewire-conversation-history.cjs +3 -1
  27. package/dist/threads/use-statewire-conversation-history.cjs.map +1 -1
  28. package/dist/threads/use-statewire-conversation-history.d.ts +3 -1
  29. package/dist/threads/use-statewire-conversation-history.js +3 -1
  30. package/dist/threads/use-statewire-conversation-history.js.map +1 -1
  31. package/dist/toolkits/index.cjs +0 -7
  32. package/dist/toolkits/index.cjs.map +1 -1
  33. package/dist/toolkits/index.d.ts +0 -2
  34. package/dist/toolkits/index.js +0 -7
  35. package/dist/toolkits/index.js.map +1 -1
  36. package/dist/voice/index.d.ts +1 -1
  37. package/dist/voice/use-athena-voice-session.cjs.map +1 -1
  38. package/dist/voice/use-athena-voice-session.d.ts +37 -2
  39. package/dist/voice/use-athena-voice-session.js.map +1 -1
  40. package/package.json +2 -2
package/README.md CHANGED
@@ -178,7 +178,7 @@ const voice = useAthenaVoiceSession({
178
178
  provider: 'openai', // "google" | "openai"
179
179
  model: 'gpt-realtime-2',
180
180
  voice: 'marin',
181
- enabled_tools: [], // [] = pure conversation
181
+ enabled_tools: [], // [] = no tools; omit for the server default set
182
182
  },
183
183
  onTranscript: (turn) => console.log(turn.speaker, turn.text),
184
184
  });
@@ -189,6 +189,18 @@ const voice = useAthenaVoiceSession({
189
189
  <button onClick={voice.stop}>Hang up</button>
190
190
  ```
191
191
 
192
+ **Voice tools.** `enabled_tools` takes voice tool names (`AthenaVoiceToolId`),
193
+ not the chat `Toolkits` ids — `'web_search_browse_toolkit'` is not a voice tool,
194
+ `'web_search'` is. Available: `web_search` (Exa; `search_web` is a legacy alias),
195
+ `browse` (read a URL), `search_knowledge_base` and `search_knowledge_base_lr`
196
+ (the workspace knowledge base; `_lr` is the thorough long-running retrieval),
197
+ `read_full_asset` and `search_assets` (as the viewer), `ask_agent` (hand a
198
+ question to the general agent), `save_note`, `get_current_time_est`. Tools that
199
+ call back into the client over LiveKit RPC (`write_to_clipboard`, the desktop and
200
+ mobile tools) are not part of the hook's contract. Omit the field for the
201
+ server's default set; pass `[]` for pure conversation. Agora refuses unknown
202
+ names with a 422 (#35466).
203
+
192
204
  **Authentication.** Served from a Marathon computer-asset URL, the hook is
193
205
  zero-config: it takes the viewer bearer from `useParentBridge()` and creates
194
206
  the session **as the person using the app**, so it shows up under their
@@ -25,13 +25,10 @@ const ASSET_TYPE_ICON_FILE = {
25
25
  image: "ic-img",
26
26
  audio: "ic-audio",
27
27
  video: "ic-video",
28
- voice_memo: "ic-audio",
28
+ voice_memo: "ic-voice_memo",
29
29
  // Presentations
30
- presentation: "ic-power_point",
31
- office_presentation: "ic-power_point",
32
30
  powerpoint_deck: "ic-power_point",
33
31
  presentation_pptx_studio: "ic-power_point",
34
- presentation_onlyoffice: "ic-power_point",
35
32
  pptx: "ic-power_point",
36
33
  // Email & Calendar
37
34
  email: "ic-mail",
@@ -81,14 +78,16 @@ const ASSET_TYPE_ICON_FILE = {
81
78
  browse_profile: "ic-profile",
82
79
  // Workflows & Automation
83
80
  aop: "ic-flow",
84
- playbook: "ic-flow",
81
+ playbook: "ic-playbook",
82
+ policy: "ic-policy",
85
83
  flow: "ic-flow",
86
- transform_project: "ic-flow",
84
+ transform_project: "ic-transform_project",
87
85
  // Collections & Notebooks
88
86
  collection: "ic-collection",
89
87
  notebook: "ic-notebook",
90
88
  // UI & Apps
91
89
  user_interface: "ic-app",
90
+ application: "ic-application",
92
91
  canvas: "ic-canvas",
93
92
  dashboard: "ic-dashboard",
94
93
  figure: "ic_figure",
@@ -97,6 +96,7 @@ const ASSET_TYPE_ICON_FILE = {
97
96
  company: "ic-company",
98
97
  // Misc
99
98
  prompt: "ic-ai",
99
+ skill: "ic-skill",
100
100
  task_item: "ic-task",
101
101
  spaces: "ic-spaces",
102
102
  secret: "ic-secret",
@@ -1 +1 @@
1
- {"version":3,"file":"asset-type-icons.cjs","sources":["../../../../../../src/chat-ui/mentions/suggestions/data/sources/asset-type-icons.ts"],"sourcesContent":["/**\n * Maps Athena asset types to SVG icon file names.\n *\n * The SVG files are bundled by the host app, which declares where they are\n * served from via `configureAssetIconBasePath`. Hosts that don't bundle them\n * fall back to the Lucide icons carried on every menu item.\n */\n\nlet iconBasePath: string | null = null;\n\n/**\n * Point the mention UI at the host's copy of the asset-type SVGs.\n * `basePath` is resolved against the host document; a trailing \"/\" is added\n * when the caller omits it.\n */\nexport function configureAssetIconBasePath({\n basePath,\n}: {\n basePath: string | null;\n}): void {\n iconBasePath = basePath ? basePath.replace(/\\/*$/, \"/\") : basePath;\n}\n\nconst ASSET_TYPE_ICON_FILE: Record<string, string> = {\n // Documents\n folder: \"ic-folder\",\n txt: \"ic-txt\",\n document: \"ic-word\",\n word_document: \"ic-word\",\n markdown: \"ic-md\",\n pdf: \"ic-pdf\",\n html: \"ic-html\",\n web_page: \"ic-html\",\n yaml: \"ic-yaml\",\n json: \"ic-json\",\n\n // Spreadsheets\n spreadsheet: \"ic-excel\",\n table: \"ic-table\",\n\n // Media\n image: \"ic-img\",\n audio: \"ic-audio\",\n video: \"ic-video\",\n voice_memo: \"ic-audio\",\n\n // Presentations\n presentation: \"ic-power_point\",\n office_presentation: \"ic-power_point\",\n powerpoint_deck: \"ic-power_point\",\n presentation_pptx_studio: \"ic-power_point\",\n presentation_onlyoffice: \"ic-power_point\",\n pptx: \"ic-power_point\",\n\n // Email & Calendar\n email: \"ic-mail\",\n email_message: \"ic-mail\",\n live_email_thread: \"ic-mail\",\n live_calendar_event: \"ic-mail\",\n\n // Code\n python: \"ic-py\",\n javascript: \"ic-js\",\n typescript: \"ic-code\",\n jsx: \"ic-code\",\n tsx: \"ic-code\",\n css: \"ic-code\",\n java: \"ic-code\",\n c: \"ic-code\",\n cpp: \"ic-code\",\n csharp: \"ic-code\",\n ruby: \"ic-code\",\n go: \"ic-code\",\n swift: \"ic-code\",\n php: \"ic-code\",\n rust: \"ic-code\",\n vue: \"ic-code\",\n wm_script: \"ic-code\",\n\n // Structured data\n database: \"ic-snippet\",\n schema: \"ic-schema\",\n catalog: \"ic-db\",\n drive: \"ic-db\",\n query_snippet: \"ic-snippet\",\n semantic_query: \"ic-snippet\",\n semantic_model: \"ic-db\",\n dataset: \"ic-db\",\n\n // Meetings & Calls\n meeting: \"ic-meeting\",\n video_session: \"ic-meeting\",\n phone_call: \"ic-phonecall\",\n\n // Sessions & Devices\n session: \"ic-session\",\n browse_session: \"ic-html\",\n virtual_machine: \"ic-html\",\n computer: \"ic-computer\",\n desktop_session: \"ic-desktop_session\",\n mobile_session: \"ic-mobile_session\",\n\n // Projects & Profiles\n project: \"ic-project\",\n browse_profile: \"ic-profile\",\n\n // Workflows & Automation\n aop: \"ic-flow\",\n playbook: \"ic-flow\",\n flow: \"ic-flow\",\n transform_project: \"ic-flow\",\n\n // Collections & Notebooks\n collection: \"ic-collection\",\n notebook: \"ic-notebook\",\n\n // UI & Apps\n user_interface: \"ic-app\",\n canvas: \"ic-canvas\",\n dashboard: \"ic-dashboard\",\n figure: \"ic_figure\",\n\n // People & Companies\n person: \"ic-person\",\n company: \"ic-company\",\n\n // Misc\n prompt: \"ic-ai\",\n task_item: \"ic-task\",\n spaces: \"ic-spaces\",\n secret: \"ic-secret\",\n photoshop: \"ic-pts\",\n illustrator: \"ic-ai\",\n zip: \"ic-zip\",\n sevenzip: \"ic-zip\",\n\n // Google Drive / SharePoint / Salesforce\n gdrive_folder: \"ic-folder\",\n gdrive_file: \"ic-file\",\n salesforce_record: \"ic-db\",\n salesforce_file: \"ic-db\",\n};\n\nconst ASSET_TYPE_LUCIDE_FALLBACK: Record<string, string> = {\n folder: \"folder\",\n spreadsheet: \"sheet\",\n image: \"image\",\n video: \"video\",\n audio: \"headphones\",\n email: \"mail\",\n database: \"database\",\n project: \"folder-kanban\",\n meeting: \"video\",\n user_interface: \"layout\",\n};\n\n/**\n * Get the SVG thumbnail URL for an Athena asset type, or undefined when the\n * host app does not bundle the icon files.\n */\nexport function assetTypeToThumb(\n assetType: string | null | undefined,\n): string | undefined {\n if (!iconBasePath) return undefined;\n const name: string =\n (assetType ? ASSET_TYPE_ICON_FILE[assetType.toLowerCase()] : undefined) ??\n \"ic-file\";\n return `${iconBasePath}${name}.svg`;\n}\n\n/**\n * Get Lucide icon name fallback for an asset type.\n * Used when thumbnailUrl can't load.\n */\nexport function assetTypeToIcon(assetType: string | null | undefined): string {\n if (!assetType) return \"file\";\n return ASSET_TYPE_LUCIDE_FALLBACK[assetType.toLowerCase()] ?? \"file\";\n}\n"],"names":[],"mappings":";;AAQA,IAAI,eAA8B;AAO3B,SAAS,2BAA2B;AAAA,EACzC;AACF,GAES;AACP,iBAAe,WAAW,SAAS,QAAQ,QAAQ,GAAG,IAAI;AAC5D;AAEA,MAAM,uBAA+C;AAAA;AAAA,EAEnD,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,UAAU;AAAA,EACV,eAAe;AAAA,EACf,UAAU;AAAA,EACV,KAAK;AAAA,EACL,MAAM;AAAA,EACN,UAAU;AAAA,EACV,MAAM;AAAA,EACN,MAAM;AAAA;AAAA,EAGN,aAAa;AAAA,EACb,OAAO;AAAA;AAAA,EAGP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,YAAY;AAAA;AAAA,EAGZ,cAAc;AAAA,EACd,qBAAqB;AAAA,EACrB,iBAAiB;AAAA,EACjB,0BAA0B;AAAA,EAC1B,yBAAyB;AAAA,EACzB,MAAM;AAAA;AAAA,EAGN,OAAO;AAAA,EACP,eAAe;AAAA,EACf,mBAAmB;AAAA,EACnB,qBAAqB;AAAA;AAAA,EAGrB,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,MAAM;AAAA,EACN,GAAG;AAAA,EACH,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,KAAK;AAAA,EACL,MAAM;AAAA,EACN,KAAK;AAAA,EACL,WAAW;AAAA;AAAA,EAGX,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,OAAO;AAAA,EACP,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,SAAS;AAAA;AAAA,EAGT,SAAS;AAAA,EACT,eAAe;AAAA,EACf,YAAY;AAAA;AAAA,EAGZ,SAAS;AAAA,EACT,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,UAAU;AAAA,EACV,iBAAiB;AAAA,EACjB,gBAAgB;AAAA;AAAA,EAGhB,SAAS;AAAA,EACT,gBAAgB;AAAA;AAAA,EAGhB,KAAK;AAAA,EACL,UAAU;AAAA,EACV,MAAM;AAAA,EACN,mBAAmB;AAAA;AAAA,EAGnB,YAAY;AAAA,EACZ,UAAU;AAAA;AAAA,EAGV,gBAAgB;AAAA,EAChB,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,QAAQ;AAAA;AAAA,EAGR,QAAQ;AAAA,EACR,SAAS;AAAA;AAAA,EAGT,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,aAAa;AAAA,EACb,KAAK;AAAA,EACL,UAAU;AAAA;AAAA,EAGV,eAAe;AAAA,EACf,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,iBAAiB;AACnB;AAEA,MAAM,6BAAqD;AAAA,EACzD,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AAAA,EACT,gBAAgB;AAClB;AAMO,SAAS,iBACd,WACoB;AACpB,MAAI,CAAC,aAAc,QAAO;AAC1B,QAAM,QACH,YAAY,qBAAqB,UAAU,YAAA,CAAa,IAAI,WAC7D;AACF,SAAO,GAAG,YAAY,GAAG,IAAI;AAC/B;AAMO,SAAS,gBAAgB,WAA8C;AAC5E,MAAI,CAAC,UAAW,QAAO;AACvB,SAAO,2BAA2B,UAAU,YAAA,CAAa,KAAK;AAChE;;;;"}
1
+ {"version":3,"file":"asset-type-icons.cjs","sources":["../../../../../../src/chat-ui/mentions/suggestions/data/sources/asset-type-icons.ts"],"sourcesContent":["/**\n * Maps Athena asset types to SVG icon file names.\n *\n * The SVG files are bundled by the host app, which declares where they are\n * served from via `configureAssetIconBasePath`. Hosts that don't bundle them\n * fall back to the Lucide icons carried on every menu item.\n */\n\nlet iconBasePath: string | null = null;\n\n/**\n * Point the mention UI at the host's copy of the asset-type SVGs.\n * `basePath` is resolved against the host document; a trailing \"/\" is added\n * when the caller omits it.\n */\nexport function configureAssetIconBasePath({\n basePath,\n}: {\n basePath: string | null;\n}): void {\n iconBasePath = basePath ? basePath.replace(/\\/*$/, \"/\") : basePath;\n}\n\nconst ASSET_TYPE_ICON_FILE: Record<string, string> = {\n // Documents\n folder: \"ic-folder\",\n txt: \"ic-txt\",\n document: \"ic-word\",\n word_document: \"ic-word\",\n markdown: \"ic-md\",\n pdf: \"ic-pdf\",\n html: \"ic-html\",\n web_page: \"ic-html\",\n yaml: \"ic-yaml\",\n json: \"ic-json\",\n\n // Spreadsheets\n spreadsheet: \"ic-excel\",\n table: \"ic-table\",\n\n // Media\n image: \"ic-img\",\n audio: \"ic-audio\",\n video: \"ic-video\",\n voice_memo: \"ic-voice_memo\",\n\n // Presentations\n powerpoint_deck: \"ic-power_point\",\n presentation_pptx_studio: \"ic-power_point\",\n pptx: \"ic-power_point\",\n\n // Email & Calendar\n email: \"ic-mail\",\n email_message: \"ic-mail\",\n live_email_thread: \"ic-mail\",\n live_calendar_event: \"ic-mail\",\n\n // Code\n python: \"ic-py\",\n javascript: \"ic-js\",\n typescript: \"ic-code\",\n jsx: \"ic-code\",\n tsx: \"ic-code\",\n css: \"ic-code\",\n java: \"ic-code\",\n c: \"ic-code\",\n cpp: \"ic-code\",\n csharp: \"ic-code\",\n ruby: \"ic-code\",\n go: \"ic-code\",\n swift: \"ic-code\",\n php: \"ic-code\",\n rust: \"ic-code\",\n vue: \"ic-code\",\n wm_script: \"ic-code\",\n\n // Structured data\n database: \"ic-snippet\",\n schema: \"ic-schema\",\n catalog: \"ic-db\",\n drive: \"ic-db\",\n query_snippet: \"ic-snippet\",\n semantic_query: \"ic-snippet\",\n semantic_model: \"ic-db\",\n dataset: \"ic-db\",\n\n // Meetings & Calls\n meeting: \"ic-meeting\",\n video_session: \"ic-meeting\",\n phone_call: \"ic-phonecall\",\n\n // Sessions & Devices\n session: \"ic-session\",\n browse_session: \"ic-html\",\n virtual_machine: \"ic-html\",\n computer: \"ic-computer\",\n desktop_session: \"ic-desktop_session\",\n mobile_session: \"ic-mobile_session\",\n\n // Projects & Profiles\n project: \"ic-project\",\n browse_profile: \"ic-profile\",\n\n // Workflows & Automation\n aop: \"ic-flow\",\n playbook: \"ic-playbook\",\n policy: \"ic-policy\",\n flow: \"ic-flow\",\n transform_project: \"ic-transform_project\",\n\n // Collections & Notebooks\n collection: \"ic-collection\",\n notebook: \"ic-notebook\",\n\n // UI & Apps\n user_interface: \"ic-app\",\n application: \"ic-application\",\n canvas: \"ic-canvas\",\n dashboard: \"ic-dashboard\",\n figure: \"ic_figure\",\n\n // People & Companies\n person: \"ic-person\",\n company: \"ic-company\",\n\n // Misc\n prompt: \"ic-ai\",\n skill: \"ic-skill\",\n task_item: \"ic-task\",\n spaces: \"ic-spaces\",\n secret: \"ic-secret\",\n photoshop: \"ic-pts\",\n illustrator: \"ic-ai\",\n zip: \"ic-zip\",\n sevenzip: \"ic-zip\",\n\n // Google Drive / SharePoint / Salesforce\n gdrive_folder: \"ic-folder\",\n gdrive_file: \"ic-file\",\n salesforce_record: \"ic-db\",\n salesforce_file: \"ic-db\",\n};\n\nconst ASSET_TYPE_LUCIDE_FALLBACK: Record<string, string> = {\n folder: \"folder\",\n spreadsheet: \"sheet\",\n image: \"image\",\n video: \"video\",\n audio: \"headphones\",\n email: \"mail\",\n database: \"database\",\n project: \"folder-kanban\",\n meeting: \"video\",\n user_interface: \"layout\",\n};\n\n/**\n * Get the SVG thumbnail URL for an Athena asset type, or undefined when the\n * host app does not bundle the icon files.\n */\nexport function assetTypeToThumb(\n assetType: string | null | undefined,\n): string | undefined {\n if (!iconBasePath) return undefined;\n const name: string =\n (assetType ? ASSET_TYPE_ICON_FILE[assetType.toLowerCase()] : undefined) ??\n \"ic-file\";\n return `${iconBasePath}${name}.svg`;\n}\n\n/**\n * Get Lucide icon name fallback for an asset type.\n * Used when thumbnailUrl can't load.\n */\nexport function assetTypeToIcon(assetType: string | null | undefined): string {\n if (!assetType) return \"file\";\n return ASSET_TYPE_LUCIDE_FALLBACK[assetType.toLowerCase()] ?? \"file\";\n}\n"],"names":[],"mappings":";;AAQA,IAAI,eAA8B;AAO3B,SAAS,2BAA2B;AAAA,EACzC;AACF,GAES;AACP,iBAAe,WAAW,SAAS,QAAQ,QAAQ,GAAG,IAAI;AAC5D;AAEA,MAAM,uBAA+C;AAAA;AAAA,EAEnD,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,UAAU;AAAA,EACV,eAAe;AAAA,EACf,UAAU;AAAA,EACV,KAAK;AAAA,EACL,MAAM;AAAA,EACN,UAAU;AAAA,EACV,MAAM;AAAA,EACN,MAAM;AAAA;AAAA,EAGN,aAAa;AAAA,EACb,OAAO;AAAA;AAAA,EAGP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,YAAY;AAAA;AAAA,EAGZ,iBAAiB;AAAA,EACjB,0BAA0B;AAAA,EAC1B,MAAM;AAAA;AAAA,EAGN,OAAO;AAAA,EACP,eAAe;AAAA,EACf,mBAAmB;AAAA,EACnB,qBAAqB;AAAA;AAAA,EAGrB,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,MAAM;AAAA,EACN,GAAG;AAAA,EACH,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,KAAK;AAAA,EACL,MAAM;AAAA,EACN,KAAK;AAAA,EACL,WAAW;AAAA;AAAA,EAGX,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,OAAO;AAAA,EACP,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,SAAS;AAAA;AAAA,EAGT,SAAS;AAAA,EACT,eAAe;AAAA,EACf,YAAY;AAAA;AAAA,EAGZ,SAAS;AAAA,EACT,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,UAAU;AAAA,EACV,iBAAiB;AAAA,EACjB,gBAAgB;AAAA;AAAA,EAGhB,SAAS;AAAA,EACT,gBAAgB;AAAA;AAAA,EAGhB,KAAK;AAAA,EACL,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,mBAAmB;AAAA;AAAA,EAGnB,YAAY;AAAA,EACZ,UAAU;AAAA;AAAA,EAGV,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,QAAQ;AAAA;AAAA,EAGR,QAAQ;AAAA,EACR,SAAS;AAAA;AAAA,EAGT,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,aAAa;AAAA,EACb,KAAK;AAAA,EACL,UAAU;AAAA;AAAA,EAGV,eAAe;AAAA,EACf,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,iBAAiB;AACnB;AAEA,MAAM,6BAAqD;AAAA,EACzD,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AAAA,EACT,gBAAgB;AAClB;AAMO,SAAS,iBACd,WACoB;AACpB,MAAI,CAAC,aAAc,QAAO;AAC1B,QAAM,QACH,YAAY,qBAAqB,UAAU,YAAA,CAAa,IAAI,WAC7D;AACF,SAAO,GAAG,YAAY,GAAG,IAAI;AAC/B;AAMO,SAAS,gBAAgB,WAA8C;AAC5E,MAAI,CAAC,UAAW,QAAO;AACvB,SAAO,2BAA2B,UAAU,YAAA,CAAa,KAAK;AAChE;;;;"}
@@ -23,13 +23,10 @@ const ASSET_TYPE_ICON_FILE = {
23
23
  image: "ic-img",
24
24
  audio: "ic-audio",
25
25
  video: "ic-video",
26
- voice_memo: "ic-audio",
26
+ voice_memo: "ic-voice_memo",
27
27
  // Presentations
28
- presentation: "ic-power_point",
29
- office_presentation: "ic-power_point",
30
28
  powerpoint_deck: "ic-power_point",
31
29
  presentation_pptx_studio: "ic-power_point",
32
- presentation_onlyoffice: "ic-power_point",
33
30
  pptx: "ic-power_point",
34
31
  // Email & Calendar
35
32
  email: "ic-mail",
@@ -79,14 +76,16 @@ const ASSET_TYPE_ICON_FILE = {
79
76
  browse_profile: "ic-profile",
80
77
  // Workflows & Automation
81
78
  aop: "ic-flow",
82
- playbook: "ic-flow",
79
+ playbook: "ic-playbook",
80
+ policy: "ic-policy",
83
81
  flow: "ic-flow",
84
- transform_project: "ic-flow",
82
+ transform_project: "ic-transform_project",
85
83
  // Collections & Notebooks
86
84
  collection: "ic-collection",
87
85
  notebook: "ic-notebook",
88
86
  // UI & Apps
89
87
  user_interface: "ic-app",
88
+ application: "ic-application",
90
89
  canvas: "ic-canvas",
91
90
  dashboard: "ic-dashboard",
92
91
  figure: "ic_figure",
@@ -95,6 +94,7 @@ const ASSET_TYPE_ICON_FILE = {
95
94
  company: "ic-company",
96
95
  // Misc
97
96
  prompt: "ic-ai",
97
+ skill: "ic-skill",
98
98
  task_item: "ic-task",
99
99
  spaces: "ic-spaces",
100
100
  secret: "ic-secret",
@@ -1 +1 @@
1
- {"version":3,"file":"asset-type-icons.js","sources":["../../../../../../src/chat-ui/mentions/suggestions/data/sources/asset-type-icons.ts"],"sourcesContent":["/**\n * Maps Athena asset types to SVG icon file names.\n *\n * The SVG files are bundled by the host app, which declares where they are\n * served from via `configureAssetIconBasePath`. Hosts that don't bundle them\n * fall back to the Lucide icons carried on every menu item.\n */\n\nlet iconBasePath: string | null = null;\n\n/**\n * Point the mention UI at the host's copy of the asset-type SVGs.\n * `basePath` is resolved against the host document; a trailing \"/\" is added\n * when the caller omits it.\n */\nexport function configureAssetIconBasePath({\n basePath,\n}: {\n basePath: string | null;\n}): void {\n iconBasePath = basePath ? basePath.replace(/\\/*$/, \"/\") : basePath;\n}\n\nconst ASSET_TYPE_ICON_FILE: Record<string, string> = {\n // Documents\n folder: \"ic-folder\",\n txt: \"ic-txt\",\n document: \"ic-word\",\n word_document: \"ic-word\",\n markdown: \"ic-md\",\n pdf: \"ic-pdf\",\n html: \"ic-html\",\n web_page: \"ic-html\",\n yaml: \"ic-yaml\",\n json: \"ic-json\",\n\n // Spreadsheets\n spreadsheet: \"ic-excel\",\n table: \"ic-table\",\n\n // Media\n image: \"ic-img\",\n audio: \"ic-audio\",\n video: \"ic-video\",\n voice_memo: \"ic-audio\",\n\n // Presentations\n presentation: \"ic-power_point\",\n office_presentation: \"ic-power_point\",\n powerpoint_deck: \"ic-power_point\",\n presentation_pptx_studio: \"ic-power_point\",\n presentation_onlyoffice: \"ic-power_point\",\n pptx: \"ic-power_point\",\n\n // Email & Calendar\n email: \"ic-mail\",\n email_message: \"ic-mail\",\n live_email_thread: \"ic-mail\",\n live_calendar_event: \"ic-mail\",\n\n // Code\n python: \"ic-py\",\n javascript: \"ic-js\",\n typescript: \"ic-code\",\n jsx: \"ic-code\",\n tsx: \"ic-code\",\n css: \"ic-code\",\n java: \"ic-code\",\n c: \"ic-code\",\n cpp: \"ic-code\",\n csharp: \"ic-code\",\n ruby: \"ic-code\",\n go: \"ic-code\",\n swift: \"ic-code\",\n php: \"ic-code\",\n rust: \"ic-code\",\n vue: \"ic-code\",\n wm_script: \"ic-code\",\n\n // Structured data\n database: \"ic-snippet\",\n schema: \"ic-schema\",\n catalog: \"ic-db\",\n drive: \"ic-db\",\n query_snippet: \"ic-snippet\",\n semantic_query: \"ic-snippet\",\n semantic_model: \"ic-db\",\n dataset: \"ic-db\",\n\n // Meetings & Calls\n meeting: \"ic-meeting\",\n video_session: \"ic-meeting\",\n phone_call: \"ic-phonecall\",\n\n // Sessions & Devices\n session: \"ic-session\",\n browse_session: \"ic-html\",\n virtual_machine: \"ic-html\",\n computer: \"ic-computer\",\n desktop_session: \"ic-desktop_session\",\n mobile_session: \"ic-mobile_session\",\n\n // Projects & Profiles\n project: \"ic-project\",\n browse_profile: \"ic-profile\",\n\n // Workflows & Automation\n aop: \"ic-flow\",\n playbook: \"ic-flow\",\n flow: \"ic-flow\",\n transform_project: \"ic-flow\",\n\n // Collections & Notebooks\n collection: \"ic-collection\",\n notebook: \"ic-notebook\",\n\n // UI & Apps\n user_interface: \"ic-app\",\n canvas: \"ic-canvas\",\n dashboard: \"ic-dashboard\",\n figure: \"ic_figure\",\n\n // People & Companies\n person: \"ic-person\",\n company: \"ic-company\",\n\n // Misc\n prompt: \"ic-ai\",\n task_item: \"ic-task\",\n spaces: \"ic-spaces\",\n secret: \"ic-secret\",\n photoshop: \"ic-pts\",\n illustrator: \"ic-ai\",\n zip: \"ic-zip\",\n sevenzip: \"ic-zip\",\n\n // Google Drive / SharePoint / Salesforce\n gdrive_folder: \"ic-folder\",\n gdrive_file: \"ic-file\",\n salesforce_record: \"ic-db\",\n salesforce_file: \"ic-db\",\n};\n\nconst ASSET_TYPE_LUCIDE_FALLBACK: Record<string, string> = {\n folder: \"folder\",\n spreadsheet: \"sheet\",\n image: \"image\",\n video: \"video\",\n audio: \"headphones\",\n email: \"mail\",\n database: \"database\",\n project: \"folder-kanban\",\n meeting: \"video\",\n user_interface: \"layout\",\n};\n\n/**\n * Get the SVG thumbnail URL for an Athena asset type, or undefined when the\n * host app does not bundle the icon files.\n */\nexport function assetTypeToThumb(\n assetType: string | null | undefined,\n): string | undefined {\n if (!iconBasePath) return undefined;\n const name: string =\n (assetType ? ASSET_TYPE_ICON_FILE[assetType.toLowerCase()] : undefined) ??\n \"ic-file\";\n return `${iconBasePath}${name}.svg`;\n}\n\n/**\n * Get Lucide icon name fallback for an asset type.\n * Used when thumbnailUrl can't load.\n */\nexport function assetTypeToIcon(assetType: string | null | undefined): string {\n if (!assetType) return \"file\";\n return ASSET_TYPE_LUCIDE_FALLBACK[assetType.toLowerCase()] ?? \"file\";\n}\n"],"names":[],"mappings":"AAQA,IAAI,eAA8B;AAO3B,SAAS,2BAA2B;AAAA,EACzC;AACF,GAES;AACP,iBAAe,WAAW,SAAS,QAAQ,QAAQ,GAAG,IAAI;AAC5D;AAEA,MAAM,uBAA+C;AAAA;AAAA,EAEnD,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,UAAU;AAAA,EACV,eAAe;AAAA,EACf,UAAU;AAAA,EACV,KAAK;AAAA,EACL,MAAM;AAAA,EACN,UAAU;AAAA,EACV,MAAM;AAAA,EACN,MAAM;AAAA;AAAA,EAGN,aAAa;AAAA,EACb,OAAO;AAAA;AAAA,EAGP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,YAAY;AAAA;AAAA,EAGZ,cAAc;AAAA,EACd,qBAAqB;AAAA,EACrB,iBAAiB;AAAA,EACjB,0BAA0B;AAAA,EAC1B,yBAAyB;AAAA,EACzB,MAAM;AAAA;AAAA,EAGN,OAAO;AAAA,EACP,eAAe;AAAA,EACf,mBAAmB;AAAA,EACnB,qBAAqB;AAAA;AAAA,EAGrB,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,MAAM;AAAA,EACN,GAAG;AAAA,EACH,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,KAAK;AAAA,EACL,MAAM;AAAA,EACN,KAAK;AAAA,EACL,WAAW;AAAA;AAAA,EAGX,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,OAAO;AAAA,EACP,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,SAAS;AAAA;AAAA,EAGT,SAAS;AAAA,EACT,eAAe;AAAA,EACf,YAAY;AAAA;AAAA,EAGZ,SAAS;AAAA,EACT,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,UAAU;AAAA,EACV,iBAAiB;AAAA,EACjB,gBAAgB;AAAA;AAAA,EAGhB,SAAS;AAAA,EACT,gBAAgB;AAAA;AAAA,EAGhB,KAAK;AAAA,EACL,UAAU;AAAA,EACV,MAAM;AAAA,EACN,mBAAmB;AAAA;AAAA,EAGnB,YAAY;AAAA,EACZ,UAAU;AAAA;AAAA,EAGV,gBAAgB;AAAA,EAChB,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,QAAQ;AAAA;AAAA,EAGR,QAAQ;AAAA,EACR,SAAS;AAAA;AAAA,EAGT,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,aAAa;AAAA,EACb,KAAK;AAAA,EACL,UAAU;AAAA;AAAA,EAGV,eAAe;AAAA,EACf,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,iBAAiB;AACnB;AAEA,MAAM,6BAAqD;AAAA,EACzD,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AAAA,EACT,gBAAgB;AAClB;AAMO,SAAS,iBACd,WACoB;AACpB,MAAI,CAAC,aAAc,QAAO;AAC1B,QAAM,QACH,YAAY,qBAAqB,UAAU,YAAA,CAAa,IAAI,WAC7D;AACF,SAAO,GAAG,YAAY,GAAG,IAAI;AAC/B;AAMO,SAAS,gBAAgB,WAA8C;AAC5E,MAAI,CAAC,UAAW,QAAO;AACvB,SAAO,2BAA2B,UAAU,YAAA,CAAa,KAAK;AAChE;"}
1
+ {"version":3,"file":"asset-type-icons.js","sources":["../../../../../../src/chat-ui/mentions/suggestions/data/sources/asset-type-icons.ts"],"sourcesContent":["/**\n * Maps Athena asset types to SVG icon file names.\n *\n * The SVG files are bundled by the host app, which declares where they are\n * served from via `configureAssetIconBasePath`. Hosts that don't bundle them\n * fall back to the Lucide icons carried on every menu item.\n */\n\nlet iconBasePath: string | null = null;\n\n/**\n * Point the mention UI at the host's copy of the asset-type SVGs.\n * `basePath` is resolved against the host document; a trailing \"/\" is added\n * when the caller omits it.\n */\nexport function configureAssetIconBasePath({\n basePath,\n}: {\n basePath: string | null;\n}): void {\n iconBasePath = basePath ? basePath.replace(/\\/*$/, \"/\") : basePath;\n}\n\nconst ASSET_TYPE_ICON_FILE: Record<string, string> = {\n // Documents\n folder: \"ic-folder\",\n txt: \"ic-txt\",\n document: \"ic-word\",\n word_document: \"ic-word\",\n markdown: \"ic-md\",\n pdf: \"ic-pdf\",\n html: \"ic-html\",\n web_page: \"ic-html\",\n yaml: \"ic-yaml\",\n json: \"ic-json\",\n\n // Spreadsheets\n spreadsheet: \"ic-excel\",\n table: \"ic-table\",\n\n // Media\n image: \"ic-img\",\n audio: \"ic-audio\",\n video: \"ic-video\",\n voice_memo: \"ic-voice_memo\",\n\n // Presentations\n powerpoint_deck: \"ic-power_point\",\n presentation_pptx_studio: \"ic-power_point\",\n pptx: \"ic-power_point\",\n\n // Email & Calendar\n email: \"ic-mail\",\n email_message: \"ic-mail\",\n live_email_thread: \"ic-mail\",\n live_calendar_event: \"ic-mail\",\n\n // Code\n python: \"ic-py\",\n javascript: \"ic-js\",\n typescript: \"ic-code\",\n jsx: \"ic-code\",\n tsx: \"ic-code\",\n css: \"ic-code\",\n java: \"ic-code\",\n c: \"ic-code\",\n cpp: \"ic-code\",\n csharp: \"ic-code\",\n ruby: \"ic-code\",\n go: \"ic-code\",\n swift: \"ic-code\",\n php: \"ic-code\",\n rust: \"ic-code\",\n vue: \"ic-code\",\n wm_script: \"ic-code\",\n\n // Structured data\n database: \"ic-snippet\",\n schema: \"ic-schema\",\n catalog: \"ic-db\",\n drive: \"ic-db\",\n query_snippet: \"ic-snippet\",\n semantic_query: \"ic-snippet\",\n semantic_model: \"ic-db\",\n dataset: \"ic-db\",\n\n // Meetings & Calls\n meeting: \"ic-meeting\",\n video_session: \"ic-meeting\",\n phone_call: \"ic-phonecall\",\n\n // Sessions & Devices\n session: \"ic-session\",\n browse_session: \"ic-html\",\n virtual_machine: \"ic-html\",\n computer: \"ic-computer\",\n desktop_session: \"ic-desktop_session\",\n mobile_session: \"ic-mobile_session\",\n\n // Projects & Profiles\n project: \"ic-project\",\n browse_profile: \"ic-profile\",\n\n // Workflows & Automation\n aop: \"ic-flow\",\n playbook: \"ic-playbook\",\n policy: \"ic-policy\",\n flow: \"ic-flow\",\n transform_project: \"ic-transform_project\",\n\n // Collections & Notebooks\n collection: \"ic-collection\",\n notebook: \"ic-notebook\",\n\n // UI & Apps\n user_interface: \"ic-app\",\n application: \"ic-application\",\n canvas: \"ic-canvas\",\n dashboard: \"ic-dashboard\",\n figure: \"ic_figure\",\n\n // People & Companies\n person: \"ic-person\",\n company: \"ic-company\",\n\n // Misc\n prompt: \"ic-ai\",\n skill: \"ic-skill\",\n task_item: \"ic-task\",\n spaces: \"ic-spaces\",\n secret: \"ic-secret\",\n photoshop: \"ic-pts\",\n illustrator: \"ic-ai\",\n zip: \"ic-zip\",\n sevenzip: \"ic-zip\",\n\n // Google Drive / SharePoint / Salesforce\n gdrive_folder: \"ic-folder\",\n gdrive_file: \"ic-file\",\n salesforce_record: \"ic-db\",\n salesforce_file: \"ic-db\",\n};\n\nconst ASSET_TYPE_LUCIDE_FALLBACK: Record<string, string> = {\n folder: \"folder\",\n spreadsheet: \"sheet\",\n image: \"image\",\n video: \"video\",\n audio: \"headphones\",\n email: \"mail\",\n database: \"database\",\n project: \"folder-kanban\",\n meeting: \"video\",\n user_interface: \"layout\",\n};\n\n/**\n * Get the SVG thumbnail URL for an Athena asset type, or undefined when the\n * host app does not bundle the icon files.\n */\nexport function assetTypeToThumb(\n assetType: string | null | undefined,\n): string | undefined {\n if (!iconBasePath) return undefined;\n const name: string =\n (assetType ? ASSET_TYPE_ICON_FILE[assetType.toLowerCase()] : undefined) ??\n \"ic-file\";\n return `${iconBasePath}${name}.svg`;\n}\n\n/**\n * Get Lucide icon name fallback for an asset type.\n * Used when thumbnailUrl can't load.\n */\nexport function assetTypeToIcon(assetType: string | null | undefined): string {\n if (!assetType) return \"file\";\n return ASSET_TYPE_LUCIDE_FALLBACK[assetType.toLowerCase()] ?? \"file\";\n}\n"],"names":[],"mappings":"AAQA,IAAI,eAA8B;AAO3B,SAAS,2BAA2B;AAAA,EACzC;AACF,GAES;AACP,iBAAe,WAAW,SAAS,QAAQ,QAAQ,GAAG,IAAI;AAC5D;AAEA,MAAM,uBAA+C;AAAA;AAAA,EAEnD,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,UAAU;AAAA,EACV,eAAe;AAAA,EACf,UAAU;AAAA,EACV,KAAK;AAAA,EACL,MAAM;AAAA,EACN,UAAU;AAAA,EACV,MAAM;AAAA,EACN,MAAM;AAAA;AAAA,EAGN,aAAa;AAAA,EACb,OAAO;AAAA;AAAA,EAGP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,YAAY;AAAA;AAAA,EAGZ,iBAAiB;AAAA,EACjB,0BAA0B;AAAA,EAC1B,MAAM;AAAA;AAAA,EAGN,OAAO;AAAA,EACP,eAAe;AAAA,EACf,mBAAmB;AAAA,EACnB,qBAAqB;AAAA;AAAA,EAGrB,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,MAAM;AAAA,EACN,GAAG;AAAA,EACH,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,KAAK;AAAA,EACL,MAAM;AAAA,EACN,KAAK;AAAA,EACL,WAAW;AAAA;AAAA,EAGX,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,OAAO;AAAA,EACP,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,SAAS;AAAA;AAAA,EAGT,SAAS;AAAA,EACT,eAAe;AAAA,EACf,YAAY;AAAA;AAAA,EAGZ,SAAS;AAAA,EACT,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,UAAU;AAAA,EACV,iBAAiB;AAAA,EACjB,gBAAgB;AAAA;AAAA,EAGhB,SAAS;AAAA,EACT,gBAAgB;AAAA;AAAA,EAGhB,KAAK;AAAA,EACL,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,mBAAmB;AAAA;AAAA,EAGnB,YAAY;AAAA,EACZ,UAAU;AAAA;AAAA,EAGV,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,QAAQ;AAAA;AAAA,EAGR,QAAQ;AAAA,EACR,SAAS;AAAA;AAAA,EAGT,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,aAAa;AAAA,EACb,KAAK;AAAA,EACL,UAAU;AAAA;AAAA,EAGV,eAAe;AAAA,EACf,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,iBAAiB;AACnB;AAEA,MAAM,6BAAqD;AAAA,EACzD,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AAAA,EACT,gBAAgB;AAClB;AAMO,SAAS,iBACd,WACoB;AACpB,MAAI,CAAC,aAAc,QAAO;AAC1B,QAAM,QACH,YAAY,qBAAqB,UAAU,YAAA,CAAa,IAAI,WAC7D;AACF,SAAO,GAAG,YAAY,GAAG,IAAI;AAC/B;AAMO,SAAS,gBAAgB,WAA8C;AAC5E,MAAI,CAAC,UAAW,QAAO;AACvB,SAAO,2BAA2B,UAAU,YAAA,CAAa,KAAK;AAChE;"}
@@ -10,8 +10,15 @@ const statewireTransport = require("./statewire-transport.cjs");
10
10
  function conversationIndexUrlFromStatewireUrl(statewireUrl) {
11
11
  return statewireUrl.replace(/\/threads\/?$/, "/conversation-index");
12
12
  }
13
+ function conversationIndexId({
14
+ workspaceId,
15
+ userId
16
+ }) {
17
+ return `${workspaceId}-${userId}`;
18
+ }
13
19
  function useConversationIndex({
14
20
  baseUrl,
21
+ workspaceId,
15
22
  userId,
16
23
  sse = false,
17
24
  headers,
@@ -20,28 +27,29 @@ function useConversationIndex({
20
27
  var _a, _b, _c;
21
28
  const headersRef = react.useRef(headers);
22
29
  headersRef.current = headers;
30
+ const indexId = conversationIndexId({ workspaceId, userId });
23
31
  const [parked, setParked] = react.useState(
24
32
  null
25
33
  );
26
- const denialRef = react.useRef({ userId, state: authDenial.initialAuthDenialTrackerState() });
27
- if (denialRef.current.userId !== userId) {
28
- denialRef.current = { userId, state: authDenial.initialAuthDenialTrackerState() };
34
+ const denialRef = react.useRef({ indexId, state: authDenial.initialAuthDenialTrackerState() });
35
+ if (denialRef.current.indexId !== indexId) {
36
+ denialRef.current = { indexId, state: authDenial.initialAuthDenialTrackerState() };
29
37
  }
30
- const isParked = parked !== null && parked.userId === userId;
38
+ const isParked = parked !== null && parked.indexId === indexId;
31
39
  const client = statewire.useStatewire({
32
40
  transport: tap.withKey(
33
- userId,
41
+ indexId,
34
42
  !enabled || isParked ? statewireSuspended.StatewireSuspended({
35
- message: (parked == null ? void 0 : parked.message) ?? (enabled ? "Conversation index authorization was refused." : "Conversation index is waiting for a signed-in user."),
43
+ message: (parked == null ? void 0 : parked.message) ?? (enabled ? "Conversation index authorization was refused." : "Conversation index is waiting for a signed-in user and workspace."),
36
44
  ...enabled ? {
37
45
  onReconnectRequested: () => {
38
- denialRef.current = { userId, state: authDenial.initialAuthDenialTrackerState() };
46
+ denialRef.current = { indexId, state: authDenial.initialAuthDenialTrackerState() };
39
47
  setParked(null);
40
48
  }
41
49
  } : {}
42
50
  }) : statewireTransport.statewireTransport(
43
51
  {
44
- url: `${baseUrl.replace(/\/+$/, "")}/${encodeURIComponent(userId)}`,
52
+ url: `${baseUrl.replace(/\/+$/, "")}/${encodeURIComponent(indexId)}`,
45
53
  headers: (ctx) => headersRef.current(ctx)
46
54
  },
47
55
  { sse }
@@ -59,9 +67,9 @@ function useConversationIndex({
59
67
  react.useEffect(() => {
60
68
  if (!enabled || !shouldPark) return;
61
69
  setParked(
62
- (prev) => (prev == null ? void 0 : prev.userId) === userId ? prev : { userId, message: parkMessage }
70
+ (prev) => (prev == null ? void 0 : prev.indexId) === indexId ? prev : { indexId, message: parkMessage }
63
71
  );
64
- }, [enabled, shouldPark, parkMessage, userId]);
72
+ }, [enabled, shouldPark, parkMessage, indexId]);
65
73
  const commandsRef = react.useRef(client.commands);
66
74
  commandsRef.current = client.commands;
67
75
  const refresh = react.useCallback(() => commandsRef.current.refresh(), []);
@@ -75,6 +83,7 @@ function useConversationIndex({
75
83
  loadMore
76
84
  };
77
85
  }
86
+ exports.conversationIndexId = conversationIndexId;
78
87
  exports.conversationIndexUrlFromStatewireUrl = conversationIndexUrlFromStatewireUrl;
79
88
  exports.useConversationIndex = useConversationIndex;
80
89
  //# sourceMappingURL=conversation-index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"conversation-index.cjs","sources":["../../../../../agent-runtime/src/conversation-index.ts"],"sourcesContent":["import { withKey } from '@assistant-ui/tap';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { useStatewire, type StatewireClient } from 'statewire';\nimport { initialAuthDenialTrackerState, observeAuthDenial } from './auth-denial';\nimport { projectDeepAgentConnection } from './errors';\nimport { StatewireSuspended } from './statewire-suspended';\nimport { statewireTransport } from './statewire-transport';\nimport type { DeepAgentHeadersResolver } from './use-deep-agent-thread';\n\n/**\n * Client side of the per-user conversation-index statewire (issue #29088):\n * agora replicates the newest page of the user's conversation list as one\n * state object, so thread creation / lifecycle / title changes arrive as ops\n * instead of newest-page polling. The replicated rows are renderable and the\n * typed commands extend or refresh the inclusive window; reconnects are free\n * because the protocol's snapshot-first attach IS the newest-page reconcile.\n */\nexport interface ConversationIndexEntry {\n thread_id: string;\n title: string;\n updated_at: string;\n state: string | null;\n runtime: string | null;\n start_channel: string | null;\n asset_type: string;\n asset_id: string | null;\n app_id: string | null;\n trigger_type: string | null;\n last_message_preview: string | null;\n message_count: number | null;\n is_unread: boolean | null;\n /**\n * Canonical status_v2 planes, present once the publishing agora reports them\n * (absent from snapshots replicated by an older pod during a rollout).\n */\n session_status?: string | null;\n run_status?: string | null;\n}\n\ninterface ConversationIndexState {\n conversations?: ConversationIndexEntry[];\n has_more?: boolean;\n total_count?: number;\n}\n\ninterface ConversationIndexCommands {\n [key: string]: (...args: never[]) => unknown;\n refresh(): void;\n load_more(): void;\n}\n\nexport interface UseConversationIndexOptions {\n /** Base URL of the conversation-index host (the `…/v2/conversation-index` mount). */\n baseUrl: string;\n /** The signed-in user's id — the index instance id. Callers gate mounting on it. */\n userId: string;\n /** Transport selection: SSE when true, WebSocket otherwise. */\n sse?: boolean;\n headers: DeepAgentHeadersResolver;\n /** Park the transport until the caller has a resolved principal. */\n enabled?: boolean;\n}\n\nexport interface UseConversationIndexResult {\n /** Newest-page entries, or `null` until the attach snapshot applies. */\n entries: ConversationIndexEntry[] | null;\n hasNextPage: boolean;\n totalCount: number | null;\n connection: StatewireClient.Connection;\n refresh: () => Promise<void>;\n loadMore: () => Promise<void>;\n}\n\n/** The conversation-index host is mounted beside the threads host. */\nexport function conversationIndexUrlFromStatewireUrl(statewireUrl: string): string {\n return statewireUrl.replace(/\\/threads\\/?$/, '/conversation-index');\n}\n\n/**\n * Attach to the user's live conversation index.\n */\nexport function useConversationIndex({\n baseUrl,\n userId,\n sse = false,\n headers,\n enabled = true,\n}: UseConversationIndexOptions): UseConversationIndexResult {\n const headersRef = useRef(headers);\n headersRef.current = headers;\n // Auth-denial park (same policy as the thread transport): the index is a\n // pure push signal, so after a streak of `unauthorized` denials the retry\n // loop stops instead of hammering the attach endpoint every 30s for as long\n // as the tab lives. A remount or the parked connection's `reconnect()`\n // re-arms it.\n const [parked, setParked] = useState<{ userId: string; message: string | undefined } | null>(\n null\n );\n const denialRef = useRef({ userId, state: initialAuthDenialTrackerState() });\n if (denialRef.current.userId !== userId) {\n denialRef.current = { userId, state: initialAuthDenialTrackerState() };\n }\n const isParked = parked !== null && parked.userId === userId;\n const client = useStatewire<ConversationIndexState | undefined, ConversationIndexCommands>({\n transport: withKey(\n userId,\n !enabled || isParked\n ? StatewireSuspended<ConversationIndexCommands>({\n message:\n parked?.message ??\n (enabled\n ? 'Conversation index authorization was refused.'\n : 'Conversation index is waiting for a signed-in user.'),\n ...(enabled\n ? {\n onReconnectRequested: () => {\n denialRef.current = { userId, state: initialAuthDenialTrackerState() };\n setParked(null);\n },\n }\n : {}),\n })\n : statewireTransport<ConversationIndexState, ConversationIndexCommands>(\n {\n url: `${baseUrl.replace(/\\/+$/, '')}/${encodeURIComponent(userId)}`,\n headers: (ctx: { staleAuth: boolean }) => headersRef.current(ctx),\n },\n { sse }\n )\n ),\n initialState: undefined,\n });\n // Observing during render is idempotent (the tracker dedupes by attempt\n // number), so a re-render of the same connection state cannot double-count.\n // The tracker consumes the projected vocabulary, never the raw connection.\n const denial = observeAuthDenial(\n denialRef.current.state,\n projectDeepAgentConnection(client.connection)\n );\n denialRef.current.state = denial.state;\n const shouldPark = denial.park;\n const parkMessage = denial.state.message;\n useEffect(() => {\n if (!enabled || !shouldPark) return;\n setParked((prev) =>\n prev?.userId === userId ? prev : { userId, message: parkMessage }\n );\n }, [enabled, shouldPark, parkMessage, userId]);\n const commandsRef = useRef(client.commands);\n commandsRef.current = client.commands;\n const refresh = useCallback(() => commandsRef.current.refresh(), []);\n const loadMore = useCallback(() => commandsRef.current.load_more(), []);\n return {\n entries: client.state?.conversations ?? null,\n hasNextPage: client.state?.has_more ?? false,\n totalCount: client.state?.total_count ?? null,\n connection: client.connection,\n refresh,\n loadMore,\n };\n}\n\n/**\n * Stable change fingerprint over the entries a list surface reacts to. An\n * empty string means \"no snapshot yet\" — effects keyed on the fingerprint\n * should treat it as a no-op rather than an empty list. An applied-but-empty\n * snapshot serializes to `'[]'`, so deleting the last conversation still\n * changes the fingerprint. JSON-encoding keeps arbitrary titles from\n * colliding across field or entry boundaries.\n */\nexport function conversationIndexFingerprint(\n entries: ConversationIndexEntry[] | null\n): string {\n if (entries === null) return '';\n return JSON.stringify(\n entries.map((entry) => [\n entry.thread_id,\n entry.updated_at,\n entry.state,\n entry.title,\n entry.run_status ?? null,\n entry.is_unread ?? null,\n ])\n );\n}\n"],"names":["useRef","useState","initialAuthDenialTrackerState","useStatewire","withKey","StatewireSuspended","statewireTransport","observeAuthDenial","projectDeepAgentConnection","useEffect","useCallback"],"mappings":";;;;;;;;;AA0EO,SAAS,qCAAqC,cAA8B;AACjF,SAAO,aAAa,QAAQ,iBAAiB,qBAAqB;AACpE;AAKO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA,MAAM;AAAA,EACN;AAAA,EACA,UAAU;AACZ,GAA4D;;AAC1D,QAAM,aAAaA,MAAAA,OAAO,OAAO;AACjC,aAAW,UAAU;AAMrB,QAAM,CAAC,QAAQ,SAAS,IAAIC,MAAAA;AAAAA,IAC1B;AAAA,EAAA;AAEF,QAAM,YAAYD,MAAAA,OAAO,EAAE,QAAQ,OAAOE,WAAAA,8BAAA,GAAiC;AAC3E,MAAI,UAAU,QAAQ,WAAW,QAAQ;AACvC,cAAU,UAAU,EAAE,QAAQ,OAAOA,WAAAA,gCAA8B;AAAA,EACrE;AACA,QAAM,WAAW,WAAW,QAAQ,OAAO,WAAW;AACtD,QAAM,SAASC,UAAAA,aAA4E;AAAA,IACzF,WAAWC,IAAAA;AAAAA,MACT;AAAA,MACA,CAAC,WAAW,WACRC,sCAA8C;AAAA,QAC5C,UACE,iCAAQ,aACP,UACG,kDACA;AAAA,QACN,GAAI,UACA;AAAA,UACE,sBAAsB,MAAM;AAC1B,sBAAU,UAAU,EAAE,QAAQ,OAAOH,WAAAA,gCAA8B;AACnE,sBAAU,IAAI;AAAA,UAChB;AAAA,QAAA,IAEF,CAAA;AAAA,MAAC,CACN,IACDI,mBAAAA;AAAAA,QACE;AAAA,UACE,KAAK,GAAG,QAAQ,QAAQ,QAAQ,EAAE,CAAC,IAAI,mBAAmB,MAAM,CAAC;AAAA,UACjE,SAAS,CAAC,QAAgC,WAAW,QAAQ,GAAG;AAAA,QAAA;AAAA,QAElE,EAAE,IAAA;AAAA,MAAI;AAAA,IACR;AAAA,IAEN,cAAc;AAAA,EAAA,CACf;AAID,QAAM,SAASC,WAAAA;AAAAA,IACb,UAAU,QAAQ;AAAA,IAClBC,OAAAA,2BAA2B,OAAO,UAAU;AAAA,EAAA;AAE9C,YAAU,QAAQ,QAAQ,OAAO;AACjC,QAAM,aAAa,OAAO;AAC1B,QAAM,cAAc,OAAO,MAAM;AACjCC,QAAAA,UAAU,MAAM;AACd,QAAI,CAAC,WAAW,CAAC,WAAY;AAC7B;AAAA,MAAU,CAAC,UACT,6BAAM,YAAW,SAAS,OAAO,EAAE,QAAQ,SAAS,YAAA;AAAA,IAAY;AAAA,EAEpE,GAAG,CAAC,SAAS,YAAY,aAAa,MAAM,CAAC;AAC7C,QAAM,cAAcT,MAAAA,OAAO,OAAO,QAAQ;AAC1C,cAAY,UAAU,OAAO;AAC7B,QAAM,UAAUU,MAAAA,YAAY,MAAM,YAAY,QAAQ,QAAA,GAAW,EAAE;AACnE,QAAM,WAAWA,MAAAA,YAAY,MAAM,YAAY,QAAQ,UAAA,GAAa,EAAE;AACtE,SAAO;AAAA,IACL,WAAS,YAAO,UAAP,mBAAc,kBAAiB;AAAA,IACxC,eAAa,YAAO,UAAP,mBAAc,aAAY;AAAA,IACvC,cAAY,YAAO,UAAP,mBAAc,gBAAe;AAAA,IACzC,YAAY,OAAO;AAAA,IACnB;AAAA,IACA;AAAA,EAAA;AAEJ;;;"}
1
+ {"version":3,"file":"conversation-index.cjs","sources":["../../../../../agent-runtime/src/conversation-index.ts"],"sourcesContent":["import { withKey } from '@assistant-ui/tap';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { useStatewire, type StatewireClient } from 'statewire';\nimport { initialAuthDenialTrackerState, observeAuthDenial } from './auth-denial';\nimport { projectDeepAgentConnection } from './errors';\nimport { StatewireSuspended } from './statewire-suspended';\nimport { statewireTransport } from './statewire-transport';\nimport type { DeepAgentHeadersResolver } from './use-deep-agent-thread';\n\n/**\n * Client side of the conversation-index statewire (issue #29088): agora\n * replicates the newest page of one viewer's conversation list as one state\n * object, so thread creation / lifecycle / title changes arrive as ops\n * instead of newest-page polling. The replicated rows are renderable and the\n * typed commands extend or refresh the inclusive window; reconnects are free\n * because the protocol's snapshot-first attach IS the newest-page reconcile.\n *\n * A viewer is a `(workspaceId, userId)` pair and every pair is its own\n * instance (`/conversation-index/<workspaceId>-<userId>`): workspaces are\n * tenants, so a tab in one workspace never shares a window with a tab in\n * another, and switching workspaces attaches to a different instance instead\n * of retagging the one other tabs are still reading.\n */\nexport interface ConversationIndexEntry {\n thread_id: string;\n title: string;\n updated_at: string;\n state: string | null;\n runtime: string | null;\n start_channel: string | null;\n asset_type: string;\n asset_id: string | null;\n app_id: string | null;\n trigger_type: string | null;\n last_message_preview: string | null;\n message_count: number | null;\n is_unread: boolean | null;\n /**\n * Canonical status_v2 planes, present once the publishing agora reports them\n * (absent from snapshots replicated by an older pod during a rollout).\n */\n session_status?: string | null;\n run_status?: string | null;\n}\n\ninterface ConversationIndexState {\n conversations?: ConversationIndexEntry[];\n has_more?: boolean;\n total_count?: number;\n}\n\ninterface ConversationIndexCommands {\n [key: string]: (...args: never[]) => unknown;\n refresh(): void;\n load_more(): void;\n}\n\nexport interface UseConversationIndexOptions {\n /** Base URL of the conversation-index host (the `…/v2/conversation-index` mount). */\n baseUrl: string;\n /** The workspace being viewed — half of the instance id. Callers gate mounting on it. */\n workspaceId: string;\n /** The signed-in user's id — the other half. Callers gate mounting on it. */\n userId: string;\n /** Transport selection: SSE when true, WebSocket otherwise. */\n sse?: boolean;\n headers: DeepAgentHeadersResolver;\n /** Park the transport until the caller has a resolved principal. */\n enabled?: boolean;\n}\n\nexport interface UseConversationIndexResult {\n /** Newest-page entries, or `null` until the attach snapshot applies. */\n entries: ConversationIndexEntry[] | null;\n hasNextPage: boolean;\n totalCount: number | null;\n connection: StatewireClient.Connection;\n refresh: () => Promise<void>;\n loadMore: () => Promise<void>;\n}\n\n/** The conversation-index host is mounted beside the threads host. */\nexport function conversationIndexUrlFromStatewireUrl(statewireUrl: string): string {\n return statewireUrl.replace(/\\/threads\\/?$/, '/conversation-index');\n}\n\n/** Instance id of one viewer's index; mirrors agora's `conversation_index_id`. */\nexport function conversationIndexId({\n workspaceId,\n userId,\n}: {\n workspaceId: string;\n userId: string;\n}): string {\n return `${workspaceId}-${userId}`;\n}\n\n/**\n * Attach to the live conversation index of one `(workspace, user)` viewer.\n */\nexport function useConversationIndex({\n baseUrl,\n workspaceId,\n userId,\n sse = false,\n headers,\n enabled = true,\n}: UseConversationIndexOptions): UseConversationIndexResult {\n const headersRef = useRef(headers);\n headersRef.current = headers;\n const indexId = conversationIndexId({ workspaceId, userId });\n // Auth-denial park (same policy as the thread transport): the index is a\n // pure push signal, so after a streak of `unauthorized` denials the retry\n // loop stops instead of hammering the attach endpoint every 30s for as long\n // as the tab lives. A remount or the parked connection's `reconnect()`\n // re-arms it.\n const [parked, setParked] = useState<{ indexId: string; message: string | undefined } | null>(\n null\n );\n const denialRef = useRef({ indexId, state: initialAuthDenialTrackerState() });\n if (denialRef.current.indexId !== indexId) {\n denialRef.current = { indexId, state: initialAuthDenialTrackerState() };\n }\n const isParked = parked !== null && parked.indexId === indexId;\n const client = useStatewire<ConversationIndexState | undefined, ConversationIndexCommands>({\n transport: withKey(\n indexId,\n !enabled || isParked\n ? StatewireSuspended<ConversationIndexCommands>({\n message:\n parked?.message ??\n (enabled\n ? 'Conversation index authorization was refused.'\n : 'Conversation index is waiting for a signed-in user and workspace.'),\n ...(enabled\n ? {\n onReconnectRequested: () => {\n denialRef.current = { indexId, state: initialAuthDenialTrackerState() };\n setParked(null);\n },\n }\n : {}),\n })\n : statewireTransport<ConversationIndexState, ConversationIndexCommands>(\n {\n url: `${baseUrl.replace(/\\/+$/, '')}/${encodeURIComponent(indexId)}`,\n headers: (ctx: { staleAuth: boolean }) => headersRef.current(ctx),\n },\n { sse }\n )\n ),\n initialState: undefined,\n });\n // Observing during render is idempotent (the tracker dedupes by attempt\n // number), so a re-render of the same connection state cannot double-count.\n // The tracker consumes the projected vocabulary, never the raw connection.\n const denial = observeAuthDenial(\n denialRef.current.state,\n projectDeepAgentConnection(client.connection)\n );\n denialRef.current.state = denial.state;\n const shouldPark = denial.park;\n const parkMessage = denial.state.message;\n useEffect(() => {\n if (!enabled || !shouldPark) return;\n setParked((prev) =>\n prev?.indexId === indexId ? prev : { indexId, message: parkMessage }\n );\n }, [enabled, shouldPark, parkMessage, indexId]);\n const commandsRef = useRef(client.commands);\n commandsRef.current = client.commands;\n const refresh = useCallback(() => commandsRef.current.refresh(), []);\n const loadMore = useCallback(() => commandsRef.current.load_more(), []);\n return {\n entries: client.state?.conversations ?? null,\n hasNextPage: client.state?.has_more ?? false,\n totalCount: client.state?.total_count ?? null,\n connection: client.connection,\n refresh,\n loadMore,\n };\n}\n\n/**\n * Stable change fingerprint over the entries a list surface reacts to. An\n * empty string means \"no snapshot yet\" — effects keyed on the fingerprint\n * should treat it as a no-op rather than an empty list. An applied-but-empty\n * snapshot serializes to `'[]'`, so deleting the last conversation still\n * changes the fingerprint. JSON-encoding keeps arbitrary titles from\n * colliding across field or entry boundaries.\n */\nexport function conversationIndexFingerprint(\n entries: ConversationIndexEntry[] | null\n): string {\n if (entries === null) return '';\n return JSON.stringify(\n entries.map((entry) => [\n entry.thread_id,\n entry.updated_at,\n entry.state,\n entry.title,\n entry.run_status ?? null,\n entry.is_unread ?? null,\n ])\n );\n}\n"],"names":["useRef","useState","initialAuthDenialTrackerState","useStatewire","withKey","StatewireSuspended","statewireTransport","observeAuthDenial","projectDeepAgentConnection","useEffect","useCallback"],"mappings":";;;;;;;;;AAkFO,SAAS,qCAAqC,cAA8B;AACjF,SAAO,aAAa,QAAQ,iBAAiB,qBAAqB;AACpE;AAGO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AACF,GAGW;AACT,SAAO,GAAG,WAAW,IAAI,MAAM;AACjC;AAKO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA,MAAM;AAAA,EACN;AAAA,EACA,UAAU;AACZ,GAA4D;;AAC1D,QAAM,aAAaA,MAAAA,OAAO,OAAO;AACjC,aAAW,UAAU;AACrB,QAAM,UAAU,oBAAoB,EAAE,aAAa,QAAQ;AAM3D,QAAM,CAAC,QAAQ,SAAS,IAAIC,MAAAA;AAAAA,IAC1B;AAAA,EAAA;AAEF,QAAM,YAAYD,MAAAA,OAAO,EAAE,SAAS,OAAOE,WAAAA,8BAAA,GAAiC;AAC5E,MAAI,UAAU,QAAQ,YAAY,SAAS;AACzC,cAAU,UAAU,EAAE,SAAS,OAAOA,WAAAA,gCAA8B;AAAA,EACtE;AACA,QAAM,WAAW,WAAW,QAAQ,OAAO,YAAY;AACvD,QAAM,SAASC,UAAAA,aAA4E;AAAA,IACzF,WAAWC,IAAAA;AAAAA,MACT;AAAA,MACA,CAAC,WAAW,WACRC,sCAA8C;AAAA,QAC5C,UACE,iCAAQ,aACP,UACG,kDACA;AAAA,QACN,GAAI,UACA;AAAA,UACE,sBAAsB,MAAM;AAC1B,sBAAU,UAAU,EAAE,SAAS,OAAOH,WAAAA,gCAA8B;AACpE,sBAAU,IAAI;AAAA,UAChB;AAAA,QAAA,IAEF,CAAA;AAAA,MAAC,CACN,IACDI,mBAAAA;AAAAA,QACE;AAAA,UACE,KAAK,GAAG,QAAQ,QAAQ,QAAQ,EAAE,CAAC,IAAI,mBAAmB,OAAO,CAAC;AAAA,UAClE,SAAS,CAAC,QAAgC,WAAW,QAAQ,GAAG;AAAA,QAAA;AAAA,QAElE,EAAE,IAAA;AAAA,MAAI;AAAA,IACR;AAAA,IAEN,cAAc;AAAA,EAAA,CACf;AAID,QAAM,SAASC,WAAAA;AAAAA,IACb,UAAU,QAAQ;AAAA,IAClBC,OAAAA,2BAA2B,OAAO,UAAU;AAAA,EAAA;AAE9C,YAAU,QAAQ,QAAQ,OAAO;AACjC,QAAM,aAAa,OAAO;AAC1B,QAAM,cAAc,OAAO,MAAM;AACjCC,QAAAA,UAAU,MAAM;AACd,QAAI,CAAC,WAAW,CAAC,WAAY;AAC7B;AAAA,MAAU,CAAC,UACT,6BAAM,aAAY,UAAU,OAAO,EAAE,SAAS,SAAS,YAAA;AAAA,IAAY;AAAA,EAEvE,GAAG,CAAC,SAAS,YAAY,aAAa,OAAO,CAAC;AAC9C,QAAM,cAAcT,MAAAA,OAAO,OAAO,QAAQ;AAC1C,cAAY,UAAU,OAAO;AAC7B,QAAM,UAAUU,MAAAA,YAAY,MAAM,YAAY,QAAQ,QAAA,GAAW,EAAE;AACnE,QAAM,WAAWA,MAAAA,YAAY,MAAM,YAAY,QAAQ,UAAA,GAAa,EAAE;AACtE,SAAO;AAAA,IACL,WAAS,YAAO,UAAP,mBAAc,kBAAiB;AAAA,IACxC,eAAa,YAAO,UAAP,mBAAc,aAAY;AAAA,IACvC,cAAY,YAAO,UAAP,mBAAc,gBAAe;AAAA,IACzC,YAAY,OAAO;AAAA,IACnB;AAAA,IACA;AAAA,EAAA;AAEJ;;;;"}
@@ -8,8 +8,15 @@ import { statewireTransport } from "./statewire-transport.js";
8
8
  function conversationIndexUrlFromStatewireUrl(statewireUrl) {
9
9
  return statewireUrl.replace(/\/threads\/?$/, "/conversation-index");
10
10
  }
11
+ function conversationIndexId({
12
+ workspaceId,
13
+ userId
14
+ }) {
15
+ return `${workspaceId}-${userId}`;
16
+ }
11
17
  function useConversationIndex({
12
18
  baseUrl,
19
+ workspaceId,
13
20
  userId,
14
21
  sse = false,
15
22
  headers,
@@ -18,28 +25,29 @@ function useConversationIndex({
18
25
  var _a, _b, _c;
19
26
  const headersRef = useRef(headers);
20
27
  headersRef.current = headers;
28
+ const indexId = conversationIndexId({ workspaceId, userId });
21
29
  const [parked, setParked] = useState(
22
30
  null
23
31
  );
24
- const denialRef = useRef({ userId, state: initialAuthDenialTrackerState() });
25
- if (denialRef.current.userId !== userId) {
26
- denialRef.current = { userId, state: initialAuthDenialTrackerState() };
32
+ const denialRef = useRef({ indexId, state: initialAuthDenialTrackerState() });
33
+ if (denialRef.current.indexId !== indexId) {
34
+ denialRef.current = { indexId, state: initialAuthDenialTrackerState() };
27
35
  }
28
- const isParked = parked !== null && parked.userId === userId;
36
+ const isParked = parked !== null && parked.indexId === indexId;
29
37
  const client = useStatewire({
30
38
  transport: withKey(
31
- userId,
39
+ indexId,
32
40
  !enabled || isParked ? StatewireSuspended({
33
- message: (parked == null ? void 0 : parked.message) ?? (enabled ? "Conversation index authorization was refused." : "Conversation index is waiting for a signed-in user."),
41
+ message: (parked == null ? void 0 : parked.message) ?? (enabled ? "Conversation index authorization was refused." : "Conversation index is waiting for a signed-in user and workspace."),
34
42
  ...enabled ? {
35
43
  onReconnectRequested: () => {
36
- denialRef.current = { userId, state: initialAuthDenialTrackerState() };
44
+ denialRef.current = { indexId, state: initialAuthDenialTrackerState() };
37
45
  setParked(null);
38
46
  }
39
47
  } : {}
40
48
  }) : statewireTransport(
41
49
  {
42
- url: `${baseUrl.replace(/\/+$/, "")}/${encodeURIComponent(userId)}`,
50
+ url: `${baseUrl.replace(/\/+$/, "")}/${encodeURIComponent(indexId)}`,
43
51
  headers: (ctx) => headersRef.current(ctx)
44
52
  },
45
53
  { sse }
@@ -57,9 +65,9 @@ function useConversationIndex({
57
65
  useEffect(() => {
58
66
  if (!enabled || !shouldPark) return;
59
67
  setParked(
60
- (prev) => (prev == null ? void 0 : prev.userId) === userId ? prev : { userId, message: parkMessage }
68
+ (prev) => (prev == null ? void 0 : prev.indexId) === indexId ? prev : { indexId, message: parkMessage }
61
69
  );
62
- }, [enabled, shouldPark, parkMessage, userId]);
70
+ }, [enabled, shouldPark, parkMessage, indexId]);
63
71
  const commandsRef = useRef(client.commands);
64
72
  commandsRef.current = client.commands;
65
73
  const refresh = useCallback(() => commandsRef.current.refresh(), []);
@@ -74,6 +82,7 @@ function useConversationIndex({
74
82
  };
75
83
  }
76
84
  export {
85
+ conversationIndexId,
77
86
  conversationIndexUrlFromStatewireUrl,
78
87
  useConversationIndex
79
88
  };
@@ -1 +1 @@
1
- {"version":3,"file":"conversation-index.js","sources":["../../../../../agent-runtime/src/conversation-index.ts"],"sourcesContent":["import { withKey } from '@assistant-ui/tap';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { useStatewire, type StatewireClient } from 'statewire';\nimport { initialAuthDenialTrackerState, observeAuthDenial } from './auth-denial';\nimport { projectDeepAgentConnection } from './errors';\nimport { StatewireSuspended } from './statewire-suspended';\nimport { statewireTransport } from './statewire-transport';\nimport type { DeepAgentHeadersResolver } from './use-deep-agent-thread';\n\n/**\n * Client side of the per-user conversation-index statewire (issue #29088):\n * agora replicates the newest page of the user's conversation list as one\n * state object, so thread creation / lifecycle / title changes arrive as ops\n * instead of newest-page polling. The replicated rows are renderable and the\n * typed commands extend or refresh the inclusive window; reconnects are free\n * because the protocol's snapshot-first attach IS the newest-page reconcile.\n */\nexport interface ConversationIndexEntry {\n thread_id: string;\n title: string;\n updated_at: string;\n state: string | null;\n runtime: string | null;\n start_channel: string | null;\n asset_type: string;\n asset_id: string | null;\n app_id: string | null;\n trigger_type: string | null;\n last_message_preview: string | null;\n message_count: number | null;\n is_unread: boolean | null;\n /**\n * Canonical status_v2 planes, present once the publishing agora reports them\n * (absent from snapshots replicated by an older pod during a rollout).\n */\n session_status?: string | null;\n run_status?: string | null;\n}\n\ninterface ConversationIndexState {\n conversations?: ConversationIndexEntry[];\n has_more?: boolean;\n total_count?: number;\n}\n\ninterface ConversationIndexCommands {\n [key: string]: (...args: never[]) => unknown;\n refresh(): void;\n load_more(): void;\n}\n\nexport interface UseConversationIndexOptions {\n /** Base URL of the conversation-index host (the `…/v2/conversation-index` mount). */\n baseUrl: string;\n /** The signed-in user's id — the index instance id. Callers gate mounting on it. */\n userId: string;\n /** Transport selection: SSE when true, WebSocket otherwise. */\n sse?: boolean;\n headers: DeepAgentHeadersResolver;\n /** Park the transport until the caller has a resolved principal. */\n enabled?: boolean;\n}\n\nexport interface UseConversationIndexResult {\n /** Newest-page entries, or `null` until the attach snapshot applies. */\n entries: ConversationIndexEntry[] | null;\n hasNextPage: boolean;\n totalCount: number | null;\n connection: StatewireClient.Connection;\n refresh: () => Promise<void>;\n loadMore: () => Promise<void>;\n}\n\n/** The conversation-index host is mounted beside the threads host. */\nexport function conversationIndexUrlFromStatewireUrl(statewireUrl: string): string {\n return statewireUrl.replace(/\\/threads\\/?$/, '/conversation-index');\n}\n\n/**\n * Attach to the user's live conversation index.\n */\nexport function useConversationIndex({\n baseUrl,\n userId,\n sse = false,\n headers,\n enabled = true,\n}: UseConversationIndexOptions): UseConversationIndexResult {\n const headersRef = useRef(headers);\n headersRef.current = headers;\n // Auth-denial park (same policy as the thread transport): the index is a\n // pure push signal, so after a streak of `unauthorized` denials the retry\n // loop stops instead of hammering the attach endpoint every 30s for as long\n // as the tab lives. A remount or the parked connection's `reconnect()`\n // re-arms it.\n const [parked, setParked] = useState<{ userId: string; message: string | undefined } | null>(\n null\n );\n const denialRef = useRef({ userId, state: initialAuthDenialTrackerState() });\n if (denialRef.current.userId !== userId) {\n denialRef.current = { userId, state: initialAuthDenialTrackerState() };\n }\n const isParked = parked !== null && parked.userId === userId;\n const client = useStatewire<ConversationIndexState | undefined, ConversationIndexCommands>({\n transport: withKey(\n userId,\n !enabled || isParked\n ? StatewireSuspended<ConversationIndexCommands>({\n message:\n parked?.message ??\n (enabled\n ? 'Conversation index authorization was refused.'\n : 'Conversation index is waiting for a signed-in user.'),\n ...(enabled\n ? {\n onReconnectRequested: () => {\n denialRef.current = { userId, state: initialAuthDenialTrackerState() };\n setParked(null);\n },\n }\n : {}),\n })\n : statewireTransport<ConversationIndexState, ConversationIndexCommands>(\n {\n url: `${baseUrl.replace(/\\/+$/, '')}/${encodeURIComponent(userId)}`,\n headers: (ctx: { staleAuth: boolean }) => headersRef.current(ctx),\n },\n { sse }\n )\n ),\n initialState: undefined,\n });\n // Observing during render is idempotent (the tracker dedupes by attempt\n // number), so a re-render of the same connection state cannot double-count.\n // The tracker consumes the projected vocabulary, never the raw connection.\n const denial = observeAuthDenial(\n denialRef.current.state,\n projectDeepAgentConnection(client.connection)\n );\n denialRef.current.state = denial.state;\n const shouldPark = denial.park;\n const parkMessage = denial.state.message;\n useEffect(() => {\n if (!enabled || !shouldPark) return;\n setParked((prev) =>\n prev?.userId === userId ? prev : { userId, message: parkMessage }\n );\n }, [enabled, shouldPark, parkMessage, userId]);\n const commandsRef = useRef(client.commands);\n commandsRef.current = client.commands;\n const refresh = useCallback(() => commandsRef.current.refresh(), []);\n const loadMore = useCallback(() => commandsRef.current.load_more(), []);\n return {\n entries: client.state?.conversations ?? null,\n hasNextPage: client.state?.has_more ?? false,\n totalCount: client.state?.total_count ?? null,\n connection: client.connection,\n refresh,\n loadMore,\n };\n}\n\n/**\n * Stable change fingerprint over the entries a list surface reacts to. An\n * empty string means \"no snapshot yet\" — effects keyed on the fingerprint\n * should treat it as a no-op rather than an empty list. An applied-but-empty\n * snapshot serializes to `'[]'`, so deleting the last conversation still\n * changes the fingerprint. JSON-encoding keeps arbitrary titles from\n * colliding across field or entry boundaries.\n */\nexport function conversationIndexFingerprint(\n entries: ConversationIndexEntry[] | null\n): string {\n if (entries === null) return '';\n return JSON.stringify(\n entries.map((entry) => [\n entry.thread_id,\n entry.updated_at,\n entry.state,\n entry.title,\n entry.run_status ?? null,\n entry.is_unread ?? null,\n ])\n );\n}\n"],"names":[],"mappings":";;;;;;;AA0EO,SAAS,qCAAqC,cAA8B;AACjF,SAAO,aAAa,QAAQ,iBAAiB,qBAAqB;AACpE;AAKO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA,MAAM;AAAA,EACN;AAAA,EACA,UAAU;AACZ,GAA4D;;AAC1D,QAAM,aAAa,OAAO,OAAO;AACjC,aAAW,UAAU;AAMrB,QAAM,CAAC,QAAQ,SAAS,IAAI;AAAA,IAC1B;AAAA,EAAA;AAEF,QAAM,YAAY,OAAO,EAAE,QAAQ,OAAO,8BAAA,GAAiC;AAC3E,MAAI,UAAU,QAAQ,WAAW,QAAQ;AACvC,cAAU,UAAU,EAAE,QAAQ,OAAO,gCAA8B;AAAA,EACrE;AACA,QAAM,WAAW,WAAW,QAAQ,OAAO,WAAW;AACtD,QAAM,SAAS,aAA4E;AAAA,IACzF,WAAW;AAAA,MACT;AAAA,MACA,CAAC,WAAW,WACR,mBAA8C;AAAA,QAC5C,UACE,iCAAQ,aACP,UACG,kDACA;AAAA,QACN,GAAI,UACA;AAAA,UACE,sBAAsB,MAAM;AAC1B,sBAAU,UAAU,EAAE,QAAQ,OAAO,gCAA8B;AACnE,sBAAU,IAAI;AAAA,UAChB;AAAA,QAAA,IAEF,CAAA;AAAA,MAAC,CACN,IACD;AAAA,QACE;AAAA,UACE,KAAK,GAAG,QAAQ,QAAQ,QAAQ,EAAE,CAAC,IAAI,mBAAmB,MAAM,CAAC;AAAA,UACjE,SAAS,CAAC,QAAgC,WAAW,QAAQ,GAAG;AAAA,QAAA;AAAA,QAElE,EAAE,IAAA;AAAA,MAAI;AAAA,IACR;AAAA,IAEN,cAAc;AAAA,EAAA,CACf;AAID,QAAM,SAAS;AAAA,IACb,UAAU,QAAQ;AAAA,IAClB,2BAA2B,OAAO,UAAU;AAAA,EAAA;AAE9C,YAAU,QAAQ,QAAQ,OAAO;AACjC,QAAM,aAAa,OAAO;AAC1B,QAAM,cAAc,OAAO,MAAM;AACjC,YAAU,MAAM;AACd,QAAI,CAAC,WAAW,CAAC,WAAY;AAC7B;AAAA,MAAU,CAAC,UACT,6BAAM,YAAW,SAAS,OAAO,EAAE,QAAQ,SAAS,YAAA;AAAA,IAAY;AAAA,EAEpE,GAAG,CAAC,SAAS,YAAY,aAAa,MAAM,CAAC;AAC7C,QAAM,cAAc,OAAO,OAAO,QAAQ;AAC1C,cAAY,UAAU,OAAO;AAC7B,QAAM,UAAU,YAAY,MAAM,YAAY,QAAQ,QAAA,GAAW,EAAE;AACnE,QAAM,WAAW,YAAY,MAAM,YAAY,QAAQ,UAAA,GAAa,EAAE;AACtE,SAAO;AAAA,IACL,WAAS,YAAO,UAAP,mBAAc,kBAAiB;AAAA,IACxC,eAAa,YAAO,UAAP,mBAAc,aAAY;AAAA,IACvC,cAAY,YAAO,UAAP,mBAAc,gBAAe;AAAA,IACzC,YAAY,OAAO;AAAA,IACnB;AAAA,IACA;AAAA,EAAA;AAEJ;"}
1
+ {"version":3,"file":"conversation-index.js","sources":["../../../../../agent-runtime/src/conversation-index.ts"],"sourcesContent":["import { withKey } from '@assistant-ui/tap';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { useStatewire, type StatewireClient } from 'statewire';\nimport { initialAuthDenialTrackerState, observeAuthDenial } from './auth-denial';\nimport { projectDeepAgentConnection } from './errors';\nimport { StatewireSuspended } from './statewire-suspended';\nimport { statewireTransport } from './statewire-transport';\nimport type { DeepAgentHeadersResolver } from './use-deep-agent-thread';\n\n/**\n * Client side of the conversation-index statewire (issue #29088): agora\n * replicates the newest page of one viewer's conversation list as one state\n * object, so thread creation / lifecycle / title changes arrive as ops\n * instead of newest-page polling. The replicated rows are renderable and the\n * typed commands extend or refresh the inclusive window; reconnects are free\n * because the protocol's snapshot-first attach IS the newest-page reconcile.\n *\n * A viewer is a `(workspaceId, userId)` pair and every pair is its own\n * instance (`/conversation-index/<workspaceId>-<userId>`): workspaces are\n * tenants, so a tab in one workspace never shares a window with a tab in\n * another, and switching workspaces attaches to a different instance instead\n * of retagging the one other tabs are still reading.\n */\nexport interface ConversationIndexEntry {\n thread_id: string;\n title: string;\n updated_at: string;\n state: string | null;\n runtime: string | null;\n start_channel: string | null;\n asset_type: string;\n asset_id: string | null;\n app_id: string | null;\n trigger_type: string | null;\n last_message_preview: string | null;\n message_count: number | null;\n is_unread: boolean | null;\n /**\n * Canonical status_v2 planes, present once the publishing agora reports them\n * (absent from snapshots replicated by an older pod during a rollout).\n */\n session_status?: string | null;\n run_status?: string | null;\n}\n\ninterface ConversationIndexState {\n conversations?: ConversationIndexEntry[];\n has_more?: boolean;\n total_count?: number;\n}\n\ninterface ConversationIndexCommands {\n [key: string]: (...args: never[]) => unknown;\n refresh(): void;\n load_more(): void;\n}\n\nexport interface UseConversationIndexOptions {\n /** Base URL of the conversation-index host (the `…/v2/conversation-index` mount). */\n baseUrl: string;\n /** The workspace being viewed — half of the instance id. Callers gate mounting on it. */\n workspaceId: string;\n /** The signed-in user's id — the other half. Callers gate mounting on it. */\n userId: string;\n /** Transport selection: SSE when true, WebSocket otherwise. */\n sse?: boolean;\n headers: DeepAgentHeadersResolver;\n /** Park the transport until the caller has a resolved principal. */\n enabled?: boolean;\n}\n\nexport interface UseConversationIndexResult {\n /** Newest-page entries, or `null` until the attach snapshot applies. */\n entries: ConversationIndexEntry[] | null;\n hasNextPage: boolean;\n totalCount: number | null;\n connection: StatewireClient.Connection;\n refresh: () => Promise<void>;\n loadMore: () => Promise<void>;\n}\n\n/** The conversation-index host is mounted beside the threads host. */\nexport function conversationIndexUrlFromStatewireUrl(statewireUrl: string): string {\n return statewireUrl.replace(/\\/threads\\/?$/, '/conversation-index');\n}\n\n/** Instance id of one viewer's index; mirrors agora's `conversation_index_id`. */\nexport function conversationIndexId({\n workspaceId,\n userId,\n}: {\n workspaceId: string;\n userId: string;\n}): string {\n return `${workspaceId}-${userId}`;\n}\n\n/**\n * Attach to the live conversation index of one `(workspace, user)` viewer.\n */\nexport function useConversationIndex({\n baseUrl,\n workspaceId,\n userId,\n sse = false,\n headers,\n enabled = true,\n}: UseConversationIndexOptions): UseConversationIndexResult {\n const headersRef = useRef(headers);\n headersRef.current = headers;\n const indexId = conversationIndexId({ workspaceId, userId });\n // Auth-denial park (same policy as the thread transport): the index is a\n // pure push signal, so after a streak of `unauthorized` denials the retry\n // loop stops instead of hammering the attach endpoint every 30s for as long\n // as the tab lives. A remount or the parked connection's `reconnect()`\n // re-arms it.\n const [parked, setParked] = useState<{ indexId: string; message: string | undefined } | null>(\n null\n );\n const denialRef = useRef({ indexId, state: initialAuthDenialTrackerState() });\n if (denialRef.current.indexId !== indexId) {\n denialRef.current = { indexId, state: initialAuthDenialTrackerState() };\n }\n const isParked = parked !== null && parked.indexId === indexId;\n const client = useStatewire<ConversationIndexState | undefined, ConversationIndexCommands>({\n transport: withKey(\n indexId,\n !enabled || isParked\n ? StatewireSuspended<ConversationIndexCommands>({\n message:\n parked?.message ??\n (enabled\n ? 'Conversation index authorization was refused.'\n : 'Conversation index is waiting for a signed-in user and workspace.'),\n ...(enabled\n ? {\n onReconnectRequested: () => {\n denialRef.current = { indexId, state: initialAuthDenialTrackerState() };\n setParked(null);\n },\n }\n : {}),\n })\n : statewireTransport<ConversationIndexState, ConversationIndexCommands>(\n {\n url: `${baseUrl.replace(/\\/+$/, '')}/${encodeURIComponent(indexId)}`,\n headers: (ctx: { staleAuth: boolean }) => headersRef.current(ctx),\n },\n { sse }\n )\n ),\n initialState: undefined,\n });\n // Observing during render is idempotent (the tracker dedupes by attempt\n // number), so a re-render of the same connection state cannot double-count.\n // The tracker consumes the projected vocabulary, never the raw connection.\n const denial = observeAuthDenial(\n denialRef.current.state,\n projectDeepAgentConnection(client.connection)\n );\n denialRef.current.state = denial.state;\n const shouldPark = denial.park;\n const parkMessage = denial.state.message;\n useEffect(() => {\n if (!enabled || !shouldPark) return;\n setParked((prev) =>\n prev?.indexId === indexId ? prev : { indexId, message: parkMessage }\n );\n }, [enabled, shouldPark, parkMessage, indexId]);\n const commandsRef = useRef(client.commands);\n commandsRef.current = client.commands;\n const refresh = useCallback(() => commandsRef.current.refresh(), []);\n const loadMore = useCallback(() => commandsRef.current.load_more(), []);\n return {\n entries: client.state?.conversations ?? null,\n hasNextPage: client.state?.has_more ?? false,\n totalCount: client.state?.total_count ?? null,\n connection: client.connection,\n refresh,\n loadMore,\n };\n}\n\n/**\n * Stable change fingerprint over the entries a list surface reacts to. An\n * empty string means \"no snapshot yet\" — effects keyed on the fingerprint\n * should treat it as a no-op rather than an empty list. An applied-but-empty\n * snapshot serializes to `'[]'`, so deleting the last conversation still\n * changes the fingerprint. JSON-encoding keeps arbitrary titles from\n * colliding across field or entry boundaries.\n */\nexport function conversationIndexFingerprint(\n entries: ConversationIndexEntry[] | null\n): string {\n if (entries === null) return '';\n return JSON.stringify(\n entries.map((entry) => [\n entry.thread_id,\n entry.updated_at,\n entry.state,\n entry.title,\n entry.run_status ?? null,\n entry.is_unread ?? null,\n ])\n );\n}\n"],"names":[],"mappings":";;;;;;;AAkFO,SAAS,qCAAqC,cAA8B;AACjF,SAAO,aAAa,QAAQ,iBAAiB,qBAAqB;AACpE;AAGO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AACF,GAGW;AACT,SAAO,GAAG,WAAW,IAAI,MAAM;AACjC;AAKO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA,MAAM;AAAA,EACN;AAAA,EACA,UAAU;AACZ,GAA4D;;AAC1D,QAAM,aAAa,OAAO,OAAO;AACjC,aAAW,UAAU;AACrB,QAAM,UAAU,oBAAoB,EAAE,aAAa,QAAQ;AAM3D,QAAM,CAAC,QAAQ,SAAS,IAAI;AAAA,IAC1B;AAAA,EAAA;AAEF,QAAM,YAAY,OAAO,EAAE,SAAS,OAAO,8BAAA,GAAiC;AAC5E,MAAI,UAAU,QAAQ,YAAY,SAAS;AACzC,cAAU,UAAU,EAAE,SAAS,OAAO,gCAA8B;AAAA,EACtE;AACA,QAAM,WAAW,WAAW,QAAQ,OAAO,YAAY;AACvD,QAAM,SAAS,aAA4E;AAAA,IACzF,WAAW;AAAA,MACT;AAAA,MACA,CAAC,WAAW,WACR,mBAA8C;AAAA,QAC5C,UACE,iCAAQ,aACP,UACG,kDACA;AAAA,QACN,GAAI,UACA;AAAA,UACE,sBAAsB,MAAM;AAC1B,sBAAU,UAAU,EAAE,SAAS,OAAO,gCAA8B;AACpE,sBAAU,IAAI;AAAA,UAChB;AAAA,QAAA,IAEF,CAAA;AAAA,MAAC,CACN,IACD;AAAA,QACE;AAAA,UACE,KAAK,GAAG,QAAQ,QAAQ,QAAQ,EAAE,CAAC,IAAI,mBAAmB,OAAO,CAAC;AAAA,UAClE,SAAS,CAAC,QAAgC,WAAW,QAAQ,GAAG;AAAA,QAAA;AAAA,QAElE,EAAE,IAAA;AAAA,MAAI;AAAA,IACR;AAAA,IAEN,cAAc;AAAA,EAAA,CACf;AAID,QAAM,SAAS;AAAA,IACb,UAAU,QAAQ;AAAA,IAClB,2BAA2B,OAAO,UAAU;AAAA,EAAA;AAE9C,YAAU,QAAQ,QAAQ,OAAO;AACjC,QAAM,aAAa,OAAO;AAC1B,QAAM,cAAc,OAAO,MAAM;AACjC,YAAU,MAAM;AACd,QAAI,CAAC,WAAW,CAAC,WAAY;AAC7B;AAAA,MAAU,CAAC,UACT,6BAAM,aAAY,UAAU,OAAO,EAAE,SAAS,SAAS,YAAA;AAAA,IAAY;AAAA,EAEvE,GAAG,CAAC,SAAS,YAAY,aAAa,OAAO,CAAC;AAC9C,QAAM,cAAc,OAAO,OAAO,QAAQ;AAC1C,cAAY,UAAU,OAAO;AAC7B,QAAM,UAAU,YAAY,MAAM,YAAY,QAAQ,QAAA,GAAW,EAAE;AACnE,QAAM,WAAW,YAAY,MAAM,YAAY,QAAQ,UAAA,GAAa,EAAE;AACtE,SAAO;AAAA,IACL,WAAS,YAAO,UAAP,mBAAc,kBAAiB;AAAA,IACxC,eAAa,YAAO,UAAP,mBAAc,aAAY;AAAA,IACvC,cAAY,YAAO,UAAP,mBAAc,gBAAe;AAAA,IACzC,YAAY,OAAO;AAAA,IACnB;AAAA,IACA;AAAA,EAAA;AAEJ;"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
- const version = "0.21.0";
3
+ const version = "0.21.1";
4
4
  const packageJson = {
5
5
  version
6
6
  };
@@ -1,4 +1,4 @@
1
- const version = "0.21.0";
1
+ const version = "0.21.1";
2
2
  const packageJson = {
3
3
  version
4
4
  };
@@ -65,6 +65,34 @@ async function listThreads(backendUrl, auth, opts = {}) {
65
65
  throw error;
66
66
  }
67
67
  }
68
+ async function fetchAccountIdentity(backendUrl, auth, opts = {}) {
69
+ const base = getAthenaApiBaseUrl(backendUrl);
70
+ const res = await fetch(`${base}/api/v0/me`, {
71
+ headers: getAuthHeaders(auth),
72
+ signal: opts.signal ?? null
73
+ });
74
+ if (!res.ok) {
75
+ throw new errors.AthenaSdkError({
76
+ code: res.status === 401 || res.status === 403 ? "auth_rejected" : "threads_list_failed",
77
+ message: `Failed to resolve account identity: ${res.status}`,
78
+ status: res.status,
79
+ hint: res.status === 401 || res.status === 403 ? "The credential was refused. Check the token/API key and that the user belongs to the workspace." : "Check backendUrl points at the Agora API base.",
80
+ context: { auth_mode: getAuthMode(auth) }
81
+ });
82
+ }
83
+ const body = await res.json();
84
+ if (typeof body.user_id !== "string" || body.user_id.length === 0) {
85
+ throw new errors.AthenaSdkError({
86
+ code: "threads_list_failed",
87
+ message: "Account identity lookup returned no user id.",
88
+ context: { auth_mode: getAuthMode(auth) }
89
+ });
90
+ }
91
+ return {
92
+ userId: body.user_id,
93
+ workspaceId: typeof body.workspace_id === "string" ? body.workspace_id : null
94
+ };
95
+ }
68
96
  async function setThreadReadState(backendUrl, auth, opts) {
69
97
  const base = getAthenaApiBaseUrl(backendUrl);
70
98
  const res = await fetch(`${base}/api/conversations/threads/read-state`, {
@@ -202,6 +230,7 @@ async function archiveThread(backendUrl, auth, threadId) {
202
230
  }
203
231
  exports.archiveThread = archiveThread;
204
232
  exports.createThread = createThread;
233
+ exports.fetchAccountIdentity = fetchAccountIdentity;
205
234
  exports.getThreadState = getThreadState;
206
235
  exports.listThreads = listThreads;
207
236
  exports.setThreadReadState = setThreadReadState;
@@ -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 { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport { AthenaSdkError } from '../diagnostics/errors';\n\nexport interface ThreadSummary {\n thread_id: string;\n title: string;\n created_at: string;\n updated_at: string;\n last_message: string | null;\n message_count: number;\n state: string | null;\n start_channel?: string | null;\n last_channel?: string | null;\n /** Client app that created the session (SESSION_APP_ID_GUIDE). */\n app_id?: string | null;\n /** Trigger marker for automated/background sessions. */\n trigger_type?: string | null;\n /** Whether the calling user has seen this thread since it last changed. */\n is_unread?: boolean;\n}\n\n/**\n * True for fetch cancellations (unmount/remount aborts, StrictMode double\n * effects). These are lifecycle noise, not failures: emitting them as\n * `*.error` diagnostics floods the console with\n * \"signal is aborted without reason\" for requests the caller itself aborted.\n */\nfunction isAbortError(error: unknown): boolean {\n return (\n (error instanceof DOMException && error.name === 'AbortError') ||\n (error instanceof Error && error.name === 'AbortError')\n );\n}\n\nexport interface ThreadListResponse {\n threads: ThreadSummary[];\n /** Server lookahead result. Older servers may omit this field. */\n has_more?: boolean;\n}\n\nexport interface ThreadCreateResponse {\n thread_id: string;\n}\n\ninterface AuthConfig {\n apiKey?: string;\n token?: string | null;\n}\n\nfunction getAuthHeaders(auth: AuthConfig): Record<string, string> {\n if (auth.token) {\n return { Authorization: `Bearer ${auth.token}` };\n }\n if (auth.apiKey) {\n return { 'X-API-KEY': auth.apiKey };\n }\n return {};\n}\n\n/**\n * Resolves the Athena API base URL from backendUrl.\n * Thread endpoints live under `/api/conversations/threads/*` and `/api/unstable/threads/`.\n * If backendUrl points at a nested Athena endpoint, trim it back to the API base first.\n */\nfunction getAthenaApiBaseUrl(backendUrl: string): string {\n const stripped = backendUrl.replace(/\\/api\\/assistant-ui\\/?$/, '');\n // If the suffix wasn't found, assume the URL already points to the API base\n if (stripped === backendUrl) {\n return backendUrl.replace(/\\/$/, '');\n }\n return stripped;\n}\n\nexport async function listThreads(\n backendUrl: string,\n auth: AuthConfig,\n opts: {\n limit?: number;\n offset?: number;\n exclude_triggered?: boolean;\n app_id?: string;\n start_channel?: string;\n search?: string;\n 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 }),\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\nexport interface ThreadReadStateResponse {\n thread_id: string;\n is_unread: boolean;\n}\n\n/** Store the calling user's read receipt for a thread. */\nexport async function setThreadReadState(\n backendUrl: string,\n auth: AuthConfig,\n opts: { threadId: string; isUnread: boolean },\n): Promise<ThreadReadStateResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/conversations/threads/read-state`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({\n thread_id: opts.threadId,\n is_unread: opts.isUnread,\n }),\n });\n if (!res.ok) {\n throw new Error(`[AthenaSDK] Failed to set thread read state: ${res.status}`);\n }\n return res.json();\n}\n\nexport 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): 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 body: JSON.stringify({\n thread_id: threadId,\n skip_cache: 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\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 throw new Error(`[AthenaSDK] Failed to archive thread: ${res.status}`);\n }\n}\n"],"names":["athenaDiagnostics","AthenaSdkError"],"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 { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport { AthenaSdkError } from '../diagnostics/errors';\n\nexport interface ThreadSummary {\n thread_id: string;\n title: string;\n created_at: string;\n updated_at: string;\n last_message: string | null;\n message_count: number;\n state: string | null;\n start_channel?: string | null;\n last_channel?: string | null;\n /** Client app that created the session (SESSION_APP_ID_GUIDE). */\n app_id?: string | null;\n /** Trigger marker for automated/background sessions. */\n trigger_type?: string | null;\n /** Whether the calling user has seen this thread since it last changed. */\n is_unread?: boolean;\n}\n\n/**\n * True for fetch cancellations (unmount/remount aborts, StrictMode double\n * effects). These are lifecycle noise, not failures: emitting them as\n * `*.error` diagnostics floods the console with\n * \"signal is aborted without reason\" for requests the caller itself aborted.\n */\nfunction isAbortError(error: unknown): boolean {\n return (\n (error instanceof DOMException && error.name === 'AbortError') ||\n (error instanceof Error && error.name === 'AbortError')\n );\n}\n\nexport interface ThreadListResponse {\n threads: ThreadSummary[];\n /** Server lookahead result. Older servers may omit this field. */\n has_more?: boolean;\n}\n\nexport interface ThreadCreateResponse {\n thread_id: string;\n}\n\ninterface AuthConfig {\n apiKey?: string;\n token?: string | null;\n}\n\nfunction getAuthHeaders(auth: AuthConfig): Record<string, string> {\n if (auth.token) {\n return { Authorization: `Bearer ${auth.token}` };\n }\n if (auth.apiKey) {\n return { 'X-API-KEY': auth.apiKey };\n }\n return {};\n}\n\n/**\n * Resolves the Athena API base URL from backendUrl.\n * Thread endpoints live under `/api/conversations/threads/*` and `/api/unstable/threads/`.\n * If backendUrl points at a nested Athena endpoint, trim it back to the API base first.\n */\nfunction getAthenaApiBaseUrl(backendUrl: string): string {\n const stripped = backendUrl.replace(/\\/api\\/assistant-ui\\/?$/, '');\n // If the suffix wasn't found, assume the URL already points to the API base\n if (stripped === backendUrl) {\n return backendUrl.replace(/\\/$/, '');\n }\n return stripped;\n}\n\nexport async function listThreads(\n backendUrl: string,\n auth: AuthConfig,\n opts: {\n limit?: number;\n offset?: number;\n exclude_triggered?: boolean;\n app_id?: string;\n start_channel?: string;\n search?: string;\n 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 }),\n });\n if (!res.ok) {\n throw new AthenaSdkError({\n code: res.status === 401 || res.status === 403 ? 'auth_rejected' : 'threads_list_failed',\n message: `Failed to list threads: ${res.status}`,\n status: res.status,\n hint:\n res.status === 401 || res.status === 403\n ? 'The credential was refused. Check the token/API key and that the user belongs to the workspace.'\n : 'Check backendUrl points at the Agora API base.',\n context: { auth_mode: getAuthMode(auth) },\n });\n }\n const data = (await res.json()) as ThreadListResponse;\n endSpan('threads.list.done', { data: { count: data.threads?.length ?? 0 } });\n return data;\n } catch (error) {\n // Cancellation is lifecycle noise (unmount/remount aborts): log it as an\n // informational abort, not an error — passing `error` would force the\n // span end to error level and spam the console.\n if (isAbortError(error)) {\n endSpan('threads.list.aborted', { data: { reason: 'request aborted' } });\n } else {\n endSpan('threads.list.error', { error });\n }\n throw error;\n }\n}\n\n/** The `(workspace, user)` pair the credential resolves to on the backend. */\nexport interface AccountIdentity {\n userId: string;\n /** `null` for a user with no current workspace. */\n workspaceId: string | null;\n}\n\n/**\n * Resolve the credential's agora identity. The conversation index is one\n * statewire instance per `(workspace, user)`, and neither half is knowable\n * from the credential alone: the SDK's auth context carries an identity\n * provider user id, not agora's, and never a workspace.\n */\nexport async function fetchAccountIdentity(\n backendUrl: string,\n auth: AuthConfig,\n opts: { signal?: AbortSignal } = {},\n): Promise<AccountIdentity> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/v0/me`, {\n headers: getAuthHeaders(auth),\n signal: opts.signal ?? null,\n });\n if (!res.ok) {\n throw new AthenaSdkError({\n code: res.status === 401 || res.status === 403 ? 'auth_rejected' : 'threads_list_failed',\n message: `Failed to resolve account identity: ${res.status}`,\n status: res.status,\n hint:\n res.status === 401 || res.status === 403\n ? 'The credential was refused. Check the token/API key and that the user belongs to the workspace.'\n : 'Check backendUrl points at the Agora API base.',\n context: { auth_mode: getAuthMode(auth) },\n });\n }\n const body = (await res.json()) as { user_id?: unknown; workspace_id?: unknown };\n if (typeof body.user_id !== 'string' || body.user_id.length === 0) {\n throw new AthenaSdkError({\n code: 'threads_list_failed',\n message: 'Account identity lookup returned no user id.',\n context: { auth_mode: getAuthMode(auth) },\n });\n }\n return {\n userId: body.user_id,\n workspaceId: typeof body.workspace_id === 'string' ? body.workspace_id : null,\n };\n}\n\nexport interface ThreadReadStateResponse {\n thread_id: string;\n is_unread: boolean;\n}\n\n/** Store the calling user's read receipt for a thread. */\nexport async function setThreadReadState(\n backendUrl: string,\n auth: AuthConfig,\n opts: { threadId: string; isUnread: boolean },\n): Promise<ThreadReadStateResponse> {\n const base = getAthenaApiBaseUrl(backendUrl);\n const res = await fetch(`${base}/api/conversations/threads/read-state`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getAuthHeaders(auth) },\n body: JSON.stringify({\n thread_id: opts.threadId,\n is_unread: opts.isUnread,\n }),\n });\n if (!res.ok) {\n throw new Error(`[AthenaSDK] Failed to set thread read state: ${res.status}`);\n }\n return res.json();\n}\n\nexport 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): 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 body: JSON.stringify({\n thread_id: threadId,\n skip_cache: 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\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 throw new Error(`[AthenaSDK] Failed to archive thread: ${res.status}`);\n }\n}\n"],"names":["athenaDiagnostics","AthenaSdkError"],"mappings":"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"}
@@ -40,6 +40,21 @@ export declare function listThreads(backendUrl: string, auth: AuthConfig, opts?:
40
40
  search?: string;
41
41
  signal?: AbortSignal;
42
42
  }): Promise<ThreadListResponse>;
43
+ /** The `(workspace, user)` pair the credential resolves to on the backend. */
44
+ export interface AccountIdentity {
45
+ userId: string;
46
+ /** `null` for a user with no current workspace. */
47
+ workspaceId: string | null;
48
+ }
49
+ /**
50
+ * Resolve the credential's agora identity. The conversation index is one
51
+ * statewire instance per `(workspace, user)`, and neither half is knowable
52
+ * from the credential alone: the SDK's auth context carries an identity
53
+ * provider user id, not agora's, and never a workspace.
54
+ */
55
+ export declare function fetchAccountIdentity(backendUrl: string, auth: AuthConfig, opts?: {
56
+ signal?: AbortSignal;
57
+ }): Promise<AccountIdentity>;
43
58
  export interface ThreadReadStateResponse {
44
59
  thread_id: string;
45
60
  is_unread: boolean;