import { flattenHeaders, generateDeterministicEmbedding, getContext, getTestId, isContentWithToolCallsResponse, isEmbeddingResponse, isErrorResponse, isTextResponse, isToolCallResponse, resolveFixtureBlocks, resolveReasoningForModel, resolveResponse, resolveStrictMode, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField } from "./helpers.js";
import { matchFixtureDiagnostic, recordMatchOptions } from "./router.js";
import { writeErrorResponse } from "./sse-writer.js";
import { createInterruptionSignal } from "./interruption.js";
import { applyChaos } from "./chaos.js";
import { proxyAndRecord } from "./recorder.js";
import { writeNDJSONStream } from "./ndjson-writer.js";

//#region src/ollama.ts
const DURATION_FIELDS = {
	done_reason: "stop",
	total_duration: 0,
	load_duration: 0,
	prompt_eval_count: 0,
	prompt_eval_duration: 0,
	eval_count: 0,
	eval_duration: 0
};
function ollamaToCompletionRequest(req) {
	const messages = [];
	for (const msg of req.messages) {
		const chatMsg = {
			role: msg.role,
			content: msg.content
		};
		if (msg.tool_calls && msg.tool_calls.length > 0) chatMsg.tool_calls = msg.tool_calls.map((tc, i) => ({
			id: `call_${i}`,
			type: "function",
			function: {
				name: tc.function.name,
				arguments: typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
			}
		}));
		messages.push(chatMsg);
	}
	let tools;
	if (req.tools && req.tools.length > 0) tools = req.tools.map((t) => ({
		type: "function",
		function: {
			name: t.function.name,
			description: t.function.description,
			parameters: t.function.parameters
		}
	}));
	return {
		model: req.model,
		messages,
		stream: req.stream ?? true,
		temperature: req.options?.temperature,
		max_tokens: req.options?.num_predict,
		tools
	};
}
function ollamaGenerateToCompletionRequest(req) {
	const messages = [];
	if (req.system) messages.push({
		role: "system",
		content: req.system
	});
	messages.push({
		role: "user",
		content: req.prompt
	});
	return {
		model: req.model,
		messages,
		stream: req.stream ?? true,
		temperature: req.options?.temperature,
		max_tokens: req.options?.num_predict
	};
}
function buildOllamaChatTextChunks(content, model, chunkSize, reasoning) {
	const chunks = [];
	const createdAt = (/* @__PURE__ */ new Date()).toISOString();
	if (reasoning) for (let i = 0; i < reasoning.length; i += chunkSize) {
		const slice = reasoning.slice(i, i + chunkSize);
		chunks.push({
			model,
			created_at: createdAt,
			message: {
				role: "assistant",
				content: "",
				reasoning_content: slice
			},
			done: false
		});
	}
	for (let i = 0; i < content.length; i += chunkSize) {
		const slice = content.slice(i, i + chunkSize);
		chunks.push({
			model,
			created_at: createdAt,
			message: {
				role: "assistant",
				content: slice
			},
			done: false
		});
	}
	chunks.push({
		model,
		created_at: createdAt,
		message: {
			role: "assistant",
			content: ""
		},
		done: true,
		...DURATION_FIELDS
	});
	return chunks;
}
function buildOllamaChatTextResponse(content, model, reasoning) {
	return {
		model,
		created_at: (/* @__PURE__ */ new Date()).toISOString(),
		message: {
			role: "assistant",
			content,
			...reasoning ? { reasoning_content: reasoning } : {}
		},
		done: true,
		...DURATION_FIELDS
	};
}
function buildOllamaChatToolCallChunks(toolCalls, model, chunkSize, logger, reasoning) {
	const ollamaToolCalls = toolCalls.map((tc) => {
		let argsObj;
		try {
			argsObj = JSON.parse(tc.arguments || "{}");
		} catch {
			logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
			argsObj = {};
		}
		return { function: {
			name: tc.name,
			arguments: argsObj
		} };
	});
	const chunks = [];
	const createdAt = (/* @__PURE__ */ new Date()).toISOString();
	if (reasoning) for (let i = 0; i < reasoning.length; i += chunkSize) {
		const slice = reasoning.slice(i, i + chunkSize);
		chunks.push({
			model,
			created_at: createdAt,
			message: {
				role: "assistant",
				content: "",
				reasoning_content: slice
			},
			done: false
		});
	}
	chunks.push({
		model,
		created_at: createdAt,
		message: {
			role: "assistant",
			content: "",
			tool_calls: ollamaToolCalls
		},
		done: false
	});
	chunks.push({
		model,
		created_at: createdAt,
		message: {
			role: "assistant",
			content: ""
		},
		done: true,
		...DURATION_FIELDS
	});
	return chunks;
}
function buildOllamaChatToolCallResponse(toolCalls, model, logger, reasoning) {
	const ollamaToolCalls = toolCalls.map((tc) => {
		let argsObj;
		try {
			argsObj = JSON.parse(tc.arguments || "{}");
		} catch {
			logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
			argsObj = {};
		}
		return { function: {
			name: tc.name,
			arguments: argsObj
		} };
	});
	return {
		model,
		created_at: (/* @__PURE__ */ new Date()).toISOString(),
		message: {
			role: "assistant",
			content: "",
			...reasoning ? { reasoning_content: reasoning } : {},
			tool_calls: ollamaToolCalls
		},
		done: true,
		...DURATION_FIELDS
	};
}
function toOllamaToolCall(tc, logger) {
	let argsObj;
	try {
		argsObj = JSON.parse(tc.arguments || "{}");
	} catch {
		logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
		argsObj = {};
	}
	return { function: {
		name: tc.name,
		arguments: argsObj
	} };
}
function buildOllamaChatContentWithToolCallsChunks(content, toolCalls, model, chunkSize, logger, reasoning, blocks) {
	const chunks = [];
	const createdAt = (/* @__PURE__ */ new Date()).toISOString();
	if (blocks && blocks.length > 0) {
		const ordered = resolveFixtureBlocks(blocks);
		if (reasoning) for (let i = 0; i < reasoning.length; i += chunkSize) {
			const slice = reasoning.slice(i, i + chunkSize);
			chunks.push({
				model,
				created_at: createdAt,
				message: {
					role: "assistant",
					content: "",
					reasoning_content: slice
				},
				done: false
			});
		}
		for (const block of ordered) if (block.type === "text") for (let i = 0; i < block.text.length; i += chunkSize) {
			const slice = block.text.slice(i, i + chunkSize);
			chunks.push({
				model,
				created_at: createdAt,
				message: {
					role: "assistant",
					content: slice
				},
				done: false
			});
		}
		else chunks.push({
			model,
			created_at: createdAt,
			message: {
				role: "assistant",
				content: "",
				tool_calls: [toOllamaToolCall(block, logger)]
			},
			done: false
		});
		chunks.push({
			model,
			created_at: createdAt,
			message: {
				role: "assistant",
				content: ""
			},
			done: true,
			...DURATION_FIELDS
		});
		return chunks;
	}
	if (reasoning) for (let i = 0; i < reasoning.length; i += chunkSize) {
		const slice = reasoning.slice(i, i + chunkSize);
		chunks.push({
			model,
			created_at: createdAt,
			message: {
				role: "assistant",
				content: "",
				reasoning_content: slice
			},
			done: false
		});
	}
	for (let i = 0; i < content.length; i += chunkSize) {
		const slice = content.slice(i, i + chunkSize);
		chunks.push({
			model,
			created_at: createdAt,
			message: {
				role: "assistant",
				content: slice
			},
			done: false
		});
	}
	const ollamaToolCalls = toolCalls.map((tc) => {
		let argsObj;
		try {
			argsObj = JSON.parse(tc.arguments || "{}");
		} catch {
			logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
			argsObj = {};
		}
		return { function: {
			name: tc.name,
			arguments: argsObj
		} };
	});
	chunks.push({
		model,
		created_at: createdAt,
		message: {
			role: "assistant",
			content: "",
			tool_calls: ollamaToolCalls
		},
		done: false
	});
	chunks.push({
		model,
		created_at: createdAt,
		message: {
			role: "assistant",
			content: ""
		},
		done: true,
		...DURATION_FIELDS
	});
	return chunks;
}
function buildOllamaChatContentWithToolCallsResponse(content, toolCalls, model, logger, reasoning, blocks) {
	if (blocks && blocks.length > 0) {
		const ordered = resolveFixtureBlocks(blocks);
		content = ordered.filter((b) => b.type === "text").map((b) => b.text).join("");
		toolCalls = ordered.filter((b) => b.type === "toolCall").map((b) => ({
			name: b.name,
			arguments: b.arguments
		}));
	}
	const ollamaToolCalls = toolCalls.map((tc) => {
		let argsObj;
		try {
			argsObj = JSON.parse(tc.arguments || "{}");
		} catch {
			logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
			argsObj = {};
		}
		return { function: {
			name: tc.name,
			arguments: argsObj
		} };
	});
	return {
		model,
		created_at: (/* @__PURE__ */ new Date()).toISOString(),
		message: {
			role: "assistant",
			content,
			...reasoning ? { reasoning_content: reasoning } : {},
			tool_calls: ollamaToolCalls
		},
		done: true,
		...DURATION_FIELDS
	};
}
function buildOllamaGenerateTextChunks(content, model, chunkSize, reasoning) {
	const chunks = [];
	const createdAt = (/* @__PURE__ */ new Date()).toISOString();
	if (reasoning) for (let i = 0; i < reasoning.length; i += chunkSize) {
		const slice = reasoning.slice(i, i + chunkSize);
		chunks.push({
			model,
			created_at: createdAt,
			response: "",
			reasoning_content: slice,
			done: false
		});
	}
	for (let i = 0; i < content.length; i += chunkSize) {
		const slice = content.slice(i, i + chunkSize);
		chunks.push({
			model,
			created_at: createdAt,
			response: slice,
			done: false
		});
	}
	chunks.push({
		model,
		created_at: createdAt,
		response: "",
		done: true,
		...DURATION_FIELDS,
		context: []
	});
	return chunks;
}
function buildOllamaGenerateTextResponse(content, model, reasoning) {
	return {
		model,
		created_at: (/* @__PURE__ */ new Date()).toISOString(),
		response: content,
		...reasoning ? { reasoning_content: reasoning } : {},
		done: true,
		...DURATION_FIELDS,
		context: []
	};
}
async function handleOllama(req, res, raw, fixtures, journal, defaults, setCorsHeaders) {
	const { logger } = defaults;
	setCorsHeaders(res);
	const urlPath = req.url ?? "/api/chat";
	let ollamaReq;
	try {
		ollamaReq = JSON.parse(raw);
	} catch (parseErr) {
		const detail = parseErr instanceof Error ? parseErr.message : "unknown";
		journal.add({
			method: req.method ?? "POST",
			path: urlPath,
			headers: flattenHeaders(req.headers),
			body: null,
			response: {
				status: 400,
				fixture: null
			}
		});
		writeErrorResponse(res, 400, JSON.stringify({ error: {
			message: `Malformed JSON body: ${detail}`,
			type: "invalid_request_error"
		} }));
		return;
	}
	if (!ollamaReq.messages || !Array.isArray(ollamaReq.messages)) {
		journal.add({
			method: req.method ?? "POST",
			path: urlPath,
			headers: flattenHeaders(req.headers),
			body: null,
			response: {
				status: 400,
				fixture: null
			}
		});
		writeErrorResponse(res, 400, JSON.stringify({ error: {
			message: "Invalid request: messages array is required",
			type: "invalid_request_error"
		} }));
		return;
	}
	const completionReq = ollamaToCompletionRequest(ollamaReq);
	completionReq._endpointType = "chat";
	completionReq._context = getContext(req);
	const testId = getTestId(req);
	const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform, recordMatchOptions(!!defaults.record, defaults.logger));
	if (fixture) {
		journal.incrementFixtureMatchCount(fixture, fixtures, testId);
		logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
	} else logger.debug(`No fixture matched for request`);
	if (applyChaos(res, fixture, defaults.chaos, req.headers, journal, {
		method: req.method ?? "POST",
		path: urlPath,
		headers: flattenHeaders(req.headers),
		body: completionReq
	}, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
	if (!fixture) {
		if (resolveStrictMode(defaults.strict, req.headers)) {
			const strictStatus = 503;
			const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);
			logger.error(strictNoMatchLogLine(req.method ?? "POST", urlPath, skippedBySequenceOrTurn));
			journal.add({
				method: req.method ?? "POST",
				path: urlPath,
				headers: flattenHeaders(req.headers),
				body: completionReq,
				response: {
					status: strictStatus,
					fixture: null,
					...strictOverrideField(defaults.strict, req.headers)
				}
			});
			writeErrorResponse(res, strictStatus, JSON.stringify({ error: {
				message: strictMessage,
				type: "invalid_request_error"
			} }));
			return;
		}
		if (defaults.record) {
			const outcome = await proxyAndRecord(req, res, completionReq, "ollama", urlPath, fixtures, defaults, raw);
			if (outcome === "handled_by_hook") return;
			if (outcome !== "not_configured") {
				journal.add({
					method: req.method ?? "POST",
					path: urlPath,
					headers: flattenHeaders(req.headers),
					body: completionReq,
					response: {
						status: res.statusCode ?? 200,
						fixture: null,
						source: "proxy"
					}
				});
				return;
			}
		}
		journal.add({
			method: req.method ?? "POST",
			path: urlPath,
			headers: flattenHeaders(req.headers),
			body: completionReq,
			response: {
				status: 404,
				fixture: null,
				...strictOverrideField(defaults.strict, req.headers)
			}
		});
		writeErrorResponse(res, 404, JSON.stringify({ error: {
			message: "No fixture matched",
			type: "invalid_request_error"
		} }));
		return;
	}
	const response = await resolveResponse(fixture, completionReq);
	const latency = fixture.latency ?? defaults.latency;
	const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
	const streaming = ollamaReq.stream !== false;
	if (isErrorResponse(response)) {
		const status = response.status ?? 500;
		journal.add({
			method: req.method ?? "POST",
			path: urlPath,
			headers: flattenHeaders(req.headers),
			body: completionReq,
			response: {
				status,
				fixture
			}
		});
		writeErrorResponse(res, status, serializeErrorResponse(response), { retryAfter: response.retryAfter });
		return;
	}
	if (isContentWithToolCallsResponse(response)) {
		if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Ollama API -- ignoring");
		const journalEntry = journal.add({
			method: req.method ?? "POST",
			path: urlPath,
			headers: flattenHeaders(req.headers),
			body: completionReq,
			response: {
				status: 200,
				fixture
			}
		});
		const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
		const effReasoning = resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, logger);
		if (!streaming) {
			const body = buildOllamaChatContentWithToolCallsResponse(response.content ?? "", response.toolCalls ?? [], completionReq.model, logger, effReasoning, response.blocks);
			res.writeHead(200, { "Content-Type": "application/json" });
			res.end(JSON.stringify(body));
		} else {
			const chunks = buildOllamaChatContentWithToolCallsChunks(response.content ?? "", response.toolCalls ?? [], completionReq.model, chunkSize, logger, effReasoning, response.blocks);
			const interruption = createInterruptionSignal(fixture);
			if (!await writeNDJSONStream(res, chunks, {
				latency,
				streamingProfile: fixture.streamingProfile,
				recordedTimings: fixture.recordedTimings,
				replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
				signal: interruption?.signal,
				onChunkSent: interruption?.tick
			})) {
				if (!res.writableEnded) res.destroy();
				journalEntry.response.interrupted = true;
				journalEntry.response.interruptReason = interruption?.reason();
			}
			interruption?.cleanup();
		}
		return;
	}
	if (isTextResponse(response)) {
		if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Ollama API -- ignoring");
		const journalEntry = journal.add({
			method: req.method ?? "POST",
			path: urlPath,
			headers: flattenHeaders(req.headers),
			body: completionReq,
			response: {
				status: 200,
				fixture
			}
		});
		const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
		const effReasoning = resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, logger);
		if (!streaming) {
			const body = buildOllamaChatTextResponse(response.content, completionReq.model, effReasoning);
			res.writeHead(200, { "Content-Type": "application/json" });
			res.end(JSON.stringify(body));
		} else {
			const chunks = buildOllamaChatTextChunks(response.content, completionReq.model, chunkSize, effReasoning);
			const interruption = createInterruptionSignal(fixture);
			if (!await writeNDJSONStream(res, chunks, {
				latency,
				streamingProfile: fixture.streamingProfile,
				recordedTimings: fixture.recordedTimings,
				replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
				signal: interruption?.signal,
				onChunkSent: interruption?.tick
			})) {
				if (!res.writableEnded) res.destroy();
				journalEntry.response.interrupted = true;
				journalEntry.response.interruptReason = interruption?.reason();
			}
			interruption?.cleanup();
		}
		return;
	}
	if (isToolCallResponse(response)) {
		if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Ollama API — ignoring");
		const journalEntry = journal.add({
			method: req.method ?? "POST",
			path: urlPath,
			headers: flattenHeaders(req.headers),
			body: completionReq,
			response: {
				status: 200,
				fixture
			}
		});
		const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
		const effReasoning = resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, logger);
		if (!streaming) {
			const body = buildOllamaChatToolCallResponse(response.toolCalls, completionReq.model, logger, effReasoning);
			res.writeHead(200, { "Content-Type": "application/json" });
			res.end(JSON.stringify(body));
		} else {
			const chunks = buildOllamaChatToolCallChunks(response.toolCalls, completionReq.model, chunkSize, logger, effReasoning);
			const interruption = createInterruptionSignal(fixture);
			if (!await writeNDJSONStream(res, chunks, {
				latency,
				streamingProfile: fixture.streamingProfile,
				recordedTimings: fixture.recordedTimings,
				replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
				signal: interruption?.signal,
				onChunkSent: interruption?.tick
			})) {
				if (!res.writableEnded) res.destroy();
				journalEntry.response.interrupted = true;
				journalEntry.response.interruptReason = interruption?.reason();
			}
			interruption?.cleanup();
		}
		return;
	}
	journal.add({
		method: req.method ?? "POST",
		path: urlPath,
		headers: flattenHeaders(req.headers),
		body: completionReq,
		response: {
			status: 500,
			fixture
		}
	});
	writeErrorResponse(res, 500, JSON.stringify({ error: {
		message: "Fixture response did not match any known type",
		type: "server_error"
	} }));
}
async function handleOllamaGenerate(req, res, raw, fixtures, journal, defaults, setCorsHeaders) {
	setCorsHeaders(res);
	const urlPath = req.url ?? "/api/generate";
	let generateReq;
	try {
		generateReq = JSON.parse(raw);
	} catch (parseErr) {
		const detail = parseErr instanceof Error ? parseErr.message : "unknown";
		journal.add({
			method: req.method ?? "POST",
			path: urlPath,
			headers: flattenHeaders(req.headers),
			body: null,
			response: {
				status: 400,
				fixture: null
			}
		});
		writeErrorResponse(res, 400, JSON.stringify({ error: {
			message: `Malformed JSON body: ${detail}`,
			type: "invalid_request_error"
		} }));
		return;
	}
	if (!generateReq.prompt || typeof generateReq.prompt !== "string") {
		journal.add({
			method: req.method ?? "POST",
			path: urlPath,
			headers: flattenHeaders(req.headers),
			body: null,
			response: {
				status: 400,
				fixture: null
			}
		});
		writeErrorResponse(res, 400, JSON.stringify({ error: {
			message: "Invalid request: prompt field is required",
			type: "invalid_request_error"
		} }));
		return;
	}
	const completionReq = ollamaGenerateToCompletionRequest(generateReq);
	completionReq._endpointType = "chat";
	completionReq._context = getContext(req);
	const testId = getTestId(req);
	const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform, recordMatchOptions(!!defaults.record, defaults.logger));
	if (fixture) {
		journal.incrementFixtureMatchCount(fixture, fixtures, testId);
		defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
	} else defaults.logger.debug(`No fixture matched for request`);
	if (applyChaos(res, fixture, defaults.chaos, req.headers, journal, {
		method: req.method ?? "POST",
		path: urlPath,
		headers: flattenHeaders(req.headers),
		body: completionReq
	}, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
	if (!fixture) {
		if (resolveStrictMode(defaults.strict, req.headers)) {
			const strictStatus = 503;
			const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);
			defaults.logger.error(strictNoMatchLogLine(req.method ?? "POST", urlPath, skippedBySequenceOrTurn));
			journal.add({
				method: req.method ?? "POST",
				path: urlPath,
				headers: flattenHeaders(req.headers),
				body: completionReq,
				response: {
					status: strictStatus,
					fixture: null,
					...strictOverrideField(defaults.strict, req.headers)
				}
			});
			writeErrorResponse(res, strictStatus, JSON.stringify({ error: {
				message: strictMessage,
				type: "invalid_request_error"
			} }));
			return;
		}
		if (defaults.record) {
			const outcome = await proxyAndRecord(req, res, completionReq, "ollama", urlPath, fixtures, defaults, raw);
			if (outcome === "handled_by_hook") return;
			if (outcome !== "not_configured") {
				journal.add({
					method: req.method ?? "POST",
					path: urlPath,
					headers: flattenHeaders(req.headers),
					body: completionReq,
					response: {
						status: res.statusCode ?? 200,
						fixture: null,
						source: "proxy"
					}
				});
				return;
			}
		}
		journal.add({
			method: req.method ?? "POST",
			path: urlPath,
			headers: flattenHeaders(req.headers),
			body: completionReq,
			response: {
				status: 404,
				fixture: null,
				...strictOverrideField(defaults.strict, req.headers)
			}
		});
		writeErrorResponse(res, 404, JSON.stringify({ error: {
			message: "No fixture matched",
			type: "invalid_request_error"
		} }));
		return;
	}
	const response = await resolveResponse(fixture, completionReq);
	const latency = fixture.latency ?? defaults.latency;
	const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
	const streaming = generateReq.stream !== false;
	if (isErrorResponse(response)) {
		const status = response.status ?? 500;
		journal.add({
			method: req.method ?? "POST",
			path: urlPath,
			headers: flattenHeaders(req.headers),
			body: completionReq,
			response: {
				status,
				fixture
			}
		});
		writeErrorResponse(res, status, serializeErrorResponse(response), { retryAfter: response.retryAfter });
		return;
	}
	if (isTextResponse(response)) {
		const journalEntry = journal.add({
			method: req.method ?? "POST",
			path: urlPath,
			headers: flattenHeaders(req.headers),
			body: completionReq,
			response: {
				status: 200,
				fixture
			}
		});
		const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
		const effReasoning = resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, defaults.logger);
		if (!streaming) {
			const body = buildOllamaGenerateTextResponse(response.content, completionReq.model, effReasoning);
			res.writeHead(200, { "Content-Type": "application/json" });
			res.end(JSON.stringify(body));
		} else {
			const chunks = buildOllamaGenerateTextChunks(response.content, completionReq.model, chunkSize, effReasoning);
			const interruption = createInterruptionSignal(fixture);
			if (!await writeNDJSONStream(res, chunks, {
				latency,
				streamingProfile: fixture.streamingProfile,
				recordedTimings: fixture.recordedTimings,
				replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
				signal: interruption?.signal,
				onChunkSent: interruption?.tick
			})) {
				if (!res.writableEnded) res.destroy();
				journalEntry.response.interrupted = true;
				journalEntry.response.interruptReason = interruption?.reason();
			}
			interruption?.cleanup();
		}
		return;
	}
	if (isToolCallResponse(response) || isContentWithToolCallsResponse(response)) {
		journal.add({
			method: req.method ?? "POST",
			path: urlPath,
			headers: flattenHeaders(req.headers),
			body: completionReq,
			response: {
				status: 400,
				fixture
			}
		});
		writeErrorResponse(res, 400, JSON.stringify({ error: {
			message: "Tool call fixtures are not supported on /api/generate — use /api/chat instead",
			type: "invalid_request_error"
		} }));
		return;
	}
	journal.add({
		method: req.method ?? "POST",
		path: urlPath,
		headers: flattenHeaders(req.headers),
		body: completionReq,
		response: {
			status: 500,
			fixture
		}
	});
	writeErrorResponse(res, 500, JSON.stringify({ error: {
		message: "Fixture response did not match any known type",
		type: "server_error"
	} }));
}
async function handleOllamaEmbeddings(req, res, raw, fixtures, journal, defaults, setCorsHeaders) {
	const { logger } = defaults;
	setCorsHeaders(res);
	const urlPath = req.url ?? "/api/embeddings";
	let embReq;
	try {
		embReq = JSON.parse(raw);
	} catch (parseErr) {
		const detail = parseErr instanceof Error ? parseErr.message : "unknown";
		journal.add({
			method: req.method ?? "POST",
			path: urlPath,
			headers: flattenHeaders(req.headers),
			body: null,
			response: {
				status: 400,
				fixture: null
			}
		});
		writeErrorResponse(res, 400, JSON.stringify({ error: {
			message: `Malformed JSON body: ${detail}`,
			type: "invalid_request_error"
		} }));
		return;
	}
	const inputText = embReq.prompt ?? (typeof embReq.input === "string" ? embReq.input : void 0) ?? (Array.isArray(embReq.input) ? embReq.input.join(" ") : void 0);
	if (!embReq.model) {
		journal.add({
			method: req.method ?? "POST",
			path: urlPath,
			headers: flattenHeaders(req.headers),
			body: null,
			response: {
				status: 400,
				fixture: null
			}
		});
		writeErrorResponse(res, 400, JSON.stringify({ error: {
			message: "Invalid request: model field is required",
			type: "invalid_request_error"
		} }));
		return;
	}
	if (!inputText) {
		journal.add({
			method: req.method ?? "POST",
			path: urlPath,
			headers: flattenHeaders(req.headers),
			body: null,
			response: {
				status: 400,
				fixture: null
			}
		});
		writeErrorResponse(res, 400, JSON.stringify({ error: {
			message: "Invalid request: prompt or input field is required",
			type: "invalid_request_error"
		} }));
		return;
	}
	const syntheticReq = {
		model: embReq.model,
		messages: [],
		embeddingInput: inputText,
		_endpointType: "embedding",
		_context: getContext(req)
	};
	const testId = getTestId(req);
	const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(fixtures, syntheticReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform, recordMatchOptions(!!defaults.record, defaults.logger));
	if (fixture) {
		journal.incrementFixtureMatchCount(fixture, fixtures, testId);
		logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
	} else logger.debug(`No fixture matched for request`);
	if (applyChaos(res, fixture, defaults.chaos, req.headers, journal, {
		method: req.method ?? "POST",
		path: urlPath,
		headers: flattenHeaders(req.headers),
		body: syntheticReq
	}, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
	if (fixture) {
		const response = await resolveResponse(fixture, syntheticReq);
		if (isErrorResponse(response)) {
			const status = response.status ?? 500;
			journal.add({
				method: req.method ?? "POST",
				path: urlPath,
				headers: flattenHeaders(req.headers),
				body: syntheticReq,
				response: {
					status,
					fixture
				}
			});
			writeErrorResponse(res, status, serializeErrorResponse(response), { retryAfter: response.retryAfter });
			return;
		}
		if (isEmbeddingResponse(response)) {
			journal.add({
				method: req.method ?? "POST",
				path: urlPath,
				headers: flattenHeaders(req.headers),
				body: syntheticReq,
				response: {
					status: 200,
					fixture
				}
			});
			const body = {
				model: embReq.model,
				embedding: [...response.embedding]
			};
			res.writeHead(200, { "Content-Type": "application/json" });
			res.end(JSON.stringify(body));
			return;
		}
		journal.add({
			method: req.method ?? "POST",
			path: urlPath,
			headers: flattenHeaders(req.headers),
			body: syntheticReq,
			response: {
				status: 500,
				fixture
			}
		});
		writeErrorResponse(res, 500, JSON.stringify({ error: {
			message: "Fixture response did not match any known embedding type (must have embedding or error)",
			type: "server_error"
		} }));
		return;
	}
	if (resolveStrictMode(defaults.strict, req.headers)) {
		const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);
		logger.error(strictNoMatchLogLine(req.method ?? "POST", urlPath, skippedBySequenceOrTurn));
		journal.add({
			method: req.method ?? "POST",
			path: urlPath,
			headers: flattenHeaders(req.headers),
			body: syntheticReq,
			response: {
				status: 503,
				fixture: null,
				...strictOverrideField(defaults.strict, req.headers)
			}
		});
		writeErrorResponse(res, 503, JSON.stringify({ error: {
			message: strictMessage,
			type: "invalid_request_error"
		} }));
		return;
	}
	if (defaults.record) {
		const outcome = await proxyAndRecord(req, res, syntheticReq, "ollama", urlPath, fixtures, defaults, raw);
		if (outcome === "handled_by_hook") return;
		if (outcome !== "not_configured") {
			journal.add({
				method: req.method ?? "POST",
				path: urlPath,
				headers: flattenHeaders(req.headers),
				body: syntheticReq,
				response: {
					status: res.statusCode ?? 200,
					fixture: null,
					source: "proxy"
				}
			});
			return;
		}
	}
	logger.warn(`No embedding fixture matched for "${inputText.slice(0, 80)}" — returning deterministic fallback`);
	const embedding = generateDeterministicEmbedding(inputText);
	journal.add({
		method: req.method ?? "POST",
		path: urlPath,
		headers: flattenHeaders(req.headers),
		body: syntheticReq,
		response: {
			status: 200,
			fixture: null
		}
	});
	const body = {
		model: embReq.model,
		embedding
	};
	res.writeHead(200, { "Content-Type": "application/json" });
	res.end(JSON.stringify(body));
}

//#endregion
export { handleOllama, handleOllamaEmbeddings, handleOllamaGenerate, ollamaToCompletionRequest };
//# sourceMappingURL=ollama.js.map