cloving 0.3.4 → 0.3.6

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.
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const events_1 = require("events");
4
+ // Passes through string chunks, looking for code blocks, when it finds one, it starts buffering until it finds the end of the block
5
+ // Then it emits the code block and clears the buffer and continues
6
+ class BlockManager extends events_1.EventEmitter {
7
+ constructor() {
8
+ super(...arguments);
9
+ this.buffer = '';
10
+ this.codeBuffer = '';
11
+ this.isBufferingCode = false;
12
+ this.isWaitingForContent = false;
13
+ }
14
+ // add content to the buffer, watching for code blocks along the way
15
+ addContent(content) {
16
+ // when getting ` , just add it to the buffer until we get a char that isn't `
17
+ if (content.includes('`')) {
18
+ this.isWaitingForContent = true;
19
+ this.buffer += content;
20
+ return;
21
+ }
22
+ else if (this.isWaitingForContent) {
23
+ content = this.buffer + content;
24
+ this.buffer = '';
25
+ }
26
+ const codeBlockMarker = '\n```';
27
+ let markerIndex = content.indexOf(codeBlockMarker);
28
+ if (content.startsWith('```'))
29
+ markerIndex = 0;
30
+ if (markerIndex !== -1) {
31
+ if (this.isBufferingCode) {
32
+ // End of code block
33
+ this.codeBuffer += content.slice(0, markerIndex + codeBlockMarker.length);
34
+ const rest = content.slice(markerIndex + codeBlockMarker.length);
35
+ this.emit('endGeneratingCode');
36
+ this.emitCodeBlock();
37
+ this.emit('content', rest);
38
+ }
39
+ else {
40
+ // Start of code block
41
+ this.emit('startGeneratingCode');
42
+ this.emitBuffer(content.slice(0, markerIndex));
43
+ this.codeBuffer = content.slice(markerIndex);
44
+ this.isBufferingCode = true;
45
+ }
46
+ }
47
+ else {
48
+ // No code block marker found
49
+ if (this.isBufferingCode) {
50
+ this.codeBuffer += content;
51
+ }
52
+ else {
53
+ this.buffer += content;
54
+ }
55
+ }
56
+ if (!this.isBufferingCode && this.buffer) {
57
+ this.emitBuffer();
58
+ }
59
+ }
60
+ emitBuffer(additionalContent = '') {
61
+ const contentToEmit = this.buffer + additionalContent;
62
+ if (contentToEmit) {
63
+ this.emit('content', contentToEmit);
64
+ this.clearBuffer();
65
+ }
66
+ }
67
+ emitCodeBlock() {
68
+ if (this.codeBuffer) {
69
+ const codeParts = this.parseCodeBuffer();
70
+ if (codeParts) {
71
+ const { language, currentStart, newEnd, currentCode, newCode } = codeParts;
72
+ this.emit('codeBlock', {
73
+ language,
74
+ currentStart,
75
+ newEnd,
76
+ currentCode,
77
+ newCode,
78
+ raw: this.codeBuffer,
79
+ });
80
+ this.emit('content', this.buffer);
81
+ this.clearBuffer();
82
+ }
83
+ }
84
+ }
85
+ /**
86
+ * Parses a code buffer into its constituent parts.
87
+ *
88
+ * This function takes a code buffer as a string, splits it into lines, and identifies the language,
89
+ * current start index, divider index, and new end index. It then extracts the current code and new code
90
+ * from the buffer based on these indices.
91
+ *
92
+ * @return {{ language: string, currentStart: string, newEnd: string, current: string, newCode: string, afterCode: string }}
93
+ * An object containing the language, current start, new end, current code, and new code.
94
+ */
95
+ parseCodeBuffer() {
96
+ const lines = this.codeBuffer.split('\n');
97
+ const language = lines[0].trim().slice(3); // Remove '```' from the language line
98
+ const currentStartIndex = lines.findIndex((line) => line.startsWith('<<<<<<< CURRENT'));
99
+ const dividerIndex = lines.findIndex((line) => line === '=======');
100
+ const newEndIndex = lines.findIndex((line) => line.startsWith('>>>>>>> NEW'));
101
+ if (currentStartIndex === -1 || dividerIndex === -1 || newEndIndex === -1) {
102
+ return null;
103
+ }
104
+ const currentCode = lines.slice(currentStartIndex + 1, dividerIndex).join('\n');
105
+ const newCode = lines.slice(dividerIndex + 1, newEndIndex).join('\n');
106
+ return {
107
+ language,
108
+ currentStart: lines[currentStartIndex],
109
+ newEnd: lines[newEndIndex],
110
+ currentCode,
111
+ newCode,
112
+ };
113
+ }
114
+ clearBuffer() {
115
+ this.buffer = '';
116
+ this.codeBuffer = '';
117
+ this.isBufferingCode = false;
118
+ this.isWaitingForContent = false;
119
+ }
120
+ }
121
+ exports.default = BlockManager;
@@ -9,13 +9,16 @@ const child_process_1 = require("child_process");
9
9
  const colors_1 = __importDefault(require("colors"));
10
10
  const axios_1 = __importDefault(require("axios"));
11
11
  const cli_highlight_1 = __importDefault(require("cli-highlight"));
12
+ const fs_1 = __importDefault(require("fs"));
13
+ const path_1 = __importDefault(require("path"));
12
14
  const CommitManager_1 = __importDefault(require("./CommitManager"));
13
15
  const ChunkManager_1 = __importDefault(require("./ChunkManager"));
16
+ const BlockManager_1 = __importDefault(require("./BlockManager"));
14
17
  const ReviewManager_1 = __importDefault(require("./ReviewManager"));
15
18
  const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
16
19
  const string_utils_1 = require("../utils/string_utils");
17
20
  const config_utils_1 = require("../utils/config_utils");
18
- const command_utils_1 = require("../utils/command_utils");
21
+ const prompt_utils_1 = require("../utils/prompt_utils");
19
22
  const specialCommands = [
20
23
  'save',
21
24
  'commit',
@@ -27,7 +30,7 @@ const specialCommands = [
27
30
  'ls <pattern>',
28
31
  'git <command>',
29
32
  'help',
30
- 'exit'
33
+ 'exit',
31
34
  ];
32
35
  class ChatManager {
33
36
  /**
@@ -56,6 +59,7 @@ class ChatManager {
56
59
  historySize: 1000,
57
60
  });
58
61
  this.chunkManager = new ChunkManager_1.default();
62
+ this.blockManager = new BlockManager_1.default();
59
63
  }
60
64
  /**
61
65
  * Initializes the ChatManager by loading context files, displaying a welcome message,
@@ -87,16 +91,16 @@ class ChatManager {
87
91
  console.log(` - ${colors_1.default.yellow(colors_1.default.bold('exit'))} Quit this session`);
88
92
  }
89
93
  /**
90
- * Checks for the latest version of the app from GitHub and compares it with the current version.
91
- * If a new version is available, it notifies the user.
92
- * @private
93
- * @returns {Promise<void>}
94
- */
94
+ * Checks for the latest version of the app from GitHub and compares it with the current version.
95
+ * If a new version is available, it notifies the user.
96
+ * @private
97
+ * @returns {Promise<void>}
98
+ */
95
99
  async checkForLatestVersion() {
96
100
  try {
97
101
  const response = await axios_1.default.get('https://api.github.com/repos/cloving-ai/cloving-cli/releases/latest');
98
102
  const latestVersion = response.data.tag_name;
99
- const currentVersion = `v${(0, command_utils_1.getPackageVersion)()}`;
103
+ const currentVersion = `v${(0, prompt_utils_1.getPackageVersion)()}`;
100
104
  if (latestVersion !== currentVersion) {
101
105
  console.log(colors_1.default.bgWhite.black(`\n 🚀 A new version of Cloving is available: ${latestVersion} `));
102
106
  console.log(colors_1.default.bgWhite.black(` You are currently using version: ${currentVersion} `));
@@ -123,9 +127,9 @@ class ChatManager {
123
127
  */
124
128
  async loadContextFiles() {
125
129
  const config = (0, config_utils_1.getClovingConfig)();
126
- const primaryLanguage = config.languages.find(lang => lang.primary);
130
+ const primaryLanguage = config.languages.find((lang) => lang.primary);
127
131
  const defaultDirectory = primaryLanguage ? primaryLanguage.directory : '.';
128
- const testingDirectories = config.testingFrameworks?.map(framework => framework.directory) || [];
132
+ const testingDirectories = config.testingFrameworks?.map((framework) => framework.directory) || [];
129
133
  const testingDirectory = testingDirectories[0];
130
134
  let files = this.options.files || [defaultDirectory, testingDirectory].filter(Boolean);
131
135
  if (files.length > 0) {
@@ -136,7 +140,7 @@ class ChatManager {
136
140
  if (!file)
137
141
  continue;
138
142
  const previousCount = Object.keys(this.contextFiles).length;
139
- this.contextFiles = await (0, command_utils_1.addFileOrDirectoryToContext)(file, this.contextFiles, this.options);
143
+ this.contextFiles = await (0, prompt_utils_1.addFileOrDirectoryToContext)(file, this.contextFiles, this.options);
140
144
  const newCount = Object.keys(this.contextFiles).length;
141
145
  const addedCount = newCount - previousCount;
142
146
  const totalTokens = this.calculateTotalTokens();
@@ -235,6 +239,13 @@ class ChatManager {
235
239
  * - Any other input is processed as a user query to the AI.
236
240
  */
237
241
  async handleCommand(command) {
242
+ // Check if the command is a single character and map it to the first matching special command
243
+ if (command.length === 1) {
244
+ const matchingCommand = specialCommands.find((cmd) => cmd.startsWith(command));
245
+ if (matchingCommand) {
246
+ command = matchingCommand.split(' ')[0]; // Use only the command part without arguments
247
+ }
248
+ }
238
249
  switch (command) {
239
250
  case 'help':
240
251
  this.displayHelp();
@@ -249,10 +260,10 @@ class ChatManager {
249
260
  await this.handleCommit();
250
261
  break;
251
262
  case 'ls':
252
- this.handleList("ls *");
263
+ this.handleList('ls *');
253
264
  break;
254
265
  case 'rm':
255
- await this.handleRemove("rm *");
266
+ await this.handleRemove('rm *');
256
267
  break;
257
268
  case 'review':
258
269
  await this.handleReview();
@@ -292,7 +303,7 @@ class ChatManager {
292
303
  }
293
304
  else {
294
305
  for (const filePath of foundFiles) {
295
- this.contextFiles = await (0, command_utils_1.addFileOrDirectoryToContext)(filePath, this.contextFiles, this.options);
306
+ this.contextFiles = await (0, prompt_utils_1.addFileOrDirectoryToContext)(filePath, this.contextFiles, this.options);
296
307
  const content = this.contextFiles[filePath];
297
308
  const tokenEstimate = this.estimateTokens(content);
298
309
  console.log(`\nAdded ${colors_1.default.bold(colors_1.default.green(filePath))} to this chat session's context (${colors_1.default.yellow(`~${tokenEstimate.toLocaleString()} tokens`)})`);
@@ -317,8 +328,10 @@ class ChatManager {
317
328
  if (error) {
318
329
  return reject(error);
319
330
  }
320
- const files = stdout.split('\n').filter((filePath) => filePath.trim() !== '')
321
- .map((filePath) => filePath.startsWith('./') ? filePath.slice(2) : filePath);
331
+ const files = stdout
332
+ .split('\n')
333
+ .filter((filePath) => filePath.trim() !== '')
334
+ .map((filePath) => (filePath.startsWith('./') ? filePath.slice(2) : filePath));
322
335
  resolve(files);
323
336
  });
324
337
  });
@@ -337,7 +350,7 @@ class ChatManager {
337
350
  const filePath = command.slice(4).trim();
338
351
  if (filePath) {
339
352
  try {
340
- this.contextFiles = await (0, command_utils_1.addFileOrDirectoryToContext)(filePath, this.contextFiles, this.options);
353
+ this.contextFiles = await (0, prompt_utils_1.addFileOrDirectoryToContext)(filePath, this.contextFiles, this.options);
341
354
  console.log(`\nAdded ${colors_1.default.bold(colors_1.default.green(filePath))} to this chat session's context`);
342
355
  this.refreshContext();
343
356
  }
@@ -350,11 +363,23 @@ class ChatManager {
350
363
  }
351
364
  this.rl.prompt();
352
365
  }
353
- refreshContext() {
354
- const updatedSystemPrompt = (0, command_utils_1.generateCodegenPrompt)(this.contextFiles);
366
+ async refreshContext() {
367
+ await this.reloadContextFiles();
368
+ const updatedSystemPrompt = (0, prompt_utils_1.generateCodegenPrompt)(this.contextFiles);
355
369
  this.chatHistory[0] = { role: 'user', content: updatedSystemPrompt };
356
370
  this.chatHistory[1] = { role: 'assistant', content: 'What would you like to do?' };
357
371
  }
372
+ async reloadContextFiles() {
373
+ for (const filePath in this.contextFiles) {
374
+ try {
375
+ const content = await fs_1.default.promises.readFile(path_1.default.resolve(filePath), 'utf8');
376
+ this.contextFiles[filePath] = content;
377
+ }
378
+ catch (error) {
379
+ console.error(`Error reading file ${filePath}:`, error);
380
+ }
381
+ }
382
+ }
358
383
  isRemoveCommand(command) {
359
384
  return command.startsWith('rm ');
360
385
  }
@@ -367,14 +392,14 @@ class ChatManager {
367
392
  // Function to filter paths using the pattern
368
393
  filterPaths(paths, pattern) {
369
394
  const regex = this.globToRegExp(pattern);
370
- return paths.filter(path => regex.test(path));
395
+ return paths.filter((path) => regex.test(path));
371
396
  }
372
397
  async handleRemove(command) {
373
398
  const pattern = command.slice(3).trim();
374
399
  if (pattern) {
375
400
  const matchedFiles = this.filterPaths(Object.keys(this.contextFiles), pattern);
376
401
  if (matchedFiles.length > 0) {
377
- matchedFiles.forEach(filePath => {
402
+ matchedFiles.forEach((filePath) => {
378
403
  delete this.contextFiles[filePath];
379
404
  console.log(`Removed ${colors_1.default.bold(colors_1.default.green(filePath))} from this chat session's context files`);
380
405
  });
@@ -402,7 +427,7 @@ class ChatManager {
402
427
  if (matchedFiles.length > 0) {
403
428
  console.log(`\nFiles in the current chat session context:`);
404
429
  let totalTokens = 0;
405
- matchedFiles.forEach(fileName => {
430
+ matchedFiles.forEach((fileName) => {
406
431
  const content = this.contextFiles[fileName];
407
432
  const tokenEstimate = this.estimateTokens(content);
408
433
  totalTokens += tokenEstimate;
@@ -417,7 +442,7 @@ class ChatManager {
417
442
  this.rl.prompt();
418
443
  }
419
444
  async handleCopy() {
420
- const lastResponse = this.chatHistory.filter(msg => msg.role === 'assistant').pop();
445
+ const lastResponse = this.chatHistory.filter((msg) => msg.role === 'assistant').pop();
421
446
  if (lastResponse) {
422
447
  copy_paste_1.default.copy(lastResponse.content, () => {
423
448
  console.info('Last response copied to clipboard.');
@@ -443,7 +468,10 @@ class ChatManager {
443
468
  this.rl.prompt();
444
469
  }
445
470
  async handleSave() {
446
- const lastResponse = this.chatHistory.slice().reverse().find(msg => msg.role === 'assistant');
471
+ const lastResponse = this.chatHistory
472
+ .slice()
473
+ .reverse()
474
+ .find((msg) => msg.role === 'assistant');
447
475
  if (lastResponse) {
448
476
  const currentNewBlocks = (0, string_utils_1.extractCurrentNewBlocks)(lastResponse.content);
449
477
  if (Object.keys(currentNewBlocks).length > 0) {
@@ -493,12 +521,15 @@ class ChatManager {
493
521
  this.isProcessing = true;
494
522
  this.chunkManager = new ChunkManager_1.default();
495
523
  try {
496
- this.refreshContext();
524
+ await this.refreshContext();
497
525
  this.prompt = this.generatePrompt(input);
498
526
  this.addChatHistory(this.prompt);
499
527
  if (!this.isSilent) {
500
- const fullPrompt = this.chatHistory.map(m => {
501
- const content = m.content.split('\n').map(line => {
528
+ const fullPrompt = this.chatHistory
529
+ .map((m) => {
530
+ const content = m.content
531
+ .split('\n')
532
+ .map((line) => {
502
533
  if (line.startsWith('###')) {
503
534
  return colors_1.default.red.bold(line);
504
535
  }
@@ -506,21 +537,27 @@ class ChatManager {
506
537
  return colors_1.default.yellow.bold(line);
507
538
  }
508
539
  return line;
509
- }).join('\n');
510
- return m.role === 'user' ? `${colors_1.default.yellow.bold('## User Prompt')}\n\n${content}` : `${colors_1.default.green.bold('## 🍀 Cloving\'s Response 🍀')}\n\n${content}`;
511
- }).join('\n\n');
540
+ })
541
+ .join('\n');
542
+ return m.role === 'user'
543
+ ? `${colors_1.default.yellow.bold('## User Prompt')}\n\n${content}`
544
+ : `${colors_1.default.green.bold("## 🍀 Cloving's Response 🍀")}\n\n${content}`;
545
+ })
546
+ .join('\n\n');
512
547
  // Review the prompt using the REPL
513
548
  const tokenCount = Math.ceil(fullPrompt.length / 4).toLocaleString();
514
- console.log(`The generated prompt is approximately ${tokenCount} tokens long. Would you like to review the prompt before sending it? ${colors_1.default.gray('(Y/n)')}`);
549
+ console.log(`${colors_1.default.yellow.bold('INFORMATION')} The generated prompt is approximately ${tokenCount} tokens long. Would you like to review the prompt before sending it? ${colors_1.default.gray('(Y/n)')}`);
515
550
  const reviewPrompt = await new Promise((resolve) => {
516
551
  this.rl.question(colors_1.default.green.bold('cloving> '), (answer) => {
517
552
  resolve(answer.trim().toLowerCase());
518
553
  });
519
554
  });
520
- if (reviewPrompt === 'y' || reviewPrompt === '') {
555
+ if (reviewPrompt.startsWith('y') || reviewPrompt === '') {
521
556
  console.log(colors_1.default.gray.bold('\n---------------------- PROMPT START ----------------------\n'));
522
557
  const promptParts = fullPrompt.split('\n```');
523
- const highlightedPrompt = promptParts.map((part, index) => index % 2 === 1 ? (0, cli_highlight_1.default)(part, { language: 'typescript' }) : part).join('\n```');
558
+ const highlightedPrompt = promptParts
559
+ .map((part, index) => index % 2 === 1 ? (0, cli_highlight_1.default)(part, { language: 'typescript' }) : part)
560
+ .join('\n```');
524
561
  console.log(highlightedPrompt);
525
562
  console.log(colors_1.default.gray.bold('\n---------------------- PROMPT END ----------------------\n'));
526
563
  console.log(`Do you want to proceed with this prompt? ${colors_1.default.gray('(Y/n)')}`);
@@ -529,7 +566,7 @@ class ChatManager {
529
566
  resolve(answer.trim().toLowerCase());
530
567
  });
531
568
  });
532
- if (confirmPrompt !== 'y' && confirmPrompt !== '') {
569
+ if (!confirmPrompt.startsWith('y') && confirmPrompt !== '') {
533
570
  console.log('Operation cancelled by user.');
534
571
  this.isProcessing = false;
535
572
  this.rl.prompt();
@@ -537,7 +574,10 @@ class ChatManager {
537
574
  }
538
575
  }
539
576
  }
540
- const responseStream = await this.gpt.streamText({ prompt: this.prompt, messages: this.chatHistory });
577
+ const responseStream = await this.gpt.streamText({
578
+ prompt: this.prompt,
579
+ messages: this.chatHistory,
580
+ });
541
581
  let accumulatedContent = '';
542
582
  this.handleResponseStream(responseStream, accumulatedContent);
543
583
  }
@@ -548,25 +588,69 @@ class ChatManager {
548
588
  addChatHistory(input) {
549
589
  this.chatHistory.push({ role: 'user', content: input });
550
590
  }
591
+ /**
592
+ * Handles the streaming response from the AI model.
593
+ * This method processes the incoming data chunks, converts them to readable output,
594
+ * and manages the accumulated content.
595
+ *
596
+ * @param responseStream - The stream of response data from the AI model.
597
+ * @param accumulatedContent - A string to accumulate the complete response content.
598
+ */
551
599
  handleResponseStream(responseStream, accumulatedContent) {
600
+ this.blockManager.on('content', (content) => {
601
+ process.stdout.write('\n------ beginning of blockManager content ------\n');
602
+ process.stdout.write(content);
603
+ process.stdout.write('\n------ end of blockManager content ------\n');
604
+ accumulatedContent += content;
605
+ });
606
+ this.blockManager.on('codeBlock', (codeBlock) => {
607
+ process.stdout.write(codeBlock.language);
608
+ process.stdout.write('\n');
609
+ process.stdout.write(codeBlock.currentStart);
610
+ process.stdout.write('\n');
611
+ process.stdout.write((0, cli_highlight_1.default)(codeBlock.currentCode, { language: codeBlock.language }));
612
+ process.stdout.write('\n=======\n');
613
+ process.stdout.write((0, cli_highlight_1.default)(codeBlock.newCode, { language: codeBlock.language }));
614
+ process.stdout.write('\n');
615
+ process.stdout.write(codeBlock.newEnd);
616
+ accumulatedContent += codeBlock.raw;
617
+ });
618
+ // Handle content chunks from the ChunkManager
552
619
  this.chunkManager.on('content', (buffer) => {
553
620
  let convertedStream = this.gpt.convertStream(buffer);
554
621
  while (convertedStream !== null) {
555
622
  const { output, lastChar } = convertedStream;
623
+ process.stdout.write('\n------ beginning of chunk ------\n');
556
624
  process.stdout.write(output);
557
- accumulatedContent += output;
625
+ process.stdout.write(`lastChar: ${lastChar}\n`);
626
+ process.stdout.write('\n------ end of chunk ------\n');
627
+ // buffer the output to the BlockManager
628
+ this.blockManager.addContent(output);
629
+ // clear the buffer from the ChunkManager
558
630
  this.chunkManager.clearBuffer(lastChar);
631
+ // Try to convert the rest of the buffer to a string
559
632
  buffer = buffer.slice(lastChar);
560
633
  convertedStream = this.gpt.convertStream(buffer);
561
634
  }
562
635
  });
636
+ // Handle incoming data chunks from the response stream
563
637
  responseStream.data.on('data', (chunk) => {
638
+ // Convert the chunk to a string and add it to the ChunkManager
639
+ if (!chunk)
640
+ return;
641
+ // a chunk is a JSON object with different keys depending on the AI service provider
564
642
  const chunkString = chunk.toString();
643
+ process.stdout.write('\n------ beginning of chunk string ------\n');
644
+ process.stdout.write(chunkString);
645
+ process.stdout.write('\n------ end of chunk string ------\n');
565
646
  this.chunkManager.addChunk(chunkString);
566
647
  });
648
+ // Handle the end of the response stream
567
649
  responseStream.data.on('end', () => {
650
+ // Finalize the response when the stream ends
568
651
  this.finalizeResponse(accumulatedContent);
569
652
  });
653
+ // Handle any errors in the response stream
570
654
  responseStream.data.on('error', (error) => {
571
655
  console.error('Error streaming response:', error);
572
656
  this.isProcessing = false;
@@ -574,9 +658,84 @@ class ChatManager {
574
658
  this.rl.prompt();
575
659
  });
576
660
  }
577
- finalizeResponse(accumulatedContent) {
661
+ processOutput(output, accumulatedContent, codeBuffer, isBufferingCode) {
662
+ const codeBlockMarker = '\n```';
663
+ let currentIndex = 0;
664
+ while (currentIndex < output.length) {
665
+ let markerIndex = output.indexOf(codeBlockMarker, currentIndex);
666
+ if (output.startsWith('```'))
667
+ markerIndex = 0;
668
+ // Check if the code block marker is found
669
+ if (markerIndex !== -1) {
670
+ if (isBufferingCode) {
671
+ // End of code block
672
+ codeBuffer += output.slice(currentIndex, markerIndex + codeBlockMarker.length);
673
+ const { language, currentStart, newEnd, current: currentCode, new: newCode } = this.parseCodeBuffer(codeBuffer);
674
+ if (language)
675
+ process.stdout.write(language);
676
+ if (currentStart)
677
+ process.stdout.write(currentStart);
678
+ if (currentCode)
679
+ process.stdout.write((0, cli_highlight_1.default)(currentCode, { language }));
680
+ process.stdout.write('=======');
681
+ if (newCode)
682
+ process.stdout.write((0, cli_highlight_1.default)(newCode, { language }));
683
+ if (newEnd)
684
+ process.stdout.write(newEnd);
685
+ accumulatedContent += codeBuffer;
686
+ codeBuffer = '';
687
+ isBufferingCode = false;
688
+ currentIndex = markerIndex + codeBlockMarker.length;
689
+ }
690
+ else {
691
+ // Start of code block
692
+ process.stdout.write(output.slice(currentIndex, markerIndex + codeBlockMarker.length));
693
+ accumulatedContent += output.slice(currentIndex, markerIndex + codeBlockMarker.length);
694
+ codeBuffer = '';
695
+ isBufferingCode = true;
696
+ currentIndex = markerIndex + codeBlockMarker.length;
697
+ }
698
+ }
699
+ else {
700
+ // No code block marker found
701
+ if (isBufferingCode) {
702
+ // buffering code
703
+ codeBuffer += output.slice(currentIndex);
704
+ }
705
+ else {
706
+ process.stdout.write(output.slice(currentIndex));
707
+ accumulatedContent += output.slice(currentIndex);
708
+ }
709
+ break;
710
+ }
711
+ }
712
+ return [isBufferingCode, codeBuffer, accumulatedContent];
713
+ }
714
+ parseCodeBuffer(codeBuffer) {
715
+ const lines = codeBuffer.split('\n');
716
+ const language = lines[0].trim();
717
+ const currentStartIndex = lines.findIndex(line => line.startsWith('<<<<<<< CURRENT'));
718
+ const dividerIndex = lines.findIndex(line => line === '=======');
719
+ const newEndIndex = lines.findIndex(line => line.startsWith('>>>>>>> NEW'));
720
+ const current = lines.slice(currentStartIndex + 1, dividerIndex).join('\n');
721
+ const newCode = lines.slice(dividerIndex + 1, newEndIndex).join('\n');
722
+ return { language, currentStart: lines[currentStartIndex], newEnd: lines[newEndIndex], current, new: newCode };
723
+ }
724
+ async finalizeResponse(accumulatedContent) {
578
725
  this.chatHistory.push({ role: 'assistant', content: accumulatedContent.trim() });
579
726
  this.isProcessing = false;
727
+ if (accumulatedContent) {
728
+ const currentNewBlocks = (0, string_utils_1.extractCurrentNewBlocks)(accumulatedContent);
729
+ const [canApply, summary] = await (0, string_utils_1.checkBlocksApplicability)(currentNewBlocks);
730
+ if (!canApply) {
731
+ console.log(`\n\n${summary}\n\n${colors_1.default.yellow.bold('WARNING')} Some of the provided code blocks could not be automatically applied, prompting the AI to try again with more detail.\n`);
732
+ this.processUserInput(`Some of the provided code blocks could not be applied,
733
+ please match the existing code with a few more lines of context and make sure it is a character for character exact match.
734
+
735
+ ${summary}`);
736
+ return;
737
+ }
738
+ }
580
739
  process.stdout.write(`
581
740
 
582
741
  You can follow up with another request or:
@@ -598,20 +757,21 @@ You can follow up with another request or:
598
757
  const errorNumber = error.response?.status || 'unknown';
599
758
  switch (errorNumber) {
600
759
  case 400:
601
- errorMessage = "Invalid model or prompt size too large. Try specifying fewer files.";
760
+ errorMessage = 'Invalid model or prompt size too large. Try specifying fewer files.';
602
761
  break;
603
762
  case 403:
604
- errorMessage = "Inactive subscription or usage limit reached";
763
+ errorMessage = 'Inactive subscription or usage limit reached';
605
764
  break;
606
765
  case 429:
607
- errorMessage = "Rate limit error";
766
+ errorMessage = 'Rate limit error';
608
767
  break;
609
768
  case 500:
610
- errorMessage = "Internal server error";
769
+ errorMessage = 'Internal server error';
611
770
  break;
612
771
  }
613
772
  const promptTokens = Math.ceil(this.prompt.length / 4).toLocaleString();
614
- console.error(`Error processing a ${promptTokens} token prompt: `, errorMessage, `(${errorNumber}) \n`);
773
+ const statusMessage = error.response?.data?.statusMessage;
774
+ console.error(colors_1.default.red(`\nError (${errorNumber}) while submitting the prompt to the AI API\n\n${statusMessage}\n`));
615
775
  this.isProcessing = false;
616
776
  this.rl.prompt();
617
777
  }
@@ -1,12 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const events_1 = require("events");
4
+ // A chunk is a JSON object with different keys depending on the AI service provider
5
+ //
6
+ // Some providers produce single response in a single chunk
7
+ //
8
+ // Others produce multiple responses in a single chunk
9
+ //
10
+ // This Chunk Manager's job is to buffer the chunks and emit them as a single string
11
+ // without automatically clearing the buffer until clearBuffer is called.
12
+ // That way, the consumer of the chunk can process it and clear the buffer after
13
+ // they have received enough data.
4
14
  class ChunkManager extends events_1.EventEmitter {
5
15
  constructor() {
6
16
  super(...arguments);
7
17
  this.buffer = '';
8
18
  }
9
19
  addChunk(chunk) {
20
+ if (!chunk)
21
+ return;
10
22
  this.buffer += chunk;
11
23
  this.emit('content', this.buffer);
12
24
  }