import { loadKnowledgeConfig, extractNumberRule } from "./knowledge_config_loader.js";
import { debug } from "./logger.js";
debug("llm_gateway", "LLM 统一网关模块已加载");
// ── 操作配置表 ──
/** 各 AI 操作类型的 Token 估算和级别配置 */
const OPERATION_PROFILES = {
    intent_refinement: { tier: "medium", estimated_tokens: 2000, description: "意图理解深化" },
    solution_brainstorm: { tier: "heavy", estimated_tokens: 4000, description: "方案脑暴推演" },
    verification_reasoning: { tier: "heavy", estimated_tokens: 3000, description: "复杂验证推理" },
    code_generation: { tier: "heavy", estimated_tokens: 5000, description: "代码生成" },
    test_generation: { tier: "medium", estimated_tokens: 3000, description: "测试用例生成" },
    review_deep_analysis: { tier: "heavy", estimated_tokens: 4000, description: "深度审查分析" },
    debug_analysis: { tier: "medium", estimated_tokens: 2500, description: "排障推理" },
};
/**
 * 从知识库 决策网关.md 读取阈值，文件不存在时使用硬编码兜底。
 * @param projectPath - 可选的项目路径
 * @returns 网关配置参数
 */
function loadGatewayConfig(projectPath) {
    const config = loadKnowledgeConfig("patterns/core/决策网关.md", projectPath);
    const body = config?.body ?? "";
    return {
        budget_total: extractNumberRule(body, "Budget_Total") ?? 100_000,
        task_budget: extractNumberRule(body, "Task_Budget") ?? 30_000,
        circuit_breaker_ratio: extractNumberRule(body, "Circuit_Breaker_Ratio") ?? 0.9,
        heartbeat_interval: extractNumberRule(body, "Heartbeat_Interval") ?? 5000,
        confidence_threshold: extractNumberRule(body, "Confidence_Threshold") ?? 0.95,
    };
}
const DEFAULT_CONFIG = loadGatewayConfig();
// ── 网关核心 ──
/**
 * LLM 统一网关 — 管理 Token 预算、熔断和调用审计。
 */
export class LLMGateway {
    budgetTotal;
    budgetUsed = 0;
    taskBudget;
    taskTokensUsed = 0;
    currentTaskId = null;
    callCount = 0;
    blockedCount = 0;
    auditLog = [];
    circuitState = "closed";
    /** 熔断器进入 open 状态的时间戳，用于 half-open 超时恢复 */
    circuitOpenedAt = 0;
    /** half-open 状态下允许的探测请求次数 */
    halfOpenProbeBudget = 1;
    /** half-open 探测已用次数（当前周期） */
    halfOpenProbeUsed = 0;
    /** 当前时间函数 — 生产环境用 Date.now，测试可注入 */
    now;
    heartbeatTimer = null;
    heartbeatCallback = null;
    operationStart = 0;
    currentOperation = null;
    config = DEFAULT_CONFIG;
    /**
     * 创建 LLM 网关实例。
     * @param budget - 可选，Token 总预算，默认从配置读取
     * @param taskBudget - 可选，单任务 Token 预算，默认从配置读取
     */
    constructor(budget, taskBudget) {
        const resolvedBudget = budget ?? this.config.budget_total;
        const resolvedTaskBudget = taskBudget ?? this.config.task_budget;
        // 安全校验: 预算必须为有限正数，防止 NaN/Infinity/负值导致熔断逻辑失效
        if (!Number.isFinite(resolvedBudget) || resolvedBudget < 0) {
            throw new RangeError(`LLMGateway: budgetTotal 必须为有限非负数，收到 ${resolvedBudget}`);
        }
        if (!Number.isFinite(resolvedTaskBudget) || resolvedTaskBudget < 0) {
            throw new RangeError(`LLMGateway: taskBudget 必须为有限非负数，收到 ${resolvedTaskBudget}`);
        }
        this.budgetTotal = resolvedBudget;
        this.taskBudget = resolvedTaskBudget;
        this.now = () => Date.now();
    }
    /** 设置心跳回调（用于 stdio 输出进度） */
    setHeartbeatCallback(cb) {
        this.heartbeatCallback = cb;
    }
    /**
     * 开始新任务的 Token 计量。
     * @param taskId - 任务 ID
     */
    beginTask(taskId) {
        this.currentTaskId = taskId;
        this.taskTokensUsed = 0;
        debug("llm_gateway", `任务 ${taskId} 开始，Token 计量已重置`);
    }
    /** 结束任务计量，停止心跳 */
    endTask() {
        this.currentTaskId = null;
        this.taskTokensUsed = 0;
        this.stopHeartbeat();
    }
    /**
     * 请求 AI 操作许可 — 检查 Token 预算和熔断状态。
     * 被拦截时返回原因说明。
     *
     * @param operation - AI 操作类型
     * @param taskId - 可选的任务 ID
     * @returns 许可结果，包含是否允许、剩余预算和拦截原因
     */
    request(operation, taskId) {
        const profile = OPERATION_PROFILES[operation];
        const tid = taskId ?? this.currentTaskId ?? undefined;
        const entry = {
            operation,
            estimated_tokens: profile.estimated_tokens,
            timestamp: new Date().toISOString(),
            blocked: false,
            task_id: tid,
        };
        // 单任务阈值检查
        if (this.taskTokensUsed + profile.estimated_tokens > this.taskBudget) {
            this.blockedCount++;
            entry.blocked = true;
            this.auditLog.push(entry);
            debug("llm_gateway", `单任务 Token 阈值触发 — ${profile.description} 被拦截`);
            return {
                allowed: false,
                estimated_tokens: profile.estimated_tokens,
                remaining_budget: this.budgetTotal - this.budgetUsed,
                remaining_task_budget: this.taskBudget - this.taskTokensUsed,
                reason: `单任务 Token 阈值触发: 当前任务已使用 ${this.taskTokensUsed}/${this.taskBudget}，${profile.description} 需要 ~${profile.estimated_tokens}。建议拆分任务或减少范围`,
            };
        }
        // 全局熔断检查（支持 half-open 探测恢复）
        // 仅在从 closed/half-open 转换时记录 openedAt，避免重置计时器
        if ((this.budgetUsed / this.budgetTotal) >= this.config.circuit_breaker_ratio) {
            if (this.circuitState !== "open") {
                this.circuitOpenedAt = this.now();
            }
            this.circuitState = "open";
        }
        if (this.circuitState === "open") {
            // 检查是否超过 half-open 超时（默认 60 秒）
            const halfOpenTimeout = 60_000; // 60 秒后允许探测
            if (this.now() - this.circuitOpenedAt >= halfOpenTimeout) {
                this.circuitState = "half-open";
                this.halfOpenProbeUsed = 0;
                debug("llm_gateway", "熔断器进入 half-open 状态，允许探测请求");
            }
        }
        if (this.circuitState === "open") {
            this.blockedCount++;
            entry.blocked = true;
            this.auditLog.push(entry);
            debug("llm_gateway", `全局熔断器已开启 — ${profile.description} 被拦截`);
            return {
                allowed: false,
                estimated_tokens: profile.estimated_tokens,
                remaining_budget: this.budgetTotal - this.budgetUsed,
                remaining_task_budget: this.taskBudget - this.taskTokensUsed,
                reason: `Token 预告警: 已使用 ${this.budgetUsed}/${this.budgetTotal}（${((this.budgetUsed / this.budgetTotal) * 100).toFixed(0)}%）。${profile.description} 需要 ~${profile.estimated_tokens} Token，超出安全阈值`,
            };
        }
        if (this.circuitState === "half-open") {
            // half-open 状态下只允许有限次探测请求
            if (this.halfOpenProbeUsed >= this.halfOpenProbeBudget) {
                this.blockedCount++;
                entry.blocked = true;
                this.auditLog.push(entry);
                debug("llm_gateway", `half-open 探测配额已用尽 — ${profile.description} 被拦截`);
                return {
                    allowed: false,
                    estimated_tokens: profile.estimated_tokens,
                    remaining_budget: this.budgetTotal - this.budgetUsed,
                    remaining_task_budget: this.taskBudget - this.taskTokensUsed,
                    reason: `half-open 探测配额已用尽，等待探测结果后决定恢复或继续熔断`,
                };
            }
            this.halfOpenProbeUsed++;
            debug("llm_gateway", `half-open 探测请求放行 (${this.halfOpenProbeUsed}/${this.halfOpenProbeBudget})`);
        }
        if (this.budgetUsed + profile.estimated_tokens > this.budgetTotal) {
            this.blockedCount++;
            entry.blocked = true;
            this.auditLog.push(entry);
            debug("llm_gateway", `Token 预算不足 — ${profile.description} 被拦截`);
            return {
                allowed: false,
                estimated_tokens: profile.estimated_tokens,
                remaining_budget: this.budgetTotal - this.budgetUsed,
                remaining_task_budget: this.taskBudget - this.taskTokensUsed,
                reason: `Token 预算不足: 剩余 ${this.budgetTotal - this.budgetUsed}，需要 ~${profile.estimated_tokens}`,
            };
        }
        this.budgetUsed += profile.estimated_tokens;
        this.taskTokensUsed += profile.estimated_tokens;
        this.callCount++;
        this.auditLog.push(entry);
        // 重型操作启动心跳
        if (profile.tier === "heavy") {
            this.startHeartbeat(operation);
        }
        debug("llm_gateway", `${profile.description} 已放行（预估 ${profile.estimated_tokens} Token）`);
        return {
            allowed: true,
            estimated_tokens: profile.estimated_tokens,
            remaining_budget: this.budgetTotal - this.budgetUsed,
            remaining_task_budget: this.taskBudget - this.taskTokensUsed,
        };
    }
    /**
     * 置信度闸口: 低于阈值禁止执行代码，必须调用 sf_brainstorm。
     * @param confidence - 当前置信度
     * @returns 闸口结果，包含是否允许和阈值
     */
    confidenceGate(confidence) {
        const threshold = this.config.confidence_threshold;
        if (confidence < threshold) {
            debug("llm_gateway", `置信度 ${confidence.toFixed(2)} 低于阈值 ${threshold}，禁止直接执行`);
            return {
                allowed: false,
                threshold,
                reason: `置信度 ${confidence.toFixed(2)} 低于阈值 ${threshold}，禁止直接输出执行代码，必须调用 sf_brainstorm 展示三轨方案`,
            };
        }
        return { allowed: true, threshold };
    }
    /**
     * 标记操作失败 — half-open 状态下探测失败时回退到 open 状态。
     * 防止故障未恢复时大量请求涌入。
     */
    failOperation() {
        this.stopHeartbeat();
        this.currentOperation = null;
        if (this.circuitState === "half-open") {
            this.circuitState = "open";
            this.circuitOpenedAt = this.now();
            debug("llm_gateway", "half-open 探测失败，熔断器回退到 open 状态");
        }
    }
    /** 标记操作完成，停止心跳。half-open 状态下探测成功则关闭熔断器 */
    completeOperation() {
        this.stopHeartbeat();
        this.currentOperation = null;
        // half-open 探测成功 → 关闭熔断器
        if (this.circuitState === "half-open") {
            this.circuitState = "closed";
            debug("llm_gateway", "half-open 探测成功，熔断器已关闭");
        }
    }
    /**
     * 报告实际 Token 用量 — 根据估算与实际的差值调整预算计数。
     * 差值为正时增加消耗，为负时回退多计部分。熔断器根据实际消耗重新评估。
     * @param operation - 操作类型
     * @param actualTokens - 实际消耗的 Token 数
     */
    reportActualUsage(operation, actualTokens) {
        // 安全校验: actualTokens 必须为有限非负数，防止 NaN/Infinity 污染预算计数
        if (!Number.isFinite(actualTokens) || actualTokens < 0) {
            throw new RangeError(`LLMGateway.reportActualUsage(): actualTokens 必须为有限非负数，收到 ${actualTokens}`);
        }
        const profile = OPERATION_PROFILES[operation];
        const diff = actualTokens - profile.estimated_tokens;
        if (diff > 0) {
            this.budgetUsed += diff;
            this.taskTokensUsed += diff;
            if ((this.budgetUsed / this.budgetTotal) >= this.config.circuit_breaker_ratio) {
                this.circuitState = "open";
                this.circuitOpenedAt = this.now();
                debug("llm_gateway", `实际用量超阈值，熔断器已开启`);
            }
        }
        else {
            this.budgetUsed = Math.max(0, this.budgetUsed + diff);
            this.taskTokensUsed = Math.max(0, this.taskTokensUsed + diff);
        }
    }
    /**
     * 获取网关当前状态快照。
     * @returns 网关状态，包含预算、调用统计和审计日志
     */
    getStatus() {
        return {
            budget_total: this.budgetTotal,
            budget_used: this.budgetUsed,
            budget_remaining: this.budgetTotal - this.budgetUsed,
            usage_ratio: this.budgetUsed / this.budgetTotal,
            circuit_open: this.circuitState !== "closed",
            circuit_state: this.circuitState,
            call_count: this.callCount,
            blocked_count: this.blockedCount,
            task_tokens_used: this.taskTokensUsed,
            audit_log: [...this.auditLog],
        };
    }
    /**
     * 重置网关状态 — 清零所有计数器、审计日志和熔断器。
     * @param budget - 可选的新 Token 总预算
     */
    reset(budget) {
        const resolvedBudget = budget ?? this.config.budget_total;
        // 安全校验: 重置时预算必须为有限正数
        if (!Number.isFinite(resolvedBudget) || resolvedBudget < 0) {
            throw new RangeError(`LLMGateway.reset(): budget 必须为有限非负数，收到 ${resolvedBudget}`);
        }
        this.budgetTotal = resolvedBudget;
        this.budgetUsed = 0;
        this.callCount = 0;
        this.blockedCount = 0;
        this.auditLog = [];
        this.circuitState = "closed";
        this.circuitOpenedAt = 0;
        this.halfOpenProbeUsed = 0;
        this.taskTokensUsed = 0;
        this.stopHeartbeat();
        debug("llm_gateway", "网关状态已重置");
    }
    /**
     * 判断操作是否为本地操作（不需要 LLM 算力）。
     * @param op - 操作名称
     * @returns 是否为本地操作
     */
    static isLocalOperation(op) {
        const LOCAL_OPS = [
            "classify", "scope_resolve", "knowledge_match", "pattern_extract",
            "drift_detect", "mutation_test", "coverage_scan", "dependency_scan",
            "migration_check", "contract_check", "debt_scan", "build_run",
        ];
        return LOCAL_OPS.includes(op);
    }
    /**
     * 判断操作是否为 AI 操作。
     * @param op - 操作类型
     * @returns 是否为 AI 操作
     */
    static isAIOperation(op) {
        return op in OPERATION_PROFILES;
    }
    /**
     * 获取操作类型的配置信息。
     * @param op - 操作类型
     * @returns 操作配置（级别、Token 估算、描述）
     */
    static getProfile(op) {
        return OPERATION_PROFILES[op];
    }
    // ── 流式心跳 ──
    /**
     * 启动流式心跳 — 重型操作期间定期发送进度报告，防止客户端超时断连。
     * @param operation - 当前 AI 操作类型
     */
    startHeartbeat(operation) {
        this.stopHeartbeat();
        this.operationStart = this.now();
        this.currentOperation = operation;
        if (!this.heartbeatCallback)
            return;
        this.heartbeatTimer = setInterval(() => {
            if (!this.heartbeatCallback || !this.currentOperation)
                return;
            const elapsed = this.now() - this.operationStart;
            const profile = OPERATION_PROFILES[this.currentOperation];
            this.heartbeatCallback({
                elapsed_ms: elapsed,
                tokens_used: this.taskTokensUsed,
                operation: this.currentOperation,
                message: `[SoloForge Heartbeat] ${profile.description} 执行中... 已耗时 ${(elapsed / 1000).toFixed(0)}s，当前任务已用 ${this.taskTokensUsed}/${this.taskBudget} Token`,
            });
        }, this.config.heartbeat_interval);
    }
    /** 停止流式心跳计时器 */
    stopHeartbeat() {
        if (this.heartbeatTimer) {
            clearInterval(this.heartbeatTimer);
            this.heartbeatTimer = null;
        }
    }
}
//# sourceMappingURL=llm_gateway.js.map