You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

203 lines
6.1 KiB

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<typeof jsonFormatterInputSchema>;
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<string, unknown>)[part];
}
}
return current;
}
export const jsonFormatterExecutor: ToolExecutor<JsonFormatterToolConfig> = {
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<ToolResult> {
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 },
};
}
},
};