Q74: billing shows money spent against allocations, not an allocation ratio

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-08 12:32:32 -04:00
1 parent 844607eae9
commit daea9797d4
7 files changed
+38 -4

No files matched your search

+6
View File
@@ -8,6 +8,7 @@ type Row = Record<string, unknown>;
type ProviderUsageWindowRow = Pick<
AppSnapshot["providerRateLimits"][number],
"orgId" | "providerId" | "windowStart" | "windowEnd" | "requestCount" | "inputCharCount" | "outputCharCount"
| "costCents" | "inputTokens" | "outputTokens" | "requestsWithoutUsage" | "unpricedRequests"
>;
function all<T>(statement: StatementSync): T[] {
@@ -224,6 +225,11 @@ function buildProviderRateLimits(
outputCharCount,
remainingRequestCount,
exhausted: remainingRequestCount === 0,
costCents: usage?.costCents ?? 0,
inputTokens: usage?.inputTokens ?? 0,
outputTokens: usage?.outputTokens ?? 0,
requestsWithoutUsage: usage?.requestsWithoutUsage ?? 0,
unpricedRequests: usage?.unpricedRequests ?? 0,
};
});
}
+4
View File
@@ -32,6 +32,10 @@ export const zh: Record<string, string> = {
"superseded": "已被取代",
"stalled": "已停滞",
"Resume": "恢复",
"Spent this month": "本月已花费",
"${spent} of ${allocated} allocated to agents": "已花 ${spent} / 分配给 Agent 的 ${allocated}",
"{count} calls not priced": "{count} 次调用未计价",
"Allocations use {percent}% of department caps": "分配额度占部门上限的 {percent}%",
"Spent": "已花费",
"Tokens": "Token",
"not reported": "未报告",
+1
View File
@@ -67,6 +67,7 @@ export function SettingsPage({ snapshot, actions }: PageProps) {
catalog={snapshot.providerCatalog}
workspaces={workspaces}
providerRequests={providerRequests}
providerRateLimits={providerRateLimits}
hiringPolicies={hiringPolicies}
actions={actions}
/>
@@ -1,3 +1,4 @@
import type { ProviderRateLimitStateRecord } from "../../../../shared/provider-runtime-domain";
import type {
AgentRecord,
HiringPolicyRecord,
@@ -19,6 +20,7 @@ export function AdvancedPanel({
catalog,
workspaces,
providerRequests,
providerRateLimits,
hiringPolicies,
actions,
}: {
@@ -28,6 +30,7 @@ export function AdvancedPanel({
catalog: ProviderCatalogEntry[];
workspaces: WorkspaceRecord[];
providerRequests: ProviderRequestRecord[];
providerRateLimits: ProviderRateLimitStateRecord[];
hiringPolicies: HiringPolicyRecord[];
actions: SnapshotActions;
}) {
@@ -50,7 +53,7 @@ export function AdvancedPanel({
)}
<div style={{ marginTop: 28 }}>
<BillingPanel agents={agents} providers={providers} providerRequests={providerRequests} hiringPolicies={hiringPolicies} />
<BillingPanel agents={agents} providers={providers} providerRequests={providerRequests} providerRateLimits={providerRateLimits} hiringPolicies={hiringPolicies} />
</div>
</div>
);
@@ -2,19 +2,25 @@ import { Avatar, Pill } from "../../shared/ui";
import type { AgentRecord, HiringPolicyRecord, ProviderRecord, ProviderRequestRecord } from "../../../../shared/domain";
import { money } from "../types";
import { buildAgentSpend } from "./billing-helpers";
import type { ProviderRateLimitStateRecord } from "../../../../shared/provider-runtime-domain";
import { t } from "../../i18n/i18n";
export function BillingPanel({
agents,
providers,
providerRequests,
providerRateLimits,
hiringPolicies,
}: {
agents: AgentRecord[];
providers: ProviderRecord[];
providerRequests: ProviderRequestRecord[];
providerRateLimits: ProviderRateLimitStateRecord[];
hiringPolicies: HiringPolicyRecord[];
}) {
// Month to date, from the provider totals: every call counts, not only the ones loaded into the snapshot.
const spentCents = providerRateLimits.reduce((s, usage) => s + usage.costCents, 0);
const unpricedCalls = providerRateLimits.reduce((s, usage) => s + usage.unpricedRequests + usage.requestsWithoutUsage, 0);
const totalBudget = agents.reduce((sum, a) => sum + a.budgetCents, 0);
const totalCap = hiringPolicies.reduce((sum, p) => sum + p.monthlyBudgetLimitCents, 0);
const monthRequests = providerRequests.filter((r) => withinDays(r.createdAt, 30));
@@ -50,12 +56,16 @@ export function BillingPanel({
</div>
</div>
<div className="card" style={{ padding: 14 }}>
<div className="card-label" style={{ marginBottom: 8 }}>{t("Budget utilization")}</div>
<div className="card-label" style={{ marginBottom: 8 }}>{t("Spent this month")}</div>
<div style={{ height: 6, background: "var(--panel-3)", borderRadius: 3, overflow: "hidden", marginBottom: 8 }}>
<div style={{ width: `${totalCap > 0 ? Math.min(100, Math.round((totalBudget / totalCap) * 100)) : 0}%`, height: "100%", background: "var(--accent)" }} />
<div style={{ width: `${totalBudget > 0 ? Math.min(100, Math.round((spentCents / totalBudget) * 100)) : 0}%`, height: "100%", background: "var(--accent)" }} />
</div>
<div style={{ fontSize: 12, color: "var(--text-3)" }}>
{totalCap > 0 ? t("{percent}% of cap", { percent: Math.round((totalBudget / totalCap) * 100) }) : t("No budget cap set")}
{t("${spent} of ${allocated} allocated to agents", { spent: (spentCents / 100).toFixed(2), allocated: (totalBudget / 100).toFixed(2) })}
{unpricedCalls > 0 && <> · {t("{count} calls not priced", { count: unpricedCalls })}</>}
</div>
<div style={{ fontSize: 12, color: "var(--text-3)", marginTop: 4 }}>
{totalCap > 0 ? t("Allocations use {percent}% of department caps", { percent: Math.round((totalBudget / totalCap) * 100) }) : t("No budget cap set")}
</div>
</div>
</div>
+6
View File
@@ -10,6 +10,12 @@ export interface ProviderRateLimitStateRecord {
outputCharCount: number;
remainingRequestCount: number;
exhausted: boolean;
/** This month's provider-reported spend and tokens; calls without usage or prices are counted, not guessed. */
costCents: number;
inputTokens: number;
outputTokens: number;
requestsWithoutUsage: number;
unpricedRequests: number;
}
export interface ProviderObservedRateLimitState {
+4
View File
@@ -5,6 +5,7 @@ import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
import { ProviderRequestStore } from "../../src/main/services/provider-request-store";
import { SnapshotService } from "../../src/main/services/snapshot-service";
import { PROVIDER_EXECUTION_MODE } from "../../src/shared/domain";
describe("usage settlement", () => {
@@ -39,6 +40,9 @@ describe("usage settlement", () => {
`).get(provider.id)).toEqual({
request_count: 2, input_tokens: 1_000_000, output_tokens: 100_000, cached_input_tokens: 400_000, cost_cents: 342, requests_without_usage: 1, unpriced_requests: 0,
});
// The month's spend reaches the renderer from these totals, not from the capped list of recent requests.
const limits = new SnapshotService(connection.db).load().providerRateLimits.find((item) => item.providerId === provider.id);
expect(limits).toMatchObject({ costCents: 342, inputTokens: 1_000_000, outputTokens: 100_000, requestsWithoutUsage: 1, unpricedRequests: 0 });
connection.close();
});
});