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.
Files changed (124) hide show
  1. package/README.md +3 -3
  2. package/config/model-tiers.json +89 -0
  3. package/install.sh +6 -1
  4. package/package.json +4 -2
  5. package/scripts/setup.js +0 -1
  6. package/src/agents/executor.js +14 -6
  7. package/src/api/middleware/session.js +15 -2
  8. package/src/api/openai-router.js +162 -37
  9. package/src/api/providers-handler.js +15 -1
  10. package/src/api/router.js +107 -2
  11. package/src/budget/index.js +4 -3
  12. package/src/clients/databricks.js +431 -234
  13. package/src/clients/gpt-utils.js +181 -0
  14. package/src/clients/ollama-utils.js +66 -140
  15. package/src/clients/routing.js +0 -1
  16. package/src/clients/standard-tools.js +99 -3
  17. package/src/config/index.js +133 -35
  18. package/src/context/toon.js +173 -0
  19. package/src/logger/index.js +23 -0
  20. package/src/orchestrator/index.js +688 -213
  21. package/src/routing/agentic-detector.js +320 -0
  22. package/src/routing/complexity-analyzer.js +202 -2
  23. package/src/routing/cost-optimizer.js +305 -0
  24. package/src/routing/index.js +168 -159
  25. package/src/routing/model-tiers.js +365 -0
  26. package/src/server.js +4 -14
  27. package/src/sessions/cleanup.js +3 -3
  28. package/src/sessions/record.js +10 -1
  29. package/src/sessions/store.js +7 -2
  30. package/src/tools/agent-task.js +48 -1
  31. package/src/tools/index.js +19 -2
  32. package/src/tools/lazy-loader.js +7 -0
  33. package/src/tools/tinyfish.js +358 -0
  34. package/src/tools/truncate.js +1 -0
  35. package/.github/FUNDING.yml +0 -15
  36. package/.github/workflows/README.md +0 -215
  37. package/.github/workflows/ci.yml +0 -69
  38. package/.github/workflows/index.yml +0 -62
  39. package/.github/workflows/web-tools-tests.yml +0 -56
  40. package/CITATIONS.bib +0 -6
  41. package/CLAWROUTER_ROUTING_PLAN.md +0 -910
  42. package/DEPLOYMENT.md +0 -1001
  43. package/LYNKR-TUI-PLAN.md +0 -984
  44. package/PERFORMANCE-REPORT.md +0 -866
  45. package/PLAN-per-client-model-routing.md +0 -252
  46. package/ROUTER_COMPARISON.md +0 -173
  47. package/TIER_ROUTING_PLAN.md +0 -771
  48. package/docs/42642f749da6234f41b6b425c3bb07c9.txt +0 -1
  49. package/docs/BingSiteAuth.xml +0 -4
  50. package/docs/docs-style.css +0 -478
  51. package/docs/docs.html +0 -197
  52. package/docs/google5be250e608e6da39.html +0 -1
  53. package/docs/index.html +0 -577
  54. package/docs/index.md +0 -577
  55. package/docs/robots.txt +0 -4
  56. package/docs/sitemap.xml +0 -44
  57. package/docs/style.css +0 -1223
  58. package/documentation/README.md +0 -100
  59. package/documentation/api.md +0 -806
  60. package/documentation/claude-code-cli.md +0 -672
  61. package/documentation/codex-cli.md +0 -397
  62. package/documentation/contributing.md +0 -571
  63. package/documentation/cursor-integration.md +0 -731
  64. package/documentation/docker.md +0 -867
  65. package/documentation/embeddings.md +0 -760
  66. package/documentation/faq.md +0 -659
  67. package/documentation/features.md +0 -396
  68. package/documentation/headroom.md +0 -519
  69. package/documentation/installation.md +0 -706
  70. package/documentation/memory-system.md +0 -476
  71. package/documentation/production.md +0 -601
  72. package/documentation/providers.md +0 -906
  73. package/documentation/testing.md +0 -629
  74. package/documentation/token-optimization.md +0 -323
  75. package/documentation/tools.md +0 -697
  76. package/documentation/troubleshooting.md +0 -893
  77. package/final-test.js +0 -33
  78. package/headroom-sidecar/config.py +0 -93
  79. package/headroom-sidecar/requirements.txt +0 -14
  80. package/headroom-sidecar/server.py +0 -451
  81. package/monitor-agents.sh +0 -31
  82. package/scripts/audit-log-reader.js +0 -399
  83. package/scripts/compact-dictionary.js +0 -204
  84. package/scripts/test-deduplication.js +0 -448
  85. package/src/db/database.sqlite +0 -0
  86. package/test/README.md +0 -212
  87. package/test/azure-openai-config.test.js +0 -204
  88. package/test/azure-openai-error-resilience.test.js +0 -238
  89. package/test/azure-openai-format-conversion.test.js +0 -354
  90. package/test/azure-openai-integration.test.js +0 -281
  91. package/test/azure-openai-routing.test.js +0 -177
  92. package/test/azure-openai-streaming.test.js +0 -171
  93. package/test/bedrock-integration.test.js +0 -471
  94. package/test/comprehensive-test-suite.js +0 -928
  95. package/test/config-validation.test.js +0 -207
  96. package/test/cursor-integration.test.js +0 -484
  97. package/test/format-conversion.test.js +0 -578
  98. package/test/hybrid-routing-integration.test.js +0 -254
  99. package/test/hybrid-routing-performance.test.js +0 -418
  100. package/test/llamacpp-integration.test.js +0 -863
  101. package/test/lmstudio-integration.test.js +0 -335
  102. package/test/memory/extractor.test.js +0 -398
  103. package/test/memory/retriever.test.js +0 -613
  104. package/test/memory/retriever.test.js.bak +0 -585
  105. package/test/memory/search.test.js +0 -537
  106. package/test/memory/search.test.js.bak +0 -389
  107. package/test/memory/store.test.js +0 -344
  108. package/test/memory/store.test.js.bak +0 -312
  109. package/test/memory/surprise.test.js +0 -300
  110. package/test/memory-performance.test.js +0 -472
  111. package/test/openai-integration.test.js +0 -686
  112. package/test/openrouter-error-resilience.test.js +0 -418
  113. package/test/passthrough-mode.test.js +0 -385
  114. package/test/performance-benchmark.js +0 -351
  115. package/test/performance-tests.js +0 -528
  116. package/test/routing.test.js +0 -219
  117. package/test/web-tools.test.js +0 -329
  118. package/test-agents-simple.js +0 -43
  119. package/test-cli-connection.sh +0 -33
  120. package/test-learning-unit.js +0 -126
  121. package/test-learning.js +0 -112
  122. package/test-parallel-agents.sh +0 -124
  123. package/test-parallel-direct.js +0 -155
  124. package/test-subagents.sh +0 -117
@@ -1,207 +0,0 @@
1
- const assert = require("assert");
2
- const { describe, it, beforeEach, afterEach } = require("node:test");
3
-
4
- describe("Config Validation Tests", () => {
5
- let originalEnv;
6
-
7
- beforeEach(() => {
8
- delete require.cache[require.resolve("../src/config")];
9
- originalEnv = { ...process.env };
10
- });
11
-
12
- afterEach(() => {
13
- process.env = originalEnv;
14
- });
15
-
16
- describe("MODEL_PROVIDER Validation", () => {
17
- it("should throw error for invalid provider", () => {
18
- process.env.MODEL_PROVIDER = "openaix";
19
-
20
- assert.throws(
21
- () => require("../src/config"),
22
- /Unsupported MODEL_PROVIDER.*openaix.*Valid options are:/
23
- );
24
- });
25
-
26
- it("should throw error for completely invalid provider", () => {
27
- process.env.MODEL_PROVIDER = "invalid";
28
-
29
- assert.throws(
30
- () => require("../src/config"),
31
- /Unsupported MODEL_PROVIDER.*invalid.*Valid options are:/
32
- );
33
- });
34
-
35
- it("should throw error for typo 'ollamma'", () => {
36
- process.env.MODEL_PROVIDER = "ollamma";
37
-
38
- assert.throws(
39
- () => require("../src/config"),
40
- /Unsupported MODEL_PROVIDER.*ollamma.*Valid options are:/
41
- );
42
- });
43
-
44
- it("should throw error for partial name 'azure'", () => {
45
- process.env.MODEL_PROVIDER = "azure";
46
-
47
- assert.throws(
48
- () => require("../src/config"),
49
- /Unsupported MODEL_PROVIDER.*azure.*Valid options are:/
50
- );
51
- });
52
-
53
- it("should list all valid providers in error message", () => {
54
- process.env.MODEL_PROVIDER = "invalid";
55
-
56
- try {
57
- require("../src/config");
58
- assert.fail("Should have thrown an error");
59
- } catch (err) {
60
- const message = err.message;
61
- // Check that all valid providers are listed
62
- assert.match(message, /azure-anthropic/);
63
- assert.match(message, /azure-openai/);
64
- assert.match(message, /bedrock/);
65
- assert.match(message, /databricks/);
66
- assert.match(message, /llamacpp/);
67
- assert.match(message, /lmstudio/);
68
- assert.match(message, /ollama/);
69
- assert.match(message, /openai/);
70
- assert.match(message, /openrouter/);
71
- }
72
- });
73
-
74
- it("should accept valid provider 'ollama'", () => {
75
- process.env.MODEL_PROVIDER = "ollama";
76
- process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
77
- process.env.FALLBACK_ENABLED = "false";
78
-
79
- const config = require("../src/config");
80
-
81
- assert.strictEqual(config.modelProvider.type, "ollama");
82
- });
83
-
84
- it("should accept valid provider 'databricks'", () => {
85
- process.env.MODEL_PROVIDER = "databricks";
86
- process.env.DATABRICKS_API_BASE = "http://test.com";
87
- process.env.DATABRICKS_API_KEY = "test-key";
88
-
89
- const config = require("../src/config");
90
-
91
- assert.strictEqual(config.modelProvider.type, "databricks");
92
- });
93
-
94
- it("should accept valid provider 'azure-anthropic'", () => {
95
- process.env.MODEL_PROVIDER = "azure-anthropic";
96
- process.env.AZURE_ANTHROPIC_ENDPOINT = "http://test.com";
97
- process.env.AZURE_ANTHROPIC_API_KEY = "test-key";
98
-
99
- const config = require("../src/config");
100
-
101
- assert.strictEqual(config.modelProvider.type, "azure-anthropic");
102
- });
103
-
104
- it("should accept valid provider 'azure-openai'", () => {
105
- process.env.MODEL_PROVIDER = "azure-openai";
106
- process.env.AZURE_OPENAI_ENDPOINT = "https://test-resource.openai.azure.com";
107
- process.env.AZURE_OPENAI_API_KEY = "test-key";
108
-
109
- const config = require("../src/config");
110
-
111
- assert.strictEqual(config.modelProvider.type, "azure-openai");
112
- });
113
-
114
- it("should accept valid provider 'openai'", () => {
115
- process.env.MODEL_PROVIDER = "openai";
116
- process.env.OPENAI_API_KEY = "test-key";
117
-
118
- const config = require("../src/config");
119
-
120
- assert.strictEqual(config.modelProvider.type, "openai");
121
- });
122
-
123
- it("should accept valid provider 'openrouter'", () => {
124
- process.env.MODEL_PROVIDER = "openrouter";
125
- process.env.OPENROUTER_API_KEY = "test-key";
126
- process.env.DATABRICKS_API_BASE = "http://test.com";
127
- process.env.DATABRICKS_API_KEY = "test-key";
128
-
129
- const config = require("../src/config");
130
-
131
- assert.strictEqual(config.modelProvider.type, "openrouter");
132
- });
133
-
134
- it("should accept valid provider 'llamacpp'", () => {
135
- process.env.MODEL_PROVIDER = "llamacpp";
136
- process.env.LLAMACPP_ENDPOINT = "http://localhost:8080";
137
- process.env.DATABRICKS_API_BASE = "http://test.com";
138
- process.env.DATABRICKS_API_KEY = "test-key";
139
-
140
- const config = require("../src/config");
141
-
142
- assert.strictEqual(config.modelProvider.type, "llamacpp");
143
- });
144
-
145
- it("should accept valid provider 'lmstudio'", () => {
146
- process.env.MODEL_PROVIDER = "lmstudio";
147
- process.env.LMSTUDIO_ENDPOINT = "http://localhost:1234";
148
- process.env.DATABRICKS_API_BASE = "http://test.com";
149
- process.env.DATABRICKS_API_KEY = "test-key";
150
-
151
- const config = require("../src/config");
152
-
153
- assert.strictEqual(config.modelProvider.type, "lmstudio");
154
- });
155
-
156
- it("should accept valid provider 'bedrock'", () => {
157
- process.env.MODEL_PROVIDER = "bedrock";
158
- process.env.AWS_BEDROCK_API_KEY = "test-key";
159
-
160
- const config = require("../src/config");
161
-
162
- assert.strictEqual(config.modelProvider.type, "bedrock");
163
- });
164
-
165
- it("should accept uppercase provider name 'OLLAMA'", () => {
166
- process.env.MODEL_PROVIDER = "OLLAMA";
167
- process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
168
- process.env.FALLBACK_ENABLED = "false";
169
-
170
- const config = require("../src/config");
171
-
172
- assert.strictEqual(config.modelProvider.type, "ollama");
173
- });
174
-
175
- it("should accept mixed-case provider name 'Databricks'", () => {
176
- process.env.MODEL_PROVIDER = "Databricks";
177
- process.env.DATABRICKS_API_BASE = "http://test.com";
178
- process.env.DATABRICKS_API_KEY = "test-key";
179
-
180
- const config = require("../src/config");
181
-
182
- assert.strictEqual(config.modelProvider.type, "databricks");
183
- });
184
-
185
- it("should default to 'databricks' when MODEL_PROVIDER not set", () => {
186
- delete process.env.MODEL_PROVIDER;
187
- process.env.DATABRICKS_API_BASE = "http://test.com";
188
- process.env.DATABRICKS_API_KEY = "test-key";
189
-
190
- const config = require("../src/config");
191
-
192
- assert.strictEqual(config.modelProvider.type, "databricks");
193
- });
194
-
195
- it("should show original case in error message for case-sensitive debugging", () => {
196
- process.env.MODEL_PROVIDER = "OpEnAIx";
197
-
198
- try {
199
- require("../src/config");
200
- assert.fail("Should have thrown an error");
201
- } catch (err) {
202
- // Error message should show original case for debugging
203
- assert.match(err.message, /OpEnAIx/);
204
- }
205
- });
206
- });
207
- });
@@ -1,484 +0,0 @@
1
- const assert = require("assert");
2
- const { describe, it, beforeEach, afterEach } = require("node:test");
3
- const {
4
- convertOpenAIToAnthropic,
5
- convertAnthropicToOpenAI,
6
- mapStopReason
7
- } = require("../src/clients/openai-format");
8
-
9
- describe("Cursor IDE Integration (OpenAI API Compatibility)", () => {
10
- describe("Format Conversion: OpenAI → Anthropic", () => {
11
- it("should convert simple OpenAI chat request to Anthropic format", () => {
12
- const openaiRequest = {
13
- model: "gpt-4",
14
- messages: [
15
- { role: "system", content: "You are a helpful assistant." },
16
- { role: "user", content: "Hello, world!" }
17
- ],
18
- max_tokens: 1000,
19
- temperature: 0.7
20
- };
21
-
22
- const anthropicRequest = convertOpenAIToAnthropic(openaiRequest);
23
-
24
- assert.strictEqual(anthropicRequest.system, "You are a helpful assistant.");
25
- assert.strictEqual(anthropicRequest.messages.length, 1);
26
- assert.strictEqual(anthropicRequest.messages[0].role, "user");
27
- assert.strictEqual(anthropicRequest.messages[0].content, "Hello, world!");
28
- assert.strictEqual(anthropicRequest.max_tokens, 1000);
29
- assert.strictEqual(anthropicRequest.temperature, 0.7);
30
- });
31
-
32
- it("should convert OpenAI tools to Anthropic format", () => {
33
- const openaiRequest = {
34
- model: "gpt-4",
35
- messages: [{ role: "user", content: "Read the file" }],
36
- tools: [
37
- {
38
- type: "function",
39
- function: {
40
- name: "Read",
41
- description: "Read a file",
42
- parameters: {
43
- type: "object",
44
- properties: {
45
- file_path: { type: "string" }
46
- },
47
- required: ["file_path"]
48
- }
49
- }
50
- }
51
- ]
52
- };
53
-
54
- const anthropicRequest = convertOpenAIToAnthropic(openaiRequest);
55
-
56
- assert.strictEqual(anthropicRequest.tools.length, 1);
57
- assert.strictEqual(anthropicRequest.tools[0].name, "Read");
58
- assert.strictEqual(anthropicRequest.tools[0].description, "Read a file");
59
- assert.deepStrictEqual(anthropicRequest.tools[0].input_schema, {
60
- type: "object",
61
- properties: {
62
- file_path: { type: "string" }
63
- },
64
- required: ["file_path"]
65
- });
66
- });
67
-
68
- it("should convert OpenAI tool_calls in assistant message", () => {
69
- const openaiRequest = {
70
- model: "gpt-4",
71
- messages: [
72
- {
73
- role: "assistant",
74
- content: "I'll read the file.",
75
- tool_calls: [
76
- {
77
- id: "call_123",
78
- type: "function",
79
- function: {
80
- name: "Read",
81
- arguments: '{"file_path": "/tmp/test.txt"}'
82
- }
83
- }
84
- ]
85
- }
86
- ]
87
- };
88
-
89
- const anthropicRequest = convertOpenAIToAnthropic(openaiRequest);
90
-
91
- assert.strictEqual(anthropicRequest.messages.length, 1);
92
- assert.strictEqual(anthropicRequest.messages[0].role, "assistant");
93
- assert.strictEqual(anthropicRequest.messages[0].content.length, 2);
94
- assert.strictEqual(anthropicRequest.messages[0].content[0].type, "text");
95
- assert.strictEqual(anthropicRequest.messages[0].content[1].type, "tool_use");
96
- assert.strictEqual(anthropicRequest.messages[0].content[1].name, "Read");
97
- assert.deepStrictEqual(anthropicRequest.messages[0].content[1].input, {
98
- file_path: "/tmp/test.txt"
99
- });
100
- });
101
-
102
- it("should convert OpenAI tool results", () => {
103
- const openaiRequest = {
104
- model: "gpt-4",
105
- messages: [
106
- {
107
- role: "tool",
108
- tool_call_id: "call_123",
109
- content: "File contents here"
110
- }
111
- ]
112
- };
113
-
114
- const anthropicRequest = convertOpenAIToAnthropic(openaiRequest);
115
-
116
- assert.strictEqual(anthropicRequest.messages.length, 1);
117
- assert.strictEqual(anthropicRequest.messages[0].role, "user");
118
- assert.strictEqual(anthropicRequest.messages[0].content[0].type, "tool_result");
119
- assert.strictEqual(anthropicRequest.messages[0].content[0].tool_use_id, "call_123");
120
- assert.strictEqual(anthropicRequest.messages[0].content[0].content, "File contents here");
121
- });
122
-
123
- it("should handle tool_choice conversion", () => {
124
- const autoRequest = {
125
- model: "gpt-4",
126
- messages: [{ role: "user", content: "test" }],
127
- tool_choice: "auto"
128
- };
129
-
130
- const noneRequest = {
131
- model: "gpt-4",
132
- messages: [{ role: "user", content: "test" }],
133
- tool_choice: "none"
134
- };
135
-
136
- const specificRequest = {
137
- model: "gpt-4",
138
- messages: [{ role: "user", content: "test" }],
139
- tool_choice: { type: "function", function: { name: "Read" } }
140
- };
141
-
142
- const anthropicAuto = convertOpenAIToAnthropic(autoRequest);
143
- const anthropicNone = convertOpenAIToAnthropic(noneRequest);
144
- const anthropicSpecific = convertOpenAIToAnthropic(specificRequest);
145
-
146
- assert.deepStrictEqual(anthropicAuto.tool_choice, { type: "auto" });
147
- assert.deepStrictEqual(anthropicNone.tool_choice, { type: "none" });
148
- assert.deepStrictEqual(anthropicSpecific.tool_choice, { type: "tool", name: "Read" });
149
- });
150
- });
151
-
152
- describe("Format Conversion: Anthropic → OpenAI", () => {
153
- it("should convert simple Anthropic response to OpenAI format", () => {
154
- const anthropicResponse = {
155
- id: "msg_123",
156
- type: "message",
157
- role: "assistant",
158
- content: [
159
- {
160
- type: "text",
161
- text: "Hello! How can I help you?"
162
- }
163
- ],
164
- stop_reason: "end_turn",
165
- usage: {
166
- input_tokens: 10,
167
- output_tokens: 20
168
- }
169
- };
170
-
171
- const openaiResponse = convertAnthropicToOpenAI(anthropicResponse, "gpt-4");
172
-
173
- assert.strictEqual(openaiResponse.id, "msg_123");
174
- assert.strictEqual(openaiResponse.object, "chat.completion");
175
- assert.strictEqual(openaiResponse.model, "gpt-4");
176
- assert.strictEqual(openaiResponse.choices.length, 1);
177
- assert.strictEqual(openaiResponse.choices[0].message.role, "assistant");
178
- assert.strictEqual(openaiResponse.choices[0].message.content, "Hello! How can I help you?");
179
- assert.strictEqual(openaiResponse.choices[0].finish_reason, "stop");
180
- assert.strictEqual(openaiResponse.usage.prompt_tokens, 10);
181
- assert.strictEqual(openaiResponse.usage.completion_tokens, 20);
182
- assert.strictEqual(openaiResponse.usage.total_tokens, 30);
183
- });
184
-
185
- it("should convert Anthropic tool_use to OpenAI tool_calls", () => {
186
- const anthropicResponse = {
187
- id: "msg_456",
188
- content: [
189
- {
190
- type: "text",
191
- text: "I'll read the file."
192
- },
193
- {
194
- type: "tool_use",
195
- id: "toolu_789",
196
- name: "Read",
197
- input: {
198
- file_path: "/tmp/test.txt"
199
- }
200
- }
201
- ],
202
- stop_reason: "tool_use",
203
- usage: {
204
- input_tokens: 50,
205
- output_tokens: 30
206
- }
207
- };
208
-
209
- const openaiResponse = convertAnthropicToOpenAI(anthropicResponse, "gpt-4");
210
-
211
- assert.strictEqual(openaiResponse.choices[0].message.content, "I'll read the file.");
212
- assert.strictEqual(openaiResponse.choices[0].message.tool_calls.length, 1);
213
- assert.strictEqual(openaiResponse.choices[0].message.tool_calls[0].id, "toolu_789");
214
- assert.strictEqual(openaiResponse.choices[0].message.tool_calls[0].type, "function");
215
- assert.strictEqual(openaiResponse.choices[0].message.tool_calls[0].function.name, "Read");
216
- assert.strictEqual(
217
- openaiResponse.choices[0].message.tool_calls[0].function.arguments,
218
- '{"file_path":"/tmp/test.txt"}'
219
- );
220
- assert.strictEqual(openaiResponse.choices[0].finish_reason, "tool_calls");
221
- });
222
- });
223
-
224
- describe("Stop Reason Mapping", () => {
225
- it("should map Anthropic stop reasons to OpenAI finish reasons", () => {
226
- assert.strictEqual(mapStopReason("end_turn"), "stop");
227
- assert.strictEqual(mapStopReason("max_tokens"), "length");
228
- assert.strictEqual(mapStopReason("stop_sequence"), "stop");
229
- assert.strictEqual(mapStopReason("tool_use"), "tool_calls");
230
- assert.strictEqual(mapStopReason("unknown_reason"), "stop");
231
- });
232
- });
233
-
234
- describe("OpenAI Router Endpoints", () => {
235
- it("GET /v1/models should return model list based on provider", () => {
236
- // This is an integration test - would need actual server running
237
- // Just verify the route exists in the router
238
- const openaiRouter = require("../src/api/openai-router");
239
- assert.ok(openaiRouter, "OpenAI router should be defined");
240
- });
241
-
242
- it("POST /v1/chat/completions should handle request", () => {
243
- // Integration test - would need actual server
244
- const openaiRouter = require("../src/api/openai-router");
245
- assert.ok(openaiRouter, "OpenAI router should be defined");
246
- });
247
-
248
- it("POST /v1/embeddings should return 501 when not configured", () => {
249
- // Integration test - would need actual server
250
- const openaiRouter = require("../src/api/openai-router");
251
- assert.ok(openaiRouter, "OpenAI router should be defined");
252
- });
253
-
254
- it("GET /v1/health should return health status", () => {
255
- // Integration test - would need actual server
256
- const openaiRouter = require("../src/api/openai-router");
257
- assert.ok(openaiRouter, "OpenAI router should be defined");
258
- });
259
- });
260
-
261
- describe("Edge Cases", () => {
262
- it("should handle empty messages array", () => {
263
- const openaiRequest = {
264
- model: "gpt-4",
265
- messages: []
266
- };
267
-
268
- const anthropicRequest = convertOpenAIToAnthropic(openaiRequest);
269
- assert.strictEqual(anthropicRequest.messages.length, 0);
270
- });
271
-
272
- it("should handle missing optional fields", () => {
273
- const openaiRequest = {
274
- model: "gpt-4",
275
- messages: [{ role: "user", content: "test" }]
276
- };
277
-
278
- const anthropicRequest = convertOpenAIToAnthropic(openaiRequest);
279
- assert.ok(anthropicRequest.max_tokens); // Should have default
280
- assert.ok(!anthropicRequest.temperature); // Should not exist if not provided
281
- });
282
-
283
- it("should handle multiple text blocks in Anthropic response", () => {
284
- const anthropicResponse = {
285
- id: "msg_multi",
286
- content: [
287
- { type: "text", text: "First part. " },
288
- { type: "text", text: "Second part." }
289
- ],
290
- stop_reason: "end_turn",
291
- usage: { input_tokens: 5, output_tokens: 10 }
292
- };
293
-
294
- const openaiResponse = convertAnthropicToOpenAI(anthropicResponse);
295
- assert.strictEqual(openaiResponse.choices[0].message.content, "First part. Second part.");
296
- });
297
- });
298
-
299
- describe("OpenRouter Embeddings Configuration", () => {
300
- it("should use configured embeddings model", () => {
301
- process.env.OPENROUTER_EMBEDDINGS_MODEL = "openai/text-embedding-3-small";
302
- delete require.cache[require.resolve("../src/config")];
303
- const config = require("../src/config");
304
-
305
- assert.strictEqual(config.openrouter.embeddingsModel, "openai/text-embedding-3-small");
306
- });
307
-
308
- it("should default to ada-002 when not configured", () => {
309
- delete process.env.OPENROUTER_EMBEDDINGS_MODEL;
310
- delete require.cache[require.resolve("../src/config")];
311
- const config = require("../src/config");
312
-
313
- assert.strictEqual(config.openrouter.embeddingsModel, "openai/text-embedding-ada-002");
314
- });
315
-
316
- it("should allow different models for chat and embeddings", () => {
317
- process.env.OPENROUTER_MODEL = "anthropic/claude-3.5-sonnet";
318
- process.env.OPENROUTER_EMBEDDINGS_MODEL = "openai/text-embedding-3-small";
319
- delete require.cache[require.resolve("../src/config")];
320
- const config = require("../src/config");
321
-
322
- assert.strictEqual(config.openrouter.model, "anthropic/claude-3.5-sonnet");
323
- assert.strictEqual(config.openrouter.embeddingsModel, "openai/text-embedding-3-small");
324
- assert.notStrictEqual(config.openrouter.model, config.openrouter.embeddingsModel);
325
- });
326
- });
327
-
328
- describe("Local Embeddings Configuration", () => {
329
- let originalEnv;
330
-
331
- beforeEach(() => {
332
- originalEnv = { ...process.env };
333
- delete require.cache[require.resolve("../src/config")];
334
- });
335
-
336
- afterEach(() => {
337
- process.env = originalEnv;
338
- delete require.cache[require.resolve("../src/config")];
339
- });
340
-
341
- describe("Ollama Embeddings", () => {
342
- it("should use configured Ollama embeddings model", () => {
343
- process.env.OLLAMA_EMBEDDINGS_MODEL = "mxbai-embed-large";
344
- const config = require("../src/config");
345
-
346
- assert.strictEqual(config.ollama.embeddingsModel, "mxbai-embed-large");
347
- });
348
-
349
- it("should default to nomic-embed-text when not configured", () => {
350
- delete process.env.OLLAMA_EMBEDDINGS_MODEL;
351
- const config = require("../src/config");
352
-
353
- assert.strictEqual(config.ollama.embeddingsModel, "nomic-embed-text");
354
- });
355
-
356
- it("should use custom Ollama embeddings endpoint", () => {
357
- process.env.OLLAMA_EMBEDDINGS_ENDPOINT = "http://localhost:9999/api/embeddings";
358
- const config = require("../src/config");
359
-
360
- assert.strictEqual(config.ollama.embeddingsEndpoint, "http://localhost:9999/api/embeddings");
361
- });
362
-
363
- it("should default Ollama embeddings endpoint to /api/embeddings", () => {
364
- delete process.env.OLLAMA_EMBEDDINGS_ENDPOINT;
365
- process.env.OLLAMA_ENDPOINT = "http://localhost:11434";
366
- const config = require("../src/config");
367
-
368
- assert.strictEqual(config.ollama.embeddingsEndpoint, "http://localhost:11434/api/embeddings");
369
- });
370
-
371
- it("should allow different models for Ollama chat and embeddings", () => {
372
- process.env.OLLAMA_MODEL = "llama3.2";
373
- process.env.OLLAMA_EMBEDDINGS_MODEL = "nomic-embed-text";
374
- const config = require("../src/config");
375
-
376
- assert.strictEqual(config.ollama.model, "llama3.2");
377
- assert.strictEqual(config.ollama.embeddingsModel, "nomic-embed-text");
378
- assert.notStrictEqual(config.ollama.model, config.ollama.embeddingsModel);
379
- });
380
- });
381
-
382
- describe("llama.cpp Embeddings", () => {
383
- it("should use configured llama.cpp embeddings endpoint", () => {
384
- process.env.LLAMACPP_EMBEDDINGS_ENDPOINT = "http://localhost:9000/embeddings";
385
- const config = require("../src/config");
386
-
387
- assert.strictEqual(config.llamacpp.embeddingsEndpoint, "http://localhost:9000/embeddings");
388
- });
389
-
390
- it("should default llama.cpp embeddings endpoint to /embeddings", () => {
391
- delete process.env.LLAMACPP_EMBEDDINGS_ENDPOINT;
392
- process.env.LLAMACPP_ENDPOINT = "http://localhost:8080";
393
- const config = require("../src/config");
394
-
395
- assert.strictEqual(config.llamacpp.embeddingsEndpoint, "http://localhost:8080/embeddings");
396
- });
397
- });
398
-
399
- describe("Embeddings Provider Priority", () => {
400
- it("should use explicit EMBEDDINGS_PROVIDER when set to ollama", () => {
401
- process.env.EMBEDDINGS_PROVIDER = "ollama";
402
- process.env.OLLAMA_EMBEDDINGS_MODEL = "nomic-embed-text";
403
- process.env.OPENROUTER_API_KEY = "sk-test";
404
-
405
- // This test verifies the config allows the explicit provider to be set
406
- // Actual provider detection logic is in openai-router.js
407
- const config = require("../src/config");
408
- assert.strictEqual(process.env.EMBEDDINGS_PROVIDER, "ollama");
409
- });
410
-
411
- it("should use explicit EMBEDDINGS_PROVIDER when set to llamacpp", () => {
412
- process.env.EMBEDDINGS_PROVIDER = "llamacpp";
413
- process.env.LLAMACPP_EMBEDDINGS_ENDPOINT = "http://localhost:8080/embeddings";
414
- process.env.OPENROUTER_API_KEY = "sk-test";
415
-
416
- const config = require("../src/config");
417
- assert.strictEqual(process.env.EMBEDDINGS_PROVIDER, "llamacpp");
418
- });
419
-
420
- it("should use explicit EMBEDDINGS_PROVIDER when set to openrouter", () => {
421
- process.env.EMBEDDINGS_PROVIDER = "openrouter";
422
- process.env.OPENROUTER_API_KEY = "sk-test";
423
- process.env.OLLAMA_EMBEDDINGS_MODEL = "nomic-embed-text";
424
-
425
- const config = require("../src/config");
426
- assert.strictEqual(process.env.EMBEDDINGS_PROVIDER, "openrouter");
427
- });
428
-
429
- it("should use explicit EMBEDDINGS_PROVIDER when set to openai", () => {
430
- process.env.EMBEDDINGS_PROVIDER = "openai";
431
- process.env.OPENAI_API_KEY = "sk-test";
432
- process.env.OLLAMA_EMBEDDINGS_MODEL = "nomic-embed-text";
433
-
434
- const config = require("../src/config");
435
- assert.strictEqual(process.env.EMBEDDINGS_PROVIDER, "openai");
436
- });
437
- });
438
-
439
- describe("Privacy and Cost Comparison", () => {
440
- it("should support 100% local setup with Ollama chat + Ollama embeddings", () => {
441
- process.env.MODEL_PROVIDER = "ollama";
442
- process.env.OLLAMA_MODEL = "llama3.2";
443
- process.env.OLLAMA_EMBEDDINGS_MODEL = "nomic-embed-text";
444
- delete process.env.OPENROUTER_API_KEY;
445
- delete process.env.OPENAI_API_KEY;
446
-
447
- const config = require("../src/config");
448
- assert.strictEqual(config.modelProvider.type, "ollama");
449
- assert.strictEqual(config.ollama.model, "llama3.2");
450
- assert.strictEqual(config.ollama.embeddingsModel, "nomic-embed-text");
451
- // Verify no cloud API keys configured (100% local)
452
- assert.ok(!config.openrouter?.apiKey);
453
- assert.ok(!config.openai?.apiKey);
454
- });
455
-
456
- it("should support 100% local setup with Ollama chat + llama.cpp embeddings", () => {
457
- process.env.MODEL_PROVIDER = "ollama";
458
- process.env.OLLAMA_MODEL = "llama3.2";
459
- process.env.LLAMACPP_EMBEDDINGS_ENDPOINT = "http://localhost:8080/embeddings";
460
- delete process.env.OPENROUTER_API_KEY;
461
- delete process.env.OPENAI_API_KEY;
462
-
463
- const config = require("../src/config");
464
- assert.strictEqual(config.modelProvider.type, "ollama");
465
- assert.strictEqual(config.ollama.model, "llama3.2");
466
- assert.strictEqual(config.llamacpp.embeddingsEndpoint, "http://localhost:8080/embeddings");
467
- // Verify no cloud API keys configured (100% local)
468
- assert.ok(!config.openrouter?.apiKey);
469
- assert.ok(!config.openai?.apiKey);
470
- });
471
-
472
- it("should support hybrid setup with Databricks chat + Ollama embeddings", () => {
473
- process.env.MODEL_PROVIDER = "databricks";
474
- process.env.DATABRICKS_API_KEY = "test-key";
475
- process.env.DATABRICKS_API_BASE = "http://test.com";
476
- process.env.OLLAMA_EMBEDDINGS_MODEL = "nomic-embed-text";
477
-
478
- const config = require("../src/config");
479
- assert.strictEqual(config.modelProvider.type, "databricks");
480
- assert.strictEqual(config.ollama.embeddingsModel, "nomic-embed-text");
481
- });
482
- });
483
- });
484
- });