const require_logger = require('./logger-q_xXORhb.cjs');
const require_esm = require('./esm-D3hKBIJw.cjs');
const require_fileExtensions = require('./fileExtensions-Da2HykPA.cjs');
const require_util = require('./util-C-nJy5yZ.cjs');
const require_fetch = require('./fetch--6dTWCD4.cjs');
const require_cache = require('./cache-BrwLt6g7.cjs');
const require_transform = require('./transform-DFn8CyvR.cjs');
const require_openai = require('./openai-DLwm54eV.cjs');
const require_util$1 = require('./util-DdYfVi1z.cjs');
let path = require("path");
path = require_logger.__toESM(path);
let __opentelemetry_api = require("@opentelemetry/api");
let __modelcontextprotocol_sdk_client_index_js = require("@modelcontextprotocol/sdk/client/index.js");

//#region src/tracing/genaiTracer.ts
const TRACER_NAME = "promptfoo.providers";
const TRACER_VERSION = "1.0.0";
const GenAIAttributes = {
	SYSTEM: "gen_ai.system",
	OPERATION_NAME: "gen_ai.operation.name",
	REQUEST_MODEL: "gen_ai.request.model",
	REQUEST_MAX_TOKENS: "gen_ai.request.max_tokens",
	REQUEST_TEMPERATURE: "gen_ai.request.temperature",
	REQUEST_TOP_P: "gen_ai.request.top_p",
	REQUEST_TOP_K: "gen_ai.request.top_k",
	REQUEST_STOP_SEQUENCES: "gen_ai.request.stop_sequences",
	REQUEST_FREQUENCY_PENALTY: "gen_ai.request.frequency_penalty",
	REQUEST_PRESENCE_PENALTY: "gen_ai.request.presence_penalty",
	RESPONSE_MODEL: "gen_ai.response.model",
	RESPONSE_ID: "gen_ai.response.id",
	RESPONSE_FINISH_REASONS: "gen_ai.response.finish_reasons",
	USAGE_INPUT_TOKENS: "gen_ai.usage.input_tokens",
	USAGE_OUTPUT_TOKENS: "gen_ai.usage.output_tokens",
	USAGE_TOTAL_TOKENS: "gen_ai.usage.total_tokens",
	USAGE_CACHED_TOKENS: "gen_ai.usage.cached_tokens",
	USAGE_REASONING_TOKENS: "gen_ai.usage.reasoning_tokens",
	USAGE_ACCEPTED_PREDICTION_TOKENS: "gen_ai.usage.accepted_prediction_tokens",
	USAGE_REJECTED_PREDICTION_TOKENS: "gen_ai.usage.rejected_prediction_tokens"
};
const PromptfooAttributes = {
	PROVIDER_ID: "promptfoo.provider.id",
	EVAL_ID: "promptfoo.eval.id",
	TEST_INDEX: "promptfoo.test.index",
	PROMPT_LABEL: "promptfoo.prompt.label",
	CACHE_HIT: "promptfoo.cache_hit",
	REQUEST_BODY: "promptfoo.request.body",
	RESPONSE_BODY: "promptfoo.response.body"
};
/** Maximum length for request/response body attributes (characters) */
const MAX_BODY_LENGTH = 4096;
/**
* Patterns to redact from request/response bodies for security.
* These patterns match common API key and secret formats.
*/
const SENSITIVE_PATTERNS = [
	{
		pattern: /\b(sk-[a-zA-Z0-9_-]{20,})/g,
		replacement: "<REDACTED_API_KEY>"
	},
	{
		pattern: /\b(pk-[a-zA-Z0-9_-]{20,})/g,
		replacement: "<REDACTED_API_KEY>"
	},
	{
		pattern: /\b(api[_-]?key["']?\s*[:=]\s*["']?)([a-zA-Z0-9_-]{16,})/gi,
		replacement: "$1<REDACTED>"
	},
	{
		pattern: /\b(secret["']?\s*[:=]\s*["']?)([a-zA-Z0-9_-]{16,})/gi,
		replacement: "$1<REDACTED>"
	},
	{
		pattern: /\b(token["']?\s*[:=]\s*["']?)([a-zA-Z0-9_-]{16,})/gi,
		replacement: "$1<REDACTED>"
	},
	{
		pattern: /\b(password["']?\s*[:=]\s*["']?)([^\s"',}{]+)/gi,
		replacement: "$1<REDACTED>"
	},
	{
		pattern: /(Authorization["']?\s*[:=]\s*["']?)(Bearer\s+)?([a-zA-Z0-9_.-]{16,})/gi,
		replacement: "$1$2<REDACTED>"
	},
	{
		pattern: /\b(AKIA[A-Z0-9]{16})/g,
		replacement: "<REDACTED_AWS_KEY>"
	},
	{
		pattern: /\b([a-zA-Z0-9/+=]{40})/g,
		replacement: (match) => {
			if (/^[A-Za-z0-9+/=]{40}$/.test(match) && match.includes("/")) return "<REDACTED_SECRET>";
			return match;
		}
	},
	{
		pattern: /\b[a-f0-9]{64,}\b/gi,
		replacement: "<REDACTED_HASH>"
	}
];
/**
* Get the tracer instance for GenAI operations.
*/
function getGenAITracer() {
	return __opentelemetry_api.trace.getTracer(TRACER_NAME, TRACER_VERSION);
}
/**
* Execute a function within a GenAI span.
*
* This wrapper:
* 1. Creates a span with GenAI semantic conventions
* 2. Sets request attributes before execution
* 3. Executes the provided function
* 4. Sets response attributes (including token usage) after execution
* 5. Handles errors and sets appropriate span status
*
* @param ctx - GenAI span context with request information
* @param fn - The async function to execute (typically the API call)
* @param resultExtractor - Optional function to extract result data from the return value
* @returns The return value from fn
*
* @example
* ```typescript
* const response = await withGenAISpan(
*   {
*     system: 'openai',
*     operationName: 'chat',
*     model: 'gpt-4',
*     providerId: 'openai:gpt-4',
*   },
*   async (span) => {
*     return await openai.chat.completions.create({...});
*   },
*   (response) => ({
*     tokenUsage: {
*       prompt: response.usage?.prompt_tokens,
*       completion: response.usage?.completion_tokens,
*     },
*     responseId: response.id,
*   })
* );
* ```
*/
async function withGenAISpan(ctx, fn, resultExtractor) {
	const tracer = getGenAITracer();
	const spanName = `${ctx.operationName} ${ctx.model}`;
	let parentContext = __opentelemetry_api.context.active();
	if (ctx.traceparent) {
		const carrier = { traceparent: ctx.traceparent };
		parentContext = __opentelemetry_api.propagation.extract(__opentelemetry_api.ROOT_CONTEXT, carrier);
	}
	const spanCallback = async (span) => {
		try {
			const value = await fn(span);
			if (resultExtractor) setGenAIResponseAttributes(span, resultExtractor(value), ctx.sanitizeBodies);
			span.setStatus({ code: __opentelemetry_api.SpanStatusCode.OK });
			return value;
		} catch (error) {
			span.setStatus({
				code: __opentelemetry_api.SpanStatusCode.ERROR,
				message: error instanceof Error ? error.message : String(error)
			});
			if (error instanceof Error) span.recordException(error);
			throw error;
		} finally {
			span.end();
		}
	};
	return tracer.startActiveSpan(spanName, {
		kind: __opentelemetry_api.SpanKind.CLIENT,
		attributes: buildRequestAttributes(ctx)
	}, parentContext, spanCallback);
}
/**
* Build request attributes for a GenAI span.
*/
function buildRequestAttributes(ctx) {
	const attrs = {
		[GenAIAttributes.SYSTEM]: ctx.system,
		[GenAIAttributes.OPERATION_NAME]: ctx.operationName,
		[GenAIAttributes.REQUEST_MODEL]: ctx.model,
		[PromptfooAttributes.PROVIDER_ID]: ctx.providerId
	};
	if (ctx.maxTokens !== void 0) attrs[GenAIAttributes.REQUEST_MAX_TOKENS] = ctx.maxTokens;
	if (ctx.temperature !== void 0) attrs[GenAIAttributes.REQUEST_TEMPERATURE] = ctx.temperature;
	if (ctx.topP !== void 0) attrs[GenAIAttributes.REQUEST_TOP_P] = ctx.topP;
	if (ctx.topK !== void 0) attrs[GenAIAttributes.REQUEST_TOP_K] = ctx.topK;
	if (ctx.stopSequences && ctx.stopSequences.length > 0) attrs[GenAIAttributes.REQUEST_STOP_SEQUENCES] = ctx.stopSequences;
	if (ctx.frequencyPenalty !== void 0) attrs[GenAIAttributes.REQUEST_FREQUENCY_PENALTY] = ctx.frequencyPenalty;
	if (ctx.presencePenalty !== void 0) attrs[GenAIAttributes.REQUEST_PRESENCE_PENALTY] = ctx.presencePenalty;
	if (ctx.evalId) attrs[PromptfooAttributes.EVAL_ID] = ctx.evalId;
	if (ctx.testIndex !== void 0) attrs[PromptfooAttributes.TEST_INDEX] = ctx.testIndex;
	if (ctx.promptLabel) attrs[PromptfooAttributes.PROMPT_LABEL] = ctx.promptLabel;
	if (ctx.requestBody) attrs[PromptfooAttributes.REQUEST_BODY] = truncateBody(ctx.requestBody, ctx.sanitizeBodies);
	return attrs;
}
/**
* Sanitize sensitive data from a body string.
* Redacts API keys, secrets, tokens, and other sensitive patterns.
*/
function sanitizeBody(body) {
	let sanitized = body;
	for (const { pattern, replacement } of SENSITIVE_PATTERNS) if (typeof replacement === "function") sanitized = sanitized.replace(pattern, replacement);
	else sanitized = sanitized.replace(pattern, replacement);
	return sanitized;
}
/**
* Truncate a body string to MAX_BODY_LENGTH.
* Optionally sanitizes sensitive data first if sanitize=true.
*
* @param body - The body string to process
* @param sanitize - Whether to sanitize sensitive data (defaults to true)
*/
function truncateBody(body, sanitize = true) {
	const processed = sanitize ? sanitizeBody(body) : body;
	if (processed.length <= MAX_BODY_LENGTH) return processed;
	return processed.slice(0, MAX_BODY_LENGTH - 15) + "... [truncated]";
}
/**
* Set response attributes on a span after the API call completes.
*
* @param span - The span to update
* @param result - The result data containing token usage and response metadata
* @param sanitize - Whether to sanitize sensitive data from response body (defaults to true)
*/
function setGenAIResponseAttributes(span, result, sanitize = true) {
	if (result.tokenUsage) {
		const usage = result.tokenUsage;
		if (usage.prompt !== void 0) span.setAttribute(GenAIAttributes.USAGE_INPUT_TOKENS, usage.prompt);
		if (usage.completion !== void 0) span.setAttribute(GenAIAttributes.USAGE_OUTPUT_TOKENS, usage.completion);
		if (usage.total !== void 0) span.setAttribute(GenAIAttributes.USAGE_TOTAL_TOKENS, usage.total);
		if (usage.cached !== void 0) span.setAttribute(GenAIAttributes.USAGE_CACHED_TOKENS, usage.cached);
		if (usage.completionDetails) {
			if (usage.completionDetails.reasoning !== void 0) span.setAttribute(GenAIAttributes.USAGE_REASONING_TOKENS, usage.completionDetails.reasoning);
			if (usage.completionDetails.acceptedPrediction !== void 0) span.setAttribute(GenAIAttributes.USAGE_ACCEPTED_PREDICTION_TOKENS, usage.completionDetails.acceptedPrediction);
			if (usage.completionDetails.rejectedPrediction !== void 0) span.setAttribute(GenAIAttributes.USAGE_REJECTED_PREDICTION_TOKENS, usage.completionDetails.rejectedPrediction);
		}
	}
	if (result.responseModel) span.setAttribute(GenAIAttributes.RESPONSE_MODEL, result.responseModel);
	if (result.responseId) span.setAttribute(GenAIAttributes.RESPONSE_ID, result.responseId);
	if (result.finishReasons && result.finishReasons.length > 0) span.setAttribute(GenAIAttributes.RESPONSE_FINISH_REASONS, result.finishReasons);
	if (result.cacheHit !== void 0) span.setAttribute(PromptfooAttributes.CACHE_HIT, result.cacheHit);
	if (result.responseBody) span.setAttribute(PromptfooAttributes.RESPONSE_BODY, truncateBody(result.responseBody, sanitize));
}

//#endregion
//#region src/util/finishReason.ts
/**
* Mapping of provider-specific finish/stop reasons to standardized OpenAI-compatible values.
*
* This normalization allows consistent finish reason handling across different LLM providers:
*
* **OpenAI Standard Values:**
* - `stop`: Natural completion (reached end_of_turn, stop sequence, etc.)
* - `length`: Token limit reached (max_tokens, context length, etc.)
* - `content_filter`: Content filtering triggered
* - `tool_calls`: Model made function/tool calls
*
* **Provider Mappings:**
* - OpenAI: `function_call` (legacy) → `tool_calls` (current)
* - Anthropic: `end_turn` → `stop`, `stop_sequence` → `stop`, `max_tokens` → `length`, `tool_use` → `tool_calls`
*
* @example
* ```typescript
* normalizeFinishReason('end_turn')     // Returns: 'stop'
* normalizeFinishReason('max_tokens')   // Returns: 'length'
* normalizeFinishReason('tool_use')     // Returns: 'tool_calls'
* normalizeFinishReason('function_call') // Returns: 'tool_calls'
* normalizeFinishReason('unknown')      // Returns: 'unknown' (passthrough)
* ```
*/
const FINISH_REASON_MAP = {
	stop: "stop",
	length: "length",
	content_filter: "content_filter",
	tool_calls: "tool_calls",
	function_call: "tool_calls",
	end_turn: "stop",
	stop_sequence: "stop",
	max_tokens: "length",
	tool_use: "tool_calls"
};
/**
* Normalize a provider-specific finish or stop reason to a standard OpenAI-compatible value.
*
* This function standardizes finish reasons across different LLM providers to enable
* consistent handling in assertions and application logic. Unknown values are passed
* through unchanged to preserve provider-specific reasons.
*
* @param raw - The raw finish_reason/stop_reason from the provider response
* @returns A normalized finish reason string, or undefined if input is invalid
*
* @example Basic usage
* ```typescript
* const result = await provider.callApi('Hello world');
* const normalized = normalizeFinishReason(result.finishReason);
* // normalized will be one of: 'stop', 'length', 'content_filter', 'tool_calls', or original value
* ```
*
* @example With finish-reason assertion
* ```yaml
* assert:
*   - type: finish-reason
*     value: stop  # Expects natural completion (works for both 'stop' and 'end_turn')
* ```
*/
function normalizeFinishReason(raw) {
	if (raw == null) return;
	if (typeof raw !== "string") return;
	const trimmed = raw.trim();
	if (trimmed === "") return;
	const key = trimmed.toLowerCase();
	return FINISH_REASON_MAP[key] ?? key;
}

//#endregion
//#region src/providers/mcp/client.ts
/**
* Get the effective request options for MCP requests.
* Priority: config values > MCP_REQUEST_TIMEOUT_MS env var > undefined (SDK default of 60s)
*/
function getEffectiveRequestOptions(config) {
	const timeout = config.timeout ?? require_logger.getEnvInt("MCP_REQUEST_TIMEOUT_MS");
	if (!timeout && !config.resetTimeoutOnProgress && !config.maxTotalTimeout) return;
	const options = {};
	if (timeout) options.timeout = timeout;
	if (config.resetTimeoutOnProgress) options.resetTimeoutOnProgress = config.resetTimeoutOnProgress;
	if (config.maxTotalTimeout) options.maxTotalTimeout = config.maxTotalTimeout;
	return options;
}
var MCPClient = class {
	clients = /* @__PURE__ */ new Map();
	tools = /* @__PURE__ */ new Map();
	config;
	transports = /* @__PURE__ */ new Map();
	oauthConfigs = /* @__PURE__ */ new Map();
	tokenExpiresAt = /* @__PURE__ */ new Map();
	tokenRefreshPromise = /* @__PURE__ */ new Map();
	get hasInitialized() {
		return this.clients.size > 0;
	}
	get connectedServers() {
		return Array.from(this.clients.keys());
	}
	/**
	* Check if debug mode is enabled (config takes priority over env var)
	*/
	get isDebugEnabled() {
		return this.config.debug ?? require_logger.getEnvBool("MCP_DEBUG") ?? false;
	}
	/**
	* Check if verbose mode is enabled (config takes priority over env var)
	*/
	get isVerboseEnabled() {
		return this.config.verbose ?? require_logger.getEnvBool("MCP_VERBOSE") ?? false;
	}
	constructor(config) {
		this.config = config;
	}
	async initialize() {
		if (!this.config.enabled) return;
		const servers = this.config.servers || (this.config.server ? [this.config.server] : []);
		for (const server of servers) {
			require_logger.logger_default.info(`connecting to server ${server.name || server.url || server.path || "default"}`);
			await this.connectToServer(server);
		}
	}
	async connectToServer(server) {
		const serverKey = server.name || server.url || server.path || "default";
		const client = new __modelcontextprotocol_sdk_client_index_js.Client({
			name: "promptfoo-MCP",
			version: "1.0.0",
			description: "Promptfoo MCP client for connecting to MCP servers during LLM evaluations"
		});
		let transport;
		try {
			const requestOptions = getEffectiveRequestOptions(this.config);
			if (server.command && server.args) {
				const { StdioClientTransport } = await import("@modelcontextprotocol/sdk/client/stdio.js");
				transport = new StdioClientTransport({
					command: server.command,
					args: server.args,
					env: process.env
				});
				await client.connect(transport, requestOptions);
			} else if (server.path) {
				const isJs = server.path.endsWith(".js");
				const isPy = server.path.endsWith(".py");
				if (!isJs && !isPy) throw new Error("Local server must be a .js or .py file");
				const command = isPy ? process.platform === "win32" ? "python" : "python3" : process.execPath;
				const { StdioClientTransport } = await import("@modelcontextprotocol/sdk/client/stdio.js");
				transport = new StdioClientTransport({
					command,
					args: [server.path],
					env: process.env
				});
				await client.connect(transport, requestOptions);
			} else if (server.url) {
				const renderedServer = require_transform.renderAuthVars(server);
				let authHeaders = {};
				if (renderedServer.auth?.type === "oauth") {
					const oauthAuth = renderedServer.auth;
					require_logger.logger_default.debug("[MCP] Fetching OAuth token");
					const { accessToken, expiresAt } = await require_transform.getOAuthTokenWithExpiry(oauthAuth, server.url);
					authHeaders = { Authorization: `Bearer ${accessToken}` };
					this.oauthConfigs.set(serverKey, {
						serverKey,
						serverConfig: server,
						auth: oauthAuth
					});
					this.tokenExpiresAt.set(serverKey, expiresAt);
				} else authHeaders = require_transform.getAuthHeaders(renderedServer);
				const headers = {
					...server.headers || {},
					...authHeaders
				};
				const queryParams = require_transform.getAuthQueryParams(renderedServer);
				const serverUrl = require_transform.applyQueryParams(server.url, queryParams);
				const transportOptions = {};
				if (Object.keys(headers).length > 0) transportOptions.requestInit = { headers };
				const hasOptions = Object.keys(transportOptions).length > 0;
				try {
					const { StreamableHTTPClientTransport } = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
					transport = new StreamableHTTPClientTransport(new URL(serverUrl), hasOptions ? transportOptions : void 0);
					await client.connect(transport, requestOptions);
					require_logger.logger_default.debug("Connected using Streamable HTTP transport");
				} catch (error) {
					require_logger.logger_default.debug(`Failed to connect to MCP server with Streamable HTTP transport ${serverKey}: ${error}`);
					const { SSEClientTransport } = await import("@modelcontextprotocol/sdk/client/sse.js");
					transport = new SSEClientTransport(new URL(serverUrl), hasOptions ? transportOptions : void 0);
					await client.connect(transport, requestOptions);
					require_logger.logger_default.debug("Connected using SSE transport");
				}
			} else throw new Error("Either command+args or path or url must be specified for MCP server");
			if (this.config.pingOnConnect) try {
				await client.ping(requestOptions);
				require_logger.logger_default.debug(`MCP server ${serverKey} ping successful`);
			} catch (pingError) {
				const pingErrorMessage = pingError instanceof Error ? pingError.message : String(pingError);
				throw new Error(`MCP server ${serverKey} ping failed: ${pingErrorMessage}`);
			}
			const serverTools = (await client.listTools(void 0, requestOptions))?.tools?.map((tool) => ({
				name: tool.name,
				description: tool.description || "",
				inputSchema: tool.inputSchema
			})) || [];
			let filteredTools = serverTools;
			if (this.config.tools) filteredTools = serverTools.filter((tool) => this.config.tools?.includes(tool.name));
			if (this.config.exclude_tools) filteredTools = filteredTools.filter((tool) => !this.config.exclude_tools?.includes(tool.name));
			this.transports.set(serverKey, transport);
			this.clients.set(serverKey, client);
			this.tools.set(serverKey, filteredTools);
			if (this.isVerboseEnabled) console.log(`Connected to MCP server ${serverKey} with tools:`, filteredTools.map((tool) => tool.name));
		} catch (error) {
			const errorMessage = error instanceof Error ? error.message : String(error);
			if (this.isDebugEnabled) require_logger.logger_default.error(`Failed to connect to MCP server ${serverKey}: ${errorMessage}`);
			throw new Error(`Failed to connect to MCP server ${serverKey}: ${errorMessage}`);
		}
	}
	getAllTools() {
		return Array.from(this.tools.values()).flat();
	}
	/**
	* Proactively refresh OAuth token for a server if it's close to expiration.
	* Uses a locking mechanism to prevent concurrent refresh attempts.
	*/
	async refreshOAuthTokenIfNeeded(serverKey) {
		const oauthConfig = this.oauthConfigs.get(serverKey);
		if (!oauthConfig) return;
		const now = Date.now();
		const expiresAt = this.tokenExpiresAt.get(serverKey);
		if (expiresAt && now + require_transform.TOKEN_REFRESH_BUFFER_MS < expiresAt) {
			require_logger.logger_default.debug(`[MCP] Token for ${serverKey} still valid, no refresh needed`);
			return;
		}
		const existingRefresh = this.tokenRefreshPromise.get(serverKey);
		if (existingRefresh) {
			require_logger.logger_default.debug(`[MCP] Token refresh already in progress for ${serverKey}, waiting...`);
			try {
				await existingRefresh;
				const newExpiresAt = this.tokenExpiresAt.get(serverKey);
				if (newExpiresAt && Date.now() + require_transform.TOKEN_REFRESH_BUFFER_MS < newExpiresAt) return;
				require_logger.logger_default.debug(`[MCP] Token expired while waiting for ${serverKey}, refreshing again...`);
			} catch {
				require_logger.logger_default.debug(`[MCP] Previous token refresh failed for ${serverKey}, retrying...`);
			}
		}
		require_logger.logger_default.debug(`[MCP] Proactively refreshing OAuth token for server ${serverKey}`);
		const refreshPromise = this.performTokenRefresh(serverKey, oauthConfig);
		this.tokenRefreshPromise.set(serverKey, refreshPromise);
		try {
			await refreshPromise;
		} finally {
			if (this.tokenRefreshPromise.get(serverKey) === refreshPromise) this.tokenRefreshPromise.delete(serverKey);
		}
	}
	/**
	* Perform the actual token refresh and reconnection.
	*/
	async performTokenRefresh(serverKey, oauthConfig) {
		const existingTransport = this.transports.get(serverKey);
		const existingClient = this.clients.get(serverKey);
		if (existingTransport) await existingTransport.close().catch(() => {});
		if (existingClient) await existingClient.close().catch(() => {});
		this.clients.delete(serverKey);
		this.transports.delete(serverKey);
		await this.connectToServer(oauthConfig.serverConfig);
		require_logger.logger_default.debug(`[MCP] Successfully refreshed OAuth token for server ${serverKey}`);
	}
	async callTool(name, args) {
		const requestOptions = getEffectiveRequestOptions(this.config);
		for (const [serverKey, client] of this.clients.entries()) if ((this.tools.get(serverKey) || []).some((tool) => tool.name === name)) {
			await this.refreshOAuthTokenIfNeeded(serverKey);
			let currentClient = this.clients.get(serverKey) || client;
			let retried = false;
			while (true) try {
				const result = await currentClient.callTool({
					name,
					arguments: args
				}, void 0, requestOptions);
				let content = "";
				if (result?.content) if (typeof result.content === "string") try {
					const parsed = JSON.parse(result.content);
					content = typeof parsed === "string" ? parsed : JSON.stringify(parsed);
				} catch {
					content = result.content;
				}
				else if (Buffer.isBuffer(result.content)) content = result.content.toString();
				else content = JSON.stringify(result.content);
				return { content };
			} catch (error) {
				const errorMessage = error instanceof Error ? error.message : String(error);
				const isAuthError = errorMessage.includes("401") || errorMessage.includes("Unauthorized") || errorMessage.includes("authorization_endpoint") || errorMessage.includes("token");
				const oauthConfig = this.oauthConfigs.get(serverKey);
				if (!retried && isAuthError && oauthConfig) {
					require_logger.logger_default.debug(`[MCP] Auth error for ${serverKey}, attempting reactive token refresh`);
					retried = true;
					try {
						await this.performTokenRefresh(serverKey, oauthConfig);
						const newClient = this.clients.get(serverKey);
						if (newClient) {
							currentClient = newClient;
							continue;
						}
					} catch (refreshError) {
						const refreshErrorMsg = refreshError instanceof Error ? refreshError.message : String(refreshError);
						require_logger.logger_default.error(`[MCP] Token refresh failed for ${serverKey}: ${refreshErrorMsg}`);
					}
				}
				if (this.isDebugEnabled) require_logger.logger_default.error(`Error calling tool ${name}: ${errorMessage}`);
				return {
					content: "",
					error: errorMessage
				};
			}
		}
		throw new Error(`Tool ${name} not found in any connected MCP server`);
	}
	async cleanup() {
		for (const [serverKey, client] of this.clients.entries()) try {
			const transport = this.transports.get(serverKey);
			if (transport) await transport.close();
			await client.close();
		} catch (error) {
			if (this.isDebugEnabled) require_logger.logger_default.error(`Error during cleanup: ${error instanceof Error ? error.message : String(error)}`);
		}
		this.clients.clear();
		this.transports.clear();
		this.tools.clear();
		this.oauthConfigs.clear();
		this.tokenExpiresAt.clear();
		this.tokenRefreshPromise.clear();
	}
};

//#endregion
//#region src/providers/openai/chat.ts
var OpenAiChatCompletionProvider = class OpenAiChatCompletionProvider extends require_openai.OpenAiGenericProvider {
	static OPENAI_CHAT_MODELS = require_util$1.OPENAI_CHAT_MODELS;
	static OPENAI_CHAT_MODEL_NAMES = require_util$1.OPENAI_CHAT_MODELS.map((model) => model.id);
	config;
	mcpClient = null;
	initializationPromise = null;
	loadedFunctionCallbacks = {};
	constructor(modelName, options = {}) {
		if (!OpenAiChatCompletionProvider.OPENAI_CHAT_MODEL_NAMES.includes(modelName)) require_logger.logger_default.debug(`Using unknown chat model: ${modelName}`);
		super(modelName, options);
		this.config = options.config || {};
		if (this.config.mcp?.enabled) this.initializationPromise = this.initializeMCP();
	}
	async initializeMCP() {
		this.mcpClient = new MCPClient(this.config.mcp);
		await this.mcpClient.initialize();
	}
	async cleanup() {
		if (this.mcpClient) {
			await this.initializationPromise;
			await this.mcpClient.cleanup();
			this.mcpClient = null;
		}
	}
	/**
	* Loads a function from an external file
	* @param fileRef The file reference in the format 'file://path/to/file:functionName'
	* @returns The loaded function
	*/
	async loadExternalFunction(fileRef) {
		let filePath = fileRef.slice(7);
		let functionName;
		if (filePath.includes(":")) {
			const splits = filePath.split(":");
			if (splits[0] && require_fileExtensions.isJavascriptFile(splits[0])) [filePath, functionName] = splits;
		}
		try {
			const resolvedPath = path.default.resolve(require_logger.cliState_default.basePath || "", filePath);
			require_logger.logger_default.debug(`Loading function from ${resolvedPath}${functionName ? `:${functionName}` : ""}`);
			const requiredModule = await require_esm.importModule(resolvedPath, functionName);
			if (typeof requiredModule === "function") return requiredModule;
			else if (requiredModule && typeof requiredModule === "object" && functionName && functionName in requiredModule) {
				const fn = requiredModule[functionName];
				if (typeof fn === "function") return fn;
			}
			throw new Error(`Function callback malformed: ${filePath} must export ${functionName ? `a named function '${functionName}'` : "a function or have a default export as a function"}`);
		} catch (error) {
			throw new Error(`Error loading function from ${filePath}: ${error.message || String(error)}`);
		}
	}
	/**
	* Executes a function callback with proper error handling
	*/
	async executeFunctionCallback(functionName, args, config) {
		try {
			let callback = this.loadedFunctionCallbacks[functionName];
			if (!callback) {
				const callbackRef = config.functionToolCallbacks?.[functionName];
				if (callbackRef && typeof callbackRef === "string") {
					const callbackStr = callbackRef;
					if (callbackStr.startsWith("file://")) callback = await this.loadExternalFunction(callbackStr);
					else callback = new Function("return " + callbackStr)();
					this.loadedFunctionCallbacks[functionName] = callback;
				} else if (typeof callbackRef === "function") {
					callback = callbackRef;
					this.loadedFunctionCallbacks[functionName] = callback;
				}
			}
			if (!callback) throw new Error(`No callback found for function '${functionName}'`);
			require_logger.logger_default.debug(`Executing function '${functionName}' with args: ${args}`);
			const result = await callback(args);
			if (result === void 0 || result === null) return "";
			else if (typeof result === "object") try {
				return JSON.stringify(result);
			} catch (error) {
				require_logger.logger_default.warn(`Error stringifying result from function '${functionName}': ${error}`);
				return String(result);
			}
			else return String(result);
		} catch (error) {
			require_logger.logger_default.error(`Error executing function '${functionName}': ${error.message || String(error)}`);
			throw error;
		}
	}
	isReasoningModel() {
		return this.modelName.startsWith("o1") || this.modelName.startsWith("o3") || this.modelName.startsWith("o4") || this.modelName.startsWith("gpt-5");
	}
	supportsTemperature() {
		return !this.isReasoningModel();
	}
	async getOpenAiBody(prompt, context$1, callApiOptions) {
		const config = {
			...this.config,
			...context$1?.prompt?.config
		};
		const messages = require_fetch.parseChatPrompt(prompt, [{
			role: "user",
			content: prompt
		}]);
		const isReasoningModel = this.isReasoningModel();
		const isGPT5Model = this.modelName.startsWith("gpt-5");
		const maxCompletionTokens = isReasoningModel ? config.max_completion_tokens ?? require_logger.getEnvInt("OPENAI_MAX_COMPLETION_TOKENS") : void 0;
		const maxTokens = isReasoningModel || isGPT5Model ? void 0 : config.max_tokens ?? require_logger.getEnvInt("OPENAI_MAX_TOKENS", 1024);
		const temperature = this.supportsTemperature() ? config.temperature ?? require_logger.getEnvFloat("OPENAI_TEMPERATURE", 0) : void 0;
		const reasoningEffort = isReasoningModel ? require_util.renderVarsInObject(config.reasoning_effort, context$1?.vars) : void 0;
		const mcpTools = this.mcpClient ? require_transform.transformMCPToolsToOpenAi(this.mcpClient.getAllTools()) : [];
		const fileTools = config.tools ? await require_util.maybeLoadToolsFromExternalFile(config.tools, context$1?.vars) || [] : [];
		const allTools = [...mcpTools, ...fileTools];
		const body = {
			model: this.modelName,
			messages,
			seed: config.seed,
			...maxTokens ? { max_tokens: maxTokens } : {},
			...maxCompletionTokens ? { max_completion_tokens: maxCompletionTokens } : {},
			...reasoningEffort ? { reasoning_effort: reasoningEffort } : {},
			...temperature ? { temperature } : {},
			...config.top_p !== void 0 || require_logger.getEnvString("OPENAI_TOP_P") ? { top_p: config.top_p ?? require_logger.getEnvFloat("OPENAI_TOP_P", 1) } : {},
			...config.presence_penalty !== void 0 || require_logger.getEnvString("OPENAI_PRESENCE_PENALTY") ? { presence_penalty: config.presence_penalty ?? require_logger.getEnvFloat("OPENAI_PRESENCE_PENALTY", 0) } : {},
			...config.frequency_penalty !== void 0 || require_logger.getEnvString("OPENAI_FREQUENCY_PENALTY") ? { frequency_penalty: config.frequency_penalty ?? require_logger.getEnvFloat("OPENAI_FREQUENCY_PENALTY", 0) } : {},
			...config.functions ? { functions: require_util.maybeLoadFromExternalFile(require_util.renderVarsInObject(config.functions, context$1?.vars)) } : {},
			...config.function_call ? { function_call: config.function_call } : {},
			...allTools.length > 0 ? { tools: allTools } : {},
			...config.tool_choice ? { tool_choice: config.tool_choice } : {},
			...config.tool_resources ? { tool_resources: config.tool_resources } : {},
			...config.response_format ? { response_format: require_util.maybeLoadFromExternalFile(require_util.renderVarsInObject(config.response_format, context$1?.vars)) } : {},
			...callApiOptions?.includeLogProbs ? { logprobs: callApiOptions.includeLogProbs } : {},
			...config.stop ? { stop: config.stop } : {},
			...config.passthrough || {},
			...this.modelName.includes("audio") ? {
				modalities: config.modalities || ["text", "audio"],
				audio: config.audio || {
					voice: "alloy",
					format: "wav"
				}
			} : {},
			...this.modelName.startsWith("gpt-5") && config.verbosity ? { verbosity: config.verbosity } : {}
		};
		if (config.reasoning_effort && (this.modelName.startsWith("o1") || this.modelName.startsWith("o3") || this.modelName.startsWith("o4") || this.modelName.startsWith("gpt-5") || this.modelName.startsWith("gpt-oss"))) body.reasoning_effort = config.reasoning_effort;
		if (config.reasoning && (this.modelName.startsWith("o1") || this.modelName.startsWith("o3") || this.modelName.startsWith("o4"))) body.reasoning = config.reasoning;
		if (config.service_tier) body.service_tier = config.service_tier;
		if (config.user) body.user = config.user;
		if (config.metadata) body.metadata = config.metadata;
		if (config.store !== void 0) body.store = config.store;
		return {
			body,
			config
		};
	}
	async callApi(prompt, context$1, callApiOptions) {
		if (this.initializationPromise) await this.initializationPromise;
		if (this.requiresApiKey() && !this.getApiKey()) throw new Error(`API key is not set. Set the ${this.config.apiKeyEnvar || "OPENAI_API_KEY"} environment variable or add \`apiKey\` to the provider config.`);
		const spanContext = {
			system: "openai",
			operationName: "chat",
			model: this.modelName,
			providerId: this.id(),
			maxTokens: this.config.max_tokens,
			temperature: this.config.temperature,
			topP: this.config.top_p,
			stopSequences: this.config.stop,
			evalId: context$1?.evaluationId || context$1?.test?.metadata?.evaluationId,
			testIndex: context$1?.test?.vars?.__testIdx,
			promptLabel: context$1?.prompt?.label,
			traceparent: context$1?.traceparent,
			requestBody: prompt
		};
		const resultExtractor = (response) => {
			const result = {};
			if (response.tokenUsage) result.tokenUsage = {
				prompt: response.tokenUsage.prompt,
				completion: response.tokenUsage.completion,
				total: response.tokenUsage.total,
				cached: response.tokenUsage.cached,
				completionDetails: {
					reasoning: response.tokenUsage.completionDetails?.reasoning,
					acceptedPrediction: response.tokenUsage.completionDetails?.acceptedPrediction,
					rejectedPrediction: response.tokenUsage.completionDetails?.rejectedPrediction
				}
			};
			if (response.finishReason) result.finishReasons = [response.finishReason];
			if (response.cached !== void 0) result.cacheHit = response.cached;
			if (response.output !== void 0) result.responseBody = typeof response.output === "string" ? response.output : JSON.stringify(response.output);
			return result;
		};
		return withGenAISpan(spanContext, () => this.callApiInternal(prompt, context$1, callApiOptions), resultExtractor);
	}
	/**
	* Internal implementation of callApi without tracing wrapper.
	* This is called by callApi after setting up the tracing span.
	*/
	async callApiInternal(prompt, context$1, callApiOptions) {
		const { body, config } = await this.getOpenAiBody(prompt, context$1, callApiOptions);
		let data;
		let status;
		let statusText;
		let cached = false;
		let latencyMs;
		let deleteFromCache;
		try {
			({data, cached, status, statusText, latencyMs, deleteFromCache} = await require_cache.fetchWithCache(`${this.getApiUrl()}/chat/completions`, {
				method: "POST",
				headers: {
					"Content-Type": "application/json",
					...this.getApiKey() ? { Authorization: `Bearer ${this.getApiKey()}` } : {},
					...this.getOrganization() ? { "OpenAI-Organization": this.getOrganization() } : {},
					...config.headers
				},
				body: JSON.stringify(body)
			}, require_fetch.REQUEST_TIMEOUT_MS, "json", context$1?.bustCache ?? context$1?.debug, this.config.maxRetries));
			if (status < 200 || status >= 300) {
				const errorMessage = `API error: ${status} ${statusText}\n${typeof data === "string" ? data : JSON.stringify(data)}`;
				if (typeof data === "object" && data?.error?.code === "invalid_prompt") return {
					output: errorMessage,
					tokenUsage: data?.usage ? require_util$1.getTokenUsage(data, cached) : void 0,
					latencyMs,
					isRefusal: true,
					guardrails: {
						flagged: true,
						flaggedInput: true
					}
				};
				return { error: errorMessage };
			}
		} catch (err) {
			require_logger.logger_default.error(`API call error: ${String(err)}`);
			await deleteFromCache?.();
			return { error: `API call error: ${String(err)}` };
		}
		try {
			const message = data.choices[0].message;
			const finishReason = normalizeFinishReason(data.choices[0].finish_reason);
			const contentFiltered = finishReason === FINISH_REASON_MAP.content_filter;
			if (message.refusal) return {
				output: message.refusal,
				tokenUsage: require_util$1.getTokenUsage(data, cached),
				cached,
				latencyMs,
				isRefusal: true,
				...finishReason && { finishReason },
				guardrails: { flagged: true }
			};
			if (contentFiltered) return {
				output: message.content || "Content filtered by provider",
				tokenUsage: require_util$1.getTokenUsage(data, cached),
				cached,
				latencyMs,
				isRefusal: true,
				finishReason: FINISH_REASON_MAP.content_filter,
				guardrails: { flagged: true }
			};
			let reasoning = "";
			let output = "";
			if (message.reasoning) {
				reasoning = message.reasoning;
				output = message.content;
			} else if (message.content && (message.function_call || message.tool_calls)) if (Array.isArray(message.tool_calls) && message.tool_calls.length === 0) output = message.content;
			else output = message;
			else if (message.content === null || message.content === void 0 || message.content === "" && message.tool_calls) output = message.function_call || message.tool_calls;
			else output = message.content;
			const logProbs = data.choices[0].logprobs?.content?.map((logProbObj) => logProbObj.logprob);
			if (config.response_format?.type === "json_schema" && typeof output === "string") try {
				output = JSON.parse(output);
			} catch (error) {
				require_logger.logger_default.error(`Failed to parse JSON output: ${error}`);
			}
			if (reasoning && (this.config.showThinking ?? true)) output = `Thinking: ${reasoning}\n\n${output}`;
			const functionCalls = message.function_call ? [message.function_call] : message.tool_calls;
			if (functionCalls && (config.functionToolCallbacks || this.mcpClient)) {
				const results = [];
				let hasSuccessfulCallback = false;
				for (const functionCall of functionCalls) {
					const functionName = functionCall.name || functionCall.function?.name;
					if (this.mcpClient) {
						if (this.mcpClient.getAllTools().find((tool) => tool.name === functionName)) try {
							const args = functionCall.arguments || functionCall.function?.arguments || "{}";
							const parsedArgs = typeof args === "string" ? JSON.parse(args) : args;
							const mcpResult = await this.mcpClient.callTool(functionName, parsedArgs);
							if (mcpResult?.error) results.push(`MCP Tool Error (${functionName}): ${mcpResult.error}`);
							else {
								const normalizeContent = (content$1) => {
									if (content$1 == null) return "";
									if (typeof content$1 === "string") return content$1;
									if (Array.isArray(content$1)) return content$1.map((part) => {
										if (typeof part === "string") return part;
										if (part && typeof part === "object") {
											if ("text" in part && part.text != null) return String(part.text);
											if ("json" in part) return JSON.stringify(part.json);
											if ("data" in part) return JSON.stringify(part.data);
											return JSON.stringify(part);
										}
										return String(part);
									}).join("\n");
									return JSON.stringify(content$1);
								};
								const content = normalizeContent(mcpResult?.content);
								results.push(`MCP Tool Result (${functionName}): ${content}`);
							}
							hasSuccessfulCallback = true;
							continue;
						} catch (error) {
							require_logger.logger_default.debug(`MCP tool execution failed for ${functionName}: ${error}`);
							results.push(`MCP Tool Error (${functionName}): ${error}`);
							hasSuccessfulCallback = true;
							continue;
						}
					}
					if (config.functionToolCallbacks && config.functionToolCallbacks[functionName]) try {
						const functionResult = await this.executeFunctionCallback(functionName, functionCall.arguments || functionCall.function?.arguments, config);
						results.push(functionResult);
						hasSuccessfulCallback = true;
					} catch (error) {
						require_logger.logger_default.debug(`Function callback failed for ${functionName} with error ${error}, falling back to original output`);
						hasSuccessfulCallback = false;
						break;
					}
				}
				if (hasSuccessfulCallback && results.length > 0) return {
					output: results.join("\n"),
					tokenUsage: require_util$1.getTokenUsage(data, cached),
					cached,
					latencyMs,
					logProbs,
					...finishReason && { finishReason },
					cost: require_util$1.calculateOpenAICost(this.modelName, config, data.usage?.prompt_tokens, data.usage?.completion_tokens, data.usage?.audio_prompt_tokens, data.usage?.audio_completion_tokens),
					guardrails: { flagged: contentFiltered }
				};
			}
			if (message.reasoning_content && typeof message.reasoning_content === "string" && typeof output === "string" && (this.config.showThinking ?? true)) output = `Thinking: ${message.reasoning_content}\n\n${output}`;
			if (message.audio) return {
				output: message.audio.transcript || "",
				audio: {
					id: message.audio.id,
					expiresAt: message.audio.expires_at,
					data: message.audio.data,
					transcript: message.audio.transcript,
					format: message.audio.format || "wav"
				},
				tokenUsage: require_util$1.getTokenUsage(data, cached),
				cached,
				latencyMs,
				logProbs,
				...finishReason && { finishReason },
				cost: require_util$1.calculateOpenAICost(this.modelName, config, data.usage?.prompt_tokens, data.usage?.completion_tokens, data.usage?.audio_prompt_tokens, data.usage?.audio_completion_tokens),
				guardrails: { flagged: contentFiltered }
			};
			return {
				output,
				tokenUsage: require_util$1.getTokenUsage(data, cached),
				cached,
				latencyMs,
				logProbs,
				...finishReason && { finishReason },
				cost: require_util$1.calculateOpenAICost(this.modelName, config, data.usage?.prompt_tokens, data.usage?.completion_tokens, data.usage?.audio_prompt_tokens, data.usage?.audio_completion_tokens),
				guardrails: { flagged: contentFiltered }
			};
		} catch (err) {
			await deleteFromCache?.();
			return { error: `API error: ${String(err)}: ${JSON.stringify(data)}` };
		}
	}
};

//#endregion
Object.defineProperty(exports, 'FINISH_REASON_MAP', {
  enumerable: true,
  get: function () {
    return FINISH_REASON_MAP;
  }
});
Object.defineProperty(exports, 'MCPClient', {
  enumerable: true,
  get: function () {
    return MCPClient;
  }
});
Object.defineProperty(exports, 'OpenAiChatCompletionProvider', {
  enumerable: true,
  get: function () {
    return OpenAiChatCompletionProvider;
  }
});
Object.defineProperty(exports, 'normalizeFinishReason', {
  enumerable: true,
  get: function () {
    return normalizeFinishReason;
  }
});
Object.defineProperty(exports, 'withGenAISpan', {
  enumerable: true,
  get: function () {
    return withGenAISpan;
  }
});
//# sourceMappingURL=chat-BbQFtNh7.cjs.map