lynkr 7.2.5 → 8.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/README.md +3 -3
- package/config/model-tiers.json +89 -0
- package/install.sh +6 -1
- package/package.json +4 -2
- package/scripts/setup.js +0 -1
- package/src/agents/executor.js +14 -6
- package/src/api/middleware/session.js +15 -2
- package/src/api/openai-router.js +162 -37
- package/src/api/providers-handler.js +15 -1
- package/src/api/router.js +107 -2
- package/src/budget/index.js +4 -3
- package/src/clients/databricks.js +431 -234
- package/src/clients/gpt-utils.js +181 -0
- package/src/clients/ollama-utils.js +66 -140
- package/src/clients/routing.js +0 -1
- package/src/clients/standard-tools.js +99 -3
- package/src/config/index.js +133 -35
- package/src/context/toon.js +173 -0
- package/src/logger/index.js +23 -0
- package/src/orchestrator/index.js +688 -213
- package/src/routing/agentic-detector.js +320 -0
- package/src/routing/complexity-analyzer.js +202 -2
- package/src/routing/cost-optimizer.js +305 -0
- package/src/routing/index.js +168 -159
- package/src/routing/model-tiers.js +365 -0
- package/src/server.js +4 -14
- package/src/sessions/cleanup.js +3 -3
- package/src/sessions/record.js +10 -1
- package/src/sessions/store.js +7 -2
- package/src/tools/agent-task.js +48 -1
- package/src/tools/index.js +19 -2
- package/src/tools/lazy-loader.js +7 -0
- package/src/tools/tinyfish.js +358 -0
- package/src/tools/truncate.js +1 -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/CLAWROUTER_ROUTING_PLAN.md +0 -910
- 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/ROUTER_COMPARISON.md +0 -173
- package/TIER_ROUTING_PLAN.md +0 -771
- package/docs/42642f749da6234f41b6b425c3bb07c9.txt +0 -1
- package/docs/BingSiteAuth.xml +0 -4
- package/docs/docs-style.css +0 -478
- package/docs/docs.html +0 -197
- package/docs/google5be250e608e6da39.html +0 -1
- package/docs/index.html +0 -577
- package/docs/index.md +0 -577
- package/docs/robots.txt +0 -4
- package/docs/sitemap.xml +0 -44
- package/docs/style.css +0 -1223
- package/documentation/README.md +0 -100
- package/documentation/api.md +0 -806
- package/documentation/claude-code-cli.md +0 -672
- package/documentation/codex-cli.md +0 -397
- package/documentation/contributing.md +0 -571
- package/documentation/cursor-integration.md +0 -731
- package/documentation/docker.md +0 -867
- package/documentation/embeddings.md +0 -760
- package/documentation/faq.md +0 -659
- package/documentation/features.md +0 -396
- package/documentation/headroom.md +0 -519
- package/documentation/installation.md +0 -706
- package/documentation/memory-system.md +0 -476
- package/documentation/production.md +0 -601
- package/documentation/providers.md +0 -906
- package/documentation/testing.md +0 -629
- package/documentation/token-optimization.md +0 -323
- package/documentation/tools.md +0 -697
- package/documentation/troubleshooting.md +0 -893
- 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/test/README.md +0 -212
- package/test/azure-openai-config.test.js +0 -204
- 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 -281
- package/test/azure-openai-routing.test.js +0 -177
- package/test/azure-openai-streaming.test.js +0 -171
- package/test/bedrock-integration.test.js +0 -471
- 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 -254
- package/test/hybrid-routing-performance.test.js +0 -418
- package/test/llamacpp-integration.test.js +0 -863
- package/test/lmstudio-integration.test.js +0 -335
- 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 -686
- 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 -219
- 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
|
@@ -1,204 +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
|
-
|
|
22
|
-
afterEach(() => {
|
|
23
|
-
// Restore original environment
|
|
24
|
-
process.env = originalConfig;
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe("Configuration Loading", () => {
|
|
28
|
-
it("should load Azure OpenAI configuration with all values set", () => {
|
|
29
|
-
process.env.AZURE_OPENAI_ENDPOINT = "https://test-resource.openai.azure.com";
|
|
30
|
-
process.env.AZURE_OPENAI_API_KEY = "test-api-key";
|
|
31
|
-
process.env.AZURE_OPENAI_DEPLOYMENT = "gpt-4o";
|
|
32
|
-
process.env.AZURE_OPENAI_API_VERSION = "2024-08-01-preview";
|
|
33
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
34
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
35
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
36
|
-
|
|
37
|
-
const config = require("../src/config");
|
|
38
|
-
|
|
39
|
-
assert.strictEqual(config.azureOpenAI.endpoint, "https://test-resource.openai.azure.com");
|
|
40
|
-
assert.strictEqual(config.azureOpenAI.apiKey, "test-api-key");
|
|
41
|
-
assert.strictEqual(config.azureOpenAI.deployment, "gpt-4o");
|
|
42
|
-
assert.strictEqual(config.azureOpenAI.apiVersion, "2024-08-01-preview");
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("should use default values when optional fields not set", () => {
|
|
46
|
-
process.env.AZURE_OPENAI_ENDPOINT = "https://test-resource.openai.azure.com";
|
|
47
|
-
process.env.AZURE_OPENAI_API_KEY = "test-api-key";
|
|
48
|
-
// Keep as empty strings (don't delete) to prevent dotenv from reloading
|
|
49
|
-
process.env.AZURE_OPENAI_DEPLOYMENT = "";
|
|
50
|
-
process.env.AZURE_OPENAI_API_VERSION = "";
|
|
51
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
52
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
53
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
54
|
-
|
|
55
|
-
const config = require("../src/config");
|
|
56
|
-
|
|
57
|
-
assert.strictEqual(config.azureOpenAI.deployment, "gpt-4o");
|
|
58
|
-
assert.strictEqual(config.azureOpenAI.apiVersion, "2024-08-01-preview");
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("should load null values when Azure OpenAI not configured", () => {
|
|
62
|
-
// Keep as empty strings (don't delete) to prevent dotenv from reloading
|
|
63
|
-
process.env.AZURE_OPENAI_ENDPOINT = "";
|
|
64
|
-
process.env.AZURE_OPENAI_API_KEY = "";
|
|
65
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
66
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
67
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
68
|
-
|
|
69
|
-
const config = require("../src/config");
|
|
70
|
-
|
|
71
|
-
assert.strictEqual(config.azureOpenAI.endpoint, null);
|
|
72
|
-
assert.strictEqual(config.azureOpenAI.apiKey, null);
|
|
73
|
-
assert.strictEqual(config.azureOpenAI.deployment, "gpt-4o"); // default
|
|
74
|
-
assert.strictEqual(config.azureOpenAI.apiVersion, "2024-08-01-preview"); // default
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
describe("Primary Provider Validation", () => {
|
|
79
|
-
it("should accept azure-openai as MODEL_PROVIDER", () => {
|
|
80
|
-
process.env.MODEL_PROVIDER = "azure-openai";
|
|
81
|
-
process.env.AZURE_OPENAI_ENDPOINT = "https://test-resource.openai.azure.com";
|
|
82
|
-
process.env.AZURE_OPENAI_API_KEY = "test-api-key";
|
|
83
|
-
|
|
84
|
-
const config = require("../src/config");
|
|
85
|
-
|
|
86
|
-
assert.strictEqual(config.modelProvider.type, "azure-openai");
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("should throw error when azure-openai is primary provider without endpoint", () => {
|
|
90
|
-
process.env.MODEL_PROVIDER = "azure-openai";
|
|
91
|
-
process.env.AZURE_OPENAI_ENDPOINT = ""; // Empty string, not deleted
|
|
92
|
-
process.env.AZURE_OPENAI_API_KEY = "test-api-key";
|
|
93
|
-
|
|
94
|
-
assert.throws(() => {
|
|
95
|
-
require("../src/config");
|
|
96
|
-
}, /AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_API_KEY/);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it("should throw error when azure-openai is primary provider without API key", () => {
|
|
100
|
-
process.env.MODEL_PROVIDER = "azure-openai";
|
|
101
|
-
process.env.AZURE_OPENAI_ENDPOINT = "https://test-resource.openai.azure.com";
|
|
102
|
-
process.env.AZURE_OPENAI_API_KEY = ""; // Empty string, not deleted
|
|
103
|
-
|
|
104
|
-
assert.throws(() => {
|
|
105
|
-
require("../src/config");
|
|
106
|
-
}, /AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_API_KEY/);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it("should throw error when azure-openai is primary provider without both", () => {
|
|
110
|
-
process.env.MODEL_PROVIDER = "azure-openai";
|
|
111
|
-
process.env.AZURE_OPENAI_ENDPOINT = ""; // Empty string, not deleted
|
|
112
|
-
process.env.AZURE_OPENAI_API_KEY = ""; // Empty string, not deleted
|
|
113
|
-
|
|
114
|
-
assert.throws(() => {
|
|
115
|
-
require("../src/config");
|
|
116
|
-
}, /AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_API_KEY/);
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
describe("Fallback Provider Validation", () => {
|
|
121
|
-
it("should accept azure-openai as fallback provider with credentials", () => {
|
|
122
|
-
process.env.PREFER_OLLAMA = "true";
|
|
123
|
-
process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
|
|
124
|
-
process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
|
|
125
|
-
process.env.FALLBACK_ENABLED = "true";
|
|
126
|
-
process.env.FALLBACK_PROVIDER = "azure-openai";
|
|
127
|
-
process.env.AZURE_OPENAI_ENDPOINT = "https://test-resource.openai.azure.com";
|
|
128
|
-
process.env.AZURE_OPENAI_API_KEY = "test-api-key";
|
|
129
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
130
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
131
|
-
|
|
132
|
-
const config = require("../src/config");
|
|
133
|
-
|
|
134
|
-
assert.strictEqual(config.modelProvider.fallbackProvider, "azure-openai");
|
|
135
|
-
});
|
|
136
|
-
|
|
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";
|
|
140
|
-
process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
|
|
141
|
-
process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
|
|
142
|
-
process.env.FALLBACK_ENABLED = "true";
|
|
143
|
-
process.env.FALLBACK_PROVIDER = "azure-openai";
|
|
144
|
-
// Set to empty strings instead of deleting (dotenv.config() in config module would reload from .env)
|
|
145
|
-
process.env.AZURE_OPENAI_ENDPOINT = "";
|
|
146
|
-
process.env.AZURE_OPENAI_API_KEY = "";
|
|
147
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
148
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
149
|
-
|
|
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/);
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
describe("Deployment and API Version Defaults", () => {
|
|
158
|
-
it("should use gpt-4o as default deployment", () => {
|
|
159
|
-
// Keep as empty string (don't delete) to prevent dotenv from reloading from .env
|
|
160
|
-
process.env.AZURE_OPENAI_DEPLOYMENT = "";
|
|
161
|
-
process.env.MODEL_PROVIDER = "azure-openai";
|
|
162
|
-
process.env.AZURE_OPENAI_ENDPOINT = "https://test-resource.openai.azure.com";
|
|
163
|
-
process.env.AZURE_OPENAI_API_KEY = "test-api-key";
|
|
164
|
-
|
|
165
|
-
const config = require("../src/config");
|
|
166
|
-
|
|
167
|
-
assert.strictEqual(config.azureOpenAI.deployment, "gpt-4o");
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it("should use custom deployment when specified", () => {
|
|
171
|
-
process.env.AZURE_OPENAI_DEPLOYMENT = "gpt-5";
|
|
172
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
173
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
174
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
175
|
-
|
|
176
|
-
const config = require("../src/config");
|
|
177
|
-
|
|
178
|
-
assert.strictEqual(config.azureOpenAI.deployment, "gpt-5");
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it("should use 2024-08-01-preview as default API version", () => {
|
|
182
|
-
// Keep as empty string (don't delete) to prevent dotenv from reloading from .env
|
|
183
|
-
process.env.AZURE_OPENAI_API_VERSION = "";
|
|
184
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
185
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
186
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
187
|
-
|
|
188
|
-
const config = require("../src/config");
|
|
189
|
-
|
|
190
|
-
assert.strictEqual(config.azureOpenAI.apiVersion, "2024-08-01-preview");
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it("should use custom API version when specified", () => {
|
|
194
|
-
process.env.AZURE_OPENAI_API_VERSION = "2025-01-01-preview";
|
|
195
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
196
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
197
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
198
|
-
|
|
199
|
-
const config = require("../src/config");
|
|
200
|
-
|
|
201
|
-
assert.strictEqual(config.azureOpenAI.apiVersion, "2025-01-01-preview");
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
});
|
|
@@ -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
|
-
});
|