src / toolsProvider.ts
import { tool, text, type ToolsProviderController } from "@lmstudio/sdk";
import { z } from "zod";
import { configSchematics } from "./configSchematics";
import { anonymize } from "./anonymizer";
export async function toolsProvider(ctl: ToolsProviderController) {
const config = ctl.getPluginConfig(configSchematics);
const configCustomTerms = config.get("alwaysRedact");
const anonymizeText = tool({
name: "anonymize_text",
description: text`
Redact personally identifiable information (PII) from a block of text.
The tool DETECTS AUTOMATICALLY (with proper validation, not just regex):
• Credit / bank card numbers (Luhn-validated)
• French NIR / numéro de sécurité sociale (mod-97 checksum)
• IBAN (mod-97 checksum)
• French phone numbers (0X XX XX XX XX, +33 X XX XX XX XX, 0033…)
• Email addresses
The tool DOES NOT detect names. You (the model) must identify any personal
names in the text and pass them in \`names\`. Pass full names like
"Jean Dupont", not just first names, to avoid false matches against common
words. You can also pass arbitrary strings to redact via \`custom_terms\`
(e.g. company names, addresses, project codenames).
Replacement uses STABLE TYPED PSEUDONYMS:
\`[NOM_1]\`, \`[CB_1]\`, \`[NIR_1]\`, \`[IBAN_1]\`, \`[TEL_1]\`,
\`[EMAIL_1]\`, \`[CUSTOM_1]\`, …
Same value → same pseudonym throughout the text, so coreference is
preserved (e.g. "Jean … Jean" both become "[NOM_1]"). The response
includes a \`mapping\` from pseudonym to original value.
Typical workflow: read a file with the filesystem plugin, call
anonymize_text on its content, then write the redacted version back.
`,
parameters: {
text: z.string().min(1).describe("The text to anonymize."),
names: z.array(z.string().min(1)).optional().describe(
"Personal names to redact. Pass full names ('Jean Dupont'), not just first names.",
),
custom_terms: z.array(z.string().min(1)).optional().describe(
"Other strings to redact: company names, addresses, project codenames, etc.",
),
include_mapping: z.boolean().optional().describe(
"Include the pseudonym → original value mapping in the response. Default true.",
),
},
implementation: (args) => {
const result = anonymize(args.text, {
names: args.names,
customTerms: args.custom_terms,
configCustomTerms,
});
return {
anonymized: result.anonymized,
counts: result.counts,
...(args.include_mapping === false ? {} : { mapping: result.mapping }),
};
},
});
return [anonymizeText];
}