import { createLogger } from "../../utils/logger/index.js";
import { init_logger } from "../../utils/logger.js";
import { getAgentDefaultModelRef, init_schema } from "../../config/schema.js";
import { getAllModels, getDefaultModelSync, init_providers, resolveModel } from "../../providers/index.js";
import { resolveAgentTurnTimeoutMs, runAgentTurnWithTimeout } from "../orchestration/run-agent-turn-with-timeout.js";
import { isAssistantTurnAborted, isAssistantTurnFailed, maybeRetryTurnAfterTransientLlmFailure } from "../orchestration/llm-turn-retry.js";
import { describeFailoverError, isFailoverError } from "../fallback/error.js";
import { parseModelRef } from "./selection.js";
import { resolveFallbackCandidates } from "../fallback/candidates.js";
import "../fallback/index.js";
//#region src/agent/models/manager.ts
init_schema();
init_logger();
init_providers();
const log = createLogger("ModelManager");
/**
* Get the last assistant content from agent messages
*/
function getLastAssistantContent(agent) {
	const messages = agent.state.messages;
	for (let i = messages.length - 1; i >= 0; i--) {
		const msg = messages[i];
		if (msg.role === "assistant") return msg.content.filter((c) => c.type === "text").map((c) => c.text || "").join("");
	}
	return null;
}
var ModelManager = class {
	defaultModel;
	config;
	currentModelName;
	currentProvider;
	sessionModels = /* @__PURE__ */ new Map();
	/** Baseline model from `agents.list` / defaults merge when the session agent is created. */
	sessionProfileDefaults = /* @__PURE__ */ new Map();
	channelManager;
	constructor(config = {}) {
		this.config = config.config;
		this.defaultModel = config.defaultModel || getDefaultModelSync(config.config);
		this.currentModelName = this.defaultModel;
		this.currentProvider = this.defaultModel.split("/")[0] || "anthropic";
	}
	/**
	* Set channel manager reference for accessing session-specific settings
	*/
	setChannelManager(channelManager) {
		this.channelManager = channelManager;
	}
	/**
	* Apply updated config so default model and failover metadata match disk/runtime config.
	*/
	updateFromConfig(config) {
		this.config = config;
		const ref = getAgentDefaultModelRef(config);
		this.defaultModel = ref ? ref : getDefaultModelSync(config);
		this.sessionProfileDefaults.clear();
	}
	/**
	* Set the config-derived default model for a session (from effective agent profile).
	* Cleared by {@link updateFromConfig} or {@link clearSessionProfileDefault}.
	*/
	setSessionProfileDefault(sessionKey, modelRef) {
		this.sessionProfileDefaults.set(sessionKey, modelRef);
	}
	clearSessionProfileDefault(sessionKey) {
		this.sessionProfileDefaults.delete(sessionKey);
	}
	/**
	* Get current model name
	*/
	getCurrentModel() {
		return this.currentModelName;
	}
	/**
	* Get current provider
	*/
	getCurrentProvider() {
		return this.currentProvider;
	}
	/**
	* Switch model for a specific session
	*/
	async switchModelForSession(sessionKey, modelId) {
		try {
			resolveModel(modelId);
			this.sessionModels.set(sessionKey, modelId);
			log.info({
				sessionKey,
				modelId
			}, "Model switched for session");
			return true;
		} catch (err) {
			log.error({
				err,
				sessionKey,
				modelId
			}, "Failed to switch model");
			return false;
		}
	}
	/** Drop in-memory session override so the global default is used again. */
	clearSessionModelOverride(sessionKey) {
		this.sessionModels.delete(sessionKey);
	}
	/**
	* Resolved pi-ai model for session (for transcript policy, tools, etc.)
	*/
	getResolvedModelForSession(sessionKey) {
		return resolveModel(this.getModelForSession(sessionKey));
	}
	/**
	* Get model for session, checking session override first
	*/
	getModelForSession(sessionKey) {
		const sessionModel = this.sessionModels.get(sessionKey);
		if (sessionModel) return sessionModel;
		const profileDefault = this.sessionProfileDefaults.get(sessionKey);
		if (profileDefault) return profileDefault;
		return this.defaultModel;
	}
	/**
	* Apply model to agent if different from current
	*/
	async applyModelForSession(agent, sessionKey) {
		const targetModelId = this.getModelForSession(sessionKey);
		let found;
		try {
			found = resolveModel(targetModelId);
		} catch (err) {
			log.error({
				err,
				sessionKey,
				modelId: targetModelId
			}, "Failed to apply model");
			return;
		}
		const sm = agent.state.model;
		if (sm && sm.provider === found.provider && sm.id === found.id) {
			this.currentModelName = targetModelId;
			this.currentProvider = found.provider || "unknown";
			return;
		}
		agent.state.model = found;
		this.currentModelName = targetModelId;
		this.currentProvider = found.provider || "unknown";
		log.info({
			sessionKey,
			modelId: targetModelId
		}, "Applied model for session");
	}
	/**
	* Ordered model candidates for the session (primary + `agents.defaults.model.fallbacks`).
	*/
	getFallbackCandidatesForSession(sessionKey) {
		const parsed = parseModelRef(this.getModelForSession(sessionKey));
		if (!parsed) return [];
		return resolveFallbackCandidates({
			cfg: this.config,
			provider: parsed.provider,
			model: parsed.model
		});
	}
	/**
	* Apply a resolved pi-ai model and sync {@link currentModelName} / {@link currentProvider}.
	*/
	applyResolvedModel(agent, model, modelRef) {
		agent.state.model = model;
		this.currentModelName = modelRef;
		this.currentProvider = model.provider || "unknown";
	}
	/**
	* Run the agent with automatic model fallback on failure.
	*/
	async runWithFallback(agent, sessionKey, userMessage, provider, model) {
		const candidates = resolveFallbackCandidates({
			cfg: this.config ?? void 0,
			provider,
			model
		});
		let lastError;
		for (let i = 0; i < candidates.length; i++) {
			const candidate = candidates[i];
			const modelRef = `${candidate.provider}/${candidate.model}`;
			let candidateModel;
			try {
				candidateModel = resolveModel(modelRef);
			} catch {
				log.warn({
					provider: candidate.provider,
					model: candidate.model
				}, "Fallback model not found");
				continue;
			}
			log.info({
				attempt: i + 1,
				total: candidates.length,
				provider: candidate.provider,
				model: candidate.model
			}, "Attempting model");
			const beforeLen = agent.state.messages.length;
			try {
				this.applyResolvedModel(agent, candidateModel, modelRef);
				const runTurn = async () => {
					await agent.prompt(userMessage);
					await agent.waitForIdle();
					await maybeRetryTurnAfterTransientLlmFailure(agent, {
						sessionKey,
						log
					});
				};
				await runAgentTurnWithTimeout(agent, runTurn, resolveAgentTurnTimeoutMs(this.config));
				if (isAssistantTurnAborted(agent)) {
					const usage = agent.state.lastUsage;
					return {
						content: getLastAssistantContent(agent),
						usage
					};
				}
				if (!isAssistantTurnFailed(agent)) {
					const usage = agent.state.lastUsage;
					log.info({
						provider: candidate.provider,
						model: candidate.model,
						success: true
					}, "Model call completed");
					return {
						content: getLastAssistantContent(agent),
						usage
					};
				}
				lastError = /* @__PURE__ */ new Error(`Assistant turn failed: ${modelRef}`);
				log.warn({
					attempt: i + 1,
					sessionKey,
					modelRef
				}, "Model turn failed after retries, trying fallback");
			} catch (err) {
				lastError = err;
				if (err instanceof DOMException && err.name === "AbortError") {
					log.info({ sessionKey }, "User aborted model call");
					throw err;
				}
				const errorDetails = {
					attempt: i + 1,
					provider: candidate.provider,
					model: candidate.model,
					errorMessage: err instanceof Error ? err.message : String(err)
				};
				if (isFailoverError(err)) {
					const described = describeFailoverError(err);
					log.warn({
						...errorDetails,
						...described
					}, "Model call failed, trying fallback");
				} else log.warn(errorDetails, "Model call failed, trying fallback");
			}
			agent.state.messages = agent.state.messages.slice(0, beforeLen);
		}
		if (lastError) {
			const em = lastError instanceof Error ? lastError.message : String(lastError);
			const attemptedModelRefs = candidates.map((c) => `${c.provider}/${c.model}`);
			log.error({
				err: lastError instanceof Error ? lastError : void 0,
				errorMessage: em,
				attemptedCandidates: candidates.length,
				attemptedModelRefs,
				sessionKey
			}, `All model candidates failed (${candidates.length} refs): ${em}`);
			throw lastError instanceof Error ? lastError : new Error(String(lastError));
		}
		return { content: null };
	}
	/**
	* Find model by reference (provider/modelId)
	*/
	findByRef(ref) {
		try {
			return resolveModel(ref);
		} catch {
			return;
		}
	}
	/**
	* Find model by provider and ID
	*/
	find(provider, modelId) {
		try {
			return resolveModel(`${provider}/${modelId}`);
		} catch {
			return;
		}
	}
	/**
	* Get all available models
	*/
	getAllModels() {
		return getAllModels();
	}
	/**
	* Get models grouped by provider
	*/
	getModelsByProvider() {
		const all = this.getAllModels();
		const grouped = /* @__PURE__ */ new Map();
		for (const model of all) {
			const list = grouped.get(model.provider) ?? [];
			list.push(model);
			grouped.set(model.provider, list);
		}
		return grouped;
	}
};
//#endregion
export { ModelManager };

//# sourceMappingURL=manager.js.map