feat(context): 添加上下文压缩 API 和 UI 组件

Server API:
- 扩展 Agent Adapter 接口添加压缩相关方法
- 新增 context.ts 路由 (GET /sessions/:id/context, POST /sessions/:id/compress)
- 扩展 config.ts 添加摘要模型配置接口 (GET/PUT /config/summary)

UI 组件:
- 新增 ContextUsage 组件显示上下文使用情况
- 扩展 ConfigPanel 添加摘要模型配置区域
- 添加 API 客户端方法和类型定义

Web 集成:
- 在 Chat 页面头部集成 ContextUsage 紧凑模式显示
This commit is contained in:
2025-12-14 20:33:51 +08:00
parent f54f24b079
commit 70a9a154a4
13 changed files with 934 additions and 10 deletions
+178
View File
@@ -17,6 +17,28 @@ import { createServerPermissionCallback } from '../permission/handler.js';
// Core 模块接口定义(避免直接依赖 @ai-assistant/core 类型)
// ============================================================================
/**
* Token 使用情况接口
*/
export interface TokenUsage {
input: number;
contextLimit: number;
available: number;
usagePercent: number;
}
/**
* 压缩结果接口
*/
export interface CompressionResult {
type: 'prune' | 'compaction' | 'both' | 'none';
status: 'success' | 'noop' | 'failed_empty_summary' | 'failed_token_inflated' | 'failed_error';
freedTokens: number;
error?: string;
originalTokens?: number;
summaryTokens?: number;
}
/**
* Agent 实例接口
*/
@@ -25,6 +47,11 @@ interface AgentInstance {
chat(message: string, onStream?: (chunk: string) => void): Promise<string>;
getToolCount(): { core: number; discovered: number; total: number };
getContextUsageFormatted(): string;
getContextUsage(): TokenUsage;
compactHistory(): Promise<{ freedTokens: number; type: string }>;
getCompressionManager(): {
shouldCompress(messages: unknown[]): boolean;
};
}
/**
@@ -49,6 +76,16 @@ interface PermissionManager {
setAskCallback(callback: (ctx: unknown) => Promise<{ allow: boolean; remember?: boolean }>): void;
}
/**
* Summary 配置接口
*/
export interface SummaryConfig {
provider: string;
apiKey: string;
model: string;
baseUrl?: string;
}
/**
* Core 模块接口
*/
@@ -56,6 +93,8 @@ interface CoreModule {
Agent: AgentConstructor;
toolRegistry: ToolRegistry;
loadConfig: () => unknown;
saveConfig: (config: Record<string, unknown>) => void;
loadSummaryConfig: () => SummaryConfig | null;
getPermissionManager: (projectRoot?: string) => PermissionManager;
}
@@ -333,3 +372,142 @@ async function generateSessionTitle(
console.log(`[Agent] Session title generated: "${title}"`);
}
}
// ============================================================================
// 上下文压缩 API
// ============================================================================
/**
* 上下文使用情况(带额外字段)
*/
export interface ContextUsageInfo extends TokenUsage {
formatted: string;
shouldCompress: boolean;
}
/**
* 获取会话的上下文使用情况
*/
export function getContextUsage(sessionId: string): ContextUsageInfo | null {
if (!coreModule) {
return null;
}
const agent = agentCache.get(sessionId);
if (!agent) {
return null;
}
const usage = agent.getContextUsage();
const formatted = agent.getContextUsageFormatted();
return {
...usage,
formatted,
shouldCompress: usage.usagePercent >= 80, // 80% 阈值建议压缩
};
}
/**
* 执行上下文压缩
*/
export async function compressContext(
sessionId: string,
force: boolean = false
): Promise<CompressionResult | null> {
if (!coreModule) {
return null;
}
const agent = agentCache.get(sessionId);
if (!agent) {
return null;
}
try {
// 使用强制压缩或普通压缩
const result = await agent.compactHistory();
return {
type: result.type as CompressionResult['type'],
status: result.freedTokens > 0 ? 'success' : 'noop',
freedTokens: result.freedTokens,
};
} catch (error) {
return {
type: 'none',
status: 'failed_error',
freedTokens: 0,
error: error instanceof Error ? error.message : String(error),
};
}
}
// ============================================================================
// 摘要配置 API
// ============================================================================
/**
* 摘要配置(不含 API Key 明文)
*/
export interface SummaryConfigInfo {
provider?: string;
model?: string;
hasApiKey: boolean;
baseUrl?: string;
}
/**
* 获取摘要配置
*/
export function getSummaryConfig(): SummaryConfigInfo | null {
if (!coreModule) {
return null;
}
const config = coreModule.loadSummaryConfig();
if (!config) {
return {
hasApiKey: false,
};
}
return {
provider: config.provider,
model: config.model,
hasApiKey: !!config.apiKey,
baseUrl: config.baseUrl,
};
}
/**
* 更新摘要配置
*/
export function updateSummaryConfig(config: {
provider?: string;
model?: string;
apiKey?: string;
baseUrl?: string;
}): boolean {
if (!coreModule) {
return false;
}
// 构建要保存的配置
const saveData: Record<string, unknown> = {};
if (config.provider !== undefined) {
saveData.summaryProvider = config.provider;
}
if (config.model !== undefined) {
saveData.summaryModel = config.model;
}
if (config.apiKey !== undefined) {
saveData.summaryApiKey = config.apiKey;
}
if (config.baseUrl !== undefined) {
saveData.summaryBaseUrl = config.baseUrl;
}
coreModule.saveConfig(saveData);
return true;
}
+11
View File
@@ -12,4 +12,15 @@ export {
processMessage,
cancelProcessing,
getAgentStats,
// 上下文压缩相关
getContextUsage,
compressContext,
getSummaryConfig,
updateSummaryConfig,
// 类型导出
type TokenUsage,
type CompressionResult,
type ContextUsageInfo,
type SummaryConfigInfo,
type SummaryConfig,
} from './adapter.js';