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