/**
 * Resolves provider/model wiring for Claude-compatible runtimes and
 * OpenAI-compatible Pi runtimes, including 9Router's Claude vs GPT split.
 */
import Anthropic from "@anthropic-ai/sdk";
import { AnthropicBedrock } from "@anthropic-ai/bedrock-sdk";
import { AnthropicVertex } from "@anthropic-ai/vertex-sdk";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";
import { complete, getModel } from "@earendil-works/pi-ai";
import { zodResponseFormat } from "openai/helpers/zod";
import { assertSupportedModelProvider, } from "../config/types.js";
import { startApiProxy } from "./api-proxy.js";
import { getConfigDir } from "../config/index.js";
import path from "node:path";
import { logger } from "../logger.js";
import { resolveAnthropicApiKey } from "../anthropic-credentials.js";
import { resolveOpenAIApiKey } from "../openai-credentials.js";
/** Detect dev mode: tsx runs .ts entry, production runs compiled .js. */
const isDevMode = process.argv[1]?.endsWith(".ts") ?? false;
/** Singleton proxy handle - lazily started on first Anthropic dev-mode query. */
let proxyHandle = null;
import { CLAUDE_SONNET_MODEL, CLAUDE_OPUS_4_6_MODEL, CLAUDE_OPUS_MODEL, GPT_MODEL, BEDROCK_CLAUDE_SONNET_MODEL, BEDROCK_CLAUDE_OPUS_4_6_MODEL, BEDROCK_CLAUDE_OPUS_MODEL, } from "../config/models.js";
const NINEROUTER_MODEL_PREFIX = "";
export const OPENAI_MODEL = GPT_MODEL;
let activeProviderRoutes = [];
export function setLlmProviderRoutes(routes) {
    activeProviderRoutes = Array.isArray(routes) ? routes : [];
}
const DEFAULT_TIER_BY_USE_CASE = {
    main_general: "balanced",
    main_coding: "heavy",
    fast_responder: "fast",
    tool_alarm: "fast",
    health_probe: "fast",
    summary: "balanced",
    disk_cleanup: "balanced",
    daily_report: "balanced",
};
function protocolForConfig(config) {
    return isOpenAIProvider(config.provider) || isGptModel(config.model) ? "openai" : "claude";
}
function resolveRouteModel(config, options = {}) {
    const tier = options.tier ?? (options.useCase ? DEFAULT_TIER_BY_USE_CASE[options.useCase] : "balanced");
    const apiProtocol = protocolForConfig(config);
    const route = activeProviderRoutes.find((item) => item.enabled !== false && item.provider === config.provider);
    const protocol = route?.api_protocols.find((item) => item.enabled && item.api_protocol === apiProtocol);
    const modelId = protocol ? (protocol.models[tier]?.trim() ?? "") : "";
    return modelId
        ? { modelId, tier, apiProtocol, routeSource: "route_v2" }
        : { modelId: null, tier, apiProtocol, routeSource: "legacy" };
}
function logRouteSelection(config, modelId, selection, useCase) {
    if (selection.routeSource !== "route_v2")
        return;
    logger.system(JSON.stringify({
        event: "llm_model_route_selected",
        use_case: useCase ?? "unspecified",
        model_tier: selection.tier,
        provider: config.provider,
        api_protocol: selection.apiProtocol,
        model: modelId,
        route_source: selection.routeSource,
    }));
}
export function isOpenAIProvider(provider) {
    return provider === "openai" || provider === "azure-openai";
}
export function isGptModel(model) {
    return model === "gpt-5.5";
}
export function getProviderLabel(provider) {
    switch (provider) {
        case "bedrock":
            return "Bedrock";
        case "vertex":
            return "Vertex AI";
        case "9router":
            return "9Router";
        case "openai":
            return "OpenAI";
        case "azure-openai":
            return "Azure OpenAI";
        default:
            return "Anthropic";
    }
}
export function getRuntimeLabel(config) {
    if (isGptModel(config.model)) {
        return config.provider === "9router"
            ? "9Router via Pi Agent SDK"
            : "Pi Agent SDK";
    }
    if (config.provider === "bedrock")
        return "Amazon Bedrock via Claude Agent SDK";
    if (config.provider === "vertex")
        return "Google Vertex AI via Claude Agent SDK";
    if (config.provider === "9router")
        return "9Router via Claude Agent SDK";
    return "Claude Agent SDK";
}
/**
 * Create an Anthropic-compatible client for Claude model paths.
 *
 * - `"anthropic"`: uses the direct API with `anthropicApiKey`.
 * - `"bedrock"`: uses `AnthropicBedrock` with explicit AWS credentials when
 *   provided, otherwise falls back to the default AWS credential provider chain.
 * - `"vertex"`: uses `AnthropicVertex` with Google Cloud project/region config.
 * - `"9router"`: uses 9Router as an Anthropic-compatible proxy for Claude models.
 *
 * OpenAI-compatible providers and GPT models use the Pi Agent SDK instead.
 */
export function createClient(config) {
    assertSupportedModelProvider(config.model, config.provider);
    if (isOpenAIProvider(config.provider) || isGptModel(config.model)) {
        throw new Error("createClient() only supports Claude-compatible Anthropic, Bedrock, Vertex, and 9Router providers");
    }
    if (config.provider === "vertex") {
        // Set credentials env var for ADC before constructing the client
        if (config.googleApplicationCredentials) {
            process.env.GOOGLE_APPLICATION_CREDENTIALS = config.googleApplicationCredentials;
        }
        const projectId = config.vertexProjectId ?? process.env.GOOGLE_CLOUD_PROJECT;
        const region = config.vertexRegion;
        return new AnthropicVertex({
            projectId,
            region,
        });
    }
    if (config.provider === "bedrock") {
        const region = config.awsRegion ?? "us-west-2";
        if (config.awsAccessKeyId && config.awsSecretAccessKey) {
            return new AnthropicBedrock({
                awsRegion: region,
                awsAccessKey: config.awsAccessKeyId,
                awsSecretKey: config.awsSecretAccessKey,
                awsSessionToken: config.awsSessionToken ?? null,
            });
        }
        return new AnthropicBedrock({ awsRegion: region });
    }
    if (config.provider === "9router") {
        return new Anthropic({
            apiKey: config.ninerouterApiKey ?? "unused",
            baseURL: config.ninerouterBaseUrl,
        });
    }
    return new Anthropic({
        apiKey: resolveAnthropicApiKey(config),
        ...(config.anthropicBaseUrl ? { baseURL: config.anthropicBaseUrl } : {}),
    });
}
export function hasProviderCredentials(provider, config) {
    switch (provider) {
        case "bedrock":
            // purposely ignore awsBearerToken
            return !!(config.awsAccessKeyId && config.awsSecretAccessKey);
        case "anthropic":
            return !!resolveAnthropicApiKey(config);
        case "9router":
            return !!config.ninerouterApiKey;
        case "vertex":
            return !!(config.googleApplicationCredentials ?? process.env.GOOGLE_APPLICATION_CREDENTIALS);
        case "openai":
            return !!resolveOpenAIApiKey(config);
        case "azure-openai":
            return !!(config.azureOpenAIEndpoint && config.azureOpenAIApiKey && config.azureOpenAIDeployment);
        default:
            return false;
    }
}
/**
 * Return the correct model identifier for direct SDK calls
 * (fast-responder, tool-alarm-evaluator, etc.).
 *
 * Agent SDK `query()` calls do NOT need this - the SDK maps models
 * internally when `CLAUDE_CODE_USE_BEDROCK=1` or `CLAUDE_CODE_USE_VERTEX=1` is set.
 */
export function getModelId(config, options = {}) {
    assertSupportedModelProvider(config.model, config.provider);
    const routeSelection = resolveRouteModel(config, options);
    if (routeSelection.modelId) {
        logRouteSelection(config, routeSelection.modelId, routeSelection, options.useCase);
        return routeSelection.modelId;
    }
    if (config.model === CLAUDE_SONNET_MODEL) {
        if (config.provider === "bedrock")
            return BEDROCK_CLAUDE_SONNET_MODEL;
        if (config.provider === "9router")
            return `${NINEROUTER_MODEL_PREFIX}${CLAUDE_SONNET_MODEL}`;
        return CLAUDE_SONNET_MODEL;
    }
    if (config.model === CLAUDE_OPUS_MODEL) {
        if (config.provider === "bedrock")
            return BEDROCK_CLAUDE_OPUS_MODEL;
        if (config.provider === "9router")
            return `${NINEROUTER_MODEL_PREFIX}${CLAUDE_OPUS_MODEL}`;
        return CLAUDE_OPUS_MODEL;
    }
    if (config.model === CLAUDE_OPUS_4_6_MODEL) {
        if (config.provider === "bedrock")
            return BEDROCK_CLAUDE_OPUS_4_6_MODEL;
        if (config.provider === "9router")
            return `${NINEROUTER_MODEL_PREFIX}${CLAUDE_OPUS_4_6_MODEL}`;
        return CLAUDE_OPUS_4_6_MODEL;
    }
    return OPENAI_MODEL;
}
/**
 * Return the Sonnet model identifier for lightweight side-calls
 * (fast-responder, etc.) that don't need the full Opus model.
 * For non-Claude providers, falls back to the main model.
 */
export function getFastResponderModelId(config) {
    const routeSelection = resolveRouteModel(config, { useCase: "fast_responder" });
    if (routeSelection.modelId) {
        logRouteSelection(config, routeSelection.modelId, routeSelection, "fast_responder");
        return routeSelection.modelId;
    }
    if (config.model === CLAUDE_OPUS_MODEL || config.model === CLAUDE_OPUS_4_6_MODEL || config.model === CLAUDE_SONNET_MODEL) {
        if (config.provider === "bedrock")
            return BEDROCK_CLAUDE_SONNET_MODEL;
        if (config.provider === "9router")
            return `${NINEROUTER_MODEL_PREFIX}${CLAUDE_SONNET_MODEL}`;
        return CLAUDE_SONNET_MODEL;
    }
    return getModelId(config);
}
/**
 * Build the `env` record for runtime SDK calls.
 *
 * - Anthropic: passes `ANTHROPIC_API_KEY`.
 * - Bedrock: passes `CLAUDE_CODE_USE_BEDROCK`, `AWS_REGION`, and either
 *   IAM credentials or `AWS_BEARER_TOKEN_BEDROCK`. IAM credentials take
 *   precedence over bearer token when both are configured. If neither is
 *   configured, process.env AWS credentials are inherited untouched so
 *   local dev (`.env` / `~/.aws/credentials`) still works.
 * - Vertex: passes `CLAUDE_CODE_USE_VERTEX`, `CLOUD_ML_REGION`,
 *   and `GOOGLE_APPLICATION_CREDENTIALS` for service account auth.
 * - OpenAI: passes `OPENAI_API_KEY` for Pi Agent SDK calls.
 * - Azure OpenAI: passes Azure OpenAI endpoint/key/deployment env vars.
 * - 9Router: passes Anthropic-compatible env vars for Claude models and
 *   OpenAI-compatible env vars for GPT models.
 */
export function buildAgentEnv(config) {
    assertSupportedModelProvider(config.model, config.provider);
    const base = { ...process.env };
    // Trigger SDK auto-compaction at 80% context usage (default is ~95%).
    // This fires between turns within a single query() call, preventing
    // unbounded context growth during long-running agent sessions.
    base.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE = "80";
    // TODO: Consider enabling session-memory compaction for higher-quality
    // summaries: base.ENABLE_CLAUDE_CODE_SM_COMPACT = "1";
    // --- CLI auto-memory ---
    //
    // The Claude Code CLI binary owns auto-memory: it picks up a project
    // memory directory (reported as `memory_paths` on `system/init`), writes
    // `CLAUDE.md` / `MEMORY.md` there after long-running sessions, and
    // surfaces relevant files back into future turns via the recall
    // supervisor (`system/memory_recall` events). We keep the default-ON
    // behavior and observe it in stream-handler.
    //
    // The CLI reads its on/off switch via the env var `CLAUDE_CODE_DISABLE_AUTO_MEMORY`
    // and the settings field `autoMemoryEnabled`. `buildAgentEnv()` spreads
    // `process.env` at the top of this function, so whatever the operator has
    // in their shell (or MDM-managed config) wins. If we ever add our own
    // dial to this, put it here.
    //
    // Defensive: if the outer process has accidentally set
    // `CLAUDE_CODE_DISABLE_AUTO_MEMORY` to a truthy value, log a warning so
    // operators notice. We do not override it — killing a legitimately
    // opted-out environment would be more surprising than losing the
    // observability we just added.
    const disableAutoMemory = base.CLAUDE_CODE_DISABLE_AUTO_MEMORY;
    if (disableAutoMemory && /^(1|true|yes)$/i.test(disableAutoMemory)) {
        logger.warn(`[auto-memory] CLAUDE_CODE_DISABLE_AUTO_MEMORY=${disableAutoMemory} in process.env; CLI auto-memory will be disabled. Unset it to enable memory_recall observability.`);
    }
    if (config.provider === "vertex") {
        base.CLAUDE_CODE_USE_VERTEX = "1";
        base.CLOUD_ML_REGION = config.vertexRegion;
        // Vertex doesn't support all beta features yet; disable to avoid unsupported-header errors
        base.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS = "1";
        if (config.googleApplicationCredentials) {
            base.GOOGLE_APPLICATION_CREDENTIALS = config.googleApplicationCredentials;
        }
        if (config.vertexProjectId) {
            base.GOOGLE_CLOUD_PROJECT = config.vertexProjectId;
        }
        delete base.ANTHROPIC_API_KEY;
        delete base.CLAUDE_CODE_USE_BEDROCK;
        delete base.AWS_REGION;
        delete base.AWS_BEARER_TOKEN_BEDROCK;
        delete base.AWS_ACCESS_KEY_ID;
        delete base.AWS_SECRET_ACCESS_KEY;
        delete base.AWS_SESSION_TOKEN;
        return base;
    }
    if (config.provider === "bedrock") {
        base.CLAUDE_CODE_USE_BEDROCK = "1";
        base.AWS_REGION = config.awsRegion ?? "us-west-2";
        const hasConfigIamCredentials = !!(config.awsAccessKeyId && config.awsSecretAccessKey);
        const hasConfigBearerToken = !!config.awsBearerToken;
        if (hasConfigIamCredentials) {
            base.AWS_ACCESS_KEY_ID = config.awsAccessKeyId;
            base.AWS_SECRET_ACCESS_KEY = config.awsSecretAccessKey;
            // Always set/clear AWS_SESSION_TOKEN based on config rather than
            // inheriting from process.env — a stale session token from the outer
            // shell combined with long-lived IAM credentials makes AWS treat the
            // pair as expired STS temp credentials.
            if (config.awsSessionToken) {
                base.AWS_SESSION_TOKEN = config.awsSessionToken;
            }
            else {
                delete base.AWS_SESSION_TOKEN;
            }
            delete base.AWS_BEARER_TOKEN_BEDROCK;
        }
        else if (hasConfigBearerToken) {
            base.AWS_BEARER_TOKEN_BEDROCK = config.awsBearerToken;
            delete base.AWS_ACCESS_KEY_ID;
            delete base.AWS_SECRET_ACCESS_KEY;
            delete base.AWS_SESSION_TOKEN;
        }
        // If config has neither IAM nor bearer credentials, inherit whatever
        // is in process.env (dev `.env`, ~/.aws/credentials via SDK chain, etc.)
        // unchanged so local development continues to work.
        delete base.ANTHROPIC_API_KEY;
        return base;
    }
    if (config.provider === "openai") {
        base.OPENAI_API_KEY = resolveOpenAIApiKey(config);
        delete base.AZURE_OPENAI_ENDPOINT;
        delete base.AZURE_OPENAI_API_KEY;
        delete base.AZURE_OPENAI_API_VERSION;
        delete base.AZURE_OPENAI_DEPLOYMENT;
        delete base.ANTHROPIC_API_KEY;
        delete base.CLAUDE_CODE_USE_BEDROCK;
        delete base.AWS_REGION;
        delete base.AWS_BEARER_TOKEN_BEDROCK;
        delete base.AWS_ACCESS_KEY_ID;
        delete base.AWS_SECRET_ACCESS_KEY;
        delete base.AWS_SESSION_TOKEN;
        return base;
    }
    if (config.provider === "9router" && isGptModel(config.model)) {
        base.OPENAI_API_KEY = config.ninerouterApiKey ?? "unused";
        delete base.ANTHROPIC_API_KEY;
        delete base.ANTHROPIC_BASE_URL;
        delete base.AZURE_OPENAI_ENDPOINT;
        delete base.AZURE_OPENAI_API_KEY;
        delete base.AZURE_OPENAI_API_VERSION;
        delete base.AZURE_OPENAI_DEPLOYMENT;
    }
    else {
        // Anthropic direct API or 9Router (Anthropic-compatible proxy).
        if (config.provider === "9router") {
            base.ANTHROPIC_API_KEY = config.ninerouterApiKey ?? "unused";
            base.ANTHROPIC_BASE_URL = config.ninerouterBaseUrl;
        }
        else {
            base.ANTHROPIC_API_KEY = resolveAnthropicApiKey(config);
            if (config.anthropicBaseUrl) {
                base.ANTHROPIC_BASE_URL = config.anthropicBaseUrl;
            }
        }
        // Dev-mode only: route API calls through the logging proxy
        if (isDevMode && proxyHandle) {
            base.ANTHROPIC_BASE_URL = `http://127.0.0.1:${proxyHandle.port}`;
        }
        delete base.OPENAI_API_KEY;
        delete base.AZURE_OPENAI_ENDPOINT;
        delete base.AZURE_OPENAI_API_KEY;
        delete base.AZURE_OPENAI_API_VERSION;
        delete base.AZURE_OPENAI_DEPLOYMENT;
    }
    delete base.CLAUDE_CODE_USE_BEDROCK;
    delete base.CLAUDE_CODE_USE_VERTEX;
    delete base.AWS_REGION;
    delete base.AWS_BEARER_TOKEN_BEDROCK;
    delete base.AWS_ACCESS_KEY_ID;
    delete base.AWS_SECRET_ACCESS_KEY;
    delete base.AWS_SESSION_TOKEN;
    return base;
}
/**
 * Start the API logging proxy for Claude-compatible dev-mode API calls.
 * No-ops in production, for Bedrock/Vertex, for OpenAI-compatible providers,
 * and for 9Router GPT models.
 *
 * Call once at startup, before the first `buildAgentEnv()` call.
 */
export async function maybeStartApiProxy(config) {
    if (!isDevMode)
        return;
    if (config.provider !== "anthropic" && config.provider !== "9router")
        return;
    if (isGptModel(config.model))
        return;
    if (proxyHandle)
        return;
    try {
        const logDir = path.join(getConfigDir(), "logs");
        const targetBaseUrl = config.provider === "9router"
            ? config.ninerouterBaseUrl
            : (config.anthropicBaseUrl ?? "https://api.anthropic.com");
        proxyHandle = await startApiProxy({ logDir, targetBaseUrl });
    }
    catch (err) {
        logger.warn(`[api-proxy] Failed to start: ${err instanceof Error ? err.message : String(err)}`);
    }
}
/**
 * Resolve the Pi `Model<Api>` + API key for the configured provider, so Pi
 * side-calls (fast-responder, tool alarm evaluator, etc.) go through the same
 * provider routing the main Pi agent uses.
 *
 * Mirrors `PiAgentSession.resolvePiModel()` / `getApiKey()` — keep them in
 * sync if you change one.
 */
export function resolvePiSideCallModel(config, options = {}) {
    const modelId = getModelId(config, options);
    if (config.provider === "azure-openai") {
        const endpoint = requireConfigSetting("azureOpenAIEndpoint", config.azureOpenAIEndpoint);
        const deployment = requireConfigSetting("azureOpenAIDeployment", config.azureOpenAIDeployment);
        return {
            model: {
                id: deployment,
                name: modelId,
                api: "azure-openai-responses",
                provider: "azure-openai-responses",
                baseUrl: endpoint,
                reasoning: true,
                input: ["text", "image"],
                cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
                contextWindow: 1_000_000,
                maxTokens: 100_000,
            },
            apiKey: requireConfigSetting("azureOpenAIApiKey", config.azureOpenAIApiKey),
        };
    }
    if (config.provider === "9router") {
        const apiKey = requireConfigSetting("ninerouterApiKey", config.ninerouterApiKey);
        const baseUrl = normalizeOpenAICompatibleBaseUrl(requireConfigSetting("ninerouterBaseUrl", config.ninerouterBaseUrl));
        return {
            model: {
                id: modelId,
                name: modelId,
                api: "openai-completions",
                provider: "9router",
                baseUrl,
                reasoning: false,
                input: ["text", "image"],
                cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
                contextWindow: 1_000_000,
                maxTokens: 100_000,
            },
            apiKey,
        };
    }
    if (modelId === "gpt-5.5" || modelId === "gpt-5.5-pro") {
        return {
            model: getModel("openai", modelId),
            apiKey: requireConfigSetting("openaiApiKey", resolveOpenAIApiKey(config)),
        };
    }
    return {
        model: {
            id: modelId,
            name: modelId,
            api: "openai-responses",
            provider: "openai",
            baseUrl: "https://api.openai.com",
            reasoning: true,
            input: ["text", "image"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 1_000_000,
            maxTokens: 100_000,
        },
        apiKey: requireConfigSetting("openaiApiKey", resolveOpenAIApiKey(config)),
    };
}
export async function runSideCall(config, options) {
    const maxTokens = options.maxTokens ?? 2048;
    if (isGptModel(config.model)) {
        const piOptions = {
            systemPrompt: options.systemPrompt,
            userMessage: options.userMessage,
            maxTokens,
            ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
            ...(options.useCase !== undefined ? { useCase: options.useCase } : {}),
        };
        if ("schema" in options) {
            const schemaName = options.schemaName ?? "side_call_response";
            const openAIResponseFormat = zodResponseFormat(options.schema, schemaName);
            piOptions.systemPrompt = appendPiStructuredOutputHint(options.systemPrompt, openAIResponseFormat);
            piOptions.onPayload = (payload, model) => injectPiStructuredOutput(payload, model, openAIResponseFormat);
        }
        const text = await runPiSideCallText(config, piOptions);
        return "schema" in options ? options.schema.parse(JSON.parse(stripJsonFence(text))) : text;
    }
    const client = createClient(config);
    const response = await client.messages.create({
        model: options.modelId ?? getModelId(config, { useCase: options.useCase ?? "fast_responder" }),
        max_tokens: maxTokens,
        ...("schema" in options
            ? {
                output_config: {
                    format: zodOutputFormat(options.schema),
                    effort: "low",
                },
            }
            : {}),
        system: options.systemPrompt,
        messages: [{ role: "user", content: options.userMessage }],
        stream: false,
    }, options.timeoutMs !== undefined ? { timeout: options.timeoutMs } : undefined);
    const text = response.content[0]?.type === "text" ? response.content[0].text : "";
    if (!text) {
        throw new Error("Claude side call returned no text content");
    }
    return "schema" in options ? options.schema.parse(JSON.parse(text)) : text;
}
function appendPiStructuredOutputHint(systemPrompt, responseFormat) {
    return [
        systemPrompt,
        "",
        "## Output format",
        "Return exactly one valid JSON object. Do not include markdown fences, prose, or any text outside the JSON object.",
        `The JSON object must conform to this schema: ${JSON.stringify(responseFormat.json_schema.schema)}`,
    ].join("\n");
}
async function runPiSideCallText(config, options) {
    const { model, apiKey } = resolvePiSideCallModel(config, { useCase: options.useCase ?? "fast_responder" });
    const result = await complete(model, {
        systemPrompt: options.systemPrompt,
        messages: [
            {
                role: "user",
                content: [{ type: "text", text: options.userMessage }],
                timestamp: Date.now(),
            },
        ],
    }, {
        apiKey,
        maxTokens: options.maxTokens,
        ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
        ...(options.onPayload ? { onPayload: options.onPayload } : {}),
    });
    if (result.errorMessage) {
        throw new Error(`Pi side call failed: ${result.errorMessage}`);
    }
    const text = result.content
        .filter((block) => block.type === "text")
        .map((block) => block.text)
        .join("")
        .trim();
    if (!text) {
        throw new Error("Pi side call returned no text content");
    }
    return text;
}
function injectPiStructuredOutput(payload, model, responseFormat) {
    if (typeof payload !== "object" || payload === null)
        return payload;
    const record = payload;
    if (model.api === "openai-responses" || model.api === "azure-openai-responses") {
        const existingText = typeof record.text === "object" && record.text !== null
            ? record.text
            : {};
        return {
            ...record,
            text: {
                ...existingText,
                format: toOpenAIResponsesTextFormat(responseFormat),
            },
        };
    }
    if (model.api === "openai-completions") {
        return {
            ...record,
            response_format: { type: "json_object" },
        };
    }
    return payload;
}
function toOpenAIResponsesTextFormat(responseFormat) {
    const { name, schema, strict, description } = responseFormat.json_schema;
    return {
        type: "json_schema",
        name,
        schema,
        ...(strict !== undefined ? { strict } : {}),
        ...(description !== undefined ? { description } : {}),
    };
}
function normalizeOpenAICompatibleBaseUrl(baseUrl) {
    const trimmed = baseUrl.trim().replace(/\/+$/, "");
    return trimmed.endsWith("/v1") ? trimmed : `${trimmed}/v1`;
}
function requireConfigSetting(name, value) {
    const trimmed = value?.trim();
    if (trimmed)
        return trimmed;
    throw new Error(`Side-call provider requires ${name}`);
}
function stripJsonFence(text) {
    const trimmed = text.trim();
    const fenceMatch = /^```(?:json)?\s*([\s\S]*?)\s*```$/i.exec(trimmed);
    return fenceMatch ? fenceMatch[1].trim() : trimmed;
}
//# sourceMappingURL=model-provider.js.map