lynkr 7.2.5 → 8.0.0

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 (64) hide show
  1. package/README.md +2 -2
  2. package/config/model-tiers.json +89 -0
  3. package/docs/docs.html +1 -0
  4. package/docs/index.md +7 -0
  5. package/docs/toon-integration-spec.md +130 -0
  6. package/documentation/README.md +3 -2
  7. package/documentation/claude-code-cli.md +23 -16
  8. package/documentation/cursor-integration.md +17 -14
  9. package/documentation/docker.md +11 -4
  10. package/documentation/embeddings.md +7 -5
  11. package/documentation/faq.md +66 -12
  12. package/documentation/features.md +22 -15
  13. package/documentation/installation.md +66 -14
  14. package/documentation/production.md +43 -8
  15. package/documentation/providers.md +145 -42
  16. package/documentation/routing.md +476 -0
  17. package/documentation/token-optimization.md +7 -5
  18. package/documentation/troubleshooting.md +81 -5
  19. package/install.sh +6 -1
  20. package/package.json +4 -2
  21. package/scripts/setup.js +0 -1
  22. package/src/agents/executor.js +14 -6
  23. package/src/api/middleware/session.js +15 -2
  24. package/src/api/openai-router.js +130 -37
  25. package/src/api/providers-handler.js +15 -1
  26. package/src/api/router.js +107 -2
  27. package/src/budget/index.js +4 -3
  28. package/src/clients/databricks.js +431 -234
  29. package/src/clients/gpt-utils.js +181 -0
  30. package/src/clients/ollama-utils.js +66 -140
  31. package/src/clients/routing.js +0 -1
  32. package/src/clients/standard-tools.js +76 -3
  33. package/src/config/index.js +113 -35
  34. package/src/context/toon.js +173 -0
  35. package/src/logger/index.js +23 -0
  36. package/src/orchestrator/index.js +686 -211
  37. package/src/routing/agentic-detector.js +320 -0
  38. package/src/routing/complexity-analyzer.js +202 -2
  39. package/src/routing/cost-optimizer.js +305 -0
  40. package/src/routing/index.js +168 -159
  41. package/src/routing/model-tiers.js +365 -0
  42. package/src/server.js +2 -2
  43. package/src/sessions/cleanup.js +3 -3
  44. package/src/sessions/record.js +10 -1
  45. package/src/sessions/store.js +7 -2
  46. package/src/tools/agent-task.js +48 -1
  47. package/src/tools/index.js +15 -2
  48. package/te +11622 -0
  49. package/test/README.md +1 -1
  50. package/test/azure-openai-config.test.js +17 -8
  51. package/test/azure-openai-integration.test.js +7 -1
  52. package/test/azure-openai-routing.test.js +41 -43
  53. package/test/bedrock-integration.test.js +18 -32
  54. package/test/hybrid-routing-integration.test.js +35 -20
  55. package/test/hybrid-routing-performance.test.js +74 -64
  56. package/test/llamacpp-integration.test.js +28 -9
  57. package/test/lmstudio-integration.test.js +20 -8
  58. package/test/openai-integration.test.js +17 -20
  59. package/test/performance-tests.js +1 -1
  60. package/test/routing.test.js +65 -59
  61. package/test/toon-compression.test.js +131 -0
  62. package/CLAWROUTER_ROUTING_PLAN.md +0 -910
  63. package/ROUTER_COMPARISON.md +0 -173
  64. package/TIER_ROUTING_PLAN.md +0 -771
package/test/README.md CHANGED
@@ -9,7 +9,7 @@ All tests for the Lynkr project are consolidated in this `test/` directory.
9
9
  **Purpose**: Tests the hybrid routing logic in isolation
10
10
  **Run**: `DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/routing.test.js`
11
11
  **Coverage**: 10 tests
12
- - Routing with PREFER_OLLAMA disabled
12
+ - Routing with tier-based routing disabled (no TIER_* vars set)
13
13
  - Simple requests → Ollama
14
14
  - Complex requests → Cloud
15
15
  - Tool capability checks
@@ -17,6 +17,12 @@ describe("Azure OpenAI Configuration Tests", () => {
17
17
  process.env.AZURE_OPENAI_API_KEY = "";
18
18
  process.env.AZURE_OPENAI_DEPLOYMENT = "";
19
19
  process.env.AZURE_OPENAI_API_VERSION = "";
20
+
21
+ // Prevent .env TIER_* values from being picked up by dotenv
22
+ process.env.TIER_SIMPLE = "";
23
+ process.env.TIER_MEDIUM = "";
24
+ process.env.TIER_COMPLEX = "";
25
+ process.env.TIER_REASONING = "";
20
26
  });
21
27
 
22
28
  afterEach(() => {
@@ -119,7 +125,7 @@ describe("Azure OpenAI Configuration Tests", () => {
119
125
 
120
126
  describe("Fallback Provider Validation", () => {
121
127
  it("should accept azure-openai as fallback provider with credentials", () => {
122
- process.env.PREFER_OLLAMA = "true";
128
+ process.env.MODEL_PROVIDER = "ollama";
123
129
  process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
124
130
  process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
125
131
  process.env.FALLBACK_ENABLED = "true";
@@ -134,9 +140,8 @@ describe("Azure OpenAI Configuration Tests", () => {
134
140
  assert.strictEqual(config.modelProvider.fallbackProvider, "azure-openai");
135
141
  });
136
142
 
137
- it("should reject when azure-openai is fallback but credentials missing", () => {
138
- process.env.MODEL_PROVIDER = "ollama"; // Set to ollama for hybrid routing scenario
139
- process.env.PREFER_OLLAMA = "true";
143
+ it("should warn when azure-openai is fallback but credentials missing", () => {
144
+ process.env.MODEL_PROVIDER = "ollama";
140
145
  process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
141
146
  process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
142
147
  process.env.FALLBACK_ENABLED = "true";
@@ -146,11 +151,15 @@ describe("Azure OpenAI Configuration Tests", () => {
146
151
  process.env.AZURE_OPENAI_API_KEY = "";
147
152
  process.env.DATABRICKS_API_KEY = "test-key";
148
153
  process.env.DATABRICKS_API_BASE = "http://test.com";
154
+ // Enable tier routing so fallback validation runs
155
+ process.env.TIER_SIMPLE = "ollama:llama3.2";
156
+ process.env.TIER_MEDIUM = "ollama:llama3.2";
157
+ process.env.TIER_COMPLEX = "ollama:llama3.2";
158
+ process.env.TIER_REASONING = "ollama:llama3.2";
149
159
 
150
- // Should throw error about missing Azure OpenAI credentials (fail-fast validation)
151
- assert.throws(() => {
152
- require("../src/config");
153
- }, /AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_API_KEY/);
160
+ // Should warn but not throw (fallback misconfigured warning)
161
+ const config = require("../src/config");
162
+ assert.strictEqual(config.modelProvider.fallbackProvider, "azure-openai");
154
163
  });
155
164
  });
156
165
 
@@ -17,6 +17,12 @@ describe("Azure OpenAI Integration Tests", () => {
17
17
  process.env.MODEL_PROVIDER = "databricks";
18
18
  process.env.DATABRICKS_API_KEY = "test-key";
19
19
  process.env.DATABRICKS_API_BASE = "http://test.com";
20
+
21
+ // Prevent .env TIER_* values from being picked up by dotenv
22
+ process.env.TIER_SIMPLE = "";
23
+ process.env.TIER_MEDIUM = "";
24
+ process.env.TIER_COMPLEX = "";
25
+ process.env.TIER_REASONING = "";
20
26
  });
21
27
 
22
28
  afterEach(() => {
@@ -169,7 +175,7 @@ describe("Azure OpenAI Integration Tests", () => {
169
175
  });
170
176
 
171
177
  it("should select azure-openai as fallback provider", () => {
172
- process.env.PREFER_OLLAMA = "true";
178
+ process.env.MODEL_PROVIDER = "ollama";
173
179
  process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
174
180
  process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
175
181
  process.env.FALLBACK_ENABLED = "true";
@@ -9,6 +9,11 @@ describe("Azure OpenAI Routing Tests", () => {
9
9
  // Clear module cache
10
10
  delete require.cache[require.resolve("../src/config")];
11
11
  delete require.cache[require.resolve("../src/clients/routing")];
12
+ delete require.cache[require.resolve("../src/routing/index.js")];
13
+ delete require.cache[require.resolve("../src/routing/model-tiers")];
14
+ delete require.cache[require.resolve("../src/routing/complexity-analyzer")];
15
+ delete require.cache[require.resolve("../src/routing/cost-optimizer")];
16
+ delete require.cache[require.resolve("../src/routing/agentic-detector")];
12
17
 
13
18
  // Store original config
14
19
  originalConfig = { ...process.env };
@@ -20,9 +25,15 @@ describe("Azure OpenAI Routing Tests", () => {
20
25
  process.env.MODEL_PROVIDER = "databricks"; // Set default to avoid validation errors
21
26
  process.env.DATABRICKS_API_KEY = "test-key";
22
27
  process.env.DATABRICKS_API_BASE = "http://test.com";
23
-
28
+
24
29
  // Explicitly set valid fallback to override any local .env pollution (e.g. lmstudio)
25
30
  process.env.FALLBACK_PROVIDER = "databricks";
31
+
32
+ // Ensure no TIER_* vars leak between tests
33
+ process.env.TIER_SIMPLE = "";
34
+ process.env.TIER_MEDIUM = "";
35
+ process.env.TIER_COMPLEX = "";
36
+ process.env.TIER_REASONING = "";
26
37
  });
27
38
 
28
39
  afterEach(() => {
@@ -31,32 +42,25 @@ describe("Azure OpenAI Routing Tests", () => {
31
42
  });
32
43
 
33
44
  describe("Primary Provider Routing", () => {
34
- it("should route to azure-openai when set as MODEL_PROVIDER", () => {
45
+ it("should route to azure-openai when set as MODEL_PROVIDER", async () => {
35
46
  process.env.MODEL_PROVIDER = "azure-openai";
36
47
  process.env.AZURE_OPENAI_ENDPOINT = "https://test.openai.azure.com";
37
48
  process.env.AZURE_OPENAI_API_KEY = "test-key";
38
- process.env.PREFER_OLLAMA = "false";
39
49
 
40
50
  routing = require("../src/clients/routing");
41
51
 
42
- const provider = routing.determineProvider({ tools: [] });
52
+ const result = await routing.determineProviderSmart({
53
+ messages: [{ role: "user", content: "test" }],
54
+ tools: []
55
+ });
43
56
 
44
- assert.strictEqual(provider, "azure-openai");
57
+ assert.strictEqual(result.provider, "azure-openai");
45
58
  });
46
59
  });
47
60
 
48
- describe("Hybrid Routing with Azure OpenAI", () => {
49
- it("should route moderate tool requests to azure-openai when available", () => {
50
- // Explicitly unset OpenRouter to ensure it's not available
51
- // Set to empty string instead of delete to prevent dotenv from reloading it
52
- process.env.OPENROUTER_API_KEY = "";
53
-
54
- process.env.PREFER_OLLAMA = "true";
55
- process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
56
- process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
57
- process.env.FALLBACK_ENABLED = "true";
58
- process.env.OLLAMA_MAX_TOOLS_FOR_ROUTING = "3";
59
- process.env.OPENROUTER_MAX_TOOLS_FOR_ROUTING = "15";
61
+ describe("Static Routing with Azure OpenAI", () => {
62
+ it("should return primary provider regardless of tool count (tier routing disabled)", async () => {
63
+ process.env.MODEL_PROVIDER = "azure-openai";
60
64
  process.env.AZURE_OPENAI_ENDPOINT = "https://test.openai.azure.com";
61
65
  process.env.AZURE_OPENAI_API_KEY = "test-key";
62
66
 
@@ -67,24 +71,19 @@ describe("Azure OpenAI Routing Tests", () => {
67
71
 
68
72
  routing = require("../src/clients/routing");
69
73
 
70
- // 5 tools: more than Ollama threshold (3), less than OpenRouter threshold (15)
71
- const provider = routing.determineProvider({
74
+ const result = await routing.determineProviderSmart({
75
+ messages: [{ role: "user", content: "test" }],
72
76
  tools: [{}, {}, {}, {}, {}]
73
77
  });
74
78
 
75
- assert.strictEqual(provider, "azure-openai");
79
+ assert.strictEqual(result.provider, "azure-openai");
80
+ assert.strictEqual(result.method, "static");
76
81
  });
77
82
 
78
- it("should prefer OpenRouter over Azure OpenAI when both configured", () => {
79
- process.env.PREFER_OLLAMA = "true";
80
- process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
81
- process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
82
- process.env.FALLBACK_ENABLED = "true";
83
- process.env.OLLAMA_MAX_TOOLS_FOR_ROUTING = "3";
84
- process.env.OPENROUTER_MAX_TOOLS_FOR_ROUTING = "15";
85
- process.env.OPENROUTER_API_KEY = "openrouter-key";
83
+ it("should return primary provider for simple requests", async () => {
84
+ process.env.MODEL_PROVIDER = "azure-openai";
86
85
  process.env.AZURE_OPENAI_ENDPOINT = "https://test.openai.azure.com";
87
- process.env.AZURE_OPENAI_API_KEY = "azure-key";
86
+ process.env.AZURE_OPENAI_API_KEY = "test-key";
88
87
 
89
88
  // Clear cache after env setup
90
89
  delete require.cache[require.resolve("../src/config/index.js")];
@@ -93,20 +92,17 @@ describe("Azure OpenAI Routing Tests", () => {
93
92
 
94
93
  routing = require("../src/clients/routing");
95
94
 
96
- // 5 tools: should prefer OpenRouter
97
- const provider = routing.determineProvider({
98
- tools: [{}, {}, {}, {}, {}]
95
+ const result = await routing.determineProviderSmart({
96
+ messages: [{ role: "user", content: "test" }],
97
+ tools: [{}, {}]
99
98
  });
100
99
 
101
- assert.strictEqual(provider, "openrouter");
100
+ assert.strictEqual(result.provider, "azure-openai");
101
+ assert.strictEqual(result.method, "static");
102
102
  });
103
103
 
104
- it("should route simple requests to Ollama even when Azure OpenAI configured", () => {
105
- process.env.PREFER_OLLAMA = "true";
106
- process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
107
- process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
108
- process.env.FALLBACK_ENABLED = "true";
109
- process.env.OLLAMA_MAX_TOOLS_FOR_ROUTING = "3";
104
+ it("should return static routing from determineProviderSmart when tiers disabled", async () => {
105
+ process.env.MODEL_PROVIDER = "azure-openai";
110
106
  process.env.AZURE_OPENAI_ENDPOINT = "https://test.openai.azure.com";
111
107
  process.env.AZURE_OPENAI_API_KEY = "test-key";
112
108
 
@@ -117,18 +113,20 @@ describe("Azure OpenAI Routing Tests", () => {
117
113
 
118
114
  routing = require("../src/clients/routing");
119
115
 
120
- // 2 tools: under Ollama threshold
121
- const provider = routing.determineProvider({
116
+ const result = await routing.determineProviderSmart({
117
+ messages: [{ role: "user", content: "test" }],
122
118
  tools: [{}, {}]
123
119
  });
124
120
 
125
- assert.strictEqual(provider, "ollama");
121
+ assert.strictEqual(result.provider, "azure-openai");
122
+ assert.strictEqual(result.method, "static");
123
+ assert.strictEqual(result.reason, "tier_routing_disabled");
126
124
  });
127
125
  });
128
126
 
129
127
  describe("Fallback Configuration", () => {
130
128
  it("should support azure-openai as fallback provider", () => {
131
- process.env.PREFER_OLLAMA = "true";
129
+ process.env.MODEL_PROVIDER = "ollama";
132
130
  process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
133
131
  process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
134
132
  process.env.FALLBACK_ENABLED = "true";
@@ -11,6 +11,12 @@ describe("AWS Bedrock Integration", () => {
11
11
  delete require.cache[require.resolve("../src/config")];
12
12
  delete require.cache[require.resolve("../src/clients/routing")];
13
13
  delete require.cache[require.resolve("../src/clients/bedrock-utils")];
14
+
15
+ // Prevent .env TIER_* values from being picked up by dotenv
16
+ process.env.TIER_SIMPLE = "";
17
+ process.env.TIER_MEDIUM = "";
18
+ process.env.TIER_COMPLEX = "";
19
+ process.env.TIER_REASONING = "";
14
20
  });
15
21
 
16
22
  afterEach(() => {
@@ -374,62 +380,45 @@ describe("AWS Bedrock Integration", () => {
374
380
  process.env.MODEL_PROVIDER = "bedrock";
375
381
  process.env.AWS_ACCESS_KEY_ID = "AKIATEST123";
376
382
  process.env.AWS_SECRET_ACCESS_KEY = "testSecretKey123";
377
- process.env.PREFER_OLLAMA = "false";
378
383
 
379
384
  const config = require("../src/config");
380
385
  const routing = require("../src/clients/routing");
381
386
 
382
387
  const payload = { messages: [{ role: "user", content: "test" }] };
383
- const provider = routing.determineProvider(payload);
388
+ const provider = routing.determineProviderSync(payload);
384
389
 
385
- // When not in hybrid mode, should use primary provider
390
+ // determineProviderSync returns static MODEL_PROVIDER
386
391
  assert.strictEqual(provider, "bedrock");
387
392
  });
388
393
 
389
- it("should route to bedrock in hybrid mode for moderate tool counts", () => {
390
- process.env.MODEL_PROVIDER = "ollama";
391
- process.env.PREFER_OLLAMA = "true";
392
- process.env.OLLAMA_MODEL = "llama3.1";
393
- process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
394
+ it("should return static routing from determineProviderSmart when tiers disabled", async () => {
395
+ process.env.MODEL_PROVIDER = "bedrock";
394
396
  process.env.AWS_ACCESS_KEY_ID = "AKIATEST123";
395
397
  process.env.AWS_SECRET_ACCESS_KEY = "testSecretKey123";
396
- process.env.OLLAMA_MAX_TOOLS_FOR_ROUTING = "2";
397
- process.env.FALLBACK_ENABLED = "true";
398
- process.env.FALLBACK_PROVIDER = "bedrock";
399
-
400
- // Clear other providers to ensure bedrock is chosen
401
- delete process.env.OPENROUTER_API_KEY;
402
- delete process.env.OPENAI_API_KEY;
403
- delete process.env.AZURE_OPENAI_API_KEY;
404
- delete process.env.AZURE_OPENAI_ENDPOINT;
405
- delete process.env.LLAMACPP_ENDPOINT;
406
- delete process.env.LMSTUDIO_ENDPOINT;
407
- delete process.env.DATABRICKS_API_KEY;
408
- delete process.env.DATABRICKS_API_BASE;
409
398
 
410
399
  const config = require("../src/config");
411
400
  const routing = require("../src/clients/routing");
412
401
 
413
- // 20 tools should exceed both Ollama and OpenRouter limits, routing to fallback provider (bedrock)
402
+ // Many tools -- but without TIER_* vars, determineProviderSmart returns static routing
414
403
  const payload = {
415
404
  messages: [{ role: "user", content: "test" }],
416
405
  tools: Array(20).fill({ name: "tool" }),
417
406
  };
418
- const provider = routing.determineProvider(payload);
407
+ const result = await routing.determineProviderSmart(payload);
419
408
 
420
- assert.strictEqual(provider, "bedrock");
409
+ assert.strictEqual(result.provider, "bedrock");
410
+ assert.strictEqual(result.method, "static");
411
+ assert.strictEqual(result.reason, "tier_routing_disabled");
421
412
  });
422
413
  });
423
414
 
424
415
  describe("Fallback Provider", () => {
425
416
  it("should allow bedrock as fallback provider", () => {
426
417
  process.env.MODEL_PROVIDER = "ollama";
427
- process.env.PREFER_OLLAMA = "true";
428
418
  process.env.OLLAMA_MODEL = "llama3.1";
429
419
  process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
430
420
  process.env.FALLBACK_PROVIDER = "bedrock";
431
- process.env.AWS_ACCESS_KEY_ID = "AKIATEST123";
432
- process.env.AWS_SECRET_ACCESS_KEY = "testSecretKey123";
421
+ process.env.AWS_BEDROCK_API_KEY = "test-bedrock-key";
433
422
  process.env.FALLBACK_ENABLED = "true";
434
423
 
435
424
  // Should not throw
@@ -439,24 +428,21 @@ describe("AWS Bedrock Integration", () => {
439
428
 
440
429
  it("should validate bedrock credentials when used as fallback", () => {
441
430
  process.env.MODEL_PROVIDER = "ollama";
442
- process.env.PREFER_OLLAMA = "true";
443
431
  process.env.OLLAMA_MODEL = "llama3.1";
444
432
  process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
445
433
  process.env.FALLBACK_PROVIDER = "bedrock";
446
434
  process.env.FALLBACK_ENABLED = "true";
447
435
  // Set to empty string to override .env file values
448
- process.env.AWS_ACCESS_KEY_ID = "";
449
- process.env.AWS_SECRET_ACCESS_KEY = "";
436
+ process.env.AWS_BEDROCK_API_KEY = "";
450
437
 
451
438
  assert.throws(
452
439
  () => require("../src/config"),
453
- /FALLBACK_PROVIDER is set to 'bedrock' but AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are not configured/
440
+ /FALLBACK_PROVIDER is set to 'bedrock' but AWS_BEDROCK_API_KEY is not configured/
454
441
  );
455
442
  });
456
443
 
457
444
  it("should not allow local providers as fallback", () => {
458
445
  process.env.MODEL_PROVIDER = "ollama";
459
- process.env.PREFER_OLLAMA = "true";
460
446
  process.env.OLLAMA_MODEL = "llama3.1";
461
447
  process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
462
448
  process.env.FALLBACK_PROVIDER = "llamacpp";
@@ -21,6 +21,12 @@ describe("Hybrid Routing Integration Tests", () => {
21
21
  process.env.DATABRICKS_API_KEY = "test-key";
22
22
  process.env.DATABRICKS_API_BASE = "http://test.databricks.com";
23
23
  process.env.MODEL_PROVIDER = "databricks";
24
+
25
+ // Set TIER_* to empty to prevent .env file values from being picked up by dotenv
26
+ process.env.TIER_SIMPLE = "";
27
+ process.env.TIER_MEDIUM = "";
28
+ process.env.TIER_COMPLEX = "";
29
+ process.env.TIER_REASONING = "";
24
30
  });
25
31
 
26
32
  afterEach(() => {
@@ -30,7 +36,7 @@ describe("Hybrid Routing Integration Tests", () => {
30
36
 
31
37
  describe("Configuration Validation", () => {
32
38
  it("should use default OLLAMA_ENDPOINT when not specified", () => {
33
- process.env.PREFER_OLLAMA = "true";
39
+ process.env.MODEL_PROVIDER = "ollama";
34
40
  delete process.env.OLLAMA_ENDPOINT;
35
41
  process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
36
42
  process.env.DATABRICKS_API_KEY = "test-key";
@@ -43,7 +49,7 @@ describe("Hybrid Routing Integration Tests", () => {
43
49
  });
44
50
 
45
51
  it("should reject invalid FALLBACK_PROVIDER", () => {
46
- process.env.PREFER_OLLAMA = "true";
52
+ process.env.MODEL_PROVIDER = "ollama";
47
53
  process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
48
54
  process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
49
55
  process.env.FALLBACK_ENABLED = "true";
@@ -55,20 +61,24 @@ describe("Hybrid Routing Integration Tests", () => {
55
61
  });
56
62
 
57
63
  it("should reject circular fallback (ollama -> ollama)", () => {
58
- process.env.PREFER_OLLAMA = "true";
64
+ process.env.MODEL_PROVIDER = "ollama";
59
65
  process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
60
66
  process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
61
67
  process.env.FALLBACK_ENABLED = "true";
62
68
  process.env.FALLBACK_PROVIDER = "ollama";
69
+ // Enable tier routing so fallback validation runs
70
+ process.env.TIER_SIMPLE = "ollama:llama3.2";
71
+ process.env.TIER_MEDIUM = "ollama:llama3.2";
72
+ process.env.TIER_COMPLEX = "ollama:llama3.2";
73
+ process.env.TIER_REASONING = "ollama:llama3.2";
63
74
 
64
75
  assert.throws(() => {
65
76
  require("../src/config");
66
77
  }, /FALLBACK_PROVIDER cannot be 'ollama'/);
67
78
  });
68
79
 
69
- it("should reject PREFER_OLLAMA with databricks fallback but no databricks credentials", () => {
70
- process.env.MODEL_PROVIDER = "ollama"; // Set to ollama for hybrid routing scenario
71
- process.env.PREFER_OLLAMA = "true";
80
+ it("should warn when databricks fallback has no credentials", () => {
81
+ process.env.MODEL_PROVIDER = "ollama";
72
82
  process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
73
83
  process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
74
84
  process.env.FALLBACK_ENABLED = "true";
@@ -76,39 +86,44 @@ describe("Hybrid Routing Integration Tests", () => {
76
86
  // Set to empty strings instead of deleting (dotenv.config() in config module would reload from .env)
77
87
  process.env.DATABRICKS_API_KEY = "";
78
88
  process.env.DATABRICKS_API_BASE = "";
89
+ // Enable tier routing so fallback validation runs
90
+ process.env.TIER_SIMPLE = "ollama:llama3.2";
91
+ process.env.TIER_MEDIUM = "ollama:llama3.2";
92
+ process.env.TIER_COMPLEX = "ollama:llama3.2";
93
+ process.env.TIER_REASONING = "ollama:llama3.2";
79
94
 
80
- // Should throw error about missing databricks credentials
81
- // (Either from standard validation or hybrid routing validation)
82
- assert.throws(() => {
83
- require("../src/config");
84
- }, /DATABRICKS_API_BASE and DATABRICKS_API_KEY/);
95
+ // Should warn but not throw (fallback misconfigured)
96
+ const config = require("../src/config");
97
+ assert.strictEqual(config.modelProvider.fallbackProvider, "databricks");
85
98
  });
86
99
 
87
- it("should accept valid hybrid routing configuration", () => {
88
- process.env.PREFER_OLLAMA = "true";
100
+ it("should accept valid tier routing configuration", () => {
101
+ process.env.MODEL_PROVIDER = "ollama";
89
102
  process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
90
103
  process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
91
104
  process.env.FALLBACK_ENABLED = "true";
92
105
  process.env.FALLBACK_PROVIDER = "databricks";
93
- process.env.OLLAMA_MAX_TOOLS_FOR_ROUTING = "3"; // Override .env which sets it to 2
94
106
  process.env.DATABRICKS_API_KEY = "test-key";
95
107
  process.env.DATABRICKS_API_BASE = "http://test.com";
108
+ process.env.TIER_SIMPLE = "ollama:llama3.2";
109
+ process.env.TIER_MEDIUM = "ollama:llama3.2";
110
+ process.env.TIER_COMPLEX = "databricks:claude-sonnet";
111
+ process.env.TIER_REASONING = "databricks:claude-sonnet";
96
112
 
97
113
  const config = require("../src/config");
98
114
 
99
- assert.strictEqual(config.modelProvider.preferOllama, true);
115
+ assert.strictEqual(config.modelProvider.type, "ollama");
100
116
  assert.strictEqual(config.modelProvider.fallbackEnabled, true);
101
- assert.strictEqual(config.modelProvider.ollamaMaxToolsForRouting, 3);
102
117
  assert.strictEqual(config.modelProvider.fallbackProvider, "databricks");
118
+ assert.strictEqual(config.modelTiers.enabled, true);
103
119
  });
104
120
  });
105
121
 
106
122
  describe("Metrics Recording", () => {
107
123
  beforeEach(() => {
108
- process.env.PREFER_OLLAMA = "true";
124
+ process.env.MODEL_PROVIDER = "ollama";
109
125
  process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
110
126
  process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
111
- process.env.OLLAMA_FALLBACK_PROVIDER = "databricks";
112
127
 
113
128
  config = require("../src/config");
114
129
  const metricsModule = require("../src/observability/metrics");
@@ -207,7 +222,7 @@ describe("Hybrid Routing Integration Tests", () => {
207
222
  it("should categorize circuit breaker errors", () => {
208
223
  // This would need to be tested by importing the function if exported
209
224
  // For now, we test via the integrated behavior
210
- process.env.PREFER_OLLAMA = "true";
225
+ process.env.MODEL_PROVIDER = "ollama";
211
226
  process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
212
227
  process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
213
228
 
@@ -231,7 +246,7 @@ describe("Hybrid Routing Integration Tests", () => {
231
246
  });
232
247
 
233
248
  it("should estimate cost savings correctly", () => {
234
- process.env.PREFER_OLLAMA = "true";
249
+ process.env.MODEL_PROVIDER = "ollama";
235
250
  process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
236
251
  process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
237
252