diff --git a/examples/unit_conversion.ts b/examples/unit_conversion.ts new file mode 100644 index 0000000..7c3b2b5 --- /dev/null +++ b/examples/unit_conversion.ts @@ -0,0 +1,125 @@ +#!/usr/bin/env npx tsx + +import { unitConversionTool, getSupportedUnits, performConversion } from '../src/tools/unitConversionTool'; + +async function demonstrateUnitConversion() { + console.log('=== Unit Conversion Tool Demo ===\n'); + + // Display supported units + const supportedUnits = getSupportedUnits(); + console.log('📊 Supported Units:'); + console.log(' Static conversions:', supportedUnits.static.slice(0, 10).join(', '), '...'); + console.log(' Currencies:', supportedUnits.currencies.slice(0, 10).join(', '), '...\n'); + + // Demo conversions + const demos = [ + // Weight conversions + { value: 10, fromUnit: 'kg', toUnit: 'lbs', description: 'Weight: Kilograms to Pounds' }, + { value: 150, fromUnit: 'lb', toUnit: 'kg', description: 'Weight: Pounds to Kilograms' }, + { value: 5000, fromUnit: 'g', toUnit: 'lb', description: 'Weight: Grams to Pounds' }, + + // Temperature conversions + { value: 25, fromUnit: 'C', toUnit: 'F', description: 'Temperature: Celsius to Fahrenheit' }, + { value: 98.6, fromUnit: 'F', toUnit: 'C', description: 'Temperature: Fahrenheit to Celsius' }, + { value: 0, fromUnit: 'C', toUnit: 'K', description: 'Temperature: Celsius to Kelvin' }, + + // Length conversions + { value: 5, fromUnit: 'km', toUnit: 'mi', description: 'Length: Kilometers to Miles' }, + { value: 6, fromUnit: 'ft', toUnit: 'm', description: 'Length: Feet to Meters' }, + { value: 100, fromUnit: 'yd', toUnit: 'm', description: 'Length: Yards to Meters' }, + + // Volume conversions + { value: 3.5, fromUnit: 'L', toUnit: 'gal', description: 'Volume: Liters to Gallons' }, + { value: 500, fromUnit: 'ml', toUnit: 'cup', description: 'Volume: Milliliters to Cups' }, + + // Area conversions + { value: 1000, fromUnit: 'm²', toUnit: 'ft²', description: 'Area: Square Meters to Square Feet' }, + { value: 2, fromUnit: 'acre', toUnit: 'hectare', description: 'Area: Acres to Hectares' }, + + // Currency conversions (will use mock rates if no API key) + { value: 1000, fromUnit: 'INR', toUnit: 'USD', description: 'Currency: Indian Rupees to US Dollars' }, + { value: 100, fromUnit: 'USD', toUnit: 'EUR', description: 'Currency: US Dollars to Euros' }, + { value: 500, fromUnit: 'GBP', toUnit: 'JPY', description: 'Currency: British Pounds to Japanese Yen' }, + ]; + + console.log('🔄 Running Conversions:\n'); + + for (const demo of demos) { + try { + console.log(`📏 ${demo.description}`); + console.log(` Input: ${demo.value} ${demo.fromUnit}`); + + const result = await unitConversionTool.execute({ + value: demo.value, + fromUnit: demo.fromUnit, + toUnit: demo.toUnit + }); + + console.log(` Output: ${result.convertedValue.toFixed(2)} ${result.toUnit}`); + if (result.formula) { + console.log(` Method: ${result.formula}`); + } + console.log(); + } catch (error) { + console.error(` Error: ${error instanceof Error ? error.message : 'Unknown error'}\n`); + } + } + + // Demo error handling + console.log('❌ Error Handling Demo:\n'); + + const errorCases = [ + { value: 100, fromUnit: 'kg', toUnit: 'C', description: 'Invalid: Different categories' }, + { value: 50, fromUnit: 'xyz', toUnit: 'kg', description: 'Invalid: Unknown unit' }, + ]; + + for (const errorCase of errorCases) { + try { + console.log(`🚫 ${errorCase.description}`); + console.log(` Attempting: ${errorCase.value} ${errorCase.fromUnit} → ${errorCase.toUnit}`); + + await unitConversionTool.execute({ + value: errorCase.value, + fromUnit: errorCase.fromUnit, + toUnit: errorCase.toUnit + }); + + console.log(' Unexpected: No error thrown'); + console.log(); + } catch (error) { + console.log(` Expected error: ${error instanceof Error ? error.message : 'Unknown error'}`); + console.log(); + } + } + + // Environment variable info + console.log('💡 Tips:'); + console.log(' - Set CURRENCY_API_KEY environment variable for live exchange rates'); + console.log(' - Supported providers: exchangerate-api.com, currencyapi.com, etc.'); + console.log(' - Without API key, mock rates are used for demonstration\n'); + + // Advanced usage example + console.log('🚀 Advanced Usage Example:\n'); + console.log('```typescript'); + console.log('// Import the tool'); + console.log("import { unitConversionTool } from '../src/tools/unitConversionTool';"); + console.log(''); + console.log('// Use the tool directly'); + console.log('const result = await unitConversionTool.execute({'); + console.log(' value: 100,'); + console.log(" fromUnit: 'USD',"); + console.log(" toUnit: 'EUR'"); + console.log('});'); + console.log(''); + console.log('// Or use the performConversion function'); + console.log("import { performConversion } from '../src/tools/unitConversionTool';"); + console.log('const result = await performConversion({'); + console.log(' value: 100,'); + console.log(" fromUnit: 'kg',"); + console.log(" toUnit: 'lbs'"); + console.log('});'); + console.log('```\n'); +} + +// Run the demonstration +demonstrateUnitConversion().catch(console.error); \ No newline at end of file diff --git a/src/tools/unitConversionTool.ts b/src/tools/unitConversionTool.ts new file mode 100644 index 0000000..b53f484 --- /dev/null +++ b/src/tools/unitConversionTool.ts @@ -0,0 +1,242 @@ +export interface ConversionRequest { + value: number; + fromUnit: string; + toUnit: string; +} + +export interface ConversionResult { + originalValue: number; + convertedValue: number; + fromUnit: string; + toUnit: string; + formula?: string; +} + +interface ConversionUnit { + category: 'weight' | 'temperature' | 'length' | 'volume' | 'area' | 'currency'; + unit: string; + toBase: (value: number) => number; + fromBase: (value: number) => number; +} + +interface CurrencyRates { + [currency: string]: number; +} + +const CONVERSION_UNITS: ConversionUnit[] = [ + // Weight conversions (base: kg) + { category: 'weight', unit: 'kg', toBase: (v) => v, fromBase: (v) => v }, + { category: 'weight', unit: 'g', toBase: (v) => v / 1000, fromBase: (v) => v * 1000 }, + { category: 'weight', unit: 'mg', toBase: (v) => v / 1000000, fromBase: (v) => v * 1000000 }, + { category: 'weight', unit: 'lb', toBase: (v) => v * 0.453592, fromBase: (v) => v / 0.453592 }, + { category: 'weight', unit: 'lbs', toBase: (v) => v * 0.453592, fromBase: (v) => v / 0.453592 }, + { category: 'weight', unit: 'oz', toBase: (v) => v * 0.0283495, fromBase: (v) => v / 0.0283495 }, + { category: 'weight', unit: 'ton', toBase: (v) => v * 1000, fromBase: (v) => v / 1000 }, + { category: 'weight', unit: 'tonne', toBase: (v) => v * 1000, fromBase: (v) => v / 1000 }, + + // Temperature conversions (base: C) + { category: 'temperature', unit: 'C', toBase: (v) => v, fromBase: (v) => v }, + { category: 'temperature', unit: 'F', toBase: (v) => (v - 32) * 5/9, fromBase: (v) => v * 9/5 + 32 }, + { category: 'temperature', unit: 'K', toBase: (v) => v - 273.15, fromBase: (v) => v + 273.15 }, + + // Length conversions (base: m) + { category: 'length', unit: 'm', toBase: (v) => v, fromBase: (v) => v }, + { category: 'length', unit: 'km', toBase: (v) => v * 1000, fromBase: (v) => v / 1000 }, + { category: 'length', unit: 'cm', toBase: (v) => v / 100, fromBase: (v) => v * 100 }, + { category: 'length', unit: 'mm', toBase: (v) => v / 1000, fromBase: (v) => v * 1000 }, + { category: 'length', unit: 'mi', toBase: (v) => v * 1609.34, fromBase: (v) => v / 1609.34 }, + { category: 'length', unit: 'mile', toBase: (v) => v * 1609.34, fromBase: (v) => v / 1609.34 }, + { category: 'length', unit: 'yd', toBase: (v) => v * 0.9144, fromBase: (v) => v / 0.9144 }, + { category: 'length', unit: 'ft', toBase: (v) => v * 0.3048, fromBase: (v) => v / 0.3048 }, + { category: 'length', unit: 'in', toBase: (v) => v * 0.0254, fromBase: (v) => v / 0.0254 }, + + // Volume conversions (base: L) + { category: 'volume', unit: 'L', toBase: (v) => v, fromBase: (v) => v }, + { category: 'volume', unit: 'l', toBase: (v) => v, fromBase: (v) => v }, + { category: 'volume', unit: 'mL', toBase: (v) => v / 1000, fromBase: (v) => v * 1000 }, + { category: 'volume', unit: 'ml', toBase: (v) => v / 1000, fromBase: (v) => v * 1000 }, + { category: 'volume', unit: 'gal', toBase: (v) => v * 3.78541, fromBase: (v) => v / 3.78541 }, + { category: 'volume', unit: 'qt', toBase: (v) => v * 0.946353, fromBase: (v) => v / 0.946353 }, + { category: 'volume', unit: 'pt', toBase: (v) => v * 0.473176, fromBase: (v) => v / 0.473176 }, + { category: 'volume', unit: 'cup', toBase: (v) => v * 0.236588, fromBase: (v) => v / 0.236588 }, + { category: 'volume', unit: 'fl oz', toBase: (v) => v * 0.0295735, fromBase: (v) => v / 0.0295735 }, + + // Area conversions (base: m²) + { category: 'area', unit: 'm2', toBase: (v) => v, fromBase: (v) => v }, + { category: 'area', unit: 'm²', toBase: (v) => v, fromBase: (v) => v }, + { category: 'area', unit: 'km2', toBase: (v) => v * 1000000, fromBase: (v) => v / 1000000 }, + { category: 'area', unit: 'km²', toBase: (v) => v * 1000000, fromBase: (v) => v / 1000000 }, + { category: 'area', unit: 'cm2', toBase: (v) => v / 10000, fromBase: (v) => v * 10000 }, + { category: 'area', unit: 'cm²', toBase: (v) => v / 10000, fromBase: (v) => v * 10000 }, + { category: 'area', unit: 'ft2', toBase: (v) => v * 0.092903, fromBase: (v) => v / 0.092903 }, + { category: 'area', unit: 'ft²', toBase: (v) => v * 0.092903, fromBase: (v) => v / 0.092903 }, + { category: 'area', unit: 'in2', toBase: (v) => v * 0.00064516, fromBase: (v) => v / 0.00064516 }, + { category: 'area', unit: 'in²', toBase: (v) => v * 0.00064516, fromBase: (v) => v / 0.00064516 }, + { category: 'area', unit: 'acre', toBase: (v) => v * 4046.86, fromBase: (v) => v / 4046.86 }, + { category: 'area', unit: 'hectare', toBase: (v) => v * 10000, fromBase: (v) => v / 10000 }, +]; + +const CURRENCY_CODES = [ + 'USD', 'EUR', 'GBP', 'JPY', 'AUD', 'CAD', 'CHF', 'CNY', 'INR', 'MXN', + 'BRL', 'ZAR', 'SGD', 'HKD', 'NZD', 'SEK', 'NOK', 'DKK', 'PLN', 'RUB', + 'TRY', 'KRW', 'MYR', 'IDR', 'THB', 'PHP', 'VND', 'CZK', 'HUF', 'ILS' +]; + +async function fetchCurrencyRates(baseCurrency: string = 'USD'): Promise { + const apiKey = process.env.CURRENCY_API_KEY; + + if (!apiKey) { + console.warn('CURRENCY_API_KEY not set, using mock rates for demonstration'); + return getMockCurrencyRates(); + } + + try { + const apiUrl = process.env.CURRENCY_API_URL || 'https://api.exchangerate-api.com/v4/latest'; + const response = await fetch(`${apiUrl}/${baseCurrency}?apikey=${apiKey}`); + + if (!response.ok) { + throw new Error(`Currency API error: ${response.status}`); + } + + const data = await response.json(); + return data.rates || getMockCurrencyRates(); + } catch (error) { + console.error('Failed to fetch currency rates:', error); + return getMockCurrencyRates(); + } +} + +function getMockCurrencyRates(): CurrencyRates { + return { + USD: 1.0, + EUR: 0.85, + GBP: 0.73, + JPY: 110.5, + AUD: 1.35, + CAD: 1.25, + CHF: 0.92, + CNY: 6.45, + INR: 83.50, + MXN: 18.20, + BRL: 5.25, + ZAR: 15.80, + SGD: 1.35, + HKD: 7.85, + NZD: 1.45, + }; +} + +function findConversionUnit(unit: string): ConversionUnit | null { + return CONVERSION_UNITS.find(u => + u.unit.toLowerCase() === unit.toLowerCase() + ) || null; +} + +function isCurrency(unit: string): boolean { + return CURRENCY_CODES.includes(unit.toUpperCase()); +} + +async function convertCurrency(value: number, from: string, to: string): Promise { + const fromCurrency = from.toUpperCase(); + const toCurrency = to.toUpperCase(); + + const rates = await fetchCurrencyRates('USD'); + + if (!rates[fromCurrency] || !rates[toCurrency]) { + throw new Error(`Currency not supported: ${!rates[fromCurrency] ? fromCurrency : toCurrency}`); + } + + // Convert through USD as base + const usdValue = value / rates[fromCurrency]; + return usdValue * rates[toCurrency]; +} + +function convertStatic(value: number, fromUnit: ConversionUnit, toUnit: ConversionUnit): number { + if (fromUnit.category !== toUnit.category) { + throw new Error(`Cannot convert between different categories: ${fromUnit.category} to ${toUnit.category}`); + } + + const baseValue = fromUnit.toBase(value); + return toUnit.fromBase(baseValue); +} + +export async function performConversion(request: ConversionRequest): Promise { + const { value, fromUnit, toUnit } = request; + + // Check if it's currency conversion + if (isCurrency(fromUnit) && isCurrency(toUnit)) { + const convertedValue = await convertCurrency(value, fromUnit, toUnit); + return { + originalValue: value, + convertedValue, + fromUnit: fromUnit.toUpperCase(), + toUnit: toUnit.toUpperCase(), + formula: `Using live exchange rates` + }; + } + + // Check if it's static unit conversion + const from = findConversionUnit(fromUnit); + const to = findConversionUnit(toUnit); + + if (!from) { + throw new Error(`Unknown unit: ${fromUnit}`); + } + + if (!to) { + throw new Error(`Unknown unit: ${toUnit}`); + } + + const convertedValue = convertStatic(value, from, to); + + return { + originalValue: value, + convertedValue, + fromUnit, + toUnit, + formula: `${fromUnit} → ${from.category} base → ${toUnit}` + }; +} + +export function getSupportedUnits(): { static: string[], currencies: string[] } { + return { + static: CONVERSION_UNITS.map(u => u.unit), + currencies: CURRENCY_CODES + }; +} + +export const unitConversionTool = { + name: 'unitConversion', + description: 'Convert between various units of measurement including weight, temperature, length, volume, area, and currencies', + + parameters: { + type: 'object', + properties: { + value: { + type: 'number', + description: 'The numeric value to convert' + }, + fromUnit: { + type: 'string', + description: 'The unit to convert from (e.g., kg, lb, C, F, USD, EUR)' + }, + toUnit: { + type: 'string', + description: 'The unit to convert to' + } + }, + required: ['value', 'fromUnit', 'toUnit'] + }, + + execute: async (params: ConversionRequest): Promise => { + if (typeof params.value !== 'number' || isNaN(params.value)) { + throw new Error('Value must be a valid number'); + } + + if (!params.fromUnit || !params.toUnit) { + throw new Error('Both fromUnit and toUnit are required'); + } + + return await performConversion(params); + } +}; \ No newline at end of file