package com.claudecode.command.impl; import com.claudecode.command.CommandContext; import com.claudecode.command.SlashCommand; import com.claudecode.console.AnsiStyle; import com.claudecode.core.TokenTracker; import org.springframework.ai.chat.messages.*; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; import java.util.ArrayList; import java.util.List; /** * /compact 命令 —— 用 AI 生成摘要来压缩上下文。 *

* 对应 claude-code/src/commands/compact.ts。 * 将详细的对话历史替换为 AI 生成的摘要,大幅减少 token 消耗。 * 保留系统提示词和最近一轮对话。 */ public class CompactCommand implements SlashCommand { private static final String COMPACT_PROMPT = """ 请将以下对话历史压缩为一段简洁的摘要。要求: 1. 保留所有关键决策、代码变更和技术细节 2. 保留文件路径、函数名等具体信息 3. 保留用户的偏好和要求 4. 省略重复的讨论和无关的细节 5. 用中文输出,控制在500字以内 对话历史: """; @Override public String name() { return "compact"; } @Override public String description() { return "Compact conversation context with AI summary"; } @Override public String execute(String args, CommandContext context) { if (context.agentLoop() == null) { return AnsiStyle.yellow(" ⚠ 没有活跃的对话可压缩。"); } List history = context.agentLoop().getMessageHistory(); int before = history.size(); if (before <= 3) { return AnsiStyle.dim(" 上下文已经很小(" + before + " 条消息),无需压缩。"); } TokenTracker tracker = context.agentLoop().getTokenTracker(); long tokensBefore = tracker.getInputTokens() + tracker.getOutputTokens(); // 尝试用 AI 生成摘要 String summary = generateSummary(context, history); // 构建压缩后的历史:系统提示 + 摘要作为系统消息 + 保留最后一轮对话 List compacted = new ArrayList<>(); compacted.add(history.getFirst()); // 原始系统提示词 if (summary != null && !summary.isBlank()) { compacted.add(new SystemMessage("[对话历史摘要] " + summary)); } // 保留最后一轮用户消息和助手回复(如果有) for (int i = Math.max(1, before - 2); i < before; i++) { compacted.add(history.get(i)); } context.agentLoop().replaceHistory(compacted); int after = compacted.size(); StringBuilder sb = new StringBuilder(); sb.append(AnsiStyle.green(" ✅ 上下文已压缩")).append("\n"); sb.append(" 消息数: ").append(before).append(" → ").append(after).append("\n"); if (tokensBefore > 0) { sb.append(" 压缩前累计 Token: ").append(TokenTracker.formatTokens(tokensBefore)).append("\n"); } if (summary != null) { sb.append(AnsiStyle.dim(" 📝 AI 摘要已生成并注入上下文")); } else { sb.append(AnsiStyle.dim(" ⚠ AI 摘要生成失败,仅保留最近对话")); } return sb.toString(); } /** 调用 AI 生成对话摘要 */ private String generateSummary(CommandContext context, List history) { try { ChatModel chatModel = context.agentLoop().getChatModel(); // 构建摘要请求的消息列史 StringBuilder dialogText = new StringBuilder(); for (Message msg : history) { switch (msg) { case UserMessage um -> dialogText.append("[用户] ").append(um.getText()).append("\n"); case AssistantMessage am -> { if (am.getText() != null && !am.getText().isBlank()) { // 截断过长的助手回复 String text = am.getText(); if (text.length() > 500) text = text.substring(0, 500) + "..."; dialogText.append("[助手] ").append(text).append("\n"); } if (am.hasToolCalls()) { for (var tc : am.getToolCalls()) { dialogText.append("[工具调用] ").append(tc.name()).append("\n"); } } } default -> {} // 跳过系统消息和工具响应 } } if (dialogText.isEmpty()) return null; Prompt summaryPrompt = new Prompt(List.of( new UserMessage(COMPACT_PROMPT + dialogText) )); ChatResponse response = chatModel.call(summaryPrompt); return response.getResult().getOutput().getText(); } catch (Exception e) { // 摘要生成失败不影响压缩操作 return null; } } }