adaptive-memory-multi-model-router 2.14.16 → 2.14.18
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/.a3m-vault.json +23 -0
- package/.github/workflows/ci.yml +253 -5
- package/.publish-tick +1 -1
- package/AGENT_COUNCIL_FINDINGS.md +142 -0
- package/LAUNCH_CHECKLIST.md +141 -0
- package/README.md +15 -17
- package/README.md.bak +836 -0
- package/articles/CHINESE_SUBMISSIONS_READY.md +322 -0
- package/articles/DEVTO_READY.md +255 -0
- package/articles/HN_POST_READY.md +137 -0
- package/articles/INDIEHACKERS_READY.md +120 -0
- package/articles/NEWSLETTER_SEND_NOW.md +259 -0
- package/articles/PRODUCTHUNT_READY.md +106 -0
- package/articles/REDDIT_SUBMISSION_READY.md +348 -0
- package/articles/TWEET_STORM_READY.md +165 -0
- package/benchmark-results.json +45 -43
- package/council-votes/architecture-vote.md +121 -0
- package/council-votes/coverage-vote.md +93 -0
- package/dist/cost/costTracker.d.ts +109 -44
- package/dist/cost/costTracker.js +321 -98
- package/dist/cost/costTracker.js.map +1 -1
- package/dist/ensemble.d.ts +21 -0
- package/dist/ensemble.js +85 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.js +12 -4
- package/dist/routing/advancedRouter.d.ts +38 -43
- package/dist/routing/advancedRouter.js +394 -408
- package/dist/routing/advancedRouter.js.map +1 -1
- package/dist/routing/providers/providerConfig.d.ts +49 -0
- package/dist/routing/providers/providerConfig.js +883 -0
- package/dist/routing/routing/advancedRouter.d.ts +62 -0
- package/dist/routing/routing/advancedRouter.js +447 -0
- package/dist/routing/utils/tokenUtils.d.ts +52 -0
- package/dist/routing/utils/tokenUtils.js +129 -0
- package/dist/server/proxyServer.d.ts +1 -1
- package/dist/tui/dashboard.js +66 -2
- package/dist/tui/dashboard.js.map +1 -1
- package/dist/utils/tokenUtils.d.ts +48 -1
- package/dist/utils/tokenUtils.js +117 -4
- package/dist/utils/tokenUtils.js.map +1 -1
- package/docs/CITATIONS.md +2 -2
- package/docs/GEO_STATUS.md +43 -157
- package/docs/ai-plugin.json +4 -4
- package/docs/llms.txt +21 -27
- package/docs/sitemap.xml +14 -20
- package/package.json +2 -2
- package/research-log.md +49 -0
- package/sitemap.xml +57 -0
- package/src/cost/costTracker.ts +576 -0
- package/src/ensemble.ts +103 -0
- package/src/index.ts +13 -3
- package/src/routing/advancedRouter.ts +536 -0
- package/src/tui/dashboard.ts +76 -3
- package/src/utils/tokenUtils.ts +142 -4
- package/test-council/1-structure-tests.test.js +353 -0
- package/test-council/1-structure-tests.test.ts +353 -0
- package/test-council/2-edge-case-tests.test.ts +361 -0
- package/test-council/3-performance-tests.test.ts +669 -0
- package/test-council/4-integration-tests.test.ts +391 -0
- package/test-council/5-agent-council-eval.test.ts +413 -0
- package/test-council/AGENT_COUNCIL_ARCHITECTURE.md +349 -0
- package/test-council/TEST_COUNCIL_REPORT.md +201 -0
- package/test-council/agents/edge-case-agent.ts +363 -0
- package/test-council/agents/performance-agent.ts +426 -0
- package/test-council/agents/structure-agent.ts +227 -0
- package/test-council/council.md +183 -0
- package/tests/security/guardrailEngine.test.ts +700 -0
- package/docs/.well-known/ai-plugin.json +0 -16
- package/research/PUBLISH_LOG.md +0 -3
package/src/tui/dashboard.ts
CHANGED
|
@@ -17,6 +17,8 @@ console.log(`
|
|
|
17
17
|
`);
|
|
18
18
|
|
|
19
19
|
import * as blessed from 'blessed';
|
|
20
|
+
import * as fs from 'fs';
|
|
21
|
+
import * as path from 'path';
|
|
20
22
|
|
|
21
23
|
// ── State ──
|
|
22
24
|
let activeModel = 'nvidia/llama-3.1-8b';
|
|
@@ -24,6 +26,30 @@ let totalCost = 0.000087;
|
|
|
24
26
|
let reqCount = 4;
|
|
25
27
|
const log: string[] = [];
|
|
26
28
|
|
|
29
|
+
// ── Vault (Bookmark Database) ──
|
|
30
|
+
interface Bookmark {
|
|
31
|
+
id: string;
|
|
32
|
+
query: string;
|
|
33
|
+
response: string;
|
|
34
|
+
model: string;
|
|
35
|
+
timestamp: number;
|
|
36
|
+
tags: string[];
|
|
37
|
+
}
|
|
38
|
+
const VAULT_PATH = path.join(process.cwd(), '.a3m-vault.json');
|
|
39
|
+
let vault: Bookmark[] = [];
|
|
40
|
+
|
|
41
|
+
function loadVault() {
|
|
42
|
+
try {
|
|
43
|
+
if (fs.existsSync(VAULT_PATH)) {
|
|
44
|
+
vault = JSON.parse(fs.readFileSync(VAULT_PATH, 'utf-8'));
|
|
45
|
+
}
|
|
46
|
+
} catch { /* ignore */ }
|
|
47
|
+
}
|
|
48
|
+
function saveVault() {
|
|
49
|
+
fs.writeFileSync(VAULT_PATH, JSON.stringify(vault, null, 2), 'utf-8');
|
|
50
|
+
}
|
|
51
|
+
loadVault();
|
|
52
|
+
|
|
27
53
|
// ── Screen ──
|
|
28
54
|
const screen = blessed.screen({
|
|
29
55
|
smartCSR: true,
|
|
@@ -78,7 +104,7 @@ function render() {
|
|
|
78
104
|
if (visible.length === 0) {
|
|
79
105
|
out += ` ${D('Type a query — auto-routed to cheapest model.')}\n\n`;
|
|
80
106
|
out += ` ${D('Commands:')}\n`;
|
|
81
|
-
out += ` {#2563eb-fg}/route{/} ${D('<query>')} /cost
|
|
107
|
+
out += ` {#2563eb-fg}/route{/} ${D('<query>')} /vault /cost\n`;
|
|
82
108
|
out += ` {#2563eb-fg}/health{/} /models /clear\n`;
|
|
83
109
|
out += ` {#2563eb-fg}/exit{/} /help\n\n`;
|
|
84
110
|
out += ` ${D('nvidia (free) · groq (free) · deepseek ($9.46)')}\n`;
|
|
@@ -88,13 +114,60 @@ function render() {
|
|
|
88
114
|
screen.render();
|
|
89
115
|
}
|
|
90
116
|
|
|
117
|
+
function vaultList() {
|
|
118
|
+
if (vault.length === 0) {
|
|
119
|
+
log.push(` {#be185d-fg}Vault{/} ${D('empty — no bookmarks yet')}`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
log.push(` {#be185d-fg}Vault{/} ${D(`${vault.length} bookmarks`)}`);
|
|
123
|
+
const show = vault.slice(-10).reverse();
|
|
124
|
+
for (const b of show) {
|
|
125
|
+
const date = new Date(b.timestamp).toLocaleDateString();
|
|
126
|
+
const snippet = b.query.length > 40 ? b.query.slice(0, 40) + '…' : b.query;
|
|
127
|
+
log.push(` {#2563eb-fg}${b.id}{/} ${D(snippet)} {#059669-fg}${b.model}{/} ${D(date)}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function vaultAdd(query: string, response = '', tags: string[] = []) {
|
|
132
|
+
const id = `bm${Date.now().toString(36)}`;
|
|
133
|
+
const bm: Bookmark = { id, query, response, model: activeModel, timestamp: Date.now(), tags };
|
|
134
|
+
vault.push(bm);
|
|
135
|
+
saveVault();
|
|
136
|
+
log.push(` {#be185d-fg}Vault{/} ${D(`saved: ${id}`)}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function vaultSearch(term: string) {
|
|
140
|
+
const q = term.toLowerCase();
|
|
141
|
+
const results = vault.filter(b => b.query.toLowerCase().includes(q) || b.response.toLowerCase().includes(q));
|
|
142
|
+
if (results.length === 0) {
|
|
143
|
+
log.push(` {#be185d-fg}Vault{/} ${D(`no results for "${term}"`)}`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
log.push(` {#be185d-fg}Vault{/} ${D(`${results.length} results for "${term}"`)}`);
|
|
147
|
+
for (const b of results.slice(0, 5)) {
|
|
148
|
+
const snippet = b.query.length > 50 ? b.query.slice(0, 50) + '…' : b.query;
|
|
149
|
+
log.push(` {#2563eb-fg}${b.id}{/} ${D(snippet)}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
91
153
|
function cmd(c: string) {
|
|
92
154
|
if (!c) return;
|
|
93
155
|
log.push(`{bold}{#0891b2-fg}▸{/} ${c}`);
|
|
94
156
|
|
|
95
157
|
if (c === '/exit' || c === '/q') { screen.destroy(); process.exit(0); }
|
|
96
|
-
else if (c === '/help') log.push(` ${D('/route /cost /health /models /model <p> /clear /exit')}`);
|
|
158
|
+
else if (c === '/help') log.push(` ${D('/route /vault /cost /health /models /model <p> /clear /exit')}`);
|
|
97
159
|
else if (c === '/clear') log.length = 0;
|
|
160
|
+
else if (c === '/vault') vaultList();
|
|
161
|
+
else if (c.startsWith('/vault list')) vaultList();
|
|
162
|
+
else if (c.startsWith('/vault add ')) {
|
|
163
|
+
const query = c.replace('/vault add ', '').trim();
|
|
164
|
+
if (query) vaultAdd(query);
|
|
165
|
+
else log.push(` ${D('Usage: /vault add <query>')}`);
|
|
166
|
+
}
|
|
167
|
+
else if (c.startsWith('/vault search ')) {
|
|
168
|
+
const term = c.replace('/vault search ', '').trim();
|
|
169
|
+
vaultSearch(term);
|
|
170
|
+
}
|
|
98
171
|
else if (c === '/cost') {
|
|
99
172
|
log.push(` {#be185d-fg}A3M{/} Cost:`);
|
|
100
173
|
log.push(` ${D('nvidia $0 | deepseek $0.000009 | groq $0 | cerebras $0')}`);
|
|
@@ -130,4 +203,4 @@ prompt.key('enter', () => { const v = prompt.getValue().trim(); prompt.clearValu
|
|
|
130
203
|
screen.append(box);
|
|
131
204
|
render();
|
|
132
205
|
prompt.focus();
|
|
133
|
-
screen.render();
|
|
206
|
+
screen.render();
|
package/src/utils/tokenUtils.ts
CHANGED
|
@@ -2,12 +2,150 @@
|
|
|
2
2
|
* Token counting utilities for provider cost estimation
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return Math.ceil(text.length / 4);
|
|
5
|
+
export interface TokenCost {
|
|
6
|
+
input_per_1k: number;
|
|
7
|
+
output_per_1k: number;
|
|
9
8
|
}
|
|
10
9
|
|
|
10
|
+
// Current provider rates (2024-2025)
|
|
11
|
+
export const MODEL_COSTS: Record<string, TokenCost> = {
|
|
12
|
+
// OpenAI
|
|
13
|
+
"gpt-4o": { input_per_1k: 2.50, output_per_1k: 10.00 },
|
|
14
|
+
"gpt-4o-mini": { input_per_1k: 0.15, output_per_1k: 0.60 },
|
|
15
|
+
"gpt-4-turbo": { input_per_1k: 10.00, output_per_1k: 30.00 },
|
|
16
|
+
"gpt-3.5-turbo": { input_per_1k: 0.50, output_per_1k: 1.50 },
|
|
17
|
+
|
|
18
|
+
// Anthropic
|
|
19
|
+
"claude-3.5-sonnet": { input_per_1k: 3.00, output_per_1k: 15.00 },
|
|
20
|
+
"claude-3-opus": { input_per_1k: 15.00, output_per_1k: 75.00 },
|
|
21
|
+
"claude-3-haiku": { input_per_1k: 0.25, output_per_1k: 1.25 },
|
|
22
|
+
|
|
23
|
+
// Google
|
|
24
|
+
"gemini-2.0-flash": { input_per_1k: 0.00, output_per_1k: 0.00 }, // Free
|
|
25
|
+
"gemini-1.5-pro": { input_per_1k: 1.25, output_per_1k: 5.00 },
|
|
26
|
+
"gemini-1.5-flash": { input_per_1k: 0.075, output_per_1k: 0.30 },
|
|
27
|
+
|
|
28
|
+
// Groq
|
|
29
|
+
"groq/llama-3.3-70b": { input_per_1k: 0.59, output_per_1k: 0.79 },
|
|
30
|
+
"groq/llama-3.1-8b": { input_per_1k: 0.05, output_per_1k: 0.08 },
|
|
31
|
+
|
|
32
|
+
// Cerebras
|
|
33
|
+
"cerebras/llama-3.3-70b": { input_per_1k: 0.60, output_per_1k: 0.60 },
|
|
34
|
+
|
|
35
|
+
// Mistral
|
|
36
|
+
"mistral-large": { input_per_1k: 2.00, output_per_1k: 6.00 },
|
|
37
|
+
"mistral-small": { input_per_1k: 0.20, output_per_1k: 0.60 },
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Count tokens in text (approximate for English).
|
|
42
|
+
* Based on ~1.3 tokens per word for typical English text.
|
|
43
|
+
*/
|
|
44
|
+
export function countTokens(text: string, model: string = "gpt-4o"): number {
|
|
45
|
+
if (!text || text.length === 0) return 0;
|
|
46
|
+
|
|
47
|
+
// Use model-specific approximation if available
|
|
48
|
+
// Otherwise use generic word-based estimate
|
|
49
|
+
const words = text.trim().split(/\s+/).length;
|
|
50
|
+
|
|
51
|
+
// Fine-tune based on model family
|
|
52
|
+
if (model.includes("claude")) {
|
|
53
|
+
// Anthropic models: ~1.5 tokens per word
|
|
54
|
+
return Math.ceil(words * 1.5);
|
|
55
|
+
} else if (model.includes("gemini")) {
|
|
56
|
+
// Google: ~1.2 tokens per word (SentencePiece)
|
|
57
|
+
return Math.ceil(words * 1.2);
|
|
58
|
+
} else if (model.includes("llama")) {
|
|
59
|
+
// Llama: ~1.4 tokens per word (BPE)
|
|
60
|
+
return Math.ceil(words * 1.4);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Default: ~1.3 tokens per word (GPT-4 average)
|
|
64
|
+
return Math.ceil(words * 1.3);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Alias for countTokens for backward compatibility.
|
|
69
|
+
*/
|
|
11
70
|
export function estimateTokens(text: string): number {
|
|
12
71
|
return countTokens(text);
|
|
13
72
|
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Estimate cost for a prompt/completion pair.
|
|
76
|
+
*/
|
|
77
|
+
export function estimateCost(
|
|
78
|
+
prompt_tokens: number,
|
|
79
|
+
completion_tokens: number,
|
|
80
|
+
model: string
|
|
81
|
+
): number {
|
|
82
|
+
const costs = MODEL_COSTS[model] || MODEL_COSTS["gpt-4o"];
|
|
83
|
+
|
|
84
|
+
const input_cost = (prompt_tokens / 1000) * costs.input_per_1k;
|
|
85
|
+
const output_cost = (completion_tokens / 1000) * costs.output_per_1k;
|
|
86
|
+
|
|
87
|
+
return input_cost + output_cost;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Estimate cost from raw text (approximates both prompt and completion).
|
|
92
|
+
*/
|
|
93
|
+
export function estimateCostFromText(
|
|
94
|
+
prompt: string,
|
|
95
|
+
completion: string,
|
|
96
|
+
model: string
|
|
97
|
+
): number {
|
|
98
|
+
const prompt_tokens = countTokens(prompt, model);
|
|
99
|
+
// Completion typically has higher token density
|
|
100
|
+
const completion_tokens = Math.ceil(countTokens(completion, model) * 1.2);
|
|
101
|
+
|
|
102
|
+
return estimateCost(prompt_tokens, completion_tokens, model);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get cost info for a model.
|
|
107
|
+
*/
|
|
108
|
+
export function getModelCost(model: string): TokenCost {
|
|
109
|
+
return MODEL_COSTS[model] || MODEL_COSTS["gpt-4o"];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* List all supported models with their costs.
|
|
114
|
+
*/
|
|
115
|
+
export function listModelsByCost(): Array<{ model: string; input: number; output: number }> {
|
|
116
|
+
return Object.entries(MODEL_COSTS)
|
|
117
|
+
.map(([model, cost]) => ({
|
|
118
|
+
model,
|
|
119
|
+
input: cost.input_per_1k,
|
|
120
|
+
output: cost.output_per_1k
|
|
121
|
+
}))
|
|
122
|
+
.sort((a, b) => (a.input + a.output) - (b.input + b.output));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Find cheapest models for a given task.
|
|
127
|
+
*/
|
|
128
|
+
export function findCheapestModels(task: "fast" | "quality" | "balanced" | "coding", count: number = 3): string[] {
|
|
129
|
+
const sorted = listModelsByCost();
|
|
130
|
+
|
|
131
|
+
// Different profiles for different needs
|
|
132
|
+
const profiles = {
|
|
133
|
+
fast: sorted.filter(m => m.output < 1.0).slice(0, count).map(m => m.model),
|
|
134
|
+
quality: sorted.filter(m => m.output > 10).slice(0, count).map(m => m.model),
|
|
135
|
+
balanced: sorted.slice(0, count * 2).slice(count, count * 2).map(m => m.model),
|
|
136
|
+
coding: sorted.filter(m => m.model.includes("codex") || m.model.includes("claude") || m.model.includes("llama")).slice(0, count).map(m => m.model)
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
return profiles[task] || profiles.balanced;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export default {
|
|
143
|
+
countTokens,
|
|
144
|
+
estimateTokens,
|
|
145
|
+
estimateCost,
|
|
146
|
+
estimateCostFromText,
|
|
147
|
+
getModelCost,
|
|
148
|
+
listModelsByCost,
|
|
149
|
+
findCheapestModels,
|
|
150
|
+
MODEL_COSTS
|
|
151
|
+
};
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structure Tests - Code Structure & Export Coverage
|
|
3
|
+
*
|
|
4
|
+
* Tests that verify all exported functions, classes, and types work correctly.
|
|
5
|
+
* This file provides comprehensive coverage of the public API surface.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
9
|
+
|
|
10
|
+
// Import all exports from the main module
|
|
11
|
+
const {
|
|
12
|
+
// Routing engine
|
|
13
|
+
routeQuery,
|
|
14
|
+
routeBatch,
|
|
15
|
+
recommendForTask,
|
|
16
|
+
extractQueryFeatures,
|
|
17
|
+
MODEL_PROFILES,
|
|
18
|
+
updateModelProfile,
|
|
19
|
+
getProviderHealth,
|
|
20
|
+
|
|
21
|
+
// Retry handling
|
|
22
|
+
ProviderRetryHandler,
|
|
23
|
+
createRetryHandler,
|
|
24
|
+
getDefaultRetryHandler,
|
|
25
|
+
DEFAULT_RETRY_CONFIG,
|
|
26
|
+
DEFAULT_PROVIDER_CONFIG,
|
|
27
|
+
PROVIDER_CONTEXT_LIMITS,
|
|
28
|
+
|
|
29
|
+
// Providers
|
|
30
|
+
DEFAULT_PROVIDERS,
|
|
31
|
+
getAvailableProviders,
|
|
32
|
+
registerProvider,
|
|
33
|
+
deregisterProvider,
|
|
34
|
+
updateProvider,
|
|
35
|
+
healthCheck,
|
|
36
|
+
checkAllProviders,
|
|
37
|
+
findCheapestAvailableProvider,
|
|
38
|
+
findFastestAvailableProvider,
|
|
39
|
+
loadConfig,
|
|
40
|
+
saveConfig,
|
|
41
|
+
|
|
42
|
+
// Cost tracking
|
|
43
|
+
CostTracker,
|
|
44
|
+
BudgetEnforcer,
|
|
45
|
+
BudgetExceededError,
|
|
46
|
+
createBudgetEnforcer,
|
|
47
|
+
|
|
48
|
+
// Memory
|
|
49
|
+
MemoryTree,
|
|
50
|
+
|
|
51
|
+
// Utilities
|
|
52
|
+
countTokens,
|
|
53
|
+
estimateTokens,
|
|
54
|
+
|
|
55
|
+
// Cache
|
|
56
|
+
SemanticCache,
|
|
57
|
+
|
|
58
|
+
// Security
|
|
59
|
+
GuardrailEngine,
|
|
60
|
+
|
|
61
|
+
// Analytics
|
|
62
|
+
CostAnalytics,
|
|
63
|
+
|
|
64
|
+
// Observability
|
|
65
|
+
Tracer,
|
|
66
|
+
getTracer,
|
|
67
|
+
createTracer,
|
|
68
|
+
MetricsCollector,
|
|
69
|
+
getMetrics,
|
|
70
|
+
createMetricsCollector,
|
|
71
|
+
observabilityMiddleware,
|
|
72
|
+
observabilityPlugin,
|
|
73
|
+
budgetAlertMiddleware,
|
|
74
|
+
|
|
75
|
+
// Ensemble
|
|
76
|
+
HALOOrchestrator,
|
|
77
|
+
|
|
78
|
+
// Factory
|
|
79
|
+
createA3MRouter,
|
|
80
|
+
} = require('../dist/index.js');
|
|
81
|
+
|
|
82
|
+
// ============================================================
|
|
83
|
+
// STRUCTURE TESTS
|
|
84
|
+
// ============================================================
|
|
85
|
+
|
|
86
|
+
describe('1. Structure - Routing Engine Exports', () => {
|
|
87
|
+
|
|
88
|
+
describe('routeQuery', () => {
|
|
89
|
+
it('is a function', () => {
|
|
90
|
+
expect(typeof routeQuery).toBe('function');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('returns object with primary_model', () => {
|
|
94
|
+
const result = routeQuery('test query');
|
|
95
|
+
expect(result).toHaveProperty('primary_model');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('returns object with fallback_models array', () => {
|
|
99
|
+
const result = routeQuery('test query');
|
|
100
|
+
expect(result).toHaveProperty('fallback_models');
|
|
101
|
+
expect(Array.isArray(result.fallback_models)).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('returns object with estimated_cost number', () => {
|
|
105
|
+
const result = routeQuery('test query');
|
|
106
|
+
expect(result).toHaveProperty('estimated_cost');
|
|
107
|
+
expect(typeof result.estimated_cost).toBe('number');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('returns object with confidence number', () => {
|
|
111
|
+
const result = routeQuery('test query');
|
|
112
|
+
expect(result).toHaveProperty('confidence');
|
|
113
|
+
expect(typeof result.confidence).toBe('number');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('routeBatch', () => {
|
|
118
|
+
it('is a function', () => {
|
|
119
|
+
expect(typeof routeBatch).toBe('function');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('returns array of same length as input', () => {
|
|
123
|
+
const queries = ['a', 'b', 'c'];
|
|
124
|
+
const results = routeBatch(queries);
|
|
125
|
+
expect(results.length).toBe(queries.length);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('recommendForTask', () => {
|
|
130
|
+
it('is a function', () => {
|
|
131
|
+
expect(typeof recommendForTask).toBe('function');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('returns object with primary field', () => {
|
|
135
|
+
const result = recommendForTask('coding');
|
|
136
|
+
expect(result).toHaveProperty('primary');
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('extractQueryFeatures', () => {
|
|
141
|
+
it('is a function', () => {
|
|
142
|
+
expect(typeof extractQueryFeatures).toBe('function');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('returns object', () => {
|
|
146
|
+
const features = extractQueryFeatures('test');
|
|
147
|
+
expect(typeof features).toBe('object');
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('MODEL_PROFILES', () => {
|
|
152
|
+
it('is an object', () => {
|
|
153
|
+
expect(typeof MODEL_PROFILES).toBe('object');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('has at least one model', () => {
|
|
157
|
+
expect(Object.keys(MODEL_PROFILES).length).toBeGreaterThan(0);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('2. Structure - Provider Configuration Exports', () => {
|
|
163
|
+
|
|
164
|
+
describe('DEFAULT_PROVIDERS', () => {
|
|
165
|
+
it('is an object', () => {
|
|
166
|
+
expect(typeof DEFAULT_PROVIDERS).toBe('object');
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('getAvailableProviders', () => {
|
|
171
|
+
it('is a function', () => {
|
|
172
|
+
expect(typeof getAvailableProviders).toBe('function');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('returns an object', () => {
|
|
176
|
+
const providers = getAvailableProviders();
|
|
177
|
+
expect(typeof providers).toBe('object');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('registerProvider', () => {
|
|
182
|
+
it('is a function', () => {
|
|
183
|
+
expect(typeof registerProvider).toBe('function');
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe('deregisterProvider', () => {
|
|
188
|
+
it('is a function', () => {
|
|
189
|
+
expect(typeof deregisterProvider).toBe('function');
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('updateProvider', () => {
|
|
194
|
+
it('is a function', () => {
|
|
195
|
+
expect(typeof updateProvider).toBe('function');
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe('healthCheck', () => {
|
|
200
|
+
it('is a function', () => {
|
|
201
|
+
expect(typeof healthCheck).toBe('function');
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe('3. Structure - Retry Handler Exports', () => {
|
|
207
|
+
|
|
208
|
+
describe('ProviderRetryHandler', () => {
|
|
209
|
+
it('is a class', () => {
|
|
210
|
+
expect(typeof ProviderRetryHandler).toBe('function');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('can be instantiated', () => {
|
|
214
|
+
const handler = new ProviderRetryHandler();
|
|
215
|
+
expect(handler).toBeInstanceOf(ProviderRetryHandler);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('has getConfig method', () => {
|
|
219
|
+
const handler = new ProviderRetryHandler();
|
|
220
|
+
expect(typeof handler.getConfig).toBe('function');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('has isRetryableError method', () => {
|
|
224
|
+
const handler = new ProviderRetryHandler();
|
|
225
|
+
expect(typeof handler.isRetryableError).toBe('function');
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe('createRetryHandler', () => {
|
|
230
|
+
it('is a function', () => {
|
|
231
|
+
expect(typeof createRetryHandler).toBe('function');
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe('DEFAULT_RETRY_CONFIG', () => {
|
|
236
|
+
it('is an object', () => {
|
|
237
|
+
expect(typeof DEFAULT_RETRY_CONFIG).toBe('object');
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe('PROVIDER_CONTEXT_LIMITS', () => {
|
|
242
|
+
it('is an object', () => {
|
|
243
|
+
expect(typeof PROVIDER_CONTEXT_LIMITS).toBe('object');
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
describe('4. Structure - Cost Tracking Exports', () => {
|
|
249
|
+
|
|
250
|
+
describe('CostTracker', () => {
|
|
251
|
+
it('is a class', () => {
|
|
252
|
+
expect(typeof CostTracker).toBe('function');
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('can be instantiated', () => {
|
|
256
|
+
const tracker = new CostTracker();
|
|
257
|
+
expect(tracker).toBeTruthy();
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
describe('BudgetEnforcer', () => {
|
|
262
|
+
it('is a class', () => {
|
|
263
|
+
expect(typeof BudgetEnforcer).toBe('function');
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe('BudgetExceededError', () => {
|
|
268
|
+
it('is an Error class', () => {
|
|
269
|
+
expect(typeof BudgetExceededError).toBe('function');
|
|
270
|
+
const error = new BudgetExceededError('test');
|
|
271
|
+
expect(error).toBeInstanceOf(Error);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe('5. Structure - Memory Exports', () => {
|
|
277
|
+
|
|
278
|
+
describe('MemoryTree', () => {
|
|
279
|
+
it('is a class', () => {
|
|
280
|
+
expect(typeof MemoryTree).toBe('function');
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('can be instantiated', () => {
|
|
284
|
+
const memory = new MemoryTree({ maxSize: 100 });
|
|
285
|
+
expect(memory).toBeTruthy();
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('has add method', () => {
|
|
289
|
+
const memory = new MemoryTree({ maxSize: 100 });
|
|
290
|
+
expect(typeof memory.add).toBe('function');
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('has search method', () => {
|
|
294
|
+
const memory = new MemoryTree({ maxSize: 100 });
|
|
295
|
+
expect(typeof memory.search).toBe('function');
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('has getStats method', () => {
|
|
299
|
+
const memory = new MemoryTree({ maxSize: 100 });
|
|
300
|
+
expect(typeof memory.getStats).toBe('function');
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
describe('6. Structure - Utility Exports', () => {
|
|
306
|
+
|
|
307
|
+
describe('countTokens', () => {
|
|
308
|
+
it('is a function', () => {
|
|
309
|
+
expect(typeof countTokens).toBe('function');
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('returns positive number for non-empty string', () => {
|
|
313
|
+
const tokens = countTokens('hello world');
|
|
314
|
+
expect(typeof tokens).toBe('number');
|
|
315
|
+
expect(tokens).toBeGreaterThan(0);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
describe('estimateTokens', () => {
|
|
320
|
+
it('is a function', () => {
|
|
321
|
+
expect(typeof estimateTokens).toBe('function');
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
describe('7. Structure - Factory Exports', () => {
|
|
327
|
+
|
|
328
|
+
describe('createA3MRouter', () => {
|
|
329
|
+
it('is a function', () => {
|
|
330
|
+
expect(typeof createA3MRouter).toBe('function');
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('returns an object', () => {
|
|
334
|
+
const router = createA3MRouter({});
|
|
335
|
+
expect(typeof router).toBe('object');
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('returns object with route function', () => {
|
|
339
|
+
const router = createA3MRouter({});
|
|
340
|
+
expect(typeof router.route).toBe('function');
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('returns object with costTracker', () => {
|
|
344
|
+
const router = createA3MRouter({});
|
|
345
|
+
expect(router.costTracker).toBeTruthy();
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('returns object with memoryTree', () => {
|
|
349
|
+
const router = createA3MRouter({});
|
|
350
|
+
expect(router.memoryTree).toBeTruthy();
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
});
|