From 596eaa305198229b9fb8aaa182ef6c144253997c Mon Sep 17 00:00:00 2001 From: digua Date: Fri, 15 May 2026 22:08:31 +0800 Subject: [PATCH] refactor: remove redundant electron/main/ai/compression wrapper Inline createCompressionLlmAdapter and logger into the sole caller (ipc/ai.ts) and import checkAndCompress/manualCompress directly from @openchatlab/node-runtime. The "old API compatibility" layer was unnecessary since there is only one internal consumer. --- electron/main/ai/compression/index.ts | 100 -------------------------- electron/main/ipc/ai.ts | 74 +++++++++++++++++-- 2 files changed, 67 insertions(+), 107 deletions(-) delete mode 100644 electron/main/ai/compression/index.ts diff --git a/electron/main/ai/compression/index.ts b/electron/main/ai/compression/index.ts deleted file mode 100644 index 4ed3764e..00000000 --- a/electron/main/ai/compression/index.ts +++ /dev/null @@ -1,100 +0,0 @@ -/** - * 上下文压缩 — Electron 薄包装 - * - * 实际逻辑已提取到 @openchatlab/node-runtime。 - * 此处提供与旧 API 兼容的函数签名,注入 Electron 的 LLM adapter 和 logger。 - */ - -export type { CompressionConfig, CompressionResult } from '@openchatlab/node-runtime' -export { - checkAndCompress as sharedCheckAndCompress, - manualCompress as sharedManualCompress, -} from '@openchatlab/node-runtime' -import type { CompressionConfig, CompressionResult, CompressionLlmAdapter } from '@openchatlab/node-runtime' -import { - checkAndCompress as sharedCheckAndCompress, - manualCompress as sharedManualCompress, -} from '@openchatlab/node-runtime' -import { buildPiModel, findModelDefinition } from '../llm' -import type { AIServiceConfig } from '../llm/types' -import { completeSimple, type PiTextContent } from '@openchatlab/node-runtime' -import { aiLogger } from '../logger' -import { getManager } from '../conversations' - -const DEFAULT_CONTEXT_WINDOW = 128000 - -function createLlmAdapter(activeAIConfig: AIServiceConfig, onCompressing?: () => void): CompressionLlmAdapter { - const modelDef = findModelDefinition(activeAIConfig.provider, activeAIConfig.model || '') - const contextWindow = modelDef?.contextWindow ?? DEFAULT_CONTEXT_WINDOW - const piModel = buildPiModel(activeAIConfig) - - return { - contextWindow, - compress: async (prompt: string, maxTokens: number) => { - onCompressing?.() - try { - const result = await completeSimple( - piModel, - { - systemPrompt: undefined, - messages: [{ role: 'user', content: [{ type: 'text', text: prompt }], timestamp: Date.now() }] as any, - }, - { apiKey: activeAIConfig.apiKey, maxTokens } - ) - const text = result.content - .filter((item): item is PiTextContent => item.type === 'text') - .map((item) => item.text) - .join('') - return text || null - } catch (error) { - aiLogger.warn('Compression', 'LLM compression attempt failed', { error: String(error) }) - return null - } - }, - } -} - -const electronLogger = { - info: (cat: string, msg: string, extra?: Record) => aiLogger.info(cat, msg, extra), - warn: (cat: string, msg: string, extra?: Record) => aiLogger.warn(cat, msg, extra), - error: (cat: string, msg: string, extra?: Record) => aiLogger.error(cat, msg, extra), -} - -/** - * 兼容旧 API:检查并执行上下文压缩 - */ -export async function checkAndCompress( - conversationId: string, - config: CompressionConfig, - systemPrompt: string, - activeAIConfig: AIServiceConfig, - onCompressing?: () => void -): Promise { - return sharedCheckAndCompress( - conversationId, - config, - systemPrompt, - createLlmAdapter(activeAIConfig, onCompressing), - getManager(), - electronLogger - ) -} - -/** - * 兼容旧 API:手动压缩 - */ -export async function manualCompress( - conversationId: string, - config: CompressionConfig, - systemPrompt: string, - activeAIConfig: AIServiceConfig -): Promise { - return sharedManualCompress( - conversationId, - config, - systemPrompt, - createLlmAdapter(activeAIConfig), - getManager(), - electronLogger - ) -} diff --git a/electron/main/ipc/ai.ts b/electron/main/ipc/ai.ts index a706daee..e37b277e 100644 --- a/electron/main/ipc/ai.ts +++ b/electron/main/ipc/ai.ts @@ -9,19 +9,71 @@ import { serializeError } from '../ai/serialize-error' import { getLogsDir } from '../paths' import { Agent, type AgentStreamChunk, type SkillContext } from '../ai/agent' import { getDefaultGeneralAssistantId } from '../ai/assistant/defaultGeneral' -import { getDefaultAssistantConfig, buildPiModel } from '../ai/llm' -import { checkAndCompress, manualCompress, type CompressionConfig } from '../ai/compression' +import { getDefaultAssistantConfig, buildPiModel, findModelDefinition } from '../ai/llm' +import type { AIServiceConfig } from '../ai/llm/types' import { countMessagesTokens } from '../ai/tokenizer' import * as assistantManager from '../ai/assistant' import type { AssistantConfig } from '../ai/assistant/types' import * as skillManager from '../ai/skills' -import { completeSimple, streamSimple, type PiMessage, type PiTextContent } from '@openchatlab/node-runtime' +import { + checkAndCompress, + manualCompress, + type CompressionConfig, + type CompressionLlmAdapter, + completeSimple, + streamSimple, + type PiMessage, + type PiTextContent, +} from '@openchatlab/node-runtime' +import { getManager as getConversationManager } from '../ai/conversations' import { t } from '../i18n' import type { ToolContext } from '../ai/tools/types' import { TOOL_REGISTRY } from '../ai/tools/definitions' import { getDefaultRulesForLocale, mergeRulesForLocale } from '@openchatlab/node-runtime' import type { IpcContext } from './types' +const DEFAULT_CONTEXT_WINDOW = 128000 + +const compressionLogger = { + info: (cat: string, msg: string, extra?: Record) => aiLogger.info(cat, msg, extra), + warn: (cat: string, msg: string, extra?: Record) => aiLogger.warn(cat, msg, extra), + error: (cat: string, msg: string, extra?: Record) => aiLogger.error(cat, msg, extra), +} + +function createCompressionLlmAdapter( + activeAIConfig: AIServiceConfig, + onCompressing?: () => void +): CompressionLlmAdapter { + const modelDef = findModelDefinition(activeAIConfig.provider, activeAIConfig.model || '') + const contextWindow = modelDef?.contextWindow ?? DEFAULT_CONTEXT_WINDOW + const piModel = buildPiModel(activeAIConfig) + + return { + contextWindow, + compress: async (prompt: string, maxTokens: number) => { + onCompressing?.() + try { + const result = await completeSimple( + piModel, + { + systemPrompt: undefined, + messages: [{ role: 'user', content: [{ type: 'text', text: prompt }], timestamp: Date.now() }] as any, + }, + { apiKey: activeAIConfig.apiKey, maxTokens } + ) + const text = result.content + .filter((item): item is PiTextContent => item.type === 'text') + .map((item) => item.text) + .join('') + return text || null + } catch (error) { + aiLogger.warn('Compression', 'LLM compression attempt failed', { error: String(error) }) + return null + } + }, + } +} + function toPiSimpleMessages(messages: Array<{ role: string; content: string }>, timestamp: number): PiMessage[] { // pi-ai 的 simple API 在类型上要求完整 Message 联合,这里沿用现有轻量消息格式并集中做兼容转换。 return messages.map((message) => ({ @@ -1120,8 +1172,7 @@ export function registerAIHandlers({ win }: IpcContext): void { context.conversationId, compressionConfig, systemPromptForCompression, - activeAIConfig, - () => { + createCompressionLlmAdapter(activeAIConfig, () => { win.webContents.send('agent:streamChunk', { requestId, chunk: { @@ -1136,7 +1187,9 @@ export function registerAIHandlers({ win }: IpcContext): void { } satisfies import('@electron/shared/types').AgentRuntimeStatus, }, }) - } + }), + getConversationManager(), + compressionLogger ) aiLogger.info('IPC', `Compression result for ${requestId}`, compressionResult) @@ -1351,7 +1404,14 @@ export function registerAIHandlers({ win }: IpcContext): void { return { success: false, error: t('llm.notConfigured') } } - const result = await manualCompress(conversationId, compressionConfig, systemPrompt, activeAIConfig) + const result = await manualCompress( + conversationId, + compressionConfig, + systemPrompt, + createCompressionLlmAdapter(activeAIConfig), + getConversationManager(), + compressionLogger + ) return { success: true, result } } catch (error) { aiLogger.error('IPC', 'Manual compression failed', { error: String(error) })