Skip to content

Add gpt-realtime-2 option#139

Merged
tleyden merged 4 commits into
mainfrom
gpt_realtime_2
May 24, 2026
Merged

Add gpt-realtime-2 option#139
tleyden merged 4 commits into
mainfrom
gpt_realtime_2

Conversation

@tleyden
Copy link
Copy Markdown
Owner

@tleyden tleyden commented May 24, 2026

Fixes #134

Summary by CodeRabbit

  • New Features

    • Realtime voice model selection added to Advanced Configuration and a dedicated configuration sheet
    • Model selection is persisted across app sessions
    • Multiple realtime model options available; selection applied to voice sessions
  • Improvements

    • Session cost tracking now reflects pricing availability per selected model and updates during the session
    • Default realtime model updated
  • Bug Fixes

    • Minor error-message formatting improvements

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 24, 2026

📝 Walkthrough

Walkthrough

This PR introduces selectable realtime voice models with persistent preference storage, integrates the selected model into voice session initialization, makes token-usage tracking pricing-aware (runtime model switching and hasPricing), gates session cost UI on model pricing availability, and adds settings UI to choose the realtime model.

Changes

Realtime Model Selection and Pricing Integration

Layer / File(s) Summary
Model preference types and persistence
lib/realtimeModelPreference.ts, modules/vm-webrtc/ios/OpenAIWebRTCClient.swift
Defines RealtimeModel union and option metadata, DEFAULT_REALTIME_MODEL, REALTIME_MODEL_OPTIONS, and loadRealtimeModelPreference() / saveRealtimeModelPreference() helpers using AsyncStorage; iOS default realtime model override changed to gpt-realtime-2.
Token tracker pricing awareness
lib/tokenUsageTracker.ts
Adds hasPricing to TokenTotals, introduces PricedModel and DEFAULT_PRICED_MODEL, adds TokenUsageTracker.hasPricingForModel(), hasPricing() and setModel(); refactors addUsage()/reset() to compute totalUSD via getPriceStructure() and reflect pricing availability.
Model selection UI component
components/settings/ConfigureRealtimeModel.tsx
New ConfigureRealtimeModel BottomSheet rendering selectable model cards with title, description, selected checkmark, and styles.
Settings sheet model action
components/settings/AdvancedConfigurationSheet.tsx
Extends AdvancedConfigurationSheetProps with onConfigureModel, adds "model" to AdvancedAction union, and renders a "Choose Model" primary action that closes the sheet and invokes the callback.
App state, preference hydration, and handler
app/index.tsx
Adds selectedRealtimeModel and realtimeModelSheetVisible state, hydrates preference on mount via loadRealtimeModelPreference(), implements handleSelectRealtimeModel() to persist selection via saveRealtimeModelPreference() and close the sheet, removes hardcoded realtime model from buildConnectionOptions, and passes selectedRealtimeModel into VoiceChat.
VoiceChat model integration and cost gating
app/VoiceChat.tsx
Adds selectedRealtimeModel prop, initializes TokenUsageTracker model-agnostically and sets the session model at start via setModel(), updates token-usage listener to use totals.hasPricing, passes model through connection options, adds hook dependency, and only displays session cost when pricing is available for the selected model.

Sequence Diagram(s)

sequenceDiagram
  participant Mount["Component mount"]
  participant Load["loadRealtimeModelPreference"]
  participant AsyncStorage
  participant Handler["handleSelectRealtimeModel"]
  participant Save["saveRealtimeModelPreference"]
  participant Voice["VoiceChat"]
  
  Mount->>Load: useEffect load on mount
  Load->>AsyncStorage: retrieve stored model
  AsyncStorage-->>Load: model value
  Load-->>Mount: update selectedRealtimeModel state
  
  rect rgba(100, 150, 200, 0.5)
  Handler->>Handler: setSelectedRealtimeModel(model)
  Handler->>Save: persist to storage
  Save->>AsyncStorage: setItem
  Handler->>Handler: close sheet
  end
  
  Load-->>Voice: pass selectedRealtimeModel prop
Loading
sequenceDiagram
  participant VoiceChat
  participant Tracker["TokenUsageTracker"]
  participant PRICES
  VoiceChat->>Tracker: setModel(selectedRealtimeModel)
  VoiceChat->>Tracker: addUsage(usage)
  Tracker->>PRICES: getPriceStructure(model)
  alt Pricing exists
    PRICES-->>Tracker: price entry
    Tracker->>Tracker: compute totalUSD
    Tracker-->>Tracker: hasPricing=true
  else No pricing
    PRICES-->>Tracker: fallback/default entry
    Tracker->>Tracker: compute totalUSD (fallback)
    Tracker-->>Tracker: hasPricing=false
  end
  Tracker-->>VoiceChat: totals with hasPricing
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through sheets and AsyncStorage bright,
I picked a model under soft moonlight,
Tokens counted, prices checked with care,
Voices started with the chosen air—
A tiny rabbit saves your model right.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and concisely describes the main change: adding support for the gpt-realtime-2 model option across the codebase.
Linked Issues check ✅ Passed The PR successfully implements support for gpt-realtime-2 API as required by issue #134, including model selection UI, preference persistence, pricing configuration, and default model updates.
Out of Scope Changes check ✅ Passed All changes are directly related to adding gpt-realtime-2 support; no out-of-scope modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch gpt_realtime_2

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

OSSF Scorecard (PR vs base)

  • Base score: 4.4
  • PR score: 4.4
  • Change: 0.00 (unchanged)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
components/settings/ConfigureRealtimeModel.tsx (1)

77-136: 💤 Low value

Consider extracting hardcoded colors to a theme constants file.

The StyleSheet contains multiple hardcoded color values (e.g., #0A84FF, #D1D1D6, #F0F6FF) that are repeated and could benefit from centralization in a theme or design tokens file for easier maintenance and consistency across the app.

♻️ Example refactor

Create a theme constants file:

// lib/theme.ts
export const Colors = {
  primary: '`#0A84FF`',
  border: '`#D1D1D6`',
  backgroundPrimary: '`#F0F6FF`',
  // ... other colors
};

Then import and use:

+import { Colors } from '../../lib/theme';
+
 const styles = StyleSheet.create({
   optionCard: {
     // ...
-    borderColor: "`#D1D1D6`",
-    backgroundColor: "`#FFFFFF`",
+    borderColor: Colors.border,
+    backgroundColor: Colors.background,
   },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/settings/ConfigureRealtimeModel.tsx` around lines 77 - 136,
Extract repeated hardcoded hex colors from the StyleSheet in
ConfigureRealtimeModel into a central theme/constants file (e.g., export a
Colors object) and replace literal values in styles with named tokens;
specifically remove hex strings used in styles.optionCard (borderColor
"`#D1D1D6`", backgroundColor "`#FFFFFF`"), styles.optionCardSelected (borderColor
"`#0A84FF`", backgroundColor "`#F0F6FF`"), styles.optionCardPressed (backgroundColor
"`#E5F1FF`"), styles.optionCheckmark (color "`#AEAEB2`"),
styles.optionCheckmarkActive (color "`#0A84FF`"),
styles.lead/optionSubtitle/helperText colors and any other repeats, then import
the Colors constants into ConfigureRealtimeModel.tsx and reference
Colors.primary, Colors.border, Colors.backgroundPrimary,
Colors.pressedBackground, Colors.muted, etc., keeping style key names unchanged
so only the color values are swapped.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/realtimeModelPreference.ts`:
- Around line 38-40: The catch blocks that currently swallow AsyncStorage errors
and return DEFAULT_REALTIME_MODEL need to log the full error and relevant values
before returning; update the catch clauses in lib/realtimeModelPreference.ts
(both the try/catch that returns DEFAULT_REALTIME_MODEL at lines shown) to
capture the thrown error (e.g., catch (err)), log the complete error object and
the key/value you attempted to read/write (not substrings) using the project
logger or console.error, and then return DEFAULT_REALTIME_MODEL so behavior is
unchanged but failures are observable; reference DEFAULT_REALTIME_MODEL and the
AsyncStorage operations in your log message for context.

In `@lib/tokenUsageTracker.ts`:
- Around line 62-64: setModel currently only assigns this.model causing
totals.hasPricing and totals.totalUSD to remain stale; update setModel so after
setting this.model it immediately recomputes the pricing state (i.e.,
update/clear totals.hasPricing and recompute totals.totalUSD) instead of waiting
for addUsage() or reset(); implement this by either invoking the same
pricing-update logic used by addUsage()/reset() or by factoring that logic into
a private helper (e.g., recomputePricingState) and calling it from setModel so
totals reflect the new model immediately.

---

Nitpick comments:
In `@components/settings/ConfigureRealtimeModel.tsx`:
- Around line 77-136: Extract repeated hardcoded hex colors from the StyleSheet
in ConfigureRealtimeModel into a central theme/constants file (e.g., export a
Colors object) and replace literal values in styles with named tokens;
specifically remove hex strings used in styles.optionCard (borderColor
"`#D1D1D6`", backgroundColor "`#FFFFFF`"), styles.optionCardSelected (borderColor
"`#0A84FF`", backgroundColor "`#F0F6FF`"), styles.optionCardPressed (backgroundColor
"`#E5F1FF`"), styles.optionCheckmark (color "`#AEAEB2`"),
styles.optionCheckmarkActive (color "`#0A84FF`"),
styles.lead/optionSubtitle/helperText colors and any other repeats, then import
the Colors constants into ConfigureRealtimeModel.tsx and reference
Colors.primary, Colors.border, Colors.backgroundPrimary,
Colors.pressedBackground, Colors.muted, etc., keeping style key names unchanged
so only the color values are swapped.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b4c2561d-f821-4331-b9e0-7ec9deb6ae6e

📥 Commits

Reviewing files that changed from the base of the PR and between 223b22b and de026ab.

📒 Files selected for processing (6)
  • app/VoiceChat.tsx
  • app/index.tsx
  • components/settings/AdvancedConfigurationSheet.tsx
  • components/settings/ConfigureRealtimeModel.tsx
  • lib/realtimeModelPreference.ts
  • lib/tokenUsageTracker.ts

Comment on lines +38 to +40
} catch {
return DEFAULT_REALTIME_MODEL;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Don’t swallow AsyncStorage failures silently.

Line 38 and Line 48 suppress persistence errors completely, which makes preference-loss bugs hard to diagnose in production.

Proposed fix
 export const loadRealtimeModelPreference = async (): Promise<RealtimeModel> => {
   try {
     const stored = await AsyncStorage.getItem(STORAGE_KEY);
     if (!stored || !isRealtimeModel(stored)) {
       return DEFAULT_REALTIME_MODEL;
     }
     return stored;
-  } catch {
+  } catch (error) {
+    console.warn("Failed to load realtime model preference", {
+      storageKey: STORAGE_KEY,
+      error,
+    });
     return DEFAULT_REALTIME_MODEL;
   }
 };

 export const saveRealtimeModelPreference = async (
   model: RealtimeModel,
 ): Promise<void> => {
   try {
     await AsyncStorage.setItem(STORAGE_KEY, model);
-  } catch {
-    // Ignore persistence errors for now; UI will fall back to default.
+  } catch (error) {
+    console.warn("Failed to save realtime model preference", {
+      storageKey: STORAGE_KEY,
+      model,
+      error,
+    });
   }
 };

As per coding guidelines, "When logging, default to logging full values and not substrings to address the observability crisis".

Also applies to: 48-50

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/realtimeModelPreference.ts` around lines 38 - 40, The catch blocks that
currently swallow AsyncStorage errors and return DEFAULT_REALTIME_MODEL need to
log the full error and relevant values before returning; update the catch
clauses in lib/realtimeModelPreference.ts (both the try/catch that returns
DEFAULT_REALTIME_MODEL at lines shown) to capture the thrown error (e.g., catch
(err)), log the complete error object and the key/value you attempted to
read/write (not substrings) using the project logger or console.error, and then
return DEFAULT_REALTIME_MODEL so behavior is unchanged but failures are
observable; reference DEFAULT_REALTIME_MODEL and the AsyncStorage operations in
your log message for context.

Comment thread lib/tokenUsageTracker.ts
Comment on lines +62 to 64
setModel(model: string): void {
this.model = model;
this.totals = {
inputText: 0,
inputAudio: 0,
outputText: 0,
outputAudio: 0,
cachedInput: 0,
totalUSD: 0,
};
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Recompute pricing state when switching models.

Line 62 updates this.model, but totals.hasPricing and totals.totalUSD can remain stale until a later addUsage() or reset(). That can leak outdated session-cost state to callers reading totals immediately after model switch.

Proposed fix
   setModel(model: string): void {
     this.model = model;
+    const p = this.getPriceStructure();
+    this.totals.hasPricing = p !== null;
+    if (!p) {
+      this.totals.totalUSD = 0;
+      return;
+    }
+    const cost =
+      this.totals.inputText * p.inputText +
+      this.totals.cachedInput * p.cachedInput +
+      this.totals.outputText * p.outputText +
+      this.totals.inputAudio * p.inputAudio +
+      this.totals.outputAudio * p.outputAudio;
+    this.totals.totalUSD = parseFloat(cost.toFixed(6));
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
setModel(model: string): void {
this.model = model;
this.totals = {
inputText: 0,
inputAudio: 0,
outputText: 0,
outputAudio: 0,
cachedInput: 0,
totalUSD: 0,
};
}
setModel(model: string): void {
this.model = model;
const p = this.getPriceStructure();
this.totals.hasPricing = p !== null;
if (!p) {
this.totals.totalUSD = 0;
return;
}
const cost =
this.totals.inputText * p.inputText +
this.totals.cachedInput * p.cachedInput +
this.totals.outputText * p.outputText +
this.totals.inputAudio * p.inputAudio +
this.totals.outputAudio * p.outputAudio;
this.totals.totalUSD = parseFloat(cost.toFixed(6));
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/tokenUsageTracker.ts` around lines 62 - 64, setModel currently only
assigns this.model causing totals.hasPricing and totals.totalUSD to remain
stale; update setModel so after setting this.model it immediately recomputes the
pricing state (i.e., update/clear totals.hasPricing and recompute
totals.totalUSD) instead of waiting for addUsage() or reset(); implement this by
either invoking the same pricing-update logic used by addUsage()/reset() or by
factoring that logic into a private helper (e.g., recomputePricingState) and
calling it from setModel so totals reflect the new model immediately.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/tokenUsageTracker.ts`:
- Around line 66-67: The code currently forces unknown models to be treated as
priced; update hasPricingForModel to check the actual pricing
map/PRICE_STRUCTURE for the given model and return false when the model isn't
present instead of always returning true, change addUsage to call
hasPricingForModel and either reject/return an error or no-op (and avoid
recording cost) when pricing is unavailable, and modify getPriceStructure so it
does not silently fall back to DEFAULT_PRICED_MODEL but returns undefined or
throws a clear “no pricing available” result for unknown model IDs; use the
existing symbols hasPricingForModel, addUsage, getPriceStructure and
DEFAULT_PRICED_MODEL when making these checks so behavior is consistent across
the file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 365356df-7e14-4399-a174-8e5b75e79a39

📥 Commits

Reviewing files that changed from the base of the PR and between 7965ae1 and 14f0a4d.

📒 Files selected for processing (1)
  • lib/tokenUsageTracker.ts

Comment thread lib/tokenUsageTracker.ts
Comment on lines +66 to +67
static hasPricingForModel(_model: string): boolean {
return true;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Unsupported model IDs should not be priced as the default model.

hasPricingForModel() and addUsage() currently force pricing to true, and getPriceStructure() silently falls back to DEFAULT_PRICED_MODEL. That can misstate cost for unknown model IDs instead of signaling “no pricing available.”

Proposed fix
-  static hasPricingForModel(_model: string): boolean {
-    return true;
+  static hasPricingForModel(model: string): boolean {
+    return Object.prototype.hasOwnProperty.call(PRICES, model);
   }
...
-    const p = this.getPriceStructure();
-    this.totals.hasPricing = true;
+    const p = this.getPriceStructure();
+    this.totals.hasPricing = p !== null;
+    if (!p) {
+      this.totals.totalUSD = 0;
+      return { ...this.totals };
+    }
...
-  private getPriceStructure(): PriceStructure {
+  private getPriceStructure(): PriceStructure | null {
     if (Object.prototype.hasOwnProperty.call(PRICES, this.model)) {
       return PRICES[this.model as PricedModel];
     }
-    return PRICES[DEFAULT_PRICED_MODEL];
+    return null;
   }

Also applies to: 87-88, 118-123

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/tokenUsageTracker.ts` around lines 66 - 67, The code currently forces
unknown models to be treated as priced; update hasPricingForModel to check the
actual pricing map/PRICE_STRUCTURE for the given model and return false when the
model isn't present instead of always returning true, change addUsage to call
hasPricingForModel and either reject/return an error or no-op (and avoid
recording cost) when pricing is unavailable, and modify getPriceStructure so it
does not silently fall back to DEFAULT_PRICED_MODEL but returns undefined or
throws a clear “no pricing available” result for unknown model IDs; use the
existing symbols hasPricingForModel, addUsage, getPriceStructure and
DEFAULT_PRICED_MODEL when making these checks so behavior is consistent across
the file.

@tleyden tleyden merged commit 56a95a4 into main May 24, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for gpt-realtime-2 API

1 participant