mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-17 03:10:38 +09:00
feat(costs): add billing, quota, and budget control plane
This commit is contained in:
parent
656b4659fc
commit
76e6cc08a6
91 changed files with 22406 additions and 769 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { deriveAgentUrlKey, deriveProjectUrlKey } from "@paperclipai/shared";
|
||||
import type { BillingType, FinanceDirection, FinanceEventKind } from "@paperclipai/shared";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
|
|
@ -53,6 +54,8 @@ export function providerDisplayName(provider: string): string {
|
|||
const map: Record<string, string> = {
|
||||
anthropic: "Anthropic",
|
||||
openai: "OpenAI",
|
||||
openrouter: "OpenRouter",
|
||||
chatgpt: "ChatGPT",
|
||||
google: "Google",
|
||||
cursor: "Cursor",
|
||||
jetbrains: "JetBrains AI",
|
||||
|
|
@ -60,6 +63,84 @@ export function providerDisplayName(provider: string): string {
|
|||
return map[provider.toLowerCase()] ?? provider;
|
||||
}
|
||||
|
||||
export function billingTypeDisplayName(billingType: BillingType): string {
|
||||
const map: Record<BillingType, string> = {
|
||||
metered_api: "Metered API",
|
||||
subscription_included: "Subscription",
|
||||
subscription_overage: "Subscription overage",
|
||||
credits: "Credits",
|
||||
fixed: "Fixed",
|
||||
unknown: "Unknown",
|
||||
};
|
||||
return map[billingType];
|
||||
}
|
||||
|
||||
export function quotaSourceDisplayName(source: string): string {
|
||||
const map: Record<string, string> = {
|
||||
"anthropic-oauth": "Anthropic OAuth",
|
||||
"claude-cli": "Claude CLI",
|
||||
"codex-rpc": "Codex app server",
|
||||
"codex-wham": "ChatGPT WHAM",
|
||||
};
|
||||
return map[source] ?? source;
|
||||
}
|
||||
|
||||
function coerceBillingType(value: unknown): BillingType | null {
|
||||
if (
|
||||
value === "metered_api" ||
|
||||
value === "subscription_included" ||
|
||||
value === "subscription_overage" ||
|
||||
value === "credits" ||
|
||||
value === "fixed" ||
|
||||
value === "unknown"
|
||||
) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function readRunCostUsd(payload: Record<string, unknown> | null): number {
|
||||
if (!payload) return 0;
|
||||
for (const key of ["costUsd", "cost_usd", "total_cost_usd"] as const) {
|
||||
const value = payload[key];
|
||||
if (typeof value === "number" && Number.isFinite(value)) return value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function visibleRunCostUsd(
|
||||
usage: Record<string, unknown> | null,
|
||||
result: Record<string, unknown> | null = null,
|
||||
): number {
|
||||
const billingType = coerceBillingType(usage?.billingType) ?? coerceBillingType(result?.billingType);
|
||||
if (billingType === "subscription_included") return 0;
|
||||
return readRunCostUsd(usage) || readRunCostUsd(result);
|
||||
}
|
||||
|
||||
export function financeEventKindDisplayName(eventKind: FinanceEventKind): string {
|
||||
const map: Record<FinanceEventKind, string> = {
|
||||
inference_charge: "Inference charge",
|
||||
platform_fee: "Platform fee",
|
||||
credit_purchase: "Credit purchase",
|
||||
credit_refund: "Credit refund",
|
||||
credit_expiry: "Credit expiry",
|
||||
byok_fee: "BYOK fee",
|
||||
gateway_overhead: "Gateway overhead",
|
||||
log_storage_charge: "Log storage",
|
||||
logpush_charge: "Logpush",
|
||||
provisioned_capacity_charge: "Provisioned capacity",
|
||||
training_charge: "Training",
|
||||
custom_model_import_charge: "Custom model import",
|
||||
custom_model_storage_charge: "Custom model storage",
|
||||
manual_adjustment: "Manual adjustment",
|
||||
};
|
||||
return map[eventKind];
|
||||
}
|
||||
|
||||
export function financeDirectionDisplayName(direction: FinanceDirection): string {
|
||||
return direction === "credit" ? "Credit" : "Debit";
|
||||
}
|
||||
|
||||
/** Build an issue URL using the human-readable identifier when available. */
|
||||
export function issueUrl(issue: { id: string; identifier?: string | null }): string {
|
||||
return `/issues/${issue.identifier ?? issue.id}`;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue