codecane 1.0.156 → 1.0.171

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 (106) hide show
  1. package/dist/browser-runner.d.ts +2 -0
  2. package/dist/browser-runner.js +210 -135
  3. package/dist/browser-runner.js.map +1 -1
  4. package/dist/chat-storage.d.ts +1 -1
  5. package/dist/chat-storage.js +35 -31
  6. package/dist/chat-storage.js.map +1 -1
  7. package/dist/checkpoints.d.ts +64 -0
  8. package/dist/checkpoints.js +147 -0
  9. package/dist/checkpoints.js.map +1 -0
  10. package/dist/cli.d.ts +22 -16
  11. package/dist/cli.js +472 -367
  12. package/dist/cli.js.map +1 -1
  13. package/dist/client.d.ts +178 -25
  14. package/dist/client.js +252 -198
  15. package/dist/client.js.map +1 -1
  16. package/dist/code-map/tsconfig.tsbuildinfo +1 -1
  17. package/dist/common/actions.d.ts +2083 -443
  18. package/dist/common/actions.js +31 -78
  19. package/dist/common/actions.js.map +1 -1
  20. package/dist/common/browser-actions.d.ts +221 -141
  21. package/dist/common/browser-actions.js +25 -12
  22. package/dist/common/browser-actions.js.map +1 -1
  23. package/dist/common/constants/tools.d.ts +3 -0
  24. package/dist/common/constants/tools.js +24 -0
  25. package/dist/common/constants/tools.js.map +1 -0
  26. package/dist/common/constants.d.ts +14 -8
  27. package/dist/common/constants.js +7 -6
  28. package/dist/common/constants.js.map +1 -1
  29. package/dist/common/message-image-handling.d.ts +41 -0
  30. package/dist/common/message-image-handling.js +57 -0
  31. package/dist/common/message-image-handling.js.map +1 -0
  32. package/dist/common/project-file-tree.js +7 -7
  33. package/dist/common/project-file-tree.js.map +1 -1
  34. package/dist/common/types/agent-state.d.ts +461 -0
  35. package/dist/common/types/agent-state.js +30 -0
  36. package/dist/common/types/agent-state.js.map +1 -0
  37. package/dist/common/types/message.d.ts +311 -0
  38. package/dist/common/types/message.js +54 -0
  39. package/dist/common/types/message.js.map +1 -0
  40. package/dist/common/types/tools.d.ts +5 -0
  41. package/dist/common/types/tools.js +3 -0
  42. package/dist/common/types/tools.js.map +1 -0
  43. package/dist/common/util/__tests__/messages.test.js +70 -0
  44. package/dist/common/util/__tests__/messages.test.js.map +1 -0
  45. package/dist/common/util/changes.js +3 -3
  46. package/dist/common/util/changes.js.map +1 -1
  47. package/dist/common/util/credentials.d.ts +4 -4
  48. package/dist/common/util/file.d.ts +6 -2
  49. package/dist/common/util/file.js +30 -27
  50. package/dist/common/util/file.js.map +1 -1
  51. package/dist/common/util/git.js +1 -1
  52. package/dist/common/util/git.js.map +1 -1
  53. package/dist/common/util/lru-cache.d.ts +9 -0
  54. package/dist/common/util/lru-cache.js +42 -0
  55. package/dist/common/util/lru-cache.js.map +1 -0
  56. package/dist/common/util/messages.d.ts +6 -0
  57. package/dist/common/util/messages.js +22 -0
  58. package/dist/common/util/messages.js.map +1 -0
  59. package/dist/common/util/min-heap.d.ts +15 -0
  60. package/dist/common/util/min-heap.js +73 -0
  61. package/dist/common/util/min-heap.js.map +1 -0
  62. package/dist/common/util/process-stream.d.ts +8 -0
  63. package/dist/common/util/process-stream.js +102 -0
  64. package/dist/common/util/process-stream.js.map +1 -0
  65. package/dist/common/util/promise.d.ts +8 -0
  66. package/dist/common/util/promise.js +25 -2
  67. package/dist/common/util/promise.js.map +1 -1
  68. package/dist/common/util/string.d.ts +31 -0
  69. package/dist/common/util/string.js +71 -1
  70. package/dist/common/util/string.js.map +1 -1
  71. package/dist/common/websockets/websocket-schema.d.ts +3920 -938
  72. package/dist/config.d.ts +1 -0
  73. package/dist/config.js +3 -2
  74. package/dist/config.js.map +1 -1
  75. package/dist/credentials.d.ts +1 -0
  76. package/dist/credentials.js +7 -3
  77. package/dist/credentials.js.map +1 -1
  78. package/dist/index.js +3 -3
  79. package/dist/index.js.map +1 -1
  80. package/dist/menu.js +16 -12
  81. package/dist/menu.js.map +1 -1
  82. package/dist/project-files.d.ts +40 -2
  83. package/dist/project-files.js +95 -17
  84. package/dist/project-files.js.map +1 -1
  85. package/dist/tool-handlers.d.ts +22 -7
  86. package/dist/tool-handlers.js +110 -43
  87. package/dist/tool-handlers.js.map +1 -1
  88. package/dist/utils/logger.d.ts +1 -0
  89. package/dist/utils/logger.js +46 -0
  90. package/dist/utils/logger.js.map +1 -0
  91. package/dist/utils/process-xml-chunks.d.ts +37 -0
  92. package/dist/utils/process-xml-chunks.js +247 -0
  93. package/dist/utils/process-xml-chunks.js.map +1 -0
  94. package/dist/utils/spinner.d.ts +11 -0
  95. package/dist/utils/spinner.js +87 -0
  96. package/dist/utils/spinner.js.map +1 -0
  97. package/dist/utils/terminal.d.ts +3 -3
  98. package/dist/utils/terminal.js +23 -24
  99. package/dist/utils/terminal.js.map +1 -1
  100. package/dist/web-scraper.d.ts +1 -1
  101. package/dist/web-scraper.js +11 -7
  102. package/dist/web-scraper.js.map +1 -1
  103. package/package.json +3 -4
  104. package/dist/__tests__/browser-runner.test.js +0 -15
  105. package/dist/__tests__/browser-runner.test.js.map +0 -1
  106. /package/dist/{__tests__/browser-runner.test.d.ts → common/util/__tests__/messages.test.d.ts} +0 -0
package/dist/client.js CHANGED
@@ -27,32 +27,36 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.Client = void 0;
30
+ const child_process_1 = require("child_process");
31
+ const fs = __importStar(require("fs"));
32
+ const path_1 = __importDefault(require("path"));
30
33
  const picocolors_1 = require("picocolors");
34
+ const ts_pattern_1 = require("ts-pattern");
35
+ const actions_1 = require("./common/actions");
36
+ const constants_1 = require("./common/constants");
37
+ const agent_state_1 = require("./common/types/agent-state");
31
38
  const websocket_client_1 = require("./common/websockets/websocket-client");
32
- const project_files_1 = require("./project-files");
33
39
  const browser_runner_1 = require("./browser-runner");
34
- const changes_1 = require("./common/util/changes");
40
+ const checkpoints_1 = require("./checkpoints");
41
+ const config_1 = require("./config");
35
42
  const credentials_1 = require("./credentials");
36
- const actions_1 = require("./common/actions");
37
- const tool_handlers_1 = require("./tool-handlers");
38
- const constants_1 = require("./common/constants");
39
- const lodash_1 = require("lodash");
40
- const path_1 = __importDefault(require("path"));
41
- const fs = __importStar(require("fs"));
42
- const ts_pattern_1 = require("ts-pattern");
43
43
  const fingerprint_1 = require("./fingerprint");
44
- const git_1 = require("./common/util/git");
45
44
  const menu_1 = require("./menu");
46
- const child_process_1 = require("child_process");
45
+ const project_files_1 = require("./project-files");
46
+ const tool_handlers_1 = require("./tool-handlers");
47
+ const spinner_1 = require("./utils/spinner");
48
+ const process_xml_chunks_1 = require("./utils/process-xml-chunks");
47
49
  class Client {
48
50
  webSocket;
49
51
  chatStorage;
50
- currentUserInputId;
51
52
  returnControlToUser;
52
53
  fingerprintId;
53
54
  costMode;
54
55
  fileVersions = [];
55
56
  fileContext;
57
+ lastChanges = [];
58
+ agentState;
59
+ originalFileVersions = {};
56
60
  user;
57
61
  lastWarnedPct = 0;
58
62
  usage = 0;
@@ -61,7 +65,7 @@ class Client {
61
65
  lastRequestCredits = 0;
62
66
  sessionCreditsUsed = 0;
63
67
  nextQuotaReset = null;
64
- browserRunner = null;
68
+ hadFileChanges = false;
65
69
  git;
66
70
  rl;
67
71
  constructor(websocketUrl, chatStorage, onWebSocketError, onWebSocketReconnect, returnControlToUser, costMode, git, rl) {
@@ -73,16 +77,15 @@ class Client {
73
77
  this.getFingerprintId();
74
78
  this.returnControlToUser = returnControlToUser;
75
79
  this.rl = rl;
76
- this.browserRunner = new browser_runner_1.BrowserRunner();
77
80
  }
78
81
  async exit() {
79
- // Clean up browser before exiting
80
- if (this.browserRunner) {
81
- await this.browserRunner.shutdown();
82
+ if (browser_runner_1.activeBrowserRunner) {
83
+ browser_runner_1.activeBrowserRunner.shutdown();
82
84
  }
83
85
  process.exit(0);
84
86
  }
85
- initFileVersions(projectFileContext) {
87
+ initAgentState(projectFileContext) {
88
+ this.agentState = (0, agent_state_1.getInitialAgentState)(projectFileContext);
86
89
  const { knowledgeFiles } = projectFileContext;
87
90
  this.fileContext = projectFileContext;
88
91
  this.fileVersions = [
@@ -114,7 +117,6 @@ class Client {
114
117
  }
115
118
  async handleReferralCode(referralCode) {
116
119
  if (this.user) {
117
- // User is logged in, so attempt to redeem referral code directly
118
120
  try {
119
121
  const redeemReferralResp = await fetch(`${process.env.NEXT_PUBLIC_APP_URL}/api/referrals`, {
120
122
  method: 'POST',
@@ -151,29 +153,122 @@ class Client {
151
153
  }
152
154
  async logout() {
153
155
  if (this.user) {
154
- // If there was an existing user, clear their existing state
155
- this.webSocket.sendAction({
156
- type: 'clear-auth-token',
157
- authToken: this.user.authToken,
158
- userId: this.user.id,
159
- fingerprintId: this.user.fingerprintId,
160
- fingerprintHash: this.user.fingerprintHash,
161
- });
162
- // attempt to delete credentials file
163
156
  try {
164
- fs.unlinkSync(credentials_1.CREDENTIALS_PATH);
165
- console.log(`Logged you out of your account (${this.user.name})`);
166
- this.user = undefined;
157
+ const response = await fetch(`${config_1.backendUrl}/api/auth/cli/logout`, {
158
+ method: 'POST',
159
+ headers: { 'Content-Type': 'application/json' },
160
+ body: JSON.stringify({
161
+ authToken: this.user.authToken,
162
+ userId: this.user.id,
163
+ fingerprintId: this.user.fingerprintId,
164
+ fingerprintHash: this.user.fingerprintHash,
165
+ }),
166
+ });
167
+ if (!response.ok) {
168
+ const error = await response.text();
169
+ console.error((0, picocolors_1.red)('Failed to log out: ' + error));
170
+ }
171
+ try {
172
+ fs.unlinkSync(credentials_1.CREDENTIALS_PATH);
173
+ console.log(`You (${this.user.name}) have been logged out.`);
174
+ this.user = undefined;
175
+ }
176
+ catch (error) {
177
+ console.error('Error removing credentials file:', error);
178
+ }
179
+ }
180
+ catch (error) {
181
+ console.error('Error during logout:', error);
167
182
  }
168
- catch (error) { }
169
183
  }
170
184
  }
171
185
  async login(referralCode) {
172
- this.webSocket.sendAction({
173
- type: 'login-code-request',
174
- fingerprintId: await this.getFingerprintId(),
175
- referralCode,
176
- });
186
+ if (this.user) {
187
+ console.log(`You are currently logged in as ${this.user.name}. Please enter "logout" first if you want to login as a different user.`);
188
+ this.returnControlToUser();
189
+ return;
190
+ }
191
+ try {
192
+ const response = await fetch(`${config_1.backendUrl}/api/auth/cli/code`, {
193
+ method: 'POST',
194
+ headers: { 'Content-Type': 'application/json' },
195
+ body: JSON.stringify({
196
+ fingerprintId: await this.getFingerprintId(),
197
+ referralCode,
198
+ }),
199
+ });
200
+ if (!response.ok) {
201
+ const error = await response.text();
202
+ console.error((0, picocolors_1.red)('Login code request failed: ' + error));
203
+ this.returnControlToUser();
204
+ return;
205
+ }
206
+ const { loginUrl, fingerprintHash } = await response.json();
207
+ const responseToUser = [
208
+ '\n',
209
+ `Press ${(0, picocolors_1.blue)('ENTER')} to open your browser and finish logging in...`,
210
+ ];
211
+ console.log(responseToUser.join('\n'));
212
+ let shouldRequestLogin = true;
213
+ this.rl.once('line', () => {
214
+ if (shouldRequestLogin) {
215
+ (0, child_process_1.spawn)(`open ${loginUrl}`, { shell: true });
216
+ console.log('Done. If nothing happened, copy and paste this link into your browser:');
217
+ console.log();
218
+ console.log((0, picocolors_1.blue)((0, picocolors_1.bold)((0, picocolors_1.underline)(loginUrl))));
219
+ }
220
+ });
221
+ const initialTime = Date.now();
222
+ const pollInterval = setInterval(async () => {
223
+ if (Date.now() - initialTime > 5 * 60 * 1000 && shouldRequestLogin) {
224
+ shouldRequestLogin = false;
225
+ console.log('Unable to login. Please try again by typing "login" in the terminal.');
226
+ this.returnControlToUser();
227
+ clearInterval(pollInterval);
228
+ return;
229
+ }
230
+ if (!shouldRequestLogin) {
231
+ clearInterval(pollInterval);
232
+ return;
233
+ }
234
+ try {
235
+ const statusResponse = await fetch(`${config_1.backendUrl}/api/auth/cli/status?fingerprintId=${await this.getFingerprintId()}&fingerprintHash=${fingerprintHash}`);
236
+ if (!statusResponse.ok) {
237
+ if (statusResponse.status !== 401) {
238
+ // Ignore 401s during polling
239
+ console.error('Error checking login status:', await statusResponse.text());
240
+ }
241
+ return;
242
+ }
243
+ const { user, message } = await statusResponse.json();
244
+ if (user) {
245
+ shouldRequestLogin = false;
246
+ this.user = user;
247
+ const credentialsPathDir = path_1.default.dirname(credentials_1.CREDENTIALS_PATH);
248
+ fs.mkdirSync(credentialsPathDir, { recursive: true });
249
+ fs.writeFileSync(credentials_1.CREDENTIALS_PATH, JSON.stringify({ default: user }));
250
+ const referralLink = `${process.env.NEXT_PUBLIC_APP_URL}/referrals`;
251
+ const responseToUser = [
252
+ 'Authentication successful! 🎉',
253
+ (0, picocolors_1.bold)(`Hey there, ${user.name}.`),
254
+ `Refer new users and earn ${constants_1.CREDITS_REFERRAL_BONUS} credits per month for each of them: ${(0, picocolors_1.blueBright)(referralLink)}`,
255
+ ];
256
+ console.log('\n' + responseToUser.join('\n'));
257
+ this.lastWarnedPct = 0;
258
+ (0, menu_1.displayGreeting)(this.costMode, null);
259
+ clearInterval(pollInterval);
260
+ this.returnControlToUser();
261
+ }
262
+ }
263
+ catch (error) {
264
+ console.error('Error checking login status:', error);
265
+ }
266
+ }, 5000);
267
+ }
268
+ catch (error) {
269
+ console.error('Error during login:', error);
270
+ this.returnControlToUser();
271
+ }
177
272
  }
178
273
  setUsage({ usage, limit, subscription_active, next_quota_reset, referralLink, session_credits_used, }) {
179
274
  this.usage = usage;
@@ -192,59 +287,13 @@ class Client {
192
287
  this.returnControlToUser();
193
288
  return;
194
289
  });
195
- this.webSocket.subscribe('tool-call', async (a) => {
196
- const { response, changes, changesAlreadyApplied, data, userInputId, addedFileVersions, resetFileVersions, } = a;
197
- if (userInputId !== this.currentUserInputId) {
198
- return;
199
- }
200
- if (resetFileVersions) {
201
- this.fileVersions = [addedFileVersions];
202
- }
203
- else {
204
- this.fileVersions.push(addedFileVersions);
205
- }
206
- const filesChanged = (0, lodash_1.uniq)(changes.map((change) => change.filePath));
207
- this.chatStorage.saveFilesChanged(filesChanged);
208
- // Stage files about to be changed if flag was set
209
- if (this.git === 'stage' && changes.length > 0) {
210
- const didStage = (0, git_1.stagePatches)((0, project_files_1.getProjectRoot)(), changes);
211
- if (didStage) {
212
- console.log((0, picocolors_1.green)('\nStaged previous changes'));
213
- }
214
- }
215
- (0, changes_1.applyChanges)((0, project_files_1.getProjectRoot)(), changes);
216
- const { id, name, input } = data;
217
- const currentChat = this.chatStorage.getCurrentChat();
218
- const messages = currentChat.messages;
219
- if (messages[messages.length - 1].role === 'assistant') {
220
- // Probably the last response from the assistant was cancelled and added immediately.
221
- return;
222
- }
223
- const assistantMessage = {
224
- role: 'assistant',
225
- content: response,
226
- };
227
- this.chatStorage.addMessage(this.chatStorage.getCurrentChat(), assistantMessage);
228
- const handler = tool_handlers_1.toolHandlers[name];
229
- if (handler) {
230
- const content = await handler(input, id);
231
- const toolResultMessage = {
232
- role: 'user',
233
- content: `${constants_1.TOOL_RESULT_MARKER}\n${content}`,
234
- };
235
- this.chatStorage.addMessage(this.chatStorage.getCurrentChat(), toolResultMessage);
236
- await this.sendUserInput([...changesAlreadyApplied, ...changes], userInputId);
237
- }
238
- else {
239
- console.error(`No handler found for tool: ${name}`);
240
- }
241
- });
242
290
  this.webSocket.subscribe('read-files', (a) => {
243
- const { filePaths } = a;
291
+ const { filePaths, requestId } = a;
244
292
  const files = (0, project_files_1.getFiles)(filePaths);
245
293
  this.webSocket.sendAction({
246
294
  type: 'read-files-response',
247
295
  files,
296
+ requestId,
248
297
  });
249
298
  });
250
299
  this.webSocket.subscribe('npm-version-status', (action) => {
@@ -253,100 +302,38 @@ class Client {
253
302
  console.warn((0, picocolors_1.yellow)(`\nThere's a new version of Codebuff! Please update to ensure proper functionality.\nUpdate now by running: npm install -g codebuff`));
254
303
  }
255
304
  });
256
- let shouldRequestLogin = true;
257
- this.webSocket.subscribe('login-code-response', async ({ loginUrl, fingerprintHash }) => {
258
- const responseToUser = [
259
- '\n',
260
- 'Press Enter to open the browser or visit:\n',
261
- (0, picocolors_1.bold)((0, picocolors_1.underline)((0, picocolors_1.blueBright)(loginUrl))),
262
- ];
263
- console.log(responseToUser.join('\n'));
264
- this.rl.on('line', () => {
265
- if (shouldRequestLogin) {
266
- (0, child_process_1.spawn)(`open ${loginUrl}`, {
267
- shell: true,
268
- });
269
- }
270
- });
271
- // call backend every few seconds to check if user has been created yet, using our fingerprintId, for up to 5 minutes
272
- const initialTime = Date.now();
273
- const handler = setInterval(async () => {
274
- if (Date.now() - initialTime > 60 * 1000 && shouldRequestLogin) {
275
- shouldRequestLogin = false;
276
- console.log('Unable to login. Please try again by typing "login" in the terminal.');
277
- clearInterval(handler);
278
- return;
279
- }
280
- if (!shouldRequestLogin) {
281
- clearInterval(handler);
282
- return;
283
- }
284
- this.webSocket.sendAction({
285
- type: 'login-status-request',
286
- fingerprintId: await this.getFingerprintId(),
287
- fingerprintHash,
288
- });
289
- }, 5000);
290
- });
291
- this.webSocket.subscribe('auth-result', async (action) => {
292
- shouldRequestLogin = false;
293
- if (action.user) {
294
- await this.logout(); // remove existing user, if it exists
295
- this.user = action.user;
296
- // Store in config file
297
- const credentialsPathDir = path_1.default.dirname(credentials_1.CREDENTIALS_PATH);
298
- fs.mkdirSync(credentialsPathDir, { recursive: true });
299
- fs.writeFileSync(credentials_1.CREDENTIALS_PATH, JSON.stringify({ default: action.user }));
300
- const referralLink = `${process.env.NEXT_PUBLIC_APP_URL}/referrals`;
301
- const responseToUser = [
302
- 'Authentication successful! 🎉',
303
- (0, picocolors_1.bold)(`Hey there, ${action.user.name}.`),
304
- `Refer new users and earn ${constants_1.CREDITS_REFERRAL_BONUS} credits per month for each of them: ${(0, picocolors_1.blueBright)(referralLink)}`,
305
- ];
306
- console.log('\n' + responseToUser.join('\n'));
307
- this.lastWarnedPct = 0;
308
- (0, menu_1.displayGreeting)(this.costMode, null);
309
- this.returnControlToUser();
310
- // this.getUsage()
311
- }
312
- else {
313
- console.warn(`Authentication failed: ${action.message}. Please try again in a few minutes or contact support at ${process.env.NEXT_PUBLIC_SUPPORT_EMAIL}.`);
314
- }
315
- });
316
305
  this.webSocket.subscribe('usage-response', (action) => {
306
+ this.returnControlToUser();
317
307
  const parsedAction = actions_1.UsageReponseSchema.safeParse(action);
318
308
  if (!parsedAction.success)
319
309
  return;
320
310
  const a = parsedAction.data;
321
- console.log(`Usage: ${a.usage} / ${a.limit} credits`);
311
+ console.log();
312
+ console.log((0, picocolors_1.green)((0, picocolors_1.underline)(`Codebuff usage:`)), `${a.usage} / ${a.limit} credits`);
322
313
  this.setUsage(a);
323
- this.returnControlToUser();
314
+ this.showUsageWarning(a.referralLink);
324
315
  });
325
316
  }
326
317
  showUsageWarning(referralLink) {
327
318
  const errorCopy = [
328
319
  this.user
329
- ? (0, picocolors_1.green)(`Visit ${process.env.NEXT_PUBLIC_APP_URL}/pricing to upgrade.`)
320
+ ? `Visit ${(0, picocolors_1.blue)((0, picocolors_1.bold)(process.env.NEXT_PUBLIC_APP_URL + '/pricing'))} to upgrade – or refer a new user and earn ${constants_1.CREDITS_REFERRAL_BONUS} credits per month: ${(0, picocolors_1.blue)((0, picocolors_1.bold)(referralLink))}`
330
321
  : (0, picocolors_1.green)('Type "login" below to sign up and get more credits!'),
331
- referralLink
332
- ? (0, picocolors_1.green)(`Refer friends by sharing this link and you'll ${(0, picocolors_1.bold)(`each earn ${constants_1.CREDITS_REFERRAL_BONUS} credits per month`)}: ${referralLink}`)
333
- : '',
334
322
  ].join('\n');
335
323
  const pct = (0, ts_pattern_1.match)(Math.floor((this.usage / this.limit) * 100))
336
324
  .with(ts_pattern_1.P.number.gte(100), () => 100)
337
325
  .with(ts_pattern_1.P.number.gte(75), () => 75)
338
326
  .otherwise(() => 0);
339
- // User has used all their allotted credits, but they haven't been notified yet
340
- if (pct >= 100 && this.lastWarnedPct < 100) {
341
- if (this.subscription_active) {
327
+ if (pct >= 100) {
328
+ this.lastWarnedPct = 100;
329
+ if (!this.subscription_active) {
330
+ console.error([(0, picocolors_1.red)('You have reached your monthly usage limit.'), errorCopy].join('\n'));
331
+ return;
332
+ }
333
+ if (this.subscription_active && this.lastWarnedPct < 100) {
342
334
  console.warn((0, picocolors_1.yellow)(`You have exceeded your monthly quota, but feel free to keep using Codebuff! We'll continue to charge you for the overage until your next billing cycle. See ${process.env.NEXT_PUBLIC_APP_URL}/usage for more details.`));
343
- this.lastWarnedPct = 100;
344
335
  return;
345
336
  }
346
- console.error([(0, picocolors_1.red)('You have reached your monthly usage limit.'), errorCopy].join('\n'));
347
- this.returnControlToUser();
348
- this.lastWarnedPct = 100;
349
- return;
350
337
  }
351
338
  if (pct > 0 && pct > this.lastWarnedPct) {
352
339
  console.warn([
@@ -371,47 +358,69 @@ class Client {
371
358
  });
372
359
  });
373
360
  }
374
- async sendUserInput(previousChanges, userInputId) {
375
- this.currentUserInputId = userInputId;
376
- const currentChat = this.chatStorage.getCurrentChat();
377
- const { messages, fileVersions: messageFileVersions } = currentChat;
378
- const currentFileVersion = messageFileVersions[messageFileVersions.length - 1]?.files ?? {};
379
- const fileContext = await (0, project_files_1.getProjectFileContext)((0, project_files_1.getProjectRoot)(), currentFileVersion, this.fileVersions);
380
- this.fileContext = fileContext;
361
+ async sendUserInput(prompt) {
362
+ if (!this.agentState) {
363
+ throw new Error('Agent state not initialized');
364
+ }
365
+ const userInputId = `mc-input-` + Math.random().toString(36).substring(2, 15);
366
+ const { responsePromise, stopResponse } = this.subscribeToResponse((chunk) => {
367
+ spinner_1.Spinner.get().stop();
368
+ process.stdout.write(chunk);
369
+ }, userInputId, () => {
370
+ spinner_1.Spinner.get().stop();
371
+ process.stdout.write((0, picocolors_1.green)((0, picocolors_1.underline)('\nCodebuff') + ':') + ' ');
372
+ }, prompt);
373
+ spinner_1.Spinner.get().start();
381
374
  this.webSocket.sendAction({
382
- type: 'user-input',
383
- userInputId,
384
- messages,
385
- fileContext,
386
- changesAlreadyApplied: previousChanges,
375
+ type: 'prompt',
376
+ promptId: userInputId,
377
+ prompt,
378
+ agentState: this.agentState,
379
+ toolResults: [],
387
380
  fingerprintId: await this.getFingerprintId(),
388
381
  authToken: this.user?.authToken,
389
382
  costMode: this.costMode,
390
383
  });
384
+ return {
385
+ responsePromise,
386
+ stopResponse,
387
+ };
391
388
  }
392
- subscribeToResponse(onChunk, userInputId, onStreamStart) {
389
+ subscribeToResponse(onChunk, userInputId, onStreamStart, prompt) {
393
390
  let responseBuffer = '';
391
+ let streamStarted = false;
394
392
  let resolveResponse;
395
393
  let rejectResponse;
396
394
  let unsubscribeChunks;
397
395
  let unsubscribeComplete;
398
- let streamStarted = false;
396
+ // Initialize XML processor with default handlers
397
+ const xmlProcessor = new process_xml_chunks_1.XmlStreamProcessor(process_xml_chunks_1.defaultTagHandlers);
399
398
  const responsePromise = new Promise((resolve, reject) => {
400
399
  resolveResponse = resolve;
401
400
  rejectResponse = reject;
402
401
  });
403
402
  const stopResponse = () => {
404
- this.currentUserInputId = undefined;
405
403
  unsubscribeChunks();
406
404
  unsubscribeComplete();
405
+ // Update the agent state with your prompt and partial response.
406
+ const { messageHistory } = this.agentState;
407
+ this.agentState = {
408
+ ...this.agentState,
409
+ messageHistory: [
410
+ ...messageHistory,
411
+ { role: 'user', content: prompt },
412
+ {
413
+ role: 'assistant',
414
+ content: responseBuffer + '[RESPONSE_CANCELED_BY_USER]',
415
+ },
416
+ ],
417
+ };
407
418
  resolveResponse({
408
- userInputId,
409
- response: responseBuffer + '\n[RESPONSE_STOPPED_BY_USER]',
410
- changes: [],
411
- changesAlreadyApplied: [],
412
- addedFileVersions: [],
413
- resetFileVersions: false,
414
- type: 'response-complete',
419
+ type: 'prompt-response',
420
+ promptId: userInputId,
421
+ agentState: this.agentState,
422
+ toolCalls: [],
423
+ toolResults: [],
415
424
  wasStoppedByUser: true,
416
425
  });
417
426
  };
@@ -419,28 +428,74 @@ class Client {
419
428
  if (a.userInputId !== userInputId)
420
429
  return;
421
430
  const { chunk } = a;
422
- if (!streamStarted && chunk.trim()) {
423
- streamStarted = true;
424
- onStreamStart();
425
- }
431
+ // Add the chunk to the response buffer
426
432
  responseBuffer += chunk;
427
- onChunk(chunk);
433
+ // Process the entire responseBuffer through our XML processor
434
+ const output = chunk; // TODO: figure out xml parsing: xmlProcessor.process(chunk)
435
+ if (output && output.trim()) {
436
+ if (!streamStarted && chunk.trim()) {
437
+ streamStarted = true;
438
+ onStreamStart();
439
+ }
440
+ onChunk(output);
441
+ }
428
442
  });
429
- unsubscribeComplete = this.webSocket.subscribe('response-complete', (action) => {
430
- const parsedAction = actions_1.ResponseCompleteSchema.safeParse(action);
431
- if (!parsedAction.success || action.userInputId !== userInputId)
443
+ unsubscribeComplete = this.webSocket.subscribe('prompt-response', async (action) => {
444
+ const parsedAction = actions_1.PromptResponseSchema.safeParse(action);
445
+ if (!parsedAction.success || action.promptId !== userInputId)
432
446
  return;
433
447
  const a = parsedAction.data;
434
- unsubscribeChunks();
435
- unsubscribeComplete();
436
- if (a.resetFileVersions) {
437
- this.fileVersions = [a.addedFileVersions];
448
+ this.agentState = a.agentState;
449
+ spinner_1.Spinner.get().stop();
450
+ let isComplete = false;
451
+ const toolResults = [...a.toolResults];
452
+ for (const toolCall of a.toolCalls) {
453
+ if (toolCall.name === 'end_turn') {
454
+ isComplete = true;
455
+ continue;
456
+ }
457
+ if (toolCall.name === 'write_file') {
458
+ // Save the file contents before writing
459
+ const { path: filePath } = toolCall.parameters;
460
+ if (filePath !== undefined) {
461
+ const fullPath = path_1.default.join((0, project_files_1.getProjectRoot)(), filePath);
462
+ if (!(fullPath in this.originalFileVersions)) {
463
+ this.originalFileVersions[fullPath] = fs.existsSync(fullPath)
464
+ ? fs.readFileSync(fullPath, 'utf8')
465
+ : null;
466
+ }
467
+ }
468
+ this.lastChanges.push(actions_1.FileChangeSchema.parse(toolCall.parameters));
469
+ this.hadFileChanges = true;
470
+ }
471
+ const toolResult = await (0, tool_handlers_1.handleToolCall)(toolCall, (0, project_files_1.getProjectRoot)());
472
+ toolResults.push(toolResult);
473
+ }
474
+ if (!isComplete) {
475
+ spinner_1.Spinner.get().start();
476
+ // Continue the prompt with the tool results.
477
+ this.webSocket.sendAction({
478
+ type: 'prompt',
479
+ promptId: userInputId,
480
+ prompt: undefined,
481
+ agentState: this.agentState,
482
+ toolResults,
483
+ fingerprintId: await this.getFingerprintId(),
484
+ authToken: this.user?.authToken,
485
+ costMode: this.costMode,
486
+ });
487
+ return;
438
488
  }
439
- else {
440
- this.fileVersions.push(a.addedFileVersions);
489
+ if (this.hadFileChanges) {
490
+ const latestCheckpointId = checkpoints_1.checkpointManager.getLatestCheckpoint().id;
491
+ console.log(`\nComplete! Type "diff" to review changes or "restore ${latestCheckpointId}" to revert.`);
492
+ this.hadFileChanges = false;
441
493
  }
494
+ // Reset the XML processor state when the stream is complete
495
+ xmlProcessor.reset();
496
+ unsubscribeChunks();
497
+ unsubscribeComplete();
442
498
  resolveResponse({ ...a, wasStoppedByUser: false });
443
- this.currentUserInputId = undefined;
444
499
  if (!a.usage ||
445
500
  !a.next_quota_reset ||
446
501
  a.subscription_active === undefined ||
@@ -454,7 +509,7 @@ class Client {
454
509
  next_quota_reset: a.next_quota_reset,
455
510
  session_credits_used: a.session_credits_used ?? 0,
456
511
  });
457
- // Indicates a change in the user's plan
512
+ this.showUsageWarning(a.referralLink);
458
513
  if (this.limit !== a.limit) {
459
514
  this.lastWarnedPct = 0;
460
515
  }
@@ -473,7 +528,6 @@ class Client {
473
528
  }
474
529
  async warmContextCache() {
475
530
  const fileContext = await (0, project_files_1.getProjectFileContext)((0, project_files_1.getProjectRoot)(), {}, this.fileVersions);
476
- // Don't wait for response anymore.
477
531
  this.webSocket.subscribe('init-response', (a) => {
478
532
  const parsedAction = actions_1.InitResponseSchema.safeParse(a);
479
533
  if (!parsedAction.success)