import { BILLING_CACHE_VAR_MAP } from './billing-expr' export const CACHE_MODE_TIMED = 'timed' export const CACHE_MODE_GENERIC = 'generic' export type CacheMode = typeof CACHE_MODE_TIMED | typeof CACHE_MODE_GENERIC export type TierConditionInput = { var: 'p' | 'c' | 'len' op: '<' | '<=' | '>' | '>=' value: number | string } export type VisualTier = { label: string conditions: TierConditionInput[] input_unit_cost: number output_unit_cost: number cache_mode: CacheMode cache_read_unit_cost?: number cache_create_unit_cost?: number cache_create_1h_unit_cost?: number image_unit_cost?: number image_output_unit_cost?: number audio_input_unit_cost?: number audio_output_unit_cost?: number [field: string]: unknown } export type VisualConfig = { tiers: VisualTier[] } export function getTierCacheMode( tier: Partial | null | undefined ): CacheMode { if (tier?.cache_mode === CACHE_MODE_TIMED) return CACHE_MODE_TIMED if (tier?.cache_mode === CACHE_MODE_GENERIC) return CACHE_MODE_GENERIC return Number(tier?.cache_create_1h_unit_cost) > 0 ? CACHE_MODE_TIMED : CACHE_MODE_GENERIC } export function normalizeVisualTier( tier: Partial = {} ): VisualTier { return { label: tier.label ?? '', input_unit_cost: Number(tier.input_unit_cost) || 0, output_unit_cost: Number(tier.output_unit_cost) || 0, cache_mode: getTierCacheMode(tier), conditions: Array.isArray(tier.conditions) ? tier.conditions : [], ...tier, cache_read_unit_cost: Number(tier.cache_read_unit_cost) || 0, cache_create_unit_cost: Number(tier.cache_create_unit_cost) || 0, cache_create_1h_unit_cost: Number(tier.cache_create_1h_unit_cost) || 0, image_unit_cost: Number(tier.image_unit_cost) || 0, image_output_unit_cost: Number(tier.image_output_unit_cost) || 0, audio_input_unit_cost: Number(tier.audio_input_unit_cost) || 0, audio_output_unit_cost: Number(tier.audio_output_unit_cost) || 0, } } export function createDefaultVisualConfig(): VisualConfig { return { tiers: [ normalizeVisualTier({ conditions: [], input_unit_cost: 0, output_unit_cost: 0, label: 'base', cache_mode: CACHE_MODE_GENERIC, }), ], } } export function normalizeVisualConfig( config: VisualConfig | null | undefined ): VisualConfig { if (!config || !Array.isArray(config.tiers) || config.tiers.length === 0) { return createDefaultVisualConfig() } return { ...config, tiers: config.tiers.map((tier) => normalizeVisualTier(tier)), } } function buildConditionStr(conditions: TierConditionInput[]): string { if (!conditions || conditions.length === 0) return '' return conditions .filter((c) => c.var && c.op && c.value != null && c.value !== '') .map((c) => `${c.var} ${c.op} ${c.value}`) .join(' && ') } function buildTierBodyExpr(tier: VisualTier): string { const parts: string[] = [] const ic = Number(tier.input_unit_cost) || 0 const oc = Number(tier.output_unit_cost) || 0 parts.push(`p * ${ic}`) parts.push(`c * ${oc}`) for (const cv of BILLING_CACHE_VAR_MAP) { const v = Number((tier as Record)[cv.field]) || 0 if (v !== 0) parts.push(`${cv.exprVar} * ${v}`) } return parts.join(' + ') } export function generateExprFromVisualConfig( config: VisualConfig | null | undefined ): string { if (!config || !config.tiers || config.tiers.length === 0) { return 'p * 0 + c * 0' } const tiers = config.tiers if (tiers.length === 1) { const tier = tiers[0] const label = tier.label || 'default' const body = `tier("${label}", ${buildTierBodyExpr(tier)})` const cond = buildConditionStr(tier.conditions) if (cond) { return `${cond} ? ${body} : p * 0 + c * 0` } return body } const parts: string[] = [] for (let i = 0; i < tiers.length; i++) { const tier = tiers[i] const label = tier.label || `tier_${i + 1}` const body = `tier("${label}", ${buildTierBodyExpr(tier)})` const cond = buildConditionStr(tier.conditions) if (i < tiers.length - 1 && cond) { parts.push(`${cond} ? ${body}`) } else { parts.push(body) } } return parts.join(' : ') } export function tryParseVisualConfig( exprStr: string | null | undefined ): VisualConfig | null { if (!exprStr) return null try { let body = exprStr const versionMatch = body.match(/^v\d+:([\s\S]*)$/) if (versionMatch) body = versionMatch[1] const cacheVarNames = BILLING_CACHE_VAR_MAP.map((cv) => cv.exprVar) const optCacheStr = cacheVarNames .map((v) => `(?:\\s*\\+\\s*${v}\\s*\\*\\s*([\\d.eE+-]+))?`) .join('') const bodyPat = `p\\s*\\*\\s*([\\d.eE+-]+)\\s*\\+\\s*c\\s*\\*\\s*([\\d.eE+-]+)${optCacheStr}` const singleRe = new RegExp(`^tier\\("([^"]*)",\\s*${bodyPat}\\)$`) const simple = body.match(singleRe) if (simple) { const tier: Record = { conditions: [], input_unit_cost: Number(simple[2]), output_unit_cost: Number(simple[3]), label: simple[1], } BILLING_CACHE_VAR_MAP.forEach((cv, i) => { const val = simple[4 + i] if (val != null) tier[cv.field] = Number(val) }) return normalizeVisualConfig({ tiers: [normalizeVisualTier(tier as Partial)], }) } const condGroup = `((?:(?:p|c|len)\\s*(?:<|<=|>|>=)\\s*[\\d.eE+]+)` + `(?:\\s*&&\\s*(?:p|c|len)\\s*(?:<|<=|>|>=)\\s*[\\d.eE+]+)*)` const tierRe = new RegExp( `(?:${condGroup}\\s*\\?\\s*)?tier\\("([^"]*)",\\s*${bodyPat}\\)`, 'g' ) const tiers: VisualTier[] = [] let match: RegExpExecArray | null while ((match = tierRe.exec(body)) !== null) { const condStr = match[1] || '' const conditions: TierConditionInput[] = [] if (condStr) { for (const cp of condStr.split(/\s*&&\s*/)) { const cm = cp.trim().match(/^(p|c|len)\s*(<|<=|>|>=)\s*([\d.eE+]+)$/) if (cm) { conditions.push({ var: cm[1] as TierConditionInput['var'], op: cm[2] as TierConditionInput['op'], value: Number(cm[3]), }) } } } const tier: Record = { conditions, input_unit_cost: Number(match[3]), output_unit_cost: Number(match[4]), label: match[2], } const m = match BILLING_CACHE_VAR_MAP.forEach((cv, i) => { const val = m[5 + i] if (val != null) tier[cv.field] = Number(val) }) tiers.push(normalizeVisualTier(tier as Partial)) } if (tiers.length === 0) return null const cfg = normalizeVisualConfig({ tiers }) const regenerated = generateExprFromVisualConfig(cfg) if (regenerated.replace(/\s+/g, '') !== body.replace(/\s+/g, '')) { return null } return cfg } catch { return null } } // --------------------------------------------------------------------------- // Local cost evaluator (for the estimator preview) // --------------------------------------------------------------------------- const ESTIMATOR_VARS = [ { var: 'cr', stateKey: 'cacheReadTokens' }, { var: 'cc', stateKey: 'cacheCreateTokens' }, { var: 'cc1h', stateKey: 'cacheCreate1hTokens' }, { var: 'img', stateKey: 'imageTokens' }, { var: 'img_o', stateKey: 'imageOutputTokens' }, { var: 'ai', stateKey: 'audioInputTokens' }, { var: 'ao', stateKey: 'audioOutputTokens' }, ] as const export type ExtraTokenValues = Record< (typeof ESTIMATOR_VARS)[number]['stateKey'], number > export type EvalResult = { cost: number matchedTier: string error: string | null } export function evalExprLocally( exprStr: string, promptTokens: number, completionTokens: number, extraTokenValues: ExtraTokenValues ): EvalResult { try { if (!exprStr || !exprStr.trim()) { return { cost: 0, matchedTier: '', error: null } } let matchedTier = '' const tierFn = (name: string, value: number) => { matchedTier = name return value } const cacheReadTokens = extraTokenValues.cacheReadTokens || 0 const cacheCreateTokens = extraTokenValues.cacheCreateTokens || 0 const cacheCreate1hTokens = extraTokenValues.cacheCreate1hTokens || 0 const len = promptTokens + cacheReadTokens + cacheCreateTokens + cacheCreate1hTokens const env: Record = { p: promptTokens, c: completionTokens, len, tier: tierFn, max: Math.max, min: Math.min, abs: Math.abs, ceil: Math.ceil, floor: Math.floor, } for (const field of ESTIMATOR_VARS) { env[field.var] = extraTokenValues[field.stateKey] || 0 } const fn = new Function( ...Object.keys(env), `"use strict"; return (${exprStr});` ) const cost = Number(fn(...Object.values(env))) || 0 return { cost, matchedTier, error: null } } catch (e) { const message = e instanceof Error ? e.message : String(e) return { cost: 0, matchedTier: '', error: message } } } export function exprUsesExtraVars(exprStr: string): boolean { if (!exprStr) return false const varNames = ESTIMATOR_VARS.map((f) => f.var).join('|') return new RegExp(`\\b(${varNames})\\b`).test(exprStr) } export const ESTIMATOR_EXTRA_FIELDS = ESTIMATOR_VARS