package com.claudecode.config; import com.claudecode.command.CommandRegistry; import com.claudecode.command.impl.*; import com.claudecode.context.ClaudeMdLoader; import com.claudecode.context.GitContext; import com.claudecode.context.SkillLoader; import com.claudecode.context.SystemPromptBuilder; import com.claudecode.core.AgentLoop; import com.claudecode.core.TokenTracker; import com.claudecode.repl.ReplSession; import com.claudecode.tool.ToolContext; import com.claudecode.tool.ToolRegistry; import com.claudecode.tool.impl.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ai.chat.model.ChatModel; 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.Configuration; import java.nio.file.Path; import java.util.Map; /** * 应用配置类 —— Spring Bean 装配。 *
* 集中管理所有组件的创建和依赖注入。
* 通过 claude-code.provider 配置切换 API 提供者(openai / anthropic)。
*/
@Configuration
public class AppConfig {
private static final Logger log = LoggerFactory.getLogger(AppConfig.class);
@Value("${claude-code.provider:openai}")
private String provider;
@Bean
public ToolContext toolContext() {
return ToolContext.defaultContext();
}
@Bean
public ToolRegistry toolRegistry() {
ToolRegistry registry = new ToolRegistry();
registry.registerAll(
new BashTool(),
new FileReadTool(),
new FileWriteTool(),
new FileEditTool(),
new GlobTool(),
new GrepTool(),
new ListFilesTool(),
new WebFetchTool(),
new TodoWriteTool(),
new AgentTool(),
new NotebookEditTool()
);
return registry;
}
@Bean
public CommandRegistry commandRegistry() {
CommandRegistry registry = new CommandRegistry();
registry.registerAll(
new HelpCommand(),
new ClearCommand(),
new CompactCommand(),
new CostCommand(),
new ModelCommand(),
new StatusCommand(),
new ContextCommand(),
new InitCommand(),
new ConfigCommand(),
new ExitCommand()
);
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
public ProviderInfo providerInfo() {
// 统一使用 AI_BASE_URL / AI_MODEL 环境变量,按 Provider 给不同默认值
String baseUrl;
String model;
if ("anthropic".equalsIgnoreCase(provider)) {
baseUrl = System.getenv().getOrDefault("AI_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_MODEL", "gpt-4o");
}
return new ProviderInfo(provider, baseUrl, model);
}
@Bean
public TokenTracker tokenTracker(ProviderInfo info) {
TokenTracker tracker = new TokenTracker();
tracker.setModel(info.model());
return tracker;
}
@Bean
public String systemPrompt() {
Path projectDir = Path.of(System.getProperty("user.dir"));
ClaudeMdLoader claudeLoader = new ClaudeMdLoader(projectDir);
String claudeMd = claudeLoader.load();
SkillLoader skillLoader = new SkillLoader(projectDir);
skillLoader.loadAll();
String skillsSummary = skillLoader.buildSkillsSummary();
GitContext gitContext = new GitContext(projectDir).collect();
String gitSummary = gitContext.buildSummary();
return new SystemPromptBuilder()
.claudeMd(claudeMd)
.skills(skillsSummary)
.git(gitSummary)
.build();
}
@Bean
public AgentLoop agentLoop(ChatModel activeChatModel, ToolRegistry toolRegistry,
ToolContext toolContext, String systemPrompt, TokenTracker tokenTracker) {
AgentLoop mainLoop = new AgentLoop(activeChatModel, toolRegistry, toolContext, systemPrompt, tokenTracker);
// 注册子 Agent 工厂
toolContext.set(AgentTool.AGENT_FACTORY_KEY,
(java.util.function.Function