fix(voice): count OAuth caption transcriptions instead of pricing them

Voice-card captions from main priced transcription usage into the API-key
dollar meter. In a ChatGPT OAuth session that could trip the $2 warning or
$5 cap while the readout said COST UNKNOWN. Count them with the session's
other OAuth usage instead.
This commit is contained in:
Sameh Khamis committed 2026-10-08 11:01:43 -07:00
1 parent fb1596d2f1
commit c99653047f
2 files changed
+34 -4

No files matched your search

+23
View File
@@ -3208,6 +3208,7 @@ test('OAuth cloud sessions report unknown cost and usage without tripping the AP
responses: 1,
input: 0,
output: 1562500,
captions: 0,
costKnown: false,
});
const after = controller.costTracker.state();
@@ -3220,6 +3221,28 @@ test('OAuth cloud sessions report unknown cost and usage without tripping the AP
assert.match(ui.costValue.title, /Usage is incomplete/);
});
test('OAuth cloud sessions count caption transcriptions without pricing them into the API cap', () => {
const { controller, ui } = costControllerHarness();
controller.status = 'listening';
controller._cost.cloudVoiceAuth = 'oauth';
controller._cost.sessionCloudVoiceAuth = 'oauth';
controller._cost.bindTranscriptionModel('gpt-4o-mini-transcribe');
const before = controller.costTracker.state();
// Priced, this much caption audio would cross the $5 cap.
const result = controller.recordTranscriptionUsage({
type: 'duration',
seconds: 3_600_000,
});
assert.equal(result.captions, 1);
assert.equal(result.costKnown, false);
const after = controller.costTracker.state();
assert.equal(after.totalUsd, before.totalUsd);
assert.equal(after.capReached, false);
assert.equal(controller.costCapStopped, false);
assert.equal(ui.costValue.textContent, 'COST UNKNOWN');
assert.match(ui.costValue.title, /1 caption transcription\(s\)/);
});
test('F1: when idle, toggling does re-price the preview meter', () => {
const { controller } = costControllerHarness();
controller.status = 'idle';
+11 -4
View File
@@ -20,7 +20,7 @@ export class RealtimeCost {
this.voiceLimits = readStoredVoiceLimits();
this.cloudVoiceAuth = readStoredCloudVoiceAuthMode();
this.sessionCloudVoiceAuth = null;
this.oauthUsage = { responses: 0, input: 0, output: 0 };
this.oauthUsage = { responses: 0, input: 0, output: 0, captions: 0 };
this.transcribeModel = null;
this.costTracker = createVoiceCostTracker({
tier: this.voiceTier,
@@ -64,12 +64,12 @@ export class RealtimeCost {
if (this.ui?.costValue) {
const liveAuth = this.sessionCloudVoiceAuth || this.cloudVoiceAuth;
if (liveAuth === 'oauth') {
const { responses, input, output } = this.oauthUsage;
const { responses, input, output, captions } = this.oauthUsage;
this.ui.costValue.hidden = false;
this.ui.costValue.textContent = 'COST UNKNOWN';
this.ui.costValue.dataset.level = 'unknown';
this.ui.costValue.title =
`ChatGPT OAuth session: ${responses} response(s), ${input} input and ${output} output tokens reported. ` +
`ChatGPT OAuth session: ${responses} response(s), ${input} input and ${output} output tokens, ${captions} caption transcription(s) reported. ` +
'USD cost and the API spend cap are unavailable for this auth mode.' +
(state.incomplete
? ' Usage is incomplete because a response was still in flight when the session ended.'
@@ -176,6 +176,13 @@ export class RealtimeCost {
*/
recordTranscriptionUsage(usage) {
if (!usage) return null;
// An OAuth session has no API-dollar meter, so captions are counted
// rather than priced into the API-key warning and cap.
if (this.sessionCloudVoiceAuth === 'oauth') {
this.oauthUsage.captions += 1;
this.syncCostUi();
return { ...this.oauthUsage, costKnown: false };
}
return this.applyCost(
this.costTracker.recordUsd(
estimateTranscriptionCostUsd(usage, this.transcribeModel),
@@ -243,7 +250,7 @@ export class RealtimeCost {
this.voiceLimits = readStoredVoiceLimits();
this.cloudVoiceAuth = readStoredCloudVoiceAuthMode();
this.sessionCloudVoiceAuth = this.cloudVoiceAuth;
this.oauthUsage = { responses: 0, input: 0, output: 0 };
this.oauthUsage = { responses: 0, input: 0, output: 0, captions: 0 };
this.costCapStopped = false;
// Provisional meter (tier-priced) so the readout shows $0.00 while
// connecting. It is REPLACED below with one bound to the model the server