protoagent 0.0.1 → 0.0.3

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.
@@ -4,6 +4,7 @@ import { generateSystemPrompt } from './config/system-prompt.js';
4
4
  import { getModelConfig } from './config/providers.js';
5
5
  import { estimateTokens, createUsageInfo, logUsageInfo, getContextInfo } from './utils/cost-tracker.js';
6
6
  import { checkAndCompactIfNeeded } from './utils/conversation-compactor.js';
7
+ import { logger } from './utils/logger.js';
7
8
  // Create system message for ProtoAgent dynamically
8
9
  async function createSystemMessage() {
9
10
  const systemPrompt = await generateSystemPrompt();
@@ -27,8 +28,13 @@ export class AgenticLoop {
27
28
  * Initialize the agentic loop with dynamic system message
28
29
  */
29
30
  async initialize() {
31
+ logger.debug('šŸš€ Initializing agentic loop', { component: 'AgenticLoop' });
30
32
  this.systemMessage = await createSystemMessage();
31
33
  this.messages = [this.systemMessage];
34
+ logger.debug('āœ… Agentic loop initialized', {
35
+ component: 'AgenticLoop',
36
+ systemMessageLength: this.systemMessage.content?.toString().length || 0
37
+ });
32
38
  }
33
39
  /**
34
40
  * Get the current conversation history
@@ -52,28 +58,67 @@ export class AgenticLoop {
52
58
  * Process a user input and run the agentic loop
53
59
  */
54
60
  async processUserInput(userInput) {
61
+ logger.debug('šŸ“„ Processing user input', {
62
+ component: 'AgenticLoop',
63
+ inputLength: userInput.length,
64
+ currentMessageCount: this.messages.length
65
+ });
55
66
  try {
56
67
  // Add user message to conversation history
68
+ logger.debug('āž• Adding user message to conversation', { component: 'AgenticLoop' });
57
69
  this.addMessage({
58
70
  role: 'user',
59
71
  content: userInput
60
72
  });
61
- console.log('šŸ¤” Thinking...');
73
+ logger.consoleLog('šŸ¤” Thinking...');
74
+ logger.info('šŸ¤” AI thinking process started');
75
+ logger.debug('🧠 Starting thinking process', {
76
+ component: 'AgenticLoop',
77
+ totalMessages: this.messages.length,
78
+ maxIterations: this.options.maxIterations
79
+ });
62
80
  // Start the agentic loop
63
81
  let continueProcessing = true;
64
82
  let iterationCount = 0;
65
83
  while (continueProcessing && iterationCount < this.options.maxIterations) {
66
84
  iterationCount++;
85
+ logger.trace(`šŸ”„ Starting agentic loop iteration ${iterationCount}/${this.options.maxIterations}`, {
86
+ component: 'AgenticLoop',
87
+ iteration: iterationCount,
88
+ messageCount: this.messages.length,
89
+ conversationTokensApprox: this.messages.reduce((sum, msg) => sum + (typeof msg.content === 'string' ? msg.content.length / 4 : 0), 0)
90
+ });
67
91
  try {
68
92
  // Check if conversation needs compaction before making API call
93
+ logger.debug('šŸ” Checking context window usage', { component: 'AgenticLoop', iteration: iterationCount });
69
94
  const modelConfig = getModelConfig(this.config.provider, this.config.model);
70
95
  if (modelConfig) {
71
96
  const contextInfo = getContextInfo(this.messages, modelConfig);
97
+ logger.debug('šŸ“Š Context analysis complete', {
98
+ component: 'AgenticLoop',
99
+ currentTokens: contextInfo.currentTokens,
100
+ maxTokens: modelConfig.contextWindow,
101
+ needsCompaction: contextInfo.needsCompaction,
102
+ percentageUsed: ((contextInfo.currentTokens / modelConfig.contextWindow) * 100).toFixed(1)
103
+ });
72
104
  if (contextInfo.needsCompaction) {
73
- console.log('\nšŸ—œļø Context window approaching limit, compacting conversation...');
105
+ logger.consoleLog('\nšŸ—œļø Context window approaching limit, compacting conversation...');
106
+ logger.info('šŸ—œļø Context compaction message displayed to user');
107
+ logger.debug('šŸ—œļø Starting conversation compaction', { component: 'AgenticLoop' });
74
108
  this.messages = await checkAndCompactIfNeeded(this.openaiClient, this.config.model, this.messages, modelConfig.contextWindow, contextInfo.currentTokens);
109
+ logger.debug('āœ… Conversation compaction complete', {
110
+ component: 'AgenticLoop',
111
+ newMessageCount: this.messages.length
112
+ });
75
113
  }
76
114
  }
115
+ logger.debug('🌐 Creating chat completion request', {
116
+ component: 'AgenticLoop',
117
+ model: this.config.model,
118
+ messageCount: this.messages.length,
119
+ toolsCount: tools.length,
120
+ streaming: true
121
+ });
77
122
  // Create completion using OpenAI with built-in retry logic and cost tracking
78
123
  const { stream, estimatedInputTokens } = await createChatCompletion(this.openaiClient, {
79
124
  model: this.config.model,
@@ -82,7 +127,13 @@ export class AgenticLoop {
82
127
  tool_choice: 'auto',
83
128
  stream: true
84
129
  }, this.config, this.messages);
130
+ logger.debug('šŸ“” Chat completion stream created', {
131
+ component: 'AgenticLoop',
132
+ estimatedInputTokens,
133
+ iteration: iterationCount
134
+ });
85
135
  // Collect the streamed response
136
+ logger.trace('šŸ“Ø Starting to collect streamed response', { component: 'AgenticLoop' });
86
137
  let assistantMessage = {
87
138
  role: 'assistant',
88
139
  content: '',
@@ -91,10 +142,25 @@ export class AgenticLoop {
91
142
  let streamedContent = '';
92
143
  let hasToolCalls = false;
93
144
  let actualUsage = undefined;
145
+ let chunkCount = 0;
94
146
  for await (const chunk of stream) {
147
+ chunkCount++;
95
148
  const delta = chunk.choices[0]?.delta;
149
+ logger.trace('šŸ“¦ Processing chunk', {
150
+ component: 'AgenticLoop',
151
+ chunkNumber: chunkCount,
152
+ hasContent: !!delta?.content,
153
+ hasToolCalls: !!delta?.tool_calls,
154
+ finishReason: chunk.choices[0]?.finish_reason
155
+ });
96
156
  if (chunk.usage) {
97
157
  actualUsage = chunk.usage;
158
+ logger.trace('šŸ“Š Received usage data', {
159
+ component: 'AgenticLoop',
160
+ promptTokens: chunk.usage.prompt_tokens,
161
+ completionTokens: chunk.usage.completion_tokens,
162
+ totalTokens: chunk.usage.total_tokens
163
+ });
98
164
  }
99
165
  if (delta?.content) {
100
166
  streamedContent += delta.content;
@@ -103,13 +169,17 @@ export class AgenticLoop {
103
169
  if (this.options.streamOutput && !hasToolCalls && !delta.tool_calls) {
104
170
  if (streamedContent === delta.content) {
105
171
  // First content chunk
106
- process.stdout.write('\\nšŸ¤– ProtoAgent: ');
172
+ process.stdout.write('\nšŸ¤– ProtoAgent: ');
107
173
  }
108
174
  process.stdout.write(delta.content);
109
175
  }
110
176
  }
111
177
  if (delta?.tool_calls) {
112
178
  hasToolCalls = true;
179
+ logger.trace('šŸ”§ Tool calls detected in delta', {
180
+ component: 'AgenticLoop',
181
+ toolCallsCount: delta.tool_calls.length
182
+ });
113
183
  // Initialize tool_calls array if not exists
114
184
  if (!assistantMessage.tool_calls) {
115
185
  assistantMessage.tool_calls = [];
@@ -117,6 +187,13 @@ export class AgenticLoop {
117
187
  // Handle tool calls in streaming
118
188
  for (const toolCallDelta of delta.tool_calls) {
119
189
  const index = toolCallDelta.index || 0;
190
+ logger.trace('šŸ› ļø Processing tool call delta', {
191
+ component: 'AgenticLoop',
192
+ index,
193
+ hasId: !!toolCallDelta.id,
194
+ hasName: !!toolCallDelta.function?.name,
195
+ hasArgs: !!toolCallDelta.function?.arguments
196
+ });
120
197
  // Ensure we have an entry at this index
121
198
  if (!assistantMessage.tool_calls[index]) {
122
199
  assistantMessage.tool_calls[index] = {
@@ -137,7 +214,63 @@ export class AgenticLoop {
137
214
  }
138
215
  }
139
216
  }
217
+ logger.debug('āœ… Stream processing complete', {
218
+ component: 'AgenticLoop',
219
+ totalChunks: chunkCount,
220
+ finalContentLength: streamedContent.length,
221
+ finalToolCallsCount: assistantMessage.tool_calls?.length || 0,
222
+ hasActualUsage: !!actualUsage
223
+ });
140
224
  const message = assistantMessage;
225
+ // DEBUG: Log the complete AI response
226
+ logger.debug('šŸ¤– AI Response received', {
227
+ component: 'AgenticLoop',
228
+ iteration: iterationCount,
229
+ responseType: message.tool_calls?.length > 0 ? 'TOOL_CALLS' : 'TEXT_RESPONSE',
230
+ contentLength: message.content?.length || 0,
231
+ contentPreview: message.content ? message.content.substring(0, 200) + (message.content.length > 200 ? '...' : '') : null,
232
+ rawMessage: JSON.stringify(message),
233
+ toolCallsCount: message.tool_calls?.length || 0,
234
+ toolNames: message.tool_calls?.map((tc) => tc.function.name) || [],
235
+ toolCallsDetails: message.tool_calls?.map((tc) => ({
236
+ id: tc.id,
237
+ name: tc.function.name,
238
+ argsLength: tc.function.arguments?.length || 0,
239
+ argsPreview: tc.function.arguments ? tc.function.arguments.substring(0, 100) + (tc.function.arguments.length > 100 ? '...' : '') : null
240
+ })) || []
241
+ });
242
+ // TRACE: Log the AI's decision after thinking
243
+ logger.trace('🧠 AI thinking complete - analyzing response...', {
244
+ component: 'AgenticLoop',
245
+ iteration: iterationCount,
246
+ hasToolCalls: !!(message.tool_calls && message.tool_calls.length > 0),
247
+ hasContent: !!(message.content && message.content.trim().length > 0),
248
+ toolCount: message.tool_calls?.length || 0,
249
+ contentLength: message.content?.length || 0
250
+ });
251
+ if (message.tool_calls && message.tool_calls.length > 0) {
252
+ logger.trace('šŸ”§ AI decided to use tools', {
253
+ component: 'AgenticLoop',
254
+ decision: 'TOOL_CALLS',
255
+ tools: message.tool_calls.map((tc) => tc.function.name).join(', '),
256
+ reasoning: 'AI determined that tool usage is needed to complete the task'
257
+ });
258
+ }
259
+ else if (message.content && message.content.trim().length > 0) {
260
+ logger.trace('šŸ’¬ AI decided to provide direct response', {
261
+ component: 'AgenticLoop',
262
+ decision: 'DIRECT_RESPONSE',
263
+ responsePreview: message.content.slice(0, 100) + (message.content.length > 100 ? '...' : ''),
264
+ reasoning: 'AI determined that a direct text response is sufficient'
265
+ });
266
+ }
267
+ else {
268
+ logger.trace('āš ļø AI provided empty response', {
269
+ component: 'AgenticLoop',
270
+ decision: 'EMPTY_RESPONSE',
271
+ reasoning: 'AI returned neither tool calls nor content - this may indicate an issue'
272
+ });
273
+ }
141
274
  // Calculate and log cost information
142
275
  if (modelConfig) {
143
276
  const finalInputTokens = actualUsage?.prompt_tokens ?? estimatedInputTokens;
@@ -148,16 +281,50 @@ export class AgenticLoop {
148
281
  }
149
282
  // Check if AI wants to use tools
150
283
  if (message.tool_calls && message.tool_calls.length > 0) {
284
+ logger.debug('šŸ”§ AI requested tool usage', {
285
+ component: 'AgenticLoop',
286
+ toolCount: message.tool_calls.length,
287
+ toolNames: message.tool_calls.map((tc) => tc.function.name)
288
+ });
151
289
  // Add the AI's message (with tool calls) to conversation
152
290
  this.addMessage(message);
153
- console.log(`šŸ”§ Using ${message.tool_calls.length} tool(s)...`);
291
+ logger.debug('āž• AI message with tool calls added to conversation', {
292
+ component: 'AgenticLoop',
293
+ messageRole: 'assistant',
294
+ hasContent: !!message.content,
295
+ contentLength: message.content?.length || 0,
296
+ toolCallsCount: message.tool_calls?.length || 0,
297
+ conversationLength: this.messages.length
298
+ });
299
+ logger.consoleLog(`šŸ”§ Using ${message.tool_calls.length} tool(s)...`);
300
+ logger.info(`šŸ”§ Tool usage message displayed: ${message.tool_calls.length} tools`);
154
301
  // Execute each tool call
155
302
  for (const toolCall of message.tool_calls) {
156
303
  const { name, arguments: args } = toolCall.function;
157
- console.log(`šŸ› ļø ${name}`);
304
+ logger.debug('šŸ› ļø Executing tool', {
305
+ component: 'AgenticLoop',
306
+ toolName: name,
307
+ toolId: toolCall.id,
308
+ argsLength: args.length
309
+ });
310
+ logger.consoleLog(`šŸ› ļø ${name}`);
311
+ logger.info(`šŸ› ļø Tool execution message displayed: ${name}`);
158
312
  try {
159
313
  const toolArgs = JSON.parse(args);
314
+ logger.debug('šŸ“‹ Tool arguments parsed', {
315
+ component: 'AgenticLoop',
316
+ toolName: name,
317
+ parsedArgs: Object.keys(toolArgs)
318
+ });
319
+ const startTime = Date.now();
160
320
  const result = await handleToolCall(name, toolArgs);
321
+ const executionTime = Date.now() - startTime;
322
+ logger.debug('āœ… Tool execution successful', {
323
+ component: 'AgenticLoop',
324
+ toolName: name,
325
+ executionTime,
326
+ resultLength: result.length
327
+ });
161
328
  // Add tool result to conversation
162
329
  this.addMessage({
163
330
  role: 'tool',
@@ -165,17 +332,25 @@ export class AgenticLoop {
165
332
  content: result
166
333
  });
167
334
  // Show abbreviated result to user
168
- const lines = result.split('\\n');
335
+ const lines = result.split('\n');
169
336
  if (lines.length > 10) {
170
- console.log(` āœ… ${lines.slice(0, 3).join('\\n ')}\\n ... (${lines.length - 6} more lines) ...\\n ${lines.slice(-3).join('\\n ')}`);
337
+ logger.consoleLog(` āœ… ${lines.slice(0, 3).join('\n ')}\n ... (${lines.length - 6} more lines) ...\n ${lines.slice(-3).join('\n ')}`);
338
+ logger.info(` āœ… Tool result displayed (abbreviated, ${lines.length} lines)`);
171
339
  }
172
340
  else {
173
- console.log(` āœ… ${result.slice(0, 200)}${result.length > 200 ? '...' : ''}`);
341
+ logger.consoleLog(` āœ… ${result.slice(0, 200)}${result.length > 200 ? '...' : ''}`);
342
+ logger.info(` āœ… Tool result displayed (full, ${result.length} chars)`);
174
343
  }
175
344
  }
176
345
  catch (error) {
177
346
  const errorMessage = error instanceof Error ? error.message : String(error);
178
- console.log(` āŒ Error: ${errorMessage}`);
347
+ logger.error('āŒ Tool execution failed', {
348
+ component: 'AgenticLoop',
349
+ toolName: name,
350
+ error: errorMessage
351
+ });
352
+ logger.consoleLog(` āŒ Error: ${errorMessage}`);
353
+ logger.error(` āŒ Tool error message displayed: ${errorMessage}`);
179
354
  // Add error result to conversation
180
355
  this.addMessage({
181
356
  role: 'tool',
@@ -185,6 +360,12 @@ export class AgenticLoop {
185
360
  }
186
361
  }
187
362
  // Continue the loop to let AI process tool results
363
+ logger.trace('šŸ”„ Continuing agentic loop for AI to process tool results', {
364
+ component: 'AgenticLoop',
365
+ decision: 'CONTINUE_PROCESSING',
366
+ reasoning: 'Tools were executed, AI needs another thinking cycle to process results',
367
+ nextIteration: iterationCount + 1
368
+ });
188
369
  continue;
189
370
  }
190
371
  else {
@@ -192,78 +373,130 @@ export class AgenticLoop {
192
373
  if (message.content && !hasToolCalls) {
193
374
  // Content was already streamed to user during the loop above
194
375
  if (this.options.streamOutput) {
195
- console.log('\\n'); // Add newline after streaming is complete
376
+ logger.consoleLog('\n'); // Add newline after streaming is complete
377
+ logger.debug('\\n AI response streaming completed');
196
378
  }
197
379
  else {
198
- console.log(`\\nšŸ¤– ProtoAgent: ${message.content}\\n`);
380
+ logger.consoleLog(`\nšŸ¤– ProtoAgent: ${message.content}\n`);
381
+ logger.info('šŸ¤– AI response displayed to user (non-streaming)');
199
382
  }
200
383
  // Add AI response to conversation history
201
384
  this.addMessage({
202
385
  role: 'assistant',
203
386
  content: message.content
204
387
  });
388
+ logger.debug('āž• AI text response added to conversation', {
389
+ component: 'AgenticLoop',
390
+ messageRole: 'assistant',
391
+ contentLength: message.content?.length || 0,
392
+ conversationLength: this.messages.length
393
+ });
205
394
  }
206
395
  else if (message.content && hasToolCalls) {
207
396
  // AI provided content along with tool calls (rare case)
208
397
  if (this.options.streamOutput) {
209
- process.stdout.write('\\nšŸ¤– ProtoAgent: ');
398
+ process.stdout.write('\nšŸ¤– ProtoAgent: ');
210
399
  process.stdout.write(message.content);
211
- console.log('\\n');
400
+ logger.consoleLog('\n');
401
+ logger.info('šŸ¤– AI response with tool calls displayed (streaming)');
212
402
  }
213
403
  else {
214
- console.log(`\\nšŸ¤– ProtoAgent: ${message.content}\\n`);
404
+ logger.consoleLog(`\nšŸ¤– ProtoAgent: ${message.content}\n`);
405
+ logger.info('šŸ¤– AI response with tool calls displayed (non-streaming)');
215
406
  }
216
407
  // Add AI response to conversation history
217
408
  this.addMessage({
218
409
  role: 'assistant',
219
410
  content: message.content
220
411
  });
412
+ logger.debug('āž• AI mixed response (content + tool calls) added to conversation', {
413
+ component: 'AgenticLoop',
414
+ messageRole: 'assistant',
415
+ contentLength: message.content?.length || 0,
416
+ conversationLength: this.messages.length
417
+ });
221
418
  }
419
+ logger.trace('āœ… Agentic loop complete - stopping processing', {
420
+ component: 'AgenticLoop',
421
+ decision: 'STOP_PROCESSING',
422
+ reasoning: 'AI provided final response without tool calls',
423
+ totalIterations: iterationCount
424
+ });
222
425
  continueProcessing = false;
223
426
  }
224
427
  }
225
428
  catch (apiError) {
226
429
  // Handle API errors that weren't caught by the retry logic
227
- console.error('\\nāŒ API Error:', apiError?.message || 'Unknown API error');
430
+ logger.trace('āŒ API Error occurred - stopping agentic loop', {
431
+ component: 'AgenticLoop',
432
+ decision: 'STOP_ON_API_ERROR',
433
+ errorStatus: apiError?.status,
434
+ errorMessage: apiError?.message,
435
+ reasoning: 'Unrecoverable API error encountered'
436
+ });
437
+ console.error('\nāŒ API Error:', apiError?.message || 'Unknown API error');
438
+ logger.error('\nāŒ API Error message displayed to user', {
439
+ component: 'AgenticLoop',
440
+ error: apiError?.message || 'Unknown API error'
441
+ });
228
442
  // Check for specific error types and provide helpful messages
229
443
  if (apiError?.status === 401) {
230
- console.log('šŸ’” Authentication failed. Your API key may be invalid or expired.');
231
- console.log(' Run: protoagent config --update-key');
444
+ logger.consoleLog('šŸ’” Authentication failed. Your API key may be invalid or expired.');
445
+ logger.consoleLog(' Run: protoagent config --update-key');
446
+ logger.info('šŸ’” Auth error help message displayed to user');
232
447
  }
233
448
  else if (apiError?.status === 403) {
234
- console.log('šŸ’” Access forbidden. Check your API key permissions or billing status.');
449
+ logger.consoleLog('šŸ’” Access forbidden. Check your API key permissions or billing status.');
450
+ logger.info('šŸ’” Access forbidden help message displayed to user');
235
451
  }
236
452
  else if (apiError?.status === 400) {
237
- console.log('šŸ’” Bad request. There may be an issue with the request format.');
238
- console.log(' This could be a bug in ProtoAgent. Please check for updates.');
453
+ logger.consoleLog('šŸ’” Bad request. There may be an issue with the request format.');
454
+ logger.consoleLog(' This could be a bug in ProtoAgent. Please check for updates.');
455
+ logger.info('šŸ’” Bad request help message displayed to user');
239
456
  }
240
457
  else {
241
- console.log('šŸ’” An unexpected API error occurred. Please try again.');
458
+ logger.consoleLog('šŸ’” An unexpected API error occurred. Please try again.');
459
+ logger.info('šŸ’” Generic API error help message displayed to user');
242
460
  }
243
461
  // Exit the processing loop for API errors
244
462
  break;
245
463
  }
246
464
  }
247
465
  if (iterationCount >= this.options.maxIterations) {
248
- console.log('\\nāš ļø Maximum iteration limit reached. Task may be incomplete.');
466
+ logger.trace('āš ļø Maximum iteration limit reached - stopping agentic loop', {
467
+ component: 'AgenticLoop',
468
+ decision: 'STOP_ON_MAX_ITERATIONS',
469
+ maxIterations: this.options.maxIterations,
470
+ reasoning: 'Reached maximum allowed iterations to prevent infinite loops'
471
+ });
472
+ logger.consoleLog('\nāš ļø Maximum iteration limit reached. Task may be incomplete.');
473
+ logger.warn('āš ļø Max iteration warning displayed to user');
249
474
  }
250
475
  }
251
476
  catch (error) {
252
477
  // Handle general processing errors
253
- console.error('\\nāŒ Error during processing:', error?.message || 'Unknown error');
478
+ console.error('\nāŒ Error during processing:', error?.message || 'Unknown error');
479
+ logger.error('\nāŒ General processing error displayed to user', {
480
+ component: 'AgenticLoop',
481
+ error: error?.message || 'Unknown error'
482
+ });
254
483
  // Provide helpful error messages for common issues
255
484
  if (error?.message?.includes('API key')) {
256
- console.log('šŸ’” There seems to be an issue with your API key configuration.');
257
- console.log(' Run: protoagent config --show');
485
+ logger.consoleLog('šŸ’” There seems to be an issue with your API key configuration.');
486
+ logger.consoleLog(' Run: protoagent config --show');
487
+ logger.info('šŸ’” API key error help message displayed');
258
488
  }
259
489
  else if (error?.message?.includes('model')) {
260
- console.log('šŸ’” There seems to be an issue with the selected model.');
261
- console.log(' Run: protoagent config --update-model');
490
+ logger.consoleLog('šŸ’” There seems to be an issue with the selected model.');
491
+ logger.consoleLog(' Run: protoagent config --update-model');
492
+ logger.info('šŸ’” Model error help message displayed');
262
493
  }
263
494
  else {
264
- console.log('šŸ’” An unexpected error occurred. Please check your configuration and try again.');
495
+ logger.consoleLog('šŸ’” An unexpected error occurred. Please check your configuration and try again.');
496
+ logger.info('šŸ’” Generic error help message displayed');
265
497
  }
266
- console.log('\\nšŸ¤– ProtoAgent: I encountered an error and cannot continue processing this request.\\n');
498
+ logger.consoleLog('\nšŸ¤– ProtoAgent: I encountered an error and cannot continue processing this request.\n');
499
+ logger.info('šŸ¤– Error termination message displayed to user');
267
500
  }
268
501
  }
269
502
  }