import { n as transformMessages, o as calculateCost, s as clampThinkingLevel, t as sanitizeSurrogates } from "./sanitize-unicode-Cy46UFkR.js";
import { FinishReason, FunctionCallingConfigMode } from "@google/genai";
//#region src/llm/providers/google-shared.ts
/**
* Shared utilities for Google Generative AI and Google Vertex providers.
*/
/**
* Determines whether a streamed Gemini `Part` should be treated as "thinking".
*
* Protocol note (Gemini / Vertex AI thought signatures):
* - `thought: true` is the definitive marker for thinking content (thought summaries).
* - `thoughtSignature` is an encrypted representation of the model's internal thought process
*   used to preserve reasoning context across multi-turn interactions.
* - `thoughtSignature` can appear on ANY part type (text, functionCall, etc.) - it does NOT
*   indicate the part itself is thinking content.
* - For non-functionCall responses, the signature appears on the last part for context replay.
* - When persisting/replaying model outputs, signature-bearing parts must be preserved as-is;
*   do not merge/move signatures across parts.
*
* See: https://ai.google.dev/gemini-api/docs/thought-signatures
*/
function isThinkingPart(part) {
	return part.thought === true;
}
/**
* Retain thought signatures during streaming.
*
* Some backends only send `thoughtSignature` on the first delta for a given part/block; later deltas may omit it.
* This helper preserves the last non-empty signature for the current block.
*
* Note: this does NOT merge or move signatures across distinct response parts. It only prevents
* a signature from being overwritten with `undefined` within the same streamed block.
*/
function retainThoughtSignature(existing, incoming) {
	if (typeof incoming === "string" && incoming.length > 0) return incoming;
	return existing;
}
const base64SignaturePattern = /^[A-Za-z0-9+/]+={0,2}$/;
function isValidThoughtSignature(signature) {
	if (!signature) return false;
	if (signature.length % 4 !== 0) return false;
	return base64SignaturePattern.test(signature);
}
/**
* Only keep signatures from the same provider/model and with valid base64.
*/
function resolveThoughtSignature(isSameProviderAndModel, signature) {
	return isSameProviderAndModel && isValidThoughtSignature(signature) ? signature : void 0;
}
/**
* Models via Google APIs that require explicit tool call IDs in function calls/responses.
*/
function requiresToolCallId(modelId) {
	return modelId.startsWith("claude-") || modelId.startsWith("gpt-oss-");
}
function getGeminiMajorVersion(modelId) {
	const match = modelId.toLowerCase().match(/^gemini(?:-live)?-(\d+)/);
	if (!match) return;
	return Number.parseInt(match[1], 10);
}
function supportsMultimodalFunctionResponse(modelId) {
	const geminiMajorVersion = getGeminiMajorVersion(modelId);
	if (geminiMajorVersion !== void 0) return geminiMajorVersion >= 3;
	return true;
}
/**
* Convert internal messages to Gemini Content[] format.
*/
function convertMessages(model, context) {
	const contents = [];
	const normalizeToolCallId = (id) => {
		if (!requiresToolCallId(model.id)) return id;
		return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
	};
	const transformedMessages = transformMessages(context.messages, model, normalizeToolCallId);
	for (const msg of transformedMessages) if (msg.role === "user") if (typeof msg.content === "string") contents.push({
		role: "user",
		parts: [{ text: sanitizeSurrogates(msg.content) }]
	});
	else {
		const parts = msg.content.map((item) => {
			if (item.type === "text") return { text: sanitizeSurrogates(item.text) };
			return { inlineData: {
				mimeType: item.mimeType,
				data: item.data
			} };
		});
		if (parts.length === 0) continue;
		contents.push({
			role: "user",
			parts
		});
	}
	else if (msg.role === "assistant") {
		const parts = [];
		const isSameProviderAndModel = msg.provider === model.provider && msg.model === model.id;
		for (const block of msg.content) if (block.type === "text") {
			if (!block.text || block.text.trim() === "") continue;
			const thoughtSignature = resolveThoughtSignature(isSameProviderAndModel, block.textSignature);
			parts.push({
				text: sanitizeSurrogates(block.text),
				...thoughtSignature && { thoughtSignature }
			});
		} else if (block.type === "thinking") {
			if (!block.thinking || block.thinking.trim() === "") continue;
			if (isSameProviderAndModel) {
				const thoughtSignature = resolveThoughtSignature(isSameProviderAndModel, block.thinkingSignature);
				parts.push({
					thought: true,
					text: sanitizeSurrogates(block.thinking),
					...thoughtSignature && { thoughtSignature }
				});
			} else parts.push({ text: sanitizeSurrogates(block.thinking) });
		} else if (block.type === "toolCall") {
			const thoughtSignature = resolveThoughtSignature(isSameProviderAndModel, block.thoughtSignature);
			const part = {
				functionCall: {
					name: block.name,
					args: block.arguments ?? {},
					...requiresToolCallId(model.id) ? { id: block.id } : {}
				},
				...thoughtSignature && { thoughtSignature }
			};
			parts.push(part);
		}
		if (parts.length === 0) continue;
		contents.push({
			role: "model",
			parts
		});
	} else if (msg.role === "toolResult") {
		const textResult = msg.content.filter((c) => c.type === "text").map((c) => c.text).join("\n");
		const imageContent = model.input.includes("image") ? msg.content.filter((c) => c.type === "image") : [];
		const hasText = textResult.length > 0;
		const hasImages = imageContent.length > 0;
		const modelSupportsMultimodalFunctionResponse = supportsMultimodalFunctionResponse(model.id);
		const responseValue = hasText ? sanitizeSurrogates(textResult) : hasImages ? "(see attached image)" : "";
		const imageParts = imageContent.map((imageBlock) => ({ inlineData: {
			mimeType: imageBlock.mimeType,
			data: imageBlock.data
		} }));
		const includeId = requiresToolCallId(model.id);
		const functionResponsePart = { functionResponse: {
			name: msg.toolName,
			response: msg.isError ? { error: responseValue } : { output: responseValue },
			...hasImages && modelSupportsMultimodalFunctionResponse && { parts: imageParts },
			...includeId ? { id: msg.toolCallId } : {}
		} };
		const lastContent = contents[contents.length - 1];
		if (lastContent?.role === "user" && lastContent.parts?.some((p) => p.functionResponse)) lastContent.parts.push(functionResponsePart);
		else contents.push({
			role: "user",
			parts: [functionResponsePart]
		});
		if (hasImages && !modelSupportsMultimodalFunctionResponse) contents.push({
			role: "user",
			parts: [{ text: "Tool result image:" }, ...imageParts]
		});
	}
	return contents;
}
const JSON_SCHEMA_META_DECLARATIONS = new Set([
	"$schema",
	"$id",
	"$anchor",
	"$dynamicAnchor",
	"$vocabulary",
	"$comment",
	"$defs",
	"definitions"
]);
/**
* Strip meta-declarations from a schema obj
*/
function sanitizeForOpenApi(schema) {
	if (typeof schema !== "object" || schema === null || Array.isArray(schema)) return schema;
	const result = {};
	for (const [key, value] of Object.entries(schema)) {
		if (JSON_SCHEMA_META_DECLARATIONS.has(key)) continue;
		result[key] = sanitizeForOpenApi(value);
	}
	return result;
}
/**
* Convert tools to Gemini function declarations format.
*
* By default uses `parametersJsonSchema` which supports full JSON Schema (including
* anyOf, oneOf, const, etc.). Set `useParameters` to true to use the legacy `parameters`
* field instead (OpenAPI 3.03 Schema). This is needed for Cloud Code Assist with Claude
* models, where the API translates `parameters` into Anthropic's `input_schema`.
*/
function convertTools(tools, useParameters = false) {
	if (tools.length === 0) return;
	return [{ functionDeclarations: tools.map((tool) => ({
		name: tool.name,
		description: tool.description,
		...useParameters ? { parameters: sanitizeForOpenApi(tool.parameters) } : { parametersJsonSchema: tool.parameters }
	})) }];
}
/**
* Map tool choice string to Gemini FunctionCallingConfigMode.
*/
function mapToolChoice(choice) {
	switch (choice) {
		case "auto": return FunctionCallingConfigMode.AUTO;
		case "none": return FunctionCallingConfigMode.NONE;
		case "any": return FunctionCallingConfigMode.ANY;
		default: return FunctionCallingConfigMode.AUTO;
	}
}
function createGoogleAssistantOutput(model, api = model.api) {
	return {
		role: "assistant",
		content: [],
		api,
		provider: model.provider,
		model: model.id,
		usage: {
			input: 0,
			output: 0,
			cacheRead: 0,
			cacheWrite: 0,
			totalTokens: 0,
			cost: {
				input: 0,
				output: 0,
				cacheRead: 0,
				cacheWrite: 0,
				total: 0
			}
		},
		stopReason: "stop",
		timestamp: Date.now()
	};
}
async function runGoogleGenerateContentLifecycle(params) {
	const { stream, model, output, options } = params;
	try {
		const client = params.createClient();
		let requestParams = params.buildParams();
		const nextParams = await options?.onPayload?.(requestParams, model);
		if (nextParams !== void 0) requestParams = nextParams;
		await consumeGoogleGenerateContentStream({
			chunks: await client.models.generateContentStream(requestParams),
			model,
			output,
			stream,
			signal: options?.signal,
			nextToolCallId: params.nextToolCallId
		});
	} catch (error) {
		for (const block of output.content) if ("index" in block) delete block.index;
		output.stopReason = options?.signal?.aborted ? "aborted" : "error";
		output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
		stream.push({
			type: "error",
			reason: output.stopReason,
			error: output
		});
		stream.end();
	}
}
function buildGoogleGenerateContentParams(model, context, options = {}, configHooks) {
	const contents = convertMessages(model, context);
	const generationConfig = {};
	if (options.temperature !== void 0) generationConfig.temperature = options.temperature;
	if (options.maxTokens !== void 0) generationConfig.maxOutputTokens = options.maxTokens;
	if (options.stop !== void 0 && options.stop.length > 0) generationConfig.stopSequences = options.stop;
	const config = {
		...Object.keys(generationConfig).length > 0 && generationConfig,
		...context.systemPrompt && { systemInstruction: sanitizeSurrogates(context.systemPrompt) },
		...context.tools && context.tools.length > 0 && { tools: convertTools(context.tools) }
	};
	if (context.tools && context.tools.length > 0 && options.toolChoice) config.toolConfig = { functionCallingConfig: { mode: mapToolChoice(options.toolChoice) } };
	else config.toolConfig = void 0;
	if (options.thinking?.enabled && model.reasoning) {
		const thinkingConfig = { includeThoughts: true };
		if (options.thinking.level !== void 0) thinkingConfig.thinkingLevel = configHooks?.mapThinkingLevel ? configHooks.mapThinkingLevel(options.thinking.level) : options.thinking.level;
		else if (options.thinking.budgetTokens !== void 0) thinkingConfig.thinkingBudget = options.thinking.budgetTokens;
		config.thinkingConfig = thinkingConfig;
	} else if (model.reasoning && options.thinking && !options.thinking.enabled) config.thinkingConfig = configHooks?.getDisabledThinkingConfig ? configHooks.getDisabledThinkingConfig(model) : getDisabledGoogleThinkingConfig(model);
	if (options.signal) {
		if (options.signal.aborted) throw new Error("Request aborted");
		config.abortSignal = options.signal;
	}
	return {
		model: model.id,
		contents,
		config
	};
}
function buildGoogleSimpleThinking(model, options, config) {
	if (!options?.reasoning) return { enabled: false };
	const clampedReasoning = clampThinkingLevel(model, options.reasoning);
	const effort = clampedReasoning === "off" || clampedReasoning === "max" ? "high" : clampedReasoning;
	if (isGemini3ProModel(model) || isGemini3FlashModel(model) || config?.includeGemma4ThinkingLevel && isGemma4Model(model)) return {
		enabled: true,
		level: getGoogleThinkingLevel(effort, model, { includeGemma4: config?.includeGemma4ThinkingLevel })
	};
	return {
		enabled: true,
		budgetTokens: getGoogleBudget(model, effort, options.thinkingBudgets, { useFlashLiteBudgets: config?.useFlashLiteBudgets })
	};
}
function getDisabledGoogleThinkingConfig(model, config) {
	const mapThinkingLevel = (level) => config?.mapThinkingLevel ? config.mapThinkingLevel(level) : level;
	if (isGemini3ProModel(model)) return { thinkingLevel: mapThinkingLevel("LOW") };
	if (isGemini3FlashModel(model)) return { thinkingLevel: mapThinkingLevel("MINIMAL") };
	if (config?.includeGemma4 && isGemma4Model(model)) return { thinkingLevel: mapThinkingLevel("MINIMAL") };
	return { thinkingBudget: 0 };
}
function isGemma4Model(model) {
	return /gemma-?4/.test(model.id.toLowerCase());
}
function isGemini3ProModel(model) {
	return /gemini-3(?:\.\d+)?-pro/.test(model.id.toLowerCase());
}
function isGemini3FlashModel(model) {
	return /gemini-3(?:\.\d+)?-flash/.test(model.id.toLowerCase());
}
function getGoogleThinkingLevel(effort, model, config) {
	if (isGemini3ProModel(model)) switch (effort) {
		case "minimal":
		case "low": return "LOW";
		case "medium":
		case "high": return "HIGH";
	}
	if (config?.includeGemma4 && isGemma4Model(model)) switch (effort) {
		case "minimal":
		case "low": return "MINIMAL";
		case "medium":
		case "high": return "HIGH";
	}
	switch (effort) {
		case "minimal": return "MINIMAL";
		case "low": return "LOW";
		case "medium": return "MEDIUM";
		case "high": return "HIGH";
	}
	return "HIGH";
}
function getGoogleBudget(model, effort, customBudgets, config) {
	if (customBudgets?.[effort] !== void 0) return customBudgets[effort];
	if (model.id.includes("2.5-pro")) return {
		minimal: 128,
		low: 2048,
		medium: 8192,
		high: 32768
	}[effort];
	if (config?.useFlashLiteBudgets && model.id.includes("2.5-flash-lite")) return {
		minimal: 512,
		low: 2048,
		medium: 8192,
		high: 24576
	}[effort];
	if (model.id.includes("2.5-flash")) return {
		minimal: 128,
		low: 2048,
		medium: 8192,
		high: 24576
	}[effort];
	return -1;
}
/**
* Map Gemini FinishReason to our StopReason.
*/
function mapStopReason(reason) {
	switch (reason) {
		case FinishReason.STOP: return "stop";
		case FinishReason.MAX_TOKENS: return "length";
		case FinishReason.BLOCKLIST:
		case FinishReason.PROHIBITED_CONTENT:
		case FinishReason.SPII:
		case FinishReason.SAFETY:
		case FinishReason.IMAGE_SAFETY:
		case FinishReason.IMAGE_PROHIBITED_CONTENT:
		case FinishReason.IMAGE_RECITATION:
		case FinishReason.IMAGE_OTHER:
		case FinishReason.RECITATION:
		case FinishReason.FINISH_REASON_UNSPECIFIED:
		case FinishReason.OTHER:
		case FinishReason.LANGUAGE:
		case FinishReason.MALFORMED_FUNCTION_CALL:
		case FinishReason.UNEXPECTED_TOOL_CALL:
		case FinishReason.NO_IMAGE: return "error";
		default: throw new Error(`Unhandled stop reason: ${String(reason)}`);
	}
}
async function consumeGoogleGenerateContentStream(params) {
	params.stream.push({
		type: "start",
		partial: params.output
	});
	let currentBlock = null;
	const blocks = params.output.content;
	const blockIndex = () => blocks.length - 1;
	const endCurrentBlock = () => {
		if (!currentBlock) return;
		if (currentBlock.type === "text") params.stream.push({
			type: "text_end",
			contentIndex: blockIndex(),
			content: currentBlock.text,
			partial: params.output
		});
		else params.stream.push({
			type: "thinking_end",
			contentIndex: blockIndex(),
			content: currentBlock.thinking,
			partial: params.output
		});
		currentBlock = null;
	};
	for await (const chunk of params.chunks) {
		params.output.responseId ||= chunk.responseId;
		const candidate = chunk.candidates?.[0];
		if (candidate?.content?.parts) for (const part of candidate.content.parts) {
			if (part.text !== void 0) {
				const isThinking = isThinkingPart(part);
				if (!currentBlock || isThinking && currentBlock.type !== "thinking" || !isThinking && currentBlock.type !== "text") {
					endCurrentBlock();
					if (isThinking) {
						currentBlock = {
							type: "thinking",
							thinking: "",
							thinkingSignature: void 0
						};
						params.output.content.push(currentBlock);
						params.stream.push({
							type: "thinking_start",
							contentIndex: blockIndex(),
							partial: params.output
						});
					} else {
						currentBlock = {
							type: "text",
							text: ""
						};
						params.output.content.push(currentBlock);
						params.stream.push({
							type: "text_start",
							contentIndex: blockIndex(),
							partial: params.output
						});
					}
				}
				if (currentBlock.type === "thinking") {
					currentBlock.thinking += part.text;
					currentBlock.thinkingSignature = retainThoughtSignature(currentBlock.thinkingSignature, part.thoughtSignature);
					params.stream.push({
						type: "thinking_delta",
						contentIndex: blockIndex(),
						delta: part.text,
						partial: params.output
					});
				} else {
					currentBlock.text += part.text;
					currentBlock.textSignature = retainThoughtSignature(currentBlock.textSignature, part.thoughtSignature);
					params.stream.push({
						type: "text_delta",
						contentIndex: blockIndex(),
						delta: part.text,
						partial: params.output
					});
				}
			}
			if (part.functionCall) {
				endCurrentBlock();
				const providedId = part.functionCall.id;
				const toolCall = {
					type: "toolCall",
					id: !providedId || params.output.content.some((block) => block.type === "toolCall" && block.id === providedId) ? params.nextToolCallId(part.functionCall.name) : providedId,
					name: part.functionCall.name || "",
					arguments: part.functionCall.args ?? {},
					...part.thoughtSignature && { thoughtSignature: part.thoughtSignature }
				};
				params.output.content.push(toolCall);
				params.stream.push({
					type: "toolcall_start",
					contentIndex: blockIndex(),
					partial: params.output
				});
				params.stream.push({
					type: "toolcall_delta",
					contentIndex: blockIndex(),
					delta: JSON.stringify(toolCall.arguments),
					partial: params.output
				});
				params.stream.push({
					type: "toolcall_end",
					contentIndex: blockIndex(),
					toolCall,
					partial: params.output
				});
			}
		}
		if (candidate?.finishReason) {
			params.output.stopReason = mapStopReason(candidate.finishReason);
			if (params.output.content.some((block) => block.type === "toolCall")) params.output.stopReason = "toolUse";
		}
		if (chunk.usageMetadata) {
			params.output.usage = {
				input: (chunk.usageMetadata.promptTokenCount || 0) - (chunk.usageMetadata.cachedContentTokenCount || 0),
				output: (chunk.usageMetadata.candidatesTokenCount || 0) + (chunk.usageMetadata.thoughtsTokenCount || 0),
				cacheRead: chunk.usageMetadata.cachedContentTokenCount || 0,
				cacheWrite: 0,
				totalTokens: chunk.usageMetadata.totalTokenCount || 0,
				cost: {
					input: 0,
					output: 0,
					cacheRead: 0,
					cacheWrite: 0,
					total: 0
				}
			};
			calculateCost(params.model, params.output.usage);
		}
	}
	endCurrentBlock();
	if (params.signal?.aborted) throw new Error("Request was aborted");
	if (params.output.stopReason === "aborted" || params.output.stopReason === "error") throw new Error("An unknown error occurred");
	params.stream.push({
		type: "done",
		reason: params.output.stopReason,
		message: params.output
	});
	params.stream.end();
}
//#endregion
export { runGoogleGenerateContentLifecycle as a, getDisabledGoogleThinkingConfig as i, buildGoogleSimpleThinking as n, createGoogleAssistantOutput as r, buildGoogleGenerateContentParams as t };
