"use strict";
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2024-2026 Gracker (Chris)
// This file is part of SmartPerfetto. See LICENSE for details.
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateAiComparisonConclusion = generateAiComparisonConclusion;
const claude_agent_sdk_1 = require("@anthropic-ai/claude-agent-sdk");
const openAiComplexityClassifier_1 = require("../agentOpenAI/openAiComplexityClassifier");
const openAiConfig_1 = require("../agentOpenAI/openAiConfig");
const claudeConfig_1 = require("../agentv3/claudeConfig");
const outputLanguage_1 = require("../agentv3/outputLanguage");
const strategyLoader_1 = require("../agentv3/strategyLoader");
const runtimeSelection_1 = require("../agentRuntime/runtimeSelection");
function truncateForPrompt(value, maxLength) {
    if (value.length <= maxLength)
        return value;
    return `${value.slice(0, maxLength)}\n...[truncated ${value.length - maxLength} chars]`;
}
function buildPrompt(input) {
    const template = (0, strategyLoader_1.loadPromptTemplate)('comparison-conclusion');
    if (!template) {
        throw new Error('comparison-conclusion prompt template is missing');
    }
    return (0, strategyLoader_1.renderTemplate)(template, {
        query: input.query,
        outputLanguage: (0, outputLanguage_1.outputLanguageDisplayName)((0, outputLanguage_1.parseOutputLanguage)(process.env.SMARTPERFETTO_OUTPUT_LANGUAGE)),
        matrixJson: truncateForPrompt(JSON.stringify(input.result.matrix, null, 2), 40000),
        deterministicFacts: input.result.conclusion.verifiedFacts.join('\n') || '(none)',
        uncertainty: input.result.conclusion.uncertainty.join('\n') || '(none)',
    });
}
function arrayOfStrings(value) {
    if (!Array.isArray(value))
        return [];
    return value
        .filter((item) => typeof item === 'string')
        .map(item => item.trim())
        .filter(Boolean)
        .slice(0, 20);
}
function extractJsonObject(text) {
    const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
    const candidate = fenced?.[1] ?? text;
    const start = candidate.indexOf('{');
    const end = candidate.lastIndexOf('}');
    if (start < 0 || end <= start)
        return null;
    try {
        return JSON.parse(candidate.slice(start, end + 1));
    }
    catch {
        return null;
    }
}
function parseAiConclusion(text, fallback, model) {
    const parsed = extractJsonObject(text);
    if (!parsed)
        return null;
    const verifiedFacts = arrayOfStrings(parsed.verifiedFacts);
    const inferences = arrayOfStrings(parsed.inferences);
    const recommendations = arrayOfStrings(parsed.recommendations);
    const uncertainty = arrayOfStrings(parsed.uncertainty);
    if (verifiedFacts.length === 0 &&
        inferences.length === 0 &&
        recommendations.length === 0 &&
        uncertainty.length === 0) {
        return null;
    }
    return {
        source: 'ai',
        ...(model ? { model } : {}),
        generatedAt: Date.now(),
        verifiedFacts: verifiedFacts.length > 0 ? verifiedFacts : fallback.verifiedFacts,
        inferences,
        recommendations,
        uncertainty: [...new Set([...fallback.uncertainty, ...uncertainty])],
    };
}
function fallbackConclusion(fallback, reason) {
    return {
        ...fallback,
        source: 'deterministic',
        generatedAt: Date.now(),
        uncertainty: [...new Set([
                ...fallback.uncertainty,
                `AI comparison conclusion was not generated: ${reason}`,
            ])],
    };
}
async function completeWithOpenAI(input) {
    const config = (0, openAiConfig_1.loadOpenAIConfig)(input.providerId, input.providerScope);
    if (!(0, openAiConfig_1.hasOpenAICredentials)(input.providerId, input.providerScope)) {
        throw new Error('OpenAI credentials are not configured');
    }
    const controller = new AbortController();
    const timeoutMs = config.classifierTimeoutMs;
    const timer = setTimeout(() => controller.abort(), timeoutMs);
    try {
        const response = await fetch((0, openAiComplexityClassifier_1.buildChatCompletionsUrl)(config.baseURL || 'https://api.openai.com/v1'), {
            method: 'POST',
            signal: controller.signal,
            headers: {
                'Content-Type': 'application/json',
                ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),
            },
            body: JSON.stringify({
                model: config.lightModel,
                messages: [{ role: 'user', content: input.prompt }],
                temperature: 0.1,
                max_tokens: 1200,
            }),
        });
        if (!response.ok) {
            throw new Error(`OpenAI comparison conclusion HTTP ${response.status}`);
        }
        const data = (await response.json());
        return {
            text: data.choices?.[0]?.message?.content || '',
            model: config.lightModel,
        };
    }
    finally {
        clearTimeout(timer);
    }
}
async function completeWithClaude(input) {
    const baseConfig = (0, claudeConfig_1.loadClaudeConfig)();
    const config = (0, claudeConfig_1.resolveRuntimeConfig)(baseConfig, input.providerId, input.providerScope);
    const env = (0, claudeConfig_1.createSdkEnv)(input.providerId, input.providerScope);
    if (!(0, claudeConfig_1.hasClaudeCredentials)(env)) {
        throw new Error('Claude credentials are not configured');
    }
    const binaryOption = (0, claudeConfig_1.getSdkBinaryOption)(env);
    const stream = (0, claude_agent_sdk_1.query)({
        prompt: input.prompt,
        options: {
            model: config.lightModel || config.model,
            maxTurns: 1,
            includePartialMessages: false,
            permissionMode: 'bypassPermissions',
            allowDangerouslySkipPermissions: true,
            cwd: config.cwd,
            effort: 'low',
            env,
            ...binaryOption,
            stderr: (data) => {
                console.warn(`[ComparisonConclusion] Claude SDK stderr: ${data.trimEnd()}`);
            },
        },
    });
    let text = '';
    for await (const message of stream) {
        if (message.type === 'assistant' && Array.isArray(message.message?.content)) {
            for (const block of message.message.content) {
                if (block.type === 'text' && typeof block.text === 'string') {
                    text += block.text;
                }
            }
        }
        if (message.type === 'result' && typeof message.result === 'string') {
            text = message.result || text;
        }
    }
    return {
        text,
        model: config.lightModel || config.model,
    };
}
class DefaultComparisonConclusionClient {
    async complete(input) {
        const selection = (0, runtimeSelection_1.resolveAgentRuntimeSelection)(input.providerId, undefined, input.providerScope);
        if (selection.kind === 'openai-agents-sdk') {
            return completeWithOpenAI(input);
        }
        return completeWithClaude(input);
    }
}
async function generateAiComparisonConclusion(input) {
    const fallback = input.result.conclusion;
    if (process.env.SMARTPERFETTO_COMPARISON_AI_DISABLED === 'true') {
        return fallbackConclusion(fallback, 'disabled by SMARTPERFETTO_COMPARISON_AI_DISABLED');
    }
    let prompt;
    try {
        prompt = buildPrompt(input);
    }
    catch (error) {
        return fallbackConclusion(fallback, error instanceof Error ? error.message : String(error));
    }
    const client = input.client || new DefaultComparisonConclusionClient();
    try {
        const output = await client.complete({
            prompt,
            providerId: input.providerId,
            providerScope: input.providerScope,
        });
        const parsed = parseAiConclusion(output.text, fallback, output.model);
        if (!parsed) {
            return fallbackConclusion(fallback, 'AI response did not contain valid conclusion JSON');
        }
        return parsed;
    }
    catch (error) {
        return fallbackConclusion(fallback, error instanceof Error ? error.message : String(error));
    }
}
//# sourceMappingURL=comparisonAiConclusionService.js.map