@huyooo/ai-chat-core 0.3.7 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/dist/adapter/index.d.ts +0 -1
  2. package/dist/adapter/model-adapter.d.ts +0 -1
  3. package/dist/adapter/model-options.d.ts +0 -1
  4. package/dist/adapter/types.d.ts +0 -1
  5. package/dist/chat-runtime.d.ts +0 -1
  6. package/dist/constants.d.ts +0 -1
  7. package/dist/events.d.ts +0 -1
  8. package/dist/extension/index.d.ts +0 -1
  9. package/dist/extension/types.d.ts +0 -1
  10. package/dist/families/index.d.ts +0 -1
  11. package/dist/families/presets.d.ts +0 -1
  12. package/dist/families/resolver.d.ts +0 -1
  13. package/dist/families/types.d.ts +0 -1
  14. package/dist/governance/command-safety.d.ts +0 -1
  15. package/dist/governance/governance.d.ts +0 -1
  16. package/dist/governance/index.d.ts +0 -1
  17. package/dist/governance/types.d.ts +0 -1
  18. package/dist/index.d.ts +0 -1
  19. package/dist/internal/management-args.d.ts +0 -1
  20. package/dist/internal/management-results.d.ts +0 -1
  21. package/dist/llm-config.d.ts +0 -1
  22. package/dist/logger/core.d.ts +0 -1
  23. package/dist/logger/index.d.ts +0 -1
  24. package/dist/orchestrator/compression-handler.d.ts +0 -1
  25. package/dist/orchestrator/context-compressor.d.ts +0 -1
  26. package/dist/orchestrator/context-summarizer.d.ts +0 -1
  27. package/dist/orchestrator/index.d.ts +0 -1
  28. package/dist/orchestrator/orchestrator.d.ts +0 -1
  29. package/dist/orchestrator/types.d.ts +0 -1
  30. package/dist/parts/index.d.ts +0 -1
  31. package/dist/parts/registry.d.ts +0 -1
  32. package/dist/parts/summaries.d.ts +0 -1
  33. package/dist/parts/types.d.ts +0 -1
  34. package/dist/platform.d.ts +0 -1
  35. package/dist/protocols/anthropic.d.ts +0 -1
  36. package/dist/protocols/ark.d.ts +0 -1
  37. package/dist/protocols/deepseek.d.ts +0 -1
  38. package/dist/protocols/error-utils.d.ts +0 -1
  39. package/dist/protocols/gemini.d.ts +0 -1
  40. package/dist/protocols/glm.d.ts +0 -1
  41. package/dist/protocols/grok.d.ts +0 -1
  42. package/dist/protocols/index.d.ts +0 -1
  43. package/dist/protocols/minimax.d.ts +0 -1
  44. package/dist/protocols/moonshot.d.ts +0 -1
  45. package/dist/protocols/openai-sse.d.ts +0 -1
  46. package/dist/protocols/openai.d.ts +0 -1
  47. package/dist/protocols/qwen.d.ts +0 -1
  48. package/dist/protocols/responses-sse.d.ts +0 -1
  49. package/dist/protocols/sse-reader.d.ts +0 -1
  50. package/dist/protocols/tool-arguments.d.ts +0 -1
  51. package/dist/protocols/types.d.ts +0 -1
  52. package/dist/protocols/vercel-gateway.d.ts +0 -1
  53. package/dist/runtime.d.ts +0 -1
  54. package/dist/skills/index.d.ts +0 -1
  55. package/dist/skills/management/admin.d.ts +0 -1
  56. package/dist/skills/management/index.d.ts +0 -1
  57. package/dist/skills/management/inputs.d.ts +0 -1
  58. package/dist/skills/management/operations.d.ts +0 -1
  59. package/dist/skills/management/types.d.ts +0 -1
  60. package/dist/skills/registry.d.ts +0 -1
  61. package/dist/skills/summaries.d.ts +0 -1
  62. package/dist/skills/types.d.ts +0 -1
  63. package/dist/test-utils/mock-sse.d.ts +0 -1
  64. package/dist/tool-manager/define-tool.d.ts +0 -1
  65. package/dist/tool-manager/formats.d.ts +0 -1
  66. package/dist/tool-manager/identity.d.ts +0 -1
  67. package/dist/tool-manager/in-process-provider.d.ts +0 -1
  68. package/dist/tool-manager/index.d.ts +0 -1
  69. package/dist/tool-manager/manager.d.ts +0 -1
  70. package/dist/tool-manager/mcp-provider.d.ts +0 -1
  71. package/dist/tool-manager/summaries.d.ts +0 -1
  72. package/dist/tool-manager/types.d.ts +0 -1
  73. package/dist/types.d.ts +0 -1
  74. package/package.json +2 -3
  75. package/dist/adapter/index.d.ts.map +0 -1
  76. package/dist/adapter/model-adapter.d.ts.map +0 -1
  77. package/dist/adapter/model-options.d.ts.map +0 -1
  78. package/dist/adapter/types.d.ts.map +0 -1
  79. package/dist/chat-runtime.d.ts.map +0 -1
  80. package/dist/constants.d.ts.map +0 -1
  81. package/dist/events.d.ts.map +0 -1
  82. package/dist/extension/index.d.ts.map +0 -1
  83. package/dist/extension/types.d.ts.map +0 -1
  84. package/dist/families/index.d.ts.map +0 -1
  85. package/dist/families/presets.d.ts.map +0 -1
  86. package/dist/families/resolver.d.ts.map +0 -1
  87. package/dist/families/types.d.ts.map +0 -1
  88. package/dist/governance/command-safety.d.ts.map +0 -1
  89. package/dist/governance/governance.d.ts.map +0 -1
  90. package/dist/governance/index.d.ts.map +0 -1
  91. package/dist/governance/types.d.ts.map +0 -1
  92. package/dist/index.d.ts.map +0 -1
  93. package/dist/internal/management-args.d.ts.map +0 -1
  94. package/dist/internal/management-results.d.ts.map +0 -1
  95. package/dist/llm-config.d.ts.map +0 -1
  96. package/dist/logger/core.d.ts.map +0 -1
  97. package/dist/logger/index.d.ts.map +0 -1
  98. package/dist/orchestrator/compression-handler.d.ts.map +0 -1
  99. package/dist/orchestrator/context-compressor.d.ts.map +0 -1
  100. package/dist/orchestrator/context-summarizer.d.ts.map +0 -1
  101. package/dist/orchestrator/index.d.ts.map +0 -1
  102. package/dist/orchestrator/orchestrator.d.ts.map +0 -1
  103. package/dist/orchestrator/types.d.ts.map +0 -1
  104. package/dist/parts/index.d.ts.map +0 -1
  105. package/dist/parts/registry.d.ts.map +0 -1
  106. package/dist/parts/summaries.d.ts.map +0 -1
  107. package/dist/parts/types.d.ts.map +0 -1
  108. package/dist/platform.d.ts.map +0 -1
  109. package/dist/protocols/anthropic.d.ts.map +0 -1
  110. package/dist/protocols/ark.d.ts.map +0 -1
  111. package/dist/protocols/deepseek.d.ts.map +0 -1
  112. package/dist/protocols/error-utils.d.ts.map +0 -1
  113. package/dist/protocols/gemini.d.ts.map +0 -1
  114. package/dist/protocols/glm.d.ts.map +0 -1
  115. package/dist/protocols/grok.d.ts.map +0 -1
  116. package/dist/protocols/index.d.ts.map +0 -1
  117. package/dist/protocols/minimax.d.ts.map +0 -1
  118. package/dist/protocols/moonshot.d.ts.map +0 -1
  119. package/dist/protocols/openai-sse.d.ts.map +0 -1
  120. package/dist/protocols/openai.d.ts.map +0 -1
  121. package/dist/protocols/qwen.d.ts.map +0 -1
  122. package/dist/protocols/responses-sse.d.ts.map +0 -1
  123. package/dist/protocols/sse-reader.d.ts.map +0 -1
  124. package/dist/protocols/tool-arguments.d.ts.map +0 -1
  125. package/dist/protocols/types.d.ts.map +0 -1
  126. package/dist/protocols/vercel-gateway.d.ts.map +0 -1
  127. package/dist/runtime.d.ts.map +0 -1
  128. package/dist/skills/index.d.ts.map +0 -1
  129. package/dist/skills/management/admin.d.ts.map +0 -1
  130. package/dist/skills/management/index.d.ts.map +0 -1
  131. package/dist/skills/management/inputs.d.ts.map +0 -1
  132. package/dist/skills/management/operations.d.ts.map +0 -1
  133. package/dist/skills/management/types.d.ts.map +0 -1
  134. package/dist/skills/registry.d.ts.map +0 -1
  135. package/dist/skills/summaries.d.ts.map +0 -1
  136. package/dist/skills/types.d.ts.map +0 -1
  137. package/dist/test-utils/mock-sse.d.ts.map +0 -1
  138. package/dist/tool-manager/define-tool.d.ts.map +0 -1
  139. package/dist/tool-manager/formats.d.ts.map +0 -1
  140. package/dist/tool-manager/identity.d.ts.map +0 -1
  141. package/dist/tool-manager/in-process-provider.d.ts.map +0 -1
  142. package/dist/tool-manager/index.d.ts.map +0 -1
  143. package/dist/tool-manager/manager.d.ts.map +0 -1
  144. package/dist/tool-manager/mcp-provider.d.ts.map +0 -1
  145. package/dist/tool-manager/summaries.d.ts.map +0 -1
  146. package/dist/tool-manager/types.d.ts.map +0 -1
  147. package/dist/types.d.ts.map +0 -1
  148. package/src/adapter/index.ts +0 -25
  149. package/src/adapter/model-adapter.ts +0 -196
  150. package/src/adapter/model-options.ts +0 -143
  151. package/src/adapter/types.ts +0 -41
  152. package/src/chat-runtime.ts +0 -515
  153. package/src/constants.ts +0 -32
  154. package/src/events.ts +0 -1084
  155. package/src/extension/index.ts +0 -24
  156. package/src/extension/types.ts +0 -49
  157. package/src/families/index.ts +0 -28
  158. package/src/families/presets.ts +0 -124
  159. package/src/families/resolver.ts +0 -22
  160. package/src/families/types.ts +0 -55
  161. package/src/governance/command-safety.ts +0 -224
  162. package/src/governance/governance.ts +0 -125
  163. package/src/governance/index.ts +0 -38
  164. package/src/governance/types.ts +0 -44
  165. package/src/index.ts +0 -426
  166. package/src/internal/management-args.ts +0 -39
  167. package/src/internal/management-results.ts +0 -60
  168. package/src/llm-config.ts +0 -137
  169. package/src/logger/core.ts +0 -96
  170. package/src/logger/index.ts +0 -8
  171. package/src/orchestrator/compression-handler.ts +0 -137
  172. package/src/orchestrator/context-compressor.ts +0 -249
  173. package/src/orchestrator/context-summarizer.ts +0 -123
  174. package/src/orchestrator/index.ts +0 -20
  175. package/src/orchestrator/orchestrator.ts +0 -1002
  176. package/src/orchestrator/types.ts +0 -70
  177. package/src/parts/index.ts +0 -20
  178. package/src/parts/registry.ts +0 -95
  179. package/src/parts/summaries.ts +0 -40
  180. package/src/parts/types.ts +0 -63
  181. package/src/platform.ts +0 -73
  182. package/src/protocols/anthropic.ts +0 -377
  183. package/src/protocols/ark.ts +0 -300
  184. package/src/protocols/deepseek.ts +0 -192
  185. package/src/protocols/error-utils.ts +0 -71
  186. package/src/protocols/gemini.ts +0 -352
  187. package/src/protocols/glm.ts +0 -212
  188. package/src/protocols/grok.ts +0 -98
  189. package/src/protocols/index.ts +0 -48
  190. package/src/protocols/minimax.ts +0 -308
  191. package/src/protocols/moonshot.ts +0 -186
  192. package/src/protocols/openai-sse.ts +0 -156
  193. package/src/protocols/openai.ts +0 -97
  194. package/src/protocols/qwen.ts +0 -358
  195. package/src/protocols/responses-sse.ts +0 -224
  196. package/src/protocols/sse-reader.ts +0 -54
  197. package/src/protocols/tool-arguments.ts +0 -32
  198. package/src/protocols/types.ts +0 -198
  199. package/src/protocols/vercel-gateway.ts +0 -391
  200. package/src/runtime.ts +0 -167
  201. package/src/skills/index.ts +0 -29
  202. package/src/skills/management/admin.ts +0 -170
  203. package/src/skills/management/index.ts +0 -27
  204. package/src/skills/management/inputs.ts +0 -79
  205. package/src/skills/management/operations.ts +0 -256
  206. package/src/skills/management/types.ts +0 -57
  207. package/src/skills/registry.ts +0 -120
  208. package/src/skills/summaries.ts +0 -48
  209. package/src/skills/types.ts +0 -65
  210. package/src/test-utils/mock-sse.ts +0 -32
  211. package/src/tool-manager/define-tool.ts +0 -201
  212. package/src/tool-manager/formats.ts +0 -146
  213. package/src/tool-manager/identity.ts +0 -80
  214. package/src/tool-manager/in-process-provider.ts +0 -164
  215. package/src/tool-manager/index.ts +0 -63
  216. package/src/tool-manager/manager.ts +0 -562
  217. package/src/tool-manager/mcp-provider.ts +0 -509
  218. package/src/tool-manager/summaries.ts +0 -136
  219. package/src/tool-manager/types.ts +0 -389
  220. package/src/types.ts +0 -1142
@@ -1,96 +0,0 @@
1
- /**
2
- * Pino 日志核心(与 SuperX 架构一致)
3
- *
4
- * 使用 pino.multistream + pino.destination,避免 pino.transport(打包后 worker 不可用)。
5
- * - 开发:pino-pretty 输出到 stdout
6
- * - 生产:JSON 输出 + 可选文件持久化
7
- * - 模块:child({ module: 'Name' }) 创建带前缀的 logger
8
- */
9
- import pino, { type Logger } from 'pino';
10
- import pinoPretty from 'pino-pretty';
11
- import path from 'node:path';
12
- import fs from 'node:fs';
13
-
14
- export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
15
-
16
- export interface LoggerConfig {
17
- /** 日志级别 */
18
- level?: LogLevel;
19
- /** 用户数据目录(用于 logs/ai-chat.log) */
20
- baseDir?: string;
21
- /** 是否开发模式(使用 pino-pretty) */
22
- isDev?: boolean;
23
- }
24
-
25
- let rootLogger: Logger | null = null;
26
-
27
- function isDev(): boolean {
28
- return process.env.NODE_ENV === 'development';
29
- }
30
-
31
- function getLogLevel(): LogLevel {
32
- const v = process.env.LOG_LEVEL?.toLowerCase();
33
- if (v && ['debug', 'info', 'warn', 'error'].includes(v)) return v as LogLevel;
34
- return isDev() ? 'debug' : 'info';
35
- }
36
-
37
- /**
38
- * 创建默认 logger(无文件,仅 stdout)
39
- */
40
- function createDefaultLogger(): Logger {
41
- const level = getLogLevel();
42
- if (isDev()) {
43
- const stream = pinoPretty({ colorize: true, translateTime: 'SYS:standard' });
44
- return pino({ level }, stream);
45
- }
46
- return pino({ level }, pino.destination(1));
47
- }
48
-
49
- /**
50
- * 创建完整配置的 logger
51
- * 使用 multistream + destination,避免 pino.transport(Electron 打包后不可用)
52
- */
53
- function createFullLogger(config: LoggerConfig): Logger {
54
- const level = config.level ?? (config.isDev ? 'debug' : 'info');
55
- const streams: Parameters<typeof pino.multistream>[0] = [];
56
-
57
- if (config.isDev) {
58
- streams.push({ stream: pinoPretty({ colorize: true, translateTime: 'SYS:standard' }), level });
59
- } else {
60
- streams.push({ stream: pino.destination(1), level });
61
- }
62
-
63
- if (config.baseDir) {
64
- const logsDir = path.join(config.baseDir, 'logs');
65
- try {
66
- fs.mkdirSync(logsDir, { recursive: true });
67
- } catch {
68
- // 忽略
69
- }
70
- const logPath = path.join(logsDir, 'ai-chat.log');
71
- streams.push({ stream: pino.destination(logPath), level });
72
- }
73
-
74
- return pino({ level }, pino.multistream(streams));
75
- }
76
-
77
- /**
78
- * 初始化 logger(应在 app.whenReady 后调用)
79
- */
80
- export function initLogger(config: LoggerConfig): void {
81
- rootLogger = createFullLogger(config);
82
- }
83
-
84
- /**
85
- * 获取 root logger,未初始化时返回默认 logger
86
- */
87
- export function getLogger(): Logger {
88
- return rootLogger ?? createDefaultLogger();
89
- }
90
-
91
- /**
92
- * 创建模块专用 logger(替代原 DebugLogger.module)
93
- */
94
- export function createModuleLogger(moduleName: string): Logger {
95
- return getLogger().child({ module: moduleName });
96
- }
@@ -1,8 +0,0 @@
1
- /**
2
- * 日志模块导出(barrel)
3
- *
4
- * - 基于 `core` 的 init / get / createModuleLogger
5
- * - 与 SuperX 一致:开发 pretty、生产 JSON、可选落盘
6
- */
7
- export { initLogger, getLogger, createModuleLogger } from './core';
8
- export type { LogLevel, LoggerConfig } from './core';
@@ -1,137 +0,0 @@
1
- /**
2
- * 压缩处理器
3
- *
4
- * 从 orchestrator 提取的两个独立压缩逻辑:
5
- * 1. compactIfNeeded — 历史消息整体压缩
6
- * 2. compressSingleMessageIfNeeded — 单条超长 user 消息压缩
7
- */
8
-
9
- import type { ProtocolMessage } from '../protocols';
10
- import type {
11
- CompactStartEvent,
12
- CompactEndEvent,
13
- ContentCompressStartEvent,
14
- ContentCompressEndEvent,
15
- } from '../events';
16
- import {
17
- createCompactStart,
18
- createCompactEnd,
19
- createContentCompressStart,
20
- createContentCompressEnd,
21
- } from '../events';
22
- import {
23
- needsCompaction,
24
- estimateTotalTokens,
25
- estimateMessageTokens,
26
- getPromptBudget,
27
- needsSingleMessageCompression,
28
- type CompactConfig,
29
- } from './context-compressor';
30
- import { summarizeHistory, compressSingleMessage } from './context-summarizer';
31
- import { createModuleLogger } from '../logger';
32
- import type { CompressionConfig } from '../llm-config';
33
- import type { SummarizeFn } from './context-summarizer';
34
-
35
- const DEFAULT_LONG_MODEL = 'qwen-long';
36
-
37
- const logger = createModuleLogger('Compression');
38
-
39
- /**
40
- * 历史消息整体压缩
41
- *
42
- * 当消息总 token 超出 context window 预算时,
43
- * 直接用当前对话模型压缩历史(无需额外配置)。
44
- *
45
- * 历史压缩触发点 = context window budget,远小于超长阈值(800K),
46
- * 所以不会出现需要切长上下文模型的场景。
47
- */
48
- export async function* compactIfNeeded(
49
- messages: ProtocolMessage[],
50
- compactConfig: CompactConfig,
51
- summarizeFn?: SummarizeFn,
52
- currentModel?: string,
53
- ): AsyncGenerator<CompactStartEvent | CompactEndEvent, ProtocolMessage[]> {
54
- if (!needsCompaction(messages, compactConfig)) {
55
- return messages;
56
- }
57
-
58
- if (!summarizeFn || !currentModel) {
59
- logger.warn('上下文超限但未配置 summarize 回调或模型,跳过压缩');
60
- return messages;
61
- }
62
-
63
- const model = currentModel;
64
-
65
- const compactStartedAt = Date.now();
66
- const tokensBeforeCompact = estimateTotalTokens(messages);
67
- const budget = getPromptBudget(compactConfig);
68
- const originalCount = messages.length;
69
-
70
- yield createCompactStart(tokensBeforeCompact, budget);
71
-
72
- const { messages: result, success, summaryContent } = await summarizeHistory(
73
- summarizeFn, messages, compactConfig, model,
74
- );
75
-
76
- yield createCompactEnd(
77
- success,
78
- tokensBeforeCompact,
79
- estimateTotalTokens(result),
80
- originalCount,
81
- result.length,
82
- compactStartedAt,
83
- summaryContent,
84
- model,
85
- );
86
-
87
- return result;
88
- }
89
-
90
- /**
91
- * 单条超长 user 消息压缩
92
- *
93
- * 检测最后一条 user 消息是否超长,如果超长则用 qwen-long 压缩。
94
- */
95
- export async function* compressSingleMessageIfNeeded(
96
- messages: ProtocolMessage[],
97
- compactConfig: CompactConfig,
98
- summarizeFn?: SummarizeFn,
99
- compressionConfig?: CompressionConfig,
100
- ): AsyncGenerator<ContentCompressStartEvent | ContentCompressEndEvent, ProtocolMessage[]> {
101
- const lastMsg = messages[messages.length - 1];
102
- if (!lastMsg || lastMsg.role !== 'user') return messages;
103
- if (!needsSingleMessageCompression(lastMsg, compactConfig)) return messages;
104
-
105
- if (!summarizeFn) {
106
- logger.warn('单条消息超长但未配置 summarize 回调,跳过压缩');
107
- return messages;
108
- }
109
-
110
- const originalTokens = estimateMessageTokens(lastMsg);
111
- const budget = getPromptBudget(compactConfig);
112
- const startedAt = Date.now();
113
-
114
- // 单条超长消息始终用长上下文模型
115
- const model = compressionConfig?.longModel ?? DEFAULT_LONG_MODEL;
116
-
117
- yield createContentCompressStart(originalTokens, budget);
118
-
119
- const result = await compressSingleMessage(summarizeFn, lastMsg, model);
120
-
121
- if (result.success) {
122
- const newMessages = [...messages];
123
- newMessages[newMessages.length - 1] = {
124
- ...lastMsg,
125
- content: result.summary,
126
- };
127
-
128
- yield createContentCompressEnd(
129
- true, result.originalTokens, result.compressedTokens, startedAt, result.summary, model,
130
- );
131
- return newMessages;
132
- }
133
-
134
- // 压缩失败,保持原文
135
- yield createContentCompressEnd(false, result.originalTokens, 0, startedAt);
136
- return messages;
137
- }
@@ -1,249 +0,0 @@
1
- /**
2
- * Context 压缩模块
3
- *
4
- * 当 prompt token 估算接近模型 context window 时,让当前模型自己总结对话历史,
5
- * 然后用 summary + 最近几条消息继续对话。
6
- *
7
- * 参考 Claude Code / Cursor 的做法:
8
- * 不机械截断,而是让 AI 生成高质量摘要,保留关键决策和上下文。
9
- */
10
-
11
- import type { ProtocolMessage } from '../protocols';
12
- import { createModuleLogger } from '../logger';
13
-
14
- const logger = createModuleLogger('ContextCompressor');
15
-
16
- // ==================== Token 估算 ====================
17
-
18
- const CHARS_PER_TOKEN = 3.2;
19
- const MESSAGE_OVERHEAD_TOKENS = 4;
20
-
21
- export function estimateStringTokens(s: string): number {
22
- if (!s) return 0;
23
- return Math.ceil(s.length / CHARS_PER_TOKEN);
24
- }
25
-
26
- export function estimateMessageTokens(msg: ProtocolMessage): number {
27
- let tokens = MESSAGE_OVERHEAD_TOKENS;
28
- tokens += estimateStringTokens(msg.content);
29
-
30
- if (msg.toolCalls) {
31
- for (const tc of msg.toolCalls) {
32
- tokens += estimateStringTokens(tc.name);
33
- tokens += estimateStringTokens(tc.arguments);
34
- tokens += 10;
35
- }
36
- }
37
-
38
- if (msg.images) {
39
- tokens += msg.images.length * 85;
40
- }
41
-
42
- return tokens;
43
- }
44
-
45
- export function estimateTotalTokens(messages: ProtocolMessage[]): number {
46
- let total = 3;
47
- for (const msg of messages) {
48
- total += estimateMessageTokens(msg);
49
- }
50
- return total;
51
- }
52
-
53
-
54
- // ==================== 配置 ====================
55
-
56
- export interface CompactConfig {
57
- contextWindowTokens: number;
58
- maxOutputTokens: number;
59
- /** 触发压缩的使用率,默认 0.80 */
60
- compactThresholdRatio?: number;
61
- /** 压缩后保留的最近消息数,默认 6 */
62
- keepRecentMessages?: number;
63
- }
64
-
65
- const DEFAULT_THRESHOLD_RATIO = 0.80;
66
- const DEFAULT_KEEP_RECENT = 6;
67
-
68
- // ==================== 核心函数 ====================
69
-
70
- /** 计算可用 prompt token 预算 */
71
- export function getPromptBudget(config: CompactConfig): number {
72
- const ratio = config.compactThresholdRatio ?? DEFAULT_THRESHOLD_RATIO;
73
- return Math.floor(config.contextWindowTokens * ratio) - config.maxOutputTokens;
74
- }
75
-
76
- /** 检测是否需要压缩 */
77
- export function needsCompaction(messages: ProtocolMessage[], config: CompactConfig): boolean {
78
- return estimateTotalTokens(messages) > getPromptBudget(config);
79
- }
80
-
81
- /**
82
- * 构建发给 AI 的总结请求
83
- *
84
- * 返回一组消息,发给当前模型让它总结对话历史。
85
- * 总结完成后调用 applySummary 组装新的消息列表。
86
- */
87
- export function buildSummarizeRequest(
88
- messages: ProtocolMessage[],
89
- config: CompactConfig,
90
- ): { summarizeMessages: ProtocolMessage[]; keepMessages: ProtocolMessage[] } {
91
- const keepRecent = config.keepRecentMessages ?? DEFAULT_KEEP_RECENT;
92
-
93
- const systemEnd = messages[0]?.role === 'system' ? 1 : 0;
94
-
95
- // 第一条 user 消息由 applySummary 原样保留,不参与总结
96
- let firstUserEnd = systemEnd;
97
- for (let i = systemEnd; i < messages.length; i++) {
98
- if (messages[i].role === 'user') {
99
- firstUserEnd = i + 1;
100
- break;
101
- }
102
- }
103
-
104
- const recentStart = Math.max(firstUserEnd, messages.length - keepRecent);
105
- const keepMessages = messages.slice(recentStart);
106
-
107
- // 中间历史:从 firstUser 之后到 recentStart
108
- // 多轮压缩时包含旧摘要(role=system),因为 applySummary 把摘要放在 firstUser 之后
109
- const middleMessages = messages.slice(firstUserEnd, recentStart);
110
-
111
- if (middleMessages.length < 2) {
112
- return { summarizeMessages: [], keepMessages: messages.slice(systemEnd) };
113
- }
114
-
115
- const estimatedTokens = estimateTotalTokens(messages);
116
- const budget = getPromptBudget(config);
117
- logger.info({ estimatedTokens, budget, middleCount: middleMessages.length, keepCount: keepMessages.length }, `准备 AI 总结: ~${estimatedTokens} tokens > budget ${budget}, 总结 ${middleMessages.length} 条中间消息, 保留最近 ${keepMessages.length} 条`);
118
-
119
- const summarizeMessages: ProtocolMessage[] = [
120
- { role: 'system', content: SUMMARIZE_SYSTEM_PROMPT },
121
- { role: 'user', content: formatMessagesForSummary(middleMessages) },
122
- ];
123
-
124
- return { summarizeMessages, keepMessages };
125
- }
126
-
127
- /**
128
- * 用 AI 返回的摘要组装新的消息列表
129
- */
130
- export function applySummary(
131
- originalMessages: ProtocolMessage[],
132
- summary: string,
133
- keepMessages: ProtocolMessage[],
134
- ): ProtocolMessage[] {
135
- const systemPrompt = originalMessages[0]?.role === 'system' ? originalMessages[0] : null;
136
-
137
- // 保留第一条 user 消息原文(初始需求/意图,AI 总结可能丢失细节)
138
- const startIdx = systemPrompt ? 1 : 0;
139
- let firstUser: ProtocolMessage | null = null;
140
- for (let i = startIdx; i < originalMessages.length; i++) {
141
- if (originalMessages[i].role === 'user') {
142
- firstUser = originalMessages[i];
143
- break;
144
- }
145
- }
146
-
147
- const result: ProtocolMessage[] = [];
148
- if (systemPrompt) result.push(systemPrompt);
149
- if (firstUser) result.push(firstUser);
150
-
151
- result.push({
152
- role: 'system',
153
- content: `[对话历史摘要]\n${summary}`,
154
- });
155
-
156
- result.push(...keepMessages);
157
-
158
- const tokens = estimateTotalTokens(result);
159
- logger.info({ originalCount: originalMessages.length, resultCount: result.length, tokens }, `AI 总结应用完成: ${originalMessages.length} → ${result.length} 条消息, ~${tokens} tokens`);
160
-
161
- return result;
162
- }
163
-
164
- // ==================== 单条消息内容压缩 ====================
165
-
166
- /**
167
- * 检测单条用户消息是否需要压缩
168
- * 阈值:单条消息 token > contextWindowTokens * 0.8 - maxOutputTokens
169
- */
170
- export function needsSingleMessageCompression(
171
- message: ProtocolMessage,
172
- config: CompactConfig,
173
- ): boolean {
174
- if (message.role !== 'user') return false;
175
- const msgTokens = estimateMessageTokens(message);
176
- const budget = getPromptBudget(config);
177
- return msgTokens > budget;
178
- }
179
-
180
- /**
181
- * 构建单条消息的总结请求
182
- */
183
- export function buildSingleMessageSummarizeRequest(
184
- message: ProtocolMessage,
185
- ): { summarizeMessages: ProtocolMessage[] } {
186
- const summarizeMessages: ProtocolMessage[] = [
187
- { role: 'system', content: CONTENT_COMPRESS_SYSTEM_PROMPT },
188
- { role: 'user', content: message.content },
189
- ];
190
- return { summarizeMessages };
191
- }
192
-
193
- const CONTENT_COMPRESS_SYSTEM_PROMPT = `你是一个文档压缩助手。用户将发送一段很长的文本(可能包含提问/指令 + 超长文档内容)。
194
-
195
- 你的任务:
196
- 1. 准确识别并保留用户的提问或指令(原文保留,不要改写)
197
- 2. 将超长文档内容压缩为结构化要点,保留所有关键信息
198
- 3. 输出格式:先输出用户的原始提问/指令,然后输出「文档要点:」+ 编号列表
199
- 4. 如果用户没有明确的提问(只是贴了文档),则直接输出文档要点
200
- 5. 要点应覆盖:核心主题、关键论点/数据、方法论、结论、重要细节
201
- 6. 不要遗漏可能影响后续分析的信息
202
-
203
- 直接输出结果,不要说"以下是摘要"之类的开头。`;
204
-
205
- // ==================== 内部 ====================
206
-
207
- const SUMMARIZE_SYSTEM_PROMPT = `你是一个对话历史压缩助手。请总结以下对话历史,保留所有关键信息:
208
-
209
- 要求:
210
- 1. 保留所有文件修改记录(哪些文件被创建/修改/删除了,具体改了什么)
211
- 2. 保留所有关键决策和结论
212
- 3. 保留错误信息和解决方案
213
- 4. 保留用户的明确要求和偏好
214
- 5. 用简洁的条目列表格式输出
215
- 6. 不要遗漏任何可能影响后续工作的信息
216
-
217
- 直接输出摘要,不要开头说"以下是摘要"之类的话。`;
218
-
219
- /** 把消息列表格式化为可读文本,供总结用 */
220
- function formatMessagesForSummary(messages: ProtocolMessage[]): string {
221
- const parts: string[] = [];
222
-
223
- for (const msg of messages) {
224
- if (msg.role === 'system') {
225
- // 上一轮的历史摘要,去掉标记前缀后直接当上下文
226
- const content = msg.content.startsWith('[对话历史摘要]\n')
227
- ? msg.content.slice('[对话历史摘要]\n'.length)
228
- : msg.content;
229
- if (content) parts.push(`[上一轮摘要]\n${content}`);
230
- continue;
231
- }
232
-
233
- const role = msg.role === 'assistant' ? 'AI' : msg.role === 'user' ? '用户' : '工具';
234
-
235
- if (msg.role === 'assistant' && msg.toolCalls && msg.toolCalls.length > 0) {
236
- const calls = msg.toolCalls.map(tc => {
237
- return ` 调用 ${tc.name}(${tc.arguments})`;
238
- }).join('\n');
239
- const text = msg.content ? `${msg.content}\n${calls}` : calls;
240
- parts.push(`[${role}]\n${text}`);
241
- } else if (msg.role === 'tool') {
242
- parts.push(`[${role}: ${msg.toolName ?? 'unknown'}]\n${msg.content}`);
243
- } else if (msg.content) {
244
- parts.push(`[${role}]\n${msg.content}`);
245
- }
246
- }
247
-
248
- return parts.join('\n\n---\n\n');
249
- }
@@ -1,123 +0,0 @@
1
- /**
2
- * Context Summarizer — 专用上下文总结模块
3
- *
4
- * 当对话历史超过当前模型的 context window 时,通过外部注入的 summarize 回调执行 AI 总结。
5
- * 回调由宿主提供(如 SuperX 走 ai-server 的 toolAI),ai-chat-core 不关心具体调用方式。
6
- */
7
-
8
- import type { ProtocolMessage } from '../protocols';
9
- import {
10
- buildSummarizeRequest,
11
- applySummary,
12
- estimateTotalTokens,
13
- estimateStringTokens,
14
- buildSingleMessageSummarizeRequest,
15
- type CompactConfig,
16
- } from './context-compressor';
17
- import { createModuleLogger } from '../logger';
18
-
19
- const logger = createModuleLogger('ContextSummarizer');
20
-
21
- // ==================== 类型 ====================
22
-
23
- /** 外部注入的 AI 总结函数,model 由 orchestrator 根据场景传入 */
24
- export type SummarizeFn = (systemPrompt: string, userPrompt: string, model: string) => Promise<string>;
25
-
26
- export interface SummarizeResult {
27
- messages: ProtocolMessage[];
28
- success: boolean;
29
- /** AI 生成的原始摘要文本,前端持久化时需加 [对话历史摘要] 前缀 */
30
- summaryContent?: string;
31
- }
32
-
33
- // ==================== 核心函数 ====================
34
-
35
- /**
36
- * 使用 AI 总结对话历史
37
- *
38
- * 1. 用 buildSummarizeRequest 分离「待总结的中间历史」和「保留的最近消息」
39
- * 2. 调用外部注入的 summarizeFn 执行总结(走 ai-server 等)
40
- * 3. 用 applySummary 组装压缩后的消息列表
41
- */
42
- export async function summarizeHistory(
43
- summarizeFn: SummarizeFn,
44
- messages: ProtocolMessage[],
45
- config: CompactConfig,
46
- model: string,
47
- ): Promise<SummarizeResult> {
48
- const { summarizeMessages, keepMessages } = buildSummarizeRequest(messages, config);
49
-
50
- if (summarizeMessages.length === 0) {
51
- logger.info('中间历史太短,跳过 AI 总结');
52
- return { messages, success: true };
53
- }
54
-
55
- const systemPrompt = summarizeMessages[0].content;
56
- const userPrompt = summarizeMessages[1].content;
57
-
58
- logger.info({ model, tokens: estimateTotalTokens(summarizeMessages), messageCount: messages.length }, `开始 AI 总结 (${model}): ~${estimateTotalTokens(summarizeMessages)} tokens, ${messages.length} 条消息`);
59
-
60
- try {
61
- const summaryContent = await summarizeFn(systemPrompt, userPrompt, model);
62
-
63
- if (!summaryContent.trim()) {
64
- logger.warn('AI 总结返回空内容');
65
- return { messages, success: false };
66
- }
67
-
68
- const compressed = applySummary(messages, summaryContent, keepMessages);
69
- logger.info({ originalCount: messages.length, compressedCount: compressed.length, tokens: estimateTotalTokens(compressed) }, `AI 总结完成: ${messages.length} → ${compressed.length} 条消息, ~${estimateTotalTokens(compressed)} tokens`);
70
- return { messages: compressed, success: true, summaryContent };
71
- } catch (err) {
72
- logger.error({ err }, 'AI 总结异常');
73
- return { messages, success: false };
74
- }
75
- }
76
-
77
- // ==================== 单条消息内容压缩 ====================
78
-
79
- export interface ContentCompressResult {
80
- /** 压缩后的摘要,替换原消息 content 发给目标模型 */
81
- summary: string;
82
- success: boolean;
83
- /** 原文估算 token 数 */
84
- originalTokens: number;
85
- /** 摘要估算 token 数 */
86
- compressedTokens: number;
87
- }
88
-
89
- /**
90
- * 使用 AI 压缩单条超长用户消息
91
- *
92
- * 直接调用 qwen-long(10M 窗口)总结用户贴的超长文档,
93
- * 返回摘要文本,由 orchestrator 替换消息 content 发给目标模型。
94
- */
95
- export async function compressSingleMessage(
96
- summarizeFn: SummarizeFn,
97
- message: ProtocolMessage,
98
- model: string,
99
- ): Promise<ContentCompressResult> {
100
- const { summarizeMessages } = buildSingleMessageSummarizeRequest(message);
101
- const systemPrompt = summarizeMessages[0].content;
102
- const userPrompt = summarizeMessages[1].content;
103
- const originalTokens = estimateStringTokens(message.content);
104
-
105
- logger.info({ model, originalTokens }, `开始单条消息压缩 (${model}): ~${originalTokens} tokens`);
106
-
107
- try {
108
- const summary = await summarizeFn(systemPrompt, userPrompt, model);
109
-
110
- if (!summary.trim()) {
111
- logger.warn('单条消息压缩返回空内容');
112
- return { summary: '', success: false, originalTokens, compressedTokens: 0 };
113
- }
114
-
115
- const compressedTokens = estimateStringTokens(summary);
116
- const suffixed = `${summary}\n\n(原文约 ${originalTokens} tokens,已由 AI 压缩提取要点)`;
117
- logger.info({ originalTokens, compressedTokens }, `单条消息压缩完成: ~${originalTokens} → ~${compressedTokens} tokens`);
118
- return { summary: suffixed, success: true, originalTokens, compressedTokens };
119
- } catch (err) {
120
- logger.error({ err }, '单条消息压缩异常');
121
- return { summary: '', success: false, originalTokens, compressedTokens: 0 };
122
- }
123
- }
@@ -1,20 +0,0 @@
1
- /**
2
- * Orchestrator 模块导出(barrel)
3
- *
4
- * - ChatOrchestrator:RawEvent → ChatEvent 循环、工具调用与压缩编排
5
- * - context-summarizer:超长历史外部总结入口
6
- * - types:编排配置与钩子类型
7
- */
8
-
9
- export { ChatOrchestrator, createOrchestrator } from './orchestrator';
10
- export { summarizeHistory } from './context-summarizer';
11
- export type { SummarizeFn, SummarizeResult } from './context-summarizer';
12
-
13
- export type {
14
- ToolExecutionHooks,
15
- OrchestratorToolExecutor,
16
- GetAutoRunConfigCallback,
17
- OrchestratorConfig,
18
- OrchestratorContext,
19
- OrchestratorOptions,
20
- } from './types';