Files
gods-eye-view/scripts/voice-bench/pricing.mjs
Bilawal Sidhu a19897ad15 feat(voice): voice core — honest layer reach, spoken summary with on-screen card, narration, point-and-ask
- Voice layer manifest (src/voice/layerManifest.js) generates the layer enums,
  aliases and analyst field hints; a test fails when a catalog layer has no entry.
- analyst_query validates fields, operators, units, values and scopes, counts up
  to 250,000 loaded records per layer, reports complete/partial/unanswered, tells
  LAYER_OFF / NOT_READY / FEED_UNAVAILABLE apart from zero, refuses mismatched
  follow-ups and never lets a superseded query replace follow-up memory. Seven
  more layers expose analyst records.
- Tool results carry a code-built say line and a display card; the voice card in
  the mic control shows captions, plan steps and the result
  (src/voice/resultDisplay.js is the one adapter). Slow tools narrate their
  current step out of band; holding Space while the assistant talks cuts it off.
  Turn spans go to the debug log; caption transcription is metered; the log
  omits what was said unless GEV_VOICE_LOG_CONTENT=1.
- Point and ask: the Space-down pointer snapshot resolves "this", "that one" and
  "around here" through 'pointer' targets, and "the second one" through
  referent:n from the last numbered list.
- Prompt states each policy once (instructions + tools 48.5k -> 46.7k chars).
- Voice benchmark harness (scripts/qa-voice-bench.mjs, scripts/voice-bench/)
  shares its phrase table and grading with scripts/qa-voice-routing.mjs.
2026-09-28 21:19:23 -05:00

59 lines
2.3 KiB
JavaScript

/**
* Cost estimation for benchmark turns (USD). Pure.
*
* OpenAI Realtime: the app's own rate table (`src/voice/voiceCost.js`).
* `gpt-realtime-2.1` has no registry entry, so it resolves to the priciest
* known table (gpt-realtime-2) — which matches OpenAI's published 2.1 rates
* as read on 2026-09-23 ($4 / $0.40 cached / $24 per 1M text tokens).
*
* Gemini Live: https://ai.google.dev/gemini-api/docs/pricing, read 2026-09-23,
* gemini-3.8-live paid tier, USD per 1M tokens. Thinking tokens bill as text
* output. The native-audio Live model rejects TEXT output, so the benchmark
* requests AUDIO and the spoken confirmation bills at the audio-output rate.
*/
import { estimateUsageCostUsd, resolveVoiceModelById } from '../../src/voice/voiceCost.js';
export const GEMINI_RATES = Object.freeze({
'gemini-3.8-live': Object.freeze({
textInput: 0.75,
audioInput: 3.0,
textOutput: 4.5,
audioOutput: 12.0,
verifiedOn: '2026-09-23',
}),
});
/** Cost of one Realtime `response.usage` for `model`. */
export function openAiUsageCostUsd(model, usage) {
return estimateUsageCostUsd(usage, resolveVoiceModelById(model).rates);
}
const modalityCount = (details, modality) =>
(Array.isArray(details) ? details : [])
.filter((d) => String(d?.modality || '').toUpperCase() === modality)
.reduce((sum, d) => sum + (Number(d?.tokenCount) || 0), 0);
/**
* Cost of one Gemini Live `usageMetadata`. Unattributed prompt tokens bill
* as audio input and unattributed response tokens as audio output — the
* conservative direction for a spend cap.
*/
export function geminiUsageCostUsd(model, usage) {
const rates = GEMINI_RATES[model] || GEMINI_RATES['gemini-3.8-live'];
if (!usage) return 0;
const prompt = Number(usage.promptTokenCount) || 0;
const response = Number(usage.responseTokenCount) || 0;
const thoughts = Number(usage.thoughtsTokenCount) || 0;
const textIn = modalityCount(usage.promptTokensDetails, 'TEXT');
const audioIn = Math.max(0, prompt - textIn);
const textOut = modalityCount(usage.responseTokensDetails, 'TEXT');
const audioOut = Math.max(0, response - textOut);
const usd =
(textIn * rates.textInput +
audioIn * rates.audioInput +
(textOut + thoughts) * rates.textOutput +
audioOut * rates.audioOutput) /
1_000_000;
return Number.isFinite(usd) && usd > 0 ? usd : 0;
}