@probelabs/probe 0.6.0-rc166 → 0.6.0-rc167
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/README.md +39 -0
- package/build/agent/ProbeAgent.js +291 -3
- package/build/agent/engines/enhanced-claude-code.js +595 -0
- package/build/agent/engines/enhanced-vercel.js +83 -0
- package/build/agent/engines/vercel.js +62 -0
- package/build/agent/index.js +1291 -161
- package/build/agent/mcp/built-in-server.js +464 -0
- package/cjs/agent/ProbeAgent.cjs +8979 -6698
- package/cjs/index.cjs +9030 -6749
- package/package.json +2 -1
- package/src/agent/ProbeAgent.js +291 -3
- package/src/agent/engines/enhanced-claude-code.js +595 -0
- package/src/agent/engines/enhanced-vercel.js +83 -0
- package/src/agent/engines/vercel.js +62 -0
- package/src/agent/mcp/built-in-server.js +464 -0
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Claude Code Engine with session management and better streaming
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
import { randomBytes } from 'crypto';
|
|
7
|
+
import fs from 'fs/promises';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import os from 'os';
|
|
10
|
+
import { EventEmitter } from 'events';
|
|
11
|
+
import { BuiltInMCPServer } from '../mcp/built-in-server.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Session manager for Claude Code conversations
|
|
15
|
+
*/
|
|
16
|
+
class ClaudeSession {
|
|
17
|
+
constructor(id, debug = false) {
|
|
18
|
+
this.id = id;
|
|
19
|
+
this.conversationId = null;
|
|
20
|
+
this.messageCount = 0;
|
|
21
|
+
this.debug = debug;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Update session with Claude's conversation ID
|
|
26
|
+
*/
|
|
27
|
+
setConversationId(convId) {
|
|
28
|
+
this.conversationId = convId;
|
|
29
|
+
if (this.debug) {
|
|
30
|
+
console.log(`[Session ${this.id}] Conversation ID: ${convId}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get resume arguments for continuing conversation
|
|
36
|
+
*/
|
|
37
|
+
getResumeArgs() {
|
|
38
|
+
if (this.conversationId && this.messageCount > 0) {
|
|
39
|
+
return ['--resume', this.conversationId];
|
|
40
|
+
}
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
incrementMessageCount() {
|
|
45
|
+
this.messageCount++;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Enhanced Claude Code Engine
|
|
51
|
+
*/
|
|
52
|
+
export async function createEnhancedClaudeCLIEngine(options = {}) {
|
|
53
|
+
const { agent, systemPrompt, customPrompt, debug, sessionId, allowedTools } = options;
|
|
54
|
+
|
|
55
|
+
// Create or reuse session
|
|
56
|
+
const session = new ClaudeSession(
|
|
57
|
+
sessionId || randomBytes(8).toString('hex'),
|
|
58
|
+
debug
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// Start built-in MCP server with ephemeral port
|
|
62
|
+
let mcpServer = null;
|
|
63
|
+
let mcpConfigPath = null;
|
|
64
|
+
|
|
65
|
+
if (agent) {
|
|
66
|
+
mcpServer = new BuiltInMCPServer(agent, {
|
|
67
|
+
port: 0, // Ephemeral port
|
|
68
|
+
host: '127.0.0.1',
|
|
69
|
+
debug: debug
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const { host, port } = await mcpServer.start();
|
|
73
|
+
|
|
74
|
+
if (debug) {
|
|
75
|
+
console.log('[DEBUG] Built-in MCP server started');
|
|
76
|
+
console.log('[DEBUG] MCP URL:', `http://${host}:${port}/mcp`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Create MCP config for Claude Code to use
|
|
80
|
+
// Note: Claude Code currently requires spawning a process, not HTTP transport
|
|
81
|
+
// Keep built-in server running but also provide process-based config for CLI
|
|
82
|
+
mcpConfigPath = path.join(os.tmpdir(), `probe-mcp-${session.id}.json`);
|
|
83
|
+
const mcpConfig = {
|
|
84
|
+
mcpServers: {
|
|
85
|
+
probe: {
|
|
86
|
+
command: 'node',
|
|
87
|
+
args: [path.join(process.cwd(), 'mcp-probe-server.js')],
|
|
88
|
+
env: {
|
|
89
|
+
PROBE_WORKSPACE: process.cwd(),
|
|
90
|
+
DEBUG: debug ? 'true' : 'false'
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
await fs.writeFile(mcpConfigPath, JSON.stringify(mcpConfig, null, 2));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (debug) {
|
|
100
|
+
console.log('[DEBUG] Enhanced Claude Code Engine');
|
|
101
|
+
console.log('[DEBUG] Session:', session.id);
|
|
102
|
+
console.log('[DEBUG] MCP Config:', mcpConfigPath);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Combine prompts
|
|
106
|
+
const fullSystemPrompt = combinePrompts(systemPrompt, customPrompt, agent);
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
sessionId: session.id,
|
|
110
|
+
session,
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Query Claude with advanced streaming
|
|
114
|
+
*/
|
|
115
|
+
async *query(prompt, opts = {}) {
|
|
116
|
+
const emitter = new EventEmitter();
|
|
117
|
+
let buffer = '';
|
|
118
|
+
let processEnded = false;
|
|
119
|
+
let currentToolCall = null;
|
|
120
|
+
let isSchemaMode = false;
|
|
121
|
+
|
|
122
|
+
// Check if this is a schema reminder or validation request
|
|
123
|
+
// In these cases, we treat Claude Code as a black box - just get the response
|
|
124
|
+
if (opts.schema || prompt.includes('JSON schema') || prompt.includes('mermaid diagram')) {
|
|
125
|
+
isSchemaMode = true;
|
|
126
|
+
if (debug) {
|
|
127
|
+
console.log('[DEBUG] Schema/validation mode - treating as black box');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// For schema mode, append the schema requirement to the prompt
|
|
132
|
+
let finalPrompt = prompt;
|
|
133
|
+
if (opts.schema && isSchemaMode) {
|
|
134
|
+
finalPrompt = `${prompt}\n\nPlease provide your response in the following JSON format:\n${opts.schema}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Build command arguments
|
|
138
|
+
const args = buildClaudeArgs({
|
|
139
|
+
systemPrompt: fullSystemPrompt,
|
|
140
|
+
mcpConfigPath,
|
|
141
|
+
session,
|
|
142
|
+
debug,
|
|
143
|
+
prompt: finalPrompt, // Use finalPrompt which may include schema
|
|
144
|
+
allowedTools: allowedTools || opts.allowedTools // Support tool filtering
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (debug) {
|
|
148
|
+
console.log('[DEBUG] Executing: claude', args.join(' '));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// CRITICAL: Claude Code requires echo pipe to work when spawned from Node.js
|
|
152
|
+
// Without it, the process hangs indefinitely waiting for stdin
|
|
153
|
+
// This has been tested extensively - see CLAUDE_CLI_ECHO_REQUIREMENT.md
|
|
154
|
+
// DO NOT REMOVE THE echo "" | PREFIX
|
|
155
|
+
// SECURITY: Shell argument escaping using POSIX single-quote method
|
|
156
|
+
// Single quotes in POSIX shells protect ALL metacharacters (;|&$`><*?) except single quote itself
|
|
157
|
+
// The pattern 'text'\''more' correctly handles embedded quotes by:
|
|
158
|
+
// 1. Closing the current quote with '
|
|
159
|
+
// 2. Adding an escaped quote \'
|
|
160
|
+
// 3. Opening a new quote with '
|
|
161
|
+
// This is the standard POSIX-compliant method and is completely safe against injection
|
|
162
|
+
const shellCmd = `echo "" | claude ${args.map(arg => {
|
|
163
|
+
// Validate arg is a string (paranoid check)
|
|
164
|
+
if (typeof arg !== 'string') {
|
|
165
|
+
throw new TypeError(`Invalid argument type: expected string, got ${typeof arg}`);
|
|
166
|
+
}
|
|
167
|
+
// Escape single quotes using POSIX method: ' -> '\''
|
|
168
|
+
const escaped = arg.replace(/'/g, "'\\''");
|
|
169
|
+
// Wrap in single quotes for complete shell metacharacter protection
|
|
170
|
+
return `'${escaped}'`;
|
|
171
|
+
}).join(' ')}`;
|
|
172
|
+
|
|
173
|
+
if (debug) {
|
|
174
|
+
console.log('[DEBUG] Shell command length:', shellCmd.length);
|
|
175
|
+
// Don't log full command if too long (e.g. system prompt)
|
|
176
|
+
if (shellCmd.length < 500) {
|
|
177
|
+
console.log('[DEBUG] Shell command:', shellCmd);
|
|
178
|
+
} else {
|
|
179
|
+
console.log('[DEBUG] Shell command (truncated):', shellCmd.substring(0, 200) + '...');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Initialize tool collector for batch emission
|
|
184
|
+
const toolCollector = [];
|
|
185
|
+
|
|
186
|
+
// Spawn using shell wrapper with echo pipe
|
|
187
|
+
const proc = spawn('sh', ['-c', shellCmd], {
|
|
188
|
+
env: { ...process.env, FORCE_COLOR: '0' },
|
|
189
|
+
stdio: ['ignore', 'pipe', 'pipe'] // Ignore stdin since echo handles it
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Handle stdout
|
|
193
|
+
proc.stdout.on('data', (data) => {
|
|
194
|
+
buffer += data.toString();
|
|
195
|
+
processJsonBuffer(buffer, emitter, session, debug, toolCollector);
|
|
196
|
+
|
|
197
|
+
// Keep only incomplete line in buffer
|
|
198
|
+
const lines = buffer.split('\n');
|
|
199
|
+
buffer = lines[lines.length - 1] || '';
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Handle stderr
|
|
203
|
+
proc.stderr.on('data', (data) => {
|
|
204
|
+
const stderr = data.toString();
|
|
205
|
+
if (debug) {
|
|
206
|
+
console.error('[STDERR]', stderr);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Check for important errors
|
|
210
|
+
if (stderr.includes('command not found')) {
|
|
211
|
+
emitter.emit('error', new Error('Claude Code not found. Please install it first.'));
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Handle process end
|
|
216
|
+
proc.on('close', (code) => {
|
|
217
|
+
processEnded = true;
|
|
218
|
+
if (code !== 0 && debug) {
|
|
219
|
+
console.log(`[DEBUG] Process exited with code ${code}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Process any remaining buffer
|
|
223
|
+
if (buffer.trim()) {
|
|
224
|
+
processJsonBuffer(buffer, emitter, session, debug, toolCollector);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Emit collected tool events as batch
|
|
228
|
+
if (toolCollector.length > 0) {
|
|
229
|
+
emitter.emit('toolBatch', {
|
|
230
|
+
tools: toolCollector,
|
|
231
|
+
timestamp: new Date().toISOString()
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
if (debug) {
|
|
235
|
+
console.log(`[DEBUG] Emitting batch of ${toolCollector.length} tool events`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
emitter.emit('end');
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
proc.on('error', (error) => {
|
|
243
|
+
emitter.emit('error', error);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Stream generator
|
|
247
|
+
const messageQueue = [];
|
|
248
|
+
let resolver = null;
|
|
249
|
+
|
|
250
|
+
emitter.on('message', (msg) => {
|
|
251
|
+
messageQueue.push(msg);
|
|
252
|
+
if (resolver) {
|
|
253
|
+
resolver();
|
|
254
|
+
resolver = null;
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
emitter.on('toolBatch', (batch) => {
|
|
259
|
+
messageQueue.push({ type: 'toolBatch', ...batch });
|
|
260
|
+
if (resolver) {
|
|
261
|
+
resolver();
|
|
262
|
+
resolver = null;
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
emitter.on('end', () => {
|
|
267
|
+
processEnded = true;
|
|
268
|
+
if (resolver) {
|
|
269
|
+
resolver();
|
|
270
|
+
resolver = null;
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
emitter.on('error', (error) => {
|
|
275
|
+
messageQueue.push({ type: 'error', error });
|
|
276
|
+
if (resolver) {
|
|
277
|
+
resolver();
|
|
278
|
+
resolver = null;
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Process messages
|
|
283
|
+
while (!processEnded || messageQueue.length > 0) {
|
|
284
|
+
if (messageQueue.length > 0) {
|
|
285
|
+
const msg = messageQueue.shift();
|
|
286
|
+
|
|
287
|
+
if (msg.type === 'text') {
|
|
288
|
+
yield { type: 'text', content: msg.content };
|
|
289
|
+
} else if (msg.type === 'tool_use') {
|
|
290
|
+
// Start tool execution
|
|
291
|
+
currentToolCall = msg;
|
|
292
|
+
yield {
|
|
293
|
+
type: 'text',
|
|
294
|
+
content: `\n🔧 Using ${msg.name}: ${JSON.stringify(msg.input)}\n`
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// Execute tool
|
|
298
|
+
const result = await executeProbleTool(agent, msg.name, msg.input);
|
|
299
|
+
yield { type: 'text', content: `${result}\n` };
|
|
300
|
+
} else if (msg.type === 'toolBatch') {
|
|
301
|
+
// Pass through the tool batch for ProbeAgent to emit
|
|
302
|
+
yield { type: 'toolBatch', tools: msg.tools, timestamp: msg.timestamp };
|
|
303
|
+
} else if (msg.type === 'session_update') {
|
|
304
|
+
// Session was updated with conversation ID
|
|
305
|
+
if (debug) {
|
|
306
|
+
console.log('[DEBUG] Session updated:', msg.conversationId);
|
|
307
|
+
}
|
|
308
|
+
} else if (msg.type === 'error') {
|
|
309
|
+
yield { type: 'error', error: msg.error };
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
} else if (!processEnded) {
|
|
313
|
+
// Wait for more messages
|
|
314
|
+
await new Promise(resolve => {
|
|
315
|
+
resolver = resolve;
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Increment message count
|
|
321
|
+
session.incrementMessageCount();
|
|
322
|
+
|
|
323
|
+
// Return session info for potential resume
|
|
324
|
+
yield {
|
|
325
|
+
type: 'metadata',
|
|
326
|
+
data: {
|
|
327
|
+
sessionId: session.id,
|
|
328
|
+
conversationId: session.conversationId,
|
|
329
|
+
messageCount: session.messageCount
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Get session info
|
|
336
|
+
*/
|
|
337
|
+
getSession() {
|
|
338
|
+
return {
|
|
339
|
+
id: session.id,
|
|
340
|
+
conversationId: session.conversationId,
|
|
341
|
+
messageCount: session.messageCount
|
|
342
|
+
};
|
|
343
|
+
},
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Clean up - MUST be called to stop MCP server and clean resources
|
|
347
|
+
*/
|
|
348
|
+
async close() {
|
|
349
|
+
try {
|
|
350
|
+
// Stop built-in MCP server
|
|
351
|
+
if (mcpServer) {
|
|
352
|
+
await mcpServer.stop();
|
|
353
|
+
if (debug) {
|
|
354
|
+
console.log('[DEBUG] Built-in MCP server stopped');
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Remove temporary MCP config file
|
|
359
|
+
if (mcpConfigPath) {
|
|
360
|
+
await fs.unlink(mcpConfigPath).catch(() => {});
|
|
361
|
+
if (debug) {
|
|
362
|
+
console.log('[DEBUG] MCP config file removed');
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (debug) {
|
|
367
|
+
console.log('[DEBUG] Engine closed, session:', session.id);
|
|
368
|
+
}
|
|
369
|
+
} catch (error) {
|
|
370
|
+
if (debug) {
|
|
371
|
+
console.error('[DEBUG] Error during cleanup:', error.message);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Process JSON buffer and emit messages
|
|
380
|
+
*/
|
|
381
|
+
function processJsonBuffer(buffer, emitter, session, debug, toolCollector = null) {
|
|
382
|
+
const lines = buffer.split('\n');
|
|
383
|
+
|
|
384
|
+
for (const line of lines) {
|
|
385
|
+
if (!line.trim()) continue;
|
|
386
|
+
|
|
387
|
+
try {
|
|
388
|
+
const parsed = JSON.parse(line);
|
|
389
|
+
|
|
390
|
+
// Claude Code might return an array of messages
|
|
391
|
+
const messages = Array.isArray(parsed) ? parsed : [parsed];
|
|
392
|
+
|
|
393
|
+
for (const msg of messages) {
|
|
394
|
+
|
|
395
|
+
switch (msg.type) {
|
|
396
|
+
case 'result':
|
|
397
|
+
// Claude Code returns a complete result object
|
|
398
|
+
if (msg.result) {
|
|
399
|
+
emitter.emit('message', { type: 'text', content: msg.result });
|
|
400
|
+
}
|
|
401
|
+
if (msg.session_id) {
|
|
402
|
+
session.setConversationId(msg.session_id);
|
|
403
|
+
emitter.emit('message', { type: 'session_update', conversationId: msg.session_id });
|
|
404
|
+
}
|
|
405
|
+
break;
|
|
406
|
+
|
|
407
|
+
case 'conversation':
|
|
408
|
+
session.setConversationId(msg.id);
|
|
409
|
+
emitter.emit('message', { type: 'session_update', conversationId: msg.id });
|
|
410
|
+
break;
|
|
411
|
+
|
|
412
|
+
case 'text':
|
|
413
|
+
if (msg.text) {
|
|
414
|
+
emitter.emit('message', { type: 'text', content: msg.text });
|
|
415
|
+
}
|
|
416
|
+
break;
|
|
417
|
+
|
|
418
|
+
case 'assistant':
|
|
419
|
+
// Claude Code emits assistant messages when using internal agents/tools
|
|
420
|
+
if (msg.message && msg.message.content) {
|
|
421
|
+
// Extract text from the content array
|
|
422
|
+
for (const content of msg.message.content) {
|
|
423
|
+
if (content.type === 'text' && content.text) {
|
|
424
|
+
emitter.emit('message', { type: 'text', content: content.text });
|
|
425
|
+
} else if (content.type === 'tool_use') {
|
|
426
|
+
// Collect tool call for batch emission
|
|
427
|
+
if (toolCollector) {
|
|
428
|
+
toolCollector.push({
|
|
429
|
+
timestamp: new Date().toISOString(),
|
|
430
|
+
name: content.name,
|
|
431
|
+
args: content.input || {},
|
|
432
|
+
id: content.id,
|
|
433
|
+
status: 'started'
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
// Internal tool use - already handled by Claude Code
|
|
437
|
+
if (debug) {
|
|
438
|
+
console.log('[DEBUG] Assistant internal tool use:', content.name);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
break;
|
|
444
|
+
|
|
445
|
+
case 'tool_use':
|
|
446
|
+
// Collect tool call for batch emission
|
|
447
|
+
if (toolCollector) {
|
|
448
|
+
toolCollector.push({
|
|
449
|
+
timestamp: new Date().toISOString(),
|
|
450
|
+
name: msg.name,
|
|
451
|
+
args: msg.input || {},
|
|
452
|
+
id: msg.id,
|
|
453
|
+
status: 'started'
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
emitter.emit('message', {
|
|
457
|
+
type: 'tool_use',
|
|
458
|
+
id: msg.id,
|
|
459
|
+
name: msg.name,
|
|
460
|
+
input: msg.input
|
|
461
|
+
});
|
|
462
|
+
break;
|
|
463
|
+
|
|
464
|
+
case 'tool_result':
|
|
465
|
+
// Mark tool as completed in collector
|
|
466
|
+
if (toolCollector && msg.tool_use_id) {
|
|
467
|
+
// Find the matching tool call and update its status
|
|
468
|
+
const toolCall = toolCollector.find(t => t.id === msg.tool_use_id);
|
|
469
|
+
if (toolCall) {
|
|
470
|
+
toolCall.status = 'completed';
|
|
471
|
+
toolCall.resultPreview = msg.content ?
|
|
472
|
+
(typeof msg.content === 'string' ?
|
|
473
|
+
msg.content.substring(0, 200) :
|
|
474
|
+
JSON.stringify(msg.content).substring(0, 200)) + '...' :
|
|
475
|
+
'No Result';
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
// Tool results are handled internally
|
|
479
|
+
if (debug) {
|
|
480
|
+
console.log('[DEBUG] Tool result:', msg);
|
|
481
|
+
}
|
|
482
|
+
break;
|
|
483
|
+
|
|
484
|
+
case 'error':
|
|
485
|
+
emitter.emit('error', new Error(msg.message || 'Unknown error'));
|
|
486
|
+
break;
|
|
487
|
+
|
|
488
|
+
default:
|
|
489
|
+
if (debug) {
|
|
490
|
+
console.log('[DEBUG] Unknown message type:', msg.type);
|
|
491
|
+
console.log('[DEBUG] Full message:', JSON.stringify(msg).substring(0, 200));
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
} // Close inner for loop for messages array
|
|
495
|
+
} catch (e) {
|
|
496
|
+
// Not valid JSON, might be partial
|
|
497
|
+
if (debug && line.trim()) {
|
|
498
|
+
console.log('[DEBUG] Non-JSON output:', line);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Build claude command arguments
|
|
506
|
+
*/
|
|
507
|
+
function buildClaudeArgs({ systemPrompt, mcpConfigPath, session, debug, prompt, allowedTools }) {
|
|
508
|
+
const args = [
|
|
509
|
+
'-p', // Short form of --print
|
|
510
|
+
prompt, // The prompt text goes right after -p
|
|
511
|
+
'--output-format', 'json'
|
|
512
|
+
];
|
|
513
|
+
|
|
514
|
+
// Add session resume if available
|
|
515
|
+
const resumeArgs = session.getResumeArgs();
|
|
516
|
+
if (resumeArgs.length > 0) {
|
|
517
|
+
args.push(...resumeArgs);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Add system prompt
|
|
521
|
+
if (systemPrompt) {
|
|
522
|
+
args.push('--system-prompt', systemPrompt);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Add MCP config
|
|
526
|
+
args.push('--mcp-config', mcpConfigPath);
|
|
527
|
+
|
|
528
|
+
// Add allowed tools filter if specified
|
|
529
|
+
// If no filter specified, allow all probe tools
|
|
530
|
+
if (allowedTools && Array.isArray(allowedTools) && allowedTools.length > 0) {
|
|
531
|
+
// Convert tool names to MCP format: mcp__probe__<toolname>
|
|
532
|
+
const mcpTools = allowedTools.map(tool =>
|
|
533
|
+
tool.startsWith('mcp__') ? tool : `mcp__probe__${tool}`
|
|
534
|
+
).join(',');
|
|
535
|
+
args.push('--allowedTools', mcpTools);
|
|
536
|
+
} else {
|
|
537
|
+
// Default: allow all probe tools
|
|
538
|
+
args.push('--allowedTools', 'mcp__probe__*');
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Add debug flag
|
|
542
|
+
if (debug) {
|
|
543
|
+
args.push('--verbose');
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return args;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Execute Probe tool through agent
|
|
551
|
+
*/
|
|
552
|
+
async function executeProbleTool(agent, toolName, params) {
|
|
553
|
+
if (!agent || !agent.toolImplementations) {
|
|
554
|
+
return 'Tool execution not available';
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Remove MCP prefix: mcp__probe__<toolname> -> <toolname>
|
|
558
|
+
const name = toolName.replace(/^mcp__probe__/, '');
|
|
559
|
+
const tool = agent.toolImplementations[name];
|
|
560
|
+
|
|
561
|
+
if (!tool) {
|
|
562
|
+
return `Unknown tool: ${name}`;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
try {
|
|
566
|
+
const result = await tool.execute(params);
|
|
567
|
+
return typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
568
|
+
} catch (error) {
|
|
569
|
+
return `Tool error: ${error.message}`;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Old createEnhancedMCPConfig function removed - now using built-in MCP server
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Combine prompts intelligently
|
|
577
|
+
*/
|
|
578
|
+
function combinePrompts(systemPrompt, customPrompt, agent) {
|
|
579
|
+
// For Claude Code, the systemPrompt already contains all necessary instructions
|
|
580
|
+
// from getClaudeNativeSystemPrompt(), so we don't need to add a base prompt
|
|
581
|
+
|
|
582
|
+
// If only customPrompt is provided (no systemPrompt), use it as the main prompt
|
|
583
|
+
if (!systemPrompt && customPrompt) {
|
|
584
|
+
return customPrompt;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// If systemPrompt is provided, it's already complete from getClaudeNativeSystemPrompt
|
|
588
|
+
// Just add customPrompt if available
|
|
589
|
+
if (systemPrompt && customPrompt) {
|
|
590
|
+
return systemPrompt + '\n\n## Additional Instructions\n' + customPrompt;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Return systemPrompt as-is if no customPrompt
|
|
594
|
+
return systemPrompt || '';
|
|
595
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Vercel AI SDK Engine with proper tool and prompt support
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { streamText } from 'ai';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create an enhanced Vercel AI SDK engine with full tool support
|
|
9
|
+
* @param {Object} agent - The ProbeAgent instance
|
|
10
|
+
* @returns {Object} Engine interface
|
|
11
|
+
*/
|
|
12
|
+
export function createEnhancedVercelEngine(agent) {
|
|
13
|
+
return {
|
|
14
|
+
/**
|
|
15
|
+
* Query the model using existing Vercel AI SDK implementation
|
|
16
|
+
* @param {string} prompt - The prompt to send
|
|
17
|
+
* @param {Object} options - Additional options
|
|
18
|
+
* @returns {AsyncIterable} Response stream
|
|
19
|
+
*/
|
|
20
|
+
async *query(prompt, options = {}) {
|
|
21
|
+
// Get the system message with tools embedded (existing behavior)
|
|
22
|
+
const systemMessage = await agent.getSystemMessage();
|
|
23
|
+
|
|
24
|
+
// Build messages array with system prompt
|
|
25
|
+
const messages = [
|
|
26
|
+
{ role: 'system', content: systemMessage },
|
|
27
|
+
...agent.history,
|
|
28
|
+
{ role: 'user', content: prompt }
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
// Use existing streamText with retry and fallback
|
|
32
|
+
const result = await agent.streamTextWithRetryAndFallback({
|
|
33
|
+
model: agent.provider(agent.model),
|
|
34
|
+
messages,
|
|
35
|
+
maxTokens: options.maxTokens || agent.maxResponseTokens,
|
|
36
|
+
temperature: options.temperature || 0.3,
|
|
37
|
+
// Note: Vercel AI SDK doesn't use structured tools for XML format
|
|
38
|
+
// The tools are embedded in the system prompt
|
|
39
|
+
experimental_telemetry: options.telemetry
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Stream the response
|
|
43
|
+
let fullContent = '';
|
|
44
|
+
for await (const chunk of result.textStream) {
|
|
45
|
+
fullContent += chunk;
|
|
46
|
+
yield { type: 'text', content: chunk };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Parse XML tool calls from the response if any
|
|
50
|
+
// This maintains compatibility with existing XML tool format
|
|
51
|
+
const toolCalls = agent.parseXmlToolCalls ? agent.parseXmlToolCalls(fullContent) : null;
|
|
52
|
+
if (toolCalls && toolCalls.length > 0) {
|
|
53
|
+
yield { type: 'tool_calls', toolCalls };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Handle finish reason
|
|
57
|
+
if (result.finishReason) {
|
|
58
|
+
yield { type: 'finish', reason: result.finishReason };
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get available tools for this engine
|
|
64
|
+
*/
|
|
65
|
+
getTools() {
|
|
66
|
+
return agent.toolImplementations || {};
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get system prompt for this engine
|
|
71
|
+
*/
|
|
72
|
+
async getSystemPrompt() {
|
|
73
|
+
return agent.getSystemMessage();
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Optional cleanup
|
|
78
|
+
*/
|
|
79
|
+
async close() {
|
|
80
|
+
// Nothing to cleanup for Vercel AI
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|