@molecule/app-ide-react 1.0.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 (250) hide show
  1. package/LICENSE +115 -0
  2. package/dist/chatTimelineOrdering.d.ts +50 -0
  3. package/dist/chatTimelineOrdering.d.ts.map +1 -0
  4. package/dist/chatTimelineOrdering.js +46 -0
  5. package/dist/chatTimelineOrdering.js.map +1 -0
  6. package/dist/command-metadata.d.ts +78 -0
  7. package/dist/command-metadata.d.ts.map +1 -0
  8. package/dist/command-metadata.js +214 -0
  9. package/dist/command-metadata.js.map +1 -0
  10. package/dist/components/ActivityCard.d.ts +28 -0
  11. package/dist/components/ActivityCard.d.ts.map +1 -0
  12. package/dist/components/ActivityCard.js +62 -0
  13. package/dist/components/ActivityCard.js.map +1 -0
  14. package/dist/components/AutoCommitBadge.d.ts +58 -0
  15. package/dist/components/AutoCommitBadge.d.ts.map +1 -0
  16. package/dist/components/AutoCommitBadge.js +78 -0
  17. package/dist/components/AutoCommitBadge.js.map +1 -0
  18. package/dist/components/ChatPanel.d.ts +184 -0
  19. package/dist/components/ChatPanel.d.ts.map +1 -0
  20. package/dist/components/ChatPanel.js +6691 -0
  21. package/dist/components/ChatPanel.js.map +1 -0
  22. package/dist/components/CommandPalette.d.ts +18 -0
  23. package/dist/components/CommandPalette.d.ts.map +1 -0
  24. package/dist/components/CommandPalette.js +33 -0
  25. package/dist/components/CommandPalette.js.map +1 -0
  26. package/dist/components/DeviceFrameSelector.d.ts +60 -0
  27. package/dist/components/DeviceFrameSelector.d.ts.map +1 -0
  28. package/dist/components/DeviceFrameSelector.js +230 -0
  29. package/dist/components/DeviceFrameSelector.js.map +1 -0
  30. package/dist/components/EditorPanel.d.ts +17 -0
  31. package/dist/components/EditorPanel.d.ts.map +1 -0
  32. package/dist/components/EditorPanel.js +241 -0
  33. package/dist/components/EditorPanel.js.map +1 -0
  34. package/dist/components/FileExplorer.d.ts +64 -0
  35. package/dist/components/FileExplorer.d.ts.map +1 -0
  36. package/dist/components/FileExplorer.js +1111 -0
  37. package/dist/components/FileExplorer.js.map +1 -0
  38. package/dist/components/FileExplorerContextMenu.d.ts +37 -0
  39. package/dist/components/FileExplorerContextMenu.d.ts.map +1 -0
  40. package/dist/components/FileExplorerContextMenu.js +241 -0
  41. package/dist/components/FileExplorerContextMenu.js.map +1 -0
  42. package/dist/components/HelpCard.d.ts +34 -0
  43. package/dist/components/HelpCard.d.ts.map +1 -0
  44. package/dist/components/HelpCard.js +28 -0
  45. package/dist/components/HelpCard.js.map +1 -0
  46. package/dist/components/Icon.d.ts +50 -0
  47. package/dist/components/Icon.d.ts.map +1 -0
  48. package/dist/components/Icon.js +25 -0
  49. package/dist/components/Icon.js.map +1 -0
  50. package/dist/components/KeyboardShortcutsPanel.d.ts +19 -0
  51. package/dist/components/KeyboardShortcutsPanel.d.ts.map +1 -0
  52. package/dist/components/KeyboardShortcutsPanel.js +187 -0
  53. package/dist/components/KeyboardShortcutsPanel.js.map +1 -0
  54. package/dist/components/MarkdownContent.d.ts +52 -0
  55. package/dist/components/MarkdownContent.d.ts.map +1 -0
  56. package/dist/components/MarkdownContent.js +303 -0
  57. package/dist/components/MarkdownContent.js.map +1 -0
  58. package/dist/components/ModelsTable.d.ts +38 -0
  59. package/dist/components/ModelsTable.d.ts.map +1 -0
  60. package/dist/components/ModelsTable.js +96 -0
  61. package/dist/components/ModelsTable.js.map +1 -0
  62. package/dist/components/PreviewPanel.d.ts +66 -0
  63. package/dist/components/PreviewPanel.d.ts.map +1 -0
  64. package/dist/components/PreviewPanel.js +1752 -0
  65. package/dist/components/PreviewPanel.js.map +1 -0
  66. package/dist/components/QuickOpen.d.ts +20 -0
  67. package/dist/components/QuickOpen.d.ts.map +1 -0
  68. package/dist/components/QuickOpen.js +72 -0
  69. package/dist/components/QuickOpen.js.map +1 -0
  70. package/dist/components/QuickPicker.d.ts +21 -0
  71. package/dist/components/QuickPicker.d.ts.map +1 -0
  72. package/dist/components/QuickPicker.js +174 -0
  73. package/dist/components/QuickPicker.js.map +1 -0
  74. package/dist/components/RelevantSkillSuggestion.d.ts +41 -0
  75. package/dist/components/RelevantSkillSuggestion.d.ts.map +1 -0
  76. package/dist/components/RelevantSkillSuggestion.js +85 -0
  77. package/dist/components/RelevantSkillSuggestion.js.map +1 -0
  78. package/dist/components/ReportModal.d.ts +35 -0
  79. package/dist/components/ReportModal.d.ts.map +1 -0
  80. package/dist/components/ReportModal.js +112 -0
  81. package/dist/components/ReportModal.js.map +1 -0
  82. package/dist/components/ResizeHandle.d.ts +21 -0
  83. package/dist/components/ResizeHandle.d.ts.map +1 -0
  84. package/dist/components/ResizeHandle.js +109 -0
  85. package/dist/components/ResizeHandle.js.map +1 -0
  86. package/dist/components/ScriptsCard.d.ts +37 -0
  87. package/dist/components/ScriptsCard.d.ts.map +1 -0
  88. package/dist/components/ScriptsCard.js +177 -0
  89. package/dist/components/ScriptsCard.js.map +1 -0
  90. package/dist/components/SearchPanel.d.ts +18 -0
  91. package/dist/components/SearchPanel.d.ts.map +1 -0
  92. package/dist/components/SearchPanel.js +449 -0
  93. package/dist/components/SearchPanel.js.map +1 -0
  94. package/dist/components/SettingsCard.d.ts +49 -0
  95. package/dist/components/SettingsCard.d.ts.map +1 -0
  96. package/dist/components/SettingsCard.js +59 -0
  97. package/dist/components/SettingsCard.js.map +1 -0
  98. package/dist/components/ShareModal.d.ts +32 -0
  99. package/dist/components/ShareModal.d.ts.map +1 -0
  100. package/dist/components/ShareModal.js +126 -0
  101. package/dist/components/ShareModal.js.map +1 -0
  102. package/dist/components/SidebarTabs.d.ts +18 -0
  103. package/dist/components/SidebarTabs.d.ts.map +1 -0
  104. package/dist/components/SidebarTabs.js +20 -0
  105. package/dist/components/SidebarTabs.js.map +1 -0
  106. package/dist/components/SkillsCard.d.ts +86 -0
  107. package/dist/components/SkillsCard.d.ts.map +1 -0
  108. package/dist/components/SkillsCard.js +227 -0
  109. package/dist/components/SkillsCard.js.map +1 -0
  110. package/dist/components/StreamingIndicator.d.ts +50 -0
  111. package/dist/components/StreamingIndicator.d.ts.map +1 -0
  112. package/dist/components/StreamingIndicator.js +142 -0
  113. package/dist/components/StreamingIndicator.js.map +1 -0
  114. package/dist/components/TabBar.d.ts +37 -0
  115. package/dist/components/TabBar.d.ts.map +1 -0
  116. package/dist/components/TabBar.js +187 -0
  117. package/dist/components/TabBar.js.map +1 -0
  118. package/dist/components/TipCard.d.ts +22 -0
  119. package/dist/components/TipCard.d.ts.map +1 -0
  120. package/dist/components/TipCard.js +73 -0
  121. package/dist/components/TipCard.js.map +1 -0
  122. package/dist/components/ToolCallCard.d.ts +18 -0
  123. package/dist/components/ToolCallCard.d.ts.map +1 -0
  124. package/dist/components/ToolCallCard.js +716 -0
  125. package/dist/components/ToolCallCard.js.map +1 -0
  126. package/dist/components/UserAvatar.d.ts +45 -0
  127. package/dist/components/UserAvatar.d.ts.map +1 -0
  128. package/dist/components/UserAvatar.js +80 -0
  129. package/dist/components/UserAvatar.js.map +1 -0
  130. package/dist/components/WorkspaceLayout.d.ts +19 -0
  131. package/dist/components/WorkspaceLayout.d.ts.map +1 -0
  132. package/dist/components/WorkspaceLayout.js +56 -0
  133. package/dist/components/WorkspaceLayout.js.map +1 -0
  134. package/dist/components/activity-utilities.d.ts +107 -0
  135. package/dist/components/activity-utilities.d.ts.map +1 -0
  136. package/dist/components/activity-utilities.js +154 -0
  137. package/dist/components/activity-utilities.js.map +1 -0
  138. package/dist/components/chat-autocommit-utilities.d.ts +172 -0
  139. package/dist/components/chat-autocommit-utilities.d.ts.map +1 -0
  140. package/dist/components/chat-autocommit-utilities.js +185 -0
  141. package/dist/components/chat-autocommit-utilities.js.map +1 -0
  142. package/dist/components/chat-card-style.d.ts +64 -0
  143. package/dist/components/chat-card-style.d.ts.map +1 -0
  144. package/dist/components/chat-card-style.js +74 -0
  145. package/dist/components/chat-card-style.js.map +1 -0
  146. package/dist/components/chat-commands.d.ts +17 -0
  147. package/dist/components/chat-commands.d.ts.map +1 -0
  148. package/dist/components/chat-commands.js +17 -0
  149. package/dist/components/chat-commands.js.map +1 -0
  150. package/dist/components/chat-commit-utilities.d.ts +29 -0
  151. package/dist/components/chat-commit-utilities.d.ts.map +1 -0
  152. package/dist/components/chat-commit-utilities.js +31 -0
  153. package/dist/components/chat-commit-utilities.js.map +1 -0
  154. package/dist/components/chat-cost-utilities.d.ts +62 -0
  155. package/dist/components/chat-cost-utilities.d.ts.map +1 -0
  156. package/dist/components/chat-cost-utilities.js +66 -0
  157. package/dist/components/chat-cost-utilities.js.map +1 -0
  158. package/dist/components/chat-effort-utilities.d.ts +96 -0
  159. package/dist/components/chat-effort-utilities.d.ts.map +1 -0
  160. package/dist/components/chat-effort-utilities.js +97 -0
  161. package/dist/components/chat-effort-utilities.js.map +1 -0
  162. package/dist/components/chat-help-utilities.d.ts +89 -0
  163. package/dist/components/chat-help-utilities.d.ts.map +1 -0
  164. package/dist/components/chat-help-utilities.js +131 -0
  165. package/dist/components/chat-help-utilities.js.map +1 -0
  166. package/dist/components/chat-model-mode-utilities.d.ts +143 -0
  167. package/dist/components/chat-model-mode-utilities.d.ts.map +1 -0
  168. package/dist/components/chat-model-mode-utilities.js +191 -0
  169. package/dist/components/chat-model-mode-utilities.js.map +1 -0
  170. package/dist/components/chat-models-utilities.d.ts +98 -0
  171. package/dist/components/chat-models-utilities.d.ts.map +1 -0
  172. package/dist/components/chat-models-utilities.js +136 -0
  173. package/dist/components/chat-models-utilities.js.map +1 -0
  174. package/dist/components/chat-report-utilities.d.ts +149 -0
  175. package/dist/components/chat-report-utilities.d.ts.map +1 -0
  176. package/dist/components/chat-report-utilities.js +151 -0
  177. package/dist/components/chat-report-utilities.js.map +1 -0
  178. package/dist/components/chat-scripts-utilities.d.ts +140 -0
  179. package/dist/components/chat-scripts-utilities.d.ts.map +1 -0
  180. package/dist/components/chat-scripts-utilities.js +158 -0
  181. package/dist/components/chat-scripts-utilities.js.map +1 -0
  182. package/dist/components/chat-settings-utilities.d.ts +72 -0
  183. package/dist/components/chat-settings-utilities.d.ts.map +1 -0
  184. package/dist/components/chat-settings-utilities.js +56 -0
  185. package/dist/components/chat-settings-utilities.js.map +1 -0
  186. package/dist/components/chat-share-utilities.d.ts +104 -0
  187. package/dist/components/chat-share-utilities.d.ts.map +1 -0
  188. package/dist/components/chat-share-utilities.js +90 -0
  189. package/dist/components/chat-share-utilities.js.map +1 -0
  190. package/dist/components/chat-skills-utilities.d.ts +242 -0
  191. package/dist/components/chat-skills-utilities.d.ts.map +1 -0
  192. package/dist/components/chat-skills-utilities.js +433 -0
  193. package/dist/components/chat-skills-utilities.js.map +1 -0
  194. package/dist/components/chat-stream-utilities.d.ts +74 -0
  195. package/dist/components/chat-stream-utilities.d.ts.map +1 -0
  196. package/dist/components/chat-stream-utilities.js +103 -0
  197. package/dist/components/chat-stream-utilities.js.map +1 -0
  198. package/dist/components/chat-tips-utilities.d.ts +108 -0
  199. package/dist/components/chat-tips-utilities.d.ts.map +1 -0
  200. package/dist/components/chat-tips-utilities.js +137 -0
  201. package/dist/components/chat-tips-utilities.js.map +1 -0
  202. package/dist/components/device-cycle.d.ts +84 -0
  203. package/dist/components/device-cycle.d.ts.map +1 -0
  204. package/dist/components/device-cycle.js +77 -0
  205. package/dist/components/device-cycle.js.map +1 -0
  206. package/dist/components/index.d.ts +36 -0
  207. package/dist/components/index.d.ts.map +1 -0
  208. package/dist/components/index.js +33 -0
  209. package/dist/components/index.js.map +1 -0
  210. package/dist/components/tool-call-utilities.d.ts +69 -0
  211. package/dist/components/tool-call-utilities.d.ts.map +1 -0
  212. package/dist/components/tool-call-utilities.js +368 -0
  213. package/dist/components/tool-call-utilities.js.map +1 -0
  214. package/dist/components/user-avatar-utilities.d.ts +41 -0
  215. package/dist/components/user-avatar-utilities.d.ts.map +1 -0
  216. package/dist/components/user-avatar-utilities.js +56 -0
  217. package/dist/components/user-avatar-utilities.js.map +1 -0
  218. package/dist/components/workspace-layout-utilities.d.ts +37 -0
  219. package/dist/components/workspace-layout-utilities.d.ts.map +1 -0
  220. package/dist/components/workspace-layout-utilities.js +58 -0
  221. package/dist/components/workspace-layout-utilities.js.map +1 -0
  222. package/dist/customEventCards.d.ts +117 -0
  223. package/dist/customEventCards.d.ts.map +1 -0
  224. package/dist/customEventCards.js +36 -0
  225. package/dist/customEventCards.js.map +1 -0
  226. package/dist/hooks/index.d.ts +8 -0
  227. package/dist/hooks/index.d.ts.map +1 -0
  228. package/dist/hooks/index.js +8 -0
  229. package/dist/hooks/index.js.map +1 -0
  230. package/dist/hooks/useKeyboardShortcuts.d.ts +48 -0
  231. package/dist/hooks/useKeyboardShortcuts.d.ts.map +1 -0
  232. package/dist/hooks/useKeyboardShortcuts.js +112 -0
  233. package/dist/hooks/useKeyboardShortcuts.js.map +1 -0
  234. package/dist/hooks/useViewport.d.ts +42 -0
  235. package/dist/hooks/useViewport.d.ts.map +1 -0
  236. package/dist/hooks/useViewport.js +61 -0
  237. package/dist/hooks/useViewport.js.map +1 -0
  238. package/dist/index.d.ts +75 -0
  239. package/dist/index.d.ts.map +1 -0
  240. package/dist/index.js +74 -0
  241. package/dist/index.js.map +1 -0
  242. package/dist/settings-metadata.d.ts +78 -0
  243. package/dist/settings-metadata.d.ts.map +1 -0
  244. package/dist/settings-metadata.js +131 -0
  245. package/dist/settings-metadata.js.map +1 -0
  246. package/dist/types.d.ts +751 -0
  247. package/dist/types.d.ts.map +1 -0
  248. package/dist/types.js +25 -0
  249. package/dist/types.js.map +1 -0
  250. package/package.json +90 -0
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Pure helpers backing the per-mode model pickers (`/model --plan`,
3
+ * `/model --execute`, `/model --commit`, and `/model --compact`).
4
+ *
5
+ * A project stores optional per-mode model ids in its settings — `planModel`
6
+ * (used in plan mode) and `executeModel` (used in execute mode) alongside the
7
+ * legacy single `chatModel`, plus two auxiliary jobs: `commitModel` (the
8
+ * commit-message generator) and `compactModel` (conversation compaction), which
9
+ * fall back to the server's own fast default (never `chatModel`) when unset.
10
+ * These helpers parse the mode flag, map a mode to its settings field, resolve
11
+ * the effective model for a mode, and compute the free-tier clamp (plan →
12
+ * `deepseek-v4-pro`, execute → `deepseek-v4-flash`, commit/compact → any
13
+ * free-tier-flagged model) against the live catalog. The clamp ids mirror the
14
+ * server's FREE_TIER_MODELS in molecule-dev's `model-selection.ts` — keep the
15
+ * two in sync (a stale Sonnet-plan assumption here once highlighted the wrong
16
+ * "current" model in the picker).
17
+ *
18
+ * Everything here is deterministic and side-effect free so it can be unit tested
19
+ * without rendering or a backend. The component owns persistence (`PATCH
20
+ * /projects/:id { settings }`) and any `t()` prose.
21
+ *
22
+ * @module
23
+ */
24
+ import type { AppModelDefinition } from '@molecule/app-ai-models';
25
+ /** A conversation mode or auxiliary job that can have its own model. */
26
+ export type ModelMode = 'plan' | 'execute' | 'commit' | 'compact';
27
+ /** The subset of project settings this module reads for model resolution. */
28
+ export interface PerModeModelSettings {
29
+ /** Model id used in plan mode (falls back to {@link PerModeModelSettings.chatModel}). */
30
+ planModel?: string;
31
+ /** Model id used in execute mode (falls back to {@link PerModeModelSettings.chatModel}). */
32
+ executeModel?: string;
33
+ /** Model id for commit-message generation (server's fast default when unset). */
34
+ commitModel?: string;
35
+ /** Model id for conversation compaction (server's fast default when unset). */
36
+ compactModel?: string;
37
+ /** Legacy single model id, used when the plan/execute field is unset. */
38
+ chatModel?: string;
39
+ }
40
+ /**
41
+ * Parses a `/model --plan`, `--execute`, `--commit`, or `--compact` command.
42
+ * Returns the targeted mode plus any trailing filter `query` (the text after
43
+ * the flag), or `null` when the input is not a mode-scoped `/model` command.
44
+ * Use this *before* the generic `/model <name>` handler so the flag is not
45
+ * mistaken for a model name.
46
+ *
47
+ * @param input - The raw chat input.
48
+ * @returns `{ mode, query }` for a mode-scoped command, else `null`.
49
+ */
50
+ export declare function parseModelModeCommand(input: string): {
51
+ mode: ModelMode;
52
+ query: string;
53
+ } | null;
54
+ /**
55
+ * Maps a mode to the project-settings field that stores its model id.
56
+ *
57
+ * @param mode - The conversation mode or auxiliary job.
58
+ * @returns The settings field for the mode (e.g. `'planModel'` for plan mode).
59
+ */
60
+ export declare function modeSettingKey(mode: ModelMode): 'planModel' | 'executeModel' | 'commitModel' | 'compactModel';
61
+ /**
62
+ * Resolves the effective model id for a mode. The conversation modes
63
+ * (plan/execute) fall back to the legacy single `chatModel` when the per-mode
64
+ * field is unset (back-compat); the auxiliary jobs (commit/compact) do NOT —
65
+ * the server applies its own fast default for them, mirrored here by returning
66
+ * `undefined`.
67
+ *
68
+ * @param settings - The project's model settings.
69
+ * @param mode - The conversation mode or auxiliary job.
70
+ * @returns The resolved model id, or `undefined` when nothing is configured.
71
+ */
72
+ export declare function resolveModeModel(settings: PerModeModelSettings, mode: ModelMode): string | undefined;
73
+ /**
74
+ * Computes the free-tier-allowed model id for a mode from the live catalog. The
75
+ * free/anon tier is clamped — with no mixing — to `deepseek-v4-pro` in plan
76
+ * mode and `deepseek-v4-flash` in execute mode (the auxiliary commit/compact
77
+ * jobs share the execute clamp), mirroring the server's FREE_TIER_MODELS.
78
+ * Matching is catalog-driven (by id/provider) so it survives id churn; when no
79
+ * specific match exists it falls back to the supplied `fallback` (the
80
+ * catalog's free-tier model id).
81
+ *
82
+ * @param models - The available models from the catalog.
83
+ * @param mode - The conversation mode or auxiliary job.
84
+ * @param fallback - The free-tier model id to use when no match is found.
85
+ * @returns The clamped model id for the mode.
86
+ */
87
+ export declare function freeTierModeModelId(models: readonly AppModelDefinition[], mode: ModelMode, fallback: string): string;
88
+ /**
89
+ * Why a model is locked for a free-tier user in a given mode — `'paid'` for a
90
+ * genuinely paid model, `'mode'` for a model the free tier CAN use, just in a
91
+ * different mode (e.g. the execute clamp shown in the plan-scoped picker).
92
+ */
93
+ export type FreeTierLockReason = 'paid' | 'mode';
94
+ /**
95
+ * The lock reason for a model in a mode under the free-tier clamp, or `null`
96
+ * when it is selectable. Pro users and `provider: 'custom'` (bring-your-own
97
+ * AI) models are never locked; a free/anon user may pick only the mode's
98
+ * clamped model (plan/execute) or any free-tier-flagged model (the auxiliary
99
+ * commit/compact jobs, mirroring the server's aux-model selection, which
100
+ * ignores a non-free platform pick anyway).
101
+ *
102
+ * A locked model splits into two very different cases the UI must not
103
+ * conflate: `'paid'` (upgrade actually unlocks it) versus `'mode'` (the model
104
+ * is free-tier-usable in ANOTHER mode — it is a different mode's clamp, or
105
+ * free-tier-flagged and thus selectable for commit/compact — so a "Pro" badge
106
+ * would be flatly wrong).
107
+ *
108
+ * @param modelId - The candidate model id.
109
+ * @param mode - The conversation mode the picker is scoped to.
110
+ * @param isFreeTier - Whether the current user is on the free/anon tier.
111
+ * @param models - The available models from the catalog.
112
+ * @param fallback - The free-tier model id to use when no clamp match is found.
113
+ * @returns `'paid'` / `'mode'` when locked, `null` when selectable.
114
+ */
115
+ export declare function freeTierLockReason(modelId: string, mode: ModelMode, isFreeTier: boolean, models: readonly AppModelDefinition[], fallback: string): FreeTierLockReason | null;
116
+ /**
117
+ * The mode a free-tier user can actually use this model in — `'plan'` /
118
+ * `'execute'` when it is that mode's clamp, `'commit'` (standing for both
119
+ * auxiliary jobs) when it is merely free-tier-flagged, `null` when the free
120
+ * tier can't use it anywhere. Drives the "free in <mode>" labeling next to a
121
+ * `'mode'`-locked row.
122
+ *
123
+ * @param modelId - The candidate model id.
124
+ * @param models - The available models from the catalog.
125
+ * @param fallback - The free-tier model id to use when no clamp match is found.
126
+ * @returns The mode the model is free-tier-usable in, or `null`.
127
+ */
128
+ export declare function freeTierUsableMode(modelId: string, models: readonly AppModelDefinition[], fallback: string): ModelMode | null;
129
+ /**
130
+ * Whether a model is locked (not selectable) for the given mode under the
131
+ * free-tier clamp — the boolean view of {@link freeTierLockReason} (which see
132
+ * for the rules: pro users and custom models never lock; plan/execute clamp to
133
+ * one model; commit/compact accept any free-tier-flagged model).
134
+ *
135
+ * @param modelId - The candidate model id.
136
+ * @param mode - The conversation mode the picker is scoped to.
137
+ * @param isFreeTier - Whether the current user is on the free/anon tier.
138
+ * @param models - The available models from the catalog.
139
+ * @param fallback - The free-tier model id to use when no clamp match is found.
140
+ * @returns `true` when the model is locked for this user and mode.
141
+ */
142
+ export declare function isModeModelLocked(modelId: string, mode: ModelMode, isFreeTier: boolean, models: readonly AppModelDefinition[], fallback: string): boolean;
143
+ //# sourceMappingURL=chat-model-mode-utilities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-model-mode-utilities.d.ts","sourceRoot":"","sources":["../../src/components/chat-model-mode-utilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEjE,wEAAwE;AACxE,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAA;AAEjE,6EAA6E;AAC7E,MAAM,WAAW,oBAAoB;IACnC,yFAAyF;IACzF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4FAA4F;IAC5F,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAI9F;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,SAAS,GACd,WAAW,GAAG,cAAc,GAAG,aAAa,GAAG,cAAc,CAW/D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,oBAAoB,EAC9B,IAAI,EAAE,SAAS,GACd,MAAM,GAAG,SAAS,CAWpB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,SAAS,kBAAkB,EAAE,EACrC,IAAI,EAAE,SAAS,EACf,QAAQ,EAAE,MAAM,GACf,MAAM,CAgBR;AAED;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,MAAM,CAAA;AAKhD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,EACf,UAAU,EAAE,OAAO,EACnB,MAAM,EAAE,SAAS,kBAAkB,EAAE,EACrC,QAAQ,EAAE,MAAM,GACf,kBAAkB,GAAG,IAAI,CAe3B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,SAAS,kBAAkB,EAAE,EACrC,QAAQ,EAAE,MAAM,GACf,SAAS,GAAG,IAAI,CAKlB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,EACf,UAAU,EAAE,OAAO,EACnB,MAAM,EAAE,SAAS,kBAAkB,EAAE,EACrC,QAAQ,EAAE,MAAM,GACf,OAAO,CAET"}
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Pure helpers backing the per-mode model pickers (`/model --plan`,
3
+ * `/model --execute`, `/model --commit`, and `/model --compact`).
4
+ *
5
+ * A project stores optional per-mode model ids in its settings — `planModel`
6
+ * (used in plan mode) and `executeModel` (used in execute mode) alongside the
7
+ * legacy single `chatModel`, plus two auxiliary jobs: `commitModel` (the
8
+ * commit-message generator) and `compactModel` (conversation compaction), which
9
+ * fall back to the server's own fast default (never `chatModel`) when unset.
10
+ * These helpers parse the mode flag, map a mode to its settings field, resolve
11
+ * the effective model for a mode, and compute the free-tier clamp (plan →
12
+ * `deepseek-v4-pro`, execute → `deepseek-v4-flash`, commit/compact → any
13
+ * free-tier-flagged model) against the live catalog. The clamp ids mirror the
14
+ * server's FREE_TIER_MODELS in molecule-dev's `model-selection.ts` — keep the
15
+ * two in sync (a stale Sonnet-plan assumption here once highlighted the wrong
16
+ * "current" model in the picker).
17
+ *
18
+ * Everything here is deterministic and side-effect free so it can be unit tested
19
+ * without rendering or a backend. The component owns persistence (`PATCH
20
+ * /projects/:id { settings }`) and any `t()` prose.
21
+ *
22
+ * @module
23
+ */
24
+ /**
25
+ * Parses a `/model --plan`, `--execute`, `--commit`, or `--compact` command.
26
+ * Returns the targeted mode plus any trailing filter `query` (the text after
27
+ * the flag), or `null` when the input is not a mode-scoped `/model` command.
28
+ * Use this *before* the generic `/model <name>` handler so the flag is not
29
+ * mistaken for a model name.
30
+ *
31
+ * @param input - The raw chat input.
32
+ * @returns `{ mode, query }` for a mode-scoped command, else `null`.
33
+ */
34
+ export function parseModelModeCommand(input) {
35
+ const match = input.trim().match(/^\/model\s+--(plan|execute|commit|compact)\b\s*(.*)$/i);
36
+ if (!match)
37
+ return null;
38
+ return { mode: match[1].toLowerCase(), query: match[2].trim() };
39
+ }
40
+ /**
41
+ * Maps a mode to the project-settings field that stores its model id.
42
+ *
43
+ * @param mode - The conversation mode or auxiliary job.
44
+ * @returns The settings field for the mode (e.g. `'planModel'` for plan mode).
45
+ */
46
+ export function modeSettingKey(mode) {
47
+ switch (mode) {
48
+ case 'plan':
49
+ return 'planModel';
50
+ case 'execute':
51
+ return 'executeModel';
52
+ case 'commit':
53
+ return 'commitModel';
54
+ case 'compact':
55
+ return 'compactModel';
56
+ }
57
+ }
58
+ /**
59
+ * Resolves the effective model id for a mode. The conversation modes
60
+ * (plan/execute) fall back to the legacy single `chatModel` when the per-mode
61
+ * field is unset (back-compat); the auxiliary jobs (commit/compact) do NOT —
62
+ * the server applies its own fast default for them, mirrored here by returning
63
+ * `undefined`.
64
+ *
65
+ * @param settings - The project's model settings.
66
+ * @param mode - The conversation mode or auxiliary job.
67
+ * @returns The resolved model id, or `undefined` when nothing is configured.
68
+ */
69
+ export function resolveModeModel(settings, mode) {
70
+ switch (mode) {
71
+ case 'plan':
72
+ return settings.planModel ?? settings.chatModel;
73
+ case 'execute':
74
+ return settings.executeModel ?? settings.chatModel;
75
+ case 'commit':
76
+ return settings.commitModel;
77
+ case 'compact':
78
+ return settings.compactModel;
79
+ }
80
+ }
81
+ /**
82
+ * Computes the free-tier-allowed model id for a mode from the live catalog. The
83
+ * free/anon tier is clamped — with no mixing — to `deepseek-v4-pro` in plan
84
+ * mode and `deepseek-v4-flash` in execute mode (the auxiliary commit/compact
85
+ * jobs share the execute clamp), mirroring the server's FREE_TIER_MODELS.
86
+ * Matching is catalog-driven (by id/provider) so it survives id churn; when no
87
+ * specific match exists it falls back to the supplied `fallback` (the
88
+ * catalog's free-tier model id).
89
+ *
90
+ * @param models - The available models from the catalog.
91
+ * @param mode - The conversation mode or auxiliary job.
92
+ * @param fallback - The free-tier model id to use when no match is found.
93
+ * @returns The clamped model id for the mode.
94
+ */
95
+ export function freeTierModeModelId(models, mode, fallback) {
96
+ if (mode === 'plan') {
97
+ const exact = models.find((m) => m.id === 'deepseek-v4-pro');
98
+ if (exact)
99
+ return exact.id;
100
+ const deepseekPro = models.find((m) => m.provider === 'deepseek' && /pro/i.test(m.id));
101
+ if (deepseekPro)
102
+ return deepseekPro.id;
103
+ const anyDeepseek = models.find((m) => m.provider === 'deepseek');
104
+ return anyDeepseek?.id ?? fallback;
105
+ }
106
+ // execute (and the commit/compact auxiliary jobs, which share its clamp)
107
+ const exact = models.find((m) => m.id === 'deepseek-v4-flash');
108
+ if (exact)
109
+ return exact.id;
110
+ const deepseekFlash = models.find((m) => m.provider === 'deepseek' && /flash/i.test(m.id));
111
+ if (deepseekFlash)
112
+ return deepseekFlash.id;
113
+ const anyDeepseek = models.find((m) => m.provider === 'deepseek');
114
+ return anyDeepseek?.id ?? fallback;
115
+ }
116
+ /** Every mode a per-mode clamp exists for — used to test "free elsewhere". */
117
+ const MODEL_MODES = ['plan', 'execute', 'commit', 'compact'];
118
+ /**
119
+ * The lock reason for a model in a mode under the free-tier clamp, or `null`
120
+ * when it is selectable. Pro users and `provider: 'custom'` (bring-your-own
121
+ * AI) models are never locked; a free/anon user may pick only the mode's
122
+ * clamped model (plan/execute) or any free-tier-flagged model (the auxiliary
123
+ * commit/compact jobs, mirroring the server's aux-model selection, which
124
+ * ignores a non-free platform pick anyway).
125
+ *
126
+ * A locked model splits into two very different cases the UI must not
127
+ * conflate: `'paid'` (upgrade actually unlocks it) versus `'mode'` (the model
128
+ * is free-tier-usable in ANOTHER mode — it is a different mode's clamp, or
129
+ * free-tier-flagged and thus selectable for commit/compact — so a "Pro" badge
130
+ * would be flatly wrong).
131
+ *
132
+ * @param modelId - The candidate model id.
133
+ * @param mode - The conversation mode the picker is scoped to.
134
+ * @param isFreeTier - Whether the current user is on the free/anon tier.
135
+ * @param models - The available models from the catalog.
136
+ * @param fallback - The free-tier model id to use when no clamp match is found.
137
+ * @returns `'paid'` / `'mode'` when locked, `null` when selectable.
138
+ */
139
+ export function freeTierLockReason(modelId, mode, isFreeTier, models, fallback) {
140
+ if (!isFreeTier)
141
+ return null;
142
+ const candidate = models.find((m) => m.id === modelId);
143
+ if (candidate?.provider === 'custom')
144
+ return null;
145
+ const allowedHere = mode === 'commit' || mode === 'compact'
146
+ ? candidate?.freeTier === true
147
+ : modelId === freeTierModeModelId(models, mode, fallback);
148
+ if (allowedHere)
149
+ return null;
150
+ const freeElsewhere = candidate?.freeTier === true ||
151
+ MODEL_MODES.some((other) => other !== mode && modelId === freeTierModeModelId(models, other, fallback));
152
+ return freeElsewhere ? 'mode' : 'paid';
153
+ }
154
+ /**
155
+ * The mode a free-tier user can actually use this model in — `'plan'` /
156
+ * `'execute'` when it is that mode's clamp, `'commit'` (standing for both
157
+ * auxiliary jobs) when it is merely free-tier-flagged, `null` when the free
158
+ * tier can't use it anywhere. Drives the "free in <mode>" labeling next to a
159
+ * `'mode'`-locked row.
160
+ *
161
+ * @param modelId - The candidate model id.
162
+ * @param models - The available models from the catalog.
163
+ * @param fallback - The free-tier model id to use when no clamp match is found.
164
+ * @returns The mode the model is free-tier-usable in, or `null`.
165
+ */
166
+ export function freeTierUsableMode(modelId, models, fallback) {
167
+ if (modelId === freeTierModeModelId(models, 'plan', fallback))
168
+ return 'plan';
169
+ if (modelId === freeTierModeModelId(models, 'execute', fallback))
170
+ return 'execute';
171
+ if (models.find((m) => m.id === modelId)?.freeTier === true)
172
+ return 'commit';
173
+ return null;
174
+ }
175
+ /**
176
+ * Whether a model is locked (not selectable) for the given mode under the
177
+ * free-tier clamp — the boolean view of {@link freeTierLockReason} (which see
178
+ * for the rules: pro users and custom models never lock; plan/execute clamp to
179
+ * one model; commit/compact accept any free-tier-flagged model).
180
+ *
181
+ * @param modelId - The candidate model id.
182
+ * @param mode - The conversation mode the picker is scoped to.
183
+ * @param isFreeTier - Whether the current user is on the free/anon tier.
184
+ * @param models - The available models from the catalog.
185
+ * @param fallback - The free-tier model id to use when no clamp match is found.
186
+ * @returns `true` when the model is locked for this user and mode.
187
+ */
188
+ export function isModeModelLocked(modelId, mode, isFreeTier, models, fallback) {
189
+ return freeTierLockReason(modelId, mode, isFreeTier, models, fallback) !== null;
190
+ }
191
+ //# sourceMappingURL=chat-model-mode-utilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-model-mode-utilities.js","sourceRoot":"","sources":["../../src/components/chat-model-mode-utilities.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Pure helpers backing the `/models` comparison table.
3
+ *
4
+ * Sorting is split out from the React component so the comparator can be unit
5
+ * tested without rendering. All functions are deterministic and side-effect
6
+ * free.
7
+ *
8
+ * @module
9
+ */
10
+ import type { AppModelDefinition } from '@molecule/app-ai-models';
11
+ /**
12
+ * Sortable columns in the `/models` table.
13
+ *
14
+ * `cutoff` (knowledge-cutoff date) replaced a former `tier` column that was
15
+ * fabricated from input price: it duplicated the `cost` axis and was sometimes
16
+ * factually wrong (a slow reasoning model could read "Balanced"). The catalog
17
+ * has no latency field, so rather than invent one we expose `knowledgeCutoff` —
18
+ * a real, factual axis that is genuinely independent of price, context, and
19
+ * output size.
20
+ */
21
+ export type ModelSortColumn = 'name' | 'context' | 'cost' | 'cutoff' | 'free' | 'region';
22
+ /** Sort direction. */
23
+ export type SortDirection = 'asc' | 'desc';
24
+ /**
25
+ * Processing region of a model, as an arbitrary region code (`'us'`, `'cn'`,
26
+ * potentially `'eu'` etc. later). `'us'` is the platform default; deliberately
27
+ * NOT a closed union so adding a region never changes this contract.
28
+ */
29
+ export type ModelProcessingRegion = string;
30
+ /**
31
+ * Resolve a model's effective processing region: the requested region when the
32
+ * model's `regions` list offers it, else the model's DEFAULT region (the first
33
+ * listed; `'us'` when the catalog omits regions). A single-entry list pins the
34
+ * model regardless of the request. Mirrors the server-side
35
+ * `effectiveModelRegion` in `@molecule/api-resource-ai-models`.
36
+ *
37
+ * @param model - The model metadata.
38
+ * @param requested - The user's per-model region choice, if any.
39
+ * @returns The effective region code.
40
+ */
41
+ export declare function effectiveModelRegion(model: AppModelDefinition, requested?: string): ModelProcessingRegion;
42
+ /**
43
+ * Combined input + output price per million tokens, used as the single
44
+ * "cost/1M tokens" figure — at the rates of the model's effective region
45
+ * (`regionPricing` override when the region has one, else the base rates).
46
+ *
47
+ * @param model - The model metadata.
48
+ * @param region - The user's per-model region choice, if any (omitted → the
49
+ * model's default region).
50
+ * @returns Sum of input and output price per million tokens in USD.
51
+ */
52
+ export declare function modelTotalCost(model: AppModelDefinition, region?: string): number;
53
+ /**
54
+ * Relative usage rate: how much faster this model consumes the user's AI
55
+ * allowance than the cheapest available model (a unitless `×N` multiplier,
56
+ * `1` for the cheapest). Derived from combined input+output list rates. This
57
+ * is the ONLY per-model "cost" figure user-facing surfaces show — AI usage is
58
+ * never presented as currency.
59
+ *
60
+ * The base is every model's DEFAULT-region cost (stable regardless of the
61
+ * user's per-model region choices); the rated model itself is priced at the
62
+ * given region so the figure tracks what a region switch actually changes.
63
+ *
64
+ * @param model - The model to rate.
65
+ * @param models - The full available-model list (supplies the cheapest base).
66
+ * @param region - The rated model's region choice, if any (omitted → its
67
+ * default region).
68
+ * @returns The multiplier, rounded to 1 decimal below 10 and whole above.
69
+ */
70
+ export declare function modelUsageRate(model: AppModelDefinition, models: readonly AppModelDefinition[], region?: string): number;
71
+ /**
72
+ * Ascending comparator for two models by the given column. Returns a negative,
73
+ * zero, or positive number suitable for `Array.prototype.sort`. Ties fall back
74
+ * to label order so the sort is stable and deterministic.
75
+ *
76
+ * @param a - First model.
77
+ * @param b - Second model.
78
+ * @param column - Column to compare by.
79
+ * @param regionOf - Resolves a model's effective processing region (used by
80
+ * the `'region'` and `'cost'` columns; omitting it treats every model as its
81
+ * default region). The caller supplies this because the effective region
82
+ * depends on per-project settings the pure helper cannot know.
83
+ * @returns Negative if `a` sorts before `b`, positive if after, zero if equal.
84
+ */
85
+ export declare function compareModels(a: AppModelDefinition, b: AppModelDefinition, column: ModelSortColumn, regionOf?: (model: AppModelDefinition) => ModelProcessingRegion): number;
86
+ /**
87
+ * Returns a new array of models sorted by the given column and direction.
88
+ * Does not mutate the input.
89
+ *
90
+ * @param models - Models to sort.
91
+ * @param column - Column to sort by.
92
+ * @param direction - `'asc'` or `'desc'`.
93
+ * @param regionOf - Resolves a model's effective processing region (used by
94
+ * the `'region'` and `'cost'` columns). See {@link compareModels}.
95
+ * @returns A new, sorted array.
96
+ */
97
+ export declare function sortModels(models: readonly AppModelDefinition[], column: ModelSortColumn, direction: SortDirection, regionOf?: (model: AppModelDefinition) => ModelProcessingRegion): AppModelDefinition[];
98
+ //# sourceMappingURL=chat-models-utilities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-models-utilities.d.ts","sourceRoot":"","sources":["../../src/components/chat-models-utilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEjE;;;;;;;;;GASG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAA;AAExF,sBAAsB;AACtB,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,MAAM,CAAA;AAE1C;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAA;AAE1C;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,kBAAkB,EACzB,SAAS,CAAC,EAAE,MAAM,GACjB,qBAAqB,CAGvB;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,kBAAkB,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAKjF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,kBAAkB,EACzB,MAAM,EAAE,SAAS,kBAAkB,EAAE,EACrC,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAMR;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAC3B,CAAC,EAAE,kBAAkB,EACrB,CAAC,EAAE,kBAAkB,EACrB,MAAM,EAAE,eAAe,EACvB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,qBAAqB,GAC9D,MAAM,CAoCR;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,SAAS,kBAAkB,EAAE,EACrC,MAAM,EAAE,eAAe,EACvB,SAAS,EAAE,aAAa,EACxB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,qBAAqB,GAC9D,kBAAkB,EAAE,CAQtB"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Pure helpers backing the `/models` comparison table.
3
+ *
4
+ * Sorting is split out from the React component so the comparator can be unit
5
+ * tested without rendering. All functions are deterministic and side-effect
6
+ * free.
7
+ *
8
+ * @module
9
+ */
10
+ /**
11
+ * Resolve a model's effective processing region: the requested region when the
12
+ * model's `regions` list offers it, else the model's DEFAULT region (the first
13
+ * listed; `'us'` when the catalog omits regions). A single-entry list pins the
14
+ * model regardless of the request. Mirrors the server-side
15
+ * `effectiveModelRegion` in `@molecule/api-resource-ai-models`.
16
+ *
17
+ * @param model - The model metadata.
18
+ * @param requested - The user's per-model region choice, if any.
19
+ * @returns The effective region code.
20
+ */
21
+ export function effectiveModelRegion(model, requested) {
22
+ const regions = model.regions ?? ['us'];
23
+ return requested && regions.includes(requested) ? requested : regions[0];
24
+ }
25
+ /**
26
+ * Combined input + output price per million tokens, used as the single
27
+ * "cost/1M tokens" figure — at the rates of the model's effective region
28
+ * (`regionPricing` override when the region has one, else the base rates).
29
+ *
30
+ * @param model - The model metadata.
31
+ * @param region - The user's per-model region choice, if any (omitted → the
32
+ * model's default region).
33
+ * @returns Sum of input and output price per million tokens in USD.
34
+ */
35
+ export function modelTotalCost(model, region) {
36
+ const override = model.regionPricing?.[effectiveModelRegion(model, region)];
37
+ return override
38
+ ? override.inputPricePerMTok + override.outputPricePerMTok
39
+ : model.inputPricePerMTok + model.outputPricePerMTok;
40
+ }
41
+ /**
42
+ * Relative usage rate: how much faster this model consumes the user's AI
43
+ * allowance than the cheapest available model (a unitless `×N` multiplier,
44
+ * `1` for the cheapest). Derived from combined input+output list rates. This
45
+ * is the ONLY per-model "cost" figure user-facing surfaces show — AI usage is
46
+ * never presented as currency.
47
+ *
48
+ * The base is every model's DEFAULT-region cost (stable regardless of the
49
+ * user's per-model region choices); the rated model itself is priced at the
50
+ * given region so the figure tracks what a region switch actually changes.
51
+ *
52
+ * @param model - The model to rate.
53
+ * @param models - The full available-model list (supplies the cheapest base).
54
+ * @param region - The rated model's region choice, if any (omitted → its
55
+ * default region).
56
+ * @returns The multiplier, rounded to 1 decimal below 10 and whole above.
57
+ */
58
+ export function modelUsageRate(model, models, region) {
59
+ const costs = models.map((m) => modelTotalCost(m)).filter((c) => c > 0);
60
+ const base = costs.length > 0 ? Math.min(...costs) : 0;
61
+ if (!Number.isFinite(base) || base <= 0)
62
+ return 1;
63
+ const rate = modelTotalCost(model, region) / base;
64
+ return rate >= 10 ? Math.round(rate) : Math.max(1, Math.round(rate * 10) / 10);
65
+ }
66
+ /**
67
+ * Ascending comparator for two models by the given column. Returns a negative,
68
+ * zero, or positive number suitable for `Array.prototype.sort`. Ties fall back
69
+ * to label order so the sort is stable and deterministic.
70
+ *
71
+ * @param a - First model.
72
+ * @param b - Second model.
73
+ * @param column - Column to compare by.
74
+ * @param regionOf - Resolves a model's effective processing region (used by
75
+ * the `'region'` and `'cost'` columns; omitting it treats every model as its
76
+ * default region). The caller supplies this because the effective region
77
+ * depends on per-project settings the pure helper cannot know.
78
+ * @returns Negative if `a` sorts before `b`, positive if after, zero if equal.
79
+ */
80
+ export function compareModels(a, b, column, regionOf) {
81
+ let primary;
82
+ switch (column) {
83
+ case 'name':
84
+ primary = a.label.localeCompare(b.label);
85
+ break;
86
+ case 'context':
87
+ primary = a.contextWindow - b.contextWindow;
88
+ break;
89
+ case 'cost':
90
+ // Region-aware: a model flipped to a differently-priced region sorts by
91
+ // the cost it actually bills at.
92
+ primary = modelTotalCost(a, regionOf?.(a)) - modelTotalCost(b, regionOf?.(b));
93
+ break;
94
+ case 'cutoff':
95
+ // Knowledge-cutoff dates are YYYY-MM-DD strings, which sort
96
+ // lexicographically as dates. Ascending puts the oldest training data
97
+ // first.
98
+ primary = a.knowledgeCutoff.localeCompare(b.knowledgeCutoff);
99
+ break;
100
+ case 'free':
101
+ // Free-tier models sort first in ascending order.
102
+ primary = (b.freeTier ? 1 : 0) - (a.freeTier ? 1 : 0);
103
+ break;
104
+ case 'region': {
105
+ // US-processed (platform-default) models sort first ascending; other
106
+ // regions follow alphabetically by region code.
107
+ const ra = regionOf?.(a) ?? 'us';
108
+ const rb = regionOf?.(b) ?? 'us';
109
+ primary = ra === rb ? 0 : ra === 'us' ? -1 : rb === 'us' ? 1 : ra.localeCompare(rb);
110
+ break;
111
+ }
112
+ }
113
+ if (primary !== 0)
114
+ return primary;
115
+ // Stable tiebreak by label so equal rows keep a deterministic order.
116
+ return a.label.localeCompare(b.label);
117
+ }
118
+ /**
119
+ * Returns a new array of models sorted by the given column and direction.
120
+ * Does not mutate the input.
121
+ *
122
+ * @param models - Models to sort.
123
+ * @param column - Column to sort by.
124
+ * @param direction - `'asc'` or `'desc'`.
125
+ * @param regionOf - Resolves a model's effective processing region (used by
126
+ * the `'region'` and `'cost'` columns). See {@link compareModels}.
127
+ * @returns A new, sorted array.
128
+ */
129
+ export function sortModels(models, column, direction, regionOf) {
130
+ // Swapping the comparator arguments for `'desc'` fully reverses the order
131
+ // (including the label tiebreak) without relying on Array#sort stability.
132
+ return [...models].sort((a, b) => direction === 'desc'
133
+ ? compareModels(b, a, column, regionOf)
134
+ : compareModels(a, b, column, regionOf));
135
+ }
136
+ //# sourceMappingURL=chat-models-utilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-models-utilities.js","sourceRoot":"","sources":["../../src/components/chat-models-utilities.ts"],"names":[],"mappings":"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"}