lynkr 8.0.0 → 9.0.1
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/.lynkr/telemetry.db +0 -0
- package/.lynkr/telemetry.db-shm +0 -0
- package/.lynkr/telemetry.db-wal +0 -0
- package/README.md +196 -322
- package/lynkr-skill.tar.gz +0 -0
- package/package.json +4 -3
- package/src/api/openai-router.js +64 -13
- package/src/api/providers-handler.js +171 -3
- package/src/api/router.js +9 -2
- package/src/clients/circuit-breaker.js +10 -247
- package/src/clients/codex-process.js +342 -0
- package/src/clients/codex-utils.js +143 -0
- package/src/clients/databricks.js +210 -63
- package/src/clients/resilience.js +540 -0
- package/src/clients/retry.js +22 -167
- package/src/clients/standard-tools.js +23 -0
- package/src/config/index.js +77 -0
- package/src/context/compression.js +42 -9
- package/src/context/distill.js +492 -0
- package/src/orchestrator/index.js +48 -8
- package/src/routing/complexity-analyzer.js +258 -5
- package/src/routing/index.js +12 -2
- package/src/routing/latency-tracker.js +148 -0
- package/src/routing/model-tiers.js +2 -0
- package/src/routing/quality-scorer.js +113 -0
- package/src/routing/telemetry.js +464 -0
- package/src/server.js +13 -12
- package/src/tools/code-graph.js +538 -0
- package/src/tools/code-mode.js +304 -0
- package/src/tools/index.js +4 -0
- package/src/tools/lazy-loader.js +18 -0
- package/src/tools/mcp-remote.js +7 -0
- package/src/tools/smart-selection.js +11 -0
- package/src/tools/tinyfish.js +358 -0
- package/src/tools/truncate.js +1 -0
- package/src/utils/payload.js +206 -0
- package/src/utils/perf-timer.js +80 -0
- package/.github/FUNDING.yml +0 -15
- package/.github/workflows/README.md +0 -215
- package/.github/workflows/ci.yml +0 -69
- package/.github/workflows/index.yml +0 -62
- package/.github/workflows/web-tools-tests.yml +0 -56
- package/CITATIONS.bib +0 -6
- package/DEPLOYMENT.md +0 -1001
- package/LYNKR-TUI-PLAN.md +0 -984
- package/PERFORMANCE-REPORT.md +0 -866
- package/PLAN-per-client-model-routing.md +0 -252
- package/docs/42642f749da6234f41b6b425c3bb07c9.txt +0 -1
- package/docs/BingSiteAuth.xml +0 -4
- package/docs/docs-style.css +0 -478
- package/docs/docs.html +0 -198
- package/docs/google5be250e608e6da39.html +0 -1
- package/docs/index.html +0 -577
- package/docs/index.md +0 -584
- package/docs/robots.txt +0 -4
- package/docs/sitemap.xml +0 -44
- package/docs/style.css +0 -1223
- package/docs/toon-integration-spec.md +0 -130
- package/documentation/README.md +0 -101
- package/documentation/api.md +0 -806
- package/documentation/claude-code-cli.md +0 -679
- package/documentation/codex-cli.md +0 -397
- package/documentation/contributing.md +0 -571
- package/documentation/cursor-integration.md +0 -734
- package/documentation/docker.md +0 -874
- package/documentation/embeddings.md +0 -762
- package/documentation/faq.md +0 -713
- package/documentation/features.md +0 -403
- package/documentation/headroom.md +0 -519
- package/documentation/installation.md +0 -758
- package/documentation/memory-system.md +0 -476
- package/documentation/production.md +0 -636
- package/documentation/providers.md +0 -1009
- package/documentation/routing.md +0 -476
- package/documentation/testing.md +0 -629
- package/documentation/token-optimization.md +0 -325
- package/documentation/tools.md +0 -697
- package/documentation/troubleshooting.md +0 -969
- package/final-test.js +0 -33
- package/headroom-sidecar/config.py +0 -93
- package/headroom-sidecar/requirements.txt +0 -14
- package/headroom-sidecar/server.py +0 -451
- package/monitor-agents.sh +0 -31
- package/scripts/audit-log-reader.js +0 -399
- package/scripts/compact-dictionary.js +0 -204
- package/scripts/test-deduplication.js +0 -448
- package/src/db/database.sqlite +0 -0
- package/te +0 -11622
- package/test/README.md +0 -212
- package/test/azure-openai-config.test.js +0 -213
- package/test/azure-openai-error-resilience.test.js +0 -238
- package/test/azure-openai-format-conversion.test.js +0 -354
- package/test/azure-openai-integration.test.js +0 -287
- package/test/azure-openai-routing.test.js +0 -175
- package/test/azure-openai-streaming.test.js +0 -171
- package/test/bedrock-integration.test.js +0 -457
- package/test/comprehensive-test-suite.js +0 -928
- package/test/config-validation.test.js +0 -207
- package/test/cursor-integration.test.js +0 -484
- package/test/format-conversion.test.js +0 -578
- package/test/hybrid-routing-integration.test.js +0 -269
- package/test/hybrid-routing-performance.test.js +0 -428
- package/test/llamacpp-integration.test.js +0 -882
- package/test/lmstudio-integration.test.js +0 -347
- package/test/memory/extractor.test.js +0 -398
- package/test/memory/retriever.test.js +0 -613
- package/test/memory/retriever.test.js.bak +0 -585
- package/test/memory/search.test.js +0 -537
- package/test/memory/search.test.js.bak +0 -389
- package/test/memory/store.test.js +0 -344
- package/test/memory/store.test.js.bak +0 -312
- package/test/memory/surprise.test.js +0 -300
- package/test/memory-performance.test.js +0 -472
- package/test/openai-integration.test.js +0 -683
- package/test/openrouter-error-resilience.test.js +0 -418
- package/test/passthrough-mode.test.js +0 -385
- package/test/performance-benchmark.js +0 -351
- package/test/performance-tests.js +0 -528
- package/test/routing.test.js +0 -225
- package/test/toon-compression.test.js +0 -131
- package/test/web-tools.test.js +0 -329
- package/test-agents-simple.js +0 -43
- package/test-cli-connection.sh +0 -33
- package/test-learning-unit.js +0 -126
- package/test-learning.js +0 -112
- package/test-parallel-agents.sh +0 -124
- package/test-parallel-direct.js +0 -155
- package/test-subagents.sh +0 -117
package/test/README.md
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
# Test Suite Documentation
|
|
2
|
-
|
|
3
|
-
All tests for the Lynkr project are consolidated in this `test/` directory.
|
|
4
|
-
|
|
5
|
-
## Test Files
|
|
6
|
-
|
|
7
|
-
### Unit Tests
|
|
8
|
-
**File**: `routing.test.js`
|
|
9
|
-
**Purpose**: Tests the hybrid routing logic in isolation
|
|
10
|
-
**Run**: `DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/routing.test.js`
|
|
11
|
-
**Coverage**: 10 tests
|
|
12
|
-
- Routing with tier-based routing disabled (no TIER_* vars set)
|
|
13
|
-
- Simple requests → Ollama
|
|
14
|
-
- Complex requests → Cloud
|
|
15
|
-
- Tool capability checks
|
|
16
|
-
- Fallback configuration
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
### Integration Tests
|
|
21
|
-
**File**: `hybrid-routing-integration.test.js`
|
|
22
|
-
**Purpose**: Tests configuration validation and metrics recording
|
|
23
|
-
**Run**: `node --test test/hybrid-routing-integration.test.js`
|
|
24
|
-
**Coverage**: 13 tests
|
|
25
|
-
- Configuration validation (5 tests)
|
|
26
|
-
- Metrics recording (6 tests)
|
|
27
|
-
- Helper functions (2 tests)
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
### Hybrid Routing Performance Tests
|
|
32
|
-
**File**: `hybrid-routing-performance.test.js`
|
|
33
|
-
**Purpose**: Measures performance overhead of hybrid routing
|
|
34
|
-
**Run**: `node test/hybrid-routing-performance.test.js`
|
|
35
|
-
**Key Metrics**:
|
|
36
|
-
- Routing decision: <0.01ms (36.8M decisions/sec)
|
|
37
|
-
- Metrics overhead: <0.01ms (43.6M ops/sec)
|
|
38
|
-
- Combined overhead: <0.02ms (15.6M ops/sec)
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
### System Performance Tests
|
|
43
|
-
**File**: `performance-tests.js`
|
|
44
|
-
**Purpose**: Tests system-wide performance optimizations
|
|
45
|
-
**Run**: `DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node test/performance-tests.js`
|
|
46
|
-
**Coverage**:
|
|
47
|
-
- Database indexes (100% complete)
|
|
48
|
-
- Persistent prompt cache
|
|
49
|
-
- Regex pattern caching (4.5x faster)
|
|
50
|
-
- Lazy loading
|
|
51
|
-
- HTTP connection pooling
|
|
52
|
-
- Response compression
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
### Middleware Benchmarks
|
|
57
|
-
**File**: `performance-benchmark.js`
|
|
58
|
-
**Purpose**: Benchmarks middleware overhead
|
|
59
|
-
**Run**: `DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node test/performance-benchmark.js`
|
|
60
|
-
**Coverage**:
|
|
61
|
-
- Metrics collection (3.4M ops/sec)
|
|
62
|
-
- Circuit breakers (3.9M ops/sec)
|
|
63
|
-
- Input validation (5.7M ops/sec)
|
|
64
|
-
- Load shedding
|
|
65
|
-
- Combined middleware stack
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
## Running All Tests
|
|
70
|
-
|
|
71
|
-
### Using npm scripts (Recommended)
|
|
72
|
-
|
|
73
|
-
**Run all tests (unit + performance):**
|
|
74
|
-
```bash
|
|
75
|
-
npm test
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
**Run only unit/integration tests:**
|
|
79
|
-
```bash
|
|
80
|
-
npm run test:unit
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
**Run only performance tests:**
|
|
84
|
-
```bash
|
|
85
|
-
npm run test:performance
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
**Run only benchmarks:**
|
|
89
|
-
```bash
|
|
90
|
-
npm run test:benchmark
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
**Run quick smoke test (routing only):**
|
|
94
|
-
```bash
|
|
95
|
-
npm run test:quick
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
**Run everything including benchmarks:**
|
|
99
|
-
```bash
|
|
100
|
-
npm run test:all
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
### Manual execution (if needed)
|
|
104
|
-
|
|
105
|
-
**Quick Test:**
|
|
106
|
-
Run all unit and integration tests:
|
|
107
|
-
```bash
|
|
108
|
-
DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
**Full Test Suite:**
|
|
112
|
-
Run everything including performance tests:
|
|
113
|
-
```bash
|
|
114
|
-
# Unit + Integration tests
|
|
115
|
-
DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/routing.test.js
|
|
116
|
-
DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/hybrid-routing-integration.test.js
|
|
117
|
-
|
|
118
|
-
# Performance tests
|
|
119
|
-
node test/hybrid-routing-performance.test.js
|
|
120
|
-
DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node test/performance-tests.js
|
|
121
|
-
DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node test/performance-benchmark.js
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
---
|
|
125
|
-
|
|
126
|
-
## Test Organization
|
|
127
|
-
|
|
128
|
-
```
|
|
129
|
-
test/
|
|
130
|
-
├── README.md ← This file
|
|
131
|
-
├── routing.test.js ← Unit tests (10 tests)
|
|
132
|
-
├── hybrid-routing-integration.test.js ← Integration tests (13 tests)
|
|
133
|
-
├── hybrid-routing-performance.test.js ← Routing performance benchmarks
|
|
134
|
-
├── performance-tests.js ← System performance tests
|
|
135
|
-
└── performance-benchmark.js ← Middleware benchmarks
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
---
|
|
139
|
-
|
|
140
|
-
## Important Notes
|
|
141
|
-
|
|
142
|
-
### Not Test Files
|
|
143
|
-
- `src/tests/` - This is **application code**, not tests! It provides test execution functionality as a feature.
|
|
144
|
-
|
|
145
|
-
### Environment Variables
|
|
146
|
-
Most tests require Databricks credentials (even though they're not used in actual API calls):
|
|
147
|
-
```bash
|
|
148
|
-
export DATABRICKS_API_KEY=test-key
|
|
149
|
-
export DATABRICKS_API_BASE=http://test.com
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### Test Results Summary
|
|
153
|
-
|
|
154
|
-
| Test Type | Status | Count |
|
|
155
|
-
|-----------|--------|-------|
|
|
156
|
-
| Unit Tests | ✅ Passing | 10/10 |
|
|
157
|
-
| Integration Tests | ✅ Passing | 13/13 |
|
|
158
|
-
| Routing Performance | ✅ Complete | <0.02ms overhead |
|
|
159
|
-
| System Performance | ✅ Complete | 100% optimizations |
|
|
160
|
-
| Middleware Benchmarks | ✅ Complete | Acceptable overhead |
|
|
161
|
-
|
|
162
|
-
---
|
|
163
|
-
|
|
164
|
-
## CI/CD Integration
|
|
165
|
-
|
|
166
|
-
To run in CI/CD pipelines:
|
|
167
|
-
|
|
168
|
-
```bash
|
|
169
|
-
#!/bin/bash
|
|
170
|
-
set -e
|
|
171
|
-
|
|
172
|
-
# Run all tests using npm
|
|
173
|
-
echo "Running test suite..."
|
|
174
|
-
npm run test:all
|
|
175
|
-
|
|
176
|
-
echo "All tests passed!"
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
Or for a faster CI pipeline (skip benchmarks):
|
|
180
|
-
|
|
181
|
-
```bash
|
|
182
|
-
#!/bin/bash
|
|
183
|
-
set -e
|
|
184
|
-
|
|
185
|
-
echo "Running tests..."
|
|
186
|
-
npm test
|
|
187
|
-
|
|
188
|
-
echo "Tests passed!"
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
---
|
|
192
|
-
|
|
193
|
-
## Adding New Tests
|
|
194
|
-
|
|
195
|
-
When adding new tests, follow these conventions:
|
|
196
|
-
|
|
197
|
-
1. **Unit tests**: Test individual functions in isolation
|
|
198
|
-
- Place in `test/`
|
|
199
|
-
- Use `node:test` framework
|
|
200
|
-
- Name: `feature-name.test.js`
|
|
201
|
-
|
|
202
|
-
2. **Integration tests**: Test multiple components together
|
|
203
|
-
- Place in `test/`
|
|
204
|
-
- Use `node:test` framework
|
|
205
|
-
- Name: `feature-name-integration.test.js`
|
|
206
|
-
|
|
207
|
-
3. **Performance tests**: Benchmark specific features
|
|
208
|
-
- Place in `test/`
|
|
209
|
-
- Use custom benchmark utilities
|
|
210
|
-
- Name: `feature-name-performance.test.js`
|
|
211
|
-
|
|
212
|
-
4. **Always**: Document in this README!
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
const assert = require("assert");
|
|
2
|
-
const { describe, it, beforeEach, afterEach } = require("node:test");
|
|
3
|
-
|
|
4
|
-
describe("Azure OpenAI Configuration Tests", () => {
|
|
5
|
-
let originalConfig;
|
|
6
|
-
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
// Clear module cache
|
|
9
|
-
delete require.cache[require.resolve("../src/config")];
|
|
10
|
-
|
|
11
|
-
// Store original config
|
|
12
|
-
originalConfig = { ...process.env };
|
|
13
|
-
|
|
14
|
-
// Set Azure OpenAI environment variables to empty strings to override .env values
|
|
15
|
-
// (deleting them would cause dotenv to reload from .env file)
|
|
16
|
-
process.env.AZURE_OPENAI_ENDPOINT = "";
|
|
17
|
-
process.env.AZURE_OPENAI_API_KEY = "";
|
|
18
|
-
process.env.AZURE_OPENAI_DEPLOYMENT = "";
|
|
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 = "";
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
afterEach(() => {
|
|
29
|
-
// Restore original environment
|
|
30
|
-
process.env = originalConfig;
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
describe("Configuration Loading", () => {
|
|
34
|
-
it("should load Azure OpenAI configuration with all values set", () => {
|
|
35
|
-
process.env.AZURE_OPENAI_ENDPOINT = "https://test-resource.openai.azure.com";
|
|
36
|
-
process.env.AZURE_OPENAI_API_KEY = "test-api-key";
|
|
37
|
-
process.env.AZURE_OPENAI_DEPLOYMENT = "gpt-4o";
|
|
38
|
-
process.env.AZURE_OPENAI_API_VERSION = "2024-08-01-preview";
|
|
39
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
40
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
41
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
42
|
-
|
|
43
|
-
const config = require("../src/config");
|
|
44
|
-
|
|
45
|
-
assert.strictEqual(config.azureOpenAI.endpoint, "https://test-resource.openai.azure.com");
|
|
46
|
-
assert.strictEqual(config.azureOpenAI.apiKey, "test-api-key");
|
|
47
|
-
assert.strictEqual(config.azureOpenAI.deployment, "gpt-4o");
|
|
48
|
-
assert.strictEqual(config.azureOpenAI.apiVersion, "2024-08-01-preview");
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("should use default values when optional fields not set", () => {
|
|
52
|
-
process.env.AZURE_OPENAI_ENDPOINT = "https://test-resource.openai.azure.com";
|
|
53
|
-
process.env.AZURE_OPENAI_API_KEY = "test-api-key";
|
|
54
|
-
// Keep as empty strings (don't delete) to prevent dotenv from reloading
|
|
55
|
-
process.env.AZURE_OPENAI_DEPLOYMENT = "";
|
|
56
|
-
process.env.AZURE_OPENAI_API_VERSION = "";
|
|
57
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
58
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
59
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
60
|
-
|
|
61
|
-
const config = require("../src/config");
|
|
62
|
-
|
|
63
|
-
assert.strictEqual(config.azureOpenAI.deployment, "gpt-4o");
|
|
64
|
-
assert.strictEqual(config.azureOpenAI.apiVersion, "2024-08-01-preview");
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("should load null values when Azure OpenAI not configured", () => {
|
|
68
|
-
// Keep as empty strings (don't delete) to prevent dotenv from reloading
|
|
69
|
-
process.env.AZURE_OPENAI_ENDPOINT = "";
|
|
70
|
-
process.env.AZURE_OPENAI_API_KEY = "";
|
|
71
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
72
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
73
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
74
|
-
|
|
75
|
-
const config = require("../src/config");
|
|
76
|
-
|
|
77
|
-
assert.strictEqual(config.azureOpenAI.endpoint, null);
|
|
78
|
-
assert.strictEqual(config.azureOpenAI.apiKey, null);
|
|
79
|
-
assert.strictEqual(config.azureOpenAI.deployment, "gpt-4o"); // default
|
|
80
|
-
assert.strictEqual(config.azureOpenAI.apiVersion, "2024-08-01-preview"); // default
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
describe("Primary Provider Validation", () => {
|
|
85
|
-
it("should accept azure-openai as MODEL_PROVIDER", () => {
|
|
86
|
-
process.env.MODEL_PROVIDER = "azure-openai";
|
|
87
|
-
process.env.AZURE_OPENAI_ENDPOINT = "https://test-resource.openai.azure.com";
|
|
88
|
-
process.env.AZURE_OPENAI_API_KEY = "test-api-key";
|
|
89
|
-
|
|
90
|
-
const config = require("../src/config");
|
|
91
|
-
|
|
92
|
-
assert.strictEqual(config.modelProvider.type, "azure-openai");
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("should throw error when azure-openai is primary provider without endpoint", () => {
|
|
96
|
-
process.env.MODEL_PROVIDER = "azure-openai";
|
|
97
|
-
process.env.AZURE_OPENAI_ENDPOINT = ""; // Empty string, not deleted
|
|
98
|
-
process.env.AZURE_OPENAI_API_KEY = "test-api-key";
|
|
99
|
-
|
|
100
|
-
assert.throws(() => {
|
|
101
|
-
require("../src/config");
|
|
102
|
-
}, /AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_API_KEY/);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it("should throw error when azure-openai is primary provider without API key", () => {
|
|
106
|
-
process.env.MODEL_PROVIDER = "azure-openai";
|
|
107
|
-
process.env.AZURE_OPENAI_ENDPOINT = "https://test-resource.openai.azure.com";
|
|
108
|
-
process.env.AZURE_OPENAI_API_KEY = ""; // Empty string, not deleted
|
|
109
|
-
|
|
110
|
-
assert.throws(() => {
|
|
111
|
-
require("../src/config");
|
|
112
|
-
}, /AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_API_KEY/);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it("should throw error when azure-openai is primary provider without both", () => {
|
|
116
|
-
process.env.MODEL_PROVIDER = "azure-openai";
|
|
117
|
-
process.env.AZURE_OPENAI_ENDPOINT = ""; // Empty string, not deleted
|
|
118
|
-
process.env.AZURE_OPENAI_API_KEY = ""; // Empty string, not deleted
|
|
119
|
-
|
|
120
|
-
assert.throws(() => {
|
|
121
|
-
require("../src/config");
|
|
122
|
-
}, /AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_API_KEY/);
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
describe("Fallback Provider Validation", () => {
|
|
127
|
-
it("should accept azure-openai as fallback provider with credentials", () => {
|
|
128
|
-
process.env.MODEL_PROVIDER = "ollama";
|
|
129
|
-
process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
|
|
130
|
-
process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
|
|
131
|
-
process.env.FALLBACK_ENABLED = "true";
|
|
132
|
-
process.env.FALLBACK_PROVIDER = "azure-openai";
|
|
133
|
-
process.env.AZURE_OPENAI_ENDPOINT = "https://test-resource.openai.azure.com";
|
|
134
|
-
process.env.AZURE_OPENAI_API_KEY = "test-api-key";
|
|
135
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
136
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
137
|
-
|
|
138
|
-
const config = require("../src/config");
|
|
139
|
-
|
|
140
|
-
assert.strictEqual(config.modelProvider.fallbackProvider, "azure-openai");
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("should warn when azure-openai is fallback but credentials missing", () => {
|
|
144
|
-
process.env.MODEL_PROVIDER = "ollama";
|
|
145
|
-
process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
|
|
146
|
-
process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
|
|
147
|
-
process.env.FALLBACK_ENABLED = "true";
|
|
148
|
-
process.env.FALLBACK_PROVIDER = "azure-openai";
|
|
149
|
-
// Set to empty strings instead of deleting (dotenv.config() in config module would reload from .env)
|
|
150
|
-
process.env.AZURE_OPENAI_ENDPOINT = "";
|
|
151
|
-
process.env.AZURE_OPENAI_API_KEY = "";
|
|
152
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
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";
|
|
159
|
-
|
|
160
|
-
// Should warn but not throw (fallback misconfigured warning)
|
|
161
|
-
const config = require("../src/config");
|
|
162
|
-
assert.strictEqual(config.modelProvider.fallbackProvider, "azure-openai");
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
describe("Deployment and API Version Defaults", () => {
|
|
167
|
-
it("should use gpt-4o as default deployment", () => {
|
|
168
|
-
// Keep as empty string (don't delete) to prevent dotenv from reloading from .env
|
|
169
|
-
process.env.AZURE_OPENAI_DEPLOYMENT = "";
|
|
170
|
-
process.env.MODEL_PROVIDER = "azure-openai";
|
|
171
|
-
process.env.AZURE_OPENAI_ENDPOINT = "https://test-resource.openai.azure.com";
|
|
172
|
-
process.env.AZURE_OPENAI_API_KEY = "test-api-key";
|
|
173
|
-
|
|
174
|
-
const config = require("../src/config");
|
|
175
|
-
|
|
176
|
-
assert.strictEqual(config.azureOpenAI.deployment, "gpt-4o");
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it("should use custom deployment when specified", () => {
|
|
180
|
-
process.env.AZURE_OPENAI_DEPLOYMENT = "gpt-5";
|
|
181
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
182
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
183
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
184
|
-
|
|
185
|
-
const config = require("../src/config");
|
|
186
|
-
|
|
187
|
-
assert.strictEqual(config.azureOpenAI.deployment, "gpt-5");
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it("should use 2024-08-01-preview as default API version", () => {
|
|
191
|
-
// Keep as empty string (don't delete) to prevent dotenv from reloading from .env
|
|
192
|
-
process.env.AZURE_OPENAI_API_VERSION = "";
|
|
193
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
194
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
195
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
196
|
-
|
|
197
|
-
const config = require("../src/config");
|
|
198
|
-
|
|
199
|
-
assert.strictEqual(config.azureOpenAI.apiVersion, "2024-08-01-preview");
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it("should use custom API version when specified", () => {
|
|
203
|
-
process.env.AZURE_OPENAI_API_VERSION = "2025-01-01-preview";
|
|
204
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
205
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
206
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
207
|
-
|
|
208
|
-
const config = require("../src/config");
|
|
209
|
-
|
|
210
|
-
assert.strictEqual(config.azureOpenAI.apiVersion, "2025-01-01-preview");
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
});
|
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
const assert = require("assert");
|
|
2
|
-
const { describe, it } = require("node:test");
|
|
3
|
-
|
|
4
|
-
describe("Azure OpenAI Error Resilience Tests", () => {
|
|
5
|
-
describe("Error Response Structure", () => {
|
|
6
|
-
it("should recognize 401 authentication error", () => {
|
|
7
|
-
const errorResponse = {
|
|
8
|
-
status: 401,
|
|
9
|
-
json: {
|
|
10
|
-
error: {
|
|
11
|
-
message: "Incorrect API key provided",
|
|
12
|
-
type: "invalid_request_error",
|
|
13
|
-
code: "invalid_api_key"
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
assert.strictEqual(errorResponse.status, 401);
|
|
19
|
-
assert.strictEqual(errorResponse.json.error.code, "invalid_api_key");
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it("should recognize 403 permission denied error", () => {
|
|
23
|
-
const errorResponse = {
|
|
24
|
-
status: 403,
|
|
25
|
-
json: {
|
|
26
|
-
error: {
|
|
27
|
-
message: "The API deployment for this resource does not exist",
|
|
28
|
-
type: "invalid_request_error",
|
|
29
|
-
code: "DeploymentNotFound"
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
assert.strictEqual(errorResponse.status, 403);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("should recognize 404 deployment not found error", () => {
|
|
38
|
-
const errorResponse = {
|
|
39
|
-
status: 404,
|
|
40
|
-
json: {
|
|
41
|
-
error: {
|
|
42
|
-
message: "The API deployment for this resource does not exist",
|
|
43
|
-
type: "invalid_request_error",
|
|
44
|
-
code: "DeploymentNotFound"
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
assert.strictEqual(errorResponse.status, 404);
|
|
50
|
-
assert.strictEqual(errorResponse.json.error.code, "DeploymentNotFound");
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("should recognize 429 rate limit error with Retry-After header", () => {
|
|
54
|
-
const errorResponse = {
|
|
55
|
-
status: 429,
|
|
56
|
-
headers: {
|
|
57
|
-
"retry-after": "2",
|
|
58
|
-
"x-ratelimit-remaining-tokens": "0",
|
|
59
|
-
"x-ratelimit-remaining-requests": "0"
|
|
60
|
-
},
|
|
61
|
-
json: {
|
|
62
|
-
error: {
|
|
63
|
-
message: "Rate limit reached",
|
|
64
|
-
type: "rate_limit_error",
|
|
65
|
-
code: "rate_limit_exceeded"
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
assert.strictEqual(errorResponse.status, 429);
|
|
71
|
-
assert.strictEqual(errorResponse.headers["retry-after"], "2");
|
|
72
|
-
assert.strictEqual(errorResponse.json.error.code, "rate_limit_exceeded");
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("should recognize 400 content filter error", () => {
|
|
76
|
-
const errorResponse = {
|
|
77
|
-
status: 400,
|
|
78
|
-
json: {
|
|
79
|
-
error: {
|
|
80
|
-
message: "The response was filtered due to the prompt triggering Azure OpenAI's content management policy",
|
|
81
|
-
type: "invalid_request_error",
|
|
82
|
-
code: "content_filter"
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
assert.strictEqual(errorResponse.status, 400);
|
|
88
|
-
assert.strictEqual(errorResponse.json.error.code, "content_filter");
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it("should recognize 500 internal server error", () => {
|
|
92
|
-
const errorResponse = {
|
|
93
|
-
status: 500,
|
|
94
|
-
json: {
|
|
95
|
-
error: {
|
|
96
|
-
message: "The server had an error while processing your request",
|
|
97
|
-
type: "server_error",
|
|
98
|
-
code: "internal_error"
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
assert.strictEqual(errorResponse.status, 500);
|
|
104
|
-
assert.strictEqual(errorResponse.json.error.type, "server_error");
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("should recognize 503 service unavailable error", () => {
|
|
108
|
-
const errorResponse = {
|
|
109
|
-
status: 503,
|
|
110
|
-
json: {
|
|
111
|
-
error: {
|
|
112
|
-
message: "The service is temporarily unavailable",
|
|
113
|
-
type: "server_error",
|
|
114
|
-
code: "service_unavailable"
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
assert.strictEqual(errorResponse.status, 503);
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
describe("Missing Choices Array Validation", () => {
|
|
124
|
-
it("should detect missing choices array", () => {
|
|
125
|
-
const invalidResponse = {
|
|
126
|
-
id: "chatcmpl-123",
|
|
127
|
-
object: "chat.completion",
|
|
128
|
-
model: "gpt-4o"
|
|
129
|
-
// choices array missing
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
assert.strictEqual(invalidResponse.choices, undefined);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it("should detect empty choices array", () => {
|
|
136
|
-
const invalidResponse = {
|
|
137
|
-
id: "chatcmpl-123",
|
|
138
|
-
object: "chat.completion",
|
|
139
|
-
model: "gpt-4o",
|
|
140
|
-
choices: []
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
assert.strictEqual(invalidResponse.choices.length, 0);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it("should validate valid choices array", () => {
|
|
147
|
-
const validResponse = {
|
|
148
|
-
choices: [
|
|
149
|
-
{
|
|
150
|
-
message: {
|
|
151
|
-
role: "assistant",
|
|
152
|
-
content: "Hello"
|
|
153
|
-
},
|
|
154
|
-
finish_reason: "stop"
|
|
155
|
-
}
|
|
156
|
-
]
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
assert.ok(validResponse.choices?.length > 0);
|
|
160
|
-
assert.ok(validResponse.choices[0].message);
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
describe("Network Error Categorization", () => {
|
|
165
|
-
it("should categorize ETIMEDOUT as timeout error", () => {
|
|
166
|
-
const error = new Error("Request timeout");
|
|
167
|
-
error.code = "ETIMEDOUT";
|
|
168
|
-
|
|
169
|
-
assert.strictEqual(error.code, "ETIMEDOUT");
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it("should categorize ECONNREFUSED as connection error", () => {
|
|
173
|
-
const error = new Error("Connection refused");
|
|
174
|
-
error.code = "ECONNREFUSED";
|
|
175
|
-
|
|
176
|
-
assert.strictEqual(error.code, "ECONNREFUSED");
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it("should categorize ENOTFOUND as DNS error", () => {
|
|
180
|
-
const error = new Error("DNS lookup failed");
|
|
181
|
-
error.code = "ENOTFOUND";
|
|
182
|
-
|
|
183
|
-
assert.strictEqual(error.code, "ENOTFOUND");
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
describe("Retry Strategy", () => {
|
|
188
|
-
it("should identify retryable 5xx errors", () => {
|
|
189
|
-
const retryableStatuses = [500, 502, 503, 504];
|
|
190
|
-
|
|
191
|
-
for (const status of retryableStatuses) {
|
|
192
|
-
assert.ok(status >= 500 && status < 600);
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it("should identify non-retryable 4xx errors", () => {
|
|
197
|
-
const nonRetryableStatuses = [400, 401, 403, 404];
|
|
198
|
-
|
|
199
|
-
for (const status of nonRetryableStatuses) {
|
|
200
|
-
assert.ok(status >= 400 && status < 500);
|
|
201
|
-
assert.notStrictEqual(status, 429); // 429 is retryable
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it("should treat 429 as retryable with backoff", () => {
|
|
206
|
-
const status = 429;
|
|
207
|
-
|
|
208
|
-
assert.strictEqual(status, 429);
|
|
209
|
-
assert.ok(status === 429); // Special handling for rate limits
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
describe("Malformed JSON Response", () => {
|
|
214
|
-
it("should handle truncated JSON response", () => {
|
|
215
|
-
const truncatedJSON = '{"choices":[{"message":{"role":"assistant","content":"Hello';
|
|
216
|
-
|
|
217
|
-
assert.throws(() => {
|
|
218
|
-
JSON.parse(truncatedJSON);
|
|
219
|
-
}, SyntaxError);
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it("should handle empty response body", () => {
|
|
223
|
-
const emptyBody = "";
|
|
224
|
-
|
|
225
|
-
assert.throws(() => {
|
|
226
|
-
JSON.parse(emptyBody);
|
|
227
|
-
}, SyntaxError);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it("should handle non-JSON response", () => {
|
|
231
|
-
const htmlError = "<html><body>Error 503</body></html>";
|
|
232
|
-
|
|
233
|
-
assert.throws(() => {
|
|
234
|
-
JSON.parse(htmlError);
|
|
235
|
-
}, SyntaxError);
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
});
|