codeep 1.0.25 → 1.0.28

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/app.js CHANGED
@@ -291,7 +291,15 @@ export const App = () => {
291
291
  });
292
292
  },
293
293
  onThinking: (text) => {
294
- setAgentThinking(prev => prev + text);
294
+ // Strip <think> and <tool_call> tags from thinking text
295
+ const cleanText = text
296
+ .replace(/<think>[\s\S]*?<\/think>/gi, '')
297
+ .replace(/<tool_call>[\s\S]*?<\/tool_call>/gi, '')
298
+ .replace(/<toolcall>[\s\S]*?<\/toolcall>/gi, '')
299
+ .trim();
300
+ if (cleanText) {
301
+ setAgentThinking(prev => prev + cleanText);
302
+ }
295
303
  },
296
304
  abortSignal: controller.signal,
297
305
  });
@@ -19,7 +19,15 @@ export function getCodeBlock(index) {
19
19
  }
20
20
  export const MessageView = memo(({ role, content }) => {
21
21
  if (role === 'user') {
22
- return (_jsx(Box, { marginY: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: "#f02a30", bold: true, children: '> ' }), _jsx(Text, { children: content })] }) }));
22
+ // For long user messages, truncate display but keep full content for processing
23
+ const maxDisplayLength = 500;
24
+ const isLong = content.length > maxDisplayLength;
25
+ const displayContent = isLong
26
+ ? content.substring(0, maxDisplayLength) + '...'
27
+ : content;
28
+ // Replace multiple newlines with single space for cleaner display
29
+ const cleanContent = displayContent.replace(/\n+/g, ' ').replace(/\s+/g, ' ').trim();
30
+ return (_jsxs(Box, { marginY: 1, flexDirection: "column", children: [_jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: "#f02a30", bold: true, children: '> ' }), _jsx(Text, { children: cleanContent })] }), isLong && (_jsxs(Text, { color: "gray", dimColor: true, children: [" (", content.length, " characters total)"] }))] }));
23
31
  }
24
32
  if (role === 'system') {
25
33
  return (_jsx(Box, { marginY: 1, justifyContent: "center", children: _jsx(Text, { italic: true, children: content }) }));
@@ -68,7 +68,7 @@ export const config = new Conf({
68
68
  agentMaxFixAttempts: 3,
69
69
  agentMaxIterations: 100,
70
70
  agentMaxDuration: 20, // minutes
71
- agentApiTimeout: 120000, // 120 seconds base timeout for agent (dynamically adjusted)
71
+ agentApiTimeout: 180000, // 180 seconds base timeout for agent (dynamically adjusted)
72
72
  protocol: 'openai',
73
73
  plan: 'lite',
74
74
  language: 'en',
@@ -75,6 +75,7 @@ function getAgentSystemPrompt(projectContext) {
75
75
  8. Use execute_command ONLY for: npm, git, composer, pip, cargo (build/package managers)
76
76
  9. When the task is complete, respond with a summary WITHOUT any tool calls
77
77
  10. IMPORTANT: After finishing, your response must NOT include any tool calls - just provide a summary
78
+ 11. IGNORE the .codeep folder - it contains internal configuration, do NOT read or modify it
78
79
 
79
80
  ## Self-Verification
80
81
  After you make changes, the system will automatically run build and tests.
@@ -142,6 +143,7 @@ When you need to use a tool, respond with:
142
143
  5. Use execute_command ONLY for: npm, git, composer, pip, cargo (build/package managers)
143
144
  6. Always read files before editing
144
145
  7. When done, respond WITHOUT tool calls
146
+ 8. IGNORE the .codeep folder - it contains internal configuration, do NOT read or modify it
145
147
 
146
148
  ## Project: ${projectContext.name} (${projectContext.type})
147
149
  ${projectContext.structure}
@@ -205,7 +207,7 @@ async function agentChat(messages, systemPrompt, onChunk, abortSignal, dynamicTi
205
207
  tools: getOpenAITools(),
206
208
  tool_choice: 'auto',
207
209
  temperature: config.get('temperature'),
208
- max_tokens: config.get('maxTokens'),
210
+ max_tokens: Math.max(config.get('maxTokens'), 16384), // Ensure enough tokens for large file generation
209
211
  };
210
212
  }
211
213
  else {
@@ -216,7 +218,7 @@ async function agentChat(messages, systemPrompt, onChunk, abortSignal, dynamicTi
216
218
  messages: messages,
217
219
  tools: getAnthropicTools(),
218
220
  temperature: config.get('temperature'),
219
- max_tokens: config.get('maxTokens'),
221
+ max_tokens: Math.max(config.get('maxTokens'), 16384), // Ensure enough tokens for large file generation
220
222
  };
221
223
  }
222
224
  const response = await fetch(endpoint, {
@@ -234,24 +236,15 @@ async function agentChat(messages, systemPrompt, onChunk, abortSignal, dynamicTi
234
236
  throw new Error(`API error: ${response.status} - ${errorText}`);
235
237
  }
236
238
  const data = await response.json();
237
- // Debug: log raw API response
238
- console.error(`[DEBUG] Raw API response:`, JSON.stringify(data, null, 2).substring(0, 1000));
239
239
  if (protocol === 'openai') {
240
240
  const message = data.choices?.[0]?.message;
241
241
  const content = message?.content || '';
242
242
  const rawToolCalls = message?.tool_calls || [];
243
- // Debug: log raw tool calls from API
244
- console.error(`[DEBUG] Raw tool_calls from API:`, JSON.stringify(rawToolCalls, null, 2));
245
243
  const toolCalls = parseOpenAIToolCalls(rawToolCalls);
246
- // Debug: log parsed tool calls
247
- console.error(`[DEBUG] Parsed tool calls:`, JSON.stringify(toolCalls, null, 2));
248
244
  // If no native tool calls, try parsing from content (some models return text-based)
249
245
  if (toolCalls.length === 0 && content) {
250
- console.error(`[DEBUG] No native tool calls, checking content for text-based calls...`);
251
- console.error(`[DEBUG] Content preview:`, content.substring(0, 500));
252
246
  const textToolCalls = parseToolCalls(content);
253
247
  if (textToolCalls.length > 0) {
254
- console.error(`[DEBUG] Found ${textToolCalls.length} text-based tool calls`);
255
248
  return { content, toolCalls: textToolCalls, usedNativeTools: false };
256
249
  }
257
250
  }
@@ -524,6 +517,7 @@ export async function runAgent(prompt, projectContext, options = {}) {
524
517
  let result;
525
518
  let consecutiveTimeouts = 0;
526
519
  const maxTimeoutRetries = 3;
520
+ const maxConsecutiveTimeouts = 9; // Allow more consecutive timeouts before giving up
527
521
  const baseTimeout = config.get('agentApiTimeout');
528
522
  try {
529
523
  while (iteration < opts.maxIterations) {
@@ -540,7 +534,6 @@ export async function runAgent(prompt, projectContext, options = {}) {
540
534
  }
541
535
  // Check abort signal
542
536
  if (opts.abortSignal?.aborted) {
543
- console.error(`[DEBUG] Agent aborted at iteration ${iteration}, signal:`, opts.abortSignal.aborted);
544
537
  result = {
545
538
  success: false,
546
539
  iterations: iteration,
@@ -551,11 +544,9 @@ export async function runAgent(prompt, projectContext, options = {}) {
551
544
  return result;
552
545
  }
553
546
  iteration++;
554
- console.error(`[DEBUG] Starting iteration ${iteration}/${opts.maxIterations}, actions: ${actions.length}`);
555
547
  opts.onIteration?.(iteration, `Iteration ${iteration}/${opts.maxIterations}`);
556
548
  // Calculate dynamic timeout based on task complexity
557
549
  const dynamicTimeout = calculateDynamicTimeout(prompt, iteration, baseTimeout);
558
- console.error(`[DEBUG] Using dynamic timeout: ${dynamicTimeout}ms (base: ${baseTimeout}ms, iteration: ${iteration})`);
559
550
  // Get AI response with retry logic for timeouts
560
551
  let chatResponse;
561
552
  let retryCount = 0;
@@ -563,7 +554,7 @@ export async function runAgent(prompt, projectContext, options = {}) {
563
554
  try {
564
555
  chatResponse = await agentChat(messages, systemPrompt, opts.onThinking, opts.abortSignal, dynamicTimeout * (1 + retryCount * 0.5) // Increase timeout on retry
565
556
  );
566
- consecutiveTimeouts = 0; // Reset on success
557
+ consecutiveTimeouts = 0; // Reset consecutive count on success
567
558
  break;
568
559
  }
569
560
  catch (error) {
@@ -583,23 +574,21 @@ export async function runAgent(prompt, projectContext, options = {}) {
583
574
  if (err.name === 'TimeoutError') {
584
575
  retryCount++;
585
576
  consecutiveTimeouts++;
586
- console.error(`[DEBUG] Timeout occurred (retry ${retryCount}/${maxTimeoutRetries}, consecutive: ${consecutiveTimeouts})`);
587
577
  opts.onIteration?.(iteration, `API timeout, retrying (${retryCount}/${maxTimeoutRetries})...`);
588
578
  if (retryCount >= maxTimeoutRetries) {
589
579
  // Too many retries for this iteration
590
- if (consecutiveTimeouts >= maxTimeoutRetries * 2) {
580
+ if (consecutiveTimeouts >= maxConsecutiveTimeouts) {
591
581
  // Too many consecutive timeouts overall, give up
592
582
  result = {
593
583
  success: false,
594
584
  iterations: iteration,
595
585
  actions,
596
586
  finalResponse: 'Agent stopped due to repeated API timeouts',
597
- error: `API timed out ${consecutiveTimeouts} times consecutively. The task may be too complex or the API is overloaded.`,
587
+ error: `API timed out ${consecutiveTimeouts} times consecutively. Try increasing the timeout in settings or simplifying the task.`,
598
588
  };
599
589
  return result;
600
590
  }
601
591
  // Skip this iteration and try next
602
- console.error(`[DEBUG] Max retries reached, skipping to next iteration`);
603
592
  messages.push({
604
593
  role: 'user',
605
594
  content: 'The previous request timed out. Please continue with the task, using simpler responses if needed.'
@@ -628,7 +617,6 @@ export async function runAgent(prompt, projectContext, options = {}) {
628
617
  }
629
618
  // If no tool calls, check if model wants to continue or is really done
630
619
  if (toolCalls.length === 0) {
631
- console.error(`[DEBUG] No tool calls at iteration ${iteration}`);
632
620
  // Remove <think>...</think> tags from response (some models include thinking)
633
621
  finalResponse = content.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
634
622
  // Check if model indicates it wants to continue (incomplete response)
@@ -643,7 +631,6 @@ export async function runAgent(prompt, projectContext, options = {}) {
643
631
  // by looking for incomplete actions (e.g., write_file without content)
644
632
  const hasIncompleteWork = iteration < 10 && wantsToContinue && finalResponse.length < 500;
645
633
  if (hasIncompleteWork) {
646
- console.error(`[DEBUG] Model wants to continue, prompting for next action`);
647
634
  messages.push({ role: 'assistant', content });
648
635
  messages.push({
649
636
  role: 'user',
@@ -652,7 +639,6 @@ export async function runAgent(prompt, projectContext, options = {}) {
652
639
  continue;
653
640
  }
654
641
  // Model is done
655
- console.error(`[DEBUG] Agent finished at iteration ${iteration}`);
656
642
  break;
657
643
  }
658
644
  // Add assistant response to history
@@ -210,25 +210,29 @@ export function parseOpenAIToolCalls(toolCalls) {
210
210
  if (!toolName)
211
211
  continue;
212
212
  let parameters = {};
213
+ const rawArgs = tc.function?.arguments || '{}';
213
214
  try {
214
- parameters = JSON.parse(tc.function?.arguments || '{}');
215
+ parameters = JSON.parse(rawArgs);
215
216
  }
216
217
  catch (e) {
217
218
  // JSON parsing failed - likely truncated response
218
- console.error(`[DEBUG] Failed to parse tool arguments for ${toolName}:`, tc.function?.arguments?.substring(0, 100));
219
- continue; // Skip this tool call entirely
219
+ // Try to extract what we can from partial JSON
220
+ const partialParams = extractPartialToolParams(toolName, rawArgs);
221
+ if (partialParams) {
222
+ parameters = partialParams;
223
+ }
224
+ else {
225
+ continue;
226
+ }
220
227
  }
221
228
  // Validate required parameters for specific tools
222
229
  if (toolName === 'write_file' && (!parameters.path || parameters.content === undefined)) {
223
- console.error(`[DEBUG] Skipping write_file with missing path or content`);
224
230
  continue;
225
231
  }
226
232
  if (toolName === 'read_file' && !parameters.path) {
227
- console.error(`[DEBUG] Skipping read_file with missing path`);
228
233
  continue;
229
234
  }
230
235
  if (toolName === 'edit_file' && (!parameters.path || parameters.old_text === undefined || parameters.new_text === undefined)) {
231
- console.error(`[DEBUG] Skipping edit_file with missing parameters`);
232
236
  continue;
233
237
  }
234
238
  parsed.push({
@@ -239,6 +243,87 @@ export function parseOpenAIToolCalls(toolCalls) {
239
243
  }
240
244
  return parsed;
241
245
  }
246
+ /**
247
+ * Extract parameters from truncated/partial JSON for tool calls
248
+ * This is a fallback when JSON.parse fails due to API truncation
249
+ */
250
+ function extractPartialToolParams(toolName, rawArgs) {
251
+ try {
252
+ // For write_file, try to extract path and content
253
+ if (toolName === 'write_file') {
254
+ const pathMatch = rawArgs.match(/"path"\s*:\s*"([^"]+)"/);
255
+ const contentMatch = rawArgs.match(/"content"\s*:\s*"([\s\S]*?)(?:"|$)/);
256
+ if (pathMatch && contentMatch) {
257
+ // Unescape the content
258
+ let content = contentMatch[1];
259
+ content = content
260
+ .replace(/\\n/g, '\n')
261
+ .replace(/\\t/g, '\t')
262
+ .replace(/\\r/g, '\r')
263
+ .replace(/\\"/g, '"')
264
+ .replace(/\\\\/g, '\\');
265
+ // If content appears truncated (doesn't end properly), add a comment
266
+ if (!content.endsWith('\n') && !content.endsWith('}') && !content.endsWith(';') && !content.endsWith('>')) {
267
+ content += '\n<!-- Content may be truncated -->\n';
268
+ }
269
+ return { path: pathMatch[1], content };
270
+ }
271
+ }
272
+ // For read_file, just need path
273
+ if (toolName === 'read_file') {
274
+ const pathMatch = rawArgs.match(/"path"\s*:\s*"([^"]+)"/);
275
+ if (pathMatch) {
276
+ return { path: pathMatch[1] };
277
+ }
278
+ }
279
+ // For list_files, just need path
280
+ if (toolName === 'list_files') {
281
+ const pathMatch = rawArgs.match(/"path"\s*:\s*"([^"]+)"/);
282
+ if (pathMatch) {
283
+ return { path: pathMatch[1] };
284
+ }
285
+ }
286
+ // For create_directory, just need path
287
+ if (toolName === 'create_directory') {
288
+ const pathMatch = rawArgs.match(/"path"\s*:\s*"([^"]+)"/);
289
+ if (pathMatch) {
290
+ return { path: pathMatch[1] };
291
+ }
292
+ }
293
+ // For edit_file, need path, old_text, new_text
294
+ if (toolName === 'edit_file') {
295
+ const pathMatch = rawArgs.match(/"path"\s*:\s*"([^"]+)"/);
296
+ const oldTextMatch = rawArgs.match(/"old_text"\s*:\s*"([\s\S]*?)(?:"|$)/);
297
+ const newTextMatch = rawArgs.match(/"new_text"\s*:\s*"([\s\S]*?)(?:"|$)/);
298
+ if (pathMatch && oldTextMatch && newTextMatch) {
299
+ return {
300
+ path: pathMatch[1],
301
+ old_text: oldTextMatch[1].replace(/\\n/g, '\n').replace(/\\"/g, '"'),
302
+ new_text: newTextMatch[1].replace(/\\n/g, '\n').replace(/\\"/g, '"'),
303
+ };
304
+ }
305
+ }
306
+ // For execute_command
307
+ if (toolName === 'execute_command') {
308
+ const commandMatch = rawArgs.match(/"command"\s*:\s*"([^"]+)"/);
309
+ if (commandMatch) {
310
+ const argsMatch = rawArgs.match(/"args"\s*:\s*\[([\s\S]*?)\]/);
311
+ let args = [];
312
+ if (argsMatch) {
313
+ const argStrings = argsMatch[1].match(/"([^"]+)"/g);
314
+ if (argStrings) {
315
+ args = argStrings.map(s => s.replace(/"/g, ''));
316
+ }
317
+ }
318
+ return { command: commandMatch[1], args };
319
+ }
320
+ }
321
+ return null;
322
+ }
323
+ catch (e) {
324
+ return null;
325
+ }
326
+ }
242
327
  /**
243
328
  * Parse tool calls from Anthropic response
244
329
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeep",
3
- "version": "1.0.25",
3
+ "version": "1.0.28",
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",