refactor(agent): 将 Summary Model 改造为内置 Sub Agent
- 扩展 AgentMode 类型添加 'internal' 模式 - 新增 summary agent preset (claude-3-5-haiku) - AgentRegistry 添加 getInternal/listInternalAgents 方法 - CompressionManager 添加 setSummaryModelFromAgentConfig - Agent 构造函数改用 Registry 配置初始化 Summary 模型 - 清理旧的 SummaryConfig 配置系统 - UI AgentsPanel 分离显示 System/Preset/Custom agents - UI AgentEditor 为 internal agent 显示简化编辑界面
This commit is contained in:
@@ -5,6 +5,7 @@ import { codeReviewerAgent } from './code-reviewer.js';
|
||||
import { buildAgent } from './build.js';
|
||||
import { planAgent } from './plan.js';
|
||||
import { visionAgent } from './vision.js';
|
||||
import { summaryAgent } from './summary.js';
|
||||
|
||||
/**
|
||||
* 预设 Agent 集合
|
||||
@@ -16,6 +17,7 @@ export const presetAgents: Record<string, Omit<AgentInfo, 'name'>> = {
|
||||
build: buildAgent,
|
||||
plan: planAgent,
|
||||
vision: visionAgent,
|
||||
summary: summaryAgent,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -32,4 +34,4 @@ export function isPresetAgent(name: string): boolean {
|
||||
return name in presetAgents;
|
||||
}
|
||||
|
||||
export { generalAgent, exploreAgent, codeReviewerAgent, buildAgent, planAgent, visionAgent };
|
||||
export { generalAgent, exploreAgent, codeReviewerAgent, buildAgent, planAgent, visionAgent, summaryAgent };
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { AgentInfo } from '../types.js';
|
||||
|
||||
/**
|
||||
* Summary Agent
|
||||
* 内部 Agent,用于对话压缩时生成摘要
|
||||
* 推荐使用成本较低的模型
|
||||
*/
|
||||
export const summaryAgent: Omit<AgentInfo, 'name'> = {
|
||||
description: '对话压缩摘要生成(内部使用)',
|
||||
mode: 'internal',
|
||||
model: {
|
||||
provider: 'anthropic',
|
||||
model: 'claude-3-5-haiku-20241022',
|
||||
},
|
||||
tools: {
|
||||
enabled: [], // 无工具,纯文本生成
|
||||
noTask: true,
|
||||
},
|
||||
maxSteps: 1,
|
||||
};
|
||||
@@ -61,16 +61,36 @@ export class AgentRegistry {
|
||||
|
||||
/**
|
||||
* 列出可作为子 Agent 的 Agent(供 Task 工具使用)
|
||||
* 排除 primary 和 internal 模式
|
||||
*/
|
||||
listSubagents(): AgentInfo[] {
|
||||
return this.list().filter((a) => a.mode !== 'primary');
|
||||
return this.list().filter((a) => a.mode !== 'primary' && a.mode !== 'internal');
|
||||
}
|
||||
|
||||
/**
|
||||
* 列出可作为主交互 Agent 的 Agent(供 /agent 命令使用)
|
||||
* 排除 internal 模式
|
||||
*/
|
||||
listPrimaryAgents(): AgentInfo[] {
|
||||
return this.list().filter((a) => a.mode !== 'subagent');
|
||||
return this.list().filter((a) => a.mode !== 'subagent' && a.mode !== 'internal');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取内部 Agent(internal 模式)
|
||||
*/
|
||||
getInternal(name: string): AgentInfo | undefined {
|
||||
const agent = this.get(name);
|
||||
if (agent?.mode === 'internal') {
|
||||
return agent;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 列出所有内部 Agent
|
||||
*/
|
||||
listInternalAgents(): AgentInfo[] {
|
||||
return this.list().filter((a) => a.mode === 'internal');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,8 +9,9 @@ export type { PermissionAction, PermissionRule };
|
||||
* - primary: 主 Agent,用户直接使用
|
||||
* - subagent: 子 Agent,由 Task 工具调用
|
||||
* - all: 两种方式都可以
|
||||
* - internal: 内部使用,不对外暴露(如 Summary Agent)
|
||||
*/
|
||||
export type AgentMode = 'primary' | 'subagent' | 'all';
|
||||
export type AgentMode = 'primary' | 'subagent' | 'all' | 'internal';
|
||||
|
||||
/**
|
||||
* Agent Bash 权限配置
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
CompressionStatus,
|
||||
DEFAULT_COMPRESSION_CONFIG,
|
||||
} from './types.js';
|
||||
import type { AgentModelConfig } from '../agent/types.js';
|
||||
import { getProviderRegistry } from '../provider/index.js';
|
||||
|
||||
/**
|
||||
* 压缩管理器
|
||||
@@ -41,6 +43,32 @@ export class CompressionManager {
|
||||
this.summaryModel = model;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Agent 配置设置摘要模型
|
||||
* @param config Agent 的模型配置
|
||||
* @param apiKey API Key
|
||||
* @param baseUrl 可选的 Base URL
|
||||
*/
|
||||
setSummaryModelFromAgentConfig(
|
||||
config: AgentModelConfig,
|
||||
apiKey: string,
|
||||
baseUrl?: string
|
||||
): void {
|
||||
const registry = getProviderRegistry();
|
||||
const provider = config.provider || 'anthropic';
|
||||
const modelName = config.model || 'claude-3-5-haiku-20241022';
|
||||
|
||||
try {
|
||||
const getModel = registry.getModelFactory(provider, {
|
||||
apiKey,
|
||||
baseUrl,
|
||||
});
|
||||
this.summaryModel = getModel(modelName);
|
||||
} catch (error) {
|
||||
console.warn('[CompressionManager] Failed to create summary model:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用于摘要生成的模型
|
||||
* 优先使用专用摘要模型,无则使用主模型
|
||||
|
||||
@@ -18,8 +18,8 @@ import {
|
||||
} from '../context/index.js';
|
||||
import type { AgentInfo, ImageData } from '../agent/types.js';
|
||||
import { agentRegistry, AgentExecutor } from '../agent/index.js';
|
||||
import { loadVisionConfig, loadSummaryConfig } from '../utils/config.js';
|
||||
import { getProviderRegistry } from '../provider/index.js';
|
||||
import { loadVisionConfig } from '../utils/config.js';
|
||||
import { getProviderRegistry, resolveApiKey } from '../provider/index.js';
|
||||
import { getHookManager } from '../hooks/index.js';
|
||||
import { getGitManager } from '../git/index.js';
|
||||
|
||||
@@ -62,15 +62,38 @@ export class Agent {
|
||||
// 设置主模型(作为摘要模型的后备)
|
||||
this.compressionManager.setModel(this.getModel(config.model));
|
||||
|
||||
// 加载摘要模型配置(可选,用于降低压缩成本)
|
||||
const summaryConfig = loadSummaryConfig();
|
||||
if (summaryConfig) {
|
||||
const summaryModelFactory = providerRegistry.getModelFactory(summaryConfig.provider, {
|
||||
apiKey: summaryConfig.apiKey,
|
||||
baseUrl: summaryConfig.baseUrl,
|
||||
});
|
||||
this.compressionManager.setSummaryModel(summaryModelFactory(summaryConfig.model));
|
||||
// 从 Agent Registry 加载 Summary Agent 配置
|
||||
this.initSummaryModel(config, providerRegistry);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Agent Registry 初始化 Summary 模型
|
||||
*/
|
||||
private initSummaryModel(
|
||||
config: AgentConfig,
|
||||
providerRegistry: ReturnType<typeof getProviderRegistry>
|
||||
): void {
|
||||
// 获取 Summary Agent(internal 模式)
|
||||
const summaryAgentInfo = agentRegistry.getInternal('summary');
|
||||
if (!summaryAgentInfo?.model) {
|
||||
return;
|
||||
}
|
||||
|
||||
const modelConfig = summaryAgentInfo.model;
|
||||
// 确定 provider(默认使用主配置的 provider)
|
||||
const provider = modelConfig.provider || config.provider;
|
||||
|
||||
// 从 ProviderRegistry 获取 API Key
|
||||
const providerConfig = providerRegistry.getConfig(provider);
|
||||
const apiKey = resolveApiKey(providerConfig) || config.apiKey;
|
||||
|
||||
if (!apiKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置 Summary 模型
|
||||
const baseUrl = providerConfig?.baseUrl;
|
||||
this.compressionManager.setSummaryModelFromAgentConfig(modelConfig, apiKey, baseUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export { Agent } from './core/agent.js';
|
||||
export { toolRegistry, todoManager, initTaskContext, updateTaskDescription, updateSkillDescription } from './tools/index.js';
|
||||
export { loadConfig, saveConfig, getConfig, loadVisionConfig, loadSummaryConfig } from './utils/config.js';
|
||||
export type { VisionConfig, SummaryConfig } from './utils/config.js';
|
||||
export { loadConfig, saveConfig, getConfig, loadVisionConfig } from './utils/config.js';
|
||||
export type { VisionConfig } from './utils/config.js';
|
||||
|
||||
// Context compression
|
||||
export {
|
||||
|
||||
@@ -19,11 +19,6 @@ interface StoredConfig {
|
||||
visionModel?: string;
|
||||
/** Vision 专用的 Base URL(用于 OpenAI 兼容的 Vision 服务) */
|
||||
visionBaseUrl?: string;
|
||||
// Summary 配置(用于对话压缩摘要生成)
|
||||
summaryProvider?: ProviderType;
|
||||
summaryModel?: string;
|
||||
/** Summary 专用的 Base URL(用于 OpenAI 兼容的 Summary 服务) */
|
||||
summaryBaseUrl?: string;
|
||||
}
|
||||
|
||||
// Vision 配置接口
|
||||
@@ -35,15 +30,6 @@ export interface VisionConfig {
|
||||
baseUrl?: string;
|
||||
}
|
||||
|
||||
// Summary 配置接口(用于对话压缩摘要生成)
|
||||
export interface SummaryConfig {
|
||||
provider: ProviderType;
|
||||
apiKey: string;
|
||||
model: string;
|
||||
/** 自定义 Base URL(用于 OpenAI 兼容的 Summary 服务) */
|
||||
baseUrl?: string;
|
||||
}
|
||||
|
||||
// 默认模型配置
|
||||
const DEFAULT_MODELS: Record<ProviderType, string> = {
|
||||
anthropic: 'claude-sonnet-4-20250514',
|
||||
@@ -58,13 +44,6 @@ const DEFAULT_VISION_MODELS: Record<ProviderType, string> = {
|
||||
openai: 'gpt-4o',
|
||||
};
|
||||
|
||||
// 默认 Summary 模型(推荐使用成本较低的模型)
|
||||
const DEFAULT_SUMMARY_MODELS: Record<ProviderType, string> = {
|
||||
anthropic: 'claude-3-5-haiku-20241022',
|
||||
deepseek: 'deepseek-chat',
|
||||
openai: 'gpt-4o-mini',
|
||||
};
|
||||
|
||||
// 默认系统提示词
|
||||
const DEFAULT_SYSTEM_PROMPT = `你是一个运行在终端中的 AI 编程助手。你可以帮助用户:
|
||||
- 读取和写入文件
|
||||
@@ -167,49 +146,6 @@ export function loadVisionConfig(): VisionConfig | null {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载 Summary 配置
|
||||
* Summary 用于对话压缩时生成摘要,推荐使用成本较低的小模型
|
||||
* 通过 ProviderRegistry 获取 API Key
|
||||
*/
|
||||
export function loadSummaryConfig(): SummaryConfig | null {
|
||||
// 从配置文件读取
|
||||
const storedConfig = getConfig();
|
||||
|
||||
// 如果没有任何 summary 相关配置,返回 null(使用主模型)
|
||||
const hasSummaryConfig = storedConfig.summaryProvider || storedConfig.summaryModel;
|
||||
|
||||
if (!hasSummaryConfig) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 确定 summary provider(默认使用主配置的 provider)
|
||||
const mainProvider = storedConfig.provider || 'anthropic';
|
||||
const finalProvider = storedConfig.summaryProvider || mainProvider;
|
||||
|
||||
// 通过 ProviderRegistry 获取 API Key
|
||||
const providerConfig = providerRegistry.getConfig(finalProvider);
|
||||
const finalApiKey = resolveApiKey(providerConfig);
|
||||
|
||||
// 如果没有 API Key,返回 null
|
||||
if (!finalApiKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 确定模型
|
||||
const finalModel = storedConfig.summaryModel || DEFAULT_SUMMARY_MODELS[finalProvider];
|
||||
|
||||
// 确定 baseUrl
|
||||
const finalBaseUrl = storedConfig.summaryBaseUrl || providerConfig?.baseUrl;
|
||||
|
||||
return {
|
||||
provider: finalProvider,
|
||||
apiKey: finalApiKey,
|
||||
model: finalModel,
|
||||
baseUrl: finalBaseUrl,
|
||||
};
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
export function saveConfig(config: Partial<StoredConfig>): void {
|
||||
// 确保目录存在
|
||||
|
||||
Reference in New Issue
Block a user