import { z } from "zod"; import type { JSONSchema7 } from "json-schema"; import type { ToolExecutor, ToolContext, ToolResult } from "../../registry"; import type { RegexTesterToolConfig } from "./config"; export const regexTesterInputSchema = z.object({ operation: z .enum(["match", "test", "replace", "extract"]) .describe("操作类型: match=匹配所有, test=测试是否匹配, replace=替换, extract=提取匹配组"), pattern: z.string().describe("正则表达式"), flags: z .string() .optional() .describe("正则标志 (g/i/m/s/u/y),默认空"), input: z.string().describe("要测试的文本"), replacement: z .string() .optional() .describe("替换字符串 (replace 操作使用),支持 $1, $2 等捕获组"), }); type RegexTesterInput = z.infer; function safeFlags(flags: string | undefined): string { if (!flags) return ""; const cleaned = flags.replace(/[^gimsuy]/g, ""); const unique = Array.from(new Set(cleaned)).join(""); return unique; } export const regexTesterExecutor: ToolExecutor = { buildInputSchema(_config: RegexTesterToolConfig): JSONSchema7 { return { type: "object", properties: { operation: { type: "string", enum: ["match", "test", "replace", "extract"], description: "操作类型", }, pattern: { type: "string", description: "正则表达式" }, flags: { type: "string", description: "正则标志 (g/i/m/s/u/y)" }, input: { type: "string", description: "要测试的文本" }, replacement: { type: "string", description: "替换字符串 (replace)" }, }, required: ["operation", "pattern", "input"], }; }, buildDescription(config: RegexTesterToolConfig): string { return `正则表达式工具。支持匹配(match)、测试(test)、替换(replace)、提取(extract)。最大输入 ${config.maxInputLength} 字符,最多 ${config.maxMatches} 个匹配。`; }, async execute( input: unknown, config: RegexTesterToolConfig, _ctx: ToolContext, ): Promise { const start = Date.now(); const parsed = regexTesterInputSchema.safeParse(input); if (!parsed.success) { return { success: false, data: null, error: `输入参数校验失败: ${parsed.error.message}`, metadata: { durationMs: Date.now() - start }, }; } const inp = parsed.data as RegexTesterInput; if (inp.input.length > config.maxInputLength) { return { success: false, data: null, error: `输入过长: ${inp.input.length} 字符 (限制 ${config.maxInputLength})`, metadata: { durationMs: Date.now() - start }, }; } let regex: RegExp; try { regex = new RegExp(inp.pattern, safeFlags(inp.flags)); } catch (e) { return { success: false, data: null, error: `正则表达式无效: ${e instanceof Error ? e.message : String(e)}`, metadata: { durationMs: Date.now() - start }, }; } try { switch (inp.operation) { case "test": { const matched = regex.test(inp.input); return { success: true, data: { operation: "test", pattern: inp.pattern, flags: safeFlags(inp.flags), matched, }, metadata: { durationMs: Date.now() - start }, }; } case "match": { const matches: Array<{ match: string; index: number; groups: string[]; }> = []; let m: RegExpExecArray | null; const globalRegex = regex.global ? regex : new RegExp(regex.source, regex.flags + "g"); let count = 0; while ((m = globalRegex.exec(inp.input)) !== null) { if (count >= config.maxMatches) break; matches.push({ match: m[0], index: m.index, groups: m.slice(1), }); count++; if (m.index === globalRegex.lastIndex) globalRegex.lastIndex++; } return { success: true, data: { operation: "match", pattern: inp.pattern, flags: safeFlags(inp.flags), totalMatches: matches.length, truncated: count >= config.maxMatches, matches, }, metadata: { durationMs: Date.now() - start }, }; } case "extract": { const globalRegex = regex.global ? regex : new RegExp(regex.source, regex.flags + "g"); const extracts: Array<{ match: string; index: number; groups: Record; }> = []; let m: RegExpExecArray | null; let count = 0; while ((m = globalRegex.exec(inp.input)) !== null) { if (count >= config.maxMatches) break; const groups: Record = {}; if (m.groups) { for (const [key, val] of Object.entries(m.groups)) { groups[key] = val ?? ""; } } else { for (let i = 1; i < m.length; i++) { groups[`${i}`] = m[i] ?? ""; } } extracts.push({ match: m[0], index: m.index, groups, }); count++; if (m.index === globalRegex.lastIndex) globalRegex.lastIndex++; } return { success: true, data: { operation: "extract", pattern: inp.pattern, flags: safeFlags(inp.flags), totalExtracts: extracts.length, truncated: count >= config.maxMatches, extracts, }, metadata: { durationMs: Date.now() - start }, }; } case "replace": { if (inp.replacement === undefined) { throw new Error("replace 操作需要 replacement 参数"); } const result = inp.input.replace(regex, inp.replacement); const replaceCount = regex.global ? (inp.input.match(new RegExp(regex.source, regex.flags.includes("g") ? regex.flags : regex.flags + "g")) ?? []).length : (regex.test(inp.input) ? 1 : 0); return { success: true, data: { operation: "replace", pattern: inp.pattern, flags: safeFlags(inp.flags), original: inp.input, result, replacement: inp.replacement, replaceCount, }, metadata: { durationMs: Date.now() - start }, }; } default: throw new Error(`未知操作: ${inp.operation}`); } } catch (e) { return { success: false, data: null, error: e instanceof Error ? e.message : String(e), metadata: { durationMs: Date.now() - start }, }; } }, };