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.
This commit is contained in:
Bilawal Sidhu committed 2026-09-28 21:19:23 -05:00
1 parent e7707d9a0f
commit a19897ad15
97 files changed
+14343 -755

No files matched your search

+71 -1
View File
@@ -154,6 +154,69 @@ export function resolveVoiceModelById(modelId) {
};
}
/* ------------------------------------------------------------------ *
* INPUT TRANSCRIPTION (voice-card captions)
* ------------------------------------------------------------------ */
/**
* ⚠️ VERIFY AT RELEASE — external prices, USD per 1M tokens, same shape as
* the realtime rate tables. Transcription is billed separately from the
* session's responses and arrives as `usage` on
* `conversation.item.input_audio_transcription.completed`. An unknown model
* bills at the most expensive entry, as for session models.
*/
const transcriptionTable = ({ textInput, audioInput, textOutput }) =>
Object.freeze({
textInput,
audioInput,
textOutput,
// Transcription output is text; usage without output details would
// otherwise be read as audio and priced at zero. Cached input is priced
// as uncached so the meter never under-counts.
audioOutput: textOutput,
textCachedInput: textInput,
audioCachedInput: audioInput,
});
export const TRANSCRIPTION_MODEL_RATES = Object.freeze({
'gpt-4o-mini-transcribe': transcriptionTable({
textInput: 1.25,
audioInput: 3,
textOutput: 5,
}),
'gpt-4o-transcribe': transcriptionTable({
textInput: 2.5,
audioInput: 6,
textOutput: 10,
}),
});
/** Per-second rate for duration-billed transcription (whisper-style). */
export const TRANSCRIPTION_USD_PER_SECOND = 0.006 / 60;
/** Rate table for a transcription model; unknown ids bill at the priciest. */
export function transcriptionRates(modelId) {
const id = typeof modelId === 'string' ? modelId.trim() : '';
if (Object.prototype.hasOwnProperty.call(TRANSCRIPTION_MODEL_RATES, id))
return TRANSCRIPTION_MODEL_RATES[id];
return Object.values(TRANSCRIPTION_MODEL_RATES).reduce((worst, rates) =>
rates.audioInput > worst.audioInput ? rates : worst,
);
}
/**
* USD for one transcription's usage: token usage priced with the model's
* rates, or duration usage priced per second.
*/
export function estimateTranscriptionCostUsd(usage, modelId) {
if (!usage) return 0;
if (usage.type === 'duration') {
const usd = nonNegative(usage.seconds) * TRANSCRIPTION_USD_PER_SECOND;
return Number.isFinite(usd) ? usd : 0;
}
return estimateUsageCostUsd(usage, transcriptionRates(modelId));
}
/* ------------------------------------------------------------------ *
* SPEND GUARD CONFIG
* ------------------------------------------------------------------ */
@@ -429,7 +492,14 @@ export function createVoiceCostTracker(options = {}) {
* @param {object} usage - `response.usage`
*/
record(usage) {
const usd = estimateUsageCostUsd(usage, model.rates);
return this.recordUsd(estimateUsageCostUsd(usage, model.rates));
},
/**
* Fold an already-priced cost (e.g. input transcription) into the same
* session total, so the warning and the cap cover it.
* @param {number} usd
*/
recordUsd(usd) {
if (usd > 0) {
totalUsd += usd;
responses += 1;