codeep 1.2.11 → 1.2.13

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 (51) hide show
  1. package/bin/codeep.js +13 -1
  2. package/dist/config/providers.d.ts +6 -0
  3. package/dist/config/providers.js +41 -2
  4. package/dist/config/providers.test.js +31 -2
  5. package/dist/utils/agent.js +2 -2
  6. package/dist/utils/tools.d.ts +64 -4
  7. package/dist/utils/tools.js +209 -4
  8. package/dist/utils/tools.test.js +12 -7
  9. package/package.json +2 -1
  10. package/dist/app.d.ts +0 -2
  11. package/dist/app.js +0 -1501
  12. package/dist/components/AgentActions.d.ts +0 -18
  13. package/dist/components/AgentActions.js +0 -122
  14. package/dist/components/AgentProgress.d.ts +0 -59
  15. package/dist/components/AgentProgress.js +0 -368
  16. package/dist/components/Export.d.ts +0 -8
  17. package/dist/components/Export.js +0 -27
  18. package/dist/components/Help.d.ts +0 -6
  19. package/dist/components/Help.js +0 -7
  20. package/dist/components/Input.d.ts +0 -9
  21. package/dist/components/Input.js +0 -334
  22. package/dist/components/Loading.d.ts +0 -17
  23. package/dist/components/Loading.js +0 -52
  24. package/dist/components/Login.d.ts +0 -7
  25. package/dist/components/Login.js +0 -77
  26. package/dist/components/Logo.d.ts +0 -8
  27. package/dist/components/Logo.js +0 -89
  28. package/dist/components/LogoutPicker.d.ts +0 -8
  29. package/dist/components/LogoutPicker.js +0 -61
  30. package/dist/components/Message.d.ts +0 -10
  31. package/dist/components/Message.js +0 -242
  32. package/dist/components/MessageList.d.ts +0 -10
  33. package/dist/components/MessageList.js +0 -42
  34. package/dist/components/ProjectPermission.d.ts +0 -7
  35. package/dist/components/ProjectPermission.js +0 -65
  36. package/dist/components/Search.d.ts +0 -10
  37. package/dist/components/Search.js +0 -30
  38. package/dist/components/SessionPicker.d.ts +0 -9
  39. package/dist/components/SessionPicker.js +0 -88
  40. package/dist/components/Sessions.d.ts +0 -12
  41. package/dist/components/Sessions.js +0 -119
  42. package/dist/components/Settings.d.ts +0 -9
  43. package/dist/components/Settings.js +0 -198
  44. package/dist/components/Spinner.d.ts +0 -34
  45. package/dist/components/Spinner.js +0 -38
  46. package/dist/components/Status.d.ts +0 -2
  47. package/dist/components/Status.js +0 -13
  48. package/dist/components/StreamingMessage.d.ts +0 -14
  49. package/dist/components/StreamingMessage.js +0 -19
  50. package/dist/index.d.ts +0 -2
  51. package/dist/index.js +0 -42
package/bin/codeep.js CHANGED
@@ -1,2 +1,14 @@
1
1
  #!/usr/bin/env node
2
- import '../dist/renderer/main.js';
2
+ import { join, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { spawn } from 'child_process';
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const main = join(__dirname, '..', 'dist', 'renderer', 'main.js');
8
+
9
+ const child = spawn(process.execPath, [main, ...process.argv.slice(2)], {
10
+ stdio: 'inherit',
11
+ env: process.env,
12
+ });
13
+
14
+ child.on('exit', (code) => process.exit(code ?? 0));
@@ -25,6 +25,11 @@ export interface ProviderConfig {
25
25
  defaultProtocol: 'openai' | 'anthropic';
26
26
  envKey?: string;
27
27
  subscribeUrl?: string;
28
+ mcpEndpoints?: {
29
+ webSearch?: string;
30
+ webReader?: string;
31
+ zread?: string;
32
+ };
28
33
  }
29
34
  export declare const PROVIDERS: Record<string, ProviderConfig>;
30
35
  export type ProviderId = keyof typeof PROVIDERS;
@@ -42,4 +47,5 @@ export declare function getProviderModels(providerId: string): {
42
47
  }[];
43
48
  export declare function getProviderBaseUrl(providerId: string, protocol: 'openai' | 'anthropic'): string | null;
44
49
  export declare function getProviderAuthHeader(providerId: string, protocol: 'openai' | 'anthropic'): 'Bearer' | 'x-api-key';
50
+ export declare function getProviderMcpEndpoints(providerId: string): ProviderConfig['mcpEndpoints'] | null;
45
51
  export declare function supportsNativeTools(providerId: string, protocol: 'openai' | 'anthropic'): boolean;
@@ -26,6 +26,41 @@ export const PROVIDERS = {
26
26
  defaultProtocol: 'openai',
27
27
  envKey: 'ZAI_API_KEY',
28
28
  subscribeUrl: 'https://z.ai/subscribe?ic=NXYNXZOV14',
29
+ mcpEndpoints: {
30
+ webSearch: 'https://api.z.ai/api/mcp/web_search_prime/mcp',
31
+ webReader: 'https://api.z.ai/api/mcp/web_reader/mcp',
32
+ zread: 'https://api.z.ai/api/mcp/zread/mcp',
33
+ },
34
+ },
35
+ 'z.ai-cn': {
36
+ name: 'Z.AI China (ZhipuAI)',
37
+ description: 'GLM Coding Plan (China)',
38
+ protocols: {
39
+ openai: {
40
+ baseUrl: 'https://open.bigmodel.cn/api/coding/paas/v4',
41
+ authHeader: 'Bearer',
42
+ supportsNativeTools: true,
43
+ },
44
+ anthropic: {
45
+ baseUrl: 'https://open.bigmodel.cn/api/anthropic',
46
+ authHeader: 'x-api-key',
47
+ supportsNativeTools: true,
48
+ },
49
+ },
50
+ models: [
51
+ { id: 'glm-5', name: 'GLM-5', description: 'Most capable GLM model (Pro/Max subscription)' },
52
+ { id: 'glm-4.7', name: 'GLM-4.7', description: 'Latest GLM model' },
53
+ { id: 'glm-4.7-flash', name: 'GLM-4.7 Flash', description: 'Faster, lighter version' },
54
+ ],
55
+ defaultModel: 'glm-4.7',
56
+ defaultProtocol: 'openai',
57
+ envKey: 'ZAI_CN_API_KEY',
58
+ subscribeUrl: 'https://open.bigmodel.cn/glm-coding',
59
+ mcpEndpoints: {
60
+ webSearch: 'https://open.bigmodel.cn/api/mcp/web_search_prime/mcp',
61
+ webReader: 'https://open.bigmodel.cn/api/mcp/web_reader/mcp',
62
+ zread: 'https://open.bigmodel.cn/api/mcp/zread/mcp',
63
+ },
29
64
  },
30
65
  'minimax': {
31
66
  name: 'MiniMax',
@@ -43,9 +78,9 @@ export const PROVIDERS = {
43
78
  },
44
79
  },
45
80
  models: [
46
- { id: 'MiniMax-M2.1', name: 'MiniMax M2.1', description: 'Latest MiniMax coding model' },
81
+ { id: 'MiniMax-M2.5', name: 'MiniMax M2.5', description: 'Latest MiniMax coding model' },
47
82
  ],
48
- defaultModel: 'MiniMax-M2.1',
83
+ defaultModel: 'MiniMax-M2.5',
49
84
  defaultProtocol: 'anthropic',
50
85
  envKey: 'MINIMAX_API_KEY',
51
86
  subscribeUrl: 'https://platform.minimax.io/subscribe/coding-plan?code=2lWvoWUhrp&source=link',
@@ -116,6 +151,10 @@ export function getProviderAuthHeader(providerId, protocol) {
116
151
  return 'Bearer';
117
152
  return provider.protocols[protocol]?.authHeader || 'Bearer';
118
153
  }
154
+ export function getProviderMcpEndpoints(providerId) {
155
+ const provider = PROVIDERS[providerId];
156
+ return provider?.mcpEndpoints || null;
157
+ }
119
158
  export function supportsNativeTools(providerId, protocol) {
120
159
  const provider = PROVIDERS[providerId];
121
160
  if (!provider)
@@ -1,11 +1,15 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { PROVIDERS, getProvider, getProviderList, getProviderModels, getProviderBaseUrl, getProviderAuthHeader, } from './providers.js';
2
+ import { PROVIDERS, getProvider, getProviderList, getProviderModels, getProviderBaseUrl, getProviderAuthHeader, getProviderMcpEndpoints, } from './providers.js';
3
3
  describe('providers', () => {
4
4
  describe('PROVIDERS constant', () => {
5
5
  it('should have z.ai provider', () => {
6
6
  expect(PROVIDERS['z.ai']).toBeDefined();
7
7
  expect(PROVIDERS['z.ai'].name).toBe('Z.AI (ZhipuAI)');
8
8
  });
9
+ it('should have z.ai-cn provider', () => {
10
+ expect(PROVIDERS['z.ai-cn']).toBeDefined();
11
+ expect(PROVIDERS['z.ai-cn'].name).toBe('Z.AI China (ZhipuAI)');
12
+ });
9
13
  it('should have minimax provider', () => {
10
14
  expect(PROVIDERS['minimax']).toBeDefined();
11
15
  expect(PROVIDERS['minimax'].name).toBe('MiniMax');
@@ -68,10 +72,11 @@ describe('providers', () => {
68
72
  expect(item.description).toBeDefined();
69
73
  }
70
74
  });
71
- it('should include z.ai and minimax', () => {
75
+ it('should include z.ai, z.ai-cn, and minimax', () => {
72
76
  const list = getProviderList();
73
77
  const ids = list.map(p => p.id);
74
78
  expect(ids).toContain('z.ai');
79
+ expect(ids).toContain('z.ai-cn');
75
80
  expect(ids).toContain('minimax');
76
81
  });
77
82
  });
@@ -125,8 +130,32 @@ describe('providers', () => {
125
130
  it('should have env key for z.ai', () => {
126
131
  expect(PROVIDERS['z.ai'].envKey).toBe('ZAI_API_KEY');
127
132
  });
133
+ it('should have env key for z.ai-cn', () => {
134
+ expect(PROVIDERS['z.ai-cn'].envKey).toBe('ZAI_CN_API_KEY');
135
+ });
128
136
  it('should have env key for minimax', () => {
129
137
  expect(PROVIDERS['minimax'].envKey).toBe('MINIMAX_API_KEY');
130
138
  });
131
139
  });
140
+ describe('MCP endpoints', () => {
141
+ it('should have MCP endpoints for z.ai', () => {
142
+ const endpoints = getProviderMcpEndpoints('z.ai');
143
+ expect(endpoints).not.toBeNull();
144
+ expect(endpoints.webSearch).toContain('api.z.ai');
145
+ expect(endpoints.webReader).toContain('api.z.ai');
146
+ expect(endpoints.zread).toContain('api.z.ai');
147
+ });
148
+ it('should have MCP endpoints for z.ai-cn', () => {
149
+ const endpoints = getProviderMcpEndpoints('z.ai-cn');
150
+ expect(endpoints).not.toBeNull();
151
+ expect(endpoints.webSearch).toContain('open.bigmodel.cn');
152
+ expect(endpoints.webReader).toContain('open.bigmodel.cn');
153
+ expect(endpoints.zread).toContain('open.bigmodel.cn');
154
+ });
155
+ it('should return null for providers without MCP endpoints', () => {
156
+ expect(getProviderMcpEndpoints('minimax')).toBeNull();
157
+ expect(getProviderMcpEndpoints('deepseek')).toBeNull();
158
+ expect(getProviderMcpEndpoints('nonexistent')).toBeNull();
159
+ });
160
+ });
132
161
  });
@@ -919,7 +919,7 @@ export async function runAgent(prompt, projectContext, options = {}) {
919
919
  }
920
920
  else {
921
921
  // Actually execute the tool
922
- toolResult = executeTool(toolCall, projectContext.root || process.cwd());
922
+ toolResult = await executeTool(toolCall, projectContext.root || process.cwd());
923
923
  }
924
924
  opts.onToolResult?.(toolResult, toolCall);
925
925
  // Log action
@@ -1010,7 +1010,7 @@ export async function runAgent(prompt, projectContext, options = {}) {
1010
1010
  const fixResults = [];
1011
1011
  for (const toolCall of fixToolCalls) {
1012
1012
  opts.onToolCall?.(toolCall);
1013
- const toolResult = executeTool(toolCall, projectContext.root || process.cwd());
1013
+ const toolResult = await executeTool(toolCall, projectContext.root || process.cwd());
1014
1014
  opts.onToolResult?.(toolResult, toolCall);
1015
1015
  const actionLog = createActionLog(toolCall, toolResult);
1016
1016
  actions.push(actionLog);
@@ -199,10 +199,70 @@ export declare const AGENT_TOOLS: {
199
199
  };
200
200
  };
201
201
  };
202
+ web_search: {
203
+ name: string;
204
+ description: string;
205
+ parameters: {
206
+ query: {
207
+ type: string;
208
+ description: string;
209
+ required: boolean;
210
+ };
211
+ domain_filter: {
212
+ type: string;
213
+ description: string;
214
+ required: boolean;
215
+ };
216
+ recency: {
217
+ type: string;
218
+ description: string;
219
+ required: boolean;
220
+ };
221
+ };
222
+ };
223
+ web_read: {
224
+ name: string;
225
+ description: string;
226
+ parameters: {
227
+ url: {
228
+ type: string;
229
+ description: string;
230
+ required: boolean;
231
+ };
232
+ format: {
233
+ type: string;
234
+ description: string;
235
+ required: boolean;
236
+ };
237
+ };
238
+ };
239
+ github_read: {
240
+ name: string;
241
+ description: string;
242
+ parameters: {
243
+ repo: {
244
+ type: string;
245
+ description: string;
246
+ required: boolean;
247
+ };
248
+ action: {
249
+ type: string;
250
+ description: string;
251
+ required: boolean;
252
+ };
253
+ query: {
254
+ type: string;
255
+ description: string;
256
+ required: boolean;
257
+ };
258
+ path: {
259
+ type: string;
260
+ description: string;
261
+ required: boolean;
262
+ };
263
+ };
264
+ };
202
265
  };
203
- /**
204
- * Format tool definitions for system prompt (text-based fallback)
205
- */
206
266
  export declare function formatToolDefinitions(): string;
207
267
  /**
208
268
  * Get tools in OpenAI Function Calling format
@@ -230,7 +290,7 @@ export declare function parseToolCalls(response: string): ToolCall[];
230
290
  /**
231
291
  * Execute a tool call
232
292
  */
233
- export declare function executeTool(toolCall: ToolCall, projectRoot: string): ToolResult;
293
+ export declare function executeTool(toolCall: ToolCall, projectRoot: string): Promise<ToolResult>;
234
294
  /**
235
295
  * Create action log from tool result
236
296
  */
@@ -12,6 +12,84 @@ import { join, dirname, relative, resolve, isAbsolute } from 'path';
12
12
  import { executeCommand } from './shell.js';
13
13
  import { recordWrite, recordEdit, recordDelete, recordMkdir, recordCommand } from './history.js';
14
14
  import { loadIgnoreRules, isIgnored } from './gitignore.js';
15
+ import { config, getApiKey } from '../config/index.js';
16
+ import { getProviderMcpEndpoints } from '../config/providers.js';
17
+ // Z.AI MCP tool names (available when user has any Z.AI API key)
18
+ const ZAI_MCP_TOOLS = ['web_search', 'web_read', 'github_read'];
19
+ // Z.AI provider IDs that have MCP endpoints
20
+ const ZAI_PROVIDER_IDS = ['z.ai', 'z.ai-cn'];
21
+ /**
22
+ * Find a Z.AI provider that has an API key configured.
23
+ * Returns the provider ID and API key, or null if none found.
24
+ * Prefers the active provider if it's Z.AI, otherwise checks all Z.AI providers.
25
+ */
26
+ function getZaiMcpConfig() {
27
+ // First check if active provider is Z.AI
28
+ const activeProvider = config.get('provider');
29
+ if (ZAI_PROVIDER_IDS.includes(activeProvider)) {
30
+ const key = getApiKey(activeProvider);
31
+ const endpoints = getProviderMcpEndpoints(activeProvider);
32
+ if (key && endpoints?.webSearch && endpoints?.webReader && endpoints?.zread) {
33
+ return { providerId: activeProvider, apiKey: key, endpoints: endpoints };
34
+ }
35
+ }
36
+ // Otherwise check all Z.AI providers for a configured key
37
+ for (const pid of ZAI_PROVIDER_IDS) {
38
+ const key = getApiKey(pid);
39
+ const endpoints = getProviderMcpEndpoints(pid);
40
+ if (key && endpoints?.webSearch && endpoints?.webReader && endpoints?.zread) {
41
+ return { providerId: pid, apiKey: key, endpoints: endpoints };
42
+ }
43
+ }
44
+ return null;
45
+ }
46
+ /**
47
+ * Check if Z.AI MCP tools are available (user has any Z.AI API key)
48
+ */
49
+ function hasZaiMcpAccess() {
50
+ return getZaiMcpConfig() !== null;
51
+ }
52
+ /**
53
+ * Call a Z.AI MCP endpoint via JSON-RPC 2.0
54
+ */
55
+ async function callZaiMcp(endpoint, toolName, args, apiKey) {
56
+ const controller = new AbortController();
57
+ const timeout = setTimeout(() => controller.abort(), 60000);
58
+ try {
59
+ const response = await fetch(endpoint, {
60
+ method: 'POST',
61
+ headers: {
62
+ 'Content-Type': 'application/json',
63
+ 'Accept': 'application/json',
64
+ 'Authorization': `Bearer ${apiKey}`,
65
+ },
66
+ body: JSON.stringify({
67
+ jsonrpc: '2.0',
68
+ id: Date.now().toString(),
69
+ method: 'tools/call',
70
+ params: { name: toolName, arguments: args },
71
+ }),
72
+ signal: controller.signal,
73
+ });
74
+ if (!response.ok) {
75
+ const errorText = await response.text().catch(() => '');
76
+ throw new Error(`MCP error ${response.status}: ${errorText || response.statusText}`);
77
+ }
78
+ const data = await response.json();
79
+ if (data.error) {
80
+ throw new Error(data.error.message || JSON.stringify(data.error));
81
+ }
82
+ // MCP returns result.content as array of {type, text} blocks
83
+ const content = data.result?.content;
84
+ if (Array.isArray(content)) {
85
+ return content.map((c) => c.text || '').join('\n');
86
+ }
87
+ return typeof data.result === 'string' ? data.result : JSON.stringify(data.result);
88
+ }
89
+ finally {
90
+ clearTimeout(timeout);
91
+ }
92
+ }
15
93
  // Tool definitions for system prompt
16
94
  export const AGENT_TOOLS = {
17
95
  read_file: {
@@ -91,13 +169,51 @@ export const AGENT_TOOLS = {
91
169
  url: { type: 'string', description: 'The URL to fetch content from', required: true },
92
170
  },
93
171
  },
172
+ web_search: {
173
+ name: 'web_search',
174
+ description: 'Search the web for real-time information. Returns titles, URLs, and summaries. Requires a Z.AI API key.',
175
+ parameters: {
176
+ query: { type: 'string', description: 'Search query', required: true },
177
+ domain_filter: { type: 'string', description: 'Limit results to specific domain (e.g. github.com)', required: false },
178
+ recency: { type: 'string', description: 'Time filter: oneDay, oneWeek, oneMonth, oneYear, noLimit', required: false },
179
+ },
180
+ },
181
+ web_read: {
182
+ name: 'web_read',
183
+ description: 'Fetch and parse a web page into clean readable text/markdown. Better than fetch_url for documentation and articles. Requires a Z.AI API key.',
184
+ parameters: {
185
+ url: { type: 'string', description: 'The URL to read', required: true },
186
+ format: { type: 'string', description: 'Output format: markdown or text (default: markdown)', required: false },
187
+ },
188
+ },
189
+ github_read: {
190
+ name: 'github_read',
191
+ description: 'Search documentation/code or read files from a public GitHub repository. Requires a Z.AI API key.',
192
+ parameters: {
193
+ repo: { type: 'string', description: 'GitHub repository in owner/repo format (e.g. facebook/react)', required: true },
194
+ action: { type: 'string', description: 'Action: search, tree, or read_file', required: true },
195
+ query: { type: 'string', description: 'Search query (for action=search)', required: false },
196
+ path: { type: 'string', description: 'File path (for action=read_file) or directory path (for action=tree)', required: false },
197
+ },
198
+ },
94
199
  };
95
200
  /**
96
201
  * Format tool definitions for system prompt (text-based fallback)
97
202
  */
203
+ /**
204
+ * Get filtered tool entries (excludes Z.AI-only tools when not using Z.AI provider)
205
+ */
206
+ function getFilteredToolEntries() {
207
+ const hasMcp = hasZaiMcpAccess();
208
+ return Object.entries(AGENT_TOOLS).filter(([name]) => {
209
+ if (ZAI_MCP_TOOLS.includes(name))
210
+ return hasMcp;
211
+ return true;
212
+ });
213
+ }
98
214
  export function formatToolDefinitions() {
99
215
  const lines = [];
100
- for (const [name, tool] of Object.entries(AGENT_TOOLS)) {
216
+ for (const [name, tool] of getFilteredToolEntries()) {
101
217
  lines.push(`### ${name}`);
102
218
  lines.push(tool.description);
103
219
  lines.push('Parameters:');
@@ -113,7 +229,7 @@ export function formatToolDefinitions() {
113
229
  * Get tools in OpenAI Function Calling format
114
230
  */
115
231
  export function getOpenAITools() {
116
- return Object.entries(AGENT_TOOLS).map(([name, tool]) => {
232
+ return getFilteredToolEntries().map(([name, tool]) => {
117
233
  const properties = {};
118
234
  const required = [];
119
235
  for (const [param, info] of Object.entries(tool.parameters)) {
@@ -153,7 +269,7 @@ export function getOpenAITools() {
153
269
  * Get tools in Anthropic Tool Use format
154
270
  */
155
271
  export function getAnthropicTools() {
156
- return Object.entries(AGENT_TOOLS).map(([name, tool]) => {
272
+ return getFilteredToolEntries().map(([name, tool]) => {
157
273
  const properties = {};
158
274
  const required = [];
159
275
  for (const [param, info] of Object.entries(tool.parameters)) {
@@ -609,7 +725,7 @@ function validatePath(path, projectRoot) {
609
725
  /**
610
726
  * Execute a tool call
611
727
  */
612
- export function executeTool(toolCall, projectRoot) {
728
+ export async function executeTool(toolCall, projectRoot) {
613
729
  // Normalize tool name to handle case variations (WRITE_FILE -> write_file)
614
730
  const tool = normalizeToolName(toolCall.tool);
615
731
  const parameters = toolCall.parameters;
@@ -899,6 +1015,90 @@ export function executeTool(toolCall, projectRoot) {
899
1015
  return { success: false, output: '', error: result.stderr || 'Failed to fetch URL', tool, parameters };
900
1016
  }
901
1017
  }
1018
+ // === Z.AI MCP Tools ===
1019
+ case 'web_search': {
1020
+ const mcpConfig = getZaiMcpConfig();
1021
+ if (!mcpConfig) {
1022
+ return { success: false, output: '', error: 'web_search requires a Z.AI API key. Configure one via /provider z.ai', tool, parameters };
1023
+ }
1024
+ const query = parameters.query;
1025
+ if (!query) {
1026
+ return { success: false, output: '', error: 'Missing required parameter: query', tool, parameters };
1027
+ }
1028
+ const args = { search_query: query };
1029
+ if (parameters.domain_filter)
1030
+ args.search_domain_filter = parameters.domain_filter;
1031
+ if (parameters.recency)
1032
+ args.search_recency_filter = parameters.recency;
1033
+ const result = await callZaiMcp(mcpConfig.endpoints.webSearch, 'webSearchPrime', args, mcpConfig.apiKey);
1034
+ const output = result.length > 15000 ? result.substring(0, 15000) + '\n\n... (truncated)' : result;
1035
+ return { success: true, output, tool, parameters };
1036
+ }
1037
+ case 'web_read': {
1038
+ const mcpConfig = getZaiMcpConfig();
1039
+ if (!mcpConfig) {
1040
+ return { success: false, output: '', error: 'web_read requires a Z.AI API key. Configure one via /provider z.ai', tool, parameters };
1041
+ }
1042
+ const url = parameters.url;
1043
+ if (!url) {
1044
+ return { success: false, output: '', error: 'Missing required parameter: url', tool, parameters };
1045
+ }
1046
+ try {
1047
+ new URL(url);
1048
+ }
1049
+ catch {
1050
+ return { success: false, output: '', error: 'Invalid URL format', tool, parameters };
1051
+ }
1052
+ const args = { url };
1053
+ if (parameters.format)
1054
+ args.return_format = parameters.format;
1055
+ const result = await callZaiMcp(mcpConfig.endpoints.webReader, 'webReader', args, mcpConfig.apiKey);
1056
+ const output = result.length > 15000 ? result.substring(0, 15000) + '\n\n... (truncated)' : result;
1057
+ return { success: true, output, tool, parameters };
1058
+ }
1059
+ case 'github_read': {
1060
+ const mcpConfig = getZaiMcpConfig();
1061
+ if (!mcpConfig) {
1062
+ return { success: false, output: '', error: 'github_read requires a Z.AI API key. Configure one via /provider z.ai', tool, parameters };
1063
+ }
1064
+ const repo = parameters.repo;
1065
+ const action = parameters.action;
1066
+ if (!repo) {
1067
+ return { success: false, output: '', error: 'Missing required parameter: repo', tool, parameters };
1068
+ }
1069
+ if (!repo.includes('/')) {
1070
+ return { success: false, output: '', error: 'Invalid repo format. Use owner/repo (e.g. facebook/react)', tool, parameters };
1071
+ }
1072
+ if (!action || !['search', 'tree', 'read_file'].includes(action)) {
1073
+ return { success: false, output: '', error: 'Invalid action. Must be: search, tree, or read_file', tool, parameters };
1074
+ }
1075
+ let mcpToolName;
1076
+ const args = { repo_name: repo };
1077
+ if (action === 'search') {
1078
+ mcpToolName = 'search_doc';
1079
+ const query = parameters.query;
1080
+ if (!query) {
1081
+ return { success: false, output: '', error: 'Missing required parameter: query (for action=search)', tool, parameters };
1082
+ }
1083
+ args.query = query;
1084
+ }
1085
+ else if (action === 'tree') {
1086
+ mcpToolName = 'get_repo_structure';
1087
+ if (parameters.path)
1088
+ args.dir_path = parameters.path;
1089
+ }
1090
+ else {
1091
+ mcpToolName = 'read_file';
1092
+ const filePath = parameters.path;
1093
+ if (!filePath) {
1094
+ return { success: false, output: '', error: 'Missing required parameter: path (for action=read_file)', tool, parameters };
1095
+ }
1096
+ args.file_path = filePath;
1097
+ }
1098
+ const result = await callZaiMcp(mcpConfig.endpoints.zread, mcpToolName, args, mcpConfig.apiKey);
1099
+ const output = result.length > 15000 ? result.substring(0, 15000) + '\n\n... (truncated)' : result;
1100
+ return { success: true, output, tool, parameters };
1101
+ }
902
1102
  default:
903
1103
  return { success: false, output: '', error: `Unknown tool: ${tool}`, tool, parameters };
904
1104
  }
@@ -1014,11 +1214,16 @@ export function createActionLog(toolCall, result) {
1014
1214
  create_directory: 'mkdir',
1015
1215
  find_files: 'search',
1016
1216
  fetch_url: 'fetch',
1217
+ web_search: 'fetch',
1218
+ web_read: 'fetch',
1219
+ github_read: 'fetch',
1017
1220
  };
1018
1221
  const target = toolCall.parameters.path ||
1019
1222
  toolCall.parameters.command ||
1020
1223
  toolCall.parameters.pattern ||
1021
1224
  toolCall.parameters.url ||
1225
+ toolCall.parameters.query ||
1226
+ toolCall.parameters.repo ||
1022
1227
  'unknown';
1023
1228
  // For write/edit actions, include FULL content in details for live code view
1024
1229
  let details;
@@ -2,11 +2,15 @@ import { describe, it, expect } from 'vitest';
2
2
  import { getOpenAITools, getAnthropicTools, parseToolCalls, parseOpenAIToolCalls, parseAnthropicToolCalls, createActionLog, AGENT_TOOLS, } from './tools.js';
3
3
  // ─── CONSTANTS ───────────────────────────────────────────────────────────────
4
4
  const ALL_TOOL_NAMES = Object.keys(AGENT_TOOLS);
5
+ // MCP tools are filtered out when no Z.AI API key is configured (e.g. in tests)
6
+ const ZAI_MCP_TOOLS = ['web_search', 'web_read', 'github_read'];
7
+ const CORE_TOOL_NAMES = ALL_TOOL_NAMES.filter(n => !ZAI_MCP_TOOLS.includes(n));
5
8
  // ─── getOpenAITools ──────────────────────────────────────────────────────────
6
9
  describe('getOpenAITools', () => {
7
10
  it('should return one entry per AGENT_TOOLS definition', () => {
8
11
  const tools = getOpenAITools();
9
- expect(tools).toHaveLength(ALL_TOOL_NAMES.length);
12
+ // MCP tools are excluded when no Z.AI API key is configured
13
+ expect(tools).toHaveLength(CORE_TOOL_NAMES.length);
10
14
  });
11
15
  it('should wrap every tool in the OpenAI function-calling envelope', () => {
12
16
  const tools = getOpenAITools();
@@ -20,10 +24,10 @@ describe('getOpenAITools', () => {
20
24
  expect(Array.isArray(tool.function.parameters.required)).toBe(true);
21
25
  }
22
26
  });
23
- it('should include all tool names from AGENT_TOOLS', () => {
27
+ it('should include all core tool names from AGENT_TOOLS', () => {
24
28
  const tools = getOpenAITools();
25
29
  const names = tools.map(t => t.function.name);
26
- for (const name of ALL_TOOL_NAMES) {
30
+ for (const name of CORE_TOOL_NAMES) {
27
31
  expect(names).toContain(name);
28
32
  }
29
33
  });
@@ -71,7 +75,8 @@ describe('getOpenAITools', () => {
71
75
  describe('getAnthropicTools', () => {
72
76
  it('should return one entry per AGENT_TOOLS definition', () => {
73
77
  const tools = getAnthropicTools();
74
- expect(tools).toHaveLength(ALL_TOOL_NAMES.length);
78
+ // MCP tools are excluded when no Z.AI API key is configured
79
+ expect(tools).toHaveLength(CORE_TOOL_NAMES.length);
75
80
  });
76
81
  it('should use Anthropic tool-use shape (name, description, input_schema)', () => {
77
82
  const tools = getAnthropicTools();
@@ -84,17 +89,17 @@ describe('getAnthropicTools', () => {
84
89
  expect(Array.isArray(tool.input_schema.required)).toBe(true);
85
90
  }
86
91
  });
87
- it('should include all tool names from AGENT_TOOLS', () => {
92
+ it('should include all core tool names from AGENT_TOOLS', () => {
88
93
  const tools = getAnthropicTools();
89
94
  const names = tools.map(t => t.name);
90
- for (const name of ALL_TOOL_NAMES) {
95
+ for (const name of CORE_TOOL_NAMES) {
91
96
  expect(names).toContain(name);
92
97
  }
93
98
  });
94
99
  it('should have same required params as OpenAI format', () => {
95
100
  const openai = getOpenAITools();
96
101
  const anthropic = getAnthropicTools();
97
- for (const name of ALL_TOOL_NAMES) {
102
+ for (const name of CORE_TOOL_NAMES) {
98
103
  const oTool = openai.find(t => t.function.name === name);
99
104
  const aTool = anthropic.find(t => t.name === name);
100
105
  expect(aTool.input_schema.required).toEqual(oTool.function.parameters.required);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeep",
3
- "version": "1.2.11",
3
+ "version": "1.2.13",
4
4
  "description": "AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -9,6 +9,7 @@
9
9
  },
10
10
  "scripts": {
11
11
  "dev": "node --import tsx src/renderer/main.ts",
12
+ "prepack": "tsc; node scripts/fix-imports.js",
12
13
  "build": "tsc && node scripts/fix-imports.js",
13
14
  "start": "node dist/renderer/main.js",
14
15
  "demo:renderer": "node --import tsx src/renderer/demo.ts",
package/dist/app.d.ts DELETED
@@ -1,2 +0,0 @@
1
- import React from 'react';
2
- export declare const App: React.FC;