@adecore/agents-react 0.0.1 → 0.17.0-beta.2
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.
- package/LICENSE +110 -0
- package/README.md +61 -0
- package/dist/agents/AccountDot.d.ts +4 -0
- package/dist/agents/AccountDot.js +8 -0
- package/dist/agents/AgentIcon.d.ts +6 -0
- package/dist/agents/AgentIcon.js +20 -0
- package/dist/agents/ModelOptionControl.d.ts +15 -0
- package/dist/agents/ModelOptionControl.js +33 -0
- package/dist/agents/ProviderLogo.d.ts +8 -0
- package/dist/agents/ProviderLogo.js +8 -0
- package/dist/agents/account-limits.d.ts +7 -0
- package/dist/agents/account-limits.js +40 -0
- package/dist/agents/accounts.d.ts +22 -0
- package/dist/agents/accounts.js +113 -0
- package/dist/agents/model-name.d.ts +12 -0
- package/dist/agents/model-name.js +56 -0
- package/dist/agents/model-options.d.ts +5 -0
- package/dist/agents/model-options.js +15 -0
- package/dist/agents/provider-paths.d.ts +2 -0
- package/dist/agents/provider-paths.js +8 -0
- package/dist/agents/status-look.d.ts +13 -0
- package/dist/agents/status-look.js +24 -0
- package/dist/chat/account-choice.d.ts +11 -0
- package/dist/chat/account-choice.js +30 -0
- package/dist/chat/actions.d.ts +6 -0
- package/dist/chat/actions.js +68 -0
- package/dist/chat/activity.d.ts +5 -0
- package/dist/chat/activity.js +6 -0
- package/dist/chat/attachments.d.ts +23 -0
- package/dist/chat/attachments.js +86 -0
- package/dist/chat/bookmarks.d.ts +14 -0
- package/dist/chat/bookmarks.js +73 -0
- package/dist/chat/chat-client.d.ts +55 -0
- package/dist/chat/chat-client.js +279 -0
- package/dist/chat/chat-references.d.ts +5 -0
- package/dist/chat/chat-references.js +5 -0
- package/dist/chat/drafts.d.ts +29 -0
- package/dist/chat/drafts.js +116 -0
- package/dist/chat/forks.d.ts +2 -0
- package/dist/chat/forks.js +28 -0
- package/dist/chat/guards.d.ts +12 -0
- package/dist/chat/guards.js +69 -0
- package/dist/chat/logic/ansi.d.ts +13 -0
- package/dist/chat/logic/ansi.js +185 -0
- package/dist/chat/logic/answers.d.ts +1 -0
- package/dist/chat/logic/answers.js +8 -0
- package/dist/chat/logic/bookmarks.d.ts +5 -0
- package/dist/chat/logic/bookmarks.js +33 -0
- package/dist/chat/logic/context-usage.d.ts +12 -0
- package/dist/chat/logic/context-usage.js +21 -0
- package/dist/chat/logic/fork.d.ts +45 -0
- package/dist/chat/logic/fork.js +148 -0
- package/dist/chat/logic/handback.d.ts +2 -0
- package/dist/chat/logic/handback.js +8 -0
- package/dist/chat/logic/json.d.ts +1 -0
- package/dist/chat/logic/json.js +4 -0
- package/dist/chat/logic/limit.d.ts +7 -0
- package/dist/chat/logic/limit.js +34 -0
- package/dist/chat/logic/resume-compaction.d.ts +14 -0
- package/dist/chat/logic/resume-compaction.js +47 -0
- package/dist/chat/logic/scrubber.d.ts +50 -0
- package/dist/chat/logic/scrubber.js +220 -0
- package/dist/chat/logic/thread-cards.d.ts +3 -0
- package/dist/chat/logic/thread-cards.js +59 -0
- package/dist/chat/logic/timeline-copy.d.ts +4 -0
- package/dist/chat/logic/timeline-copy.js +59 -0
- package/dist/chat/logic/timeline-target.d.ts +12 -0
- package/dist/chat/logic/timeline-target.js +34 -0
- package/dist/chat/logic/timeline.d.ts +115 -0
- package/dist/chat/logic/timeline.js +343 -0
- package/dist/chat/logic/tool-catalog.d.ts +10 -0
- package/dist/chat/logic/tool-catalog.js +44 -0
- package/dist/chat/logic/tools.d.ts +15 -0
- package/dist/chat/logic/tools.js +82 -0
- package/dist/chat/logic/welcome.d.ts +2 -0
- package/dist/chat/logic/welcome.js +10 -0
- package/dist/chat/logic/workflow.d.ts +14 -0
- package/dist/chat/logic/workflow.js +47 -0
- package/dist/chat/mentions.d.ts +65 -0
- package/dist/chat/mentions.js +184 -0
- package/dist/chat/persisted-json.d.ts +5 -0
- package/dist/chat/persisted-json.js +58 -0
- package/dist/chat/preferences.d.ts +27 -0
- package/dist/chat/preferences.js +139 -0
- package/dist/chat/quote.d.ts +2 -0
- package/dist/chat/quote.js +25 -0
- package/dist/chat/recent-messages.d.ts +17 -0
- package/dist/chat/recent-messages.js +36 -0
- package/dist/chat/resume-compaction-dismissals.d.ts +3 -0
- package/dist/chat/resume-compaction-dismissals.js +14 -0
- package/dist/chat/runtime-modes.d.ts +4 -0
- package/dist/chat/runtime-modes.js +15 -0
- package/dist/chat/stash.d.ts +24 -0
- package/dist/chat/stash.js +91 -0
- package/dist/chat/subagent-conversation.d.ts +41 -0
- package/dist/chat/subagent-conversation.js +167 -0
- package/dist/chat/subagent-list.d.ts +19 -0
- package/dist/chat/subagent-list.js +106 -0
- package/dist/chat/subagent-support.d.ts +6 -0
- package/dist/chat/subagent-support.js +14 -0
- package/dist/chat/subagent-view.d.ts +35 -0
- package/dist/chat/subagent-view.js +96 -0
- package/dist/chat/thumbnails.d.ts +10 -0
- package/dist/chat/thumbnails.js +89 -0
- package/dist/chat/timeline-flash.d.ts +12 -0
- package/dist/chat/timeline-flash.js +16 -0
- package/dist/chat/timeline-scroll.d.ts +27 -0
- package/dist/chat/timeline-scroll.js +152 -0
- package/dist/chat/ui/AccountPill.d.ts +3 -0
- package/dist/chat/ui/AccountPill.js +23 -0
- package/dist/chat/ui/AnsiOutput.d.ts +5 -0
- package/dist/chat/ui/AnsiOutput.js +9 -0
- package/dist/chat/ui/BookmarkMarker.d.ts +8 -0
- package/dist/chat/ui/BookmarkMarker.js +57 -0
- package/dist/chat/ui/BookmarkSubmenu.d.ts +3 -0
- package/dist/chat/ui/BookmarkSubmenu.js +33 -0
- package/dist/chat/ui/ChatActivity.d.ts +7 -0
- package/dist/chat/ui/ChatActivity.js +84 -0
- package/dist/chat/ui/ChatReferenceChip.d.ts +4 -0
- package/dist/chat/ui/ChatReferenceChip.js +16 -0
- package/dist/chat/ui/CodeBlock.d.ts +4 -0
- package/dist/chat/ui/CodeBlock.js +132 -0
- package/dist/chat/ui/Composer.d.ts +15 -0
- package/dist/chat/ui/Composer.js +856 -0
- package/dist/chat/ui/ComposerInput.d.ts +29 -0
- package/dist/chat/ui/ComposerInput.js +127 -0
- package/dist/chat/ui/DiffPool.d.ts +5 -0
- package/dist/chat/ui/DiffPool.js +26 -0
- package/dist/chat/ui/EditDiff.d.ts +4 -0
- package/dist/chat/ui/EditDiff.js +21 -0
- package/dist/chat/ui/FadingWords.d.ts +3 -0
- package/dist/chat/ui/FadingWords.js +5 -0
- package/dist/chat/ui/ImageView.d.ts +6 -0
- package/dist/chat/ui/ImageView.js +81 -0
- package/dist/chat/ui/LimitState.d.ts +8 -0
- package/dist/chat/ui/LimitState.js +112 -0
- package/dist/chat/ui/Markdown.d.ts +18 -0
- package/dist/chat/ui/Markdown.js +159 -0
- package/dist/chat/ui/MessageActions.d.ts +9 -0
- package/dist/chat/ui/MessageActions.js +53 -0
- package/dist/chat/ui/Pickers.d.ts +21 -0
- package/dist/chat/ui/Pickers.js +111 -0
- package/dist/chat/ui/PromptComposer.d.ts +13 -0
- package/dist/chat/ui/PromptComposer.js +56 -0
- package/dist/chat/ui/QuoteButton.d.ts +7 -0
- package/dist/chat/ui/QuoteButton.js +74 -0
- package/dist/chat/ui/ResumeCompactionDock.d.ts +5 -0
- package/dist/chat/ui/ResumeCompactionDock.js +14 -0
- package/dist/chat/ui/RunSettings.d.ts +28 -0
- package/dist/chat/ui/RunSettings.js +153 -0
- package/dist/chat/ui/Scrubber.d.ts +17 -0
- package/dist/chat/ui/Scrubber.js +118 -0
- package/dist/chat/ui/SubagentControls.d.ts +11 -0
- package/dist/chat/ui/SubagentControls.js +36 -0
- package/dist/chat/ui/SubagentInfo.d.ts +4 -0
- package/dist/chat/ui/SubagentInfo.js +34 -0
- package/dist/chat/ui/SubagentStopButton.d.ts +7 -0
- package/dist/chat/ui/SubagentStopButton.js +34 -0
- package/dist/chat/ui/SubagentTimeline.d.ts +4 -0
- package/dist/chat/ui/SubagentTimeline.js +107 -0
- package/dist/chat/ui/Timeline.d.ts +6 -0
- package/dist/chat/ui/Timeline.js +424 -0
- package/dist/chat/ui/TimelineMenu.d.ts +12 -0
- package/dist/chat/ui/TimelineMenu.js +54 -0
- package/dist/chat/ui/UnifiedDiff.d.ts +11 -0
- package/dist/chat/ui/UnifiedDiff.js +66 -0
- package/dist/chat/ui/UploadThumb.d.ts +4 -0
- package/dist/chat/ui/UploadThumb.js +11 -0
- package/dist/chat/ui/Welcome.d.ts +4 -0
- package/dist/chat/ui/Welcome.js +16 -0
- package/dist/chat/ui/chips.d.ts +4 -0
- package/dist/chat/ui/chips.js +11 -0
- package/dist/chat/ui/code-lines.d.ts +18 -0
- package/dist/chat/ui/code-lines.js +46 -0
- package/dist/chat/ui/code-streaming.d.ts +1 -0
- package/dist/chat/ui/code-streaming.js +3 -0
- package/dist/chat/ui/code-theme.d.ts +3 -0
- package/dist/chat/ui/code-theme.js +12 -0
- package/dist/chat/ui/composer/chips.d.ts +6 -0
- package/dist/chat/ui/composer/chips.js +31 -0
- package/dist/chat/ui/composer/editor.d.ts +11 -0
- package/dist/chat/ui/composer/editor.js +98 -0
- package/dist/chat/ui/composer/insert.d.ts +2 -0
- package/dist/chat/ui/composer/insert.js +19 -0
- package/dist/chat/ui/composer/keys.d.ts +30 -0
- package/dist/chat/ui/composer/keys.js +100 -0
- package/dist/chat/ui/diff-theme.d.ts +2 -0
- package/dist/chat/ui/diff-theme.js +20 -0
- package/dist/chat/ui/file-links.d.ts +5 -0
- package/dist/chat/ui/file-links.js +20 -0
- package/dist/chat/ui/find-reveal.d.ts +7 -0
- package/dist/chat/ui/find-reveal.js +13 -0
- package/dist/chat/ui/full-diff.d.ts +6 -0
- package/dist/chat/ui/full-diff.js +19 -0
- package/dist/chat/ui/icons.d.ts +4 -0
- package/dist/chat/ui/icons.js +20 -0
- package/dist/chat/ui/markdown-blocks.d.ts +6 -0
- package/dist/chat/ui/markdown-blocks.js +103 -0
- package/dist/chat/ui/quote-selection.d.ts +3 -0
- package/dist/chat/ui/quote-selection.js +46 -0
- package/dist/chat/ui/rehype-chips.d.ts +23 -0
- package/dist/chat/ui/rehype-chips.js +32 -0
- package/dist/chat/ui/rehype-fade.d.ts +23 -0
- package/dist/chat/ui/rehype-fade.js +36 -0
- package/dist/chat/ui/remark-html-as-text.d.ts +8 -0
- package/dist/chat/ui/remark-html-as-text.js +26 -0
- package/dist/chat/ui/reply-context.d.ts +5 -0
- package/dist/chat/ui/reply-context.js +2 -0
- package/dist/chat/ui/reveal.d.ts +13 -0
- package/dist/chat/ui/reveal.js +114 -0
- package/dist/chat/ui/rows/ForksRow.d.ts +4 -0
- package/dist/chat/ui/rows/ForksRow.js +38 -0
- package/dist/chat/ui/rows/MessageRows.d.ts +40 -0
- package/dist/chat/ui/rows/MessageRows.js +204 -0
- package/dist/chat/ui/rows/Rows.d.ts +12 -0
- package/dist/chat/ui/rows/Rows.js +61 -0
- package/dist/chat/ui/rows/SubagentRow.d.ts +17 -0
- package/dist/chat/ui/rows/SubagentRow.js +101 -0
- package/dist/chat/ui/rows/TaskRow.d.ts +8 -0
- package/dist/chat/ui/rows/TaskRow.js +63 -0
- package/dist/chat/ui/rows/WorkRows.d.ts +47 -0
- package/dist/chat/ui/rows/WorkRows.js +168 -0
- package/dist/chat/ui/rows/WorkflowRow.d.ts +8 -0
- package/dist/chat/ui/rows/WorkflowRow.js +48 -0
- package/dist/chat/ui/rows/row-rhythm.d.ts +6 -0
- package/dist/chat/ui/rows/row-rhythm.js +34 -0
- package/dist/chat/ui/useToggleSet.d.ts +6 -0
- package/dist/chat/ui/useToggleSet.js +25 -0
- package/dist/host.d.ts +195 -0
- package/dist/host.js +55 -0
- package/dist/lazy.d.ts +6 -0
- package/dist/lazy.js +57 -0
- package/dist/locales/en/agent-chat.json +446 -0
- package/dist/locales/en/agent-prompts.json +42 -0
- package/dist/locales/en/agent-providers.json +131 -0
- package/dist/locales/en/agent-usage.json +131 -0
- package/dist/locales/nl/agent-chat.json +446 -0
- package/dist/locales/nl/agent-prompts.json +42 -0
- package/dist/locales/nl/agent-providers.json +131 -0
- package/dist/locales/nl/agent-usage.json +131 -0
- package/dist/locales.d.ts +5 -0
- package/dist/locales.js +20 -0
- package/dist/mounted-registry.d.ts +7 -0
- package/dist/mounted-registry.js +39 -0
- package/dist/port-transport.d.ts +6 -0
- package/dist/port-transport.js +129 -0
- package/dist/prompts/logic/focus.d.ts +2 -0
- package/dist/prompts/logic/focus.js +23 -0
- package/dist/prompts/logic/keys.d.ts +42 -0
- package/dist/prompts/logic/keys.js +94 -0
- package/dist/prompts/logic/prompts.d.ts +34 -0
- package/dist/prompts/logic/prompts.fixtures.d.ts +5 -0
- package/dist/prompts/logic/prompts.fixtures.js +79 -0
- package/dist/prompts/logic/prompts.js +32 -0
- package/dist/prompts/logic/subjects.d.ts +50 -0
- package/dist/prompts/logic/subjects.js +63 -0
- package/dist/prompts/logic/useFocusAfterAnswer.d.ts +6 -0
- package/dist/prompts/logic/useFocusAfterAnswer.js +28 -0
- package/dist/prompts/logic/usePromptSession.d.ts +24 -0
- package/dist/prompts/logic/usePromptSession.js +87 -0
- package/dist/prompts/ui/ApprovalBody.d.ts +13 -0
- package/dist/prompts/ui/ApprovalBody.js +34 -0
- package/dist/prompts/ui/PromptActions.d.ts +27 -0
- package/dist/prompts/ui/PromptActions.js +18 -0
- package/dist/prompts/ui/PromptCard.d.ts +16 -0
- package/dist/prompts/ui/PromptCard.js +50 -0
- package/dist/prompts/ui/PromptView.d.ts +2 -0
- package/dist/prompts/ui/PromptView.js +47 -0
- package/dist/prompts/ui/QuestionBody.d.ts +9 -0
- package/dist/prompts/ui/QuestionBody.js +87 -0
- package/dist/providers/AccountColors.d.ts +5 -0
- package/dist/providers/AccountColors.js +8 -0
- package/dist/providers/AccountDetail.d.ts +12 -0
- package/dist/providers/AccountDetail.js +69 -0
- package/dist/providers/AccountVariables.d.ts +8 -0
- package/dist/providers/AccountVariables.js +43 -0
- package/dist/providers/AddAccountForm.d.ts +10 -0
- package/dist/providers/AddAccountForm.js +54 -0
- package/dist/providers/CliDetail.d.ts +12 -0
- package/dist/providers/CliDetail.js +54 -0
- package/dist/providers/ProvidersPane.d.ts +9 -0
- package/dist/providers/ProvidersPane.js +86 -0
- package/dist/providers/account-actions.d.ts +7 -0
- package/dist/providers/account-actions.js +57 -0
- package/dist/providers/parts.d.ts +9 -0
- package/dist/providers/parts.js +18 -0
- package/dist/providers/provider-abilities.d.ts +2 -0
- package/dist/providers/provider-abilities.js +7 -0
- package/dist/providers/variables.d.ts +13 -0
- package/dist/providers/variables.js +63 -0
- package/dist/scope.d.ts +11 -0
- package/dist/scope.js +10 -0
- package/dist/settings/sections.d.ts +13 -0
- package/dist/settings/sections.js +32 -0
- package/dist/state/chats.d.ts +44 -0
- package/dist/state/chats.js +199 -0
- package/dist/state/provider-accounts.d.ts +17 -0
- package/dist/state/provider-accounts.js +67 -0
- package/dist/state/providers.d.ts +17 -0
- package/dist/state/providers.js +33 -0
- package/dist/state/usage.d.ts +44 -0
- package/dist/state/usage.js +121 -0
- package/dist/storage.d.ts +20 -0
- package/dist/storage.js +89 -0
- package/dist/theme.css +212 -0
- package/dist/transport.d.ts +24 -0
- package/dist/transport.js +18 -0
- package/dist/ui/PlainTextarea.d.ts +2 -0
- package/dist/ui/PlainTextarea.js +5 -0
- package/dist/usage/LimitsList.d.ts +18 -0
- package/dist/usage/LimitsList.js +73 -0
- package/dist/usage/UsageBreakdown.d.ts +9 -0
- package/dist/usage/UsageBreakdown.js +67 -0
- package/dist/usage/UsageChart.d.ts +10 -0
- package/dist/usage/UsageChart.js +47 -0
- package/dist/usage/UsageDialog.d.ts +6 -0
- package/dist/usage/UsageDialog.js +12 -0
- package/dist/usage/UsageLimits.d.ts +1 -0
- package/dist/usage/UsageLimits.js +57 -0
- package/dist/usage/UsageLimitsCard.d.ts +4 -0
- package/dist/usage/UsageLimitsCard.js +26 -0
- package/dist/usage/UsagePage.d.ts +7 -0
- package/dist/usage/UsagePage.js +154 -0
- package/dist/usage/UsagePane.d.ts +3 -0
- package/dist/usage/UsagePane.js +21 -0
- package/dist/usage/UsageSummary.d.ts +12 -0
- package/dist/usage/UsageSummary.js +15 -0
- package/dist/usage/UsageTiles.d.ts +7 -0
- package/dist/usage/UsageTiles.js +19 -0
- package/dist/usage/format.d.ts +13 -0
- package/dist/usage/format.js +55 -0
- package/dist/usage/limit-groups.d.ts +22 -0
- package/dist/usage/limit-groups.js +86 -0
- package/dist/usage/limits.d.ts +4 -0
- package/dist/usage/limits.js +58 -0
- package/dist/usage/money.d.ts +1 -0
- package/dist/usage/money.js +13 -0
- package/dist/usage/summary.d.ts +34 -0
- package/dist/usage/summary.js +94 -0
- package/package.json +895 -3
- package/src/agents/AccountDot.tsx +14 -0
- package/src/agents/AgentIcon.tsx +26 -0
- package/src/agents/ModelOptionControl.tsx +68 -0
- package/src/agents/ProviderLogo.tsx +20 -0
- package/src/agents/account-limits.ts +61 -0
- package/src/agents/accounts.ts +137 -0
- package/src/agents/model-name.ts +70 -0
- package/src/agents/model-options.ts +21 -0
- package/src/agents/provider-paths.ts +10 -0
- package/src/agents/status-look.ts +43 -0
- package/src/chat/account-choice.ts +44 -0
- package/src/chat/actions.ts +72 -0
- package/src/chat/activity.ts +8 -0
- package/src/chat/attachments.ts +118 -0
- package/src/chat/bookmarks.ts +87 -0
- package/src/chat/chat-client.ts +345 -0
- package/src/chat/chat-references.ts +11 -0
- package/src/chat/drafts.ts +165 -0
- package/src/chat/forks.ts +33 -0
- package/src/chat/guards.ts +84 -0
- package/src/chat/logic/ansi.ts +206 -0
- package/src/chat/logic/answers.ts +8 -0
- package/src/chat/logic/bookmarks.ts +42 -0
- package/src/chat/logic/context-usage.ts +36 -0
- package/src/chat/logic/fork.ts +200 -0
- package/src/chat/logic/handback.ts +10 -0
- package/src/chat/logic/json.ts +4 -0
- package/src/chat/logic/limit.ts +46 -0
- package/src/chat/logic/resume-compaction.ts +71 -0
- package/src/chat/logic/scrubber.ts +289 -0
- package/src/chat/logic/thread-cards.ts +64 -0
- package/src/chat/logic/timeline-copy.ts +65 -0
- package/src/chat/logic/timeline-target.ts +52 -0
- package/src/chat/logic/timeline.ts +419 -0
- package/src/chat/logic/tool-catalog.ts +62 -0
- package/src/chat/logic/tools.ts +102 -0
- package/src/chat/logic/welcome.ts +13 -0
- package/src/chat/logic/workflow.ts +66 -0
- package/src/chat/mentions.ts +232 -0
- package/src/chat/persisted-json.ts +67 -0
- package/src/chat/preferences.ts +179 -0
- package/src/chat/quote.ts +26 -0
- package/src/chat/recent-messages.ts +59 -0
- package/src/chat/resume-compaction-dismissals.ts +17 -0
- package/src/chat/runtime-modes.ts +18 -0
- package/src/chat/stash.ts +122 -0
- package/src/chat/subagent-conversation.ts +203 -0
- package/src/chat/subagent-list.ts +141 -0
- package/src/chat/subagent-support.ts +21 -0
- package/src/chat/subagent-view.ts +137 -0
- package/src/chat/thumbnails.ts +108 -0
- package/src/chat/timeline-flash.ts +33 -0
- package/src/chat/timeline-scroll.ts +199 -0
- package/src/chat/ui/AccountPill.tsx +31 -0
- package/src/chat/ui/AnsiOutput.tsx +18 -0
- package/src/chat/ui/BookmarkMarker.tsx +108 -0
- package/src/chat/ui/BookmarkSubmenu.tsx +53 -0
- package/src/chat/ui/ChatActivity.tsx +198 -0
- package/src/chat/ui/ChatReferenceChip.tsx +28 -0
- package/src/chat/ui/CodeBlock.tsx +175 -0
- package/src/chat/ui/Composer.tsx +1326 -0
- package/src/chat/ui/ComposerInput.tsx +187 -0
- package/src/chat/ui/DiffPool.tsx +36 -0
- package/src/chat/ui/EditDiff.tsx +30 -0
- package/src/chat/ui/FadingWords.tsx +18 -0
- package/src/chat/ui/ImageView.tsx +159 -0
- package/src/chat/ui/LimitState.tsx +165 -0
- package/src/chat/ui/Markdown.tsx +258 -0
- package/src/chat/ui/MessageActions.tsx +88 -0
- package/src/chat/ui/Pickers.tsx +272 -0
- package/src/chat/ui/PromptComposer.tsx +119 -0
- package/src/chat/ui/QuoteButton.tsx +103 -0
- package/src/chat/ui/ResumeCompactionDock.tsx +28 -0
- package/src/chat/ui/RunSettings.tsx +546 -0
- package/src/chat/ui/Scrubber.tsx +262 -0
- package/src/chat/ui/SubagentControls.tsx +66 -0
- package/src/chat/ui/SubagentInfo.tsx +67 -0
- package/src/chat/ui/SubagentStopButton.tsx +45 -0
- package/src/chat/ui/SubagentTimeline.tsx +170 -0
- package/src/chat/ui/Timeline.tsx +628 -0
- package/src/chat/ui/TimelineMenu.tsx +124 -0
- package/src/chat/ui/UnifiedDiff.tsx +99 -0
- package/src/chat/ui/UploadThumb.tsx +13 -0
- package/src/chat/ui/Welcome.tsx +22 -0
- package/src/chat/ui/chips.ts +15 -0
- package/src/chat/ui/code-lines.ts +60 -0
- package/src/chat/ui/code-streaming.ts +4 -0
- package/src/chat/ui/code-theme.ts +15 -0
- package/src/chat/ui/composer/chips.ts +43 -0
- package/src/chat/ui/composer/editor.ts +118 -0
- package/src/chat/ui/composer/insert.ts +22 -0
- package/src/chat/ui/composer/keys.ts +144 -0
- package/src/chat/ui/diff-theme.ts +23 -0
- package/src/chat/ui/file-links.ts +24 -0
- package/src/chat/ui/find-reveal.ts +23 -0
- package/src/chat/ui/full-diff.ts +25 -0
- package/src/chat/ui/icons.tsx +24 -0
- package/src/chat/ui/markdown-blocks.ts +123 -0
- package/src/chat/ui/quote-selection.ts +53 -0
- package/src/chat/ui/rehype-chips.ts +62 -0
- package/src/chat/ui/rehype-fade.ts +64 -0
- package/src/chat/ui/remark-html-as-text.ts +36 -0
- package/src/chat/ui/reply-context.ts +4 -0
- package/src/chat/ui/reveal.ts +135 -0
- package/src/chat/ui/rows/ForksRow.tsx +79 -0
- package/src/chat/ui/rows/MessageRows.tsx +415 -0
- package/src/chat/ui/rows/Rows.tsx +81 -0
- package/src/chat/ui/rows/SubagentRow.tsx +209 -0
- package/src/chat/ui/rows/TaskRow.tsx +91 -0
- package/src/chat/ui/rows/WorkRows.tsx +445 -0
- package/src/chat/ui/rows/WorkflowRow.tsx +120 -0
- package/src/chat/ui/rows/row-rhythm.ts +37 -0
- package/src/chat/ui/useToggleSet.ts +39 -0
- package/src/host.ts +324 -0
- package/src/lazy.tsx +64 -0
- package/src/locales/en/agent-chat.json +446 -0
- package/src/locales/en/agent-prompts.json +42 -0
- package/src/locales/en/agent-providers.json +131 -0
- package/src/locales/en/agent-usage.json +131 -0
- package/src/locales/nl/agent-chat.json +446 -0
- package/src/locales/nl/agent-prompts.json +42 -0
- package/src/locales/nl/agent-providers.json +131 -0
- package/src/locales/nl/agent-usage.json +131 -0
- package/src/locales.ts +33 -0
- package/src/mounted-registry.ts +45 -0
- package/src/port-transport.ts +146 -0
- package/src/prompts/logic/focus.ts +25 -0
- package/src/prompts/logic/keys.ts +116 -0
- package/src/prompts/logic/prompts.fixtures.ts +84 -0
- package/src/prompts/logic/prompts.ts +58 -0
- package/src/prompts/logic/subjects.ts +122 -0
- package/src/prompts/logic/useFocusAfterAnswer.ts +38 -0
- package/src/prompts/logic/usePromptSession.ts +114 -0
- package/src/prompts/ui/ApprovalBody.tsx +109 -0
- package/src/prompts/ui/PromptActions.tsx +109 -0
- package/src/prompts/ui/PromptCard.tsx +101 -0
- package/src/prompts/ui/PromptView.tsx +111 -0
- package/src/prompts/ui/QuestionBody.tsx +174 -0
- package/src/providers/AccountColors.tsx +8 -0
- package/src/providers/AccountDetail.tsx +209 -0
- package/src/providers/AccountVariables.tsx +126 -0
- package/src/providers/AddAccountForm.tsx +121 -0
- package/src/providers/CliDetail.tsx +157 -0
- package/src/providers/ProvidersPane.tsx +236 -0
- package/src/providers/account-actions.ts +67 -0
- package/src/providers/parts.tsx +25 -0
- package/src/providers/provider-abilities.ts +9 -0
- package/src/providers/variables.ts +87 -0
- package/src/scope.ts +30 -0
- package/src/settings/sections.ts +47 -0
- package/src/state/chats.ts +279 -0
- package/src/state/provider-accounts.ts +87 -0
- package/src/state/providers.ts +50 -0
- package/src/state/usage.ts +196 -0
- package/src/storage.ts +113 -0
- package/src/theme.css +212 -0
- package/src/transport.ts +50 -0
- package/src/ui/PlainTextarea.tsx +6 -0
- package/src/usage/LimitsList.tsx +232 -0
- package/src/usage/UsageBreakdown.tsx +210 -0
- package/src/usage/UsageChart.tsx +113 -0
- package/src/usage/UsageDialog.tsx +20 -0
- package/src/usage/UsageLimits.tsx +145 -0
- package/src/usage/UsageLimitsCard.tsx +36 -0
- package/src/usage/UsagePage.tsx +276 -0
- package/src/usage/UsagePane.tsx +52 -0
- package/src/usage/UsageSummary.tsx +50 -0
- package/src/usage/UsageTiles.tsx +34 -0
- package/src/usage/format.ts +71 -0
- package/src/usage/limit-groups.ts +119 -0
- package/src/usage/limits.ts +71 -0
- package/src/usage/money.ts +14 -0
- package/src/usage/summary.ts +137 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import i18next from 'i18next';
|
|
2
|
+
import type { AgentKind, ProviderAccount, ProviderAccountMap } from '@adecore/agent-contracts';
|
|
3
|
+
import { accountFor, readChatPreferences, rememberChatAccount } from '../chat/preferences';
|
|
4
|
+
import { chatHost } from '../host';
|
|
5
|
+
import type { ChatScope } from '../scope';
|
|
6
|
+
import { providerAccountsOf, useProviderAccountsStore } from '../state/provider-accounts';
|
|
7
|
+
import type { ChatTransport } from '../transport';
|
|
8
|
+
|
|
9
|
+
function words(key: string, values?: Record<string, string>): string {
|
|
10
|
+
return i18next.t(`agent-providers:${key}`, values);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function reason(e: unknown): string {
|
|
14
|
+
return e instanceof Error ? e.message : String(e);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function linkTo(scope: ChatScope): ChatTransport {
|
|
18
|
+
if (scope.transport.status !== 'open') {
|
|
19
|
+
throw new Error(words('notAnswering'));
|
|
20
|
+
}
|
|
21
|
+
return scope.transport;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* The whole map, as `accounts.save` takes it. Answers false when the host refused, which a toast then says. */
|
|
25
|
+
export async function saveAccounts(scope: ChatScope, accounts: ProviderAccountMap): Promise<boolean> {
|
|
26
|
+
try {
|
|
27
|
+
useProviderAccountsStore.getState().set(scope.id, await linkTo(scope).request('accounts.save', { accounts }));
|
|
28
|
+
return true;
|
|
29
|
+
} catch (e) {
|
|
30
|
+
chatHost().notify({ id: `providers-save-${scope.id}`, kind: 'error', title: words('account.saveFailed'), description: reason(e) });
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* One account changed and every other one as the host last said. */
|
|
36
|
+
export function saveAccount(scope: ChatScope, id: string, account: ProviderAccount): Promise<boolean> {
|
|
37
|
+
const current = providerAccountsOf(scope.id).accounts?.accounts ?? {};
|
|
38
|
+
return saveAccounts(scope, { ...current, [id]: account });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/*
|
|
42
|
+
* Forgets an account; its folder stays where it is. A pick for new agents that named it goes back to
|
|
43
|
+
* the CLI's default account, since no chat started from it yet.
|
|
44
|
+
*/
|
|
45
|
+
export async function removeAccount(scope: ChatScope, id: string, kind: AgentKind): Promise<void> {
|
|
46
|
+
const { [id]: _gone, ...rest } = providerAccountsOf(scope.id).accounts?.accounts ?? {};
|
|
47
|
+
if (!(await saveAccounts(scope, rest))) {
|
|
48
|
+
throw new Error(words('account.saveFailed'));
|
|
49
|
+
}
|
|
50
|
+
if (accountFor(readChatPreferences(), scope.id, kind) === id) {
|
|
51
|
+
rememberChatAccount(scope.id, kind, null);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* An account in a folder the host makes for it; answers its id. Throws what the host said, for the form to show. */
|
|
56
|
+
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
|
+
useProviderAccountsStore.getState().set(scope.id, accounts);
|
|
60
|
+
return id;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* An account in a folder that is already on the host. Throws what the host said, for the form to show. */
|
|
64
|
+
export async function linkAccount(scope: ChatScope, id: string, account: ProviderAccount): Promise<void> {
|
|
65
|
+
const current = providerAccountsOf(scope.id).accounts?.accounts ?? {};
|
|
66
|
+
useProviderAccountsStore.getState().set(scope.id, await linkTo(scope).request('accounts.save', { accounts: { ...current, [id]: account } }));
|
|
67
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import type { AgentKind } from '@adecore/agent-contracts';
|
|
3
|
+
import { AgentIcon } from '../agents/AgentIcon';
|
|
4
|
+
|
|
5
|
+
// Each CLI in its color of the usage charts, so a mark here reads as the same CLI there.
|
|
6
|
+
const CLI_COLORS: Record<AgentKind, string> = {
|
|
7
|
+
apple: 'text-text-muted',
|
|
8
|
+
claude: 'text-chart-claude',
|
|
9
|
+
codex: 'text-chart-codex',
|
|
10
|
+
gemini: 'text-chart-gemini',
|
|
11
|
+
copilot: 'text-chart-copilot'
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function CliMark({ kind, size = 14, className }: { kind: AgentKind; size?: number; className?: string }) {
|
|
15
|
+
return <AgentIcon kind={kind} size={size} className={clsx('shrink-0', CLI_COLORS[kind], className)} />;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* As tall as the title's first line, so the mark sits beside the name rather than between the two lines. */
|
|
19
|
+
export function CliTile({ kind }: { kind: AgentKind }) {
|
|
20
|
+
return (
|
|
21
|
+
<span className="grid h-6 shrink-0 place-items-center">
|
|
22
|
+
<CliMark kind={kind} size={24} />
|
|
23
|
+
</span>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import i18next from 'i18next';
|
|
2
|
+
import type { ProviderInfo } from '@adecore/agent-contracts';
|
|
3
|
+
|
|
4
|
+
/* What a provider offers, in one sentence, where it can be opened and whether its hooks report status. */
|
|
5
|
+
export function providerAbilities(provider: ProviderInfo): string {
|
|
6
|
+
const where = provider.capabilities.chat ? i18next.t('agent-providers:abilities.chatAndTerminal') : i18next.t('agent-providers:abilities.terminalOnly');
|
|
7
|
+
const status = provider.capabilities.hooks ? i18next.t('agent-providers:abilities.reportsStatus') : i18next.t('agent-providers:abilities.noStatus');
|
|
8
|
+
return `${where} ${status}`;
|
|
9
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import i18next from 'i18next';
|
|
2
|
+
import type { ProviderAccountVariable } from '@adecore/agent-contracts';
|
|
3
|
+
|
|
4
|
+
const NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
|
|
5
|
+
|
|
6
|
+
/* One variable as the person edits it, before it is saved. */
|
|
7
|
+
export interface VariableDraft {
|
|
8
|
+
/* Keeps a row in place while its name is typed. */
|
|
9
|
+
key: string;
|
|
10
|
+
name: string;
|
|
11
|
+
value: string;
|
|
12
|
+
sensitive: boolean;
|
|
13
|
+
/* The name the machine keeps a sensitive value under, while the value itself is not retyped. */
|
|
14
|
+
kept: string | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let drafted = 0;
|
|
18
|
+
function nextKey(): string {
|
|
19
|
+
drafted += 1;
|
|
20
|
+
return `variable-${drafted}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function draftsOf(env: readonly ProviderAccountVariable[] | undefined): VariableDraft[] {
|
|
24
|
+
return (env ?? []).map((variable) => ({
|
|
25
|
+
key: nextKey(),
|
|
26
|
+
name: variable.name,
|
|
27
|
+
value: variable.value,
|
|
28
|
+
sensitive: variable.sensitive,
|
|
29
|
+
kept: variable.sensitive && variable.valueRedacted === true ? variable.name : null
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function emptyDraft(sensitive: boolean): VariableDraft {
|
|
34
|
+
return { key: nextKey(), name: '', value: '', sensitive, kept: null };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/*
|
|
38
|
+
* A value the keychain holds only counts under the name it was kept under, and only while it stays
|
|
39
|
+
* sensitive: the machine finds it by that name, and never hands it out to be stored in plain text.
|
|
40
|
+
*/
|
|
41
|
+
function keepsValue(draft: VariableDraft): boolean {
|
|
42
|
+
return draft.value === '' && draft.sensitive && draft.kept === draft.name;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* The first thing that keeps these from being saved, in a sentence; null when they can be. */
|
|
46
|
+
export function variablesProblem(drafts: readonly VariableDraft[]): string | null {
|
|
47
|
+
const words = (key: string, name: string): string => i18next.t(`agent-providers:account.variables.problem.${key}`, { name });
|
|
48
|
+
const seen = new Set<string>();
|
|
49
|
+
for (const draft of drafts) {
|
|
50
|
+
const name = draft.name.trim();
|
|
51
|
+
if (name === '') {
|
|
52
|
+
return words('noName', '');
|
|
53
|
+
}
|
|
54
|
+
if (!NAME.test(name)) {
|
|
55
|
+
return words('invalidName', name);
|
|
56
|
+
}
|
|
57
|
+
if (seen.has(name)) {
|
|
58
|
+
return words('duplicate', name);
|
|
59
|
+
}
|
|
60
|
+
seen.add(name);
|
|
61
|
+
if (draft.value === '' && !keepsValue({ ...draft, name })) {
|
|
62
|
+
return words('noValue', name);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* The variables as `accounts.save` takes them; a kept value goes back empty and redacted, so the machine keeps what it has. */
|
|
69
|
+
export function variablesOf(drafts: readonly VariableDraft[]): ProviderAccountVariable[] {
|
|
70
|
+
return drafts.map((draft) => {
|
|
71
|
+
const name = draft.name.trim();
|
|
72
|
+
if (keepsValue({ ...draft, name })) {
|
|
73
|
+
return { name, value: '', sensitive: true, valueRedacted: true };
|
|
74
|
+
}
|
|
75
|
+
return { name, value: draft.value, sensitive: draft.sensitive };
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Whether the drafts say something other than what the machine holds. */
|
|
80
|
+
export function variablesChanged(drafts: readonly VariableDraft[], env: readonly ProviderAccountVariable[] | undefined): boolean {
|
|
81
|
+
return (
|
|
82
|
+
JSON.stringify(variablesOf(drafts)) !==
|
|
83
|
+
JSON.stringify(
|
|
84
|
+
(env ?? []).map(({ name, value, sensitive, valueRedacted }) => ({ name, value, sensitive, ...(valueRedacted ? { valueRedacted } : {}) }))
|
|
85
|
+
)
|
|
86
|
+
);
|
|
87
|
+
}
|
package/src/scope.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
import type { ChatClient } from './chat/chat-client';
|
|
3
|
+
import type { ChatTransport } from './transport';
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* One host of chats, as everything under it sees it. An app with several hosts renders a scope per
|
|
7
|
+
* host and every row the chat keeps names the scope it came from, so two hosts never paint each
|
|
8
|
+
* other's threads.
|
|
9
|
+
*/
|
|
10
|
+
export interface ChatScope {
|
|
11
|
+
/* Names this host in the stores that keep one row per host: its providers, accounts and usage. */
|
|
12
|
+
readonly id: string;
|
|
13
|
+
/* Where a chat's row is kept in the chats store. The app decides what a key looks like; the chat never builds one. */
|
|
14
|
+
keyOf(chatId: string): string;
|
|
15
|
+
/* Whether a key of the chats store is one of this scope's. */
|
|
16
|
+
owns(key: string): boolean;
|
|
17
|
+
readonly transport: ChatTransport;
|
|
18
|
+
readonly chats: ChatClient;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const ChatScopeContext = createContext<ChatScope | null>(null);
|
|
22
|
+
|
|
23
|
+
/* Throws outside a scope on purpose: a component that reads a chat has to know which host it runs on. */
|
|
24
|
+
export function useChatScope(): ChatScope {
|
|
25
|
+
const scope = useContext(ChatScopeContext);
|
|
26
|
+
if (scope === null) {
|
|
27
|
+
throw new Error('This component reads a chat, so it has to be rendered inside a ChatScopeContext');
|
|
28
|
+
}
|
|
29
|
+
return scope;
|
|
30
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
import i18next from 'i18next';
|
|
3
|
+
import { Brain, ChartNoAxesColumn, type LucideIcon } from 'lucide-react';
|
|
4
|
+
import type { SettingsSectionEntry } from '@adecore/ui/settings';
|
|
5
|
+
|
|
6
|
+
/* A section of this package for the settings dialog of @adecore/ui, with its words read when it is drawn. */
|
|
7
|
+
export interface AgentsSettingsSection {
|
|
8
|
+
id: string;
|
|
9
|
+
icon: LucideIcon;
|
|
10
|
+
/* A list beside a detail that scrolls on its own (`MasterDetail`). */
|
|
11
|
+
split?: boolean;
|
|
12
|
+
label(): string;
|
|
13
|
+
description(): string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* The agent CLIs of the host and the accounts they sign in with: `ProvidersPane`. */
|
|
17
|
+
export const PROVIDERS_SECTION: AgentsSettingsSection = {
|
|
18
|
+
id: 'providers',
|
|
19
|
+
icon: Brain,
|
|
20
|
+
split: true,
|
|
21
|
+
label: () => i18next.t('agent-providers:section.label'),
|
|
22
|
+
description: () => i18next.t('agent-providers:section.description')
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/* The currency of the usage page and the way to it: `UsagePane`. */
|
|
26
|
+
export const USAGE_SECTION: AgentsSettingsSection = {
|
|
27
|
+
id: 'usage',
|
|
28
|
+
icon: ChartNoAxesColumn,
|
|
29
|
+
label: () => i18next.t('agent-usage:section.label'),
|
|
30
|
+
description: () => i18next.t('agent-usage:section.description')
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* The entry the dialog takes for a section, with the pane the app hands it: the pane of this package
|
|
35
|
+
* itself, a lazy one, or a wrapper that gives the pane what only the app knows. Built when the dialog
|
|
36
|
+
* draws, since the words follow the language.
|
|
37
|
+
*/
|
|
38
|
+
export function settingsSection(section: AgentsSettingsSection, pane: ComponentType): SettingsSectionEntry {
|
|
39
|
+
return {
|
|
40
|
+
id: section.id,
|
|
41
|
+
icon: section.icon,
|
|
42
|
+
label: section.label(),
|
|
43
|
+
description: section.description(),
|
|
44
|
+
pane,
|
|
45
|
+
split: section.split
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import type { ChatApprovalItem, ChatBookmark, ChatEvent, ChatHistoryResult, ChatInfo, ChatItem, ChatQuestionItem } from '@adecore/agent-contracts';
|
|
3
|
+
import { useChatScope } from '../scope';
|
|
4
|
+
|
|
5
|
+
export interface ChatState {
|
|
6
|
+
info: ChatInfo;
|
|
7
|
+
items: Record<string, ChatItem>;
|
|
8
|
+
/*
|
|
9
|
+
* The items as the timeline groups them: the same map as `items`, except that a delta growing a
|
|
10
|
+
* reply or a thought that already has text, or the output of a running call, leaves it as it was.
|
|
11
|
+
* The rows are derived from this, so a word arriving touches the row that draws it and not the
|
|
12
|
+
* whole thread; that row reads its text from `items` with `useCurrentItem`.
|
|
13
|
+
*/
|
|
14
|
+
structure: Record<string, ChatItem>;
|
|
15
|
+
order: string[];
|
|
16
|
+
/* Where the part of the thread this client holds begins; absent when it holds the whole thread. */
|
|
17
|
+
history?: ChatHistoryPage;
|
|
18
|
+
/*
|
|
19
|
+
* Requests from before that part that still wait, oldest first. They are in `items` and
|
|
20
|
+
* `structure` but not in `order`, since the thread on screen does not reach back to them yet.
|
|
21
|
+
*/
|
|
22
|
+
waitingBefore?: string[];
|
|
23
|
+
/* The messages marked to come back to, as the host last said; absent until it said anything. */
|
|
24
|
+
bookmarks?: ChatBookmark[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type ChatHistoryPage = ChatHistoryResult['history'];
|
|
28
|
+
|
|
29
|
+
/* The newest part of a thread, as `chat.attach` hands it over with a `historyLimit`. */
|
|
30
|
+
export interface ChatPage {
|
|
31
|
+
history: ChatHistoryPage;
|
|
32
|
+
/* Every request of the chat that still waits, wherever in the thread it is. */
|
|
33
|
+
pending: ChatItem[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Rows keyed with the scope's `keyOf`, so a thread says which host it runs on. */
|
|
37
|
+
export type ChatsById = Record<string, ChatState>;
|
|
38
|
+
|
|
39
|
+
/* What a chat is doing, without its thread. */
|
|
40
|
+
export type ChatStatuses = Record<string, Pick<ChatState, 'info'>>;
|
|
41
|
+
|
|
42
|
+
/* What a chat client writes. It owns one host's link, so it speaks in chat ids alone. */
|
|
43
|
+
export interface ChatSink {
|
|
44
|
+
/* Without a page the items are the whole thread. */
|
|
45
|
+
reset(chatId: string, info: ChatInfo, items: ChatItem[], page?: ChatPage): void;
|
|
46
|
+
/* The page before the one the chat holds, read with `cursor`. */
|
|
47
|
+
prepend(chatId: string, cursor: string, page: ChatHistoryResult): void;
|
|
48
|
+
apply(chatId: string, event: ChatEvent): void;
|
|
49
|
+
/* What a chat is doing, for a thread nobody in this window has open. */
|
|
50
|
+
status(chatId: string, info: ChatInfo): void;
|
|
51
|
+
bookmarks(chatId: string, bookmarks: ChatBookmark[]): void;
|
|
52
|
+
forget(chatId: string): void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface ChatsStore {
|
|
56
|
+
byKey: ChatsById;
|
|
57
|
+
/* Replaced only when a chat's info is, never for a streamed word, so what reads the status alone is not redrawn by one. */
|
|
58
|
+
statusByKey: ChatStatuses;
|
|
59
|
+
reset(key: string, info: ChatInfo, items: ChatItem[], page?: ChatPage): void;
|
|
60
|
+
prepend(key: string, cursor: string, page: ChatHistoryResult): void;
|
|
61
|
+
apply(key: string, event: ChatEvent): void;
|
|
62
|
+
status(key: string, info: ChatInfo): void;
|
|
63
|
+
bookmarks(key: string, bookmarks: ChatBookmark[]): void;
|
|
64
|
+
forget(key: string): void;
|
|
65
|
+
/* Drops the threads whose key matches, such as every thread of one host. They keep running there; this client is done looking at them. */
|
|
66
|
+
forgetWhere(matches: (key: string) => boolean): void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isWaiting(item: ChatItem | undefined): item is ChatApprovalItem | ChatQuestionItem {
|
|
70
|
+
return (item?.kind === 'approval' && item.decision === 'pending') || (item?.kind === 'question' && item.state === 'pending');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function stateOf(info: ChatInfo, items: ChatItem[], page?: ChatPage): ChatState {
|
|
74
|
+
const byId = Object.fromEntries(items.map((item) => [item.id, item]));
|
|
75
|
+
const order = items.map((item) => item.id);
|
|
76
|
+
if (page === undefined || page.history.cursor === null) {
|
|
77
|
+
return { info, items: byId, structure: byId, order };
|
|
78
|
+
}
|
|
79
|
+
const before = page.pending.filter((item) => !(item.id in byId));
|
|
80
|
+
for (const item of before) {
|
|
81
|
+
byId[item.id] = item;
|
|
82
|
+
}
|
|
83
|
+
return { info, items: byId, structure: byId, order, history: page.history, waitingBefore: before.map((item) => item.id) };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* An older host names no place in the thread, and it always hands over the whole of it. */
|
|
87
|
+
function isBeforePage(state: ChatState, historyIndex: number | undefined): boolean {
|
|
88
|
+
return state.history !== undefined && historyIndex !== undefined && historyIndex < state.history.start;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* An item from before the page this client holds matters only as a request that waits, or one that stopped waiting. */
|
|
92
|
+
function withEarlierItem(state: ChatState, item: ChatItem): ChatState {
|
|
93
|
+
const waiting = state.waitingBefore ?? [];
|
|
94
|
+
const held = waiting.includes(item.id);
|
|
95
|
+
if (isWaiting(item)) {
|
|
96
|
+
return { ...withItem(state, item), waitingBefore: held ? waiting : [...waiting, item.id] };
|
|
97
|
+
}
|
|
98
|
+
return held ? { ...withItem(state, item), waitingBefore: waiting.filter((id) => id !== item.id) } : state;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* The page before the one a chat holds, unless the thread was read again since that page was asked for. */
|
|
102
|
+
export function prependPage(state: ChatState, cursor: string, page: ChatHistoryResult): ChatState {
|
|
103
|
+
if (state.history?.cursor !== cursor) {
|
|
104
|
+
return state;
|
|
105
|
+
}
|
|
106
|
+
const held = new Set(state.order);
|
|
107
|
+
const older = page.items.filter((item) => !held.has(item.id));
|
|
108
|
+
const olderIds = new Set(older.map((item) => item.id));
|
|
109
|
+
const items = { ...state.items };
|
|
110
|
+
for (const item of older) {
|
|
111
|
+
items[item.id] = item;
|
|
112
|
+
}
|
|
113
|
+
const order = [...olderIds, ...state.order];
|
|
114
|
+
if (page.history.cursor === null) {
|
|
115
|
+
const { history: _history, waitingBefore: _waitingBefore, ...rest } = state;
|
|
116
|
+
return { ...rest, items, structure: items, order };
|
|
117
|
+
}
|
|
118
|
+
const waitingBefore = (state.waitingBefore ?? []).filter((id) => !olderIds.has(id));
|
|
119
|
+
return { ...state, items, structure: items, order, history: page.history, waitingBefore };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* The requests that still wait, oldest first: those from before the page this client holds, then the thread's own. */
|
|
123
|
+
export function waitingRequestsOf(chat: Pick<ChatState, 'structure' | 'order' | 'waitingBefore'> | undefined): Array<ChatApprovalItem | ChatQuestionItem> {
|
|
124
|
+
return [...(chat?.waitingBefore ?? []), ...(chat?.order ?? [])].map((id) => chat?.structure[id]).filter(isWaiting);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* The state with one item replaced, in the thread and in the structure the rows come from. */
|
|
128
|
+
function withItem(state: ChatState, item: ChatItem): ChatState {
|
|
129
|
+
const items = { ...state.items, [item.id]: item };
|
|
130
|
+
return { ...state, items, structure: items };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Cheap enough, since a status only arrives when the host saw one change, never on a streamed word. */
|
|
134
|
+
function sameInfo(left: ChatInfo, right: ChatInfo): boolean {
|
|
135
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* The statuses with this chat's info in them, the same object when that info is already there. */
|
|
139
|
+
function withStatus(statuses: ChatStatuses, key: string, info: ChatInfo): ChatStatuses {
|
|
140
|
+
return statuses[key]?.info === info ? statuses : { ...statuses, [key]: { info } };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function without<T>(rows: Record<string, T>, matches: (key: string) => boolean): Record<string, T> {
|
|
144
|
+
return Object.fromEntries(Object.entries(rows).filter(([key]) => !matches(key)));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function applyEvent(state: ChatState, event: ChatEvent): ChatState {
|
|
148
|
+
switch (event.type) {
|
|
149
|
+
case 'item': {
|
|
150
|
+
if (isBeforePage(state, event.historyIndex)) {
|
|
151
|
+
return withEarlierItem(state, event.item);
|
|
152
|
+
}
|
|
153
|
+
const known = event.item.id in state.items;
|
|
154
|
+
return { ...withItem(state, event.item), order: known ? state.order : [...state.order, event.item.id] };
|
|
155
|
+
}
|
|
156
|
+
case 'delta': {
|
|
157
|
+
const item = state.items[event.itemId];
|
|
158
|
+
if (item?.kind === 'assistant' || item?.kind === 'thinking') {
|
|
159
|
+
const grown = { ...item, text: item.text + event.text };
|
|
160
|
+
// The first text is structure after all, since an empty reply that is not streaming has no row.
|
|
161
|
+
if (item.text === '') {
|
|
162
|
+
return withItem(state, grown);
|
|
163
|
+
}
|
|
164
|
+
return { ...state, items: { ...state.items, [event.itemId]: grown } };
|
|
165
|
+
}
|
|
166
|
+
if (item?.kind === 'tool' && item.state === 'running') {
|
|
167
|
+
const progress = item.progress ?? { startedAt: null, description: null, output: null };
|
|
168
|
+
const grown = { ...item, progress: { ...progress, output: (progress.output ?? '') + event.text } };
|
|
169
|
+
return { ...state, items: { ...state.items, [event.itemId]: grown } };
|
|
170
|
+
}
|
|
171
|
+
return state;
|
|
172
|
+
}
|
|
173
|
+
case 'info':
|
|
174
|
+
return { ...state, info: event.info };
|
|
175
|
+
case 'reset':
|
|
176
|
+
return stateOf(event.info, event.items);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export const useChats = create<ChatsStore>((set) => ({
|
|
181
|
+
byKey: {},
|
|
182
|
+
statusByKey: {},
|
|
183
|
+
reset(key, info, items, page) {
|
|
184
|
+
set((s) => {
|
|
185
|
+
// The bookmarks are not part of the thread; they come in on their own and outlive a reset of it.
|
|
186
|
+
const bookmarks = s.byKey[key]?.bookmarks;
|
|
187
|
+
const next = stateOf(info, items, page);
|
|
188
|
+
return {
|
|
189
|
+
byKey: { ...s.byKey, [key]: bookmarks === undefined ? next : { ...next, bookmarks } },
|
|
190
|
+
statusByKey: withStatus(s.statusByKey, key, info)
|
|
191
|
+
};
|
|
192
|
+
});
|
|
193
|
+
},
|
|
194
|
+
prepend(key, cursor, page) {
|
|
195
|
+
set((s) => {
|
|
196
|
+
const current = s.byKey[key];
|
|
197
|
+
if (!current) {
|
|
198
|
+
return {};
|
|
199
|
+
}
|
|
200
|
+
const next = prependPage(current, cursor, page);
|
|
201
|
+
return next === current ? {} : { byKey: { ...s.byKey, [key]: next } };
|
|
202
|
+
});
|
|
203
|
+
},
|
|
204
|
+
apply(key, event) {
|
|
205
|
+
set((s) => {
|
|
206
|
+
const current = s.byKey[key];
|
|
207
|
+
if (!current) {
|
|
208
|
+
return {};
|
|
209
|
+
}
|
|
210
|
+
const next = applyEvent(current, event);
|
|
211
|
+
return { byKey: { ...s.byKey, [key]: next }, statusByKey: withStatus(s.statusByKey, key, next.info) };
|
|
212
|
+
});
|
|
213
|
+
},
|
|
214
|
+
/*
|
|
215
|
+
* The status of a chat this window is not reading, which the host sends for every chat it has
|
|
216
|
+
* loaded. The thread stays empty until somebody attaches, since this is what a header, a list and
|
|
217
|
+
* the counters need, and they ask the info and never the items.
|
|
218
|
+
*/
|
|
219
|
+
status(key, info) {
|
|
220
|
+
set((s) => {
|
|
221
|
+
const current = s.byKey[key];
|
|
222
|
+
if (!current) {
|
|
223
|
+
return { byKey: { ...s.byKey, [key]: stateOf(info, []) }, statusByKey: withStatus(s.statusByKey, key, info) };
|
|
224
|
+
}
|
|
225
|
+
// An attached client already had this on `chat.event`; writing it again would redraw the thread.
|
|
226
|
+
if (sameInfo(current.info, info)) {
|
|
227
|
+
return {};
|
|
228
|
+
}
|
|
229
|
+
return { byKey: { ...s.byKey, [key]: { ...current, info } }, statusByKey: withStatus(s.statusByKey, key, info) };
|
|
230
|
+
});
|
|
231
|
+
},
|
|
232
|
+
bookmarks(key, bookmarks) {
|
|
233
|
+
set((s) => {
|
|
234
|
+
const current = s.byKey[key];
|
|
235
|
+
if (!current) {
|
|
236
|
+
return {};
|
|
237
|
+
}
|
|
238
|
+
return { byKey: { ...s.byKey, [key]: { ...current, bookmarks } } };
|
|
239
|
+
});
|
|
240
|
+
},
|
|
241
|
+
forget(key) {
|
|
242
|
+
set((s) => {
|
|
243
|
+
const next = { ...s.byKey };
|
|
244
|
+
delete next[key];
|
|
245
|
+
const statuses = { ...s.statusByKey };
|
|
246
|
+
delete statuses[key];
|
|
247
|
+
return { byKey: next, statusByKey: statuses };
|
|
248
|
+
});
|
|
249
|
+
},
|
|
250
|
+
forgetWhere(matches) {
|
|
251
|
+
set((s) => ({ byKey: without(s.byKey, matches), statusByKey: without(s.statusByKey, matches) }));
|
|
252
|
+
}
|
|
253
|
+
}));
|
|
254
|
+
|
|
255
|
+
/* The sink of one host's chat client: it hands over chat ids, this puts them under the keys of its scope. */
|
|
256
|
+
export function chatSink(keyOf: (chatId: string) => string): ChatSink {
|
|
257
|
+
return {
|
|
258
|
+
reset: (chatId, info, items, page) => useChats.getState().reset(keyOf(chatId), info, items, page),
|
|
259
|
+
prepend: (chatId, cursor, page) => useChats.getState().prepend(keyOf(chatId), cursor, page),
|
|
260
|
+
apply: (chatId, event) => useChats.getState().apply(keyOf(chatId), event),
|
|
261
|
+
status: (chatId, info) => useChats.getState().status(keyOf(chatId), info),
|
|
262
|
+
bookmarks: (chatId, bookmarks) => useChats.getState().bookmarks(keyOf(chatId), bookmarks),
|
|
263
|
+
forget: (chatId) => useChats.getState().forget(keyOf(chatId))
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* One chat's thread in the scope this is rendered in. The selector keeps a render tied to the field it reads. */
|
|
268
|
+
export function useChatRow<T>(chatId: string, select: (row: ChatState | undefined) => T): T {
|
|
269
|
+
const { keyOf } = useChatScope();
|
|
270
|
+
return useChats((s) => select(s.byKey[keyOf(chatId)]));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/* The item as the thread holds it now, for a row derived from the structure, which a delta leaves alone. */
|
|
274
|
+
export function useCurrentItem<T extends ChatItem>(chatId: string, derived: T): T {
|
|
275
|
+
return useChatRow(chatId, (row) => {
|
|
276
|
+
const item = row?.items[derived.id];
|
|
277
|
+
return item?.kind === derived.kind ? (item as T) : derived;
|
|
278
|
+
});
|
|
279
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import type { ProviderAccounts } from '@adecore/agent-contracts';
|
|
3
|
+
import { useChatScope } from '../scope';
|
|
4
|
+
import type { ChatTransport } from '../transport';
|
|
5
|
+
|
|
6
|
+
export interface ProviderAccountsRow {
|
|
7
|
+
/* Null until the host answered, and for a host too old to keep accounts. */
|
|
8
|
+
accounts: ProviderAccounts | null;
|
|
9
|
+
loaded: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* One object for a host that has not answered yet, so a selector gets a stable snapshot. */
|
|
13
|
+
const NONE: ProviderAccountsRow = { accounts: null, loaded: false };
|
|
14
|
+
|
|
15
|
+
interface ProviderAccountsStore {
|
|
16
|
+
byScope: Record<string, ProviderAccountsRow>;
|
|
17
|
+
set(scopeId: string, accounts: ProviderAccounts | null): void;
|
|
18
|
+
forget(scopeId: string): void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* The accounts of every agent CLI on each host, and what each CLI last said about who is signed in there. */
|
|
22
|
+
export const useProviderAccountsStore = create<ProviderAccountsStore>((set, get) => ({
|
|
23
|
+
byScope: {},
|
|
24
|
+
set(scopeId, accounts) {
|
|
25
|
+
set({ byScope: { ...get().byScope, [scopeId]: { accounts, loaded: true } } });
|
|
26
|
+
},
|
|
27
|
+
forget(scopeId) {
|
|
28
|
+
const { [scopeId]: _gone, ...rest } = get().byScope;
|
|
29
|
+
set({ byScope: rest });
|
|
30
|
+
}
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
/* The accounts of the host in scope, like `useProviders`. */
|
|
34
|
+
export function useProviderAccounts<T>(select: (row: ProviderAccountsRow) => T): T {
|
|
35
|
+
const { id } = useChatScope();
|
|
36
|
+
return useProviderAccountsStore((s) => select(s.byScope[id] ?? NONE));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function providerAccountsOf(scopeId: string): ProviderAccountsRow {
|
|
40
|
+
return useProviderAccountsStore.getState().byScope[scopeId] ?? NONE;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* The accounts as `accountFor` takes them: undefined until the host answered, null for one that keeps none. */
|
|
44
|
+
export function knownAccounts(row: ProviderAccountsRow): ProviderAccounts | null | undefined {
|
|
45
|
+
return row.loaded ? row.accounts : undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/*
|
|
49
|
+
* Keeps one host's accounts in the store: the whole list on every fresh link, then every change the
|
|
50
|
+
* host announces. Answers the function that stops it.
|
|
51
|
+
*/
|
|
52
|
+
export function watchProviderAccounts(scopeId: string, transport: ChatTransport): () => void {
|
|
53
|
+
const store = useProviderAccountsStore.getState();
|
|
54
|
+
// A list asked for before a change arrived is older than that change, so it is dropped.
|
|
55
|
+
let changes = 0;
|
|
56
|
+
const load = (): void => {
|
|
57
|
+
const asked = changes;
|
|
58
|
+
transport
|
|
59
|
+
.request('accounts.list', {})
|
|
60
|
+
.then((accounts) => {
|
|
61
|
+
if (changes === asked) {
|
|
62
|
+
store.set(scopeId, accounts);
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
.catch(() => {
|
|
66
|
+
// A host from before accounts never answers; it has only each CLI's own login.
|
|
67
|
+
if (changes === asked && !providerAccountsOf(scopeId).loaded) {
|
|
68
|
+
store.set(scopeId, null);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
const off = [
|
|
73
|
+
transport.on('accounts.changed', (accounts) => {
|
|
74
|
+
changes += 1;
|
|
75
|
+
store.set(scopeId, accounts);
|
|
76
|
+
}),
|
|
77
|
+
transport.subscribeStatus((status) => {
|
|
78
|
+
if (status === 'open') {
|
|
79
|
+
load();
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
];
|
|
83
|
+
if (transport.status === 'open') {
|
|
84
|
+
load();
|
|
85
|
+
}
|
|
86
|
+
return () => off.forEach((stop) => stop());
|
|
87
|
+
}
|