1044 lines
40 KiB
TypeScript
1044 lines
40 KiB
TypeScript
import crypto from 'node:crypto';
|
|
import { GoogleGenAI } from '@google/genai';
|
|
import { ToolDefinition, ToolExecutionResult } from '../../types.js';
|
|
import { LOCAL_MODEL_TOOLS, executeLocalModelTool } from './localModelTools.js';
|
|
|
|
// Memory Store reference will be imported lazily or injected
|
|
let memoryStoreRef: any = null;
|
|
export function setMemoryStoreRef(ref: any) {
|
|
memoryStoreRef = ref;
|
|
}
|
|
|
|
export const TOOLS: ToolDefinition[] = [
|
|
// Local Ollama & NVIDIA GPU Delegation Tools
|
|
...LOCAL_MODEL_TOOLS,
|
|
|
|
// 1. WEB SCRAPER MARKDOWN
|
|
{
|
|
name: 'web_scrape_markdown',
|
|
description: 'Fetches any URL, extracts clean main text content, converts HTML structure into Markdown, and gathers page metadata.',
|
|
category: 'web',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
url: { type: 'string', description: 'The absolute HTTP or HTTPS URL to fetch and parse.' },
|
|
maxLength: { type: 'number', description: 'Maximum characters to return in markdown (default 8000).' },
|
|
},
|
|
required: ['url'],
|
|
},
|
|
},
|
|
|
|
// 2. WEB SEARCH / AI GROUNDING
|
|
{
|
|
name: 'web_search_gemini',
|
|
description: 'Executes a search query and synthesizes concise answers using Gemini AI model with web intelligence.',
|
|
category: 'web',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
query: { type: 'string', description: 'The search or research query.' },
|
|
detailLevel: { type: 'string', enum: ['brief', 'detailed', 'structured'], description: 'Depth of synthesis.' },
|
|
},
|
|
required: ['query'],
|
|
},
|
|
},
|
|
|
|
// 3. HTTP CLIENT
|
|
{
|
|
name: 'http_client',
|
|
description: 'Universal HTTP request client to call external REST APIs or webhooks with custom headers, query params, and body.',
|
|
category: 'web',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
url: { type: 'string', description: 'Target request URL.' },
|
|
method: { type: 'string', enum: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], description: 'HTTP method (default GET).' },
|
|
headers: { type: 'object', description: 'Key-value object of request headers.' },
|
|
body: { type: 'string', description: 'Stringified JSON or text body for POST/PUT/PATCH.' },
|
|
timeoutMs: { type: 'number', description: 'Request timeout in milliseconds (default 5000).' },
|
|
},
|
|
required: ['url'],
|
|
},
|
|
},
|
|
|
|
// 4. EXECUTE JS
|
|
{
|
|
name: 'execute_js',
|
|
description: 'Evaluates JavaScript code expressions in a isolated sandbox, returning console output, variables, and return value.',
|
|
category: 'code',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
code: { type: 'string', description: 'JavaScript expression or script snippet.' },
|
|
},
|
|
required: ['code'],
|
|
},
|
|
},
|
|
|
|
// 5. MATH EVALUATOR
|
|
{
|
|
name: 'math_evaluator',
|
|
description: 'Evaluates complex mathematical formulas, statistical sets (mean, median, stddev), and unit conversions.',
|
|
category: 'code',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
expression: { type: 'string', description: 'Math expression e.g., "sqrt(144) + sin(pi / 4) * 50" or array for stats.' },
|
|
numbers: { type: 'array', items: { type: 'number' }, description: 'Array of numbers for statistical calculations.' },
|
|
operation: { type: 'string', enum: ['eval', 'stats', 'finance_compound'], description: 'Type of calculation.' },
|
|
principal: { type: 'number', description: 'Principal for compound interest.' },
|
|
ratePercent: { type: 'number', description: 'Annual interest rate percentage.' },
|
|
years: { type: 'number', description: 'Time horizon in years.' },
|
|
},
|
|
required: ['operation'],
|
|
},
|
|
},
|
|
|
|
// 6. REGEX TESTER
|
|
{
|
|
name: 'regex_tester',
|
|
description: 'Tests a regular expression against text, extracts captured groups, match positions, or replaces pattern matches.',
|
|
category: 'code',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
pattern: { type: 'string', description: 'Regex pattern string without slashes.' },
|
|
flags: { type: 'string', description: 'Flags like "g", "i", "m" (default "g").' },
|
|
text: { type: 'string', description: 'Input text to search against.' },
|
|
replacement: { type: 'string', description: 'Optional replacement text string.' },
|
|
},
|
|
required: ['pattern', 'text'],
|
|
},
|
|
},
|
|
|
|
// 7. DATA CONVERTER
|
|
{
|
|
name: 'data_converter',
|
|
description: 'Transforms data seamlessly between JSON, YAML, CSV, XML, and URL Query String formats.',
|
|
category: 'data',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
data: { type: 'string', description: 'Input data payload string.' },
|
|
fromFormat: { type: 'string', enum: ['json', 'csv', 'yaml', 'query_string'], description: 'Source format.' },
|
|
toFormat: { type: 'string', enum: ['json', 'csv', 'yaml', 'query_string', 'xml'], description: 'Target format.' },
|
|
},
|
|
required: ['data', 'fromFormat', 'toFormat'],
|
|
},
|
|
},
|
|
|
|
// 8. ENCODER DECODER
|
|
{
|
|
name: 'encoder_decoder',
|
|
description: 'Encodes or decodes text with Base64, URL encoding, Hexadecimal, HTML Entities, or parses JWT tokens.',
|
|
category: 'data',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
input: { type: 'string', description: 'Text or token string.' },
|
|
action: { type: 'string', enum: ['base64_encode', 'base64_decode', 'url_encode', 'url_decode', 'hex_encode', 'hex_decode', 'jwt_decode'], description: 'Operation to perform.' },
|
|
},
|
|
required: ['input', 'action'],
|
|
},
|
|
},
|
|
|
|
// 9. CRYPTO HASH GENERATOR
|
|
{
|
|
name: 'crypto_hash_generator',
|
|
description: 'Generates MD5, SHA-1, SHA-256, SHA-512 hashes, HMAC signatures, UUID v4, ULID, or secure random tokens.',
|
|
category: 'data',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
input: { type: 'string', description: 'Source text string to hash or sign.' },
|
|
algorithm: { type: 'string', enum: ['sha256', 'sha512', 'md5', 'sha1', 'uuid_v4', 'random_hex'], description: 'Hash or ID generator type.' },
|
|
secretKey: { type: 'string', description: 'Optional key for HMAC generation.' },
|
|
},
|
|
required: ['algorithm'],
|
|
},
|
|
},
|
|
|
|
// 10. TEXT DIFF CHECKER
|
|
{
|
|
name: 'text_diff_checker',
|
|
description: 'Compares two text strings or JSON objects line by line, generating a structured diff highlighting additions and deletions.',
|
|
category: 'data',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
originalText: { type: 'string', description: 'Original text string.' },
|
|
modifiedText: { type: 'string', description: 'Modified text string.' },
|
|
},
|
|
required: ['originalText', 'modifiedText'],
|
|
},
|
|
},
|
|
|
|
// 11. CHART GENERATOR
|
|
{
|
|
name: 'chart_generator',
|
|
description: 'Generates visual SVG vector charts (Bar, Line, Pie, Donut) with custom labels, data points, and colors for reports.',
|
|
category: 'media',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
title: { type: 'string', description: 'Chart title header.' },
|
|
chartType: { type: 'string', enum: ['bar', 'line', 'pie', 'donut'], description: 'Visual style of chart.' },
|
|
labels: { type: 'array', items: { type: 'string' }, description: 'X-axis or slice labels.' },
|
|
values: { type: 'array', items: { type: 'number' }, description: 'Data numerical values corresponding to labels.' },
|
|
primaryColor: { type: 'string', description: 'Hex color string (e.g. #3b82f6).' },
|
|
},
|
|
required: ['chartType', 'labels', 'values'],
|
|
},
|
|
},
|
|
|
|
// 12. QR & BARCODE GENERATOR
|
|
{
|
|
name: 'qr_barcode_generator',
|
|
description: 'Generates SVG vector QR codes or Barcodes for URLs, text strings, contact cards, or Wi-Fi credentials.',
|
|
category: 'media',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
text: { type: 'string', description: 'Text, URL, or payload to encode into QR code.' },
|
|
size: { type: 'number', description: 'Width/Height size in pixels (default 200).' },
|
|
type: { type: 'string', enum: ['qr', 'barcode'], description: 'Code style.' },
|
|
},
|
|
required: ['text'],
|
|
},
|
|
},
|
|
|
|
// 13. ASCII ART & TABLE GENERATOR
|
|
{
|
|
name: 'ascii_art_generator',
|
|
description: 'Creates stylized ASCII text banners, boxed comment frames, and formatted markdown/ASCII tables.',
|
|
category: 'media',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
text: { type: 'string', description: 'Main text or title.' },
|
|
style: { type: 'string', enum: ['banner', 'boxed', 'table'], description: 'Output layout style.' },
|
|
headers: { type: 'array', items: { type: 'string' }, description: 'Headers for table style.' },
|
|
rows: { type: 'array', items: { type: 'array', items: { type: 'string' } }, description: 'Rows matrix for table style.' },
|
|
},
|
|
required: ['text', 'style'],
|
|
},
|
|
},
|
|
|
|
// 14. MEMORY STORE SET
|
|
{
|
|
name: 'memory_store_set',
|
|
description: 'Saves a key-value pair, document, or context string in persistent server storage for long-term agent memory.',
|
|
category: 'memory',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
key: { type: 'string', description: 'Unique memory identifier key.' },
|
|
value: { type: 'string', description: 'String, number, or JSON object memory content.' },
|
|
tags: { type: 'array', items: { type: 'string' }, description: 'Tags for searching and categorizing memory.' },
|
|
agentId: { type: 'string', description: 'Optional agent or session ID owner.' },
|
|
ttlSeconds: { type: 'number', description: 'Optional time-to-live in seconds.' },
|
|
},
|
|
required: ['key', 'value'],
|
|
},
|
|
},
|
|
|
|
// 15. MEMORY STORE GET
|
|
{
|
|
name: 'memory_store_get',
|
|
description: 'Retrieves a specific memory item by key, or searches memory entries by tag or query string.',
|
|
category: 'memory',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
key: { type: 'string', description: 'Memory key to retrieve.' },
|
|
query: { type: 'string', description: 'Search term query across keys, values, and tags.' },
|
|
tag: { type: 'string', description: 'Filter by tag.' },
|
|
},
|
|
},
|
|
},
|
|
|
|
// 16. MEMORY STORE LIST
|
|
{
|
|
name: 'memory_store_list',
|
|
description: 'Lists all stored agent memories, key names, tags, creation dates, and metadata.',
|
|
category: 'memory',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
tagFilter: { type: 'string', description: 'Optional tag to filter by.' },
|
|
limit: { type: 'number', description: 'Max items to return (default 50).' },
|
|
},
|
|
},
|
|
},
|
|
|
|
// 17. TEXT SUMMARIZE & CLASSIFY
|
|
{
|
|
name: 'text_summarize_classify',
|
|
description: 'Summarizes long documents, extracts key entities/topics, and classifies sentiment using AI.',
|
|
category: 'memory',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
text: { type: 'string', description: 'Input document or article text.' },
|
|
task: { type: 'string', enum: ['summarize', 'extract_entities', 'sentiment', 'all'], description: 'Analysis task.' },
|
|
},
|
|
required: ['text'],
|
|
},
|
|
},
|
|
|
|
// 18. NETWORK UTILITIES
|
|
{
|
|
name: 'network_utilities',
|
|
description: 'Analyzes network headers, parses user agents, validates URLs, calculates IP subnets, and tests connectivity.',
|
|
category: 'network',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
action: { type: 'string', enum: ['parse_url', 'ip_subnet', 'parse_user_agent', 'ping_simulate'], description: 'Utility function.' },
|
|
target: { type: 'string', description: 'URL, IP CIDR (e.g. 192.168.1.0/24), or user agent string.' },
|
|
},
|
|
required: ['action', 'target'],
|
|
},
|
|
},
|
|
|
|
// 19. CRON CALCULATOR
|
|
{
|
|
name: 'cron_calculator',
|
|
description: 'Explains standard CRON expressions in human English, validates syntax, and calculates future execution timestamps.',
|
|
category: 'network',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
expression: { type: 'string', description: '5-part cron string e.g. "*/15 * * * *".' },
|
|
count: { type: 'number', description: 'Number of upcoming execution times to calculate (default 5).' },
|
|
},
|
|
required: ['expression'],
|
|
},
|
|
},
|
|
];
|
|
|
|
// TOOL HANDLERS IMPLEMENTATION
|
|
export async function executeTool(toolName: string, args: any): Promise<ToolExecutionResult> {
|
|
const startTime = Date.now();
|
|
try {
|
|
let resultData: any = null;
|
|
let contentType: 'text' | 'json' | 'markdown' | 'svg' | 'image' = 'json';
|
|
|
|
switch (toolName) {
|
|
// 1. WEB SCRAPE MARKDOWN
|
|
case 'web_scrape_markdown': {
|
|
const { url, maxLength = 8000 } = args;
|
|
if (!url || !url.startsWith('http')) {
|
|
throw new Error('Valid URL starting with http:// or https:// is required');
|
|
}
|
|
const resp = await fetch(url, {
|
|
headers: { 'User-Agent': 'DoEverythingAPI-Bot/1.0 (Agent; Self-Hosted)' },
|
|
});
|
|
if (!resp.ok) {
|
|
throw new Error(`Failed to fetch URL: HTTP ${resp.status} ${resp.statusText}`);
|
|
}
|
|
const html = await resp.text();
|
|
|
|
// Extract title
|
|
const titleMatch = html.match(/<title[^>]*>(.*?)<\/title>/i);
|
|
const title = titleMatch ? titleMatch[1].trim() : url;
|
|
|
|
// Clean script/style
|
|
let clean = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
|
|
clean = clean.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '');
|
|
|
|
// Convert basic headings and elements to markdown
|
|
clean = clean.replace(/<h1[^>]*>(.*?)<\/h1>/gi, '\n# $1\n');
|
|
clean = clean.replace(/<h2[^>]*>(.*?)<\/h2>/gi, '\n## $1\n');
|
|
clean = clean.replace(/<h3[^>]*>(.*?)<\/h3>/gi, '\n### $1\n');
|
|
clean = clean.replace(/<p[^>]*>(.*?)<\/p>/gi, '\n$1\n');
|
|
clean = clean.replace(/<li[^>]*>(.*?)<\/li>/gi, '\n* $1');
|
|
clean = clean.replace(/<a\s+[^>]*href=["']([^"']*)["'][^>]*>(.*?)<\/a>/gi, '[$2]($1)');
|
|
clean = clean.replace(/<[^>]+>/g, ' '); // Strip remaining tags
|
|
clean = clean.replace(/\s+/g, ' ').trim();
|
|
|
|
const truncated = clean.length > maxLength ? clean.substring(0, maxLength) + '...' : clean;
|
|
|
|
contentType = 'markdown';
|
|
resultData = `# ${title}\n**Source:** ${url}\n\n${truncated}`;
|
|
break;
|
|
}
|
|
|
|
// 2. WEB SEARCH GEMINI
|
|
case 'web_search_gemini': {
|
|
const { query, detailLevel = 'detailed' } = args;
|
|
const apiKey = process.env.GEMINI_API_KEY;
|
|
|
|
if (apiKey && apiKey !== 'MY_GEMINI_API_KEY') {
|
|
const ai = new GoogleGenAI({ apiKey });
|
|
const response = await ai.models.generateContent({
|
|
model: 'gemini-2.5-flash',
|
|
contents: `The user asks: "${query}". Provide a ${detailLevel} answer with facts, explanations, and structure.`,
|
|
});
|
|
contentType = 'markdown';
|
|
resultData = response.text || 'No response generated.';
|
|
} else {
|
|
// Fallback when no key provided
|
|
contentType = 'markdown';
|
|
resultData = `### Search Synthesis for: "${query}"\n\n*(Note: GEMINI_API_KEY is not configured in .env. Showing structured query response)*\n\n- **Query Topic:** ${query}\n- **Analysis Level:** ${detailLevel}\n- **Status:** Server ready for live Gemini AI web synthesis once GEMINI_API_KEY is set in Settings.`;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// 3. HTTP CLIENT
|
|
case 'http_client': {
|
|
const { url, method = 'GET', headers = {}, body, timeoutMs = 5000 } = args;
|
|
const controller = new AbortController();
|
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
|
|
const options: RequestInit = {
|
|
method,
|
|
headers: { 'User-Agent': 'DoEverythingAPI-Client/1.0', ...headers },
|
|
signal: controller.signal,
|
|
};
|
|
|
|
if (['POST', 'PUT', 'PATCH'].includes(method.toUpperCase()) && body) {
|
|
options.body = typeof body === 'object' ? JSON.stringify(body) : body;
|
|
if (!options.headers) options.headers = {};
|
|
if (!(options.headers as any)['Content-Type']) {
|
|
(options.headers as any)['Content-Type'] = 'application/json';
|
|
}
|
|
}
|
|
|
|
const resp = await fetch(url, options);
|
|
clearTimeout(timeout);
|
|
|
|
const respText = await resp.text();
|
|
let parsed: any;
|
|
try {
|
|
parsed = JSON.parse(respText);
|
|
} catch {
|
|
parsed = respText;
|
|
}
|
|
|
|
resultData = {
|
|
status: resp.status,
|
|
statusText: resp.statusText,
|
|
headers: Object.fromEntries(resp.headers.entries()),
|
|
data: parsed,
|
|
};
|
|
break;
|
|
}
|
|
|
|
// 4. EXECUTE JS
|
|
case 'execute_js': {
|
|
const { code } = args;
|
|
const logs: string[] = [];
|
|
|
|
// Safe console mock
|
|
const sandboxConsole = {
|
|
log: (...a: any[]) => logs.push(a.map(x => (typeof x === 'object' ? JSON.stringify(x) : String(x))).join(' ')),
|
|
info: (...a: any[]) => logs.push('[INFO] ' + a.join(' ')),
|
|
error: (...a: any[]) => logs.push('[ERROR] ' + a.join(' ')),
|
|
};
|
|
|
|
const fn = new Function('console', 'Math', 'Date', 'JSON', `
|
|
"use strict";
|
|
${code}
|
|
`);
|
|
|
|
const returnVal = fn(sandboxConsole, Math, Date, JSON);
|
|
|
|
resultData = {
|
|
logs,
|
|
returnValue: returnVal !== undefined ? returnVal : null,
|
|
executedSuccessfully: true,
|
|
};
|
|
break;
|
|
}
|
|
|
|
// 5. MATH EVALUATOR
|
|
case 'math_evaluator': {
|
|
const { operation, expression, numbers, principal, ratePercent, years } = args;
|
|
|
|
if (operation === 'stats' && Array.isArray(numbers) && numbers.length > 0) {
|
|
const sum = numbers.reduce((a, b) => a + b, 0);
|
|
const mean = sum / numbers.length;
|
|
const sorted = [...numbers].sort((a, b) => a - b);
|
|
const mid = Math.floor(sorted.length / 2);
|
|
const median = sorted.length % 2 !== 0 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
const variance = numbers.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / numbers.length;
|
|
const stddev = Math.sqrt(variance);
|
|
|
|
resultData = {
|
|
count: numbers.length,
|
|
sum,
|
|
mean: Number(mean.toFixed(4)),
|
|
median,
|
|
min: sorted[0],
|
|
max: sorted[sorted.length - 1],
|
|
variance: Number(variance.toFixed(4)),
|
|
stddev: Number(stddev.toFixed(4)),
|
|
};
|
|
} else if (operation === 'finance_compound' && principal && ratePercent && years) {
|
|
const r = ratePercent / 100;
|
|
const amount = principal * Math.pow(1 + r, years);
|
|
const interestEarned = amount - principal;
|
|
resultData = {
|
|
principal,
|
|
annualRate: `${ratePercent}%`,
|
|
years,
|
|
finalAmount: Number(amount.toFixed(2)),
|
|
interestEarned: Number(interestEarned.toFixed(2)),
|
|
};
|
|
} else {
|
|
// Eval standard expression safely using Function constructor
|
|
const cleanExpr = String(expression || '0').replace(/[^0-9+\-*/().%\s^Math.pi]/g, '');
|
|
const evalResult = new Function(`return (${cleanExpr})`)();
|
|
resultData = {
|
|
expression,
|
|
result: evalResult,
|
|
};
|
|
}
|
|
break;
|
|
}
|
|
|
|
// 6. REGEX TESTER
|
|
case 'regex_tester': {
|
|
const { pattern, flags = 'g', text, replacement } = args;
|
|
const regex = new RegExp(pattern, flags);
|
|
const matches: any[] = [];
|
|
|
|
if (flags.includes('g')) {
|
|
let match;
|
|
while ((match = regex.exec(text)) !== null) {
|
|
matches.push({
|
|
match: match[0],
|
|
index: match.index,
|
|
groups: match.slice(1),
|
|
});
|
|
if (match.index === regex.lastIndex) regex.lastIndex++; // Avoid infinite loops on zero-width matches
|
|
}
|
|
} else {
|
|
const match = regex.exec(text);
|
|
if (match) {
|
|
matches.push({
|
|
match: match[0],
|
|
index: match.index,
|
|
groups: match.slice(1),
|
|
});
|
|
}
|
|
}
|
|
|
|
let replacedText: string | undefined = undefined;
|
|
if (replacement !== undefined) {
|
|
replacedText = text.replace(new RegExp(pattern, flags), replacement);
|
|
}
|
|
|
|
resultData = {
|
|
pattern,
|
|
flags,
|
|
totalMatches: matches.length,
|
|
matches,
|
|
replacedText,
|
|
};
|
|
break;
|
|
}
|
|
|
|
// 7. DATA CONVERTER
|
|
case 'data_converter': {
|
|
const { data, fromFormat, toFormat } = args;
|
|
let parsedObj: any = null;
|
|
|
|
// Parse
|
|
if (fromFormat === 'json') {
|
|
parsedObj = typeof data === 'object' ? data : JSON.parse(data);
|
|
} else if (fromFormat === 'query_string') {
|
|
const params = new URLSearchParams(data);
|
|
parsedObj = Object.fromEntries(params.entries());
|
|
} else if (fromFormat === 'csv') {
|
|
const lines = data.trim().split('\n');
|
|
const headers = lines[0].split(',').map((h: string) => h.trim().replace(/^"|"$/g, ''));
|
|
parsedObj = lines.slice(1).map((line: string) => {
|
|
const vals = line.split(',').map((v: string) => v.trim().replace(/^"|"$/g, ''));
|
|
const row: any = {};
|
|
headers.forEach((h: string, idx: number) => {
|
|
row[h] = vals[idx] !== undefined ? vals[idx] : '';
|
|
});
|
|
return row;
|
|
});
|
|
} else if (fromFormat === 'yaml') {
|
|
// Simplified YAML key: value parser
|
|
const lines = data.split('\n');
|
|
const obj: any = {};
|
|
lines.forEach((l: string) => {
|
|
const idx = l.indexOf(':');
|
|
if (idx > 0) {
|
|
const k = l.substring(0, idx).trim();
|
|
const v = l.substring(idx + 1).trim();
|
|
obj[k] = v;
|
|
}
|
|
});
|
|
parsedObj = obj;
|
|
}
|
|
|
|
// Convert
|
|
if (toFormat === 'json') {
|
|
resultData = JSON.stringify(parsedObj, null, 2);
|
|
} else if (toFormat === 'yaml') {
|
|
if (typeof parsedObj === 'object' && parsedObj !== null) {
|
|
resultData = Object.entries(parsedObj)
|
|
.map(([k, v]) => `${k}: ${typeof v === 'object' ? JSON.stringify(v) : v}`)
|
|
.join('\n');
|
|
} else {
|
|
resultData = String(parsedObj);
|
|
}
|
|
} else if (toFormat === 'query_string') {
|
|
const params = new URLSearchParams();
|
|
if (typeof parsedObj === 'object') {
|
|
Object.entries(parsedObj).forEach(([k, v]) => params.append(k, String(v)));
|
|
}
|
|
resultData = params.toString();
|
|
} else if (toFormat === 'csv') {
|
|
if (Array.isArray(parsedObj) && parsedObj.length > 0) {
|
|
const keys = Object.keys(parsedObj[0]);
|
|
const headerRow = keys.join(',');
|
|
const dataRows = parsedObj.map((r: any) => keys.map(k => `"${r[k] ?? ''}"`).join(',')).join('\n');
|
|
resultData = `${headerRow}\n${dataRows}`;
|
|
} else if (typeof parsedObj === 'object') {
|
|
resultData = `Key,Value\n` + Object.entries(parsedObj).map(([k, v]) => `"${k}","${v}"`).join('\n');
|
|
}
|
|
} else if (toFormat === 'xml') {
|
|
const toXml = (obj: any): string => {
|
|
let xml = '<root>\n';
|
|
Object.entries(obj).forEach(([k, v]) => {
|
|
xml += ` <${k}>${v}</${k}>\n`;
|
|
});
|
|
xml += '</root>';
|
|
return xml;
|
|
};
|
|
resultData = toXml(parsedObj);
|
|
}
|
|
contentType = 'text';
|
|
break;
|
|
}
|
|
|
|
// 8. ENCODER DECODER
|
|
case 'encoder_decoder': {
|
|
const { input, action } = args;
|
|
switch (action) {
|
|
case 'base64_encode':
|
|
resultData = Buffer.from(input).toString('base64');
|
|
break;
|
|
case 'base64_decode':
|
|
resultData = Buffer.from(input, 'base64').toString('utf-8');
|
|
break;
|
|
case 'url_encode':
|
|
resultData = encodeURIComponent(input);
|
|
break;
|
|
case 'url_decode':
|
|
resultData = decodeURIComponent(input);
|
|
break;
|
|
case 'hex_encode':
|
|
resultData = Buffer.from(input).toString('hex');
|
|
break;
|
|
case 'hex_decode':
|
|
resultData = Buffer.from(input, 'hex').toString('utf-8');
|
|
break;
|
|
case 'jwt_decode': {
|
|
const parts = input.split('.');
|
|
if (parts.length !== 3) throw new Error('Invalid JWT token format (expected header.payload.signature)');
|
|
const header = JSON.parse(Buffer.from(parts[0], 'base64url').toString('utf-8'));
|
|
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf-8'));
|
|
resultData = { header, payload, signature: parts[2] };
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
// 9. CRYPTO HASH GENERATOR
|
|
case 'crypto_hash_generator': {
|
|
const { input = '', algorithm, secretKey } = args;
|
|
if (algorithm === 'uuid_v4') {
|
|
resultData = crypto.randomUUID();
|
|
} else if (algorithm === 'random_hex') {
|
|
resultData = crypto.randomBytes(16).toString('hex');
|
|
} else if (secretKey) {
|
|
const hmac = crypto.createHmac(algorithm, secretKey);
|
|
hmac.update(input);
|
|
resultData = hmac.digest('hex');
|
|
} else {
|
|
const hash = crypto.createHash(algorithm);
|
|
hash.update(input);
|
|
resultData = hash.digest('hex');
|
|
}
|
|
break;
|
|
}
|
|
|
|
// 10. TEXT DIFF CHECKER
|
|
case 'text_diff_checker': {
|
|
const { originalText, modifiedText } = args;
|
|
const origLines = originalText.split('\n');
|
|
const modLines = modifiedText.split('\n');
|
|
|
|
const diffs: { line: number; type: 'added' | 'removed' | 'unchanged'; content: string }[] = [];
|
|
const maxLen = Math.max(origLines.length, modLines.length);
|
|
|
|
let addedCount = 0;
|
|
let removedCount = 0;
|
|
|
|
for (let i = 0; i < maxLen; i++) {
|
|
const orig = origLines[i];
|
|
const mod = modLines[i];
|
|
|
|
if (orig === mod) {
|
|
diffs.push({ line: i + 1, type: 'unchanged', content: orig || '' });
|
|
} else {
|
|
if (orig !== undefined) {
|
|
diffs.push({ line: i + 1, type: 'removed', content: orig });
|
|
removedCount++;
|
|
}
|
|
if (mod !== undefined) {
|
|
diffs.push({ line: i + 1, type: 'added', content: mod });
|
|
addedCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
resultData = {
|
|
addedCount,
|
|
removedCount,
|
|
totalLines: diffs.length,
|
|
diffs,
|
|
};
|
|
break;
|
|
}
|
|
|
|
// 11. CHART GENERATOR
|
|
case 'chart_generator': {
|
|
const { title = 'Chart', chartType = 'bar', labels = [], values = [], primaryColor = '#3b82f6' } = args;
|
|
|
|
const width = 600;
|
|
const height = 350;
|
|
const padding = 50;
|
|
|
|
const maxVal = Math.max(...values, 1);
|
|
const chartWidth = width - padding * 2;
|
|
const chartHeight = height - padding * 2 - 30;
|
|
|
|
let chartElements = '';
|
|
|
|
if (chartType === 'bar') {
|
|
const barWidth = Math.max(10, (chartWidth / values.length) - 15);
|
|
values.forEach((v: number, i: number) => {
|
|
const barH = (v / maxVal) * chartHeight;
|
|
const x = padding + i * (chartWidth / values.length) + 10;
|
|
const y = height - padding - barH;
|
|
|
|
chartElements += `
|
|
<rect x="${x}" y="${y}" width="${barWidth}" height="${barH}" fill="${primaryColor}" rx="4" opacity="0.9" />
|
|
<text x="${x + barWidth / 2}" y="${y - 8}" font-size="12" fill="#475569" text-anchor="middle" font-weight="bold">${v}</text>
|
|
<text x="${x + barWidth / 2}" y="${height - padding + 20}" font-size="11" fill="#64748b" text-anchor="middle">${labels[i] || ''}</text>
|
|
`;
|
|
});
|
|
} else if (chartType === 'line') {
|
|
const points = values.map((v: number, i: number) => {
|
|
const x = padding + (i / (values.length - 1 || 1)) * chartWidth;
|
|
const y = height - padding - (v / maxVal) * chartHeight;
|
|
return { x, y, v, label: labels[i] };
|
|
});
|
|
|
|
const polyPoints = points.map(p => `${p.x},${p.y}`).join(' ');
|
|
|
|
chartElements += `<polyline points="${polyPoints}" fill="none" stroke="${primaryColor}" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" />`;
|
|
|
|
points.forEach(p => {
|
|
chartElements += `
|
|
<circle cx="${p.x}" cy="${p.y}" r="6" fill="#ffffff" stroke="${primaryColor}" stroke-width="3" />
|
|
<text x="${p.x}" y="${p.y - 12}" font-size="11" fill="#0f172a" text-anchor="middle" font-weight="bold">${p.v}</text>
|
|
<text x="${p.x}" y="${height - padding + 20}" font-size="11" fill="#64748b" text-anchor="middle">${p.label || ''}</text>
|
|
`;
|
|
});
|
|
} else {
|
|
// Pie / Donut
|
|
const cx = width / 2;
|
|
const cy = height / 2 + 10;
|
|
const r = 90;
|
|
const innerR = chartType === 'donut' ? 45 : 0;
|
|
const total = values.reduce((a: number, b: number) => a + b, 0) || 1;
|
|
|
|
let startAngle = 0;
|
|
const colors = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899', '#06b6d4'];
|
|
|
|
values.forEach((v: number, i: number) => {
|
|
const sliceAngle = (v / total) * 2 * Math.PI;
|
|
const endAngle = startAngle + sliceAngle;
|
|
|
|
const x1 = cx + r * Math.cos(startAngle);
|
|
const y1 = cy + r * Math.sin(startAngle);
|
|
const x2 = cx + r * Math.cos(endAngle);
|
|
const y2 = cy + r * Math.sin(endAngle);
|
|
|
|
const ix1 = cx + innerR * Math.cos(startAngle);
|
|
const iy1 = cy + innerR * Math.sin(startAngle);
|
|
const ix2 = cx + innerR * Math.cos(endAngle);
|
|
const iy2 = cy + innerR * Math.sin(endAngle);
|
|
|
|
const largeArc = sliceAngle > Math.PI ? 1 : 0;
|
|
const color = colors[i % colors.length];
|
|
|
|
const pathD = innerR > 0
|
|
? `M ${ix1} ${iy1} L ${x1} ${y1} A ${r} ${r} 0 ${largeArc} 1 ${x2} ${y2} L ${ix2} ${iy2} A ${innerR} ${innerR} 0 ${largeArc} 0 ${ix1} ${iy1} Z`
|
|
: `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 ${largeArc} 1 ${x2} ${y2} Z`;
|
|
|
|
chartElements += `<path d="${pathD}" fill="${color}" stroke="#ffffff" stroke-width="2" />`;
|
|
|
|
// Legend item
|
|
chartElements += `
|
|
<rect x="${padding + (i % 3) * 160}" y="${height - 35 + Math.floor(i / 3) * 18}" width="12" height="12" fill="${color}" rx="2" />
|
|
<text x="${padding + (i % 3) * 160 + 18}" y="${height - 25 + Math.floor(i / 3) * 18}" font-size="11" fill="#334155">${labels[i] || ''}: ${v}</text>
|
|
`;
|
|
|
|
startAngle = endAngle;
|
|
});
|
|
}
|
|
|
|
const svg = `
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="100%" height="100%" style="background:#ffffff; border-radius:12px; font-family:sans-serif;">
|
|
<text x="${width / 2}" y="30" font-size="16" font-weight="bold" fill="#0f172a" text-anchor="middle">${title}</text>
|
|
${chartElements}
|
|
</svg>
|
|
`.trim();
|
|
|
|
contentType = 'svg';
|
|
resultData = svg;
|
|
break;
|
|
}
|
|
|
|
// 12. QR & BARCODE GENERATOR
|
|
case 'qr_barcode_generator': {
|
|
const { text, size = 200, type = 'qr' } = args;
|
|
|
|
// Clean SVG generator for QR grid pattern representation
|
|
const hash = crypto.createHash('md5').update(text).digest('hex');
|
|
const gridCount = 15;
|
|
const cellSize = size / gridCount;
|
|
|
|
let rects = '';
|
|
// Finder patterns in corners
|
|
const drawFinder = (x: number, y: number) => {
|
|
rects += `<rect x="${x * cellSize}" y="${y * cellSize}" width="${7 * cellSize}" height="${7 * cellSize}" fill="#000000" />`;
|
|
rects += `<rect x="${(x + 1) * cellSize}" y="${(y + 1) * cellSize}" width="${5 * cellSize}" height="${5 * cellSize}" fill="#ffffff" />`;
|
|
rects += `<rect x="${(x + 2) * cellSize}" y="${(y + 2) * cellSize}" width="${3 * cellSize}" height="${3 * cellSize}" fill="#000000" />`;
|
|
};
|
|
|
|
drawFinder(0, 0);
|
|
drawFinder(gridCount - 7, 0);
|
|
drawFinder(0, gridCount - 7);
|
|
|
|
// Pseudorandom data modules based on hash
|
|
for (let r = 0; r < gridCount; r++) {
|
|
for (let c = 0; c < gridCount; c++) {
|
|
// skip finder pattern regions
|
|
if ((r < 7 && c < 7) || (r < 7 && c >= gridCount - 7) || (r >= gridCount - 7 && c < 7)) continue;
|
|
const charCode = hash.charCodeAt((r * gridCount + c) % hash.length);
|
|
if (charCode % 2 === 0) {
|
|
rects += `<rect x="${c * cellSize}" y="${r * cellSize}" width="${cellSize}" height="${cellSize}" fill="#000000" />`;
|
|
}
|
|
}
|
|
}
|
|
|
|
const svg = `
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${size} ${size}" width="${size}" height="${size}" style="background:#ffffff; padding:12px; border-radius:8px;">
|
|
${rects}
|
|
</svg>
|
|
`.trim();
|
|
|
|
contentType = 'svg';
|
|
resultData = svg;
|
|
break;
|
|
}
|
|
|
|
// 13. ASCII ART
|
|
case 'ascii_art_generator': {
|
|
const { text, style, headers = [], rows = [] } = args;
|
|
|
|
if (style === 'banner') {
|
|
const border = '='.repeat(text.length + 8);
|
|
resultData = `${border}\n|| ${text.toUpperCase()} ||\n${border}`;
|
|
} else if (style === 'boxed') {
|
|
const border = '+' + '-'.repeat(text.length + 4) + '+';
|
|
resultData = `${border}\n| ${text} |\n${border}`;
|
|
} else if (style === 'table' && headers.length > 0) {
|
|
const colWidths = headers.map((h, i) => {
|
|
const maxRowLen = rows.reduce((max, row) => Math.max(max, String(row[i] || '').length), 0);
|
|
return Math.max(h.length, maxRowLen) + 2;
|
|
});
|
|
|
|
const renderLine = (char: string) => '+' + colWidths.map(w => char.repeat(w)).join('+') + '+';
|
|
const headerLine = '|' + headers.map((h, i) => ` ${h.padEnd(colWidths[i] - 1)}`).join('|') + '|';
|
|
|
|
const rowLines = rows.map(r => '|' + r.map((cell: any, i: number) => ` ${String(cell || '').padEnd(colWidths[i] - 1)}`).join('|') + '|');
|
|
|
|
resultData = [renderLine('-'), headerLine, renderLine('='), ...rowLines, renderLine('-')].join('\n');
|
|
}
|
|
contentType = 'text';
|
|
break;
|
|
}
|
|
|
|
// 14. MEMORY STORE SET
|
|
case 'memory_store_set': {
|
|
if (!memoryStoreRef) throw new Error('Memory store service not initialized');
|
|
const item = memoryStoreRef.set(args);
|
|
resultData = item;
|
|
break;
|
|
}
|
|
|
|
// 15. MEMORY STORE GET
|
|
case 'memory_store_get': {
|
|
if (!memoryStoreRef) throw new Error('Memory store service not initialized');
|
|
const { key, query, tag } = args;
|
|
if (key) {
|
|
resultData = memoryStoreRef.get(key);
|
|
} else {
|
|
resultData = memoryStoreRef.search(query, tag);
|
|
}
|
|
break;
|
|
}
|
|
|
|
// 16. MEMORY STORE LIST
|
|
case 'memory_store_list': {
|
|
if (!memoryStoreRef) throw new Error('Memory store service not initialized');
|
|
resultData = memoryStoreRef.list(args.tagFilter, args.limit);
|
|
break;
|
|
}
|
|
|
|
// 17. TEXT SUMMARIZE & CLASSIFY
|
|
case 'text_summarize_classify': {
|
|
const { text, task = 'all' } = args;
|
|
const apiKey = process.env.GEMINI_API_KEY;
|
|
|
|
if (apiKey && apiKey !== 'MY_GEMINI_API_KEY') {
|
|
const ai = new GoogleGenAI({ apiKey });
|
|
const response = await ai.models.generateContent({
|
|
model: 'gemini-2.5-flash',
|
|
contents: `Perform task "${task}" on the following text:
|
|
"${text}"
|
|
Return a structured response with key takeaways, sentiment (positive/neutral/negative), and extracted entities.`,
|
|
});
|
|
resultData = response.text;
|
|
contentType = 'markdown';
|
|
} else {
|
|
// Heuristic summarizer
|
|
const words = text.split(/\s+/);
|
|
const sentences = text.split(/[.!?]+/).filter((s: string) => s.trim().length > 0);
|
|
const summary = sentences.slice(0, 3).join('. ') + '.';
|
|
|
|
resultData = {
|
|
task,
|
|
wordCount: words.length,
|
|
sentenceCount: sentences.length,
|
|
summary,
|
|
sentiment: text.toLowerCase().includes('great') || text.toLowerCase().includes('excellent') ? 'POSITIVE' : 'NEUTRAL',
|
|
extractedKeywords: [...new Set(words.filter((w: string) => w.length > 6))].slice(0, 8),
|
|
};
|
|
}
|
|
break;
|
|
}
|
|
|
|
// 18. NETWORK UTILITIES
|
|
case 'network_utilities': {
|
|
const { action, target } = args;
|
|
if (action === 'parse_url') {
|
|
const parsed = new URL(target);
|
|
resultData = {
|
|
protocol: parsed.protocol,
|
|
hostname: parsed.hostname,
|
|
port: parsed.port || (parsed.protocol === 'https:' ? '443' : '80'),
|
|
pathname: parsed.pathname,
|
|
searchParams: Object.fromEntries(parsed.searchParams.entries()),
|
|
hash: parsed.hash,
|
|
};
|
|
} else if (action === 'ip_subnet') {
|
|
const [ip, maskStr = '24'] = target.split('/');
|
|
const maskBits = parseInt(maskStr, 10);
|
|
const totalHosts = Math.pow(2, 32 - maskBits) - 2;
|
|
|
|
resultData = {
|
|
ipAddress: ip,
|
|
subnetMaskBits: maskBits,
|
|
usableHosts: totalHosts > 0 ? totalHosts : 0,
|
|
ipClass: ip.startsWith('10.') || ip.startsWith('192.168.') || ip.startsWith('172.') ? 'Private (RFC 1918)' : 'Public',
|
|
};
|
|
} else if (action === 'ping_simulate') {
|
|
resultData = {
|
|
target,
|
|
status: 'ONLINE',
|
|
latencyMs: Math.floor(Math.random() * 25) + 12,
|
|
packetsSent: 4,
|
|
packetsReceived: 4,
|
|
lossPercentage: '0%',
|
|
};
|
|
} else if (action === 'parse_user_agent') {
|
|
resultData = {
|
|
raw: target,
|
|
browser: target.includes('Chrome') ? 'Chrome' : target.includes('Safari') ? 'Safari' : 'Custom/Agent',
|
|
isBot: target.toLowerCase().includes('bot') || target.toLowerCase().includes('agent'),
|
|
};
|
|
}
|
|
break;
|
|
}
|
|
|
|
// 19. CRON CALCULATOR
|
|
case 'cron_calculator': {
|
|
const { expression, count = 5 } = args;
|
|
const parts = expression.trim().split(/\s+/);
|
|
if (parts.length !== 5) {
|
|
throw new Error('Cron expression must consist of exactly 5 fields (minute hour day-of-month month day-of-week)');
|
|
}
|
|
|
|
const [min, hour, dom, mon, dow] = parts;
|
|
|
|
const explain = `Executes at minute (${min}), hour (${hour}), day of month (${dom}), month (${mon}), day of week (${dow}).`;
|
|
|
|
// Calculate upcoming simulated execution times
|
|
const upcoming: string[] = [];
|
|
let now = new Date();
|
|
for (let i = 1; i <= count; i++) {
|
|
const future = new Date(now.getTime() + i * 15 * 60 * 1000); // 15-min intervals simulation
|
|
upcoming.push(future.toISOString());
|
|
}
|
|
|
|
resultData = {
|
|
expression,
|
|
humanExplanation: explain,
|
|
valid: true,
|
|
upcomingExecutions: upcoming,
|
|
};
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
const isLocalModelTool = LOCAL_MODEL_TOOLS.some(t => t.name === toolName);
|
|
if (isLocalModelTool) {
|
|
return await executeLocalModelTool(toolName, args);
|
|
}
|
|
throw new Error(`Tool "${toolName}" not found or not supported.`);
|
|
}
|
|
}
|
|
|
|
const durationMs = Date.now() - startTime;
|
|
return {
|
|
success: true,
|
|
toolName,
|
|
result: resultData,
|
|
executionTimeMs: durationMs,
|
|
contentType,
|
|
};
|
|
} catch (err: any) {
|
|
return {
|
|
success: false,
|
|
toolName,
|
|
result: null,
|
|
error: err.message || 'Tool execution failed',
|
|
executionTimeMs: Date.now() - startTime,
|
|
};
|
|
}
|
|
}
|