@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,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vercel AI SDK Engine - wraps existing ProbeAgent logic
|
|
3
|
+
* This maintains full backward compatibility
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { streamText } from 'ai';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create a Vercel AI SDK engine
|
|
10
|
+
* @param {Object} agent - The ProbeAgent instance
|
|
11
|
+
* @returns {Object} Engine interface
|
|
12
|
+
*/
|
|
13
|
+
export function createVercelEngine(agent) {
|
|
14
|
+
return {
|
|
15
|
+
/**
|
|
16
|
+
* Query the model using existing Vercel AI SDK implementation
|
|
17
|
+
* @param {string} prompt - The prompt to send
|
|
18
|
+
* @param {Object} options - Additional options
|
|
19
|
+
* @returns {AsyncIterable} Response stream
|
|
20
|
+
*/
|
|
21
|
+
async *query(prompt, options = {}) {
|
|
22
|
+
// Build messages array
|
|
23
|
+
const messages = [
|
|
24
|
+
...agent.history,
|
|
25
|
+
{ role: 'user', content: prompt }
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
// Use existing streamText with retry and fallback
|
|
29
|
+
const result = await agent.streamTextWithRetryAndFallback({
|
|
30
|
+
model: agent.provider(agent.model),
|
|
31
|
+
messages,
|
|
32
|
+
maxTokens: options.maxTokens || agent.maxResponseTokens,
|
|
33
|
+
temperature: options.temperature,
|
|
34
|
+
tools: options.tools,
|
|
35
|
+
toolChoice: options.toolChoice,
|
|
36
|
+
experimental_telemetry: options.telemetry
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Stream the response
|
|
40
|
+
for await (const chunk of result.textStream) {
|
|
41
|
+
yield { type: 'text', content: chunk };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Handle tool calls if any
|
|
45
|
+
if (result.toolCalls && result.toolCalls.length > 0) {
|
|
46
|
+
yield { type: 'tool_calls', toolCalls: result.toolCalls };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Handle finish reason
|
|
50
|
+
if (result.finishReason) {
|
|
51
|
+
yield { type: 'finish', reason: result.finishReason };
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Optional cleanup
|
|
57
|
+
*/
|
|
58
|
+
async close() {
|
|
59
|
+
// Nothing to cleanup for Vercel AI
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in MCP Server for Probe
|
|
3
|
+
* Runs in the same process as ProbeAgent, eliminating spawn overhead
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createServer } from 'http';
|
|
7
|
+
import { EventEmitter } from 'events';
|
|
8
|
+
import { Server as MCPServer } from '@modelcontextprotocol/sdk/server/index.js';
|
|
9
|
+
import {
|
|
10
|
+
CallToolRequestSchema,
|
|
11
|
+
ListToolsRequestSchema
|
|
12
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Built-in MCP Server that runs in-process
|
|
16
|
+
*/
|
|
17
|
+
export class BuiltInMCPServer extends EventEmitter {
|
|
18
|
+
constructor(agent, options = {}) {
|
|
19
|
+
super();
|
|
20
|
+
this.agent = agent;
|
|
21
|
+
this.port = options.port || 0; // 0 = ephemeral port
|
|
22
|
+
this.host = options.host || '127.0.0.1';
|
|
23
|
+
this.httpServer = null;
|
|
24
|
+
this.mcpServer = null;
|
|
25
|
+
this.connections = new Set();
|
|
26
|
+
this.debug = options.debug || false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Start the built-in MCP server
|
|
31
|
+
*/
|
|
32
|
+
async start() {
|
|
33
|
+
// Create HTTP server for SSE/HTTP transport
|
|
34
|
+
this.httpServer = createServer();
|
|
35
|
+
|
|
36
|
+
// Handle SSE connections
|
|
37
|
+
this.httpServer.on('request', (req, res) => {
|
|
38
|
+
this.handleRequest(req, res);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Create MCP server
|
|
42
|
+
this.mcpServer = new MCPServer({
|
|
43
|
+
name: 'probe-builtin',
|
|
44
|
+
version: '1.0.0'
|
|
45
|
+
}, {
|
|
46
|
+
capabilities: {
|
|
47
|
+
tools: {}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Register MCP handlers
|
|
52
|
+
this.registerHandlers();
|
|
53
|
+
|
|
54
|
+
// Start listening on ephemeral port
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
this.httpServer.listen(this.port, this.host, () => {
|
|
57
|
+
const address = this.httpServer.address();
|
|
58
|
+
this.port = address.port;
|
|
59
|
+
|
|
60
|
+
if (this.debug) {
|
|
61
|
+
console.log(`[MCP] Built-in server started at http://${this.host}:${this.port}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.emit('ready', { host: this.host, port: this.port });
|
|
65
|
+
resolve({ host: this.host, port: this.port });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
this.httpServer.on('error', reject);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Handle HTTP requests (SSE and JSON-RPC)
|
|
74
|
+
*/
|
|
75
|
+
handleRequest(req, res) {
|
|
76
|
+
const { method, url } = req;
|
|
77
|
+
|
|
78
|
+
// CORS headers for local development
|
|
79
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
80
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
81
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
82
|
+
|
|
83
|
+
if (method === 'OPTIONS') {
|
|
84
|
+
res.writeHead(204);
|
|
85
|
+
res.end();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Handle SSE endpoint
|
|
90
|
+
if (url === '/sse' && method === 'GET') {
|
|
91
|
+
this.handleSSE(req, res);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Handle JSON-RPC endpoint
|
|
96
|
+
if (url === '/rpc' && method === 'POST') {
|
|
97
|
+
this.handleJSONRPC(req, res);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Handle stdio-like protocol over HTTP
|
|
102
|
+
if (url === '/mcp' && method === 'POST') {
|
|
103
|
+
this.handleMCPProtocol(req, res);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Health check
|
|
108
|
+
if (url === '/health') {
|
|
109
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
110
|
+
res.end(JSON.stringify({
|
|
111
|
+
status: 'ok',
|
|
112
|
+
server: 'probe-builtin-mcp',
|
|
113
|
+
tools: this.getToolCount()
|
|
114
|
+
}));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 404 for unknown endpoints
|
|
119
|
+
res.writeHead(404);
|
|
120
|
+
res.end('Not Found');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Handle Server-Sent Events connection
|
|
125
|
+
*/
|
|
126
|
+
handleSSE(req, res) {
|
|
127
|
+
res.writeHead(200, {
|
|
128
|
+
'Content-Type': 'text/event-stream',
|
|
129
|
+
'Cache-Control': 'no-cache',
|
|
130
|
+
'Connection': 'keep-alive'
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Send initial connection event
|
|
134
|
+
res.write('event: connected\n');
|
|
135
|
+
res.write(`data: ${JSON.stringify({ type: 'connected', server: 'probe-builtin' })}\n\n`);
|
|
136
|
+
|
|
137
|
+
// Store connection
|
|
138
|
+
this.connections.add(res);
|
|
139
|
+
|
|
140
|
+
// Clean up on close
|
|
141
|
+
req.on('close', () => {
|
|
142
|
+
this.connections.delete(res);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Handle JSON-RPC requests
|
|
148
|
+
*/
|
|
149
|
+
async handleJSONRPC(req, res) {
|
|
150
|
+
let body = '';
|
|
151
|
+
|
|
152
|
+
req.on('data', chunk => {
|
|
153
|
+
body += chunk.toString();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
req.on('end', async () => {
|
|
157
|
+
try {
|
|
158
|
+
const request = JSON.parse(body);
|
|
159
|
+
const response = await this.processRequest(request);
|
|
160
|
+
|
|
161
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
162
|
+
res.end(JSON.stringify(response));
|
|
163
|
+
} catch (error) {
|
|
164
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
165
|
+
res.end(JSON.stringify({
|
|
166
|
+
jsonrpc: '2.0',
|
|
167
|
+
error: {
|
|
168
|
+
code: -32700,
|
|
169
|
+
message: 'Parse error',
|
|
170
|
+
data: error.message
|
|
171
|
+
},
|
|
172
|
+
id: null
|
|
173
|
+
}));
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Handle MCP protocol messages
|
|
180
|
+
*/
|
|
181
|
+
async handleMCPProtocol(req, res) {
|
|
182
|
+
let body = '';
|
|
183
|
+
|
|
184
|
+
req.on('data', chunk => {
|
|
185
|
+
body += chunk.toString();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
req.on('end', async () => {
|
|
189
|
+
try {
|
|
190
|
+
const message = JSON.parse(body);
|
|
191
|
+
|
|
192
|
+
// Process through MCP server handlers
|
|
193
|
+
let response;
|
|
194
|
+
|
|
195
|
+
if (message.method === 'tools/list') {
|
|
196
|
+
response = await this.handleListTools();
|
|
197
|
+
} else if (message.method === 'tools/call') {
|
|
198
|
+
response = await this.handleCallTool(message.params);
|
|
199
|
+
} else {
|
|
200
|
+
response = {
|
|
201
|
+
error: {
|
|
202
|
+
code: -32601,
|
|
203
|
+
message: 'Method not found'
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
209
|
+
res.end(JSON.stringify(response));
|
|
210
|
+
} catch (error) {
|
|
211
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
212
|
+
res.end(JSON.stringify({
|
|
213
|
+
error: {
|
|
214
|
+
code: -32603,
|
|
215
|
+
message: 'Internal error',
|
|
216
|
+
data: error.message
|
|
217
|
+
}
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Process JSON-RPC request
|
|
225
|
+
*/
|
|
226
|
+
async processRequest(request) {
|
|
227
|
+
const { jsonrpc, method, params, id } = request;
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
let result;
|
|
231
|
+
|
|
232
|
+
switch (method) {
|
|
233
|
+
case 'tools/list':
|
|
234
|
+
result = await this.handleListTools();
|
|
235
|
+
break;
|
|
236
|
+
|
|
237
|
+
case 'tools/call':
|
|
238
|
+
result = await this.handleCallTool(params);
|
|
239
|
+
break;
|
|
240
|
+
|
|
241
|
+
default:
|
|
242
|
+
return {
|
|
243
|
+
jsonrpc: '2.0',
|
|
244
|
+
error: {
|
|
245
|
+
code: -32601,
|
|
246
|
+
message: 'Method not found'
|
|
247
|
+
},
|
|
248
|
+
id
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
jsonrpc: '2.0',
|
|
254
|
+
result,
|
|
255
|
+
id
|
|
256
|
+
};
|
|
257
|
+
} catch (error) {
|
|
258
|
+
return {
|
|
259
|
+
jsonrpc: '2.0',
|
|
260
|
+
error: {
|
|
261
|
+
code: -32603,
|
|
262
|
+
message: 'Internal error',
|
|
263
|
+
data: error.message
|
|
264
|
+
},
|
|
265
|
+
id
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Register MCP protocol handlers
|
|
272
|
+
*/
|
|
273
|
+
registerHandlers() {
|
|
274
|
+
// Handle list tools request
|
|
275
|
+
this.mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
276
|
+
return this.handleListTools();
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Handle tool execution
|
|
280
|
+
this.mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
281
|
+
return this.handleCallTool(request.params);
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Handle list tools request
|
|
287
|
+
*/
|
|
288
|
+
async handleListTools() {
|
|
289
|
+
const tools = [];
|
|
290
|
+
|
|
291
|
+
// Get tools from agent
|
|
292
|
+
if (this.agent && this.agent.allowedTools) {
|
|
293
|
+
const toolDefs = {
|
|
294
|
+
search: {
|
|
295
|
+
description: 'Search for code patterns using semantic search',
|
|
296
|
+
inputSchema: {
|
|
297
|
+
type: 'object',
|
|
298
|
+
properties: {
|
|
299
|
+
query: { type: 'string', description: 'Search query' },
|
|
300
|
+
path: { type: 'string', description: 'Directory to search', default: '.' },
|
|
301
|
+
maxResults: { type: 'integer', default: 10 }
|
|
302
|
+
},
|
|
303
|
+
required: ['query']
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
extract: {
|
|
307
|
+
description: 'Extract code from specific file location',
|
|
308
|
+
inputSchema: {
|
|
309
|
+
type: 'object',
|
|
310
|
+
properties: {
|
|
311
|
+
path: { type: 'string', description: 'File path with optional line number' }
|
|
312
|
+
},
|
|
313
|
+
required: ['path']
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
listFiles: {
|
|
317
|
+
description: 'List files in a directory',
|
|
318
|
+
inputSchema: {
|
|
319
|
+
type: 'object',
|
|
320
|
+
properties: {
|
|
321
|
+
path: { type: 'string', description: 'Directory path' },
|
|
322
|
+
pattern: { type: 'string', description: 'File pattern' }
|
|
323
|
+
},
|
|
324
|
+
required: ['path']
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
searchFiles: {
|
|
328
|
+
description: 'Search for files by name pattern',
|
|
329
|
+
inputSchema: {
|
|
330
|
+
type: 'object',
|
|
331
|
+
properties: {
|
|
332
|
+
pattern: { type: 'string', description: 'File name pattern' },
|
|
333
|
+
path: { type: 'string', description: 'Directory to search' }
|
|
334
|
+
},
|
|
335
|
+
required: ['pattern']
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
query: {
|
|
339
|
+
description: 'Query code using AST patterns',
|
|
340
|
+
inputSchema: {
|
|
341
|
+
type: 'object',
|
|
342
|
+
properties: {
|
|
343
|
+
query: { type: 'string', description: 'AST query' },
|
|
344
|
+
path: { type: 'string', description: 'Directory to search' }
|
|
345
|
+
},
|
|
346
|
+
required: ['query']
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
for (const [name, def] of Object.entries(toolDefs)) {
|
|
352
|
+
if (this.agent.allowedTools.isEnabled(name)) {
|
|
353
|
+
tools.push({
|
|
354
|
+
name: `mcp__probe__${name}`,
|
|
355
|
+
description: def.description,
|
|
356
|
+
inputSchema: def.inputSchema
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return { tools };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Handle tool execution
|
|
367
|
+
*/
|
|
368
|
+
async handleCallTool(params) {
|
|
369
|
+
const { name, arguments: args } = params;
|
|
370
|
+
|
|
371
|
+
// Extract tool name from MCP format
|
|
372
|
+
const toolName = name.replace('mcp__probe__', '');
|
|
373
|
+
|
|
374
|
+
// Check if tool is enabled
|
|
375
|
+
if (!this.agent.allowedTools.isEnabled(toolName)) {
|
|
376
|
+
throw new Error(`Tool ${name} is not enabled`);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Get tool implementation
|
|
380
|
+
const tool = this.agent.toolImplementations[toolName];
|
|
381
|
+
if (!tool) {
|
|
382
|
+
throw new Error(`Tool ${name} not found`);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
try {
|
|
386
|
+
// Execute tool directly (no spawning!)
|
|
387
|
+
const result = await tool.execute(args);
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
content: [{
|
|
391
|
+
type: 'text',
|
|
392
|
+
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
|
|
393
|
+
}]
|
|
394
|
+
};
|
|
395
|
+
} catch (error) {
|
|
396
|
+
return {
|
|
397
|
+
content: [{
|
|
398
|
+
type: 'text',
|
|
399
|
+
text: `Error executing ${name}: ${error.message}`
|
|
400
|
+
}],
|
|
401
|
+
isError: true
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Get the number of available tools
|
|
408
|
+
*/
|
|
409
|
+
getToolCount() {
|
|
410
|
+
if (!this.agent || !this.agent.allowedTools) {
|
|
411
|
+
return 0;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const tools = ['search', 'extract', 'listFiles', 'searchFiles', 'query'];
|
|
415
|
+
return tools.filter(name => this.agent.allowedTools.isEnabled(name)).length;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Broadcast message to all SSE connections
|
|
420
|
+
*/
|
|
421
|
+
broadcast(event, data) {
|
|
422
|
+
const message = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
423
|
+
|
|
424
|
+
for (const connection of this.connections) {
|
|
425
|
+
connection.write(message);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Stop the server
|
|
431
|
+
*/
|
|
432
|
+
async stop() {
|
|
433
|
+
// Close all SSE connections
|
|
434
|
+
for (const connection of this.connections) {
|
|
435
|
+
connection.end();
|
|
436
|
+
}
|
|
437
|
+
this.connections.clear();
|
|
438
|
+
|
|
439
|
+
// Close HTTP server
|
|
440
|
+
if (this.httpServer) {
|
|
441
|
+
return new Promise((resolve) => {
|
|
442
|
+
this.httpServer.close(() => {
|
|
443
|
+
if (this.debug) {
|
|
444
|
+
console.log('[MCP] Built-in server stopped');
|
|
445
|
+
}
|
|
446
|
+
resolve();
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Get server configuration for MCP clients
|
|
454
|
+
*/
|
|
455
|
+
getConfig() {
|
|
456
|
+
return {
|
|
457
|
+
transport: 'http',
|
|
458
|
+
url: `http://${this.host}:${this.port}/mcp`,
|
|
459
|
+
// Alternative transports:
|
|
460
|
+
// sse: `http://${this.host}:${this.port}/sse`,
|
|
461
|
+
// rpc: `http://${this.host}:${this.port}/rpc`
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
}
|