/**
 * provider-assembler.ts — Build per-mode provider arrays from models config.
 *
 * Replicates the provider-building logic from the legacy generate-configs.ts
 * so the new compiler produces identical provider configurations.
 *
 * Separated into its own module so GenerateConfigsStep can import it
 * without pulling in the full legacy generate-configs machinery.
 *
 * W0134 — per-mode maxToolRounds
 *
 * The agentic naive variant gets a higher round budget than agentic
 * optimized: naive simulates current real-world agent behavior under
 * retrieval pressure (it spends rounds on retries when fetches fail) and
 * benefits from more headroom; optimized bypasses Jina via the .md-direct
 * branch and rarely needs more than a couple of rounds. Bumping globally
 * would inflate optimized cost without changing its measured behavior.
 *
 * Resolution order (most specific wins):
 *   1. `model.config.maxToolRounds` — per-model override.
 *   2. `defaults.modeMaxToolRounds[variant]` — per-variant override.
 *   3. `defaults.maxToolRounds` — global default.
 *   4. Hard fallback (5).
 */
import { extractModelName, extractProvider, mergeConfig, } from "../../_vendor/ailf-core/index.js";
import { loadConfigFile } from "./config-loader.js";
import { modelMatchesLiteracyVariant } from "./mode-bases/literacy.js";
import { MOCK_PROVIDER_ABSPATH } from "../../promptfoo-providers/mock-path.js";
/**
 * Apply the W0110 replay swap to a list of literacy provider records.
 *
 * When `AILF_REPLAY_LLMS=1`, every provider's `id` is rewritten to the
 * file-based AILF mock provider so the Promptfoo subprocess never makes
 * a live LLM call. We preserve `label` and stash the original `id` in
 * `config.originalId` so reports remain interpretable. This mirrors the
 * top-level `buildProviders` swap in `promptfoo-compiler.ts` — it exists
 * here because the literacy mode runs through this assembler, not
 * `compileToPromptfoo`, so without this hook the replay flag was a no-op
 * for every literacy run (W0138 Slice 2 surface).
 *
 * `originalId` is set with `?? p.id` so a record that's already been
 * swapped (or that pre-stashed an `originalId` for any other reason)
 * doesn't get its real model id clobbered by the file:// path.
 */
function applyReplaySwap(providers) {
    if (process.env.AILF_REPLAY_LLMS !== "1")
        return providers;
    return providers.map((raw) => {
        const p = raw;
        const config = p.config ?? {};
        return {
            id: `file://${MOCK_PROVIDER_ABSPATH}`,
            label: p.label,
            config: { ...config, originalId: config.originalId ?? p.id },
        };
    });
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Load models config and assemble provider arrays for literacy variants.
 *
 * Returns provider arrays keyed by literacy variant name (baseline,
 * agentic, observed). These are consumed by the YAML writer to produce
 * the per-variant promptfoo config files.
 *
 * `loaded` (optional) lets callers pre-load and pre-filter the
 * `ModelsConfig` so a caller-side filter (e.g. W0281's
 * `filterModelsByRequest`) actually takes effect on the assembled
 * providers — building providers from the unfiltered set would silently
 * defeat the filter, since promptfoo decides which LLMs to call from the
 * providers array, not the returned `models` field.
 */
export function loadModelsAndProviders(rootDir, source, searchMode, allowedOrigins, loaded) {
    const models = loaded ?? loadModelsYaml(rootDir);
    return {
        models,
        providers: {
            baseline: applyReplaySwap(buildBaselineProviders(models)),
            agentic: applyReplaySwap(buildAgenticProviders(models, source, searchMode, allowedOrigins)),
            observed: applyReplaySwap(buildObservedProviders(models)),
        },
    };
}
// ---------------------------------------------------------------------------
// Baseline providers
// ---------------------------------------------------------------------------
function buildBaselineProviders(models) {
    return models.models
        .filter((m) => modelMatchesLiteracyVariant(m, "baseline"))
        .map((model) => ({
        config: {
            ...mergeConfig(models.defaults, model.config),
            ...(model.timeoutMs ? { timeoutMs: model.timeoutMs } : {}),
        },
        id: model.id,
        label: model.label,
    }));
}
// ---------------------------------------------------------------------------
// Observed providers
// ---------------------------------------------------------------------------
function buildObservedProviders(models) {
    return models.models
        .filter((m) => modelMatchesLiteracyVariant(m, "observed"))
        .map((model) => {
        const modelName = extractModelName(model.id);
        return {
            config: {
                ...mergeConfig(models.defaults, model.config),
                ...(model.timeoutMs ? { timeoutMs: model.timeoutMs } : {}),
                modelName,
                observe: true,
                recordOptions: models.defaults.observerOptions ?? {},
            },
            id: "file://dist/agent-observer/provider.js",
            label: `${model.label} (Observed)`,
        };
    });
}
// ---------------------------------------------------------------------------
// Agentic providers
// ---------------------------------------------------------------------------
function buildAgenticProviders(models, source, searchMode, _allowedOrigins) {
    const naiveModels = models.models.filter((m) => modelMatchesLiteracyVariant(m, "agentic-naive"));
    const optimizedModels = models.models.filter((m) => modelMatchesLiteracyVariant(m, "agentic-optimized"));
    const resolvedSearchMode = searchMode ?? "open";
    const sourceConfig = source
        ? {
            ...(source.allowedOrigins?.length
                ? { allowedOrigins: source.allowedOrigins }
                : {}),
            docBaseUrl: source.baseUrl,
            ...(source.headers && Object.keys(source.headers).length > 0
                ? { customHeaders: source.headers }
                : {}),
            llmsTxtUrl: source.llmsTxt,
            ...(source.priorityDomain
                ? { priorityDomain: source.priorityDomain }
                : {}),
            ...(resolvedSearchMode !== "open"
                ? { searchMode: resolvedSearchMode }
                : {}),
        }
        : {};
    const providers = [];
    for (const model of naiveModels) {
        const modelName = extractModelName(model.id);
        const provider = extractProvider(model.id);
        providers.push({
            config: {
                ...mergeConfig(models.defaults, model.config, {
                    agentMode: "naive",
                    maxToolRounds: resolveMaxToolRounds(models, model, "agentic-naive"),
                    model: modelName,
                    provider,
                }),
                ...(model.timeoutMs ? { timeoutMs: model.timeoutMs } : {}),
                ...sourceConfig,
                observe: true,
                observerOptions: models.defaults.observerOptions ?? {},
            },
            id: "file://dist/agent-observer/agentic-provider.js",
            label: `${model.label} (Naive Agent)`,
        });
    }
    for (const model of optimizedModels) {
        const modelName = extractModelName(model.id);
        const provider = extractProvider(model.id);
        providers.push({
            config: {
                ...mergeConfig(models.defaults, model.config, {
                    agentMode: "optimized",
                    maxToolRounds: resolveMaxToolRounds(models, model, "agentic-optimized"),
                    model: modelName,
                    provider,
                }),
                ...(model.timeoutMs ? { timeoutMs: model.timeoutMs } : {}),
                ...sourceConfig,
                observe: true,
                observerOptions: models.defaults.observerOptions ?? {},
            },
            id: "file://dist/agent-observer/agentic-provider.js",
            label: `${model.label} (Optimized Agent)`,
        });
    }
    return providers;
}
/**
 * Resolve `maxToolRounds` for an agentic variant (W0134).
 *
 * Most-specific wins: per-model `config.maxToolRounds` > per-variant
 * `defaults.modeMaxToolRounds[variant]` > global `defaults.maxToolRounds`
 * > hard fallback (5).
 */
export function resolveMaxToolRounds(models, model, variant) {
    const perModel = model.config?.maxToolRounds;
    if (typeof perModel === "number")
        return perModel;
    const modeOverrides = models.defaults.modeMaxToolRounds;
    const perVariant = modeOverrides?.[variant];
    if (typeof perVariant === "number")
        return perVariant;
    const globalDefault = models.defaults.maxToolRounds;
    if (typeof globalDefault === "number")
        return globalDefault;
    return 5;
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/**
 * Load the `ModelsConfig` for `rootDir` from disk. Exported so callers
 * that need to pre-filter the model set before provider assembly (e.g.
 * `PipelineRequest.models`) can hand the filtered config back to
 * `loadModelsAndProviders` via its optional `loaded` parameter.
 */
export function loadModelsYaml(rootDir) {
    return loadConfigFile("models", rootDir).data;
}
