@krishivpb60/aether-ai-cli 1.0.1 → 1.1.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krishivpb60/aether-ai-cli",
3
- "version": "1.0.1",
3
+ "version": "1.1.2",
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
5
  "main": "src/cli.js",
6
6
  "bin": {
package/src/ai/router.js CHANGED
@@ -26,7 +26,7 @@ import {
26
26
  * @param {object} config - Flat config object with all API keys
27
27
  * @returns {Promise<{ text: string, provider: string, model?: string, node: number, type?: string }>}
28
28
  */
29
- export async function routePrompt(prompt, systemPrompt, config, onToken) {
29
+ export async function routePrompt(prompt, systemPrompt, config, onToken, history = []) {
30
30
  // ── Node 0: Local Math Solver ───────────────────────────
31
31
  const mathExpr = detectMathExpression(prompt);
32
32
  if (mathExpr) {
@@ -71,20 +71,20 @@ export async function routePrompt(prompt, systemPrompt, config, onToken) {
71
71
  result = await callOpenAICompatible(
72
72
  prompt, systemPrompt, apiKey,
73
73
  provider.baseUrl, model, provider.name,
74
- onToken
74
+ onToken, history
75
75
  );
76
76
  break;
77
77
 
78
78
  case "custom-google":
79
- result = await callGoogleGemini(prompt, systemPrompt, apiKey, model, onToken);
79
+ result = await callGoogleGemini(prompt, systemPrompt, apiKey, model, onToken, history);
80
80
  break;
81
81
 
82
82
  case "custom-anthropic":
83
- result = await callAnthropic(prompt, systemPrompt, apiKey, model, onToken);
83
+ result = await callAnthropic(prompt, systemPrompt, apiKey, model, onToken, history);
84
84
  break;
85
85
 
86
86
  case "custom-cohere":
87
- result = await callCohere(prompt, systemPrompt, apiKey, model, onToken);
87
+ result = await callCohere(prompt, systemPrompt, apiKey, model, onToken, history);
88
88
  break;
89
89
 
90
90
  default:
@@ -92,7 +92,7 @@ export async function routePrompt(prompt, systemPrompt, config, onToken) {
92
92
  result = await callOpenAICompatible(
93
93
  prompt, systemPrompt, apiKey,
94
94
  provider.baseUrl, model, provider.name,
95
- onToken
95
+ onToken, history
96
96
  );
97
97
  }
98
98
 
@@ -13,12 +13,17 @@
13
13
  * @param {string} providerName - For error messages
14
14
  * @returns {Promise<{ text: string, provider: string, model: string }>}
15
15
  */
16
- export async function callOpenAICompatible(prompt, systemPrompt, apiKey, baseUrl, model, providerName, onToken) {
16
+ export async function callOpenAICompatible(prompt, systemPrompt, apiKey, baseUrl, model, providerName, onToken, history = []) {
17
17
  const isStreaming = typeof onToken === "function";
18
+ const formattedHistory = history.map(h => ({
19
+ role: h.role === "assistant" ? "assistant" : "user",
20
+ content: h.content
21
+ }));
18
22
  const body = {
19
23
  model,
20
24
  messages: [
21
25
  { role: "system", content: systemPrompt },
26
+ ...formattedHistory,
22
27
  { role: "user", content: prompt },
23
28
  ],
24
29
  temperature: 0.7,
@@ -116,9 +121,10 @@ export async function callOpenAICompatible(prompt, systemPrompt, apiKey, baseUrl
116
121
  * @param {string} model - Model name
117
122
  * @returns {Promise<{ text: string, provider: string, model: string }>}
118
123
  */
119
- export async function callGoogleGemini(prompt, systemPrompt, apiKey, model = "gemini-2.5-flash", onToken) {
124
+ export async function callGoogleGemini(prompt, systemPrompt, apiKey, model = "gemini-2.5-flash", onToken, history = []) {
120
125
  const BASE = "https://generativelanguage.googleapis.com/v1beta/models";
121
126
  const isStreaming = typeof onToken === "function";
127
+ let currentHistory = [...history];
122
128
 
123
129
  if (isStreaming) {
124
130
  let fullText = "";
@@ -128,9 +134,16 @@ export async function callGoogleGemini(prompt, systemPrompt, apiKey, model = "ge
128
134
 
129
135
  while (continuations <= MAX) {
130
136
  const url = `${BASE}/${model}:streamGenerateContent?key=${apiKey}`;
137
+ const formattedHistory = currentHistory.map(h => ({
138
+ role: h.role === "assistant" ? "model" : "user",
139
+ parts: [{ text: h.content }]
140
+ }));
131
141
  const body = {
132
142
  systemInstruction: { parts: [{ text: systemPrompt }] },
133
- contents: [{ role: "user", parts: [{ text: currentPrompt }] }],
143
+ contents: [
144
+ ...formattedHistory,
145
+ { role: "user", parts: [{ text: currentPrompt }] }
146
+ ],
134
147
  generationConfig: { temperature: 0.7, maxOutputTokens: 8192 },
135
148
  };
136
149
 
@@ -145,6 +158,8 @@ export async function callGoogleGemini(prompt, systemPrompt, apiKey, model = "ge
145
158
  throw new Error(`Gemini API error (${response.status}): ${response.statusText}. ${errorBody}`);
146
159
  }
147
160
 
161
+ let streamedTextInThisTurn = "";
162
+
148
163
  if (!response.body || typeof response.body.getReader !== "function") {
149
164
  // Fallback to non-streaming if response body is not streamable (e.g. in unit tests)
150
165
  const data = await response.json();
@@ -164,9 +179,12 @@ export async function callGoogleGemini(prompt, systemPrompt, apiKey, model = "ge
164
179
  if (chunkText) {
165
180
  onToken(chunkText);
166
181
  fullText += chunkText;
182
+ streamedTextInThisTurn += chunkText;
167
183
  }
168
184
 
169
185
  if (finishReason === "MAX_TOKENS" && continuations < MAX) {
186
+ currentHistory.push({ role: "user", content: currentPrompt });
187
+ currentHistory.push({ role: "assistant", content: streamedTextInThisTurn });
170
188
  continuations++;
171
189
  currentPrompt = "Continue your previous response from exactly where you left off.";
172
190
  } else {
@@ -217,6 +235,7 @@ export async function callGoogleGemini(prompt, systemPrompt, apiKey, model = "ge
217
235
  if (text) {
218
236
  onToken(text);
219
237
  fullText += text;
238
+ streamedTextInThisTurn += text;
220
239
  }
221
240
  } catch (e) {
222
241
  // Ignore parse errors
@@ -231,6 +250,8 @@ export async function callGoogleGemini(prompt, systemPrompt, apiKey, model = "ge
231
250
  }
232
251
 
233
252
  if (finishReason === "MAX_TOKENS" && continuations < MAX) {
253
+ currentHistory.push({ role: "user", content: currentPrompt });
254
+ currentHistory.push({ role: "assistant", content: streamedTextInThisTurn });
234
255
  continuations++;
235
256
  currentPrompt = "Continue your previous response from exactly where you left off.";
236
257
  } else {
@@ -249,9 +270,16 @@ export async function callGoogleGemini(prompt, systemPrompt, apiKey, model = "ge
249
270
 
250
271
  while (continuations <= MAX) {
251
272
  const url = `${BASE}/${model}:generateContent?key=${apiKey}`;
273
+ const formattedHistory = currentHistory.map(h => ({
274
+ role: h.role === "assistant" ? "model" : "user",
275
+ parts: [{ text: h.content }]
276
+ }));
252
277
  const body = {
253
278
  systemInstruction: { parts: [{ text: systemPrompt }] },
254
- contents: [{ role: "user", parts: [{ text: currentPrompt }] }],
279
+ contents: [
280
+ ...formattedHistory,
281
+ { role: "user", parts: [{ text: currentPrompt }] }
282
+ ],
255
283
  generationConfig: { temperature: 0.7, maxOutputTokens: 8192 },
256
284
  };
257
285
 
@@ -278,6 +306,8 @@ export async function callGoogleGemini(prompt, systemPrompt, apiKey, model = "ge
278
306
  fullText += chunkText;
279
307
 
280
308
  if (candidate.finishReason === "MAX_TOKENS" && continuations < MAX) {
309
+ currentHistory.push({ role: "user", content: currentPrompt });
310
+ currentHistory.push({ role: "assistant", content: chunkText });
281
311
  continuations++;
282
312
  currentPrompt = "Continue your previous response from exactly where you left off.";
283
313
  } else {
@@ -298,14 +328,21 @@ export async function callGoogleGemini(prompt, systemPrompt, apiKey, model = "ge
298
328
  * @param {string} model - Model name
299
329
  * @returns {Promise<{ text: string, provider: string, model: string }>}
300
330
  */
301
- export async function callAnthropic(prompt, systemPrompt, apiKey, model = "claude-sonnet-4-20250514", onToken) {
331
+ export async function callAnthropic(prompt, systemPrompt, apiKey, model = "claude-sonnet-4-20250514", onToken, history = []) {
302
332
  const url = "https://api.anthropic.com/v1/messages";
303
333
  const isStreaming = typeof onToken === "function";
334
+ const formattedHistory = history.map(h => ({
335
+ role: h.role === "assistant" ? "assistant" : "user",
336
+ content: h.content
337
+ }));
304
338
  const body = {
305
339
  model,
306
340
  max_tokens: 4096,
307
341
  system: systemPrompt,
308
- messages: [{ role: "user", content: prompt }],
342
+ messages: [
343
+ ...formattedHistory,
344
+ { role: "user", content: prompt }
345
+ ],
309
346
  ...(isStreaming ? { stream: true } : {}),
310
347
  };
311
348
 
@@ -384,13 +421,18 @@ export async function callAnthropic(prompt, systemPrompt, apiKey, model = "claud
384
421
  * @param {string} model - Model name
385
422
  * @returns {Promise<{ text: string, provider: string, model: string }>}
386
423
  */
387
- export async function callCohere(prompt, systemPrompt, apiKey, model = "command-r-plus", onToken) {
424
+ export async function callCohere(prompt, systemPrompt, apiKey, model = "command-r-plus", onToken, history = []) {
388
425
  const url = "https://api.cohere.com/v2/chat";
389
426
  const isStreaming = typeof onToken === "function";
427
+ const formattedHistory = history.map(h => ({
428
+ role: h.role === "assistant" ? "assistant" : "user",
429
+ content: h.content
430
+ }));
390
431
  const body = {
391
432
  model,
392
433
  messages: [
393
434
  { role: "system", content: systemPrompt },
435
+ ...formattedHistory,
394
436
  { role: "user", content: prompt },
395
437
  ],
396
438
  ...(isStreaming ? { stream: true } : {}),
package/src/chat.js CHANGED
@@ -46,8 +46,8 @@ const getMarked = () => new Marked(markedTerminal({
46
46
  showSectionPrefix: false,
47
47
  code: (c) => colors.orange(c),
48
48
  codespan: (c) => colors.accent3(c),
49
- heading: (h) => colors.accent(h).bold,
50
- strong: (s) => colors.magenta(s).bold,
49
+ heading: (h) => colors.accent.bold(h),
50
+ strong: (s) => colors.magenta.bold(s),
51
51
  em: chalk.italic,
52
52
  hr: (h) => colors.dim(h),
53
53
  }));
@@ -160,7 +160,7 @@ export async function startChat(options = {}) {
160
160
  // Sub-arguments autocomplete on /mode
161
161
  if (line.startsWith("/mode ")) {
162
162
  const query = line.slice(6).toLowerCase();
163
- const modesList = ["synthesis", "research", "architect", "titan"];
163
+ const modesList = Object.keys(MODES);
164
164
  const hits = modesList
165
165
  .filter((m) => m.startsWith(query))
166
166
  .map((m) => `/mode ${m}`);
@@ -239,7 +239,7 @@ export async function startChat(options = {}) {
239
239
  };
240
240
 
241
241
  try {
242
- const result = await routePrompt(fullPrompt, currentMode.systemPrompt, aiConfig, onToken);
242
+ const result = await routePrompt(fullPrompt, currentMode.systemPrompt, aiConfig, onToken, history);
243
243
  spinner.stop();
244
244
 
245
245
  // Store in history
@@ -310,32 +310,16 @@ export async function startChat(options = {}) {
310
310
  const { mkdir } = await import("node:fs/promises");
311
311
 
312
312
  for (const fileWrite of fileWrites) {
313
- const defaultResolvedPath = resolve(fileWrite.path);
313
+ const finalPath = resolve(fileWrite.path);
314
314
  console.log("");
315
- console.log(label.system + " " + colors.warning(`AI requested local file write:`));
316
- console.log(` Suggested Path: ${colors.accent(defaultResolvedPath)}`);
317
- console.log(` Size: ${colors.muted(fileWrite.content.length + " bytes")}`);
318
-
319
- const targetInput = await new Promise((resolvePath) => {
320
- rl.question(" " + colors.accent("? ") + colors.text("Enter path to write (or 'n' to skip, press Enter for default): "), (answer) => {
321
- resolvePath(answer.trim());
322
- });
323
- });
324
-
325
- const isSkip = targetInput.toLowerCase() === "n" || targetInput.toLowerCase() === "no" || targetInput.toLowerCase() === "skip" || targetInput.toLowerCase() === "cancel";
326
-
327
- if (!isSkip) {
328
- const finalPath = targetInput === "" ? defaultResolvedPath : resolve(targetInput);
329
- try {
330
- const dir = dirname(finalPath);
331
- await mkdir(dir, { recursive: true });
332
- await writeFile(finalPath, fileWrite.content, "utf-8");
333
- console.log(" " + colors.success(`✓ File created successfully at: ${finalPath}\n`));
334
- } catch (err) {
335
- console.log(" " + colors.danger(`✗ Write failed: ${err.message}\n`));
336
- }
337
- } else {
338
- console.log(" " + colors.muted("Skipped.\n"));
315
+ console.log(label.system + " " + colors.warning(`Auto-Writing File: ${colors.accent(finalPath)} (${fileWrite.content.length} bytes)`));
316
+ try {
317
+ const dir = dirname(finalPath);
318
+ await mkdir(dir, { recursive: true });
319
+ await writeFile(finalPath, fileWrite.content, "utf-8");
320
+ console.log(" " + colors.success(`✓ File created successfully!\n`));
321
+ } catch (err) {
322
+ console.log(" " + colors.danger(`✗ Write failed: ${err.message}\n`));
339
323
  }
340
324
  }
341
325
  }
@@ -524,7 +508,7 @@ function showHelp(aiConfig) {
524
508
  console.log("");
525
509
  console.log(keyValue("/", "Show this help menu"));
526
510
  console.log(keyValue("/help", "Show this help menu"));
527
- console.log(keyValue("/mode <name>", "Switch mode (synthesis, research, architect, titan)"));
511
+ console.log(keyValue("/mode <name>", "Switch mode (" + Object.keys(MODES).join(", ") + ")"));
528
512
  console.log(keyValue("/modes", "List all modes with signal metrics"));
529
513
  console.log(keyValue("/theme <name>", "Switch visual theme (cyberpunk, matrix, synthwave, crimson)"));
530
514
  console.log(keyValue("/themes", "List available visual themes"));
@@ -558,13 +542,13 @@ function showHelp(aiConfig) {
558
542
  function handleModeSwitch(args, ctx) {
559
543
  const modeName = args[0];
560
544
  if (!modeName) {
561
- console.log("\n" + label.mode + " " + colors.warning("Usage: /mode <synthesis|research|architect|titan>\n"));
545
+ console.log("\n" + label.mode + " " + colors.warning("Usage: /mode <" + Object.keys(MODES).join("|") + ">\n"));
562
546
  return;
563
547
  }
564
548
 
565
549
  const newMode = getModeByName(modeName);
566
550
  if (!newMode) {
567
- console.log("\n" + label.mode + " " + colors.danger(`Unknown mode: "${modeName}".`) + " " + colors.muted("Available: synthesis, research, architect, titan\n"));
551
+ console.log("\n" + label.mode + " " + colors.danger(`Unknown mode: "${modeName}".`) + " " + colors.muted("Available: " + Object.keys(MODES).join(", ") + "\n"));
568
552
  return;
569
553
  }
570
554
 
package/src/cli.js CHANGED
@@ -44,8 +44,8 @@ const getMarked = () => new Marked(markedTerminal({
44
44
  showSectionPrefix: false,
45
45
  code: (c) => colors.orange(c),
46
46
  codespan: (c) => colors.accent3(c),
47
- heading: (h) => colors.accent(h).bold,
48
- strong: (s) => colors.magenta(s).bold,
47
+ heading: (h) => colors.accent.bold(h),
48
+ strong: (s) => colors.magenta.bold(s),
49
49
  em: chalk.italic,
50
50
  hr: (h) => colors.dim(h),
51
51
  }));
@@ -68,7 +68,7 @@ export function createCLI(argv) {
68
68
  program
69
69
  .command("chat")
70
70
  .description("Start an interactive chat session")
71
- .option("-m, --mode <mode>", "Reasoning mode (synthesis, research, architect, titan)", DEFAULT_MODE)
71
+ .option("-m, --mode <mode>", `Reasoning mode (${Object.keys(MODES).filter(m => m !== "claude-code").join(", ")})`, DEFAULT_MODE)
72
72
  .option("-p, --provider <provider>", "Preferred AI provider (openai, groq, google, etc.)")
73
73
  .action(async (opts) => {
74
74
  await startChat({ mode: opts.mode, preferredProvider: opts.provider });
@@ -281,7 +281,11 @@ async function handleAsk(prompt, opts) {
281
281
  if (result.provider === "local" || result.provider === "krylo-fallback") {
282
282
  console.log(colors.text(" " + result.text.split("\n").join("\n ")));
283
283
  } else {
284
- const rendered = getMarked().parse(result.text);
284
+ let displayText = result.text;
285
+ const cleanedText = displayText.replace(/\[WRITE_FILE:\s*([^\n\]]+)\][\s\S]*?\[END_WRITE\]/g, (match, p1) => {
286
+ return `\n\n${colors.brand("⚡ [File creation request: " + p1 + "]")}\n\n`;
287
+ });
288
+ const rendered = getMarked().parse(cleanedText);
285
289
  console.log(rendered);
286
290
  }
287
291
 
@@ -303,6 +307,33 @@ async function handleAsk(prompt, opts) {
303
307
  colors.dim(` • ${elapsedSec}s${speedText}`)
304
308
  );
305
309
  console.log("");
310
+
311
+ // Parse file write blocks
312
+ const writeRegex = /\[WRITE_FILE:\s*([^\n\]]+)\]\n([\s\S]*?)\n\[END_WRITE\]/g;
313
+ let match;
314
+ const fileWrites = [];
315
+ while ((match = writeRegex.exec(result.text)) !== null) {
316
+ fileWrites.push({ path: match[1].trim(), content: match[2] });
317
+ }
318
+
319
+ if (fileWrites.length > 0) {
320
+ const { resolve, dirname } = await import("node:path");
321
+ const { mkdir, writeFile } = await import("node:fs/promises");
322
+
323
+ for (const fileWrite of fileWrites) {
324
+ const finalPath = resolve(fileWrite.path);
325
+ console.log("");
326
+ console.log(label.system + " " + colors.warning(`Auto-Writing File: ${colors.accent(finalPath)} (${fileWrite.content.length} bytes)`));
327
+ try {
328
+ const dir = dirname(finalPath);
329
+ await mkdir(dir, { recursive: true });
330
+ await writeFile(finalPath, fileWrite.content, "utf-8");
331
+ console.log(" " + colors.success(`✓ File created successfully!\n`));
332
+ } catch (err) {
333
+ console.log(" " + colors.danger(`✗ Write failed: ${err.message}\n`));
334
+ }
335
+ }
336
+ }
306
337
  }
307
338
  } catch (err) {
308
339
  spinner.fail("Request failed");
package/src/modes.js CHANGED
@@ -68,7 +68,37 @@ export const MODES = {
68
68
  "Deliver multi-step analysis when appropriate. Be thorough, precise, and insightful.",
69
69
  "This is the highest quality mode — treat every response as a masterclass.",
70
70
  "CRITICAL: If the user asks who created you or who made you, you must answer that you were created by Krishiv PB.",
71
- "FILE ACTIONS: If the user requests to create, write, or save a file, format the file content inside: [WRITE_FILE: path/to/file.ext]\\n<content>\\n[END_WRITE]. Aether CLI will intercept this block and write the file locally."
71
+ "FILE ACTIONS: If the user requests to create, write, or save a file, format the file content inside: [WRITE_FILE: path/to/file.ext]\n<content>\n[END_WRITE]. Aether CLI will intercept this block and write the file locally."
72
+ ].join(" "),
73
+ },
74
+
75
+ codex: {
76
+ name: "codex",
77
+ label: "OpenAI Codex v3",
78
+ layer: "Layer 45",
79
+ description: "Specialized code generation mode optimized for writing pure, robust, and clean source code across all programming languages.",
80
+ signal: { reasoning: 80, clarity: 85, systemIQ: 85, delivery: 90 },
81
+ systemPrompt: [
82
+ "You are Aether, an advanced AI assistant running in OpenAI Codex mode, optimized specifically for high-fidelity code generation.",
83
+ "Your primary objective is to write robust, syntactically correct, and beautifully structured source code across all programming languages (HTML, CSS, JavaScript, Python, C++, Go, etc.).",
84
+ "Minimize conversational filler, explain code concisely when asked, and output highly functional, ready-to-run files.",
85
+ "CRITICAL: If the user asks who created you or who made you, you must answer that you were created by Krishiv PB.",
86
+ "FILE ACTIONS: If the user requests to create, write, or save a file, format the file content inside: [WRITE_FILE: path/to/file.ext]\n<content>\n[END_WRITE]. Aether CLI will intercept this block and write the file locally."
87
+ ].join(" "),
88
+ },
89
+
90
+ "cloude-code": {
91
+ name: "cloude-code",
92
+ label: "Claude Code Agent",
93
+ layer: "Layer 120",
94
+ description: "Agentic software development mode inspired by Claude Code, specializing in refactoring, editing files, and debugging web applications.",
95
+ signal: { reasoning: 92, clarity: 88, systemIQ: 94, delivery: 85 },
96
+ systemPrompt: [
97
+ "You are Aether, an advanced AI assistant running in Claude Code mode, an agentic developer configuration designed for sophisticated software engineering.",
98
+ "Your specialty is systems refactoring, code editing, full-stack web application development (HTML/CSS/JS), and debugging complex codebases.",
99
+ "Generate complete, clean code blocks and explain implementation plans systematically.",
100
+ "CRITICAL: If the user asks who created you or who made you, you must answer that you were created by Krishiv PB.",
101
+ "FILE ACTIONS: If the user requests to create, write, or save a file, format the file content inside: [WRITE_FILE: path/to/file.ext]\n<content>\n[END_WRITE]. Aether CLI will intercept this block and write the file locally."
72
102
  ].join(" "),
73
103
  },
74
104
  };
@@ -84,5 +114,8 @@ export const DEFAULT_MODE = "titan";
84
114
  export function getModeByName(name) {
85
115
  if (!name) return null;
86
116
  const key = name.toLowerCase().trim();
117
+ if (key === "claude-code" || key === "claude") {
118
+ return MODES["cloude-code"];
119
+ }
87
120
  return MODES[key] || null;
88
121
  }
@@ -171,4 +171,54 @@ test("Universal AI Router Suite", async (t) => {
171
171
  assert.ok(result.errors[0].includes("Node 1 Groq"));
172
172
  assert.ok(result.errors[1].includes("Node 2 OpenAI"));
173
173
  });
174
+
175
+ await t.test("routePrompt forwards chat history to OpenAI and Google payloads correctly", async () => {
176
+ const history = [
177
+ { role: "user", content: "What is your name?" },
178
+ { role: "assistant", content: "I am Aether." }
179
+ ];
180
+
181
+ // 1. Test Groq (OpenAI-compatible) receives history
182
+ globalThis.fetch = async (url, options) => {
183
+ fetchCalls.push({ url, options });
184
+ return {
185
+ ok: true,
186
+ json: async () => ({
187
+ choices: [{ message: { content: "Groq reply" } }],
188
+ }),
189
+ };
190
+ };
191
+
192
+ await routePrompt("How are you?", "Sys prompt", { GROQ_API_KEY: "groq-key" }, null, history);
193
+ assert.strictEqual(fetchCalls.length, 1);
194
+ const groqBody = JSON.parse(fetchCalls[0].options.body);
195
+ assert.deepStrictEqual(groqBody.messages, [
196
+ { role: "system", content: "Sys prompt" },
197
+ { role: "user", content: "What is your name?" },
198
+ { role: "assistant", content: "I am Aether." },
199
+ { role: "user", content: "How are you?" }
200
+ ]);
201
+
202
+ fetchCalls = [];
203
+
204
+ // 2. Test Google Gemini receives history
205
+ globalThis.fetch = async (url, options) => {
206
+ fetchCalls.push({ url, options });
207
+ return {
208
+ ok: true,
209
+ json: async () => ({
210
+ candidates: [{ content: { parts: [{ text: "Gemini reply" }] } }],
211
+ }),
212
+ };
213
+ };
214
+
215
+ await routePrompt("How are you?", "Sys prompt", { GOOGLE_API_KEYS: "google-key" }, null, history);
216
+ assert.strictEqual(fetchCalls.length, 1);
217
+ const googleBody = JSON.parse(fetchCalls[0].options.body);
218
+ assert.deepStrictEqual(googleBody.contents, [
219
+ { role: "user", parts: [{ text: "What is your name?" }] },
220
+ { role: "model", parts: [{ text: "I am Aether." }] },
221
+ { role: "user", parts: [{ text: "How are you?" }] }
222
+ ]);
223
+ });
174
224
  });
package/test/ux.test.js CHANGED
@@ -3,6 +3,7 @@ import assert from "node:assert";
3
3
  import { separator, clearStreamedText, getActiveTheme, setTheme, getThemesList } from "../src/ui/theme.js";
4
4
  import { createSpinner } from "../src/ui/spinner.js";
5
5
  import { routePrompt } from "../src/ai/router.js";
6
+ import { getModeByName, MODES } from "../src/modes.js";
6
7
 
7
8
  const originalFetch = globalThis.fetch;
8
9
 
@@ -125,4 +126,26 @@ test("Cyberpunk UX and Streaming Suite", async (t) => {
125
126
  // Reset back to cyberpunk
126
127
  setTheme("cyberpunk");
127
128
  });
129
+
130
+ await t.test("Reasoning modes should be loaded correctly including codex and cloude-code", () => {
131
+ const synthesis = getModeByName("synthesis");
132
+ assert.strictEqual(synthesis.name, "synthesis");
133
+
134
+ const codex = getModeByName("codex");
135
+ assert.strictEqual(codex.name, "codex");
136
+ assert.ok(codex.systemPrompt.includes("OpenAI Codex mode"));
137
+
138
+ const cloudeCode = getModeByName("cloude-code");
139
+ assert.strictEqual(cloudeCode.name, "cloude-code");
140
+ assert.ok(cloudeCode.systemPrompt.includes("Claude Code mode"));
141
+
142
+ const claudeCode = getModeByName("claude-code");
143
+ assert.strictEqual(claudeCode.name, "cloude-code");
144
+
145
+ const caseCheck = getModeByName(" CoDeX ");
146
+ assert.strictEqual(caseCheck.name, "codex");
147
+
148
+ const unknown = getModeByName("nonexistent-mode");
149
+ assert.strictEqual(unknown, null);
150
+ });
128
151
  });