// Paid-list-price estimate, not a quota lookup or an invoice. Sources checked // 2026-09-08: https://ai.google.dev/gemini-api/docs/pricing and /api/live. // Live prompt counts include history billed again on each generation request. export const VOICE_PRICING = Object.freeze({ model: 'gemini-3.1-flash-live-preview', checked: '2026-09-08', input: Object.freeze({ TEXT: 0.75, AUDIO: 3, IMAGE: 1, VIDEO: 1 }), output: Object.freeze({ TEXT: 4.5, AUDIO: 12 }), }) export const DEFAULT_VOICE_LIMITS = Object.freeze({ minutes: 10, usd: 0.25 }) export function voiceLimits(value = DEFAULT_VOICE_LIMITS) { const minutes = Number(value.minutes), usd = Number(value.usd) // Ephemeral credentials currently expire after ten minutes; don't offer a // duration the connection cannot actually support. if (!Number.isInteger(minutes) || minutes < 1 || minutes > 10 || !Number.isFinite(usd) || usd < 0.01 || usd > 10) throw new Error('Ses sınırı 1–10 dakika ve 0,01–10 USD arasında olmalı.') return { minutes, usd } } function count(value) { if (value === undefined) return 0 // protobuf omits zero-valued fields if (!Number.isSafeInteger(value) || value < 0) throw new Error('Geçersiz kullanım sayacı.') return value } function modalityCost(total, details, rates) { if (details !== undefined && !Array.isArray(details)) throw new Error('Geçersiz kullanım ayrıntısı.') let tokens = 0, low = 0, high = 0, uncertain = false const minimum = Math.min(...Object.values(rates)), maximum = Math.max(...Object.values(rates)) for (const detail of details || []) { const n = count(detail?.tokenCount), rate = Object.hasOwn(rates, detail?.modality) ? rates[detail.modality] : undefined tokens += n; low += n * (rate ?? minimum); high += n * (rate ?? maximum) if (rate === undefined && n) uncertain = true } if (tokens > total) throw new Error('Kullanım toplamı ayrıntılarla uyuşmuyor.') const remainder = total - tokens return { low: low + remainder * minimum, high: high + remainder * maximum, uncertain: uncertain || remainder > 0 } } export function estimateUsage(metadata, model) { if (model !== VOICE_PRICING.model) throw new Error('Bu ses modelinin fiyatı doğrulanmadı.') if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata) || !['promptTokenCount', 'responseTokenCount', 'totalTokenCount'].some(k => k in metadata)) throw new Error('Google kullanım bilgisi eksik.') const prompt = count(metadata.promptTokenCount), response = count(metadata.responseTokenCount) const thoughts = count(metadata.thoughtsTokenCount), tools = count(metadata.toolUsePromptTokenCount) const total = count(metadata.totalTokenCount) if (metadata.totalTokenCount !== undefined && total < prompt + response) throw new Error('Kullanım toplamı sayaçlarla uyuşmuyor.') const input = modalityCost(prompt, metadata.promptTokensDetails, VOICE_PRICING.input) const output = modalityCost(response, metadata.responseTokensDetails, VOICE_PRICING.output) const tool = modalityCost(tools, metadata.toolUsePromptTokensDetails, VOICE_PRICING.input) // Thinking/tool counters may overlap aggregate counts. Keep a range instead // of promising a falsely precise bill or silently treating them as free. const residual = Math.max(0, total - prompt - response) const extraUpper = Math.max(residual * 12, thoughts * 4.5 + tool.high) return { lowUsd: (input.low + output.low) / 1e6, highUsd: (input.high + output.high + extraUpper) / 1e6, uncertain: input.uncertain || output.uncertain || extraUpper > 0 || count(metadata.cachedContentTokenCount) > 0 } } export class VoiceUsage { constructor(model, limits = DEFAULT_VOICE_LIMITS) { this.model = model; this.limits = voiceLimits(limits) this.lowUsd = 0; this.highUsd = 0; this.reports = 0; this.uncertain = false } add(metadata) { // Do not de-duplicate equal reports: distinct requests can have equal // token counts, and subtracting prior prompt counts loses history cost. const cost = estimateUsage(metadata, this.model) this.lowUsd += cost.lowUsd; this.highUsd += cost.highUsd this.uncertain ||= cost.uncertain; this.reports++ return this.snapshot() } snapshot() { return { lowUsd: this.lowUsd, highUsd: this.highUsd, reports: this.reports, uncertain: this.uncertain, reached: this.highUsd >= this.limits.usd } } } export function usageLabel(usage, seconds) { const cost = !usage.reports ? 'Maliyet henüz bilinmiyor; Google kullanım bildirimi bekleniyor.' : `Bildirilen kullanımın ücretli tarife tahmini: ${usage.lowUsd.toFixed(4)}–${usage.highUsd.toFixed(4)} USD${usage.uncertain ? ' (eksik ayrıntılar için aralık)' : ''}.` return `Yerel süre: ${seconds} sn. ${cost} Gerçek fatura veya kalan ücretsiz kota değildir; IDE modeli dahil değildir.` }