@probelabs/probe 0.6.0-rc100
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 +583 -0
- package/bin/.gitkeep +0 -0
- package/bin/probe +158 -0
- package/bin/probe-binary +0 -0
- package/build/agent/ProbeAgent.d.ts +199 -0
- package/build/agent/ProbeAgent.js +1486 -0
- package/build/agent/acp/README.md +347 -0
- package/build/agent/acp/connection.js +237 -0
- package/build/agent/acp/connection.test.js +311 -0
- package/build/agent/acp/examples/simple-client.js +212 -0
- package/build/agent/acp/examples/tool-lifecycle.js +230 -0
- package/build/agent/acp/final-test.js +173 -0
- package/build/agent/acp/index.js +5 -0
- package/build/agent/acp/integration.test.js +385 -0
- package/build/agent/acp/manual-test.js +410 -0
- package/build/agent/acp/protocol-test.js +190 -0
- package/build/agent/acp/server.js +448 -0
- package/build/agent/acp/server.test.js +371 -0
- package/build/agent/acp/test-runner.js +216 -0
- package/build/agent/acp/test-utils/README.md +315 -0
- package/build/agent/acp/test-utils/acp-tester.js +484 -0
- package/build/agent/acp/test-utils/mock-acp-client.js +434 -0
- package/build/agent/acp/tools.js +368 -0
- package/build/agent/acp/tools.test.js +334 -0
- package/build/agent/acp/types.js +218 -0
- package/build/agent/acp/types.test.js +327 -0
- package/build/agent/appTracer.js +360 -0
- package/build/agent/fileSpanExporter.js +169 -0
- package/build/agent/index.js +7426 -0
- package/build/agent/mcp/client.js +338 -0
- package/build/agent/mcp/config.js +313 -0
- package/build/agent/mcp/index.js +64 -0
- package/build/agent/mcp/xmlBridge.js +371 -0
- package/build/agent/mockProvider.js +53 -0
- package/build/agent/probeTool.js +257 -0
- package/build/agent/schemaUtils.js +1726 -0
- package/build/agent/simpleTelemetry.js +267 -0
- package/build/agent/telemetry.js +225 -0
- package/build/agent/tokenCounter.js +395 -0
- package/build/agent/tools.js +163 -0
- package/build/cli.js +49 -0
- package/build/delegate.js +267 -0
- package/build/directory-resolver.js +237 -0
- package/build/downloader.js +750 -0
- package/build/extract.js +149 -0
- package/build/index.js +70 -0
- package/build/mcp/index.js +514 -0
- package/build/mcp/index.ts +608 -0
- package/build/query.js +116 -0
- package/build/search.js +247 -0
- package/build/tools/common.js +410 -0
- package/build/tools/index.js +40 -0
- package/build/tools/langchain.js +88 -0
- package/build/tools/system-message.js +121 -0
- package/build/tools/vercel.js +271 -0
- package/build/utils/file-lister.js +193 -0
- package/build/utils.js +128 -0
- package/cjs/agent/ProbeAgent.cjs +5829 -0
- package/cjs/index.cjs +6217 -0
- package/cjs/package.json +3 -0
- package/index.d.ts +401 -0
- package/package.json +114 -0
- package/scripts/postinstall.js +172 -0
- package/src/agent/ProbeAgent.d.ts +199 -0
- package/src/agent/ProbeAgent.js +1486 -0
- package/src/agent/acp/README.md +347 -0
- package/src/agent/acp/connection.js +237 -0
- package/src/agent/acp/connection.test.js +311 -0
- package/src/agent/acp/examples/simple-client.js +212 -0
- package/src/agent/acp/examples/tool-lifecycle.js +230 -0
- package/src/agent/acp/final-test.js +173 -0
- package/src/agent/acp/index.js +5 -0
- package/src/agent/acp/integration.test.js +385 -0
- package/src/agent/acp/manual-test.js +410 -0
- package/src/agent/acp/protocol-test.js +190 -0
- package/src/agent/acp/server.js +448 -0
- package/src/agent/acp/server.test.js +371 -0
- package/src/agent/acp/test-runner.js +216 -0
- package/src/agent/acp/test-utils/README.md +315 -0
- package/src/agent/acp/test-utils/acp-tester.js +484 -0
- package/src/agent/acp/test-utils/mock-acp-client.js +434 -0
- package/src/agent/acp/tools.js +368 -0
- package/src/agent/acp/tools.test.js +334 -0
- package/src/agent/acp/types.js +218 -0
- package/src/agent/acp/types.test.js +327 -0
- package/src/agent/appTracer.js +360 -0
- package/src/agent/fileSpanExporter.js +169 -0
- package/src/agent/index.js +813 -0
- package/src/agent/mcp/client.js +338 -0
- package/src/agent/mcp/config.js +313 -0
- package/src/agent/mcp/index.js +64 -0
- package/src/agent/mcp/xmlBridge.js +371 -0
- package/src/agent/mockProvider.js +53 -0
- package/src/agent/probeTool.js +257 -0
- package/src/agent/schemaUtils.js +1726 -0
- package/src/agent/simpleTelemetry.js +267 -0
- package/src/agent/telemetry.js +225 -0
- package/src/agent/tokenCounter.js +395 -0
- package/src/agent/tools.js +163 -0
- package/src/cli.js +49 -0
- package/src/delegate.js +267 -0
- package/src/directory-resolver.js +237 -0
- package/src/downloader.js +750 -0
- package/src/extract.js +149 -0
- package/src/index.js +70 -0
- package/src/mcp/index.ts +608 -0
- package/src/query.js +116 -0
- package/src/search.js +247 -0
- package/src/tools/common.js +410 -0
- package/src/tools/index.js +40 -0
- package/src/tools/langchain.js +88 -0
- package/src/tools/system-message.js +121 -0
- package/src/tools/vercel.js +271 -0
- package/src/utils/file-lister.js +193 -0
- package/src/utils.js +128 -0
|
@@ -0,0 +1,813 @@
|
|
|
1
|
+
import { ProbeAgent } from './ProbeAgent.js';
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import {
|
|
5
|
+
CallToolRequestSchema,
|
|
6
|
+
ErrorCode,
|
|
7
|
+
ListToolsRequestSchema,
|
|
8
|
+
McpError,
|
|
9
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
10
|
+
import { readFileSync, existsSync } from 'fs';
|
|
11
|
+
import { resolve } from 'path';
|
|
12
|
+
import { initializeSimpleTelemetryFromOptions, SimpleAppTracer } from './simpleTelemetry.js';
|
|
13
|
+
import {
|
|
14
|
+
cleanSchemaResponse,
|
|
15
|
+
processSchemaResponse,
|
|
16
|
+
isJsonSchema,
|
|
17
|
+
validateJsonResponse,
|
|
18
|
+
createJsonCorrectionPrompt,
|
|
19
|
+
isMermaidSchema,
|
|
20
|
+
validateMermaidResponse,
|
|
21
|
+
createMermaidCorrectionPrompt,
|
|
22
|
+
validateAndFixMermaidResponse
|
|
23
|
+
} from './schemaUtils.js';
|
|
24
|
+
import { ACPServer } from './acp/index.js';
|
|
25
|
+
|
|
26
|
+
// Helper function to detect if input is a file path and read it
|
|
27
|
+
function readInputContent(input) {
|
|
28
|
+
if (!input) return null;
|
|
29
|
+
|
|
30
|
+
// Check if the input looks like a file path and exists
|
|
31
|
+
try {
|
|
32
|
+
const resolvedPath = resolve(input);
|
|
33
|
+
if (existsSync(resolvedPath)) {
|
|
34
|
+
return readFileSync(resolvedPath, 'utf-8').trim();
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
// If file reading fails, treat as literal string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Return as literal string if not a valid file
|
|
41
|
+
return input;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Function to check if stdin has data available
|
|
45
|
+
function isStdinAvailable() {
|
|
46
|
+
// Check if stdin is not a TTY (indicates piped input)
|
|
47
|
+
// Also ensure we're not in an interactive terminal session
|
|
48
|
+
return !process.stdin.isTTY && process.stdin.readable;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Function to read from stdin with timeout detection for interactive vs piped usage
|
|
52
|
+
function readFromStdin() {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
let data = '';
|
|
55
|
+
let hasReceivedData = false;
|
|
56
|
+
let dataChunks = [];
|
|
57
|
+
|
|
58
|
+
// Short timeout to detect if this is interactive usage (no immediate data)
|
|
59
|
+
const timeout = setTimeout(() => {
|
|
60
|
+
if (!hasReceivedData) {
|
|
61
|
+
reject(new Error('INTERACTIVE_MODE'));
|
|
62
|
+
}
|
|
63
|
+
}, 100); // Very short timeout - piped input should arrive immediately
|
|
64
|
+
|
|
65
|
+
process.stdin.setEncoding('utf8');
|
|
66
|
+
|
|
67
|
+
// Try to read immediately to see if data is available
|
|
68
|
+
process.stdin.on('readable', () => {
|
|
69
|
+
let chunk;
|
|
70
|
+
while ((chunk = process.stdin.read()) !== null) {
|
|
71
|
+
hasReceivedData = true;
|
|
72
|
+
clearTimeout(timeout);
|
|
73
|
+
dataChunks.push(chunk);
|
|
74
|
+
data += chunk;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
process.stdin.on('end', () => {
|
|
79
|
+
clearTimeout(timeout);
|
|
80
|
+
const trimmed = data.trim();
|
|
81
|
+
if (!trimmed && dataChunks.length === 0) {
|
|
82
|
+
reject(new Error('No input received from stdin'));
|
|
83
|
+
} else {
|
|
84
|
+
resolve(trimmed);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
process.stdin.on('error', (error) => {
|
|
89
|
+
clearTimeout(timeout);
|
|
90
|
+
reject(error);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Force a read attempt to trigger readable event if data is available
|
|
94
|
+
process.nextTick(() => {
|
|
95
|
+
const chunk = process.stdin.read();
|
|
96
|
+
if (chunk !== null) {
|
|
97
|
+
hasReceivedData = true;
|
|
98
|
+
clearTimeout(timeout);
|
|
99
|
+
data += chunk;
|
|
100
|
+
dataChunks.push(chunk);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Parse command line arguments
|
|
107
|
+
function parseArgs() {
|
|
108
|
+
const args = process.argv.slice(2);
|
|
109
|
+
const config = {
|
|
110
|
+
mcp: false,
|
|
111
|
+
acp: false,
|
|
112
|
+
question: null,
|
|
113
|
+
path: null,
|
|
114
|
+
prompt: null,
|
|
115
|
+
systemPrompt: null,
|
|
116
|
+
schema: null,
|
|
117
|
+
provider: null,
|
|
118
|
+
model: null,
|
|
119
|
+
allowEdit: false,
|
|
120
|
+
verbose: false,
|
|
121
|
+
help: false,
|
|
122
|
+
maxIterations: null,
|
|
123
|
+
maxResponseTokens: null,
|
|
124
|
+
traceFile: undefined,
|
|
125
|
+
traceRemote: undefined,
|
|
126
|
+
traceConsole: false,
|
|
127
|
+
useStdin: false, // New flag to indicate stdin should be used
|
|
128
|
+
outline: false, // New flag to enable outline format
|
|
129
|
+
noMermaidValidation: false // New flag to disable mermaid validation
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
for (let i = 0; i < args.length; i++) {
|
|
133
|
+
const arg = args[i];
|
|
134
|
+
|
|
135
|
+
if (arg === '--mcp') {
|
|
136
|
+
config.mcp = true;
|
|
137
|
+
} else if (arg === '--acp') {
|
|
138
|
+
config.acp = true;
|
|
139
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
140
|
+
config.help = true;
|
|
141
|
+
} else if (arg === '--verbose') {
|
|
142
|
+
config.verbose = true;
|
|
143
|
+
} else if (arg === '--allow-edit') {
|
|
144
|
+
config.allowEdit = true;
|
|
145
|
+
} else if (arg === '--path' && i + 1 < args.length) {
|
|
146
|
+
config.path = args[++i];
|
|
147
|
+
} else if (arg === '--prompt' && i + 1 < args.length) {
|
|
148
|
+
config.prompt = args[++i];
|
|
149
|
+
} else if (arg === '--system-prompt' && i + 1 < args.length) {
|
|
150
|
+
config.systemPrompt = args[++i];
|
|
151
|
+
} else if (arg === '--schema' && i + 1 < args.length) {
|
|
152
|
+
config.schema = args[++i];
|
|
153
|
+
} else if (arg === '--provider' && i + 1 < args.length) {
|
|
154
|
+
config.provider = args[++i];
|
|
155
|
+
} else if (arg === '--model' && i + 1 < args.length) {
|
|
156
|
+
config.model = args[++i];
|
|
157
|
+
} else if (arg === '--max-iterations' && i + 1 < args.length) {
|
|
158
|
+
config.maxIterations = parseInt(args[++i], 10);
|
|
159
|
+
} else if (arg === '--max-response-tokens' && i + 1 < args.length) {
|
|
160
|
+
config.maxResponseTokens = parseInt(args[++i], 10);
|
|
161
|
+
} else if (arg === '--trace-file' && i + 1 < args.length) {
|
|
162
|
+
config.traceFile = args[++i];
|
|
163
|
+
} else if (arg === '--trace-remote' && i + 1 < args.length) {
|
|
164
|
+
config.traceRemote = args[++i];
|
|
165
|
+
} else if (arg === '--trace-console') {
|
|
166
|
+
config.traceConsole = true;
|
|
167
|
+
} else if (arg === '--outline') {
|
|
168
|
+
config.outline = true;
|
|
169
|
+
} else if (arg === '--no-mermaid-validation') {
|
|
170
|
+
config.noMermaidValidation = true;
|
|
171
|
+
} else if (!arg.startsWith('--') && !config.question) {
|
|
172
|
+
// First non-flag argument is the question
|
|
173
|
+
config.question = arg;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Auto-detect stdin usage if no question provided and stdin appears to be piped
|
|
178
|
+
// For simplicity, let's use a more practical approach:
|
|
179
|
+
// If user provides no arguments at all, we try to read from stdin with a short timeout
|
|
180
|
+
// This works better across different environments
|
|
181
|
+
if (!config.question && !config.mcp && !config.acp && !config.help) {
|
|
182
|
+
// We'll check for stdin in the main function with a timeout approach
|
|
183
|
+
config.useStdin = true;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return config;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Show help message
|
|
190
|
+
function showHelp() {
|
|
191
|
+
console.log(`
|
|
192
|
+
probe agent - AI-powered code exploration tool
|
|
193
|
+
|
|
194
|
+
Usage:
|
|
195
|
+
probe agent <question> Answer a question about the codebase
|
|
196
|
+
probe agent <file> Read question from file
|
|
197
|
+
echo "question" | probe agent Read question from stdin (pipe input)
|
|
198
|
+
probe agent --mcp Start as MCP server
|
|
199
|
+
probe agent --acp Start as ACP server
|
|
200
|
+
|
|
201
|
+
Options:
|
|
202
|
+
--path <dir> Search directory (default: current)
|
|
203
|
+
--prompt <type> Persona: code-explorer, engineer, code-review, support, architect
|
|
204
|
+
--system-prompt <text|file> Custom system prompt (text or file path)
|
|
205
|
+
--schema <schema|file> Output schema (JSON, XML, any format - text or file path)
|
|
206
|
+
--provider <name> Force AI provider: anthropic, openai, google
|
|
207
|
+
--model <name> Override model name
|
|
208
|
+
--allow-edit Enable code modification capabilities
|
|
209
|
+
--verbose Enable verbose output
|
|
210
|
+
--outline Use outline-xml format for code search results
|
|
211
|
+
--mcp Run as MCP server
|
|
212
|
+
--acp Run as ACP server (Agent Client Protocol)
|
|
213
|
+
--max-iterations <number> Max tool iterations (default: 30)
|
|
214
|
+
--max-response-tokens <number> Max tokens for AI response (overrides model defaults)
|
|
215
|
+
--trace-file <path> Enable tracing to file (JSONL format)
|
|
216
|
+
--trace-remote <endpoint> Enable tracing to remote OTLP endpoint
|
|
217
|
+
--trace-console Enable tracing to console output
|
|
218
|
+
--no-mermaid-validation Disable automatic mermaid diagram validation and fixing
|
|
219
|
+
--help, -h Show this help message
|
|
220
|
+
|
|
221
|
+
Environment Variables:
|
|
222
|
+
ANTHROPIC_API_KEY Anthropic Claude API key
|
|
223
|
+
OPENAI_API_KEY OpenAI GPT API key
|
|
224
|
+
GOOGLE_API_KEY Google Gemini API key
|
|
225
|
+
FORCE_PROVIDER Force specific provider (anthropic, openai, google)
|
|
226
|
+
MODEL_NAME Override model name
|
|
227
|
+
MAX_RESPONSE_TOKENS Maximum tokens for AI response
|
|
228
|
+
DEBUG Enable verbose mode (set to '1')
|
|
229
|
+
|
|
230
|
+
Examples:
|
|
231
|
+
probe agent "How does authentication work?"
|
|
232
|
+
probe agent question.txt # Read question from file
|
|
233
|
+
echo "How does the search algorithm work?" | probe agent # Read from stdin
|
|
234
|
+
cat requirements.txt | probe agent --prompt architect # Pipe file content
|
|
235
|
+
probe agent "Find all database queries" --path ./src --prompt engineer
|
|
236
|
+
probe agent "Review this code for bugs" --prompt code-review --system-prompt custom-prompt.txt
|
|
237
|
+
probe agent "List all functions" --schema '{"functions": [{"name": "string", "file": "string"}]}'
|
|
238
|
+
probe agent "Analyze codebase" --schema schema.json # Schema from file
|
|
239
|
+
probe agent "Debug issue" --trace-file ./debug.jsonl --verbose
|
|
240
|
+
probe agent "Analyze code" --trace-remote http://localhost:4318/v1/traces
|
|
241
|
+
probe agent --mcp # Start MCP server mode
|
|
242
|
+
probe agent --acp # Start ACP server mode
|
|
243
|
+
|
|
244
|
+
Personas:
|
|
245
|
+
code-explorer Default. Explores and explains code structure and functionality
|
|
246
|
+
engineer Senior engineer focused on implementation and architecture
|
|
247
|
+
code-review Reviews code for bugs, performance, and best practices
|
|
248
|
+
support Helps troubleshoot issues and solve problems
|
|
249
|
+
architect Focuses on software architecture and high-level design
|
|
250
|
+
`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// MCP Server implementation
|
|
254
|
+
class ProbeAgentMcpServer {
|
|
255
|
+
constructor() {
|
|
256
|
+
this.server = new Server(
|
|
257
|
+
{
|
|
258
|
+
name: '@buger/probe-agent',
|
|
259
|
+
version: '1.0.0',
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
capabilities: {
|
|
263
|
+
tools: {},
|
|
264
|
+
},
|
|
265
|
+
}
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
this.setupToolHandlers();
|
|
269
|
+
this.server.onerror = (error) => console.error('[MCP Error]', error);
|
|
270
|
+
process.on('SIGINT', async () => {
|
|
271
|
+
await this.server.close();
|
|
272
|
+
process.exit(0);
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
setupToolHandlers() {
|
|
277
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
278
|
+
tools: [
|
|
279
|
+
{
|
|
280
|
+
name: 'search_code',
|
|
281
|
+
description: "Search code and answer questions about the codebase using an AI agent. This tool provides intelligent responses based on code analysis.",
|
|
282
|
+
inputSchema: {
|
|
283
|
+
type: 'object',
|
|
284
|
+
properties: {
|
|
285
|
+
query: {
|
|
286
|
+
type: 'string',
|
|
287
|
+
description: 'The question or request about the codebase.',
|
|
288
|
+
},
|
|
289
|
+
path: {
|
|
290
|
+
type: 'string',
|
|
291
|
+
description: 'Optional path to the directory to search in. Defaults to current directory.',
|
|
292
|
+
},
|
|
293
|
+
prompt: {
|
|
294
|
+
type: 'string',
|
|
295
|
+
description: 'Optional persona type: code-explorer, engineer, code-review, support, architect.',
|
|
296
|
+
},
|
|
297
|
+
system_prompt: {
|
|
298
|
+
type: 'string',
|
|
299
|
+
description: 'Optional custom system prompt (text or file path).',
|
|
300
|
+
},
|
|
301
|
+
provider: {
|
|
302
|
+
type: 'string',
|
|
303
|
+
description: 'Optional AI provider to force: anthropic, openai, google.',
|
|
304
|
+
},
|
|
305
|
+
model: {
|
|
306
|
+
type: 'string',
|
|
307
|
+
description: 'Optional model name override.',
|
|
308
|
+
},
|
|
309
|
+
allow_edit: {
|
|
310
|
+
type: 'boolean',
|
|
311
|
+
description: 'Enable code modification capabilities.',
|
|
312
|
+
},
|
|
313
|
+
max_iterations: {
|
|
314
|
+
type: 'number',
|
|
315
|
+
description: 'Maximum number of tool iterations (default: 30).',
|
|
316
|
+
},
|
|
317
|
+
max_response_tokens: {
|
|
318
|
+
type: 'number',
|
|
319
|
+
description: 'Maximum tokens for AI response (overrides model defaults).',
|
|
320
|
+
},
|
|
321
|
+
schema: {
|
|
322
|
+
type: 'string',
|
|
323
|
+
description: 'Optional output schema (JSON, XML, or any format - text or file path).',
|
|
324
|
+
},
|
|
325
|
+
no_mermaid_validation: {
|
|
326
|
+
type: 'boolean',
|
|
327
|
+
description: 'Disable automatic mermaid diagram validation and fixing.',
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
required: ['query']
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
}));
|
|
335
|
+
|
|
336
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
337
|
+
if (request.params.name !== 'search_code') {
|
|
338
|
+
throw new McpError(
|
|
339
|
+
ErrorCode.MethodNotFound,
|
|
340
|
+
`Unknown tool: ${request.params.name}`
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
const args = request.params.arguments;
|
|
346
|
+
|
|
347
|
+
// Validate required fields
|
|
348
|
+
if (!args.query) {
|
|
349
|
+
throw new Error("Query is required");
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Set MAX_TOOL_ITERATIONS if provided
|
|
353
|
+
if (args.max_iterations) {
|
|
354
|
+
process.env.MAX_TOOL_ITERATIONS = args.max_iterations.toString();
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Process system prompt if provided (could be file or literal string)
|
|
358
|
+
let systemPrompt = null;
|
|
359
|
+
if (args.system_prompt) {
|
|
360
|
+
systemPrompt = readInputContent(args.system_prompt);
|
|
361
|
+
if (!systemPrompt) {
|
|
362
|
+
throw new Error('System prompt could not be read');
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Process query input (could be file or literal string)
|
|
367
|
+
const query = readInputContent(args.query);
|
|
368
|
+
if (!query) {
|
|
369
|
+
throw new Error('Query is required and could not be read');
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Process schema if provided (could be file or literal string)
|
|
373
|
+
let schema = null;
|
|
374
|
+
if (args.schema) {
|
|
375
|
+
schema = readInputContent(args.schema);
|
|
376
|
+
if (!schema) {
|
|
377
|
+
throw new Error('Schema could not be read');
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Create agent with configuration
|
|
382
|
+
const agentConfig = {
|
|
383
|
+
path: args.path || process.cwd(),
|
|
384
|
+
promptType: args.prompt || 'code-explorer',
|
|
385
|
+
customPrompt: systemPrompt,
|
|
386
|
+
provider: args.provider,
|
|
387
|
+
model: args.model,
|
|
388
|
+
allowEdit: !!args.allow_edit,
|
|
389
|
+
debug: process.env.DEBUG === '1',
|
|
390
|
+
maxResponseTokens: args.max_response_tokens,
|
|
391
|
+
disableMermaidValidation: !!args.no_mermaid_validation
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const agent = new ProbeAgent(agentConfig);
|
|
395
|
+
let result = await agent.answer(query, [], { schema });
|
|
396
|
+
|
|
397
|
+
// If schema is provided, make a follow-up request to format the output
|
|
398
|
+
if (schema) {
|
|
399
|
+
const schemaPrompt = `Now you need to respond according to this schema:\n\n${schema}\n\nPlease reformat your previous response to match this schema exactly. Only return the formatted response, no additional text.`;
|
|
400
|
+
|
|
401
|
+
try {
|
|
402
|
+
result = await agent.answer(schemaPrompt, [], { schema });
|
|
403
|
+
// Clean the schema response to remove code blocks and formatting
|
|
404
|
+
result = cleanSchemaResponse(result);
|
|
405
|
+
|
|
406
|
+
// Check for mermaid diagrams in response and validate/fix them regardless of schema
|
|
407
|
+
if (!args.no_mermaid_validation) {
|
|
408
|
+
try {
|
|
409
|
+
const mermaidValidation = await validateAndFixMermaidResponse(result, {
|
|
410
|
+
debug: args.debug,
|
|
411
|
+
path: agentConfig.path,
|
|
412
|
+
provider: args.provider,
|
|
413
|
+
model: args.model
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
if (mermaidValidation.wasFixed) {
|
|
417
|
+
result = mermaidValidation.fixedResponse;
|
|
418
|
+
if (args.debug) {
|
|
419
|
+
console.error(`[DEBUG] Mermaid diagrams fixed using specialized agent`);
|
|
420
|
+
mermaidValidation.fixingResults.forEach((fixResult, index) => {
|
|
421
|
+
if (fixResult.wasFixed) {
|
|
422
|
+
console.error(`[DEBUG] Fixed diagram ${index + 1}: ${fixResult.originalError}`);
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
} else if (!mermaidValidation.isValid && mermaidValidation.diagrams && mermaidValidation.diagrams.length > 0 && args.debug) {
|
|
427
|
+
console.error(`[DEBUG] Mermaid validation failed: ${mermaidValidation.errors?.join(', ')}`);
|
|
428
|
+
}
|
|
429
|
+
} catch (error) {
|
|
430
|
+
if (args.debug) {
|
|
431
|
+
console.error(`[DEBUG] Enhanced mermaid validation failed: ${error.message}`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
} else if (args.debug) {
|
|
435
|
+
console.error(`[DEBUG] Mermaid validation skipped due to --no-mermaid-validation flag`);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Then, if schema expects JSON, validate and retry if invalid
|
|
439
|
+
if (isJsonSchema(schema)) {
|
|
440
|
+
const validation = validateJsonResponse(result);
|
|
441
|
+
if (!validation.isValid) {
|
|
442
|
+
// Retry once with correction prompt
|
|
443
|
+
const correctionPrompt = createJsonCorrectionPrompt(result, schema, validation.error);
|
|
444
|
+
try {
|
|
445
|
+
result = await agent.answer(correctionPrompt, [], { schema });
|
|
446
|
+
result = cleanSchemaResponse(result);
|
|
447
|
+
|
|
448
|
+
// Validate again after correction
|
|
449
|
+
const finalValidation = validateJsonResponse(result);
|
|
450
|
+
if (!finalValidation.isValid && args.debug) {
|
|
451
|
+
console.error(`[DEBUG] JSON validation failed after retry: ${finalValidation.error}`);
|
|
452
|
+
}
|
|
453
|
+
} catch (retryError) {
|
|
454
|
+
// If retry fails, keep the original result
|
|
455
|
+
if (args.debug) {
|
|
456
|
+
console.error(`[DEBUG] JSON correction retry failed: ${retryError.message}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
} catch (error) {
|
|
462
|
+
// If schema formatting fails, use original result
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Get token usage for debugging
|
|
467
|
+
const tokenUsage = agent.getTokenUsage();
|
|
468
|
+
console.error(`Token usage: ${JSON.stringify(tokenUsage)}`);
|
|
469
|
+
|
|
470
|
+
return {
|
|
471
|
+
content: [
|
|
472
|
+
{
|
|
473
|
+
type: 'text',
|
|
474
|
+
text: result,
|
|
475
|
+
},
|
|
476
|
+
],
|
|
477
|
+
};
|
|
478
|
+
} catch (error) {
|
|
479
|
+
console.error(`Error executing search_code:`, error);
|
|
480
|
+
return {
|
|
481
|
+
content: [
|
|
482
|
+
{
|
|
483
|
+
type: 'text',
|
|
484
|
+
text: `Error: ${error.message}`,
|
|
485
|
+
},
|
|
486
|
+
],
|
|
487
|
+
isError: true,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
async run() {
|
|
494
|
+
const transport = new StdioServerTransport();
|
|
495
|
+
await this.server.connect(transport);
|
|
496
|
+
console.error('Probe Agent MCP server running on stdio');
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Main function
|
|
501
|
+
async function main() {
|
|
502
|
+
const config = parseArgs();
|
|
503
|
+
|
|
504
|
+
if (config.help) {
|
|
505
|
+
showHelp();
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (config.mcp) {
|
|
510
|
+
// Start as MCP server
|
|
511
|
+
const server = new ProbeAgentMcpServer();
|
|
512
|
+
await server.run();
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (config.acp) {
|
|
517
|
+
// Start as ACP server
|
|
518
|
+
const server = new ACPServer({
|
|
519
|
+
provider: config.provider,
|
|
520
|
+
model: config.model,
|
|
521
|
+
path: config.path,
|
|
522
|
+
allowEdit: config.allowEdit,
|
|
523
|
+
debug: config.verbose
|
|
524
|
+
});
|
|
525
|
+
await server.start();
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Handle stdin input if detected
|
|
530
|
+
if (config.useStdin) {
|
|
531
|
+
try {
|
|
532
|
+
if (config.verbose) {
|
|
533
|
+
console.error('[DEBUG] Reading question from stdin...');
|
|
534
|
+
}
|
|
535
|
+
config.question = await readFromStdin();
|
|
536
|
+
if (!config.question) {
|
|
537
|
+
console.error('Error: No input received from stdin');
|
|
538
|
+
process.exit(1);
|
|
539
|
+
}
|
|
540
|
+
} catch (error) {
|
|
541
|
+
// If this is interactive mode (no piped input), show help
|
|
542
|
+
if (error.message === 'INTERACTIVE_MODE') {
|
|
543
|
+
showHelp();
|
|
544
|
+
process.exit(0);
|
|
545
|
+
} else {
|
|
546
|
+
console.error(`Error reading from stdin: ${error.message}`);
|
|
547
|
+
process.exit(1);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (!config.question) {
|
|
553
|
+
showHelp();
|
|
554
|
+
process.exit(1);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
try {
|
|
558
|
+
// Initialize tracing if any tracing options are provided
|
|
559
|
+
let telemetryConfig = null;
|
|
560
|
+
let appTracer = null;
|
|
561
|
+
if (config.traceFile !== undefined || config.traceRemote !== undefined || config.traceConsole) {
|
|
562
|
+
try {
|
|
563
|
+
telemetryConfig = initializeSimpleTelemetryFromOptions(config);
|
|
564
|
+
appTracer = new SimpleAppTracer(telemetryConfig);
|
|
565
|
+
if (config.verbose) {
|
|
566
|
+
console.error('[DEBUG] Simple tracing initialized');
|
|
567
|
+
}
|
|
568
|
+
} catch (error) {
|
|
569
|
+
if (config.verbose) {
|
|
570
|
+
console.error(`[DEBUG] Failed to initialize tracing: ${error.message}`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Set environment variables if provided via flags
|
|
576
|
+
if (config.verbose) {
|
|
577
|
+
process.env.DEBUG = '1';
|
|
578
|
+
}
|
|
579
|
+
if (config.provider) {
|
|
580
|
+
process.env.FORCE_PROVIDER = config.provider;
|
|
581
|
+
}
|
|
582
|
+
if (config.model) {
|
|
583
|
+
process.env.MODEL_NAME = config.model;
|
|
584
|
+
}
|
|
585
|
+
if (config.maxIterations) {
|
|
586
|
+
process.env.MAX_TOOL_ITERATIONS = config.maxIterations.toString();
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Process question input (could be file or literal string)
|
|
590
|
+
const question = readInputContent(config.question);
|
|
591
|
+
if (!question) {
|
|
592
|
+
console.error('Error: Question is required and could not be read');
|
|
593
|
+
process.exit(1);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Process system prompt if provided (could be file or literal string)
|
|
597
|
+
let systemPrompt = null;
|
|
598
|
+
if (config.systemPrompt) {
|
|
599
|
+
systemPrompt = readInputContent(config.systemPrompt);
|
|
600
|
+
if (!systemPrompt) {
|
|
601
|
+
console.error('Error: System prompt could not be read');
|
|
602
|
+
process.exit(1);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Process schema if provided (could be file or literal string)
|
|
607
|
+
let schema = null;
|
|
608
|
+
if (config.schema) {
|
|
609
|
+
schema = readInputContent(config.schema);
|
|
610
|
+
if (!schema) {
|
|
611
|
+
console.error('Error: Schema could not be read');
|
|
612
|
+
process.exit(1);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Create and configure agent
|
|
617
|
+
const agentConfig = {
|
|
618
|
+
path: config.path,
|
|
619
|
+
promptType: config.prompt,
|
|
620
|
+
customPrompt: systemPrompt,
|
|
621
|
+
allowEdit: config.allowEdit,
|
|
622
|
+
debug: config.verbose,
|
|
623
|
+
tracer: appTracer,
|
|
624
|
+
outline: config.outline,
|
|
625
|
+
maxResponseTokens: config.maxResponseTokens,
|
|
626
|
+
disableMermaidValidation: config.noMermaidValidation
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
const agent = new ProbeAgent(agentConfig);
|
|
630
|
+
|
|
631
|
+
// Execute with tracing if available
|
|
632
|
+
let result;
|
|
633
|
+
if (appTracer) {
|
|
634
|
+
const sessionSpan = appTracer.createSessionSpan({
|
|
635
|
+
'question': question.substring(0, 100) + (question.length > 100 ? '...' : ''),
|
|
636
|
+
'path': config.path || process.cwd(),
|
|
637
|
+
'prompt_type': config.prompt || 'code-explorer'
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
try {
|
|
641
|
+
result = await appTracer.withSpan('agent.answer',
|
|
642
|
+
() => agent.answer(question, [], { schema }),
|
|
643
|
+
{ 'question.length': question.length }
|
|
644
|
+
);
|
|
645
|
+
} finally {
|
|
646
|
+
if (sessionSpan) {
|
|
647
|
+
sessionSpan.end();
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
} else {
|
|
651
|
+
result = await agent.answer(question, [], { schema });
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// If schema is provided, make a follow-up request to format the output
|
|
655
|
+
if (schema) {
|
|
656
|
+
if (config.verbose) {
|
|
657
|
+
console.error('[DEBUG] Schema provided, making follow-up request to format output...');
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const schemaPrompt = `Now you need to respond according to this schema:\n\n${schema}\n\nPlease reformat your previous response to match this schema exactly. Only return the formatted response, no additional text.`;
|
|
661
|
+
|
|
662
|
+
try {
|
|
663
|
+
if (appTracer) {
|
|
664
|
+
result = await appTracer.withSpan('agent.schema_formatting',
|
|
665
|
+
() => agent.answer(schemaPrompt, [], { schema }),
|
|
666
|
+
{ 'schema.length': schema.length }
|
|
667
|
+
);
|
|
668
|
+
} else {
|
|
669
|
+
result = await agent.answer(schemaPrompt, [], { schema });
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Clean the schema response to remove code blocks and formatting
|
|
673
|
+
const cleaningResult = processSchemaResponse(result, schema, {
|
|
674
|
+
debug: config.verbose
|
|
675
|
+
});
|
|
676
|
+
result = cleaningResult.cleaned;
|
|
677
|
+
|
|
678
|
+
if (config.verbose && cleaningResult.debug && cleaningResult.debug.wasModified) {
|
|
679
|
+
console.error('[DEBUG] Schema response was cleaned:');
|
|
680
|
+
console.error(` Original length: ${cleaningResult.debug.originalLength}`);
|
|
681
|
+
console.error(` Cleaned length: ${cleaningResult.debug.cleanedLength}`);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Check for mermaid diagrams in response and validate/fix them regardless of schema
|
|
685
|
+
if (!config.noMermaidValidation) {
|
|
686
|
+
try {
|
|
687
|
+
const mermaidValidationResult = await validateAndFixMermaidResponse(result, {
|
|
688
|
+
debug: config.verbose,
|
|
689
|
+
path: config.path,
|
|
690
|
+
provider: config.provider,
|
|
691
|
+
model: config.model,
|
|
692
|
+
tracer: appTracer
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
if (mermaidValidationResult.wasFixed) {
|
|
696
|
+
result = mermaidValidationResult.fixedResponse;
|
|
697
|
+
if (config.verbose) {
|
|
698
|
+
console.error(`[DEBUG] Mermaid diagrams fixed using specialized agent`);
|
|
699
|
+
mermaidValidationResult.fixingResults.forEach((fixResult, index) => {
|
|
700
|
+
if (fixResult.wasFixed) {
|
|
701
|
+
console.error(`[DEBUG] Fixed diagram ${index + 1}: ${fixResult.originalError}`);
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
} else if (!mermaidValidationResult.isValid && mermaidValidationResult.diagrams && mermaidValidationResult.diagrams.length > 0 && config.verbose) {
|
|
706
|
+
console.error(`[DEBUG] Mermaid validation failed: ${mermaidValidationResult.errors?.join(', ')}`);
|
|
707
|
+
}
|
|
708
|
+
} catch (error) {
|
|
709
|
+
if (config.verbose) {
|
|
710
|
+
console.error(`[DEBUG] Enhanced mermaid validation failed: ${error.message}`);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
} else if (config.verbose) {
|
|
714
|
+
console.error(`[DEBUG] Mermaid validation skipped due to --no-mermaid-validation flag`);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Then, if schema expects JSON, validate and retry if invalid
|
|
718
|
+
if (isJsonSchema(schema)) {
|
|
719
|
+
const validation = validateJsonResponse(result);
|
|
720
|
+
if (!validation.isValid) {
|
|
721
|
+
if (config.verbose) {
|
|
722
|
+
console.error(`[DEBUG] JSON validation failed: ${validation.error}`);
|
|
723
|
+
console.error('[DEBUG] Attempting to correct JSON...');
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// Retry once with correction prompt
|
|
727
|
+
const correctionPrompt = createJsonCorrectionPrompt(result, schema, validation.error);
|
|
728
|
+
try {
|
|
729
|
+
if (appTracer) {
|
|
730
|
+
result = await appTracer.withSpan('agent.json_correction',
|
|
731
|
+
() => agent.answer(correctionPrompt, [], { schema }),
|
|
732
|
+
{ 'original_error': validation.error }
|
|
733
|
+
);
|
|
734
|
+
} else {
|
|
735
|
+
result = await agent.answer(correctionPrompt, [], { schema });
|
|
736
|
+
}
|
|
737
|
+
result = cleanSchemaResponse(result);
|
|
738
|
+
|
|
739
|
+
// Validate again after correction
|
|
740
|
+
const finalValidation = validateJsonResponse(result);
|
|
741
|
+
if (config.verbose) {
|
|
742
|
+
if (finalValidation.isValid) {
|
|
743
|
+
console.error('[DEBUG] JSON correction successful');
|
|
744
|
+
} else {
|
|
745
|
+
console.error(`[DEBUG] JSON validation failed after retry: ${finalValidation.error}`);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
} catch (retryError) {
|
|
749
|
+
// If retry fails, keep the original result
|
|
750
|
+
if (config.verbose) {
|
|
751
|
+
console.error(`[DEBUG] JSON correction retry failed: ${retryError.message}`);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
} else if (config.verbose) {
|
|
755
|
+
console.error('[DEBUG] JSON validation passed');
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
} catch (error) {
|
|
759
|
+
if (config.verbose) {
|
|
760
|
+
console.error('[DEBUG] Schema formatting failed, using original result');
|
|
761
|
+
}
|
|
762
|
+
// If schema formatting fails, use original result
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Output the result
|
|
767
|
+
console.log(result);
|
|
768
|
+
|
|
769
|
+
// Show token usage in verbose mode
|
|
770
|
+
if (config.verbose) {
|
|
771
|
+
const tokenUsage = agent.getTokenUsage();
|
|
772
|
+
console.error(`\n[DEBUG] Token usage: ${JSON.stringify(tokenUsage, null, 2)}`);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Flush and shutdown tracing
|
|
776
|
+
if (appTracer) {
|
|
777
|
+
try {
|
|
778
|
+
await appTracer.flush();
|
|
779
|
+
if (config.verbose) {
|
|
780
|
+
console.error('[DEBUG] Tracing flushed');
|
|
781
|
+
}
|
|
782
|
+
} catch (error) {
|
|
783
|
+
if (config.verbose) {
|
|
784
|
+
console.error(`[DEBUG] Failed to flush tracing: ${error.message}`);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
} catch (error) {
|
|
790
|
+
console.error(`Error: ${error.message}`);
|
|
791
|
+
if (config.verbose) {
|
|
792
|
+
console.error(error.stack);
|
|
793
|
+
}
|
|
794
|
+
process.exit(1);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// Handle uncaught exceptions
|
|
799
|
+
process.on('uncaughtException', (error) => {
|
|
800
|
+
console.error('Uncaught Exception:', error);
|
|
801
|
+
process.exit(1);
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
805
|
+
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
806
|
+
process.exit(1);
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
// Run main function
|
|
810
|
+
main().catch((error) => {
|
|
811
|
+
console.error('Fatal error:', error);
|
|
812
|
+
process.exit(1);
|
|
813
|
+
});
|