@probelabs/probe 0.6.0-rc125 → 0.6.0-rc126
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/build/agent/ProbeAgent.js +105 -8
- package/build/agent/hooks/HookManager.js +146 -0
- package/build/agent/hooks/index.js +1 -0
- package/build/agent/index.js +328 -14
- package/build/agent/storage/InMemoryStorageAdapter.js +49 -0
- package/build/agent/storage/StorageAdapter.js +51 -0
- package/build/agent/storage/index.js +2 -0
- package/build/index.js +8 -0
- package/cjs/agent/ProbeAgent.cjs +345 -31
- package/cjs/index.cjs +353 -31
- package/index.d.ts +95 -0
- package/package.json +1 -1
- package/src/agent/ProbeAgent.js +105 -8
- package/src/agent/hooks/HookManager.js +146 -0
- package/src/agent/hooks/index.js +1 -0
- package/src/agent/storage/InMemoryStorageAdapter.js +49 -0
- package/src/agent/storage/StorageAdapter.js +51 -0
- package/src/agent/storage/index.js +2 -0
- package/src/index.js +8 -0
|
@@ -10,6 +10,8 @@ import { existsSync } from 'fs';
|
|
|
10
10
|
import { readFile, stat } from 'fs/promises';
|
|
11
11
|
import { resolve, isAbsolute, dirname } from 'path';
|
|
12
12
|
import { TokenCounter } from './tokenCounter.js';
|
|
13
|
+
import { InMemoryStorageAdapter } from './storage/InMemoryStorageAdapter.js';
|
|
14
|
+
import { HookManager, HOOK_TYPES } from './hooks/HookManager.js';
|
|
13
15
|
import {
|
|
14
16
|
createTools,
|
|
15
17
|
searchToolDefinition,
|
|
@@ -80,6 +82,8 @@ export class ProbeAgent {
|
|
|
80
82
|
* @param {string} [options.mcpConfigPath] - Path to MCP configuration file
|
|
81
83
|
* @param {Object} [options.mcpConfig] - MCP configuration object (overrides mcpConfigPath)
|
|
82
84
|
* @param {Array} [options.mcpServers] - Deprecated, use mcpConfig instead
|
|
85
|
+
* @param {Object} [options.storageAdapter] - Custom storage adapter for history management
|
|
86
|
+
* @param {Object} [options.hooks] - Hook callbacks for events (e.g., {'tool:start': callback})
|
|
83
87
|
*/
|
|
84
88
|
constructor(options = {}) {
|
|
85
89
|
// Basic configuration
|
|
@@ -95,6 +99,19 @@ export class ProbeAgent {
|
|
|
95
99
|
this.maxIterations = options.maxIterations || null;
|
|
96
100
|
this.disableMermaidValidation = !!options.disableMermaidValidation;
|
|
97
101
|
|
|
102
|
+
// Storage adapter (defaults to in-memory)
|
|
103
|
+
this.storageAdapter = options.storageAdapter || new InMemoryStorageAdapter();
|
|
104
|
+
|
|
105
|
+
// Hook manager
|
|
106
|
+
this.hooks = new HookManager();
|
|
107
|
+
|
|
108
|
+
// Register hooks from options
|
|
109
|
+
if (options.hooks) {
|
|
110
|
+
for (const [hookName, callback] of Object.entries(options.hooks)) {
|
|
111
|
+
this.hooks.on(hookName, callback);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
98
115
|
// Bash configuration
|
|
99
116
|
this.enableBash = !!options.enableBash;
|
|
100
117
|
this.bashConfig = options.bashConfig || {};
|
|
@@ -152,9 +169,29 @@ export class ProbeAgent {
|
|
|
152
169
|
|
|
153
170
|
/**
|
|
154
171
|
* Initialize the agent asynchronously (must be called after constructor)
|
|
155
|
-
* This method initializes MCP and merges MCP tools into the tool list
|
|
172
|
+
* This method initializes MCP and merges MCP tools into the tool list, and loads history from storage
|
|
156
173
|
*/
|
|
157
174
|
async initialize() {
|
|
175
|
+
// Load history from storage adapter
|
|
176
|
+
try {
|
|
177
|
+
const history = await this.storageAdapter.loadHistory(this.sessionId);
|
|
178
|
+
this.history = history;
|
|
179
|
+
|
|
180
|
+
if (this.debug && history.length > 0) {
|
|
181
|
+
console.log(`[DEBUG] Loaded ${history.length} messages from storage for session ${this.sessionId}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Emit storage load hook
|
|
185
|
+
await this.hooks.emit(HOOK_TYPES.STORAGE_LOAD, {
|
|
186
|
+
sessionId: this.sessionId,
|
|
187
|
+
messages: history
|
|
188
|
+
});
|
|
189
|
+
} catch (error) {
|
|
190
|
+
console.error(`[ERROR] Failed to load history from storage:`, error);
|
|
191
|
+
// Continue with empty history if storage fails
|
|
192
|
+
this.history = [];
|
|
193
|
+
}
|
|
194
|
+
|
|
158
195
|
// Initialize MCP if enabled and not already initialized
|
|
159
196
|
if (this.enableMcp && !this._mcpInitialized) {
|
|
160
197
|
this._mcpInitialized = true; // Prevent multiple initialization attempts
|
|
@@ -193,6 +230,12 @@ export class ProbeAgent {
|
|
|
193
230
|
this.mcpBridge = null;
|
|
194
231
|
}
|
|
195
232
|
}
|
|
233
|
+
|
|
234
|
+
// Emit agent initialized hook
|
|
235
|
+
await this.hooks.emit(HOOK_TYPES.AGENT_INITIALIZED, {
|
|
236
|
+
sessionId: this.sessionId,
|
|
237
|
+
agent: this
|
|
238
|
+
});
|
|
196
239
|
}
|
|
197
240
|
|
|
198
241
|
/**
|
|
@@ -261,7 +304,8 @@ export class ProbeAgent {
|
|
|
261
304
|
// Get API keys from environment variables
|
|
262
305
|
const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
|
|
263
306
|
const openaiApiKey = process.env.OPENAI_API_KEY;
|
|
264
|
-
|
|
307
|
+
// Support both GOOGLE_GENERATIVE_AI_API_KEY (official) and GOOGLE_API_KEY (legacy)
|
|
308
|
+
const googleApiKey = process.env.GOOGLE_GENERATIVE_AI_API_KEY || process.env.GOOGLE_API_KEY;
|
|
265
309
|
const awsAccessKeyId = process.env.AWS_ACCESS_KEY_ID;
|
|
266
310
|
const awsSecretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
|
|
267
311
|
const awsRegion = process.env.AWS_REGION;
|
|
@@ -320,7 +364,7 @@ export class ProbeAgent {
|
|
|
320
364
|
} else if ((awsAccessKeyId && awsSecretAccessKey && awsRegion) || awsApiKey) {
|
|
321
365
|
this.initializeBedrockModel(awsAccessKeyId, awsSecretAccessKey, awsRegion, awsSessionToken, awsApiKey, awsBedrockBaseUrl, modelName);
|
|
322
366
|
} else {
|
|
323
|
-
throw new Error('No API key provided. Please set ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY, AWS credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION), or AWS_BEDROCK_API_KEY environment variables.');
|
|
367
|
+
throw new Error('No API key provided. Please set ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY (or GOOGLE_API_KEY), AWS credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION), or AWS_BEDROCK_API_KEY environment variables.');
|
|
324
368
|
}
|
|
325
369
|
}
|
|
326
370
|
|
|
@@ -1080,6 +1124,16 @@ When troubleshooting:
|
|
|
1080
1124
|
}
|
|
1081
1125
|
|
|
1082
1126
|
try {
|
|
1127
|
+
// Track initial history length for storage
|
|
1128
|
+
const oldHistoryLength = this.history.length;
|
|
1129
|
+
|
|
1130
|
+
// Emit user message hook
|
|
1131
|
+
await this.hooks.emit(HOOK_TYPES.MESSAGE_USER, {
|
|
1132
|
+
sessionId: this.sessionId,
|
|
1133
|
+
message,
|
|
1134
|
+
images
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1083
1137
|
// Generate system message
|
|
1084
1138
|
const systemMessage = await this.getSystemMessage();
|
|
1085
1139
|
|
|
@@ -1196,9 +1250,13 @@ When troubleshooting:
|
|
|
1196
1250
|
temperature: 0.3,
|
|
1197
1251
|
});
|
|
1198
1252
|
|
|
1199
|
-
// Collect the streamed response
|
|
1253
|
+
// Collect the streamed response - stream all content for now
|
|
1200
1254
|
for await (const delta of result.textStream) {
|
|
1201
1255
|
assistantResponseContent += delta;
|
|
1256
|
+
// For now, stream everything - we'll handle segmentation after tools execute
|
|
1257
|
+
if (options.onStream) {
|
|
1258
|
+
options.onStream(delta);
|
|
1259
|
+
}
|
|
1202
1260
|
}
|
|
1203
1261
|
|
|
1204
1262
|
// Record token usage
|
|
@@ -1283,6 +1341,16 @@ When troubleshooting:
|
|
|
1283
1341
|
const validation = attemptCompletionSchema.safeParse(params);
|
|
1284
1342
|
if (validation.success) {
|
|
1285
1343
|
finalResult = validation.data.result;
|
|
1344
|
+
|
|
1345
|
+
// Stream the final result if callback is provided
|
|
1346
|
+
if (options.onStream && finalResult) {
|
|
1347
|
+
const chunkSize = 50; // Characters per chunk for smoother streaming
|
|
1348
|
+
for (let i = 0; i < finalResult.length; i += chunkSize) {
|
|
1349
|
+
const chunk = finalResult.slice(i, Math.min(i + chunkSize, finalResult.length));
|
|
1350
|
+
options.onStream(chunk);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1286
1354
|
if (this.debug) console.log(`[DEBUG] Task completed successfully with result: ${finalResult.substring(0, 100)}...`);
|
|
1287
1355
|
} else {
|
|
1288
1356
|
console.error(`[ERROR] Invalid attempt_completion parameters:`, validation.error);
|
|
@@ -1365,12 +1433,13 @@ When troubleshooting:
|
|
|
1365
1433
|
console.error(`[DEBUG] ========================================\n`);
|
|
1366
1434
|
}
|
|
1367
1435
|
|
|
1368
|
-
// Emit tool start event
|
|
1436
|
+
// Emit tool start event with stream pause signal
|
|
1369
1437
|
this.events.emit('toolCall', {
|
|
1370
1438
|
timestamp: new Date().toISOString(),
|
|
1371
1439
|
name: toolName,
|
|
1372
1440
|
args: toolParams,
|
|
1373
|
-
status: 'started'
|
|
1441
|
+
status: 'started',
|
|
1442
|
+
pauseStream: true // Signal to pause text streaming
|
|
1374
1443
|
});
|
|
1375
1444
|
|
|
1376
1445
|
// Execute tool with tracing if available
|
|
@@ -1597,6 +1666,21 @@ IMPORTANT: When using <attempt_complete>, this must be the ONLY content in your
|
|
|
1597
1666
|
// Update token counter with final history
|
|
1598
1667
|
this.tokenCounter.updateHistory(this.history);
|
|
1599
1668
|
|
|
1669
|
+
// Save new messages to storage (save only the new ones added in this turn)
|
|
1670
|
+
try {
|
|
1671
|
+
const messagesToSave = currentMessages.slice(oldHistoryLength);
|
|
1672
|
+
for (const message of messagesToSave) {
|
|
1673
|
+
await this.storageAdapter.saveMessage(this.sessionId, message);
|
|
1674
|
+
await this.hooks.emit(HOOK_TYPES.STORAGE_SAVE, {
|
|
1675
|
+
sessionId: this.sessionId,
|
|
1676
|
+
message
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
} catch (error) {
|
|
1680
|
+
console.error(`[ERROR] Failed to save messages to storage:`, error);
|
|
1681
|
+
// Continue even if storage fails
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1600
1684
|
// Schema handling - format response according to provided schema
|
|
1601
1685
|
// Skip schema processing if result came from attempt_completion tool
|
|
1602
1686
|
// Don't apply schema formatting if we failed due to max iterations
|
|
@@ -2040,11 +2124,24 @@ Convert your previous response content into actual JSON data that follows this s
|
|
|
2040
2124
|
/**
|
|
2041
2125
|
* Clear conversation history and reset counters
|
|
2042
2126
|
*/
|
|
2043
|
-
clearHistory() {
|
|
2127
|
+
async clearHistory() {
|
|
2128
|
+
// Clear in storage
|
|
2129
|
+
try {
|
|
2130
|
+
await this.storageAdapter.clearHistory(this.sessionId);
|
|
2131
|
+
} catch (error) {
|
|
2132
|
+
console.error(`[ERROR] Failed to clear history in storage:`, error);
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
// Clear in-memory
|
|
2044
2136
|
this.history = [];
|
|
2045
2137
|
this.tokenCounter.clear();
|
|
2046
2138
|
clearToolExecutionData(this.sessionId);
|
|
2047
|
-
|
|
2139
|
+
|
|
2140
|
+
// Emit hook
|
|
2141
|
+
await this.hooks.emit(HOOK_TYPES.STORAGE_CLEAR, {
|
|
2142
|
+
sessionId: this.sessionId
|
|
2143
|
+
});
|
|
2144
|
+
|
|
2048
2145
|
if (this.debug) {
|
|
2049
2146
|
console.log(`[DEBUG] Cleared conversation history and reset counters for session ${this.sessionId}`);
|
|
2050
2147
|
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook manager for ProbeAgent
|
|
3
|
+
* Enables event-driven integration with external systems
|
|
4
|
+
*/
|
|
5
|
+
export class HookManager {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.hooks = new Map(); // hookName -> Set<callback>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Register a hook callback
|
|
12
|
+
* @param {string} hookName - Name of the hook
|
|
13
|
+
* @param {Function} callback - Callback function
|
|
14
|
+
* @returns {Function} Unregister function
|
|
15
|
+
*/
|
|
16
|
+
on(hookName, callback) {
|
|
17
|
+
if (!this.hooks.has(hookName)) {
|
|
18
|
+
this.hooks.set(hookName, new Set());
|
|
19
|
+
}
|
|
20
|
+
this.hooks.get(hookName).add(callback);
|
|
21
|
+
|
|
22
|
+
// Return unregister function
|
|
23
|
+
return () => this.off(hookName, callback);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Register a one-time hook callback
|
|
28
|
+
* @param {string} hookName - Name of the hook
|
|
29
|
+
* @param {Function} callback - Callback function
|
|
30
|
+
* @returns {Function} Unregister function
|
|
31
|
+
*/
|
|
32
|
+
once(hookName, callback) {
|
|
33
|
+
const wrappedCallback = async (data) => {
|
|
34
|
+
this.off(hookName, wrappedCallback);
|
|
35
|
+
await callback(data);
|
|
36
|
+
};
|
|
37
|
+
return this.on(hookName, wrappedCallback);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Unregister a hook callback
|
|
42
|
+
* @param {string} hookName - Name of the hook
|
|
43
|
+
* @param {Function} callback - Callback function
|
|
44
|
+
*/
|
|
45
|
+
off(hookName, callback) {
|
|
46
|
+
const callbacks = this.hooks.get(hookName);
|
|
47
|
+
if (callbacks) {
|
|
48
|
+
callbacks.delete(callback);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Emit a hook event
|
|
54
|
+
* @param {string} hookName - Name of the hook
|
|
55
|
+
* @param {any} data - Data to pass to callbacks
|
|
56
|
+
* @returns {Promise<void>}
|
|
57
|
+
*/
|
|
58
|
+
async emit(hookName, data) {
|
|
59
|
+
const callbacks = this.hooks.get(hookName);
|
|
60
|
+
if (!callbacks || callbacks.size === 0) return;
|
|
61
|
+
|
|
62
|
+
// Execute all callbacks in parallel using Promise.allSettled
|
|
63
|
+
// This ensures one failing hook doesn't break others
|
|
64
|
+
const promises = Array.from(callbacks).map(callback => {
|
|
65
|
+
try {
|
|
66
|
+
return Promise.resolve(callback(data));
|
|
67
|
+
} catch (error) {
|
|
68
|
+
// Catch synchronous errors
|
|
69
|
+
return Promise.reject(error);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const results = await Promise.allSettled(promises);
|
|
74
|
+
|
|
75
|
+
// Log any rejected promises
|
|
76
|
+
results.forEach((result, index) => {
|
|
77
|
+
if (result.status === 'rejected') {
|
|
78
|
+
console.error(`[HookManager] Error in hook "${hookName}" (callback ${index + 1}):`, result.reason);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Clear all hooks or hooks for a specific event
|
|
85
|
+
* @param {string} [hookName] - Optional hook name to clear
|
|
86
|
+
*/
|
|
87
|
+
clear(hookName) {
|
|
88
|
+
if (hookName) {
|
|
89
|
+
this.hooks.delete(hookName);
|
|
90
|
+
} else {
|
|
91
|
+
this.hooks.clear();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get list of registered hook names
|
|
97
|
+
* @returns {string[]} Array of hook names
|
|
98
|
+
*/
|
|
99
|
+
getHookNames() {
|
|
100
|
+
return Array.from(this.hooks.keys());
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get number of callbacks for a hook
|
|
105
|
+
* @param {string} hookName - Name of the hook
|
|
106
|
+
* @returns {number} Number of callbacks
|
|
107
|
+
*/
|
|
108
|
+
getCallbackCount(hookName) {
|
|
109
|
+
const callbacks = this.hooks.get(hookName);
|
|
110
|
+
return callbacks ? callbacks.size : 0;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Available hook types
|
|
116
|
+
* @type {Object<string, string>}
|
|
117
|
+
*/
|
|
118
|
+
export const HOOK_TYPES = {
|
|
119
|
+
// Lifecycle hooks
|
|
120
|
+
AGENT_INITIALIZED: 'agent:initialized',
|
|
121
|
+
AGENT_CLEANUP: 'agent:cleanup',
|
|
122
|
+
|
|
123
|
+
// Message hooks
|
|
124
|
+
MESSAGE_USER: 'message:user',
|
|
125
|
+
MESSAGE_ASSISTANT: 'message:assistant',
|
|
126
|
+
MESSAGE_SYSTEM: 'message:system',
|
|
127
|
+
|
|
128
|
+
// Tool execution hooks
|
|
129
|
+
TOOL_START: 'tool:start',
|
|
130
|
+
TOOL_END: 'tool:end',
|
|
131
|
+
TOOL_ERROR: 'tool:error',
|
|
132
|
+
|
|
133
|
+
// AI streaming hooks
|
|
134
|
+
AI_STREAM_START: 'ai:stream:start',
|
|
135
|
+
AI_STREAM_DELTA: 'ai:stream:delta',
|
|
136
|
+
AI_STREAM_END: 'ai:stream:end',
|
|
137
|
+
|
|
138
|
+
// Storage hooks
|
|
139
|
+
STORAGE_LOAD: 'storage:load',
|
|
140
|
+
STORAGE_SAVE: 'storage:save',
|
|
141
|
+
STORAGE_CLEAR: 'storage:clear',
|
|
142
|
+
|
|
143
|
+
// Iteration hooks
|
|
144
|
+
ITERATION_START: 'iteration:start',
|
|
145
|
+
ITERATION_END: 'iteration:end',
|
|
146
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { HookManager, HOOK_TYPES } from './HookManager.js';
|