Files
wehub-resource-sync e30e75b5d4
Changesets / Create Version PR (push) Has been cancelled
Deploy Shadcn Registry / Deploy Production (push) Has been cancelled
Template Metrics / LOC + Bundle Size (push) Has been cancelled
Code Quality / Oxlint + Oxfmt (push) Has been cancelled
Code Quality / Template Sync (push) Has been cancelled
Code Quality / Build Changed Packages (push) Has been cancelled
Code Quality / Test Changed Packages (push) Has been cancelled
Deploy Expo Example / Deploy Production (push) Has been cancelled
Deploy Ink Example / Deploy Production (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.12) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

131 lines
3.9 KiB
TypeScript

export type BudgetDenyCode =
| "CHAT_TURN_LIMIT"
| "CHAT_BUDGET_EXCEEDED"
| "DAILY_BUDGET_EXCEEDED"
| "DAILY_CHAT_LIMIT";
export type BudgetLimits = {
chatBudgetMicroUsd: number;
dailyBudgetMicroUsd: number;
maxTurnsPerChat: number;
maxChatsPerDay: number;
};
export type BudgetSnapshot = {
sessionTurns: number;
sessionSpentMicroUsd: number;
dailySpentMicroUsd: number;
dailyChatCount: number;
isNewSession: boolean;
};
export type TokenUsage = {
inputTokens?: number | undefined;
outputTokens?: number | undefined;
};
const DEFAULT_INPUT_MICRO_USD_PER_M = 250_000;
const DEFAULT_OUTPUT_MICRO_USD_PER_M = 2_000_000;
export function usdToMicroUsd(usd: number): number {
return Math.round(usd * 1_000_000);
}
/** Product defaults — used when env vars are unset. */
export const DEFAULT_BUDGET_LIMITS: BudgetLimits = {
chatBudgetMicroUsd: usdToMicroUsd(1),
dailyBudgetMicroUsd: usdToMicroUsd(2),
maxTurnsPerChat: 8,
maxChatsPerDay: 10,
};
export function parseUsdEnv(name: string, fallbackUsd: number): number {
const raw = process.env[name];
if (!raw) return usdToMicroUsd(fallbackUsd);
const parsed = Number(raw);
if (!Number.isFinite(parsed) || parsed <= 0)
return usdToMicroUsd(fallbackUsd);
return usdToMicroUsd(parsed);
}
export function parseIntEnv(name: string, fallback: number): number {
const raw = process.env[name];
if (!raw) return fallback;
const parsed = Number.parseInt(raw, 10);
if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
return parsed;
}
export function getBudgetLimits(): BudgetLimits {
return {
chatBudgetMicroUsd: parseUsdEnv(
"XULUX_CHAT_BUDGET_USD",
DEFAULT_BUDGET_LIMITS.chatBudgetMicroUsd / 1_000_000,
),
dailyBudgetMicroUsd: parseUsdEnv(
"XULUX_DAILY_BUDGET_USD",
DEFAULT_BUDGET_LIMITS.dailyBudgetMicroUsd / 1_000_000,
),
maxTurnsPerChat: parseIntEnv(
"XULUX_MAX_TURNS_PER_CHAT",
DEFAULT_BUDGET_LIMITS.maxTurnsPerChat,
),
maxChatsPerDay: parseIntEnv(
"XULUX_MAX_CHATS_PER_DAY",
DEFAULT_BUDGET_LIMITS.maxChatsPerDay,
),
};
}
export function estimateMicroUsd(usage: TokenUsage, _modelId?: string): number {
const inputTokens = usage.inputTokens ?? 0;
const outputTokens = usage.outputTokens ?? 0;
const inputMicro = (inputTokens * DEFAULT_INPUT_MICRO_USD_PER_M) / 1_000_000;
const outputMicro =
(outputTokens * DEFAULT_OUTPUT_MICRO_USD_PER_M) / 1_000_000;
return Math.max(1, Math.ceil(inputMicro + outputMicro));
}
export function evaluateBudget(
snap: BudgetSnapshot,
limits: BudgetLimits,
): BudgetDenyCode | null {
if (snap.dailySpentMicroUsd >= limits.dailyBudgetMicroUsd) {
return "DAILY_BUDGET_EXCEEDED";
}
if (snap.sessionTurns >= limits.maxTurnsPerChat) {
return "CHAT_TURN_LIMIT";
}
if (snap.sessionSpentMicroUsd >= limits.chatBudgetMicroUsd) {
return "CHAT_BUDGET_EXCEEDED";
}
if (snap.isNewSession && snap.dailyChatCount >= limits.maxChatsPerDay) {
return "DAILY_CHAT_LIMIT";
}
return null;
}
export function budgetDenyMessage(
code: BudgetDenyCode,
limits: BudgetLimits,
): string {
switch (code) {
case "CHAT_TURN_LIMIT":
return `This chat reached the ${limits.maxTurnsPerChat}-message limit. Start a new chat to continue.`;
case "CHAT_BUDGET_EXCEEDED":
return `This chat reached the ~$${limits.chatBudgetMicroUsd / 1_000_000} usage limit. Start a new chat to continue.`;
case "DAILY_BUDGET_EXCEEDED":
return `Daily usage limit reached (~$${limits.dailyBudgetMicroUsd / 1_000_000}). Try again tomorrow.`;
case "DAILY_CHAT_LIMIT":
return `You reached the ${limits.maxChatsPerDay}-chat limit for today. Try again tomorrow.`;
}
}
export function isDailyLimitCode(code: BudgetDenyCode): boolean {
return code === "DAILY_BUDGET_EXCEEDED" || code === "DAILY_CHAT_LIMIT";
}
export function utcDateKey(date = new Date()): string {
return date.toISOString().slice(0, 10);
}