src / toolsProvider.ts

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

// Safe alphabets - exclude easily confused characters:
// - Lowercase: no 'l' (looks like '1' or 'I')
// - Uppercase: no 'I' (looks like '1' or 'l'), no 'O' (looks like '0')
// - Numbers: no '1' (looks like 'I' or 'l'), no '0' (looks like 'O')
const SAFE_LOWERCASE = "abcdefghijkmnopqrstuvwxyz"; // 23 chars (no 'l')
const SAFE_UPPERCASE = "ABCDEFGHJKLMNPQRSTUVWXYZ"; // 24 chars (no 'I', no 'O')
const SAFE_NUMBERS = "23456789"; // 8 chars (no '0', no '1')
const SAFE_SPECIAL = "!@#$%^&*()_+-=[]{}|;:,.<>?"; // 24 chars

export async function toolsProvider(_ctl: ToolsProviderController) {
  const generatePasswordTool = tool({
    name: "generate_password",
    description: text`
    Generates cryptographically secure passwords with customizable character sets. At least one must be enabled. Safe alphabets avoid confusing characters. Default length=12, all sets on.",
    `,
    parameters: {
      length: z.number().int().min(4).max(64).default(12).describe("Password length, min:4, max:64, default: 12 characters."),
      Lowercase: z.boolean().default(true).describe("Enabled lowercase letters"),
      Uppercase: z
        .boolean()
        .default(true)
        .describe("Enabled uppercase letters"),
      Numbers: z.boolean().default(true).describe("Enabled numbers, excludes '0', '1'"),
      SpecialChars: z.boolean().default(true).describe("Enabled special characters"),
    },
    implementation: async ({ length, Lowercase, Uppercase, Numbers, SpecialChars }) => {
      // Build charset from enabled character sets
      let charset = "";
      if (Lowercase) charset += SAFE_LOWERCASE;
      if (Uppercase) charset += SAFE_UPPERCASE;
      if (Numbers) charset += SAFE_NUMBERS;
      if (SpecialChars) charset += SAFE_SPECIAL;

      // Ensure at least one character set is enabled
      if (charset.length === 0) {
        throw new Error(
          "At least one character set must be enabled (Lowercase, Uppercase, Numbers, or SpecialChars)"
        );
      }

      let password = "";
      const crypto = await import("crypto");

      for (let i = 0; i < length; i++) {
        const randomIndex = crypto.randomInt(0, charset.length);
        password += charset[randomIndex];
      }

      return {
        password,
        length: password.length,
        options: {
          Lowercase,
          Uppercase,
          Numbers,
          SpecialChars,
        },
      };
    },
  });

  return [generatePasswordTool];
}