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.
191 lines
5.8 KiB
191 lines
5.8 KiB
import { z } from "zod";
|
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
import type { JSONSchema7 } from "json-schema";
|
|
import type { ToolExecutor, ToolContext, ToolResult } from "../../registry";
|
|
import type { UuidToolConfig } from "./config";
|
|
|
|
export const uuidInputSchema = z.object({
|
|
operation: z
|
|
.enum(["uuid", "random-string", "random-int"])
|
|
.describe("操作类型: uuid=生成UUID, random-string=随机字符串, random-int=随机整数"),
|
|
version: z
|
|
.enum(["v4", "v7"])
|
|
.optional()
|
|
.describe("UUID 版本 (uuid 操作使用)"),
|
|
length: z
|
|
.number()
|
|
.int()
|
|
.positive()
|
|
.max(256)
|
|
.optional()
|
|
.describe("随机字符串长度 (random-string 操作使用)"),
|
|
encoding: z
|
|
.enum(["hex", "base64", "base64url", "alphanumeric"])
|
|
.optional()
|
|
.describe("随机字符串编码方式 (random-string 操作使用)"),
|
|
min: z
|
|
.number()
|
|
.int()
|
|
.optional()
|
|
.describe("最小值 (random-int 操作使用)"),
|
|
max: z
|
|
.number()
|
|
.int()
|
|
.optional()
|
|
.describe("最大值 (random-int 操作使用)"),
|
|
count: z
|
|
.number()
|
|
.int()
|
|
.positive()
|
|
.max(100)
|
|
.optional()
|
|
.describe("生成数量,默认 1"),
|
|
});
|
|
|
|
type UuidInput = z.infer<typeof uuidInputSchema>;
|
|
|
|
const ALPHANUMERIC_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
|
|
function generateUuidV7(): string {
|
|
const timestamp = Date.now();
|
|
const timestampHex = timestamp.toString(16).padStart(12, "0");
|
|
const random = randomBytes(10);
|
|
random[0] = (random[0]! & 0x0f) | 0x70;
|
|
random[2] = (random[2]! & 0x3f) | 0x80;
|
|
const randomHex = random.toString("hex");
|
|
return `${timestampHex.slice(0, 8)}-${timestampHex.slice(8, 12)}-${randomHex.slice(0, 4)}-${randomHex.slice(4, 8)}-${randomHex.slice(8, 20)}`;
|
|
}
|
|
|
|
function generateRandomString(length: number, encoding: string): string {
|
|
switch (encoding) {
|
|
case "hex":
|
|
return randomBytes(Math.ceil(length / 2)).toString("hex").slice(0, length);
|
|
case "base64":
|
|
return randomBytes(Math.ceil(length * 0.75)).toString("base64").slice(0, length);
|
|
case "base64url":
|
|
return randomBytes(Math.ceil(length * 0.75))
|
|
.toString("base64url")
|
|
.slice(0, length);
|
|
case "alphanumeric": {
|
|
const bytes = randomBytes(length);
|
|
let result = "";
|
|
for (let i = 0; i < length; i++) {
|
|
result += ALPHANUMERIC_CHARS[bytes[i]! % ALPHANUMERIC_CHARS.length];
|
|
}
|
|
return result;
|
|
}
|
|
default:
|
|
throw new Error(`未知编码: ${encoding}`);
|
|
}
|
|
}
|
|
|
|
function generateRandomInt(min: number, max: number): number {
|
|
if (min >= max) throw new Error("min 必须小于 max");
|
|
const range = max - min;
|
|
const bytes = randomBytes(8);
|
|
const randomValue = bytes.readBigUInt64BE();
|
|
return min + Number(randomValue % BigInt(range));
|
|
}
|
|
|
|
export const uuidExecutor: ToolExecutor<UuidToolConfig> = {
|
|
buildInputSchema(_config: UuidToolConfig): JSONSchema7 {
|
|
return {
|
|
type: "object",
|
|
properties: {
|
|
operation: {
|
|
type: "string",
|
|
enum: ["uuid", "random-string", "random-int"],
|
|
description: "操作类型",
|
|
},
|
|
version: { type: "string", enum: ["v4", "v7"], description: "UUID 版本" },
|
|
length: { type: "number", description: "随机字符串长度" },
|
|
encoding: {
|
|
type: "string",
|
|
enum: ["hex", "base64", "base64url", "alphanumeric"],
|
|
description: "编码方式",
|
|
},
|
|
min: { type: "number", description: "最小值" },
|
|
max: { type: "number", description: "最大值" },
|
|
count: { type: "number", description: "生成数量" },
|
|
},
|
|
required: ["operation"],
|
|
};
|
|
},
|
|
|
|
buildDescription(config: UuidToolConfig): string {
|
|
return `生成随机标识符。支持 UUID v4/v7、随机字符串(hex/base64/base64url/alphanumeric)、随机整数。默认 UUID 版本: ${config.defaultVersion},默认字符串长度: ${config.defaultLength}。`;
|
|
},
|
|
|
|
async execute(
|
|
input: unknown,
|
|
config: UuidToolConfig,
|
|
_ctx: ToolContext,
|
|
): Promise<ToolResult> {
|
|
const start = Date.now();
|
|
|
|
const parsed = uuidInputSchema.safeParse(input);
|
|
if (!parsed.success) {
|
|
return {
|
|
success: false,
|
|
data: null,
|
|
error: `输入参数校验失败: ${parsed.error.message}`,
|
|
metadata: { durationMs: Date.now() - start },
|
|
};
|
|
}
|
|
|
|
const inp = parsed.data as UuidInput;
|
|
const count = inp.count ?? 1;
|
|
|
|
try {
|
|
const results: unknown[] = [];
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
switch (inp.operation) {
|
|
case "uuid": {
|
|
const version = inp.version ?? config.defaultVersion;
|
|
if (version === "v4") {
|
|
results.push(randomUUID());
|
|
} else {
|
|
results.push(generateUuidV7());
|
|
}
|
|
break;
|
|
}
|
|
|
|
case "random-string": {
|
|
const length = inp.length ?? config.defaultLength;
|
|
const encoding = inp.encoding ?? "hex";
|
|
results.push(generateRandomString(length, encoding));
|
|
break;
|
|
}
|
|
|
|
case "random-int": {
|
|
const min = inp.min ?? 0;
|
|
const max = inp.max ?? 100;
|
|
results.push(generateRandomInt(min, max));
|
|
break;
|
|
}
|
|
|
|
default:
|
|
throw new Error(`未知操作: ${inp.operation}`);
|
|
}
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
operation: inp.operation,
|
|
count,
|
|
results: count === 1 ? results[0] : results,
|
|
},
|
|
metadata: { durationMs: Date.now() - start },
|
|
};
|
|
} catch (e) {
|
|
return {
|
|
success: false,
|
|
data: null,
|
|
error: e instanceof Error ? e.message : String(e),
|
|
metadata: { durationMs: Date.now() - start },
|
|
};
|
|
}
|
|
},
|
|
};
|
|
|