@huyooo/ai-chat-core 0.2.45 → 0.3.2

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 (247) hide show
  1. package/dist/adapter/index.d.ts +11 -0
  2. package/dist/adapter/index.d.ts.map +1 -0
  3. package/dist/adapter/model-adapter.d.ts +25 -0
  4. package/dist/adapter/model-adapter.d.ts.map +1 -0
  5. package/dist/adapter/model-options.d.ts +53 -0
  6. package/dist/adapter/model-options.d.ts.map +1 -0
  7. package/dist/adapter/types.d.ts +28 -0
  8. package/dist/adapter/types.d.ts.map +1 -0
  9. package/dist/chat-runtime.d.ts +96 -0
  10. package/dist/chat-runtime.d.ts.map +1 -0
  11. package/dist/constants.d.ts +12 -0
  12. package/dist/constants.d.ts.map +1 -0
  13. package/dist/events.d.ts +605 -1
  14. package/dist/events.d.ts.map +1 -0
  15. package/dist/events.js +1 -1
  16. package/dist/extension/index.d.ts +9 -0
  17. package/dist/extension/index.d.ts.map +1 -0
  18. package/dist/extension/types.d.ts +46 -0
  19. package/dist/extension/types.d.ts.map +1 -0
  20. package/dist/families/index.d.ts +11 -0
  21. package/dist/families/index.d.ts.map +1 -0
  22. package/dist/families/presets.d.ts +31 -0
  23. package/dist/families/presets.d.ts.map +1 -0
  24. package/dist/families/resolver.d.ts +11 -0
  25. package/dist/families/resolver.d.ts.map +1 -0
  26. package/dist/families/types.d.ts +29 -0
  27. package/dist/families/types.d.ts.map +1 -0
  28. package/dist/governance/command-safety.d.ts +34 -0
  29. package/dist/governance/command-safety.d.ts.map +1 -0
  30. package/dist/governance/governance.d.ts +19 -0
  31. package/dist/governance/governance.d.ts.map +1 -0
  32. package/dist/governance/index.d.ts +12 -0
  33. package/dist/governance/index.d.ts.map +1 -0
  34. package/dist/governance/types.d.ts +29 -0
  35. package/dist/governance/types.d.ts.map +1 -0
  36. package/dist/index.d.ts +72 -804
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +51 -1
  39. package/dist/internal/management-args.d.ts +13 -0
  40. package/dist/internal/management-args.d.ts.map +1 -0
  41. package/dist/internal/management-results.d.ts +21 -0
  42. package/dist/internal/management-results.d.ts.map +1 -0
  43. package/dist/llm-config.d.ts +108 -0
  44. package/dist/llm-config.d.ts.map +1 -0
  45. package/dist/logger/core.d.ts +31 -0
  46. package/dist/logger/core.d.ts.map +1 -0
  47. package/dist/logger/index.d.ts +9 -0
  48. package/dist/logger/index.d.ts.map +1 -0
  49. package/dist/orchestrator/compression-handler.d.ts +29 -0
  50. package/dist/orchestrator/compression-handler.d.ts.map +1 -0
  51. package/dist/orchestrator/context-compressor.d.ts +51 -0
  52. package/dist/orchestrator/context-compressor.d.ts.map +1 -0
  53. package/dist/orchestrator/context-summarizer.d.ts +41 -0
  54. package/dist/orchestrator/context-summarizer.d.ts.map +1 -0
  55. package/dist/orchestrator/index.d.ts +12 -0
  56. package/dist/orchestrator/index.d.ts.map +1 -0
  57. package/dist/orchestrator/orchestrator.d.ts +46 -0
  58. package/dist/orchestrator/orchestrator.d.ts.map +1 -0
  59. package/dist/orchestrator/types.d.ts +58 -0
  60. package/dist/orchestrator/types.d.ts.map +1 -0
  61. package/dist/parts/index.d.ts +13 -0
  62. package/dist/parts/index.d.ts.map +1 -0
  63. package/dist/parts/registry.d.ts +11 -0
  64. package/dist/parts/registry.d.ts.map +1 -0
  65. package/dist/parts/summaries.d.ts +9 -0
  66. package/dist/parts/summaries.d.ts.map +1 -0
  67. package/dist/parts/types.d.ts +61 -0
  68. package/dist/parts/types.d.ts.map +1 -0
  69. package/dist/platform.d.ts +17 -0
  70. package/dist/platform.d.ts.map +1 -0
  71. package/dist/platform.js +1 -0
  72. package/dist/protocols/anthropic.d.ts +20 -0
  73. package/dist/protocols/anthropic.d.ts.map +1 -0
  74. package/dist/protocols/ark.d.ts +36 -0
  75. package/dist/protocols/ark.d.ts.map +1 -0
  76. package/dist/protocols/deepseek.d.ts +24 -0
  77. package/dist/protocols/deepseek.d.ts.map +1 -0
  78. package/dist/protocols/error-utils.d.ts +14 -0
  79. package/dist/protocols/error-utils.d.ts.map +1 -0
  80. package/dist/protocols/gemini.d.ts +24 -0
  81. package/dist/protocols/gemini.d.ts.map +1 -0
  82. package/dist/protocols/glm.d.ts +20 -0
  83. package/dist/protocols/glm.d.ts.map +1 -0
  84. package/dist/protocols/grok.d.ts +20 -0
  85. package/dist/protocols/grok.d.ts.map +1 -0
  86. package/dist/protocols/index.d.ts +31 -0
  87. package/dist/protocols/index.d.ts.map +1 -0
  88. package/dist/protocols/minimax.d.ts +38 -0
  89. package/dist/protocols/minimax.d.ts.map +1 -0
  90. package/dist/protocols/moonshot.d.ts +20 -0
  91. package/dist/protocols/moonshot.d.ts.map +1 -0
  92. package/dist/protocols/openai-sse.d.ts +33 -0
  93. package/dist/protocols/openai-sse.d.ts.map +1 -0
  94. package/dist/protocols/openai.d.ts +19 -0
  95. package/dist/protocols/openai.d.ts.map +1 -0
  96. package/dist/protocols/qwen.d.ts +26 -0
  97. package/dist/protocols/qwen.d.ts.map +1 -0
  98. package/dist/protocols/responses-sse.d.ts +30 -0
  99. package/dist/protocols/responses-sse.d.ts.map +1 -0
  100. package/dist/protocols/sse-reader.d.ts +23 -0
  101. package/dist/protocols/sse-reader.d.ts.map +1 -0
  102. package/dist/protocols/tool-arguments.d.ts +8 -0
  103. package/dist/protocols/tool-arguments.d.ts.map +1 -0
  104. package/dist/protocols/types.d.ts +148 -0
  105. package/dist/protocols/types.d.ts.map +1 -0
  106. package/dist/protocols/vercel-gateway.d.ts +15 -0
  107. package/dist/protocols/vercel-gateway.d.ts.map +1 -0
  108. package/dist/runtime.d.ts +151 -0
  109. package/dist/runtime.d.ts.map +1 -0
  110. package/dist/runtime.js +1 -0
  111. package/dist/skills/index.d.ts +14 -0
  112. package/dist/skills/index.d.ts.map +1 -0
  113. package/dist/skills/management/admin.d.ts +10 -0
  114. package/dist/skills/management/admin.d.ts.map +1 -0
  115. package/dist/skills/management/index.d.ts +11 -0
  116. package/dist/skills/management/index.d.ts.map +1 -0
  117. package/dist/skills/management/inputs.d.ts +44 -0
  118. package/dist/skills/management/inputs.d.ts.map +1 -0
  119. package/dist/skills/management/operations.d.ts +78 -0
  120. package/dist/skills/management/operations.d.ts.map +1 -0
  121. package/dist/skills/management/types.d.ts +70 -0
  122. package/dist/skills/management/types.d.ts.map +1 -0
  123. package/dist/skills/registry.d.ts +37 -0
  124. package/dist/skills/registry.d.ts.map +1 -0
  125. package/dist/skills/summaries.d.ts +9 -0
  126. package/dist/skills/summaries.d.ts.map +1 -0
  127. package/dist/skills/types.d.ts +61 -0
  128. package/dist/skills/types.d.ts.map +1 -0
  129. package/dist/test-utils/mock-sse.d.ts +13 -0
  130. package/dist/test-utils/mock-sse.d.ts.map +1 -0
  131. package/dist/tool-manager/define-tool.d.ts +35 -0
  132. package/dist/tool-manager/define-tool.d.ts.map +1 -0
  133. package/dist/tool-manager/formats.d.ts +46 -0
  134. package/dist/tool-manager/formats.d.ts.map +1 -0
  135. package/dist/tool-manager/identity.d.ts +18 -0
  136. package/dist/tool-manager/identity.d.ts.map +1 -0
  137. package/dist/tool-manager/in-process-provider.d.ts +15 -0
  138. package/dist/tool-manager/in-process-provider.d.ts.map +1 -0
  139. package/dist/tool-manager/index.d.ts +18 -0
  140. package/dist/tool-manager/index.d.ts.map +1 -0
  141. package/dist/tool-manager/manager.d.ts +18 -0
  142. package/dist/tool-manager/manager.d.ts.map +1 -0
  143. package/dist/tool-manager/mcp-provider.d.ts +21 -0
  144. package/dist/tool-manager/mcp-provider.d.ts.map +1 -0
  145. package/dist/tool-manager/summaries.d.ts +39 -0
  146. package/dist/tool-manager/summaries.d.ts.map +1 -0
  147. package/dist/tool-manager/types.d.ts +314 -0
  148. package/dist/tool-manager/types.d.ts.map +1 -0
  149. package/dist/types.d.ts +663 -0
  150. package/dist/types.d.ts.map +1 -0
  151. package/package.json +26 -15
  152. package/src/adapter/index.ts +25 -0
  153. package/src/adapter/model-adapter.ts +196 -0
  154. package/src/adapter/model-options.ts +143 -0
  155. package/src/adapter/types.ts +41 -0
  156. package/src/chat-runtime.ts +515 -0
  157. package/src/constants.ts +9 -102
  158. package/src/events.ts +364 -150
  159. package/src/extension/index.ts +24 -0
  160. package/src/extension/types.ts +49 -0
  161. package/src/families/index.ts +28 -0
  162. package/src/families/presets.ts +124 -0
  163. package/src/families/resolver.ts +22 -0
  164. package/src/families/types.ts +55 -0
  165. package/src/governance/command-safety.ts +224 -0
  166. package/src/governance/governance.ts +125 -0
  167. package/src/governance/index.ts +38 -0
  168. package/src/governance/types.ts +44 -0
  169. package/src/index.ts +250 -145
  170. package/src/internal/management-args.ts +39 -0
  171. package/src/internal/management-results.ts +60 -0
  172. package/src/llm-config.ts +137 -0
  173. package/src/logger/core.ts +96 -0
  174. package/src/logger/index.ts +8 -0
  175. package/src/orchestrator/compression-handler.ts +137 -0
  176. package/src/{providers → orchestrator}/context-compressor.ts +79 -47
  177. package/src/orchestrator/context-summarizer.ts +123 -0
  178. package/src/orchestrator/index.ts +20 -0
  179. package/src/orchestrator/orchestrator.ts +1002 -0
  180. package/src/orchestrator/types.ts +70 -0
  181. package/src/parts/index.ts +20 -0
  182. package/src/parts/registry.ts +95 -0
  183. package/src/parts/summaries.ts +40 -0
  184. package/src/parts/types.ts +63 -0
  185. package/src/platform.ts +73 -0
  186. package/src/protocols/anthropic.ts +377 -0
  187. package/src/protocols/ark.ts +300 -0
  188. package/src/protocols/deepseek.ts +192 -0
  189. package/src/{providers/protocols → protocols}/error-utils.ts +17 -20
  190. package/src/protocols/gemini.ts +352 -0
  191. package/src/protocols/glm.ts +212 -0
  192. package/src/protocols/grok.ts +98 -0
  193. package/src/protocols/index.ts +48 -0
  194. package/src/protocols/minimax.ts +308 -0
  195. package/src/protocols/moonshot.ts +186 -0
  196. package/src/protocols/openai-sse.ts +156 -0
  197. package/src/protocols/openai.ts +97 -0
  198. package/src/protocols/qwen.ts +358 -0
  199. package/src/protocols/responses-sse.ts +224 -0
  200. package/src/protocols/sse-reader.ts +54 -0
  201. package/src/protocols/tool-arguments.ts +32 -0
  202. package/src/{providers/protocols → protocols}/types.ts +46 -37
  203. package/src/protocols/vercel-gateway.ts +391 -0
  204. package/src/runtime.ts +167 -0
  205. package/src/skills/index.ts +29 -0
  206. package/src/skills/management/admin.ts +170 -0
  207. package/src/skills/management/index.ts +27 -0
  208. package/src/skills/management/inputs.ts +79 -0
  209. package/src/skills/management/operations.ts +256 -0
  210. package/src/skills/management/types.ts +57 -0
  211. package/src/skills/registry.ts +120 -0
  212. package/src/skills/summaries.ts +48 -0
  213. package/src/skills/types.ts +65 -0
  214. package/src/test-utils/mock-sse.ts +3 -3
  215. package/src/tool-manager/define-tool.ts +201 -0
  216. package/src/tool-manager/formats.ts +146 -0
  217. package/src/tool-manager/identity.ts +80 -0
  218. package/src/tool-manager/in-process-provider.ts +164 -0
  219. package/src/tool-manager/index.ts +63 -0
  220. package/src/tool-manager/manager.ts +562 -0
  221. package/src/tool-manager/mcp-provider.ts +509 -0
  222. package/src/tool-manager/summaries.ts +136 -0
  223. package/src/tool-manager/types.ts +389 -0
  224. package/src/types.ts +750 -191
  225. package/dist/events-CU5D5ray.d.ts +0 -1128
  226. package/src/agent.ts +0 -409
  227. package/src/internal/update-plan.ts +0 -2
  228. package/src/internal/web-search.ts +0 -77
  229. package/src/mcp/client-manager.ts +0 -302
  230. package/src/mcp/index.ts +0 -2
  231. package/src/mcp/types.ts +0 -43
  232. package/src/providers/context-summarizer.ts +0 -70
  233. package/src/providers/index.ts +0 -125
  234. package/src/providers/model-registry.ts +0 -466
  235. package/src/providers/orchestrator.ts +0 -839
  236. package/src/providers/protocols/anthropic.ts +0 -406
  237. package/src/providers/protocols/ark.ts +0 -362
  238. package/src/providers/protocols/deepseek.ts +0 -344
  239. package/src/providers/protocols/gemini.ts +0 -350
  240. package/src/providers/protocols/index.ts +0 -36
  241. package/src/providers/protocols/openai.ts +0 -420
  242. package/src/providers/protocols/qwen.ts +0 -315
  243. package/src/providers/types.ts +0 -264
  244. package/src/providers/unified-adapter.ts +0 -367
  245. package/src/router.ts +0 -72
  246. package/src/tools.ts +0 -162
  247. package/src/utils.ts +0 -86
@@ -0,0 +1,137 @@
1
+ /**
2
+ * LLM 配置(Model 中心)
3
+ *
4
+ * LLMConfig.models 是模型的唯一真相源:
5
+ * - key = 模型 ID(自定义字符串)
6
+ * - value = ModelConfig(路由 + 元数据)
7
+ * - 前端只展示这里配置的模型
8
+ * - 每个模型必须显式指定 family,不做任何隐式推导
9
+ */
10
+
11
+ import type { ProtocolFactory, Protocol, ProtocolConfig } from './protocols';
12
+ import type { ModelFamilyConfig, ModelFamilyId } from './families';
13
+
14
+ /** 模型定价(每百万 tokens) */
15
+ export interface ModelPricing {
16
+ currency: 'CNY' | 'USD';
17
+ /** 输入价格(每百万 tokens) */
18
+ input: number;
19
+ /** 输出价格,单值或 [最低, 最高] 区间(如思考/非思考差价) */
20
+ output: number | [number, number];
21
+ /** 缓存命中价格(每百万 tokens,部分模型支持) */
22
+ cached?: number;
23
+ }
24
+
25
+ /** 单条模型访问路径 */
26
+ export interface ModelRoute {
27
+ /** 基础 URL */
28
+ baseUrl: string;
29
+ /** 访问 key(代理 token 或厂商 key) */
30
+ accessKey: string;
31
+ /** 可选,透传给厂商的 key(BYOK,优先于 accessKey) */
32
+ vendorKey?: string;
33
+ /** 协议类型(内置或自定义 protocolId) */
34
+ protocol: string;
35
+ /** 该供应商 API 要求的 model 名 */
36
+ providerModelId: string;
37
+ /** 协议子路径,直连 '',代理 '/openai' 等 */
38
+ path?: string;
39
+ }
40
+
41
+ /** 模型配置(路由 + 元数据) */
42
+ export interface ModelConfig {
43
+ /** 路由链(数组顺序 = 降级优先级) */
44
+ routes: ModelRoute[];
45
+ /** 显示名称(不传则从预设库补全,再 fallback 到 modelId) */
46
+ displayName?: string;
47
+ /**
48
+ * 所属家族(决定 thinking/toolCall 行为)——必填,不做任何隐式推导
49
+ * - 字符串:引用内置 ModelFamilyId 或 LLMConfig.families 中的自定义 familyId
50
+ * - 对象:内联自定义家族配置
51
+ */
52
+ family: ModelFamilyId | ModelFamilyConfig;
53
+ /** 是否在前端模型选择器中显示(默认 true) */
54
+ visible?: boolean;
55
+ /** 是否支持深度思考(必填,不做隐式推导) */
56
+ supportsThinking: boolean;
57
+ /** 是否支持图片理解(必填,不做隐式推导) */
58
+ supportsVision: boolean;
59
+ /** 上下文窗口 token 数(必填,压缩器依赖此值计算预算,前端展示自动推导为 "256K" 格式) */
60
+ contextWindowTokens: number;
61
+ /** 定价信息(结构化,展示时自动格式化) */
62
+ pricing?: ModelPricing;
63
+ /** 最大输出 token(必填,每个模型限制不同,不做隐式推导) */
64
+ maxOutputTokens: number;
65
+ }
66
+
67
+ /**
68
+ * 压缩模型配置(可选,不配则用默认值)
69
+ *
70
+ * 历史压缩直接复用当前对话模型,零配置。
71
+ * 仅单条超长消息(如用户粘贴巨量文本)需要长上下文模型。
72
+ */
73
+ export interface CompressionConfig {
74
+ /** 超长内容压缩模型(默认 qwen-long) */
75
+ longModel?: string;
76
+ }
77
+
78
+ /** 完整 LLM 配置 */
79
+ export interface LLMConfig {
80
+ /**
81
+ * 模型配置(唯一真相源)
82
+ *
83
+ * key = 模型 ID,value = 路由 + 元数据。
84
+ * 前端模型选择器只展示这里 visible !== false 的模型。
85
+ */
86
+ models: Record<string, ModelConfig>;
87
+
88
+ /**
89
+ * 自定义协议工厂(可插拔)
90
+ *
91
+ * key = 自定义 protocolId,value = Protocol 工厂函数。
92
+ * ModelRoute.protocol 引用此处的 key 或内置 protocolId。
93
+ */
94
+ protocols?: Record<string, ProtocolFactory>;
95
+
96
+ /**
97
+ * 自定义家族配置(可插拔)
98
+ *
99
+ * key = 自定义 familyId,value = ModelFamilyConfig。
100
+ * ModelConfig.family 引用此处的 key 或内置 familyId。
101
+ */
102
+ families?: Record<string, ModelFamilyConfig>;
103
+
104
+ /** 压缩模型配置(可选) */
105
+ compression?: CompressionConfig;
106
+ }
107
+
108
+ /** 解析 route 的完整 API URL */
109
+ export function resolveRouteUrl(route: ModelRoute): string {
110
+ const p = route.path ?? '';
111
+ return p ? `${route.baseUrl.replace(/\/$/, '')}${p}` : route.baseUrl;
112
+ }
113
+
114
+ /**
115
+ * 获取模型的 route 链(按优先级)
116
+ */
117
+ export function getRouteChain(
118
+ config: LLMConfig,
119
+ modelId: string,
120
+ ): ModelRoute[] {
121
+ return config.models?.[modelId]?.routes ?? [];
122
+ }
123
+
124
+ /**
125
+ * 查找第一个支持指定 protocol 的 route
126
+ */
127
+ export function findRouteByProtocol(
128
+ config: LLMConfig,
129
+ protocolId: string,
130
+ ): ModelRoute | undefined {
131
+ for (const modelConfig of Object.values(config.models)) {
132
+ if (!modelConfig?.routes) continue;
133
+ const found = modelConfig.routes.find(r => r.protocol === protocolId);
134
+ if (found) return found;
135
+ }
136
+ return undefined;
137
+ }
@@ -0,0 +1,96 @@
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
+ }
@@ -0,0 +1,8 @@
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';
@@ -0,0 +1,137 @@
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
+ }
@@ -8,22 +8,22 @@
8
8
  * 不机械截断,而是让 AI 生成高质量摘要,保留关键决策和上下文。
9
9
  */
10
10
 
11
- import type { StandardMessage } from './types';
12
- import { DebugLogger } from '../utils';
11
+ import type { ProtocolMessage } from '../protocols';
12
+ import { createModuleLogger } from '../logger';
13
13
 
14
- const logger = DebugLogger.module('ContextCompressor');
14
+ const logger = createModuleLogger('ContextCompressor');
15
15
 
16
16
  // ==================== Token 估算 ====================
17
17
 
18
18
  const CHARS_PER_TOKEN = 3.2;
19
19
  const MESSAGE_OVERHEAD_TOKENS = 4;
20
20
 
21
- function estimateStringTokens(s: string): number {
21
+ export function estimateStringTokens(s: string): number {
22
22
  if (!s) return 0;
23
23
  return Math.ceil(s.length / CHARS_PER_TOKEN);
24
24
  }
25
25
 
26
- export function estimateMessageTokens(msg: StandardMessage): number {
26
+ export function estimateMessageTokens(msg: ProtocolMessage): number {
27
27
  let tokens = MESSAGE_OVERHEAD_TOKENS;
28
28
  tokens += estimateStringTokens(msg.content);
29
29
 
@@ -42,7 +42,7 @@ export function estimateMessageTokens(msg: StandardMessage): number {
42
42
  return tokens;
43
43
  }
44
44
 
45
- export function estimateTotalTokens(messages: StandardMessage[]): number {
45
+ export function estimateTotalTokens(messages: ProtocolMessage[]): number {
46
46
  let total = 3;
47
47
  for (const msg of messages) {
48
48
  total += estimateMessageTokens(msg);
@@ -50,6 +50,7 @@ export function estimateTotalTokens(messages: StandardMessage[]): number {
50
50
  return total;
51
51
  }
52
52
 
53
+
53
54
  // ==================== 配置 ====================
54
55
 
55
56
  export interface CompactConfig {
@@ -73,7 +74,7 @@ export function getPromptBudget(config: CompactConfig): number {
73
74
  }
74
75
 
75
76
  /** 检测是否需要压缩 */
76
- export function needsCompaction(messages: StandardMessage[], config: CompactConfig): boolean {
77
+ export function needsCompaction(messages: ProtocolMessage[], config: CompactConfig): boolean {
77
78
  return estimateTotalTokens(messages) > getPromptBudget(config);
78
79
  }
79
80
 
@@ -84,15 +85,14 @@ export function needsCompaction(messages: StandardMessage[], config: CompactConf
84
85
  * 总结完成后调用 applySummary 组装新的消息列表。
85
86
  */
86
87
  export function buildSummarizeRequest(
87
- messages: StandardMessage[],
88
+ messages: ProtocolMessage[],
88
89
  config: CompactConfig,
89
- ): { summarizeMessages: StandardMessage[]; keepMessages: StandardMessage[] } {
90
+ ): { summarizeMessages: ProtocolMessage[]; keepMessages: ProtocolMessage[] } {
90
91
  const keepRecent = config.keepRecentMessages ?? DEFAULT_KEEP_RECENT;
91
92
 
92
- // 找边界
93
- let systemEnd = 0;
94
- if (messages[0]?.role === 'system') systemEnd = 1;
93
+ const systemEnd = messages[0]?.role === 'system' ? 1 : 0;
95
94
 
95
+ // 第一条 user 消息由 applySummary 原样保留,不参与总结
96
96
  let firstUserEnd = systemEnd;
97
97
  for (let i = systemEnd; i < messages.length; i++) {
98
98
  if (messages[i].role === 'user') {
@@ -101,32 +101,24 @@ export function buildSummarizeRequest(
101
101
  }
102
102
  }
103
103
 
104
- // 要保留的最近消息
105
104
  const recentStart = Math.max(firstUserEnd, messages.length - keepRecent);
106
105
  const keepMessages = messages.slice(recentStart);
107
106
 
108
- // 要被总结的中间历史
107
+ // 中间历史:从 firstUser 之后到 recentStart
108
+ // 多轮压缩时包含旧摘要(role=system),因为 applySummary 把摘要放在 firstUser 之后
109
109
  const middleMessages = messages.slice(firstUserEnd, recentStart);
110
110
 
111
111
  if (middleMessages.length < 2) {
112
- // 中间太短,没什么可总结的
113
112
  return { summarizeMessages: [], keepMessages: messages.slice(systemEnd) };
114
113
  }
115
114
 
116
115
  const estimatedTokens = estimateTotalTokens(messages);
117
116
  const budget = getPromptBudget(config);
118
- logger.info(`准备 AI 总结: ~${estimatedTokens} tokens > budget ${budget}, 总结 ${middleMessages.length} 条中间消息, 保留最近 ${keepMessages.length} 条`);
119
-
120
- // 构建总结请求:把中间历史交给模型
121
- const summarizeMessages: StandardMessage[] = [
122
- {
123
- role: 'system',
124
- content: SUMMARIZE_SYSTEM_PROMPT,
125
- },
126
- {
127
- role: 'user',
128
- content: formatMessagesForSummary(middleMessages),
129
- },
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) },
130
122
  ];
131
123
 
132
124
  return { summarizeMessages, keepMessages };
@@ -136,16 +128,15 @@ export function buildSummarizeRequest(
136
128
  * 用 AI 返回的摘要组装新的消息列表
137
129
  */
138
130
  export function applySummary(
139
- originalMessages: StandardMessage[],
131
+ originalMessages: ProtocolMessage[],
140
132
  summary: string,
141
- keepMessages: StandardMessage[],
142
- ): StandardMessage[] {
143
- // 取原始的 system prompt
133
+ keepMessages: ProtocolMessage[],
134
+ ): ProtocolMessage[] {
144
135
  const systemPrompt = originalMessages[0]?.role === 'system' ? originalMessages[0] : null;
145
136
 
146
- // 取第一条 user 消息
137
+ // 保留第一条 user 消息原文(初始需求/意图,AI 总结可能丢失细节)
147
138
  const startIdx = systemPrompt ? 1 : 0;
148
- let firstUser: StandardMessage | null = null;
139
+ let firstUser: ProtocolMessage | null = null;
149
140
  for (let i = startIdx; i < originalMessages.length; i++) {
150
141
  if (originalMessages[i].role === 'user') {
151
142
  firstUser = originalMessages[i];
@@ -153,25 +144,64 @@ export function applySummary(
153
144
  }
154
145
  }
155
146
 
156
- const result: StandardMessage[] = [];
147
+ const result: ProtocolMessage[] = [];
157
148
  if (systemPrompt) result.push(systemPrompt);
158
149
  if (firstUser) result.push(firstUser);
159
150
 
160
- // 插入 AI 生成的摘要
161
151
  result.push({
162
152
  role: 'system',
163
153
  content: `[对话历史摘要]\n${summary}`,
164
154
  });
165
155
 
166
- // 拼上最近保留的消息
167
156
  result.push(...keepMessages);
168
157
 
169
158
  const tokens = estimateTotalTokens(result);
170
- logger.info(`AI 总结应用完成: ${originalMessages.length} → ${result.length} 条消息, ~${tokens} tokens`);
159
+ logger.info({ originalCount: originalMessages.length, resultCount: result.length, tokens }, `AI 总结应用完成: ${originalMessages.length} → ${result.length} 条消息, ~${tokens} tokens`);
171
160
 
172
161
  return result;
173
162
  }
174
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
+
175
205
  // ==================== 内部 ====================
176
206
 
177
207
  const SUMMARIZE_SYSTEM_PROMPT = `你是一个对话历史压缩助手。请总结以下对话历史,保留所有关键信息:
@@ -187,27 +217,29 @@ const SUMMARIZE_SYSTEM_PROMPT = `你是一个对话历史压缩助手。请总
187
217
  直接输出摘要,不要开头说"以下是摘要"之类的话。`;
188
218
 
189
219
  /** 把消息列表格式化为可读文本,供总结用 */
190
- function formatMessagesForSummary(messages: StandardMessage[]): string {
220
+ function formatMessagesForSummary(messages: ProtocolMessage[]): string {
191
221
  const parts: string[] = [];
192
222
 
193
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
+
194
233
  const role = msg.role === 'assistant' ? 'AI' : msg.role === 'user' ? '用户' : '工具';
195
234
 
196
235
  if (msg.role === 'assistant' && msg.toolCalls && msg.toolCalls.length > 0) {
197
236
  const calls = msg.toolCalls.map(tc => {
198
- const argsPreview = tc.arguments.length > 200
199
- ? tc.arguments.slice(0, 200) + '...'
200
- : tc.arguments;
201
- return ` 调用 ${tc.name}(${argsPreview})`;
237
+ return ` 调用 ${tc.name}(${tc.arguments})`;
202
238
  }).join('\n');
203
239
  const text = msg.content ? `${msg.content}\n${calls}` : calls;
204
240
  parts.push(`[${role}]\n${text}`);
205
241
  } else if (msg.role === 'tool') {
206
- // tool result 保留前 1000 字符(总结模型能看到足够信息)
207
- const content = msg.content.length > 1000
208
- ? msg.content.slice(0, 1000) + `... (共 ${msg.content.length} 字符)`
209
- : msg.content;
210
- parts.push(`[${role}: ${msg.toolName ?? 'unknown'}]\n${content}`);
242
+ parts.push(`[${role}: ${msg.toolName ?? 'unknown'}]\n${msg.content}`);
211
243
  } else if (msg.content) {
212
244
  parts.push(`[${role}]\n${msg.content}`);
213
245
  }