Skip to content

fix(realtime): resolve virtual-model aliases on realtime and audio endpoints#514

Merged
SantiagoDePolonia merged 1 commit into
mainfrom
fix/realtime-audio-alias-resolution
Jul 8, 2026
Merged

fix(realtime): resolve virtual-model aliases on realtime and audio endpoints#514
SantiagoDePolonia merged 1 commit into
mainfrom
fix/realtime-audio-alias-resolution

Conversation

@SantiagoDePolonia

@SantiagoDePolonia SantiagoDePolonia commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Problem

Virtual-model aliases 404 on every realtime and audio endpoint, while resolving correctly on chat, responses, and embeddings. Commit 2c3ab68 (#500) and CLAUDE.md both claim aliases work on these routes — they don't.

Reproduced against live OpenAI (alias → openai/gpt-realtime, target present in /v1/models):

Endpoint Alias (before) Concrete
POST /v1/realtime/calls?model=… 404 201
POST /v1/realtime/client_secrets (session.model) 404 200
GET /v1/realtime?model=… (websocket) 404 101
POST /v1/audio/speech 404 200
POST /v1/audio/transcriptions 404 200
POST /v1/chat/completions (control) 200 200

Root cause

Two independent defects, both required to reach the bug:

  1. The resolver was never wired. realtimeService and audioService copied provider, modelAuthorizer, budgetChecker, rateLimiter — but not h.modelResolver (the virtualmodels.Service). prepare() type-asserted the registry-only selectorResolver on the Router, which canonicalizes concrete ids and returns an alias unchanged with no error.
  2. The raw model was forwarded anyway. The parsed selector was discarded and the raw request model was passed to router.Realtime*Target / CreateSpeech / CreateTranscription, which then 404'd in the provider lookup.

These endpoints resolve their model in the service layer because OperationRealtime and the audio operations are absent from the workflow middleware's resolveWorkflow switch, so ensureRequestModelResolution (the chat path's route to the alias resolver) never runs for them.

Fix

Both services now resolve through a shared resolveServiceModel helper that runs the same two-step gateway.ResolveExecutionSelector (alias table → provider registry) used by chat/responses/embeddings, and forward the resolved concrete selector upstream. Wiring added in both service constructors.

Why the existing test missed it

realtime_webrtc_service_test.go injected a mock whose ResolveModel faked the alias resolution the production Router never performs, and asserted the router received the raw alias ("voice-alias") under a comment claiming the opposite. That assertion is corrected, and new regression tests in realtime_audio_alias_resolution_test.go drive a real virtualmodels.Service through a registry-only provider double across all three realtime routes plus speech and transcription. Each new test fails if the resolver is unwired or the raw model is forwarded (both verified by reverting each half independently).

Verification

  • internal/server, internal/providers, internal/gateway, internal/virtualmodels: green; make test-race + make lint pass in pre-commit.
  • Live end-to-end against OpenAI: all five endpoints now resolve the alias (client_secrets 200 with session.model rewritten; calls 201 with Location; websocket 101; speech 200; transcription 200).
  • Full 162-scenario release E2E matrix: 162/162 PASS, no regressions.

Found during pre-release QA of the current release branch.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Audio and realtime requests now correctly use resolved model names before routing, so aliases are handled consistently across speech, transcription, web, and websocket flows.
    • Multipart and session-based requests now forward the concrete model to downstream routing instead of the original alias.
    • Invalid or unresolvable model requests now fail cleanly with clearer request errors.
  • Tests

    • Added coverage for alias resolution across realtime and audio request paths.

…dpoints

Aliases 404'd on every realtime and audio endpoint while resolving fine on
chat, responses, and embeddings:

  POST /v1/realtime/calls?model=<alias>            -> 404 model not found
  POST /v1/realtime/client_secrets (session.model) -> 404
  GET  /v1/realtime?model=<alias>                  -> 404
  POST /v1/audio/speech                            -> 404
  POST /v1/audio/transcriptions                    -> 404

Two causes. realtimeService and audioService never received the virtual-model
resolver (h.modelResolver), so prepare() only type-asserted the registry-only
selectorResolver on the Router, which canonicalizes concrete ids and leaves an
alias untouched. And even the parsed selector was discarded: the raw request
model was forwarded to the router, which then failed the provider lookup. These
endpoints resolve their model in the service layer because OperationRealtime and
the audio operations are absent from the workflow middleware's resolveWorkflow
switch, so ensureRequestModelResolution never runs for them.

Both services now resolve through the shared resolveServiceModel helper, which
runs the same two-step gateway.ResolveExecutionSelector (alias table, then
provider registry) used by chat/responses/embeddings, and forward the resolved
concrete selector upstream.

The prior unit test could not catch this: it injected a mock whose ResolveModel
faked the alias resolution the production Router never performs, and asserted the
router received the raw alias under a comment claiming the opposite. Replaced with
regression tests that drive a real virtualmodels.Service through a registry-only
provider double for all three realtime routes plus speech and transcription; each
fails if the resolver is unwired or the raw model is forwarded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 032f40a5-41c0-4de8-b671-97672334e1c6

📥 Commits

Reviewing files that changed from the base of the PR and between 8e53f67 and a43d63e.

📒 Files selected for processing (7)
  • internal/server/audio_service.go
  • internal/server/handlers.go
  • internal/server/realtime_audio_alias_resolution_test.go
  • internal/server/realtime_service.go
  • internal/server/realtime_webrtc_service.go
  • internal/server/realtime_webrtc_service_test.go
  • internal/server/request_model_resolution.go

📝 Walkthrough

Walkthrough

Audio and realtime services now resolve model aliases to concrete selectors via a new resolveServiceModel helper before dispatching to providers. audioRoute/realtimeRoute store the resolved core.ModelSelector, handlers rewrite request fields accordingly, handlers.go wires the resolver, and new/updated tests verify alias resolution.

Changes

Alias Resolution Wiring

Layer / File(s) Summary
Shared resolution helper
internal/server/request_model_resolution.go
New resolveServiceModel validates the requested selector via core.ParseModelSelector and resolves it through gateway.ResolveExecutionSelector, returning a concrete core.ModelSelector or propagating errors.
Audio service resolution
internal/server/audio_service.go
Adds modelResolver field, stores resolved selector in audioRoute, resolves via resolveServiceModel in prepare/routeFor, and rewrites req.Model/req.Provider in CreateSpeech/CreateTranscription before dispatch.
Realtime & WebRTC service resolution
internal/server/realtime_service.go, internal/server/realtime_webrtc_service.go
Adds modelResolver field, stores resolved selector in realtimeRoute, replaces inline selector parsing with resolveServiceModel, and uses route.selector.Model/Provider for RealtimeTarget, RealtimeCallTarget, and RealtimeClientSecretTarget.
Handler wiring and tests
internal/server/handlers.go, internal/server/realtime_audio_alias_resolution_test.go, internal/server/realtime_webrtc_service_test.go
Wires modelResolver into audio()/realtime() service constructors; adds new tests verifying alias-to-concrete resolution across realtime client secrets, calls, websocket, audio speech, and transcription flows via a virtualmodels.Service; updates existing multipart test to expect resolved model.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Service as audioService/realtimeService
  participant resolveServiceModel
  participant Gateway as gateway.ResolveExecutionSelector
  participant Router as Provider Router

  Client->>Service: request with alias model
  Service->>resolveServiceModel: resolve(requested selector)
  resolveServiceModel->>Gateway: ResolveExecutionSelector(ctx, provider, resolver)
  Gateway-->>resolveServiceModel: resolved core.ModelSelector
  resolveServiceModel-->>Service: resolved selector
  Service->>Service: store selector on route, rewrite req.Model/Provider
  Service->>Router: dispatch/target lookup using resolved model/provider
  Router-->>Client: response
Loading

Possibly related PRs

  • ENTERPILOT/GoModel#177: Builds on the same selector/alias resolution refactor and RequestModelResolver/ResolveModel API used by resolveServiceModel.
  • ENTERPILOT/GoModel#357: Changes the gateway.ResolveExecutionSelector pipeline that resolveServiceModel directly depends on.
  • ENTERPILOT/GoModel#372: Also modifies audio_service.go's audioRoute/routeFor around resolved model selectors, for pricing/usage logging.

Poem

A rabbit hops through aliased trails,
resolving names before it sails 🐇
"gpt-realtime-2," the true selector sings,
no more masquerading, aliased things!
Through audio, realtime, and calls so bright,
the right model dispatches — pure delight!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: resolving virtual-model aliases on realtime and audio endpoints.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 fix/realtime-audio-alias-resolution

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 85.00000% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/server/request_model_resolution.go 71.42% 1 Missing and 1 partial ⚠️
internal/server/audio_service.go 80.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

Safe to merge with low risk.

The changed paths resolve aliases before authorization, budgeting, rate limiting, provider lookup, and upstream forwarding. Tests cover the affected realtime websocket, WebRTC signaling, client secrets, speech, and transcription routes. No blocking correctness or security issues were identified.

No files require special attention.

T-Rex T-Rex Logs

What T-Rex did

  • Ran the targeted proof for realtime_audio_alias and confirmed the run produced PASS results with EXIT_CODE: 0.
  • Ran the broader smoke test for realtime_audio_alias and documented the EXIT_CODE: 1 with internal/providers failure details.
  • Noted sandbox constraints prevented running live OpenAI, Docker-backed integration, and lint checks as requested.
  • Uploaded and cataloged two log artifacts to capture the targeted and broader proof results for review.

View all artifacts

T-Rex Ran code and verified through T-Rex

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Client
participant Service as realtime/audio service
participant VM as virtualmodels.Service
participant Gateway as gateway.ResolveExecutionSelector
participant Router as Provider Router
participant Upstream as Concrete Provider

Client->>Service: Request with alias model
Service->>Gateway: resolveServiceModel(alias, provider hint)
Gateway->>VM: Resolve alias for request user_path
VM-->>Gateway: concrete provider/model selector
Gateway->>Router: registry canonicalization
Router-->>Gateway: resolved concrete selector
Service->>Service: authorize, budget, rate-limit concrete selector
Service->>Router: route using concrete selector
Router->>Upstream: forward rewritten concrete model
Upstream-->>Client: Provider response
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Client
participant Service as realtime/audio service
participant VM as virtualmodels.Service
participant Gateway as gateway.ResolveExecutionSelector
participant Router as Provider Router
participant Upstream as Concrete Provider

Client->>Service: Request with alias model
Service->>Gateway: resolveServiceModel(alias, provider hint)
Gateway->>VM: Resolve alias for request user_path
VM-->>Gateway: concrete provider/model selector
Gateway->>Router: registry canonicalization
Router-->>Gateway: resolved concrete selector
Service->>Service: authorize, budget, rate-limit concrete selector
Service->>Router: route using concrete selector
Router->>Upstream: forward rewritten concrete model
Upstream-->>Client: Provider response
Loading

Reviews (1): Last reviewed commit: "fix(realtime): resolve virtual-model ali..." | Re-trigger Greptile

@SantiagoDePolonia SantiagoDePolonia merged commit ebd8320 into main Jul 8, 2026
29 of 31 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.

2 participants