src / toolsProvider.ts

import { text, tool, type Tool, type ToolsProviderController } from "@lmstudio/sdk";
import { z } from "zod";
import { configSchematics } from "./configSchematics";

export async function toolsProvider(ctl: ToolsProviderController) {
  const tools: Tool[] = [];
  
  // Retrieve plugin configuration
  const pluginConfig = ctl.getPluginConfig(configSchematics);
  const braveApiKey = pluginConfig.get("braveApiKey");
  
  // Fetch URL Tool (general HTTP GET, no auth needed)
  const fetchTool = tool({
    name: "fetch_url",
    description: text`
      Perform an HTTP GET request to a URL and return the response content as text.
      Useful for accessing web pages, APIs, or files. Handles text-based responses (e.g., HTML, JSON).
      If the content is too large, it may be truncated; check status for warnings.
    `,
    parameters: {
      url: z.string().url().describe("The URL to fetch (must be a valid HTTP/HTTPS URL)."),
    },
    implementation: async ({ url }, { status, warn, signal }) => {
      try {
        status(`Fetching URL: ${url}`);
        const response = await fetch(url, { signal });
        if (!response.ok) {
          return `Error: ${response.status} ${response.statusText}`;
        }
        const text = await response.text();
        if (text.length > 100000) {  // Arbitrary limit to prevent overwhelming the model
          warn("Response content is very large; truncating to first 100,000 characters.");
          return text.slice(0, 100000) + "\n\n[Truncated: Content too long]";
        }
        return text;
      } catch (error) {
        return `Error fetching URL: ${error.message}`;
      }
    },
  });
  tools.push(fetchTool);
  
  // Search Web Tool (using Brave Search API)
  const searchTool = tool({
    name: "search_web",
    description: text`
        Search the web using the Brave Search API and return formatted results (titles, URLs, descriptions).
        Returns up to 10 results. Use for general queries; for specific pages, use fetch_url.
      `,
    parameters: {
      query: z.string().describe("The search query (e.g., 'latest AI news 2025')."),
      count: z.number().int().min(1).max(20).optional().default(10).describe("Number of results (default 10, max 20)."),
      country: z.string().optional().default("us").describe("Country code for localized results (e.g., 'us', 'gb')."),
      lang: z.string().optional().default("en").describe("Search language (e.g., 'en', 'fr')."),
    },
    implementation: async ({ query, count, country, lang }, { status, warn, signal }) => {
      try {
        status(`Searching web for: ${query}`);
        const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${count}&country=${country}&search_lang=${lang}`;
        const response = await fetch(url, {
          headers: { "X-Subscription-Token": braveApiKey },
          signal,
        });
        if (!response.ok) {
          if (response.status === 429) {
            warn("Rate limit exceeded; check your Brave API quota.");
          }
          return `Error: ${response.status} ${response.statusText}`;
        }
        const data = await response.json();
        if (!data.web || !data.web.results) {
          return "No results found.";
        }
        // Format results for the model
        let formatted = "Search Results:\n";
        data.web.results.slice(0, count).forEach((result: any, index: number) => {
          formatted += `${index + 1}. Title: ${result.title}\n   URL: ${result.url}\n   Description: ${result.description}\n\n`;
        });
        return formatted;
      } catch (error) {
        return `Error during search: ${error.message}`;
      }
    },
  });
  tools.push(searchTool);
  
  
  return tools;
}