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.
Files changed (128) hide show
  1. package/.lynkr/telemetry.db +0 -0
  2. package/.lynkr/telemetry.db-shm +0 -0
  3. package/.lynkr/telemetry.db-wal +0 -0
  4. package/README.md +196 -322
  5. package/lynkr-skill.tar.gz +0 -0
  6. package/package.json +4 -3
  7. package/src/api/openai-router.js +64 -13
  8. package/src/api/providers-handler.js +171 -3
  9. package/src/api/router.js +9 -2
  10. package/src/clients/circuit-breaker.js +10 -247
  11. package/src/clients/codex-process.js +342 -0
  12. package/src/clients/codex-utils.js +143 -0
  13. package/src/clients/databricks.js +210 -63
  14. package/src/clients/resilience.js +540 -0
  15. package/src/clients/retry.js +22 -167
  16. package/src/clients/standard-tools.js +23 -0
  17. package/src/config/index.js +77 -0
  18. package/src/context/compression.js +42 -9
  19. package/src/context/distill.js +492 -0
  20. package/src/orchestrator/index.js +48 -8
  21. package/src/routing/complexity-analyzer.js +258 -5
  22. package/src/routing/index.js +12 -2
  23. package/src/routing/latency-tracker.js +148 -0
  24. package/src/routing/model-tiers.js +2 -0
  25. package/src/routing/quality-scorer.js +113 -0
  26. package/src/routing/telemetry.js +464 -0
  27. package/src/server.js +13 -12
  28. package/src/tools/code-graph.js +538 -0
  29. package/src/tools/code-mode.js +304 -0
  30. package/src/tools/index.js +4 -0
  31. package/src/tools/lazy-loader.js +18 -0
  32. package/src/tools/mcp-remote.js +7 -0
  33. package/src/tools/smart-selection.js +11 -0
  34. package/src/tools/tinyfish.js +358 -0
  35. package/src/tools/truncate.js +1 -0
  36. package/src/utils/payload.js +206 -0
  37. package/src/utils/perf-timer.js +80 -0
  38. package/.github/FUNDING.yml +0 -15
  39. package/.github/workflows/README.md +0 -215
  40. package/.github/workflows/ci.yml +0 -69
  41. package/.github/workflows/index.yml +0 -62
  42. package/.github/workflows/web-tools-tests.yml +0 -56
  43. package/CITATIONS.bib +0 -6
  44. package/DEPLOYMENT.md +0 -1001
  45. package/LYNKR-TUI-PLAN.md +0 -984
  46. package/PERFORMANCE-REPORT.md +0 -866
  47. package/PLAN-per-client-model-routing.md +0 -252
  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 -198
  52. package/docs/google5be250e608e6da39.html +0 -1
  53. package/docs/index.html +0 -577
  54. package/docs/index.md +0 -584
  55. package/docs/robots.txt +0 -4
  56. package/docs/sitemap.xml +0 -44
  57. package/docs/style.css +0 -1223
  58. package/docs/toon-integration-spec.md +0 -130
  59. package/documentation/README.md +0 -101
  60. package/documentation/api.md +0 -806
  61. package/documentation/claude-code-cli.md +0 -679
  62. package/documentation/codex-cli.md +0 -397
  63. package/documentation/contributing.md +0 -571
  64. package/documentation/cursor-integration.md +0 -734
  65. package/documentation/docker.md +0 -874
  66. package/documentation/embeddings.md +0 -762
  67. package/documentation/faq.md +0 -713
  68. package/documentation/features.md +0 -403
  69. package/documentation/headroom.md +0 -519
  70. package/documentation/installation.md +0 -758
  71. package/documentation/memory-system.md +0 -476
  72. package/documentation/production.md +0 -636
  73. package/documentation/providers.md +0 -1009
  74. package/documentation/routing.md +0 -476
  75. package/documentation/testing.md +0 -629
  76. package/documentation/token-optimization.md +0 -325
  77. package/documentation/tools.md +0 -697
  78. package/documentation/troubleshooting.md +0 -969
  79. package/final-test.js +0 -33
  80. package/headroom-sidecar/config.py +0 -93
  81. package/headroom-sidecar/requirements.txt +0 -14
  82. package/headroom-sidecar/server.py +0 -451
  83. package/monitor-agents.sh +0 -31
  84. package/scripts/audit-log-reader.js +0 -399
  85. package/scripts/compact-dictionary.js +0 -204
  86. package/scripts/test-deduplication.js +0 -448
  87. package/src/db/database.sqlite +0 -0
  88. package/te +0 -11622
  89. package/test/README.md +0 -212
  90. package/test/azure-openai-config.test.js +0 -213
  91. package/test/azure-openai-error-resilience.test.js +0 -238
  92. package/test/azure-openai-format-conversion.test.js +0 -354
  93. package/test/azure-openai-integration.test.js +0 -287
  94. package/test/azure-openai-routing.test.js +0 -175
  95. package/test/azure-openai-streaming.test.js +0 -171
  96. package/test/bedrock-integration.test.js +0 -457
  97. package/test/comprehensive-test-suite.js +0 -928
  98. package/test/config-validation.test.js +0 -207
  99. package/test/cursor-integration.test.js +0 -484
  100. package/test/format-conversion.test.js +0 -578
  101. package/test/hybrid-routing-integration.test.js +0 -269
  102. package/test/hybrid-routing-performance.test.js +0 -428
  103. package/test/llamacpp-integration.test.js +0 -882
  104. package/test/lmstudio-integration.test.js +0 -347
  105. package/test/memory/extractor.test.js +0 -398
  106. package/test/memory/retriever.test.js +0 -613
  107. package/test/memory/retriever.test.js.bak +0 -585
  108. package/test/memory/search.test.js +0 -537
  109. package/test/memory/search.test.js.bak +0 -389
  110. package/test/memory/store.test.js +0 -344
  111. package/test/memory/store.test.js.bak +0 -312
  112. package/test/memory/surprise.test.js +0 -300
  113. package/test/memory-performance.test.js +0 -472
  114. package/test/openai-integration.test.js +0 -683
  115. package/test/openrouter-error-resilience.test.js +0 -418
  116. package/test/passthrough-mode.test.js +0 -385
  117. package/test/performance-benchmark.js +0 -351
  118. package/test/performance-tests.js +0 -528
  119. package/test/routing.test.js +0 -225
  120. package/test/toon-compression.test.js +0 -131
  121. package/test/web-tools.test.js +0 -329
  122. package/test-agents-simple.js +0 -43
  123. package/test-cli-connection.sh +0 -33
  124. package/test-learning-unit.js +0 -126
  125. package/test-learning.js +0 -112
  126. package/test-parallel-agents.sh +0 -124
  127. package/test-parallel-direct.js +0 -155
  128. 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
- });