import { z } from "zod"; import type { JSONSchema7 } from "json-schema"; import type { ToolExecutor, ToolContext, ToolResult } from "../../registry"; import type { JsonFormatterToolConfig } from "./config"; export const jsonFormatterInputSchema = z.object({ operation: z .enum(["format", "minify", "validate", "extract"]) .describe("操作类型: format=格式化, minify=压缩, validate=校验, extract=提取字段"), input: z.string().describe("JSON 字符串"), indent: z .number() .int() .positive() .max(8) .optional() .describe("缩进空格数 (format 操作使用)"), path: z .string() .optional() .describe("字段路径 (extract 操作使用),如 a.b.c 或 a[0].b"), }); type JsonFormatterInput = z.infer; function extractByPath(obj: unknown, path: string): unknown { const parts = path .replace(/\[(\d+)\]/g, ".$1") .split(".") .filter(Boolean); let current: unknown = obj; for (const part of parts) { if (current === null || current === undefined) { throw new Error(`路径 ${path} 在 ${part} 处遇到 null/undefined`); } if (typeof part === "string" && /^\d+$/.test(part)) { const arr = current as unknown[]; const idx = Number(part); if (!Array.isArray(arr)) { throw new Error(`路径 ${path} 在 ${part} 处期望数组`); } if (idx < 0 || idx >= arr.length) { throw new Error(`路径 ${path} 索引 ${idx} 越界`); } current = arr[idx]; } else { if (typeof current !== "object") { throw new Error(`路径 ${path} 在 ${part} 处期望对象`); } current = (current as Record)[part]; } } return current; } export const jsonFormatterExecutor: ToolExecutor = { buildInputSchema(_config: JsonFormatterToolConfig): JSONSchema7 { return { type: "object", properties: { operation: { type: "string", enum: ["format", "minify", "validate", "extract"], description: "操作类型", }, input: { type: "string", description: "JSON 字符串" }, indent: { type: "number", description: "缩进空格数 (format)" }, path: { type: "string", description: "字段路径 (extract)" }, }, required: ["operation", "input"], }; }, buildDescription(config: JsonFormatterToolConfig): string { return `JSON 工具。支持格式化、压缩、校验、字段提取(支持 a.b.c 和 a[0].b 路径)。默认缩进 ${config.indent} 空格,最大输入 ${config.maxInputLength} 字符。`; }, async execute( input: unknown, config: JsonFormatterToolConfig, _ctx: ToolContext, ): Promise { const start = Date.now(); const parsed = jsonFormatterInputSchema.safeParse(input); if (!parsed.success) { return { success: false, data: null, error: `输入参数校验失败: ${parsed.error.message}`, metadata: { durationMs: Date.now() - start }, }; } const inp = parsed.data as JsonFormatterInput; if (inp.input.length > config.maxInputLength) { return { success: false, data: null, error: `输入过长: ${inp.input.length} 字符 (限制 ${config.maxInputLength})`, metadata: { durationMs: Date.now() - start }, }; } try { let parsedJson: unknown; try { parsedJson = JSON.parse(inp.input); } catch (e) { if (inp.operation === "validate") { return { success: true, data: { valid: false, error: e instanceof Error ? e.message : String(e), }, metadata: { durationMs: Date.now() - start }, }; } throw new Error(`JSON 解析失败: ${e instanceof Error ? e.message : String(e)}`); } switch (inp.operation) { case "format": { const indent = inp.indent ?? config.indent; const formatted = JSON.stringify(parsedJson, null, indent); return { success: true, data: { operation: "format", output: formatted, inputLength: inp.input.length, outputLength: formatted.length, }, metadata: { durationMs: Date.now() - start }, }; } case "minify": { const minified = JSON.stringify(parsedJson); return { success: true, data: { operation: "minify", output: minified, inputLength: inp.input.length, outputLength: minified.length, saved: inp.input.length - minified.length, }, metadata: { durationMs: Date.now() - start }, }; } case "validate": { return { success: true, data: { valid: true, type: Array.isArray(parsedJson) ? "array" : typeof parsedJson, size: typeof parsedJson === "object" && parsedJson !== null ? Object.keys(parsedJson).length : undefined, }, metadata: { durationMs: Date.now() - start }, }; } case "extract": { if (!inp.path) throw new Error("extract 操作需要 path 参数"); const value = extractByPath(parsedJson, inp.path); return { success: true, data: { operation: "extract", path: inp.path, value, type: value === null ? "null" : Array.isArray(value) ? "array" : typeof value, }, 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 }, }; } }, };