import { z } from "zod"; import type { JSONSchema7 } from "json-schema"; import type { ToolExecutor, ToolContext, ToolResult } from "../../registry"; import type { CalculatorToolConfig } from "./config"; export const calculatorInputSchema = z.object({ expression: z .string() .min(1) .describe("要计算的数学表达式,例如 (1+2)*3、sqrt(16)、sin(3.14159/2)、log(100,10)"), }); type CalculatorInput = z.infer; const ALLOWED_FUNCTIONS: Record number> = { sqrt: Math.sqrt, cbrt: Math.cbrt, abs: Math.abs, sin: Math.sin, cos: Math.cos, tan: Math.tan, asin: Math.asin, acos: Math.acos, atan: Math.atan, sinh: Math.sinh, cosh: Math.cosh, tanh: Math.tanh, log: (x: number, base?: number) => base ? Math.log(x) / Math.log(base) : Math.log(x), log2: Math.log2, log10: Math.log10, ln: Math.log, exp: Math.exp, pow: Math.pow, floor: Math.floor, ceil: Math.ceil, round: Math.round, sign: Math.sign, max: Math.max, min: Math.min, }; const CONSTANTS: Record = { PI: Math.PI, pi: Math.PI, E: Math.E, e: Math.E, }; function tokenize(expr: string): string[] { const tokens: string[] = []; let i = 0; while (i < expr.length) { const ch = expr[i]!; if (ch === " " || ch === "\t" || ch === "\n") { i++; continue; } if (/[0-9.]/.test(ch)) { let num = ""; while (i < expr.length && /[0-9.eE+\-]/.test(expr[i]!)) { if ((expr[i] === "+" || expr[i] === "-") && !/[eE]/.test(num[num.length - 1] ?? "")) { break; } num += expr[i]!; i++; } tokens.push(num); continue; } if (/[a-zA-Z_]/.test(ch)) { let name = ""; while (i < expr.length && /[a-zA-Z0-9_]/.test(expr[i]!)) { name += expr[i]!; i++; } tokens.push(name); continue; } tokens.push(ch); i++; } return tokens; } class Parser { private pos = 0; constructor(private tokens: string[]) {} parse(): number { const result = this.parseExpression(); if (this.pos < this.tokens.length) { throw new Error(`意外的 token: ${this.tokens[this.pos]}`); } return result; } private parseExpression(): number { let left = this.parseTerm(); while (this.pos < this.tokens.length) { const op = this.tokens[this.pos]; if (op !== "+" && op !== "-") break; this.pos++; const right = this.parseTerm(); left = op === "+" ? left + right : left - right; } return left; } private parseTerm(): number { let left = this.parseFactor(); while (this.pos < this.tokens.length) { const op = this.tokens[this.pos]; if (op !== "*" && op !== "/" && op !== "%") break; this.pos++; const right = this.parseFactor(); if (op === "*") left = left * right; else if (op === "/") { if (right === 0) throw new Error("除以零"); left = left / right; } else { left = left % right; } } return left; } private parseFactor(): number { let base = this.parseUnary(); if (this.pos < this.tokens.length && this.tokens[this.pos] === "^") { this.pos++; const exp = this.parseFactor(); base = Math.pow(base, exp); } return base; } private parseUnary(): number { if (this.pos < this.tokens.length) { const op = this.tokens[this.pos]; if (op === "-") { this.pos++; return -this.parseUnary(); } if (op === "+") { this.pos++; return this.parseUnary(); } } return this.parsePrimary(); } private parsePrimary(): number { const token = this.tokens[this.pos]; if (!token) throw new Error("表达式不完整"); if (token === "(") { this.pos++; const result = this.parseExpression(); if (this.tokens[this.pos] !== ")") throw new Error("缺少右括号 )"); this.pos++; return result; } if (/^[0-9.]/.test(token) || /^[0-9.]+e[+\-]?[0-9]+$/i.test(token)) { this.pos++; const num = Number(token); if (Number.isNaN(num)) throw new Error(`无效数字: ${token}`); return num; } if (token in CONSTANTS) { this.pos++; return CONSTANTS[token]!; } if (token in ALLOWED_FUNCTIONS) { this.pos++; if (this.tokens[this.pos] !== "(") throw new Error(`函数 ${token} 后需要括号`); this.pos++; const args: number[] = []; if (this.tokens[this.pos] !== ")") { args.push(this.parseExpression()); while (this.tokens[this.pos] === ",") { this.pos++; args.push(this.parseExpression()); } } if (this.tokens[this.pos] !== ")") throw new Error(`函数 ${token} 缺少右括号`); this.pos++; const fn = ALLOWED_FUNCTIONS[token]!; return fn(...args); } throw new Error(`未知标识符: ${token}`); } } export const calculatorExecutor: ToolExecutor = { buildInputSchema(_config: CalculatorToolConfig): JSONSchema7 { return { type: "object", properties: { expression: { type: "string", description: "要计算的数学表达式,例如 (1+2)*3、sqrt(16)、sin(3.14159/2)、log(100,10)", }, }, required: ["expression"], }; }, buildDescription(config: CalculatorToolConfig): string { return `数学表达式计算器。支持四则运算、幂(^)、取模(%)、以及数学函数: ${Object.keys(ALLOWED_FUNCTIONS).join(", ")}。常量: PI, E。最大表达式长度 ${config.maxExpressionLength} 字符,精度 ${config.precision} 位小数。`; }, async execute( input: unknown, config: CalculatorToolConfig, _ctx: ToolContext, ): Promise { const start = Date.now(); const parsed = calculatorInputSchema.safeParse(input); if (!parsed.success) { return { success: false, data: null, error: `输入参数校验失败: ${parsed.error.message}`, metadata: { durationMs: Date.now() - start }, }; } const { expression } = parsed.data as CalculatorInput; if (expression.length > config.maxExpressionLength) { return { success: false, data: null, error: `表达式过长: ${expression.length} 字符 (限制 ${config.maxExpressionLength})`, metadata: { durationMs: Date.now() - start }, }; } try { const tokens = tokenize(expression); const parser = new Parser(tokens); const result = parser.parse(); if (!Number.isFinite(result)) { return { success: false, data: null, error: "计算结果为无穷大或 NaN", metadata: { durationMs: Date.now() - start }, }; } const rounded = Number(result.toFixed(config.precision)); return { success: true, data: { expression, result: rounded, }, 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 }, }; } }, };