Skip to content

Commit d526d3d

Browse files
committed
Extend drift detection for GA realtime protocol
Model canary for 5 GA models, GA protocol probe with Beta normalization, updated SDK shapes with phase field, GA connection mode in ws-providers.
1 parent 058b444 commit d526d3d

3 files changed

Lines changed: 313 additions & 32 deletions

File tree

src/__tests__/drift/sdk-shapes.ts

Lines changed: 218 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -720,7 +720,223 @@ export function openaiRealtimeTextEventShapes(): SSEEventShape[] {
720720
session: {
721721
id: "sess_abc123",
722722
object: "realtime.session",
723-
model: "gpt-4o-mini",
723+
model: "gpt-realtime-2",
724+
modalities: ["text"],
725+
instructions: "",
726+
tools: [],
727+
audio: {
728+
voice: null,
729+
input_audio_format: null,
730+
output_audio_format: null,
731+
input_audio_noise_reduction: null,
732+
input_audio_transcription: null,
733+
},
734+
turn_detection: null,
735+
temperature: 0.8,
736+
expires_at: 1700000000,
737+
max_response_output_tokens: "inf",
738+
input_audio_transcription: null,
739+
tool_choice: "auto",
740+
type: "conversation",
741+
reasoning: null,
742+
},
743+
}),
744+
},
745+
{
746+
type: "session.updated",
747+
dataShape: extractShape({
748+
type: "session.updated",
749+
event_id: "event_abc123",
750+
session: {
751+
object: "realtime.session",
752+
model: "gpt-realtime-2",
753+
modalities: ["text"],
754+
instructions: "",
755+
tools: [],
756+
audio: {
757+
voice: null,
758+
input_audio_format: null,
759+
output_audio_format: null,
760+
input_audio_noise_reduction: null,
761+
input_audio_transcription: null,
762+
},
763+
turn_detection: null,
764+
temperature: 0.8,
765+
expires_at: 1700000000,
766+
max_response_output_tokens: "inf",
767+
input_audio_transcription: null,
768+
tool_choice: "auto",
769+
type: "conversation",
770+
reasoning: null,
771+
},
772+
}),
773+
},
774+
{
775+
type: "conversation.item.added",
776+
dataShape: extractShape({
777+
type: "conversation.item.added",
778+
event_id: "event_abc123",
779+
previous_item_id: null,
780+
item: {
781+
type: "message",
782+
id: "item_abc123",
783+
role: "user",
784+
content: [{ type: "input_text", text: "Say hello" }],
785+
},
786+
}),
787+
},
788+
{
789+
type: "response.created",
790+
dataShape: extractShape({
791+
type: "response.created",
792+
event_id: "event_abc123",
793+
response: {
794+
id: "resp_abc123",
795+
object: "realtime.response",
796+
status: "in_progress",
797+
status_details: null,
798+
output: [],
799+
usage: null,
800+
},
801+
}),
802+
},
803+
{
804+
type: "response.output_item.added",
805+
dataShape: extractShape({
806+
type: "response.output_item.added",
807+
event_id: "event_abc123",
808+
response_id: "resp_abc123",
809+
output_index: 0,
810+
item: {
811+
id: "item_abc123",
812+
type: "message",
813+
role: "assistant",
814+
status: "in_progress",
815+
content: [],
816+
phase: "final_answer",
817+
},
818+
}),
819+
},
820+
{
821+
type: "response.content_part.added",
822+
dataShape: extractShape({
823+
type: "response.content_part.added",
824+
event_id: "event_abc123",
825+
response_id: "resp_abc123",
826+
item_id: "item_abc123",
827+
output_index: 0,
828+
content_index: 0,
829+
part: { type: "output_text", text: "" },
830+
}),
831+
},
832+
{
833+
type: "response.output_text.delta",
834+
dataShape: extractShape({
835+
type: "response.output_text.delta",
836+
event_id: "event_abc123",
837+
response_id: "resp_abc123",
838+
item_id: "item_abc123",
839+
output_index: 0,
840+
content_index: 0,
841+
delta: "Hello",
842+
}),
843+
},
844+
{
845+
type: "response.output_text.done",
846+
dataShape: extractShape({
847+
type: "response.output_text.done",
848+
event_id: "event_abc123",
849+
response_id: "resp_abc123",
850+
item_id: "item_abc123",
851+
output_index: 0,
852+
content_index: 0,
853+
text: "Hello!",
854+
}),
855+
},
856+
{
857+
type: "response.content_part.done",
858+
dataShape: extractShape({
859+
type: "response.content_part.done",
860+
event_id: "event_abc123",
861+
response_id: "resp_abc123",
862+
item_id: "item_abc123",
863+
output_index: 0,
864+
content_index: 0,
865+
part: { type: "output_text", text: "Hello!" },
866+
}),
867+
},
868+
{
869+
type: "response.output_item.done",
870+
dataShape: extractShape({
871+
type: "response.output_item.done",
872+
event_id: "event_abc123",
873+
response_id: "resp_abc123",
874+
output_index: 0,
875+
item: {
876+
id: "item_abc123",
877+
type: "message",
878+
role: "assistant",
879+
status: "completed",
880+
content: [{ type: "output_text", text: "Hello!" }],
881+
phase: "final_answer",
882+
},
883+
}),
884+
},
885+
{
886+
type: "conversation.item.done",
887+
dataShape: extractShape({
888+
type: "conversation.item.done",
889+
event_id: "event_abc123",
890+
item: {
891+
id: "item_abc123",
892+
object: "realtime.item",
893+
type: "message",
894+
role: "assistant",
895+
status: "completed",
896+
content: [{ type: "output_text", text: "Hello!" }],
897+
},
898+
}),
899+
},
900+
{
901+
type: "response.done",
902+
dataShape: extractShape({
903+
type: "response.done",
904+
event_id: "event_abc123",
905+
response: {
906+
id: "resp_abc123",
907+
object: "realtime.response",
908+
status: "completed",
909+
output: [
910+
{
911+
id: "item_abc123",
912+
type: "message",
913+
role: "assistant",
914+
status: "completed",
915+
content: [{ type: "output_text", text: "Hello!" }],
916+
},
917+
],
918+
usage: { total_tokens: 0, input_tokens: 0, output_tokens: 0 },
919+
},
920+
}),
921+
},
922+
];
923+
}
924+
925+
/**
926+
* Beta Realtime event shapes — uses legacy Beta event names.
927+
* Useful for three-way comparison when testing Beta compatibility shim.
928+
*/
929+
export function openaiRealtimeBetaTextEventShapes(): SSEEventShape[] {
930+
return [
931+
{
932+
type: "session.created",
933+
dataShape: extractShape({
934+
type: "session.created",
935+
event_id: "event_abc123",
936+
session: {
937+
id: "sess_abc123",
938+
object: "realtime.session",
939+
model: "gpt-realtime-2",
724940
modalities: ["text"],
725941
instructions: "",
726942
tools: [],
@@ -743,7 +959,7 @@ export function openaiRealtimeTextEventShapes(): SSEEventShape[] {
743959
event_id: "event_abc123",
744960
session: {
745961
object: "realtime.session",
746-
model: "gpt-4o-mini",
962+
model: "gpt-realtime-2",
747963
modalities: ["text"],
748964
instructions: "",
749965
tools: [],

src/__tests__/drift/ws-providers.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -344,23 +344,27 @@ export async function openaiRealtimeWS(
344344
config: ProviderConfig,
345345
text: string,
346346
tools?: object[],
347+
beta = true,
347348
): Promise<WSResult> {
348349
// Realtime API requires a realtime-specific model (gpt-4o-mini doesn't work)
350+
const headers: Record<string, string> = {
351+
Authorization: `Bearer ${config.apiKey}`,
352+
};
353+
if (beta) {
354+
headers["OpenAI-Beta"] = "realtime=v1";
355+
}
349356
const ws = await connectTLSWebSocket(
350357
"api.openai.com",
351-
"/v1/realtime?model=gpt-4o-mini-realtime-preview",
352-
{
353-
Authorization: `Bearer ${config.apiKey}`,
354-
"OpenAI-Beta": "realtime=v1",
355-
},
358+
"/v1/realtime?model=gpt-realtime-2",
359+
headers,
356360
);
357361

358362
// Step 1: Wait for session.created
359363
const sessionCreated = await ws.waitUntil((msg: any) => msg?.type === "session.created");
360364

361365
// Step 2: Send session.update
362366
const session: Record<string, unknown> = {
363-
model: "gpt-4o-mini-realtime-preview",
367+
model: "gpt-realtime-2",
364368
modalities: ["text"],
365369
};
366370
if (tools) session.tools = tools;
@@ -381,8 +385,11 @@ export async function openaiRealtimeWS(
381385
}),
382386
);
383387

384-
// Step 5: Wait for conversation.item.created
385-
const itemCreated = await ws.waitUntil((msg: any) => msg?.type === "conversation.item.created");
388+
// Step 5: Wait for conversation.item.created (Beta) or conversation.item.added (GA)
389+
const itemCreated = await ws.waitUntil(
390+
(msg: any) =>
391+
msg?.type === "conversation.item.created" || msg?.type === "conversation.item.added",
392+
);
386393

387394
// Step 6: Send response.create
388395
ws.send(JSON.stringify({ type: "response.create" }));

0 commit comments

Comments
 (0)