@agent-native/dispatch 0.2.5 → 0.2.7
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/actions/list-dispatch-usage-metrics.js +1 -1
- package/dist/actions/list-dispatch-usage-metrics.js.map +1 -1
- package/dist/actions/view-screen.d.ts.map +1 -1
- package/dist/actions/view-screen.js +1 -0
- package/dist/actions/view-screen.js.map +1 -1
- package/dist/routes/pages/metrics.d.ts.map +1 -1
- package/dist/routes/pages/metrics.js +53 -18
- package/dist/routes/pages/metrics.js.map +1 -1
- package/dist/routes/pages/overview.js +1 -1
- package/dist/routes/pages/overview.js.map +1 -1
- package/dist/server/lib/usage-metrics-store.d.ts +2 -0
- package/dist/server/lib/usage-metrics-store.d.ts.map +1 -1
- package/dist/server/lib/usage-metrics-store.js +27 -1
- package/dist/server/lib/usage-metrics-store.js.map +1 -1
- package/package.json +2 -2
- package/src/actions/list-dispatch-usage-metrics.ts +1 -1
- package/src/actions/view-screen.ts +1 -0
- package/src/routes/pages/metrics.tsx +130 -27
- package/src/routes/pages/overview.tsx +3 -3
- package/src/server/lib/usage-metrics-store.ts +43 -1
|
@@ -77,7 +77,17 @@ interface RecentUsageMetric {
|
|
|
77
77
|
costCents: number;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
interface UsageBillingMode {
|
|
81
|
+
unit: "usd" | "builder-credits";
|
|
82
|
+
label: string;
|
|
83
|
+
shortLabel: string;
|
|
84
|
+
source: "estimated-provider-cost" | "builder-agent-credits";
|
|
85
|
+
hardCostMarginMultiplier?: number;
|
|
86
|
+
creditsPerUsd?: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
80
89
|
interface DispatchUsageMetrics {
|
|
90
|
+
billing?: UsageBillingMode;
|
|
81
91
|
sinceDays: number;
|
|
82
92
|
access: {
|
|
83
93
|
viewerEmail: string;
|
|
@@ -110,7 +120,37 @@ interface DispatchUsageMetrics {
|
|
|
110
120
|
|
|
111
121
|
const RANGES = [7, 30, 90] as const;
|
|
112
122
|
|
|
113
|
-
|
|
123
|
+
const USD_BILLING: UsageBillingMode = {
|
|
124
|
+
unit: "usd",
|
|
125
|
+
label: "Estimated spend",
|
|
126
|
+
shortLabel: "Cost",
|
|
127
|
+
source: "estimated-provider-cost",
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
function displayAmountFromCostCents(
|
|
131
|
+
cents: number,
|
|
132
|
+
billing: UsageBillingMode,
|
|
133
|
+
): number {
|
|
134
|
+
if (billing.unit !== "builder-credits") return cents;
|
|
135
|
+
const margin = billing.hardCostMarginMultiplier ?? 1.25;
|
|
136
|
+
const creditsPerUsd = billing.creditsPerUsd ?? 20;
|
|
137
|
+
const credits = (cents / 100) * margin * creditsPerUsd;
|
|
138
|
+
return credits <= 0 ? 0 : Math.ceil(credits * 1000) / 1000;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function formatCredits(credits: number): string {
|
|
142
|
+
if (!Number.isFinite(credits) || credits === 0) return "0 credits";
|
|
143
|
+
const maximumFractionDigits = credits < 1 ? 3 : credits < 10 ? 2 : 1;
|
|
144
|
+
const value = credits.toLocaleString(undefined, {
|
|
145
|
+
maximumFractionDigits,
|
|
146
|
+
});
|
|
147
|
+
return `${value} ${credits === 1 ? "credit" : "credits"}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function formatSpend(cents: number, billing: UsageBillingMode): string {
|
|
151
|
+
if (billing.unit === "builder-credits") {
|
|
152
|
+
return formatCredits(displayAmountFromCostCents(cents, billing));
|
|
153
|
+
}
|
|
114
154
|
if (!Number.isFinite(cents) || cents === 0) return "$0.00";
|
|
115
155
|
if (Math.abs(cents) < 1) return `${cents.toFixed(3)}¢`;
|
|
116
156
|
if (Math.abs(cents) < 100) return `${cents.toFixed(2)}¢`;
|
|
@@ -150,8 +190,15 @@ function displayApp(value: string | null | undefined): string {
|
|
|
150
190
|
return trimmed;
|
|
151
191
|
}
|
|
152
192
|
|
|
153
|
-
function
|
|
154
|
-
|
|
193
|
+
function maxSpend(
|
|
194
|
+
rows: Array<{ costCents: number }>,
|
|
195
|
+
billing: UsageBillingMode,
|
|
196
|
+
): number {
|
|
197
|
+
return rows.reduce(
|
|
198
|
+
(max, row) =>
|
|
199
|
+
Math.max(max, displayAmountFromCostCents(row.costCents, billing)),
|
|
200
|
+
0,
|
|
201
|
+
);
|
|
155
202
|
}
|
|
156
203
|
|
|
157
204
|
function barWidth(value: number, max: number): string {
|
|
@@ -257,8 +304,14 @@ function LoadingMetrics() {
|
|
|
257
304
|
);
|
|
258
305
|
}
|
|
259
306
|
|
|
260
|
-
function AppSpendRows({
|
|
261
|
-
|
|
307
|
+
function AppSpendRows({
|
|
308
|
+
rows,
|
|
309
|
+
billing,
|
|
310
|
+
}: {
|
|
311
|
+
rows: UsageMetricBucket[];
|
|
312
|
+
billing: UsageBillingMode;
|
|
313
|
+
}) {
|
|
314
|
+
const max = maxSpend(rows, billing);
|
|
262
315
|
if (rows.length === 0) {
|
|
263
316
|
return (
|
|
264
317
|
<div className="rounded-lg border border-dashed px-4 py-8 text-sm text-muted-foreground">
|
|
@@ -282,7 +335,7 @@ function AppSpendRows({ rows }: { rows: UsageMetricBucket[] }) {
|
|
|
282
335
|
</div>
|
|
283
336
|
<div className="shrink-0 text-right">
|
|
284
337
|
<div className="font-medium tabular-nums text-foreground">
|
|
285
|
-
{
|
|
338
|
+
{formatSpend(row.costCents, billing)}
|
|
286
339
|
</div>
|
|
287
340
|
<div className="text-xs text-muted-foreground">
|
|
288
341
|
{formatNumber(row.calls)} calls
|
|
@@ -292,7 +345,12 @@ function AppSpendRows({ rows }: { rows: UsageMetricBucket[] }) {
|
|
|
292
345
|
<div className="h-2 overflow-hidden rounded-full bg-muted">
|
|
293
346
|
<div
|
|
294
347
|
className="h-full rounded-full bg-foreground"
|
|
295
|
-
style={{
|
|
348
|
+
style={{
|
|
349
|
+
width: barWidth(
|
|
350
|
+
displayAmountFromCostCents(row.costCents, billing),
|
|
351
|
+
max,
|
|
352
|
+
),
|
|
353
|
+
}}
|
|
296
354
|
/>
|
|
297
355
|
</div>
|
|
298
356
|
</div>
|
|
@@ -335,7 +393,13 @@ function DailyActivity({ rows }: { rows: DailyUsageMetric[] }) {
|
|
|
335
393
|
);
|
|
336
394
|
}
|
|
337
395
|
|
|
338
|
-
function AppAccessTable({
|
|
396
|
+
function AppAccessTable({
|
|
397
|
+
rows,
|
|
398
|
+
billing,
|
|
399
|
+
}: {
|
|
400
|
+
rows: AppAccessMetric[];
|
|
401
|
+
billing: UsageBillingMode;
|
|
402
|
+
}) {
|
|
339
403
|
const visibleRows = rows.filter((row) => !row.isDispatch);
|
|
340
404
|
if (visibleRows.length === 0) {
|
|
341
405
|
return (
|
|
@@ -353,7 +417,9 @@ function AppAccessTable({ rows }: { rows: AppAccessMetric[] }) {
|
|
|
353
417
|
<th className="px-2 py-2 font-medium">Access</th>
|
|
354
418
|
<th className="px-2 py-2 text-right font-medium">Users</th>
|
|
355
419
|
<th className="px-2 py-2 text-right font-medium">Chats</th>
|
|
356
|
-
<th className="px-2 py-2 text-right font-medium">
|
|
420
|
+
<th className="px-2 py-2 text-right font-medium">
|
|
421
|
+
{billing.shortLabel}
|
|
422
|
+
</th>
|
|
357
423
|
<th className="px-2 py-2 text-right font-medium">Last activity</th>
|
|
358
424
|
</tr>
|
|
359
425
|
</thead>
|
|
@@ -385,7 +451,7 @@ function AppAccessTable({ rows }: { rows: AppAccessMetric[] }) {
|
|
|
385
451
|
{formatNumber(row.chatCalls)}
|
|
386
452
|
</td>
|
|
387
453
|
<td className="px-2 py-3 text-right tabular-nums">
|
|
388
|
-
{
|
|
454
|
+
{formatSpend(row.costCents, billing)}
|
|
389
455
|
</td>
|
|
390
456
|
<td className="px-2 py-3 text-right text-muted-foreground">
|
|
391
457
|
{timeAgo(row.lastActiveAt)}
|
|
@@ -398,7 +464,13 @@ function AppAccessTable({ rows }: { rows: AppAccessMetric[] }) {
|
|
|
398
464
|
);
|
|
399
465
|
}
|
|
400
466
|
|
|
401
|
-
function UserTable({
|
|
467
|
+
function UserTable({
|
|
468
|
+
rows,
|
|
469
|
+
billing,
|
|
470
|
+
}: {
|
|
471
|
+
rows: UserUsageMetric[];
|
|
472
|
+
billing: UsageBillingMode;
|
|
473
|
+
}) {
|
|
402
474
|
if (rows.length === 0) {
|
|
403
475
|
return (
|
|
404
476
|
<div className="rounded-lg border border-dashed px-4 py-8 text-sm text-muted-foreground">
|
|
@@ -417,7 +489,9 @@ function UserTable({ rows }: { rows: UserUsageMetric[] }) {
|
|
|
417
489
|
<th className="px-2 py-2 text-right font-medium">Chats</th>
|
|
418
490
|
<th className="px-2 py-2 text-right font-medium">Threads</th>
|
|
419
491
|
<th className="px-2 py-2 text-right font-medium">Tokens</th>
|
|
420
|
-
<th className="px-2 py-2 text-right font-medium">
|
|
492
|
+
<th className="px-2 py-2 text-right font-medium">
|
|
493
|
+
{billing.shortLabel}
|
|
494
|
+
</th>
|
|
421
495
|
</tr>
|
|
422
496
|
</thead>
|
|
423
497
|
<tbody>
|
|
@@ -447,7 +521,7 @@ function UserTable({ rows }: { rows: UserUsageMetric[] }) {
|
|
|
447
521
|
{formatTokens(row.inputTokens + row.outputTokens)}
|
|
448
522
|
</td>
|
|
449
523
|
<td className="px-2 py-3 text-right tabular-nums">
|
|
450
|
-
{
|
|
524
|
+
{formatSpend(row.costCents, billing)}
|
|
451
525
|
</td>
|
|
452
526
|
</tr>
|
|
453
527
|
))}
|
|
@@ -460,11 +534,13 @@ function UserTable({ rows }: { rows: UserUsageMetric[] }) {
|
|
|
460
534
|
function CompactBreakdown({
|
|
461
535
|
rows,
|
|
462
536
|
empty,
|
|
537
|
+
billing,
|
|
463
538
|
}: {
|
|
464
539
|
rows: UsageMetricBucket[];
|
|
465
540
|
empty: string;
|
|
541
|
+
billing: UsageBillingMode;
|
|
466
542
|
}) {
|
|
467
|
-
const max =
|
|
543
|
+
const max = maxSpend(rows, billing);
|
|
468
544
|
if (rows.length === 0) {
|
|
469
545
|
return <div className="text-sm text-muted-foreground">{empty}</div>;
|
|
470
546
|
}
|
|
@@ -477,13 +553,18 @@ function CompactBreakdown({
|
|
|
477
553
|
{row.label}
|
|
478
554
|
</span>
|
|
479
555
|
<span className="shrink-0 tabular-nums text-muted-foreground">
|
|
480
|
-
{
|
|
556
|
+
{formatSpend(row.costCents, billing)}
|
|
481
557
|
</span>
|
|
482
558
|
</div>
|
|
483
559
|
<div className="h-1.5 overflow-hidden rounded-full bg-muted">
|
|
484
560
|
<div
|
|
485
561
|
className="h-full rounded-full bg-muted-foreground"
|
|
486
|
-
style={{
|
|
562
|
+
style={{
|
|
563
|
+
width: barWidth(
|
|
564
|
+
displayAmountFromCostCents(row.costCents, billing),
|
|
565
|
+
max,
|
|
566
|
+
),
|
|
567
|
+
}}
|
|
487
568
|
/>
|
|
488
569
|
</div>
|
|
489
570
|
</div>
|
|
@@ -492,7 +573,13 @@ function CompactBreakdown({
|
|
|
492
573
|
);
|
|
493
574
|
}
|
|
494
575
|
|
|
495
|
-
function RecentTable({
|
|
576
|
+
function RecentTable({
|
|
577
|
+
rows,
|
|
578
|
+
billing,
|
|
579
|
+
}: {
|
|
580
|
+
rows: RecentUsageMetric[];
|
|
581
|
+
billing: UsageBillingMode;
|
|
582
|
+
}) {
|
|
496
583
|
if (rows.length === 0) {
|
|
497
584
|
return (
|
|
498
585
|
<div className="rounded-lg border border-dashed px-4 py-8 text-sm text-muted-foreground">
|
|
@@ -510,7 +597,9 @@ function RecentTable({ rows }: { rows: RecentUsageMetric[] }) {
|
|
|
510
597
|
<th className="px-2 py-2 font-medium">App</th>
|
|
511
598
|
<th className="px-2 py-2 font-medium">Label</th>
|
|
512
599
|
<th className="px-2 py-2 font-medium">Model</th>
|
|
513
|
-
<th className="px-2 py-2 text-right font-medium">
|
|
600
|
+
<th className="px-2 py-2 text-right font-medium">
|
|
601
|
+
{billing.shortLabel}
|
|
602
|
+
</th>
|
|
514
603
|
</tr>
|
|
515
604
|
</thead>
|
|
516
605
|
<tbody>
|
|
@@ -534,7 +623,7 @@ function RecentTable({ rows }: { rows: RecentUsageMetric[] }) {
|
|
|
534
623
|
</div>
|
|
535
624
|
</td>
|
|
536
625
|
<td className="px-2 py-3 text-right tabular-nums">
|
|
537
|
-
{
|
|
626
|
+
{formatSpend(row.costCents, billing)}
|
|
538
627
|
</td>
|
|
539
628
|
</tr>
|
|
540
629
|
))}
|
|
@@ -552,6 +641,7 @@ export default function MetricsRoute() {
|
|
|
552
641
|
{ refetchInterval: 30_000 },
|
|
553
642
|
);
|
|
554
643
|
const metrics = data as DispatchUsageMetrics | undefined;
|
|
644
|
+
const billing = metrics?.billing ?? USD_BILLING;
|
|
555
645
|
const totalTokens = useMemo(() => {
|
|
556
646
|
if (!metrics) return 0;
|
|
557
647
|
return (
|
|
@@ -565,7 +655,11 @@ export default function MetricsRoute() {
|
|
|
565
655
|
return (
|
|
566
656
|
<DispatchShell
|
|
567
657
|
title="Metrics"
|
|
568
|
-
description=
|
|
658
|
+
description={
|
|
659
|
+
billing.unit === "builder-credits"
|
|
660
|
+
? "Workspace-wide Builder.io credit spend, chat volume, user activity, and app access."
|
|
661
|
+
: "Workspace-wide LLM spend, chat volume, user activity, and app access."
|
|
662
|
+
}
|
|
569
663
|
>
|
|
570
664
|
<div className="space-y-4">
|
|
571
665
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
@@ -593,8 +687,8 @@ export default function MetricsRoute() {
|
|
|
593
687
|
<>
|
|
594
688
|
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-5">
|
|
595
689
|
<MetricCard
|
|
596
|
-
label=
|
|
597
|
-
value={
|
|
690
|
+
label={billing.label}
|
|
691
|
+
value={formatSpend(metrics.totals.costCents, billing)}
|
|
598
692
|
detail={`${formatTokens(totalTokens)} total tokens`}
|
|
599
693
|
icon={<IconCoin size={17} />}
|
|
600
694
|
/>
|
|
@@ -625,8 +719,15 @@ export default function MetricsRoute() {
|
|
|
625
719
|
</div>
|
|
626
720
|
|
|
627
721
|
<div className="grid gap-4 xl:grid-cols-[minmax(0,1.35fr)_minmax(320px,0.65fr)]">
|
|
628
|
-
<Panel
|
|
629
|
-
|
|
722
|
+
<Panel
|
|
723
|
+
title={
|
|
724
|
+
billing.unit === "builder-credits"
|
|
725
|
+
? "Credit Spend By App"
|
|
726
|
+
: "Spend By App"
|
|
727
|
+
}
|
|
728
|
+
icon={<IconChartBar size={16} />}
|
|
729
|
+
>
|
|
730
|
+
<AppSpendRows rows={metrics.byApp} billing={billing} />
|
|
630
731
|
</Panel>
|
|
631
732
|
<Panel title="Daily Activity" icon={<IconClockHour4 size={16} />}>
|
|
632
733
|
<DailyActivity rows={metrics.daily} />
|
|
@@ -634,11 +735,11 @@ export default function MetricsRoute() {
|
|
|
634
735
|
</div>
|
|
635
736
|
|
|
636
737
|
<Panel title="Access By App" icon={<IconApps size={16} />}>
|
|
637
|
-
<AppAccessTable rows={metrics.appAccess} />
|
|
738
|
+
<AppAccessTable rows={metrics.appAccess} billing={billing} />
|
|
638
739
|
</Panel>
|
|
639
740
|
|
|
640
741
|
<Panel title="Users" icon={<IconUsersGroup size={16} />}>
|
|
641
|
-
<UserTable rows={metrics.byUser} />
|
|
742
|
+
<UserTable rows={metrics.byUser} billing={billing} />
|
|
642
743
|
</Panel>
|
|
643
744
|
|
|
644
745
|
<div className="grid gap-4 lg:grid-cols-2">
|
|
@@ -646,18 +747,20 @@ export default function MetricsRoute() {
|
|
|
646
747
|
<CompactBreakdown
|
|
647
748
|
rows={metrics.byModel}
|
|
648
749
|
empty="No model usage in this window."
|
|
750
|
+
billing={billing}
|
|
649
751
|
/>
|
|
650
752
|
</Panel>
|
|
651
753
|
<Panel title="Work Types" icon={<IconActivity size={16} />}>
|
|
652
754
|
<CompactBreakdown
|
|
653
755
|
rows={metrics.byLabel}
|
|
654
756
|
empty="No labeled usage in this window."
|
|
757
|
+
billing={billing}
|
|
655
758
|
/>
|
|
656
759
|
</Panel>
|
|
657
760
|
</div>
|
|
658
761
|
|
|
659
762
|
<Panel title="Recent LLM Calls" icon={<IconActivity size={16} />}>
|
|
660
|
-
<RecentTable rows={metrics.recent} />
|
|
763
|
+
<RecentTable rows={metrics.recent} billing={billing} />
|
|
661
764
|
</Panel>
|
|
662
765
|
</>
|
|
663
766
|
) : null}
|
|
@@ -435,15 +435,15 @@ function StepRow({ step }: { step: ChecklistStep }) {
|
|
|
435
435
|
<div
|
|
436
436
|
className={`flex items-start gap-4 rounded-xl border px-5 py-4 ${done ? "border-border/50 bg-muted/20" : "bg-card"}`}
|
|
437
437
|
>
|
|
438
|
-
{/*
|
|
438
|
+
{/* Status marker */}
|
|
439
439
|
<div className="flex-none pt-0.5">
|
|
440
440
|
{done ? (
|
|
441
441
|
<div className="flex h-7 w-7 items-center justify-center rounded-full bg-emerald-500/15 text-emerald-600 dark:text-emerald-400">
|
|
442
442
|
<IconCheck size={16} strokeWidth={2.5} />
|
|
443
443
|
</div>
|
|
444
444
|
) : (
|
|
445
|
-
<div className="flex h-7 w-7 items-center justify-center rounded-full border
|
|
446
|
-
{
|
|
445
|
+
<div className="flex h-7 w-7 items-center justify-center rounded-full border border-muted-foreground/30 text-muted-foreground">
|
|
446
|
+
<IconListCheck size={15} />
|
|
447
447
|
</div>
|
|
448
448
|
)}
|
|
449
449
|
</div>
|
|
@@ -1,5 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
getUsageSummary,
|
|
3
|
+
usageBillingForEngine,
|
|
4
|
+
type UsageBillingMode,
|
|
5
|
+
} from "@agent-native/core/usage";
|
|
2
6
|
import { getDbExec } from "@agent-native/core/db";
|
|
7
|
+
import {
|
|
8
|
+
detectEngineFromEnv,
|
|
9
|
+
detectEngineFromUserSecrets,
|
|
10
|
+
getAgentEngineEntry,
|
|
11
|
+
isAgentEngineSettingConfigured,
|
|
12
|
+
isStoredEngineUsable,
|
|
13
|
+
registerBuiltinEngines,
|
|
14
|
+
} from "@agent-native/core/agent/engine";
|
|
15
|
+
import { getSetting } from "@agent-native/core/settings";
|
|
3
16
|
import { currentOrgId, currentOwnerEmail } from "./dispatch-store.js";
|
|
4
17
|
import {
|
|
5
18
|
listWorkspaceApps,
|
|
@@ -8,6 +21,8 @@ import {
|
|
|
8
21
|
|
|
9
22
|
const DAY_MS = 86_400_000;
|
|
10
23
|
|
|
24
|
+
registerBuiltinEngines();
|
|
25
|
+
|
|
11
26
|
export interface UsageMetricBucket {
|
|
12
27
|
key: string;
|
|
13
28
|
label: string;
|
|
@@ -70,6 +85,7 @@ export interface RecentUsageMetric {
|
|
|
70
85
|
}
|
|
71
86
|
|
|
72
87
|
export interface DispatchUsageMetrics {
|
|
88
|
+
billing: UsageBillingMode;
|
|
73
89
|
sinceMs: number;
|
|
74
90
|
sinceDays: number;
|
|
75
91
|
generatedAt: number;
|
|
@@ -159,6 +175,30 @@ function isEnvAdmin(email: string): boolean {
|
|
|
159
175
|
].includes(normalized);
|
|
160
176
|
}
|
|
161
177
|
|
|
178
|
+
async function detectUsageEngineName(): Promise<string | null> {
|
|
179
|
+
try {
|
|
180
|
+
const stored = (await getSetting("agent-engine")) as {
|
|
181
|
+
engine?: string;
|
|
182
|
+
} | null;
|
|
183
|
+
if (isAgentEngineSettingConfigured(stored)) {
|
|
184
|
+
return (stored as { engine: string }).engine;
|
|
185
|
+
}
|
|
186
|
+
if (stored && typeof stored.engine === "string") {
|
|
187
|
+
const entry = getAgentEngineEntry(stored.engine);
|
|
188
|
+
if (entry && isStoredEngineUsable(stored, entry)) {
|
|
189
|
+
return stored.engine;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const detectedFromUser = await detectEngineFromUserSecrets();
|
|
194
|
+
if (detectedFromUser) return detectedFromUser.name;
|
|
195
|
+
|
|
196
|
+
return detectEngineFromEnv()?.name ?? null;
|
|
197
|
+
} catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
162
202
|
async function queryRows<T extends Record<string, unknown>>(
|
|
163
203
|
sql: string,
|
|
164
204
|
args: unknown[] = [],
|
|
@@ -353,6 +393,7 @@ export async function listDispatchUsageMetrics(input: {
|
|
|
353
393
|
const { viewerEmail, orgId, role } = await assertCanViewMetrics();
|
|
354
394
|
const sinceDays = Math.max(1, Math.min(365, input.sinceDays ?? 30));
|
|
355
395
|
const sinceMs = Date.now() - sinceDays * DAY_MS;
|
|
396
|
+
const billing = usageBillingForEngine(await detectUsageEngineName());
|
|
356
397
|
|
|
357
398
|
// Initializes token_usage on fresh deployments before the read-only
|
|
358
399
|
// aggregate queries below. The fake owner avoids changing visible data.
|
|
@@ -568,6 +609,7 @@ export async function listDispatchUsageMetrics(input: {
|
|
|
568
609
|
);
|
|
569
610
|
|
|
570
611
|
return {
|
|
612
|
+
billing,
|
|
571
613
|
sinceMs,
|
|
572
614
|
sinceDays,
|
|
573
615
|
generatedAt: Date.now(),
|