Add gpt-realtime-2 option#139
Conversation
📝 WalkthroughWalkthroughThis 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. ChangesRealtime Model Selection and Pricing Integration
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
|
OSSF Scorecard (PR vs base)
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
components/settings/ConfigureRealtimeModel.tsx (1)
77-136: 💤 Low valueConsider 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
📒 Files selected for processing (6)
app/VoiceChat.tsxapp/index.tsxcomponents/settings/AdvancedConfigurationSheet.tsxcomponents/settings/ConfigureRealtimeModel.tsxlib/realtimeModelPreference.tslib/tokenUsageTracker.ts
| } catch { | ||
| return DEFAULT_REALTIME_MODEL; | ||
| } |
There was a problem hiding this comment.
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.
| setModel(model: string): void { | ||
| this.model = model; | ||
| this.totals = { | ||
| inputText: 0, | ||
| inputAudio: 0, | ||
| outputText: 0, | ||
| outputAudio: 0, | ||
| cachedInput: 0, | ||
| totalUSD: 0, | ||
| }; | ||
| } |
There was a problem hiding this comment.
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.
| 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.
There was a problem hiding this comment.
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
📒 Files selected for processing (1)
lib/tokenUsageTracker.ts
| static hasPricingForModel(_model: string): boolean { | ||
| return true; |
There was a problem hiding this comment.
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.
Fixes #134
Summary by CodeRabbit
New Features
Improvements
Bug Fixes