Skip to content

fix(ai-openai): migrate WebRTC realtime adapter to OpenAI GA API#699

Merged
tombeckenham merged 5 commits into
TanStack:mainfrom
amitsaroj:fix/openai-realtime-ga-migration
Jun 10, 2026
Merged

fix(ai-openai): migrate WebRTC realtime adapter to OpenAI GA API#699
tombeckenham merged 5 commits into
TanStack:mainfrom
amitsaroj:fix/openai-realtime-ga-migration

Conversation

@amitsaroj

@amitsaroj amitsaroj commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

🎯 Changes

Fixes #586

Migrates @tanstack/ai-openai's realtime adapters from the deprecated OpenAI Beta realtime API (shut down 2026-05-12) to the GA API. The Beta shape caused all openaiRealtime() connections to fail with beta_api_shape_disabled.

Full GA migration:

  • SDP exchange (openaiRealtime()): POST /v1/realtime/calls instead of the Beta /v1/realtime?model=... (GA rejects ?model=; the model is bound to the ephemeral key).
  • Token minting (openaiRealtimeToken()): POST /v1/realtime/client_secrets instead of the retired Beta /v1/realtime/sessions, parsing the GA top-level value/expires_at response shape.
  • session.update: GA payload shape via a new pure buildSessionUpdate() helper — required session.type, audio.input.transcription, audio.input.turn_detection, audio.output.voice, output_modalities, max_output_tokens. GA rejects the entire update on any unknown (Beta-named) field, so the original two-line fix alone still left instructions/tools/voice unapplied. temperature (removed in GA) is dropped with a debug log.
  • Server events: handled under GA names (response.output_audio_transcript.*, response.output_audio.*, output_text/output_audio content parts).
  • Models: default is now gpt-realtime; the gpt-4o-(mini-)realtime-preview ids (shut down by OpenAI 2026-05-07) are removed from OpenAIRealtimeModel, docs, and examples.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.

🧪 Test Plan

  • New unit tests: realtime-session-update.test.ts (GA session.update shape, no Beta field names) and realtime-token.test.ts (client-secret request/response parsing).
  • Live-verified against OpenAI's API: client_secrets → 200 with ek_… key; /v1/realtime/calls → 201 with SDP answer using the ephemeral key; session.update with the exact built payload → session.updated echoing voice, semantic VAD, tools, output_modalities, and max_output_tokens.
  • WebRTC itself isn't coverable by the aimock E2E harness; unit + live verification stand in.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Realtime connections now default to the gpt-realtime model.
    • WebRTC SDP exchange moved to the GA realtime calls endpoint.
    • Session.update payloads now match GA shape (temperature no longer sent) and realtime transcript/stream event names follow GA naming.
  • Documentation
    • Examples, quickstarts, and migration guides updated to reflect GA API usage and model changes.

@coderabbitai

coderabbitai Bot commented Jun 3, 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: b4516998-24db-4a8e-add5-2a32ba9d665b

📥 Commits

Reviewing files that changed from the base of the PR and between 676dd84 and 0b2d118.

📒 Files selected for processing (2)
  • packages/ai-openai/src/realtime/session-update.ts
  • packages/ai-openai/tests/realtime-session-update.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ai-openai/tests/realtime-session-update.test.ts
  • packages/ai-openai/src/realtime/session-update.ts

📝 Walkthrough

Walkthrough

Migrate the OpenAI realtime adapter from Beta to GA: mint ephemeral tokens via POST /v1/realtime/client_secrets, POST SDP to /v1/realtime/calls, construct GA-shaped session.update payloads, rename GA event/content keys, and update types, tests, docs, and examples.

Changes

OpenAI Realtime Adapter Fixes

Layer / File(s) Summary
Client-secret token flow & parsing
packages/ai-openai/src/realtime/token.ts, packages/ai-openai/tests/realtime-token.test.ts
POSTs to /v1/realtime/client_secrets, adds buildClientSecretRequest and parseClientSecretResponse, maps GA response (value/expires_at) to internal RealtimeToken and defaults model to gpt-realtime.
GA session.update builder and tests
packages/ai-openai/src/realtime/session-update.ts, packages/ai-openai/tests/realtime-session-update.test.ts
Adds buildSessionUpdate producing GA-shaped session.update (type: 'realtime', audio.input.transcription, turn_detection handling, audio.output.voice, tools mapping, output_modalities, max_output_tokens) and tests validating field shapes and exclusions (no temperature).
Adapter: SDP exchange, event renames, message parsing, updateSession
packages/ai-openai/src/realtime/adapter.ts
Default model changed to gpt-realtime; SDP exchange now POSTs to /v1/realtime/calls (no ?model); updateSession rewired to always use buildSessionUpdate and drop temperature; server event handlers and message part parsing updated to GA names (response.output_*, output_text/output_audio).
Types: models & client-secret response
packages/ai-openai/src/realtime/types.ts
Narrow OpenAIRealtimeModel to gpt-realtime/gpt-realtime-mini; add OpenAIRealtimeClientSecretResponse minimal shape replacing prior session response type.
Docs and examples
docs/*, examples/*, packages/ai/src/realtime/index.ts, .changeset/*
Example adapters, docs, and changeset updated to reference gpt-realtime/gpt-realtime-mini, document GA endpoints and session.update shape changes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • tombeckenham
  • jherr

Poem

🐰 I hopped through endpoints, swift and spry,
Switched /sessions to client_secrets, no more shy,
SDP now travels to /calls with glee,
Session updates trimmed — temperature set free,
Tiny rabbit cheers: GA realtime, let it be!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: migrating the WebRTC realtime adapter from the deprecated OpenAI Beta API to the GA API.
Description check ✅ Passed The description comprehensively covers the changes made, includes a checklist with all items completed, identifies the linked issue (#586), and provides a detailed test plan with verification approach.
Linked Issues check ✅ Passed The PR fully addresses issue #586: migrates SDP endpoint to POST /v1/realtime/calls, updates token minting to use /v1/realtime/client_secrets, handles GA event naming, updates session.update payload, removes deprecated models, and includes comprehensive testing and verification.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the GA API migration: adapter endpoint updates, session payload restructuring, event handler updates, model removals, documentation updates, example code updates, and test coverage additions.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

amitsaroj and others added 2 commits June 7, 2026 23:53
Completes the GA migration started in this PR so the whole realtime flow
works against OpenAI's GA API (the Beta shape was shut down 2026-05-12):

- openaiRealtimeToken() mints ephemeral keys via POST
  /v1/realtime/client_secrets (the Beta /v1/realtime/sessions endpoint is
  retired) and parses the GA top-level value/expires_at response shape
- session.update payloads use the GA shape via a new pure
  buildSessionUpdate() helper: required session.type, audio.input.*,
  audio.output.voice, output_modalities, max_output_tokens; temperature
  (removed in GA) is dropped with a debug log instead of getting the whole
  update rejected with unknown_parameter
- server events handled under GA names (response.output_audio_transcript.*,
  response.output_audio.*, output_text/output_audio content parts)
- removed the now-unused model local in createWebRTCConnection (the GA
  /calls endpoint rejects ?model=; the model is bound to the ephemeral key)
- default model gpt-realtime; dead gpt-4o-(mini-)realtime-preview ids
  (shut down 2026-05-07) removed from OpenAIRealtimeModel, docs, and
  examples
- unit tests for the session.update payload and client-secret
  request/response shapes; changeset added

Live-verified against the OpenAI API: client_secrets 200 (ek_ token),
/v1/realtime/calls 201 with SDP answer, and session.updated echoing voice,
semantic VAD, tools, output_modalities, and max_output_tokens.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tombeckenham

Copy link
Copy Markdown
Contributor

Thanks @amitsaroj — the two endpoint fixes here were exactly right, but on review the GA migration needed a few more pieces to actually work end to end, so I've pushed a completing commit (676dd84) to this branch (maintainer edit):

  • openaiRealtimeToken() was still minting tokens via the retired Beta /v1/realtime/sessions; it now uses /v1/realtime/client_secrets with the GA response shape.
  • GA rejects an entire session.update if it contains any Beta-named field (voice, modalities, input_audio_transcription, temperature, …), so adding type: 'realtime' alone still left instructions/tools/voice unapplied. The payload now uses the full GA shape (audio.input/output, output_modalities, max_output_tokens).
  • Server events are now handled under their GA names (response.output_audio_transcript.*, response.output_audio.*).
  • The now-unused model local was failing pnpm test:types (noUnusedLocals) — fork CI hadn't run, which is why it slipped through.
  • Added unit tests, a changeset, and updated dead gpt-4o-realtime-preview model ids (shut down 2026-05-07) across types/docs/examples.

Live-verified against OpenAI's GA API: token minting (200, ek_…), SDP exchange on /calls (201 with answer), and session.update accepted with all config echoed back in session.updated. pnpm test:pr is green locally.

@nx-cloud

nx-cloud Bot commented Jun 10, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 676dd84

Command Status Duration Result
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 1m 7s View ↗

☁️ Nx Cloud last updated this comment at 2026-06-10 04:12:41 UTC

@pkg-pr-new

pkg-pr-new Bot commented Jun 10, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/@tanstack/ai@699

@tanstack/ai-anthropic

npm i https://pkg.pr.new/@tanstack/ai-anthropic@699

@tanstack/ai-client

npm i https://pkg.pr.new/@tanstack/ai-client@699

@tanstack/ai-code-mode

npm i https://pkg.pr.new/@tanstack/ai-code-mode@699

@tanstack/ai-code-mode-skills

npm i https://pkg.pr.new/@tanstack/ai-code-mode-skills@699

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/@tanstack/ai-devtools-core@699

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/@tanstack/ai-elevenlabs@699

@tanstack/ai-event-client

npm i https://pkg.pr.new/@tanstack/ai-event-client@699

@tanstack/ai-fal

npm i https://pkg.pr.new/@tanstack/ai-fal@699

@tanstack/ai-gemini

npm i https://pkg.pr.new/@tanstack/ai-gemini@699

@tanstack/ai-grok

npm i https://pkg.pr.new/@tanstack/ai-grok@699

@tanstack/ai-groq

npm i https://pkg.pr.new/@tanstack/ai-groq@699

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-isolate-cloudflare@699

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/@tanstack/ai-isolate-node@699

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs@699

@tanstack/ai-mcp

npm i https://pkg.pr.new/@tanstack/ai-mcp@699

@tanstack/ai-ollama

npm i https://pkg.pr.new/@tanstack/ai-ollama@699

@tanstack/ai-openai

npm i https://pkg.pr.new/@tanstack/ai-openai@699

@tanstack/ai-openrouter

npm i https://pkg.pr.new/@tanstack/ai-openrouter@699

@tanstack/ai-preact

npm i https://pkg.pr.new/@tanstack/ai-preact@699

@tanstack/ai-react

npm i https://pkg.pr.new/@tanstack/ai-react@699

@tanstack/ai-react-ui

npm i https://pkg.pr.new/@tanstack/ai-react-ui@699

@tanstack/ai-solid

npm i https://pkg.pr.new/@tanstack/ai-solid@699

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/@tanstack/ai-solid-ui@699

@tanstack/ai-svelte

npm i https://pkg.pr.new/@tanstack/ai-svelte@699

@tanstack/ai-utils

npm i https://pkg.pr.new/@tanstack/ai-utils@699

@tanstack/ai-vue

npm i https://pkg.pr.new/@tanstack/ai-vue@699

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/@tanstack/ai-vue-ui@699

@tanstack/openai-base

npm i https://pkg.pr.new/@tanstack/openai-base@699

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/@tanstack/preact-ai-devtools@699

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/@tanstack/react-ai-devtools@699

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/@tanstack/solid-ai-devtools@699

commit: 676dd84

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/ai-openai/tests/realtime-token.test.ts (1)

1-56: ⚡ Quick win

Move this test next to src/realtime/token.ts to match test placement rules.

The test implementation is good, but this new file is not colocated with the source module it validates. Please place it alongside packages/ai-openai/src/realtime/token.ts as a *.test.ts file.

As per coding guidelines, "Place unit tests alongside source code in *.test.ts files".

🤖 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 `@packages/ai-openai/tests/realtime-token.test.ts` around lines 1 - 56, Move
the test file so it is colocated with the implementation: create
packages/ai-openai/src/realtime/token.test.ts containing the current test body,
update the imports to import { buildClientSecretRequest,
parseClientSecretResponse } from './token' (relative import to token.ts), and
remove the original test from packages/ai-openai/tests; ensure the test filename
ends with .test.ts and that references to buildClientSecretRequest and
parseClientSecretResponse remain unchanged.

Source: Coding guidelines

🤖 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 `@packages/ai-openai/src/realtime/token.ts`:
- Around line 38-55: The parser (parseClientSecretResponse) must fail-fast for
empty token strings and avoid propagating non-string session.model values:
update the validation to require data.value to be a non-empty string (e.g.,
non-whitespace) before returning, and when building config.model only accept
data.session?.model if typeof === 'string' (otherwise use fallbackModel); keep
the existing checks for data.expires_at and preserve provider/token/expiresAt
behavior.

---

Nitpick comments:
In `@packages/ai-openai/tests/realtime-token.test.ts`:
- Around line 1-56: Move the test file so it is colocated with the
implementation: create packages/ai-openai/src/realtime/token.test.ts containing
the current test body, update the imports to import { buildClientSecretRequest,
parseClientSecretResponse } from './token' (relative import to token.ts), and
remove the original test from packages/ai-openai/tests; ensure the test filename
ends with .test.ts and that references to buildClientSecretRequest and
parseClientSecretResponse remain unchanged.
🪄 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

Run ID: 089bb19e-776a-4c5d-be70-a48a2910c626

📥 Commits

Reviewing files that changed from the base of the PR and between b6841a8 and 676dd84.

📒 Files selected for processing (12)
  • .changeset/openai-realtime-ga-migration.md
  • docs/media/realtime-chat.md
  • docs/reference/functions/realtimeToken.md
  • examples/ts-code-mode-web/src/routes/_execute-prompt/api.realtime-token.ts
  • examples/ts-react-chat/src/lib/use-realtime.ts
  • packages/ai-openai/src/realtime/adapter.ts
  • packages/ai-openai/src/realtime/session-update.ts
  • packages/ai-openai/src/realtime/token.ts
  • packages/ai-openai/src/realtime/types.ts
  • packages/ai-openai/tests/realtime-session-update.test.ts
  • packages/ai-openai/tests/realtime-token.test.ts
  • packages/ai/src/realtime/index.ts
✅ Files skipped from review due to trivial changes (5)
  • examples/ts-code-mode-web/src/routes/_execute-prompt/api.realtime-token.ts
  • docs/reference/functions/realtimeToken.md
  • .changeset/openai-realtime-ga-migration.md
  • packages/ai/src/realtime/index.ts
  • docs/media/realtime-chat.md

Comment on lines +38 to +55
if (
!data ||
typeof data.value !== 'string' ||
typeof data.expires_at !== 'number' ||
!Number.isFinite(data.expires_at)
) {
throw new Error(
'OpenAI realtime client secret response missing or malformed `value`/`expires_at`',
)
}

return {
provider: 'openai',
token: data.value,
expiresAt: data.expires_at * 1000,
config: {
model: data.session?.model ?? fallbackModel,
},

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

Harden response validation for value and session.model.

parseClientSecretResponse currently accepts an empty token string and can propagate a non-string session.model from untrusted API JSON into config.model. This can create late auth/config failures instead of failing fast at parse time.

Proposed fix
   if (
     !data ||
     typeof data.value !== 'string' ||
+    data.value.length === 0 ||
     typeof data.expires_at !== 'number' ||
     !Number.isFinite(data.expires_at)
   ) {
     throw new Error(
       'OpenAI realtime client secret response missing or malformed `value`/`expires_at`',
     )
   }

+  const resolvedModel =
+    typeof data.session?.model === 'string' && data.session.model.length > 0
+      ? data.session.model
+      : fallbackModel
+
   return {
     provider: 'openai',
     token: data.value,
     expiresAt: data.expires_at * 1000,
     config: {
-      model: data.session?.model ?? fallbackModel,
+      model: resolvedModel,
     },
   }
🤖 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 `@packages/ai-openai/src/realtime/token.ts` around lines 38 - 55, The parser
(parseClientSecretResponse) must fail-fast for empty token strings and avoid
propagating non-string session.model values: update the validation to require
data.value to be a non-empty string (e.g., non-whitespace) before returning, and
when building config.model only accept data.session?.model if typeof ===
'string' (otherwise use fallbackModel); keep the existing checks for
data.expires_at and preserve provider/token/expiresAt behavior.

tombeckenham and others added 2 commits June 10, 2026 14:46
The GA realtime API only accepts ['audio'] or ['text'] for
output_modalities; the Beta API accepted ['audio', 'text'] and the
provider-agnostic RealtimeSessionConfig still legitimately produces it
(e.g. the example UI's audio+text mode). Sending both got the whole
session.update rejected with: Invalid modalities: ['audio', 'text'].

Collapse to ['audio'] when audio is requested — GA audio replies still
stream text via response.output_audio_transcript.* events, so visible
behavior is unchanged. Live-verified: session.updated accepted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@tombeckenham tombeckenham left a comment

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.

Thank you. This is approved

@tombeckenham tombeckenham merged commit 215b6b4 into TanStack:main Jun 10, 2026
3 checks passed
@github-actions github-actions Bot mentioned this pull request Jun 9, 2026
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.

[ai-openai] Realtime WebRTC SDP exchange uses deprecated Beta endpoint — fails with beta_api_shape_disabled

2 participants