codeep 1.0.19 → 1.0.21

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.
@@ -28,7 +28,7 @@ const SETTINGS = [
28
28
  value: () => config.get('apiTimeout'),
29
29
  type: 'number',
30
30
  min: 5000,
31
- max: 120000,
31
+ max: 300000,
32
32
  step: 5000,
33
33
  },
34
34
  {
@@ -69,6 +69,15 @@ const SETTINGS = [
69
69
  { value: 'manual', label: 'Manual' },
70
70
  ],
71
71
  },
72
+ {
73
+ key: 'agentApiTimeout',
74
+ label: 'Agent API Timeout (ms)',
75
+ value: () => config.get('agentApiTimeout'),
76
+ type: 'number',
77
+ min: 30000,
78
+ max: 300000,
79
+ step: 10000,
80
+ },
72
81
  ];
73
82
  export const Settings = ({ onClose, notify }) => {
74
83
  const [selected, setSelected] = useState(0);
@@ -37,6 +37,7 @@ interface ConfigSchema {
37
37
  agentMaxFixAttempts: number;
38
38
  agentMaxIterations: number;
39
39
  agentMaxDuration: number;
40
+ agentApiTimeout: number;
40
41
  projectPermissions: ProjectPermission[];
41
42
  providerApiKeys: ProviderApiKey[];
42
43
  }
@@ -68,6 +68,7 @@ export const config = new Conf({
68
68
  agentMaxFixAttempts: 3,
69
69
  agentMaxIterations: 100,
70
70
  agentMaxDuration: 20, // minutes
71
+ agentApiTimeout: 90000, // 90 seconds base timeout for agent (dynamically adjusted)
71
72
  protocol: 'openai',
72
73
  plan: 'lite',
73
74
  language: 'en',
@@ -75,7 +76,7 @@ export const config = new Conf({
75
76
  currentSessionId: '',
76
77
  temperature: 0.7,
77
78
  maxTokens: 4096,
78
- apiTimeout: 30000,
79
+ apiTimeout: 60000,
79
80
  rateLimitApi: 30, // 30 requests per minute
80
81
  rateLimitCommands: 100, // 100 commands per minute
81
82
  projectPermissions: [],
@@ -1,6 +1,61 @@
1
1
  /**
2
2
  * Agent loop - autonomous task execution
3
3
  */
4
+ /**
5
+ * Custom error class for timeout - allows distinguishing from user abort
6
+ */
7
+ class TimeoutError extends Error {
8
+ constructor(message = 'Request timed out') {
9
+ super(message);
10
+ this.name = 'TimeoutError';
11
+ }
12
+ }
13
+ /**
14
+ * Calculate dynamic timeout based on task complexity
15
+ * Complex tasks (creating pages, multiple files) need more time
16
+ */
17
+ function calculateDynamicTimeout(prompt, iteration, baseTimeout) {
18
+ const promptLower = prompt.toLowerCase();
19
+ // Keywords that indicate complex tasks requiring more time
20
+ const complexKeywords = [
21
+ 'create', 'build', 'implement', 'generate', 'make', 'develop', 'setup',
22
+ 'website', 'app', 'application', 'page', 'component', 'feature',
23
+ 'kreiraj', 'napravi', 'izgradi', 'generiraj', 'razvij', 'stranica', 'aplikacija'
24
+ ];
25
+ // Keywords indicating very large tasks
26
+ const veryComplexKeywords = [
27
+ 'full', 'complete', 'entire', 'whole', 'multiple', 'all',
28
+ 'cijeli', 'kompletni', 'sve', 'višestruki'
29
+ ];
30
+ let multiplier = 1.0;
31
+ // Check for complex keywords
32
+ const hasComplexKeyword = complexKeywords.some(kw => promptLower.includes(kw));
33
+ if (hasComplexKeyword) {
34
+ multiplier = 2.0; // Double timeout for complex tasks
35
+ }
36
+ // Check for very complex keywords
37
+ const hasVeryComplexKeyword = veryComplexKeywords.some(kw => promptLower.includes(kw));
38
+ if (hasVeryComplexKeyword) {
39
+ multiplier = 3.0; // Triple timeout for very complex tasks
40
+ }
41
+ // Long prompts usually mean complex tasks
42
+ if (prompt.length > 200) {
43
+ multiplier = Math.max(multiplier, 2.0);
44
+ }
45
+ if (prompt.length > 500) {
46
+ multiplier = Math.max(multiplier, 3.0);
47
+ }
48
+ // Later iterations might need more time (context is larger)
49
+ if (iteration > 5) {
50
+ multiplier *= 1.2;
51
+ }
52
+ if (iteration > 10) {
53
+ multiplier *= 1.3;
54
+ }
55
+ // Minimum 60 seconds, maximum 5 minutes for a single API call
56
+ const calculatedTimeout = baseTimeout * multiplier;
57
+ return Math.min(Math.max(calculatedTimeout, 60000), 300000);
58
+ }
4
59
  import { parseToolCalls, executeTool, createActionLog, formatToolDefinitions, getOpenAITools, getAnthropicTools, parseOpenAIToolCalls, parseAnthropicToolCalls } from './tools.js';
5
60
  import { config, getApiKey } from '../config/index.js';
6
61
  import { getProviderBaseUrl, getProviderAuthHeader, supportsNativeTools } from '../config/providers.js';
@@ -123,7 +178,7 @@ You have FULL access. Execute tasks autonomously.`;
123
178
  /**
124
179
  * Make a chat API call for agent mode with native tool support
125
180
  */
126
- async function agentChat(messages, systemPrompt, onChunk, abortSignal) {
181
+ async function agentChat(messages, systemPrompt, onChunk, abortSignal, dynamicTimeout) {
127
182
  const protocol = config.get('protocol');
128
183
  const model = config.get('model');
129
184
  const apiKey = getApiKey();
@@ -139,9 +194,17 @@ async function agentChat(messages, systemPrompt, onChunk, abortSignal) {
139
194
  return await agentChatFallback(messages, systemPrompt, onChunk, abortSignal);
140
195
  }
141
196
  const controller = new AbortController();
142
- const timeout = setTimeout(() => controller.abort(), config.get('apiTimeout'));
197
+ const timeoutMs = dynamicTimeout || config.get('apiTimeout');
198
+ let isTimeout = false;
199
+ const timeout = setTimeout(() => {
200
+ isTimeout = true;
201
+ controller.abort();
202
+ }, timeoutMs);
143
203
  if (abortSignal) {
144
- abortSignal.addEventListener('abort', () => controller.abort());
204
+ abortSignal.addEventListener('abort', () => {
205
+ isTimeout = false; // User abort, not timeout
206
+ controller.abort();
207
+ });
145
208
  }
146
209
  const headers = {
147
210
  'Content-Type': 'application/json',
@@ -224,6 +287,14 @@ async function agentChat(messages, systemPrompt, onChunk, abortSignal) {
224
287
  }
225
288
  catch (error) {
226
289
  const err = error;
290
+ // Check if this was a timeout vs user abort
291
+ if (err.name === 'AbortError') {
292
+ if (isTimeout) {
293
+ throw new TimeoutError(`API request timed out after ${timeoutMs}ms`);
294
+ }
295
+ // User abort - rethrow as-is
296
+ throw error;
297
+ }
227
298
  // If native tools failed, try fallback
228
299
  if (err.message.includes('tools') || err.message.includes('function')) {
229
300
  return await agentChatFallback(messages, systemPrompt, onChunk, abortSignal);
@@ -237,7 +308,7 @@ async function agentChat(messages, systemPrompt, onChunk, abortSignal) {
237
308
  /**
238
309
  * Fallback chat without native tools (text-based parsing)
239
310
  */
240
- async function agentChatFallback(messages, systemPrompt, onChunk, abortSignal) {
311
+ async function agentChatFallback(messages, systemPrompt, onChunk, abortSignal, dynamicTimeout) {
241
312
  const protocol = config.get('protocol');
242
313
  const model = config.get('model');
243
314
  const apiKey = getApiKey();
@@ -248,9 +319,17 @@ async function agentChatFallback(messages, systemPrompt, onChunk, abortSignal) {
248
319
  throw new Error(`Provider ${providerId} does not support ${protocol} protocol`);
249
320
  }
250
321
  const controller = new AbortController();
251
- const timeout = setTimeout(() => controller.abort(), config.get('apiTimeout'));
322
+ const timeoutMs = dynamicTimeout || config.get('apiTimeout');
323
+ let isTimeout = false;
324
+ const timeout = setTimeout(() => {
325
+ isTimeout = true;
326
+ controller.abort();
327
+ }, timeoutMs);
252
328
  if (abortSignal) {
253
- abortSignal.addEventListener('abort', () => controller.abort());
329
+ abortSignal.addEventListener('abort', () => {
330
+ isTimeout = false; // User abort, not timeout
331
+ controller.abort();
332
+ });
254
333
  }
255
334
  const headers = {
256
335
  'Content-Type': 'application/json',
@@ -325,6 +404,18 @@ async function agentChatFallback(messages, systemPrompt, onChunk, abortSignal) {
325
404
  const toolCalls = parseToolCalls(content);
326
405
  return { content, toolCalls, usedNativeTools: false };
327
406
  }
407
+ catch (error) {
408
+ const err = error;
409
+ // Check if this was a timeout vs user abort
410
+ if (err.name === 'AbortError') {
411
+ if (isTimeout) {
412
+ throw new TimeoutError(`API request timed out after ${timeoutMs}ms`);
413
+ }
414
+ // User abort - rethrow as-is
415
+ throw error;
416
+ }
417
+ throw error;
418
+ }
328
419
  finally {
329
420
  clearTimeout(timeout);
330
421
  }
@@ -441,6 +532,9 @@ export async function runAgent(prompt, projectContext, options = {}) {
441
532
  let iteration = 0;
442
533
  let finalResponse = '';
443
534
  let result;
535
+ let consecutiveTimeouts = 0;
536
+ const maxTimeoutRetries = 3;
537
+ const baseTimeout = config.get('agentApiTimeout');
444
538
  try {
445
539
  while (iteration < opts.maxIterations) {
446
540
  // Check timeout
@@ -469,24 +563,69 @@ export async function runAgent(prompt, projectContext, options = {}) {
469
563
  iteration++;
470
564
  console.error(`[DEBUG] Starting iteration ${iteration}/${opts.maxIterations}, actions: ${actions.length}`);
471
565
  opts.onIteration?.(iteration, `Iteration ${iteration}/${opts.maxIterations}`);
472
- // Get AI response
566
+ // Calculate dynamic timeout based on task complexity
567
+ const dynamicTimeout = calculateDynamicTimeout(prompt, iteration, baseTimeout);
568
+ console.error(`[DEBUG] Using dynamic timeout: ${dynamicTimeout}ms (base: ${baseTimeout}ms, iteration: ${iteration})`);
569
+ // Get AI response with retry logic for timeouts
473
570
  let chatResponse;
474
- try {
475
- chatResponse = await agentChat(messages, systemPrompt, opts.onThinking, opts.abortSignal);
476
- }
477
- catch (error) {
478
- const err = error;
479
- if (err.name === 'AbortError') {
480
- result = {
481
- success: false,
482
- iterations: iteration,
483
- actions,
484
- finalResponse: 'Agent was stopped',
485
- aborted: true,
486
- };
487
- return result;
571
+ let retryCount = 0;
572
+ while (true) {
573
+ try {
574
+ chatResponse = await agentChat(messages, systemPrompt, opts.onThinking, opts.abortSignal, dynamicTimeout * (1 + retryCount * 0.5) // Increase timeout on retry
575
+ );
576
+ consecutiveTimeouts = 0; // Reset on success
577
+ break;
578
+ }
579
+ catch (error) {
580
+ const err = error;
581
+ // Handle user abort (not timeout)
582
+ if (err.name === 'AbortError') {
583
+ result = {
584
+ success: false,
585
+ iterations: iteration,
586
+ actions,
587
+ finalResponse: 'Agent was stopped by user',
588
+ aborted: true,
589
+ };
590
+ return result;
591
+ }
592
+ // Handle timeout with retry
593
+ if (err.name === 'TimeoutError') {
594
+ retryCount++;
595
+ consecutiveTimeouts++;
596
+ console.error(`[DEBUG] Timeout occurred (retry ${retryCount}/${maxTimeoutRetries}, consecutive: ${consecutiveTimeouts})`);
597
+ opts.onIteration?.(iteration, `API timeout, retrying (${retryCount}/${maxTimeoutRetries})...`);
598
+ if (retryCount >= maxTimeoutRetries) {
599
+ // Too many retries for this iteration
600
+ if (consecutiveTimeouts >= maxTimeoutRetries * 2) {
601
+ // Too many consecutive timeouts overall, give up
602
+ result = {
603
+ success: false,
604
+ iterations: iteration,
605
+ actions,
606
+ finalResponse: 'Agent stopped due to repeated API timeouts',
607
+ error: `API timed out ${consecutiveTimeouts} times consecutively. The task may be too complex or the API is overloaded.`,
608
+ };
609
+ return result;
610
+ }
611
+ // Skip this iteration and try next
612
+ console.error(`[DEBUG] Max retries reached, skipping to next iteration`);
613
+ messages.push({
614
+ role: 'user',
615
+ content: 'The previous request timed out. Please continue with the task, using simpler responses if needed.'
616
+ });
617
+ break;
618
+ }
619
+ // Wait before retry (exponential backoff)
620
+ await new Promise(resolve => setTimeout(resolve, 1000 * retryCount));
621
+ continue;
622
+ }
623
+ throw error;
488
624
  }
489
- throw error;
625
+ }
626
+ // If we broke out due to max retries without a response, continue to next iteration
627
+ if (!chatResponse) {
628
+ continue;
490
629
  }
491
630
  let { content, toolCalls, usedNativeTools } = chatResponse;
492
631
  // If native tools were used but no tool calls returned, try parsing text-based tool calls
@@ -508,12 +647,15 @@ export async function runAgent(prompt, projectContext, options = {}) {
508
647
  const taskKeywords = ['create', 'build', 'make', 'implement', 'add', 'generate', 'write', 'setup', 'develop', 'kreiraj', 'napravi', 'dodaj', 'website', 'app', 'feature'];
509
648
  const promptLowerCase = prompt.toLowerCase();
510
649
  const isCreationTask = taskKeywords.some(kw => promptLowerCase.includes(kw));
511
- const hasCreatedFiles = actions.some(a => a.type === 'write' || a.type === 'mkdir');
650
+ // Only count actual FILE writes, not directories
651
+ const hasCreatedFiles = actions.some(a => a.type === 'write');
512
652
  const writeFileCount = actions.filter(a => a.type === 'write').length;
653
+ const hasMkdir = actions.some(a => a.type === 'mkdir');
513
654
  const isVeryEarlyIteration = iteration <= 5; // Extended to 5 iterations
514
- console.error(`[DEBUG] Task check: isCreationTask=${isCreationTask}, hasCreatedFiles=${hasCreatedFiles}, writeFileCount=${writeFileCount}, iteration=${iteration}`);
515
- // STRICT RULE: If it's a creation task, agent MUST create files
516
- // Don't accept early stopping even if it has "done some actions"
655
+ console.error(`[DEBUG] Task check: isCreationTask=${isCreationTask}, hasCreatedFiles=${hasCreatedFiles}, writeFileCount=${writeFileCount}, hasMkdir=${hasMkdir}, iteration=${iteration}`);
656
+ console.error(`[DEBUG] Actions breakdown:`, actions.map(a => `${a.type}:${a.target}`).join(', '));
657
+ // STRICT RULE: If it's a creation task, agent MUST create FILES (not just directories)
658
+ // Creating a directory is not enough - we need actual file content
517
659
  if (isCreationTask && !hasCreatedFiles && iteration <= 10) {
518
660
  console.error(`[DEBUG] BLOCKING early stop - creation task detected but NO files created yet (iteration ${iteration}, ${actions.length} actions)`);
519
661
  // Force agent to create files
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeep",
3
- "version": "1.0.19",
3
+ "version": "1.0.21",
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",