import { AGENT_TOOL_NAMES, MAX_TOOL_CALLS_PER_RESPONSE } from "./types.js";
import { fetchWithTimeout } from "./http.js";
import { getNvidiaReasoningEffort } from "./reasoning.js";
import { attachTokenCost } from "./tokenAccounting.js";
export const defaultNvidiaModel = "meta/llama-3.1-70b-instruct";
export const defaultNvidiaBaseUrl = "https://integrate.api.nvidia.com/v1";
export class NvidiaClient {
    apiKey;
    baseUrl;
    runtimeOptions;
    constructor(apiKey = readNvidiaApiKey(), baseUrl = process.env.PATCHPILOT_NVIDIA_BASE_URL ?? defaultNvidiaBaseUrl, runtimeOptions = readNvidiaRuntimeOptions()) {
        this.apiKey = apiKey;
        this.baseUrl = baseUrl.replace(/\/$/, "");
        this.runtimeOptions = runtimeOptions;
    }
    async chat(options) {
        this.assertConfigured();
        const startedAt = Date.now();
        const response = await this.fetchNvidia("/chat/completions", {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                Authorization: `Bearer ${this.apiKey}`
            },
            body: JSON.stringify({
                model: options.model,
                messages: options.messages,
                max_tokens: this.runtimeOptions.maxTokens,
                temperature: this.runtimeOptions.temperature,
                reasoning_effort: getNvidiaReasoningEffort(options.model, options.reasoningEffort),
                response_format: options.formatJson ? agentResponseFormat : undefined
            }),
            signal: options.signal
        });
        const durationMs = Date.now() - startedAt;
        const payload = (await readJsonSafely(response));
        if (!response.ok || payload.error) {
            const reason = payload.error?.message ? ` ${payload.error.message}` : "";
            if (response.status === 401) {
                throw new Error("NVIDIA authentication failed. Check NVIDIA_API_KEY.");
            }
            if (response.status === 429) {
                throw new Error(`NVIDIA rate limit hit for model "${options.model}".${reason}`);
            }
            throw new Error(`NVIDIA chat failed for model "${options.model}": HTTP ${response.status}.${reason}`);
        }
        const content = payload.choices?.[0]?.message?.content?.trim() ?? "";
        const finishReason = payload.choices?.[0]?.finish_reason;
        if (isTruncatedFinishReason(finishReason)) {
            throw new Error(`NVIDIA response for model "${options.model}" was truncated by max_tokens (${this.runtimeOptions.maxTokens}).`);
        }
        if (!content) {
            throw new Error("NVIDIA returned an empty response.");
        }
        return {
            content,
            telemetry: toTelemetry(payload, durationMs, options.model)
        };
    }
    async listModels() {
        this.assertConfigured();
        const response = await this.fetchNvidia("/models");
        const payload = (await readJsonSafely(response));
        if (!response.ok || payload.error) {
            const reason = payload.error?.message ? ` ${payload.error.message}` : "";
            throw new Error(`NVIDIA models failed with HTTP ${response.status}.${reason}`);
        }
        const models = [
            ...new Set(payload.data
                ?.map((model) => model.id?.trim())
                .filter((model) => Boolean(model))
                .filter(isLikelyNvidiaChatModel) ?? [])
        ].sort();
        return models.length > 0 ? models : [defaultNvidiaModel];
    }
    async fetchNvidia(path, init) {
        try {
            return await fetchWithTimeout(`${this.baseUrl}${path}`, init, {
                timeoutMs: init?.method === "POST" ? 90_000 : 8000,
                retries: init?.method === "POST" ? 2 : 1,
                label: `NVIDIA ${path}`
            });
        }
        catch (error) {
            const suffix = error instanceof Error ? ` ${error.message}` : "";
            throw new Error(`Cannot reach NVIDIA API at ${this.baseUrl}.${suffix}`);
        }
    }
    assertConfigured() {
        if (!this.apiKey) {
            throw new Error("NVIDIA API key missing. Set NVIDIA_API_KEY in PatchPilot config, .env, or your shell.");
        }
    }
}
const agentResponseFormat = {
    type: "json_schema",
    json_schema: {
        name: "patchpilot_agent_response",
        strict: true,
        schema: {
            anyOf: [
                {
                    type: "object",
                    additionalProperties: false,
                    required: ["action", "message", "tool_calls"],
                    properties: {
                        action: { const: "tools" },
                        message: { type: "string" },
                        tool_calls: {
                            type: "array",
                            minItems: 1,
                            maxItems: MAX_TOOL_CALLS_PER_RESPONSE,
                            items: {
                                type: "object",
                                additionalProperties: false,
                                required: ["name", "arguments"],
                                properties: {
                                    name: {
                                        enum: AGENT_TOOL_NAMES
                                    },
                                    arguments: {
                                        type: "object",
                                        additionalProperties: true
                                    }
                                }
                            }
                        }
                    }
                },
                {
                    type: "object",
                    additionalProperties: false,
                    required: ["action", "message"],
                    properties: {
                        action: { const: "final" },
                        message: { type: "string" }
                    }
                }
            ]
        }
    }
};
export function readNvidiaApiKey(env = process.env) {
    return env.NVIDIA_API_KEY?.trim() || env.PATCHPILOT_NVIDIA_API_KEY?.trim() || "";
}
function isLikelyNvidiaChatModel(model) {
    const normalizedModel = model.toLowerCase();
    if (/(^|\/)(embed|rerank|rank|guard|safety|audio|asr|tts|ocr|vlm|vision|bge|nvlm|kosmos|phi-3-vision)/.test(normalizedModel)) {
        return false;
    }
    return /(llama|mistral|mixtral|nemotron|qwen|deepseek|gpt-oss|gemma|phi|granite)/.test(normalizedModel);
}
function readNvidiaRuntimeOptions(env = process.env) {
    return {
        maxTokens: readPositiveInteger(env.PATCHPILOT_NUM_PREDICT, 8192),
        temperature: readTemperature(env.PATCHPILOT_TEMPERATURE, 0.1)
    };
}
function isTruncatedFinishReason(value) {
    return typeof value === "string" && /length|max_?tokens/i.test(value);
}
function toTelemetry(payload, durationMs, model) {
    const promptTokens = payload.usage?.prompt_tokens ?? 0;
    const responseTokens = payload.usage?.completion_tokens ?? 0;
    return attachTokenCost({
        promptTokens,
        cachedPromptTokens: 0,
        cacheWriteTokens: 0,
        responseTokens,
        totalTokens: payload.usage?.total_tokens ?? promptTokens + responseTokens,
        evalTokensPerSecond: responseTokens > 0 && durationMs > 0 ? responseTokens / (durationMs / 1000) : null,
        promptDurationMs: 0,
        responseDurationMs: durationMs,
        totalDurationMs: durationMs,
        tokenSource: "provider"
    }, "nvidia", model);
}
async function readJsonSafely(response) {
    try {
        return await response.json();
    }
    catch {
        return {};
    }
}
function readPositiveInteger(value, fallback) {
    const parsedValue = Number.parseInt(value ?? "", 10);
    return Number.isFinite(parsedValue) && parsedValue > 0 ? parsedValue : fallback;
}
function readTemperature(value, fallback) {
    const parsedValue = Number.parseFloat(value ?? "");
    return Number.isFinite(parsedValue) && parsedValue >= 0 && parsedValue <= 2 ? parsedValue : fallback;
}
//# sourceMappingURL=nvidia.js.map