stagent 0.1.11 → 0.1.12
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/README.md +35 -4
- package/package.json +3 -2
- package/src/__tests__/e2e/blueprint.test.ts +63 -0
- package/src/__tests__/e2e/cross-runtime.test.ts +77 -0
- package/src/__tests__/e2e/helpers.ts +286 -0
- package/src/__tests__/e2e/parallel-workflow.test.ts +120 -0
- package/src/__tests__/e2e/sequence-workflow.test.ts +109 -0
- package/src/__tests__/e2e/setup.ts +156 -0
- package/src/__tests__/e2e/single-task.test.ts +170 -0
- package/src/app/api/command-palette/recent/route.ts +41 -18
- package/src/app/api/context/batch/route.ts +44 -0
- package/src/app/api/permissions/presets/route.ts +80 -0
- package/src/app/api/playbook/status/route.ts +15 -0
- package/src/app/api/profiles/route.ts +23 -20
- package/src/app/api/settings/pricing/route.ts +15 -0
- package/src/app/costs/page.tsx +53 -43
- package/src/app/playbook/[slug]/page.tsx +76 -0
- package/src/app/playbook/page.tsx +54 -0
- package/src/app/profiles/page.tsx +7 -4
- package/src/app/settings/page.tsx +2 -2
- package/src/components/costs/cost-dashboard.tsx +226 -320
- package/src/components/dashboard/activity-feed.tsx +6 -2
- package/src/components/notifications/batch-proposal-review.tsx +150 -0
- package/src/components/notifications/notification-item.tsx +6 -3
- package/src/components/notifications/pending-approval-host.tsx +57 -11
- package/src/components/playbook/adoption-heatmap.tsx +69 -0
- package/src/components/playbook/journey-card.tsx +110 -0
- package/src/components/playbook/playbook-action-button.tsx +22 -0
- package/src/components/playbook/playbook-browser.tsx +143 -0
- package/src/components/playbook/playbook-card.tsx +102 -0
- package/src/components/playbook/playbook-detail-view.tsx +223 -0
- package/src/components/playbook/playbook-homepage.tsx +142 -0
- package/src/components/playbook/playbook-toc.tsx +90 -0
- package/src/components/playbook/playbook-updated-badge.tsx +23 -0
- package/src/components/playbook/related-docs.tsx +30 -0
- package/src/components/profiles/__tests__/learned-context-panel.test.tsx +175 -0
- package/src/components/profiles/context-proposal-review.tsx +7 -3
- package/src/components/profiles/learned-context-panel.tsx +116 -8
- package/src/components/profiles/profile-detail-view.tsx +6 -3
- package/src/components/settings/__tests__/auth-config-section.test.tsx +147 -0
- package/src/components/settings/api-key-form.tsx +5 -43
- package/src/components/settings/auth-config-section.tsx +10 -6
- package/src/components/settings/auth-status-badge.tsx +8 -0
- package/src/components/settings/budget-guardrails-section.tsx +403 -620
- package/src/components/settings/connection-test-control.tsx +63 -0
- package/src/components/settings/permissions-section.tsx +85 -75
- package/src/components/settings/permissions-sections.tsx +24 -0
- package/src/components/settings/presets-section.tsx +159 -0
- package/src/components/settings/pricing-registry-panel.tsx +164 -0
- package/src/components/shared/app-sidebar.tsx +2 -0
- package/src/components/shared/command-palette.tsx +30 -0
- package/src/components/shared/light-markdown.tsx +134 -0
- package/src/components/workflows/loop-status-view.tsx +8 -4
- package/src/components/workflows/workflow-status-view.tsx +16 -9
- package/src/lib/agents/learned-context.ts +27 -15
- package/src/lib/agents/learning-session.ts +234 -0
- package/src/lib/agents/pattern-extractor.ts +19 -0
- package/src/lib/agents/profiles/__tests__/sort.test.ts +42 -0
- package/src/lib/agents/profiles/sort.ts +7 -0
- package/src/lib/constants/settings.ts +1 -0
- package/src/lib/db/schema.ts +3 -0
- package/src/lib/docs/adoption.ts +105 -0
- package/src/lib/docs/journey-tracker.ts +21 -0
- package/src/lib/docs/reader.ts +102 -0
- package/src/lib/docs/types.ts +54 -0
- package/src/lib/docs/usage-stage.ts +60 -0
- package/src/lib/notifications/actionable.ts +18 -10
- package/src/lib/settings/__tests__/budget-guardrails.test.ts +86 -24
- package/src/lib/settings/budget-guardrails.ts +213 -85
- package/src/lib/settings/permission-presets.ts +150 -0
- package/src/lib/settings/runtime-setup.ts +71 -0
- package/src/lib/usage/__tests__/ledger.test.ts +2 -2
- package/src/lib/usage/__tests__/pricing-registry.test.ts +78 -0
- package/src/lib/usage/ledger.ts +1 -1
- package/src/lib/usage/pricing-registry.ts +570 -0
- package/src/lib/usage/pricing.ts +15 -95
- package/src/lib/utils/__tests__/learned-context-history.test.ts +171 -0
- package/src/lib/utils/learned-context-history.ts +150 -0
- package/src/lib/validators/__tests__/settings.test.ts +23 -16
- package/src/lib/validators/settings.ts +3 -9
- package/src/lib/workflows/engine.ts +18 -0
|
@@ -2,17 +2,17 @@ import Link from "next/link";
|
|
|
2
2
|
import {
|
|
3
3
|
AlertTriangle,
|
|
4
4
|
ArrowRight,
|
|
5
|
+
CalendarClock,
|
|
5
6
|
CalendarRange,
|
|
6
7
|
Coins,
|
|
7
8
|
ShieldAlert,
|
|
8
9
|
ShieldCheck,
|
|
9
10
|
Wallet,
|
|
10
11
|
} from "lucide-react";
|
|
11
|
-
import {
|
|
12
|
-
import type {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from "@/lib/usage/ledger";
|
|
12
|
+
import type { UsageAuditEntry, ProviderModelBreakdownEntry } from "@/lib/usage/ledger";
|
|
13
|
+
import type { BudgetWindowStatus } from "@/lib/settings/budget-guardrails";
|
|
14
|
+
import type { RuntimeSetupState } from "@/lib/settings/runtime-setup";
|
|
15
|
+
import type { PricingRegistrySnapshot } from "@/lib/usage/pricing-registry";
|
|
16
16
|
import { Badge } from "@/components/ui/badge";
|
|
17
17
|
import { Button } from "@/components/ui/button";
|
|
18
18
|
import { DonutRing } from "@/components/charts/donut-ring";
|
|
@@ -29,29 +29,12 @@ import {
|
|
|
29
29
|
} from "@/components/ui/table";
|
|
30
30
|
import { EmptyState } from "@/components/shared/empty-state";
|
|
31
31
|
import { CostFilters } from "@/components/costs/cost-filters";
|
|
32
|
-
|
|
33
|
-
type BudgetHealth = "unlimited" | "ok" | "warning" | "blocked";
|
|
34
|
-
type BudgetMetric = "spend" | "tokens";
|
|
35
|
-
type BudgetWindow = "daily" | "monthly";
|
|
36
|
-
|
|
37
|
-
interface BudgetStatus {
|
|
38
|
-
id: string;
|
|
39
|
-
scopeId: string;
|
|
40
|
-
scopeLabel: string;
|
|
41
|
-
runtimeId: string | null;
|
|
42
|
-
metric: BudgetMetric;
|
|
43
|
-
window: BudgetWindow;
|
|
44
|
-
currentValue: number;
|
|
45
|
-
limitValue: number | null;
|
|
46
|
-
ratio: number | null;
|
|
47
|
-
health: BudgetHealth;
|
|
48
|
-
resetAtIso: string;
|
|
49
|
-
}
|
|
32
|
+
import { PricingRegistryPanel } from "@/components/settings/pricing-registry-panel";
|
|
50
33
|
|
|
51
34
|
interface CostSummary {
|
|
52
|
-
todaySpendMicros: number;
|
|
53
35
|
monthSpendMicros: number;
|
|
54
|
-
|
|
36
|
+
derivedDailyBudgetMicros: number;
|
|
37
|
+
remainingMonthlyHeadroomMicros: number;
|
|
55
38
|
monthTokens: number;
|
|
56
39
|
}
|
|
57
40
|
|
|
@@ -66,12 +49,6 @@ interface RuntimeBreakdownRow {
|
|
|
66
49
|
unknownPricingRuns: number;
|
|
67
50
|
}
|
|
68
51
|
|
|
69
|
-
interface ModelVisualMeta {
|
|
70
|
-
share: number;
|
|
71
|
-
valueLabel: string;
|
|
72
|
-
basisLabel: string;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
52
|
interface TrendSeries {
|
|
76
53
|
spend7: number[];
|
|
77
54
|
spend30: number[];
|
|
@@ -90,17 +67,14 @@ interface CostDashboardProps {
|
|
|
90
67
|
filters: FilterState;
|
|
91
68
|
summary: CostSummary;
|
|
92
69
|
trendSeries: TrendSeries;
|
|
93
|
-
budgetStatuses:
|
|
70
|
+
budgetStatuses: Array<BudgetWindowStatus & { resetAtIso: string }>;
|
|
71
|
+
runtimeStates: Record<string, RuntimeSetupState>;
|
|
72
|
+
pricing: PricingRegistrySnapshot;
|
|
94
73
|
runtimeBreakdown: RuntimeBreakdownRow[];
|
|
95
74
|
modelBreakdown: ProviderModelBreakdownEntry[];
|
|
96
75
|
auditEntries: UsageAuditEntry[];
|
|
97
76
|
}
|
|
98
77
|
|
|
99
|
-
const runtimeCatalog = listRuntimeCatalog();
|
|
100
|
-
const runtimeLabelMap = new Map<string, string>(
|
|
101
|
-
runtimeCatalog.map((runtime) => [runtime.id, runtime.label])
|
|
102
|
-
);
|
|
103
|
-
|
|
104
78
|
function formatCurrencyMicros(value: number | null | undefined) {
|
|
105
79
|
const amount = value ?? 0;
|
|
106
80
|
return new Intl.NumberFormat("en-US", {
|
|
@@ -126,10 +100,6 @@ function formatPercent(value: number) {
|
|
|
126
100
|
return `${Math.round(value)}%`;
|
|
127
101
|
}
|
|
128
102
|
|
|
129
|
-
function clampPercent(value: number) {
|
|
130
|
-
return Math.max(0, Math.min(100, value));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
103
|
function formatDateTime(value: string) {
|
|
134
104
|
return new Date(value).toLocaleString(undefined, {
|
|
135
105
|
dateStyle: "medium",
|
|
@@ -164,17 +134,15 @@ function formatActivityLabel(value: UsageAuditEntry["activityType"]) {
|
|
|
164
134
|
return "Task assist";
|
|
165
135
|
case "profile_test":
|
|
166
136
|
return "Profile test";
|
|
137
|
+
case "pattern_extraction":
|
|
138
|
+
return "Pattern extraction";
|
|
139
|
+
case "context_summarization":
|
|
140
|
+
return "Context summarization";
|
|
167
141
|
default:
|
|
168
|
-
return value
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
function formatLedgerStatusLabel(value: UsageAuditEntry["status"]) {
|
|
173
|
-
switch (value) {
|
|
174
|
-
case "unknown_pricing":
|
|
175
|
-
return "Unknown pricing";
|
|
176
|
-
default:
|
|
177
|
-
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
142
|
+
return value
|
|
143
|
+
.split("_")
|
|
144
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
145
|
+
.join(" ");
|
|
178
146
|
}
|
|
179
147
|
}
|
|
180
148
|
|
|
@@ -194,44 +162,14 @@ function statusBadge(status: UsageAuditEntry["status"]) {
|
|
|
194
162
|
</Badge>
|
|
195
163
|
);
|
|
196
164
|
case "unknown_pricing":
|
|
197
|
-
return
|
|
198
|
-
<Badge variant="outline" className="border-border/70 text-muted-foreground">
|
|
199
|
-
Unknown pricing
|
|
200
|
-
</Badge>
|
|
201
|
-
);
|
|
165
|
+
return <Badge variant="secondary">Pricing unavailable</Badge>;
|
|
202
166
|
case "cancelled":
|
|
203
167
|
return <Badge variant="secondary">Cancelled</Badge>;
|
|
204
168
|
default:
|
|
205
|
-
return <Badge variant="secondary">{
|
|
169
|
+
return <Badge variant="secondary">{status}</Badge>;
|
|
206
170
|
}
|
|
207
171
|
}
|
|
208
172
|
|
|
209
|
-
function budgetBadge(status: BudgetStatus) {
|
|
210
|
-
if (status.health === "blocked") {
|
|
211
|
-
return <Badge variant="destructive">Blocked</Badge>;
|
|
212
|
-
}
|
|
213
|
-
if (status.health === "warning") {
|
|
214
|
-
return (
|
|
215
|
-
<Badge
|
|
216
|
-
variant="outline"
|
|
217
|
-
className="border-status-warning/30 bg-status-warning/10 text-status-warning"
|
|
218
|
-
>
|
|
219
|
-
Warning
|
|
220
|
-
</Badge>
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
if (status.health === "ok") {
|
|
224
|
-
return <Badge variant="success">Tracked</Badge>;
|
|
225
|
-
}
|
|
226
|
-
return <Badge variant="secondary">Unlimited</Badge>;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function formatBudgetValue(status: BudgetStatus, value: number) {
|
|
230
|
-
return status.metric === "spend"
|
|
231
|
-
? formatCurrencyMicros(value)
|
|
232
|
-
: formatTokenCount(value);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
173
|
function renderEntityLink(entry: UsageAuditEntry) {
|
|
236
174
|
if (entry.taskId && entry.taskTitle) {
|
|
237
175
|
return (
|
|
@@ -264,36 +202,6 @@ function renderEntityLink(entry: UsageAuditEntry) {
|
|
|
264
202
|
return <span className="font-medium">{formatActivityLabel(entry.activityType)}</span>;
|
|
265
203
|
}
|
|
266
204
|
|
|
267
|
-
function resolveModelVisualMeta(
|
|
268
|
-
row: ProviderModelBreakdownEntry,
|
|
269
|
-
totals: { costMicros: number; totalTokens: number }
|
|
270
|
-
): ModelVisualMeta {
|
|
271
|
-
if (totals.costMicros > 0 && row.costMicros > 0) {
|
|
272
|
-
const share = clampPercent((row.costMicros / totals.costMicros) * 100);
|
|
273
|
-
return {
|
|
274
|
-
share,
|
|
275
|
-
valueLabel: formatCurrencyMicros(row.costMicros),
|
|
276
|
-
basisLabel: `${formatPercent(share)} of filtered spend`,
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (totals.totalTokens > 0 && row.totalTokens > 0) {
|
|
281
|
-
const share = clampPercent((row.totalTokens / totals.totalTokens) * 100);
|
|
282
|
-
return {
|
|
283
|
-
share,
|
|
284
|
-
valueLabel: `${formatCompactCount(row.totalTokens)} tokens`,
|
|
285
|
-
basisLabel: `${formatPercent(share)} of filtered tokens`,
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return {
|
|
290
|
-
share: 0,
|
|
291
|
-
valueLabel:
|
|
292
|
-
row.unknownPricingRuns === row.runs ? "Pricing unavailable" : formatCurrencyMicros(0),
|
|
293
|
-
basisLabel: "No measurable cost or token usage",
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
|
|
297
205
|
function SummaryCard({
|
|
298
206
|
eyebrow,
|
|
299
207
|
title,
|
|
@@ -328,21 +236,30 @@ function SummaryCard({
|
|
|
328
236
|
);
|
|
329
237
|
}
|
|
330
238
|
|
|
239
|
+
function getStatus(
|
|
240
|
+
statuses: Array<BudgetWindowStatus & { resetAtIso: string }>,
|
|
241
|
+
scopeId: string,
|
|
242
|
+
window: "daily" | "monthly"
|
|
243
|
+
) {
|
|
244
|
+
return statuses.find(
|
|
245
|
+
(status) => status.scopeId === scopeId && status.window === window
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
331
249
|
export function CostDashboard({
|
|
332
250
|
filters,
|
|
333
251
|
summary,
|
|
334
252
|
trendSeries,
|
|
335
253
|
budgetStatuses,
|
|
254
|
+
runtimeStates,
|
|
255
|
+
pricing,
|
|
336
256
|
runtimeBreakdown,
|
|
337
257
|
modelBreakdown,
|
|
338
258
|
auditEntries,
|
|
339
259
|
}: CostDashboardProps) {
|
|
260
|
+
const configuredRuntimes = Object.values(runtimeStates).filter((runtime) => runtime.configured);
|
|
340
261
|
const warnings = budgetStatuses.filter((status) => status.health === "warning");
|
|
341
262
|
const blocked = budgetStatuses.filter((status) => status.health === "blocked");
|
|
342
|
-
const configuredBudgets = budgetStatuses.filter((status) => status.limitValue != null);
|
|
343
|
-
const nearestBudget = configuredBudgets
|
|
344
|
-
.slice()
|
|
345
|
-
.sort((left, right) => (right.ratio ?? 0) - (left.ratio ?? 0))[0];
|
|
346
263
|
const hasUsage =
|
|
347
264
|
summary.monthSpendMicros > 0 ||
|
|
348
265
|
summary.monthTokens > 0 ||
|
|
@@ -352,13 +269,16 @@ export function CostDashboard({
|
|
|
352
269
|
(total, row) => total + row.unknownPricingRuns,
|
|
353
270
|
0
|
|
354
271
|
);
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
272
|
+
const activeMixLabel =
|
|
273
|
+
configuredRuntimes.length === 0
|
|
274
|
+
? "No providers configured"
|
|
275
|
+
: configuredRuntimes.length === 1
|
|
276
|
+
? configuredRuntimes[0]!.label
|
|
277
|
+
: configuredRuntimes.map((runtime) => runtime.label).join(" + ");
|
|
278
|
+
const dominantRuntime = runtimeBreakdown[0] ?? null;
|
|
279
|
+
const pacingTone =
|
|
280
|
+
blocked.length > 0 ? "blocked" : warnings.length > 0 ? "warning" : "healthy";
|
|
281
|
+
const overallMonthly = getStatus(budgetStatuses, "overall", "monthly");
|
|
362
282
|
|
|
363
283
|
return (
|
|
364
284
|
<div className="flex flex-col gap-6">
|
|
@@ -370,8 +290,8 @@ export function CostDashboard({
|
|
|
370
290
|
<div className="space-y-2">
|
|
371
291
|
<h1 className="text-2xl font-bold tracking-tight">Cost & Usage</h1>
|
|
372
292
|
<p className="max-w-2xl text-sm text-muted-foreground">
|
|
373
|
-
|
|
374
|
-
runtime
|
|
293
|
+
Track current spend pacing, provider mix, and the execution history behind paid
|
|
294
|
+
runtime work without juggling a second budgeting model.
|
|
375
295
|
</p>
|
|
376
296
|
</div>
|
|
377
297
|
</div>
|
|
@@ -381,151 +301,136 @@ export function CostDashboard({
|
|
|
381
301
|
runtimeId={filters.runtimeId}
|
|
382
302
|
status={filters.status}
|
|
383
303
|
activityType={filters.activityType}
|
|
384
|
-
runtimeOptions={
|
|
385
|
-
id: runtime.
|
|
304
|
+
runtimeOptions={configuredRuntimes.map((runtime) => ({
|
|
305
|
+
id: runtime.runtimeId,
|
|
386
306
|
label: runtime.label,
|
|
387
307
|
}))}
|
|
388
308
|
/>
|
|
389
309
|
|
|
390
|
-
{blocked.length > 0 ? (
|
|
391
|
-
<div className="surface-card rounded-3xl border border-status-failed/25 bg-status-failed/8 p-5">
|
|
392
|
-
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
|
393
|
-
<div className="space-y-2">
|
|
394
|
-
<div className="flex items-center gap-2 text-status-failed">
|
|
395
|
-
<ShieldAlert className="h-4 w-4" />
|
|
396
|
-
<p className="text-sm font-semibold">Provider activity is currently blocked</p>
|
|
397
|
-
</div>
|
|
398
|
-
<p className="text-sm text-muted-foreground">
|
|
399
|
-
One or more active budget windows have been exceeded. New paid work
|
|
400
|
-
will remain blocked until the affected window resets.
|
|
401
|
-
</p>
|
|
402
|
-
</div>
|
|
403
|
-
<div className="grid gap-2 lg:min-w-[320px]">
|
|
404
|
-
{blocked.slice(0, 2).map((status) => (
|
|
405
|
-
<div
|
|
406
|
-
key={status.id}
|
|
407
|
-
className="surface-card-muted flex items-start justify-between gap-3 rounded-2xl p-3"
|
|
408
|
-
>
|
|
409
|
-
<div>
|
|
410
|
-
<p className="text-sm font-medium">
|
|
411
|
-
{status.scopeLabel} {status.window} {status.metric}
|
|
412
|
-
</p>
|
|
413
|
-
<p className="text-xs text-muted-foreground">
|
|
414
|
-
{formatBudgetValue(status, status.currentValue)} of{" "}
|
|
415
|
-
{formatBudgetValue(status, status.limitValue ?? 0)} used
|
|
416
|
-
</p>
|
|
417
|
-
</div>
|
|
418
|
-
<p className="text-right text-xs text-muted-foreground">
|
|
419
|
-
Resets {formatDateTime(status.resetAtIso)}
|
|
420
|
-
</p>
|
|
421
|
-
</div>
|
|
422
|
-
))}
|
|
423
|
-
</div>
|
|
424
|
-
</div>
|
|
425
|
-
</div>
|
|
426
|
-
) : null}
|
|
427
|
-
|
|
428
|
-
{blocked.length === 0 && warnings.length > 0 ? (
|
|
429
|
-
<div className="surface-card rounded-3xl border border-status-warning/25 bg-status-warning/8 p-5">
|
|
430
|
-
<div className="flex items-start gap-3">
|
|
431
|
-
<AlertTriangle className="mt-0.5 h-4 w-4 text-status-warning" />
|
|
432
|
-
<div className="space-y-2">
|
|
433
|
-
<p className="text-sm font-semibold">Budget usage is approaching a cap</p>
|
|
434
|
-
<p className="text-sm text-muted-foreground">
|
|
435
|
-
{warnings[0].scopeLabel} {warnings[0].window} {warnings[0].metric} is at{" "}
|
|
436
|
-
{formatPercent((warnings[0].ratio ?? 0) * 100)} of its configured
|
|
437
|
-
limit and resets {formatDateTime(warnings[0].resetAtIso)}.
|
|
438
|
-
</p>
|
|
439
|
-
</div>
|
|
440
|
-
</div>
|
|
441
|
-
</div>
|
|
442
|
-
) : null}
|
|
443
|
-
|
|
444
310
|
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-5">
|
|
445
311
|
<SummaryCard
|
|
446
|
-
eyebrow="
|
|
312
|
+
eyebrow="Month"
|
|
447
313
|
title="Spend"
|
|
448
|
-
value={formatCurrencyMicros(summary.
|
|
449
|
-
detail="
|
|
314
|
+
value={formatCurrencyMicros(summary.monthSpendMicros)}
|
|
315
|
+
detail="Budget basis for the current month"
|
|
450
316
|
icon={Wallet}
|
|
451
317
|
/>
|
|
452
318
|
<SummaryCard
|
|
453
|
-
eyebrow="
|
|
454
|
-
title="
|
|
455
|
-
value={formatCurrencyMicros(summary.
|
|
456
|
-
detail="
|
|
319
|
+
eyebrow="Derived"
|
|
320
|
+
title="Daily Budget"
|
|
321
|
+
value={formatCurrencyMicros(summary.derivedDailyBudgetMicros)}
|
|
322
|
+
detail="Calculated from the monthly cap"
|
|
457
323
|
icon={CalendarRange}
|
|
458
324
|
/>
|
|
459
325
|
<SummaryCard
|
|
460
|
-
eyebrow="
|
|
461
|
-
title="
|
|
462
|
-
value={
|
|
463
|
-
detail=
|
|
464
|
-
icon={
|
|
326
|
+
eyebrow="Remaining"
|
|
327
|
+
title="Monthly Headroom"
|
|
328
|
+
value={formatCurrencyMicros(summary.remainingMonthlyHeadroomMicros)}
|
|
329
|
+
detail="Spend left before the monthly cap"
|
|
330
|
+
icon={ShieldCheck}
|
|
465
331
|
/>
|
|
466
332
|
<SummaryCard
|
|
467
|
-
eyebrow="
|
|
468
|
-
title="
|
|
469
|
-
value={
|
|
470
|
-
detail={
|
|
471
|
-
icon={
|
|
333
|
+
eyebrow="Providers"
|
|
334
|
+
title="Active Mix"
|
|
335
|
+
value={configuredRuntimes.length === 0 ? "None" : String(configuredRuntimes.length)}
|
|
336
|
+
detail={activeMixLabel}
|
|
337
|
+
icon={ArrowRight}
|
|
472
338
|
/>
|
|
339
|
+
<SummaryCard
|
|
340
|
+
eyebrow="Pricing"
|
|
341
|
+
title="Freshness"
|
|
342
|
+
value={pricing.stale ? "Stale" : "Current"}
|
|
343
|
+
detail={
|
|
344
|
+
pricing.lastUpdatedIso
|
|
345
|
+
? `Updated ${formatDateTime(pricing.lastUpdatedIso)}`
|
|
346
|
+
: "No refresh recorded yet"
|
|
347
|
+
}
|
|
348
|
+
icon={CalendarClock}
|
|
349
|
+
/>
|
|
350
|
+
</div>
|
|
473
351
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
352
|
+
<div
|
|
353
|
+
className={`surface-card rounded-3xl p-5 ${
|
|
354
|
+
pacingTone === "blocked"
|
|
355
|
+
? "border border-status-failed/25 bg-status-failed/8"
|
|
356
|
+
: pacingTone === "warning"
|
|
357
|
+
? "border border-status-warning/25 bg-status-warning/8"
|
|
358
|
+
: ""
|
|
359
|
+
}`}
|
|
360
|
+
>
|
|
361
|
+
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
|
362
|
+
<div className="space-y-2">
|
|
363
|
+
<div
|
|
364
|
+
className={`flex items-center gap-2 ${
|
|
365
|
+
pacingTone === "blocked"
|
|
366
|
+
? "text-status-failed"
|
|
367
|
+
: pacingTone === "warning"
|
|
368
|
+
? "text-status-warning"
|
|
369
|
+
: "text-status-completed"
|
|
370
|
+
}`}
|
|
371
|
+
>
|
|
372
|
+
{pacingTone === "blocked" ? (
|
|
373
|
+
<ShieldAlert className="h-4 w-4" />
|
|
374
|
+
) : pacingTone === "warning" ? (
|
|
375
|
+
<AlertTriangle className="h-4 w-4" />
|
|
485
376
|
) : (
|
|
486
|
-
<ShieldCheck className="h-4 w-4
|
|
377
|
+
<ShieldCheck className="h-4 w-4" />
|
|
487
378
|
)}
|
|
379
|
+
<p className="text-sm font-semibold">
|
|
380
|
+
{pacingTone === "blocked"
|
|
381
|
+
? "Budget pacing is blocked"
|
|
382
|
+
: pacingTone === "warning"
|
|
383
|
+
? "Budget pacing is near a cap"
|
|
384
|
+
: "Budget pacing is on track"}
|
|
385
|
+
</p>
|
|
488
386
|
</div>
|
|
387
|
+
<p className="text-sm text-muted-foreground">
|
|
388
|
+
{pacingTone === "blocked"
|
|
389
|
+
? "One or more active spend windows have been exceeded. New paid work remains blocked until the affected window resets."
|
|
390
|
+
: pacingTone === "warning"
|
|
391
|
+
? "A configured spend window is approaching its limit. Review the active provider mix before it becomes a hard stop."
|
|
392
|
+
: "Spend is within the configured pacing windows. Derived daily caps continue to roll forward from the monthly budget."}
|
|
393
|
+
</p>
|
|
489
394
|
</div>
|
|
490
395
|
|
|
491
|
-
|
|
492
|
-
<div className="
|
|
493
|
-
<
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
396
|
+
<div className="grid gap-2 lg:min-w-[340px]">
|
|
397
|
+
<div className="surface-card-muted rounded-2xl p-3">
|
|
398
|
+
<p className="text-sm font-medium">Primary spend driver</p>
|
|
399
|
+
<p className="text-xs text-muted-foreground">
|
|
400
|
+
{dominantRuntime
|
|
401
|
+
? `${dominantRuntime.label} represents ${formatPercent(
|
|
402
|
+
dominantRuntime.share
|
|
403
|
+
)} of filtered spend.`
|
|
404
|
+
: "No provider has recorded spend in the current filtered window."}
|
|
405
|
+
</p>
|
|
406
|
+
</div>
|
|
407
|
+
{overallMonthly ? (
|
|
408
|
+
<div className="surface-card-muted rounded-2xl p-3">
|
|
409
|
+
<p className="text-sm font-medium">
|
|
410
|
+
{formatCurrencyMicros(overallMonthly.currentValue)} of{" "}
|
|
411
|
+
{overallMonthly.limitValue == null
|
|
412
|
+
? "Unlimited"
|
|
413
|
+
: formatCurrencyMicros(overallMonthly.limitValue)}
|
|
505
414
|
</p>
|
|
506
415
|
<p className="text-xs text-muted-foreground">
|
|
507
|
-
|
|
508
|
-
{formatDateTime(nearestBudget.resetAtIso)}.
|
|
416
|
+
Monthly reset {formatDateTime(overallMonthly.resetAtIso)}.
|
|
509
417
|
</p>
|
|
510
418
|
</div>
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
<div className="space-y-2">
|
|
514
|
-
<Badge variant="secondary">Unconfigured</Badge>
|
|
515
|
-
<p className="text-sm text-muted-foreground">
|
|
516
|
-
No spend or token caps are configured yet. Usage is being metered,
|
|
517
|
-
but there is no automatic stop condition.
|
|
518
|
-
</p>
|
|
519
|
-
</div>
|
|
520
|
-
)}
|
|
419
|
+
) : null}
|
|
420
|
+
</div>
|
|
521
421
|
</div>
|
|
522
422
|
</div>
|
|
523
423
|
|
|
424
|
+
<PricingRegistryPanel
|
|
425
|
+
initialSnapshot={pricing}
|
|
426
|
+
showClaudePlans={runtimeStates["claude-code"]?.billingMode === "subscription"}
|
|
427
|
+
/>
|
|
428
|
+
|
|
524
429
|
{hasUsage ? (
|
|
525
430
|
<>
|
|
526
431
|
<div className="grid gap-6 xl:grid-cols-[minmax(0,1.15fr)_minmax(0,0.85fr)]">
|
|
527
432
|
<div className="surface-card rounded-3xl p-5">
|
|
528
|
-
<SectionHeading>
|
|
433
|
+
<SectionHeading>Spend Trends</SectionHeading>
|
|
529
434
|
<div className="grid gap-4 lg:grid-cols-2">
|
|
530
435
|
<div className="surface-card-muted rounded-2xl p-4">
|
|
531
436
|
<div className="mb-4 flex items-center justify-between gap-3">
|
|
@@ -572,9 +477,9 @@ export function CostDashboard({
|
|
|
572
477
|
<div className="surface-card-muted rounded-2xl p-4">
|
|
573
478
|
<div className="mb-4 flex items-center justify-between gap-3">
|
|
574
479
|
<div>
|
|
575
|
-
<p className="text-sm font-medium">
|
|
480
|
+
<p className="text-sm font-medium">Activity tokens</p>
|
|
576
481
|
<p className="text-xs text-muted-foreground">
|
|
577
|
-
|
|
482
|
+
Secondary telemetry for the same window
|
|
578
483
|
</p>
|
|
579
484
|
</div>
|
|
580
485
|
<Badge variant="outline">{formatCompactCount(summary.monthTokens)} tokens</Badge>
|
|
@@ -614,7 +519,9 @@ export function CostDashboard({
|
|
|
614
519
|
</div>
|
|
615
520
|
|
|
616
521
|
<div className="surface-card rounded-3xl p-5">
|
|
617
|
-
<SectionHeading>
|
|
522
|
+
<SectionHeading>
|
|
523
|
+
{runtimeBreakdown.length <= 1 ? "Active Provider" : "Provider Breakdown"}
|
|
524
|
+
</SectionHeading>
|
|
618
525
|
<div className="space-y-3">
|
|
619
526
|
{runtimeBreakdown.length > 0 ? (
|
|
620
527
|
runtimeBreakdown.map((runtime) => (
|
|
@@ -623,22 +530,32 @@ export function CostDashboard({
|
|
|
623
530
|
className="surface-card-muted flex items-center justify-between gap-4 rounded-2xl p-4"
|
|
624
531
|
>
|
|
625
532
|
<div className="flex items-center gap-4">
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
533
|
+
{runtimeBreakdown.length > 1 ? (
|
|
534
|
+
<DonutRing
|
|
535
|
+
value={runtime.share}
|
|
536
|
+
size={44}
|
|
537
|
+
strokeWidth={4}
|
|
538
|
+
color="var(--chart-1)"
|
|
539
|
+
trackColor="var(--muted)"
|
|
540
|
+
label={`${runtime.label} share of spend`}
|
|
541
|
+
/>
|
|
542
|
+
) : (
|
|
543
|
+
<div className="rounded-2xl border border-border/60 bg-background/40 px-3 py-2 text-sm font-semibold">
|
|
544
|
+
{runtime.label}
|
|
545
|
+
</div>
|
|
546
|
+
)}
|
|
634
547
|
<div className="space-y-1">
|
|
635
548
|
<div className="flex items-center gap-2">
|
|
636
549
|
<p className="text-sm font-medium">{runtime.label}</p>
|
|
637
550
|
<Badge variant="outline">{runtime.providerId}</Badge>
|
|
551
|
+
{runtimeStates[runtime.runtimeId]?.billingMode === "subscription" ? (
|
|
552
|
+
<Badge variant="secondary">Plan priced</Badge>
|
|
553
|
+
) : null}
|
|
638
554
|
</div>
|
|
639
555
|
<p className="text-xs text-muted-foreground">
|
|
640
|
-
{
|
|
641
|
-
|
|
556
|
+
{runtimeBreakdown.length > 1
|
|
557
|
+
? `${formatPercent(runtime.share)} of filtered spend across ${runtime.runs} runs`
|
|
558
|
+
: `${runtime.runs} filtered runs in the selected window`}
|
|
642
559
|
</p>
|
|
643
560
|
</div>
|
|
644
561
|
</div>
|
|
@@ -662,9 +579,8 @@ export function CostDashboard({
|
|
|
662
579
|
{runtime.unknownPricingRuns > 0 ? (
|
|
663
580
|
<div className="col-span-2">
|
|
664
581
|
<p className="text-xs text-muted-foreground">
|
|
665
|
-
{runtime.unknownPricingRuns}
|
|
666
|
-
{runtime.unknownPricingRuns === 1 ? "" : "s"}
|
|
667
|
-
pricing data
|
|
582
|
+
{runtime.unknownPricingRuns} row
|
|
583
|
+
{runtime.unknownPricingRuns === 1 ? "" : "s"} without price data
|
|
668
584
|
</p>
|
|
669
585
|
</div>
|
|
670
586
|
) : null}
|
|
@@ -673,7 +589,8 @@ export function CostDashboard({
|
|
|
673
589
|
))
|
|
674
590
|
) : (
|
|
675
591
|
<div className="surface-card-muted rounded-2xl p-4 text-sm text-muted-foreground">
|
|
676
|
-
No metered
|
|
592
|
+
No metered provider activity exists for{" "}
|
|
593
|
+
{formatDateRangeLabel(filters.dateRange).toLowerCase()}.
|
|
677
594
|
</div>
|
|
678
595
|
)}
|
|
679
596
|
</div>
|
|
@@ -685,12 +602,13 @@ export function CostDashboard({
|
|
|
685
602
|
<div>
|
|
686
603
|
<SectionHeading className="mb-2">Model Breakdown</SectionHeading>
|
|
687
604
|
<p className="text-sm text-muted-foreground">
|
|
688
|
-
|
|
605
|
+
Spend-first concentration by model for{" "}
|
|
606
|
+
{formatDateRangeLabel(filters.dateRange).toLowerCase()}.
|
|
689
607
|
</p>
|
|
690
608
|
</div>
|
|
691
609
|
{filteredUnknownPricingRuns > 0 ? (
|
|
692
|
-
<Badge variant="
|
|
693
|
-
{filteredUnknownPricingRuns}
|
|
610
|
+
<Badge variant="secondary">
|
|
611
|
+
{filteredUnknownPricingRuns} pricing gap
|
|
694
612
|
{filteredUnknownPricingRuns === 1 ? "" : "s"}
|
|
695
613
|
</Badge>
|
|
696
614
|
) : null}
|
|
@@ -698,66 +616,49 @@ export function CostDashboard({
|
|
|
698
616
|
|
|
699
617
|
{modelBreakdown.length > 0 ? (
|
|
700
618
|
<div className="space-y-3">
|
|
701
|
-
{modelBreakdown.map((row) =>
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
{row.providerId} • {row.runs} run
|
|
725
|
-
{row.runs === 1 ? "" : "s"} •{" "}
|
|
726
|
-
{formatCompactCount(row.totalTokens)} tokens
|
|
727
|
-
</p>
|
|
728
|
-
</div>
|
|
729
|
-
<div className="text-left sm:text-right">
|
|
730
|
-
<p className="text-sm font-medium">{visual.valueLabel}</p>
|
|
731
|
-
<p className="text-xs text-muted-foreground">
|
|
732
|
-
{visual.basisLabel}
|
|
733
|
-
</p>
|
|
734
|
-
</div>
|
|
735
|
-
</div>
|
|
619
|
+
{modelBreakdown.map((row) => (
|
|
620
|
+
<div
|
|
621
|
+
key={`${row.runtimeId}-${row.modelId ?? "unknown"}`}
|
|
622
|
+
className="surface-card-muted rounded-2xl p-4"
|
|
623
|
+
>
|
|
624
|
+
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
|
|
625
|
+
<div className="min-w-0 space-y-1">
|
|
626
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
627
|
+
<p className="text-sm font-medium">
|
|
628
|
+
{row.modelId ?? "Unknown model"}
|
|
629
|
+
</p>
|
|
630
|
+
<Badge variant="outline">
|
|
631
|
+
{runtimeStates[row.runtimeId]?.label ?? row.runtimeId}
|
|
632
|
+
</Badge>
|
|
633
|
+
{row.unknownPricingRuns > 0 ? (
|
|
634
|
+
<Badge variant="secondary">Pricing unavailable</Badge>
|
|
635
|
+
) : null}
|
|
636
|
+
</div>
|
|
637
|
+
<p className="text-xs text-muted-foreground">
|
|
638
|
+
{row.providerId} • {row.runs} run
|
|
639
|
+
{row.runs === 1 ? "" : "s"} • {formatCompactCount(row.totalTokens)} tokens
|
|
640
|
+
</p>
|
|
641
|
+
</div>
|
|
736
642
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
) : (
|
|
752
|
-
<span>Cost and token totals are both shown above</span>
|
|
753
|
-
)}
|
|
754
|
-
</div>
|
|
755
|
-
</div>
|
|
643
|
+
<div className="grid gap-2 text-left sm:min-w-[180px] sm:text-right">
|
|
644
|
+
<div>
|
|
645
|
+
<p className="text-xs uppercase tracking-wide text-muted-foreground">
|
|
646
|
+
Spend
|
|
647
|
+
</p>
|
|
648
|
+
<p className="font-medium">{formatCurrencyMicros(row.costMicros)}</p>
|
|
649
|
+
</div>
|
|
650
|
+
<div>
|
|
651
|
+
<p className="text-xs uppercase tracking-wide text-muted-foreground">
|
|
652
|
+
Tokens
|
|
653
|
+
</p>
|
|
654
|
+
<p className="text-sm text-muted-foreground">
|
|
655
|
+
{formatCompactCount(row.totalTokens)}
|
|
656
|
+
</p>
|
|
756
657
|
</div>
|
|
757
658
|
</div>
|
|
758
659
|
</div>
|
|
759
|
-
|
|
760
|
-
|
|
660
|
+
</div>
|
|
661
|
+
))}
|
|
761
662
|
</div>
|
|
762
663
|
) : (
|
|
763
664
|
<div className="surface-card-muted rounded-2xl p-4 text-sm text-muted-foreground">
|
|
@@ -786,8 +687,8 @@ export function CostDashboard({
|
|
|
786
687
|
<TableHead>Activity</TableHead>
|
|
787
688
|
<TableHead>Linked entity</TableHead>
|
|
788
689
|
<TableHead>Runtime</TableHead>
|
|
789
|
-
<TableHead>Tokens</TableHead>
|
|
790
690
|
<TableHead>Cost</TableHead>
|
|
691
|
+
<TableHead>Tokens</TableHead>
|
|
791
692
|
<TableHead>Status</TableHead>
|
|
792
693
|
</TableRow>
|
|
793
694
|
</TableHeader>
|
|
@@ -825,19 +726,24 @@ export function CostDashboard({
|
|
|
825
726
|
<TableCell className="align-top">
|
|
826
727
|
<div className="space-y-1">
|
|
827
728
|
<p className="font-medium">
|
|
828
|
-
{
|
|
729
|
+
{runtimeStates[entry.runtimeId]?.label ?? entry.runtimeId}
|
|
730
|
+
</p>
|
|
731
|
+
<p className="text-xs text-muted-foreground">
|
|
732
|
+
{entry.providerId}
|
|
733
|
+
{runtimeStates[entry.runtimeId]?.billingMode === "subscription"
|
|
734
|
+
? " • plan priced"
|
|
735
|
+
: ""}
|
|
829
736
|
</p>
|
|
830
|
-
<p className="text-xs text-muted-foreground">{entry.providerId}</p>
|
|
831
737
|
</div>
|
|
832
738
|
</TableCell>
|
|
833
|
-
<TableCell className="align-top text-right">
|
|
834
|
-
{formatCompactCount(entry.totalTokens ?? 0)}
|
|
835
|
-
</TableCell>
|
|
836
739
|
<TableCell className="align-top text-right">
|
|
837
740
|
{entry.status === "unknown_pricing"
|
|
838
741
|
? "Unavailable"
|
|
839
742
|
: formatCurrencyMicros(entry.costMicros)}
|
|
840
743
|
</TableCell>
|
|
744
|
+
<TableCell className="align-top text-right text-muted-foreground">
|
|
745
|
+
{formatCompactCount(entry.totalTokens ?? 0)}
|
|
746
|
+
</TableCell>
|
|
841
747
|
<TableCell className="align-top">{statusBadge(entry.status)}</TableCell>
|
|
842
748
|
</TableRow>
|
|
843
749
|
))}
|