Rebasely Beta: data may be purged

examples/MCP-tools Public

TypeScript · 111 lines · 5.4 KiB · at 2090b14

import { z } from "zod";import { ToolError } from "./errors.ts";import { defineTool } from "./types.ts";const QUANTITIES = ["length", "mass", "volume", "temperature"] as const;type Quantity = (typeof QUANTITIES)[number];// Each unit converts to and from its quantity's base unit: metres, kilograms, litres, kelvin.type Unit = {  symbol: string;  // Other ways people and models write the unit, matched case-insensitively.  names: string[];  quantity: Quantity;  toBase: (v: number) => number;  fromBase: (v: number) => number;};function linear(quantity: Quantity, symbol: string, factor: number, names: string[]): Unit {  return { symbol, names, quantity, toBase: (v) => v * factor, fromBase: (v) => v / factor };}// Imperial volumes are US customary; the description says so.const UNITS: Unit[] = [  linear("length", "mm", 0.001, ["millimeter", "millimeters", "millimetre", "millimetres"]),  linear("length", "cm", 0.01, ["centimeter", "centimeters", "centimetre", "centimetres"]),  linear("length", "m", 1, ["meter", "meters", "metre", "metres"]),  linear("length", "km", 1000, ["kilometer", "kilometers", "kilometre", "kilometres"]),  linear("length", "in", 0.0254, ["inch", "inches", "\""]),  linear("length", "ft", 0.3048, ["foot", "feet", "'"]),  linear("length", "yd", 0.9144, ["yard", "yards"]),  linear("length", "mi", 1609.344, ["mile", "miles"]),  linear("length", "nmi", 1852, ["nautical mile", "nautical miles"]),  linear("mass", "mg", 1e-6, ["milligram", "milligrams"]),  linear("mass", "g", 0.001, ["gram", "grams"]),  linear("mass", "kg", 1, ["kilogram", "kilograms", "kilo", "kilos"]),  linear("mass", "t", 1000, ["tonne", "tonnes", "metric ton", "metric tons"]),  linear("mass", "oz", 0.028349523125, ["ounce", "ounces"]),  linear("mass", "lb", 0.45359237, ["lbs", "pound", "pounds"]),  linear("mass", "st", 6.35029318, ["stone", "stones"]),  linear("volume", "ml", 0.001, ["milliliter", "milliliters", "millilitre", "millilitres"]),  linear("volume", "l", 1, ["liter", "liters", "litre", "litres"]),  linear("volume", "m3", 1000, ["m³", "cubic meter", "cubic meters", "cubic metre", "cubic metres"]),  linear("volume", "tsp", 0.00492892159375, ["teaspoon", "teaspoons"]),  linear("volume", "tbsp", 0.01478676478125, ["tablespoon", "tablespoons"]),  linear("volume", "fl-oz", 0.0295735295625, ["fl oz", "floz", "fluid ounce", "fluid ounces"]),  linear("volume", "cup", 0.2365882365, ["cups"]),  linear("volume", "pt", 0.473176473, ["pint", "pints"]),  linear("volume", "qt", 0.946352946, ["quart", "quarts"]),  linear("volume", "gal", 3.785411784, ["gallon", "gallons"]),  { symbol: "C", names: ["°C", "celsius", "centigrade"], quantity: "temperature", toBase: (v) => v + 273.15, fromBase: (v) => v - 273.15 },  { symbol: "F", names: ["°F", "fahrenheit"], quantity: "temperature", toBase: (v) => ((v - 32) * 5) / 9 + 273.15, fromBase: (v) => ((v - 273.15) * 9) / 5 + 32 },  { symbol: "K", names: ["kelvin"], quantity: "temperature", toBase: (v) => v, fromBase: (v) => v },];const key = (text: string) => text.trim().toLowerCase().replace(/\s+/g, " ");const BY_KEY = new Map(UNITS.flatMap((u) => [u.symbol, ...u.names].map((n) => [key(n), u] as const)));export function findUnit(text: string): Unit | undefined {  return BY_KEY.get(key(text));}function unitOrThrow(text: string): Unit {  const unit = findUnit(text);  if (!unit) throw new ToolError(`Unknown unit "${text}". Known units, by quantity: ${knownUnits()}.`);  return unit;}function knownUnits(): string {  return QUANTITIES.map((q) => `${q}: ${UNITS.filter((u) => u.quantity === q).map((u) => u.symbol).join(", ")}`).join("; ");}// Twelve significant digits hides float noise (0.30000000000000004) without losing anything a// person would read.function tidy(v: number): number {  return Number(v.toPrecision(12));}export const convertUnits = defineTool({  name: "convert_units",  title: "Convert units",  description:    "Convert a value between units of length, mass, volume or temperature. " +    "Use it instead of doing the arithmetic yourself whenever the user needs an exact figure. " +    "Give units as symbols or names (km or kilometers, F or fahrenheit). Symbols: length mm, cm, m, km, in, ft, yd, mi, nmi; mass mg, g, kg, t, oz, lb, st; " +    "volume ml, l, m3, tsp, tbsp, fl-oz, cup, pt, qt, gal (US customary); temperature C, F, K. " +    "Both units must measure the same quantity.",  input: z.object({    value: z.number().describe("The number to convert, in the `from` unit."),    from: z.string().min(1).describe("The unit the value is in, for example km, pounds or °F."),    to: z.string().min(1).describe("The unit to convert to, for example mi, kg or celsius."),  }),  output: z.object({    quantity: z.enum(QUANTITIES),    value: z.number(),    from: z.string(),    result: z.number(),    to: z.string(),  }),  run({ value, from, to }) {    const a = unitOrThrow(from);    const b = unitOrThrow(to);    if (a.quantity !== b.quantity) {      throw new ToolError(`Cannot convert ${a.symbol} (${a.quantity}) to ${b.symbol} (${b.quantity}). Pick two units of the same quantity.`);    }    const base = a.toBase(value);    if (a.quantity === "temperature" && base < 0) {      throw new ToolError(`${value} ${a.symbol} is below absolute zero (${tidy(a.fromBase(0))} ${a.symbol}).`);    }    return { quantity: a.quantity, value, from: a.symbol, result: tidy(b.fromBase(base)), to: b.symbol };  },});