@adecore/agents-react 0.39.0 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/AgentModelLabel.js +2 -1
- package/dist/agents/model-name.js +2 -1
- package/dist/agents/provider-paths.d.ts +2 -1
- package/dist/agents/provider-paths.js +7 -3
- package/dist/chat/account-choice.js +57 -28
- package/dist/chat/bookmarks.js +2 -1
- package/dist/chat/drafts.d.ts +0 -1
- package/dist/chat/drafts.js +9 -28
- package/dist/chat/logic/picker-entries.js +2 -8
- package/dist/chat/logic/resume-compaction.js +2 -5
- package/dist/chat/logic/timeline-copy.js +2 -8
- package/dist/chat/logic/timeline.js +9 -10
- package/dist/chat/logic/workflow.js +2 -4
- package/dist/chat/subagent-conversation.js +2 -1
- package/dist/chat/ui/GoalDock.js +2 -1
- package/dist/chat/ui/LimitState.js +4 -6
- package/dist/chat/ui/composer/use-continuable-turn.js +0 -1
- package/dist/chat/ui/composer/use-notice.js +2 -1
- package/dist/chat/ui/composer/use-prompt-history.js +1 -1
- package/dist/chat/ui/composer/use-prompt-menus.js +216 -253
- package/dist/chat/ui/composer/use-take-back.js +2 -2
- package/dist/chat/ui/diff/ApprovalReview.js +1 -1
- package/dist/chat/ui/highlighter.d.ts +0 -6
- package/dist/chat/ui/highlighter.js +0 -12
- package/dist/chat/ui/icons.d.ts +0 -1
- package/dist/chat/ui/icons.js +0 -3
- package/dist/chat/ui/rows/MessageRows.js +2 -5
- package/dist/chat/ui/rows/row-rhythm.js +2 -5
- package/dist/chat/ui/theme-watch.js +3 -7
- package/dist/prompts/logic/usePromptSession.js +2 -1
- package/dist/providers/AddAccountForm.js +2 -1
- package/dist/providers/CliDetail.js +104 -130
- package/dist/providers/account-actions.js +6 -9
- package/dist/state/chats.d.ts +1 -0
- package/dist/state/chats.js +6 -11
- package/dist/state/provider-accounts.d.ts +1 -1
- package/dist/state/provider-accounts.js +2 -8
- package/dist/transport.d.ts +2 -0
- package/dist/transport.js +14 -0
- package/dist/usage/ChartLogo.d.ts +5 -0
- package/dist/usage/ChartLogo.js +48 -0
- package/dist/usage/LimitsList.js +6 -16
- package/dist/usage/UsageBreakdown.js +17 -40
- package/dist/usage/UsageLimits.js +183 -211
- package/dist/usage/UsagePage.js +3 -17
- package/dist/usage/UsageSummary.js +4 -9
- package/dist/usage/limits.js +16 -31
- package/dist/usage/summary.d.ts +1 -1
- package/dist/usage/summary.js +3 -5
- package/package.json +26 -26
- package/src/agents/AccountDot.tsx +1 -0
- package/src/agents/AgentIcon.tsx +2 -0
- package/src/agents/AgentModelLabel.tsx +4 -6
- package/src/agents/ModelOptionControl.tsx +8 -0
- package/src/agents/account-limits.ts +3 -0
- package/src/agents/accounts.ts +12 -0
- package/src/agents/model-name.ts +13 -1
- package/src/agents/model-options.ts +2 -0
- package/src/agents/model-requirement.ts +4 -0
- package/src/agents/option-words.ts +2 -0
- package/src/agents/provider-paths.ts +8 -3
- package/src/agents/status-look.ts +1 -0
- package/src/chat/account-choice.ts +25 -19
- package/src/chat/actions.ts +5 -0
- package/src/chat/attachments.ts +8 -0
- package/src/chat/bookmarks.ts +13 -1
- package/src/chat/chat-client.ts +63 -0
- package/src/chat/chat-references.ts +3 -0
- package/src/chat/composer-effort.ts +5 -0
- package/src/chat/composer-send.ts +4 -0
- package/src/chat/drafts.ts +21 -17
- package/src/chat/forks.ts +4 -0
- package/src/chat/guards.ts +2 -0
- package/src/chat/index.ts +7 -0
- package/src/chat/logic/ansi.ts +47 -0
- package/src/chat/logic/bookmarks.ts +7 -0
- package/src/chat/logic/command-looks.ts +29 -0
- package/src/chat/logic/context-usage.ts +3 -0
- package/src/chat/logic/continue-after-stop.ts +5 -0
- package/src/chat/logic/effort.ts +4 -0
- package/src/chat/logic/embedded-code.ts +63 -0
- package/src/chat/logic/fork.ts +22 -0
- package/src/chat/logic/generated-image.ts +7 -0
- package/src/chat/logic/goal.ts +7 -0
- package/src/chat/logic/handback.ts +2 -0
- package/src/chat/logic/limit.ts +8 -0
- package/src/chat/logic/picker-entries.ts +16 -4
- package/src/chat/logic/resume-compaction.ts +22 -5
- package/src/chat/logic/scrubber.ts +38 -5
- package/src/chat/logic/thought.ts +5 -0
- package/src/chat/logic/thread-cards.ts +6 -0
- package/src/chat/logic/timeline-copy.ts +4 -9
- package/src/chat/logic/timeline-target.ts +15 -0
- package/src/chat/logic/timeline.ts +113 -10
- package/src/chat/logic/tools.ts +30 -0
- package/src/chat/logic/visual-height.ts +5 -0
- package/src/chat/logic/visual-viewport.ts +1 -0
- package/src/chat/logic/welcome.ts +3 -0
- package/src/chat/logic/workflow.ts +26 -7
- package/src/chat/mentions.ts +56 -0
- package/src/chat/pending-prompts.ts +16 -0
- package/src/chat/persisted-json.ts +15 -0
- package/src/chat/preferences.ts +16 -0
- package/src/chat/prompt-hold.ts +2 -0
- package/src/chat/quote.ts +7 -0
- package/src/chat/reading-width.ts +1 -0
- package/src/chat/recent-messages.ts +5 -0
- package/src/chat/resume-compaction-dismissals.ts +1 -0
- package/src/chat/stash.ts +12 -0
- package/src/chat/subagent-conversation.ts +24 -4
- package/src/chat/subagent-list.ts +14 -0
- package/src/chat/subagent-support.ts +1 -0
- package/src/chat/subagent-view.ts +9 -0
- package/src/chat/thumbnails.ts +14 -0
- package/src/chat/timeline-flash.ts +2 -0
- package/src/chat/timeline-scroll.ts +23 -0
- package/src/chat/ui/AccountPill.tsx +4 -0
- package/src/chat/ui/AnsiOutput.tsx +1 -0
- package/src/chat/ui/BookmarkMarker.tsx +5 -0
- package/src/chat/ui/BookmarkSubmenu.tsx +1 -0
- package/src/chat/ui/ChatActivity.tsx +13 -0
- package/src/chat/ui/CodeBlock.tsx +8 -0
- package/src/chat/ui/Composer.tsx +15 -0
- package/src/chat/ui/ComposerInput.tsx +22 -0
- package/src/chat/ui/DiffPool.tsx +3 -0
- package/src/chat/ui/EditDiff.tsx +5 -0
- package/src/chat/ui/GoalDock.tsx +13 -1
- package/src/chat/ui/ImageView.tsx +11 -0
- package/src/chat/ui/LimitState.tsx +23 -6
- package/src/chat/ui/Markdown.tsx +41 -0
- package/src/chat/ui/MermaidBlock.tsx +16 -0
- package/src/chat/ui/MessageActions.tsx +8 -0
- package/src/chat/ui/ModelMarks.tsx +1 -0
- package/src/chat/ui/Pickers.tsx +26 -0
- package/src/chat/ui/PromptComposer.tsx +2 -0
- package/src/chat/ui/PromptEditor.tsx +6 -0
- package/src/chat/ui/QuoteButton.tsx +12 -0
- package/src/chat/ui/ReferenceChip.tsx +3 -0
- package/src/chat/ui/ResumeCompactionDock.tsx +1 -0
- package/src/chat/ui/RowTime.tsx +1 -0
- package/src/chat/ui/RunSettings.tsx +38 -0
- package/src/chat/ui/Scrubber.tsx +14 -0
- package/src/chat/ui/SessionLine.tsx +1 -0
- package/src/chat/ui/ShellCommand.tsx +32 -0
- package/src/chat/ui/SubagentControls.tsx +7 -0
- package/src/chat/ui/SubagentInfo.tsx +1 -0
- package/src/chat/ui/SubagentStopButton.tsx +6 -0
- package/src/chat/ui/SubagentTimeline.tsx +10 -0
- package/src/chat/ui/Timeline.tsx +119 -0
- package/src/chat/ui/TimelineMenu.tsx +5 -0
- package/src/chat/ui/UnifiedDiff.tsx +6 -0
- package/src/chat/ui/UploadThumb.tsx +2 -0
- package/src/chat/ui/VisualDialogs.tsx +3 -0
- package/src/chat/ui/VisualFrame.tsx +15 -0
- package/src/chat/ui/Welcome.tsx +1 -0
- package/src/chat/ui/chip-clipboard.ts +20 -0
- package/src/chat/ui/code-lines.ts +14 -0
- package/src/chat/ui/code-theme.ts +1 -0
- package/src/chat/ui/composer/ComposerControls.tsx +5 -0
- package/src/chat/ui/composer/ComposerMenu.tsx +12 -0
- package/src/chat/ui/composer/DraftParts.tsx +2 -0
- package/src/chat/ui/composer/QueuedMessages.tsx +4 -0
- package/src/chat/ui/composer/chip-paste.ts +16 -0
- package/src/chat/ui/composer/chips.ts +6 -0
- package/src/chat/ui/composer/editor.ts +9 -0
- package/src/chat/ui/composer/insert.ts +2 -0
- package/src/chat/ui/composer/key-handler.ts +39 -0
- package/src/chat/ui/composer/keys.ts +23 -0
- package/src/chat/ui/composer/placeholder.ts +6 -0
- package/src/chat/ui/composer/prompt-picks.ts +1 -0
- package/src/chat/ui/composer/surface.ts +5 -0
- package/src/chat/ui/composer/use-chat-controls.ts +13 -0
- package/src/chat/ui/composer/use-compaction-offer.ts +3 -0
- package/src/chat/ui/composer/use-composer-menus.ts +2 -0
- package/src/chat/ui/composer/use-composer-submit.ts +27 -0
- package/src/chat/ui/composer/use-continuable-turn.ts +3 -5
- package/src/chat/ui/composer/use-draft-files.ts +24 -0
- package/src/chat/ui/composer/use-effort-stepper.ts +8 -0
- package/src/chat/ui/composer/use-notice.ts +4 -1
- package/src/chat/ui/composer/use-pending-requests.ts +3 -0
- package/src/chat/ui/composer/use-prompt-history.ts +20 -11
- package/src/chat/ui/composer/use-prompt-hold.ts +3 -0
- package/src/chat/ui/composer/use-prompt-menus.ts +34 -14
- package/src/chat/ui/composer/use-take-back.ts +20 -2
- package/src/chat/ui/diff/ApprovalReview.tsx +18 -15
- package/src/chat/ui/diff/DiffFallbackBoundary.tsx +1 -0
- package/src/chat/ui/diff/DiffOmitted.tsx +1 -0
- package/src/chat/ui/diff/DiffStack.tsx +2 -0
- package/src/chat/ui/diff/DiffUnavailable.tsx +1 -0
- package/src/chat/ui/diff/EditorEditDiff.tsx +13 -0
- package/src/chat/ui/diff/EditorPatchDiff.tsx +4 -0
- package/src/chat/ui/diff/LegacyDiffPool.tsx +2 -0
- package/src/chat/ui/diff/LegacyEditDiff.tsx +2 -0
- package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +8 -0
- package/src/chat/ui/diff/RawDiff.tsx +1 -0
- package/src/chat/ui/diff/ReviewDiff.tsx +1 -0
- package/src/chat/ui/diff/diff-source-budget.ts +8 -0
- package/src/chat/ui/diff/editor-edit-model.ts +4 -0
- package/src/chat/ui/diff/review-plan.ts +9 -0
- package/src/chat/ui/diff/review-settle.ts +1 -0
- package/src/chat/ui/diff/syntax-runtime.ts +52 -0
- package/src/chat/ui/diff/use-review-stack.ts +9 -0
- package/src/chat/ui/diff-syntax.ts +12 -0
- package/src/chat/ui/diff-theme.ts +2 -0
- package/src/chat/ui/file-links.ts +2 -0
- package/src/chat/ui/find-reveal.ts +1 -0
- package/src/chat/ui/full-diff.ts +1 -0
- package/src/chat/ui/highlighter.ts +29 -15
- package/src/chat/ui/icons.tsx +2 -4
- package/src/chat/ui/index.ts +4 -0
- package/src/chat/ui/intelligent-ui/UiBlockFrame.tsx +25 -0
- package/src/chat/ui/intelligent-ui/UiFallbackPart.tsx +1 -0
- package/src/chat/ui/intelligent-ui/UiReply.tsx +63 -0
- package/src/chat/ui/intelligent-ui/block-local.ts +5 -0
- package/src/chat/ui/intelligent-ui/chart-data.ts +15 -0
- package/src/chat/ui/intelligent-ui/node-text.ts +17 -0
- package/src/chat/ui/intelligent-ui/renderers/binding.ts +1 -0
- package/src/chat/ui/intelligent-ui/renderers/chart.tsx +31 -0
- package/src/chat/ui/intelligent-ui/renderers/choices.tsx +4 -0
- package/src/chat/ui/intelligent-ui/renderers/content.tsx +18 -0
- package/src/chat/ui/intelligent-ui/renderers/data.tsx +11 -0
- package/src/chat/ui/intelligent-ui/renderers/inputs.tsx +13 -0
- package/src/chat/ui/intelligent-ui/renderers/links.tsx +9 -0
- package/src/chat/ui/intelligent-ui/renderers/map-slots.ts +15 -0
- package/src/chat/ui/intelligent-ui/renderers/map-style.ts +15 -0
- package/src/chat/ui/intelligent-ui/renderers/map-view.tsx +48 -0
- package/src/chat/ui/intelligent-ui/renderers/map.tsx +20 -0
- package/src/chat/ui/intelligent-ui/renderers/place-point.ts +3 -0
- package/src/chat/ui/intelligent-ui/renderers/structure.tsx +3 -0
- package/src/chat/ui/intelligent-ui/renderers/text.tsx +8 -0
- package/src/chat/ui/intelligent-ui/reply-parts.ts +5 -0
- package/src/chat/ui/intelligent-ui/table-data.ts +17 -0
- package/src/chat/ui/intelligent-ui/use-in-view.ts +4 -0
- package/src/chat/ui/intelligent-ui/use-later.ts +4 -0
- package/src/chat/ui/intelligent-ui/use-truncated.ts +5 -0
- package/src/chat/ui/intelligent-ui/use-ui-links.ts +29 -0
- package/src/chat/ui/intelligent-ui/use-ui-queries.ts +60 -0
- package/src/chat/ui/markdown-blocks.ts +64 -0
- package/src/chat/ui/mermaid.ts +4 -0
- package/src/chat/ui/quote-selection.ts +10 -0
- package/src/chat/ui/rehype-chips.ts +5 -0
- package/src/chat/ui/rehype-fade.ts +2 -0
- package/src/chat/ui/remark-html-as-text.ts +5 -0
- package/src/chat/ui/reveal.ts +18 -0
- package/src/chat/ui/rows/ForksRow.tsx +5 -0
- package/src/chat/ui/rows/GeneratedImageRow.tsx +10 -0
- package/src/chat/ui/rows/MessageRows.tsx +29 -5
- package/src/chat/ui/rows/RowContainer.tsx +1 -0
- package/src/chat/ui/rows/Rows.tsx +2 -0
- package/src/chat/ui/rows/SubagentRow.tsx +13 -0
- package/src/chat/ui/rows/TaskRow.tsx +7 -0
- package/src/chat/ui/rows/VisualRow.tsx +4 -0
- package/src/chat/ui/rows/WorkRows.tsx +46 -0
- package/src/chat/ui/rows/WorkflowRow.tsx +10 -0
- package/src/chat/ui/rows/row-rhythm.ts +7 -5
- package/src/chat/ui/theme-watch.ts +8 -7
- package/src/chat/ui/thread-references.ts +7 -0
- package/src/chat/ui/use-diff-source-tokens.ts +8 -0
- package/src/chat/ui/useToggleSet.ts +3 -0
- package/src/chat/ui/visual-theme.ts +30 -0
- package/src/chat/visual-bridge.ts +23 -0
- package/src/chat/visual-viewport.ts +12 -0
- package/src/host-hooks.ts +6 -4
- package/src/host.ts +2 -0
- package/src/index.ts +3 -0
- package/src/lazy.tsx +9 -0
- package/src/mounted-registry.ts +4 -0
- package/src/port-transport.ts +32 -0
- package/src/prompts/index.ts +2 -0
- package/src/prompts/logic/focus.ts +5 -0
- package/src/prompts/logic/keys.ts +12 -0
- package/src/prompts/logic/prompts.ts +8 -0
- package/src/prompts/logic/subjects.ts +8 -0
- package/src/prompts/logic/useFocusAfterAnswer.ts +2 -0
- package/src/prompts/logic/usePromptSession.ts +13 -1
- package/src/prompts/ui/ApprovalBody.tsx +2 -0
- package/src/prompts/ui/PromptActions.tsx +2 -0
- package/src/prompts/ui/PromptCard.tsx +6 -0
- package/src/prompts/ui/PromptView.tsx +6 -0
- package/src/prompts/ui/QuestionBody.tsx +11 -0
- package/src/providers/AccountColors.tsx +1 -0
- package/src/providers/AccountDetail.tsx +7 -0
- package/src/providers/AccountVariables.tsx +2 -0
- package/src/providers/AddAccountForm.tsx +5 -1
- package/src/providers/CliDetail.tsx +6 -5
- package/src/providers/InstallPage.tsx +11 -0
- package/src/providers/ProviderList.tsx +3 -0
- package/src/providers/ProvidersPane.tsx +18 -0
- package/src/providers/account-actions.ts +10 -8
- package/src/providers/install-steps.ts +3 -0
- package/src/providers/pane-target.ts +5 -0
- package/src/providers/provider-abilities.ts +1 -0
- package/src/providers/provider-groups.ts +6 -0
- package/src/providers/variables.ts +12 -0
- package/src/scope.ts +2 -0
- package/src/state/chats.ts +61 -14
- package/src/state/index.ts +2 -0
- package/src/state/provider-accounts.ts +7 -9
- package/src/state/providers.ts +1 -0
- package/src/state/usage.ts +6 -0
- package/src/storage.ts +18 -0
- package/src/testing/prompts.ts +5 -0
- package/src/transport.ts +18 -0
- package/src/usage/ChartLogo.tsx +12 -0
- package/src/usage/LimitsList.tsx +7 -8
- package/src/usage/UsageBreakdown.tsx +11 -4
- package/src/usage/UsageChart.tsx +5 -0
- package/src/usage/UsageLimits.tsx +10 -7
- package/src/usage/UsageLimitsCard.tsx +2 -0
- package/src/usage/UsagePage.tsx +16 -19
- package/src/usage/UsageSummary.tsx +4 -5
- package/src/usage/UsageTiles.tsx +2 -0
- package/src/usage/format.ts +7 -0
- package/src/usage/limit-groups.ts +14 -0
- package/src/usage/limits.ts +11 -13
- package/src/usage/money.ts +1 -0
- package/src/usage/summary.ts +15 -7
|
@@ -6,8 +6,8 @@ import type { UsageModel, UsageProject, UsageProvider, UsageSummaryResult } from
|
|
|
6
6
|
import { EmptyState, Icon, SectionLabel, Segmented, Tooltip } from '@adecore/ui';
|
|
7
7
|
import { useChatScope } from '../scope';
|
|
8
8
|
import type { UsageMetric } from '../state/usage';
|
|
9
|
-
import { ProviderLogo } from '../agents/ProviderLogo';
|
|
10
9
|
import { USAGE_PROVIDERS, totalTokensOf } from '@adecore/agent-contracts';
|
|
10
|
+
import { ChartLogo } from './ChartLogo';
|
|
11
11
|
import { useModelName } from '../agents/model-name';
|
|
12
12
|
import { formatCount, formatTokens, PROVIDER_COLORS, PROVIDER_LABELS, shortPath, slotLabel } from './format';
|
|
13
13
|
import { useMoney } from './money';
|
|
@@ -33,9 +33,7 @@ function barWidth(value: number, top: number): number {
|
|
|
33
33
|
function ProviderMark({ provider }: { provider: UsageProvider }) {
|
|
34
34
|
return (
|
|
35
35
|
<Tooltip label={PROVIDER_LABELS[provider]}>
|
|
36
|
-
<
|
|
37
|
-
<ProviderLogo provider={provider} />
|
|
38
|
-
</span>
|
|
36
|
+
<ChartLogo provider={provider} />
|
|
39
37
|
</Tooltip>
|
|
40
38
|
);
|
|
41
39
|
}
|
|
@@ -47,16 +45,19 @@ function ModelName({ provider, slug }: { provider: UsageProvider; slug: string }
|
|
|
47
45
|
|
|
48
46
|
function PricedAs({ provider, slug }: { provider: UsageProvider; slug: string }) {
|
|
49
47
|
const { t } = useTranslation('agent-usage');
|
|
48
|
+
|
|
50
49
|
return t('breakdown.models.pricedAs', { model: useModelName(provider, slug) });
|
|
51
50
|
}
|
|
52
51
|
|
|
53
52
|
function ModelRows({ models, metric, total }: { models: readonly UsageModel[]; metric: UsageMetric; total: number }) {
|
|
54
53
|
const { t } = useTranslation('agent-usage');
|
|
55
54
|
const money = useMoney();
|
|
55
|
+
|
|
56
56
|
/* On the tokens metric the biggest row is the one that moved the most, not the dearest. */
|
|
57
57
|
const sorted = [...models].sort((a, b) =>
|
|
58
58
|
metric === 'tokens' ? totalTokensOf(b.totals) - totalTokensOf(a.totals) : (b.costUsd ?? -1) - (a.costUsd ?? -1)
|
|
59
59
|
);
|
|
60
|
+
|
|
60
61
|
return (
|
|
61
62
|
<div className={TABLE}>
|
|
62
63
|
<div className={HEAD}>
|
|
@@ -111,6 +112,7 @@ function ModelRows({ models, metric, total }: { models: readonly UsageModel[]; m
|
|
|
111
112
|
function ProjectRow({ project, top, money }: { project: UsageProject; top: number; money(usd: number): string }) {
|
|
112
113
|
// The usage on screen is one host's, so the look of a project is that host's too.
|
|
113
114
|
const look = useHostProjectLook(useChatScope().id, project.projectId);
|
|
115
|
+
|
|
114
116
|
return (
|
|
115
117
|
/* The mark sits on the title rather than between the two lines, so the icons line up with the names. */
|
|
116
118
|
<div className={`${ROW} h-auto items-start py-1.5`}>
|
|
@@ -129,6 +131,7 @@ function ProjectRow({ project, top, money }: { project: UsageProject; top: numbe
|
|
|
129
131
|
{USAGE_PROVIDERS.map((provider) => {
|
|
130
132
|
const share = project.byProvider[provider];
|
|
131
133
|
const width = barWidth(share?.costUsd ?? 0, top);
|
|
134
|
+
|
|
132
135
|
return width === 0 ? null : <span key={provider} style={{ width: `${width}%`, background: PROVIDER_COLORS[provider] }} />;
|
|
133
136
|
})}
|
|
134
137
|
</span>
|
|
@@ -141,6 +144,7 @@ function ProjectRow({ project, top, money }: { project: UsageProject; top: numbe
|
|
|
141
144
|
function ProjectRows({ projects }: { projects: readonly UsageProject[] }) {
|
|
142
145
|
const money = useMoney();
|
|
143
146
|
const top = Math.max(...projects.map((project) => project.costUsd), 0);
|
|
147
|
+
|
|
144
148
|
return (
|
|
145
149
|
<div className={clsx(TABLE, 'gap-1')}>
|
|
146
150
|
{projects.map((project) => (
|
|
@@ -155,9 +159,11 @@ function DayRows({ summary, providers }: { summary: UsageSummaryResult; provider
|
|
|
155
159
|
const { t } = useTranslation('agent-usage');
|
|
156
160
|
const money = useMoney();
|
|
157
161
|
const rows = deriveDays(summary);
|
|
162
|
+
|
|
158
163
|
if (rows.length === 0) {
|
|
159
164
|
return <EmptyState>{t('breakdown.days.empty')}</EmptyState>;
|
|
160
165
|
}
|
|
166
|
+
|
|
161
167
|
return (
|
|
162
168
|
<div className={TABLE}>
|
|
163
169
|
<div className={HEAD}>
|
|
@@ -198,6 +204,7 @@ export function UsageBreakdown({ summary, metric, providers }: UsageBreakdownPro
|
|
|
198
204
|
const { t } = useTranslation('agent-usage');
|
|
199
205
|
const [tab, setTab] = useState<Breakdown>('models');
|
|
200
206
|
const total = summary.models.reduce((sum, model) => sum + (model.costUsd ?? 0), 0);
|
|
207
|
+
|
|
201
208
|
return (
|
|
202
209
|
<section className="flex flex-col gap-3">
|
|
203
210
|
<div className="flex items-center gap-3">
|
package/src/usage/UsageChart.tsx
CHANGED
|
@@ -48,20 +48,25 @@ export function UsageChart({ slots, providers, format, labelEvery }: UsageChartP
|
|
|
48
48
|
<svg width={width} height={HEIGHT} role="presentation">
|
|
49
49
|
{ticks.map((tick) => {
|
|
50
50
|
const y = Math.round(HEIGHT - (tick / scale.max) * HEIGHT) + 0.5;
|
|
51
|
+
|
|
51
52
|
return <line key={tick} x1={0} x2={width} y1={y} y2={y} stroke="var(--border)" strokeWidth={1} />;
|
|
52
53
|
})}
|
|
53
54
|
{slots.map((slot, index) => {
|
|
54
55
|
const left = Math.round(index * band + (band - barWidth) / 2);
|
|
55
56
|
let bottom = HEIGHT;
|
|
57
|
+
|
|
56
58
|
return (
|
|
57
59
|
<g key={slot.slot} opacity={hovered === null || hovered === index ? 1 : DIMMED}>
|
|
58
60
|
{providers.map((provider) => {
|
|
59
61
|
const value = slot.byProvider[provider] ?? 0;
|
|
62
|
+
|
|
60
63
|
if (value <= 0) {
|
|
61
64
|
return null;
|
|
62
65
|
}
|
|
66
|
+
|
|
63
67
|
const height = Math.max(1, Math.round((value / scale.max) * HEIGHT));
|
|
64
68
|
bottom -= height;
|
|
69
|
+
|
|
65
70
|
return <rect key={provider} x={left} y={bottom} width={barWidth} height={height} fill={PROVIDER_COLORS[provider]} />;
|
|
66
71
|
})}
|
|
67
72
|
</g>
|
|
@@ -5,13 +5,13 @@ import type { UsageProvider } from '@adecore/agent-contracts';
|
|
|
5
5
|
import { AccountDot } from '../agents/AccountDot';
|
|
6
6
|
import { Button, Icon, SectionLabel, Segmented, Tooltip, useNow } from '@adecore/ui';
|
|
7
7
|
import { useChatHost } from '../host';
|
|
8
|
-
import {
|
|
8
|
+
import { ChartLogo } from './ChartLogo';
|
|
9
|
+
import { PROVIDER_LABELS } from './format';
|
|
9
10
|
import { accountNote, checkedLabel, hasSeveralAccounts, isSignedOut, type LimitAccount } from './limit-groups';
|
|
10
11
|
import { WindowBar } from './LimitsList';
|
|
11
12
|
import { useLimitGroups, useUsageLimits } from './limits';
|
|
12
|
-
import {
|
|
13
|
+
import { useProviderAccounts } from '../state/provider-accounts';
|
|
13
14
|
import { useChatScope } from '../scope';
|
|
14
|
-
import { ProviderLogo } from '../agents/ProviderLogo';
|
|
15
15
|
import { useHostLoginBlocked } from '../host-hooks';
|
|
16
16
|
|
|
17
17
|
const MINUTE_MS = 60_000;
|
|
@@ -26,6 +26,7 @@ interface Login {
|
|
|
26
26
|
|
|
27
27
|
function LoginButton({ account, login }: { account: LimitAccount; login: Login }) {
|
|
28
28
|
const { t } = useTranslation('agent-usage');
|
|
29
|
+
|
|
29
30
|
const button = (
|
|
30
31
|
<Button
|
|
31
32
|
variant="secondary"
|
|
@@ -38,6 +39,7 @@ function LoginButton({ account, login }: { account: LimitAccount; login: Login }
|
|
|
38
39
|
{t('limits.logIn')}
|
|
39
40
|
</Button>
|
|
40
41
|
);
|
|
42
|
+
|
|
41
43
|
return login.blocked === null ? (
|
|
42
44
|
button
|
|
43
45
|
) : (
|
|
@@ -54,12 +56,11 @@ function LimitCard({ account, named, now, login }: { account: LimitAccount; name
|
|
|
54
56
|
const note = signedOut ? null : accountNote(account);
|
|
55
57
|
const plan = signedOut ? null : account.entry?.plan;
|
|
56
58
|
const checked = signedOut || account.entry === null ? null : checkedLabel(account.entry, now);
|
|
59
|
+
|
|
57
60
|
return (
|
|
58
61
|
<div className="flex min-w-0 flex-col gap-3.5 rounded-xl border border-border bg-surface bg-clip-padding px-4 py-3.5">
|
|
59
62
|
<p className="flex min-w-0 items-center gap-2 text-xs font-medium">
|
|
60
|
-
<
|
|
61
|
-
<ProviderLogo provider={account.kind} />
|
|
62
|
-
</span>
|
|
63
|
+
<ChartLogo provider={account.kind} className="shrink-0" />
|
|
63
64
|
<span className="shrink-0">{PROVIDER_LABELS[account.kind]}</span>
|
|
64
65
|
{named && (
|
|
65
66
|
<>
|
|
@@ -100,7 +101,7 @@ export function UsageLimits() {
|
|
|
100
101
|
const groups = useLimitGroups(limits);
|
|
101
102
|
const now = useNow(MINUTE_MS);
|
|
102
103
|
const scopeId = useChatScope().id;
|
|
103
|
-
const loginCommands =
|
|
104
|
+
const loginCommands = useProviderAccounts((row) => row.accounts?.loginCommands);
|
|
104
105
|
const { openLogin } = host;
|
|
105
106
|
const blocked = useHostLoginBlocked(scopeId);
|
|
106
107
|
const [filter, setFilter] = useState<Filter>('all');
|
|
@@ -108,9 +109,11 @@ export function UsageLimits() {
|
|
|
108
109
|
if (limits === null) {
|
|
109
110
|
return null;
|
|
110
111
|
}
|
|
112
|
+
|
|
111
113
|
const filterable = groups.length > 1 && hasSeveralAccounts(groups);
|
|
112
114
|
const current = filterable && groups.some((group) => group.kind === filter) ? filter : 'all';
|
|
113
115
|
const shown = current === 'all' ? groups : groups.filter((group) => group.kind === current);
|
|
116
|
+
|
|
114
117
|
const loginFor = (kind: UsageProvider): Login | null =>
|
|
115
118
|
openLogin === null || loginCommands?.[kind] === undefined
|
|
116
119
|
? null
|
|
@@ -16,6 +16,7 @@ function CardBody() {
|
|
|
16
16
|
if (limits === null) {
|
|
17
17
|
return <p className="text-xs text-text-faint">{i18next.t('agent-usage:limits.loading')}</p>;
|
|
18
18
|
}
|
|
19
|
+
|
|
19
20
|
return hasSeveralAccounts(groups) ? <AccountLimitsList groups={groups} now={now} /> : <LimitsList limits={limits} now={now} compact />;
|
|
20
21
|
}
|
|
21
22
|
|
|
@@ -25,6 +26,7 @@ function CardBody() {
|
|
|
25
26
|
*/
|
|
26
27
|
export function UsageLimitsCard({ children }: { children: ReactElement<Record<string, unknown>> }) {
|
|
27
28
|
const [opened, setOpened] = useState(false);
|
|
29
|
+
|
|
28
30
|
return (
|
|
29
31
|
<PreviewCard.Root onOpenChange={(open) => open && setOpened(true)}>
|
|
30
32
|
<PreviewCard.Trigger render={children} delay={500} />
|
package/src/usage/UsagePage.tsx
CHANGED
|
@@ -8,7 +8,7 @@ import { useChatHost } from '../host';
|
|
|
8
8
|
import { useChatScope } from '../scope';
|
|
9
9
|
import { useProvidersStore } from '../state/providers';
|
|
10
10
|
import { askedKey, summaryPayload, USAGE_PERIODS, useUsage, useUsageStore, type UsageMetric, type UsagePeriod } from '../state/usage';
|
|
11
|
-
import type
|
|
11
|
+
import { whenOpen, type ChatTransport } from '../transport';
|
|
12
12
|
import { formatClock, formatCount, formatDate, formatTokens, PROVIDER_LABELS } from './format';
|
|
13
13
|
import { useMoney } from './money';
|
|
14
14
|
import { deriveUsage, labelEveryFor } from './summary';
|
|
@@ -44,9 +44,11 @@ function HostAgents() {
|
|
|
44
44
|
const { id } = useChatScope();
|
|
45
45
|
const row = useProvidersStore((s) => s.byScope[id]);
|
|
46
46
|
const installed = (row?.providers ?? []).filter((provider) => provider.installed);
|
|
47
|
+
|
|
47
48
|
if (!row?.loaded) {
|
|
48
49
|
return null;
|
|
49
50
|
}
|
|
51
|
+
|
|
50
52
|
return (
|
|
51
53
|
<div className="flex flex-col items-center gap-2">
|
|
52
54
|
<p className="text-xs text-text-muted">
|
|
@@ -66,16 +68,9 @@ function HostAgents() {
|
|
|
66
68
|
*/
|
|
67
69
|
function useSummary(scopeId: string, transport: ChatTransport, period: UsagePeriod, account: string | null): () => void {
|
|
68
70
|
useEffect(() => {
|
|
69
|
-
const subscribe = (): void => void transport.request('usage.subscribe', {}).catch(() => undefined);
|
|
70
|
-
if (transport.status === 'open') {
|
|
71
|
-
subscribe();
|
|
72
|
-
}
|
|
73
71
|
// A host knows its followers per link, so every connection has to be told again, on a reconnect and on a move to another host.
|
|
74
|
-
const off = transport.
|
|
75
|
-
|
|
76
|
-
subscribe();
|
|
77
|
-
}
|
|
78
|
-
});
|
|
72
|
+
const off = whenOpen(transport, () => void transport.request('usage.subscribe', {}).catch(() => undefined));
|
|
73
|
+
|
|
79
74
|
return () => {
|
|
80
75
|
off();
|
|
81
76
|
void transport.request('usage.unsubscribe', {}).catch(() => undefined);
|
|
@@ -86,6 +81,7 @@ function useSummary(scopeId: string, transport: ChatTransport, period: UsagePeri
|
|
|
86
81
|
const payload = summaryPayload(period, account);
|
|
87
82
|
const asked = askedKey(payload);
|
|
88
83
|
useUsageStore.getState().setLoading(scopeId, true);
|
|
84
|
+
|
|
89
85
|
transport
|
|
90
86
|
.request('usage.summary', payload)
|
|
91
87
|
.then((summary) => useUsageStore.getState().receive(scopeId, asked, summary))
|
|
@@ -93,17 +89,11 @@ function useSummary(scopeId: string, transport: ChatTransport, period: UsagePeri
|
|
|
93
89
|
}, [transport, scopeId, period, account]);
|
|
94
90
|
|
|
95
91
|
useEffect(() => {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
92
|
+
// Until the other host answers there is nothing to ask, and what is on screen is the host that left.
|
|
93
|
+
const offStatus = whenOpen(transport, load);
|
|
99
94
|
// A scan that found something is the sign to ask again; nothing else changes the numbers.
|
|
100
95
|
const offChanged = transport.on('usage.changed', load);
|
|
101
|
-
|
|
102
|
-
const offStatus = transport.subscribeStatus((status) => {
|
|
103
|
-
if (status === 'open') {
|
|
104
|
-
load();
|
|
105
|
-
}
|
|
106
|
-
});
|
|
96
|
+
|
|
107
97
|
return () => {
|
|
108
98
|
offChanged();
|
|
109
99
|
offStatus();
|
|
@@ -118,16 +108,21 @@ function Provenance() {
|
|
|
118
108
|
const summary = useUsage((s) => s.summary);
|
|
119
109
|
const currency = useUsage((s) => s.currency);
|
|
120
110
|
const failed = useUsage((s) => s.failed);
|
|
111
|
+
|
|
121
112
|
if (failed) {
|
|
122
113
|
return <FormError className="mt-auto text-center">{t('provenance.failed')}</FormError>;
|
|
123
114
|
}
|
|
115
|
+
|
|
124
116
|
if (summary === null) {
|
|
125
117
|
return null;
|
|
126
118
|
}
|
|
119
|
+
|
|
127
120
|
const prices =
|
|
128
121
|
summary.pricing.fetchedAt === null ? t('provenance.bundledPrices') : t('provenance.fetchedPrices', { date: formatDate(summary.pricing.fetchedAt) });
|
|
122
|
+
|
|
129
123
|
// The rate is named only when it is being used, so a page in dollars says nothing about euros.
|
|
130
124
|
const rate = currency === 'USD' || summary.rate === null ? null : t('provenance.rate', { currency: summary.rate.currency, date: summary.rate.date });
|
|
125
|
+
|
|
131
126
|
return (
|
|
132
127
|
<p className="mt-auto text-center text-xs text-text-muted">
|
|
133
128
|
{t('provenance.scanned', { count: summary.scan.files, time: formatClock(summary.scan.at), files: formatCount(summary.scan.files) })} · {prices}
|
|
@@ -162,9 +157,11 @@ function LoadingBody() {
|
|
|
162
157
|
*/
|
|
163
158
|
function AccountPicker({ accounts, value, onChange }: { accounts: readonly UsageAccount[]; value: string | null; onChange(account: string | null): void }) {
|
|
164
159
|
const { t } = useTranslation('agent-usage');
|
|
160
|
+
|
|
165
161
|
if (!USAGE_PROVIDERS.some((kind) => accounts.filter((account) => account.kind === kind).length > 1)) {
|
|
166
162
|
return null;
|
|
167
163
|
}
|
|
164
|
+
|
|
168
165
|
return (
|
|
169
166
|
<Select
|
|
170
167
|
value={value ?? ALL_ACCOUNTS}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { useTranslation } from 'react-i18next';
|
|
2
2
|
import type { UsageTotals } from '@adecore/agent-contracts';
|
|
3
3
|
import type { UsageMetric } from '../state/usage';
|
|
4
|
-
import { ProviderLogo } from '../agents/ProviderLogo';
|
|
5
4
|
import { totalTokensOf } from '@adecore/agent-contracts';
|
|
6
|
-
import {
|
|
5
|
+
import { ChartLogo } from './ChartLogo';
|
|
6
|
+
import { formatCount, formatTokens, PROVIDER_LABELS } from './format';
|
|
7
7
|
import { useMoney } from './money';
|
|
8
8
|
import type { ProviderTotal } from './summary';
|
|
9
9
|
|
|
@@ -22,6 +22,7 @@ interface UsageSummaryProps {
|
|
|
22
22
|
export function UsageSummary({ metric, costUsd, totals, sessions, providers }: UsageSummaryProps) {
|
|
23
23
|
const { t } = useTranslation('agent-usage');
|
|
24
24
|
const money = useMoney();
|
|
25
|
+
|
|
25
26
|
return (
|
|
26
27
|
<div className="flex flex-col gap-4">
|
|
27
28
|
<div>
|
|
@@ -35,9 +36,7 @@ export function UsageSummary({ metric, costUsd, totals, sessions, providers }: U
|
|
|
35
36
|
<div className="flex flex-col gap-2">
|
|
36
37
|
{providers.map((provider) => (
|
|
37
38
|
<div key={provider.provider} className="flex items-center gap-2 text-xs">
|
|
38
|
-
<
|
|
39
|
-
<ProviderLogo provider={provider.provider} />
|
|
40
|
-
</span>
|
|
39
|
+
<ChartLogo provider={provider.provider} />
|
|
41
40
|
<span className="text-text">{PROVIDER_LABELS[provider.provider]}</span>
|
|
42
41
|
<span className="ml-auto tabular-nums text-text-muted">
|
|
43
42
|
{money(provider.costUsd)} · {formatTokens(totalTokensOf(provider.totals))}
|
package/src/usage/UsageTiles.tsx
CHANGED
|
@@ -13,6 +13,7 @@ interface UsageTilesProps {
|
|
|
13
13
|
export function UsageTiles({ totals, cacheSavingsUsd }: UsageTilesProps) {
|
|
14
14
|
const { t } = useTranslation('agent-usage');
|
|
15
15
|
const money = useMoney();
|
|
16
|
+
|
|
16
17
|
const tiles: { id: string; value: string }[] = [
|
|
17
18
|
{ id: 'processed', value: formatTokens(totalTokensOf(totals)) },
|
|
18
19
|
{ id: 'uncachedInput', value: formatTokens(totals.input) },
|
|
@@ -21,6 +22,7 @@ export function UsageTiles({ totals, cacheSavingsUsd }: UsageTilesProps) {
|
|
|
21
22
|
{ id: 'output', value: formatTokens(totals.output) },
|
|
22
23
|
{ id: 'cacheSavings', value: money(cacheSavingsUsd) }
|
|
23
24
|
];
|
|
25
|
+
|
|
24
26
|
return (
|
|
25
27
|
<div className="grid grid-cols-2 gap-2 md:grid-cols-6">
|
|
26
28
|
{tiles.map((tile) => (
|
package/src/usage/format.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { UsageProvider, UsageRate } from '@adecore/agent-contracts';
|
|
2
2
|
import { formatDay, formatHour, formatWeekdayDay, formatMoney, formatNumber, formatTokens } from '@adecore/ui/format';
|
|
3
|
+
|
|
3
4
|
export { formatClock } from '@adecore/ui/format';
|
|
4
5
|
|
|
5
6
|
/* Dollars are what a price table is in; euros are what the page can be set to. */
|
|
@@ -21,8 +22,10 @@ export function moneyFormat(currency: UsageCurrency, rate: UsageRate | null): (u
|
|
|
21
22
|
const converts = currency !== 'USD' && rate !== null && rate.currency === currency;
|
|
22
23
|
const factor = converts ? rate.rate : 1;
|
|
23
24
|
const code = converts ? currency : 'USD';
|
|
25
|
+
|
|
24
26
|
return (usd) => {
|
|
25
27
|
const value = usd * factor;
|
|
28
|
+
|
|
26
29
|
// Under a cent the two decimals of a currency read as zero, which is the one thing it is not.
|
|
27
30
|
return formatMoney(value, code, value !== 0 && Math.abs(value) < 0.01 ? 4 : undefined);
|
|
28
31
|
};
|
|
@@ -61,16 +64,19 @@ function hourOfSlot(slot: string): number | null {
|
|
|
61
64
|
function atHour(slot: string, hour: number): Date {
|
|
62
65
|
const day = dateOfSlot(slot);
|
|
63
66
|
day.setHours(hour);
|
|
67
|
+
|
|
64
68
|
return day;
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
export function slotLabel(slot: string): string {
|
|
68
72
|
const hour = hourOfSlot(slot);
|
|
73
|
+
|
|
69
74
|
return hour === null ? formatWeekdayDay(dateOfSlot(slot)) : formatHour(atHour(slot, hour));
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
export function slotAxisLabel(slot: string): string {
|
|
73
78
|
const hour = hourOfSlot(slot);
|
|
79
|
+
|
|
74
80
|
return hour === null ? formatDay(dateOfSlot(slot)) : formatHour(atHour(slot, hour));
|
|
75
81
|
}
|
|
76
82
|
|
|
@@ -81,5 +87,6 @@ export function formatDate(at: number): string {
|
|
|
81
87
|
/* Enough of a path's tail to tell two checkouts of the same repository apart, under a row that already carries the name. */
|
|
82
88
|
export function shortPath(path: string): string {
|
|
83
89
|
const parts = path.split('/').filter(Boolean);
|
|
90
|
+
|
|
84
91
|
return parts.length <= 3 ? path : `…/${parts.slice(-3).join('/')}`;
|
|
85
92
|
}
|
|
@@ -46,12 +46,15 @@ export function limitGroups(snapshot: UsageLimitsSnapshot, accounts: ProviderAcc
|
|
|
46
46
|
if (!snapshot.providers.some((entry) => entry.kind === kind)) {
|
|
47
47
|
return [];
|
|
48
48
|
}
|
|
49
|
+
|
|
49
50
|
const known = accountsOfKind(accounts, kind);
|
|
51
|
+
|
|
50
52
|
const read = snapshot.providers
|
|
51
53
|
.filter((entry) => entry.kind === kind)
|
|
52
54
|
.map((entry): LimitAccount => {
|
|
53
55
|
const id = limitsAccountId(entry);
|
|
54
56
|
const account = known.find((each) => each.id === id);
|
|
57
|
+
|
|
55
58
|
return {
|
|
56
59
|
id,
|
|
57
60
|
kind,
|
|
@@ -61,6 +64,7 @@ export function limitGroups(snapshot: UsageLimitsSnapshot, accounts: ProviderAcc
|
|
|
61
64
|
status: account?.status ?? null
|
|
62
65
|
};
|
|
63
66
|
});
|
|
67
|
+
|
|
64
68
|
const unread = known
|
|
65
69
|
.filter((account) => account.account.enabled !== false && !read.some((row) => row.id === account.id))
|
|
66
70
|
.map((account): LimitAccount => ({
|
|
@@ -71,7 +75,9 @@ export function limitGroups(snapshot: UsageLimitsSnapshot, accounts: ProviderAcc
|
|
|
71
75
|
entry: null,
|
|
72
76
|
status: account.status
|
|
73
77
|
}));
|
|
78
|
+
|
|
74
79
|
const rows = [...read, ...unread];
|
|
80
|
+
|
|
75
81
|
return rows.length === 0 ? [] : [{ kind, accounts: rows }];
|
|
76
82
|
});
|
|
77
83
|
}
|
|
@@ -88,11 +94,13 @@ export function isSignedOut(account: LimitAccount): boolean {
|
|
|
88
94
|
/* The window that resets first from now, the one reset a line per window has no room to repeat. */
|
|
89
95
|
export function nextReset(windows: readonly UsageWindow[], now: number): (UsageWindow & { resetsAt: number }) | null {
|
|
90
96
|
let next: (UsageWindow & { resetsAt: number }) | null = null;
|
|
97
|
+
|
|
91
98
|
for (const window of windows) {
|
|
92
99
|
if (window.resetsAt !== null && window.resetsAt > now && (next === null || window.resetsAt < next.resetsAt)) {
|
|
93
100
|
next = { ...window, resetsAt: window.resetsAt };
|
|
94
101
|
}
|
|
95
102
|
}
|
|
103
|
+
|
|
96
104
|
return next;
|
|
97
105
|
}
|
|
98
106
|
|
|
@@ -100,6 +108,7 @@ export function nextReset(windows: readonly UsageWindow[], now: number): (UsageW
|
|
|
100
108
|
export function paceText(window: UsageWindow, now: number): string {
|
|
101
109
|
const percent = Math.round(window.used * 100);
|
|
102
110
|
const pace = evenPace(window, now);
|
|
111
|
+
|
|
103
112
|
return pace === null
|
|
104
113
|
? i18next.t('agent-usage:limits.used', { percent })
|
|
105
114
|
: i18next.t(`agent-usage:limits.pace.${pace.standing}`, { percent, pace: pace.percent });
|
|
@@ -110,8 +119,10 @@ export function checkedLabel(entry: UsageLimitsProvider, now: number): string |
|
|
|
110
119
|
if (entry.checkedAt <= 0) {
|
|
111
120
|
return null;
|
|
112
121
|
}
|
|
122
|
+
|
|
113
123
|
const past = now - entry.checkedAt;
|
|
114
124
|
const when = past < MINUTE ? i18next.t('agent-usage:limits.checked.now') : i18next.t('agent-usage:limits.checked.ago', { duration: formatDuration(past) });
|
|
125
|
+
|
|
115
126
|
return entry.source === 'event' ? i18next.t('agent-usage:limits.checked.fromTurn', { when }) : when;
|
|
116
127
|
}
|
|
117
128
|
|
|
@@ -120,13 +131,16 @@ export function explain(provider: UsageLimitsProvider): string | null {
|
|
|
120
131
|
if (provider.unavailable === null) {
|
|
121
132
|
return provider.windows.length === 0 ? i18next.t('agent-usage:limits.none') : null;
|
|
122
133
|
}
|
|
134
|
+
|
|
123
135
|
if (provider.unavailable.reason === 'not-installed') {
|
|
124
136
|
return i18next.t('agent-usage:limits.notInstalled');
|
|
125
137
|
}
|
|
138
|
+
|
|
126
139
|
if (provider.unavailable.reason === 'no-subscription') {
|
|
127
140
|
// A provider that knows why it has no plan says so; the rest are accounts on an API key.
|
|
128
141
|
return provider.unavailable.message ?? i18next.t('agent-usage:limits.noSubscription');
|
|
129
142
|
}
|
|
143
|
+
|
|
130
144
|
return provider.unavailable.message ?? i18next.t('agent-usage:limits.unreachable');
|
|
131
145
|
}
|
|
132
146
|
|
package/src/usage/limits.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
2
|
import type { UsageLimitsSnapshot } from '@adecore/agent-contracts';
|
|
3
3
|
import { useChatScope, type ChatScope } from '../scope';
|
|
4
|
-
import {
|
|
4
|
+
import { useProviderAccounts } from '../state/provider-accounts';
|
|
5
5
|
import { useUsage, useUsageStore } from '../state/usage';
|
|
6
|
+
import { whenOpen } from '../transport';
|
|
6
7
|
import { limitGroups, type LimitGroup } from './limit-groups';
|
|
7
8
|
|
|
8
9
|
interface Hold {
|
|
@@ -18,21 +19,16 @@ const holds = new Map<string, Hold>();
|
|
|
18
19
|
function hold(scope: ChatScope): Hold {
|
|
19
20
|
const link = scope.transport;
|
|
20
21
|
const scopeId = scope.id;
|
|
21
|
-
|
|
22
|
+
|
|
23
|
+
const offStatus = whenOpen(link, () => {
|
|
22
24
|
link.request('usage.limits', {})
|
|
23
25
|
.then((snapshot) => useUsageStore.getState().setLimits(scopeId, snapshot))
|
|
24
26
|
.catch(() => undefined);
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
ask();
|
|
28
|
-
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
29
|
// A running turn reports its own numbers, which is what keeps a bar moving between reads.
|
|
30
30
|
const offChanged = link.on('usage.limitsChanged', (snapshot) => useUsageStore.getState().setLimits(scopeId, snapshot));
|
|
31
|
-
|
|
32
|
-
if (status === 'open') {
|
|
33
|
-
ask();
|
|
34
|
-
}
|
|
35
|
-
});
|
|
31
|
+
|
|
36
32
|
return {
|
|
37
33
|
readers: 0,
|
|
38
34
|
release: () => {
|
|
@@ -51,8 +47,10 @@ export function useUsageLimits(): UsageLimitsSnapshot | null {
|
|
|
51
47
|
const held = holds.get(scope.id) ?? hold(scope);
|
|
52
48
|
held.readers += 1;
|
|
53
49
|
holds.set(scope.id, held);
|
|
50
|
+
|
|
54
51
|
return () => {
|
|
55
52
|
held.readers -= 1;
|
|
53
|
+
|
|
56
54
|
if (held.readers === 0) {
|
|
57
55
|
held.release();
|
|
58
56
|
holds.delete(scope.id);
|
|
@@ -65,7 +63,7 @@ export function useUsageLimits(): UsageLimitsSnapshot | null {
|
|
|
65
63
|
|
|
66
64
|
/* The snapshot per CLI and account, with the accounts of the same host it is read from. */
|
|
67
65
|
export function useLimitGroups(limits: UsageLimitsSnapshot | null): LimitGroup[] {
|
|
68
|
-
const
|
|
69
|
-
|
|
66
|
+
const accounts = useProviderAccounts((row) => row.accounts);
|
|
67
|
+
|
|
70
68
|
return limits === null ? [] : limitGroups(limits, accounts);
|
|
71
69
|
}
|
package/src/usage/money.ts
CHANGED
package/src/usage/summary.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { UsageProvider, UsageSummaryResult, UsageTotals } from '@adecore/agent-contracts';
|
|
2
|
-
import type
|
|
2
|
+
import { dayOf, type UsageMetric } from '../state/usage';
|
|
3
3
|
import { EMPTY_TOTALS, USAGE_PROVIDERS, addTotals, totalTokensOf } from '@adecore/agent-contracts';
|
|
4
4
|
|
|
5
5
|
/* One bar of the chart, what each provider put in this slot, and the height of the stack. */
|
|
@@ -21,10 +21,12 @@ export function niceScale(max: number, ticks = 4): { max: number; step: number }
|
|
|
21
21
|
if (!Number.isFinite(max) || max <= 0) {
|
|
22
22
|
return { max: 1, step: 1 };
|
|
23
23
|
}
|
|
24
|
+
|
|
24
25
|
const rough = max / ticks;
|
|
25
26
|
const magnitude = 10 ** Math.floor(Math.log10(rough));
|
|
26
27
|
const normalized = rough / magnitude;
|
|
27
28
|
const step = (normalized <= 1 ? 1 : normalized <= 2 ? 2 : normalized <= 5 ? 5 : 10) * magnitude;
|
|
29
|
+
|
|
28
30
|
return { max: Math.ceil(max / step) * step, step };
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -38,26 +40,25 @@ export interface DerivedUsage {
|
|
|
38
40
|
cacheSavingsUsd: number;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
function pad(value: number): string {
|
|
42
|
-
return String(value).padStart(2, '0');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
43
|
/*
|
|
46
44
|
* Every slot the period has, whether anything happened in it or not: an empty day keeps its place on
|
|
47
45
|
* the axis, so a gap in the work reads as a gap and not as a shorter week.
|
|
48
46
|
*/
|
|
49
47
|
export function enumerateSlots(summary: Pick<UsageSummaryResult, 'from' | 'to' | 'resolution'>): string[] {
|
|
50
48
|
if (summary.resolution === 'hour') {
|
|
51
|
-
return Array.from({ length: 24 }, (_, hour) => `${summary.to}T${
|
|
49
|
+
return Array.from({ length: 24 }, (_, hour) => `${summary.to}T${String(hour).padStart(2, '0')}`);
|
|
52
50
|
}
|
|
51
|
+
|
|
53
52
|
const slots: string[] = [];
|
|
54
53
|
const at = new Date(`${summary.from}T00:00:00`);
|
|
55
54
|
const end = new Date(`${summary.to}T00:00:00`);
|
|
55
|
+
|
|
56
56
|
// A calendar loop rather than adding milliseconds, since a day is 23 or 25 hours long twice a year.
|
|
57
57
|
while (at <= end && slots.length < 400) {
|
|
58
|
-
slots.push(
|
|
58
|
+
slots.push(dayOf(at));
|
|
59
59
|
at.setDate(at.getDate() + 1);
|
|
60
60
|
}
|
|
61
|
+
|
|
61
62
|
return slots;
|
|
62
63
|
}
|
|
63
64
|
|
|
@@ -72,9 +73,11 @@ export function labelEveryFor(slots: number): number {
|
|
|
72
73
|
*/
|
|
73
74
|
export function deriveUsage(summary: UsageSummaryResult, metric: UsageMetric): DerivedUsage {
|
|
74
75
|
const bySlot = new Map<string, ChartSlot>();
|
|
76
|
+
|
|
75
77
|
for (const slot of enumerateSlots(summary)) {
|
|
76
78
|
bySlot.set(slot, { slot, byProvider: {}, total: 0 });
|
|
77
79
|
}
|
|
80
|
+
|
|
78
81
|
const byProvider = new Map<UsageProvider, ProviderTotal>();
|
|
79
82
|
let totals = EMPTY_TOTALS;
|
|
80
83
|
let costUsd = 0;
|
|
@@ -83,10 +86,12 @@ export function deriveUsage(summary: UsageSummaryResult, metric: UsageMetric): D
|
|
|
83
86
|
for (const bucket of summary.buckets) {
|
|
84
87
|
const value = metric === 'cost' ? (bucket.costUsd ?? 0) : totalTokensOf(bucket.totals);
|
|
85
88
|
const slot = bySlot.get(bucket.slot);
|
|
89
|
+
|
|
86
90
|
if (slot !== undefined) {
|
|
87
91
|
slot.byProvider[bucket.provider] = (slot.byProvider[bucket.provider] ?? 0) + value;
|
|
88
92
|
slot.total += value;
|
|
89
93
|
}
|
|
94
|
+
|
|
90
95
|
const provider = byProvider.get(bucket.provider) ?? { provider: bucket.provider, costUsd: 0, totals: EMPTY_TOTALS };
|
|
91
96
|
provider.costUsd += bucket.costUsd ?? 0;
|
|
92
97
|
provider.totals = addTotals(provider.totals, bucket.totals);
|
|
@@ -97,6 +102,7 @@ export function deriveUsage(summary: UsageSummaryResult, metric: UsageMetric): D
|
|
|
97
102
|
}
|
|
98
103
|
|
|
99
104
|
const providers = [...byProvider.values()].sort((a, b) => b.costUsd - a.costUsd);
|
|
105
|
+
|
|
100
106
|
return {
|
|
101
107
|
slots: [...bySlot.values()],
|
|
102
108
|
providers,
|
|
@@ -122,6 +128,7 @@ export interface UsageDay {
|
|
|
122
128
|
*/
|
|
123
129
|
export function deriveDays(summary: UsageSummaryResult): UsageDay[] {
|
|
124
130
|
const rows = new Map<string, UsageDay>();
|
|
131
|
+
|
|
125
132
|
for (const bucket of summary.buckets) {
|
|
126
133
|
const day = bucket.slot.slice(0, 10);
|
|
127
134
|
const row = rows.get(day) ?? { slot: day, costByProvider: {}, costUsd: 0, tokens: 0 };
|
|
@@ -131,5 +138,6 @@ export function deriveDays(summary: UsageSummaryResult): UsageDay[] {
|
|
|
131
138
|
row.tokens += totalTokensOf(bucket.totals);
|
|
132
139
|
rows.set(day, row);
|
|
133
140
|
}
|
|
141
|
+
|
|
134
142
|
return [...rows.values()].sort((a, b) => b.slot.localeCompare(a.slot));
|
|
135
143
|
}
|