/**
 * src/models/nvidia.ts
 * NVIDIA NIM Provider — The ONLY AI provider for Al Moasis
 * Uses NVIDIA's free API for Qwen 2.5 72B and other models
 */
import { config, MODELS } from '../config.js';
import { createChildLogger } from '../utils/logger.js';
import { ModelError } from './provider.js';
const log = createChildLogger('models:nvidia');
// Defaults to the TaseesAI hosted proxy (taseesai-nim-proxy on Vercel) which
// injects the real NIM key server-side. Override with NVIDIA_NIM_BASE_URL +
// NVIDIA_NIM_API_KEY to call NVIDIA directly.
const NVIDIA_NIM_BASE_URL = config.NVIDIA_NIM_BASE_URL;
// ─── Provider ────────────────────────────────────────────────────────
export class NVIDIAProvider {
    name = 'nvidia-nim';
    model = MODELS.primary.model;
    async chat(messages, options = {}) {
        const startTime = Date.now();
        // Pass messages through with structural fields preserved (tool_call_id,
        // tool_calls). Treat unknown roles as 'user' for safety.
        const nimMessages = messages.map(msg => {
            const m = msg;
            const role = m.role === 'system' || m.role === 'assistant' || m.role === 'tool' ? m.role : 'user';
            return {
                role,
                content: m.content ?? null,
                ...(m.name ? { name: m.name } : {}),
                ...(m.tool_call_id ? { tool_call_id: m.tool_call_id } : {}),
                ...(m.tool_calls ? { tool_calls: m.tool_calls } : {}),
            };
        });
        const body = {
            model: this.model,
            messages: nimMessages,
            temperature: options.temperature ?? 0.2,
            max_tokens: options.maxTokens ?? 1024,
        };
        if (options.topP !== undefined)
            body.top_p = options.topP;
        if (options.stop)
            body.stop = options.stop;
        if (options.responseFormat?.type === 'json_object') {
            body.response_format = { type: 'json_object' };
        }
        // Forward tools so the LLM can actually call them. Without this, the model
        // doesn't know the tools exist and just describes them in markdown text
        // (which is what we used to see — agent saying it would call `write_file`
        // but never actually invoking it).
        if (options.tools && options.tools.length > 0) {
            body.tools = options.tools.map((t) => ({
                type: 'function',
                function: {
                    name: t.function.name,
                    description: t.function.description,
                    parameters: t.function.parameters,
                },
            }));
            if (options.toolChoice)
                body.tool_choice = options.toolChoice;
        }
        const url = `${NVIDIA_NIM_BASE_URL}/chat/completions`;
        log.debug({ model: this.model, messageCount: messages.length }, 'Calling NVIDIA NIM API');
        const requestHeaders = {
            'Content-Type': 'application/json',
        };
        // Direct-to-NVIDIA mode: include user's own key. Default proxy mode: omit
        // header — the proxy injects its server-side key.
        if (config.NVIDIA_NIM_API_KEY) {
            requestHeaders['Authorization'] = `Bearer ${config.NVIDIA_NIM_API_KEY}`;
        }
        else if (process.env.ALMOASIS_LICENSE_KEY) {
            requestHeaders['x-almoasis-license'] = process.env.ALMOASIS_LICENSE_KEY;
        }
        // Kimi K2.6 is a thinking model — long-form turns (contracts, plans)
        // can run 60-120s in reasoning before content. Bumped from 60s to 240s
        // for the demo so the gateway doesn't E503 on legitimate slow turns.
        const timeoutMs = options.timeoutMs ?? 240_000;
        let response;
        try {
            response = await fetch(url, {
                method: 'POST',
                headers: requestHeaders,
                body: JSON.stringify(body),
                signal: AbortSignal.timeout(timeoutMs),
            });
        }
        catch (err) {
            // Network failures, DNS, connection-reset, AbortSignal-driven timeouts
            // all land here. None of them are HTTP errors so we synthesize one
            // with a 504-ish status so the failover/retry layer can recognize them
            // as retryable. Without this they bubble up as plain Errors and the
            // retry guard (`err instanceof ModelError && err.isRetryable`) skips
            // them — that's why "build me a website" turns died on the first slow
            // response instead of getting a second shot.
            const isAbort = err?.name === 'AbortError'
                || err?.name === 'TimeoutError';
            const status = isAbort ? 504 : 502;
            const message = err instanceof Error ? err.message : String(err);
            const latencyMs = Date.now() - startTime;
            log.error({ model: this.model, status, message, latencyMs, timeoutMs }, 'NVIDIA NIM fetch failed');
            throw new ModelError(`NVIDIA NIM ${isAbort ? 'timed out' : 'network failure'} after ${latencyMs}ms: ${message}`, status, this.model);
        }
        if (!response.ok) {
            const errorText = await response.text().catch(() => 'Unknown error');
            const latencyMs = Date.now() - startTime;
            log.error({ model: this.model, status: response.status, errorText, latencyMs }, 'NVIDIA NIM API failed');
            throw new ModelError(`NVIDIA NIM error: ${response.status} — ${errorText}`, response.status, this.model);
        }
        const data = (await response.json());
        const latencyMs = Date.now() - startTime;
        const choice = data.choices?.[0];
        if (!choice) {
            throw new ModelError('NVIDIA NIM returned no choices', 500, this.model);
        }
        const content = choice.message.content ?? '';
        // Surface tool calls back to the caller. brain.ts walks them in the tool
        // loop and re-invokes the model with the tool results.
        const toolCalls = choice.message.tool_calls?.map((tc) => ({
            id: tc.id,
            type: 'function',
            function: { name: tc.function.name, arguments: tc.function.arguments },
        }));
        const result = {
            content,
            tokensInput: data.usage?.prompt_tokens ?? 0,
            tokensOutput: data.usage?.completion_tokens ?? 0,
            model: data.model || this.model,
            latencyMs,
            finishReason: choice.finish_reason,
            ...(toolCalls && toolCalls.length > 0 ? { toolCalls } : {}),
        };
        log.debug({ model: this.model, tokensIn: result.tokensInput, tokensOut: result.tokensOutput, latencyMs }, 'NVIDIA NIM call completed');
        return result;
    }
}
// ─── Singleton Export ────────────────────────────────────────────────
export const nvidia = new NVIDIAProvider();
//# sourceMappingURL=nvidia.js.map