#!/usr/bin/env node
import { T as safeJsonStringify, c as logger } from "./logger-4ntIRWmf.js";
import { U as VERSION, r as fetchWithProxy } from "./fetch-gOBR1GF3.js";
import { t as invariant } from "./invariant-BtWWVVhl.js";
import { l as isLoggedIntoCloud, s as getUserEmail } from "./accounts-C7cRj8fd.js";
import { n as sleep } from "./time-CrpgfF3R.js";
import { Z as getNunjucksEngine } from "./util-DAS7mIn_.js";
import { o as createEmptyTokenUsage, r as accumulateResponseTokenUsage } from "./tokenUsageUtils-n_A6wlLq.js";
import { c as neverGenerateRemote, i as getRemoteGenerationUrl, r as getRemoteGenerationHeaders } from "./remoteGeneration-B4mKZKcR.js";
import { a as extractPromptFromTags, o as getSessionId, r as extractInputVarsFromPrompt } from "./util-P47brFMS.js";
import { a as assertRemoteMaterializationHandled, c as isRemoteMaterializationUpgradeError, s as buildRemoteMaterializedInputVariables } from "./promptfoo-Dvbe6-Pe.js";
import { o as getGraderAssertionValue, p as tryUnblocking, s as getLastMessageContent, t as buildGraderResultAssertion } from "./shared-CNYo4scP.js";
import { i as throwIfTargetPromptExceedsMaxChars } from "./promptLength-Xit4MiMV.js";
import { n as checkExfilTracking } from "./indirectWebPwn-DPkpm78R.js";
import { n as renderPrompt } from "./evaluatorHelpers-Jx0MTlXG.js";
import { t as Strategies } from "./strategies-MVVX30dH.js";
import { a as fetchTraceContext, i as extractTraceIdFromTraceparent, n as formatTraceForMetadata, r as formatTraceSummary, t as resolveTracingOptions } from "./tracingOptions-BhiuS6_i.js";
import { t as applyRuntimeTransforms } from "./runtimeTransform-DSR6tS6g.js";
import { i as getGoalRubric } from "./prompts-dInqkC-y.js";
import chalk from "chalk";
import dedent from "dedent";
//#region src/redteam/providers/goat.ts
const ATTACHED_IMAGE_OUTPUT_PLACEHOLDER = "[Image output attached. Inspect the attached image directly for visual grading.]";
var GoatProvider = class {
	config;
	nunjucks;
	perTurnLayers;
	successfulAttacks = [];
	id() {
		return "promptfoo:redteam:goat";
	}
	constructor(options = {}) {
		if (neverGenerateRemote()) throw new Error(`GOAT strategy requires remote grading to be enabled`);
		invariant(typeof options.injectVar === "string", "Expected injectVar to be set");
		let maxTurns = options.maxTurns ?? 5;
		if (!isLoggedIntoCloud()) maxTurns = Math.min(maxTurns, 10);
		this.config = {
			maxTurns,
			...options.maxCharsPerMessage ? { maxCharsPerMessage: options.maxCharsPerMessage } : {},
			injectVar: options.injectVar,
			stateful: options.stateful ?? false,
			excludeTargetOutputFromAgenticAttackGeneration: options.excludeTargetOutputFromAgenticAttackGeneration ?? false,
			continueAfterSuccess: options.continueAfterSuccess ?? false,
			tracing: options.tracing,
			_perTurnLayers: options._perTurnLayers,
			inputs: options.inputs
		};
		this.perTurnLayers = options._perTurnLayers ?? [];
		this.nunjucks = getNunjucksEngine();
		logger.debug("[GOAT] Constructor options", {
			injectVar: options.injectVar,
			maxTurns: options.maxTurns,
			maxCharsPerMessage: options.maxCharsPerMessage,
			stateful: options.stateful,
			continueAfterSuccess: options.continueAfterSuccess,
			perTurnLayers: this.perTurnLayers.map((l) => typeof l === "string" ? l : l.id),
			inputs: options.inputs
		});
	}
	async callApi(_prompt, context, options) {
		this.successfulAttacks = [];
		const tracingOptions = resolveTracingOptions({
			strategyId: "goat",
			test: context?.test,
			config: this.config
		});
		const shouldFetchTrace = tracingOptions.enabled && (tracingOptions.includeInAttack || tracingOptions.includeInGrading);
		const traceSnapshots = [];
		let response = void 0;
		logger.debug("[GOAT] callApi context", { context });
		invariant(context?.originalProvider, "Expected originalProvider to be set");
		invariant(context?.vars, "Expected vars to be set");
		const targetProvider = context?.originalProvider;
		invariant(targetProvider, "Expected originalProvider to be set");
		const maxCharsPerMessage = this.config.maxCharsPerMessage ?? (context?.test?.metadata?.strategyConfig)?.maxCharsPerMessage ?? (context?.test?.metadata?.pluginConfig)?.maxCharsPerMessage;
		const messages = [];
		const totalTokenUsage = createEmptyTokenUsage();
		const redteamHistory = [];
		let lastTargetResponse = void 0;
		let lastTransformDisplayVars;
		let lastFinalAttackPrompt;
		let assertToUse;
		let graderPassed;
		let storedGraderResult;
		const { getGraderById } = await import("./graders-DNj8u0Ai.js");
		let test;
		if (context?.test) {
			test = context?.test;
			assertToUse = test?.assert?.find((a) => a.type && a.type.includes(test?.metadata?.pluginId));
			if (!assertToUse) assertToUse = test?.assert?.find((a) => a.type);
		}
		let previousAttackerMessage = "";
		let previousTargetOutput = "";
		let previousTraceSummary;
		const additionalRubric = getGoalRubric(context?.test?.metadata?.goal || context?.vars[this.config.injectVar]);
		let stopReason = "Max turns reached";
		for (let turn = 0; turn < this.config.maxTurns; turn++) try {
			if (turn > 0 && previousTargetOutput) {
				const unblockingResult = await tryUnblocking({
					messages,
					lastResponse: previousTargetOutput,
					goal: context?.test?.metadata?.goal || context?.vars[this.config.injectVar],
					purpose: context?.test?.metadata?.purpose
				});
				if (unblockingResult.success && unblockingResult.unblockingPrompt) {
					logger.debug(`[GOAT] Sending unblocking response: ${unblockingResult.unblockingPrompt}`);
					messages.push({
						role: "user",
						content: unblockingResult.unblockingPrompt
					});
					let unblockingTargetPrompt = this.config.stateful ? unblockingResult.unblockingPrompt : JSON.stringify(messages);
					if (this.perTurnLayers.length > 0) {
						const transformResult = await applyRuntimeTransforms(unblockingResult.unblockingPrompt, this.config.injectVar, this.perTurnLayers, Strategies, {
							evaluationId: context?.evaluationId,
							testCaseId: context?.test?.metadata?.testCaseId,
							purpose: context?.test?.metadata?.purpose,
							goal: context?.test?.metadata?.goal
						});
						if (transformResult.error) {
							logger.warn("[GOAT] Transform failed for unblocking prompt", { error: transformResult.error });
							continue;
						}
						unblockingTargetPrompt = transformResult.prompt;
					}
					throwIfTargetPromptExceedsMaxChars(unblockingTargetPrompt, maxCharsPerMessage);
					const unblockingResponse = await targetProvider.callApi(unblockingTargetPrompt, context, options);
					if (!unblockingResponse.cached && targetProvider.delay && targetProvider.delay > 0) {
						logger.debug(`Sleeping for ${targetProvider.delay}ms`);
						await sleep(targetProvider.delay);
					}
					accumulateResponseTokenUsage(totalTokenUsage, unblockingResponse);
					const unblockingOutput = typeof unblockingResponse.output === "string" ? unblockingResponse.output : safeJsonStringify(unblockingResponse.output);
					if (unblockingOutput) messages.push({
						role: "assistant",
						content: unblockingOutput
					});
					if (unblockingResponse.error) logger.error(`[GOAT] Target returned an error: ${unblockingResponse.error}`);
				}
			}
			let body;
			let failureReason;
			if (this.config.excludeTargetOutputFromAgenticAttackGeneration && turn > 0) {
				body = JSON.stringify({
					goal: context?.test?.metadata?.goal || context?.vars[this.config.injectVar],
					targetOutput: previousTargetOutput,
					attackAttempt: previousAttackerMessage,
					task: "extract-goat-failure",
					modifiers: context?.test?.metadata?.modifiers,
					traceSummary: previousTraceSummary
				});
				logger.debug(`[GOAT] Sending request to ${getRemoteGenerationUrl()}: ${body}`);
				response = await fetchWithProxy(getRemoteGenerationUrl(), {
					body,
					headers: getRemoteGenerationHeaders(),
					method: "POST"
				}, options?.abortSignal);
				const data = await response.json();
				if (!data.message) {
					logger.info("[GOAT] Invalid message from GOAT, skipping turn", { data });
					continue;
				}
				failureReason = data.message;
				logger.debug(`[GOAT] Previous attack attempt failure reason: ${failureReason}`);
			}
			body = JSON.stringify({
				goal: context?.test?.metadata?.goal || context?.vars[this.config.injectVar],
				i: turn,
				messages: this.config.excludeTargetOutputFromAgenticAttackGeneration ? messages.filter((m) => m.role !== "assistant") : messages,
				prompt: context?.prompt?.raw,
				task: "goat",
				version: VERSION,
				email: getUserEmail(),
				excludeTargetOutputFromAgenticAttackGeneration: this.config.excludeTargetOutputFromAgenticAttackGeneration,
				failureReason,
				purpose: context?.test?.metadata?.purpose,
				modifiers: context?.test?.metadata?.modifiers,
				traceSummary: previousTraceSummary,
				inputs: this.config.inputs
			});
			logger.debug(`[GOAT] Sending request to ${getRemoteGenerationUrl()}: ${body}`);
			response = await fetchWithProxy(getRemoteGenerationUrl(), {
				body,
				headers: getRemoteGenerationHeaders(),
				method: "POST"
			}, options?.abortSignal);
			const data = await response.json();
			if (typeof data?.message !== "object" || !data.message?.content || !data.message?.role) {
				logger.info("[GOAT] Invalid message from GOAT, skipping turn", { data });
				continue;
			}
			const attackerMessage = data.message;
			previousAttackerMessage = attackerMessage?.content;
			let processedMessage = attackerMessage.content;
			const extractedPrompt = extractPromptFromTags(attackerMessage.content);
			if (extractedPrompt) processedMessage = extractedPrompt;
			if (this.config.inputs) assertRemoteMaterializationHandled(data, "GOAT multi-input generation");
			const currentInputVars = extractInputVarsFromPrompt(processedMessage, this.config.inputs);
			let materializedInputVars;
			if (currentInputVars && this.config.inputs) materializedInputVars = buildRemoteMaterializedInputVariables(data, currentInputVars, this.config.inputs);
			const currentRenderInputVars = materializedInputVars?.vars ?? currentInputVars;
			if (currentInputVars && this.config.inputs) try {
				const parsed = JSON.parse(processedMessage);
				if (typeof parsed.prompt === "string") processedMessage = parsed.prompt;
			} catch {}
			const attackerVars = {
				[this.config.injectVar]: processedMessage,
				...currentRenderInputVars || {}
			};
			const targetVars = {
				...context.vars,
				...attackerVars
			};
			const renderedAttackerPrompt = await renderPrompt(context.prompt, targetVars, context.filters, targetProvider, Object.keys(attackerVars));
			messages.push({
				role: attackerMessage.role,
				content: renderedAttackerPrompt
			});
			logger.debug(dedent`
          ${chalk.bold.green(`GOAT turn ${turn} history:`)}
          ${chalk.cyan(JSON.stringify(messages, null, 2))}
        `);
			const latestMessageContent = messages[messages.length - 1].content;
			let targetPrompt = this.config.stateful ? latestMessageContent : JSON.stringify(messages);
			logger.debug(`GOAT turn ${turn} target prompt: ${renderedAttackerPrompt}`);
			let lastTransformResult;
			if (this.perTurnLayers.length > 0) {
				logger.debug("[GOAT] Applying per-turn transforms", {
					turn,
					layers: this.perTurnLayers.map((l) => typeof l === "string" ? l : l.id)
				});
				lastTransformResult = await applyRuntimeTransforms(latestMessageContent, this.config.injectVar, this.perTurnLayers, Strategies, {
					evaluationId: context?.evaluationId,
					testCaseId: context?.test?.metadata?.testCaseId,
					purpose: context?.test?.metadata?.purpose,
					goal: context?.test?.metadata?.goal
				});
				if (lastTransformResult.error) {
					logger.warn("[GOAT] Transform failed, skipping turn", {
						turn,
						error: lastTransformResult.error
					});
					continue;
				}
				if (lastTransformResult.audio || lastTransformResult.image) {
					const historyWithoutCurrentTurn = messages.slice(0, -1);
					const hybridPayload = {
						_promptfoo_audio_hybrid: true,
						history: historyWithoutCurrentTurn,
						currentTurn: {
							role: "user",
							transcript: latestMessageContent,
							...lastTransformResult.audio && { audio: lastTransformResult.audio },
							...lastTransformResult.image && { image: lastTransformResult.image }
						}
					};
					targetPrompt = JSON.stringify(hybridPayload);
					logger.debug("[GOAT] Using hybrid format (history + audio/image current turn)", {
						turn,
						historyLength: historyWithoutCurrentTurn.length,
						hasAudio: !!lastTransformResult.audio,
						hasImage: !!lastTransformResult.image
					});
				} else targetPrompt = lastTransformResult.prompt;
				logger.debug("[GOAT] Per-turn transforms applied", {
					turn,
					hasAudio: !!lastTransformResult.audio,
					hasImage: !!lastTransformResult.image
				});
				if (lastTransformResult.displayVars) lastTransformDisplayVars = lastTransformResult.displayVars;
				lastFinalAttackPrompt = lastTransformResult.prompt;
			}
			const iterationStart = Date.now();
			throwIfTargetPromptExceedsMaxChars(targetPrompt, maxCharsPerMessage);
			const targetContext = context ? {
				...context,
				vars: {
					...targetVars,
					[this.config.injectVar]: targetPrompt
				}
			} : context;
			const targetResponse = await targetProvider.callApi(targetPrompt, targetContext, options);
			if (!targetResponse.cached && targetProvider.delay && targetProvider.delay > 0) {
				logger.debug(`Sleeping for ${targetProvider.delay}ms`);
				await sleep(targetProvider.delay);
			}
			accumulateResponseTokenUsage(totalTokenUsage, targetResponse);
			logger.debug(`GOAT turn ${turn} target response`, { response: targetResponse });
			let traceContext = null;
			let computedTraceSummary;
			if (shouldFetchTrace) {
				const traceparent = context?.traceparent ?? void 0;
				const traceId = traceparent ? extractTraceIdFromTraceparent(traceparent) : null;
				if (traceId) {
					traceContext = await fetchTraceContext(traceId, {
						earliestStartTime: iterationStart,
						includeInternalSpans: tracingOptions.includeInternalSpans,
						maxSpans: tracingOptions.maxSpans,
						maxDepth: tracingOptions.maxDepth,
						maxRetries: tracingOptions.maxRetries,
						retryDelayMs: tracingOptions.retryDelayMs,
						spanFilter: tracingOptions.spanFilter,
						sanitizeAttributes: tracingOptions.sanitizeAttributes
					});
					if (traceContext) {
						targetResponse.traceContext = traceContext;
						traceSnapshots.push(traceContext);
						if (tracingOptions.includeInAttack || tracingOptions.includeInGrading) {
							computedTraceSummary = formatTraceSummary(traceContext);
							targetResponse.traceSummary = computedTraceSummary;
						}
					}
				}
			}
			if (targetResponse.sessionId) {
				context = context ?? {
					vars: {},
					prompt: {
						raw: "",
						label: "target"
					}
				};
				context.vars.sessionId = targetResponse.sessionId;
			}
			if (targetResponse.conversationEnded) {
				logger.info("[GOAT] Target ended conversation", {
					turn,
					reason: targetResponse.conversationEndReason
				});
				const endedOutput = typeof targetResponse.output === "string" ? targetResponse.output : safeJsonStringify(targetResponse.output);
				if (endedOutput) {
					messages.push({
						role: "assistant",
						content: endedOutput
					});
					redteamHistory.push({
						prompt: attackerMessage.content,
						promptAudio: lastTransformResult?.audio,
						promptImage: lastTransformResult?.image,
						output: endedOutput,
						outputAudio: targetResponse.audio?.data && targetResponse.audio?.format ? {
							data: targetResponse.audio.data,
							format: targetResponse.audio.format
						} : void 0,
						inputVars: currentRenderInputVars
					});
				}
				lastTargetResponse = targetResponse;
				stopReason = "Target ended conversation";
				break;
			}
			if (targetResponse.error) throw new Error(`[GOAT] Target returned an error: ${targetResponse.error}`);
			const hasTargetImages = Boolean(targetResponse.images?.length);
			invariant(targetResponse.output || hasTargetImages, `[GOAT] Expected target response output or images to be set, but got: ${safeJsonStringify(targetResponse)}`);
			const stringifiedOutput = typeof targetResponse.output === "string" ? targetResponse.output : safeJsonStringify(targetResponse.output);
			const finalOutput = stringifiedOutput || (hasTargetImages ? ATTACHED_IMAGE_OUTPUT_PLACEHOLDER : "");
			const finalResponse = targetResponse;
			if (!stringifiedOutput && !hasTargetImages) {
				logger.debug("[GOAT] Target response output is not a string or JSON", { response: targetResponse });
				continue;
			}
			messages.push({
				role: "assistant",
				content: finalOutput
			});
			redteamHistory.push({
				prompt: attackerMessage.content,
				promptAudio: lastTransformResult?.audio,
				promptImage: lastTransformResult?.image,
				output: finalOutput,
				outputAudio: targetResponse.audio?.data && targetResponse.audio?.format ? {
					data: targetResponse.audio.data,
					format: targetResponse.audio.format
				} : void 0,
				inputVars: currentRenderInputVars
			});
			const attackTraceSummary = tracingOptions.includeInAttack ? computedTraceSummary : void 0;
			const gradingTraceSummary = tracingOptions.includeInGrading ? computedTraceSummary : void 0;
			previousTraceSummary = attackTraceSummary;
			previousTargetOutput = finalOutput;
			lastTargetResponse = finalResponse;
			const grader = assertToUse ? getGraderById(assertToUse.type) : void 0;
			if (test && assertToUse && grader && finalOutput) {
				let gradingContext = {
					providerResponse: finalResponse,
					...finalResponse.images?.length ? { imageOutputs: finalResponse.images } : {}
				};
				if (finalResponse.metadata?.wasExfiltrated === void 0) {
					const webPageUuid = test.metadata?.webPageUuid;
					if (webPageUuid) {
						const evalId = context?.evaluationId ?? test.metadata?.evaluationId;
						logger.debug("[GOAT] Fetching exfil tracking from server API", {
							webPageUuid,
							evalId
						});
						const exfilData = await checkExfilTracking(webPageUuid, evalId);
						if (exfilData) gradingContext = {
							...gradingContext ?? {},
							...tracingOptions.includeInGrading ? {
								traceContext: targetResponse.traceContext,
								traceSummary: gradingTraceSummary
							} : {},
							wasExfiltrated: exfilData.wasExfiltrated,
							exfilCount: exfilData.exfilCount,
							exfilRecords: exfilData.exfilRecords
						};
					}
				} else {
					logger.debug("[GOAT] Using exfil data from provider response metadata");
					gradingContext = {
						...gradingContext ?? {},
						...tracingOptions.includeInGrading ? {
							traceContext: targetResponse.traceContext,
							traceSummary: gradingTraceSummary
						} : {},
						wasExfiltrated: Boolean(finalResponse.metadata.wasExfiltrated),
						exfilCount: Number(finalResponse.metadata.exfilCount) || 0,
						exfilRecords: []
					};
				}
				if (tracingOptions.includeInGrading && !gradingContext?.traceContext) gradingContext = {
					...gradingContext ?? {},
					traceContext: targetResponse.traceContext,
					traceSummary: gradingTraceSummary
				};
				const { grade, rubric } = await grader.getResult(attackerMessage.content, finalOutput, test, targetProvider, getGraderAssertionValue(assertToUse), additionalRubric, void 0, gradingContext);
				graderPassed = grade.pass;
				storedGraderResult = {
					...grade,
					assertion: buildGraderResultAssertion(grade.assertion, assertToUse, rubric)
				};
			}
			if (graderPassed === false) {
				this.successfulAttacks.push({
					turn,
					prompt: attackerMessage.content,
					response: finalOutput,
					traceSummary: attackTraceSummary
				});
				if (this.config.continueAfterSuccess) {} else {
					stopReason = "Grader failed";
					break;
				}
			}
		} catch (error) {
			if (error instanceof Error && error.name === "AbortError") {
				logger.debug("[GOAT] Operation aborted");
				throw error;
			}
			if (isRemoteMaterializationUpgradeError(error)) throw error;
			logger.error(`[GOAT] An error occurred in GOAT turn ${turn}.  The test will continue to the next turn in the conversation.`, { error: error.message || error });
		}
		const finalPrompt = getLastMessageContent(messages, "user") || "";
		return {
			output: getLastMessageContent(messages, "assistant") || "",
			prompt: finalPrompt,
			metadata: {
				redteamFinalPrompt: lastFinalAttackPrompt || finalPrompt,
				messages,
				stopReason,
				redteamHistory,
				successfulAttacks: this.successfulAttacks,
				totalSuccessfulAttacks: this.successfulAttacks.length,
				storedGraderResult,
				traceSnapshots: traceSnapshots.length > 0 ? traceSnapshots.map((snapshot) => formatTraceForMetadata(snapshot)) : void 0,
				sessionId: getSessionId(lastTargetResponse, context),
				...lastTransformDisplayVars && { transformDisplayVars: lastTransformDisplayVars }
			},
			tokenUsage: totalTokenUsage,
			guardrails: lastTargetResponse?.guardrails
		};
	}
};
//#endregion
export { GoatProvider as default };

//# sourceMappingURL=goat-Dc5bO3O0.js.map