codeep 1.2.17 → 1.2.19

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 (64) hide show
  1. package/README.md +20 -7
  2. package/dist/api/index.d.ts +7 -0
  3. package/dist/api/index.js +21 -17
  4. package/dist/config/providers.d.ts +6 -0
  5. package/dist/config/providers.js +11 -0
  6. package/dist/renderer/App.d.ts +1 -5
  7. package/dist/renderer/App.js +106 -486
  8. package/dist/renderer/agentExecution.d.ts +36 -0
  9. package/dist/renderer/agentExecution.js +394 -0
  10. package/dist/renderer/commands.d.ts +16 -0
  11. package/dist/renderer/commands.js +838 -0
  12. package/dist/renderer/handlers.d.ts +87 -0
  13. package/dist/renderer/handlers.js +260 -0
  14. package/dist/renderer/highlight.d.ts +18 -0
  15. package/dist/renderer/highlight.js +130 -0
  16. package/dist/renderer/main.d.ts +4 -2
  17. package/dist/renderer/main.js +103 -1550
  18. package/dist/utils/agent.d.ts +5 -15
  19. package/dist/utils/agent.js +9 -693
  20. package/dist/utils/agentChat.d.ts +46 -0
  21. package/dist/utils/agentChat.js +343 -0
  22. package/dist/utils/agentStream.d.ts +23 -0
  23. package/dist/utils/agentStream.js +216 -0
  24. package/dist/utils/keychain.js +3 -2
  25. package/dist/utils/learning.js +9 -3
  26. package/dist/utils/mcpIntegration.d.ts +61 -0
  27. package/dist/utils/mcpIntegration.js +154 -0
  28. package/dist/utils/project.js +8 -3
  29. package/dist/utils/skills.js +21 -11
  30. package/dist/utils/smartContext.d.ts +4 -0
  31. package/dist/utils/smartContext.js +51 -14
  32. package/dist/utils/toolExecution.d.ts +27 -0
  33. package/dist/utils/toolExecution.js +525 -0
  34. package/dist/utils/toolParsing.d.ts +18 -0
  35. package/dist/utils/toolParsing.js +302 -0
  36. package/dist/utils/tools.d.ts +11 -24
  37. package/dist/utils/tools.js +22 -1187
  38. package/package.json +3 -1
  39. package/dist/config/config.test.d.ts +0 -1
  40. package/dist/config/config.test.js +0 -157
  41. package/dist/config/providers.test.d.ts +0 -1
  42. package/dist/config/providers.test.js +0 -187
  43. package/dist/hooks/index.d.ts +0 -4
  44. package/dist/hooks/index.js +0 -4
  45. package/dist/hooks/useAgent.d.ts +0 -29
  46. package/dist/hooks/useAgent.js +0 -148
  47. package/dist/utils/agent.test.d.ts +0 -1
  48. package/dist/utils/agent.test.js +0 -315
  49. package/dist/utils/git.test.d.ts +0 -1
  50. package/dist/utils/git.test.js +0 -193
  51. package/dist/utils/gitignore.test.d.ts +0 -1
  52. package/dist/utils/gitignore.test.js +0 -167
  53. package/dist/utils/project.test.d.ts +0 -1
  54. package/dist/utils/project.test.js +0 -212
  55. package/dist/utils/ratelimit.test.d.ts +0 -1
  56. package/dist/utils/ratelimit.test.js +0 -131
  57. package/dist/utils/retry.test.d.ts +0 -1
  58. package/dist/utils/retry.test.js +0 -163
  59. package/dist/utils/smartContext.test.d.ts +0 -1
  60. package/dist/utils/smartContext.test.js +0 -382
  61. package/dist/utils/tools.test.d.ts +0 -1
  62. package/dist/utils/tools.test.js +0 -681
  63. package/dist/utils/validation.test.d.ts +0 -1
  64. package/dist/utils/validation.test.js +0 -164
@@ -0,0 +1,61 @@
1
+ /**
2
+ * MCP (Model Context Protocol) integration for Z.AI and MiniMax.
3
+ *
4
+ * Provides config discovery and API call helpers used by toolExecution.ts.
5
+ */
6
+ export interface McpContentItem {
7
+ text?: string;
8
+ }
9
+ export interface MinimaxApiResponse {
10
+ content?: McpContentItem[];
11
+ }
12
+ export interface ZaiMcpResponse {
13
+ error?: {
14
+ message?: string;
15
+ };
16
+ result?: {
17
+ content?: McpContentItem[];
18
+ } | string;
19
+ }
20
+ export declare const ZAI_MCP_TOOLS: string[];
21
+ export declare const ZAI_PROVIDER_IDS: string[];
22
+ export declare const MINIMAX_MCP_TOOLS: string[];
23
+ export declare const MINIMAX_PROVIDER_IDS: string[];
24
+ /**
25
+ * Find a Z.AI provider that has an API key configured.
26
+ * Returns the provider ID and API key, or null if none found.
27
+ * Prefers the active provider if it's Z.AI, otherwise checks all Z.AI providers.
28
+ */
29
+ export declare function getZaiMcpConfig(): {
30
+ providerId: string;
31
+ apiKey: string;
32
+ endpoints: {
33
+ webSearch: string;
34
+ webReader: string;
35
+ zread: string;
36
+ };
37
+ } | null;
38
+ /**
39
+ * Check if Z.AI MCP tools are available (user has any Z.AI API key)
40
+ */
41
+ export declare function hasZaiMcpAccess(): boolean;
42
+ /**
43
+ * Find a MiniMax provider that has an API key configured.
44
+ * Returns the base host URL and API key, or null if none found.
45
+ */
46
+ export declare function getMinimaxMcpConfig(): {
47
+ host: string;
48
+ apiKey: string;
49
+ } | null;
50
+ /**
51
+ * Check if MiniMax MCP tools are available
52
+ */
53
+ export declare function hasMinimaxMcpAccess(): boolean;
54
+ /**
55
+ * Call a MiniMax MCP REST API endpoint
56
+ */
57
+ export declare function callMinimaxApi(host: string, path: string, body: Record<string, unknown>, apiKey: string): Promise<string>;
58
+ /**
59
+ * Call a Z.AI MCP endpoint via JSON-RPC 2.0
60
+ */
61
+ export declare function callZaiMcp(endpoint: string, toolName: string, args: Record<string, unknown>, apiKey: string): Promise<string>;
@@ -0,0 +1,154 @@
1
+ /**
2
+ * MCP (Model Context Protocol) integration for Z.AI and MiniMax.
3
+ *
4
+ * Provides config discovery and API call helpers used by toolExecution.ts.
5
+ */
6
+ import { config, getApiKey } from '../config/index.js';
7
+ import { getProviderMcpEndpoints, PROVIDERS } from '../config/providers.js';
8
+ // Z.AI MCP tool names (available when user has any Z.AI API key)
9
+ export const ZAI_MCP_TOOLS = ['web_search', 'web_read', 'github_read'];
10
+ // Z.AI provider IDs that have MCP endpoints
11
+ export const ZAI_PROVIDER_IDS = ['z.ai', 'z.ai-cn'];
12
+ // MiniMax MCP tool names (available when user has any MiniMax API key)
13
+ export const MINIMAX_MCP_TOOLS = ['minimax_web_search', 'minimax_understand_image'];
14
+ // MiniMax provider IDs
15
+ export const MINIMAX_PROVIDER_IDS = ['minimax', 'minimax-cn'];
16
+ /**
17
+ * Find a Z.AI provider that has an API key configured.
18
+ * Returns the provider ID and API key, or null if none found.
19
+ * Prefers the active provider if it's Z.AI, otherwise checks all Z.AI providers.
20
+ */
21
+ export function getZaiMcpConfig() {
22
+ // First check if active provider is Z.AI
23
+ const activeProvider = config.get('provider');
24
+ if (ZAI_PROVIDER_IDS.includes(activeProvider)) {
25
+ const key = getApiKey(activeProvider);
26
+ const endpoints = getProviderMcpEndpoints(activeProvider);
27
+ if (key && endpoints?.webSearch && endpoints?.webReader && endpoints?.zread) {
28
+ return { providerId: activeProvider, apiKey: key, endpoints: endpoints };
29
+ }
30
+ }
31
+ // Otherwise check all Z.AI providers for a configured key
32
+ for (const pid of ZAI_PROVIDER_IDS) {
33
+ const key = getApiKey(pid);
34
+ const endpoints = getProviderMcpEndpoints(pid);
35
+ if (key && endpoints?.webSearch && endpoints?.webReader && endpoints?.zread) {
36
+ return { providerId: pid, apiKey: key, endpoints: endpoints };
37
+ }
38
+ }
39
+ return null;
40
+ }
41
+ /**
42
+ * Check if Z.AI MCP tools are available (user has any Z.AI API key)
43
+ */
44
+ export function hasZaiMcpAccess() {
45
+ return getZaiMcpConfig() !== null;
46
+ }
47
+ /**
48
+ * Find a MiniMax provider that has an API key configured.
49
+ * Returns the base host URL and API key, or null if none found.
50
+ */
51
+ export function getMinimaxMcpConfig() {
52
+ // First check if active provider is MiniMax
53
+ const activeProvider = config.get('provider');
54
+ if (MINIMAX_PROVIDER_IDS.includes(activeProvider)) {
55
+ const key = getApiKey(activeProvider);
56
+ if (key) {
57
+ const provider = PROVIDERS[activeProvider];
58
+ const baseUrl = provider?.protocols?.openai?.baseUrl;
59
+ if (baseUrl) {
60
+ const host = baseUrl.replace(/\/v1\/?$/, '');
61
+ return { host, apiKey: key };
62
+ }
63
+ }
64
+ }
65
+ // Otherwise check all MiniMax providers
66
+ for (const pid of MINIMAX_PROVIDER_IDS) {
67
+ const key = getApiKey(pid);
68
+ if (key) {
69
+ const provider = PROVIDERS[pid];
70
+ const baseUrl = provider?.protocols?.openai?.baseUrl;
71
+ if (baseUrl) {
72
+ const host = baseUrl.replace(/\/v1\/?$/, '');
73
+ return { host, apiKey: key };
74
+ }
75
+ }
76
+ }
77
+ return null;
78
+ }
79
+ /**
80
+ * Check if MiniMax MCP tools are available
81
+ */
82
+ export function hasMinimaxMcpAccess() {
83
+ return getMinimaxMcpConfig() !== null;
84
+ }
85
+ /**
86
+ * Call a MiniMax MCP REST API endpoint
87
+ */
88
+ export async function callMinimaxApi(host, path, body, apiKey) {
89
+ const controller = new AbortController();
90
+ const timeout = setTimeout(() => controller.abort(), 60000);
91
+ try {
92
+ const response = await fetch(`${host}${path}`, {
93
+ method: 'POST',
94
+ headers: {
95
+ 'Content-Type': 'application/json',
96
+ 'Authorization': `Bearer ${apiKey}`,
97
+ },
98
+ body: JSON.stringify(body),
99
+ signal: controller.signal,
100
+ });
101
+ if (!response.ok) {
102
+ const errorText = await response.text().catch(() => '');
103
+ throw new Error(`MiniMax API error ${response.status}: ${errorText || response.statusText}`);
104
+ }
105
+ const data = await response.json();
106
+ if (data.content && Array.isArray(data.content)) {
107
+ return data.content.map((c) => c.text || '').join('\n');
108
+ }
109
+ return JSON.stringify(data);
110
+ }
111
+ finally {
112
+ clearTimeout(timeout);
113
+ }
114
+ }
115
+ /**
116
+ * Call a Z.AI MCP endpoint via JSON-RPC 2.0
117
+ */
118
+ export async function callZaiMcp(endpoint, toolName, args, apiKey) {
119
+ const controller = new AbortController();
120
+ const timeout = setTimeout(() => controller.abort(), 60000);
121
+ try {
122
+ const response = await fetch(endpoint, {
123
+ method: 'POST',
124
+ headers: {
125
+ 'Content-Type': 'application/json',
126
+ 'Accept': 'application/json',
127
+ 'Authorization': `Bearer ${apiKey}`,
128
+ },
129
+ body: JSON.stringify({
130
+ jsonrpc: '2.0',
131
+ id: Date.now().toString(),
132
+ method: 'tools/call',
133
+ params: { name: toolName, arguments: args },
134
+ }),
135
+ signal: controller.signal,
136
+ });
137
+ if (!response.ok) {
138
+ const errorText = await response.text().catch(() => '');
139
+ throw new Error(`MCP error ${response.status}: ${errorText || response.statusText}`);
140
+ }
141
+ const data = await response.json();
142
+ if (data.error) {
143
+ throw new Error(data.error.message || JSON.stringify(data.error));
144
+ }
145
+ const result = data.result;
146
+ if (result && typeof result === 'object' && Array.isArray(result.content)) {
147
+ return result.content.map((c) => c.text || '').join('\n');
148
+ }
149
+ return typeof result === 'string' ? result : JSON.stringify(result);
150
+ }
151
+ finally {
152
+ clearTimeout(timeout);
153
+ }
154
+ }
@@ -57,9 +57,14 @@ export function isProjectDirectory(dir = process.cwd()) {
57
57
  */
58
58
  export function getProjectType(dir = process.cwd()) {
59
59
  if (existsSync(join(dir, 'package.json'))) {
60
- const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'));
61
- if (pkg.dependencies?.typescript || pkg.devDependencies?.typescript || existsSync(join(dir, 'tsconfig.json'))) {
62
- return 'TypeScript/Node.js';
60
+ try {
61
+ const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'));
62
+ if (pkg.dependencies?.typescript || pkg.devDependencies?.typescript || existsSync(join(dir, 'tsconfig.json'))) {
63
+ return 'TypeScript/Node.js';
64
+ }
65
+ }
66
+ catch {
67
+ // Corrupt package.json — fall through to return JavaScript/Node.js
63
68
  }
64
69
  return 'JavaScript/Node.js';
65
70
  }
@@ -671,9 +671,6 @@ export function parseSkillChain(input) {
671
671
  */
672
672
  export function parseSkillArgs(args, skill) {
673
673
  const result = {};
674
- if (!args.trim()) {
675
- return result;
676
- }
677
674
  // Pattern to match key=value or key="value with spaces" or just "value"
678
675
  const keyValuePattern = /(\w+)=(?:"([^"]+)"|'([^']+)'|(\S+))/g;
679
676
  const quotedPattern = /^["'](.+)["']$/;
@@ -710,20 +707,33 @@ export function parseSkillArgs(args, skill) {
710
707
  }
711
708
  /**
712
709
  * Sanitize text for safe use inside shell commands.
713
- * Strips markdown formatting and escapes double quotes.
710
+ * Strips markdown formatting and removes shell metacharacters to prevent
711
+ * command injection via $(), backtick subshells, semicolons, pipes, etc.
714
712
  */
715
713
  function sanitizeForShell(text) {
716
- return text
714
+ const firstLine = text
717
715
  // Strip markdown code blocks
718
716
  .replace(/```[\s\S]*?```/g, '')
719
- // Strip inline backticks
720
- .replace(/`([^`]*)`/g, '$1')
717
+ // Strip inline backtick code spans (remove content too, not just markers)
718
+ .replace(/`[^`]*`/g, '')
721
719
  // Strip bold/italic markers
722
720
  .replace(/\*{1,3}([^*]+)\*{1,3}/g, '$1')
723
- // Take only the first non-empty line (commit messages should be one line)
724
- .split('\n').map(l => l.trim()).filter(Boolean)[0] || text.trim()
725
- // Escape double quotes for shell safety
726
- .replace(/"/g, '\\"');
721
+ // Take only the first non-empty line
722
+ .split('\n').map(l => l.trim()).filter(Boolean)[0] || text.trim();
723
+ return firstLine
724
+ // Remove $(...) subshell expansion
725
+ .replace(/\$\([^)]*\)/g, '')
726
+ // Remove ${...} variable/subshell expansion
727
+ .replace(/\$\{[^}]*\}/g, '')
728
+ // Remove bare $var variable references
729
+ .replace(/\$\w+/g, '')
730
+ // Remove command chaining operators
731
+ .replace(/[;|]/g, '')
732
+ // Remove newlines and null bytes
733
+ .replace(/[\n\r\0]/g, ' ')
734
+ // Escape double quotes for safe embedding in "..." shell strings
735
+ .replace(/"/g, '\\"')
736
+ .trim();
727
737
  }
728
738
  /**
729
739
  * Interpolate parameters into skill step content
@@ -15,6 +15,10 @@ export interface SmartContextResult {
15
15
  totalSize: number;
16
16
  truncated: boolean;
17
17
  }
18
+ /**
19
+ * Clear the import resolution cache (useful between invocations in tests).
20
+ */
21
+ export declare function clearImportResolutionCache(): void;
18
22
  /**
19
23
  * Gather smart context for a target file or task
20
24
  */
@@ -4,6 +4,7 @@
4
4
  import { existsSync, readFileSync, statSync } from 'fs';
5
5
  import { join, dirname, basename, extname, relative } from 'path';
6
6
  import { loadIgnoreRules, isIgnored } from './gitignore.js';
7
+ import { logger } from './logger.js';
7
8
  // Max context size (characters)
8
9
  const MAX_CONTEXT_SIZE = 50000;
9
10
  const MAX_FILES = 15;
@@ -82,24 +83,44 @@ function extractImports(content, ext) {
82
83
  }
83
84
  return imports;
84
85
  }
86
+ // Cache for resolveImportPath — avoids redundant disk lookups across import graphs
87
+ const importResolutionCache = new Map();
85
88
  /**
86
- * Resolve import path to actual file path
89
+ * Resolve import path to actual file path.
90
+ * Results are cached by (fromFile, importPath) to avoid O(n²) disk I/O.
87
91
  */
88
92
  function resolveImportPath(importPath, fromFile, projectRoot) {
93
+ const cacheKey = `${fromFile}|${importPath}`;
94
+ if (importResolutionCache.has(cacheKey)) {
95
+ return importResolutionCache.get(cacheKey);
96
+ }
89
97
  const fromDir = dirname(fromFile);
90
98
  const ext = extname(fromFile);
99
+ let result;
91
100
  // Skip external packages
92
101
  if (!importPath.startsWith('.') && !importPath.startsWith('/')) {
93
102
  // Could be a local alias like @/components
94
103
  if (importPath.startsWith('@/') || importPath.startsWith('~/')) {
95
104
  const aliasPath = importPath.replace(/^[@~]\//, 'src/');
96
- return resolveWithExtensions(join(projectRoot, aliasPath), ext);
105
+ result = resolveWithExtensions(join(projectRoot, aliasPath), ext);
106
+ }
107
+ else {
108
+ result = null;
97
109
  }
98
- return null;
99
110
  }
100
- // Resolve relative path
101
- const resolved = join(fromDir, importPath);
102
- return resolveWithExtensions(resolved, ext);
111
+ else {
112
+ // Resolve relative path
113
+ const resolved = join(fromDir, importPath);
114
+ result = resolveWithExtensions(resolved, ext);
115
+ }
116
+ importResolutionCache.set(cacheKey, result);
117
+ return result;
118
+ }
119
+ /**
120
+ * Clear the import resolution cache (useful between invocations in tests).
121
+ */
122
+ export function clearImportResolutionCache() {
123
+ importResolutionCache.clear();
103
124
  }
104
125
  /**
105
126
  * Try to resolve path with various extensions
@@ -167,7 +188,9 @@ function findRelatedByNaming(filePath, projectRoot) {
167
188
  size: stat.size,
168
189
  });
169
190
  }
170
- catch { }
191
+ catch (err) {
192
+ logger.debug('smartContext: statSync failed for main file', { path: basePath, err: String(err) });
193
+ }
171
194
  }
172
195
  }
173
196
  }
@@ -185,7 +208,9 @@ function findRelatedByNaming(filePath, projectRoot) {
185
208
  size: stat.size,
186
209
  });
187
210
  }
188
- catch { }
211
+ catch (err) {
212
+ logger.debug('smartContext: statSync failed for related file', { path: relatedPath, err: String(err) });
213
+ }
189
214
  }
190
215
  }
191
216
  return related;
@@ -217,7 +242,9 @@ function findTypeDefinitions(projectRoot, imports) {
217
242
  size: stat.size,
218
243
  });
219
244
  }
220
- catch { }
245
+ catch (err) {
246
+ logger.debug('smartContext: statSync failed for type definition', { path: fullPath, err: String(err) });
247
+ }
221
248
  }
222
249
  }
223
250
  return related;
@@ -250,7 +277,9 @@ function findConfigFiles(projectRoot) {
250
277
  size: stat.size,
251
278
  });
252
279
  }
253
- catch { }
280
+ catch (err) {
281
+ logger.debug('smartContext: statSync failed for config file', { path: fullPath, err: String(err) });
282
+ }
254
283
  }
255
284
  }
256
285
  return related;
@@ -294,7 +323,9 @@ export function gatherSmartContext(targetFile, projectContext, taskDescription)
294
323
  size: impStat.size,
295
324
  });
296
325
  }
297
- catch { }
326
+ catch (err) {
327
+ logger.debug('smartContext: statSync failed for import', { path: resolved, err: String(err) });
328
+ }
298
329
  }
299
330
  }
300
331
  // Find related by naming
@@ -305,7 +336,9 @@ export function gatherSmartContext(targetFile, projectContext, taskDescription)
305
336
  }
306
337
  }
307
338
  }
308
- catch { }
339
+ catch (err) {
340
+ logger.debug('smartContext: failed to process target file', { path: targetPath, err: String(err) });
341
+ }
309
342
  }
310
343
  }
311
344
  // Find type definitions
@@ -360,7 +393,9 @@ export function gatherSmartContext(targetFile, projectContext, taskDescription)
360
393
  truncated = true;
361
394
  }
362
395
  }
363
- catch { }
396
+ catch (err) {
397
+ logger.debug('smartContext: readFileSync failed for context file', { path: file.path, err: String(err) });
398
+ }
364
399
  }
365
400
  else if (file.content) {
366
401
  totalSize += file.content.length;
@@ -400,7 +435,9 @@ function extractMentionedFiles(task, projectRoot) {
400
435
  });
401
436
  }
402
437
  }
403
- catch { }
438
+ catch (err) {
439
+ logger.debug('smartContext: statSync failed for mentioned file', { path: fullPath, err: String(err) });
440
+ }
404
441
  }
405
442
  }
406
443
  return related;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Tool execution - runs agent tool calls against the filesystem and shell.
3
+ *
4
+ * validatePath() ensures all file operations stay within the project root.
5
+ * executeTool() dispatches to individual tool handlers.
6
+ * listDirectory() and htmlToText() are private helpers.
7
+ * createActionLog() converts a ToolCall+ToolResult into a history ActionLog.
8
+ */
9
+ import { ToolCall, ToolResult, ActionLog } from './tools';
10
+ /**
11
+ * Validate path is within project root.
12
+ * Uses realpathSync to resolve symlinks, preventing symlink traversal attacks
13
+ * where a symlink inside the project could point to files outside it.
14
+ */
15
+ export declare function validatePath(path: string, projectRoot: string): {
16
+ valid: boolean;
17
+ absolutePath: string;
18
+ error?: string;
19
+ };
20
+ /**
21
+ * Execute a tool call and return the result.
22
+ */
23
+ export declare function executeTool(toolCall: ToolCall, projectRoot: string): Promise<ToolResult>;
24
+ /**
25
+ * Create action log from tool result
26
+ */
27
+ export declare function createActionLog(toolCall: ToolCall, result: ToolResult): ActionLog;