deeper-cli 1.0.0

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 (188) hide show
  1. package/README.md +254 -0
  2. package/dist/cli/index.d.ts +1 -0
  3. package/dist/cli/index.js +12067 -0
  4. package/dist/cli/index.js.map +1 -0
  5. package/dist/index.d.ts +415 -0
  6. package/dist/index.js +1599 -0
  7. package/dist/index.js.map +1 -0
  8. package/docs/superpowers/plans/2026-05-14-deepercode-implementation.md +24 -0
  9. package/docs/superpowers/plans/2026-05-14-deepercode-plan.md +1248 -0
  10. package/docs/superpowers/specs/2026-05-14-deepercode-design.md +560 -0
  11. package/package.json +60 -0
  12. package/src/cli/bootstrap.ts +69 -0
  13. package/src/cli/chat-repl.ts +932 -0
  14. package/src/cli/commands/chat.ts +39 -0
  15. package/src/cli/commands/chat.tsx +39 -0
  16. package/src/cli/commands/config.ts +133 -0
  17. package/src/cli/commands/mcp.ts +172 -0
  18. package/src/cli/commands/run.ts +147 -0
  19. package/src/cli/commands/skill.ts +152 -0
  20. package/src/cli/index.ts +184 -0
  21. package/src/core/bugscan.ts +145 -0
  22. package/src/core/config.ts +285 -0
  23. package/src/core/constants.ts +49 -0
  24. package/src/core/eventbus.ts +202 -0
  25. package/src/core/logger.ts +109 -0
  26. package/src/core/storage.ts +96 -0
  27. package/src/index.ts +26 -0
  28. package/src/mcp/ConfigLoader.ts +74 -0
  29. package/src/mcp/MCPClient.ts +326 -0
  30. package/src/mcp/ResourceAdapter.ts +58 -0
  31. package/src/mcp/SSETransport.ts +133 -0
  32. package/src/mcp/StdioTransport.ts +116 -0
  33. package/src/mcp/ToolAdapter.ts +71 -0
  34. package/src/mcp/types.ts +58 -0
  35. package/src/memory/xmemory.ts +275 -0
  36. package/src/model/DeepSeekClient.ts +292 -0
  37. package/src/model/MessageBuilder.ts +155 -0
  38. package/src/model/RetryManager.ts +82 -0
  39. package/src/model/StreamHandler.ts +158 -0
  40. package/src/model/types.ts +86 -0
  41. package/src/skills/SkillCreator.ts +153 -0
  42. package/src/skills/SkillEngine.ts +158 -0
  43. package/src/skills/SkillExecutor.ts +107 -0
  44. package/src/skills/SkillLoader.ts +182 -0
  45. package/src/skills/SkillRegistry.ts +73 -0
  46. package/src/skills/SkillTrigger.ts +82 -0
  47. package/src/skills/types.ts +28 -0
  48. package/src/tools/ToolExecutor.ts +103 -0
  49. package/src/tools/ToolRegistry.ts +71 -0
  50. package/src/tools/ToolValidator.ts +103 -0
  51. package/src/tools/builtin/ai/context_summarize.ts +76 -0
  52. package/src/tools/builtin/ai/memory_store.ts +86 -0
  53. package/src/tools/builtin/ai/prompt_template.ts +71 -0
  54. package/src/tools/builtin/ai/skill_create.ts +53 -0
  55. package/src/tools/builtin/ai/subagent.ts +39 -0
  56. package/src/tools/builtin/ai/todo_manager.ts +157 -0
  57. package/src/tools/builtin/ai/token_count.ts +196 -0
  58. package/src/tools/builtin/ai/tool_create.ts +52 -0
  59. package/src/tools/builtin/code/analyze_deps.ts +72 -0
  60. package/src/tools/builtin/code/bug_scan.ts +80 -0
  61. package/src/tools/builtin/code/code_metrics.ts +111 -0
  62. package/src/tools/builtin/code/extract_function.ts +86 -0
  63. package/src/tools/builtin/code/format_code.ts +57 -0
  64. package/src/tools/builtin/code/generate_code.ts +75 -0
  65. package/src/tools/builtin/code/import_organizer.ts +82 -0
  66. package/src/tools/builtin/code/lint_code.ts +48 -0
  67. package/src/tools/builtin/code/parse_ast.ts +86 -0
  68. package/src/tools/builtin/code/refactor_code.ts +63 -0
  69. package/src/tools/builtin/code/type_check.ts +48 -0
  70. package/src/tools/builtin/data/chart_generate.ts +62 -0
  71. package/src/tools/builtin/data/csv_parse.ts +56 -0
  72. package/src/tools/builtin/data/data_diff.ts +79 -0
  73. package/src/tools/builtin/data/data_transform.ts +74 -0
  74. package/src/tools/builtin/data/data_validate.ts +75 -0
  75. package/src/tools/builtin/data/json_parse.ts +71 -0
  76. package/src/tools/builtin/data/template_render.ts +58 -0
  77. package/src/tools/builtin/data/toml_parse.ts +42 -0
  78. package/src/tools/builtin/data/xml_parse.ts +79 -0
  79. package/src/tools/builtin/data/yaml_parse.ts +42 -0
  80. package/src/tools/builtin/database/db_backup.ts +53 -0
  81. package/src/tools/builtin/database/db_restore.ts +51 -0
  82. package/src/tools/builtin/database/db_schema.ts +66 -0
  83. package/src/tools/builtin/database/nosql_query.ts +50 -0
  84. package/src/tools/builtin/database/orm_generate.ts +66 -0
  85. package/src/tools/builtin/database/redis_command.ts +46 -0
  86. package/src/tools/builtin/database/sql_migrate.ts +55 -0
  87. package/src/tools/builtin/database/sql_query.ts +60 -0
  88. package/src/tools/builtin/filesystem/batch_read.ts +56 -0
  89. package/src/tools/builtin/filesystem/batch_write.ts +67 -0
  90. package/src/tools/builtin/filesystem/copy_file.ts +36 -0
  91. package/src/tools/builtin/filesystem/create_dir.ts +30 -0
  92. package/src/tools/builtin/filesystem/delete_file.ts +30 -0
  93. package/src/tools/builtin/filesystem/diff_files.ts +47 -0
  94. package/src/tools/builtin/filesystem/edit_file.ts +47 -0
  95. package/src/tools/builtin/filesystem/file_info.ts +52 -0
  96. package/src/tools/builtin/filesystem/glob_find.ts +44 -0
  97. package/src/tools/builtin/filesystem/list_dir.ts +51 -0
  98. package/src/tools/builtin/filesystem/merge_files.ts +44 -0
  99. package/src/tools/builtin/filesystem/move_file.ts +37 -0
  100. package/src/tools/builtin/filesystem/read_file.ts +55 -0
  101. package/src/tools/builtin/filesystem/watch_file.ts +33 -0
  102. package/src/tools/builtin/filesystem/write_file.ts +45 -0
  103. package/src/tools/builtin/index.ts +244 -0
  104. package/src/tools/builtin/network/api_call.ts +79 -0
  105. package/src/tools/builtin/network/browser_action.ts +54 -0
  106. package/src/tools/builtin/network/check_url.ts +59 -0
  107. package/src/tools/builtin/network/download_file.ts +64 -0
  108. package/src/tools/builtin/network/graphql_query.ts +46 -0
  109. package/src/tools/builtin/network/http_request.ts +61 -0
  110. package/src/tools/builtin/network/parse_html.ts +101 -0
  111. package/src/tools/builtin/network/proxy_request.ts +53 -0
  112. package/src/tools/builtin/network/screenshot_page.ts +58 -0
  113. package/src/tools/builtin/network/web_fetch.ts +70 -0
  114. package/src/tools/builtin/network/web_search.ts +128 -0
  115. package/src/tools/builtin/network/websocket_connect.ts +70 -0
  116. package/src/tools/builtin/project/build_project.ts +68 -0
  117. package/src/tools/builtin/project/config_manage.ts +99 -0
  118. package/src/tools/builtin/project/coverage_report.ts +59 -0
  119. package/src/tools/builtin/project/docker_manage.ts +90 -0
  120. package/src/tools/builtin/project/env_manage.ts +88 -0
  121. package/src/tools/builtin/project/npm_manage.ts +71 -0
  122. package/src/tools/builtin/project/project_init.ts +59 -0
  123. package/src/tools/builtin/project/run_test.ts +74 -0
  124. package/src/tools/builtin/search/codebase_search.ts +76 -0
  125. package/src/tools/builtin/search/find_definition.ts +84 -0
  126. package/src/tools/builtin/search/find_references.ts +75 -0
  127. package/src/tools/builtin/search/fuzzy_find.ts +75 -0
  128. package/src/tools/builtin/search/grep_search.ts +90 -0
  129. package/src/tools/builtin/search/regex_find.ts +91 -0
  130. package/src/tools/builtin/search/search_docs.ts +51 -0
  131. package/src/tools/builtin/search/search_package.ts +50 -0
  132. package/src/tools/builtin/search/symbol_search.ts +82 -0
  133. package/src/tools/builtin/search/text_search.ts +63 -0
  134. package/src/tools/builtin/security/decrypt_file.ts +54 -0
  135. package/src/tools/builtin/security/encrypt_file.ts +52 -0
  136. package/src/tools/builtin/security/hash_generate.ts +48 -0
  137. package/src/tools/builtin/security/jwt_decode.ts +53 -0
  138. package/src/tools/builtin/security/secret_scan.ts +82 -0
  139. package/src/tools/builtin/security/vulnerability_check.ts +71 -0
  140. package/src/tools/builtin/shell/background_terminal.ts +38 -0
  141. package/src/tools/builtin/shell/check_status.ts +48 -0
  142. package/src/tools/builtin/shell/interactive_terminal.ts +31 -0
  143. package/src/tools/builtin/shell/kill_terminal.ts +29 -0
  144. package/src/tools/builtin/shell/list_terminals.ts +61 -0
  145. package/src/tools/builtin/shell/pipe_commands.ts +55 -0
  146. package/src/tools/builtin/shell/process-pool.ts +150 -0
  147. package/src/tools/builtin/shell/run_async.ts +73 -0
  148. package/src/tools/builtin/shell/run_command.ts +60 -0
  149. package/src/tools/builtin/shell/send_ctrl_keys.ts +43 -0
  150. package/src/tools/builtin/shell/send_keys.ts +36 -0
  151. package/src/tools/builtin/shell/send_text.ts +35 -0
  152. package/src/tools/builtin/shell/shell_script.ts +65 -0
  153. package/src/tools/builtin/shell/stop_command.ts +40 -0
  154. package/src/tools/builtin/shell/terminal_resize.ts +31 -0
  155. package/src/tools/builtin/shell/terminal_screenshot.ts +28 -0
  156. package/src/tools/builtin/system/log_viewer.ts +89 -0
  157. package/src/tools/builtin/system/notify_user.ts +55 -0
  158. package/src/tools/builtin/system/process_list.ts +66 -0
  159. package/src/tools/builtin/system/resource_monitor.ts +66 -0
  160. package/src/tools/builtin/system/system_info.ts +41 -0
  161. package/src/tools/tool-types.ts +97 -0
  162. package/src/ui/AgentTree.tsx +98 -0
  163. package/src/ui/App.tsx +46 -0
  164. package/src/ui/ChatView.tsx +278 -0
  165. package/src/ui/ConfirmDialog.tsx +68 -0
  166. package/src/ui/DiffView.tsx +64 -0
  167. package/src/ui/FilePreview.tsx +59 -0
  168. package/src/ui/InputBox.tsx +267 -0
  169. package/src/ui/MessageBubble.tsx +30 -0
  170. package/src/ui/Spinner.tsx +35 -0
  171. package/src/ui/StatusBar.tsx +41 -0
  172. package/src/ui/ToolCallCard.tsx +73 -0
  173. package/src/ui/ansi.ts +50 -0
  174. package/src/ui/markdown.ts +238 -0
  175. package/src/ui/themes/dark.ts +4 -0
  176. package/src/ui/themes/default.ts +25 -0
  177. package/src/ui/themes/light.ts +14 -0
  178. package/tests/unit/BuiltinTools.test.ts +129 -0
  179. package/tests/unit/BuiltinToolsIntegration.test.ts +111 -0
  180. package/tests/unit/FilesystemTools.test.ts +211 -0
  181. package/tests/unit/SkillLoader.test.ts +141 -0
  182. package/tests/unit/SkillRegistry.test.ts +113 -0
  183. package/tests/unit/ToolExecutor.test.ts +160 -0
  184. package/tests/unit/ToolRegistry.test.ts +103 -0
  185. package/tests/unit/ToolValidator.test.ts +137 -0
  186. package/tsconfig.json +28 -0
  187. package/tsup.config.ts +17 -0
  188. package/vitest.config.ts +20 -0
@@ -0,0 +1,58 @@
1
+ import type { JSONSchema } from '../tools/tool-types.js';
2
+
3
+ export interface MCPServerConfig {
4
+ name: string;
5
+ type: 'stdio' | 'sse';
6
+ command?: string;
7
+ args?: string[];
8
+ url?: string;
9
+ headers?: Record<string, string>;
10
+ env?: Record<string, string>;
11
+ }
12
+
13
+ export interface MCPTool {
14
+ name: string;
15
+ description: string;
16
+ inputSchema: JSONSchema;
17
+ }
18
+
19
+ export interface MCPResource {
20
+ uri: string;
21
+ name: string;
22
+ description?: string;
23
+ mimeType?: string;
24
+ }
25
+
26
+ export interface JSONRPCRequest {
27
+ jsonrpc: '2.0';
28
+ id: number | string;
29
+ method: string;
30
+ params?: Record<string, unknown>;
31
+ }
32
+
33
+ export interface JSONRPCResponse {
34
+ jsonrpc: '2.0';
35
+ id: number | string;
36
+ result?: unknown;
37
+ error?: {
38
+ code: number;
39
+ message: string;
40
+ data?: unknown;
41
+ };
42
+ }
43
+
44
+ export interface JSONRPCNotification {
45
+ jsonrpc: '2.0';
46
+ method: string;
47
+ params?: Record<string, unknown>;
48
+ }
49
+
50
+ export type JSONRPCMessage = JSONRPCRequest | JSONRPCResponse | JSONRPCNotification;
51
+
52
+ export interface MCPTransport {
53
+ connect(config: MCPServerConfig): Promise<void>;
54
+ disconnect(): void;
55
+ send(message: JSONRPCMessage): Promise<void>;
56
+ onMessage(handler: (message: JSONRPCMessage) => void): () => void;
57
+ isConnected(): boolean;
58
+ }
@@ -0,0 +1,275 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { DEEPER_HOME } from '../core/constants.js';
4
+
5
+ export interface MemoryEntry {
6
+ id: string;
7
+ type: 'working' | 'episodic' | 'semantic' | 'procedural';
8
+ content: string;
9
+ tags: string[];
10
+ importance: number; // 0-10
11
+ accuracy: number; // 0-10, 自评置信度
12
+ createdAt: number;
13
+ accessedAt: number;
14
+ accessCount: number;
15
+ sessionId: string;
16
+ source: string; // 'user' | 'agent' | 'tool' | 'system'
17
+ references: string[]; // 关联条目 ID
18
+ }
19
+
20
+ interface XMemStats {
21
+ totalEntries: number;
22
+ byType: Record<string, number>;
23
+ totalTokens: number;
24
+ lastCleanup: number;
25
+ }
26
+
27
+ const MEM_DIR = join(DEEPER_HOME, 'xmemory');
28
+ const STATS_FILE = join(MEM_DIR, 'stats.json');
29
+ const MAX_WORKING_MEM = 50;
30
+ const MAX_TOTAL_MEM = 2000;
31
+ const CLEANUP_THRESHOLD = 1500;
32
+
33
+ let currentSessionId = '';
34
+
35
+ export function setSessionId(id: string) { currentSessionId = id; }
36
+
37
+ function uid(): string {
38
+ return `mem_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
39
+ }
40
+
41
+ function ensureDir(): void {
42
+ if (!existsSync(MEM_DIR)) mkdirSync(MEM_DIR, { recursive: true });
43
+ if (!existsSync(STATS_FILE)) {
44
+ writeFileSync(STATS_FILE, JSON.stringify({ totalEntries: 0, byType: {}, totalTokens: 0, lastCleanup: Date.now() }), 'utf-8');
45
+ }
46
+ }
47
+
48
+ function loadStats(): XMemStats {
49
+ try {
50
+ return JSON.parse(readFileSync(STATS_FILE, 'utf-8')) as XMemStats;
51
+ } catch { return { totalEntries: 0, byType: {}, totalTokens: 0, lastCleanup: 0 }; }
52
+ }
53
+
54
+ function saveStats(s: XMemStats): void {
55
+ writeFileSync(STATS_FILE, JSON.stringify(s, null, 2), 'utf-8');
56
+ }
57
+
58
+ export class XMemory {
59
+ private working: MemoryEntry[] = [];
60
+ private index: Map<string, MemoryEntry> = new Map();
61
+ private dirty = false;
62
+
63
+ constructor() {
64
+ ensureDir();
65
+ }
66
+
67
+ // ============ 写入 ============
68
+ store(
69
+ type: MemoryEntry['type'],
70
+ content: string,
71
+ tags: string[] = [],
72
+ importance = 5,
73
+ accuracy = 7,
74
+ source: MemoryEntry['source'] = 'agent',
75
+ references: string[] = [],
76
+ ): string {
77
+ const id = uid();
78
+ const entry: MemoryEntry = {
79
+ id, type, content: content.slice(0, 2000), tags,
80
+ importance: Math.min(10, Math.max(0, importance)),
81
+ accuracy: Math.min(10, Math.max(0, accuracy)),
82
+ createdAt: Date.now(), accessedAt: Date.now(),
83
+ accessCount: 1, sessionId: currentSessionId,
84
+ source, references,
85
+ };
86
+
87
+ if (type === 'working') {
88
+ this.working.push(entry);
89
+ if (this.working.length > MAX_WORKING_MEM) {
90
+ this.working.shift();
91
+ }
92
+ }
93
+
94
+ this.index.set(id, entry);
95
+ this.dirty = true;
96
+
97
+ if (this.index.size > CLEANUP_THRESHOLD) {
98
+ this.autoCleanup();
99
+ }
100
+
101
+ return id;
102
+ }
103
+
104
+ storeEpisodic(content: string, tags: string[] = [], importance = 5): string {
105
+ return this.store('episodic', content, tags, importance, 7, 'agent');
106
+ }
107
+
108
+ storeSemantic(content: string, tags: string[] = [], importance = 7): string {
109
+ return this.store('semantic', content, tags, importance, 9, 'agent');
110
+ }
111
+
112
+ storeProcedural(content: string, tags: string[] = [], importance = 8): string {
113
+ return this.store('procedural', content, tags, importance, 9, 'agent');
114
+ }
115
+
116
+ storeWorking(content: string, tags: string[] = []): string {
117
+ return this.store('working', content, tags, 3, 5, 'agent');
118
+ }
119
+
120
+ // ============ 检索 ============
121
+ recall(query: string, limit = 5, minImportance = 0): MemoryEntry[] {
122
+ const keywords = query.toLowerCase().split(/[\s,,。]+/).filter(w => w.length > 1);
123
+ if (keywords.length === 0) return [];
124
+
125
+ const scored: Array<{ entry: MemoryEntry; score: number }> = [];
126
+ const now = Date.now();
127
+
128
+ for (const entry of this.index.values()) {
129
+ if (entry.importance < minImportance) continue;
130
+ let score = 0;
131
+ const c = (entry.content || '').toLowerCase();
132
+ const t = entry.tags.join(' ').toLowerCase();
133
+
134
+ for (const kw of keywords) {
135
+ if (c.includes(kw)) score += 3;
136
+ if (t.includes(kw)) score += 2;
137
+ if (entry.type === 'procedural' && c.includes(kw)) score += 1;
138
+ }
139
+
140
+ // 衰减: 重要度高 + 最近访问 权重高
141
+ const age = (now - entry.accessedAt) / (1000 * 60 * 60);
142
+ score += entry.importance * 0.5;
143
+ score -= Math.min(age / 24, 5); // 每 24 小时衰减 max 5
144
+
145
+ if (score > 0) scored.push({ entry, score });
146
+ }
147
+
148
+ scored.sort((a, b) => b.score - a.score);
149
+ return scored.slice(0, limit).map(s => {
150
+ s.entry.accessedAt = now;
151
+ s.entry.accessCount++;
152
+ return s.entry;
153
+ });
154
+ }
155
+
156
+ getByType(type: MemoryEntry['type'], limit = 20): MemoryEntry[] {
157
+ const res: MemoryEntry[] = [];
158
+ for (const entry of this.index.values()) {
159
+ if (entry.type === type) res.push(entry);
160
+ }
161
+ res.sort((a, b) => b.createdAt - a.createdAt);
162
+ return res.slice(0, limit);
163
+ }
164
+
165
+ getWorking(): MemoryEntry[] {
166
+ return this.working;
167
+ }
168
+
169
+ getWorkingContext(maxTokens = 2000): string {
170
+ if (this.working.length === 0) return '';
171
+ const lines = this.working.map(e => `[工作记忆] ${e.content.slice(0, 300)}`);
172
+ let result = '';
173
+ for (const line of lines) {
174
+ if ((result + line).length > maxTokens * 4) break;
175
+ result += line + '\n';
176
+ }
177
+ return result;
178
+ }
179
+
180
+ getProceduralHints(task: string, limit = 5): string {
181
+ const recalled = this.recall(task, limit, 3);
182
+ const proc = recalled.filter(r => r.type === 'procedural' || r.type === 'semantic');
183
+ if (proc.length === 0) return '';
184
+ return '[记忆提示]\n' + proc.map(p => `- ${p.content.slice(0, 250)}`).join('\n');
185
+ }
186
+
187
+ getSessionSummary(): string {
188
+ const entries = [...this.index.values()].filter(e => e.sessionId === currentSessionId);
189
+ if (entries.length === 0) return '';
190
+ const key = entries
191
+ .filter(e => e.importance >= 5)
192
+ .sort((a, b) => b.importance - a.importance)
193
+ .slice(0, 8);
194
+ return `[XMemory·本会话]\n` + key.map(e => {
195
+ const t = e.type === 'semantic' ? '知识' : e.type === 'procedural' ? '技能' : e.type === 'episodic' ? '经历' : '工作';
196
+ return `[${t}] ${e.content.slice(0, 200)}`;
197
+ }).join('\n');
198
+ }
199
+
200
+ // ============ 持久化 ============
201
+ async save(): Promise<void> {
202
+ if (!this.dirty) return;
203
+ ensureDir();
204
+
205
+ // 分批写入:每个 type 一个文件
206
+ const byType: Record<string, MemoryEntry[]> = {};
207
+ for (const entry of this.index.values()) {
208
+ if (!byType[entry.type]) byType[entry.type] = [];
209
+ byType[entry.type].push(entry);
210
+ }
211
+
212
+ for (const [type, entries] of Object.entries(byType)) {
213
+ const file = join(MEM_DIR, `${type}.json`);
214
+ writeFileSync(file, JSON.stringify(entries.slice(-500)), 'utf-8'); // 每个类型最多 500 条
215
+ }
216
+
217
+ const stats = loadStats();
218
+ stats.totalEntries = this.index.size;
219
+ stats.byType = {};
220
+ for (const entry of this.index.values()) {
221
+ stats.byType[entry.type] = (stats.byType[entry.type] || 0) + 1;
222
+ }
223
+ stats.totalTokens = [...this.index.values()].reduce((s, e) => s + e.content.length, 0);
224
+ saveStats(stats);
225
+
226
+ this.dirty = false;
227
+ }
228
+
229
+ async load(): Promise<void> {
230
+ ensureDir();
231
+ let total = 0;
232
+
233
+ for (const type of ['working', 'episodic', 'semantic', 'procedural']) {
234
+ const file = join(MEM_DIR, `${type}.json`);
235
+ if (!existsSync(file)) continue;
236
+ try {
237
+ const entries = JSON.parse(readFileSync(file, 'utf-8')) as MemoryEntry[];
238
+ for (const entry of entries) {
239
+ if (total > MAX_TOTAL_MEM) break;
240
+ this.index.set(entry.id, entry);
241
+ if (entry.type === 'working') this.working.push(entry);
242
+ total++;
243
+ }
244
+ } catch { /* skip corrupt files */ }
245
+ }
246
+
247
+ if (this.working.length > MAX_WORKING_MEM) {
248
+ this.working = this.working.slice(-MAX_WORKING_MEM);
249
+ }
250
+ }
251
+
252
+ // ============ 维护 ============
253
+ private autoCleanup(): void {
254
+ const entries = [...this.index.values()];
255
+ entries.sort((a, b) => {
256
+ const scoreA = a.importance * 2 + a.accessCount - (Date.now() - a.accessedAt) / (1000 * 60 * 60 * 24);
257
+ const scoreB = b.importance * 2 + b.accessCount - (Date.now() - b.accessedAt) / (1000 * 60 * 60 * 24);
258
+ return scoreA - scoreB;
259
+ });
260
+
261
+ const toKeep = entries.slice(-500);
262
+ this.index.clear();
263
+ this.working = [];
264
+ for (const entry of toKeep) {
265
+ this.index.set(entry.id, entry);
266
+ if (entry.type === 'working') this.working.push(entry);
267
+ }
268
+ this.dirty = true;
269
+ }
270
+
271
+ get totalEntries(): number { return this.index.size; }
272
+ get dirtyState(): boolean { return this.dirty; }
273
+ }
274
+
275
+ export const xmemory = new XMemory();
@@ -0,0 +1,292 @@
1
+ import type { ChatMessage, DeepSeekConfig, StreamChunk } from './types.js';
2
+ import type { ToolDefinition, ToolCall } from '../tools/tool-types.js';
3
+ import { RetryManager } from './RetryManager.js';
4
+ import { StreamHandler } from './StreamHandler.js';
5
+ import { logger } from '../core/logger.js';
6
+
7
+ interface ChatCompletionRequest {
8
+ model: string;
9
+ messages: Array<Record<string, unknown>>;
10
+ temperature?: number;
11
+ max_tokens?: number;
12
+ stream?: boolean;
13
+ tools?: Array<Record<string, unknown>>;
14
+ tool_choice?: string;
15
+ }
16
+
17
+ interface ChatCompletionResponse {
18
+ id: string;
19
+ object: string;
20
+ created: number;
21
+ model: string;
22
+ choices: Array<{
23
+ index: number;
24
+ message: {
25
+ role: string;
26
+ content: string | null;
27
+ tool_calls?: Array<{
28
+ id: string;
29
+ type: string;
30
+ function: {
31
+ name: string;
32
+ arguments: string;
33
+ };
34
+ }>;
35
+ reasoning_content?: string;
36
+ };
37
+ finish_reason: string;
38
+ }>;
39
+ }
40
+
41
+ const DEFAULT_TIMEOUT_MS = 120000;
42
+ const MAX_RETRIES = 3;
43
+
44
+ export class DeepSeekClient {
45
+ private config: DeepSeekConfig;
46
+ private retryManager: RetryManager;
47
+
48
+ constructor(config: DeepSeekConfig) {
49
+ this.config = config;
50
+ this.retryManager = new RetryManager(config.maxTokens > 0 ? MAX_RETRIES : MAX_RETRIES);
51
+ }
52
+
53
+ async chat(
54
+ messages: ChatMessage[],
55
+ tools?: ToolDefinition[],
56
+ overrides?: Partial<DeepSeekConfig>,
57
+ ): Promise<ChatMessage> {
58
+ const cfg = this.mergeConfig(overrides);
59
+ const body = this.buildRequestBody(messages, tools, cfg, false);
60
+
61
+ const response = await this.retryManager.execute(async () => {
62
+ const result = await this.retryManager.withTimeout(
63
+ () => this.makeRequest(cfg, body),
64
+ cfg.maxTokens > 0 ? DEFAULT_TIMEOUT_MS : DEFAULT_TIMEOUT_MS,
65
+ );
66
+ return result;
67
+ }, this.shouldRetry);
68
+
69
+ const data = (await response.json()) as ChatCompletionResponse;
70
+
71
+ if (!data.choices || data.choices.length === 0) {
72
+ throw new Error('No choices returned from API');
73
+ }
74
+
75
+ const choice = data.choices[0];
76
+ const message = choice.message;
77
+
78
+ const result: ChatMessage = {
79
+ role: 'assistant',
80
+ content: message.content,
81
+ };
82
+
83
+ if (message.tool_calls) {
84
+ result.tool_calls = message.tool_calls.map((tc) => ({
85
+ id: tc.id,
86
+ name: tc.function.name,
87
+ arguments: this.parseArguments(tc.function.arguments),
88
+ }));
89
+ }
90
+
91
+ if (message.reasoning_content) {
92
+ result.thinking = message.reasoning_content;
93
+ }
94
+
95
+ return result;
96
+ }
97
+
98
+ async chatStream(
99
+ messages: ChatMessage[],
100
+ tools?: ToolDefinition[],
101
+ overrides?: Partial<DeepSeekConfig>,
102
+ ): Promise<AsyncIterable<StreamChunk>> {
103
+ const cfg = this.mergeConfig(overrides);
104
+ const body = this.buildRequestBody(messages, tools, cfg, true);
105
+
106
+ const response = await this.retryManager.execute(async () => {
107
+ const result = await this.retryManager.withTimeout(
108
+ () => this.makeRequest(cfg, body),
109
+ DEFAULT_TIMEOUT_MS,
110
+ );
111
+ return result;
112
+ }, this.shouldRetry);
113
+
114
+ if (!response.ok) {
115
+ const errorBody = await response.text().catch(() => '');
116
+ throw new Error(`API request failed: ${response.status} ${response.statusText} - ${errorBody}`);
117
+ }
118
+
119
+ if (!response.body) {
120
+ throw new Error('Response body is empty');
121
+ }
122
+
123
+ return this.createStreamIterable(response.body);
124
+ }
125
+
126
+ private async *createStreamIterable(body: unknown): AsyncIterable<StreamChunk> {
127
+ const handler = new StreamHandler();
128
+ let buffer = '';
129
+ const stream = body as AsyncIterable<Uint8Array>;
130
+
131
+ for await (const chunk of stream) {
132
+ const text = typeof chunk === 'string' ? chunk : new TextDecoder().decode(chunk);
133
+ buffer += text;
134
+
135
+ const lines = buffer.split('\n');
136
+ buffer = lines.pop() ?? '';
137
+
138
+ for (const line of lines) {
139
+ const trimmed = line.trim();
140
+ if (!trimmed || trimmed.startsWith(':')) {
141
+ continue;
142
+ }
143
+
144
+ if (trimmed.startsWith('data: ')) {
145
+ const data = trimmed.slice(6);
146
+ const result = handler.handleEvent('message', data);
147
+ if (result) {
148
+ yield result;
149
+ }
150
+ } else if (trimmed === 'data: [DONE]') {
151
+ yield { type: 'done' };
152
+ return;
153
+ }
154
+ }
155
+ }
156
+
157
+ if (buffer.trim()) {
158
+ const trimmed = buffer.trim();
159
+ if (trimmed.startsWith('data: ')) {
160
+ const data = trimmed.slice(6);
161
+ const result = handler.handleEvent('message', data);
162
+ if (result) {
163
+ yield result;
164
+ }
165
+ }
166
+ }
167
+
168
+ if (!handler.isFinished()) {
169
+ yield { type: 'done' };
170
+ }
171
+ }
172
+
173
+ private buildRequestBody(
174
+ messages: ChatMessage[],
175
+ tools: ToolDefinition[] | undefined,
176
+ config: DeepSeekConfig,
177
+ stream: boolean,
178
+ ): string {
179
+ const body: ChatCompletionRequest = {
180
+ model: config.model,
181
+ messages: messages.map((m) => {
182
+ const msg: Record<string, unknown> = {
183
+ role: m.role,
184
+ content: m.content,
185
+ };
186
+ if (m.tool_calls) {
187
+ msg.tool_calls = m.tool_calls.map((tc) => ({
188
+ id: tc.id,
189
+ type: 'function',
190
+ function: {
191
+ name: tc.name,
192
+ arguments: JSON.stringify(tc.arguments),
193
+ },
194
+ }));
195
+ }
196
+ if (m.tool_call_id) {
197
+ msg.tool_call_id = m.tool_call_id;
198
+ }
199
+ if (m.name) {
200
+ msg.name = m.name;
201
+ }
202
+ return msg;
203
+ }),
204
+ temperature: config.temperature,
205
+ max_tokens: config.maxTokens,
206
+ stream,
207
+ };
208
+
209
+ if (tools && tools.length > 0) {
210
+ body.tools = tools.map((tool) => ({
211
+ type: 'function',
212
+ function: {
213
+ name: tool.name,
214
+ description: tool.description,
215
+ parameters: tool.parameters,
216
+ },
217
+ }));
218
+ body.tool_choice = 'auto';
219
+ }
220
+
221
+ return JSON.stringify(body);
222
+ }
223
+
224
+ private async makeRequest(config: DeepSeekConfig, body: string): Promise<Response> {
225
+ const url = `${config.baseUrl}/v1/chat/completions`;
226
+
227
+ logger.debug('DeepSeek API request', { url, model: config.model });
228
+
229
+ const response = await fetch(url, {
230
+ method: 'POST',
231
+ headers: {
232
+ 'Content-Type': 'application/json',
233
+ 'Authorization': `Bearer ${config.apiKey}`,
234
+ 'Accept': 'application/json',
235
+ },
236
+ body,
237
+ });
238
+
239
+ if (!response.ok) {
240
+ const errorBody = await response.text().catch(() => '');
241
+ logger.error('DeepSeek API error', {
242
+ status: response.status,
243
+ statusText: response.statusText,
244
+ body: errorBody.slice(0, 500),
245
+ });
246
+ throw new Error(`API request failed: ${response.status} ${response.statusText} - ${errorBody.slice(0, 200)}`);
247
+ }
248
+
249
+ return response;
250
+ }
251
+
252
+ private shouldRetry(error: Error, attempt: number): boolean {
253
+ const message = error.message.toLowerCase();
254
+
255
+ if (message.includes('429') || message.includes('rate limit')) {
256
+ return true;
257
+ }
258
+ if (message.includes('5') && (message.includes('500') || message.includes('502') || message.includes('503') || message.includes('504'))) {
259
+ return true;
260
+ }
261
+ if (message.includes('timeout') || message.includes('abort')) {
262
+ return attempt < 2;
263
+ }
264
+ if (message.includes('econnreset') || message.includes('econnrefused')) {
265
+ return true;
266
+ }
267
+
268
+ return false;
269
+ }
270
+
271
+ private parseArguments(argsStr: string): Record<string, unknown> {
272
+ try {
273
+ return JSON.parse(argsStr) as Record<string, unknown>;
274
+ } catch {
275
+ return {};
276
+ }
277
+ }
278
+
279
+ private mergeConfig(overrides?: Partial<DeepSeekConfig>): DeepSeekConfig {
280
+ if (!overrides) {
281
+ return this.config;
282
+ }
283
+ return {
284
+ ...this.config,
285
+ ...overrides,
286
+ think: {
287
+ ...this.config.think,
288
+ ...(overrides.think ?? {}),
289
+ },
290
+ };
291
+ }
292
+ }