/** Immutable, credential-blind models.json snapshot. */
import { readFile } from "node:fs/promises";
import { Type } from "typebox";
import { Compile } from "typebox/compile";
import { stripJsonComments } from "../utils/json.js";
import { normalizePath } from "../utils/paths.js";
const PercentileCutoffsSchema = Type.Object({
    p50: Type.Optional(Type.Number()),
    p75: Type.Optional(Type.Number()),
    p90: Type.Optional(Type.Number()),
    p99: Type.Optional(Type.Number()),
});
const OpenRouterRoutingSchema = Type.Object({
    allow_fallbacks: Type.Optional(Type.Boolean()),
    require_parameters: Type.Optional(Type.Boolean()),
    data_collection: Type.Optional(Type.Union([Type.Literal("deny"), Type.Literal("allow")])),
    zdr: Type.Optional(Type.Boolean()),
    enforce_distillable_text: Type.Optional(Type.Boolean()),
    order: Type.Optional(Type.Array(Type.String())),
    only: Type.Optional(Type.Array(Type.String())),
    ignore: Type.Optional(Type.Array(Type.String())),
    quantizations: Type.Optional(Type.Array(Type.String())),
    sort: Type.Optional(Type.Union([
        Type.String(),
        Type.Object({
            by: Type.Optional(Type.String()),
            partition: Type.Optional(Type.Union([Type.String(), Type.Null()])),
        }),
    ])),
    max_price: Type.Optional(Type.Object({
        prompt: Type.Optional(Type.Union([Type.Number(), Type.String()])),
        completion: Type.Optional(Type.Union([Type.Number(), Type.String()])),
        image: Type.Optional(Type.Union([Type.Number(), Type.String()])),
        audio: Type.Optional(Type.Union([Type.Number(), Type.String()])),
        request: Type.Optional(Type.Union([Type.Number(), Type.String()])),
    })),
    preferred_min_throughput: Type.Optional(Type.Union([Type.Number(), PercentileCutoffsSchema])),
    preferred_max_latency: Type.Optional(Type.Union([Type.Number(), PercentileCutoffsSchema])),
});
const VercelGatewayRoutingSchema = Type.Object({
    only: Type.Optional(Type.Array(Type.String())),
    order: Type.Optional(Type.Array(Type.String())),
});
const ThinkingLevelMapValueSchema = Type.Union([Type.String(), Type.Null()]);
const ThinkingLevelMapSchema = Type.Object({
    off: Type.Optional(ThinkingLevelMapValueSchema),
    minimal: Type.Optional(ThinkingLevelMapValueSchema),
    low: Type.Optional(ThinkingLevelMapValueSchema),
    medium: Type.Optional(ThinkingLevelMapValueSchema),
    high: Type.Optional(ThinkingLevelMapValueSchema),
    xhigh: Type.Optional(ThinkingLevelMapValueSchema),
    max: Type.Optional(ThinkingLevelMapValueSchema),
});
const ChatTemplateKwargScalarSchema = Type.Union([Type.String(), Type.Number(), Type.Boolean(), Type.Null()]);
const ChatTemplateKwargVariableSchema = Type.Object({
    $var: Type.Union([Type.Literal("thinking.enabled"), Type.Literal("thinking.effort")]),
    omitWhenOff: Type.Optional(Type.Boolean()),
});
const ChatTemplateKwargSchema = Type.Union([ChatTemplateKwargScalarSchema, ChatTemplateKwargVariableSchema]);
const OpenAICompletionsCompatSchema = Type.Object({
    supportsStore: Type.Optional(Type.Boolean()),
    supportsDeveloperRole: Type.Optional(Type.Boolean()),
    supportsReasoningEffort: Type.Optional(Type.Boolean()),
    supportsUsageInStreaming: Type.Optional(Type.Boolean()),
    maxTokensField: Type.Optional(Type.Union([Type.Literal("max_completion_tokens"), Type.Literal("max_tokens")])),
    requiresToolResultName: Type.Optional(Type.Boolean()),
    requiresAssistantAfterToolResult: Type.Optional(Type.Boolean()),
    requiresThinkingAsText: Type.Optional(Type.Boolean()),
    requiresReasoningContentOnAssistantMessages: Type.Optional(Type.Boolean()),
    thinkingFormat: Type.Optional(Type.Union([
        Type.Literal("openai"),
        Type.Literal("openrouter"),
        Type.Literal("together"),
        Type.Literal("baseten"),
        Type.Literal("deepseek"),
        Type.Literal("zai"),
        Type.Literal("qwen"),
        Type.Literal("chat-template"),
        Type.Literal("qwen-chat-template"),
        Type.Literal("string-thinking"),
        Type.Literal("ant-ling"),
    ])),
    chatTemplateKwargs: Type.Optional(Type.Record(Type.String(), ChatTemplateKwargSchema)),
    chatTemplateArgs: Type.Optional(Type.Record(Type.String(), ChatTemplateKwargSchema)),
    cacheControlFormat: Type.Optional(Type.Literal("anthropic")),
    openRouterRouting: Type.Optional(OpenRouterRoutingSchema),
    vercelGatewayRouting: Type.Optional(VercelGatewayRoutingSchema),
    supportsOpenAIGrammarTools: Type.Optional(Type.Boolean()),
    supportsStrictMode: Type.Optional(Type.Boolean()),
    sendSessionAffinityHeaders: Type.Optional(Type.Boolean()),
    deferredToolsMode: Type.Optional(Type.Literal("kimi")),
    sessionAffinityFormat: Type.Optional(Type.Union([Type.Literal("openai"), Type.Literal("openai-nosession"), Type.Literal("openrouter")])),
    supportsLongCacheRetention: Type.Optional(Type.Boolean()),
});
const OpenAIResponsesCompatSchema = Type.Object({
    supportsDeveloperRole: Type.Optional(Type.Boolean()),
    sessionAffinityFormat: Type.Optional(Type.Union([Type.Literal("openai"), Type.Literal("openai-nosession"), Type.Literal("openrouter")])),
    supportsLongCacheRetention: Type.Optional(Type.Boolean()),
    supportsStrictMode: Type.Optional(Type.Boolean()),
    supportsOpenAIGrammarTools: Type.Optional(Type.Boolean()),
    supportsAdditionalTools: Type.Optional(Type.Boolean()),
    supportsToolSearch: Type.Optional(Type.Boolean()),
});
const AnthropicMessagesCompatSchema = Type.Object({
    supportsEagerToolInputStreaming: Type.Optional(Type.Boolean()),
    supportsLongCacheRetention: Type.Optional(Type.Boolean()),
    sendSessionAffinityHeaders: Type.Optional(Type.Boolean()),
    supportsCacheControlOnTools: Type.Optional(Type.Boolean()),
    supportsTemperature: Type.Optional(Type.Boolean()),
    forceAdaptiveThinking: Type.Optional(Type.Boolean()),
    allowEmptySignature: Type.Optional(Type.Boolean()),
    supportsStrictTools: Type.Optional(Type.Boolean()),
    supportsToolReferences: Type.Optional(Type.Boolean()),
});
const ProviderCompatSchema = Type.Union([
    OpenAICompletionsCompatSchema,
    OpenAIResponsesCompatSchema,
    AnthropicMessagesCompatSchema,
]);
const ModelCostRatesSchema = {
    input: Type.Number(),
    output: Type.Number(),
    cacheRead: Type.Number(),
    cacheWrite: Type.Number(),
};
const ModelCostTierSchema = Type.Object({
    inputTokensAbove: Type.Number(),
    ...ModelCostRatesSchema,
});
const ModelCostSchema = Type.Object({
    ...ModelCostRatesSchema,
    tiers: Type.Optional(Type.Array(ModelCostTierSchema)),
});
const ModelDefinitionSchema = Type.Object({
    id: Type.String({ minLength: 1 }),
    name: Type.Optional(Type.String({ minLength: 1 })),
    api: Type.Optional(Type.String({ minLength: 1 })),
    baseUrl: Type.Optional(Type.String({ minLength: 1 })),
    reasoning: Type.Optional(Type.Boolean()),
    thinkingLevelMap: Type.Optional(ThinkingLevelMapSchema),
    input: Type.Optional(Type.Array(Type.Union([Type.Literal("text"), Type.Literal("image")]))),
    cost: Type.Optional(ModelCostSchema),
    contextWindow: Type.Optional(Type.Number()),
    maxTokens: Type.Optional(Type.Number()),
    samplingParams: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
    headers: Type.Optional(Type.Record(Type.String(), Type.String())),
    compat: Type.Optional(ProviderCompatSchema),
});
const ModelOverrideSchema = Type.Object({
    name: Type.Optional(Type.String({ minLength: 1 })),
    reasoning: Type.Optional(Type.Boolean()),
    thinkingLevelMap: Type.Optional(ThinkingLevelMapSchema),
    input: Type.Optional(Type.Array(Type.Union([Type.Literal("text"), Type.Literal("image")]))),
    cost: Type.Optional(Type.Object({
        input: Type.Optional(Type.Number()),
        output: Type.Optional(Type.Number()),
        cacheRead: Type.Optional(Type.Number()),
        cacheWrite: Type.Optional(Type.Number()),
        tiers: Type.Optional(Type.Array(ModelCostTierSchema)),
    })),
    contextWindow: Type.Optional(Type.Number()),
    maxTokens: Type.Optional(Type.Number()),
    samplingParams: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
    headers: Type.Optional(Type.Record(Type.String(), Type.String())),
    compat: Type.Optional(ProviderCompatSchema),
});
const ProviderConfigSchema = Type.Object({
    name: Type.Optional(Type.String({ minLength: 1 })),
    baseUrl: Type.Optional(Type.String({ minLength: 1 })),
    apiKey: Type.Optional(Type.String({ minLength: 1 })),
    api: Type.Optional(Type.String({ minLength: 1 })),
    oauth: Type.Optional(Type.Literal("radius")),
    headers: Type.Optional(Type.Record(Type.String(), Type.String())),
    compat: Type.Optional(ProviderCompatSchema),
    authHeader: Type.Optional(Type.Boolean()),
    models: Type.Optional(Type.Array(ModelDefinitionSchema)),
    modelOverrides: Type.Optional(Type.Record(Type.String(), ModelOverrideSchema)),
});
const ModelsConfigSchema = Type.Object({
    providers: Type.Record(Type.String(), ProviderConfigSchema),
});
const validateModelsConfig = Compile(ModelsConfigSchema);
function formatValidationPath(error) {
    if (error.keyword === "required") {
        const requiredProperties = error.params.requiredProperties;
        const requiredProperty = requiredProperties?.[0];
        if (requiredProperty) {
            const basePath = error.instancePath.replace(/^\//, "").replace(/\//g, ".");
            return basePath ? `${basePath}.${requiredProperty}` : requiredProperty;
        }
    }
    const path = error.instancePath.replace(/^\//, "").replace(/\//g, ".");
    return path || "root";
}
function deepFreeze(value) {
    if (typeof value !== "object" || value === null || Object.isFrozen(value))
        return value;
    for (const child of Object.values(value))
        deepFreeze(child);
    return Object.freeze(value);
}
/** One immutable load of models.json. */
export class ModelConfig {
    providers;
    error;
    constructor(providers, error) {
        this.providers = providers;
        this.error = error;
    }
    static async load(modelsJsonPath) {
        if (!modelsJsonPath)
            return new ModelConfig(new Map());
        const path = normalizePath(modelsJsonPath);
        let content;
        try {
            content = await readFile(path, "utf-8");
        }
        catch (error) {
            if (error.code === "ENOENT")
                return new ModelConfig(new Map());
            return new ModelConfig(new Map(), `Failed to load models.json: ${error instanceof Error ? error.message : error}\n\nFile: ${path}`);
        }
        let parsed;
        try {
            parsed = JSON.parse(stripJsonComments(content));
        }
        catch (error) {
            return new ModelConfig(new Map(), `Failed to parse models.json: ${error instanceof Error ? error.message : error}\n\nFile: ${path}`);
        }
        if (!validateModelsConfig.Check(parsed)) {
            const errors = validateModelsConfig
                .Errors(parsed)
                .map((error) => `  - ${formatValidationPath(error)}: ${error.message}`)
                .join("\n") || "Unknown schema error";
            return new ModelConfig(new Map(), `Invalid models.json schema:\n${errors}\n\nFile: ${path}`);
        }
        const config = parsed;
        const providers = new Map();
        for (const [providerId, provider] of Object.entries(config.providers)) {
            providers.set(providerId, deepFreeze(structuredClone(provider)));
        }
        return new ModelConfig(providers);
    }
    getProvider(providerId) {
        return this.providers.get(providerId);
    }
    getProviderIds() {
        return [...this.providers.keys()];
    }
    getError() {
        return this.error;
    }
}
//# sourceMappingURL=model-config.js.map