"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AICore = void 0;
const githubModelsClient_1 = require("../../utils/githubModelsClient");
const openRouterClient_1 = require("../../utils/openRouterClient");
const geminiClient_1 = require("../../utils/geminiClient");
const XAIClient_1 = require("../../clients/XAIClient");
const claudeAgentClient_1 = require("../../utils/claudeAgentClient");
const BrainClient_1 = require("../brain/BrainClient");
const TokenCostModel_1 = require("./TokenCostModel");
const chalk_1 = __importDefault(require("chalk"));
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const dotenv = __importStar(require("dotenv"));
const dotenvExpand = __importStar(require("dotenv-expand"));
/**
 * AICore: The centralized logic for AI provider selection,
 * token injection, and unified interface for all agents.
 */
class AICore {
    constructor(config) {
        this.providers = new Map();
        this.config = config;
        this.refreshProviders();
    }
    static getInstance(config) {
        if (!AICore.instance) {
            if (!config) {
                throw new Error("AICore must be initialized with config first");
            }
            AICore.instance = new AICore(config);
        }
        return AICore.instance;
    }
    loadEnvironmentVariables() {
        const cwd = this.config.workingDirectory || process.cwd();
        const candidates = [
            path.join(cwd, ".mycontext", ".env.local"),
            path.join(cwd, ".mycontext", ".env"),
            path.join(cwd, ".env.local"),
            path.join(cwd, ".env"),
        ];
        for (const p of candidates) {
            if (fs.existsSync(p)) {
                const result = dotenv.config({ path: p, override: true });
                dotenvExpand.expand(result);
            }
        }
    }
    refreshProviders() {
        // Reload env vars before refreshing
        this.loadEnvironmentVariables();
        // 1. Claude Agent SDK
        const claude = new claudeAgentClient_1.ClaudeAgentClient();
        if (claude.hasApiKey())
            this.providers.set("claude", claude);
        // 2. GitHub Models (Primary High Quality)
        const github = new githubModelsClient_1.GitHubModelsClient();
        if (github.hasApiKey())
            this.providers.set("github", github);
        // 3. OpenRouter
        const openrouter = new openRouterClient_1.OpenRouterClient();
        if (openrouter.hasApiKey())
            this.providers.set("openrouter", openrouter);
        // 4. Gemini
        const gemini = new geminiClient_1.GeminiClient();
        if (gemini.hasApiKey())
            this.providers.set("gemini", gemini);
        // 5. XAI
        const xai = new XAIClient_1.XAIClient();
        if (xai.hasApiKey())
            this.providers.set("xai", xai);
    }
    /**
     * Force a complete refresh of environment variables and providers.
     * Useful when you need to ensure the latest .env values are loaded.
     */
    forceRefresh() {
        // Clear existing providers
        this.providers.clear();
        // Clear exhausted models circuit breaker
        AICore.exhaustedModels.clear();
        // Reload everything
        this.refreshProviders();
    }
    /**
     * Check if any AI provider has a valid key configured
     */
    hasAnyProvider() {
        return this.providers.size > 0;
    }
    getClient(name) {
        return this.providers.get(name);
    }
    getBestClient() {
        const preferred = process.env.PREFERRED_PROVIDER || process.env.MYCONTEXT_PREFERRED_PROVIDER || this.config.preferredProvider;
        if (preferred && this.providers.has(preferred)) {
            const client = this.providers.get(preferred);
            if (client)
                return client;
        }
        // Priority Chain with capability fallbacks and circuit breaking
        const priority = [
            { provider: "claude", model: "claude-3-5-sonnet-latest" },
            { provider: "github", model: "gpt-4o" },
            { provider: "github", model: "gpt-4o-mini" },
            { provider: "github", model: "Llama-3.3-70B-Instruct" },
            { provider: "github", model: "Phi-4" },
            { provider: "gemini", model: "gemini-2.0-flash" },
            { provider: "xai", model: "grok-beta" }
        ];
        for (const entry of priority) {
            const client = this.providers.get(entry.provider);
            const modelId = `${entry.provider}:${entry.model || "default"}`;
            if (client && !AICore.exhaustedModels.has(modelId)) {
                return client;
            }
        }
        // Default to first available if everything is "exhausted" (last resort)
        for (const client of this.providers.values()) {
            return client;
        }
        throw new Error("No AI providers available. Please configure API keys.");
    }
    /**
     * High-level generation utility that handles formatting and basic retries
     */
    async generateStructuredText(prompt, schema, options) {
        const enhancedPrompt = `${prompt}\n\nIMPORTANT: You MUST return ONLY valid JSON that conforms to the following schema:\n${schema}\n\nDo not include code blocks, just the raw JSON string.`;
        let lastResponse = "";
        const MAX_ATTEMPTS = 2;
        for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
            try {
                const response = await this.generateText(attempt === 1 ? enhancedPrompt : `The following JSON response had a syntax error. Please fix the syntax and return ONLY the valid JSON object:\n\n${lastResponse}`, options);
                lastResponse = response;
                // Extract everything between the first '{' and the last '}' 
                const start = response.indexOf("{");
                const end = response.lastIndexOf("}");
                if (start === -1 || end === -1) {
                    throw new Error("Could not find JSON object in response");
                }
                const cleanJson = response.substring(start, end + 1);
                return JSON.parse(cleanJson);
            }
            catch (e) {
                const errorMessage = e instanceof Error ? e.message : String(e);
                // If it's a safety filter or API-level error, don't try to repair it as there's no JSON to fix
                const isFilter = errorMessage.includes("content management policy");
                if (isFilter || attempt === MAX_ATTEMPTS) {
                    throw e; // Bubble up 429s and filters to prompt the parent fallback
                }
                console.warn(chalk_1.default.yellow(`[AICore] JSON parse failed on attempt ${attempt}. Retrying with repair prompt...`));
            }
        }
        throw new Error("AI failed to produce structured JSON context.");
    }
    /**
     * Record token usage to the Brain — fire-and-forget (never blocks generation)
     */
    recordUsage(provider, model, inputTokens, outputTokens) {
        const costUSD = (0, TokenCostModel_1.calculateCostUSD)(model, inputTokens, outputTokens);
        const brain = BrainClient_1.BrainClient.getInstance(this.config.workingDirectory);
        brain
            .recordTokenUsage("AICore", provider, model, { inputTokens, outputTokens, totalTokens: inputTokens + outputTokens }, costUSD)
            .catch(() => { });
    }
    /**
     * Proxy for generateText with automatic fallback and circuit breaking
     */
    async generateText(prompt, options = {}) {
        let sequence = [
            { provider: "claude", model: "claude-3-5-sonnet-latest" },
            { provider: "github", model: "gpt-4o" },
            { provider: "github", model: "gpt-4o-mini" },
            { provider: "github", model: "o1-mini" },
            { provider: "github", model: "o1-preview" },
            { provider: "github", model: "Llama-3.3-70B-Instruct" },
            { provider: "github", model: "Meta-Llama-3.1-405B-Instruct" },
            { provider: "github", model: "Meta-Llama-3.1-70B-Instruct" },
            { provider: "github", model: "Meta-Llama-3.1-8B-Instruct" },
            { provider: "github", model: "Meta-Llama-3-70B-Instruct" },
            { provider: "github", model: "Meta-Llama-3-8B-Instruct" },
            { provider: "github", model: "Phi-4" },
            { provider: "gemini", model: "gemini-2.0-flash" },
            { provider: "xai", model: "grok-beta" },
            { provider: "openrouter", model: "meta-llama/llama-3.3-70b-instruct:free" },
        ];
        const preferred = process.env.PREFERRED_PROVIDER || process.env.MYCONTEXT_PREFERRED_PROVIDER || this.config.preferredProvider;
        if (preferred) {
            const preferredSequence = sequence.filter(entry => entry.provider === preferred);
            const remainingSequence = sequence.filter(entry => entry.provider !== preferred);
            // If preferred is openrouter and we don't have a default model in sequence, add one
            if (preferred === "openrouter" && preferredSequence.length === 0) {
                preferredSequence.push({ provider: "openrouter", model: "meta-llama/llama-3.3-70b-instruct:free" });
            }
            sequence = [...preferredSequence, ...remainingSequence];
        }
        let lastError = null;
        for (const entry of sequence) {
            const client = this.providers.get(entry.provider);
            const modelId = `${entry.provider}:${entry.model}`;
            if (!client || AICore.exhaustedModels.has(modelId)) {
                if (process.env.DEBUG || process.env.VERBOSE) {
                    if (!client)
                        console.log(chalk_1.default.gray(`[AICore] Skipping ${modelId} (No API key)`));
                    else
                        console.log(chalk_1.default.gray(`[AICore] Skipping ${modelId} (Exhausted)`));
                }
                continue;
            }
            // Add a retry loop for transient rate limits
            const maxRetries = 2;
            let delayMs = 1500;
            for (let attempt = 0; attempt <= maxRetries; attempt++) {
                try {
                    if (process.env.DEBUG || process.env.VERBOSE) {
                        console.log(chalk_1.default.gray(`[AICore] Attempting ${modelId} (Attempt ${attempt + 1})...`));
                    }
                    const generationOptions = { ...options, model: entry.model };
                    let responseContent = "";
                    // Use generateTextResult when available for metadata
                    if (typeof client.generateTextResult === "function") {
                        const result = await client.generateTextResult(prompt, generationOptions);
                        if (result.usage) {
                            this.recordUsage(result.provider ?? client.clientType, result.model ?? entry.model, result.usage.inputTokens, result.usage.outputTokens);
                        }
                        responseContent = result.content;
                    }
                    else {
                        responseContent = await client.generateText(prompt, generationOptions);
                    }
                    return responseContent;
                }
                catch (error) {
                    lastError = error;
                    const message = error.message || String(error);
                    // Check if it's a rate limit error (429)
                    const isRateLimit = message.includes("429") || message.toLowerCase().includes("rate limit") ||
                        message.includes("daily quota") || message.includes("86400s exceeded") ||
                        message.toLowerCase().includes("reached quota");
                    if (isRateLimit && attempt < maxRetries) {
                        console.log(chalk_1.default.yellow(`⚠️  Model ${modelId} rate limited. Retrying in ${(delayMs / 1000).toFixed(1)}s (Attempt ${attempt + 1}/${maxRetries})...`));
                        await new Promise(resolve => setTimeout(resolve, delayMs));
                        delayMs *= 2; // Exponential backoff
                        continue;
                    }
                    // If it's a rate limit or quota error and we ran out of retries, switch models
                    if (isRateLimit || message.toLowerCase().includes("unknown model")) {
                        const isDaily = message.includes("daily quota") || message.includes("86400s exceeded") || message.toLowerCase().includes("reached quota");
                        const limitType = isDaily ? "DAILY QUOTA" : "MINUTE LIMIT";
                        if (process.env.DEBUG || process.env.VERBOSE) {
                            console.log(chalk_1.default.yellow(`⚠️  Model ${modelId} reached ${limitType}, switching to next available...`));
                        }
                        // Circuit break for the remainder of the process if it's a daily limit
                        if (isDaily) {
                            AICore.exhaustedModels.add(modelId);
                        }
                        break; // Break the retry loop to try the next model in sequence
                    }
                    // For auth errors, skip the whole provider
                    if (message.includes("401") || message.toLowerCase().includes("unauthorized") || message.toLowerCase().includes("invalid api key")) {
                        console.log(chalk_1.default.red(`❌ Auth failure on ${modelId}. Check your API keys.`));
                        AICore.exhaustedModels.add(modelId);
                        break; // Break retry loop
                    }
                    console.log(chalk_1.default.gray(`ℹ️  Model ${modelId} error: ${message.substring(0, 80)}${message.length > 80 ? '...' : ''}`));
                    break; // Break retry loop for other errors
                }
            }
        }
        if (lastError)
            throw lastError;
        throw new Error("No AI providers available - check API keys.");
    }
    /**
     * Proxy for generateVisionText with automatic fallback
     */
    async generateVisionText(prompt, imagePath, options = {}) {
        const clients = this.getVisionClients();
        let lastError = null;
        for (const client of clients) {
            try {
                return await client.generateVisionText(prompt, imagePath, options);
            }
            catch (error) {
                lastError = error;
                const message = error.message || String(error);
                if (message.includes("429") || message.toLowerCase().includes("rate limit"))
                    continue;
            }
        }
        if (lastError)
            throw lastError;
        throw new Error("No AI providers with vision support available");
    }
    /**
     * Proxy for generateImage
     */
    async generateImage(prompt, outputPath, options = {}) {
        const clients = this.getAvailableClients();
        let lastError = null;
        for (const client of clients) {
            try {
                return await client.generateImage(prompt, outputPath, options);
            }
            catch (error) {
                lastError = error;
                const message = error.message || String(error);
                if (message.includes("429") || message.toLowerCase().includes("rate limit"))
                    continue;
            }
        }
        if (lastError)
            throw lastError;
        throw new Error("No AI providers with image generation support available");
    }
    getAvailableClients() {
        const priority = ["claude", "github", "openrouter", "gemini", "xai"];
        return priority
            .map(type => this.providers.get(type))
            .filter((client) => !!client && client.hasApiKey());
    }
    getVisionClients() {
        const visionPriority = ["gemini", "claude", "openrouter", "xai"];
        return visionPriority
            .map(type => this.providers.get(type))
            .filter((client) => !!client && client.hasApiKey());
    }
    async generateComponent(prompt, context, options = {}) {
        return this.generateText(prompt, options);
    }
    async generateComponentRefinement(componentCode, prompt, context, options = {}) {
        const fullPrompt = `Existing Code:\n\`\`\`tsx\n${componentCode}\n\`\`\`\n\nRequest: ${prompt}`;
        return this.generateText(fullPrompt, options);
    }
    async getLivingContext() {
        try {
            const contextPath = path.join(this.config.workingDirectory, ".mycontext", "context.json");
            if (await fs.pathExists(contextPath)) {
                return await fs.readJson(contextPath);
            }
            return null;
        }
        catch (error) {
            return null;
        }
    }
    async saveLivingContext(context) {
        try {
            const contextPath = path.join(this.config.workingDirectory, ".mycontext", "context.json");
            await fs.ensureDir(path.dirname(contextPath));
            await fs.writeJson(contextPath, context, { spaces: 2 });
        }
        catch (error) {
            throw error;
        }
    }
    async updateLivingContext(updates) {
        const contextPath = path.join(this.config.workingDirectory, ".mycontext", "context.json");
        const existingContext = (await this.getLivingContext()) || {};
        const updatedContext = { ...existingContext, ...updates };
        await fs.writeJson(contextPath, updatedContext, { spaces: 2 });
    }
}
exports.AICore = AICore;
// Circuit Breaker: Track models that hit hard rate limits
AICore.exhaustedModels = new Set();
//# sourceMappingURL=AICore.js.map