codeep 1.0.20 → 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.
- package/dist/components/Settings.js +10 -1
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.js +2 -1
- package/dist/utils/agent.js +161 -22
- package/package.json +1 -1
|
@@ -28,7 +28,7 @@ const SETTINGS = [
|
|
|
28
28
|
value: () => config.get('apiTimeout'),
|
|
29
29
|
type: 'number',
|
|
30
30
|
min: 5000,
|
|
31
|
-
max:
|
|
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);
|
package/dist/config/index.d.ts
CHANGED
package/dist/config/index.js
CHANGED
|
@@ -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:
|
|
79
|
+
apiTimeout: 60000,
|
|
79
80
|
rateLimitApi: 30, // 30 requests per minute
|
|
80
81
|
rateLimitCommands: 100, // 100 commands per minute
|
|
81
82
|
projectPermissions: [],
|
package/dist/utils/agent.js
CHANGED
|
@@ -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
|
|
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', () =>
|
|
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
|
|
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', () =>
|
|
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
|
-
//
|
|
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
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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
|
-
|
|
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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeep",
|
|
3
|
-
"version": "1.0.
|
|
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",
|