src / tools / searchImages.ts
src / tools / searchImages.ts
import { tool, ToolsProviderController } from "@lmstudio/sdk";
import { z } from "zod";
import { defaultConfigurationService } from "../config/ConfigurationService";
import { defaultImageSearchEngine } from "../bootstrap";
import { ImageSearchEngine } from "../image_search/engine/ImageSearchEngine";
import { ImagePresentationFormatter } from "../image_search/presentation/ImagePresentationFormatter";
/**
* Factory function creating an LM Studio search_images tool instance.
* Obtains active chat working directory from ctl.getWorkingDirectory() and passes it to ImageSearchEngine.
* Matches Daniel Sig's working LM Studio image search tool contract.
*/
export function createSearchImagesTool(
imageSearchEngine: ImageSearchEngine = defaultImageSearchEngine,
ctl?: ToolsProviderController
) {
return tool({
name: "Image Search",
description: "Searches the web for relevant images and downloads them for displaying in the current chat.",
parameters: {
query: z.string().describe("Search query string for images"),
numResults: z
.number()
.optional()
.describe("Maximum number of image search results to return (default: 10, max: 30)"),
},
implementation: async ({ query, numResults }) => {
// 1. Request Validation
if (!query || !query.trim()) {
return "Error: Query parameter must be a non-empty string.";
}
const defaultCount = defaultConfigurationService.getMaxImageResults();
const count = numResults ?? defaultCount;
if (!Number.isInteger(count) || count <= 0 || count > 30) {
return "Error: num_results must be a positive integer between 1 and 30.";
}
// Obtain active chat working directory from LM Studio SDK controller (fail fast if missing)
if (!ctl) {
throw new Error("ToolsProviderController is required for Image Search tool.");
}
const workingDirectory = ctl.getWorkingDirectory();
// 2. Engine Invocation & Presentation Formatting
try {
const localImages = await imageSearchEngine.searchImages(query.trim(), {
numResults: count,
workingDirectory,
});
if (!localImages || localImages.length === 0) {
return `No images found for query '${query.trim()}'.`;
}
const markdownImages = ImagePresentationFormatter.formatMarkdownImageArray(localImages, count);
if (markdownImages.length === 0) {
return `No images could be downloaded for query '${query.trim()}'.`;
}
return markdownImages;
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
return `Error during image search: ${msg}`;
}
},
});
}
/**
* Singleton search_images tool instance using defaultImageSearchEngine from bootstrap.
*/
export const searchImagesTool = createSearchImagesTool();
import { tool, ToolsProviderController } from "@lmstudio/sdk";
import { z } from "zod";
import { defaultConfigurationService } from "../config/ConfigurationService";
import { defaultImageSearchEngine } from "../bootstrap";
import { ImageSearchEngine } from "../image_search/engine/ImageSearchEngine";
import { ImagePresentationFormatter } from "../image_search/presentation/ImagePresentationFormatter";
/**
* Factory function creating an LM Studio search_images tool instance.
* Obtains active chat working directory from ctl.getWorkingDirectory() and passes it to ImageSearchEngine.
* Matches Daniel Sig's working LM Studio image search tool contract.
*/
export function createSearchImagesTool(
imageSearchEngine: ImageSearchEngine = defaultImageSearchEngine,
ctl?: ToolsProviderController
) {
return tool({
name: "Image Search",
description: "Searches the web for relevant images and downloads them for displaying in the current chat.",
parameters: {
query: z.string().describe("Search query string for images"),
numResults: z
.number()
.optional()
.describe("Maximum number of image search results to return (default: 10, max: 30)"),
},
implementation: async ({ query, numResults }) => {
// 1. Request Validation
if (!query || !query.trim()) {
return "Error: Query parameter must be a non-empty string.";
}
const defaultCount = defaultConfigurationService.getMaxImageResults();
const count = numResults ?? defaultCount;
if (!Number.isInteger(count) || count <= 0 || count > 30) {
return "Error: num_results must be a positive integer between 1 and 30.";
}
// Obtain active chat working directory from LM Studio SDK controller (fail fast if missing)
if (!ctl) {
throw new Error("ToolsProviderController is required for Image Search tool.");
}
const workingDirectory = ctl.getWorkingDirectory();
// 2. Engine Invocation & Presentation Formatting
try {
const localImages = await imageSearchEngine.searchImages(query.trim(), {
numResults: count,
workingDirectory,
});
if (!localImages || localImages.length === 0) {
return `No images found for query '${query.trim()}'.`;
}
const markdownImages = ImagePresentationFormatter.formatMarkdownImageArray(localImages, count);
if (markdownImages.length === 0) {
return `No images could be downloaded for query '${query.trim()}'.`;
}
return markdownImages;
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
return `Error during image search: ${msg}`;
}
},
});
}
/**
* Singleton search_images tool instance using defaultImageSearchEngine from bootstrap.
*/
export const searchImagesTool = createSearchImagesTool();