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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user