@probelabs/probe 0.6.0-rc102 → 0.6.0-rc103
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 +252 -2
- package/build/agent/index.js +228 -36
- package/build/mcp/index.js +23 -144
- package/build/mcp/index.ts +22 -161
- package/cjs/agent/ProbeAgent.cjs +193 -8
- package/cjs/index.cjs +205 -20
- package/package.json +1 -1
- package/src/agent/ProbeAgent.js +252 -2
- package/src/agent/index.js +26 -14
- package/src/mcp/index.ts +22 -161
package/build/mcp/index.ts
CHANGED
|
@@ -112,37 +112,19 @@ console.log(`Bin directory: ${binDir}`);
|
|
|
112
112
|
interface SearchCodeArgs {
|
|
113
113
|
path: string;
|
|
114
114
|
query: string | string[];
|
|
115
|
-
filesOnly?: boolean;
|
|
116
|
-
ignore?: string[];
|
|
117
|
-
excludeFilenames?: boolean;
|
|
118
115
|
exact?: boolean;
|
|
119
116
|
maxResults?: number;
|
|
120
117
|
maxTokens?: number;
|
|
121
118
|
allowTests?: boolean;
|
|
122
119
|
session?: string;
|
|
123
|
-
timeout?: number;
|
|
124
120
|
noGitignore?: boolean;
|
|
125
121
|
}
|
|
126
122
|
|
|
127
|
-
interface QueryCodeArgs {
|
|
128
|
-
path: string;
|
|
129
|
-
pattern: string;
|
|
130
|
-
language?: string;
|
|
131
|
-
ignore?: string[];
|
|
132
|
-
allowTests?: boolean;
|
|
133
|
-
maxResults?: number;
|
|
134
|
-
format?: 'markdown' | 'plain' | 'json' | 'color';
|
|
135
|
-
timeout?: number;
|
|
136
|
-
noGitignore?: boolean;
|
|
137
|
-
}
|
|
138
123
|
|
|
139
124
|
interface ExtractCodeArgs {
|
|
140
125
|
path: string;
|
|
141
126
|
files: string[];
|
|
142
127
|
allowTests?: boolean;
|
|
143
|
-
contextLines?: number;
|
|
144
|
-
format?: 'markdown' | 'plain' | 'json';
|
|
145
|
-
timeout?: number;
|
|
146
128
|
noGitignore?: boolean;
|
|
147
129
|
}
|
|
148
130
|
|
|
@@ -193,38 +175,22 @@ class ProbeServer {
|
|
|
193
175
|
},
|
|
194
176
|
query: {
|
|
195
177
|
type: 'string',
|
|
196
|
-
description: 'Elastic search query. Supports logical operators (AND, OR, NOT), and grouping with parentheses. Examples: "config", "(term1 OR term2) AND term3"
|
|
197
|
-
},
|
|
198
|
-
filesOnly: {
|
|
199
|
-
type: 'boolean',
|
|
200
|
-
description: 'Skip AST parsing and just output unique files',
|
|
201
|
-
},
|
|
202
|
-
ignore: {
|
|
203
|
-
type: 'array',
|
|
204
|
-
items: { type: 'string' },
|
|
205
|
-
description: 'Custom patterns to ignore (in addition to .gitignore and common patterns)'
|
|
206
|
-
},
|
|
207
|
-
excludeFilenames: {
|
|
208
|
-
type: 'boolean',
|
|
209
|
-
description: 'Exclude filenames from being used for matching'
|
|
178
|
+
description: 'Elastic search query. Supports logical operators (AND, OR, NOT), and grouping with parentheses. For exact matches of specific identifiers, use quotes: "MyFunction", "SpecificStruct", "exact_variable_name". Examples: "config", "(term1 OR term2) AND term3", "getUserData", "struct Config".',
|
|
210
179
|
},
|
|
211
180
|
exact: {
|
|
212
181
|
type: 'boolean',
|
|
213
|
-
description: '
|
|
182
|
+
description: 'When you exactly know what you are looking for, like known function name, struct name, or variable name, set this flag for precise matching'
|
|
214
183
|
},
|
|
215
184
|
allowTests: {
|
|
216
185
|
type: 'boolean',
|
|
217
|
-
description: 'Allow test files and test code blocks in results (
|
|
186
|
+
description: 'Allow test files and test code blocks in results (enabled by default)',
|
|
187
|
+
default: true
|
|
218
188
|
},
|
|
219
189
|
session: {
|
|
220
190
|
type: 'string',
|
|
221
191
|
description: 'Session identifier for caching. Set to "new" if unknown, or want to reset cache. Re-use session ID returned from previous searches',
|
|
222
192
|
default: "new",
|
|
223
193
|
},
|
|
224
|
-
timeout: {
|
|
225
|
-
type: 'number',
|
|
226
|
-
description: 'Timeout for the search operation in seconds (default: 30)',
|
|
227
|
-
},
|
|
228
194
|
noGitignore: {
|
|
229
195
|
type: 'boolean',
|
|
230
196
|
description: 'Skip .gitignore files (will use PROBE_NO_GITIGNORE environment variable if not set)',
|
|
@@ -233,50 +199,6 @@ class ProbeServer {
|
|
|
233
199
|
required: ['path', 'query']
|
|
234
200
|
},
|
|
235
201
|
},
|
|
236
|
-
{
|
|
237
|
-
name: 'query_code',
|
|
238
|
-
description: "Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.",
|
|
239
|
-
inputSchema: {
|
|
240
|
-
type: 'object',
|
|
241
|
-
properties: {
|
|
242
|
-
path: {
|
|
243
|
-
type: 'string',
|
|
244
|
-
description: 'Absolute path to the directory to search in (e.g., "/Users/username/projects/myproject").',
|
|
245
|
-
},
|
|
246
|
-
pattern: {
|
|
247
|
-
type: 'string',
|
|
248
|
-
description: 'The ast-grep pattern to search for. Examples: "fn $NAME($$$PARAMS) $$$BODY" for Rust functions, "def $NAME($$$PARAMS): $$$BODY" for Python functions.',
|
|
249
|
-
},
|
|
250
|
-
language: {
|
|
251
|
-
type: 'string',
|
|
252
|
-
description: 'The programming language to search in. If not specified, the tool will try to infer the language from file extensions. Supported languages: rust, javascript, typescript, python, go, c, cpp, java, ruby, php, swift, csharp, yaml.',
|
|
253
|
-
},
|
|
254
|
-
ignore: {
|
|
255
|
-
type: 'array',
|
|
256
|
-
items: { type: 'string' },
|
|
257
|
-
description: 'Custom patterns to ignore (in addition to common patterns)',
|
|
258
|
-
},
|
|
259
|
-
maxResults: {
|
|
260
|
-
type: 'number',
|
|
261
|
-
description: 'Maximum number of results to return'
|
|
262
|
-
},
|
|
263
|
-
format: {
|
|
264
|
-
type: 'string',
|
|
265
|
-
enum: ['markdown', 'plain', 'json', 'color'],
|
|
266
|
-
description: 'Output format for the query results'
|
|
267
|
-
},
|
|
268
|
-
timeout: {
|
|
269
|
-
type: 'number',
|
|
270
|
-
description: 'Timeout for the query operation in seconds (default: 30)',
|
|
271
|
-
},
|
|
272
|
-
noGitignore: {
|
|
273
|
-
type: 'boolean',
|
|
274
|
-
description: 'Skip .gitignore files (will use PROBE_NO_GITIGNORE environment variable if not set)',
|
|
275
|
-
}
|
|
276
|
-
},
|
|
277
|
-
required: ['path', 'pattern']
|
|
278
|
-
},
|
|
279
|
-
},
|
|
280
202
|
{
|
|
281
203
|
name: 'extract_code',
|
|
282
204
|
description: "Extract code blocks from files based on line number, or symbol name. Fetch full file when line number is not provided.",
|
|
@@ -294,22 +216,8 @@ class ProbeServer {
|
|
|
294
216
|
},
|
|
295
217
|
allowTests: {
|
|
296
218
|
type: 'boolean',
|
|
297
|
-
description: 'Allow test files and test code blocks in results (
|
|
298
|
-
|
|
299
|
-
contextLines: {
|
|
300
|
-
type: 'number',
|
|
301
|
-
description: 'Number of context lines to include before and after the extracted block when AST parsing fails to find a suitable node',
|
|
302
|
-
default: 0
|
|
303
|
-
},
|
|
304
|
-
format: {
|
|
305
|
-
type: 'string',
|
|
306
|
-
enum: ['markdown', 'plain', 'json'],
|
|
307
|
-
description: 'Output format for the extracted code',
|
|
308
|
-
default: 'markdown'
|
|
309
|
-
},
|
|
310
|
-
timeout: {
|
|
311
|
-
type: 'number',
|
|
312
|
-
description: 'Timeout for the extract operation in seconds (default: 30)',
|
|
219
|
+
description: 'Allow test files and test code blocks in results (enabled by default)',
|
|
220
|
+
default: true
|
|
313
221
|
},
|
|
314
222
|
noGitignore: {
|
|
315
223
|
type: 'boolean',
|
|
@@ -323,8 +231,8 @@ class ProbeServer {
|
|
|
323
231
|
}));
|
|
324
232
|
|
|
325
233
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
326
|
-
if (request.params.name !== 'search_code' && request.params.name !== '
|
|
327
|
-
request.params.name !== 'probe' && request.params.name !== '
|
|
234
|
+
if (request.params.name !== 'search_code' && request.params.name !== 'extract_code' &&
|
|
235
|
+
request.params.name !== 'probe' && request.params.name !== 'extract') {
|
|
328
236
|
throw new McpError(
|
|
329
237
|
ErrorCode.MethodNotFound,
|
|
330
238
|
`Unknown tool: ${request.params.name}`
|
|
@@ -356,9 +264,6 @@ class ProbeServer {
|
|
|
356
264
|
}
|
|
357
265
|
|
|
358
266
|
result = await this.executeCodeSearch(args);
|
|
359
|
-
} else if (request.params.name === 'query_code' || request.params.name === 'query') {
|
|
360
|
-
const args = request.params.arguments as unknown as QueryCodeArgs;
|
|
361
|
-
result = await this.executeCodeQuery(args);
|
|
362
267
|
} else { // extract_code or extract
|
|
363
268
|
const args = request.params.arguments as unknown as ExtractCodeArgs;
|
|
364
269
|
result = await this.executeCodeExtract(args);
|
|
@@ -409,13 +314,15 @@ class ProbeServer {
|
|
|
409
314
|
};
|
|
410
315
|
|
|
411
316
|
// Add optional parameters only if they exist
|
|
412
|
-
if (args.filesOnly !== undefined) options.filesOnly = args.filesOnly;
|
|
413
|
-
if (args.ignore !== undefined) options.ignore = args.ignore;
|
|
414
|
-
if (args.excludeFilenames !== undefined) options.excludeFilenames = args.excludeFilenames;
|
|
415
317
|
if (args.exact !== undefined) options.exact = args.exact;
|
|
416
318
|
if (args.maxResults !== undefined) options.maxResults = args.maxResults;
|
|
417
319
|
if (args.maxTokens !== undefined) options.maxTokens = args.maxTokens;
|
|
418
|
-
|
|
320
|
+
// Set allowTests to true by default if not specified
|
|
321
|
+
if (args.allowTests !== undefined) {
|
|
322
|
+
options.allowTests = args.allowTests;
|
|
323
|
+
} else {
|
|
324
|
+
options.allowTests = true;
|
|
325
|
+
}
|
|
419
326
|
// Use noGitignore from args, or fall back to PROBE_NO_GITIGNORE environment variable
|
|
420
327
|
if (args.noGitignore !== undefined) {
|
|
421
328
|
options.noGitignore = args.noGitignore;
|
|
@@ -427,12 +334,6 @@ class ProbeServer {
|
|
|
427
334
|
} else {
|
|
428
335
|
options.session = "new";
|
|
429
336
|
}
|
|
430
|
-
// Use timeout from args, or fall back to instance default
|
|
431
|
-
if (args.timeout !== undefined) {
|
|
432
|
-
options.timeout = args.timeout;
|
|
433
|
-
} else if (this.defaultTimeout !== undefined) {
|
|
434
|
-
options.timeout = this.defaultTimeout;
|
|
435
|
-
}
|
|
436
337
|
|
|
437
338
|
// Handle format options
|
|
438
339
|
if (this.defaultFormat === 'outline-xml') {
|
|
@@ -467,50 +368,6 @@ class ProbeServer {
|
|
|
467
368
|
}
|
|
468
369
|
}
|
|
469
370
|
|
|
470
|
-
private async executeCodeQuery(args: QueryCodeArgs): Promise<string> {
|
|
471
|
-
try {
|
|
472
|
-
// Validate required parameters
|
|
473
|
-
if (!args.path) {
|
|
474
|
-
throw new Error("Path is required");
|
|
475
|
-
}
|
|
476
|
-
if (!args.pattern) {
|
|
477
|
-
throw new Error("Pattern is required");
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// Create a single options object with both pattern and path
|
|
481
|
-
const options: any = {
|
|
482
|
-
path: args.path,
|
|
483
|
-
pattern: args.pattern,
|
|
484
|
-
language: args.language,
|
|
485
|
-
ignore: args.ignore,
|
|
486
|
-
allowTests: args.allowTests,
|
|
487
|
-
maxResults: args.maxResults,
|
|
488
|
-
format: args.format,
|
|
489
|
-
timeout: args.timeout || this.defaultTimeout
|
|
490
|
-
};
|
|
491
|
-
|
|
492
|
-
// Use noGitignore from args, or fall back to PROBE_NO_GITIGNORE environment variable
|
|
493
|
-
if (args.noGitignore !== undefined) {
|
|
494
|
-
options.noGitignore = args.noGitignore;
|
|
495
|
-
} else if (process.env.PROBE_NO_GITIGNORE) {
|
|
496
|
-
options.noGitignore = process.env.PROBE_NO_GITIGNORE === 'true';
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
console.log("Executing query with options:", JSON.stringify({
|
|
500
|
-
path: options.path,
|
|
501
|
-
pattern: options.pattern
|
|
502
|
-
}));
|
|
503
|
-
|
|
504
|
-
const result = await query(options);
|
|
505
|
-
return result;
|
|
506
|
-
} catch (error: any) {
|
|
507
|
-
console.error('Error executing code query:', error);
|
|
508
|
-
throw new McpError(
|
|
509
|
-
'MethodNotFound' as unknown as ErrorCode,
|
|
510
|
-
`Error executing code query: ${error.message || String(error)}`
|
|
511
|
-
);
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
371
|
|
|
515
372
|
private async executeCodeExtract(args: ExtractCodeArgs): Promise<string> {
|
|
516
373
|
try {
|
|
@@ -526,12 +383,16 @@ class ProbeServer {
|
|
|
526
383
|
const options: any = {
|
|
527
384
|
files: args.files,
|
|
528
385
|
path: args.path,
|
|
529
|
-
|
|
530
|
-
contextLines: args.contextLines,
|
|
531
|
-
format: args.format,
|
|
532
|
-
timeout: args.timeout || this.defaultTimeout
|
|
386
|
+
format: 'xml'
|
|
533
387
|
};
|
|
534
388
|
|
|
389
|
+
// Set allowTests to true by default if not specified
|
|
390
|
+
if (args.allowTests !== undefined) {
|
|
391
|
+
options.allowTests = args.allowTests;
|
|
392
|
+
} else {
|
|
393
|
+
options.allowTests = true;
|
|
394
|
+
}
|
|
395
|
+
|
|
535
396
|
// Use noGitignore from args, or fall back to PROBE_NO_GITIGNORE environment variable
|
|
536
397
|
if (args.noGitignore !== undefined) {
|
|
537
398
|
options.noGitignore = args.noGitignore;
|
package/cjs/agent/ProbeAgent.cjs
CHANGED
|
@@ -1840,7 +1840,7 @@ async function delegate({ task, timeout = 300, debug = false, currentIteration =
|
|
|
1840
1840
|
console.error(`[DELEGATE] Using binary at: ${binaryPath}`);
|
|
1841
1841
|
console.error(`[DELEGATE] Command args: ${args.join(" ")}`);
|
|
1842
1842
|
}
|
|
1843
|
-
return new Promise((
|
|
1843
|
+
return new Promise((resolve2, reject) => {
|
|
1844
1844
|
const delegationSpan = tracer ? tracer.createDelegationSpan(sessionId, task) : null;
|
|
1845
1845
|
const process2 = (0, import_child_process5.spawn)(binaryPath, args, {
|
|
1846
1846
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -1905,7 +1905,7 @@ async function delegate({ task, timeout = 300, debug = false, currentIteration =
|
|
|
1905
1905
|
delegationSpan.end();
|
|
1906
1906
|
}
|
|
1907
1907
|
}
|
|
1908
|
-
|
|
1908
|
+
resolve2(response);
|
|
1909
1909
|
} else {
|
|
1910
1910
|
const errorMessage = stderr.trim() || `Delegate process failed with exit code ${code}`;
|
|
1911
1911
|
if (debug) {
|
|
@@ -2782,7 +2782,7 @@ function createMockProvider() {
|
|
|
2782
2782
|
provider: "mock",
|
|
2783
2783
|
// Mock the doGenerate method used by Vercel AI SDK
|
|
2784
2784
|
doGenerate: async ({ messages, tools: tools2 }) => {
|
|
2785
|
-
await new Promise((
|
|
2785
|
+
await new Promise((resolve2) => setTimeout(resolve2, 10));
|
|
2786
2786
|
return {
|
|
2787
2787
|
text: "This is a mock response for testing",
|
|
2788
2788
|
toolCalls: [],
|
|
@@ -4608,7 +4608,7 @@ __export(ProbeAgent_exports, {
|
|
|
4608
4608
|
ProbeAgent: () => ProbeAgent
|
|
4609
4609
|
});
|
|
4610
4610
|
module.exports = __toCommonJS(ProbeAgent_exports);
|
|
4611
|
-
var import_anthropic, import_openai, import_google, import_ai2, import_crypto5, import_events2, MAX_TOOL_ITERATIONS, MAX_HISTORY_MESSAGES, ProbeAgent;
|
|
4611
|
+
var import_anthropic, import_openai, import_google, import_ai2, import_crypto5, import_events2, import_fs6, import_promises, import_path8, MAX_TOOL_ITERATIONS, MAX_HISTORY_MESSAGES, SUPPORTED_IMAGE_EXTENSIONS, MAX_IMAGE_FILE_SIZE, ProbeAgent;
|
|
4612
4612
|
var init_ProbeAgent = __esm({
|
|
4613
4613
|
"src/agent/ProbeAgent.js"() {
|
|
4614
4614
|
import_anthropic = require("@ai-sdk/anthropic");
|
|
@@ -4617,6 +4617,9 @@ var init_ProbeAgent = __esm({
|
|
|
4617
4617
|
import_ai2 = require("ai");
|
|
4618
4618
|
import_crypto5 = require("crypto");
|
|
4619
4619
|
import_events2 = require("events");
|
|
4620
|
+
import_fs6 = require("fs");
|
|
4621
|
+
import_promises = require("fs/promises");
|
|
4622
|
+
import_path8 = require("path");
|
|
4620
4623
|
init_tokenCounter();
|
|
4621
4624
|
init_tools2();
|
|
4622
4625
|
init_common();
|
|
@@ -4627,6 +4630,8 @@ var init_ProbeAgent = __esm({
|
|
|
4627
4630
|
init_mcp();
|
|
4628
4631
|
MAX_TOOL_ITERATIONS = parseInt(process.env.MAX_TOOL_ITERATIONS || "30", 10);
|
|
4629
4632
|
MAX_HISTORY_MESSAGES = 100;
|
|
4633
|
+
SUPPORTED_IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "webp", "gif", "bmp", "svg"];
|
|
4634
|
+
MAX_IMAGE_FILE_SIZE = 20 * 1024 * 1024;
|
|
4630
4635
|
ProbeAgent = class {
|
|
4631
4636
|
/**
|
|
4632
4637
|
* Create a new ProbeAgent instance
|
|
@@ -4670,6 +4675,8 @@ var init_ProbeAgent = __esm({
|
|
|
4670
4675
|
}
|
|
4671
4676
|
this.initializeTools();
|
|
4672
4677
|
this.history = [];
|
|
4678
|
+
this.pendingImages = /* @__PURE__ */ new Map();
|
|
4679
|
+
this.currentImages = [];
|
|
4673
4680
|
this.events = new import_events2.EventEmitter();
|
|
4674
4681
|
this.enableMcp = !!options.enableMcp || process.env.ENABLE_MCP === "1";
|
|
4675
4682
|
this.mcpConfigPath = options.mcpConfigPath || null;
|
|
@@ -4795,6 +4802,175 @@ var init_ProbeAgent = __esm({
|
|
|
4795
4802
|
console.log(`Using Google API with model: ${this.model}${apiUrl ? ` (URL: ${apiUrl})` : ""}`);
|
|
4796
4803
|
}
|
|
4797
4804
|
}
|
|
4805
|
+
/**
|
|
4806
|
+
* Process assistant response content and detect/load image references
|
|
4807
|
+
* @param {string} content - The assistant's response content
|
|
4808
|
+
* @returns {Promise<void>}
|
|
4809
|
+
*/
|
|
4810
|
+
async processImageReferences(content) {
|
|
4811
|
+
if (!content) return;
|
|
4812
|
+
const extensionsPattern = `(?:${SUPPORTED_IMAGE_EXTENSIONS.join("|")})`;
|
|
4813
|
+
const imagePatterns = [
|
|
4814
|
+
// Direct file path mentions: "./screenshot.png", "/path/to/image.jpg", etc.
|
|
4815
|
+
new RegExp(`(?:\\.?\\.\\/)?[^\\s"'<>\\[\\]]+\\.${extensionsPattern}(?!\\w)`, "gi"),
|
|
4816
|
+
// Contextual mentions: "look at image.png", "the file screenshot.jpg shows"
|
|
4817
|
+
new RegExp(`(?:image|file|screenshot|diagram|photo|picture|graphic)\\s*:?\\s*([^\\s"'<>\\[\\]]+\\.${extensionsPattern})(?!\\w)`, "gi"),
|
|
4818
|
+
// Tool result mentions: often contain file paths
|
|
4819
|
+
new RegExp(`(?:found|saved|created|generated).*?([^\\s"'<>\\[\\]]+\\.${extensionsPattern})(?!\\w)`, "gi")
|
|
4820
|
+
];
|
|
4821
|
+
const foundPaths = /* @__PURE__ */ new Set();
|
|
4822
|
+
for (const pattern of imagePatterns) {
|
|
4823
|
+
let match;
|
|
4824
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
4825
|
+
const imagePath = match[1] || match[0];
|
|
4826
|
+
if (imagePath && imagePath.length > 0) {
|
|
4827
|
+
foundPaths.add(imagePath.trim());
|
|
4828
|
+
}
|
|
4829
|
+
}
|
|
4830
|
+
}
|
|
4831
|
+
if (foundPaths.size === 0) return;
|
|
4832
|
+
if (this.debug) {
|
|
4833
|
+
console.log(`[DEBUG] Found ${foundPaths.size} potential image references:`, Array.from(foundPaths));
|
|
4834
|
+
}
|
|
4835
|
+
for (const imagePath of foundPaths) {
|
|
4836
|
+
await this.loadImageIfValid(imagePath);
|
|
4837
|
+
}
|
|
4838
|
+
}
|
|
4839
|
+
/**
|
|
4840
|
+
* Load and cache an image if it's valid and accessible
|
|
4841
|
+
* @param {string} imagePath - Path to the image file
|
|
4842
|
+
* @returns {Promise<boolean>} - True if image was loaded successfully
|
|
4843
|
+
*/
|
|
4844
|
+
async loadImageIfValid(imagePath) {
|
|
4845
|
+
try {
|
|
4846
|
+
if (this.pendingImages.has(imagePath)) {
|
|
4847
|
+
if (this.debug) {
|
|
4848
|
+
console.log(`[DEBUG] Image already loaded: ${imagePath}`);
|
|
4849
|
+
}
|
|
4850
|
+
return true;
|
|
4851
|
+
}
|
|
4852
|
+
const allowedDirs = this.allowedFolders && this.allowedFolders.length > 0 ? this.allowedFolders : [process.cwd()];
|
|
4853
|
+
let absolutePath;
|
|
4854
|
+
let isPathAllowed = false;
|
|
4855
|
+
if ((0, import_path8.isAbsolute)(imagePath)) {
|
|
4856
|
+
absolutePath = imagePath;
|
|
4857
|
+
isPathAllowed = allowedDirs.some((dir) => absolutePath.startsWith((0, import_path8.resolve)(dir)));
|
|
4858
|
+
} else {
|
|
4859
|
+
for (const dir of allowedDirs) {
|
|
4860
|
+
const resolvedPath = (0, import_path8.resolve)(dir, imagePath);
|
|
4861
|
+
if (resolvedPath.startsWith((0, import_path8.resolve)(dir))) {
|
|
4862
|
+
absolutePath = resolvedPath;
|
|
4863
|
+
isPathAllowed = true;
|
|
4864
|
+
break;
|
|
4865
|
+
}
|
|
4866
|
+
}
|
|
4867
|
+
}
|
|
4868
|
+
if (!isPathAllowed) {
|
|
4869
|
+
if (this.debug) {
|
|
4870
|
+
console.log(`[DEBUG] Image path outside allowed directories: ${imagePath}`);
|
|
4871
|
+
}
|
|
4872
|
+
return false;
|
|
4873
|
+
}
|
|
4874
|
+
let fileStats;
|
|
4875
|
+
try {
|
|
4876
|
+
fileStats = await (0, import_promises.stat)(absolutePath);
|
|
4877
|
+
} catch (error) {
|
|
4878
|
+
if (this.debug) {
|
|
4879
|
+
console.log(`[DEBUG] Image file not found: ${absolutePath}`);
|
|
4880
|
+
}
|
|
4881
|
+
return false;
|
|
4882
|
+
}
|
|
4883
|
+
if (fileStats.size > MAX_IMAGE_FILE_SIZE) {
|
|
4884
|
+
if (this.debug) {
|
|
4885
|
+
console.log(`[DEBUG] Image file too large: ${absolutePath} (${fileStats.size} bytes, max: ${MAX_IMAGE_FILE_SIZE})`);
|
|
4886
|
+
}
|
|
4887
|
+
return false;
|
|
4888
|
+
}
|
|
4889
|
+
const extension = absolutePath.toLowerCase().split(".").pop();
|
|
4890
|
+
if (!SUPPORTED_IMAGE_EXTENSIONS.includes(extension)) {
|
|
4891
|
+
if (this.debug) {
|
|
4892
|
+
console.log(`[DEBUG] Unsupported image format: ${extension}`);
|
|
4893
|
+
}
|
|
4894
|
+
return false;
|
|
4895
|
+
}
|
|
4896
|
+
const mimeTypes = {
|
|
4897
|
+
"png": "image/png",
|
|
4898
|
+
"jpg": "image/jpeg",
|
|
4899
|
+
"jpeg": "image/jpeg",
|
|
4900
|
+
"webp": "image/webp",
|
|
4901
|
+
"gif": "image/gif",
|
|
4902
|
+
"bmp": "image/bmp",
|
|
4903
|
+
"svg": "image/svg+xml"
|
|
4904
|
+
};
|
|
4905
|
+
const mimeType = mimeTypes[extension];
|
|
4906
|
+
const fileBuffer = await (0, import_promises.readFile)(absolutePath);
|
|
4907
|
+
const base64Data = fileBuffer.toString("base64");
|
|
4908
|
+
const dataUrl = `data:${mimeType};base64,${base64Data}`;
|
|
4909
|
+
this.pendingImages.set(imagePath, dataUrl);
|
|
4910
|
+
if (this.debug) {
|
|
4911
|
+
console.log(`[DEBUG] Successfully loaded image: ${imagePath} (${fileBuffer.length} bytes)`);
|
|
4912
|
+
}
|
|
4913
|
+
return true;
|
|
4914
|
+
} catch (error) {
|
|
4915
|
+
if (this.debug) {
|
|
4916
|
+
console.log(`[DEBUG] Failed to load image ${imagePath}: ${error.message}`);
|
|
4917
|
+
}
|
|
4918
|
+
return false;
|
|
4919
|
+
}
|
|
4920
|
+
}
|
|
4921
|
+
/**
|
|
4922
|
+
* Get all currently loaded images as an array for AI model consumption
|
|
4923
|
+
* @returns {Array<string>} - Array of base64 data URLs
|
|
4924
|
+
*/
|
|
4925
|
+
getCurrentImages() {
|
|
4926
|
+
return Array.from(this.pendingImages.values());
|
|
4927
|
+
}
|
|
4928
|
+
/**
|
|
4929
|
+
* Clear loaded images (useful for new conversations)
|
|
4930
|
+
*/
|
|
4931
|
+
clearLoadedImages() {
|
|
4932
|
+
this.pendingImages.clear();
|
|
4933
|
+
this.currentImages = [];
|
|
4934
|
+
if (this.debug) {
|
|
4935
|
+
console.log("[DEBUG] Cleared all loaded images");
|
|
4936
|
+
}
|
|
4937
|
+
}
|
|
4938
|
+
/**
|
|
4939
|
+
* Prepare messages for AI consumption, adding images to the latest user message if available
|
|
4940
|
+
* @param {Array} messages - Current conversation messages
|
|
4941
|
+
* @returns {Array} - Messages formatted for AI SDK with potential image content
|
|
4942
|
+
*/
|
|
4943
|
+
prepareMessagesWithImages(messages) {
|
|
4944
|
+
const loadedImages = this.getCurrentImages();
|
|
4945
|
+
if (loadedImages.length === 0) {
|
|
4946
|
+
return messages;
|
|
4947
|
+
}
|
|
4948
|
+
const messagesWithImages = [...messages];
|
|
4949
|
+
const lastUserMessageIndex = messagesWithImages.map((m) => m.role).lastIndexOf("user");
|
|
4950
|
+
if (lastUserMessageIndex === -1) {
|
|
4951
|
+
if (this.debug) {
|
|
4952
|
+
console.log("[DEBUG] No user messages found to attach images to");
|
|
4953
|
+
}
|
|
4954
|
+
return messages;
|
|
4955
|
+
}
|
|
4956
|
+
const lastUserMessage = messagesWithImages[lastUserMessageIndex];
|
|
4957
|
+
if (typeof lastUserMessage.content === "string") {
|
|
4958
|
+
messagesWithImages[lastUserMessageIndex] = {
|
|
4959
|
+
...lastUserMessage,
|
|
4960
|
+
content: [
|
|
4961
|
+
{ type: "text", text: lastUserMessage.content },
|
|
4962
|
+
...loadedImages.map((imageData) => ({
|
|
4963
|
+
type: "image",
|
|
4964
|
+
image: imageData
|
|
4965
|
+
}))
|
|
4966
|
+
]
|
|
4967
|
+
};
|
|
4968
|
+
if (this.debug) {
|
|
4969
|
+
console.log(`[DEBUG] Added ${loadedImages.length} images to the latest user message`);
|
|
4970
|
+
}
|
|
4971
|
+
}
|
|
4972
|
+
return messagesWithImages;
|
|
4973
|
+
}
|
|
4798
4974
|
/**
|
|
4799
4975
|
* Initialize mock model for testing
|
|
4800
4976
|
*/
|
|
@@ -5176,9 +5352,10 @@ You are working with a repository located at: ${searchDirectory}
|
|
|
5176
5352
|
let assistantResponseContent = "";
|
|
5177
5353
|
try {
|
|
5178
5354
|
const executeAIRequest = async () => {
|
|
5355
|
+
const messagesForAI = this.prepareMessagesWithImages(currentMessages);
|
|
5179
5356
|
const result = await (0, import_ai2.streamText)({
|
|
5180
5357
|
model: this.provider(this.model),
|
|
5181
|
-
messages:
|
|
5358
|
+
messages: messagesForAI,
|
|
5182
5359
|
maxTokens: maxResponseTokens,
|
|
5183
5360
|
temperature: 0.3
|
|
5184
5361
|
});
|
|
@@ -5212,6 +5389,9 @@ You are working with a repository located at: ${searchDirectory}
|
|
|
5212
5389
|
const assistantPreview = createMessagePreview(assistantResponseContent);
|
|
5213
5390
|
console.log(`[DEBUG] Assistant response (${assistantResponseContent.length} chars): ${assistantPreview}`);
|
|
5214
5391
|
}
|
|
5392
|
+
if (assistantResponseContent) {
|
|
5393
|
+
await this.processImageReferences(assistantResponseContent);
|
|
5394
|
+
}
|
|
5215
5395
|
const validTools = [
|
|
5216
5396
|
"search",
|
|
5217
5397
|
"query",
|
|
@@ -5336,12 +5516,17 @@ ${toolResultContent}
|
|
|
5336
5516
|
throw toolError;
|
|
5337
5517
|
}
|
|
5338
5518
|
currentMessages.push({ role: "assistant", content: assistantResponseContent });
|
|
5519
|
+
const toolResultContent = typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult, null, 2);
|
|
5520
|
+
const toolResultMessage = `<tool_result>
|
|
5521
|
+
${toolResultContent}
|
|
5522
|
+
</tool_result>`;
|
|
5339
5523
|
currentMessages.push({
|
|
5340
5524
|
role: "user",
|
|
5341
|
-
content:
|
|
5342
|
-
${typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult, null, 2)}
|
|
5343
|
-
</tool_result>`
|
|
5525
|
+
content: toolResultMessage
|
|
5344
5526
|
});
|
|
5527
|
+
if (toolResultContent) {
|
|
5528
|
+
await this.processImageReferences(toolResultContent);
|
|
5529
|
+
}
|
|
5345
5530
|
if (this.debug) {
|
|
5346
5531
|
console.log(`[DEBUG] Tool ${toolName} executed successfully. Result length: ${typeof toolResult === "string" ? toolResult.length : JSON.stringify(toolResult).length}`);
|
|
5347
5532
|
}
|