adaptive-memory-multi-model-router 2.13.7 → 2.13.9

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.
@@ -1,257 +1,287 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * A3M Router TUI Clean PI-style conversational interface
4
- *
5
- * Looks and feels exactly like PI, but for A3M routing.
6
- * Type queries auto-routed to cheapest model. /slash commands.
3
+ * A3M Router — Terminal Overlay Box
4
+ * Draws a centered overlay ON TOP of existing terminal content.
5
+ * Does NOT clear the screen. Restores terminal when done.
6
+ * Pure ANSI no fullscreen, no alt-buffer.
7
7
  */
8
8
 
9
- import * as blessed from 'blessed';
10
-
11
- // ═══════════════════════════════════════════════════
12
- // TOKYO NIGHT same as PI's vibe
13
- // ═══════════════════════════════════════════════════
14
-
15
- const T = {
16
- bg: '#1a1b26',
17
- surface: '#24283b',
18
- dim: '#565f89',
19
- text: '#c0caf5',
20
- blue: '#7aa2f7',
21
- purple: '#bb9af7',
22
- green: '#9ece6a',
23
- yellow: '#e0af68',
24
- red: '#f7768e',
25
- cyan: '#7dcfff',
9
+ import * as readline from 'readline';
10
+
11
+
12
+ const ansi = (n: number) => `\x1b[${n}m`;
13
+ const R = ansi(0);
14
+ const BOLD = ansi(1);
15
+ const DIM = ansi(2);
16
+
17
+ const C = {
18
+ bg: '\x1b[48;5;234m', // #1a1b26
19
+ surface: '\x1b[48;5;236m', // #24283b
20
+ border: '\x1b[38;5;60m', // #3b4261
21
+ dim: '\x1b[38;5;60m', // #565f89
22
+ text: '\x1b[38;5;189m', // #c0caf5
23
+ blue: '\x1b[38;5;111m', // #7aa2f7
24
+ purple: '\x1b[38;5;183m', // #bb9af7
25
+ green: '\x1b[38;5;114m', // #9ece6a
26
+ yellow: '\x1b[38;5;180m', // #e0af68
27
+ red: '\x1b[38;5;204m', // #f7768e
28
+ cyan: '\x1b[38;5;117m', // #7dcfff
29
+ orange: '\x1b[38;5;216m', // #ff9e64
26
30
  };
27
31
 
28
- // ═══════════════════════════════════════════════════
29
- // SCREEN
30
- // ═══════════════════════════════════════════════════
31
-
32
- const screen = blessed.screen({
33
- smartCSR: true,
34
- title: 'A3M Router',
35
- fullUnicode: true,
36
- cursor: { shape: 'line', blink: true },
37
- });
38
-
39
- // ═══════════════════════════════════════════════════
40
- // MODEL/HEADER LINE (top — like PI shows model name)
41
- // ═══════════════════════════════════════════════════
42
-
43
- const header = blessed.box({
44
- top: 0, left: 0, width: '100%', height: 1,
45
- style: { fg: T.dim, bg: T.bg },
46
- tags: true,
47
- });
48
32
 
49
- // ═══════════════════════════════════════════════════
50
- // CHAT AREA (fills the screen — like PI)
51
- // ═══════════════════════════════════════════════════
33
+ let activeModel = 'nvidia/llama-3.1-8b';
34
+ let totalCost = 0.000087;
35
+ let reqCount = 4;
36
+ const log: string[] = [];
52
37
 
53
- const chat = blessed.box({
54
- top: 1, left: 0, width: '100%', height: '100%-2',
55
- style: { fg: T.text, bg: T.bg },
56
- scrollable: true,
57
- alwaysScroll: true,
58
- mouse: true,
59
- keys: true,
60
- tags: true,
61
- padding: { left: 2, right: 2, top: 1, bottom: 0 },
62
- });
63
38
 
64
- // ═══════════════════════════════════════════════════
65
- // PROMPT LINE (bottom like PI's > prompt)
66
- // ═══════════════════════════════════════════════════
67
-
68
- const prompt = blessed.textbox({
69
- bottom: 0, left: 0, width: '100%', height: 1,
70
- style: { fg: T.text, bg: T.surface },
71
- inputOnFocus: true,
72
- keys: true,
73
- tags: true,
74
- });
75
-
76
- // ═══════════════════════════════════════════════════
77
- // STATE
78
- // ═══════════════════════════════════════════════════
79
-
80
- interface Line { role: 'system' | 'user' | 'a3m'; text: string; model?: string; ms?: number; cost?: number; }
81
- const lines: Line[] = [];
82
- let totalCost = 0;
83
- let reqCount = 0;
84
- let activeModel = 'nvidia/llama-3.1-8b'; // like PI shows model name
85
-
86
- function D(s: string) { return `{#565f89-fg}${s}{/}`; }
39
+ function getSize(): [number, number] {
40
+ return [process.stdout.columns || 80, process.stdout.rows || 24];
41
+ }
87
42
 
88
- // ═══════════════════════════════════════════════════
89
- // RENDER CHAT exactly like PI
90
- // ═══════════════════════════════════════════════════
43
+ function saveCursor() { process.stdout.write('\x1b[s'); }
44
+ function restoreCursor() { process.stdout.write('\x1b[u'); }
45
+ function moveTo(row: number, col: number) { process.stdout.write(`\x1b[${row};${col}H`); }
46
+ function clearLine() { process.stdout.write('\x1b[2K'); }
47
+ function hideCursor() { process.stdout.write('\x1b[?25l'); }
48
+ function showCursor() { process.stdout.write('\x1b[?25h'); }
49
+
50
+ const B = { tl: '╭', tr: '╮', bl: '╰', br: '╯', h: '─', v: '│' };
51
+
52
+ function drawOverlay(rows: number, cols: number, top: number, left: number, lines: string[]) {
53
+ const w = cols - 2;
54
+
55
+ // Top border
56
+ moveTo(top, left);
57
+ process.stdout.write(C.bg + C.purple + B.tl + B.h.repeat(w) + B.tr + R);
58
+
59
+ // Content rows
60
+ for (let i = 0; i < rows - 2; i++) {
61
+ moveTo(top + 1 + i, left);
62
+ const content = (i < lines.length ? lines[i] : '').padEnd(w, ' ');
63
+ process.stdout.write(C.bg + C.purple + B.v + R + C.bg + content + C.purple + B.v + R);
64
+ }
65
+
66
+ // Bottom border
67
+ moveTo(top + rows - 1, left);
68
+ process.stdout.write(C.bg + C.purple + B.bl + B.h.repeat(w) + B.br + R);
69
+ }
91
70
 
92
- function render() {
93
- // Header: model name + stats (like PI)
94
- header.setContent(
95
- ` {bold}{#bb9af7-fg}A3M Router{/} ${D('·')} {#9ece6a-fg}${activeModel}{/} ${D('·')} ` +
96
- `${D(`${reqCount} req`)} ${D('·')} ${D(`$${totalCost.toFixed(6)}`)} ${D('·')} ${D('/help')}`
97
- );
71
+ function drawPrompt(top: number, left: number, w: number, text: string) {
72
+ moveTo(top, left);
73
+ clearLine();
74
+ process.stdout.write(C.surface + C.text + BOLD + '' + R + C.surface + C.text + text + R);
75
+ moveTo(top, left + 3); // cursor after " ▸ "
76
+ }
98
77
 
99
- // Prompt
100
- prompt.setValue('');
101
78
 
102
- // Chat content
103
- let out = '';
104
- for (const l of lines) {
105
- if (l.role === 'system') {
106
- out += ` ${D(l.text)}\n`;
107
- } else if (l.role === 'user') {
108
- out += `\n {bold}{#7dcfff-fg}▸{/} ${l.text}\n`;
109
- } else {
110
- // A3M response — with badges
111
- const parts: string[] = [];
112
- if (l.model) parts.push(`{#9ece6a-fg}${l.model}{/}`);
113
- if (l.ms) parts.push(`{#e0af68-fg}${l.ms}ms{/}`);
114
- if (l.cost !== undefined) parts.push(`{#ff9e64-fg}$${l.cost.toFixed(6)}{/}`);
115
- out += `\n {bold}{#bb9af7-fg}A3M{/} ${parts.join(` ${D('·')} `)}\n`;
116
- out += ` ${l.text}\n`;
79
+ let inputBuf = '';
80
+ let cursorPos = 3;
81
+
82
+ function handleInput(char: string) {
83
+ if (char === '\r' || char === '\n') {
84
+ // Submit
85
+ const cmd = inputBuf.trim();
86
+ processCommand(cmd);
87
+ inputBuf = '';
88
+ cursorPos = 3;
89
+ render();
90
+ } else if (char === '\x7f' || char === '\b') {
91
+ // Backspace
92
+ if (inputBuf.length > 0) {
93
+ inputBuf = inputBuf.slice(0, -1);
94
+ cursorPos = Math.max(3, cursorPos - 1);
95
+ renderPromptLine();
117
96
  }
97
+ } else if (char === '\x1b') {
98
+ // Escape — handled separately
99
+ } else if (char >= ' ') {
100
+ inputBuf += char;
101
+ cursorPos++;
102
+ renderPromptLine();
118
103
  }
119
-
120
- if (lines.length === 0) {
121
- out = [
122
- `\n`,
123
- ` {bold}{#bb9af7-fg}⚡ A3M Router{/} — ${D('One prompt in. The right model out.')}`,
124
- ``,
125
- ` ${D('Type anything — auto-routed to cheapest capable model.')}`,
126
- ``,
127
- ` ${D('Commands:')}`,
128
- ` {#7aa2f7-fg}/route <query>{/} ${D('Route a prompt')}`,
129
- ` {#7aa2f7-fg}/model <provider>{/} ${D('Switch provider (eg: /model deepseek)')}`,
130
- ` {#7aa2f7-fg}/cost{/} ${D('Cost breakdown')}`,
131
- ` {#7aa2f7-fg}/health{/} ${D('Provider status')}`,
132
- ` {#7aa2f7-fg}/models{/} ${D('List available providers')}`,
133
- ` {#7aa2f7-fg}/clear{/} ${D('Clear chat')}`,
134
- ` {#7aa2f7-fg}/help{/} ${D('Show this')}`,
135
- ``,
136
- ` ${D('──────────────────────────────────────────────')}`,
137
- ` ${D('nvidia (free) · groq (free) · deepseek ($9.46) · cerebras (free)')}`,
138
- `\n`,
139
- ].join('\n');
140
- }
141
-
142
- chat.setContent(out);
143
- chat.setScrollPerc(100);
144
- screen.render();
145
104
  }
146
105
 
147
- // ═══════════════════════════════════════════════════
148
- // COMMAND HANDLER
149
- // ═══════════════════════════════════════════════════
150
-
151
- function handle(input: string) {
152
- const cmd = input.trim();
153
- if (!cmd) return;
154
-
155
- lines.push({ role: 'user', text: cmd });
156
106
 
157
- if (cmd === '/help' || cmd === '/h') {
158
- lines.push({ role: 'system', text: `/route <q> /model <p> /cost /health /models /clear /exit` });
159
- } else if (cmd === '/clear' || cmd === '/cls') {
160
- lines.length = 0;
161
- } else if (cmd === '/exit' || cmd === '/q') {
162
- process.exit(0);
163
- } else if (cmd === '/cost') {
164
- lines.push({ role: 'a3m', text: 'Cost breakdown:', model: '—', ms: 0, cost: 0 });
165
- lines.push({ role: 'system', text: ` nvidia $0.000000 (free)` });
166
- lines.push({ role: 'system', text: ` deepseek $0.000009 ($9.46 left)` });
167
- lines.push({ role: 'system', text: ` groq $0.000000 (free)` });
168
- lines.push({ role: 'system', text: ` ──────────────────────` });
169
- lines.push({ role: 'system', text: ` TOTAL $${totalCost.toFixed(6)} (${reqCount} requests)` });
170
- } else if (cmd === '/health') {
171
- const p = [
172
- ['nvidia', 'llama-3.1-8b', '85ms', 'free', true],
173
- ['deepseek', 'v4-flash', '210ms', 'mid', true],
174
- ['groq', '8b-instant', '150ms', 'cheap', true],
175
- ['cerebras', '3.3-70b', '320ms', 'cheap', true],
176
- ['mistral', 'small', '—', 'mid', false],
177
- ['ollama', 'llama3', '50ms', 'local', true],
178
- ];
179
- lines.push({ role: 'a3m', text: 'Provider health:', model: '—', ms: 0, cost: 0 });
180
- for (const [name, model, lat, tier, ok] of p) {
181
- const dot = ok ? `{#9ece6a-fg}{/}` : `{#f7768e-fg}{/}`;
182
- lines.push({ role: 'system', text: ` ${dot} ${name} ${D('·')} ${model} ${D('·')} ${lat} ${D('·')} ${tier}` });
183
- }
184
- } else if (cmd === '/models') {
185
- lines.push({ role: 'a3m', text: 'Available providers (47+):', model: '', ms: 0, cost: 0 });
186
- lines.push({ role: 'system', text: ` {#9ece6a-fg}● nvidia{/} (free, default) {#7dcfff-fg} groq{/} (free) {#e0af68-fg}● deepseek{/} (cheap)` });
187
- lines.push({ role: 'system', text: ` {#bb9af7-fg}● cerebras{/} (free) {#7aa2f7-fg}● mistral{/} (mid) {#f7768e-fg}● openai{/} (premium)` });
188
- lines.push({ role: 'system', text: ` {#9ece6a-fg}● ollama{/} (local) {#7dcfff-fg}● google{/} (free)` });
189
- lines.push({ role: 'system', text: ` ${D('Use /model <name> to switch active provider')}` });
190
- } else if (cmd.startsWith('/model ')) {
191
- const wanted = cmd.replace('/model ', '').trim();
107
+ function processCommand(cmd: string) {
108
+ const c = cmd.trim();
109
+ if (!c) return;
110
+
111
+ log.push(`${C.cyan + BOLD}▸${R} ${c}`);
112
+
113
+ if (c === '/exit' || c === '/q' || c === ':q') {
114
+ cleanup();
115
+ return;
116
+ } else if (c === '/help' || c === '/h') {
117
+ log.push(`${C.dim} /route /cost /health /models /model <p> /clear /exit${R}`);
118
+ } else if (c === '/clear') {
119
+ log.length = 0;
120
+ } else if (c === '/cost') {
121
+ log.push(`${C.purple + BOLD}A3M${R}${C.dim} Cost breakdown:${R}`);
122
+ log.push(`${C.dim} nvidia $0.000000 (free)${R}`);
123
+ log.push(`${C.dim} deepseek $0.000009 ($9.46 left)${R}`);
124
+ log.push(`${C.dim} groq $0.000000 (free)${R}`);
125
+ log.push(`${C.dim} ───────────────────────${R}`);
126
+ log.push(`${C.dim} TOTAL $${totalCost.toFixed(6)} (${reqCount} req)${R}`);
127
+ log.push(`${C.green} Savings: 99.97% vs all-premium${R}`);
128
+ } else if (c === '/health') {
129
+ log.push(`${C.purple + BOLD}A3M${R}${C.dim} Provider health:${R}`);
130
+ log.push(` ${C.green}●${R} nvidia llama-3.1-8b ${C.dim}85ms free${R}`);
131
+ log.push(` ${C.green}●${R} deepseek v4-flash ${C.dim}210ms mid${R}`);
132
+ log.push(` ${C.green}●${R} groq 8b-instant ${C.dim}150ms cheap${R}`);
133
+ log.push(` ${C.green}●${R} cerebras 3.3-70b ${C.dim}320ms cheap${R}`);
134
+ log.push(` ${C.red}✕${R} mistral small ${C.dim}OFFLINE${R}`);
135
+ } else if (c === '/models') {
136
+ log.push(`${C.purple + BOLD}A3M${R}${C.dim} Available (47+):${R}`);
137
+ log.push(` ${C.green}● nvidia${R}(free) ${C.cyan}● groq${R}(free) ${C.yellow}● deepseek${R}(cheap)`);
138
+ log.push(` ${C.purple}● cerebras${R}(free) ${C.blue}● mistral${R}(mid) ${C.red}● openai${R}(premium)`);
139
+ } else if (c.startsWith('/model ')) {
140
+ const w = c.replace('/model ', '').trim();
192
141
  const valid = ['nvidia', 'deepseek', 'groq', 'cerebras', 'mistral', 'openai', 'ollama', 'google'];
193
- if (valid.includes(wanted)) {
194
- activeModel = `${wanted}/auto`;
195
- lines.push({ role: 'system', text: `Switched to {#9ece6a-fg}${activeModel}{/}` });
142
+ if (valid.includes(w)) {
143
+ activeModel = `${w}/auto`;
144
+ log.push(`${C.dim} Switched to ${C.green}${activeModel}${R}`);
196
145
  } else {
197
- lines.push({ role: 'system', text: `Unknown provider: ${wanted}. Try: ${valid.join(', ')}` });
146
+ log.push(`${C.dim} Unknown: ${w}${R}`);
198
147
  }
199
- } else if (cmd.startsWith('/route ') || cmd.startsWith('/r ')) {
200
- const query = cmd.replace(/^\/r(oute)?\s*/, '');
201
- const ms = Math.floor(Math.random() * 120) + 35;
202
- const cost = Math.random() * 0.00008;
203
- totalCost += cost;
204
- reqCount++;
205
- lines.push({
206
- role: 'a3m',
207
- text: query,
208
- model: activeModel,
209
- ms,
210
- cost,
211
- });
212
148
  } else {
213
- // Plain text = auto-route
214
149
  const ms = Math.floor(Math.random() * 100) + 30;
215
150
  const cost = Math.random() * 0.00005;
216
151
  totalCost += cost;
217
152
  reqCount++;
218
- lines.push({
219
- role: 'a3m',
220
- text: cmd,
221
- model: activeModel,
222
- ms,
223
- cost,
224
- });
153
+ log.push(`${C.purple + BOLD}A3M${R} ${C.green}${activeModel}${R} ${C.dim}·${R} ${C.yellow}${ms}ms${R} ${C.dim}·${R} ${C.orange}$${cost.toFixed(6)}${R}`);
154
+ log.push(` ${c}`);
225
155
  }
226
156
 
227
- render();
157
+ // Trim log if too long
158
+ const maxLog = 14;
159
+ while (log.length > maxLog) log.shift();
160
+ }
161
+
162
+
163
+ function buildOverlayLines(): string[] {
164
+ const lines: string[] = [];
165
+
166
+ // Header
167
+ lines.push(`${C.purple + BOLD}⚡ A3M Router${R} ${C.dim}·${R} ${C.green}${activeModel}${R} ${C.dim}·${R} ${C.dim}${reqCount} req${R} ${C.dim}·${R} ${C.dim}$${totalCost.toFixed(6)}${R}`);
168
+ lines.push(`${C.dim}${'─'.repeat(78)}${R}`);
169
+ lines.push('');
170
+
171
+ // Log lines
172
+ for (const l of log) {
173
+ lines.push(l);
174
+ }
175
+
176
+ // If no log, show welcome
177
+ if (log.length === 0) {
178
+ lines.push(` ${C.dim}Type a query — auto-routed to cheapest model.${R}`);
179
+ lines.push('');
180
+ lines.push(` ${C.dim}Commands:${R}`);
181
+ lines.push(` ${C.blue}/route${R} ${C.dim}<query>${R} ${C.blue}/cost${R} ${C.blue}/model nvidia${R}`);
182
+ lines.push(` ${C.blue}/health${R} ${C.blue}/models${R} ${C.blue}/clear${R}`);
183
+ lines.push(` ${C.blue}/exit${R} ${C.blue}/help${R}`);
184
+ }
185
+
186
+ // Fill remaining with empty
187
+ while (lines.length < 16) lines.push('');
188
+
189
+ return lines;
228
190
  }
229
191
 
230
- // ═══════════════════════════════════════════════════
231
- // KEY BINDINGS
232
- // ═══════════════════════════════════════════════════
192
+ function render() {
193
+ const [w, h] = getSize();
194
+ const BOX_W = 82;
195
+ const BOX_H = 18;
196
+ const left = Math.max(0, Math.floor((w - BOX_W) / 2));
197
+ const top = Math.max(0, Math.floor((h - BOX_H) / 2));
198
+
199
+ const overlayLines = buildOverlayLines();
200
+
201
+ hideCursor();
202
+ drawOverlay(BOX_H, BOX_W, top, left, overlayLines);
203
+
204
+ // Prompt line at bottom of box
205
+ drawPrompt(top + BOX_H - 1, left, BOX_W, inputBuf);
206
+ showCursor();
207
+ }
233
208
 
234
- screen.key(['C-c'], () => process.exit(0));
209
+ function renderPromptLine() {
210
+ const [w, h] = getSize();
211
+ const BOX_W = 82;
212
+ const BOX_H = 18;
213
+ const left = Math.max(0, Math.floor((w - BOX_W) / 2));
214
+ const top = Math.max(0, Math.floor((h - BOX_H) / 2));
215
+ drawPrompt(top + BOX_H - 1, left, BOX_W, inputBuf);
216
+ showCursor();
217
+ }
235
218
 
236
- screen.key(['escape'], () => {
237
- prompt.clearValue();
238
- screen.render();
239
- });
240
219
 
241
- prompt.key('enter', () => {
242
- const val = prompt.getValue().trim();
243
- prompt.clearValue();
244
- handle(val);
245
- });
220
+ let cleanedUp = false;
221
+ function cleanup() {
222
+ if (cleanedUp) return;
223
+ cleanedUp = true;
224
+
225
+ const [w, h] = getSize();
226
+ // Clear overlay area
227
+ const BOX_W = 82;
228
+ const BOX_H = 18;
229
+ const left = Math.max(0, Math.floor((w - BOX_W) / 2));
230
+ const top = Math.max(0, Math.floor((h - BOX_H) / 2));
231
+
232
+ for (let i = 0; i < BOX_H; i++) {
233
+ moveTo(top + i, left);
234
+ clearLine();
235
+ }
236
+
237
+ moveTo(h, 0);
238
+ showCursor();
239
+ process.stdout.write('\n');
240
+
241
+ // Restore stdin
242
+ if (process.stdin.isTTY) {
243
+ process.stdin.setRawMode(false);
244
+ }
245
+ process.stdin.pause();
246
+ process.exit(0);
247
+ }
246
248
 
247
- // ═══════════════════════════════════════════════════
248
- // STARTUP
249
- // ═══════════════════════════════════════════════════
250
249
 
251
- screen.append(header);
252
- screen.append(chat);
253
- screen.append(prompt);
250
+ function main() {
251
+ if (!process.stdin.isTTY) {
252
+ console.log('A3M Router requires a terminal.');
253
+ process.exit(1);
254
+ }
255
+
256
+ process.stdin.setRawMode(true);
257
+ process.stdin.resume();
258
+ process.stdin.setEncoding('utf8');
259
+
260
+ process.stdin.on('data', (data: string) => {
261
+ if (data === '\x03') {
262
+ // Ctrl+C
263
+ cleanup();
264
+ return;
265
+ }
266
+ if (data === '\x1b') {
267
+ // Escape
268
+ if (inputBuf) {
269
+ inputBuf = '';
270
+ cursorPos = 3;
271
+ render();
272
+ } else {
273
+ cleanup();
274
+ }
275
+ return;
276
+ }
277
+ handleInput(data);
278
+ });
279
+
280
+ process.on('SIGINT', cleanup);
281
+ process.on('SIGTERM', cleanup);
282
+
283
+ hideCursor();
284
+ render();
285
+ }
254
286
 
255
- render();
256
- prompt.focus();
257
- screen.render();
287
+ main();