feat(voice): add ChatGPT OAuth auth option

This commit is contained in:
devv-shayan committed 2026-09-27 20:58:08 +05:00
1 parent b210ab0fe4
commit 881e24485c
23 files changed
+918 -34

No files matched your search

+41
View File
@@ -0,0 +1,41 @@
export const CLOUD_VOICE_AUTH_MODES = Object.freeze(['api-key', 'oauth']);
export const DEFAULT_CLOUD_VOICE_AUTH_MODE = 'api-key';
export const CLOUD_VOICE_AUTH_STORAGE_KEY = 'godsEyeView.voice.cloudAuth';
function storageRef(storage) {
if (storage) return storage;
try {
return typeof localStorage === 'undefined' ? null : localStorage;
} catch {
return null;
}
}
export function normalizeCloudVoiceAuthMode(mode) {
const normalized = String(mode || '')
.trim()
.toLowerCase();
return CLOUD_VOICE_AUTH_MODES.includes(normalized)
? normalized
: DEFAULT_CLOUD_VOICE_AUTH_MODE;
}
export function readStoredCloudVoiceAuthMode(storage) {
try {
return normalizeCloudVoiceAuthMode(
storageRef(storage)?.getItem(CLOUD_VOICE_AUTH_STORAGE_KEY),
);
} catch {
return DEFAULT_CLOUD_VOICE_AUTH_MODE;
}
}
export function writeStoredCloudVoiceAuthMode(mode, storage) {
const resolved = normalizeCloudVoiceAuthMode(mode);
try {
storageRef(storage)?.setItem(CLOUD_VOICE_AUTH_STORAGE_KEY, resolved);
} catch {
/* best effort */
}
return resolved;
}
+29
View File
@@ -0,0 +1,29 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {
CLOUD_VOICE_AUTH_STORAGE_KEY,
readStoredCloudVoiceAuthMode,
writeStoredCloudVoiceAuthMode,
} from './cloudVoiceAuth.js';
function memoryStorage(seed = {}) {
const values = new Map(Object.entries(seed));
return {
getItem: (key) => values.get(key) ?? null,
setItem: (key, value) => values.set(key, value),
};
}
test('cloud voice auth defaults to API key and persists OAuth independently', () => {
const storage = memoryStorage();
assert.equal(readStoredCloudVoiceAuthMode(storage), 'api-key');
assert.equal(writeStoredCloudVoiceAuthMode('oauth', storage), 'oauth');
assert.equal(readStoredCloudVoiceAuthMode(storage), 'oauth');
assert.equal(writeStoredCloudVoiceAuthMode('unknown', storage), 'api-key');
assert.equal(
readStoredCloudVoiceAuthMode(
memoryStorage({ [CLOUD_VOICE_AUTH_STORAGE_KEY]: 'oauth' }),
),
'oauth',
);
});
+14
View File
@@ -3194,6 +3194,20 @@ test('F1: the toggle still records the next-session preference while live', () =
assert.match(ui.tierButton.title, /this session stays on/i);
});
test('OAuth cloud sessions hide API spend and do not trip the API spend guard', () => {
const { controller, ui } = costControllerHarness();
controller.status = 'listening';
controller._cost.cloudVoiceAuth = 'oauth';
controller._cost.sessionCloudVoiceAuth = 'oauth';
controller.syncCostUi();
const before = controller.costTracker.state();
assert.equal(ui.costValue.hidden, true);
assert.equal(controller.recordUsage(usdUsage(100)), null);
const after = controller.costTracker.state();
assert.equal(after.totalUsd, before.totalUsd);
assert.equal(controller.costCapStopped, false);
});
test('F1: when idle, toggling does re-price the preview meter', () => {
const { controller } = costControllerHarness();
controller.status = 'idle';
+10 -2
View File
@@ -15,11 +15,19 @@ export function createRealtimeBackend({
);
return Object.freeze({
protocol: 'openai-realtime',
async requestToken({ tier = DEFAULT_VOICE_TIER, signal } = {}) {
async requestToken({
tier = DEFAULT_VOICE_TIER,
authMode = 'api-key',
signal,
} = {}) {
signal = scoped(signal);
signal.throwIfAborted();
const separator = tokenEndpoint.includes('?') ? '&' : '?';
const url = `${tokenEndpoint}${separator}tier=${encodeURIComponent(resolveVoiceModel(tier).tier)}`;
const query = new URLSearchParams({
tier: resolveVoiceModel(tier).tier,
});
if (authMode === 'oauth') query.set('auth', 'oauth');
const url = `${tokenEndpoint}${separator}${query}`;
const response = await tokenTransport(url, {
signal,
cache: 'no-store',
+16
View File
@@ -41,6 +41,22 @@ test('separate configured token and SDP transports preserve model metadata and c
]);
});
test('OAuth is an additive token query while API key stays the default', async () => {
const calls = [];
const backend = createRealtimeBackend({
tokenTransport: async (input) => {
calls.push(input);
return tokenReply();
},
});
await backend.requestToken();
await backend.requestToken({ authMode: 'oauth' });
assert.deepEqual(calls, [
'/api/realtime/token?tier=standard',
'/api/realtime/token?tier=standard&auth=oauth',
]);
});
test('denied tokens never negotiate or retry; reconnect requests a fresh token', async () => {
let requests = 0;
const backend = createRealtimeBackend({
+2
View File
@@ -82,6 +82,7 @@ export class RealtimeConnection {
this.debugLog('session.starting', {
epoch,
tier: this.cost.voiceTier,
authMode: this.cost.sessionCloudVoiceAuth,
connection: this.connectionDiagnostics(),
});
let localStream = null;
@@ -89,6 +90,7 @@ export class RealtimeConnection {
try {
const minted = await this.backend.requestToken({
tier: this.cost.voiceTier,
authMode: this.cost.sessionCloudVoiceAuth,
signal,
});
const token = minted.token;
+8
View File
@@ -4,6 +4,7 @@ import {
writeStoredVoiceTier,
writeStoredVoiceLimits,
} from './realtimePreferences.js';
import { readStoredCloudVoiceAuthMode } from './cloudVoiceAuth.js';
import {
createVoiceCostTracker,
resolveVoiceModel,
@@ -16,6 +17,8 @@ export class RealtimeCost {
Object.assign(this, { readUi, readStatus }, operations);
this.voiceTier = readStoredVoiceTier();
this.voiceLimits = readStoredVoiceLimits();
this.cloudVoiceAuth = readStoredCloudVoiceAuthMode();
this.sessionCloudVoiceAuth = null;
this.costTracker = createVoiceCostTracker({
tier: this.voiceTier,
limits: this.voiceLimits,
@@ -56,6 +59,8 @@ export class RealtimeCost {
}; applies next session`;
}
if (this.ui?.costValue) {
const liveAuth = this.sessionCloudVoiceAuth || this.cloudVoiceAuth;
this.ui.costValue.hidden = liveAuth === 'oauth';
this.ui.costValue.textContent = state.display;
this.ui.costValue.dataset.level = state.level;
this.ui.costValue.title =
@@ -136,6 +141,7 @@ export class RealtimeCost {
*/
recordUsage(usage) {
if (!usage) return null;
if (this.sessionCloudVoiceAuth === 'oauth') return null;
const state = this.costTracker.record(usage);
this.syncCostUi();
if (state.warnCrossed) {
@@ -190,6 +196,8 @@ export class RealtimeCost {
prepareSession() {
this.voiceTier = readStoredVoiceTier();
this.voiceLimits = readStoredVoiceLimits();
this.cloudVoiceAuth = readStoredCloudVoiceAuthMode();
this.sessionCloudVoiceAuth = this.cloudVoiceAuth;
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