@adecore/agents-react 0.39.0 → 0.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (317) hide show
  1. package/dist/agents/AgentModelLabel.js +2 -1
  2. package/dist/agents/model-name.js +2 -1
  3. package/dist/agents/provider-paths.d.ts +2 -1
  4. package/dist/agents/provider-paths.js +7 -3
  5. package/dist/chat/account-choice.js +57 -28
  6. package/dist/chat/bookmarks.js +2 -1
  7. package/dist/chat/drafts.d.ts +0 -1
  8. package/dist/chat/drafts.js +9 -28
  9. package/dist/chat/logic/picker-entries.js +2 -8
  10. package/dist/chat/logic/resume-compaction.js +2 -5
  11. package/dist/chat/logic/timeline-copy.js +2 -8
  12. package/dist/chat/logic/timeline.js +9 -10
  13. package/dist/chat/logic/workflow.js +2 -4
  14. package/dist/chat/subagent-conversation.js +2 -1
  15. package/dist/chat/ui/GoalDock.js +2 -1
  16. package/dist/chat/ui/LimitState.js +4 -6
  17. package/dist/chat/ui/composer/use-continuable-turn.js +0 -1
  18. package/dist/chat/ui/composer/use-notice.js +2 -1
  19. package/dist/chat/ui/composer/use-prompt-history.js +1 -1
  20. package/dist/chat/ui/composer/use-prompt-menus.js +216 -253
  21. package/dist/chat/ui/composer/use-take-back.js +2 -2
  22. package/dist/chat/ui/diff/ApprovalReview.js +1 -1
  23. package/dist/chat/ui/highlighter.d.ts +0 -6
  24. package/dist/chat/ui/highlighter.js +0 -12
  25. package/dist/chat/ui/icons.d.ts +0 -1
  26. package/dist/chat/ui/icons.js +0 -3
  27. package/dist/chat/ui/rows/MessageRows.js +2 -5
  28. package/dist/chat/ui/rows/row-rhythm.js +2 -5
  29. package/dist/chat/ui/theme-watch.js +3 -7
  30. package/dist/prompts/logic/usePromptSession.js +2 -1
  31. package/dist/providers/AddAccountForm.js +2 -1
  32. package/dist/providers/CliDetail.js +104 -130
  33. package/dist/providers/account-actions.js +6 -9
  34. package/dist/state/chats.d.ts +1 -0
  35. package/dist/state/chats.js +6 -11
  36. package/dist/state/provider-accounts.d.ts +1 -1
  37. package/dist/state/provider-accounts.js +2 -8
  38. package/dist/transport.d.ts +2 -0
  39. package/dist/transport.js +14 -0
  40. package/dist/usage/ChartLogo.d.ts +5 -0
  41. package/dist/usage/ChartLogo.js +48 -0
  42. package/dist/usage/LimitsList.js +6 -16
  43. package/dist/usage/UsageBreakdown.js +17 -40
  44. package/dist/usage/UsageLimits.js +183 -211
  45. package/dist/usage/UsagePage.js +3 -17
  46. package/dist/usage/UsageSummary.js +4 -9
  47. package/dist/usage/limits.js +16 -31
  48. package/dist/usage/summary.d.ts +1 -1
  49. package/dist/usage/summary.js +3 -5
  50. package/package.json +27 -27
  51. package/src/agents/AccountDot.tsx +1 -0
  52. package/src/agents/AgentIcon.tsx +2 -0
  53. package/src/agents/AgentModelLabel.tsx +4 -6
  54. package/src/agents/ModelOptionControl.tsx +8 -0
  55. package/src/agents/account-limits.ts +3 -0
  56. package/src/agents/accounts.ts +12 -0
  57. package/src/agents/model-name.ts +13 -1
  58. package/src/agents/model-options.ts +2 -0
  59. package/src/agents/model-requirement.ts +4 -0
  60. package/src/agents/option-words.ts +2 -0
  61. package/src/agents/provider-paths.ts +8 -3
  62. package/src/agents/status-look.ts +1 -0
  63. package/src/chat/account-choice.ts +25 -19
  64. package/src/chat/actions.ts +5 -0
  65. package/src/chat/attachments.ts +8 -0
  66. package/src/chat/bookmarks.ts +13 -1
  67. package/src/chat/chat-client.ts +63 -0
  68. package/src/chat/chat-references.ts +3 -0
  69. package/src/chat/composer-effort.ts +5 -0
  70. package/src/chat/composer-send.ts +4 -0
  71. package/src/chat/drafts.ts +21 -17
  72. package/src/chat/forks.ts +4 -0
  73. package/src/chat/guards.ts +2 -0
  74. package/src/chat/index.ts +7 -0
  75. package/src/chat/logic/ansi.ts +47 -0
  76. package/src/chat/logic/bookmarks.ts +7 -0
  77. package/src/chat/logic/command-looks.ts +29 -0
  78. package/src/chat/logic/context-usage.ts +3 -0
  79. package/src/chat/logic/continue-after-stop.ts +5 -0
  80. package/src/chat/logic/effort.ts +4 -0
  81. package/src/chat/logic/embedded-code.ts +63 -0
  82. package/src/chat/logic/fork.ts +22 -0
  83. package/src/chat/logic/generated-image.ts +7 -0
  84. package/src/chat/logic/goal.ts +7 -0
  85. package/src/chat/logic/handback.ts +2 -0
  86. package/src/chat/logic/limit.ts +8 -0
  87. package/src/chat/logic/picker-entries.ts +16 -4
  88. package/src/chat/logic/resume-compaction.ts +22 -5
  89. package/src/chat/logic/scrubber.ts +38 -5
  90. package/src/chat/logic/thought.ts +5 -0
  91. package/src/chat/logic/thread-cards.ts +6 -0
  92. package/src/chat/logic/timeline-copy.ts +4 -9
  93. package/src/chat/logic/timeline-target.ts +15 -0
  94. package/src/chat/logic/timeline.ts +113 -10
  95. package/src/chat/logic/tools.ts +30 -0
  96. package/src/chat/logic/visual-height.ts +5 -0
  97. package/src/chat/logic/visual-viewport.ts +1 -0
  98. package/src/chat/logic/welcome.ts +3 -0
  99. package/src/chat/logic/workflow.ts +26 -7
  100. package/src/chat/mentions.ts +56 -0
  101. package/src/chat/pending-prompts.ts +16 -0
  102. package/src/chat/persisted-json.ts +15 -0
  103. package/src/chat/preferences.ts +16 -0
  104. package/src/chat/prompt-hold.ts +2 -0
  105. package/src/chat/quote.ts +7 -0
  106. package/src/chat/reading-width.ts +1 -0
  107. package/src/chat/recent-messages.ts +5 -0
  108. package/src/chat/resume-compaction-dismissals.ts +1 -0
  109. package/src/chat/stash.ts +12 -0
  110. package/src/chat/subagent-conversation.ts +24 -4
  111. package/src/chat/subagent-list.ts +14 -0
  112. package/src/chat/subagent-support.ts +1 -0
  113. package/src/chat/subagent-view.ts +9 -0
  114. package/src/chat/thumbnails.ts +14 -0
  115. package/src/chat/timeline-flash.ts +2 -0
  116. package/src/chat/timeline-scroll.ts +23 -0
  117. package/src/chat/ui/AccountPill.tsx +4 -0
  118. package/src/chat/ui/AnsiOutput.tsx +1 -0
  119. package/src/chat/ui/BookmarkMarker.tsx +5 -0
  120. package/src/chat/ui/BookmarkSubmenu.tsx +1 -0
  121. package/src/chat/ui/ChatActivity.tsx +13 -0
  122. package/src/chat/ui/CodeBlock.tsx +8 -0
  123. package/src/chat/ui/Composer.tsx +15 -0
  124. package/src/chat/ui/ComposerInput.tsx +22 -0
  125. package/src/chat/ui/DiffPool.tsx +3 -0
  126. package/src/chat/ui/EditDiff.tsx +5 -0
  127. package/src/chat/ui/GoalDock.tsx +13 -1
  128. package/src/chat/ui/ImageView.tsx +11 -0
  129. package/src/chat/ui/LimitState.tsx +23 -6
  130. package/src/chat/ui/Markdown.tsx +41 -0
  131. package/src/chat/ui/MermaidBlock.tsx +16 -0
  132. package/src/chat/ui/MessageActions.tsx +8 -0
  133. package/src/chat/ui/ModelMarks.tsx +1 -0
  134. package/src/chat/ui/Pickers.tsx +26 -0
  135. package/src/chat/ui/PromptComposer.tsx +2 -0
  136. package/src/chat/ui/PromptEditor.tsx +6 -0
  137. package/src/chat/ui/QuoteButton.tsx +12 -0
  138. package/src/chat/ui/ReferenceChip.tsx +3 -0
  139. package/src/chat/ui/ResumeCompactionDock.tsx +1 -0
  140. package/src/chat/ui/RowTime.tsx +1 -0
  141. package/src/chat/ui/RunSettings.tsx +38 -0
  142. package/src/chat/ui/Scrubber.tsx +14 -0
  143. package/src/chat/ui/SessionLine.tsx +1 -0
  144. package/src/chat/ui/ShellCommand.tsx +32 -0
  145. package/src/chat/ui/SubagentControls.tsx +7 -0
  146. package/src/chat/ui/SubagentInfo.tsx +1 -0
  147. package/src/chat/ui/SubagentStopButton.tsx +6 -0
  148. package/src/chat/ui/SubagentTimeline.tsx +10 -0
  149. package/src/chat/ui/Timeline.tsx +119 -0
  150. package/src/chat/ui/TimelineMenu.tsx +5 -0
  151. package/src/chat/ui/UnifiedDiff.tsx +6 -0
  152. package/src/chat/ui/UploadThumb.tsx +2 -0
  153. package/src/chat/ui/VisualDialogs.tsx +3 -0
  154. package/src/chat/ui/VisualFrame.tsx +15 -0
  155. package/src/chat/ui/Welcome.tsx +1 -0
  156. package/src/chat/ui/chip-clipboard.ts +20 -0
  157. package/src/chat/ui/code-lines.ts +14 -0
  158. package/src/chat/ui/code-theme.ts +1 -0
  159. package/src/chat/ui/composer/ComposerControls.tsx +5 -0
  160. package/src/chat/ui/composer/ComposerMenu.tsx +12 -0
  161. package/src/chat/ui/composer/DraftParts.tsx +2 -0
  162. package/src/chat/ui/composer/QueuedMessages.tsx +4 -0
  163. package/src/chat/ui/composer/chip-paste.ts +16 -0
  164. package/src/chat/ui/composer/chips.ts +6 -0
  165. package/src/chat/ui/composer/editor.ts +9 -0
  166. package/src/chat/ui/composer/insert.ts +2 -0
  167. package/src/chat/ui/composer/key-handler.ts +39 -0
  168. package/src/chat/ui/composer/keys.ts +23 -0
  169. package/src/chat/ui/composer/placeholder.ts +6 -0
  170. package/src/chat/ui/composer/prompt-picks.ts +1 -0
  171. package/src/chat/ui/composer/surface.ts +5 -0
  172. package/src/chat/ui/composer/use-chat-controls.ts +13 -0
  173. package/src/chat/ui/composer/use-compaction-offer.ts +3 -0
  174. package/src/chat/ui/composer/use-composer-menus.ts +2 -0
  175. package/src/chat/ui/composer/use-composer-submit.ts +27 -0
  176. package/src/chat/ui/composer/use-continuable-turn.ts +3 -5
  177. package/src/chat/ui/composer/use-draft-files.ts +24 -0
  178. package/src/chat/ui/composer/use-effort-stepper.ts +8 -0
  179. package/src/chat/ui/composer/use-notice.ts +4 -1
  180. package/src/chat/ui/composer/use-pending-requests.ts +3 -0
  181. package/src/chat/ui/composer/use-prompt-history.ts +20 -11
  182. package/src/chat/ui/composer/use-prompt-hold.ts +3 -0
  183. package/src/chat/ui/composer/use-prompt-menus.ts +34 -14
  184. package/src/chat/ui/composer/use-take-back.ts +20 -2
  185. package/src/chat/ui/diff/ApprovalReview.tsx +18 -15
  186. package/src/chat/ui/diff/DiffFallbackBoundary.tsx +1 -0
  187. package/src/chat/ui/diff/DiffOmitted.tsx +1 -0
  188. package/src/chat/ui/diff/DiffStack.tsx +2 -0
  189. package/src/chat/ui/diff/DiffUnavailable.tsx +1 -0
  190. package/src/chat/ui/diff/EditorEditDiff.tsx +13 -0
  191. package/src/chat/ui/diff/EditorPatchDiff.tsx +4 -0
  192. package/src/chat/ui/diff/LegacyDiffPool.tsx +2 -0
  193. package/src/chat/ui/diff/LegacyEditDiff.tsx +2 -0
  194. package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +8 -0
  195. package/src/chat/ui/diff/RawDiff.tsx +1 -0
  196. package/src/chat/ui/diff/ReviewDiff.tsx +1 -0
  197. package/src/chat/ui/diff/diff-source-budget.ts +8 -0
  198. package/src/chat/ui/diff/editor-edit-model.ts +4 -0
  199. package/src/chat/ui/diff/review-plan.ts +9 -0
  200. package/src/chat/ui/diff/review-settle.ts +1 -0
  201. package/src/chat/ui/diff/syntax-runtime.ts +52 -0
  202. package/src/chat/ui/diff/use-review-stack.ts +9 -0
  203. package/src/chat/ui/diff-syntax.ts +12 -0
  204. package/src/chat/ui/diff-theme.ts +2 -0
  205. package/src/chat/ui/file-links.ts +2 -0
  206. package/src/chat/ui/find-reveal.ts +1 -0
  207. package/src/chat/ui/full-diff.ts +1 -0
  208. package/src/chat/ui/highlighter.ts +29 -15
  209. package/src/chat/ui/icons.tsx +2 -4
  210. package/src/chat/ui/index.ts +4 -0
  211. package/src/chat/ui/intelligent-ui/UiBlockFrame.tsx +25 -0
  212. package/src/chat/ui/intelligent-ui/UiFallbackPart.tsx +1 -0
  213. package/src/chat/ui/intelligent-ui/UiReply.tsx +63 -0
  214. package/src/chat/ui/intelligent-ui/block-local.ts +5 -0
  215. package/src/chat/ui/intelligent-ui/chart-data.ts +15 -0
  216. package/src/chat/ui/intelligent-ui/node-text.ts +17 -0
  217. package/src/chat/ui/intelligent-ui/renderers/binding.ts +1 -0
  218. package/src/chat/ui/intelligent-ui/renderers/chart.tsx +31 -0
  219. package/src/chat/ui/intelligent-ui/renderers/choices.tsx +4 -0
  220. package/src/chat/ui/intelligent-ui/renderers/content.tsx +18 -0
  221. package/src/chat/ui/intelligent-ui/renderers/data.tsx +11 -0
  222. package/src/chat/ui/intelligent-ui/renderers/inputs.tsx +13 -0
  223. package/src/chat/ui/intelligent-ui/renderers/links.tsx +9 -0
  224. package/src/chat/ui/intelligent-ui/renderers/map-slots.ts +15 -0
  225. package/src/chat/ui/intelligent-ui/renderers/map-style.ts +15 -0
  226. package/src/chat/ui/intelligent-ui/renderers/map-view.tsx +48 -0
  227. package/src/chat/ui/intelligent-ui/renderers/map.tsx +20 -0
  228. package/src/chat/ui/intelligent-ui/renderers/place-point.ts +3 -0
  229. package/src/chat/ui/intelligent-ui/renderers/structure.tsx +3 -0
  230. package/src/chat/ui/intelligent-ui/renderers/text.tsx +8 -0
  231. package/src/chat/ui/intelligent-ui/reply-parts.ts +5 -0
  232. package/src/chat/ui/intelligent-ui/table-data.ts +17 -0
  233. package/src/chat/ui/intelligent-ui/use-in-view.ts +4 -0
  234. package/src/chat/ui/intelligent-ui/use-later.ts +4 -0
  235. package/src/chat/ui/intelligent-ui/use-truncated.ts +5 -0
  236. package/src/chat/ui/intelligent-ui/use-ui-links.ts +29 -0
  237. package/src/chat/ui/intelligent-ui/use-ui-queries.ts +60 -0
  238. package/src/chat/ui/markdown-blocks.ts +64 -0
  239. package/src/chat/ui/mermaid.ts +4 -0
  240. package/src/chat/ui/quote-selection.ts +10 -0
  241. package/src/chat/ui/rehype-chips.ts +5 -0
  242. package/src/chat/ui/rehype-fade.ts +2 -0
  243. package/src/chat/ui/remark-html-as-text.ts +5 -0
  244. package/src/chat/ui/reveal.ts +18 -0
  245. package/src/chat/ui/rows/ForksRow.tsx +5 -0
  246. package/src/chat/ui/rows/GeneratedImageRow.tsx +10 -0
  247. package/src/chat/ui/rows/MessageRows.tsx +29 -5
  248. package/src/chat/ui/rows/RowContainer.tsx +1 -0
  249. package/src/chat/ui/rows/Rows.tsx +2 -0
  250. package/src/chat/ui/rows/SubagentRow.tsx +13 -0
  251. package/src/chat/ui/rows/TaskRow.tsx +7 -0
  252. package/src/chat/ui/rows/VisualRow.tsx +4 -0
  253. package/src/chat/ui/rows/WorkRows.tsx +46 -0
  254. package/src/chat/ui/rows/WorkflowRow.tsx +10 -0
  255. package/src/chat/ui/rows/row-rhythm.ts +7 -5
  256. package/src/chat/ui/theme-watch.ts +8 -7
  257. package/src/chat/ui/thread-references.ts +7 -0
  258. package/src/chat/ui/use-diff-source-tokens.ts +8 -0
  259. package/src/chat/ui/useToggleSet.ts +3 -0
  260. package/src/chat/ui/visual-theme.ts +30 -0
  261. package/src/chat/visual-bridge.ts +23 -0
  262. package/src/chat/visual-viewport.ts +12 -0
  263. package/src/host-hooks.ts +6 -4
  264. package/src/host.ts +2 -0
  265. package/src/index.ts +3 -0
  266. package/src/lazy.tsx +9 -0
  267. package/src/mounted-registry.ts +4 -0
  268. package/src/port-transport.ts +32 -0
  269. package/src/prompts/index.ts +2 -0
  270. package/src/prompts/logic/focus.ts +5 -0
  271. package/src/prompts/logic/keys.ts +12 -0
  272. package/src/prompts/logic/prompts.ts +8 -0
  273. package/src/prompts/logic/subjects.ts +8 -0
  274. package/src/prompts/logic/useFocusAfterAnswer.ts +2 -0
  275. package/src/prompts/logic/usePromptSession.ts +13 -1
  276. package/src/prompts/ui/ApprovalBody.tsx +2 -0
  277. package/src/prompts/ui/PromptActions.tsx +2 -0
  278. package/src/prompts/ui/PromptCard.tsx +6 -0
  279. package/src/prompts/ui/PromptView.tsx +6 -0
  280. package/src/prompts/ui/QuestionBody.tsx +11 -0
  281. package/src/providers/AccountColors.tsx +1 -0
  282. package/src/providers/AccountDetail.tsx +7 -0
  283. package/src/providers/AccountVariables.tsx +2 -0
  284. package/src/providers/AddAccountForm.tsx +5 -1
  285. package/src/providers/CliDetail.tsx +6 -5
  286. package/src/providers/InstallPage.tsx +11 -0
  287. package/src/providers/ProviderList.tsx +3 -0
  288. package/src/providers/ProvidersPane.tsx +18 -0
  289. package/src/providers/account-actions.ts +10 -8
  290. package/src/providers/install-steps.ts +3 -0
  291. package/src/providers/pane-target.ts +5 -0
  292. package/src/providers/provider-abilities.ts +1 -0
  293. package/src/providers/provider-groups.ts +6 -0
  294. package/src/providers/variables.ts +12 -0
  295. package/src/scope.ts +2 -0
  296. package/src/state/chats.ts +61 -14
  297. package/src/state/index.ts +2 -0
  298. package/src/state/provider-accounts.ts +7 -9
  299. package/src/state/providers.ts +1 -0
  300. package/src/state/usage.ts +6 -0
  301. package/src/storage.ts +18 -0
  302. package/src/testing/prompts.ts +5 -0
  303. package/src/transport.ts +18 -0
  304. package/src/usage/ChartLogo.tsx +12 -0
  305. package/src/usage/LimitsList.tsx +7 -8
  306. package/src/usage/UsageBreakdown.tsx +11 -4
  307. package/src/usage/UsageChart.tsx +5 -0
  308. package/src/usage/UsageLimits.tsx +10 -7
  309. package/src/usage/UsageLimitsCard.tsx +2 -0
  310. package/src/usage/UsagePage.tsx +16 -19
  311. package/src/usage/UsageSummary.tsx +4 -5
  312. package/src/usage/UsageTiles.tsx +2 -0
  313. package/src/usage/format.ts +7 -0
  314. package/src/usage/limit-groups.ts +14 -0
  315. package/src/usage/limits.ts +11 -13
  316. package/src/usage/money.ts +1 -0
  317. package/src/usage/summary.ts +15 -7
@@ -37,6 +37,7 @@ export function AccountVariables({ id, account, secretsAvailable }: AccountVaria
37
37
  const env = variablesOf(drafts);
38
38
  const { env: _env, ...rest } = account;
39
39
  setSaving(true);
40
+
40
41
  try {
41
42
  await saveAccount(scope, host, id, env.length === 0 ? rest : { ...rest, env });
42
43
  } finally {
@@ -63,6 +64,7 @@ export function AccountVariables({ id, account, secretsAvailable }: AccountVaria
63
64
  const lockLabel = draft.sensitive ? t('account.variables.unlock', { name }) : t('account.variables.lock', { name });
64
65
  // Off a keychain a value can only be made plain, never secret.
65
66
  const lockDisabled = !secretsAvailable && !draft.sensitive;
67
+
66
68
  return (
67
69
  <div key={draft.key} className="flex min-w-0 items-center gap-2 px-3.5 py-3 font-mono text-code">
68
70
  <Input
@@ -12,6 +12,7 @@ import { DetailHeader, SettingsRow, SettingsSection } from '@adecore/ui/settings
12
12
  import { Button, FormError, Icon, Input } from '@adecore/ui';
13
13
  import { CliTile } from './parts';
14
14
  import { providerAccountsOf } from '../state/provider-accounts';
15
+ import { errorMessage } from '../transport';
15
16
 
16
17
  interface AddAccountFormProps {
17
18
  provider: ProviderInfo;
@@ -41,11 +42,14 @@ export function AddAccountForm({ provider, entries, login, onCancel, onAdded }:
41
42
 
42
43
  const submit = async (event: FormEvent): Promise<void> => {
43
44
  event.preventDefault();
45
+
44
46
  if (label === '' || busy) {
45
47
  return;
46
48
  }
49
+
47
50
  setBusy(true);
48
51
  setError(null);
52
+
49
53
  try {
50
54
  if (linking) {
51
55
  const taken = new Set(Object.keys(providerAccountsOf(scope.id).accounts?.accounts ?? {}));
@@ -56,7 +60,7 @@ export function AddAccountForm({ provider, entries, login, onCancel, onAdded }:
56
60
  onAdded(await createAccount(scope, provider.kind, label, color), label, true);
57
61
  }
58
62
  } catch (e) {
59
- setError(e instanceof Error ? e.message : String(e));
63
+ setError(errorMessage(e));
60
64
  setBusy(false);
61
65
  }
62
66
  };
@@ -5,14 +5,14 @@ import { AccountDot } from '../agents/AccountDot';
5
5
  import { ModelOptionControl } from '../agents/ModelOptionControl';
6
6
  import { carryOptions } from '../agents/model-options';
7
7
  import { optionWord } from '../agents/option-words';
8
- import { ACCOUNT_TONE_CLASSES, accountName, accountStatusLine, type AccountEntry, type AccountLogin } from '../agents/accounts';
8
+ import { ACCOUNT_TONE_CLASSES, accountName, accountStatusLine, offeredAccounts, type AccountEntry, type AccountLogin } from '../agents/accounts';
9
9
  import { accountFor, forgetChatSelection, rememberChatAccount, rememberChatSelection, selectionFor, useChatPreferences } from '../chat/preferences';
10
10
  import { useChatScope } from '../scope';
11
11
  import { providerAbilities } from './provider-abilities';
12
12
  import { DetailHeader, SettingsRow, SettingsSection } from '@adecore/ui/settings';
13
13
  import { Button, Icon, Select } from '@adecore/ui';
14
14
  import { CliTile } from './parts';
15
- import { useProviderAccountsStore } from '../state/provider-accounts';
15
+ import { knownAccounts, useProviderAccounts } from '../state/provider-accounts';
16
16
 
17
17
  const PROVIDER_DEFAULT = '';
18
18
 
@@ -21,6 +21,7 @@ function ModelOptionRows({ provider, model, options }: { provider: AgentKind; mo
21
21
  const setOption = (id: string, value: string | boolean): void => {
22
22
  rememberChatSelection(provider, { model: model.slug, options: { ...options, [id]: value } });
23
23
  };
24
+
24
25
  return (
25
26
  <>
26
27
  {model.options.map((option) => (
@@ -53,10 +54,9 @@ export function CliDetail({ provider, entries, defaults, login, onAddAccount, on
53
54
  const preferences = useChatPreferences();
54
55
  const selection = selectionFor(preferences, provider.kind);
55
56
  const model = provider.models.find((entry) => entry.slug === selection?.model);
56
- const machineAccounts = useProviderAccountsStore((s) => (s.byScope[scopeId]?.loaded ? s.byScope[scopeId].accounts : undefined));
57
+ const machineAccounts = useProviderAccounts(knownAccounts);
57
58
  const picked = accountFor(preferences, scopeId, provider.kind, machineAccounts) ?? provider.kind;
58
- // An account that is off is out of every picker, unless it is the one picked already.
59
- const choosable = (entries ?? []).filter((entry) => entry.account.enabled !== false || entry.id === picked);
59
+ const choosable = offeredAccounts(entries ?? [], picked);
60
60
  const canAddAccount = login !== null;
61
61
 
62
62
  return (
@@ -137,6 +137,7 @@ export function CliDetail({ provider, entries, defaults, login, onAddAccount, on
137
137
  <SettingsSection title={t('cli.accounts')} footer={canAddAccount ? undefined : t('cli.onlyDefault', { provider: provider.name })}>
138
138
  {entries.map((entry) => {
139
139
  const status = accountStatusLine(entry.status, login === 'terminal');
140
+
140
141
  return (
141
142
  <button
142
143
  key={entry.id}
@@ -13,6 +13,7 @@ import { CliMark } from './parts';
13
13
  function CommandBlock({ command }: { command: string }) {
14
14
  const { t } = useTranslation('agent-providers');
15
15
  const [copied, setCopied] = useState<string | null>(null);
16
+
16
17
  return (
17
18
  <div className="flex min-w-0 items-center gap-2 rounded-lg border border-border bg-surface-sunken py-1 pr-1 pl-3">
18
19
  <code className="min-w-0 grow font-mono text-code break-all text-text select-text">{command}</code>
@@ -58,6 +59,7 @@ function Step({ index, state, title, children }: { index: number; state: Install
58
59
  function SignInWay({ way, canRun, onRun }: { way: ProviderSignIn; canRun: boolean; onRun(command: string): void }) {
59
60
  const { t } = useTranslation('agent-providers');
60
61
  const { providerKeyNote } = useChatHost();
62
+
61
63
  switch (way.kind) {
62
64
  case 'command':
63
65
  return (
@@ -74,6 +76,7 @@ function SignInWay({ way, canRun, onRun }: { way: ProviderSignIn; canRun: boolea
74
76
  );
75
77
  case 'env': {
76
78
  const note = providerKeyNote?.(way.variable) ?? null;
79
+
77
80
  return (
78
81
  <p className="text-xs text-text-muted">
79
82
  <Trans
@@ -110,10 +113,13 @@ export function InstallPage({ provider, signedIn, extra }: InstallPageProps) {
110
113
  const platform = chosen ?? initial;
111
114
  const install = provider.install;
112
115
  const run = host.runProviderCommand;
116
+
113
117
  const onRun = (command: string): void => run?.(scopeId, provider.kind, command);
118
+
114
119
  const command = platform === null ? undefined : install?.commands[platform];
115
120
 
116
121
  const steps: { title: string; done: boolean; body: ReactNode }[] = [];
122
+
117
123
  steps.push({
118
124
  title: t('install.install.title'),
119
125
  done: provider.installed,
@@ -151,10 +157,13 @@ export function InstallPage({ provider, signedIn, extra }: InstallPageProps) {
151
157
  </>
152
158
  )
153
159
  });
160
+
154
161
  if (extra !== null) {
155
162
  steps.push(extra);
156
163
  }
164
+
157
165
  const ways = install?.signIn ?? [];
166
+
158
167
  steps.push({
159
168
  title: t('install.signIn.title'),
160
169
  done: provider.installed && signedIn,
@@ -170,11 +179,13 @@ export function InstallPage({ provider, signedIn, extra }: InstallPageProps) {
170
179
  ))
171
180
  )
172
181
  });
182
+
173
183
  steps.push({
174
184
  title: t('install.start.title'),
175
185
  done: false,
176
186
  body: <p className="text-xs text-text-muted">{t('install.start.description', { provider: provider.name })}</p>
177
187
  });
188
+
178
189
  const states = installStepStates(steps.map((step) => step.done));
179
190
 
180
191
  return (
@@ -63,13 +63,16 @@ interface ProviderListProps {
63
63
  /* The providers by whether they are ready, need setup or are not installed; a group without providers is left out. */
64
64
  export function ProviderList({ groups, current, canAddAccount, canAddProvider = false, onPick }: ProviderListProps) {
65
65
  const { t } = useTranslation('agent-providers');
66
+
66
67
  const picksCli = (kind: AgentKind): boolean => (current?.kind === 'cli' || current?.kind === 'install') && current.cli === kind;
68
+
67
69
  const picksAccount = (id: string): boolean => current?.kind === 'account' && current.id === id;
68
70
 
69
71
  const row = ({ provider, entries, note }: ProviderRow) => {
70
72
  // One account has no row of its own, so picking it lights up its provider.
71
73
  const several = entries.length > 1;
72
74
  const selected = picksCli(provider.kind) || (!several && entries[0] !== undefined && picksAccount(entries[0].id));
75
+
73
76
  return (
74
77
  <div key={provider.kind} className="flex min-w-0 shrink-0 flex-col gap-0.5">
75
78
  <ListRow
@@ -49,11 +49,14 @@ export function ProvidersPane({ target = null, detailOf, defaults = true, instal
49
49
 
50
50
  const groups = groupProviders(providers, accounts, setupStatusOf);
51
51
  const installed = [...groups.ready, ...groups.setup].map((row) => row.provider);
52
+
52
53
  const entriesOf = (kind: AgentKind): AccountEntry[] => accountsOfKind(accounts, kind);
54
+
53
55
  const loginOf = (kind: AgentKind) => accountLoginOf(accounts, kind);
54
56
 
55
57
  // What was picked may be gone since (an account removed elsewhere); the first CLI stands in for it.
56
58
  const pickedEntry = picked?.kind === 'account' ? (Object.hasOwn(accounts?.accounts ?? {}, picked.id) ? picked.id : null) : null;
59
+
57
60
  // A CLI taken off the host since it was picked makes way too, so the detail never stays empty.
58
61
  const keeps = (entry: Picked): boolean => {
59
62
  switch (entry.kind) {
@@ -67,17 +70,22 @@ export function ProvidersPane({ target = null, detailOf, defaults = true, instal
67
70
  return true;
68
71
  }
69
72
  };
73
+
70
74
  const current: Picked | null = picked !== null && keeps(picked) ? picked : installed[0] ? { kind: 'cli', cli: installed[0].kind } : null;
71
75
  const currentAccount = current?.kind === 'account' ? accounts?.accounts[current.id] : undefined;
76
+
72
77
  const currentCli =
73
78
  current === null || current.kind === 'catalog' ? null : current.kind === 'account' ? (currentAccount?.kind as AgentKind | undefined) : current.cli;
79
+
74
80
  const provider = (current?.kind === 'install' ? providers : installed).find((entry) => entry.kind === currentCli) ?? null;
75
81
 
76
82
  // A search result lands on a row of a CLI's detail, so a picked account makes way for its CLI.
77
83
  const [seenTarget, setSeenTarget] = useState(target);
84
+
78
85
  if (target !== seenTarget) {
79
86
  setSeenTarget(target);
80
87
  const opened = pickOfTarget(target, providers);
88
+
81
89
  if (opened !== null) {
82
90
  setPicked(opened);
83
91
  } else if (target?.startsWith('providers.') && current?.kind !== 'cli' && provider !== null) {
@@ -87,6 +95,7 @@ export function ProvidersPane({ target = null, detailOf, defaults = true, instal
87
95
 
88
96
  const added = (id: string, name: string, made: boolean): void => {
89
97
  setPicked({ kind: 'account', id });
98
+
90
99
  if (made && provider !== null && loginOf(provider.kind) === 'terminal' && openLogin !== null && loginBlocked === null) {
91
100
  void openLogin(scopeId, provider.kind, id, name);
92
101
  }
@@ -111,22 +120,28 @@ export function ProvidersPane({ target = null, detailOf, defaults = true, instal
111
120
  if (current?.kind === 'catalog') {
112
121
  return addPage?.((kind) => setPicked(pickOfCli(kind, providers))) ?? null;
113
122
  }
123
+
114
124
  if (current === null || provider === null) {
115
125
  return null;
116
126
  }
127
+
117
128
  if (current.kind === 'install') {
118
129
  const extra = installStepOf?.(provider) ?? null;
119
130
  const statuses = entriesOf(provider.kind).map((entry) => entry.status?.state);
120
131
  // Without accounts to read, nothing says the CLI is signed out, so being there is all it takes.
121
132
  const signedIn = statuses.length === 0 || statuses.includes('ready');
133
+
122
134
  if (!provider.installed || !signedIn || (extra !== null && !extra.done)) {
123
135
  return <InstallPage key={provider.kind} provider={provider} signedIn={signedIn} extra={extra} />;
124
136
  }
125
137
  }
138
+
126
139
  const own = current.kind === 'cli' || current.kind === 'install' ? (detailOf?.(provider) ?? null) : null;
140
+
127
141
  if (own !== null) {
128
142
  return own;
129
143
  }
144
+
130
145
  if (current.kind === 'add') {
131
146
  return (
132
147
  <AddAccountForm
@@ -139,8 +154,10 @@ export function ProvidersPane({ target = null, detailOf, defaults = true, instal
139
154
  />
140
155
  );
141
156
  }
157
+
142
158
  if (current.kind === 'account') {
143
159
  const entry = entriesOf(provider.kind).find((candidate) => candidate.id === current.id);
160
+
144
161
  return (
145
162
  entry && (
146
163
  <AccountDetail
@@ -156,6 +173,7 @@ export function ProvidersPane({ target = null, detailOf, defaults = true, instal
156
173
  )
157
174
  );
158
175
  }
176
+
159
177
  return (
160
178
  <>
161
179
  <CliDetail
@@ -4,20 +4,17 @@ import { accountFor, readChatPreferences, rememberChatAccount } from '../chat/pr
4
4
  import type { ChatHost } from '../host';
5
5
  import type { ChatScope } from '../scope';
6
6
  import { providerAccountsOf, useProviderAccountsStore } from '../state/provider-accounts';
7
- import type { ChatTransport } from '../transport';
7
+ import { errorMessage, type ChatTransport } from '../transport';
8
8
 
9
9
  function words(key: string, values?: Record<string, string>): string {
10
10
  return i18next.t(`agent-providers:${key}`, values);
11
11
  }
12
12
 
13
- function reason(e: unknown): string {
14
- return e instanceof Error ? e.message : String(e);
15
- }
16
-
17
13
  function linkTo(scope: ChatScope): ChatTransport {
18
14
  if (scope.transport.status !== 'open') {
19
15
  throw new Error(words('notAnswering'));
20
16
  }
17
+
21
18
  return scope.transport;
22
19
  }
23
20
 
@@ -25,9 +22,11 @@ function linkTo(scope: ChatScope): ChatTransport {
25
22
  export async function saveAccounts(scope: ChatScope, host: Pick<ChatHost, 'notify'>, accounts: ProviderAccountMap): Promise<boolean> {
26
23
  try {
27
24
  useProviderAccountsStore.getState().set(scope.id, await linkTo(scope).request('accounts.save', { accounts }));
25
+
28
26
  return true;
29
27
  } catch (e) {
30
- host.notify({ id: `providers-save-${scope.id}`, kind: 'error', title: words('account.saveFailed'), description: reason(e) });
28
+ host.notify({ id: `providers-save-${scope.id}`, kind: 'error', title: words('account.saveFailed'), description: errorMessage(e) });
29
+
31
30
  return false;
32
31
  }
33
32
  }
@@ -35,6 +34,7 @@ export async function saveAccounts(scope: ChatScope, host: Pick<ChatHost, 'notif
35
34
  /* One account changed and every other one as the host last said. */
36
35
  export function saveAccount(scope: ChatScope, host: Pick<ChatHost, 'notify'>, id: string, account: ProviderAccount): Promise<boolean> {
37
36
  const current = providerAccountsOf(scope.id).accounts?.accounts ?? {};
37
+
38
38
  return saveAccounts(scope, host, { ...current, [id]: account });
39
39
  }
40
40
 
@@ -44,9 +44,11 @@ export function saveAccount(scope: ChatScope, host: Pick<ChatHost, 'notify'>, id
44
44
  */
45
45
  export async function removeAccount(scope: ChatScope, host: Pick<ChatHost, 'notify'>, id: string, kind: AgentKind): Promise<void> {
46
46
  const { [id]: _gone, ...rest } = providerAccountsOf(scope.id).accounts?.accounts ?? {};
47
+
47
48
  if (!(await saveAccounts(scope, host, rest))) {
48
49
  throw new Error(words('account.saveFailed'));
49
50
  }
51
+
50
52
  if (accountFor(readChatPreferences(), scope.id, kind) === id) {
51
53
  rememberChatAccount(scope.id, kind, null);
52
54
  }
@@ -54,9 +56,9 @@ export async function removeAccount(scope: ChatScope, host: Pick<ChatHost, 'noti
54
56
 
55
57
  /* An account in a folder the host makes for it; answers its id. Throws what the host said, for the form to show. */
56
58
  export async function createAccount(scope: ChatScope, kind: AgentKind, label: string, color: string): Promise<string> {
57
- const created = await linkTo(scope).request('accounts.create', { kind, label, color });
58
- const { id, ...accounts } = created;
59
+ const { id, ...accounts } = await linkTo(scope).request('accounts.create', { kind, label, color });
59
60
  useProviderAccountsStore.getState().set(scope.id, accounts);
61
+
60
62
  return id;
61
63
  }
62
64
 
@@ -17,6 +17,7 @@ const PLATFORMS: readonly ProviderPlatform[] = ['darwin', 'linux', 'win32'];
17
17
  /* The first step not done is the current one. */
18
18
  export function installStepStates(done: readonly boolean[]): InstallStepState[] {
19
19
  const first = done.indexOf(false);
20
+
20
21
  return done.map((value, index) => (value ? 'done' : index === first ? 'current' : 'later'));
21
22
  }
22
23
 
@@ -25,11 +26,13 @@ export function pagePlatform(): ProviderPlatform {
25
26
  if (isApplePlatform()) {
26
27
  return 'darwin';
27
28
  }
29
+
28
30
  return typeof navigator !== 'undefined' && /windows/i.test(navigator.userAgent) ? 'win32' : 'linux';
29
31
  }
30
32
 
31
33
  /* The systems a provider has a command for, and the one the page opens on: the machine's when it has one there. */
32
34
  export function installPlatforms(provider: ProviderInfo, machine: ProviderPlatform): { offered: ProviderPlatform[]; initial: ProviderPlatform | null } {
33
35
  const offered = PLATFORMS.filter((platform) => Boolean(provider.install?.commands[platform]));
36
+
34
37
  return { offered, initial: offered.includes(machine) ? machine : (offered[0] ?? null) };
35
38
  }
@@ -16,12 +16,15 @@ export function pickOfTarget(target: string | null, providers: ProviderInfo[]):
16
16
  if (target === 'providers.apple') {
17
17
  return { kind: 'cli', cli: 'apple' };
18
18
  }
19
+
19
20
  if (target === ADD_TARGET) {
20
21
  return { kind: 'catalog' };
21
22
  }
23
+
22
24
  if (!target?.startsWith(INSTALL_TARGET) || target.length === INSTALL_TARGET.length) {
23
25
  return null;
24
26
  }
27
+
25
28
  return pickOfCli(target.slice(INSTALL_TARGET.length) as AgentKind, providers);
26
29
  }
27
30
 
@@ -32,8 +35,10 @@ export function pickOfTarget(target: string | null, providers: ProviderInfo[]):
32
35
  */
33
36
  export function pickOfCli(cli: AgentKind, providers: ProviderInfo[]): Picked {
34
37
  const provider = providers.find((entry) => entry.kind === cli);
38
+
35
39
  if (provider !== undefined && (provider.installed || !provider.capabilities.terminal)) {
36
40
  return { kind: 'cli', cli };
37
41
  }
42
+
38
43
  return { kind: 'install', cli };
39
44
  }
@@ -5,6 +5,7 @@ import type { ProviderCapabilities, ProviderInfo } from '@adecore/agent-contract
5
5
  export function providerAbilities(provider: ProviderInfo): string {
6
6
  const where = provider.capabilities.chat ? i18next.t('agent-providers:abilities.chatAndTerminal') : i18next.t('agent-providers:abilities.terminalOnly');
7
7
  const status = provider.capabilities.hooks ? i18next.t('agent-providers:abilities.reportsStatus') : i18next.t('agent-providers:abilities.noStatus');
8
+
8
9
  return `${where} ${status}`;
9
10
  }
10
11
 
@@ -24,6 +24,7 @@ export interface ProviderGroups {
24
24
  /* What an account says beside its name: its plan while it is ready, a short word when it is not, nothing while it is checked. */
25
25
  export function accountNote(status: ProviderAccountStatus | null): ProviderSetupStatus | null {
26
26
  const words = (key: string): string => i18next.t(`agent-providers:${key}`);
27
+
27
28
  switch (status?.state ?? 'checking') {
28
29
  case 'checking':
29
30
  return null;
@@ -57,14 +58,18 @@ export function groupProviders(
57
58
  setupStatusOf?: (provider: ProviderInfo) => ProviderSetupStatus | null
58
59
  ): ProviderGroups {
59
60
  const groups: ProviderGroups = { ready: [], setup: [], missing: [] };
61
+
60
62
  for (const provider of providers) {
61
63
  // A provider that never runs in a terminal is no CLI to install: it is turned on in its own detail.
62
64
  if (!provider.installed && provider.capabilities.terminal) {
63
65
  groups.missing.push(provider);
66
+
64
67
  continue;
65
68
  }
69
+
66
70
  const entries = provider.installed ? accountsOfKind(accounts, provider.kind) : [];
67
71
  const hostStatus = setupStatusOf?.(provider) ?? null;
72
+
68
73
  if (hostStatus !== null) {
69
74
  groups.setup.push({ provider, entries, note: hostStatus });
70
75
  } else if (entries.length === 0 || entries.some(usable)) {
@@ -73,5 +78,6 @@ export function groupProviders(
73
78
  groups.setup.push({ provider, entries, note: accountNote(entries[0]!.status) });
74
79
  }
75
80
  }
81
+
76
82
  return groups;
77
83
  }
@@ -15,8 +15,10 @@ export interface VariableDraft {
15
15
  }
16
16
 
17
17
  let drafted = 0;
18
+
18
19
  function nextKey(): string {
19
20
  drafted += 1;
21
+
20
22
  return `variable-${drafted}`;
21
23
  }
22
24
 
@@ -45,23 +47,31 @@ function keepsValue(draft: VariableDraft): boolean {
45
47
  /* The first thing that keeps these from being saved, in a sentence; null when they can be. */
46
48
  export function variablesProblem(drafts: readonly VariableDraft[]): string | null {
47
49
  const words = (key: string, name: string): string => i18next.t(`agent-providers:account.variables.problem.${key}`, { name });
50
+
48
51
  const seen = new Set<string>();
52
+
49
53
  for (const draft of drafts) {
50
54
  const name = draft.name.trim();
55
+
51
56
  if (name === '') {
52
57
  return words('noName', '');
53
58
  }
59
+
54
60
  if (!NAME.test(name)) {
55
61
  return words('invalidName', name);
56
62
  }
63
+
57
64
  if (seen.has(name)) {
58
65
  return words('duplicate', name);
59
66
  }
67
+
60
68
  seen.add(name);
69
+
61
70
  if (draft.value === '' && !keepsValue({ ...draft, name })) {
62
71
  return words('noValue', name);
63
72
  }
64
73
  }
74
+
65
75
  return null;
66
76
  }
67
77
 
@@ -69,9 +79,11 @@ export function variablesProblem(drafts: readonly VariableDraft[]): string | nul
69
79
  export function variablesOf(drafts: readonly VariableDraft[]): ProviderAccountVariable[] {
70
80
  return drafts.map((draft) => {
71
81
  const name = draft.name.trim();
82
+
72
83
  if (keepsValue({ ...draft, name })) {
73
84
  return { name, value: '', sensitive: true, valueRedacted: true };
74
85
  }
86
+
75
87
  return { name, value: draft.value, sensitive: draft.sensitive };
76
88
  });
77
89
  }
package/src/scope.ts CHANGED
@@ -23,8 +23,10 @@ export const ChatScopeContext = createContext<ChatScope | null>(null);
23
23
  /* Throws outside a scope on purpose: a component that reads a chat has to know which host it runs on. */
24
24
  export function useChatScope(): ChatScope {
25
25
  const scope = useContext(ChatScopeContext);
26
+
26
27
  if (scope === null) {
27
28
  throw new Error('This component reads a chat, so it has to be rendered inside a ChatScopeContext');
28
29
  }
30
+
29
31
  return scope;
30
32
  }