/**
 * LM Studio plugin entry — registers two web-fetching tools with the
 * host's tool-provider system.
 *
 * Tool implementations live in `./tools/*.ts` as pure functions so
 * they stay portable across SDK API changes. Only the `main(ctx)`
 * glue here couples to `@lmstudio/sdk`. If a future SDK release
 * shifts the registration shape (the SDK is still marked
 * `@experimental` for plugin support as of 1.5.x), update this
 * file's contents — `tools/*` and `util/*` should keep compiling
 * unchanged.
 *
 * The pattern matches the `PluginContext` interface exported by
 * `@lmstudio/sdk`: a chained builder where you call
 * `ctx.withToolsProvider(...)` (and friends) once per plugin
 * capability you want to register. The host calls `main(ctx)` once
 * at plugin load time.
 */

import { tool } from "@lmstudio/sdk";
import type { PluginContext } from "@lmstudio/sdk";
import { z } from "zod";
import { fetchUrl } from "./tools/fetchUrl.js";
import { researchUrl } from "./tools/researchUrl.js";

const fetchUrlTool = tool({
  name: "fetch_url",
  description:
    "Fetch a URL and return its content. Auto-detects content type: " +
    "JSON is pretty-printed, HTML is stripped to readable text with the " +
    "page's <title>, plain text is returned verbatim, binary is summarised. " +
    "Use this when the user gives you a specific URL or when you need raw " +
    "content from a known endpoint. For 'find me information about X', " +
    "prefer research_url instead — it pulls the article body, not the " +
    "navigation chrome.",
  parameters: {
    url: z
      .string()
      .url()
      .describe("Absolute http(s) URL to fetch."),
    method: z
      .string()
      .optional()
      .describe("HTTP method. Defaults to GET."),
    headers: z
      .record(z.string(), z.string())
      .optional()
      .describe("Extra request headers."),
    body: z
      .string()
      .optional()
      .describe("Request body for non-GET methods."),
    timeout: z
      .number()
      .optional()
      .describe("Request timeout in ms (default 30000, max 120000)."),
    allowPrivate: z
      .boolean()
      .optional()
      .describe(
        "Allow targets that resolve to RFC1918 / loopback / link-local " +
          "addresses (localhost, 192.168.*, 10.*, 172.16-31.*, " +
          "169.254.*, fe80::*, fc00::/7). Off by default to keep the " +
          "model out of cloud-metadata services and the user's intranet.",
      ),
  },
  implementation: async ({ url, method, headers, body, timeout, allowPrivate }) => {
    const result = await fetchUrl({
      url,
      method,
      headers,
      body,
      timeout,
      allowPrivate,
    });
    // The implementation contract returns whatever string the LLM
    // should see in the tool result. JSON-stringify so the model gets
    // stable, parseable structure rather than a `[object Object]`.
    return JSON.stringify(result, null, 2);
  },
});

const researchUrlTool = tool({
  name: "research_url",
  description:
    "Fetch a URL and return its readable article body, biased toward " +
    "the <main>/<article> region with header/nav/footer/aside removed. " +
    "Best for 'tell me what this page says' on news sites, blog posts, " +
    "GitHub READMEs, Wikipedia, docs sites. Pure HTTP, no JS execution: " +
    "single-page apps that hydrate from JSON will return near-empty text — " +
    "if you see {spaSuspected:true} in the result, the page needs a real " +
    "browser to render and this plugin can't help.",
  parameters: {
    url: z
      .string()
      .url()
      .describe("Absolute http(s) URL to research."),
    timeout: z
      .number()
      .optional()
      .describe("Request timeout in ms (default 30000, max 120000)."),
    allowPrivate: z
      .boolean()
      .optional()
      .describe(
        "Allow targets that resolve to RFC1918 / loopback / link-local. " +
          "Off by default.",
      ),
  },
  implementation: async ({ url, timeout, allowPrivate }) => {
    const result = await researchUrl({ url, timeout, allowPrivate });
    return JSON.stringify(result, null, 2);
  },
});

/**
 * Plugin entry point. LM Studio's plugin runner calls this once at
 * load time with a `PluginContext` builder. We register both tools
 * via the chained `withToolsProvider(...)` API; the callback is
 * invoked any time the host needs a fresh list of tools (e.g. when
 * settings change).
 */
export async function main(ctx: PluginContext): Promise<void> {
  ctx.withToolsProvider(async () => [fetchUrlTool, researchUrlTool]);
}

// Some plugin loaders look at the default export instead of `main`.
// Re-export so either entry path works.
export default main;
