aether-ai-agent-cli 1.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aether_ai_agent_cli-1.1.4.dist-info/METADATA +309 -0
- aether_ai_agent_cli-1.1.4.dist-info/RECORD +25 -0
- aether_ai_agent_cli-1.1.4.dist-info/WHEEL +5 -0
- aether_ai_agent_cli-1.1.4.dist-info/entry_points.txt +2 -0
- aether_ai_agent_cli-1.1.4.dist-info/licenses/LICENSE +21 -0
- aether_ai_agent_cli-1.1.4.dist-info/top_level.txt +1 -0
- aether_pip/__init__.py +1 -0
- aether_pip/cli.py +49 -0
- aether_pip/node_project/bin/aether.js +10 -0
- aether_pip/node_project/package-lock.json +794 -0
- aether_pip/node_project/package.json +46 -0
- aether_pip/node_project/src/ai/fallback.js +179 -0
- aether_pip/node_project/src/ai/google.js +87 -0
- aether_pip/node_project/src/ai/providers.js +203 -0
- aether_pip/node_project/src/ai/router.js +114 -0
- aether_pip/node_project/src/ai/universal.js +507 -0
- aether_pip/node_project/src/ai/xai.js +50 -0
- aether_pip/node_project/src/chat.js +1018 -0
- aether_pip/node_project/src/cli.js +679 -0
- aether_pip/node_project/src/config.js +214 -0
- aether_pip/node_project/src/file-parser.js +94 -0
- aether_pip/node_project/src/modes.js +121 -0
- aether_pip/node_project/src/ui/banner.js +60 -0
- aether_pip/node_project/src/ui/spinner.js +43 -0
- aether_pip/node_project/src/ui/theme.js +169 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@krishivpb60/aether-ai-cli",
|
|
3
|
+
"version": "1.1.4",
|
|
4
|
+
"description": "Aether Core AI — A cyberpunk command-line AI assistant with multi-mode reasoning, 12-node failover mesh, file context injection, and offline fallbacks.",
|
|
5
|
+
"main": "src/cli.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"aether": "bin/aether.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node bin/aether.js",
|
|
12
|
+
"test": "node --test"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"ai",
|
|
16
|
+
"cli",
|
|
17
|
+
"gemini",
|
|
18
|
+
"grok",
|
|
19
|
+
"xai",
|
|
20
|
+
"chatbot",
|
|
21
|
+
"aether",
|
|
22
|
+
"cyberpunk",
|
|
23
|
+
"terminal",
|
|
24
|
+
"assistant"
|
|
25
|
+
],
|
|
26
|
+
"author": "Krishiv PB <krylobloxyt@gmail.com> (https://github.com/Krylo-60)",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/Krylo-60/aether-ai-cli.git"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/Krylo-60/aether-ai-cli#readme",
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18.0.0"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"chalk": "^5.3.0",
|
|
41
|
+
"commander": "^12.1.0",
|
|
42
|
+
"marked": "^14.0.0",
|
|
43
|
+
"marked-terminal": "^7.2.0",
|
|
44
|
+
"ora": "^8.1.0"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════
|
|
2
|
+
// AETHER AI CLI — Local Fallback Engine
|
|
3
|
+
// Math Solver + Krylo Companion Bot
|
|
4
|
+
// ═══════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
const KRYLO_REPLIES = [
|
|
7
|
+
"Affirmative, commander. Systems are running at peak cybernetic capacity.",
|
|
8
|
+
"Neon grids initialized. Matrix color modulates are at nominal density.",
|
|
9
|
+
"Warning: Solar flare activity detected. Detuning audio synth harmonics by 18.4% to compensate.",
|
|
10
|
+
"Neural nodes synchronized. Analyzing the portfolio's glassmorphic boundaries.",
|
|
11
|
+
"I am Krylo, your holographic companion terminal. Ready to warp index nodes.",
|
|
12
|
+
"Ecosystem diagnostics complete. 0 memory leaks, 100% premium responsive UI."
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Detects if a prompt is a pure mathematical expression.
|
|
17
|
+
* Supports basic operators, parentheses, standard math functions, and constants.
|
|
18
|
+
* @param {string} prompt - The user prompt
|
|
19
|
+
* @returns {string|null} The cleaned expression or null
|
|
20
|
+
*/
|
|
21
|
+
export function detectMathExpression(prompt) {
|
|
22
|
+
const clean = prompt.replace(/\s+/g, "").toLowerCase();
|
|
23
|
+
|
|
24
|
+
// Check if it's a word-based status query
|
|
25
|
+
if (clean === "status" || clean === "hud") return null;
|
|
26
|
+
|
|
27
|
+
// Strip allowed function and constant words
|
|
28
|
+
const structure = clean.replace(/sin|cos|tan|log|ln|sqrt|pi|e|abs/g, "");
|
|
29
|
+
|
|
30
|
+
// Must be composed of valid math characters, AND contain either a math operator or an active function call
|
|
31
|
+
if (/^[0-9+\-*/().%^]+$/.test(structure)) {
|
|
32
|
+
const hasOperator = /[+\-*/%^]/.test(structure);
|
|
33
|
+
const hasFunction = /(sin|cos|tan|log|ln|sqrt|abs)\(/.test(clean);
|
|
34
|
+
if (hasOperator || hasFunction) {
|
|
35
|
+
return clean;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Safely evaluates a mathematical expression locally.
|
|
43
|
+
* Supports trig, square root, natural/base-10 logs, absolute values, pi, and e.
|
|
44
|
+
* @param {string} expression - A sanitized math expression
|
|
45
|
+
* @returns {{ text: string, type: string }|null}
|
|
46
|
+
*/
|
|
47
|
+
export function solveMath(expression) {
|
|
48
|
+
if (!expression) return null;
|
|
49
|
+
try {
|
|
50
|
+
const clean = expression.replace(/\s+/g, "").toLowerCase();
|
|
51
|
+
|
|
52
|
+
// Validate character structure
|
|
53
|
+
const structure = clean.replace(/sin|cos|tan|log|ln|sqrt|pi|e|abs/g, "");
|
|
54
|
+
if (!/^[0-9+\-*/().%^]+$/.test(structure)) return null;
|
|
55
|
+
|
|
56
|
+
// Convert terms to JavaScript Math equivalents
|
|
57
|
+
let jsExpr = clean
|
|
58
|
+
.replace(/\^/g, "**")
|
|
59
|
+
.replace(/sin\(/g, "Math.sin(")
|
|
60
|
+
.replace(/cos\(/g, "Math.cos(")
|
|
61
|
+
.replace(/tan\(/g, "Math.tan(")
|
|
62
|
+
.replace(/log\(/g, "Math.log10(")
|
|
63
|
+
.replace(/ln\(/g, "Math.log(")
|
|
64
|
+
.replace(/sqrt\(/g, "Math.sqrt(")
|
|
65
|
+
.replace(/abs\(/g, "Math.abs(")
|
|
66
|
+
.replace(/\bpi\b/g, "Math.PI")
|
|
67
|
+
.replace(/\be\b/g, "Math.E");
|
|
68
|
+
|
|
69
|
+
const result = Function(`"use strict"; return (${jsExpr})`)();
|
|
70
|
+
if (typeof result === "number" && !isNaN(result) && isFinite(result)) {
|
|
71
|
+
return {
|
|
72
|
+
text: [
|
|
73
|
+
"🤖 [LOCAL MATH SOLVER]",
|
|
74
|
+
` Expression: ${expression}`,
|
|
75
|
+
` Result: ${Number(result.toFixed(6))}`,
|
|
76
|
+
].join("\n"),
|
|
77
|
+
type: "local-math",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
} catch {
|
|
81
|
+
// Not a valid expression
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Starts a mainframe security bypass mini-game.
|
|
88
|
+
* @returns {{ text: string, type: string }}
|
|
89
|
+
*/
|
|
90
|
+
export function runMainframeHack() {
|
|
91
|
+
return {
|
|
92
|
+
text: [
|
|
93
|
+
"⚡ [LOCAL TERMINAL SECURITY BYPASS GAME]",
|
|
94
|
+
" MAINFRAME HACK PROTOCOL LOADED.",
|
|
95
|
+
" ────────────────────────────────────────",
|
|
96
|
+
" Objective: Bypass security by guessing the 4-digit PIN (digits 0-9).",
|
|
97
|
+
" For each guess, you will get feedback:",
|
|
98
|
+
" • 'Hit' - correct digit in correct position.",
|
|
99
|
+
" • 'Close' - correct digit but in wrong position.",
|
|
100
|
+
" You have 6 attempts before security lock-out.",
|
|
101
|
+
" ",
|
|
102
|
+
" Type `/guess <number>` to input breach code (e.g. /guess 2941)",
|
|
103
|
+
" To abort, type `/abort`.",
|
|
104
|
+
" ────────────────────────────────────────",
|
|
105
|
+
].join("\n"),
|
|
106
|
+
type: "mainframe-game",
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Generates a local Krylo companion reply based on keywords.
|
|
112
|
+
* @param {string} prompt - The user prompt
|
|
113
|
+
* @returns {{ text: string, type: string }}
|
|
114
|
+
*/
|
|
115
|
+
export function generateKryloReply(prompt) {
|
|
116
|
+
const clean = prompt.toLowerCase();
|
|
117
|
+
|
|
118
|
+
if (clean.includes("help") || clean.includes("shortcut") || clean.includes("command")) {
|
|
119
|
+
return {
|
|
120
|
+
text: [
|
|
121
|
+
"💡 [SYSTEM DECK CHEAT SHEET]",
|
|
122
|
+
" • Use `Ctrl + K` to open the Portal Search.",
|
|
123
|
+
" • Use `Ctrl + Shift + L` to open the Links Directory.",
|
|
124
|
+
" • Trigger Konami Code `↑↑↓↓←→←→BA` to launch Matrix mode!",
|
|
125
|
+
" • Type `/mode <name>` to switch reasoning modes.",
|
|
126
|
+
" • Type `/attach <file>` to inject file context.",
|
|
127
|
+
" • Type `/export` to save the conversation.",
|
|
128
|
+
].join("\n"),
|
|
129
|
+
type: "krylo-local",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (clean.includes("status") || clean.includes("hud") || clean.includes("cpu") || clean.includes("ping") || clean.includes("diagnostics")) {
|
|
134
|
+
return {
|
|
135
|
+
text: [
|
|
136
|
+
"📊 [LIVE DIAGNOSTIC READOUT]",
|
|
137
|
+
" • CPU Core Load: 15.4% (Optimized)",
|
|
138
|
+
" • Ping Latency: 12ms (Hyper-Fast)",
|
|
139
|
+
" • Memory Usage: 247MB / 8192MB",
|
|
140
|
+
" • Canvas Sparklines: Active and tracking vectors",
|
|
141
|
+
" • Failover Mesh: All 12 nodes standing by",
|
|
142
|
+
].join("\n"),
|
|
143
|
+
type: "krylo-local",
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (clean.includes("matrix") || clean.includes("rain") || clean.includes("color")) {
|
|
148
|
+
return {
|
|
149
|
+
text: [
|
|
150
|
+
"⚡ [NEURAL GRIDS MODULATION]",
|
|
151
|
+
" • Five stream channels active:",
|
|
152
|
+
" Classic Green, Cyber Cyan, Neon Purple,",
|
|
153
|
+
" Overdrive Red, Golden Matrix.",
|
|
154
|
+
" • Detuned Web Audio frequency active.",
|
|
155
|
+
" • Matrix rain density: 94.2%",
|
|
156
|
+
].join("\n"),
|
|
157
|
+
type: "krylo-local",
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (clean.includes("who") || clean.includes("name") || clean.includes("creator")) {
|
|
162
|
+
return {
|
|
163
|
+
text: [
|
|
164
|
+
"🤖 [HOLOGRAPHIC COMPANION PROTOCOL]",
|
|
165
|
+
" • Identification: Krylo (Nexus Companion)",
|
|
166
|
+
" • Purpose: Pair-programming assistant & Commander companion",
|
|
167
|
+
" • Creator: Krishiv PB — The Master Coder",
|
|
168
|
+
" • Version: Aether Core AI v110 — Fusion Build",
|
|
169
|
+
].join("\n"),
|
|
170
|
+
type: "krylo-local",
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const index = Math.floor(Math.random() * KRYLO_REPLIES.length);
|
|
175
|
+
return {
|
|
176
|
+
text: `🤖 [KRYLO TERMINAL RESPONSE]\n ${KRYLO_REPLIES[index]}`,
|
|
177
|
+
type: "krylo-local",
|
|
178
|
+
};
|
|
179
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════
|
|
2
|
+
// AETHER AI CLI — Google Gemini API Provider
|
|
3
|
+
// ═══════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
const BASE_URL = "https://generativelanguage.googleapis.com/v1beta/models";
|
|
6
|
+
const MAX_CONTINUATIONS = 3;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Sends a prompt to the Google Gemini API.
|
|
10
|
+
* Handles continuation if finishReason is MAX_TOKENS.
|
|
11
|
+
* @param {string} prompt - The user message
|
|
12
|
+
* @param {string} systemPrompt - System prompt for the mode
|
|
13
|
+
* @param {string} apiKey - Google API key
|
|
14
|
+
* @param {string} [model='gemini-2.5-flash'] - Model name
|
|
15
|
+
* @returns {Promise<{ text: string, provider: string, model: string }>}
|
|
16
|
+
*/
|
|
17
|
+
export async function callGemini(prompt, systemPrompt, apiKey, model = "gemini-2.5-flash") {
|
|
18
|
+
let fullText = "";
|
|
19
|
+
let currentPrompt = prompt;
|
|
20
|
+
let continuations = 0;
|
|
21
|
+
|
|
22
|
+
while (continuations <= MAX_CONTINUATIONS) {
|
|
23
|
+
const url = `${BASE_URL}/${model}:generateContent?key=${apiKey}`;
|
|
24
|
+
|
|
25
|
+
const body = {
|
|
26
|
+
systemInstruction: {
|
|
27
|
+
parts: [{ text: systemPrompt }],
|
|
28
|
+
},
|
|
29
|
+
contents: [
|
|
30
|
+
{
|
|
31
|
+
role: "user",
|
|
32
|
+
parts: [{ text: currentPrompt }],
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
generationConfig: {
|
|
36
|
+
temperature: 0.7,
|
|
37
|
+
maxOutputTokens: 8192,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const response = await fetch(url, {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: { "Content-Type": "application/json" },
|
|
44
|
+
body: JSON.stringify(body),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
const errorBody = await response.text().catch(() => "");
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Gemini API error (${response.status}): ${response.statusText}. ${errorBody}`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const data = await response.json();
|
|
55
|
+
|
|
56
|
+
// Check for blocked content
|
|
57
|
+
if (data.promptFeedback?.blockReason) {
|
|
58
|
+
throw new Error(`Content blocked: ${data.promptFeedback.blockReason}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const candidate = data.candidates?.[0];
|
|
62
|
+
if (!candidate) {
|
|
63
|
+
throw new Error("Gemini API returned no candidates");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const chunkText = candidate.content?.parts
|
|
67
|
+
?.map((p) => p.text)
|
|
68
|
+
.filter(Boolean)
|
|
69
|
+
.join("") || "";
|
|
70
|
+
|
|
71
|
+
fullText += chunkText;
|
|
72
|
+
|
|
73
|
+
// Check if the response was cut short
|
|
74
|
+
if (candidate.finishReason === "MAX_TOKENS" && continuations < MAX_CONTINUATIONS) {
|
|
75
|
+
continuations++;
|
|
76
|
+
currentPrompt = "Continue your previous response from exactly where you left off.";
|
|
77
|
+
} else {
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!fullText.trim()) {
|
|
83
|
+
throw new Error("Gemini API returned empty response");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return { text: fullText, provider: "google", model };
|
|
87
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════
|
|
2
|
+
// AETHER AI CLI — Universal Provider Registry
|
|
3
|
+
// Supports ANY OpenAI-compatible API + custom providers
|
|
4
|
+
// ═══════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Registry of all supported AI providers.
|
|
8
|
+
* Each provider defines its API format, base URL, default model, and pricing tier.
|
|
9
|
+
*
|
|
10
|
+
* Providers with `format: "openai"` use the standard /v1/chat/completions endpoint.
|
|
11
|
+
* Providers with `format: "custom"` have their own handler in dedicated files.
|
|
12
|
+
*/
|
|
13
|
+
export const PROVIDERS = {
|
|
14
|
+
// ── Free Tier Providers ─────────────────────────────────
|
|
15
|
+
groq: {
|
|
16
|
+
name: "Groq",
|
|
17
|
+
key: "GROQ_API_KEY",
|
|
18
|
+
format: "openai",
|
|
19
|
+
baseUrl: "https://api.groq.com/openai/v1/chat/completions",
|
|
20
|
+
defaultModel: "llama-3.3-70b-versatile",
|
|
21
|
+
models: ["llama-3.3-70b-versatile", "llama-3.1-8b-instant", "mixtral-8x7b-32768", "gemma2-9b-it"],
|
|
22
|
+
tier: "free",
|
|
23
|
+
description: "Ultra-fast inference on Llama, Mixtral, Gemma (generous free tier)",
|
|
24
|
+
},
|
|
25
|
+
together: {
|
|
26
|
+
name: "Together AI",
|
|
27
|
+
key: "TOGETHER_API_KEY",
|
|
28
|
+
format: "openai",
|
|
29
|
+
baseUrl: "https://api.together.xyz/v1/chat/completions",
|
|
30
|
+
defaultModel: "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
|
|
31
|
+
models: ["meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo", "mistralai/Mixtral-8x7B-Instruct-v0.1", "Qwen/Qwen2.5-72B-Instruct-Turbo"],
|
|
32
|
+
tier: "free",
|
|
33
|
+
description: "Open-source models with free credits on signup",
|
|
34
|
+
},
|
|
35
|
+
cerebras: {
|
|
36
|
+
name: "Cerebras",
|
|
37
|
+
key: "CEREBRAS_API_KEY",
|
|
38
|
+
format: "openai",
|
|
39
|
+
baseUrl: "https://api.cerebras.ai/v1/chat/completions",
|
|
40
|
+
defaultModel: "llama-3.3-70b",
|
|
41
|
+
models: ["llama-3.3-70b", "llama-3.1-8b"],
|
|
42
|
+
tier: "free",
|
|
43
|
+
description: "Fastest inference engine — free tier available",
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// ── OpenAI-Compatible Providers ─────────────────────────
|
|
47
|
+
openai: {
|
|
48
|
+
name: "OpenAI",
|
|
49
|
+
key: "OPENAI_API_KEY",
|
|
50
|
+
format: "openai",
|
|
51
|
+
baseUrl: "https://api.openai.com/v1/chat/completions",
|
|
52
|
+
defaultModel: "gpt-4o",
|
|
53
|
+
models: ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "gpt-3.5-turbo", "o1-mini", "o3-mini"],
|
|
54
|
+
tier: "paid",
|
|
55
|
+
description: "GPT-4o, GPT-4, o1 reasoning models",
|
|
56
|
+
},
|
|
57
|
+
mistral: {
|
|
58
|
+
name: "Mistral AI",
|
|
59
|
+
key: "MISTRAL_API_KEY",
|
|
60
|
+
format: "openai",
|
|
61
|
+
baseUrl: "https://api.mistral.ai/v1/chat/completions",
|
|
62
|
+
defaultModel: "mistral-large-latest",
|
|
63
|
+
models: ["mistral-large-latest", "mistral-medium-latest", "mistral-small-latest", "open-mistral-nemo"],
|
|
64
|
+
tier: "paid",
|
|
65
|
+
description: "Mistral Large, Medium, and open-weight models",
|
|
66
|
+
},
|
|
67
|
+
fireworks: {
|
|
68
|
+
name: "Fireworks AI",
|
|
69
|
+
key: "FIREWORKS_API_KEY",
|
|
70
|
+
format: "openai",
|
|
71
|
+
baseUrl: "https://api.fireworks.ai/inference/v1/chat/completions",
|
|
72
|
+
defaultModel: "accounts/fireworks/models/llama-v3p1-70b-instruct",
|
|
73
|
+
models: ["accounts/fireworks/models/llama-v3p1-70b-instruct", "accounts/fireworks/models/mixtral-8x7b-instruct"],
|
|
74
|
+
tier: "free",
|
|
75
|
+
description: "Fast open-source model hosting with free tier",
|
|
76
|
+
},
|
|
77
|
+
openrouter: {
|
|
78
|
+
name: "OpenRouter",
|
|
79
|
+
key: "OPENROUTER_API_KEY",
|
|
80
|
+
format: "openai",
|
|
81
|
+
baseUrl: "https://openrouter.ai/api/v1/chat/completions",
|
|
82
|
+
defaultModel: "meta-llama/llama-3.1-70b-instruct:free",
|
|
83
|
+
models: ["meta-llama/llama-3.1-70b-instruct:free", "google/gemini-2.5-flash-preview:free", "mistralai/mistral-7b-instruct:free", "anthropic/claude-sonnet-4", "openai/gpt-4o"],
|
|
84
|
+
tier: "free+paid",
|
|
85
|
+
description: "Gateway to 200+ models — many free options available",
|
|
86
|
+
},
|
|
87
|
+
deepseek: {
|
|
88
|
+
name: "DeepSeek",
|
|
89
|
+
key: "DEEPSEEK_API_KEY",
|
|
90
|
+
format: "openai",
|
|
91
|
+
baseUrl: "https://api.deepseek.com/v1/chat/completions",
|
|
92
|
+
defaultModel: "deepseek-chat",
|
|
93
|
+
models: ["deepseek-chat", "deepseek-reasoner"],
|
|
94
|
+
tier: "paid",
|
|
95
|
+
description: "DeepSeek-V3 and R1 reasoning model",
|
|
96
|
+
},
|
|
97
|
+
perplexity: {
|
|
98
|
+
name: "Perplexity",
|
|
99
|
+
key: "PERPLEXITY_API_KEY",
|
|
100
|
+
format: "openai",
|
|
101
|
+
baseUrl: "https://api.perplexity.ai/chat/completions",
|
|
102
|
+
defaultModel: "sonar",
|
|
103
|
+
models: ["sonar", "sonar-pro", "sonar-reasoning"],
|
|
104
|
+
tier: "paid",
|
|
105
|
+
description: "Search-augmented AI with real-time web access",
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
// ── Custom Format Providers ─────────────────────────────
|
|
109
|
+
xai: {
|
|
110
|
+
name: "xAI Grok",
|
|
111
|
+
key: "XAI_API_KEY",
|
|
112
|
+
format: "openai",
|
|
113
|
+
baseUrl: "https://api.x.ai/v1/chat/completions",
|
|
114
|
+
defaultModel: "grok-2",
|
|
115
|
+
models: ["grok-2", "grok-2-mini"],
|
|
116
|
+
tier: "paid",
|
|
117
|
+
description: "Grok-2 by xAI — witty, uncensored",
|
|
118
|
+
},
|
|
119
|
+
google: {
|
|
120
|
+
name: "Google Gemini",
|
|
121
|
+
key: "GOOGLE_API_KEY",
|
|
122
|
+
format: "custom-google",
|
|
123
|
+
baseUrl: "https://generativelanguage.googleapis.com/v1beta/models",
|
|
124
|
+
defaultModel: "gemini-2.5-flash",
|
|
125
|
+
models: ["gemini-2.5-flash", "gemini-2.5-pro", "gemini-2.0-flash", "gemini-1.5-pro"],
|
|
126
|
+
tier: "free+paid",
|
|
127
|
+
description: "Gemini 2.5 Flash/Pro — free tier with generous limits",
|
|
128
|
+
},
|
|
129
|
+
anthropic: {
|
|
130
|
+
name: "Anthropic Claude",
|
|
131
|
+
key: "ANTHROPIC_API_KEY",
|
|
132
|
+
format: "custom-anthropic",
|
|
133
|
+
baseUrl: "https://api.anthropic.com/v1/messages",
|
|
134
|
+
defaultModel: "claude-sonnet-4-20250514",
|
|
135
|
+
models: ["claude-sonnet-4-20250514", "claude-3-5-haiku-20241022", "claude-3-5-sonnet-20241022"],
|
|
136
|
+
tier: "paid",
|
|
137
|
+
description: "Claude Sonnet 4, Haiku — premium reasoning",
|
|
138
|
+
},
|
|
139
|
+
cohere: {
|
|
140
|
+
name: "Cohere",
|
|
141
|
+
key: "COHERE_API_KEY",
|
|
142
|
+
format: "custom-cohere",
|
|
143
|
+
baseUrl: "https://api.cohere.com/v2/chat",
|
|
144
|
+
defaultModel: "command-r-plus",
|
|
145
|
+
models: ["command-r-plus", "command-r", "command-light"],
|
|
146
|
+
tier: "free+paid",
|
|
147
|
+
description: "Command R+ — free tier for developers",
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Gets a flat list of all provider keys that can be configured.
|
|
153
|
+
* @returns {string[]}
|
|
154
|
+
*/
|
|
155
|
+
export function getAllConfigKeys() {
|
|
156
|
+
const keys = new Set();
|
|
157
|
+
for (const p of Object.values(PROVIDERS)) {
|
|
158
|
+
keys.add(p.key);
|
|
159
|
+
}
|
|
160
|
+
// Also include multi-key support for Google
|
|
161
|
+
keys.add("GOOGLE_API_KEYS");
|
|
162
|
+
return [...keys];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Gets a provider by its config key name (e.g., "OPENAI_API_KEY" → openai provider).
|
|
167
|
+
* @param {string} configKey
|
|
168
|
+
* @returns {object|null}
|
|
169
|
+
*/
|
|
170
|
+
export function getProviderByKey(configKey) {
|
|
171
|
+
for (const [id, p] of Object.entries(PROVIDERS)) {
|
|
172
|
+
if (p.key === configKey) return { id, ...p };
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Gets all providers that have a valid key in the given config.
|
|
179
|
+
* @param {object} config - Config object with API keys
|
|
180
|
+
* @returns {Array<{ id: string, provider: object, apiKey: string }>}
|
|
181
|
+
*/
|
|
182
|
+
export function getActiveProviders(config) {
|
|
183
|
+
const active = [];
|
|
184
|
+
for (const [id, provider] of Object.entries(PROVIDERS)) {
|
|
185
|
+
const apiKey = config[provider.key];
|
|
186
|
+
if (apiKey) {
|
|
187
|
+
active.push({ id, provider, apiKey });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return active;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Groups providers by pricing tier for display.
|
|
195
|
+
* @returns {{ free: object[], paid: object[], mixed: object[] }}
|
|
196
|
+
*/
|
|
197
|
+
export function getProvidersByTier() {
|
|
198
|
+
const result = { free: [], "free+paid": [], paid: [] };
|
|
199
|
+
for (const [id, provider] of Object.entries(PROVIDERS)) {
|
|
200
|
+
result[provider.tier]?.push({ id, ...provider });
|
|
201
|
+
}
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════
|
|
2
|
+
// AETHER AI CLI — Universal AI Router with Failover Mesh
|
|
3
|
+
// Routes through ALL configured providers automatically
|
|
4
|
+
// ═══════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
import { detectMathExpression, solveMath, generateKryloReply } from "./fallback.js";
|
|
7
|
+
import { PROVIDERS, getActiveProviders } from "./providers.js";
|
|
8
|
+
import {
|
|
9
|
+
callOpenAICompatible,
|
|
10
|
+
callGoogleGemini,
|
|
11
|
+
callAnthropic,
|
|
12
|
+
callCohere,
|
|
13
|
+
} from "./universal.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Routes a prompt through the universal AI failover mesh.
|
|
17
|
+
*
|
|
18
|
+
* Priority Order:
|
|
19
|
+
* 1. Local math solver (if pure math expression)
|
|
20
|
+
* 2. All configured providers, in order of priority
|
|
21
|
+
* 3. Google key rotation (if multiple keys)
|
|
22
|
+
* 4. Krylo companion fallback (if everything else fails)
|
|
23
|
+
*
|
|
24
|
+
* @param {string} prompt - The user prompt
|
|
25
|
+
* @param {string} systemPrompt - The mode system prompt
|
|
26
|
+
* @param {object} config - Flat config object with all API keys
|
|
27
|
+
* @returns {Promise<{ text: string, provider: string, model?: string, node: number, type?: string }>}
|
|
28
|
+
*/
|
|
29
|
+
export async function routePrompt(prompt, systemPrompt, config, onToken, history = []) {
|
|
30
|
+
// ── Node 0: Local Math Solver ───────────────────────────
|
|
31
|
+
const mathExpr = detectMathExpression(prompt);
|
|
32
|
+
if (mathExpr) {
|
|
33
|
+
const mathResult = solveMath(mathExpr);
|
|
34
|
+
if (mathResult) {
|
|
35
|
+
return { ...mathResult, provider: "local", node: 0 };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── Gather all active providers ─────────────────────────
|
|
40
|
+
const active = getActiveProviders(config);
|
|
41
|
+
|
|
42
|
+
// Add extra Google keys for rotation
|
|
43
|
+
const googleExtraKeys = config.GOOGLE_API_KEYS;
|
|
44
|
+
if (googleExtraKeys) {
|
|
45
|
+
const extras = googleExtraKeys.split(",").map((k) => k.trim()).filter(Boolean);
|
|
46
|
+
for (const key of extras) {
|
|
47
|
+
// Avoid duplicates
|
|
48
|
+
if (!active.some((a) => a.id === "google" && a.apiKey === key)) {
|
|
49
|
+
active.push({ id: "google-extra", provider: PROVIDERS.google, apiKey: key });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── No providers configured → Krylo ────────────────────
|
|
55
|
+
if (active.length === 0) {
|
|
56
|
+
const kryloReply = generateKryloReply(prompt);
|
|
57
|
+
return { ...kryloReply, provider: "krylo-fallback", node: 0 };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── Try each provider in order ──────────────────────────
|
|
61
|
+
const errors = [];
|
|
62
|
+
let nodeIndex = 1;
|
|
63
|
+
|
|
64
|
+
for (const { id, provider, apiKey } of active) {
|
|
65
|
+
try {
|
|
66
|
+
const model = config[`${id.toUpperCase()}_MODEL`] || provider.defaultModel;
|
|
67
|
+
let result;
|
|
68
|
+
|
|
69
|
+
switch (provider.format) {
|
|
70
|
+
case "openai":
|
|
71
|
+
result = await callOpenAICompatible(
|
|
72
|
+
prompt, systemPrompt, apiKey,
|
|
73
|
+
provider.baseUrl, model, provider.name,
|
|
74
|
+
onToken, history
|
|
75
|
+
);
|
|
76
|
+
break;
|
|
77
|
+
|
|
78
|
+
case "custom-google":
|
|
79
|
+
result = await callGoogleGemini(prompt, systemPrompt, apiKey, model, onToken, history);
|
|
80
|
+
break;
|
|
81
|
+
|
|
82
|
+
case "custom-anthropic":
|
|
83
|
+
result = await callAnthropic(prompt, systemPrompt, apiKey, model, onToken, history);
|
|
84
|
+
break;
|
|
85
|
+
|
|
86
|
+
case "custom-cohere":
|
|
87
|
+
result = await callCohere(prompt, systemPrompt, apiKey, model, onToken, history);
|
|
88
|
+
break;
|
|
89
|
+
|
|
90
|
+
default:
|
|
91
|
+
// Treat unknown formats as OpenAI-compatible
|
|
92
|
+
result = await callOpenAICompatible(
|
|
93
|
+
prompt, systemPrompt, apiKey,
|
|
94
|
+
provider.baseUrl, model, provider.name,
|
|
95
|
+
onToken, history
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { ...result, node: nodeIndex };
|
|
100
|
+
} catch (err) {
|
|
101
|
+
errors.push(`[Node ${nodeIndex} ${provider.name}] ${err.message}`);
|
|
102
|
+
nodeIndex++;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ── Final Fallback: Krylo Companion ─────────────────────
|
|
107
|
+
const kryloReply = generateKryloReply(prompt);
|
|
108
|
+
return {
|
|
109
|
+
...kryloReply,
|
|
110
|
+
provider: "krylo-fallback",
|
|
111
|
+
node: 0,
|
|
112
|
+
errors,
|
|
113
|
+
};
|
|
114
|
+
}
|