/**
 * AgentChatHandler 聊天处理器
 * 使用 AI SDK 的 ToolLoopAgent 处理工具调用循环
 */

const { EventEmitter } = require('../utils/event-emitter');
const { logger } = require('../utils/logger');
const { generateText, stepCountIs, isLoopFinished } = require('ai');
const { prepareMessagesForAPI } = require('../utils');
const fs = require('fs/promises');

/**
 * 从消息中提取 toolCallId（兼容两种格式）
 * 格式1: msg.toolCallId (旧格式)
 * 格式2: msg.content[0].toolCallId (AI SDK CoreMessage 格式)
 * @param {Object} msg - 消息对象
 * @returns {string|null}
 */
function extractToolCallId(msg) {
  if (msg.toolCallId) return msg.toolCallId;
  if (Array.isArray(msg.content) && msg.content.length > 0) {
    const firstContent = msg.content[0];
    if (firstContent && firstContent.toolCallId) return firstContent.toolCallId;
  }
  return null;
}

// 模型上下文限制表（留 15-20% 余量给 system prompt 和输出）
const MODEL_CONTEXT_LIMITS = {
  // DeepSeek
  'deepseek-chat': 128000,
  'deepseek-coder': 128000,
  'deepseek-reasoner': 128000,
  // MiniMax
  'MiniMax-M2.7': 110000,
  // OpenAI
  'gpt-4': 100000,
  'gpt-4o': 100000,
  'gpt-4o-mini': 100000,
  'gpt-4-turbo': 100000,
  // Anthropic
  'claude-3-5-sonnet': 150000,
  'claude-3-opus': 150000,
  'claude-3-sonnet': 150000,
  'glm-5.1': 200000,
};

/**
 * 纯 JavaScript token 计数器
 * 参考 Claude Code 的实现，使用 bytes/token 比率估算
 * - 普通文本：4 bytes ≈ 1 token
 * - JSON 等密集文本：2 bytes ≈ 1 token
 */
class SimpleTokenizer {
  constructor() {
    // 无需初始化
  }

  /**
   * 估算文本的 token 数
   * @param {string} text
   * @param {number} [bytesPerToken=4] - bytes per token 比率
   * @returns {number}
   */
  encode(text, bytesPerToken = 4) {
    if (!text || typeof text !== 'string') {
      return 0;
    }

    // 清理文本
    const cleanText = text
      .replace(/\0+/g, '')
      .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '')
      .slice(0, 100000);

    if (!cleanText) {
      return 0;
    }

    return Math.ceil(cleanText.length / bytesPerToken);
  }

  /**
   * 估算 JSON 文本的 token 数（更密集）
   * @param {string} text
   * @returns {number}
   */
  encodeForJSON(text) {
    return this.encode(text, 2);
  }
}

// 全局 tokenizer 实例
const _globalTokenizer = new SimpleTokenizer();

// 压缩超时时间（毫秒）
const COMPRESSION_TIMEOUT = 30000;

class AgentChatHandler extends EventEmitter {
  /**
   * @param {Agent} agent - Agent 实例
   * @param {Object} config - 配置
   */
  constructor(agent, config = {}) {
    super();

    this.agent = agent;
    this.config = config;

    this.model = config.model || 'deepseek-chat';
    this.provider = config.provider || 'deepseek';
    this.apiKey = config.apiKey;
    this.baseURL = config.baseURL;
    this.providerOptions = config.providerOptions || {};

    this._systemPrompt = config.systemPrompt || 'You are a helpful assistant.';
    // this._messages = [];  // 移除共享消息存储！改用 Per-Session
    this._sessionMessageStores = new Map(); // sessionId → { messages: [], historyLoaded: false, compressionState: {} }
    this._tools = new Map();
    this._maxSteps = 20; // 降低默认步骤数，减少上下文消耗

    // 上下文压缩配置：根据模型自动设置限制
    const modelKey = Object.keys(MODEL_CONTEXT_LIMITS).find((k) =>
      this.model.toLowerCase().includes(k.toLowerCase())
    );
    const defaultLimit = modelKey ? MODEL_CONTEXT_LIMITS[modelKey] : 40000;
    this._maxContextTokens = config.maxContextTokens || defaultLimit;
    this._compressionThreshold = config.compressionThreshold || 0.6; // 60% 就压缩，早触发
    this._keepRecentMessages = config.keepRecentMessages || 20; // 保留最近 20 条
    this._enableSmartCompress = config.enableSmartCompress !== false; // 默认开启智能摘要
    this._encoder = null;
    this._compressionCount = 0; // 压缩次数统计
    this._compressionInProgress = false; // 压缩锁，防止并发压缩
    this._compressionPromise = null; // 正在进行的压缩 Promise

    // 工具结果压缩配置
    this._maxToolResultSize = config.maxToolResultSize || 4000; // 工具结果超过此大小则压缩（字节）

    // 初始化编码器
    // 使用纯 JS tokenizer
    this._encoder = _globalTokenizer;

    // Session 历史存储配置
    this._sessionHistoryKey = config.sessionHistoryKey || 'chat_history';

    // Per-Session 队列管理
    this._sessionQueues = new Map(); // sessionId -> Queue of {message, options, resolve, reject}
    this._processingSessions = new Set(); // sessionId -> processing flag

    // Session Memory prepareStep（从 memory 插件获取）
    this._sessionMemoryPrepareStep = null;
    if (agent?.framework) {
      const memoryPlugin = agent.framework.pluginManager.get('memory');
      if (memoryPlugin?.instance?.getPrepareStep) {
        this._sessionMemoryPrepareStep = memoryPlugin.instance.getPrepareStep();
        logger.debug('Session memory prepareStep loaded');
      }
    }
  }

  /**
   * 获取 Per-Session 的消息存储
   * @param {string} sessionId - 会话 ID
   * @returns {Object} 消息存储对象
   * @private
   */
  _getSessionMessageStore(sessionId) {
    if (!sessionId) {
      // 无 session 的单次请求，使用临时存储
      return {
        messages: [],
        historyLoaded: false,
        compressionState: {},
        lastUsage: null, // API 返回的真实 usage
        save: () => {},
      };
    }

    if (!this._sessionMessageStores.has(sessionId)) {
      // 自动从 session 加载消息
      const savedMessages = this._loadHistoryFromSession(sessionId);
      this._sessionMessageStores.set(sessionId, {
        sessionId, // 保存 sessionId 引用
        messages: savedMessages,
        historyLoaded: true, // 已加载
        compressionState: {
          lastCompressedAt: null,
          lastTokenCount: 0,
          count: 0,
        },
        lastUsage: null, // API 返回的真实 usage
        save: () => {
          // 简洁的保存方法
          if (this.agent?.framework) {
            const sessionCtx = this.agent.framework.getOrCreateSessionContext(sessionId);
            if (sessionCtx) {
              sessionCtx.replaceMessages(this._sessionMessageStores.get(sessionId).messages);
            }
          }
        },
      });
    }
    return this._sessionMessageStores.get(sessionId);
  }

  /**
   * 从 SessionContext 加载聊天历史
   * @param {string} sessionId - 会话 ID
   * @returns {Array} 消息数组
   * @private
   */
  _loadHistoryFromSession(sessionId) {
    if (!sessionId || !this.agent?.framework) return [];

    try {
      const sessionCtx = this.agent.framework.getOrCreateSessionContext(sessionId);
      if (!sessionCtx) return [];

      const messages = sessionCtx.getMessages();
      return messages || [];
    } catch (err) {
      // 忽略加载错误
    }
    return [];
  }

  /**
   * 保存聊天历史到 SessionContext
   * @param {string} sessionId - 会话 ID
   * @param {Array} messages - 消息数组
   * @private
   */
  _saveHistoryToSession(sessionId, messages) {
    if (!sessionId || !this.agent?.framework || !messages) return;

    try {
      const sessionCtx = this.agent.framework.getOrCreateSessionContext(sessionId);
      if (!sessionCtx) return;

      // 清理消息格式后整体替换
      const cleanedMessages = prepareMessagesForAPI(messages);
      sessionCtx.replaceMessages(cleanedMessages);
    } catch (err) {
      // 忽略保存错误
    }
  }

  /**
   * 将消息加入队列（Per-Session）
   * @param {string} sessionId - 会话 ID
   * @param {string|Object} message - 消息
   * @param {Object} options - 选项
   * @returns {Promise}
   */
  enqueue(sessionId, message, options = {}) {
    if (!this._sessionQueues.has(sessionId)) {
      this._sessionQueues.set(sessionId, []);
    }
    const queue = this._sessionQueues.get(sessionId);

    if (this._processingSessions.has(sessionId)) {
      // 已在处理中，加入队列
      return new Promise((resolve, reject) => {
        queue.push({ message, options, resolve, reject });
      });
    }

    // 直接处理
    return this._processWithSession(sessionId, message, options);
  }

  /**
   * 处理单个 session 的消息
   * @param {string} sessionId - 会话 ID
   * @param {string|Object} message - 消息
   * @param {Object} options - 选项
   * @returns {Promise}
   * @private
   */
  async _processWithSession(sessionId, message, options) {
    this._processingSessions.add(sessionId);
    try {
      return await this._doChat(message, options);
    } finally {
      this._processingSessions.delete(sessionId);
      // 处理队列中的下一条
      setImmediate(() => this._drainQueue(sessionId));
    }
  }

  /**
   * 消费队列中的下一条消息
   * @param {string} sessionId - 会话 ID
   * @private
   */
  _drainQueue(sessionId) {
    const queue = this._sessionQueues.get(sessionId);
    if (!queue || queue.length === 0) return;

    const item = queue.shift();
    this._processWithSession(sessionId, item.message, item.options)
      .then(item.resolve)
      .catch(item.reject);
  }

  /**
   * 静态方法：保留接口兼容性（无实际作用）
   */
  static clearEncoderCache() {
    // 不再需要清理
  }

  /**
   * 计算文本的 token 数
   * @param {string} text
   * @param {number} [bytesPerToken=4] - bytes per token 比率
   * @returns {number}
   * @private
   */
  _countTokens(text, bytesPerToken = 4) {
    if (!text || typeof text !== 'string') return 0;
    // SimpleTokenizer.encode 已经处理了清理逻辑
    try {
      return this._encoder.encode(text, bytesPerToken);
    } catch (err) {
      // 估算失败时使用字符计数
      return Math.ceil(text.length / bytesPerToken);
    }
  }

  /**
   * 计算消息列表的总 token 数
   * 参考 Claude Code 的分块计数逻辑
   * @param {Array} messages
   * @returns {number}
   * @private
   */
  _countMessagesTokens(messages) {
    let total = 0;
    for (const msg of messages) {
      if (!msg) continue;
      total += this._countMessageTokens(msg);
    }
    return total;
  }

  /**
   * 计算单条消息的 token 数
   * @param {Object} msg - 消息对象
   * @returns {number}
   * @private
   */
  _countMessageTokens(msg) {
    let total = 0;
    total += 4; // role 标记

    if (typeof msg.content === 'string') {
      total += this._countTokens(msg.content);
    } else if (Array.isArray(msg.content)) {
      for (const part of msg.content) {
        total += this._countContentBlockTokens(part);
      }
    } else if (msg.content && typeof msg.content === 'object') {
      // 处理工具结果等对象类型
      try {
        const str = JSON.stringify(msg.content);
        total += this._countTokens(str, 2); // JSON 用更密集的比率
      } catch (e) {
        // 无法序列化的内容，忽略
      }
    }
    total += 4; // 结尾标记
    return total;
  }

  /**
   * 计算单个内容块的 token 数
   * 参考 Claude Code 的实现
   * 兼容 AI SDK 6.x 标准格式
   * @param {Object} block - 内容块
   * @returns {number}
   * @private
   */
  _countContentBlockTokens(block) {
    if (!block || typeof block !== 'object') {
      return 0;
    }

    const tokenizer = this._encoder;

    // 文本块
    if (block.type === 'text') {
      return tokenizer.encode(block.text || '');
    }

    // 工具调用块 - 兼容 tool-call 和 tool-use 两种类型
    if (block.type === 'tool-call' || block.type === 'tool-use' || block.type === 'tool_call') {
      let count = 0;
      if (block.name || block.toolName) {
        count += tokenizer.encode(String(block.name || block.toolName || ''));
      }
      if (block.input) {
        try {
          count += tokenizer.encode(JSON.stringify(block.input), 2);
        } catch (e) {}
      }
      return count;
    }

    // 工具结果块 - 兼容 tool-result 和 tool_result 两种类型
    if (block.type === 'tool-result' || block.type === 'tool_result') {
      let count = 0;
      if (block.content) {
        if (typeof block.content === 'string') {
          count += tokenizer.encode(block.content);
        } else if (Array.isArray(block.content)) {
          for (const c of block.content) {
            if (c && c.type === 'text') {
              count += tokenizer.encode(c.text || '');
            }
          }
        }
      }
      if (block.toolUseId || block.tool_call_id) {
        count += 10; // 工具结果标识
      }
      return count;
    }

    // 输入块 (tool_input)
    if (block.type === 'input') {
      try {
        return tokenizer.encode(JSON.stringify(block), 2);
      } catch (e) {
        return 0;
      }
    }

    // 其他类型，尝试 JSON 序列化
    try {
      return tokenizer.encode(JSON.stringify(block), 2);
    } catch (e) {
      return 0;
    }
  }

  /**
   * 估算消息列表的 token 数（不带消息结构的简单估算）
   * 用于快速检查
   * @param {Array} messages
   * @returns {number}
   * @private
   */
  _roughEstimateMessagesTokens(messages) {
    let total = 0;
    for (const msg of messages) {
      if (!msg) continue;
      if (typeof msg.content === 'string') {
        total += this._countTokens(msg.content);
      } else if (Array.isArray(msg.content)) {
        for (const part of msg.content) {
          if (part && part.text) {
            total += this._countTokens(String(part.text));
          }
        }
      }
    }
    return total;
  }

  /**
   * 基于 API usage 和新消息估算计算当前上下文 token 数
   * 参考 Claude Code 的 tokenCountWithEstimation 实现
   * @param {Array} messages - 消息数组
   * @param {Object} lastUsage - 上一次 API 返回的 usage
   * @param {number} baseMessageCount - 基准消息数量（usage 对应时）
   * @returns {number} 估算的总 token 数
   * @private
   */
  _tokenCountWithEstimation(messages, lastUsage, baseMessageCount = 0) {
    // 如果有真实的 usage 数据，使用它作为基准
    if (lastUsage && typeof lastUsage === 'object') {
      const inputTokens = lastUsage.inputTokens || 0;
      const outputTokens = lastUsage.outputTokens || 0;
      const cacheReadTokens = lastUsage.inputTokenDetails?.cacheReadTokens || 0;
      const cacheWriteTokens = lastUsage.inputTokenDetails?.cacheWriteTokens || 0;

      // 计算已有消息的真实 token 数
      const baseTokens = inputTokens + cacheReadTokens + outputTokens;

      // 估算新增消息的 token 数
      if (messages.length > baseMessageCount) {
        const newMessages = messages.slice(baseMessageCount);
        const newMessagesTokens = this._countMessagesTokens(newMessages);
        return baseTokens + newMessagesTokens;
      }

      return baseTokens;
    }

    // 没有 usage 数据，使用纯估算
    return this._countMessagesTokens(messages);
  }

  /**
   * 更新 messageStore 的 lastUsage
   * @param {Object} messageStore
   * @param {Object} usage - API 返回的 usage
   * @private
   */
  _updateMessageStoreUsage(messageStore, usage) {
    if (messageStore && usage) {
      messageStore.lastUsage = {
        inputTokens: usage.inputTokens,
        outputTokens: usage.outputTokens,
        inputTokenDetails: usage.inputTokenDetails || {},
      };
    }
  }

  /**
   * 计算工具定义的 token 数（估算）
   * @returns {number}
   * @private
   */
  _countToolsTokens() {
    let total = 0;
    for (const toolDef of this._tools.values()) {
      // 工具名 + 描述
      total += this._countTokens(String(toolDef.name || ''));
      total += this._countTokens(String(toolDef.description || ''));

      // 工具参数 schema
      if (toolDef.inputSchema) {
        const schemaStr =
          typeof toolDef.inputSchema === 'string'
            ? toolDef.inputSchema
            : JSON.stringify(toolDef.inputSchema);
        total += this._countTokens(schemaStr);
      }
    }
    return total;
  }

  /**
   * 压缩上下文消息（智能摘要模式，支持超时控制）
   * 策略：
   * 1. 如果启用了智能摘要且有 AI 客户端，对早期消息进行 AI 总结
   * 2. 否则使用简单裁剪 + 标记
   * 3. 支持超时控制，防止压缩阻塞太久
   * @param {string} sessionId - 会话 ID（用于压缩后同步）
   * @param {Array} messages - 消息数组引用
   * @param {Object} messageStore - 消息存储对象
   * @private
   */
  async _compressContext(sessionId, messages, messageStore) {
    // 如果已经有压缩在进行，返回现有的 Promise
    if (this._compressionInProgress && this._compressionPromise) {
      logger.debug('Compression already in progress, waiting for it...');
      return this._compressionPromise;
    }

    if (messages.length <= this._keepRecentMessages) {
      return;
    }

    this._compressionInProgress = true;

    // 创建压缩 Promise，带超时控制
    this._compressionPromise = this._executeCompressionWithTimeout(
      sessionId,
      messages,
      messageStore
    ).finally(() => {
      this._compressionInProgress = false;
      this._compressionPromise = null;
    });

    return this._compressionPromise;
  }

  /**
   * 执行压缩（带超时）
   * @param {string} sessionId - 会话 ID
   * @param {Array} messages - 消息数组引用
   * @param {Object} messageStore - 消息存储对象
   * @private
   */
  async _executeCompressionWithTimeout(sessionId, messages, messageStore) {
    try {
      return await Promise.race([
        this._doCompress(sessionId, messages, messageStore),
        this._createTimeoutPromise(),
      ]);
    } catch (err) {
      logger.warn('Compression failed:', err.message);
      // 压缩失败时使用简单的截断策略
      this._simpleCompress(sessionId, messages, messageStore);
    }
  }

  /**
   * 创建超时 Promise
   * @private
   */
  _createTimeoutPromise() {
    return new Promise((_, reject) => {
      setTimeout(() => {
        reject(new Error('Compression timeout'));
      }, COMPRESSION_TIMEOUT);
    });
  }

  async compressToolResults(messages) {
    try {
      const recentMessages = prepareMessagesForAPI(messages.slice(-10));
      const messagesToSummarize = messages.slice(0, -10);
      let summaryContent = '';
      const summaryText = await this._summarizeMessages(messagesToSummarize);
      summaryContent = `[早期对话摘要]: ${summaryText}`;
      const summary = {
        role: 'assistant',
        content: summaryContent,
      };
      logger.info(`AI summary generated (${summaryText.length} chars)`);
      return [summary, ...recentMessages];
    } catch (err) {
      logger.warn('Tool result compression failed:', err.message);
      return prepareMessagesForAPI(messages);
    }
  }

  /**
   * 执行实际压缩逻辑
   * @param {string} sessionId - 会话 ID
   * @param {Array} messages - 消息数组引用
   * @param {Object} messageStore - 消息存储对象
   * @private
   */
  async _doCompress(sessionId, messages, messageStore) {
    const systemMessages = messages.filter((m) => m.role === 'system');
    const otherMessages = messages.filter((m) => m.role !== 'system');

    // 保留最近的 N 条非系统消息
    const recentMessages = otherMessages.slice(-this._keepRecentMessages);
    const messagesToSummarize = otherMessages.slice(0, -this._keepRecentMessages);

    const compressedCount = messagesToSummarize.length;
    let summaryContent = '';

    // 尝试使用 AI 总结
    if (this._enableSmartCompress && this._aiClient) {
      try {
        const summaryText = await this._summarizeMessages(messagesToSummarize);
        summaryContent = `[早期对话摘要]: ${summaryText}`;
        logger.info(`AI summary generated (${summaryText.length} chars)`);
      } catch (err) {
        logger.warn('AI summary failed, using simple compression:', err.message);
        summaryContent = `[上下文已压缩: 省略了 ${compressedCount} 条早期消息。保留了最近 ${this._keepRecentMessages} 条对话记录。]`;
      }
    } else {
      summaryContent = `[上下文已压缩: 省略了 ${compressedCount} 条早期消息。保留了最近 ${this._keepRecentMessages} 条对话记录。]`;
    }

    const summary = {
      role: 'assistant',
      content: summaryContent,
    };

    // 构建保留的消息，确保 tool call 和 tool result 配对不被分离
    const assistantToolCalls = new Map(); // toolCallId -> assistant message index
    const toolResults = new Map(); // toolCallId -> tool result indices

    // 工具类型检查辅助函数（兼容 AI SDK 6.x）
    const isToolCall = (item) =>
      item && (item.type === 'tool-call' || item.type === 'tool-use') && item.toolCallId;
    const isToolResult = (item) =>
      item && (item.type === 'tool-result' || item.type === 'tool_result') && item.toolCallId;

    // 第一遍：找出所有 tool call 和它们的 results
    for (let i = 0; i < recentMessages.length; i++) {
      const msg = recentMessages[i];
      if (msg.role === 'assistant' && msg.content) {
        const content = Array.isArray(msg.content) ? msg.content : [msg.content];
        for (const item of content) {
          if (isToolCall(item)) {
            assistantToolCalls.set(item.toolCallId, i);
          }
        }
      }
      if (msg.role === 'tool' && Array.isArray(msg.content)) {
        // 遍历 tool message 中的所有 tool-results
        for (const item of msg.content) {
          if (isToolResult(item)) {
            if (!toolResults.has(item.toolCallId)) {
              toolResults.set(item.toolCallId, []);
            }
            toolResults.get(item.toolCallId).push(i);
          }
        }
      }
    }

    // 第二遍：找出哪些 tool call 没有对应的 result（需要保留）
    const orphanToolCalls = new Set();
    for (const [toolCallId, assistantIdx] of assistantToolCalls) {
      if (!toolResults.has(toolCallId)) {
        orphanToolCalls.add(toolCallId);
      }
    }

    // 过滤时保留：有 result 的 tool call，以及没有 result 的 tool call（但要附带其 result）
    // 第一遍：先确定哪些 assistant 消息要保留
    const assistantIndicesToKeep = new Set();
    for (let i = 0; i < recentMessages.length; i++) {
      const msg = recentMessages[i];
      if (msg.role === 'assistant') {
        // 检查这个 assistant 消息是否有 tool call
        let hasToolCall = false;
        if (msg.content) {
          const content = Array.isArray(msg.content) ? msg.content : [msg.content];
          for (const item of content) {
            if (isToolCall(item)) {
              hasToolCall = true;
              break;
            }
          }
        }
        if (hasToolCall) {
          assistantIndicesToKeep.add(i);
        } else if (i >= recentMessages.length - 3) {
          // 保留最近几条 assistant 消息（它们可能包含重要上下文）
          assistantIndicesToKeep.add(i);
        }
      }
    }

    // 如果有孤儿的 tool call（没有 result），保留该 assistant 消息
    for (const [toolCallId, assistantIdx] of assistantToolCalls) {
      if (!toolResults.has(toolCallId)) {
        assistantIndicesToKeep.add(assistantIdx);
      }
    }

    // 第二遍：基于 assistant 的保留情况，确定哪些 tool result 要保留
    const indicesToKeep = new Set();
    for (let i = 0; i < recentMessages.length; i++) {
      const msg = recentMessages[i];
      if (msg.role === 'tool') {
        // 检查这个 tool result 是否对应一个被保留的 assistant tool_call
        // 如果对应的 assistant 消息被删除了，这个 tool result 也应该被删除（否则会报 orphaned error）
        let hasPairedAssistant = false;
        const content = Array.isArray(msg.content) ? msg.content : [msg.content];
        for (const item of content) {
          if (isToolResult(item)) {
            // 检查这个 toolCallId 对应的 assistant 消息是否被保留
            const assistantIdx = assistantToolCalls.get(item.toolCallId);
            if (assistantIdx !== undefined && assistantIndicesToKeep.has(assistantIdx)) {
              hasPairedAssistant = true;
              break;
            }
          }
        }
        // 只有有配对的 assistant 消息被保留时，才保留这个 tool result
        if (hasPairedAssistant) {
          indicesToKeep.add(i);
        } else {
          logger.debug(`Dropping orphaned tool result at index ${i}`);
        }
      } else if (msg.role === 'assistant') {
        if (assistantIndicesToKeep.has(i)) {
          indicesToKeep.add(i);
        }
      } else {
        // user, system 等角色默认保留
        indicesToKeep.add(i);
      }
    }

    // 按索引排序并重建消息数组
    const sortedIndices = Array.from(indicesToKeep).sort((a, b) => a - b);
    const filteredRecentMessages = sortedIndices.map((i) => recentMessages[i]);

    // 直接修改传入的 messages 数组
    messages.length = 0;
    messages.push(...systemMessages, summary, ...filteredRecentMessages);

    // 更新压缩状态
    this._compressionCount++;
    if (messageStore.compressionState) {
      messageStore.compressionState.count++;
      messageStore.compressionState.lastCompressedAt = Date.now();
      messageStore.compressionState.lastTokenCount = this._countMessagesTokens(messages);
    }

    const totalTokens = this._countMessagesTokens(messages);
    logger.info(
      `Context compressed (${this._compressionCount} times). Messages: ${messages.length}, Est. tokens: ${totalTokens}`
    );

    // 压缩后立即同步到 session
    if (sessionId) {
      this._saveHistoryToSession(sessionId, messages);
    }
  }

  /**
   * 简单压缩（当 AI 压缩失败时使用）
   * @param {string} sessionId - 会话 ID
   * @param {Array} messages - 消息数组引用
   * @param {Object} messageStore - 消息存储对象
   * @private
   */
  _simpleCompress(sessionId, messages, messageStore) {
    const systemMessages = messages.filter((m) => m.role === 'system');
    const otherMessages = messages.filter((m) => m.role !== 'system');
    const recentMessages = otherMessages.slice(-this._keepRecentMessages);
    const compressedCount = otherMessages.length - this._keepRecentMessages;

    const summaryContent = `[上下文已压缩: 省略了 ${compressedCount} 条早期消息。保留了最近 ${this._keepRecentMessages} 条对话记录。]`;

    const summary = {
      role: 'assistant',
      content: summaryContent,
    };

    // 工具类型检查辅助函数（兼容 AI SDK 6.x）
    const isToolCall = (item) =>
      item && (item.type === 'tool-call' || item.type === 'tool-use') && item.toolCallId;
    const isToolResult = (item) =>
      item && (item.type === 'tool-result' || item.type === 'tool_result') && item.toolCallId;

    // 构建保留的消息，确保 tool call 和 tool result 配对不被分离
    const assistantToolCalls = new Map(); // toolCallId -> assistant message index
    const toolResults = new Map(); // toolCallId -> tool result indices

    // 第一遍：找出所有 tool call 和它们的 results
    for (let i = 0; i < recentMessages.length; i++) {
      const msg = recentMessages[i];
      if (msg.role === 'assistant' && msg.content) {
        const content = Array.isArray(msg.content) ? msg.content : [msg.content];
        for (const item of content) {
          if (isToolCall(item)) {
            assistantToolCalls.set(item.toolCallId, i);
          }
        }
      }
      if (msg.role === 'tool' && Array.isArray(msg.content)) {
        // 遍历 tool message 中的所有 tool-results
        for (const item of msg.content) {
          if (isToolResult(item)) {
            if (!toolResults.has(item.toolCallId)) {
              toolResults.set(item.toolCallId, []);
            }
            toolResults.get(item.toolCallId).push(i);
          }
        }
      }
    }

    // 第二遍：找出哪些 tool call 没有对应的 result（需要保留）
    const orphanToolCalls = new Set();
    for (const [toolCallId, assistantIdx] of assistantToolCalls) {
      if (!toolResults.has(toolCallId)) {
        orphanToolCalls.add(toolCallId);
      }
    }

    // 过滤时保留：有 result 的 tool call，以及没有 result 的 tool call
    const indicesToKeep = new Set();
    for (let i = 0; i < recentMessages.length; i++) {
      const msg = recentMessages[i];
      if (msg.role === 'tool') {
        // 保留所有 tool result
        indicesToKeep.add(i);
      } else if (msg.role === 'assistant') {
        let hasToolCall = false;
        if (msg.content) {
          const content = Array.isArray(msg.content) ? msg.content : [msg.content];
          for (const item of content) {
            if (isToolCall(item)) {
              hasToolCall = true;
              break;
            }
          }
        }
        if (hasToolCall) {
          indicesToKeep.add(i);
        } else if (i >= recentMessages.length - 3) {
          // 保留最近几条 assistant 消息
          indicesToKeep.add(i);
        }
      } else {
        // user, system 等角色默认保留
        indicesToKeep.add(i);
      }
    }

    // 如果有孤儿的 tool call（没有 result），保留该 assistant 消息
    for (const [toolCallId, assistantIdx] of assistantToolCalls) {
      if (!toolResults.has(toolCallId)) {
        indicesToKeep.add(assistantIdx);
      }
    }

    // 按索引排序并重建消息数组
    const sortedIndices = Array.from(indicesToKeep).sort((a, b) => a - b);
    const filteredRecentMessages = sortedIndices.map((i) => recentMessages[i]);

    // 直接修改传入的 messages 数组
    messages.length = 0;
    messages.push(...systemMessages, summary, ...filteredRecentMessages);

    // 更新压缩状态
    this._compressionCount++;
    if (messageStore.compressionState) {
      messageStore.compressionState.count++;
      messageStore.compressionState.lastCompressedAt = Date.now();
      messageStore.compressionState.lastTokenCount = this._countMessagesTokens(messages);
    }

    logger.info(
      `Context simple compressed (${this._compressionCount} times). Messages: ${messages.length}`
    );

    // 压缩后立即同步到 session
    if (sessionId) {
      this._saveHistoryToSession(sessionId, messages);
    }
  }

  /**
   * 使用 AI 对消息进行总结
   * @param {Array} messages - 要总结的消息
   * @returns {Promise<string>} 总结文本
   * @private
   */
  async _summarizeMessages(messages) {
    if (!this._aiClient || messages.length === 0) {
      return '（无早期对话）';
    }

    // 构建总结提示
    const conversationText = messages
      .map((m) => {
        const role = m.role === 'user' ? '用户' : '助手';
        const content = typeof m.content === 'string' ? m.content : JSON.stringify(m.content);
        return `${role}: ${content}`;
      })
      .join('\n');

    const summarizePrompt = `请简洁地总结以下对话的要点，保留关键信息和用户需求：

${conversationText}

总结要求：
1. 提取用户的主要需求和意图
2. 保留关键的技术细节或决策
3. 不要超过 1000 字
4. 用中文回复`;

    // 使用 AI SDK 6.x 的 generateText
    const { text } = await generateText({
      model: this._aiClient,
      prompt: summarizePrompt,
      ...this.providerOptions,
    });

    return text || '（总结生成失败）';
  }

  /**
   * 检查工具结果是否需要压缩
   * @param {any} result - 工具返回结果
   * @returns {boolean}
   * @private
   */
  _shouldCompressToolResult(result) {
    if (!result || this._maxToolResultSize <= 0) return false;

    // 计算结果的大小
    let size = 0;
    if (typeof result === 'string') {
      size = result.length;
    } else if (typeof result === 'object') {
      try {
        size = JSON.stringify(result).length;
      } catch {
        size = String(result).length;
      }
    }

    return size > this._maxToolResultSize;
  }

  /**
   * 压缩工具结果
   * @param {any} result - 工具返回结果
   * @returns {Promise<any>} 压缩后的结果
   * @private
   */
  async _compressToolResult(result) {
    if (!this._shouldCompressToolResult(result)) {
      return result;
    }

    if (!this._aiClient) {
      logger.warn('Cannot compress tool result: no AI client');
      return this._fallbackCompress(result);
    }

    try {
      const originalSize =
        typeof result === 'string' ? result.length : JSON.stringify(result).length;
      const resultStr = typeof result === 'string' ? result : JSON.stringify(result, null, 2);

      // 对于超大型内容（如网页），采用更好的截断策略
      const maxInputSize = 6000; // 保留给 AI 处理的输入大小
      const shouldTruncate = resultStr.length > maxInputSize;
      const truncatedContent = resultStr.substring(0, maxInputSize);
      const truncatedNote = shouldTruncate ? `\n\n[内容已截断，原始长度 ${originalSize} 字符]` : '';

      // 检测内容类型
      const isHTML =
        resultStr.startsWith('<') || resultStr.includes('<html') || resultStr.includes('<!DOCTYPE');
      const isJSON = !isHTML && (resultStr.startsWith('{') || resultStr.startsWith('['));
      const contentTypeHint = isHTML ? '（HTML 网页内容）' : isJSON ? '（JSON 数据）' : '';

      // 构建压缩提示
      const compressPrompt = `以下是一个工具执行结果${contentTypeHint}，长度 ${originalSize} 字符。请简洁地总结其核心内容：

${truncatedContent}${truncatedNote}

请提取并保留：
1. 主要标题和主题
2. 关键信息点（不超过 5 个）
3. 重要数据或结论

用简洁的中文总结，不超过 1000 字：`;

      // 使用 AI SDK 6.x 的 generateText
      const { text } = await generateText({
        model: this._aiClient,
        prompt: compressPrompt,
        ...this.providerOptions,
        maxTokens: 500,
      });

      const summary = text || '（总结生成失败）';
      const compressed = `[工具结果已压缩${contentTypeHint}: ${originalSize} -> ${summary.length} 字符]\n\n${summary}`;

      logger.info(`Tool result compressed: ${originalSize} -> ${summary.length} chars`);
      return compressed;
    } catch (err) {
      logger.warn('Tool result compression failed:', err.message);
      return this._fallbackCompress(result);
    }
  }

  /**
   * 回退压缩方法（当 AI 客户端不可用时）
   * @param {any} result - 工具返回结果
   * @returns {string} 压缩后的结果
   * @private
   */
  _fallbackCompress(result) {
    const originalSize = typeof result === 'string' ? result.length : JSON.stringify(result).length;
    const resultStr = typeof result === 'string' ? result : JSON.stringify(result, null, 2);

    // 简单截断策略：保留前 2000 字符
    const maxSize = 2000;
    if (resultStr.length <= maxSize) {
      return result;
    }

    const compressed = `[工具结果已压缩（简单截断）: ${originalSize} -> ${maxSize} 字符]\n\n${resultStr.substring(0, maxSize)}\n\n...[内容已截断，原文 ${originalSize} 字符]`;
    logger.info(`Tool result fallback compressed: ${originalSize} -> ${maxSize} chars`);
    return compressed;
  }

  /**
   * 设置 AI 客户端
   * @param {Object} client - AI 模型客户端
   */
  setAIClient(client) {
    this._aiClient = client;
    return this;
  }

  /**
   * 设置系统提示
   * @param {string} prompt
   */
  setSystemPrompt(prompt) {
    this._systemPrompt = prompt;
    return this;
  }

  /**
   * 注册工具
   * @param {Object} toolDef - 工具定义
   */
  registerTool(toolDef) {
    this._tools.set(toolDef.name, toolDef);
    return this;
  }

  /**
   * 清空对话历史
   * @param {string} sessionId - 可选，指定 session 则同时清除 session 中的历史
   */
  clearHistory(sessionId) {
    if (sessionId) {
      // 清除 Per-Session 消息存储
      const messageStore = this._sessionMessageStores.get(sessionId);
      if (messageStore) {
        messageStore.messages = [];
        messageStore.historyLoaded = false;
        messageStore.compressionState = {
          lastCompressedAt: null,
          lastTokenCount: 0,
          count: 0,
        };
      }
      // 清除 SessionContext 中的历史
      if (this.agent?.framework) {
        try {
          const sessionCtx = this.agent.framework.getSessionContext(sessionId);
          if (sessionCtx) {
            sessionCtx.clearMessages();
          }
        } catch (err) {
          // 忽略错误
        }
      }
    } else {
      // 清除所有 Per-Session 消息存储
      this._sessionMessageStores.clear();
    }
    this._compressionCount = 0;
    return this;
  }

  /**
   * 获取已注册的工具
   * @returns {Array}
   */
  getTools() {
    return Array.from(this._tools.values());
  }

  /**
   * 导入 AI SDK（动态导入）
   * @private
   */
  async _importAI() {
    try {
      const ai = require('ai');
      return {
        tool: ai.tool,
        ToolLoopAgent: ai.ToolLoopAgent,
      };
    } catch (err) {
      throw new Error('AI SDK not found. Please install: npm install ai');
    }
  }

  /**
   * 发送消息（非流式）- 使用队列机制
   * @param {string|Object} message - 消息
   * @param {Object} options - 选项
   */
  async chat(message, options = {}) {
    const sessionId = options.sessionId || null;
    return this.enqueue(sessionId, message, options);
  }

  /**
   * 执行实际聊天逻辑
   * @param {string|Object} message - 消息
   * @param {Object} options - 选项
   * @private
   */
  async _doChat(message, options = {}) {
    const sessionId = options.sessionId || null;
    const framework = this.agent.framework;

    // 获取 Per-Session 的消息存储（自动从 session 加载）
    const messageStore = this._getSessionMessageStore(sessionId);
    const messages = messageStore.messages;

    try {
      // 关键：每次 chat 调用时刷新系统提示词，确保包含最新的工具/技能描述
      // 解决上下文过长时 LLM 不调用工具的问题
      this._systemPrompt = this.agent._buildSystemPrompt();
      // 动态导入 AI SDK
      const { tool, ToolLoopAgent } = await this._importAI();

      const userMessage =
        typeof message === 'string' ? { role: 'user', content: message } : message;
      messages.push(userMessage);

      // 检查是否需要压缩上下文（包括工具定义）
      const messagesTokens = this._countMessagesTokens(messages);
      const toolsTokens = this._countToolsTokens();
      const systemPromptTokens = this._countTokens(this._systemPrompt);
      const totalTokens = messagesTokens + toolsTokens + systemPromptTokens;
      const limit = this._maxContextTokens * 0.5; // 降低到 50%，更早压缩，防止 context window 超限

      if (totalTokens > limit) {
        logger.info(
          `Context large (${totalTokens}/${this._maxContextTokens} tokens = msgs:${messagesTokens} + tools:${toolsTokens} + sys:${systemPromptTokens}), compressing...`
        );
        // 使用带超时控制的压缩（传入 messages 引用）
        await this._compressContext(sessionId, messages, messageStore);
        // 压缩后验证消息配对（防止 orphaned tool result）
        const validatedMessages = this._validateMessagesPairing(messages);
        if (validatedMessages.length !== messages.length) {
          messages.length = 0;
          messages.push(...validatedMessages);
        }
      }

      const maxSteps = options.maxSteps || this._maxSteps;
      const tools = this._getAITools(tool);

      // 验证消息配对（防止 API 调用时报 orphaned tool result 错误）
      const validatedMessages = this._validateMessagesPairing(messages);
      if (validatedMessages.length !== messages.length) {
        messages.length = 0;
        messages.push(...validatedMessages);
      }

      // 验证并修复消息中的不完整工具调用（在 API 调用前执行，防止开始聊天的时候就报错）
      this._validateToolCalls(messages);

      // 最终检查：在 API 调用前再次验证 token 数
      const finalMessagesTokens = this._countMessagesTokens(messages);
      const finalToolsTokens = this._countToolsTokens();
      const finalSystemPromptTokens = this._countTokens(this._systemPrompt);
      const finalTotalTokens = finalMessagesTokens + finalToolsTokens + finalSystemPromptTokens;
      const finalLimit = this._maxContextTokens * 0.5;

      // logger.info(
      //   `[API Call Check] messages=${messages.length}, tokens=(${finalMessagesTokens}+${finalToolsTokens}+${finalSystemPromptTokens}=${finalTotalTokens}) vs limit=${finalLimit}`
      // );

      // 如果仍然超过限制，强制压缩
      if (finalTotalTokens > finalLimit) {
        logger.warn(`[API Call Check] Still over limit after validation, forcing compression`);
        await this._compressContext(sessionId, messages, messageStore);
        const compressedMessages = this._validateMessagesPairing(messages);
        messages.length = 0;
        messages.push(...compressedMessages);
        const afterTokens = this._countMessagesTokens(messages);
        logger.info(
          `[After Forced Compression] messages=${messages.length}, tokens=${afterTokens}`
        );
      }

      // 准备传给 agent 的消息
      const prepareStepChainStream = async ({ stepNumber, messages: inputMessages }) => {
        try {
          // 1. 验证消息格式
          if (!Array.isArray(inputMessages)) {
            logger.warn('prepareStep received non-array messages');
            return { messages: inputMessages };
          }

          // 2. 修复不完整的工具调用（input 只有 { 或其他无效值）
          this._validateToolCalls(inputMessages);

          // 3. 验证消息配对并更新 inputMessages（防止 API 报 orphaned tool result 错误）
          const pairedMessages = this._validateMessagesPairing(inputMessages);
          if (pairedMessages.length !== inputMessages.length) {
            logger.debug(
              `prepareStep pairing: ${inputMessages.length} -> ${pairedMessages.length}`
            );
          }
          // 重要：用配对验证后的结果替换 inputMessages
          inputMessages.length = 0;
          inputMessages.push(...pairedMessages);

          // 4. Token 数检查：超过限制才修剪
          const tokenCount = this._countMessagesTokens(inputMessages);
          const tokenLimit = this._maxContextTokens * 0.75; // 75% 阈值，保留缓冲空间
          if (inputMessages.length <= 30 && tokenCount <= tokenLimit) {
            return { messages: inputMessages }; // 消息少且 token 未超限，不修剪
          }

          // 5. 保留配对完整的消息（使用 AI SDK 标准格式）
          const pruned = this._prepareMessagesForAI(inputMessages);
          logger.debug(`prepareStep pruned: ${inputMessages.length} -> ${pruned.length} messages`);

          // 6. 返回 AI SDK 6.x 要求的格式
          return { messages: pruned };
        } catch (err) {
          logger.error('prepareStep error:', err.message);
          return { messages: inputMessages };
        }
      };

      if (!this._aiClient) {
        throw new Error('AI client not configured.');
      }

      // 不使用 prepareStep，消息格式已在 _saveHistoryToSession 中处理
      const agent = new ToolLoopAgent({
        model: this._aiClient,
        instructions: this._systemPrompt,
        tools: tools,
        prepareStep: prepareStepChainStream,
      });

      // 使用 runInSession 让工具执行时能获取 sessionId（Per-Session 隔离）
      const result = await framework.runInSession(sessionId, {}, async () => {
        return agent.generate({ messages, ...this.providerOptions });
      });

      // 捕获 API 返回的 usage 用于更准确的 token 估算
      if (result.usage) {
        this._updateMessageStoreUsage(messageStore, result.usage);
        logger.debug(
          `API usage: input=${result.usage.inputTokens}, output=${result.usage.outputTokens}`
        );
      }

      messages.push(...result.response.messages);

      // 触发 agent:message 事件，让 memory 插件可以自动提取记忆
      const userMsg = messages[messages.length - result.response.messages.length - 1];
      this.emit('message', { content: result.text, sessionId: sessionId, userMessage: userMsg });

      // 生成后检查：如果消息太长，下次需要压缩
      const afterTokens = this._countMessagesTokens(messages);
      if (afterTokens > this._maxContextTokens * 0.5) {
        logger.info(`After generation: ${afterTokens} tokens, will compress on next turn`);
      }

      return {
        success: true,
        message: result.text || '',
        stepCount: result.stepCount || 1,
      };
    } catch (err) {
      // 捕获 AI API 瞬时错误（如 Invalid JSON response、服务负载过高等）
      const errorMsg = err.message || String(err);
      const causeMsg = err.cause?.message || String(err.cause) || '';
      const fullMsg = errorMsg + causeMsg;

      const isTransientError =
        fullMsg.includes('Invalid JSON response') ||
        fullMsg.includes('负载较高') ||
        fullMsg.includes('Failed after') ||
        fullMsg.includes('timeout') ||
        fullMsg.includes('retry') ||
        err.name === 'AI_RetryError' ||
        err.name === 'AI_APICallError';

      logger.warn(
        `[AgentChat] Error caught in _doChat: msg="${errorMsg}", name=${err.name}, isTransient=${isTransientError}`
      );

      if (isTransientError) {
        return {
          success: false,
          message: 'AI 服务暂时不可用，请稍后重试。',
          error: errorMsg,
          stepCount: 0,
        };
      }

      // 未知错误，打印调试信息后继续抛出
      logger.error(
        `[AgentChat] Unknown error in _doChat: ${err.message}, name: ${err.name}, cause: ${err.cause?.message || err.cause}`
      );
      throw err;
    } finally {
      // 校验并修复消息中的不完整工具调用
      this._validateToolCalls(messages);
      // 确保保存聊天历史到 session（无论成功还是失败）
      messageStore.save();
    }
  }

  /**
   * 发送消息（流式）
   * @param {string|Object} message - 消息
   * @param {Object} options - 选项
   */
  async *chatStream(message, options = {}) {
    const sessionId = options.sessionId || null;
    const framework = this.agent.framework;

    // 获取 Per-Session 的消息存储
    const messageStore = this._getSessionMessageStore(sessionId);
    const messages = messageStore.messages;

    try {
      // 关键：每次 chat 调用时刷新系统提示词，确保包含最新的工具/技能描述
      // 解决上下文过长时 LLM 不调用工具的问题
      this._systemPrompt = this.agent._buildSystemPrompt();
      // 动态导入 AI SDK
      const { tool, ToolLoopAgent } = await this._importAI();
      // await fs.writeFile('system.md', this._systemPrompt);
      const userMessage =
        typeof message === 'string' ? { role: 'user', content: message } : message;
      messages.push(userMessage);

      // 检查是否需要压缩上下文（包括工具定义）
      const messagesTokens = this._countMessagesTokens(messages);
      const toolsTokens = this._countToolsTokens();
      const systemPromptTokens = this._countTokens(this._systemPrompt);
      const totalTokens = messagesTokens + toolsTokens + systemPromptTokens;
      const limit = this._maxContextTokens * 0.5; // 降低到 50%，更早压缩，防止 context window 超限
      // 对于流式调用，如果上下文太大，先压缩再开始
      if (totalTokens > limit) {
        logger.info(
          `Context large (${totalTokens}/${this._maxContextTokens} tokens), compressing...`
        );
        // 流式调用时等待压缩完成（使用带超时控制的压缩）
        await this._compressContext(sessionId, messages, messageStore);
        // 压缩后验证消息配对（防止 orphaned tool result）
        const validatedMessages = this._validateMessagesPairing(messages);
        if (validatedMessages.length !== messages.length) {
          messages.length = 0;
          messages.push(...validatedMessages);
        }
      }

      const maxSteps = options.maxSteps || this._maxSteps;
      const tools = this._getAITools(tool);
      if (!this._aiClient) {
        throw new Error('AI client not configured.');
      }

      // 验证消息配对（防止 API 调用时报 orphaned tool result 错误）
      const validatedMessages = this._validateMessagesPairing(messages);
      if (validatedMessages.length !== messages.length) {
        messages.length = 0;
        messages.push(...validatedMessages);
      }

      // 验证并修复消息中的不完整工具调用（在 API 调用前执行，防止开始聊天的时候就报错）
      this._validateToolCalls(messages);

      // 最终检查：在 API 调用前再次验证 token 数
      const finalMessagesTokens = this._countMessagesTokens(messages);
      const finalToolsTokens = this._countToolsTokens();
      const finalSystemPromptTokens = this._countTokens(this._systemPrompt);
      const finalTotalTokens = finalMessagesTokens + finalToolsTokens + finalSystemPromptTokens;
      const finalLimit = this._maxContextTokens * 0.5;

      logger.info(
        `[API Call Check (stream)] messages=${messages.length}, tokens=(${finalMessagesTokens}+${finalToolsTokens}+${finalSystemPromptTokens}=${finalTotalTokens}) vs limit=${finalLimit}`
      );

      // 如果仍然超过限制，强制压缩
      if (finalTotalTokens > finalLimit) {
        logger.warn(`[API Call Check (stream)] Still over limit, forcing compression`);
        await this._compressContext(sessionId, messages, messageStore);
        const compressedMessages = this._validateMessagesPairing(messages);
        messages.length = 0;
        messages.push(...compressedMessages);
        const afterTokens = this._countMessagesTokens(messages);
        logger.info(
          `[After Forced Compression (stream)] messages=${messages.length}, tokens=${afterTokens}`
        );
      }

      // 准备传给 agent 的消息 (流式版本)
      const prepareStepChainStream = async ({ stepNumber, messages: inputMessages }) => {
        try {
          // 1. 验证消息格式
          if (!Array.isArray(inputMessages)) {
            logger.warn('prepareStep received non-array messages');
            return { messages: inputMessages };
          }

          // 2. 修复不完整的工具调用（input 只有 { 或其他无效值）
          this._validateToolCalls(inputMessages);

          // 3. 验证消息配对并更新 inputMessages（防止 API 报 orphaned tool result 错误）
          const pairedMessages = this._validateMessagesPairing(inputMessages);
          if (pairedMessages.length !== inputMessages.length) {
            logger.debug(
              `prepareStep pairing: ${inputMessages.length} -> ${pairedMessages.length}`
            );
          }
          // 重要：用配对验证后的结果替换 inputMessages
          inputMessages.length = 0;
          inputMessages.push(...pairedMessages);

          // 4. Token 数检查：超过限制才修剪
          const tokenCount = this._countMessagesTokens(inputMessages);
          const tokenLimit = this._maxContextTokens * 0.75; // 75% 阈值，保留缓冲空间
          if (inputMessages.length <= 30 && tokenCount <= tokenLimit) {
            return { messages: inputMessages }; // 消息少且 token 未超限，不修剪
          }

          // 5. 保留配对完整的消息（使用 AI SDK 标准格式）
          const pruned = this._prepareMessagesForAI(inputMessages);
          logger.debug(`prepareStep pruned: ${inputMessages.length} -> ${pruned.length} messages`);

          // 6. 返回 AI SDK 6.x 要求的格式
          return { messages: pruned };
        } catch (err) {
          logger.error('prepareStep error:', err.message);
          return { messages: inputMessages };
        }
      };

      const agent = new ToolLoopAgent({
        model: this._aiClient,
        instructions: this._systemPrompt,
        tools: tools,
        stopWhen: isLoopFinished(),
        prepareStep: prepareStepChainStream,
      });

      // 使用 runInSession 让工具执行时能获取 sessionId（Per-Session 隔离）
      const result = await framework.runInSession(sessionId, {}, async () => {
        return agent.stream({
          messages,
          ...this.providerOptions,
        });
      });

      const stream = result.fullStream;
      let fullText = '';

      // 流式迭代器
      const iterator = stream[Symbol.asyncIterator] ? stream : stream.fullStream;

      for await (const part of iterator || stream) {
        if (part.type === 'text-delta') {
          const text = part.text || part.textDelta || '';
          fullText += text;
          yield { type: 'text', text };
        } else if (part.type === 'reasoning') {
          this.emit('thinking', { content: part.text });
        } else if (part.type === 'tool-call') {
          yield { type: 'tool-call', toolName: part.toolName, input: part.input };
        } else if (part.type === 'tool-result') {
          yield { type: 'tool-result', toolName: part.toolName, result: part.output };
        } else if (part.type === 'error') {
          yield { type: 'error', error: part.error };
        }
      }

      const finishMessages = (await result.response).messages;
      messages.push(...finishMessages);
      // 捕获 API 返回的 usage 用于更准确的 token 估算
      const usage = await result.totalUsage;
      if (usage) {
        try {
          this._updateMessageStoreUsage(messageStore, usage);
          logger.debug(
            `API usage (stream): input=${usage.inputTokens}, output=${usage.outputTokens}`
          );
        } catch (e) {
          logger.debug('Failed to capture stream usage:', e.message);
        }
      }

      // 触发 agent:message 事件，让 memory 插件可以自动提取记忆
      const userMsg = messages[messages.length - finishMessages.length - 1];
      this.emit('message', { content: fullText, sessionId: sessionId, userMessage: userMsg });
    } catch (err) {
      // 捕获 AI API 瞬时错误
      const errorMsg = err.message || String(err);
      const causeMsg = err.cause?.message || String(err.cause) || '';
      const fullMsg = errorMsg + causeMsg;

      const isTransientError =
        fullMsg.includes('Invalid JSON response') ||
        fullMsg.includes('负载较高') ||
        fullMsg.includes('Failed after') ||
        fullMsg.includes('timeout') ||
        fullMsg.includes('retry') ||
        err.name === 'AI_RetryError' ||
        err.name === 'AI_APICallError';

      if (isTransientError) {
        logger.warn(`[AgentChat] Transient AI error in stream: ${errorMsg}`);
        yield { type: 'error', error: 'AI 服务暂时不可用，请稍后重试。' };
      } else {
        logger.error(`[AgentChat] Stream error: ${err.message}, name: ${err.name}`);
        this.emit('error', { error: err.message });
        yield { type: 'error', error: err.message };
      }
    } finally {
      // 校验并修复消息中的不完整工具调用
      this._validateToolCalls(messages);
      // 确保保存聊天历史到 session（无论成功还是失败）
      messageStore.save();
    }
  }

  /**
   * 为 AI SDK prepareStep 准备消息
   * 确保 tool call 和 tool result 正确配对
   * @param {Array} messages - 消息列表
   * @returns {Array} 过滤后的消息数组
   * @private
   */
  _prepareMessagesForAI(messages) {
    if (messages.length <= 15) {
      return messages;
    }

    // 1. 确定要保留的消息范围（保留最近 N 条）
    const keepRecentCount = Math.min(30, Math.floor(messages.length * 0.4));
    const minIndexToKeep = Math.max(0, messages.length - keepRecentCount);

    // 2. 只从保留下来的消息（minIndexToKeep 之后）中收集 assistant 的 tool-call IDs
    // 重要：这样可以确保只收集保留下来的 assistant 消息的 tool-call IDs
    // 防止 assistant 消息被修剪但其 tool-call ID 仍然在集合中导致孤儿的 tool-result
    const assistantToolCallIds = new Set();
    for (let i = minIndexToKeep; i < messages.length; i++) {
      const msg = messages[i];
      if (msg.role === 'assistant' && Array.isArray(msg.content)) {
        for (const item of msg.content) {
          if ((item.type === 'tool-call' || item.type === 'tool-use') && item.toolCallId) {
            assistantToolCallIds.add(item.toolCallId);
          }
        }
      }
    }

    // 3. 收集有配对的 tool result indices（只在保留下来的消息中检查）
    const pairedToolResultIndices = new Set();
    for (let i = minIndexToKeep; i < messages.length; i++) {
      const msg = messages[i];
      if (msg.role === 'tool' && Array.isArray(msg.content)) {
        for (const item of msg.content) {
          if ((item.type === 'tool-result' || item.type === 'tool_result') && item.toolCallId) {
            if (assistantToolCallIds.has(item.toolCallId)) {
              pairedToolResultIndices.add(i);
            }
          }
        }
      }
    }

    // 4. 构建最终保留索引集合
    const indicesToKeep = new Set();

    // 从 minIndexToKeep 开始迭代，和原始代码保持一致
    for (let i = minIndexToKeep; i < messages.length; i++) {
      const msg = messages[i];
      if (msg.role === 'tool') {
        // 工具消息：只有有配对的才能保留
        if (pairedToolResultIndices.has(i)) {
          indicesToKeep.add(i);
        }
      } else {
        // 其他角色（user, system, assistant 等）默认保留
        indicesToKeep.add(i);
      }
    }

    // 5. 构建最终结果数组
    const result = [];
    for (let i = 0; i < messages.length; i++) {
      if (!indicesToKeep.has(i)) {
        continue;
      }

      const msg = messages[i];
      if (msg.role === 'assistant' && Array.isArray(msg.content)) {
        // 检查是否有未配对的 tool-call
        const hasUnpaired = msg.content.some((item) => {
          if (item.type === 'tool-call' || item.type === 'tool-use') {
            return !assistantToolCallIds.has(item.toolCallId);
          }
          return false;
        });

        if (hasUnpaired) {
          // 过滤掉未配对的 tool-call
          const filteredContent = msg.content.filter((item) => {
            if (item.type === 'tool-call' || item.type === 'tool-use') {
              return assistantToolCallIds.has(item.toolCallId);
            }
            return true;
          });

          if (filteredContent.length > 0) {
            result.push({ ...msg, content: filteredContent });
          }
        } else {
          result.push(msg);
        }
      } else if (msg.role === 'tool') {
        // 工具消息已经在第4步检查过了，直接添加
        result.push(msg);
      } else {
        // 其他角色（user, system等）默认保留
        result.push(msg);
      }
    }

    return result;
  }

  /**
   * 修剪消息确保配对的 tool call/result 不被拆分
   * @param {number} stepNumber - 当前步骤号
   * @param {Array} messages - 消息列表
   * @returns {Object} { messages } 或空对象
   * @private
   */
  _prepareStepForPruning(stepNumber, messages) {
    if (messages.length <= 50) {
      return {};
    }

    // 工具类型检查辅助函数（兼容 AI SDK 6.x）
    const isToolCall = (item) =>
      item && (item.type === 'tool-call' || item.type === 'tool-use') && item.toolCallId;
    const isToolResult = (item) =>
      item && (item.type === 'tool-result' || item.type === 'tool_result') && item.toolCallId;

    // 构建配对索引集合：确保 assistant 的 tool call 和 tool result 不会被单独裁剪
    const assistantIndices = new Map(); // toolCallId -> assistant index
    const toolResultIndices = new Map(); // toolCallId -> tool result indices

    for (let i = 0; i < messages.length; i++) {
      const msg = messages[i];
      if (msg.role === 'assistant' && msg.content) {
        const content = Array.isArray(msg.content) ? msg.content : [msg.content];
        for (const item of content) {
          if (isToolCall(item)) {
            assistantIndices.set(item.toolCallId, i);
          }
        }
      }
      if (msg.role === 'tool' && Array.isArray(msg.content)) {
        // 遍历 tool message 中的所有 tool-results
        for (const item of msg.content) {
          if (isToolResult(item)) {
            if (!toolResultIndices.has(item.toolCallId)) {
              toolResultIndices.set(item.toolCallId, []);
            }
            toolResultIndices.get(item.toolCallId).push(i);
          }
        }
      }
    }

    const recentCount = Math.max(10, messages.length - 10);
    let minIndexToKeep = messages.length - recentCount;

    // 情况1：保留被裁剪范围内的 assistant 消息及其 tool result
    for (const [toolCallId, assistantIdx] of assistantIndices) {
      if (assistantIdx < minIndexToKeep) {
        minIndexToKeep = Math.min(minIndexToKeep, assistantIdx);
      }
    }

    // 情况2：保留被裁剪范围内的 tool result 及其 assistant 消息
    for (const [toolCallId, resultIndices] of toolResultIndices) {
      for (const resultIdx of resultIndices) {
        if (resultIdx >= minIndexToKeep) {
          const assistantIdx = assistantIndices.get(toolCallId);
          if (assistantIdx !== undefined && assistantIdx < minIndexToKeep) {
            minIndexToKeep = Math.min(minIndexToKeep, assistantIdx);
          }
        }
      }
    }

    const pruned = messages.slice(minIndexToKeep);
    // logger.info(
    //   `prepareStep pruned: ${messages.length} -> ${pruned.length} (step ${stepNumber}, kept from index ${minIndexToKeep})`
    // );
    return { messages: pruned };
  }

  /**
   * 验证并修复消息中的 tool call/result 配对
   * 删除没有对应 assistant tool_call 的 tool result（会导致 API 报错）
   * 兼容 AI SDK 6.x 标准格式
   * @param {Array} messages - 消息列表
   * @private
   */
  _validateMessagesPairing(messages) {
    // 收集所有 assistant 的 tool-call IDs（兼容多种类型名）
    const assistantToolCallIds = new Set();
    for (const msg of messages) {
      if (msg.role === 'assistant' && Array.isArray(msg.content)) {
        for (const item of msg.content) {
          // 兼容 tool-call 和 tool-use 两种类型
          if ((item.type === 'tool-call' || item.type === 'tool-use') && item.toolCallId) {
            assistantToolCallIds.add(item.toolCallId);
          }
        }
      }
    }

    // 检查并删除没有配对的 tool result
    let removedCount = 0;
    for (const msg of messages) {
      if (msg.role === 'tool' && Array.isArray(msg.content)) {
        const originalLength = msg.content.length;
        msg.content = msg.content.filter((item) => {
          // 兼容 tool-result 和 tool_result 两种类型
          if (
            item &&
            (item.type === 'tool-result' || item.type === 'tool_result') &&
            item.toolCallId
          ) {
            if (!assistantToolCallIds.has(item.toolCallId)) {
              removedCount++;
              return false; // 删除没有配对的 tool result
            }
          }
          return true;
        });

        // 如果所有 content 都被删除了，标记整个消息待删除
        if (msg.content.length === 0 && originalLength > 0) {
          msg._orphaned = true;
        }
      }
    }

    // 移除被标记为 orphaned 的 tool 消息
    const originalLength = messages.length;
    const filtered = messages.filter((msg) => !(msg.role === 'tool' && msg._orphaned));

    if (removedCount > 0 || filtered.length !== originalLength) {
      logger.debug(
        `Removed ${removedCount} orphaned tool-results, ${originalLength - filtered.length} orphaned tool messages`
      );
    }

    return filtered;
  }

  /**
   * 校验并修复消息中的工具调用参数
   * 移除不完整的 JSON（如只有 "{" ）的工具调用
   * 同时清理 tool result 中的错误信息
   * 兼容 AI SDK 6.x 标准格式
   * @param {Array} messages - 消息列表
   * @private
   */
  _validateToolCalls(messages) {
    let fixedCount = 0;
    // 收集被跳过的 toolCallId，用于清理对应的 tool-result
    const invalidatedToolCallIds = new Set();

    for (const msg of messages) {
      // 清理 assistant 消息中的不完整 tool-call
      if (msg.role === 'assistant' && Array.isArray(msg.content)) {
        for (const item of msg.content) {
          // 兼容 tool-call 和 tool-use 两种类型
          if (item.type !== 'tool-call' && item.type !== 'tool-use') {
            continue;
          }

          const input = item.input;
          if (typeof input !== 'string') {
            continue;
          }

          // 检查 input 是否是有效的 JSON（不是不完整的）
          const trimmed = input.trim();
          if (trimmed === '{' || trimmed === '' || !trimmed.startsWith('{')) {
            // 不完整的 JSON，移除这个 tool-call
            // 记录 toolCallId，以便后续清理对应的 tool-result
            if (item.toolCallId) {
              invalidatedToolCallIds.add(item.toolCallId);
            }
            logger.warn(
              `_validateToolCalls: invalid tool-call input="${input}", toolCallId=${item.toolCallId}, converting to text`
            );
            item.type = 'text';
            item.text = `（工具调用 ${item.toolName} 参数不完整，已跳过）`;
            delete item.toolCallId;
            delete item.toolName;
            delete item.input;
            fixedCount++;
          }
        }
      }
    }

    // 如果有无效的 tool-call，清理对应的 tool-result
    if (invalidatedToolCallIds.size > 0) {
      logger.warn(
        `_validateToolCalls: removing ${invalidatedToolCallIds.size} tool-results with invalidated toolCallIds`
      );
      for (const msg of messages) {
        if (msg.role === 'tool' && Array.isArray(msg.content)) {
          // 过滤掉引用了无效 toolCallId 的 tool-result
          const oldLen = msg.content.length;
          msg.content = msg.content.filter((item) => {
            if (item.type !== 'tool-result' && item.type !== 'tool_result') {
              return true;
            }
            // 如果 tool-result 引用的 toolCallId 已被标记为无效，则移除
            if (item.toolCallId && invalidatedToolCallIds.has(item.toolCallId)) {
              logger.warn(
                `_validateToolCalls: removing orphaned tool-result with toolCallId=${item.toolCallId}`
              );
              fixedCount++;
              return false;
            }
            return true;
          });
        }
      }
    }

    if (fixedCount > 0) {
      logger.info(`_validateToolCalls: Fixed ${fixedCount} incomplete tool calls/results`);
    }
  }

  /**
   * 获取 AI SDK 格式的工具
   * AI SDK 6.x 需要对象形式 { toolName: tool }
   * @param {Function} toolFn - AI SDK 的 tool 函数
   * @private
   */
  _getAITools(toolFn) {
    const tools = {};

    for (const [name, toolDef] of this._tools) {
      const toolName = toolDef.name || name;

      // 使用 AI SDK 的 tool() 格式
      // 支持 inputSchema (zod schema) 或 parameters (旧格式)
      const toolConfig = {
        name: toolName,
        description: toolDef.description || '',
        execute: async (args) => {
          // 清理参数：移除 undefined、function 等无效值
          const cleanedArgs = this._cleanToolArgs(args);

          // 执行工具
          this.emit('tool-call', { name: toolName, args: cleanedArgs });
          try {
            const result = await toolDef.execute(cleanedArgs, this.agent.framework);
            this.emit('tool-result', { name: toolName, args: cleanedArgs, result });
            if (result !== null && typeof result === 'object') {
              return JSON.stringify(result);
            }
            return String(result);
          } catch (err) {
            this.emit('tool-error', { name: toolName, args: cleanedArgs, error: err.message });
            return { error: err.message };
          }
        },
      };

      // 支持 inputSchema (zod) 或 parameters (旧格式)
      if (toolDef.inputSchema) {
        toolConfig.inputSchema = toolDef.inputSchema;
      } else if (toolDef.parameters) {
        toolConfig.parameters = toolDef.parameters;
      }

      // AI SDK 6.x 使用对象形式，键为工具名
      tools[toolName] = toolFn(toolConfig);
    }

    return tools;
  }

  /**
   * 清理工具参数，移除无效值
   * @param {Object} args - 原始参数
   * @returns {Object} 清理后的参数
   * @private
   */
  _cleanToolArgs(args) {
    if (!args || typeof args !== 'object') {
      return {};
    }

    const cleaned = {};
    for (const [key, value] of Object.entries(args)) {
      // 跳过 undefined、function、symbol 等无效值
      if (value === undefined || value === null) {
        continue;
      }
      if (typeof value === 'function' || typeof value === 'symbol') {
        continue;
      }
      // 递归清理嵌套对象
      if (typeof value === 'object' && !Array.isArray(value)) {
        cleaned[key] = this._cleanToolArgs(value);
      } else if (Array.isArray(value)) {
        cleaned[key] = value
          .map((item) =>
            typeof item === 'object' && item !== null ? this._cleanToolArgs(item) : item
          )
          .filter((item) => item !== undefined && typeof item !== 'function');
      } else {
        cleaned[key] = value;
      }
    }
    return cleaned;
  }

  /**
   * 销毁
   */
  destroy() {
    this._sessionMessageStores.clear();
    this._sessionMessageStores = new Map();
    this._tools.clear();
    this._encoder = null;
    this.removeAllListeners();
  }
}

module.exports = { AgentChatHandler };
