const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
const require_constants = require('./constants.cjs');
const require_helpers = require('./helpers.cjs');
require('./journal.cjs');
const require_router = require('./router.cjs');
const require_sse_writer = require('./sse-writer.cjs');
const require_interruption = require('./interruption.cjs');
let node_crypto = require("node:crypto");

//#region src/ws-realtime.ts
/**
* WebSocket handler for OpenAI Realtime API.
*
* Accepts Realtime API messages (session.update, conversation.item.create,
* response.create) over WebSocket and sends back Realtime API events as
* individual WebSocket text frames.
*/
/** Generate a Realtime-API-style ID with underscore separator (e.g. event_xxx, item_xxx). */
function realtimeId(prefix) {
	return `${prefix}_${(0, node_crypto.randomBytes)(12).toString("base64url")}`;
}
function realtimeItemsToMessages(items, instructions, logger) {
	const messages = [];
	if (instructions) messages.push({
		role: "system",
		content: instructions
	});
	for (const item of items) if (item.type === "message") {
		const role = item.role === "assistant" ? "assistant" : item.role === "system" ? "system" : "user";
		if (item.content?.some((p) => p.type === "input_text" || p.type === "input_image" || p.type === "input_audio") && item.content) {
			const mappedContent = item.content.map((part) => {
				if (part.type === "input_text") return {
					type: "text",
					text: part.text ?? ""
				};
				if (part.type === "input_image") return {
					type: "image_url",
					image_url: { url: part.url ?? "" }
				};
				if (part.type === "input_audio") return {
					type: "text",
					text: "[audio input]"
				};
				return part;
			});
			messages.push({
				role,
				content: mappedContent
			});
		} else {
			const text = item.content?.map((c) => c.text).filter(Boolean).join("") ?? "";
			messages.push({
				role,
				content: text
			});
		}
	} else if (item.type === "function_call") {
		if (!item.name) logger?.warn("Realtime function_call item missing 'name'");
		messages.push({
			role: "assistant",
			content: null,
			tool_calls: [{
				id: item.call_id ?? require_helpers.generateToolCallId(),
				type: "function",
				function: {
					name: item.name ?? "",
					arguments: item.arguments ?? ""
				}
			}]
		});
	} else if (item.type === "function_call_output") {
		if (!item.output) logger?.warn("Realtime function_call_output item missing 'output'");
		messages.push({
			role: "tool",
			content: item.output ?? "",
			tool_call_id: item.call_id
		});
	}
	return messages;
}
/** GA -> Beta event name mapping */
const GA_TO_BETA_EVENT = {
	"response.output_text.delta": "response.text.delta",
	"response.output_text.done": "response.text.done",
	"response.output_audio.delta": "response.audio.delta",
	"response.output_audio.done": "response.audio.done",
	"response.output_audio_transcript.delta": "response.audio_transcript.delta",
	"response.output_audio_transcript.done": "response.audio_transcript.done",
	"conversation.item.added": "conversation.item.created"
};
/** GA -> Beta content type mapping */
const GA_TO_BETA_CONTENT_TYPE = {
	output_text: "text",
	output_audio: "audio"
};
/** Events suppressed in Beta mode (GA-only events) */
const BETA_SUPPRESSED_EVENTS = new Set(["conversation.item.done"]);
function translateGAToBeta(event) {
	const type = event.type;
	if (BETA_SUPPRESSED_EVENTS.has(type)) return null;
	const translated = { ...event };
	if (GA_TO_BETA_EVENT[type]) translated.type = GA_TO_BETA_EVENT[type];
	if (translated.part && typeof translated.part === "object") {
		const part = { ...translated.part };
		if (typeof part.type === "string" && GA_TO_BETA_CONTENT_TYPE[part.type]) part.type = GA_TO_BETA_CONTENT_TYPE[part.type];
		translated.part = part;
	}
	if (translated.content_part && typeof translated.content_part === "object") {
		const cp = { ...translated.content_part };
		if (typeof cp.type === "string" && GA_TO_BETA_CONTENT_TYPE[cp.type]) cp.type = GA_TO_BETA_CONTENT_TYPE[cp.type];
		translated.content_part = cp;
	}
	if (Array.isArray(translated.content)) translated.content = translated.content.map((c) => {
		if (typeof c.type === "string" && GA_TO_BETA_CONTENT_TYPE[c.type]) return {
			...c,
			type: GA_TO_BETA_CONTENT_TYPE[c.type]
		};
		return c;
	});
	if (translated.item && typeof translated.item === "object") {
		const item = { ...translated.item };
		delete item.phase;
		if (Array.isArray(item.content)) item.content = item.content.map((c) => {
			if (typeof c.type === "string" && GA_TO_BETA_CONTENT_TYPE[c.type]) return {
				...c,
				type: GA_TO_BETA_CONTENT_TYPE[c.type]
			};
			return c;
		});
		translated.item = item;
	}
	if (translated.response && typeof translated.response === "object") {
		const resp = { ...translated.response };
		if (Array.isArray(resp.output)) resp.output = resp.output.map((outItem) => {
			const o = { ...outItem };
			if (Array.isArray(o.content)) o.content = o.content.map((c) => typeof c.type === "string" && GA_TO_BETA_CONTENT_TYPE[c.type] ? {
				...c,
				type: GA_TO_BETA_CONTENT_TYPE[c.type]
			} : c);
			return o;
		});
		translated.response = resp;
	}
	if (type === "session.created" || type === "session.updated") {
		if (translated.session && typeof translated.session === "object") {
			const session = { ...translated.session };
			if (session.audio && typeof session.audio === "object") {
				const audio = session.audio;
				session.voice = audio.voice;
				session.input_audio_format = audio.input_audio_format;
				session.output_audio_format = audio.output_audio_format;
				session.input_audio_transcription = audio.input_audio_transcription;
				delete session.audio;
			}
			delete session.type;
			delete session.reasoning;
			translated.session = session;
		}
	}
	return translated;
}
function sendEvent(ws, event, isBeta) {
	const out = {
		...event,
		event_id: event.event_id ?? realtimeId("event")
	};
	if (isBeta) {
		const translated = translateGAToBeta(out);
		if (translated === null) return;
		ws.send(JSON.stringify(translated));
	} else ws.send(JSON.stringify(out));
}
function buildErrorRealtimeEvent(ws, message, isBeta, type = "invalid_request_error", code) {
	sendEvent(ws, {
		type: "error",
		error: {
			message,
			type,
			code
		}
	}, isBeta);
}
function handleWebSocketRealtime(ws, fixtures, journal, defaults) {
	const { logger } = defaults;
	const sessionId = realtimeId("sess");
	const isBeta = defaults.upgradeHeaders?.["openai-beta"] ? String(defaults.upgradeHeaders["openai-beta"]).includes("realtime=v1") : false;
	const session = {
		model: defaults.model,
		modalities: ["text"],
		instructions: "",
		tools: [],
		voice: null,
		input_audio_format: null,
		output_audio_format: null,
		input_audio_noise_reduction: null,
		input_audio_transcription: null,
		turn_detection: null,
		temperature: .8,
		type: "conversation",
		reasoning: null
	};
	const conversationItems = [];
	sendEvent(ws, {
		type: "session.created",
		session: {
			id: sessionId,
			object: "realtime.session",
			model: session.model,
			expires_at: Math.floor(Date.now() / 1e3) + 3600,
			modalities: session.modalities,
			instructions: session.instructions,
			tools: session.tools,
			tool_choice: "auto",
			temperature: session.temperature,
			max_response_output_tokens: "inf",
			audio: {
				voice: session.voice,
				input_audio_format: session.input_audio_format,
				output_audio_format: session.output_audio_format,
				input_audio_noise_reduction: session.input_audio_noise_reduction,
				input_audio_transcription: session.input_audio_transcription
			},
			turn_detection: session.turn_detection,
			type: session.type,
			reasoning: session.reasoning
		}
	}, isBeta);
	let pending = Promise.resolve();
	ws.on("message", (raw) => {
		pending = pending.then(() => processMessage(raw, ws, fixtures, journal, defaults, session, conversationItems, isBeta).catch((err) => {
			const msg = err instanceof Error ? err.message : "Internal error";
			logger.error(`WebSocket realtime error: ${msg}`);
			try {
				buildErrorRealtimeEvent(ws, msg, isBeta, "server_error");
			} catch (sendErr) {
				defaults.logger.debug(`Failed to send error to client: ${sendErr instanceof Error ? sendErr.message : "unknown"}`);
			}
		}));
	});
}
async function processMessage(raw, ws, fixtures, journal, defaults, session, conversationItems, isBeta) {
	let parsed;
	try {
		parsed = JSON.parse(raw);
	} catch (parseErr) {
		buildErrorRealtimeEvent(ws, `Malformed JSON: ${parseErr instanceof Error ? parseErr.message : "unknown"}`, isBeta, "invalid_request_error", "invalid_json");
		return;
	}
	const msgType = parsed.type;
	if (msgType === "session.update") {
		if (parsed.session) {
			const s = parsed.session;
			const validTypes = new Set([
				"conversation",
				"transcription",
				"translation"
			]);
			if (s.type !== void 0) {
				if (!validTypes.has(s.type)) {
					sendEvent(ws, {
						type: "error",
						error: {
							message: `Invalid session type: ${s.type}`,
							type: "invalid_request_error",
							code: "invalid_session_config"
						}
					}, isBeta);
					return;
				}
			}
			const prevSession = { ...session };
			if (s.instructions !== void 0) session.instructions = s.instructions;
			if (s.tools !== void 0) session.tools = s.tools;
			if (s.modalities !== void 0) session.modalities = s.modalities;
			if (s.model !== void 0) session.model = s.model;
			if (s.temperature !== void 0) session.temperature = s.temperature;
			if (s.type !== void 0) session.type = s.type;
			if (s.audio) {
				const audio = s.audio;
				if (audio.voice !== void 0) session.voice = audio.voice;
				if (audio.input_audio_format !== void 0) session.input_audio_format = audio.input_audio_format;
				if (audio.output_audio_format !== void 0) session.output_audio_format = audio.output_audio_format;
				if (audio.input_audio_noise_reduction !== void 0) session.input_audio_noise_reduction = audio.input_audio_noise_reduction;
				if (audio.input_audio_transcription !== void 0) session.input_audio_transcription = audio.input_audio_transcription;
			}
			if (s.voice !== void 0) session.voice = s.voice;
			if (s.input_audio_format !== void 0) session.input_audio_format = s.input_audio_format;
			if (s.output_audio_format !== void 0) session.output_audio_format = s.output_audio_format;
			if (s.input_audio_noise_reduction !== void 0) session.input_audio_noise_reduction = s.input_audio_noise_reduction;
			if (s.input_audio_transcription !== void 0) session.input_audio_transcription = s.input_audio_transcription;
			if (s.turn_detection !== void 0) session.turn_detection = s.turn_detection;
			if (s.reasoning !== void 0) session.reasoning = s.reasoning;
			const transcriptionModels = new Set([
				"gpt-4o-transcribe",
				"gpt-4o-mini-transcribe",
				"gpt-realtime-whisper",
				"whisper-1"
			]);
			const translationModels = new Set([
				"gpt-4o-transcribe",
				"gpt-4o-mini-transcribe",
				"gpt-realtime-translate"
			]);
			if (session.type === "transcription" && !transcriptionModels.has(session.model)) {
				Object.assign(session, prevSession);
				sendEvent(ws, {
					type: "error",
					error: {
						message: `Model ${s.model ?? prevSession.model} does not support session type transcription`,
						type: "invalid_request_error",
						code: "invalid_session_config"
					}
				}, isBeta);
				return;
			}
			if (session.type === "translation" && !translationModels.has(session.model)) {
				Object.assign(session, prevSession);
				sendEvent(ws, {
					type: "error",
					error: {
						message: `Model ${s.model ?? prevSession.model} does not support session type translation`,
						type: "invalid_request_error",
						code: "invalid_session_config"
					}
				}, isBeta);
				return;
			}
		}
		sendEvent(ws, {
			type: "session.updated",
			session: {
				object: "realtime.session",
				model: session.model,
				expires_at: Math.floor(Date.now() / 1e3) + 3600,
				modalities: session.modalities,
				instructions: session.instructions,
				tools: session.tools,
				tool_choice: "auto",
				temperature: session.temperature,
				max_response_output_tokens: "inf",
				audio: {
					voice: session.voice,
					input_audio_format: session.input_audio_format,
					output_audio_format: session.output_audio_format,
					input_audio_noise_reduction: session.input_audio_noise_reduction,
					input_audio_transcription: session.input_audio_transcription
				},
				turn_detection: session.turn_detection,
				type: session.type,
				reasoning: session.reasoning
			}
		}, isBeta);
		return;
	}
	if (msgType === "conversation.item.create") {
		if (!parsed.item) {
			buildErrorRealtimeEvent(ws, "Missing 'item' in conversation.item.create", isBeta, "invalid_request_error");
			return;
		}
		const item = parsed.item;
		if (!item.id) item.id = realtimeId("item");
		const previousId = conversationItems.length > 0 ? conversationItems[conversationItems.length - 1].id ?? null : null;
		conversationItems.push(item);
		sendEvent(ws, {
			type: "conversation.item.added",
			previous_item_id: previousId,
			item
		}, isBeta);
		return;
	}
	if (msgType === "response.create") {
		await handleResponseCreate(ws, fixtures, journal, defaults, session, conversationItems, isBeta, parsed.response);
		return;
	}
	if (msgType === "input_audio_buffer.append") return;
	if (msgType === "input_audio_buffer.commit") {
		sendEvent(ws, { type: "input_audio_buffer.committed" }, isBeta);
		if (session.type === "transcription" || session.type === "translation") {
			const audioItem = {
				type: "message",
				id: realtimeId("item"),
				role: "user",
				content: [{
					type: "input_audio",
					transcript: null
				}]
			};
			conversationItems.push(audioItem);
			sendEvent(ws, {
				type: "conversation.item.added",
				item: audioItem
			}, isBeta);
		}
		return;
	}
	if (msgType === "input_audio_buffer.clear") {
		sendEvent(ws, { type: "input_audio_buffer.cleared" }, isBeta);
		return;
	}
	if (msgType === "response.cancel") {
		sendEvent(ws, { type: "response.cancelled" }, isBeta);
		return;
	}
}
async function handleResponseCreate(ws, fixtures, journal, defaults, session, conversationItems, isBeta, responseOverrides) {
	const messages = realtimeItemsToMessages(conversationItems, (responseOverrides?.instructions ?? session.instructions) || void 0, defaults.logger);
	const endpointType = {
		conversation: "realtime",
		transcription: "realtime-transcription",
		translation: "realtime-translation"
	}[session.type] ?? "realtime";
	const realtimeContextHeader = defaults.upgradeHeaders?.["x-aimock-context"];
	const realtimeContext = typeof realtimeContextHeader === "string" ? realtimeContextHeader : Array.isArray(realtimeContextHeader) && realtimeContextHeader.length > 0 ? realtimeContextHeader[0] : void 0;
	const completionReq = {
		model: session.model,
		messages,
		_endpointType: endpointType,
		_context: realtimeContext
	};
	const testId = defaults.testId ?? require_constants.DEFAULT_TEST_ID;
	const { fixture, skippedBySequenceOrTurn } = require_router.matchFixtureDiagnostic(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
	const responseId = realtimeId("resp");
	if (fixture) journal.incrementFixtureMatchCount(fixture, fixtures, testId);
	if (!fixture) {
		if (require_helpers.resolveStrictMode(defaults.strict, defaults.upgradeHeaders)) {
			const strictMessage = require_helpers.strictNoMatchMessage(skippedBySequenceOrTurn);
			defaults.logger.error(require_helpers.strictNoMatchLogLine("WS", "/v1/realtime", skippedBySequenceOrTurn));
			journal.add({
				method: "WS",
				path: "/v1/realtime",
				headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
				body: completionReq,
				response: {
					status: 503,
					fixture: null,
					...require_helpers.strictOverrideField(defaults.strict, defaults.upgradeHeaders)
				}
			});
			ws.close(1008, strictMessage);
			return;
		}
		journal.add({
			method: "WS",
			path: "/v1/realtime",
			headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
			body: completionReq,
			response: {
				status: 404,
				fixture: null,
				...require_helpers.strictOverrideField(defaults.strict, defaults.upgradeHeaders)
			}
		});
		sendEvent(ws, {
			type: "response.created",
			response: {
				id: responseId,
				object: "realtime.response",
				status: "failed",
				status_details: null,
				output: [],
				usage: null
			}
		}, isBeta);
		sendEvent(ws, {
			type: "response.done",
			response: {
				id: responseId,
				object: "realtime.response",
				status: "failed",
				output: [],
				status_details: {
					type: "error",
					error: {
						message: "No fixture matched",
						type: "invalid_request_error",
						code: "no_fixture_match"
					}
				},
				usage: {
					total_tokens: 0,
					input_tokens: 0,
					output_tokens: 0
				}
			}
		}, isBeta);
		return;
	}
	const response = await require_helpers.resolveResponse(fixture, completionReq);
	const latency = fixture.latency ?? defaults.latency;
	const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
	if (require_helpers.isErrorResponse(response)) {
		const status = response.status ?? 500;
		journal.add({
			method: "WS",
			path: "/v1/realtime",
			headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
			body: completionReq,
			response: {
				status,
				fixture
			}
		});
		sendEvent(ws, {
			type: "response.created",
			response: {
				id: responseId,
				object: "realtime.response",
				status: "failed",
				status_details: null,
				output: [],
				usage: null
			}
		}, isBeta);
		sendEvent(ws, {
			type: "response.done",
			response: {
				id: responseId,
				object: "realtime.response",
				status: "failed",
				output: [],
				status_details: {
					type: "error",
					error: {
						message: response.error.message,
						type: response.error.type,
						code: response.error.code
					}
				},
				usage: {
					total_tokens: 0,
					input_tokens: 0,
					output_tokens: 0
				}
			}
		}, isBeta);
		return;
	}
	if (require_helpers.isContentWithToolCallsResponse(response)) {
		const journalEntry = journal.add({
			method: "WS",
			path: "/v1/realtime",
			headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
			body: completionReq,
			response: {
				status: 200,
				fixture
			}
		});
		if (response.blocks && response.blocks.length > 0) {
			await streamRealtimeBlocks(ws, require_helpers.resolveFixtureBlocks(response.blocks), responseId, fixture, defaults, latency, chunkSize, isBeta, conversationItems, journalEntry);
			return;
		}
		sendEvent(ws, {
			type: "response.created",
			response: {
				id: responseId,
				object: "realtime.response",
				status: "in_progress",
				status_details: null,
				output: [],
				usage: null
			}
		}, isBeta);
		const interruption = require_interruption.createInterruptionSignal(fixture);
		let interrupted = false;
		const allOutputItems = [];
		const textItemId = realtimeId("item");
		const contentIndex = 0;
		const textOutputIndex = 0;
		const textOutputItem = {
			id: textItemId,
			type: "message",
			role: "assistant",
			status: "completed",
			content: [{
				type: "output_text",
				text: response.content
			}]
		};
		const textPhase = response.toolCalls && response.toolCalls.length > 0 ? "commentary" : "final_answer";
		sendEvent(ws, {
			type: "response.output_item.added",
			response_id: responseId,
			output_index: textOutputIndex,
			item: {
				id: textItemId,
				type: "message",
				role: "assistant",
				status: "in_progress",
				content: [],
				phase: textPhase
			}
		}, isBeta);
		sendEvent(ws, {
			type: "response.content_part.added",
			response_id: responseId,
			item_id: textItemId,
			output_index: textOutputIndex,
			content_index: contentIndex,
			part: {
				type: "output_text",
				text: ""
			}
		}, isBeta);
		const content = response.content ?? "";
		const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
		const { recordedTimings } = fixture;
		let eventIndex = 0;
		for (let i = 0; i < content.length; i += chunkSize) {
			if (ws.isClosed) break;
			const chunkDelay = require_sse_writer.calculateDelay(eventIndex, void 0, latency, recordedTimings, replaySpeed);
			if (chunkDelay > 0) await require_sse_writer.delay(chunkDelay, interruption?.signal);
			if (interruption?.signal.aborted) {
				interrupted = true;
				break;
			}
			if (ws.isClosed) break;
			const chunk = content.slice(i, i + chunkSize);
			try {
				sendEvent(ws, {
					type: "response.output_text.delta",
					response_id: responseId,
					item_id: textItemId,
					output_index: textOutputIndex,
					content_index: contentIndex,
					delta: chunk
				}, isBeta);
			} catch (err) {
				defaults.logger.debug("[ws-realtime] send failed during text streaming, closing", err);
				break;
			}
			eventIndex++;
			interruption?.tick();
			if (interruption?.signal.aborted) {
				interrupted = true;
				break;
			}
		}
		if (interrupted) {
			ws.destroy();
			journalEntry.response.interrupted = true;
			journalEntry.response.interruptReason = interruption?.reason();
			interruption?.cleanup();
			return;
		}
		if (ws.isClosed) {
			interruption?.cleanup();
			return;
		}
		sendEvent(ws, {
			type: "response.output_text.done",
			response_id: responseId,
			item_id: textItemId,
			output_index: textOutputIndex,
			content_index: contentIndex,
			text: content
		}, isBeta);
		if (ws.isClosed) {
			interruption?.cleanup();
			return;
		}
		sendEvent(ws, {
			type: "response.content_part.done",
			response_id: responseId,
			item_id: textItemId,
			output_index: textOutputIndex,
			content_index: contentIndex,
			part: {
				type: "output_text",
				text: content
			}
		}, isBeta);
		if (ws.isClosed) {
			interruption?.cleanup();
			return;
		}
		sendEvent(ws, {
			type: "response.output_item.done",
			response_id: responseId,
			output_index: textOutputIndex,
			item: {
				...textOutputItem,
				phase: textPhase
			}
		}, isBeta);
		sendEvent(ws, {
			type: "conversation.item.done",
			item: {
				id: textItemId,
				object: "realtime.item",
				type: "message",
				role: "assistant",
				status: "completed",
				content: textOutputItem.content
			}
		}, isBeta);
		if (ws.isClosed) {
			interruption?.cleanup();
			return;
		}
		allOutputItems.push(textOutputItem);
		const toolCalls = response.toolCalls ?? [];
		for (let tcIdx = 0; tcIdx < toolCalls.length; tcIdx++) {
			const tc = toolCalls[tcIdx];
			const callId = tc.id ?? require_helpers.generateToolCallId();
			const itemId = realtimeId("item");
			const outputIndex = tcIdx + 1;
			const toolOutputItem = {
				id: itemId,
				type: "function_call",
				status: "completed",
				call_id: callId,
				name: tc.name,
				arguments: tc.arguments
			};
			sendEvent(ws, {
				type: "response.output_item.added",
				response_id: responseId,
				output_index: outputIndex,
				item: {
					id: itemId,
					type: "function_call",
					status: "in_progress",
					call_id: callId,
					name: tc.name,
					arguments: "",
					phase: "final_answer"
				}
			}, isBeta);
			const args = tc.arguments;
			for (let i = 0; i < args.length; i += chunkSize) {
				if (ws.isClosed) break;
				const chunkDelay = require_sse_writer.calculateDelay(eventIndex, void 0, latency, recordedTimings, replaySpeed);
				if (chunkDelay > 0) await require_sse_writer.delay(chunkDelay, interruption?.signal);
				if (interruption?.signal.aborted) {
					interrupted = true;
					break;
				}
				if (ws.isClosed) break;
				const chunk = args.slice(i, i + chunkSize);
				try {
					sendEvent(ws, {
						type: "response.function_call_arguments.delta",
						response_id: responseId,
						item_id: itemId,
						output_index: outputIndex,
						call_id: callId,
						delta: chunk
					}, isBeta);
				} catch (err) {
					defaults.logger.debug("[ws-realtime] send failed during tool call streaming, closing", err);
					break;
				}
				eventIndex++;
				interruption?.tick();
				if (interruption?.signal.aborted) {
					interrupted = true;
					break;
				}
			}
			if (interrupted) break;
			if (ws.isClosed) break;
			sendEvent(ws, {
				type: "response.function_call_arguments.done",
				response_id: responseId,
				item_id: itemId,
				output_index: outputIndex,
				call_id: callId,
				arguments: args
			}, isBeta);
			if (ws.isClosed) break;
			sendEvent(ws, {
				type: "response.output_item.done",
				response_id: responseId,
				output_index: outputIndex,
				item: {
					...toolOutputItem,
					phase: "final_answer"
				}
			}, isBeta);
			sendEvent(ws, {
				type: "conversation.item.done",
				item: {
					id: itemId,
					object: "realtime.item",
					type: "function_call",
					status: "completed",
					call_id: callId,
					name: tc.name,
					arguments: args
				}
			}, isBeta);
			if (ws.isClosed) break;
			allOutputItems.push(toolOutputItem);
		}
		if (interrupted) {
			ws.destroy();
			journalEntry.response.interrupted = true;
			journalEntry.response.interruptReason = interruption?.reason();
			interruption?.cleanup();
			return;
		}
		interruption?.cleanup();
		if (ws.isClosed) return;
		sendEvent(ws, {
			type: "response.done",
			response: {
				id: responseId,
				object: "realtime.response",
				status: "completed",
				output: allOutputItems,
				usage: {
					total_tokens: 0,
					input_tokens: 0,
					output_tokens: 0
				}
			}
		}, isBeta);
		conversationItems.push({
			type: "message",
			id: textItemId,
			role: "assistant",
			content: [{
				type: "text",
				text: content
			}]
		});
		for (const item of allOutputItems.slice(1)) conversationItems.push(item);
		return;
	}
	if (require_helpers.isTextResponse(response)) {
		const journalEntry = journal.add({
			method: "WS",
			path: "/v1/realtime",
			headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
			body: completionReq,
			response: {
				status: 200,
				fixture
			}
		});
		const itemId = realtimeId("item");
		const contentIndex = 0;
		const outputIndex = 0;
		const outputItem = {
			id: itemId,
			type: "message",
			role: "assistant",
			status: "completed",
			content: [{
				type: "output_text",
				text: response.content
			}]
		};
		sendEvent(ws, {
			type: "response.created",
			response: {
				id: responseId,
				object: "realtime.response",
				status: "in_progress",
				status_details: null,
				output: [],
				usage: null
			}
		}, isBeta);
		sendEvent(ws, {
			type: "response.output_item.added",
			response_id: responseId,
			output_index: outputIndex,
			item: {
				id: itemId,
				type: "message",
				role: "assistant",
				status: "in_progress",
				content: [],
				phase: "final_answer"
			}
		}, isBeta);
		sendEvent(ws, {
			type: "response.content_part.added",
			response_id: responseId,
			item_id: itemId,
			output_index: outputIndex,
			content_index: contentIndex,
			part: {
				type: "output_text",
				text: ""
			}
		}, isBeta);
		const content = response.content ?? "";
		const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
		const { recordedTimings } = fixture;
		const interruption = require_interruption.createInterruptionSignal(fixture);
		let interrupted = false;
		let eventIndex = 0;
		for (let i = 0; i < content.length; i += chunkSize) {
			if (ws.isClosed) break;
			const chunkDelay = require_sse_writer.calculateDelay(eventIndex, void 0, latency, recordedTimings, replaySpeed);
			if (chunkDelay > 0) await require_sse_writer.delay(chunkDelay, interruption?.signal);
			if (interruption?.signal.aborted) {
				interrupted = true;
				break;
			}
			if (ws.isClosed) break;
			const chunk = content.slice(i, i + chunkSize);
			try {
				sendEvent(ws, {
					type: "response.output_text.delta",
					response_id: responseId,
					item_id: itemId,
					output_index: outputIndex,
					content_index: contentIndex,
					delta: chunk
				}, isBeta);
			} catch (err) {
				defaults.logger.debug("[ws-realtime] send failed during text streaming, closing", err);
				break;
			}
			eventIndex++;
			interruption?.tick();
			if (interruption?.signal.aborted) {
				interrupted = true;
				break;
			}
		}
		if (interrupted) {
			ws.destroy();
			journalEntry.response.interrupted = true;
			journalEntry.response.interruptReason = interruption?.reason();
			interruption?.cleanup();
			return;
		}
		interruption?.cleanup();
		if (ws.isClosed) return;
		sendEvent(ws, {
			type: "response.output_text.done",
			response_id: responseId,
			item_id: itemId,
			output_index: outputIndex,
			content_index: contentIndex,
			text: content
		}, isBeta);
		sendEvent(ws, {
			type: "response.content_part.done",
			response_id: responseId,
			item_id: itemId,
			output_index: outputIndex,
			content_index: contentIndex,
			part: {
				type: "output_text",
				text: content
			}
		}, isBeta);
		sendEvent(ws, {
			type: "response.output_item.done",
			response_id: responseId,
			output_index: outputIndex,
			item: {
				...outputItem,
				phase: "final_answer"
			}
		}, isBeta);
		sendEvent(ws, {
			type: "conversation.item.done",
			item: {
				id: itemId,
				object: "realtime.item",
				type: "message",
				role: "assistant",
				status: "completed",
				content: outputItem.content
			}
		}, isBeta);
		sendEvent(ws, {
			type: "response.done",
			response: {
				id: responseId,
				object: "realtime.response",
				status: "completed",
				output: [outputItem],
				usage: {
					total_tokens: 0,
					input_tokens: 0,
					output_tokens: 0
				}
			}
		}, isBeta);
		conversationItems.push({
			type: "message",
			id: itemId,
			role: "assistant",
			content: [{
				type: "text",
				text: content
			}]
		});
		return;
	}
	if (require_helpers.isToolCallResponse(response)) {
		const journalEntry = journal.add({
			method: "WS",
			path: "/v1/realtime",
			headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
			body: completionReq,
			response: {
				status: 200,
				fixture
			}
		});
		sendEvent(ws, {
			type: "response.created",
			response: {
				id: responseId,
				object: "realtime.response",
				status: "in_progress",
				status_details: null,
				output: [],
				usage: null
			}
		}, isBeta);
		const outputItems = [];
		const interruption = require_interruption.createInterruptionSignal(fixture);
		const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
		const { recordedTimings } = fixture;
		let interrupted = false;
		let eventIndex = 0;
		for (let tcIdx = 0; tcIdx < response.toolCalls.length; tcIdx++) {
			const tc = response.toolCalls[tcIdx];
			const callId = tc.id ?? require_helpers.generateToolCallId();
			const itemId = realtimeId("item");
			const outputItem = {
				id: itemId,
				type: "function_call",
				status: "completed",
				call_id: callId,
				name: tc.name,
				arguments: tc.arguments
			};
			sendEvent(ws, {
				type: "response.output_item.added",
				response_id: responseId,
				output_index: tcIdx,
				item: {
					id: itemId,
					type: "function_call",
					status: "in_progress",
					call_id: callId,
					name: tc.name,
					arguments: "",
					phase: "final_answer"
				}
			}, isBeta);
			const args = tc.arguments;
			for (let i = 0; i < args.length; i += chunkSize) {
				if (ws.isClosed) break;
				const chunkDelay = require_sse_writer.calculateDelay(eventIndex, void 0, latency, recordedTimings, replaySpeed);
				if (chunkDelay > 0) await require_sse_writer.delay(chunkDelay, interruption?.signal);
				if (interruption?.signal.aborted) {
					interrupted = true;
					break;
				}
				if (ws.isClosed) break;
				const chunk = args.slice(i, i + chunkSize);
				try {
					sendEvent(ws, {
						type: "response.function_call_arguments.delta",
						response_id: responseId,
						item_id: itemId,
						output_index: tcIdx,
						call_id: callId,
						delta: chunk
					}, isBeta);
				} catch (err) {
					defaults.logger.debug("[ws-realtime] send failed during tool call streaming, closing", err);
					break;
				}
				eventIndex++;
				interruption?.tick();
				if (interruption?.signal.aborted) {
					interrupted = true;
					break;
				}
			}
			if (interrupted) break;
			if (ws.isClosed) break;
			sendEvent(ws, {
				type: "response.function_call_arguments.done",
				response_id: responseId,
				item_id: itemId,
				output_index: tcIdx,
				call_id: callId,
				arguments: args
			}, isBeta);
			sendEvent(ws, {
				type: "response.output_item.done",
				response_id: responseId,
				output_index: tcIdx,
				item: {
					...outputItem,
					phase: "final_answer"
				}
			}, isBeta);
			sendEvent(ws, {
				type: "conversation.item.done",
				item: {
					id: itemId,
					object: "realtime.item",
					type: "function_call",
					status: "completed",
					call_id: callId,
					name: tc.name,
					arguments: args
				}
			}, isBeta);
			outputItems.push(outputItem);
		}
		if (interrupted) {
			ws.destroy();
			journalEntry.response.interrupted = true;
			journalEntry.response.interruptReason = interruption?.reason();
			interruption?.cleanup();
			return;
		}
		interruption?.cleanup();
		if (ws.isClosed) return;
		sendEvent(ws, {
			type: "response.done",
			response: {
				id: responseId,
				object: "realtime.response",
				status: "completed",
				output: outputItems,
				usage: {
					total_tokens: 0,
					input_tokens: 0,
					output_tokens: 0
				}
			}
		}, isBeta);
		for (const item of outputItems) conversationItems.push(item);
		return;
	}
	journal.add({
		method: "WS",
		path: "/v1/realtime",
		headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
		body: completionReq,
		response: {
			status: 500,
			fixture
		}
	});
	buildErrorRealtimeEvent(ws, "Fixture response did not match any known type", isBeta, "server_error");
}
/**
* Stream a content+toolCalls fixture's ordered `blocks` over the Realtime WS
* surface (#274). Each block becomes its own output item, sequenced on the wire
* with an incrementing `output_index` so a client observes the blocks in array
* order (text and tool-call items can interleave in any order, unlike the
* separate channels of chat-completions). Emits the same per-item event shapes
* as the legacy text/tool branches; a blocks-only fixture therefore never
* produces an empty payload.
*/
async function streamRealtimeBlocks(ws, blocks, responseId, fixture, defaults, latency, chunkSize, isBeta, conversationItems, journalEntry) {
	sendEvent(ws, {
		type: "response.created",
		response: {
			id: responseId,
			object: "realtime.response",
			status: "in_progress",
			status_details: null,
			output: [],
			usage: null
		}
	}, isBeta);
	const interruption = require_interruption.createInterruptionSignal(fixture);
	let interrupted = false;
	const allOutputItems = [];
	const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
	const { recordedTimings } = fixture;
	let eventIndex = 0;
	const hasToolCalls = blocks.some((b) => b.type === "toolCall");
	for (let outputIndex = 0; outputIndex < blocks.length; outputIndex++) {
		if (ws.isClosed || interrupted) break;
		const block = blocks[outputIndex];
		if (block.type === "text") {
			const textItemId = realtimeId("item");
			const contentIndex = 0;
			const textPhase = hasToolCalls ? "commentary" : "final_answer";
			const text = block.text;
			const textOutputItem = {
				id: textItemId,
				type: "message",
				role: "assistant",
				status: "completed",
				content: [{
					type: "output_text",
					text
				}]
			};
			sendEvent(ws, {
				type: "response.output_item.added",
				response_id: responseId,
				output_index: outputIndex,
				item: {
					id: textItemId,
					type: "message",
					role: "assistant",
					status: "in_progress",
					content: [],
					phase: textPhase
				}
			}, isBeta);
			sendEvent(ws, {
				type: "response.content_part.added",
				response_id: responseId,
				item_id: textItemId,
				output_index: outputIndex,
				content_index: contentIndex,
				part: {
					type: "output_text",
					text: ""
				}
			}, isBeta);
			for (let i = 0; i < text.length; i += chunkSize) {
				if (ws.isClosed) break;
				const chunkDelay = require_sse_writer.calculateDelay(eventIndex, void 0, latency, recordedTimings, replaySpeed);
				if (chunkDelay > 0) await require_sse_writer.delay(chunkDelay, interruption?.signal);
				if (interruption?.signal.aborted) {
					interrupted = true;
					break;
				}
				if (ws.isClosed) break;
				const chunk = text.slice(i, i + chunkSize);
				try {
					sendEvent(ws, {
						type: "response.output_text.delta",
						response_id: responseId,
						item_id: textItemId,
						output_index: outputIndex,
						content_index: contentIndex,
						delta: chunk
					}, isBeta);
				} catch (err) {
					defaults.logger.debug("[ws-realtime] send failed during block text streaming, closing", err);
					break;
				}
				eventIndex++;
				interruption?.tick();
				if (interruption?.signal.aborted) {
					interrupted = true;
					break;
				}
			}
			if (interrupted || ws.isClosed) break;
			sendEvent(ws, {
				type: "response.output_text.done",
				response_id: responseId,
				item_id: textItemId,
				output_index: outputIndex,
				content_index: contentIndex,
				text
			}, isBeta);
			if (ws.isClosed) break;
			sendEvent(ws, {
				type: "response.content_part.done",
				response_id: responseId,
				item_id: textItemId,
				output_index: outputIndex,
				content_index: contentIndex,
				part: {
					type: "output_text",
					text
				}
			}, isBeta);
			if (ws.isClosed) break;
			sendEvent(ws, {
				type: "response.output_item.done",
				response_id: responseId,
				output_index: outputIndex,
				item: {
					...textOutputItem,
					phase: textPhase
				}
			}, isBeta);
			sendEvent(ws, {
				type: "conversation.item.done",
				item: {
					id: textItemId,
					object: "realtime.item",
					type: "message",
					role: "assistant",
					status: "completed",
					content: textOutputItem.content
				}
			}, isBeta);
			if (ws.isClosed) break;
			allOutputItems.push(textOutputItem);
		} else {
			const callId = block.id ?? require_helpers.generateToolCallId();
			const itemId = realtimeId("item");
			const args = block.arguments;
			const toolOutputItem = {
				id: itemId,
				type: "function_call",
				status: "completed",
				call_id: callId,
				name: block.name,
				arguments: args
			};
			sendEvent(ws, {
				type: "response.output_item.added",
				response_id: responseId,
				output_index: outputIndex,
				item: {
					id: itemId,
					type: "function_call",
					status: "in_progress",
					call_id: callId,
					name: block.name,
					arguments: "",
					phase: "final_answer"
				}
			}, isBeta);
			for (let i = 0; i < args.length; i += chunkSize) {
				if (ws.isClosed) break;
				const chunkDelay = require_sse_writer.calculateDelay(eventIndex, void 0, latency, recordedTimings, replaySpeed);
				if (chunkDelay > 0) await require_sse_writer.delay(chunkDelay, interruption?.signal);
				if (interruption?.signal.aborted) {
					interrupted = true;
					break;
				}
				if (ws.isClosed) break;
				const chunk = args.slice(i, i + chunkSize);
				try {
					sendEvent(ws, {
						type: "response.function_call_arguments.delta",
						response_id: responseId,
						item_id: itemId,
						output_index: outputIndex,
						call_id: callId,
						delta: chunk
					}, isBeta);
				} catch (err) {
					defaults.logger.debug("[ws-realtime] send failed during block tool call streaming, closing", err);
					break;
				}
				eventIndex++;
				interruption?.tick();
				if (interruption?.signal.aborted) {
					interrupted = true;
					break;
				}
			}
			if (interrupted || ws.isClosed) break;
			sendEvent(ws, {
				type: "response.function_call_arguments.done",
				response_id: responseId,
				item_id: itemId,
				output_index: outputIndex,
				call_id: callId,
				arguments: args
			}, isBeta);
			if (ws.isClosed) break;
			sendEvent(ws, {
				type: "response.output_item.done",
				response_id: responseId,
				output_index: outputIndex,
				item: {
					...toolOutputItem,
					phase: "final_answer"
				}
			}, isBeta);
			sendEvent(ws, {
				type: "conversation.item.done",
				item: {
					id: itemId,
					object: "realtime.item",
					type: "function_call",
					status: "completed",
					call_id: callId,
					name: block.name,
					arguments: args
				}
			}, isBeta);
			if (ws.isClosed) break;
			allOutputItems.push(toolOutputItem);
		}
	}
	if (interrupted) {
		ws.destroy();
		journalEntry.response.interrupted = true;
		journalEntry.response.interruptReason = interruption?.reason();
		interruption?.cleanup();
		return;
	}
	interruption?.cleanup();
	if (ws.isClosed) return;
	sendEvent(ws, {
		type: "response.done",
		response: {
			id: responseId,
			object: "realtime.response",
			status: "completed",
			output: allOutputItems,
			usage: {
				total_tokens: 0,
				input_tokens: 0,
				output_tokens: 0
			}
		}
	}, isBeta);
	for (const item of allOutputItems) {
		const it = item;
		if (it.type === "message") conversationItems.push({
			type: "message",
			id: it.id,
			role: "assistant",
			content: [{
				type: "text",
				text: it.content?.[0]?.text ?? ""
			}]
		});
		else conversationItems.push(item);
	}
}

//#endregion
exports.handleWebSocketRealtime = handleWebSocketRealtime;
//# sourceMappingURL=ws-realtime.cjs.map