|
|
|
@ -12,21 +12,31 @@ import com.claudecode.repl.ReplSession; |
|
|
|
import com.claudecode.tool.ToolContext; |
|
|
|
import com.claudecode.tool.ToolContext; |
|
|
|
import com.claudecode.tool.ToolRegistry; |
|
|
|
import com.claudecode.tool.ToolRegistry; |
|
|
|
import com.claudecode.tool.impl.*; |
|
|
|
import com.claudecode.tool.impl.*; |
|
|
|
|
|
|
|
import org.slf4j.Logger; |
|
|
|
|
|
|
|
import org.slf4j.LoggerFactory; |
|
|
|
import org.springframework.ai.chat.model.ChatModel; |
|
|
|
import org.springframework.ai.chat.model.ChatModel; |
|
|
|
import org.springframework.beans.factory.annotation.Qualifier; |
|
|
|
import org.springframework.beans.factory.annotation.Qualifier; |
|
|
|
|
|
|
|
import org.springframework.beans.factory.annotation.Value; |
|
|
|
import org.springframework.context.annotation.Bean; |
|
|
|
import org.springframework.context.annotation.Bean; |
|
|
|
import org.springframework.context.annotation.Configuration; |
|
|
|
import org.springframework.context.annotation.Configuration; |
|
|
|
|
|
|
|
|
|
|
|
import java.nio.file.Path; |
|
|
|
import java.nio.file.Path; |
|
|
|
|
|
|
|
import java.util.Map; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* 应用配置类 —— Spring Bean 装配。 |
|
|
|
* 应用配置类 —— Spring Bean 装配。 |
|
|
|
* <p> |
|
|
|
* <p> |
|
|
|
* 集中管理所有组件的创建和依赖注入。 |
|
|
|
* 集中管理所有组件的创建和依赖注入。 |
|
|
|
|
|
|
|
* 通过 claude-code.provider 配置切换 API 提供者(openai / anthropic)。 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@Configuration |
|
|
|
@Configuration |
|
|
|
public class AppConfig { |
|
|
|
public class AppConfig { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static final Logger log = LoggerFactory.getLogger(AppConfig.class); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Value("${claude-code.provider:openai}") |
|
|
|
|
|
|
|
private String provider; |
|
|
|
|
|
|
|
|
|
|
|
@Bean |
|
|
|
@Bean |
|
|
|
public ToolContext toolContext() { |
|
|
|
public ToolContext toolContext() { |
|
|
|
return ToolContext.defaultContext(); |
|
|
|
return ToolContext.defaultContext(); |
|
|
|
@ -69,11 +79,45 @@ public class AppConfig { |
|
|
|
return registry; |
|
|
|
return registry; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 根据 claude-code.provider 配置选择 ChatModel。 |
|
|
|
|
|
|
|
* - "anthropic" → 使用 anthropicChatModel |
|
|
|
|
|
|
|
* - "openai"(默认)→ 使用 openAiChatModel |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
@Bean |
|
|
|
|
|
|
|
public ChatModel activeChatModel( |
|
|
|
|
|
|
|
@Qualifier("openAiChatModel") ChatModel openAiModel, |
|
|
|
|
|
|
|
@Qualifier("anthropicChatModel") ChatModel anthropicModel) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ("anthropic".equalsIgnoreCase(provider)) { |
|
|
|
|
|
|
|
log.info("使用 Anthropic 原生 API"); |
|
|
|
|
|
|
|
return anthropicModel; |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
log.info("使用 OpenAI 兼容 API"); |
|
|
|
|
|
|
|
return openAiModel; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Bean |
|
|
|
@Bean |
|
|
|
public TokenTracker tokenTracker() { |
|
|
|
public ProviderInfo providerInfo() { |
|
|
|
|
|
|
|
String baseUrl; |
|
|
|
|
|
|
|
String model; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ("anthropic".equalsIgnoreCase(provider)) { |
|
|
|
|
|
|
|
baseUrl = System.getenv().getOrDefault("ANTHROPIC_BASE_URL", "https://api.anthropic.com"); |
|
|
|
|
|
|
|
model = System.getenv().getOrDefault("AI_MODEL", "claude-sonnet-4-20250514"); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
baseUrl = System.getenv().getOrDefault("AI_BASE_URL", "https://api.openai.com"); |
|
|
|
|
|
|
|
model = System.getenv().getOrDefault("AI_OPENAI_MODEL", "gpt-4o"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return new ProviderInfo(provider, baseUrl, model); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Bean |
|
|
|
|
|
|
|
public TokenTracker tokenTracker(ProviderInfo info) { |
|
|
|
TokenTracker tracker = new TokenTracker(); |
|
|
|
TokenTracker tracker = new TokenTracker(); |
|
|
|
String model = System.getenv().getOrDefault("AI_MODEL", "claude-sonnet-4-20250514"); |
|
|
|
tracker.setModel(info.model()); |
|
|
|
tracker.setModel(model); |
|
|
|
|
|
|
|
return tracker; |
|
|
|
return tracker; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -81,16 +125,13 @@ public class AppConfig { |
|
|
|
public String systemPrompt() { |
|
|
|
public String systemPrompt() { |
|
|
|
Path projectDir = Path.of(System.getProperty("user.dir")); |
|
|
|
Path projectDir = Path.of(System.getProperty("user.dir")); |
|
|
|
|
|
|
|
|
|
|
|
// 加载 CLAUDE.md
|
|
|
|
|
|
|
|
ClaudeMdLoader claudeLoader = new ClaudeMdLoader(projectDir); |
|
|
|
ClaudeMdLoader claudeLoader = new ClaudeMdLoader(projectDir); |
|
|
|
String claudeMd = claudeLoader.load(); |
|
|
|
String claudeMd = claudeLoader.load(); |
|
|
|
|
|
|
|
|
|
|
|
// 加载 Skills
|
|
|
|
|
|
|
|
SkillLoader skillLoader = new SkillLoader(projectDir); |
|
|
|
SkillLoader skillLoader = new SkillLoader(projectDir); |
|
|
|
skillLoader.loadAll(); |
|
|
|
skillLoader.loadAll(); |
|
|
|
String skillsSummary = skillLoader.buildSkillsSummary(); |
|
|
|
String skillsSummary = skillLoader.buildSkillsSummary(); |
|
|
|
|
|
|
|
|
|
|
|
// 收集 Git 上下文
|
|
|
|
|
|
|
|
GitContext gitContext = new GitContext(projectDir).collect(); |
|
|
|
GitContext gitContext = new GitContext(projectDir).collect(); |
|
|
|
String gitSummary = gitContext.buildSummary(); |
|
|
|
String gitSummary = gitContext.buildSummary(); |
|
|
|
|
|
|
|
|
|
|
|
@ -102,14 +143,14 @@ public class AppConfig { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Bean |
|
|
|
@Bean |
|
|
|
public AgentLoop agentLoop(@Qualifier("anthropicChatModel") ChatModel chatModel, ToolRegistry toolRegistry, |
|
|
|
public AgentLoop agentLoop(ChatModel activeChatModel, ToolRegistry toolRegistry, |
|
|
|
ToolContext toolContext, String systemPrompt, TokenTracker tokenTracker) { |
|
|
|
ToolContext toolContext, String systemPrompt, TokenTracker tokenTracker) { |
|
|
|
AgentLoop mainLoop = new AgentLoop(chatModel, toolRegistry, toolContext, systemPrompt, tokenTracker); |
|
|
|
AgentLoop mainLoop = new AgentLoop(activeChatModel, toolRegistry, toolContext, systemPrompt, tokenTracker); |
|
|
|
|
|
|
|
|
|
|
|
// 注册子 Agent 工厂到 ToolContext,使 AgentTool 能创建独立的 AgentLoop
|
|
|
|
// 注册子 Agent 工厂
|
|
|
|
toolContext.set(AgentTool.AGENT_FACTORY_KEY, |
|
|
|
toolContext.set(AgentTool.AGENT_FACTORY_KEY, |
|
|
|
(java.util.function.Function<String, String>) prompt -> { |
|
|
|
(java.util.function.Function<String, String>) prompt -> { |
|
|
|
AgentLoop subLoop = new AgentLoop(chatModel, toolRegistry, toolContext, systemPrompt); |
|
|
|
AgentLoop subLoop = new AgentLoop(activeChatModel, toolRegistry, toolContext, systemPrompt); |
|
|
|
return subLoop.run(prompt); |
|
|
|
return subLoop.run(prompt); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
@ -118,7 +159,11 @@ public class AppConfig { |
|
|
|
|
|
|
|
|
|
|
|
@Bean |
|
|
|
@Bean |
|
|
|
public ReplSession replSession(AgentLoop agentLoop, ToolRegistry toolRegistry, |
|
|
|
public ReplSession replSession(AgentLoop agentLoop, ToolRegistry toolRegistry, |
|
|
|
CommandRegistry commandRegistry) { |
|
|
|
CommandRegistry commandRegistry, ProviderInfo providerInfo) { |
|
|
|
return new ReplSession(agentLoop, toolRegistry, commandRegistry); |
|
|
|
return new ReplSession(agentLoop, toolRegistry, commandRegistry, providerInfo); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** API 提供者信息,供 Banner 和命令显示 */ |
|
|
|
|
|
|
|
public record ProviderInfo(String provider, String baseUrl, String model) { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|