diff --git a/backend/DeviceHub/DeviceHubEndpoint.cs b/backend/DeviceHub/DeviceHubEndpoint.cs index 62e1b54..be57004 100644 --- a/backend/DeviceHub/DeviceHubEndpoint.cs +++ b/backend/DeviceHub/DeviceHubEndpoint.cs @@ -130,8 +130,14 @@ public static class DeviceHubEndpoint { var enabledTools = JsonSerializer.Deserialize(device.Config?.EnabledToolsJson ?? "[]") ?? Array.Empty(); + // Promote any deprecated Beta-era model name to the GA name. Plan 4's + // admin UI will let users set a specific value; until then, anything + // that mentions the old preview slug routes to gpt-realtime. + var model = device.Config?.Model ?? "gpt-realtime"; + if (model.StartsWith("gpt-4o-realtime-preview", StringComparison.Ordinal)) + model = "gpt-realtime"; var cfg = new RealtimeSettings( - Model: device.Config?.Model ?? "gpt-4o-realtime-preview", + Model: model, Voice: device.Config?.Voice ?? "alloy", SystemPrompt: device.Config?.SystemPrompt ?? "", IdleTimeoutSeconds: device.Config?.IdleTimeoutSeconds ?? 30, diff --git a/backend/Realtime/OpenAIRealtimeUpstream.cs b/backend/Realtime/OpenAIRealtimeUpstream.cs index 167bfb5..2720915 100644 --- a/backend/Realtime/OpenAIRealtimeUpstream.cs +++ b/backend/Realtime/OpenAIRealtimeUpstream.cs @@ -11,7 +11,8 @@ public class OpenAIRealtimeUpstream : IRealtimeUpstream public async Task ConnectAsync(string apiKey, string model, CancellationToken ct) { _ws.Options.SetRequestHeader("Authorization", $"Bearer {apiKey}"); - _ws.Options.SetRequestHeader("OpenAI-Beta", "realtime=v1"); + // The GA Realtime API does NOT take the OpenAI-Beta: realtime=v1 header. + // Sending it returns upstream error code=beta_api_shape_disabled. var uri = new Uri($"wss://api.openai.com/v1/realtime?model={Uri.EscapeDataString(model)}"); await _ws.ConnectAsync(uri, ct); } diff --git a/backend/Realtime/RealtimeEvents.cs b/backend/Realtime/RealtimeEvents.cs index 1c5bcad..415d17a 100644 --- a/backend/Realtime/RealtimeEvents.cs +++ b/backend/Realtime/RealtimeEvents.cs @@ -6,6 +6,12 @@ namespace backend.Realtime; public static class RealtimeEvents { + // GA Realtime API shape (the Beta API shape was retired in 2026 with + // upstream error "beta_api_shape_disabled"). Key differences: + // - session.type = "realtime" is required. + // - voice / audio formats / transcription / turn_detection now nested + // under session.audio.{input,output}. + // - No OpenAI-Beta header on the upgrade (see OpenAIRealtimeUpstream). public static JsonObject SessionUpdate(RealtimeSettings cfg, IEnumerable enabledTools) { var tools = new JsonArray(); @@ -25,18 +31,36 @@ public static class RealtimeEvents ["type"] = "session.update", ["session"] = new JsonObject { - ["voice"] = cfg.Voice, + ["type"] = "realtime", + ["model"] = cfg.Model, ["instructions"] = cfg.SystemPrompt, - ["modalities"] = new JsonArray("text", "audio"), - ["input_audio_format"] = "pcm16", - ["output_audio_format"] = "pcm16", - ["input_audio_transcription"] = new JsonObject { ["model"] = "whisper-1" }, - ["turn_detection"] = new JsonObject + ["audio"] = new JsonObject { - ["type"] = "server_vad", - ["threshold"] = 0.5, - ["prefix_padding_ms"] = 300, - ["silence_duration_ms"] = 500, + ["input"] = new JsonObject + { + ["format"] = new JsonObject + { + ["type"] = "audio/pcm", + ["rate"] = 24000, + }, + ["transcription"] = new JsonObject { ["model"] = "whisper-1" }, + ["turn_detection"] = new JsonObject + { + ["type"] = "server_vad", + ["threshold"] = 0.5, + ["prefix_padding_ms"] = 300, + ["silence_duration_ms"] = 500, + }, + }, + ["output"] = new JsonObject + { + ["voice"] = cfg.Voice, + ["format"] = new JsonObject + { + ["type"] = "audio/pcm", + ["rate"] = 24000, + }, + }, }, ["tools"] = tools, ["tool_choice"] = "auto", diff --git a/backend/Settings/SystemSettings.cs b/backend/Settings/SystemSettings.cs index 650b1de..0de1980 100644 --- a/backend/Settings/SystemSettings.cs +++ b/backend/Settings/SystemSettings.cs @@ -6,6 +6,6 @@ public class SystemSettings public string DefaultSystemPrompt { get; set; } = "You are a helpful voice assistant. Keep responses concise."; public string DefaultVoice { get; set; } = "alloy"; - public string DefaultModel { get; set; } = "gpt-4o-realtime-preview"; + public string DefaultModel { get; set; } = "gpt-realtime"; public int DefaultIdleTimeoutSeconds { get; set; } = 30; }