@athenaintel/react 0.32.0 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/README.md +148 -9
  2. package/dist/agent-runtime/src/composer-markdown.cjs.map +1 -1
  3. package/dist/agent-runtime/src/composer-markdown.js.map +1 -1
  4. package/dist/agent-runtime/src/deep-agent-transport-recipe.cjs +2 -3
  5. package/dist/agent-runtime/src/deep-agent-transport-recipe.cjs.map +1 -1
  6. package/dist/agent-runtime/src/deep-agent-transport-recipe.js +3 -4
  7. package/dist/agent-runtime/src/deep-agent-transport-recipe.js.map +1 -1
  8. package/dist/agent-runtime/src/edit-command.cjs.map +1 -1
  9. package/dist/agent-runtime/src/edit-command.js.map +1 -1
  10. package/dist/agent-runtime/src/generated/runconfig-contract.cjs +4 -0
  11. package/dist/agent-runtime/src/generated/runconfig-contract.cjs.map +1 -1
  12. package/dist/agent-runtime/src/generated/runconfig-contract.js +4 -0
  13. package/dist/agent-runtime/src/generated/runconfig-contract.js.map +1 -1
  14. package/dist/agent-runtime/src/messages.cjs +57 -2
  15. package/dist/agent-runtime/src/messages.cjs.map +1 -1
  16. package/dist/agent-runtime/src/messages.js +57 -2
  17. package/dist/agent-runtime/src/messages.js.map +1 -1
  18. package/dist/agent-runtime/src/quota-safe-local-storage-session.cjs +24 -3
  19. package/dist/agent-runtime/src/quota-safe-local-storage-session.cjs.map +1 -1
  20. package/dist/agent-runtime/src/quota-safe-local-storage-session.js +24 -3
  21. package/dist/agent-runtime/src/quota-safe-local-storage-session.js.map +1 -1
  22. package/dist/agent-runtime/src/statewire-transport.cjs +4 -13
  23. package/dist/agent-runtime/src/statewire-transport.cjs.map +1 -1
  24. package/dist/agent-runtime/src/statewire-transport.js +5 -14
  25. package/dist/agent-runtime/src/statewire-transport.js.map +1 -1
  26. package/dist/agent-runtime/src/tool-metadata.cjs +12 -1
  27. package/dist/agent-runtime/src/tool-metadata.cjs.map +1 -1
  28. package/dist/agent-runtime/src/tool-metadata.js +12 -1
  29. package/dist/agent-runtime/src/tool-metadata.js.map +1 -1
  30. package/dist/agent-runtime/src/use-deep-agent-thread.cjs.map +1 -1
  31. package/dist/agent-runtime/src/use-deep-agent-thread.js.map +1 -1
  32. package/dist/assets/AthenaAssetEmbed.cjs +3 -1
  33. package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
  34. package/dist/assets/AthenaAssetEmbed.d.ts +7 -0
  35. package/dist/assets/AthenaAssetEmbed.js +4 -2
  36. package/dist/assets/AthenaAssetEmbed.js.map +1 -1
  37. package/dist/assets/use-asset-embed.cjs +71 -71
  38. package/dist/assets/use-asset-embed.cjs.map +1 -1
  39. package/dist/assets/use-asset-embed.d.ts +20 -7
  40. package/dist/assets/use-asset-embed.js +72 -72
  41. package/dist/assets/use-asset-embed.js.map +1 -1
  42. package/dist/catalog/agent-mode-menu.cjs +115 -0
  43. package/dist/catalog/agent-mode-menu.cjs.map +1 -0
  44. package/dist/catalog/agent-mode-menu.d.ts +101 -0
  45. package/dist/catalog/agent-mode-menu.js +115 -0
  46. package/dist/catalog/agent-mode-menu.js.map +1 -0
  47. package/dist/catalog/agent-model-options.cjs +122 -0
  48. package/dist/catalog/agent-model-options.cjs.map +1 -0
  49. package/dist/catalog/agent-model-options.d.ts +115 -0
  50. package/dist/catalog/agent-model-options.js +122 -0
  51. package/dist/catalog/agent-model-options.js.map +1 -0
  52. package/dist/catalog/chat-pickers.cjs +235 -4
  53. package/dist/catalog/chat-pickers.cjs.map +1 -1
  54. package/dist/catalog/chat-pickers.d.ts +55 -3
  55. package/dist/catalog/chat-pickers.js +238 -7
  56. package/dist/catalog/chat-pickers.js.map +1 -1
  57. package/dist/catalog/runtime-catalog.cjs +121 -3
  58. package/dist/catalog/runtime-catalog.cjs.map +1 -1
  59. package/dist/catalog/runtime-catalog.d.ts +120 -0
  60. package/dist/catalog/runtime-catalog.js +124 -6
  61. package/dist/catalog/runtime-catalog.js.map +1 -1
  62. package/dist/catalog/use-athena-chat-mode.cjs +139 -0
  63. package/dist/catalog/use-athena-chat-mode.cjs.map +1 -0
  64. package/dist/catalog/use-athena-chat-mode.d.ts +43 -0
  65. package/dist/catalog/use-athena-chat-mode.js +139 -0
  66. package/dist/catalog/use-athena-chat-mode.js.map +1 -0
  67. package/dist/chat/AthenaChat.cjs +2 -0
  68. package/dist/chat/AthenaChat.cjs.map +1 -1
  69. package/dist/chat/AthenaChat.d.ts +8 -7
  70. package/dist/chat/AthenaChat.js +2 -0
  71. package/dist/chat/AthenaChat.js.map +1 -1
  72. package/dist/chat/super-grouping/SuperGroupingCard.cjs +30 -3
  73. package/dist/chat/super-grouping/SuperGroupingCard.cjs.map +1 -1
  74. package/dist/chat/super-grouping/SuperGroupingCard.d.ts +12 -9
  75. package/dist/chat/super-grouping/SuperGroupingCard.js +31 -4
  76. package/dist/chat/super-grouping/SuperGroupingCard.js.map +1 -1
  77. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs.map +1 -1
  78. package/dist/chat/super-grouping/SuperGroupingTranscript.d.ts +8 -8
  79. package/dist/chat/super-grouping/SuperGroupingTranscript.js.map +1 -1
  80. package/dist/chat/super-grouping/activity-timeline.cjs.map +1 -1
  81. package/dist/chat/super-grouping/activity-timeline.d.ts +4 -4
  82. package/dist/chat/super-grouping/activity-timeline.js.map +1 -1
  83. package/dist/chat/super-grouping/index.d.ts +6 -5
  84. package/dist/chat/super-grouping/toolkit-map.cjs +3 -1
  85. package/dist/chat/super-grouping/toolkit-map.cjs.map +1 -1
  86. package/dist/chat/super-grouping/toolkit-map.js +3 -1
  87. package/dist/chat/super-grouping/toolkit-map.js.map +1 -1
  88. package/dist/chat-ui/index.cjs +1 -0
  89. package/dist/chat-ui/index.cjs.map +1 -1
  90. package/dist/chat-ui/index.d.ts +1 -1
  91. package/dist/chat-ui/index.js +2 -1
  92. package/dist/chat-ui/mentions/suggestions/data/api-client.cjs +33 -2
  93. package/dist/chat-ui/mentions/suggestions/data/api-client.cjs.map +1 -1
  94. package/dist/chat-ui/mentions/suggestions/data/api-client.d.ts +14 -0
  95. package/dist/chat-ui/mentions/suggestions/data/api-client.js +33 -2
  96. package/dist/chat-ui/mentions/suggestions/data/api-client.js.map +1 -1
  97. package/dist/chat-ui/statewire.cjs +3 -0
  98. package/dist/chat-ui/statewire.cjs.map +1 -1
  99. package/dist/chat-ui/statewire.d.ts +4 -2
  100. package/dist/chat-ui/statewire.js +3 -0
  101. package/dist/chat-ui/statewire.js.map +1 -1
  102. package/dist/index.cjs +4 -0
  103. package/dist/index.cjs.map +1 -1
  104. package/dist/index.d.ts +7 -4
  105. package/dist/index.js +6 -2
  106. package/dist/index.js.map +1 -1
  107. package/dist/lib/use-athena-rest-query.cjs +4 -0
  108. package/dist/lib/use-athena-rest-query.cjs.map +1 -1
  109. package/dist/lib/use-athena-rest-query.d.ts +6 -0
  110. package/dist/lib/use-athena-rest-query.js +4 -0
  111. package/dist/lib/use-athena-rest-query.js.map +1 -1
  112. package/dist/provider/AthenaProvider.cjs +81 -9
  113. package/dist/provider/AthenaProvider.cjs.map +1 -1
  114. package/dist/provider/AthenaProvider.d.ts +25 -5
  115. package/dist/provider/AthenaProvider.js +83 -11
  116. package/dist/provider/AthenaProvider.js.map +1 -1
  117. package/dist/provider/agent-selection-context.cjs +6 -0
  118. package/dist/provider/agent-selection-context.cjs.map +1 -0
  119. package/dist/provider/agent-selection-context.d.ts +14 -0
  120. package/dist/provider/agent-selection-context.js +6 -0
  121. package/dist/provider/agent-selection-context.js.map +1 -0
  122. package/dist/provider/chat-mode-context.cjs +6 -0
  123. package/dist/provider/chat-mode-context.cjs.map +1 -0
  124. package/dist/provider/chat-mode-context.d.ts +35 -0
  125. package/dist/provider/chat-mode-context.js +6 -0
  126. package/dist/provider/chat-mode-context.js.map +1 -0
  127. package/dist/react/package.json.cjs +1 -1
  128. package/dist/react/package.json.js +1 -1
  129. package/dist/runtime/StatewireThreadConfigLaneSync.cjs +125 -0
  130. package/dist/runtime/StatewireThreadConfigLaneSync.cjs.map +1 -0
  131. package/dist/runtime/StatewireThreadConfigLaneSync.d.ts +54 -0
  132. package/dist/runtime/StatewireThreadConfigLaneSync.js +125 -0
  133. package/dist/runtime/StatewireThreadConfigLaneSync.js.map +1 -0
  134. package/dist/runtime/statewire-model-lane.cjs +0 -12
  135. package/dist/runtime/statewire-model-lane.cjs.map +1 -1
  136. package/dist/runtime/statewire-model-lane.d.ts +3 -1
  137. package/dist/runtime/statewire-model-lane.js +0 -12
  138. package/dist/runtime/statewire-model-lane.js.map +1 -1
  139. package/dist/runtime/statewire-run-config.cjs +10 -6
  140. package/dist/runtime/statewire-run-config.cjs.map +1 -1
  141. package/dist/runtime/statewire-run-config.d.ts +7 -0
  142. package/dist/runtime/statewire-run-config.js +10 -6
  143. package/dist/runtime/statewire-run-config.js.map +1 -1
  144. package/dist/runtime/statewire-server-lane-context.cjs.map +1 -1
  145. package/dist/runtime/statewire-server-lane-context.d.ts +2 -2
  146. package/dist/runtime/statewire-server-lane-context.js.map +1 -1
  147. package/dist/runtime/statewire-thread-config-lane.cjs +84 -0
  148. package/dist/runtime/statewire-thread-config-lane.cjs.map +1 -0
  149. package/dist/runtime/statewire-thread-config-lane.d.ts +94 -0
  150. package/dist/runtime/statewire-thread-config-lane.js +84 -0
  151. package/dist/runtime/statewire-thread-config-lane.js.map +1 -0
  152. package/dist/runtime/useAthenaStatewireRuntime.cjs +4 -2
  153. package/dist/runtime/useAthenaStatewireRuntime.cjs.map +1 -1
  154. package/dist/runtime/useAthenaStatewireRuntime.d.ts +2 -0
  155. package/dist/runtime/useAthenaStatewireRuntime.js +4 -2
  156. package/dist/runtime/useAthenaStatewireRuntime.js.map +1 -1
  157. package/dist/threads/use-athena-thread-model.cjs +11 -3
  158. package/dist/threads/use-athena-thread-model.cjs.map +1 -1
  159. package/dist/threads/use-athena-thread-model.d.ts +5 -0
  160. package/dist/threads/use-athena-thread-model.js +12 -4
  161. package/dist/threads/use-athena-thread-model.js.map +1 -1
  162. package/package.json +3 -3
  163. package/dist/runtime/StatewireModelLaneSync.cjs +0 -96
  164. package/dist/runtime/StatewireModelLaneSync.cjs.map +0 -1
  165. package/dist/runtime/StatewireModelLaneSync.d.ts +0 -45
  166. package/dist/runtime/StatewireModelLaneSync.js +0 -96
  167. package/dist/runtime/StatewireModelLaneSync.js.map +0 -1
@@ -0,0 +1,115 @@
1
+ const BASE_MODE_ID = "base";
2
+ const WEB_ENTRY = "web";
3
+ const MAX_MODE_KEY_LENGTH = 64;
4
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5
+ const isModeKey = (value) => typeof value === "string" && value.length > 0 && value.length <= MAX_MODE_KEY_LENGTH;
6
+ const decoded = (value) => {
7
+ if (typeof value !== "string") return value;
8
+ try {
9
+ return JSON.parse(value);
10
+ } catch {
11
+ return value;
12
+ }
13
+ };
14
+ const uniqueKeys = (value) => {
15
+ const kept = [];
16
+ if (Array.isArray(value)) {
17
+ for (const entry of value) {
18
+ if (isModeKey(entry) && !kept.includes(entry)) kept.push(entry);
19
+ }
20
+ }
21
+ return kept;
22
+ };
23
+ function publishedAgentModeSource(athenaMetadata) {
24
+ if (!isRecord(athenaMetadata) || !athenaMetadata.published_version_id) return null;
25
+ const config = athenaMetadata.published_config;
26
+ if (!isRecord(config)) return null;
27
+ const agentModes = decoded(config.agentModes);
28
+ const stored = isRecord(agentModes) ? agentModes : {};
29
+ const startModes = {};
30
+ if (isRecord(stored.startModes)) {
31
+ for (const [channel, mode] of Object.entries(stored.startModes)) {
32
+ if (isModeKey(channel) && isModeKey(mode)) startModes[channel] = mode;
33
+ }
34
+ }
35
+ const switchModes = {};
36
+ if (isRecord(stored.switchModes)) {
37
+ for (const [channel, targets] of Object.entries(stored.switchModes)) {
38
+ const kept = uniqueKeys(targets);
39
+ if (isModeKey(channel) && kept.length > 0) switchModes[channel] = kept;
40
+ }
41
+ }
42
+ const channels = {};
43
+ const customChannels = decoded(config.customChannels);
44
+ if (Array.isArray(customChannels)) {
45
+ for (const entry of customChannels) {
46
+ if (!isRecord(entry) || !isModeKey(entry.id) || entry.id in channels) continue;
47
+ const name = typeof entry.name === "string" ? entry.name.trim() : "";
48
+ channels[entry.id] = { label: name || entry.id, enabled: entry.enabled !== false };
49
+ }
50
+ }
51
+ return {
52
+ modes: uniqueKeys(stored.modes).filter((id) => id !== BASE_MODE_ID),
53
+ startModes,
54
+ switchModes,
55
+ lockedChannels: uniqueKeys(stored.lockedChannels),
56
+ channels
57
+ };
58
+ }
59
+ function agentModeEntry({
60
+ source,
61
+ channel
62
+ }) {
63
+ return channel && isCustomEntry({ source, channel }) ? channel : WEB_ENTRY;
64
+ }
65
+ const isCustomEntry = ({ source, channel }) => channel !== BASE_MODE_ID && !source.modes.includes(channel) && channel in source.channels;
66
+ function agentModeOfChannel({
67
+ source,
68
+ channel
69
+ }) {
70
+ return isCustomEntry({ source, channel }) ? source.startModes[channel] ?? channel : channel;
71
+ }
72
+ const optionLabel = ({ source, id }) => {
73
+ var _a;
74
+ return id === BASE_MODE_ID ? "Default" : ((_a = source.channels[id]) == null ? void 0 : _a.label) ?? id;
75
+ };
76
+ function agentModeMenu({
77
+ source,
78
+ channel,
79
+ hostModes
80
+ }) {
81
+ var _a;
82
+ const entry = agentModeEntry({ source, channel });
83
+ const startModeId = source.startModes[entry] ?? (entry === WEB_ENTRY ? BASE_MODE_ID : entry);
84
+ const ownMode = channel && agentModeOfChannel({ source, channel }) === channel ? [channel] : [];
85
+ const offered = /* @__PURE__ */ new Set([startModeId, ...ownMode, ...source.switchModes[entry] ?? []]);
86
+ const options = [];
87
+ for (const id of hostModes) {
88
+ if (!offered.has(id) || options.some((option) => option.id === id)) continue;
89
+ if (id !== BASE_MODE_ID && ((_a = source.channels[id]) == null ? void 0 : _a.enabled) === false) continue;
90
+ options.push({ id, label: optionLabel({ source, id }) });
91
+ }
92
+ return {
93
+ entry,
94
+ startModeId,
95
+ firstSendModeId: channel ? agentModeOfChannel({ source, channel }) : startModeId,
96
+ options,
97
+ locked: source.lockedChannels.includes(entry)
98
+ };
99
+ }
100
+ function sessionChannelFromMetadata(athenaMetadata) {
101
+ if (!isRecord(athenaMetadata)) return null;
102
+ const current = athenaMetadata.collab_current_channel_id;
103
+ if (isModeKey(current)) return current;
104
+ const stamp = athenaMetadata.collab_channel_id;
105
+ return isModeKey(stamp) ? stamp : null;
106
+ }
107
+ export {
108
+ BASE_MODE_ID,
109
+ agentModeEntry,
110
+ agentModeMenu,
111
+ agentModeOfChannel,
112
+ publishedAgentModeSource,
113
+ sessionChannelFromMetadata
114
+ };
115
+ //# sourceMappingURL=agent-mode-menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-mode-menu.js","sources":["../../src/catalog/agent-mode-menu.ts"],"sourcesContent":["/**\n * A collab agent's mode menu for an SDK chat, from its published definition.\n *\n * A chat offers the modes a session entered through its channel may run in,\n * by the rule agora checks every switch with (`resolve_mode_switch`): the\n * channel's start mode, the channel itself, and the modes the agent's author\n * lists for it (`agentModes.switchModes[<entry>]`). A channel the author\n * locks (`agentModes.lockedChannels`) keeps each session in the mode it\n * starts in: people pick the mode before a session's first message, and it\n * stays. A custom channel that is not itself a mode is its own entry; no\n * channel, a mode or a built-in channel is the Default (Text) entry (`web`).\n *\n * The host lists which of those modes its menu shows (`AthenaProvider`'s\n * `modes`); a mode the agent does not offer there is left out, never sent.\n *\n * Pure, for `useAthenaChatMode` and the tests.\n */\n\n/** The id of the agent as configured, with no mode's overrides (agora's `base`). */\nexport const BASE_MODE_ID = 'base';\n\n/** The Default (Text) entry: chats that name no custom channel of their own. */\nconst WEB_ENTRY = 'web';\n\n/** agora's limit on a mode key's length (`_MAX_MODE_KEY_LENGTH`). */\nconst MAX_MODE_KEY_LENGTH = 64;\n\n/** What an agent's published definition says about its modes. */\nexport interface AgentModeSource {\n /** Custom channels the author marked as modes (`agentModes.modes`). */\n modes: readonly string[];\n /** Channel key → the mode its sessions start in (`agentModes.startModes`). */\n startModes: Readonly<Record<string, string>>;\n /** Entry channel key → the modes its sessions may switch to (`agentModes.switchModes`). */\n switchModes: Readonly<Record<string, readonly string[]>>;\n /** Entry channels that keep each session in its start mode (`agentModes.lockedChannels`). */\n lockedChannels: readonly string[];\n /** The agent's custom channels by id (`customChannels`). */\n channels: Readonly<Record<string, { label: string; enabled: boolean }>>;\n}\n\n/** One mode a chat may run in. */\nexport interface AthenaChatModeOption {\n /** What a send names as `collab_channel_id`: `base` or a mode's id. */\n id: string;\n /** The author's name for it (`Default` for `base`). */\n label: string;\n}\n\n/** The mode menu of a chat on one channel of an agent. */\nexport interface AgentModeMenu {\n /** The entry channel its sessions are judged by (`web` for the Default (Text) entry). */\n entry: string;\n /** The mode a session of the entry starts in. */\n startModeId: string;\n /** The mode a new chat's first send runs in when nobody picks one: the\n * entry's start mode, or the channel itself when it names a mode. */\n firstSendModeId: string;\n /** The modes the host listed that the agent offers there, in the host's order. */\n options: readonly AthenaChatModeOption[];\n /** The channel keeps each session in the mode it starts in. */\n locked: boolean;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst isModeKey = (value: unknown): value is string =>\n typeof value === 'string' && value.length > 0 && value.length <= MAX_MODE_KEY_LENGTH;\n\n/** A stored JSON field: the live doc keeps it as a string, the caches decoded. */\nconst decoded = (value: unknown): unknown => {\n if (typeof value !== 'string') return value;\n try {\n return JSON.parse(value) as unknown;\n } catch {\n return value;\n }\n};\n\nconst uniqueKeys = (value: unknown): string[] => {\n const kept: string[] = [];\n if (Array.isArray(value)) {\n for (const entry of value) {\n if (isModeKey(entry) && !kept.includes(entry)) kept.push(entry);\n }\n }\n return kept;\n};\n\n/**\n * What an agent's published definition (`athenaMetadata.published_config`)\n * says about its modes, read as leniently as agora reads it\n * (`parse_agent_modes`, `parse_custom_channels`): malformed parts are dropped.\n * `null` for an agent never published.\n */\nexport function publishedAgentModeSource(athenaMetadata: unknown): AgentModeSource | null {\n if (!isRecord(athenaMetadata) || !athenaMetadata.published_version_id) return null;\n const config = athenaMetadata.published_config;\n if (!isRecord(config)) return null;\n const agentModes = decoded(config.agentModes);\n const stored = isRecord(agentModes) ? agentModes : {};\n const startModes: Record<string, string> = {};\n if (isRecord(stored.startModes)) {\n for (const [channel, mode] of Object.entries(stored.startModes)) {\n if (isModeKey(channel) && isModeKey(mode)) startModes[channel] = mode;\n }\n }\n const switchModes: Record<string, string[]> = {};\n if (isRecord(stored.switchModes)) {\n for (const [channel, targets] of Object.entries(stored.switchModes)) {\n const kept = uniqueKeys(targets);\n if (isModeKey(channel) && kept.length > 0) switchModes[channel] = kept;\n }\n }\n const channels: Record<string, { label: string; enabled: boolean }> = {};\n const customChannels = decoded(config.customChannels);\n if (Array.isArray(customChannels)) {\n for (const entry of customChannels) {\n if (!isRecord(entry) || !isModeKey(entry.id) || entry.id in channels) continue;\n const name = typeof entry.name === 'string' ? entry.name.trim() : '';\n channels[entry.id] = { label: name || entry.id, enabled: entry.enabled !== false };\n }\n }\n return {\n modes: uniqueKeys(stored.modes).filter((id) => id !== BASE_MODE_ID),\n startModes,\n switchModes,\n lockedChannels: uniqueKeys(stored.lockedChannels),\n channels,\n };\n}\n\n/**\n * The entry a session on `channel` is judged by (agora `session_entry_channel`):\n * one of the agent's custom channels that is not a mode, else the Default\n * (Text) entry.\n */\nexport function agentModeEntry({\n source,\n channel,\n}: {\n source: AgentModeSource;\n channel: string | null;\n}): string {\n return channel && isCustomEntry({ source, channel }) ? channel : WEB_ENTRY;\n}\n\nconst isCustomEntry = ({ source, channel }: { source: AgentModeSource; channel: string }) =>\n channel !== BASE_MODE_ID && !source.modes.includes(channel) && channel in source.channels;\n\n/**\n * The mode a run on `channel` runs in, as agora compiles it: a custom entry\n * channel follows its start mode even when a send names it, while `base`, a\n * mode or a built-in channel runs its own layer.\n */\nexport function agentModeOfChannel({\n source,\n channel,\n}: {\n source: AgentModeSource;\n channel: string;\n}): string {\n return isCustomEntry({ source, channel }) ? (source.startModes[channel] ?? channel) : channel;\n}\n\nconst optionLabel = ({ source, id }: { source: AgentModeSource; id: string }): string =>\n id === BASE_MODE_ID ? 'Default' : (source.channels[id]?.label ?? id);\n\n/**\n * The menu of a chat on `channel` (the provider's `channel`, `null` for none)\n * that lists `hostModes`: the modes the agent offers its entry — its start\n * mode, the channel itself (unless naming it runs that start mode), its switch\n * list — that the host listed, in the host's order, without a mode that is\n * switched off.\n */\nexport function agentModeMenu({\n source,\n channel,\n hostModes,\n}: {\n source: AgentModeSource;\n channel: string | null;\n hostModes: readonly string[];\n}): AgentModeMenu {\n const entry = agentModeEntry({ source, channel });\n const startModeId = source.startModes[entry] ?? (entry === WEB_ENTRY ? BASE_MODE_ID : entry);\n // A custom entry channel that starts in another mode runs that mode when\n // named, so it is no option of its own.\n const ownMode = channel && agentModeOfChannel({ source, channel }) === channel ? [channel] : [];\n const offered = new Set([startModeId, ...ownMode, ...(source.switchModes[entry] ?? [])]);\n const options: AthenaChatModeOption[] = [];\n for (const id of hostModes) {\n if (!offered.has(id) || options.some((option) => option.id === id)) continue;\n if (id !== BASE_MODE_ID && source.channels[id]?.enabled === false) continue;\n options.push({ id, label: optionLabel({ source, id }) });\n }\n return {\n entry,\n startModeId,\n firstSendModeId: channel ? agentModeOfChannel({ source, channel }) : startModeId,\n options,\n locked: source.lockedChannels.includes(entry),\n };\n}\n\n/**\n * The channel agora reads as a session's current one, from its session\n * asset's metadata: the mode it last switched to or started in\n * (`collab_current_channel_id`), else its channel stamp (`collab_channel_id`).\n * A send that names it continues the session without switching it; the mode\n * it runs in is {@link agentModeOfChannel} of it. `null` when neither is set.\n */\nexport function sessionChannelFromMetadata(athenaMetadata: unknown): string | null {\n if (!isRecord(athenaMetadata)) return null;\n const current = athenaMetadata.collab_current_channel_id;\n if (isModeKey(current)) return current;\n const stamp = athenaMetadata.collab_channel_id;\n return isModeKey(stamp) ? stamp : null;\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const statewireRunConfig = require("../runtime/statewire-run-config.cjs");
4
+ function allowsAnyModel(value) {
5
+ return value !== false;
6
+ }
7
+ const LEGACY_DEFAULT_ENTRY_ID = "default";
8
+ const LEGACY_DEFAULT_ENTRY_LABEL = "Default";
9
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
10
+ const nonBlank = (value) => typeof value === "string" && value.trim() !== "" ? value.trim() : null;
11
+ function agentAssetIdOf(agent) {
12
+ const trimmed = nonBlank(agent);
13
+ if (trimmed === null) return null;
14
+ return statewireRunConfig.parseCollabAgentRef(trimmed) ?? (trimmed.includes(":") ? null : trimmed);
15
+ }
16
+ function labeledModelsOf(value) {
17
+ let rows = value;
18
+ if (typeof rows === "string") {
19
+ try {
20
+ rows = JSON.parse(rows);
21
+ } catch {
22
+ return [];
23
+ }
24
+ }
25
+ if (!Array.isArray(rows)) return [];
26
+ const entries = [];
27
+ for (const row of rows) {
28
+ if (!isRecord(row)) continue;
29
+ const model = nonBlank(row.model);
30
+ if (model === null) continue;
31
+ entries.push({
32
+ id: typeof row.id === "string" && row.id !== "" ? row.id : model,
33
+ model,
34
+ label: nonBlank(row.label) ?? model,
35
+ isDefault: row.isDefault === true
36
+ });
37
+ }
38
+ return entries;
39
+ }
40
+ function channelModelsOf(value) {
41
+ if (!isRecord(value)) return {};
42
+ const out = {};
43
+ for (const [channel, override] of Object.entries(value)) {
44
+ const model = isRecord(override) ? nonBlank(override.model) : null;
45
+ if (model !== null) out[channel] = model;
46
+ }
47
+ return out;
48
+ }
49
+ function isPublishedAgent(athenaMetadata) {
50
+ if (!isRecord(athenaMetadata)) return false;
51
+ const versionId = athenaMetadata.published_version_id;
52
+ return typeof versionId === "string" && versionId !== "" || typeof versionId === "number" && Number.isFinite(versionId);
53
+ }
54
+ function publishedAgentModelSource(athenaMetadata) {
55
+ if (!isRecord(athenaMetadata) || !isPublishedAgent(athenaMetadata)) return null;
56
+ const config = athenaMetadata.published_config;
57
+ if (!isRecord(config)) return null;
58
+ const source = {
59
+ model: nonBlank(config.model),
60
+ models: labeledModelsOf(config.models),
61
+ channelModels: channelModelsOf(config.channelOverrides),
62
+ // The editor stores the field name; agora also accepts its snake-case alias.
63
+ allowAnyModel: allowsAnyModel(
64
+ Object.hasOwn(config, "allowAnyModel") ? config.allowAnyModel : config.allow_any_model
65
+ )
66
+ };
67
+ const namesAModel = source.model !== null || source.models.length > 0 || Object.keys(source.channelModels).length > 0;
68
+ return namesAModel ? source : null;
69
+ }
70
+ function isAutomaticModelLabel({
71
+ entry,
72
+ modelLabel
73
+ }) {
74
+ const label = entry.label.trim();
75
+ if (label === "") return true;
76
+ if (entry.id === LEGACY_DEFAULT_ENTRY_ID && label === LEGACY_DEFAULT_ENTRY_LABEL) return true;
77
+ return label === entry.model || label === modelLabel(entry.model);
78
+ }
79
+ function agentDefaultModel({
80
+ source,
81
+ channel
82
+ }) {
83
+ const marked = source.models.find((entry) => entry.isDefault) ?? source.models[0];
84
+ return (channel ? source.channelModels[channel] : void 0) ?? source.model ?? (marked == null ? void 0 : marked.model) ?? null;
85
+ }
86
+ function buildAgentModelOptions({
87
+ source,
88
+ channel,
89
+ pinnedModel,
90
+ modelLabel
91
+ }) {
92
+ const options = [];
93
+ const add = (model, label) => {
94
+ if (options.some((option) => option.model === model)) return;
95
+ options.push({ model, label, isDefault: false, pinnedOutside: false });
96
+ };
97
+ for (const entry of source.models) {
98
+ add(
99
+ entry.model,
100
+ isAutomaticModelLabel({ entry, modelLabel }) ? modelLabel(entry.model) : entry.label.trim()
101
+ );
102
+ }
103
+ if (options.length === 0 && source.model !== null) add(source.model, modelLabel(source.model));
104
+ const defaultModel = agentDefaultModel({ source, channel });
105
+ if (defaultModel !== null) {
106
+ add(defaultModel, modelLabel(defaultModel));
107
+ for (const option of options) option.isDefault = option.model === defaultModel;
108
+ }
109
+ const pinned = nonBlank(pinnedModel);
110
+ if (pinned !== null && !options.some((option) => option.model === pinned)) {
111
+ options.push({ model: pinned, label: modelLabel(pinned), isDefault: false, pinnedOutside: true });
112
+ }
113
+ return { options, defaultModel };
114
+ }
115
+ exports.agentAssetIdOf = agentAssetIdOf;
116
+ exports.agentDefaultModel = agentDefaultModel;
117
+ exports.allowsAnyModel = allowsAnyModel;
118
+ exports.buildAgentModelOptions = buildAgentModelOptions;
119
+ exports.isAutomaticModelLabel = isAutomaticModelLabel;
120
+ exports.isPublishedAgent = isPublishedAgent;
121
+ exports.publishedAgentModelSource = publishedAgentModelSource;
122
+ //# sourceMappingURL=agent-model-options.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-model-options.cjs","sources":["../../src/catalog/agent-model-options.ts"],"sourcesContent":["import { parseCollabAgentRef } from '../runtime/statewire-run-config';\n\n/**\n * A collab agent's own model choices, from its published definition: exactly\n * the models its author listed (the agent editor's Models section), the\n * default marked. The rules are Athena's own composer's (Olympus) and the\n * Jevin app's, so every host offers the same choices for one agent:\n *\n * - the options are the agent's labeled models, by the author's labels; an\n * automatic name (empty, the editor's old `{ id: 'default', label: 'Default' }`,\n * or one that only restates the model) shows the model's catalog label;\n * - a legacy agent with no labeled list offers its single `model`;\n * - the session channel's model override (`channelOverrides[channel].model`)\n * is that channel's default, added when the list lacks it;\n * - a model the thread pins that is none of the agent's models is shown as it\n * is (`pinnedOutside`), never replaced.\n *\n * Restricting a picker to these choices is opt-in per agent: only an agent\n * whose \"Allow any workspace model\" switch is off (`allowAnyModel: false`)\n * does; every other agent keeps the workspace's models.\n *\n * Pure, for `useAthenaAgentModelOptions` and the tests.\n */\n\n/** One model choice of a collab agent. */\nexport interface AthenaAgentModelOption {\n /** The model id a send carries (`model`). */\n model: string;\n /** The author's label, or the model's catalog label for an automatic name. */\n label: string;\n /** The model the thread runs while it pins none (the agent's own, or its channel's). */\n isDefault: boolean;\n /**\n * A model the thread's server config pins that is none of the agent's models\n * (pinned by another client, or by an older picker): shown, never replaced.\n */\n pinnedOutside: boolean;\n}\n\n/** One entry of the agent's labeled model list. */\nexport interface AgentLabeledModel {\n id: string;\n model: string;\n label: string;\n isDefault: boolean;\n}\n\n/** What an agent's published definition says about its models. */\nexport interface AgentModelSource {\n /** The model the definition runs (`config.model`); `null` when it names none. */\n model: string | null;\n /** The labeled list (`config.models`), complete rows only, in the author's order. */\n models: readonly AgentLabeledModel[];\n /** Per-channel model overrides (`channelOverrides[<channel>].model`), by channel id. */\n channelModels: Readonly<Record<string, string>>;\n /**\n * Whether people may pick any workspace model (`config.allowAnyModel`, the\n * agent editor's \"Allow any workspace model\" switch; see\n * {@link allowsAnyModel}). Only `false` restricts a picker to the agent's\n * own models.\n */\n allowAnyModel: boolean;\n}\n\n/**\n * Whether a config's `allowAnyModel` value lets people pick any model: only an\n * explicit `false` restricts; absent, `null`, `true` and any non-boolean allow\n * (agora's `collab_agent_allows_any_model`), so every agent saved before the\n * switch existed behaves as it always has.\n */\nexport function allowsAnyModel(value: unknown): boolean {\n return value !== false;\n}\n\n/** The display label of a model id; the id itself when the catalog does not know it. */\nexport type ModelLabelOf = (model: string) => string;\n\n/** The row the agent editor used to create for an agent with no stored entries. */\nconst LEGACY_DEFAULT_ENTRY_ID = 'default';\nconst LEGACY_DEFAULT_ENTRY_LABEL = 'Default';\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst nonBlank = (value: unknown): string | null =>\n typeof value === 'string' && value.trim() !== '' ? value.trim() : null;\n\n/**\n * The collab agent's asset id for an `agent` value: a `collab_agent:<id>` ref\n * (the provider's `agent` shape) or the bare asset id (the agent picker's\n * value). `null` for a blank value.\n */\nexport function agentAssetIdOf(agent: string | null | undefined): string | null {\n const trimmed = nonBlank(agent);\n if (trimmed === null) return null;\n return parseCollabAgentRef(trimmed) ?? (trimmed.includes(':') ? null : trimmed);\n}\n\n/** A stored `models` value: an array of rows, JSON-encoded or not; malformed rows dropped. */\nfunction labeledModelsOf(value: unknown): AgentLabeledModel[] {\n let rows: unknown = value;\n if (typeof rows === 'string') {\n try {\n rows = JSON.parse(rows);\n } catch {\n return [];\n }\n }\n if (!Array.isArray(rows)) return [];\n const entries: AgentLabeledModel[] = [];\n for (const row of rows) {\n if (!isRecord(row)) continue;\n const model = nonBlank(row.model);\n if (model === null) continue;\n entries.push({\n id: typeof row.id === 'string' && row.id !== '' ? row.id : model,\n model,\n label: nonBlank(row.label) ?? model,\n isDefault: row.isDefault === true,\n });\n }\n return entries;\n}\n\n/** A stored `channelOverrides` value's per-channel models. */\nfunction channelModelsOf(value: unknown): Record<string, string> {\n if (!isRecord(value)) return {};\n const out: Record<string, string> = {};\n for (const [channel, override] of Object.entries(value)) {\n const model = isRecord(override) ? nonBlank(override.model) : null;\n if (model !== null) out[channel] = model;\n }\n return out;\n}\n\n/** Whether the agent asset's `athena_metadata` points at a published version. */\nexport function isPublishedAgent(athenaMetadata: unknown): boolean {\n if (!isRecord(athenaMetadata)) return false;\n const versionId = athenaMetadata.published_version_id;\n return (\n (typeof versionId === 'string' && versionId !== '') ||\n (typeof versionId === 'number' && Number.isFinite(versionId))\n );\n}\n\n/**\n * The model source of the agent's PUBLISHED definition, which every SDK run\n * compiles, from the agent asset's `athena_metadata` (publish writes\n * `published_config` beside `published_version_id`). `null` for an agent\n * that was never published, or whose published definition names no model\n * (not even a channel's).\n */\nexport function publishedAgentModelSource(athenaMetadata: unknown): AgentModelSource | null {\n if (!isRecord(athenaMetadata) || !isPublishedAgent(athenaMetadata)) return null;\n const config = athenaMetadata.published_config;\n if (!isRecord(config)) return null;\n const source: AgentModelSource = {\n model: nonBlank(config.model),\n models: labeledModelsOf(config.models),\n channelModels: channelModelsOf(config.channelOverrides),\n // The editor stores the field name; agora also accepts its snake-case alias.\n allowAnyModel: allowsAnyModel(\n Object.hasOwn(config, 'allowAnyModel') ? config.allowAnyModel : config.allow_any_model,\n ),\n };\n const namesAModel =\n source.model !== null ||\n source.models.length > 0 ||\n Object.keys(source.channelModels).length > 0;\n return namesAModel ? source : null;\n}\n\n/**\n * Whether an entry's name is automatic: empty, the editor's old \"Default\", or\n * a name that only restates the entry's model (its id or catalog label).\n */\nexport function isAutomaticModelLabel({\n entry,\n modelLabel,\n}: {\n entry: Pick<AgentLabeledModel, 'id' | 'model' | 'label'>;\n modelLabel: ModelLabelOf;\n}): boolean {\n const label = entry.label.trim();\n if (label === '') return true;\n if (entry.id === LEGACY_DEFAULT_ENTRY_ID && label === LEGACY_DEFAULT_ENTRY_LABEL) return true;\n return label === entry.model || label === modelLabel(entry.model);\n}\n\n/**\n * The model a thread runs while it pins none: the channel's override, else the\n * definition's `model`, else its marked (or first) labeled entry.\n */\nexport function agentDefaultModel({\n source,\n channel,\n}: {\n source: AgentModelSource;\n channel: string | null;\n}): string | null {\n const marked = source.models.find((entry) => entry.isDefault) ?? source.models[0];\n return (channel ? source.channelModels[channel] : undefined) ?? source.model ?? marked?.model ?? null;\n}\n\n/**\n * The agent's model choices and its default, by the rules above. Options keep\n * the author's order, one per model (the first entry of a model names it); the\n * default is marked, and appended when the list lacks it; a pinned model\n * outside the list comes last.\n */\nexport function buildAgentModelOptions({\n source,\n channel,\n pinnedModel,\n modelLabel,\n}: {\n source: AgentModelSource;\n channel: string | null;\n /** The model the thread's server config pins; `null` while it pins none (or is unknown). */\n pinnedModel: string | null;\n modelLabel: ModelLabelOf;\n}): { options: AthenaAgentModelOption[]; defaultModel: string | null } {\n const options: AthenaAgentModelOption[] = [];\n const add = (model: string, label: string) => {\n if (options.some((option) => option.model === model)) return;\n options.push({ model, label, isDefault: false, pinnedOutside: false });\n };\n for (const entry of source.models) {\n add(\n entry.model,\n isAutomaticModelLabel({ entry, modelLabel }) ? modelLabel(entry.model) : entry.label.trim(),\n );\n }\n if (options.length === 0 && source.model !== null) add(source.model, modelLabel(source.model));\n\n const defaultModel = agentDefaultModel({ source, channel });\n if (defaultModel !== null) {\n add(defaultModel, modelLabel(defaultModel));\n for (const option of options) option.isDefault = option.model === defaultModel;\n }\n\n const pinned = nonBlank(pinnedModel);\n if (pinned !== null && !options.some((option) => option.model === pinned)) {\n options.push({ model: pinned, label: modelLabel(pinned), isDefault: false, pinnedOutside: true });\n }\n return { options, defaultModel };\n}\n"],"names":["parseCollabAgentRef"],"mappings":"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"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * A collab agent's own model choices, from its published definition: exactly
3
+ * the models its author listed (the agent editor's Models section), the
4
+ * default marked. The rules are Athena's own composer's (Olympus) and the
5
+ * Jevin app's, so every host offers the same choices for one agent:
6
+ *
7
+ * - the options are the agent's labeled models, by the author's labels; an
8
+ * automatic name (empty, the editor's old `{ id: 'default', label: 'Default' }`,
9
+ * or one that only restates the model) shows the model's catalog label;
10
+ * - a legacy agent with no labeled list offers its single `model`;
11
+ * - the session channel's model override (`channelOverrides[channel].model`)
12
+ * is that channel's default, added when the list lacks it;
13
+ * - a model the thread pins that is none of the agent's models is shown as it
14
+ * is (`pinnedOutside`), never replaced.
15
+ *
16
+ * Restricting a picker to these choices is opt-in per agent: only an agent
17
+ * whose "Allow any workspace model" switch is off (`allowAnyModel: false`)
18
+ * does; every other agent keeps the workspace's models.
19
+ *
20
+ * Pure, for `useAthenaAgentModelOptions` and the tests.
21
+ */
22
+ /** One model choice of a collab agent. */
23
+ export interface AthenaAgentModelOption {
24
+ /** The model id a send carries (`model`). */
25
+ model: string;
26
+ /** The author's label, or the model's catalog label for an automatic name. */
27
+ label: string;
28
+ /** The model the thread runs while it pins none (the agent's own, or its channel's). */
29
+ isDefault: boolean;
30
+ /**
31
+ * A model the thread's server config pins that is none of the agent's models
32
+ * (pinned by another client, or by an older picker): shown, never replaced.
33
+ */
34
+ pinnedOutside: boolean;
35
+ }
36
+ /** One entry of the agent's labeled model list. */
37
+ export interface AgentLabeledModel {
38
+ id: string;
39
+ model: string;
40
+ label: string;
41
+ isDefault: boolean;
42
+ }
43
+ /** What an agent's published definition says about its models. */
44
+ export interface AgentModelSource {
45
+ /** The model the definition runs (`config.model`); `null` when it names none. */
46
+ model: string | null;
47
+ /** The labeled list (`config.models`), complete rows only, in the author's order. */
48
+ models: readonly AgentLabeledModel[];
49
+ /** Per-channel model overrides (`channelOverrides[<channel>].model`), by channel id. */
50
+ channelModels: Readonly<Record<string, string>>;
51
+ /**
52
+ * Whether people may pick any workspace model (`config.allowAnyModel`, the
53
+ * agent editor's "Allow any workspace model" switch; see
54
+ * {@link allowsAnyModel}). Only `false` restricts a picker to the agent's
55
+ * own models.
56
+ */
57
+ allowAnyModel: boolean;
58
+ }
59
+ /**
60
+ * Whether a config's `allowAnyModel` value lets people pick any model: only an
61
+ * explicit `false` restricts; absent, `null`, `true` and any non-boolean allow
62
+ * (agora's `collab_agent_allows_any_model`), so every agent saved before the
63
+ * switch existed behaves as it always has.
64
+ */
65
+ export declare function allowsAnyModel(value: unknown): boolean;
66
+ /** The display label of a model id; the id itself when the catalog does not know it. */
67
+ export type ModelLabelOf = (model: string) => string;
68
+ /**
69
+ * The collab agent's asset id for an `agent` value: a `collab_agent:<id>` ref
70
+ * (the provider's `agent` shape) or the bare asset id (the agent picker's
71
+ * value). `null` for a blank value.
72
+ */
73
+ export declare function agentAssetIdOf(agent: string | null | undefined): string | null;
74
+ /** Whether the agent asset's `athena_metadata` points at a published version. */
75
+ export declare function isPublishedAgent(athenaMetadata: unknown): boolean;
76
+ /**
77
+ * The model source of the agent's PUBLISHED definition, which every SDK run
78
+ * compiles, from the agent asset's `athena_metadata` (publish writes
79
+ * `published_config` beside `published_version_id`). `null` for an agent
80
+ * that was never published, or whose published definition names no model
81
+ * (not even a channel's).
82
+ */
83
+ export declare function publishedAgentModelSource(athenaMetadata: unknown): AgentModelSource | null;
84
+ /**
85
+ * Whether an entry's name is automatic: empty, the editor's old "Default", or
86
+ * a name that only restates the entry's model (its id or catalog label).
87
+ */
88
+ export declare function isAutomaticModelLabel({ entry, modelLabel, }: {
89
+ entry: Pick<AgentLabeledModel, 'id' | 'model' | 'label'>;
90
+ modelLabel: ModelLabelOf;
91
+ }): boolean;
92
+ /**
93
+ * The model a thread runs while it pins none: the channel's override, else the
94
+ * definition's `model`, else its marked (or first) labeled entry.
95
+ */
96
+ export declare function agentDefaultModel({ source, channel, }: {
97
+ source: AgentModelSource;
98
+ channel: string | null;
99
+ }): string | null;
100
+ /**
101
+ * The agent's model choices and its default, by the rules above. Options keep
102
+ * the author's order, one per model (the first entry of a model names it); the
103
+ * default is marked, and appended when the list lacks it; a pinned model
104
+ * outside the list comes last.
105
+ */
106
+ export declare function buildAgentModelOptions({ source, channel, pinnedModel, modelLabel, }: {
107
+ source: AgentModelSource;
108
+ channel: string | null;
109
+ /** The model the thread's server config pins; `null` while it pins none (or is unknown). */
110
+ pinnedModel: string | null;
111
+ modelLabel: ModelLabelOf;
112
+ }): {
113
+ options: AthenaAgentModelOption[];
114
+ defaultModel: string | null;
115
+ };
@@ -0,0 +1,122 @@
1
+ import { parseCollabAgentRef } from "../runtime/statewire-run-config.js";
2
+ function allowsAnyModel(value) {
3
+ return value !== false;
4
+ }
5
+ const LEGACY_DEFAULT_ENTRY_ID = "default";
6
+ const LEGACY_DEFAULT_ENTRY_LABEL = "Default";
7
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
8
+ const nonBlank = (value) => typeof value === "string" && value.trim() !== "" ? value.trim() : null;
9
+ function agentAssetIdOf(agent) {
10
+ const trimmed = nonBlank(agent);
11
+ if (trimmed === null) return null;
12
+ return parseCollabAgentRef(trimmed) ?? (trimmed.includes(":") ? null : trimmed);
13
+ }
14
+ function labeledModelsOf(value) {
15
+ let rows = value;
16
+ if (typeof rows === "string") {
17
+ try {
18
+ rows = JSON.parse(rows);
19
+ } catch {
20
+ return [];
21
+ }
22
+ }
23
+ if (!Array.isArray(rows)) return [];
24
+ const entries = [];
25
+ for (const row of rows) {
26
+ if (!isRecord(row)) continue;
27
+ const model = nonBlank(row.model);
28
+ if (model === null) continue;
29
+ entries.push({
30
+ id: typeof row.id === "string" && row.id !== "" ? row.id : model,
31
+ model,
32
+ label: nonBlank(row.label) ?? model,
33
+ isDefault: row.isDefault === true
34
+ });
35
+ }
36
+ return entries;
37
+ }
38
+ function channelModelsOf(value) {
39
+ if (!isRecord(value)) return {};
40
+ const out = {};
41
+ for (const [channel, override] of Object.entries(value)) {
42
+ const model = isRecord(override) ? nonBlank(override.model) : null;
43
+ if (model !== null) out[channel] = model;
44
+ }
45
+ return out;
46
+ }
47
+ function isPublishedAgent(athenaMetadata) {
48
+ if (!isRecord(athenaMetadata)) return false;
49
+ const versionId = athenaMetadata.published_version_id;
50
+ return typeof versionId === "string" && versionId !== "" || typeof versionId === "number" && Number.isFinite(versionId);
51
+ }
52
+ function publishedAgentModelSource(athenaMetadata) {
53
+ if (!isRecord(athenaMetadata) || !isPublishedAgent(athenaMetadata)) return null;
54
+ const config = athenaMetadata.published_config;
55
+ if (!isRecord(config)) return null;
56
+ const source = {
57
+ model: nonBlank(config.model),
58
+ models: labeledModelsOf(config.models),
59
+ channelModels: channelModelsOf(config.channelOverrides),
60
+ // The editor stores the field name; agora also accepts its snake-case alias.
61
+ allowAnyModel: allowsAnyModel(
62
+ Object.hasOwn(config, "allowAnyModel") ? config.allowAnyModel : config.allow_any_model
63
+ )
64
+ };
65
+ const namesAModel = source.model !== null || source.models.length > 0 || Object.keys(source.channelModels).length > 0;
66
+ return namesAModel ? source : null;
67
+ }
68
+ function isAutomaticModelLabel({
69
+ entry,
70
+ modelLabel
71
+ }) {
72
+ const label = entry.label.trim();
73
+ if (label === "") return true;
74
+ if (entry.id === LEGACY_DEFAULT_ENTRY_ID && label === LEGACY_DEFAULT_ENTRY_LABEL) return true;
75
+ return label === entry.model || label === modelLabel(entry.model);
76
+ }
77
+ function agentDefaultModel({
78
+ source,
79
+ channel
80
+ }) {
81
+ const marked = source.models.find((entry) => entry.isDefault) ?? source.models[0];
82
+ return (channel ? source.channelModels[channel] : void 0) ?? source.model ?? (marked == null ? void 0 : marked.model) ?? null;
83
+ }
84
+ function buildAgentModelOptions({
85
+ source,
86
+ channel,
87
+ pinnedModel,
88
+ modelLabel
89
+ }) {
90
+ const options = [];
91
+ const add = (model, label) => {
92
+ if (options.some((option) => option.model === model)) return;
93
+ options.push({ model, label, isDefault: false, pinnedOutside: false });
94
+ };
95
+ for (const entry of source.models) {
96
+ add(
97
+ entry.model,
98
+ isAutomaticModelLabel({ entry, modelLabel }) ? modelLabel(entry.model) : entry.label.trim()
99
+ );
100
+ }
101
+ if (options.length === 0 && source.model !== null) add(source.model, modelLabel(source.model));
102
+ const defaultModel = agentDefaultModel({ source, channel });
103
+ if (defaultModel !== null) {
104
+ add(defaultModel, modelLabel(defaultModel));
105
+ for (const option of options) option.isDefault = option.model === defaultModel;
106
+ }
107
+ const pinned = nonBlank(pinnedModel);
108
+ if (pinned !== null && !options.some((option) => option.model === pinned)) {
109
+ options.push({ model: pinned, label: modelLabel(pinned), isDefault: false, pinnedOutside: true });
110
+ }
111
+ return { options, defaultModel };
112
+ }
113
+ export {
114
+ agentAssetIdOf,
115
+ agentDefaultModel,
116
+ allowsAnyModel,
117
+ buildAgentModelOptions,
118
+ isAutomaticModelLabel,
119
+ isPublishedAgent,
120
+ publishedAgentModelSource
121
+ };
122
+ //# sourceMappingURL=agent-model-options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-model-options.js","sources":["../../src/catalog/agent-model-options.ts"],"sourcesContent":["import { parseCollabAgentRef } from '../runtime/statewire-run-config';\n\n/**\n * A collab agent's own model choices, from its published definition: exactly\n * the models its author listed (the agent editor's Models section), the\n * default marked. The rules are Athena's own composer's (Olympus) and the\n * Jevin app's, so every host offers the same choices for one agent:\n *\n * - the options are the agent's labeled models, by the author's labels; an\n * automatic name (empty, the editor's old `{ id: 'default', label: 'Default' }`,\n * or one that only restates the model) shows the model's catalog label;\n * - a legacy agent with no labeled list offers its single `model`;\n * - the session channel's model override (`channelOverrides[channel].model`)\n * is that channel's default, added when the list lacks it;\n * - a model the thread pins that is none of the agent's models is shown as it\n * is (`pinnedOutside`), never replaced.\n *\n * Restricting a picker to these choices is opt-in per agent: only an agent\n * whose \"Allow any workspace model\" switch is off (`allowAnyModel: false`)\n * does; every other agent keeps the workspace's models.\n *\n * Pure, for `useAthenaAgentModelOptions` and the tests.\n */\n\n/** One model choice of a collab agent. */\nexport interface AthenaAgentModelOption {\n /** The model id a send carries (`model`). */\n model: string;\n /** The author's label, or the model's catalog label for an automatic name. */\n label: string;\n /** The model the thread runs while it pins none (the agent's own, or its channel's). */\n isDefault: boolean;\n /**\n * A model the thread's server config pins that is none of the agent's models\n * (pinned by another client, or by an older picker): shown, never replaced.\n */\n pinnedOutside: boolean;\n}\n\n/** One entry of the agent's labeled model list. */\nexport interface AgentLabeledModel {\n id: string;\n model: string;\n label: string;\n isDefault: boolean;\n}\n\n/** What an agent's published definition says about its models. */\nexport interface AgentModelSource {\n /** The model the definition runs (`config.model`); `null` when it names none. */\n model: string | null;\n /** The labeled list (`config.models`), complete rows only, in the author's order. */\n models: readonly AgentLabeledModel[];\n /** Per-channel model overrides (`channelOverrides[<channel>].model`), by channel id. */\n channelModels: Readonly<Record<string, string>>;\n /**\n * Whether people may pick any workspace model (`config.allowAnyModel`, the\n * agent editor's \"Allow any workspace model\" switch; see\n * {@link allowsAnyModel}). Only `false` restricts a picker to the agent's\n * own models.\n */\n allowAnyModel: boolean;\n}\n\n/**\n * Whether a config's `allowAnyModel` value lets people pick any model: only an\n * explicit `false` restricts; absent, `null`, `true` and any non-boolean allow\n * (agora's `collab_agent_allows_any_model`), so every agent saved before the\n * switch existed behaves as it always has.\n */\nexport function allowsAnyModel(value: unknown): boolean {\n return value !== false;\n}\n\n/** The display label of a model id; the id itself when the catalog does not know it. */\nexport type ModelLabelOf = (model: string) => string;\n\n/** The row the agent editor used to create for an agent with no stored entries. */\nconst LEGACY_DEFAULT_ENTRY_ID = 'default';\nconst LEGACY_DEFAULT_ENTRY_LABEL = 'Default';\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst nonBlank = (value: unknown): string | null =>\n typeof value === 'string' && value.trim() !== '' ? value.trim() : null;\n\n/**\n * The collab agent's asset id for an `agent` value: a `collab_agent:<id>` ref\n * (the provider's `agent` shape) or the bare asset id (the agent picker's\n * value). `null` for a blank value.\n */\nexport function agentAssetIdOf(agent: string | null | undefined): string | null {\n const trimmed = nonBlank(agent);\n if (trimmed === null) return null;\n return parseCollabAgentRef(trimmed) ?? (trimmed.includes(':') ? null : trimmed);\n}\n\n/** A stored `models` value: an array of rows, JSON-encoded or not; malformed rows dropped. */\nfunction labeledModelsOf(value: unknown): AgentLabeledModel[] {\n let rows: unknown = value;\n if (typeof rows === 'string') {\n try {\n rows = JSON.parse(rows);\n } catch {\n return [];\n }\n }\n if (!Array.isArray(rows)) return [];\n const entries: AgentLabeledModel[] = [];\n for (const row of rows) {\n if (!isRecord(row)) continue;\n const model = nonBlank(row.model);\n if (model === null) continue;\n entries.push({\n id: typeof row.id === 'string' && row.id !== '' ? row.id : model,\n model,\n label: nonBlank(row.label) ?? model,\n isDefault: row.isDefault === true,\n });\n }\n return entries;\n}\n\n/** A stored `channelOverrides` value's per-channel models. */\nfunction channelModelsOf(value: unknown): Record<string, string> {\n if (!isRecord(value)) return {};\n const out: Record<string, string> = {};\n for (const [channel, override] of Object.entries(value)) {\n const model = isRecord(override) ? nonBlank(override.model) : null;\n if (model !== null) out[channel] = model;\n }\n return out;\n}\n\n/** Whether the agent asset's `athena_metadata` points at a published version. */\nexport function isPublishedAgent(athenaMetadata: unknown): boolean {\n if (!isRecord(athenaMetadata)) return false;\n const versionId = athenaMetadata.published_version_id;\n return (\n (typeof versionId === 'string' && versionId !== '') ||\n (typeof versionId === 'number' && Number.isFinite(versionId))\n );\n}\n\n/**\n * The model source of the agent's PUBLISHED definition, which every SDK run\n * compiles, from the agent asset's `athena_metadata` (publish writes\n * `published_config` beside `published_version_id`). `null` for an agent\n * that was never published, or whose published definition names no model\n * (not even a channel's).\n */\nexport function publishedAgentModelSource(athenaMetadata: unknown): AgentModelSource | null {\n if (!isRecord(athenaMetadata) || !isPublishedAgent(athenaMetadata)) return null;\n const config = athenaMetadata.published_config;\n if (!isRecord(config)) return null;\n const source: AgentModelSource = {\n model: nonBlank(config.model),\n models: labeledModelsOf(config.models),\n channelModels: channelModelsOf(config.channelOverrides),\n // The editor stores the field name; agora also accepts its snake-case alias.\n allowAnyModel: allowsAnyModel(\n Object.hasOwn(config, 'allowAnyModel') ? config.allowAnyModel : config.allow_any_model,\n ),\n };\n const namesAModel =\n source.model !== null ||\n source.models.length > 0 ||\n Object.keys(source.channelModels).length > 0;\n return namesAModel ? source : null;\n}\n\n/**\n * Whether an entry's name is automatic: empty, the editor's old \"Default\", or\n * a name that only restates the entry's model (its id or catalog label).\n */\nexport function isAutomaticModelLabel({\n entry,\n modelLabel,\n}: {\n entry: Pick<AgentLabeledModel, 'id' | 'model' | 'label'>;\n modelLabel: ModelLabelOf;\n}): boolean {\n const label = entry.label.trim();\n if (label === '') return true;\n if (entry.id === LEGACY_DEFAULT_ENTRY_ID && label === LEGACY_DEFAULT_ENTRY_LABEL) return true;\n return label === entry.model || label === modelLabel(entry.model);\n}\n\n/**\n * The model a thread runs while it pins none: the channel's override, else the\n * definition's `model`, else its marked (or first) labeled entry.\n */\nexport function agentDefaultModel({\n source,\n channel,\n}: {\n source: AgentModelSource;\n channel: string | null;\n}): string | null {\n const marked = source.models.find((entry) => entry.isDefault) ?? source.models[0];\n return (channel ? source.channelModels[channel] : undefined) ?? source.model ?? marked?.model ?? null;\n}\n\n/**\n * The agent's model choices and its default, by the rules above. Options keep\n * the author's order, one per model (the first entry of a model names it); the\n * default is marked, and appended when the list lacks it; a pinned model\n * outside the list comes last.\n */\nexport function buildAgentModelOptions({\n source,\n channel,\n pinnedModel,\n modelLabel,\n}: {\n source: AgentModelSource;\n channel: string | null;\n /** The model the thread's server config pins; `null` while it pins none (or is unknown). */\n pinnedModel: string | null;\n modelLabel: ModelLabelOf;\n}): { options: AthenaAgentModelOption[]; defaultModel: string | null } {\n const options: AthenaAgentModelOption[] = [];\n const add = (model: string, label: string) => {\n if (options.some((option) => option.model === model)) return;\n options.push({ model, label, isDefault: false, pinnedOutside: false });\n };\n for (const entry of source.models) {\n add(\n entry.model,\n isAutomaticModelLabel({ entry, modelLabel }) ? modelLabel(entry.model) : entry.label.trim(),\n );\n }\n if (options.length === 0 && source.model !== null) add(source.model, modelLabel(source.model));\n\n const defaultModel = agentDefaultModel({ source, channel });\n if (defaultModel !== null) {\n add(defaultModel, modelLabel(defaultModel));\n for (const option of options) option.isDefault = option.model === defaultModel;\n }\n\n const pinned = nonBlank(pinnedModel);\n if (pinned !== null && !options.some((option) => option.model === pinned)) {\n options.push({ model: pinned, label: modelLabel(pinned), isDefault: false, pinnedOutside: true });\n }\n return { options, defaultModel };\n}\n"],"names":[],"mappings":"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"}