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.
Files changed (69) hide show
  1. package/.a3m-vault.json +23 -0
  2. package/.github/workflows/ci.yml +253 -5
  3. package/.publish-tick +1 -1
  4. package/AGENT_COUNCIL_FINDINGS.md +142 -0
  5. package/LAUNCH_CHECKLIST.md +141 -0
  6. package/README.md +15 -17
  7. package/README.md.bak +836 -0
  8. package/articles/CHINESE_SUBMISSIONS_READY.md +322 -0
  9. package/articles/DEVTO_READY.md +255 -0
  10. package/articles/HN_POST_READY.md +137 -0
  11. package/articles/INDIEHACKERS_READY.md +120 -0
  12. package/articles/NEWSLETTER_SEND_NOW.md +259 -0
  13. package/articles/PRODUCTHUNT_READY.md +106 -0
  14. package/articles/REDDIT_SUBMISSION_READY.md +348 -0
  15. package/articles/TWEET_STORM_READY.md +165 -0
  16. package/benchmark-results.json +45 -43
  17. package/council-votes/architecture-vote.md +121 -0
  18. package/council-votes/coverage-vote.md +93 -0
  19. package/dist/cost/costTracker.d.ts +109 -44
  20. package/dist/cost/costTracker.js +321 -98
  21. package/dist/cost/costTracker.js.map +1 -1
  22. package/dist/ensemble.d.ts +21 -0
  23. package/dist/ensemble.js +85 -0
  24. package/dist/index.d.ts +9 -5
  25. package/dist/index.js +12 -4
  26. package/dist/routing/advancedRouter.d.ts +38 -43
  27. package/dist/routing/advancedRouter.js +394 -408
  28. package/dist/routing/advancedRouter.js.map +1 -1
  29. package/dist/routing/providers/providerConfig.d.ts +49 -0
  30. package/dist/routing/providers/providerConfig.js +883 -0
  31. package/dist/routing/routing/advancedRouter.d.ts +62 -0
  32. package/dist/routing/routing/advancedRouter.js +447 -0
  33. package/dist/routing/utils/tokenUtils.d.ts +52 -0
  34. package/dist/routing/utils/tokenUtils.js +129 -0
  35. package/dist/server/proxyServer.d.ts +1 -1
  36. package/dist/tui/dashboard.js +66 -2
  37. package/dist/tui/dashboard.js.map +1 -1
  38. package/dist/utils/tokenUtils.d.ts +48 -1
  39. package/dist/utils/tokenUtils.js +117 -4
  40. package/dist/utils/tokenUtils.js.map +1 -1
  41. package/docs/CITATIONS.md +2 -2
  42. package/docs/GEO_STATUS.md +43 -157
  43. package/docs/ai-plugin.json +4 -4
  44. package/docs/llms.txt +21 -27
  45. package/docs/sitemap.xml +14 -20
  46. package/package.json +2 -2
  47. package/research-log.md +49 -0
  48. package/sitemap.xml +57 -0
  49. package/src/cost/costTracker.ts +576 -0
  50. package/src/ensemble.ts +103 -0
  51. package/src/index.ts +13 -3
  52. package/src/routing/advancedRouter.ts +536 -0
  53. package/src/tui/dashboard.ts +76 -3
  54. package/src/utils/tokenUtils.ts +142 -4
  55. package/test-council/1-structure-tests.test.js +353 -0
  56. package/test-council/1-structure-tests.test.ts +353 -0
  57. package/test-council/2-edge-case-tests.test.ts +361 -0
  58. package/test-council/3-performance-tests.test.ts +669 -0
  59. package/test-council/4-integration-tests.test.ts +391 -0
  60. package/test-council/5-agent-council-eval.test.ts +413 -0
  61. package/test-council/AGENT_COUNCIL_ARCHITECTURE.md +349 -0
  62. package/test-council/TEST_COUNCIL_REPORT.md +201 -0
  63. package/test-council/agents/edge-case-agent.ts +363 -0
  64. package/test-council/agents/performance-agent.ts +426 -0
  65. package/test-council/agents/structure-agent.ts +227 -0
  66. package/test-council/council.md +183 -0
  67. package/tests/security/guardrailEngine.test.ts +700 -0
  68. package/docs/.well-known/ai-plugin.json +0 -16
  69. package/research/PUBLISH_LOG.md +0 -3
@@ -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 /model nvidia\n`;
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();
@@ -2,12 +2,150 @@
2
2
  * Token counting utilities for provider cost estimation
3
3
  */
4
4
 
5
- export function countTokens(text: string): number {
6
- if (!text) return 0;
7
- // Rough estimate: ~4 chars per token for English text
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
+ });