@copilotkit/aimock 1.22.0 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +37 -0
  4. package/README.md +10 -10
  5. package/dist/agui-types.d.ts.map +1 -1
  6. package/dist/aimock-cli.cjs +0 -0
  7. package/dist/aimock-cli.js +0 -0
  8. package/dist/bedrock-converse.cjs +62 -22
  9. package/dist/bedrock-converse.cjs.map +1 -1
  10. package/dist/bedrock-converse.d.cts.map +1 -1
  11. package/dist/bedrock-converse.d.ts.map +1 -1
  12. package/dist/bedrock-converse.js +62 -22
  13. package/dist/bedrock-converse.js.map +1 -1
  14. package/dist/bedrock.cjs +59 -20
  15. package/dist/bedrock.cjs.map +1 -1
  16. package/dist/bedrock.d.cts.map +1 -1
  17. package/dist/bedrock.d.ts.map +1 -1
  18. package/dist/bedrock.js +59 -20
  19. package/dist/bedrock.js.map +1 -1
  20. package/dist/cli.cjs +8 -2
  21. package/dist/cli.cjs.map +1 -1
  22. package/dist/cli.js +8 -2
  23. package/dist/cli.js.map +1 -1
  24. package/dist/cohere.cjs +29 -9
  25. package/dist/cohere.cjs.map +1 -1
  26. package/dist/cohere.d.cts.map +1 -1
  27. package/dist/cohere.d.ts.map +1 -1
  28. package/dist/cohere.js +30 -10
  29. package/dist/cohere.js.map +1 -1
  30. package/dist/config-loader.d.cts.map +1 -1
  31. package/dist/constants.cjs +8 -0
  32. package/dist/constants.cjs.map +1 -0
  33. package/dist/constants.d.cts +8 -0
  34. package/dist/constants.d.cts.map +1 -0
  35. package/dist/constants.d.ts +8 -0
  36. package/dist/constants.d.ts.map +1 -0
  37. package/dist/constants.js +7 -0
  38. package/dist/constants.js.map +1 -0
  39. package/dist/elevenlabs-audio.cjs +41 -18
  40. package/dist/elevenlabs-audio.cjs.map +1 -1
  41. package/dist/elevenlabs-audio.d.cts.map +1 -1
  42. package/dist/elevenlabs-audio.d.ts.map +1 -1
  43. package/dist/elevenlabs-audio.js +42 -19
  44. package/dist/elevenlabs-audio.js.map +1 -1
  45. package/dist/embeddings.cjs +19 -17
  46. package/dist/embeddings.cjs.map +1 -1
  47. package/dist/embeddings.d.cts.map +1 -1
  48. package/dist/embeddings.d.ts.map +1 -1
  49. package/dist/embeddings.js +20 -18
  50. package/dist/embeddings.js.map +1 -1
  51. package/dist/fal-audio.cjs +128 -39
  52. package/dist/fal-audio.cjs.map +1 -1
  53. package/dist/fal-audio.d.cts.map +1 -1
  54. package/dist/fal-audio.d.ts.map +1 -1
  55. package/dist/fal-audio.js +129 -40
  56. package/dist/fal-audio.js.map +1 -1
  57. package/dist/fal.cjs +25 -8
  58. package/dist/fal.cjs.map +1 -1
  59. package/dist/fal.d.cts.map +1 -1
  60. package/dist/fal.d.ts.map +1 -1
  61. package/dist/fal.js +26 -9
  62. package/dist/fal.js.map +1 -1
  63. package/dist/fixture-loader.cjs +11 -2
  64. package/dist/fixture-loader.cjs.map +1 -1
  65. package/dist/fixture-loader.d.cts.map +1 -1
  66. package/dist/fixture-loader.d.ts.map +1 -1
  67. package/dist/fixture-loader.js +11 -2
  68. package/dist/fixture-loader.js.map +1 -1
  69. package/dist/gemini-interactions.cjs +29 -7
  70. package/dist/gemini-interactions.cjs.map +1 -1
  71. package/dist/gemini-interactions.js +28 -8
  72. package/dist/gemini-interactions.js.map +1 -1
  73. package/dist/gemini.cjs +45 -19
  74. package/dist/gemini.cjs.map +1 -1
  75. package/dist/gemini.d.cts.map +1 -1
  76. package/dist/gemini.d.ts.map +1 -1
  77. package/dist/gemini.js +45 -19
  78. package/dist/gemini.js.map +1 -1
  79. package/dist/helpers.cjs +52 -8
  80. package/dist/helpers.cjs.map +1 -1
  81. package/dist/helpers.d.cts +6 -0
  82. package/dist/helpers.d.cts.map +1 -1
  83. package/dist/helpers.d.ts +6 -0
  84. package/dist/helpers.d.ts.map +1 -1
  85. package/dist/helpers.js +52 -9
  86. package/dist/helpers.js.map +1 -1
  87. package/dist/images.cjs +26 -8
  88. package/dist/images.cjs.map +1 -1
  89. package/dist/images.d.cts.map +1 -1
  90. package/dist/images.d.ts.map +1 -1
  91. package/dist/images.js +27 -9
  92. package/dist/images.js.map +1 -1
  93. package/dist/index.cjs +2 -1
  94. package/dist/index.d.cts +2 -1
  95. package/dist/index.d.ts +2 -1
  96. package/dist/index.js +2 -1
  97. package/dist/journal.cjs +17 -7
  98. package/dist/journal.cjs.map +1 -1
  99. package/dist/journal.d.cts +2 -3
  100. package/dist/journal.d.cts.map +1 -1
  101. package/dist/journal.d.ts +2 -3
  102. package/dist/journal.d.ts.map +1 -1
  103. package/dist/journal.js +15 -4
  104. package/dist/journal.js.map +1 -1
  105. package/dist/messages.cjs +33 -12
  106. package/dist/messages.cjs.map +1 -1
  107. package/dist/messages.d.cts.map +1 -1
  108. package/dist/messages.d.ts.map +1 -1
  109. package/dist/messages.js +33 -12
  110. package/dist/messages.js.map +1 -1
  111. package/dist/model-utils.cjs +11 -0
  112. package/dist/model-utils.cjs.map +1 -0
  113. package/dist/model-utils.js +10 -0
  114. package/dist/model-utils.js.map +1 -0
  115. package/dist/ollama.cjs +59 -18
  116. package/dist/ollama.cjs.map +1 -1
  117. package/dist/ollama.d.cts.map +1 -1
  118. package/dist/ollama.d.ts.map +1 -1
  119. package/dist/ollama.js +60 -19
  120. package/dist/ollama.js.map +1 -1
  121. package/dist/recorder.cjs +30 -11
  122. package/dist/recorder.cjs.map +1 -1
  123. package/dist/recorder.d.cts.map +1 -1
  124. package/dist/recorder.d.ts.map +1 -1
  125. package/dist/recorder.js +30 -11
  126. package/dist/recorder.js.map +1 -1
  127. package/dist/responses.cjs +61 -52
  128. package/dist/responses.cjs.map +1 -1
  129. package/dist/responses.d.cts +1 -1
  130. package/dist/responses.d.cts.map +1 -1
  131. package/dist/responses.d.ts +1 -1
  132. package/dist/responses.d.ts.map +1 -1
  133. package/dist/responses.js +62 -53
  134. package/dist/responses.js.map +1 -1
  135. package/dist/router.cjs +7 -3
  136. package/dist/router.cjs.map +1 -1
  137. package/dist/router.js +7 -3
  138. package/dist/router.js.map +1 -1
  139. package/dist/server.cjs +64 -180
  140. package/dist/server.cjs.map +1 -1
  141. package/dist/server.d.cts.map +1 -1
  142. package/dist/server.d.ts.map +1 -1
  143. package/dist/server.js +40 -156
  144. package/dist/server.js.map +1 -1
  145. package/dist/speech.cjs +26 -8
  146. package/dist/speech.cjs.map +1 -1
  147. package/dist/speech.d.cts.map +1 -1
  148. package/dist/speech.d.ts.map +1 -1
  149. package/dist/speech.js +27 -9
  150. package/dist/speech.js.map +1 -1
  151. package/dist/transcription.cjs +57 -19
  152. package/dist/transcription.cjs.map +1 -1
  153. package/dist/transcription.d.cts.map +1 -1
  154. package/dist/transcription.d.ts.map +1 -1
  155. package/dist/transcription.js +58 -20
  156. package/dist/transcription.js.map +1 -1
  157. package/dist/types.d.cts +19 -2
  158. package/dist/types.d.cts.map +1 -1
  159. package/dist/types.d.ts +19 -2
  160. package/dist/types.d.ts.map +1 -1
  161. package/dist/video.cjs +50 -14
  162. package/dist/video.cjs.map +1 -1
  163. package/dist/video.d.cts +8 -1
  164. package/dist/video.d.cts.map +1 -1
  165. package/dist/video.d.ts +8 -1
  166. package/dist/video.d.ts.map +1 -1
  167. package/dist/video.js +51 -15
  168. package/dist/video.js.map +1 -1
  169. package/dist/ws-gemini-live.cjs +34 -27
  170. package/dist/ws-gemini-live.cjs.map +1 -1
  171. package/dist/ws-gemini-live.d.cts +2 -2
  172. package/dist/ws-gemini-live.d.cts.map +1 -1
  173. package/dist/ws-gemini-live.d.ts.map +1 -1
  174. package/dist/ws-gemini-live.js +34 -27
  175. package/dist/ws-gemini-live.js.map +1 -1
  176. package/dist/ws-realtime.cjs +776 -175
  177. package/dist/ws-realtime.cjs.map +1 -1
  178. package/dist/ws-realtime.d.cts +2 -2
  179. package/dist/ws-realtime.d.cts.map +1 -1
  180. package/dist/ws-realtime.d.ts.map +1 -1
  181. package/dist/ws-realtime.js +776 -175
  182. package/dist/ws-realtime.js.map +1 -1
  183. package/dist/ws-responses.cjs +48 -12
  184. package/dist/ws-responses.cjs.map +1 -1
  185. package/dist/ws-responses.d.cts.map +1 -1
  186. package/dist/ws-responses.d.ts.map +1 -1
  187. package/dist/ws-responses.js +49 -13
  188. package/dist/ws-responses.js.map +1 -1
  189. package/package.json +2 -2
@@ -1,6 +1,7 @@
1
1
  const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
2
+ const require_constants = require('./constants.cjs');
2
3
  const require_helpers = require('./helpers.cjs');
3
- const require_journal = require('./journal.cjs');
4
+ require('./journal.cjs');
4
5
  const require_router = require('./router.cjs');
5
6
  const require_sse_writer = require('./sse-writer.cjs');
6
7
  const require_interruption = require('./interruption.cjs');
@@ -25,12 +26,34 @@ function realtimeItemsToMessages(items, instructions, logger) {
25
26
  content: instructions
26
27
  });
27
28
  for (const item of items) if (item.type === "message") {
28
- const text = item.content?.[0]?.text ?? "";
29
29
  const role = item.role === "assistant" ? "assistant" : item.role === "system" ? "system" : "user";
30
- messages.push({
31
- role,
32
- content: text
33
- });
30
+ if (item.content?.some((p) => p.type === "input_text" || p.type === "input_image" || p.type === "input_audio") && item.content) {
31
+ const mappedContent = item.content.map((part) => {
32
+ if (part.type === "input_text") return {
33
+ type: "text",
34
+ text: part.text ?? ""
35
+ };
36
+ if (part.type === "input_image") return {
37
+ type: "image_url",
38
+ image_url: { url: part.url ?? "" }
39
+ };
40
+ if (part.type === "input_audio") return {
41
+ type: "text",
42
+ text: "[audio input]"
43
+ };
44
+ return part;
45
+ });
46
+ messages.push({
47
+ role,
48
+ content: mappedContent
49
+ });
50
+ } else {
51
+ const text = item.content?.[0]?.text ?? "";
52
+ messages.push({
53
+ role,
54
+ content: text
55
+ });
56
+ }
34
57
  } else if (item.type === "function_call") {
35
58
  if (!item.name) logger?.warn("Realtime function_call item missing 'name'");
36
59
  messages.push({
@@ -55,23 +78,112 @@ function realtimeItemsToMessages(items, instructions, logger) {
55
78
  }
56
79
  return messages;
57
80
  }
58
- function evt(type, extra = {}) {
59
- return JSON.stringify({
60
- type,
61
- event_id: realtimeId("event"),
62
- ...extra
81
+ /** GA -> Beta event name mapping */
82
+ const GA_TO_BETA_EVENT = {
83
+ "response.output_text.delta": "response.text.delta",
84
+ "response.output_text.done": "response.text.done",
85
+ "response.output_audio.delta": "response.audio.delta",
86
+ "response.output_audio.done": "response.audio.done",
87
+ "response.output_audio_transcript.delta": "response.audio_transcript.delta",
88
+ "response.output_audio_transcript.done": "response.audio_transcript.done",
89
+ "conversation.item.added": "conversation.item.created"
90
+ };
91
+ /** GA -> Beta content type mapping */
92
+ const GA_TO_BETA_CONTENT_TYPE = {
93
+ output_text: "text",
94
+ output_audio: "audio"
95
+ };
96
+ /** Events suppressed in Beta mode (GA-only events) */
97
+ const BETA_SUPPRESSED_EVENTS = new Set(["conversation.item.done"]);
98
+ function translateGAToBeta(event) {
99
+ const type = event.type;
100
+ if (BETA_SUPPRESSED_EVENTS.has(type)) return null;
101
+ const translated = { ...event };
102
+ if (GA_TO_BETA_EVENT[type]) translated.type = GA_TO_BETA_EVENT[type];
103
+ if (translated.part && typeof translated.part === "object") {
104
+ const part = { ...translated.part };
105
+ if (typeof part.type === "string" && GA_TO_BETA_CONTENT_TYPE[part.type]) part.type = GA_TO_BETA_CONTENT_TYPE[part.type];
106
+ translated.part = part;
107
+ }
108
+ if (translated.content_part && typeof translated.content_part === "object") {
109
+ const cp = { ...translated.content_part };
110
+ if (typeof cp.type === "string" && GA_TO_BETA_CONTENT_TYPE[cp.type]) cp.type = GA_TO_BETA_CONTENT_TYPE[cp.type];
111
+ translated.content_part = cp;
112
+ }
113
+ if (Array.isArray(translated.content)) translated.content = translated.content.map((c) => {
114
+ if (typeof c.type === "string" && GA_TO_BETA_CONTENT_TYPE[c.type]) return {
115
+ ...c,
116
+ type: GA_TO_BETA_CONTENT_TYPE[c.type]
117
+ };
118
+ return c;
63
119
  });
120
+ if (translated.item && typeof translated.item === "object") {
121
+ const item = { ...translated.item };
122
+ delete item.phase;
123
+ if (Array.isArray(item.content)) item.content = item.content.map((c) => {
124
+ if (typeof c.type === "string" && GA_TO_BETA_CONTENT_TYPE[c.type]) return {
125
+ ...c,
126
+ type: GA_TO_BETA_CONTENT_TYPE[c.type]
127
+ };
128
+ return c;
129
+ });
130
+ translated.item = item;
131
+ }
132
+ if (translated.response && typeof translated.response === "object") {
133
+ const resp = { ...translated.response };
134
+ if (Array.isArray(resp.output)) resp.output = resp.output.map((outItem) => {
135
+ const o = { ...outItem };
136
+ if (Array.isArray(o.content)) o.content = o.content.map((c) => typeof c.type === "string" && GA_TO_BETA_CONTENT_TYPE[c.type] ? {
137
+ ...c,
138
+ type: GA_TO_BETA_CONTENT_TYPE[c.type]
139
+ } : c);
140
+ return o;
141
+ });
142
+ translated.response = resp;
143
+ }
144
+ if (type === "session.created" || type === "session.updated") {
145
+ if (translated.session && typeof translated.session === "object") {
146
+ const session = { ...translated.session };
147
+ if (session.audio && typeof session.audio === "object") {
148
+ const audio = session.audio;
149
+ session.voice = audio.voice;
150
+ session.input_audio_format = audio.input_audio_format;
151
+ session.output_audio_format = audio.output_audio_format;
152
+ session.input_audio_transcription = audio.input_audio_transcription;
153
+ delete session.audio;
154
+ }
155
+ delete session.type;
156
+ delete session.reasoning;
157
+ translated.session = session;
158
+ }
159
+ }
160
+ return translated;
161
+ }
162
+ function sendEvent(ws, event, isBeta) {
163
+ const out = {
164
+ ...event,
165
+ event_id: event.event_id ?? realtimeId("event")
166
+ };
167
+ if (isBeta) {
168
+ const translated = translateGAToBeta(out);
169
+ if (translated === null) return;
170
+ ws.send(JSON.stringify(translated));
171
+ } else ws.send(JSON.stringify(out));
64
172
  }
65
- function buildErrorRealtimeEvent(message, type = "invalid_request_error", code) {
66
- return evt("error", { error: {
67
- message,
68
- type,
69
- code
70
- } });
173
+ function buildErrorRealtimeEvent(ws, message, isBeta, type = "invalid_request_error", code) {
174
+ sendEvent(ws, {
175
+ type: "error",
176
+ error: {
177
+ message,
178
+ type,
179
+ code
180
+ }
181
+ }, isBeta);
71
182
  }
72
183
  function handleWebSocketRealtime(ws, fixtures, journal, defaults) {
73
184
  const { logger } = defaults;
74
185
  const sessionId = realtimeId("sess");
186
+ const isBeta = defaults.upgradeHeaders?.["openai-beta"] ? String(defaults.upgradeHeaders["openai-beta"]).includes("realtime=v1") : false;
75
187
  const session = {
76
188
  model: defaults.model,
77
189
  modalities: ["text"],
@@ -80,98 +192,253 @@ function handleWebSocketRealtime(ws, fixtures, journal, defaults) {
80
192
  voice: null,
81
193
  input_audio_format: null,
82
194
  output_audio_format: null,
195
+ input_audio_noise_reduction: null,
196
+ input_audio_transcription: null,
83
197
  turn_detection: null,
84
- temperature: .8
198
+ temperature: .8,
199
+ type: "conversation",
200
+ reasoning: null
85
201
  };
86
202
  const conversationItems = [];
87
- ws.send(evt("session.created", { session: {
88
- id: sessionId,
89
- object: "realtime.session",
90
- ...session,
91
- expires_at: Math.floor(Date.now() / 1e3) + 3600,
92
- max_response_output_tokens: "inf",
93
- input_audio_transcription: null,
94
- tool_choice: "auto"
95
- } }));
203
+ sendEvent(ws, {
204
+ type: "session.created",
205
+ session: {
206
+ id: sessionId,
207
+ object: "realtime.session",
208
+ model: session.model,
209
+ expires_at: Math.floor(Date.now() / 1e3) + 3600,
210
+ modalities: session.modalities,
211
+ instructions: session.instructions,
212
+ tools: session.tools,
213
+ tool_choice: "auto",
214
+ temperature: session.temperature,
215
+ max_response_output_tokens: "inf",
216
+ audio: {
217
+ voice: session.voice,
218
+ input_audio_format: session.input_audio_format,
219
+ output_audio_format: session.output_audio_format,
220
+ input_audio_noise_reduction: session.input_audio_noise_reduction,
221
+ input_audio_transcription: session.input_audio_transcription
222
+ },
223
+ turn_detection: session.turn_detection,
224
+ type: session.type,
225
+ reasoning: session.reasoning
226
+ }
227
+ }, isBeta);
96
228
  let pending = Promise.resolve();
97
229
  ws.on("message", (raw) => {
98
- pending = pending.then(() => processMessage(raw, ws, fixtures, journal, defaults, session, conversationItems).catch((err) => {
230
+ pending = pending.then(() => processMessage(raw, ws, fixtures, journal, defaults, session, conversationItems, isBeta).catch((err) => {
99
231
  const msg = err instanceof Error ? err.message : "Internal error";
100
232
  logger.error(`WebSocket realtime error: ${msg}`);
101
233
  try {
102
- ws.send(buildErrorRealtimeEvent(msg, "server_error"));
103
- } catch {}
234
+ buildErrorRealtimeEvent(ws, msg, isBeta, "server_error");
235
+ } catch (sendErr) {
236
+ defaults.logger.debug(`Failed to send error to client: ${sendErr instanceof Error ? sendErr.message : "unknown"}`);
237
+ }
104
238
  }));
105
239
  });
106
240
  }
107
- async function processMessage(raw, ws, fixtures, journal, defaults, session, conversationItems) {
241
+ async function processMessage(raw, ws, fixtures, journal, defaults, session, conversationItems, isBeta) {
108
242
  let parsed;
109
243
  try {
110
244
  parsed = JSON.parse(raw);
111
245
  } catch (parseErr) {
112
- const detail = parseErr instanceof Error ? parseErr.message : "unknown";
113
- ws.send(buildErrorRealtimeEvent(`Malformed JSON: ${detail}`, "invalid_request_error", "invalid_json"));
246
+ buildErrorRealtimeEvent(ws, `Malformed JSON: ${parseErr instanceof Error ? parseErr.message : "unknown"}`, isBeta, "invalid_request_error", "invalid_json");
114
247
  return;
115
248
  }
116
249
  const msgType = parsed.type;
117
250
  if (msgType === "session.update") {
118
251
  if (parsed.session) {
119
- if (parsed.session.instructions !== void 0) session.instructions = parsed.session.instructions;
120
- if (parsed.session.tools !== void 0) session.tools = parsed.session.tools;
121
- if (parsed.session.modalities !== void 0) session.modalities = parsed.session.modalities;
122
- if (parsed.session.model !== void 0) session.model = parsed.session.model;
123
- if (parsed.session.temperature !== void 0) session.temperature = parsed.session.temperature;
252
+ const s = parsed.session;
253
+ const validTypes = new Set([
254
+ "conversation",
255
+ "transcription",
256
+ "translation"
257
+ ]);
258
+ if (s.type !== void 0) {
259
+ if (!validTypes.has(s.type)) {
260
+ sendEvent(ws, {
261
+ type: "error",
262
+ error: {
263
+ message: `Invalid session type: ${s.type}`,
264
+ type: "invalid_request_error",
265
+ code: "invalid_session_config"
266
+ }
267
+ }, isBeta);
268
+ return;
269
+ }
270
+ }
271
+ const prevModel = session.model;
272
+ const prevType = session.type;
273
+ if (s.instructions !== void 0) session.instructions = s.instructions;
274
+ if (s.tools !== void 0) session.tools = s.tools;
275
+ if (s.modalities !== void 0) session.modalities = s.modalities;
276
+ if (s.model !== void 0) session.model = s.model;
277
+ if (s.temperature !== void 0) session.temperature = s.temperature;
278
+ if (s.type !== void 0) session.type = s.type;
279
+ if (s.audio) {
280
+ const audio = s.audio;
281
+ if (audio.voice !== void 0) session.voice = audio.voice;
282
+ if (audio.input_audio_format !== void 0) session.input_audio_format = audio.input_audio_format;
283
+ if (audio.output_audio_format !== void 0) session.output_audio_format = audio.output_audio_format;
284
+ if (audio.input_audio_noise_reduction !== void 0) session.input_audio_noise_reduction = audio.input_audio_noise_reduction;
285
+ if (audio.input_audio_transcription !== void 0) session.input_audio_transcription = audio.input_audio_transcription;
286
+ }
287
+ if (s.voice !== void 0) session.voice = s.voice;
288
+ if (s.input_audio_format !== void 0) session.input_audio_format = s.input_audio_format;
289
+ if (s.output_audio_format !== void 0) session.output_audio_format = s.output_audio_format;
290
+ if (s.reasoning !== void 0) session.reasoning = s.reasoning;
291
+ const transcriptionModels = new Set([
292
+ "gpt-4o-transcribe",
293
+ "gpt-4o-mini-transcribe",
294
+ "gpt-realtime-whisper",
295
+ "whisper-1"
296
+ ]);
297
+ const translationModels = new Set([
298
+ "gpt-4o-transcribe",
299
+ "gpt-4o-mini-transcribe",
300
+ "gpt-realtime-translate"
301
+ ]);
302
+ if (session.type === "transcription" && !transcriptionModels.has(session.model)) {
303
+ session.model = prevModel;
304
+ session.type = prevType;
305
+ sendEvent(ws, {
306
+ type: "error",
307
+ error: {
308
+ message: `Model ${s.model ?? prevModel} does not support session type transcription`,
309
+ type: "invalid_request_error",
310
+ code: "invalid_session_config"
311
+ }
312
+ }, isBeta);
313
+ return;
314
+ }
315
+ if (session.type === "translation" && !translationModels.has(session.model)) {
316
+ session.model = prevModel;
317
+ session.type = prevType;
318
+ sendEvent(ws, {
319
+ type: "error",
320
+ error: {
321
+ message: `Model ${s.model ?? prevModel} does not support session type translation`,
322
+ type: "invalid_request_error",
323
+ code: "invalid_session_config"
324
+ }
325
+ }, isBeta);
326
+ return;
327
+ }
124
328
  }
125
- ws.send(evt("session.updated", { session: {
126
- ...session,
127
- object: "realtime.session",
128
- expires_at: Math.floor(Date.now() / 1e3) + 3600,
129
- max_response_output_tokens: "inf",
130
- input_audio_transcription: null,
131
- tool_choice: "auto"
132
- } }));
329
+ sendEvent(ws, {
330
+ type: "session.updated",
331
+ session: {
332
+ object: "realtime.session",
333
+ model: session.model,
334
+ expires_at: Math.floor(Date.now() / 1e3) + 3600,
335
+ modalities: session.modalities,
336
+ instructions: session.instructions,
337
+ tools: session.tools,
338
+ tool_choice: "auto",
339
+ temperature: session.temperature,
340
+ max_response_output_tokens: "inf",
341
+ audio: {
342
+ voice: session.voice,
343
+ input_audio_format: session.input_audio_format,
344
+ output_audio_format: session.output_audio_format,
345
+ input_audio_noise_reduction: session.input_audio_noise_reduction,
346
+ input_audio_transcription: session.input_audio_transcription
347
+ },
348
+ turn_detection: session.turn_detection,
349
+ type: session.type,
350
+ reasoning: session.reasoning
351
+ }
352
+ }, isBeta);
133
353
  return;
134
354
  }
135
355
  if (msgType === "conversation.item.create") {
136
356
  if (!parsed.item) {
137
- ws.send(buildErrorRealtimeEvent("Missing 'item' in conversation.item.create", "invalid_request_error"));
357
+ buildErrorRealtimeEvent(ws, "Missing 'item' in conversation.item.create", isBeta, "invalid_request_error");
138
358
  return;
139
359
  }
140
360
  const item = parsed.item;
141
361
  if (!item.id) item.id = realtimeId("item");
142
362
  const previousId = conversationItems.length > 0 ? conversationItems[conversationItems.length - 1].id ?? null : null;
143
363
  conversationItems.push(item);
144
- ws.send(evt("conversation.item.created", {
364
+ sendEvent(ws, {
365
+ type: "conversation.item.added",
145
366
  previous_item_id: previousId,
146
367
  item
147
- }));
368
+ }, isBeta);
148
369
  return;
149
370
  }
150
371
  if (msgType === "response.create") {
151
- await handleResponseCreate(ws, fixtures, journal, defaults, session, conversationItems);
372
+ await handleResponseCreate(ws, fixtures, journal, defaults, session, conversationItems, isBeta, parsed.response);
373
+ return;
374
+ }
375
+ if (msgType === "input_audio_buffer.append") return;
376
+ if (msgType === "input_audio_buffer.commit") {
377
+ sendEvent(ws, { type: "input_audio_buffer.committed" }, isBeta);
378
+ if (session.type === "transcription" || session.type === "translation") {
379
+ const audioItem = {
380
+ type: "message",
381
+ id: realtimeId("item"),
382
+ role: "user",
383
+ content: [{
384
+ type: "input_audio",
385
+ transcript: null
386
+ }]
387
+ };
388
+ conversationItems.push(audioItem);
389
+ sendEvent(ws, {
390
+ type: "conversation.item.added",
391
+ item: audioItem
392
+ }, isBeta);
393
+ }
394
+ return;
395
+ }
396
+ if (msgType === "input_audio_buffer.clear") {
397
+ sendEvent(ws, { type: "input_audio_buffer.cleared" }, isBeta);
398
+ return;
399
+ }
400
+ if (msgType === "response.cancel") {
401
+ sendEvent(ws, { type: "response.cancelled" }, isBeta);
152
402
  return;
153
403
  }
154
404
  }
155
- async function handleResponseCreate(ws, fixtures, journal, defaults, session, conversationItems) {
156
- const messages = realtimeItemsToMessages(conversationItems, session.instructions || void 0, defaults.logger);
405
+ async function handleResponseCreate(ws, fixtures, journal, defaults, session, conversationItems, isBeta, responseOverrides) {
406
+ const messages = realtimeItemsToMessages(conversationItems, (responseOverrides?.instructions ?? session.instructions) || void 0, defaults.logger);
407
+ const endpointType = {
408
+ conversation: "realtime",
409
+ transcription: "realtime-transcription",
410
+ translation: "realtime-translation"
411
+ }[session.type] ?? "realtime";
157
412
  const completionReq = {
158
413
  model: session.model,
159
- messages
414
+ messages,
415
+ _endpointType: endpointType
160
416
  };
161
- const testId = defaults.testId ?? require_journal.DEFAULT_TEST_ID;
417
+ const testId = defaults.testId ?? require_constants.DEFAULT_TEST_ID;
162
418
  const fixture = require_router.matchFixture(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
163
419
  const responseId = realtimeId("resp");
164
420
  if (fixture) journal.incrementFixtureMatchCount(fixture, fixtures, testId);
165
421
  if (!fixture) {
166
422
  if (require_helpers.resolveStrictMode(defaults.strict, defaults.upgradeHeaders)) {
167
423
  defaults.logger.warn(`STRICT: No fixture matched for WebSocket message`);
424
+ journal.add({
425
+ method: "WS",
426
+ path: "/v1/realtime",
427
+ headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
428
+ body: completionReq,
429
+ response: {
430
+ status: 503,
431
+ fixture: null,
432
+ ...require_helpers.strictOverrideField(defaults.strict, defaults.upgradeHeaders)
433
+ }
434
+ });
168
435
  ws.close(1008, "Strict mode: no fixture matched");
169
436
  return;
170
437
  }
171
438
  journal.add({
172
439
  method: "WS",
173
440
  path: "/v1/realtime",
174
- headers: {},
441
+ headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
175
442
  body: completionReq,
176
443
  response: {
177
444
  status: 404,
@@ -179,33 +446,39 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
179
446
  ...require_helpers.strictOverrideField(defaults.strict, defaults.upgradeHeaders)
180
447
  }
181
448
  });
182
- ws.send(evt("response.created", { response: {
183
- id: responseId,
184
- object: "realtime.response",
185
- status: "failed",
186
- status_details: null,
187
- output: [],
188
- usage: null
189
- } }));
190
- ws.send(evt("response.done", { response: {
191
- id: responseId,
192
- object: "realtime.response",
193
- status: "failed",
194
- output: [],
195
- status_details: {
196
- type: "error",
197
- error: {
198
- message: "No fixture matched",
199
- type: "invalid_request_error",
200
- code: "no_fixture_match"
449
+ sendEvent(ws, {
450
+ type: "response.created",
451
+ response: {
452
+ id: responseId,
453
+ object: "realtime.response",
454
+ status: "failed",
455
+ status_details: null,
456
+ output: [],
457
+ usage: null
458
+ }
459
+ }, isBeta);
460
+ sendEvent(ws, {
461
+ type: "response.done",
462
+ response: {
463
+ id: responseId,
464
+ object: "realtime.response",
465
+ status: "failed",
466
+ output: [],
467
+ status_details: {
468
+ type: "error",
469
+ error: {
470
+ message: "No fixture matched",
471
+ type: "invalid_request_error",
472
+ code: "no_fixture_match"
473
+ }
474
+ },
475
+ usage: {
476
+ total_tokens: 0,
477
+ input_tokens: 0,
478
+ output_tokens: 0
201
479
  }
202
- },
203
- usage: {
204
- total_tokens: 0,
205
- input_tokens: 0,
206
- output_tokens: 0
207
480
  }
208
- } }));
481
+ }, isBeta);
209
482
  return;
210
483
  }
211
484
  const response = await require_helpers.resolveResponse(fixture, completionReq);
@@ -216,47 +489,322 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
216
489
  journal.add({
217
490
  method: "WS",
218
491
  path: "/v1/realtime",
219
- headers: {},
492
+ headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
220
493
  body: completionReq,
221
494
  response: {
222
495
  status,
223
496
  fixture
224
497
  }
225
498
  });
226
- ws.send(evt("response.created", { response: {
227
- id: responseId,
228
- object: "realtime.response",
229
- status: "failed",
230
- status_details: null,
231
- output: [],
232
- usage: null
233
- } }));
234
- ws.send(evt("response.done", { response: {
235
- id: responseId,
236
- object: "realtime.response",
237
- status: "failed",
238
- output: [],
239
- status_details: {
240
- type: "error",
241
- error: {
242
- message: response.error.message,
243
- type: response.error.type,
244
- code: response.error.code
499
+ sendEvent(ws, {
500
+ type: "response.created",
501
+ response: {
502
+ id: responseId,
503
+ object: "realtime.response",
504
+ status: "failed",
505
+ status_details: null,
506
+ output: [],
507
+ usage: null
508
+ }
509
+ }, isBeta);
510
+ sendEvent(ws, {
511
+ type: "response.done",
512
+ response: {
513
+ id: responseId,
514
+ object: "realtime.response",
515
+ status: "failed",
516
+ output: [],
517
+ status_details: {
518
+ type: "error",
519
+ error: {
520
+ message: response.error.message,
521
+ type: response.error.type,
522
+ code: response.error.code
523
+ }
524
+ },
525
+ usage: {
526
+ total_tokens: 0,
527
+ input_tokens: 0,
528
+ output_tokens: 0
245
529
  }
246
- },
247
- usage: {
248
- total_tokens: 0,
249
- input_tokens: 0,
250
- output_tokens: 0
251
530
  }
252
- } }));
531
+ }, isBeta);
532
+ return;
533
+ }
534
+ if (require_helpers.isContentWithToolCallsResponse(response)) {
535
+ const journalEntry = journal.add({
536
+ method: "WS",
537
+ path: "/v1/realtime",
538
+ headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
539
+ body: completionReq,
540
+ response: {
541
+ status: 200,
542
+ fixture
543
+ }
544
+ });
545
+ sendEvent(ws, {
546
+ type: "response.created",
547
+ response: {
548
+ id: responseId,
549
+ object: "realtime.response",
550
+ status: "in_progress",
551
+ status_details: null,
552
+ output: [],
553
+ usage: null
554
+ }
555
+ }, isBeta);
556
+ const interruption = require_interruption.createInterruptionSignal(fixture);
557
+ let interrupted = false;
558
+ const allOutputItems = [];
559
+ const textItemId = realtimeId("item");
560
+ const contentIndex = 0;
561
+ const textOutputIndex = 0;
562
+ const textOutputItem = {
563
+ id: textItemId,
564
+ type: "message",
565
+ role: "assistant",
566
+ status: "completed",
567
+ content: [{
568
+ type: "output_text",
569
+ text: response.content
570
+ }]
571
+ };
572
+ const textPhase = response.toolCalls && response.toolCalls.length > 0 ? "commentary" : "final_answer";
573
+ sendEvent(ws, {
574
+ type: "response.output_item.added",
575
+ response_id: responseId,
576
+ output_index: textOutputIndex,
577
+ item: {
578
+ id: textItemId,
579
+ type: "message",
580
+ role: "assistant",
581
+ status: "in_progress",
582
+ content: [],
583
+ phase: textPhase
584
+ }
585
+ }, isBeta);
586
+ sendEvent(ws, {
587
+ type: "response.content_part.added",
588
+ response_id: responseId,
589
+ item_id: textItemId,
590
+ output_index: textOutputIndex,
591
+ content_index: contentIndex,
592
+ part: {
593
+ type: "output_text",
594
+ text: ""
595
+ }
596
+ }, isBeta);
597
+ const content = response.content;
598
+ for (let i = 0; i < content.length; i += chunkSize) {
599
+ if (ws.isClosed) break;
600
+ if (latency > 0) await require_sse_writer.delay(latency, interruption?.signal);
601
+ if (interruption?.signal.aborted) {
602
+ interrupted = true;
603
+ break;
604
+ }
605
+ if (ws.isClosed) break;
606
+ sendEvent(ws, {
607
+ type: "response.output_text.delta",
608
+ response_id: responseId,
609
+ item_id: textItemId,
610
+ output_index: textOutputIndex,
611
+ content_index: contentIndex,
612
+ delta: content.slice(i, i + chunkSize)
613
+ }, isBeta);
614
+ interruption?.tick();
615
+ if (interruption?.signal.aborted) {
616
+ interrupted = true;
617
+ break;
618
+ }
619
+ }
620
+ if (interrupted) {
621
+ ws.destroy();
622
+ journalEntry.response.interrupted = true;
623
+ journalEntry.response.interruptReason = interruption?.reason();
624
+ interruption?.cleanup();
625
+ return;
626
+ }
627
+ if (ws.isClosed) {
628
+ interruption?.cleanup();
629
+ return;
630
+ }
631
+ sendEvent(ws, {
632
+ type: "response.output_text.done",
633
+ response_id: responseId,
634
+ item_id: textItemId,
635
+ output_index: textOutputIndex,
636
+ content_index: contentIndex,
637
+ text: content
638
+ }, isBeta);
639
+ if (ws.isClosed) {
640
+ interruption?.cleanup();
641
+ return;
642
+ }
643
+ sendEvent(ws, {
644
+ type: "response.content_part.done",
645
+ response_id: responseId,
646
+ item_id: textItemId,
647
+ output_index: textOutputIndex,
648
+ content_index: contentIndex,
649
+ part: {
650
+ type: "output_text",
651
+ text: content
652
+ }
653
+ }, isBeta);
654
+ if (ws.isClosed) {
655
+ interruption?.cleanup();
656
+ return;
657
+ }
658
+ sendEvent(ws, {
659
+ type: "response.output_item.done",
660
+ response_id: responseId,
661
+ output_index: textOutputIndex,
662
+ item: {
663
+ ...textOutputItem,
664
+ phase: textPhase
665
+ }
666
+ }, isBeta);
667
+ sendEvent(ws, {
668
+ type: "conversation.item.done",
669
+ item: {
670
+ id: textItemId,
671
+ object: "realtime.item",
672
+ type: "message",
673
+ role: "assistant",
674
+ status: "completed",
675
+ content: textOutputItem.content
676
+ }
677
+ }, isBeta);
678
+ if (ws.isClosed) {
679
+ interruption?.cleanup();
680
+ return;
681
+ }
682
+ allOutputItems.push(textOutputItem);
683
+ for (let tcIdx = 0; tcIdx < response.toolCalls.length; tcIdx++) {
684
+ const tc = response.toolCalls[tcIdx];
685
+ const callId = tc.id ?? require_helpers.generateToolCallId();
686
+ const itemId = realtimeId("item");
687
+ const outputIndex = tcIdx + 1;
688
+ const toolOutputItem = {
689
+ id: itemId,
690
+ type: "function_call",
691
+ status: "completed",
692
+ call_id: callId,
693
+ name: tc.name,
694
+ arguments: tc.arguments
695
+ };
696
+ sendEvent(ws, {
697
+ type: "response.output_item.added",
698
+ response_id: responseId,
699
+ output_index: outputIndex,
700
+ item: {
701
+ id: itemId,
702
+ type: "function_call",
703
+ status: "in_progress",
704
+ call_id: callId,
705
+ name: tc.name,
706
+ arguments: "",
707
+ phase: "final_answer"
708
+ }
709
+ }, isBeta);
710
+ const args = tc.arguments;
711
+ for (let i = 0; i < args.length; i += chunkSize) {
712
+ if (ws.isClosed) break;
713
+ if (latency > 0) await require_sse_writer.delay(latency, interruption?.signal);
714
+ if (interruption?.signal.aborted) {
715
+ interrupted = true;
716
+ break;
717
+ }
718
+ if (ws.isClosed) break;
719
+ sendEvent(ws, {
720
+ type: "response.function_call_arguments.delta",
721
+ response_id: responseId,
722
+ item_id: itemId,
723
+ output_index: outputIndex,
724
+ call_id: callId,
725
+ delta: args.slice(i, i + chunkSize)
726
+ }, isBeta);
727
+ interruption?.tick();
728
+ if (interruption?.signal.aborted) {
729
+ interrupted = true;
730
+ break;
731
+ }
732
+ }
733
+ if (interrupted) break;
734
+ if (ws.isClosed) break;
735
+ sendEvent(ws, {
736
+ type: "response.function_call_arguments.done",
737
+ response_id: responseId,
738
+ item_id: itemId,
739
+ output_index: outputIndex,
740
+ call_id: callId,
741
+ arguments: args
742
+ }, isBeta);
743
+ if (ws.isClosed) break;
744
+ sendEvent(ws, {
745
+ type: "response.output_item.done",
746
+ response_id: responseId,
747
+ output_index: outputIndex,
748
+ item: {
749
+ ...toolOutputItem,
750
+ phase: "final_answer"
751
+ }
752
+ }, isBeta);
753
+ sendEvent(ws, {
754
+ type: "conversation.item.done",
755
+ item: {
756
+ id: itemId,
757
+ object: "realtime.item",
758
+ type: "function_call",
759
+ status: "completed",
760
+ call_id: callId,
761
+ name: tc.name,
762
+ arguments: args
763
+ }
764
+ }, isBeta);
765
+ if (ws.isClosed) break;
766
+ allOutputItems.push(toolOutputItem);
767
+ }
768
+ if (interrupted) {
769
+ ws.destroy();
770
+ journalEntry.response.interrupted = true;
771
+ journalEntry.response.interruptReason = interruption?.reason();
772
+ interruption?.cleanup();
773
+ return;
774
+ }
775
+ interruption?.cleanup();
776
+ if (ws.isClosed) return;
777
+ sendEvent(ws, {
778
+ type: "response.done",
779
+ response: {
780
+ id: responseId,
781
+ object: "realtime.response",
782
+ status: "completed",
783
+ output: allOutputItems,
784
+ usage: {
785
+ total_tokens: 0,
786
+ input_tokens: 0,
787
+ output_tokens: 0
788
+ }
789
+ }
790
+ }, isBeta);
791
+ conversationItems.push({
792
+ type: "message",
793
+ id: textItemId,
794
+ role: "assistant",
795
+ content: [{
796
+ type: "text",
797
+ text: content
798
+ }]
799
+ });
800
+ for (const item of allOutputItems.slice(1)) conversationItems.push(item);
253
801
  return;
254
802
  }
255
803
  if (require_helpers.isTextResponse(response)) {
256
804
  const journalEntry = journal.add({
257
805
  method: "WS",
258
806
  path: "/v1/realtime",
259
- headers: {},
807
+ headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
260
808
  body: completionReq,
261
809
  response: {
262
810
  status: 200,
@@ -272,19 +820,23 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
272
820
  role: "assistant",
273
821
  status: "completed",
274
822
  content: [{
275
- type: "text",
823
+ type: "output_text",
276
824
  text: response.content
277
825
  }]
278
826
  };
279
- ws.send(evt("response.created", { response: {
280
- id: responseId,
281
- object: "realtime.response",
282
- status: "in_progress",
283
- status_details: null,
284
- output: [],
285
- usage: null
286
- } }));
287
- ws.send(evt("response.output_item.added", {
827
+ sendEvent(ws, {
828
+ type: "response.created",
829
+ response: {
830
+ id: responseId,
831
+ object: "realtime.response",
832
+ status: "in_progress",
833
+ status_details: null,
834
+ output: [],
835
+ usage: null
836
+ }
837
+ }, isBeta);
838
+ sendEvent(ws, {
839
+ type: "response.output_item.added",
288
840
  response_id: responseId,
289
841
  output_index: outputIndex,
290
842
  item: {
@@ -292,19 +844,21 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
292
844
  type: "message",
293
845
  role: "assistant",
294
846
  status: "in_progress",
295
- content: []
847
+ content: [],
848
+ phase: "final_answer"
296
849
  }
297
- }));
298
- ws.send(evt("response.content_part.added", {
850
+ }, isBeta);
851
+ sendEvent(ws, {
852
+ type: "response.content_part.added",
299
853
  response_id: responseId,
300
854
  item_id: itemId,
301
855
  output_index: outputIndex,
302
856
  content_index: contentIndex,
303
857
  part: {
304
- type: "text",
858
+ type: "output_text",
305
859
  text: ""
306
860
  }
307
- }));
861
+ }, isBeta);
308
862
  const content = response.content;
309
863
  const interruption = require_interruption.createInterruptionSignal(fixture);
310
864
  let interrupted = false;
@@ -316,14 +870,14 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
316
870
  break;
317
871
  }
318
872
  if (ws.isClosed) break;
319
- const chunk = content.slice(i, i + chunkSize);
320
- ws.send(evt("response.text.delta", {
873
+ sendEvent(ws, {
874
+ type: "response.output_text.delta",
321
875
  response_id: responseId,
322
876
  item_id: itemId,
323
877
  output_index: outputIndex,
324
878
  content_index: contentIndex,
325
- delta: chunk
326
- }));
879
+ delta: content.slice(i, i + chunkSize)
880
+ }, isBeta);
327
881
  interruption?.tick();
328
882
  if (interruption?.signal.aborted) {
329
883
  interrupted = true;
@@ -339,39 +893,59 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
339
893
  }
340
894
  interruption?.cleanup();
341
895
  if (ws.isClosed) return;
342
- ws.send(evt("response.text.done", {
896
+ sendEvent(ws, {
897
+ type: "response.output_text.done",
343
898
  response_id: responseId,
344
899
  item_id: itemId,
345
900
  output_index: outputIndex,
346
901
  content_index: contentIndex,
347
902
  text: content
348
- }));
349
- ws.send(evt("response.content_part.done", {
903
+ }, isBeta);
904
+ sendEvent(ws, {
905
+ type: "response.content_part.done",
350
906
  response_id: responseId,
351
907
  item_id: itemId,
352
908
  output_index: outputIndex,
353
909
  content_index: contentIndex,
354
910
  part: {
355
- type: "text",
911
+ type: "output_text",
356
912
  text: content
357
913
  }
358
- }));
359
- ws.send(evt("response.output_item.done", {
914
+ }, isBeta);
915
+ sendEvent(ws, {
916
+ type: "response.output_item.done",
360
917
  response_id: responseId,
361
918
  output_index: outputIndex,
362
- item: outputItem
363
- }));
364
- ws.send(evt("response.done", { response: {
365
- id: responseId,
366
- object: "realtime.response",
367
- status: "completed",
368
- output: [outputItem],
369
- usage: {
370
- total_tokens: 0,
371
- input_tokens: 0,
372
- output_tokens: 0
919
+ item: {
920
+ ...outputItem,
921
+ phase: "final_answer"
922
+ }
923
+ }, isBeta);
924
+ sendEvent(ws, {
925
+ type: "conversation.item.done",
926
+ item: {
927
+ id: itemId,
928
+ object: "realtime.item",
929
+ type: "message",
930
+ role: "assistant",
931
+ status: "completed",
932
+ content: outputItem.content
933
+ }
934
+ }, isBeta);
935
+ sendEvent(ws, {
936
+ type: "response.done",
937
+ response: {
938
+ id: responseId,
939
+ object: "realtime.response",
940
+ status: "completed",
941
+ output: [outputItem],
942
+ usage: {
943
+ total_tokens: 0,
944
+ input_tokens: 0,
945
+ output_tokens: 0
946
+ }
373
947
  }
374
- } }));
948
+ }, isBeta);
375
949
  conversationItems.push({
376
950
  type: "message",
377
951
  id: itemId,
@@ -387,21 +961,24 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
387
961
  const journalEntry = journal.add({
388
962
  method: "WS",
389
963
  path: "/v1/realtime",
390
- headers: {},
964
+ headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
391
965
  body: completionReq,
392
966
  response: {
393
967
  status: 200,
394
968
  fixture
395
969
  }
396
970
  });
397
- ws.send(evt("response.created", { response: {
398
- id: responseId,
399
- object: "realtime.response",
400
- status: "in_progress",
401
- status_details: null,
402
- output: [],
403
- usage: null
404
- } }));
971
+ sendEvent(ws, {
972
+ type: "response.created",
973
+ response: {
974
+ id: responseId,
975
+ object: "realtime.response",
976
+ status: "in_progress",
977
+ status_details: null,
978
+ output: [],
979
+ usage: null
980
+ }
981
+ }, isBeta);
405
982
  const outputItems = [];
406
983
  const interruption = require_interruption.createInterruptionSignal(fixture);
407
984
  let interrupted = false;
@@ -417,7 +994,8 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
417
994
  name: tc.name,
418
995
  arguments: tc.arguments
419
996
  };
420
- ws.send(evt("response.output_item.added", {
997
+ sendEvent(ws, {
998
+ type: "response.output_item.added",
421
999
  response_id: responseId,
422
1000
  output_index: tcIdx,
423
1001
  item: {
@@ -426,9 +1004,10 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
426
1004
  status: "in_progress",
427
1005
  call_id: callId,
428
1006
  name: tc.name,
429
- arguments: ""
1007
+ arguments: "",
1008
+ phase: "final_answer"
430
1009
  }
431
- }));
1010
+ }, isBeta);
432
1011
  const args = tc.arguments;
433
1012
  for (let i = 0; i < args.length; i += chunkSize) {
434
1013
  if (ws.isClosed) break;
@@ -439,13 +1018,14 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
439
1018
  }
440
1019
  if (ws.isClosed) break;
441
1020
  const chunk = args.slice(i, i + chunkSize);
442
- ws.send(evt("response.function_call_arguments.delta", {
1021
+ sendEvent(ws, {
1022
+ type: "response.function_call_arguments.delta",
443
1023
  response_id: responseId,
444
1024
  item_id: itemId,
445
1025
  output_index: tcIdx,
446
1026
  call_id: callId,
447
1027
  delta: chunk
448
- }));
1028
+ }, isBeta);
449
1029
  interruption?.tick();
450
1030
  if (interruption?.signal.aborted) {
451
1031
  interrupted = true;
@@ -453,18 +1033,36 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
453
1033
  }
454
1034
  }
455
1035
  if (interrupted) break;
456
- ws.send(evt("response.function_call_arguments.done", {
1036
+ if (ws.isClosed) break;
1037
+ sendEvent(ws, {
1038
+ type: "response.function_call_arguments.done",
457
1039
  response_id: responseId,
458
1040
  item_id: itemId,
459
1041
  output_index: tcIdx,
460
1042
  call_id: callId,
461
1043
  arguments: args
462
- }));
463
- ws.send(evt("response.output_item.done", {
1044
+ }, isBeta);
1045
+ sendEvent(ws, {
1046
+ type: "response.output_item.done",
464
1047
  response_id: responseId,
465
1048
  output_index: tcIdx,
466
- item: outputItem
467
- }));
1049
+ item: {
1050
+ ...outputItem,
1051
+ phase: "final_answer"
1052
+ }
1053
+ }, isBeta);
1054
+ sendEvent(ws, {
1055
+ type: "conversation.item.done",
1056
+ item: {
1057
+ id: itemId,
1058
+ object: "realtime.item",
1059
+ type: "function_call",
1060
+ status: "completed",
1061
+ call_id: callId,
1062
+ name: tc.name,
1063
+ arguments: args
1064
+ }
1065
+ }, isBeta);
468
1066
  outputItems.push(outputItem);
469
1067
  }
470
1068
  if (interrupted) {
@@ -476,31 +1074,34 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
476
1074
  }
477
1075
  interruption?.cleanup();
478
1076
  if (ws.isClosed) return;
479
- ws.send(evt("response.done", { response: {
480
- id: responseId,
481
- object: "realtime.response",
482
- status: "completed",
483
- output: outputItems,
484
- usage: {
485
- total_tokens: 0,
486
- input_tokens: 0,
487
- output_tokens: 0
1077
+ sendEvent(ws, {
1078
+ type: "response.done",
1079
+ response: {
1080
+ id: responseId,
1081
+ object: "realtime.response",
1082
+ status: "completed",
1083
+ output: outputItems,
1084
+ usage: {
1085
+ total_tokens: 0,
1086
+ input_tokens: 0,
1087
+ output_tokens: 0
1088
+ }
488
1089
  }
489
- } }));
1090
+ }, isBeta);
490
1091
  for (const item of outputItems) conversationItems.push(item);
491
1092
  return;
492
1093
  }
493
1094
  journal.add({
494
1095
  method: "WS",
495
1096
  path: "/v1/realtime",
496
- headers: {},
1097
+ headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
497
1098
  body: completionReq,
498
1099
  response: {
499
1100
  status: 500,
500
1101
  fixture
501
1102
  }
502
1103
  });
503
- ws.send(buildErrorRealtimeEvent("Fixture response did not match any known type", "server_error"));
1104
+ buildErrorRealtimeEvent(ws, "Fixture response did not match any known type", isBeta, "server_error");
504
1105
  }
505
1106
 
506
1107
  //#endregion