Files
routstr-core/ui/lib/services/costValidation.ts
T
2025-10-21 22:07:47 +02:00

193 lines
5.2 KiB
TypeScript

import { type Model } from '@/lib/api/schemas/models';
export interface CostCalculationInput {
inputTokens: number;
outputTokens: number;
model: Model;
}
export interface CostCalculationResult {
inputCost: number;
outputCost: number;
baseCost: number; // Sum of input and output costs
minCostPerRequest: number;
finalCost: number; // Max of baseCost and minCostPerRequest
isMinimumApplied: boolean; // Whether minimum cost was applied
}
export interface RequestCostValidation {
isValid: boolean;
actualCost: number;
minimumCost: number;
message?: string;
}
/**
* Calculate the cost for a model request
*/
export function calculateRequestCost(
input: CostCalculationInput
): CostCalculationResult {
const { inputTokens, outputTokens, model } = input;
// Calculate base costs based on token usage (per 1M tokens)
const inputCost = (inputTokens / 1000000) * model.input_cost;
const outputCost = (outputTokens / 1000000) * model.output_cost;
const baseCost = inputCost + outputCost;
// Apply minimum cost per request
const minCostPerRequest = model.min_cost_per_request || 0;
const finalCost = Math.max(baseCost, minCostPerRequest);
const isMinimumApplied = finalCost > baseCost;
return {
inputCost,
outputCost,
baseCost,
minCostPerRequest,
finalCost,
isMinimumApplied,
};
}
/**
* Validate that a request meets the minimum cost requirements
*/
export function validateRequestCost(
model: Model,
inputTokens: number,
outputTokens: number
): RequestCostValidation {
const calculation = calculateRequestCost({
inputTokens,
outputTokens,
model,
});
const isValid = calculation.finalCost >= calculation.minCostPerRequest;
if (!isValid) {
return {
isValid: false,
actualCost: calculation.baseCost,
minimumCost: calculation.minCostPerRequest,
message: `Request cost ($${calculation.baseCost.toFixed(6)}) is below the minimum required cost ($${calculation.minCostPerRequest.toFixed(6)}) for model "${model.name}".`,
};
}
return {
isValid: true,
actualCost: calculation.finalCost,
minimumCost: calculation.minCostPerRequest,
message: calculation.isMinimumApplied
? `Minimum cost of $${calculation.minCostPerRequest.toFixed(6)} applied.`
: undefined,
};
}
/**
* Estimate the minimum tokens needed to meet the minimum cost requirement
*/
export function estimateMinimumTokensForCost(model: Model): {
inputTokensOnly: number;
outputTokensOnly: number;
balancedTokens: { input: number; output: number };
} {
const minCost = model.min_cost_per_request || 0;
if (minCost === 0) {
return {
inputTokensOnly: 0,
outputTokensOnly: 0,
balancedTokens: { input: 0, output: 0 },
};
}
// Calculate tokens needed if using only input tokens (per 1M tokens)
const inputTokensOnly =
model.input_cost > 0
? Math.ceil((minCost * 1000000) / model.input_cost)
: 0;
// Calculate tokens needed if using only output tokens (per 1M tokens)
const outputTokensOnly =
model.output_cost > 0
? Math.ceil((minCost * 1000000) / model.output_cost)
: 0;
// Calculate balanced approach (50/50 split)
const halfCost = minCost / 2;
const balancedInput =
model.input_cost > 0
? Math.ceil((halfCost * 1000000) / model.input_cost)
: 0;
const balancedOutput =
model.output_cost > 0
? Math.ceil((halfCost * 1000000) / model.output_cost)
: 0;
return {
inputTokensOnly,
outputTokensOnly,
balancedTokens: { input: balancedInput, output: balancedOutput },
};
}
/**
* Format cost for display
*/
export function formatCost(cost: number): string {
if (cost === 0) return 'Free';
if (cost < 0.000001) return `$${cost.toFixed(8)}`;
if (cost < 0.001) return `$${cost.toFixed(6)}`;
if (cost < 0.01) return `$${cost.toFixed(4)}`;
return `$${cost.toFixed(3)}`;
}
/**
* Format cost breakdown for display
*/
export function formatCostBreakdown(
calculation: CostCalculationResult
): string {
const parts = [];
if (calculation.inputCost > 0) {
parts.push(`Input: ${formatCost(calculation.inputCost)}`);
}
if (calculation.outputCost > 0) {
parts.push(`Output: ${formatCost(calculation.outputCost)}`);
}
if (calculation.isMinimumApplied) {
parts.push(`Minimum: ${formatCost(calculation.minCostPerRequest)}`);
}
return parts.join(' | ');
}
/**
* Calculate minimum cost per request based on context length and input cost
* Formula: (context_length / 1,000,000) * input_cost
* This represents the cost if the full context window is used for input
*/
export function calculateMinCostPerRequest(
contextLength: number | undefined,
inputCost: number
): number {
// If no context length is specified, use a reasonable default minimum
if (!contextLength || contextLength <= 0) {
// Use a minimum of 1000 tokens worth of input cost as base minimum
return (1000 / 1_000_000) * inputCost;
}
// Calculate based on full context window usage
const costForFullContext = (contextLength / 1_000_000) * inputCost;
// Ensure there's always a reasonable minimum cost (at least 100 tokens worth)
const minimumFloor = (100 / 1_000_000) * inputCost;
return Math.max(costForFullContext, minimumFloor);
}