protoagent 0.0.2 ā 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.
- package/dist/agentic-loop.js +262 -29
- package/dist/config/client.js +157 -18
- package/dist/config/commands.js +29 -28
- package/dist/config/setup.js +20 -19
- package/dist/config/system-prompt.js +62 -0
- package/dist/index.js +84 -17
- package/dist/tools/index.js +72 -16
- package/dist/tools/run-shell-command.js +15 -14
- package/dist/utils/conversation-compactor.js +7 -6
- package/dist/utils/cost-tracker.js +5 -4
- package/dist/utils/file-operations-approval.js +33 -45
- package/dist/utils/logger.js +176 -3
- package/package.json +1 -1
package/dist/config/client.js
CHANGED
|
@@ -4,28 +4,47 @@
|
|
|
4
4
|
import OpenAI from 'openai';
|
|
5
5
|
import { geminiProvider, cerebrasProvider, getModelConfig } from './providers.js';
|
|
6
6
|
import { estimateTokens, getContextInfo } from '../utils/cost-tracker.js';
|
|
7
|
+
import { logger } from '../utils/logger.js';
|
|
7
8
|
/**
|
|
8
9
|
* Create OpenAI client from configuration
|
|
9
10
|
*/
|
|
10
11
|
export function createOpenAIClient(config) {
|
|
12
|
+
logger.debug('š¤ Creating OpenAI client', {
|
|
13
|
+
component: 'OpenAIClient',
|
|
14
|
+
provider: config.provider,
|
|
15
|
+
model: config.model
|
|
16
|
+
});
|
|
11
17
|
if (config.provider === 'openai') {
|
|
18
|
+
logger.debug('š Using OpenAI provider', { component: 'OpenAIClient' });
|
|
12
19
|
return new OpenAI({
|
|
13
20
|
apiKey: config.credentials.OPENAI_API_KEY
|
|
14
21
|
});
|
|
15
22
|
}
|
|
16
23
|
if (config.provider === 'gemini') {
|
|
24
|
+
logger.debug('š Using Gemini provider', {
|
|
25
|
+
component: 'OpenAIClient',
|
|
26
|
+
baseURL: geminiProvider.baseURL
|
|
27
|
+
});
|
|
17
28
|
return new OpenAI({
|
|
18
29
|
apiKey: config.credentials.GEMINI_API_KEY,
|
|
19
30
|
baseURL: geminiProvider.baseURL
|
|
20
31
|
});
|
|
21
32
|
}
|
|
22
33
|
if (config.provider === 'cerebras') {
|
|
34
|
+
logger.debug('š Using Cerebras provider', {
|
|
35
|
+
component: 'OpenAIClient',
|
|
36
|
+
baseURL: cerebrasProvider.baseURL
|
|
37
|
+
});
|
|
23
38
|
return new OpenAI({
|
|
24
39
|
apiKey: config.credentials.CEREBRAS_API_KEY,
|
|
25
40
|
baseURL: cerebrasProvider.baseURL
|
|
26
41
|
});
|
|
27
42
|
}
|
|
28
|
-
|
|
43
|
+
logger.error('ā Unknown provider', {
|
|
44
|
+
component: 'OpenAIClient',
|
|
45
|
+
provider: config.provider
|
|
46
|
+
});
|
|
47
|
+
throw new Error(`Unknown provider: ${config.provider}`);
|
|
29
48
|
}
|
|
30
49
|
/**
|
|
31
50
|
* Sleep for a given number of milliseconds
|
|
@@ -37,12 +56,22 @@ function sleep(ms) {
|
|
|
37
56
|
* Check if an error is retryable
|
|
38
57
|
*/
|
|
39
58
|
function isRetryableError(error) {
|
|
59
|
+
logger.debug('š Checking if error is retryable', {
|
|
60
|
+
component: 'OpenAIClient',
|
|
61
|
+
status: error?.status,
|
|
62
|
+
code: error?.code
|
|
63
|
+
});
|
|
40
64
|
// Check for rate limiting (429)
|
|
41
65
|
if (error?.status === 429) {
|
|
66
|
+
logger.debug('ā³ Error is rate limit (429)', { component: 'OpenAIClient' });
|
|
42
67
|
return true;
|
|
43
68
|
}
|
|
44
69
|
// Check for server errors (5xx)
|
|
45
70
|
if (error?.status >= 500 && error?.status < 600) {
|
|
71
|
+
logger.debug('š§ Error is server error (5xx)', {
|
|
72
|
+
component: 'OpenAIClient',
|
|
73
|
+
status: error.status
|
|
74
|
+
});
|
|
46
75
|
return true;
|
|
47
76
|
}
|
|
48
77
|
// Check for network/connection errors
|
|
@@ -51,25 +80,50 @@ function isRetryableError(error) {
|
|
|
51
80
|
error?.code === 'ECONNREFUSED' ||
|
|
52
81
|
error?.message?.includes('network') ||
|
|
53
82
|
error?.message?.includes('timeout')) {
|
|
83
|
+
logger.debug('š Error is network related', {
|
|
84
|
+
component: 'OpenAIClient',
|
|
85
|
+
code: error.code,
|
|
86
|
+
message: error.message
|
|
87
|
+
});
|
|
54
88
|
return true;
|
|
55
89
|
}
|
|
90
|
+
logger.debug('ā Error is not retryable', { component: 'OpenAIClient' });
|
|
56
91
|
return false;
|
|
57
92
|
}
|
|
58
93
|
/**
|
|
59
94
|
* Get appropriate delay for rate limiting based on error
|
|
60
95
|
*/
|
|
61
96
|
function getRateLimitDelay(error, attempt) {
|
|
97
|
+
logger.debug('ā±ļø Calculating rate limit delay', {
|
|
98
|
+
component: 'OpenAIClient',
|
|
99
|
+
attempt,
|
|
100
|
+
retryAfterHeader: error?.headers?.['retry-after']
|
|
101
|
+
});
|
|
62
102
|
// Check for Retry-After header (OpenAI provides this for rate limits)
|
|
63
103
|
if (error?.headers?.['retry-after']) {
|
|
64
104
|
const retryAfter = parseInt(error.headers['retry-after'], 10);
|
|
65
105
|
if (!isNaN(retryAfter)) {
|
|
66
|
-
|
|
106
|
+
const delayMs = retryAfter * 1000;
|
|
107
|
+
logger.debug('šØ Using Retry-After header delay', {
|
|
108
|
+
component: 'OpenAIClient',
|
|
109
|
+
retryAfter,
|
|
110
|
+
delayMs
|
|
111
|
+
});
|
|
112
|
+
return delayMs; // Convert to milliseconds
|
|
67
113
|
}
|
|
68
114
|
}
|
|
69
115
|
// Default exponential backoff for rate limits: 2^attempt seconds (min 2s, max 60s)
|
|
70
116
|
const baseDelay = Math.min(Math.pow(2, attempt) * 1000, 60000);
|
|
71
117
|
const jitter = Math.random() * 1000; // Add jitter to prevent thundering herd
|
|
72
|
-
|
|
118
|
+
const finalDelay = Math.max(baseDelay + jitter, 2000); // Minimum 2 seconds
|
|
119
|
+
logger.debug('ā” Using exponential backoff delay', {
|
|
120
|
+
component: 'OpenAIClient',
|
|
121
|
+
attempt,
|
|
122
|
+
baseDelay,
|
|
123
|
+
jitter,
|
|
124
|
+
finalDelay
|
|
125
|
+
});
|
|
126
|
+
return finalDelay;
|
|
73
127
|
}
|
|
74
128
|
/**
|
|
75
129
|
* Get delay for general retryable errors
|
|
@@ -78,7 +132,15 @@ function getRetryDelay(attempt) {
|
|
|
78
132
|
// Exponential backoff: 1, 2, 4 seconds with jitter
|
|
79
133
|
const baseDelay = Math.pow(2, attempt - 1) * 1000;
|
|
80
134
|
const jitter = Math.random() * 500;
|
|
81
|
-
|
|
135
|
+
const finalDelay = Math.min(baseDelay + jitter, 4000); // Max 4 seconds
|
|
136
|
+
logger.debug('š Calculating retry delay', {
|
|
137
|
+
component: 'OpenAIClient',
|
|
138
|
+
attempt,
|
|
139
|
+
baseDelay,
|
|
140
|
+
jitter,
|
|
141
|
+
finalDelay
|
|
142
|
+
});
|
|
143
|
+
return finalDelay;
|
|
82
144
|
}
|
|
83
145
|
/**
|
|
84
146
|
* Create chat completion with OpenAI with retry logic and cost tracking
|
|
@@ -86,6 +148,13 @@ function getRetryDelay(attempt) {
|
|
|
86
148
|
export async function createChatCompletion(client, params, config, messages) {
|
|
87
149
|
const maxRetries = 3;
|
|
88
150
|
let lastError;
|
|
151
|
+
logger.debug('š Starting chat completion request', {
|
|
152
|
+
component: 'OpenAIClient',
|
|
153
|
+
provider: config.provider,
|
|
154
|
+
model: config.model,
|
|
155
|
+
messageCount: messages.length,
|
|
156
|
+
maxRetries
|
|
157
|
+
});
|
|
89
158
|
// Get model configuration for cost tracking
|
|
90
159
|
const modelConfig = getModelConfig(config.provider, config.model);
|
|
91
160
|
// Estimate input tokens for cost calculation
|
|
@@ -95,37 +164,78 @@ export async function createChatCompletion(client, params, config, messages) {
|
|
|
95
164
|
}
|
|
96
165
|
return total;
|
|
97
166
|
}, 0);
|
|
167
|
+
logger.debug('š Token estimation complete', {
|
|
168
|
+
component: 'OpenAIClient',
|
|
169
|
+
estimatedInputTokens,
|
|
170
|
+
hasModelConfig: !!modelConfig
|
|
171
|
+
});
|
|
98
172
|
// Log context information before making the request
|
|
99
173
|
if (modelConfig) {
|
|
100
174
|
const contextInfo = getContextInfo(messages, modelConfig);
|
|
101
|
-
|
|
175
|
+
logger.consoleLog(`š Context: ${contextInfo.currentTokens}/${contextInfo.maxTokens} tokens (${contextInfo.utilizationPercentage.toFixed(1)}%)`, {
|
|
176
|
+
component: 'OpenAIClient',
|
|
177
|
+
...contextInfo
|
|
178
|
+
});
|
|
102
179
|
if (contextInfo.needsCompaction) {
|
|
103
|
-
|
|
180
|
+
logger.consoleLog(`ā ļø Context approaching limit - automatic compaction will trigger soon`, {
|
|
181
|
+
component: 'OpenAIClient',
|
|
182
|
+
needsCompaction: true
|
|
183
|
+
});
|
|
104
184
|
}
|
|
105
185
|
}
|
|
106
186
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
187
|
+
logger.debug(`šÆ Attempt ${attempt}/${maxRetries}`, {
|
|
188
|
+
component: 'OpenAIClient',
|
|
189
|
+
attempt,
|
|
190
|
+
maxRetries
|
|
191
|
+
});
|
|
107
192
|
try {
|
|
193
|
+
logger.debug('š” Making API request', {
|
|
194
|
+
component: 'OpenAIClient',
|
|
195
|
+
stream: params.stream || true,
|
|
196
|
+
includeUsage: true
|
|
197
|
+
});
|
|
108
198
|
const stream = await client.chat.completions.create({
|
|
109
199
|
...params,
|
|
110
200
|
stream: true,
|
|
111
201
|
stream_options: { include_usage: true }
|
|
112
202
|
});
|
|
203
|
+
logger.debug('ā
API request successful', {
|
|
204
|
+
component: 'OpenAIClient',
|
|
205
|
+
attempt,
|
|
206
|
+
estimatedInputTokens
|
|
207
|
+
});
|
|
113
208
|
return { stream, estimatedInputTokens };
|
|
114
209
|
}
|
|
115
210
|
catch (error) {
|
|
116
211
|
lastError = error;
|
|
212
|
+
logger.debug('ā API request failed', {
|
|
213
|
+
component: 'OpenAIClient',
|
|
214
|
+
attempt,
|
|
215
|
+
errorStatus: error?.status,
|
|
216
|
+
errorCode: error?.code,
|
|
217
|
+
errorMessage: error?.message
|
|
218
|
+
});
|
|
117
219
|
// Handle rate limiting (429) specially
|
|
118
220
|
if (error?.status === 429) {
|
|
119
221
|
const delay = getRateLimitDelay(error, attempt);
|
|
120
222
|
const seconds = Math.round(delay / 1000);
|
|
121
223
|
if (attempt < maxRetries) {
|
|
122
|
-
|
|
224
|
+
logger.consoleLog(`\nā³ Rate limited by API. Waiting ${seconds} seconds before retry (attempt ${attempt}/${maxRetries})...`, {
|
|
225
|
+
component: 'OpenAIClient',
|
|
226
|
+
attempt,
|
|
227
|
+
maxRetries,
|
|
228
|
+
delayMs: delay
|
|
229
|
+
});
|
|
123
230
|
await sleep(delay);
|
|
124
231
|
continue;
|
|
125
232
|
}
|
|
126
233
|
else {
|
|
127
|
-
|
|
128
|
-
|
|
234
|
+
logger.consoleLog('\nā Rate limit exceeded. Maximum retries reached.', {
|
|
235
|
+
component: 'OpenAIClient',
|
|
236
|
+
maxRetries
|
|
237
|
+
});
|
|
238
|
+
logger.consoleLog('š” Tip: Consider upgrading your API plan or waiting before making more requests.');
|
|
129
239
|
break;
|
|
130
240
|
}
|
|
131
241
|
}
|
|
@@ -133,34 +243,63 @@ export async function createChatCompletion(client, params, config, messages) {
|
|
|
133
243
|
if (isRetryableError(error) && attempt < maxRetries) {
|
|
134
244
|
const delay = getRetryDelay(attempt);
|
|
135
245
|
const seconds = Math.round(delay / 1000);
|
|
136
|
-
|
|
246
|
+
logger.consoleLog(`\nā ļø API error (${error?.status || error?.code || 'unknown'}). Retrying in ${seconds} seconds (attempt ${attempt}/${maxRetries})...`, {
|
|
247
|
+
component: 'OpenAIClient',
|
|
248
|
+
attempt,
|
|
249
|
+
maxRetries,
|
|
250
|
+
errorType: error?.status || error?.code || 'unknown',
|
|
251
|
+
delayMs: delay
|
|
252
|
+
});
|
|
137
253
|
await sleep(delay);
|
|
138
254
|
continue;
|
|
139
255
|
}
|
|
140
256
|
// For non-retryable errors or max retries reached, break immediately
|
|
257
|
+
logger.error('š„ Breaking retry loop', {
|
|
258
|
+
component: 'OpenAIClient',
|
|
259
|
+
attempt,
|
|
260
|
+
maxRetries,
|
|
261
|
+
isRetryable: isRetryableError(error)
|
|
262
|
+
});
|
|
141
263
|
break;
|
|
142
264
|
}
|
|
143
265
|
}
|
|
144
266
|
// If we get here, all retries failed
|
|
145
|
-
|
|
267
|
+
logger.consoleLog('\nā API request failed after all retry attempts.', {
|
|
268
|
+
component: 'OpenAIClient',
|
|
269
|
+
maxRetries,
|
|
270
|
+
lastErrorStatus: lastError?.status,
|
|
271
|
+
lastErrorCode: lastError?.code
|
|
272
|
+
});
|
|
146
273
|
// Provide user-friendly error messages
|
|
147
274
|
if (lastError?.status === 401) {
|
|
148
|
-
|
|
149
|
-
|
|
275
|
+
logger.consoleLog('š” This looks like an authentication error. Check your API key configuration.');
|
|
276
|
+
logger.consoleLog(' Run: protoagent config --update-key');
|
|
277
|
+
logger.error('š Authentication error detected', { component: 'OpenAIClient' });
|
|
150
278
|
}
|
|
151
279
|
else if (lastError?.status === 429) {
|
|
152
|
-
|
|
280
|
+
logger.consoleLog('š” Rate limit exceeded. Try again later or upgrade your API plan.');
|
|
281
|
+
logger.error('ā³ Rate limit error detected', { component: 'OpenAIClient' });
|
|
153
282
|
}
|
|
154
283
|
else if (lastError?.status === 403) {
|
|
155
|
-
|
|
284
|
+
logger.consoleLog('š” Access forbidden. Check your API key permissions and billing status.');
|
|
285
|
+
logger.error('š« Forbidden access error detected', { component: 'OpenAIClient' });
|
|
156
286
|
}
|
|
157
287
|
else if (lastError?.status >= 500) {
|
|
158
|
-
|
|
288
|
+
logger.consoleLog('š” Server error. The API service may be temporarily unavailable.');
|
|
289
|
+
logger.error('š§ Server error detected', {
|
|
290
|
+
component: 'OpenAIClient',
|
|
291
|
+
status: lastError.status
|
|
292
|
+
});
|
|
159
293
|
}
|
|
160
294
|
else if (lastError?.code === 'ENOTFOUND' || lastError?.message?.includes('network')) {
|
|
161
|
-
|
|
295
|
+
logger.consoleLog('š” Network connection error. Check your internet connection.');
|
|
296
|
+
logger.error('š Network error detected', {
|
|
297
|
+
component: 'OpenAIClient',
|
|
298
|
+
code: lastError?.code
|
|
299
|
+
});
|
|
162
300
|
}
|
|
163
301
|
// Suggest graceful shutdown
|
|
164
|
-
|
|
302
|
+
logger.consoleLog('\nšŖ ProtoAgent will now exit. Please resolve the issue and try again.');
|
|
303
|
+
logger.error('šŖ Exiting due to API failure', { component: 'OpenAIClient' });
|
|
165
304
|
process.exit(1);
|
|
166
305
|
}
|
package/dist/config/commands.js
CHANGED
|
@@ -7,6 +7,7 @@ import { setupConfig } from './setup.js';
|
|
|
7
7
|
import { openaiProvider, geminiProvider, cerebrasProvider } from './providers.js';
|
|
8
8
|
import fs from 'fs/promises';
|
|
9
9
|
import path from 'path';
|
|
10
|
+
import { logger } from '../utils/logger.js';
|
|
10
11
|
/**
|
|
11
12
|
* Mask API key for display (show first 8 and last 4 characters)
|
|
12
13
|
*/
|
|
@@ -26,27 +27,27 @@ export async function showCurrentConfig() {
|
|
|
26
27
|
try {
|
|
27
28
|
const configExists = await hasConfig();
|
|
28
29
|
if (!configExists) {
|
|
29
|
-
|
|
30
|
+
logger.consoleLog('ā No configuration found. Run ProtoAgent to set up.');
|
|
30
31
|
return;
|
|
31
32
|
}
|
|
32
33
|
const config = await loadConfig();
|
|
33
34
|
const configDir = getConfigDirectory();
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
logger.consoleLog('\nš Current ProtoAgent Configuration:');
|
|
36
|
+
logger.consoleLog('ā'.repeat(40));
|
|
37
|
+
logger.consoleLog(`Provider: ${config.provider}`);
|
|
38
|
+
logger.consoleLog(`Model: ${config.model}`);
|
|
38
39
|
// Show the appropriate API key based on provider
|
|
39
40
|
if (config.provider === 'openai' && config.credentials.OPENAI_API_KEY) {
|
|
40
|
-
|
|
41
|
+
logger.consoleLog(`API Key: ${maskApiKey(config.credentials.OPENAI_API_KEY)}`);
|
|
41
42
|
}
|
|
42
43
|
else if (config.provider === 'gemini' && config.credentials.GEMINI_API_KEY) {
|
|
43
|
-
|
|
44
|
+
logger.consoleLog(`API Key: ${maskApiKey(config.credentials.GEMINI_API_KEY)}`);
|
|
44
45
|
}
|
|
45
46
|
else if (config.provider === 'cerebras' && config.credentials.CEREBRAS_API_KEY) {
|
|
46
|
-
|
|
47
|
+
logger.consoleLog(`API Key: ${maskApiKey(config.credentials.CEREBRAS_API_KEY)}`);
|
|
47
48
|
}
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
logger.consoleLog(`Config Location: ${configDir}/config.json`);
|
|
50
|
+
logger.consoleLog('ā'.repeat(40));
|
|
50
51
|
}
|
|
51
52
|
catch (error) {
|
|
52
53
|
console.error(`ā Error reading configuration: ${error.message}`);
|
|
@@ -59,11 +60,11 @@ export async function updateApiKey() {
|
|
|
59
60
|
try {
|
|
60
61
|
const configExists = await hasConfig();
|
|
61
62
|
if (!configExists) {
|
|
62
|
-
|
|
63
|
+
logger.consoleLog('ā No configuration found. Run ProtoAgent to set up first.');
|
|
63
64
|
return;
|
|
64
65
|
}
|
|
65
66
|
const config = await loadConfig();
|
|
66
|
-
|
|
67
|
+
logger.consoleLog(`\nš Update ${config.provider.toUpperCase()} API Key`);
|
|
67
68
|
// Show current API key based on provider
|
|
68
69
|
let currentKey = '';
|
|
69
70
|
let helpUrl = '';
|
|
@@ -83,8 +84,8 @@ export async function updateApiKey() {
|
|
|
83
84
|
helpUrl = 'https://cloud.cerebras.ai/platform';
|
|
84
85
|
keyPrefix = '';
|
|
85
86
|
}
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
logger.consoleLog(`Current API Key: ${maskApiKey(currentKey)}`);
|
|
88
|
+
logger.consoleLog(`Get your API key from: ${helpUrl}\n`);
|
|
88
89
|
const { newApiKey } = await inquirer.prompt([
|
|
89
90
|
{
|
|
90
91
|
type: 'password',
|
|
@@ -113,8 +114,8 @@ export async function updateApiKey() {
|
|
|
113
114
|
config.credentials.CEREBRAS_API_KEY = newApiKey.trim();
|
|
114
115
|
}
|
|
115
116
|
await saveConfig(config);
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
logger.consoleLog('\nā
API key updated successfully!');
|
|
118
|
+
logger.consoleLog(`New API Key: ${maskApiKey(newApiKey)}`);
|
|
118
119
|
}
|
|
119
120
|
catch (error) {
|
|
120
121
|
console.error(`ā Error updating API key: ${error.message}`);
|
|
@@ -127,12 +128,12 @@ export async function updateModel() {
|
|
|
127
128
|
try {
|
|
128
129
|
const configExists = await hasConfig();
|
|
129
130
|
if (!configExists) {
|
|
130
|
-
|
|
131
|
+
logger.consoleLog('ā No configuration found. Run ProtoAgent to set up first.');
|
|
131
132
|
return;
|
|
132
133
|
}
|
|
133
134
|
const config = await loadConfig();
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
logger.consoleLog(`\nš¤ Update ${config.provider.toUpperCase()} Model`);
|
|
136
|
+
logger.consoleLog(`Current Model: ${config.model}\n`);
|
|
136
137
|
// Get available models based on provider
|
|
137
138
|
let availableModels = [];
|
|
138
139
|
if (config.provider === 'openai') {
|
|
@@ -157,16 +158,16 @@ export async function updateModel() {
|
|
|
157
158
|
}
|
|
158
159
|
]);
|
|
159
160
|
if (newModel === config.model) {
|
|
160
|
-
|
|
161
|
+
logger.consoleLog('⨠No change needed - same model selected.');
|
|
161
162
|
return;
|
|
162
163
|
}
|
|
163
164
|
// Update configuration
|
|
164
165
|
const previousModel = config.model;
|
|
165
166
|
config.model = newModel;
|
|
166
167
|
await saveConfig(config);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
168
|
+
logger.consoleLog('\nā
Model updated successfully!');
|
|
169
|
+
logger.consoleLog(`Previous Model: ${previousModel}`);
|
|
170
|
+
logger.consoleLog(`New Model: ${newModel}`);
|
|
170
171
|
}
|
|
171
172
|
catch (error) {
|
|
172
173
|
console.error(`ā Error updating model: ${error.message}`);
|
|
@@ -179,8 +180,8 @@ export async function resetConfiguration() {
|
|
|
179
180
|
try {
|
|
180
181
|
const configExists = await hasConfig();
|
|
181
182
|
if (configExists) {
|
|
182
|
-
|
|
183
|
-
|
|
183
|
+
logger.consoleLog('\nā ļø Reset Configuration');
|
|
184
|
+
logger.consoleLog('This will delete your current configuration and set up ProtoAgent from scratch.');
|
|
184
185
|
const { confirm } = await inquirer.prompt([
|
|
185
186
|
{
|
|
186
187
|
type: 'confirm',
|
|
@@ -190,16 +191,16 @@ export async function resetConfiguration() {
|
|
|
190
191
|
}
|
|
191
192
|
]);
|
|
192
193
|
if (!confirm) {
|
|
193
|
-
|
|
194
|
+
logger.consoleLog('Configuration reset cancelled.');
|
|
194
195
|
return;
|
|
195
196
|
}
|
|
196
197
|
// Delete existing configuration
|
|
197
198
|
const configPath = path.join(getConfigDirectory(), 'config.json');
|
|
198
199
|
await fs.unlink(configPath);
|
|
199
|
-
|
|
200
|
+
logger.consoleLog('ā
Existing configuration deleted.');
|
|
200
201
|
}
|
|
201
202
|
// Run setup again
|
|
202
|
-
|
|
203
|
+
logger.consoleLog('\nš Starting fresh configuration setup...');
|
|
203
204
|
await setupConfig();
|
|
204
205
|
}
|
|
205
206
|
catch (error) {
|
package/dist/config/setup.js
CHANGED
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
import inquirer from 'inquirer';
|
|
5
5
|
import { openaiProvider, geminiProvider, cerebrasProvider } from './providers.js';
|
|
6
6
|
import { saveConfig, getConfigDirectory } from './manager.js';
|
|
7
|
+
import { logger } from '../utils/logger.js';
|
|
7
8
|
/**
|
|
8
9
|
* Run interactive configuration setup
|
|
9
10
|
*/
|
|
10
11
|
export async function setupConfig() {
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
logger.consoleLog('\nš¤ Welcome to ProtoAgent!');
|
|
13
|
+
logger.consoleLog("Let's set up your model configuration.\n");
|
|
13
14
|
// Step 1: Select provider and model
|
|
14
15
|
const modelChoices = [
|
|
15
16
|
new inquirer.Separator('--- OpenAI Models ---'),
|
|
@@ -47,9 +48,9 @@ export async function setupConfig() {
|
|
|
47
48
|
CEREBRAS_API_KEY: ''
|
|
48
49
|
};
|
|
49
50
|
if (provider === 'openai') {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
logger.consoleLog(`\nš ${openaiProvider.auth.label} Setup`);
|
|
52
|
+
logger.consoleLog("You'll need an API key from OpenAI.");
|
|
53
|
+
logger.consoleLog(`Get your API key from: ${openaiProvider.auth.helpUrl}`);
|
|
53
54
|
const response = await inquirer.prompt([
|
|
54
55
|
{
|
|
55
56
|
type: 'password',
|
|
@@ -71,9 +72,9 @@ export async function setupConfig() {
|
|
|
71
72
|
credentials.OPENAI_API_KEY = apiKey;
|
|
72
73
|
}
|
|
73
74
|
else if (provider === 'gemini') {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
logger.consoleLog(`\nš ${geminiProvider.auth.label} Setup`);
|
|
76
|
+
logger.consoleLog("You'll need an API key from Gemini.");
|
|
77
|
+
logger.consoleLog(`Get your API key from: ${geminiProvider.auth.helpUrl}`);
|
|
77
78
|
const response = await inquirer.prompt([
|
|
78
79
|
{
|
|
79
80
|
type: 'password',
|
|
@@ -93,9 +94,9 @@ export async function setupConfig() {
|
|
|
93
94
|
credentials.GEMINI_API_KEY = apiKey;
|
|
94
95
|
}
|
|
95
96
|
else if (provider === 'cerebras') {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
logger.consoleLog(`\nš ${cerebrasProvider.auth.label} Setup`);
|
|
98
|
+
logger.consoleLog("You'll need an API key from Cerebras.");
|
|
99
|
+
logger.consoleLog(`Get your API key from: ${cerebrasProvider.auth.helpUrl}`);
|
|
99
100
|
const response = await inquirer.prompt([
|
|
100
101
|
{
|
|
101
102
|
type: 'password',
|
|
@@ -115,7 +116,7 @@ export async function setupConfig() {
|
|
|
115
116
|
credentials.CEREBRAS_API_KEY = apiKey;
|
|
116
117
|
}
|
|
117
118
|
// Step 3: Confirm configuration
|
|
118
|
-
|
|
119
|
+
logger.consoleLog('\nš Configuration Summary:');
|
|
119
120
|
let providerName = '';
|
|
120
121
|
if (provider === 'openai') {
|
|
121
122
|
providerName = openaiProvider.name;
|
|
@@ -126,9 +127,9 @@ export async function setupConfig() {
|
|
|
126
127
|
else if (provider === 'cerebras') {
|
|
127
128
|
providerName = cerebrasProvider.name;
|
|
128
129
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
logger.consoleLog(`Provider: ${providerName}`);
|
|
131
|
+
logger.consoleLog(`Model: ${model}`);
|
|
132
|
+
logger.consoleLog(`API Key: ${'*'.repeat(Math.min(apiKey.length, 20))}...`);
|
|
132
133
|
const { confirm } = await inquirer.prompt([
|
|
133
134
|
{
|
|
134
135
|
type: 'confirm',
|
|
@@ -138,7 +139,7 @@ export async function setupConfig() {
|
|
|
138
139
|
}
|
|
139
140
|
]);
|
|
140
141
|
if (!confirm) {
|
|
141
|
-
|
|
142
|
+
logger.consoleLog('Configuration cancelled.');
|
|
142
143
|
process.exit(0);
|
|
143
144
|
}
|
|
144
145
|
// Step 4: Save configuration
|
|
@@ -149,9 +150,9 @@ export async function setupConfig() {
|
|
|
149
150
|
};
|
|
150
151
|
try {
|
|
151
152
|
await saveConfig(config);
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
153
|
+
logger.consoleLog(`\nā
Configuration saved successfully!`);
|
|
154
|
+
logger.consoleLog(`Config location: ${getConfigDirectory()}/config.json`);
|
|
155
|
+
logger.consoleLog("\nYou're all set! ProtoAgent is ready to use.\n");
|
|
155
156
|
return config;
|
|
156
157
|
}
|
|
157
158
|
catch (error) {
|
|
@@ -9,6 +9,7 @@ import { searchFilesTool } from '../tools/search-files.js';
|
|
|
9
9
|
import { runShellCommandTool } from '../tools/run-shell-command.js';
|
|
10
10
|
import fs from 'fs/promises';
|
|
11
11
|
import path from 'path';
|
|
12
|
+
import { logger } from '../utils/logger.js';
|
|
12
13
|
// Collect all tools
|
|
13
14
|
const allTools = [
|
|
14
15
|
readFileTool,
|
|
@@ -82,10 +83,23 @@ async function generateDirectoryTree(dirPath = '.', depth = 0, maxDepth = 3) {
|
|
|
82
83
|
}
|
|
83
84
|
// Generate project context asynchronously
|
|
84
85
|
async function generateProjectContext() {
|
|
86
|
+
logger.debug('š Generating project context', { component: 'SystemPrompt' });
|
|
85
87
|
const workingDir = getCurrentWorkingDirectory();
|
|
86
88
|
const projectName = path.basename(workingDir);
|
|
89
|
+
logger.debug('š Project info', {
|
|
90
|
+
component: 'SystemPrompt',
|
|
91
|
+
workingDir,
|
|
92
|
+
projectName
|
|
93
|
+
});
|
|
87
94
|
try {
|
|
95
|
+
const treeStartTime = Date.now();
|
|
88
96
|
const tree = await generateDirectoryTree();
|
|
97
|
+
const treeGenerationTime = Date.now() - treeStartTime;
|
|
98
|
+
logger.debug('š³ Directory tree generated', {
|
|
99
|
+
component: 'SystemPrompt',
|
|
100
|
+
treeLength: tree.length,
|
|
101
|
+
generationTime: treeGenerationTime
|
|
102
|
+
});
|
|
89
103
|
return `
|
|
90
104
|
## Current Project Context:
|
|
91
105
|
|
|
@@ -96,6 +110,10 @@ async function generateProjectContext() {
|
|
|
96
110
|
${tree}`;
|
|
97
111
|
}
|
|
98
112
|
catch (error) {
|
|
113
|
+
logger.error('ā Failed to generate directory tree', {
|
|
114
|
+
component: 'SystemPrompt',
|
|
115
|
+
error: error instanceof Error ? error.message : String(error)
|
|
116
|
+
});
|
|
99
117
|
return `
|
|
100
118
|
## Current Project Context:
|
|
101
119
|
|
|
@@ -107,7 +125,15 @@ ${tree}`;
|
|
|
107
125
|
}
|
|
108
126
|
// Generate the complete system prompt with project context
|
|
109
127
|
export async function generateSystemPrompt() {
|
|
128
|
+
logger.debug('šļø Generating system prompt', { component: 'SystemPrompt' });
|
|
129
|
+
const startTime = Date.now();
|
|
110
130
|
const projectContext = await generateProjectContext();
|
|
131
|
+
const generationTime = Date.now() - startTime;
|
|
132
|
+
logger.debug('ā
System prompt generated', {
|
|
133
|
+
component: 'SystemPrompt',
|
|
134
|
+
generationTime,
|
|
135
|
+
contextLength: projectContext.length
|
|
136
|
+
});
|
|
111
137
|
return `You are ProtoAgent, a helpful coding assistant with file system and shell command capabilities. Your objective is to help the user
|
|
112
138
|
complete their coding tasks, most likely related to the directory you were started in.
|
|
113
139
|
|
|
@@ -131,6 +157,24 @@ When users ask you to work with files or run commands, you should:
|
|
|
131
157
|
- Show the user the results of your operations
|
|
132
158
|
- Ask for confirmation before running any write, delete operations
|
|
133
159
|
|
|
160
|
+
## CRITICAL: Always Be Proactive With Tool Usage
|
|
161
|
+
|
|
162
|
+
**When users ask about existing code, projects, or codebases:**
|
|
163
|
+
1. IMMEDIATELY start using tools to explore and understand the codebase
|
|
164
|
+
2. Use find/search commands to locate relevant files and directories
|
|
165
|
+
3. Use read_file to examine source code files
|
|
166
|
+
4. Use list_directory and view_directory_tree to understand project structure
|
|
167
|
+
5. NEVER just "think" about code - always examine it with tools first
|
|
168
|
+
|
|
169
|
+
**When asked to analyze or compare codebases:**
|
|
170
|
+
1. Start by searching for the mentioned projects/directories
|
|
171
|
+
2. Explore their structure systematically
|
|
172
|
+
3. Read key files (main entry points, package.json, README, etc.)
|
|
173
|
+
4. Build understanding iteratively through tool usage
|
|
174
|
+
5. Document findings as you discover them
|
|
175
|
+
|
|
176
|
+
**MANDATORY: If a user mentions specific codebases or asks you to analyze source code, you MUST start using tools immediately to explore and understand the code before providing any analysis.**
|
|
177
|
+
|
|
134
178
|
## TODO TRACKING - MANDATORY REQUIREMENT:
|
|
135
179
|
|
|
136
180
|
**YOU MUST ALWAYS USE TODO TRACKING FOR ANY NON-TRIVIAL TASK:**
|
|
@@ -244,6 +288,24 @@ When users ask you to work with files or run commands, you should:
|
|
|
244
288
|
- Show the user the results of your operations
|
|
245
289
|
- Ask for confirmation before running any write, delete operations
|
|
246
290
|
|
|
291
|
+
## CRITICAL: Always Be Proactive With Tool Usage
|
|
292
|
+
|
|
293
|
+
**When users ask about existing code, projects, or codebases:**
|
|
294
|
+
1. IMMEDIATELY start using tools to explore and understand the codebase
|
|
295
|
+
2. Use find/search commands to locate relevant files and directories
|
|
296
|
+
3. Use read_file to examine source code files
|
|
297
|
+
4. Use list_directory and view_directory_tree to understand project structure
|
|
298
|
+
5. NEVER just "think" about code - always examine it with tools first
|
|
299
|
+
|
|
300
|
+
**When asked to analyze or compare codebases:**
|
|
301
|
+
1. Start by searching for the mentioned projects/directories
|
|
302
|
+
2. Explore their structure systematically
|
|
303
|
+
3. Read key files (main entry points, package.json, README, etc.)
|
|
304
|
+
4. Build understanding iteratively through tool usage
|
|
305
|
+
5. Document findings as you discover them
|
|
306
|
+
|
|
307
|
+
**MANDATORY: If a user mentions specific codebases or asks you to analyze source code, you MUST start using tools immediately to explore and understand the code before providing any analysis.**
|
|
308
|
+
|
|
247
309
|
## File Operation Guidelines - CRITICAL:
|
|
248
310
|
|
|
249
311
|
**ALWAYS PREFER EDITING OVER WRITING FILES:**
|