@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.
@@ -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". Use quotes for exact matches, like function or type names.',
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: 'Perform exact search without tokenization (case-insensitive)'
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 (disabled by default)'
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 (disabled by default)',
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 !== 'query_code' && request.params.name !== 'extract_code' &&
327
- request.params.name !== 'probe' && request.params.name !== 'query' && request.params.name !== 'extract') {
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
- if (args.allowTests !== undefined) options.allowTests = args.allowTests;
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
- allowTests: args.allowTests,
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;
@@ -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((resolve, reject) => {
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
- resolve(response);
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((resolve) => setTimeout(resolve, 10));
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: currentMessages,
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: `<tool_result>
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
  }