c9ai
Version:
Universal AI assistant with vibe-based workflows, hybrid cloud+local AI, and comprehensive tool integration
559 lines (458 loc) • 17.8 kB
JavaScript
;
/**
* Enhanced Math Processor - Transparent computation with dynamic programming
* Shows internal workings and can write/execute custom programs
*/
const { getProvider } = require("../providers");
const jitTool = require("../tools/jit-executor");
const fs = require("node:fs");
const path = require("node:path");
const { promisify } = require("node:util");
const { exec } = require("node:child_process");
const execAsync = promisify(exec);
class EnhancedMathProcessor {
constructor(options = {}) {
this.provider = options.provider || "llamacpp";
this.fallbackProvider = options.fallbackProvider || "openai";
this.confidenceThreshold = options.confidenceThreshold || 0.7;
this.onProgress = options.onProgress || (() => {});
this.onUserInput = options.onUserInput || (() => Promise.resolve(true));
this.aborted = false;
// Available functions in JIT executor
this.availableFunctions = [
'sqrt', 'pow', 'sin', 'cos', 'tan', 'log', 'log10', 'log2', 'exp',
'abs', 'round', 'floor', 'ceil', 'max', 'min', 'random',
'asin', 'acos', 'atan', 'atan2', 'sinh', 'cosh', 'tanh',
'sum', 'mean', 'median', 'std',
'pv', 'fv', 'npv', 'irr', 'cagr', 'compound'
];
// Math detection patterns
this.mathIndicators = [
/what\s+is\s+\d+/i,
/calculate|compute|solve|find/i,
/\d+\s*[\+\-\*\/\%]\s*\d+/,
/how\s+much|how\s+many/i,
/percentage|percent|\%/i,
/interest|compound|growth|return/i,
/npv|irr|roi|cagr/i,
/present\s+value|future\s+value/i,
/profit|revenue|cost|margin/i,
/average|mean|sum|total/i,
/square\s+root|sqrt|power|exponent/i,
/prime\s+numbers?/i,
/fibonacci/i,
/factorial/i,
/(\d+).*(\d+)/
];
}
/**
* Check for abort signal
*/
checkAbort() {
if (this.aborted) {
throw new Error("Operation aborted by user");
}
}
/**
* Abort the current operation
*/
abort() {
this.aborted = true;
this.onProgress("status", "❌ Operation aborted by user");
}
/**
* Reset abort state for new operations
*/
reset() {
this.aborted = false;
}
/**
* Check if text contains math intent
*/
hasMathIntent(text) {
const normalizedText = text.toLowerCase().trim();
return this.mathIndicators.some(pattern => pattern.test(normalizedText));
}
/**
* Extract functions used in an expression
*/
extractFunctions(expression) {
const functionPattern = /([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g;
const functions = [];
let match;
while ((match = functionPattern.exec(expression)) !== null) {
functions.push(match[1]);
}
return [...new Set(functions)]; // Remove duplicates
}
/**
* Check if all functions in expression are available
*/
validateFunctions(expression) {
const usedFunctions = this.extractFunctions(expression);
const unavailableFunctions = usedFunctions.filter(fn =>
!this.availableFunctions.includes(fn)
);
return {
isValid: unavailableFunctions.length === 0,
unavailableFunctions,
usedFunctions
};
}
/**
* Convert natural language to @calc expression using AI
*/
async convertToCalc(text) {
this.checkAbort();
if (!this.hasMathIntent(text)) {
this.onProgress("status", "🤔 No mathematical intent detected");
return null;
}
this.onProgress("status", "🧠 Converting natural language to mathematical expression...");
const systemPrompt = `Convert this math question to a @calc command:
Examples:
"What is 25% of 200?" → @calc 200 * 0.25
"Calculate 45 times 8" → @calc 45 * 8
"Find compound growth of $10000 at 7% for 10 years" → @calc compound(10000, 0.07, 10)
"Generate prime numbers up to 100" → @calc prime(100)
Question: "${text}"
@calc`;
try {
const provider = getProvider(this.provider);
this.onProgress("ai", `🤖 Using ${this.provider} for conversion...`);
const response = await provider.call({
messages: [{ role: "user", content: systemPrompt }],
temperature: 0.1,
max_tokens: 100
});
this.checkAbort();
let result = response.text?.trim();
this.onProgress("debug", `🧠 Raw AI response: "${result}"`);
// Clean up the response
if (result) {
const calcMatch = result.match(/@calc\s+(.+)/);
if (calcMatch) {
result = `@calc ${calcMatch[1].trim()}`;
} else if (!result.startsWith("@calc")) {
result = `@calc ${result}`;
}
// Handle date calculations
const currentYear = new Date().getFullYear();
result = result.replace(/current_year/g, currentYear.toString());
result = result.replace(/(\d{4})\s*to\s*(\d{4})/g, (match, start, end) => {
return (parseInt(end) - parseInt(start)).toString();
});
result = result.replace(/\n.*$/s, '').replace(/["""]/g, '').trim();
}
this.onProgress("debug", `📐 Cleaned result: "${result}"`);
if (!result || result === "@calc" || result.includes("NO_MATH")) {
return null;
}
return {
success: true,
originalText: text,
calcExpression: result,
expression: result.replace('@calc ', '')
};
} catch (error) {
this.onProgress("error", `⚠️ AI conversion failed: ${error.message}`);
return null;
}
}
/**
* Generate code for missing functions
*/
async generateMissingFunction(functionName, context) {
this.checkAbort();
this.onProgress("status", `🛠️ Function '${functionName}' not found. Generating custom implementation...`);
const codePrompt = `Write a JavaScript function called '${functionName}' for use in mathematical calculations.
Context: ${context}
Requirements:
1. Write ONLY the function implementation
2. Make it efficient and mathematically correct
3. Handle edge cases appropriately
4. Return the result directly
Examples:
- prime(n) should return array of prime numbers up to n
- fibonacci(n) should return the nth fibonacci number or sequence
- factorial(n) should return n!
Function name: ${functionName}
Generate the JavaScript function:`;
try {
const provider = getProvider(this.fallbackProvider);
this.onProgress("ai", `🤖 Using ${this.fallbackProvider} to generate ${functionName} function...`);
const response = await provider.call({
messages: [{ role: "user", content: codePrompt }],
temperature: 0.2,
max_tokens: 500
});
this.checkAbort();
let code = response.text?.trim();
this.onProgress("debug", `💻 Generated code:\n${code}`);
return code;
} catch (error) {
this.onProgress("error", `❌ Code generation failed: ${error.message}`);
throw error;
}
}
/**
* Create and execute dynamic JIT program
*/
async executeDynamicProgram(expression, missingFunctions, functionCode) {
this.checkAbort();
this.onProgress("status", "🔨 Creating dynamic calculation program...");
// Generate dynamic JIT program
const programCode = `
// Dynamic JIT Calculator - Generated by C9AI Enhanced Math Processor
const vm = require('vm');
try {
// Create execution context with all standard functions
const context = {
// Core Math functions
Math: Math,
sqrt: Math.sqrt,
pow: Math.pow,
sin: Math.sin,
cos: Math.cos,
tan: Math.tan,
log: Math.log,
log10: Math.log10,
log2: Math.log2,
exp: Math.exp,
PI: Math.PI,
E: Math.E,
abs: Math.abs,
round: Math.round,
floor: Math.floor,
ceil: Math.ceil,
max: Math.max,
min: Math.min,
random: Math.random,
asin: Math.asin,
acos: Math.acos,
atan: Math.atan,
atan2: Math.atan2,
sinh: Math.sinh,
cosh: Math.cosh,
tanh: Math.tanh,
pi: Math.PI,
e: Math.E,
// Statistics functions
sum: (...args) => args.reduce((a, b) => a + b, 0),
mean: (...args) => args.reduce((a, b) => a + b, 0) / args.length,
median: (...args) => {
const sorted = args.sort((a, b) => a - b);
const mid = Math.floor(sorted.length / 2);
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
},
std: (...args) => {
const avg = args.reduce((a, b) => a + b, 0) / args.length;
const variance = args.reduce((a, b) => a + Math.pow(b - avg, 2), 0) / args.length;
return Math.sqrt(variance);
},
// Financial functions
pv: (rate, nper, pmt, fv = 0) => {
if (rate === 0) return -(pmt * nper + fv);
return -(pmt * (1 - Math.pow(1 + rate, -nper)) / rate + fv * Math.pow(1 + rate, -nper));
},
fv: (rate, nper, pmt, pv = 0) => {
if (rate === 0) return -(pv + pmt * nper);
return -(pv * Math.pow(1 + rate, nper) + pmt * (Math.pow(1 + rate, nper) - 1) / rate);
},
npv: (rate, ...values) => {
// Net Present Value - Excel standard: NPV(rate, value1, value2, ...)
// Also accepts: NPV(rate, [value1, value2, ...])
let cashFlows;
if (values.length === 1 && Array.isArray(values[0])) {
// Called as npv(rate, [array])
cashFlows = values[0];
} else {
// Called as npv(rate, val1, val2, val3, ...)
cashFlows = values;
}
if (!Array.isArray(cashFlows) || cashFlows.length === 0) {
throw new Error('NPV requires cash flow values');
}
return cashFlows.reduce((acc, value, index) => acc + value / Math.pow(1 + rate, index), 0);
},
irr: (values, guess = 0.1) => {
let rate = guess;
for (let i = 0; i < 100; i++) {
const npvRate = values.reduce((acc, value, index) => acc + value / Math.pow(1 + rate, index), 0);
const derivRate = values.reduce((acc, value, index) => acc - index * value / Math.pow(1 + rate, index + 1), 0);
const newRate = rate - npvRate / derivRate;
if (Math.abs(newRate - rate) < 0.0001) return newRate;
rate = newRate;
}
return rate;
},
cagr: (beginning, ending, periods) => Math.pow(ending / beginning, 1 / periods) - 1,
compound: (principal, rate, periods) => principal * Math.pow(1 + rate, periods),
// Dynamic functions
${functionCode}
// Result storage
result: undefined
};
// Add case-insensitive aliases for all functions
const functionNames = Object.keys(context).filter(key => typeof context[key] === 'function');
functionNames.forEach(name => {
// Add uppercase version
const upperName = name.toUpperCase();
if (!context[upperName]) {
context[upperName] = context[name];
}
// Add title case version for common functions
const titleName = name.charAt(0).toUpperCase() + name.slice(1);
if (!context[titleName]) {
context[titleName] = context[name];
}
});
// Make context safe for execution
vm.createContext(context);
// Execute expression
const expression = ${JSON.stringify(expression)};
const finalExpression = 'result = ' + expression;
console.log('Executing:', finalExpression);
vm.runInContext(finalExpression, context, {
timeout: 30000,
displayErrors: true
});
if (context.result === undefined || context.result === null) {
throw new Error('Expression did not produce a result');
}
if (typeof context.result === 'number' && !isFinite(context.result)) {
throw new Error('Result is not a finite number');
}
console.log('Success:', JSON.stringify({
success: true,
result: context.result,
type: typeof context.result
}));
} catch (error) {
console.error('Error:', JSON.stringify({
success: false,
error: error.message,
stack: error.stack
}));
process.exit(1);
}`;
// Write and execute the dynamic program
const tempDir = path.join(require("node:os").tmpdir(), 'c9ai-dynamic');
const filename = `dynamic_calc_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.js`;
const filepath = path.join(tempDir, filename);
// Ensure temp directory exists
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
this.onProgress("status", "💾 Writing dynamic program to disk...");
fs.writeFileSync(filepath, programCode);
this.checkAbort();
this.onProgress("status", "🚀 Executing dynamic calculation...");
try {
const { stdout, stderr } = await execAsync(`node "${filepath}"`, {
timeout: 30000
});
this.checkAbort();
// Clean up
fs.unlinkSync(filepath);
if (stderr) {
this.onProgress("debug", `stderr: ${stderr}`);
}
// Parse result
const lines = stdout.trim().split('\n');
const resultLine = lines.find(line => line.startsWith('Success:'));
if (resultLine) {
const result = JSON.parse(resultLine.substring('Success:'.length).trim());
this.onProgress("success", `✅ Calculation completed successfully`);
return result;
} else {
throw new Error('No result found in output');
}
} catch (error) {
// Clean up on error
if (fs.existsSync(filepath)) {
fs.unlinkSync(filepath);
}
this.onProgress("error", `❌ Execution failed: ${error.message}`);
throw error;
}
}
/**
* Main processing function with full transparency
*/
async process(text) {
this.reset();
this.onProgress("start", `🔍 Processing: "${text}"`);
try {
// Step 1: Try to convert to @calc
this.onProgress("phase", "Phase 1: Natural Language Analysis");
const conversion = await this.convertToCalc(text);
if (!conversion) {
this.onProgress("status", "❌ Not a mathematical query - passing to general AI");
return { converted: false, text: text, reason: "Not mathematical" };
}
this.onProgress("success", `✅ Converted to: ${conversion.calcExpression}`);
// Step 2: Validate available functions
this.onProgress("phase", "Phase 2: Function Validation");
const validation = this.validateFunctions(conversion.expression);
if (validation.isValid) {
this.onProgress("success", `✅ All functions available: [${validation.usedFunctions.join(', ')}]`);
// Execute with standard JIT
this.onProgress("phase", "Phase 3: Standard Calculation");
this.onProgress("status", "🔢 Executing with built-in functions...");
const result = await jitTool.execute({
type: 'calc',
expression: conversion.expression
});
this.onProgress("success", `✅ Result: ${result.result}`);
return {
converted: true,
calcExpression: conversion.calcExpression,
result: result.result,
method: "standard",
functions: validation.usedFunctions
};
} else {
// Step 3: Handle missing functions
this.onProgress("warning", `⚠️ Missing functions: [${validation.unavailableFunctions.join(', ')}]`);
this.onProgress("phase", "Phase 3: Dynamic Function Generation");
// Ask user permission
const permission = await this.onUserInput(
`Function '${validation.unavailableFunctions[0]}' not found. Generate and execute custom code?`
);
this.checkAbort();
if (!permission) {
this.onProgress("status", "❌ User declined code generation");
return { converted: false, text: text, reason: "User declined dynamic execution" };
}
// Generate missing functions
let functionCode = '';
for (const funcName of validation.unavailableFunctions) {
this.checkAbort();
const code = await this.generateMissingFunction(funcName, text);
functionCode += code + '\n\n';
}
this.onProgress("phase", "Phase 4: Dynamic Execution");
// Execute dynamic program
const result = await this.executeDynamicProgram(conversion.expression, validation.unavailableFunctions, functionCode);
this.onProgress("success", `🎉 Dynamic calculation completed!`);
return {
converted: true,
calcExpression: conversion.calcExpression,
result: result.result,
method: "dynamic",
functions: validation.usedFunctions,
generatedFunctions: validation.unavailableFunctions,
generatedCode: functionCode
};
}
} catch (error) {
if (error.message === "Operation aborted by user") {
return { converted: false, text: text, reason: "Aborted by user" };
}
this.onProgress("error", `❌ Processing failed: ${error.message}`);
return { converted: false, text: text, reason: error.message };
}
}
}
module.exports = { EnhancedMathProcessor };