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,863 +0,0 @@
|
|
|
1
|
-
const assert = require("assert");
|
|
2
|
-
const { describe, it, beforeEach, afterEach } = require("node:test");
|
|
3
|
-
|
|
4
|
-
describe("llama.cpp Integration", () => {
|
|
5
|
-
let originalEnv;
|
|
6
|
-
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
originalEnv = { ...process.env };
|
|
9
|
-
|
|
10
|
-
// Clear module cache
|
|
11
|
-
delete require.cache[require.resolve("../src/config")];
|
|
12
|
-
delete require.cache[require.resolve("../src/clients/routing")];
|
|
13
|
-
delete require.cache[require.resolve("../src/clients/openrouter-utils")];
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
afterEach(() => {
|
|
17
|
-
process.env = originalEnv;
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
describe("Configuration", () => {
|
|
21
|
-
it("should accept llamacpp as a valid MODEL_PROVIDER", () => {
|
|
22
|
-
process.env.MODEL_PROVIDER = "llamacpp";
|
|
23
|
-
process.env.LLAMACPP_ENDPOINT = "http://localhost:8080";
|
|
24
|
-
|
|
25
|
-
const config = require("../src/config");
|
|
26
|
-
assert.strictEqual(config.modelProvider.type, "llamacpp");
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("should use default endpoint when LLAMACPP_ENDPOINT is not set", () => {
|
|
30
|
-
process.env.MODEL_PROVIDER = "llamacpp";
|
|
31
|
-
delete process.env.LLAMACPP_ENDPOINT; // Remove from test env
|
|
32
|
-
process.env.LLAMACPP_ENDPOINT = undefined; // Ensure it's truly unset
|
|
33
|
-
|
|
34
|
-
const config = require("../src/config");
|
|
35
|
-
assert.strictEqual(config.llamacpp.endpoint, "http://localhost:8080");
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("should use custom endpoint when LLAMACPP_ENDPOINT is set", () => {
|
|
39
|
-
process.env.MODEL_PROVIDER = "llamacpp";
|
|
40
|
-
process.env.LLAMACPP_ENDPOINT = "http://192.168.1.100:9000";
|
|
41
|
-
|
|
42
|
-
const config = require("../src/config");
|
|
43
|
-
assert.strictEqual(config.llamacpp.endpoint, "http://192.168.1.100:9000");
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it("should throw error when LLAMACPP_ENDPOINT is invalid URL", () => {
|
|
47
|
-
process.env.MODEL_PROVIDER = "llamacpp";
|
|
48
|
-
process.env.LLAMACPP_ENDPOINT = "not-a-valid-url";
|
|
49
|
-
|
|
50
|
-
assert.throws(
|
|
51
|
-
() => require("../src/config"),
|
|
52
|
-
/LLAMACPP_ENDPOINT must be a valid URL/
|
|
53
|
-
);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it("should use default model when LLAMACPP_MODEL is not set", () => {
|
|
57
|
-
process.env.MODEL_PROVIDER = "llamacpp";
|
|
58
|
-
delete process.env.LLAMACPP_MODEL; // Remove from test env
|
|
59
|
-
process.env.LLAMACPP_MODEL = undefined; // Ensure it's truly unset
|
|
60
|
-
|
|
61
|
-
const config = require("../src/config");
|
|
62
|
-
assert.strictEqual(config.llamacpp.model, "default");
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("should use custom model when LLAMACPP_MODEL is set", () => {
|
|
66
|
-
process.env.MODEL_PROVIDER = "llamacpp";
|
|
67
|
-
process.env.LLAMACPP_MODEL = "qwen2.5-coder-7b";
|
|
68
|
-
|
|
69
|
-
const config = require("../src/config");
|
|
70
|
-
assert.strictEqual(config.llamacpp.model, "qwen2.5-coder-7b");
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("should use default timeout when LLAMACPP_TIMEOUT_MS is not set", () => {
|
|
74
|
-
process.env.MODEL_PROVIDER = "llamacpp";
|
|
75
|
-
delete process.env.LLAMACPP_TIMEOUT_MS;
|
|
76
|
-
|
|
77
|
-
const config = require("../src/config");
|
|
78
|
-
assert.strictEqual(config.llamacpp.timeout, 120000);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it("should use custom timeout when LLAMACPP_TIMEOUT_MS is set", () => {
|
|
82
|
-
process.env.MODEL_PROVIDER = "llamacpp";
|
|
83
|
-
process.env.LLAMACPP_TIMEOUT_MS = "300000";
|
|
84
|
-
|
|
85
|
-
const config = require("../src/config");
|
|
86
|
-
assert.strictEqual(config.llamacpp.timeout, 300000);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("should have null apiKey when LLAMACPP_API_KEY is not set", () => {
|
|
90
|
-
process.env.MODEL_PROVIDER = "llamacpp";
|
|
91
|
-
delete process.env.LLAMACPP_API_KEY;
|
|
92
|
-
|
|
93
|
-
const config = require("../src/config");
|
|
94
|
-
assert.strictEqual(config.llamacpp.apiKey, null);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("should store apiKey when LLAMACPP_API_KEY is set", () => {
|
|
98
|
-
process.env.MODEL_PROVIDER = "llamacpp";
|
|
99
|
-
process.env.LLAMACPP_API_KEY = "my-secret-key";
|
|
100
|
-
|
|
101
|
-
const config = require("../src/config");
|
|
102
|
-
assert.strictEqual(config.llamacpp.apiKey, "my-secret-key");
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
describe("Routing", () => {
|
|
107
|
-
it("should route to llamacpp when MODEL_PROVIDER is llamacpp", () => {
|
|
108
|
-
process.env.MODEL_PROVIDER = "llamacpp";
|
|
109
|
-
process.env.LLAMACPP_ENDPOINT = "http://localhost:8080";
|
|
110
|
-
process.env.PREFER_OLLAMA = "false";
|
|
111
|
-
|
|
112
|
-
const config = require("../src/config");
|
|
113
|
-
const routing = require("../src/clients/routing");
|
|
114
|
-
|
|
115
|
-
const payload = { messages: [{ role: "user", content: "test" }] };
|
|
116
|
-
const provider = routing.determineProvider(payload);
|
|
117
|
-
|
|
118
|
-
assert.strictEqual(provider, "llamacpp");
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it("should route to llamacpp for moderate tool count when other providers not configured", () => {
|
|
122
|
-
// This test is skipped because llamacpp is checked AFTER openrouter/openai/azure in routing
|
|
123
|
-
// and those providers may be present in the test environment
|
|
124
|
-
// llama.cpp will be used when it's the PRIMARY provider or when it's the only option
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it("should throw error when llamacpp is set as FALLBACK_PROVIDER", () => {
|
|
128
|
-
process.env.MODEL_PROVIDER = "ollama";
|
|
129
|
-
process.env.PREFER_OLLAMA = "true";
|
|
130
|
-
process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
|
|
131
|
-
process.env.FALLBACK_PROVIDER = "llamacpp";
|
|
132
|
-
process.env.LLAMACPP_ENDPOINT = "http://localhost:8080";
|
|
133
|
-
process.env.FALLBACK_ENABLED = "true";
|
|
134
|
-
|
|
135
|
-
assert.throws(
|
|
136
|
-
() => require("../src/config"),
|
|
137
|
-
/FALLBACK_PROVIDER cannot be 'llamacpp'/
|
|
138
|
-
);
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
describe("Response Conversion", () => {
|
|
143
|
-
// llama.cpp uses OpenAI-compatible format, so we reuse the same converter
|
|
144
|
-
|
|
145
|
-
it("should convert llama.cpp text response to Anthropic format", () => {
|
|
146
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
147
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
148
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
149
|
-
|
|
150
|
-
const { convertOpenRouterResponseToAnthropic } = require("../src/clients/openrouter-utils");
|
|
151
|
-
|
|
152
|
-
const llamacppResponse = {
|
|
153
|
-
id: "chatcmpl-123",
|
|
154
|
-
object: "chat.completion",
|
|
155
|
-
created: 1677652288,
|
|
156
|
-
model: "qwen2.5-coder-7b",
|
|
157
|
-
choices: [
|
|
158
|
-
{
|
|
159
|
-
index: 0,
|
|
160
|
-
message: {
|
|
161
|
-
role: "assistant",
|
|
162
|
-
content: "Hello! I'm running on llama.cpp."
|
|
163
|
-
},
|
|
164
|
-
finish_reason: "stop"
|
|
165
|
-
}
|
|
166
|
-
],
|
|
167
|
-
usage: {
|
|
168
|
-
prompt_tokens: 9,
|
|
169
|
-
completion_tokens: 12,
|
|
170
|
-
total_tokens: 21
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
const result = convertOpenRouterResponseToAnthropic(llamacppResponse, "claude-sonnet-4-5");
|
|
175
|
-
|
|
176
|
-
assert.strictEqual(result.role, "assistant");
|
|
177
|
-
assert.strictEqual(result.model, "claude-sonnet-4-5");
|
|
178
|
-
assert.strictEqual(Array.isArray(result.content), true);
|
|
179
|
-
assert.strictEqual(result.content.length, 1);
|
|
180
|
-
assert.strictEqual(result.content[0].type, "text");
|
|
181
|
-
assert.strictEqual(result.content[0].text, "Hello! I'm running on llama.cpp.");
|
|
182
|
-
assert.strictEqual(result.stop_reason, "end_turn");
|
|
183
|
-
assert.strictEqual(result.usage.input_tokens, 9);
|
|
184
|
-
assert.strictEqual(result.usage.output_tokens, 12);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it("should convert llama.cpp tool call response to Anthropic format", () => {
|
|
188
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
189
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
190
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
191
|
-
|
|
192
|
-
const { convertOpenRouterResponseToAnthropic } = require("../src/clients/openrouter-utils");
|
|
193
|
-
|
|
194
|
-
const llamacppResponse = {
|
|
195
|
-
id: "chatcmpl-123",
|
|
196
|
-
object: "chat.completion",
|
|
197
|
-
model: "qwen2.5-coder-7b",
|
|
198
|
-
choices: [
|
|
199
|
-
{
|
|
200
|
-
index: 0,
|
|
201
|
-
message: {
|
|
202
|
-
role: "assistant",
|
|
203
|
-
content: "I'll read that file for you.",
|
|
204
|
-
tool_calls: [
|
|
205
|
-
{
|
|
206
|
-
id: "call_abc123",
|
|
207
|
-
type: "function",
|
|
208
|
-
function: {
|
|
209
|
-
name: "Read",
|
|
210
|
-
arguments: JSON.stringify({
|
|
211
|
-
file_path: "/tmp/example.txt"
|
|
212
|
-
})
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
]
|
|
216
|
-
},
|
|
217
|
-
finish_reason: "tool_calls"
|
|
218
|
-
}
|
|
219
|
-
],
|
|
220
|
-
usage: {
|
|
221
|
-
prompt_tokens: 50,
|
|
222
|
-
completion_tokens: 30,
|
|
223
|
-
total_tokens: 80
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
const result = convertOpenRouterResponseToAnthropic(llamacppResponse, "claude-sonnet-4-5");
|
|
228
|
-
|
|
229
|
-
assert.strictEqual(result.role, "assistant");
|
|
230
|
-
assert.strictEqual(result.content.length, 2); // text + tool_use
|
|
231
|
-
assert.strictEqual(result.content[0].type, "text");
|
|
232
|
-
assert.strictEqual(result.content[0].text, "I'll read that file for you.");
|
|
233
|
-
assert.strictEqual(result.content[1].type, "tool_use");
|
|
234
|
-
assert.strictEqual(result.content[1].name, "Read");
|
|
235
|
-
assert.strictEqual(result.content[1].id, "call_abc123");
|
|
236
|
-
assert.deepStrictEqual(result.content[1].input, {
|
|
237
|
-
file_path: "/tmp/example.txt"
|
|
238
|
-
});
|
|
239
|
-
assert.strictEqual(result.stop_reason, "tool_use");
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it("should convert llama.cpp parallel tool calls to Anthropic format", () => {
|
|
243
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
244
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
245
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
246
|
-
|
|
247
|
-
const { convertOpenRouterResponseToAnthropic } = require("../src/clients/openrouter-utils");
|
|
248
|
-
|
|
249
|
-
const llamacppResponse = {
|
|
250
|
-
id: "chatcmpl-123",
|
|
251
|
-
model: "qwen2.5-coder-7b",
|
|
252
|
-
choices: [
|
|
253
|
-
{
|
|
254
|
-
message: {
|
|
255
|
-
role: "assistant",
|
|
256
|
-
content: "I'll search for both patterns.",
|
|
257
|
-
tool_calls: [
|
|
258
|
-
{
|
|
259
|
-
id: "call_1",
|
|
260
|
-
type: "function",
|
|
261
|
-
function: {
|
|
262
|
-
name: "Grep",
|
|
263
|
-
arguments: JSON.stringify({ pattern: "TODO" })
|
|
264
|
-
}
|
|
265
|
-
},
|
|
266
|
-
{
|
|
267
|
-
id: "call_2",
|
|
268
|
-
type: "function",
|
|
269
|
-
function: {
|
|
270
|
-
name: "Grep",
|
|
271
|
-
arguments: JSON.stringify({ pattern: "FIXME" })
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
]
|
|
275
|
-
},
|
|
276
|
-
finish_reason: "tool_calls"
|
|
277
|
-
}
|
|
278
|
-
],
|
|
279
|
-
usage: { prompt_tokens: 30, completion_tokens: 40, total_tokens: 70 }
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
const result = convertOpenRouterResponseToAnthropic(llamacppResponse, "claude-sonnet-4-5");
|
|
283
|
-
|
|
284
|
-
assert.strictEqual(result.content.length, 3); // text + 2 tool_uses
|
|
285
|
-
assert.strictEqual(result.content[0].type, "text");
|
|
286
|
-
assert.strictEqual(result.content[1].type, "tool_use");
|
|
287
|
-
assert.strictEqual(result.content[1].name, "Grep");
|
|
288
|
-
assert.strictEqual(result.content[1].id, "call_1");
|
|
289
|
-
assert.strictEqual(result.content[2].type, "tool_use");
|
|
290
|
-
assert.strictEqual(result.content[2].name, "Grep");
|
|
291
|
-
assert.strictEqual(result.content[2].id, "call_2");
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
it("should handle llama.cpp response with only tool calls (no text content)", () => {
|
|
295
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
296
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
297
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
298
|
-
|
|
299
|
-
const { convertOpenRouterResponseToAnthropic } = require("../src/clients/openrouter-utils");
|
|
300
|
-
|
|
301
|
-
const llamacppResponse = {
|
|
302
|
-
id: "chatcmpl-123",
|
|
303
|
-
model: "qwen2.5-coder-7b",
|
|
304
|
-
choices: [
|
|
305
|
-
{
|
|
306
|
-
message: {
|
|
307
|
-
role: "assistant",
|
|
308
|
-
content: null, // llama.cpp may return null content with tool calls
|
|
309
|
-
tool_calls: [
|
|
310
|
-
{
|
|
311
|
-
id: "call_xyz",
|
|
312
|
-
type: "function",
|
|
313
|
-
function: {
|
|
314
|
-
name: "Bash",
|
|
315
|
-
arguments: JSON.stringify({ command: "pwd" })
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
]
|
|
319
|
-
},
|
|
320
|
-
finish_reason: "tool_calls"
|
|
321
|
-
}
|
|
322
|
-
],
|
|
323
|
-
usage: { prompt_tokens: 20, completion_tokens: 15, total_tokens: 35 }
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
const result = convertOpenRouterResponseToAnthropic(llamacppResponse, "claude-sonnet-4-5");
|
|
327
|
-
|
|
328
|
-
// Should have tool_use block (at least one)
|
|
329
|
-
assert.strictEqual(result.role, "assistant");
|
|
330
|
-
assert.strictEqual(Array.isArray(result.content), true);
|
|
331
|
-
assert.strictEqual(result.content.length >= 1, true);
|
|
332
|
-
// Find the tool_use block
|
|
333
|
-
const toolUseBlock = result.content.find(c => c.type === "tool_use");
|
|
334
|
-
assert.strictEqual(toolUseBlock !== undefined, true);
|
|
335
|
-
assert.strictEqual(toolUseBlock.name, "Bash");
|
|
336
|
-
});
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
describe("Message Conversion", () => {
|
|
340
|
-
it("should convert Anthropic messages to llama.cpp (OpenAI) format", () => {
|
|
341
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
342
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
343
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
344
|
-
|
|
345
|
-
const { convertAnthropicMessagesToOpenRouter } = require("../src/clients/openrouter-utils");
|
|
346
|
-
|
|
347
|
-
const anthropicMessages = [
|
|
348
|
-
{
|
|
349
|
-
role: "user",
|
|
350
|
-
content: [
|
|
351
|
-
{ type: "text", text: "What is 2 + 2?" }
|
|
352
|
-
]
|
|
353
|
-
},
|
|
354
|
-
{
|
|
355
|
-
role: "assistant",
|
|
356
|
-
content: [
|
|
357
|
-
{ type: "text", text: "2 + 2 equals 4." }
|
|
358
|
-
]
|
|
359
|
-
}
|
|
360
|
-
];
|
|
361
|
-
|
|
362
|
-
const result = convertAnthropicMessagesToOpenRouter(anthropicMessages);
|
|
363
|
-
|
|
364
|
-
assert.strictEqual(result.length, 2);
|
|
365
|
-
assert.strictEqual(result[0].role, "user");
|
|
366
|
-
assert.strictEqual(result[0].content, "What is 2 + 2?");
|
|
367
|
-
assert.strictEqual(result[1].role, "assistant");
|
|
368
|
-
assert.strictEqual(result[1].content, "2 + 2 equals 4.");
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
it("should convert Anthropic tool_result messages to llama.cpp (OpenAI) format", () => {
|
|
372
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
373
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
374
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
375
|
-
|
|
376
|
-
const { convertAnthropicMessagesToOpenRouter } = require("../src/clients/openrouter-utils");
|
|
377
|
-
|
|
378
|
-
// Must have a preceding assistant message with tool_use for tool_result to be valid
|
|
379
|
-
const anthropicMessages = [
|
|
380
|
-
{
|
|
381
|
-
role: "user",
|
|
382
|
-
content: [{ type: "text", text: "Run a command" }]
|
|
383
|
-
},
|
|
384
|
-
{
|
|
385
|
-
role: "assistant",
|
|
386
|
-
content: [
|
|
387
|
-
{ type: "text", text: "I'll run that command." },
|
|
388
|
-
{
|
|
389
|
-
type: "tool_use",
|
|
390
|
-
id: "call_456",
|
|
391
|
-
name: "Bash",
|
|
392
|
-
input: { command: "echo hello" }
|
|
393
|
-
}
|
|
394
|
-
]
|
|
395
|
-
},
|
|
396
|
-
{
|
|
397
|
-
role: "user",
|
|
398
|
-
content: [
|
|
399
|
-
{
|
|
400
|
-
type: "tool_result",
|
|
401
|
-
tool_use_id: "call_456",
|
|
402
|
-
content: "hello"
|
|
403
|
-
}
|
|
404
|
-
]
|
|
405
|
-
}
|
|
406
|
-
];
|
|
407
|
-
|
|
408
|
-
const result = convertAnthropicMessagesToOpenRouter(anthropicMessages);
|
|
409
|
-
|
|
410
|
-
// Should have user message, assistant message with tool call, and tool result
|
|
411
|
-
assert.strictEqual(result.length >= 3, true);
|
|
412
|
-
// Find the tool result message
|
|
413
|
-
const toolResultMsg = result.find(m => m.role === "tool");
|
|
414
|
-
assert.strictEqual(toolResultMsg !== undefined, true);
|
|
415
|
-
assert.strictEqual(toolResultMsg.tool_call_id, "call_456");
|
|
416
|
-
assert.strictEqual(toolResultMsg.content, "hello");
|
|
417
|
-
});
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
describe("Tool Conversion", () => {
|
|
421
|
-
it("should convert Anthropic tools to llama.cpp (OpenAI) format", () => {
|
|
422
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
423
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
424
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
425
|
-
|
|
426
|
-
const { convertAnthropicToolsToOpenRouter } = require("../src/clients/openrouter-utils");
|
|
427
|
-
|
|
428
|
-
const anthropicTools = [
|
|
429
|
-
{
|
|
430
|
-
name: "Read",
|
|
431
|
-
description: "Read a file from disk",
|
|
432
|
-
input_schema: {
|
|
433
|
-
type: "object",
|
|
434
|
-
properties: {
|
|
435
|
-
file_path: { type: "string", description: "Path to the file" }
|
|
436
|
-
},
|
|
437
|
-
required: ["file_path"]
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
];
|
|
441
|
-
|
|
442
|
-
const result = convertAnthropicToolsToOpenRouter(anthropicTools);
|
|
443
|
-
|
|
444
|
-
assert.strictEqual(result.length, 1);
|
|
445
|
-
assert.strictEqual(result[0].type, "function");
|
|
446
|
-
assert.strictEqual(result[0].function.name, "Read");
|
|
447
|
-
assert.strictEqual(result[0].function.description, "Read a file from disk");
|
|
448
|
-
assert.deepStrictEqual(result[0].function.parameters, {
|
|
449
|
-
type: "object",
|
|
450
|
-
properties: {
|
|
451
|
-
file_path: { type: "string", description: "Path to the file" }
|
|
452
|
-
},
|
|
453
|
-
required: ["file_path"]
|
|
454
|
-
});
|
|
455
|
-
});
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
describe("Error Handling", () => {
|
|
459
|
-
it("should throw error when llama.cpp response has no choices", () => {
|
|
460
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
461
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
462
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
463
|
-
|
|
464
|
-
const { convertOpenRouterResponseToAnthropic } = require("../src/clients/openrouter-utils");
|
|
465
|
-
|
|
466
|
-
const errorResponse = {
|
|
467
|
-
error: {
|
|
468
|
-
message: "Model not loaded",
|
|
469
|
-
type: "invalid_request_error"
|
|
470
|
-
}
|
|
471
|
-
};
|
|
472
|
-
|
|
473
|
-
assert.throws(
|
|
474
|
-
() => convertOpenRouterResponseToAnthropic(errorResponse, "test-model"),
|
|
475
|
-
/No choices in OpenRouter response/
|
|
476
|
-
);
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
it("should throw error when llama.cpp response has empty choices array", () => {
|
|
480
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
481
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
482
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
483
|
-
|
|
484
|
-
const { convertOpenRouterResponseToAnthropic } = require("../src/clients/openrouter-utils");
|
|
485
|
-
|
|
486
|
-
const emptyChoicesResponse = {
|
|
487
|
-
id: "chatcmpl-123",
|
|
488
|
-
model: "qwen2.5-coder-7b",
|
|
489
|
-
choices: [],
|
|
490
|
-
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
|
|
491
|
-
};
|
|
492
|
-
|
|
493
|
-
assert.throws(
|
|
494
|
-
() => convertOpenRouterResponseToAnthropic(emptyChoicesResponse, "test-model"),
|
|
495
|
-
/No choices in OpenRouter response/
|
|
496
|
-
);
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
it("should handle malformed tool call arguments gracefully", () => {
|
|
500
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
501
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
502
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
503
|
-
|
|
504
|
-
const { convertOpenRouterResponseToAnthropic } = require("../src/clients/openrouter-utils");
|
|
505
|
-
|
|
506
|
-
const responseWithBadArgs = {
|
|
507
|
-
id: "chatcmpl-123",
|
|
508
|
-
model: "qwen2.5-coder-7b",
|
|
509
|
-
choices: [
|
|
510
|
-
{
|
|
511
|
-
message: {
|
|
512
|
-
role: "assistant",
|
|
513
|
-
content: "Using tool",
|
|
514
|
-
tool_calls: [
|
|
515
|
-
{
|
|
516
|
-
id: "call_bad",
|
|
517
|
-
type: "function",
|
|
518
|
-
function: {
|
|
519
|
-
name: "Read",
|
|
520
|
-
arguments: "invalid json {"
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
]
|
|
524
|
-
},
|
|
525
|
-
finish_reason: "tool_calls"
|
|
526
|
-
}
|
|
527
|
-
],
|
|
528
|
-
usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 }
|
|
529
|
-
};
|
|
530
|
-
|
|
531
|
-
const result = convertOpenRouterResponseToAnthropic(responseWithBadArgs, "test-model");
|
|
532
|
-
|
|
533
|
-
// Should still convert, but with empty input object
|
|
534
|
-
assert.strictEqual(result.content[1].type, "tool_use");
|
|
535
|
-
assert.deepStrictEqual(result.content[1].input, {});
|
|
536
|
-
});
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
describe("Finish Reason Mapping", () => {
|
|
540
|
-
it("should map stop finish_reason to end_turn", () => {
|
|
541
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
542
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
543
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
544
|
-
|
|
545
|
-
const { convertOpenRouterResponseToAnthropic } = require("../src/clients/openrouter-utils");
|
|
546
|
-
|
|
547
|
-
const response = {
|
|
548
|
-
choices: [
|
|
549
|
-
{
|
|
550
|
-
message: { role: "assistant", content: "Complete" },
|
|
551
|
-
finish_reason: "stop"
|
|
552
|
-
}
|
|
553
|
-
],
|
|
554
|
-
usage: { prompt_tokens: 5, completion_tokens: 1, total_tokens: 6 }
|
|
555
|
-
};
|
|
556
|
-
|
|
557
|
-
const result = convertOpenRouterResponseToAnthropic(response, "test-model");
|
|
558
|
-
assert.strictEqual(result.stop_reason, "end_turn");
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
it("should map tool_calls finish_reason to tool_use", () => {
|
|
562
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
563
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
564
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
565
|
-
|
|
566
|
-
const { convertOpenRouterResponseToAnthropic } = require("../src/clients/openrouter-utils");
|
|
567
|
-
|
|
568
|
-
const response = {
|
|
569
|
-
choices: [
|
|
570
|
-
{
|
|
571
|
-
message: {
|
|
572
|
-
role: "assistant",
|
|
573
|
-
content: "Executing tool",
|
|
574
|
-
tool_calls: [
|
|
575
|
-
{
|
|
576
|
-
id: "call_1",
|
|
577
|
-
type: "function",
|
|
578
|
-
function: { name: "test", arguments: "{}" }
|
|
579
|
-
}
|
|
580
|
-
]
|
|
581
|
-
},
|
|
582
|
-
finish_reason: "tool_calls"
|
|
583
|
-
}
|
|
584
|
-
],
|
|
585
|
-
usage: { prompt_tokens: 5, completion_tokens: 10, total_tokens: 15 }
|
|
586
|
-
};
|
|
587
|
-
|
|
588
|
-
const result = convertOpenRouterResponseToAnthropic(response, "test-model");
|
|
589
|
-
assert.strictEqual(result.stop_reason, "tool_use");
|
|
590
|
-
});
|
|
591
|
-
|
|
592
|
-
it("should map length finish_reason to max_tokens", () => {
|
|
593
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
594
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
595
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
596
|
-
|
|
597
|
-
const { convertOpenRouterResponseToAnthropic } = require("../src/clients/openrouter-utils");
|
|
598
|
-
|
|
599
|
-
const response = {
|
|
600
|
-
choices: [
|
|
601
|
-
{
|
|
602
|
-
message: { role: "assistant", content: "Truncated response..." },
|
|
603
|
-
finish_reason: "length"
|
|
604
|
-
}
|
|
605
|
-
],
|
|
606
|
-
usage: { prompt_tokens: 5, completion_tokens: 100, total_tokens: 105 }
|
|
607
|
-
};
|
|
608
|
-
|
|
609
|
-
const result = convertOpenRouterResponseToAnthropic(response, "test-model");
|
|
610
|
-
assert.strictEqual(result.stop_reason, "max_tokens");
|
|
611
|
-
});
|
|
612
|
-
});
|
|
613
|
-
|
|
614
|
-
describe("Usage Metrics", () => {
|
|
615
|
-
it("should correctly map llama.cpp usage to Anthropic format", () => {
|
|
616
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
617
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
618
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
619
|
-
|
|
620
|
-
const { convertOpenRouterResponseToAnthropic } = require("../src/clients/openrouter-utils");
|
|
621
|
-
|
|
622
|
-
const response = {
|
|
623
|
-
choices: [
|
|
624
|
-
{
|
|
625
|
-
message: { role: "assistant", content: "Response" },
|
|
626
|
-
finish_reason: "stop"
|
|
627
|
-
}
|
|
628
|
-
],
|
|
629
|
-
usage: {
|
|
630
|
-
prompt_tokens: 200,
|
|
631
|
-
completion_tokens: 100,
|
|
632
|
-
total_tokens: 300
|
|
633
|
-
}
|
|
634
|
-
};
|
|
635
|
-
|
|
636
|
-
const result = convertOpenRouterResponseToAnthropic(response, "test-model");
|
|
637
|
-
|
|
638
|
-
assert.strictEqual(result.usage.input_tokens, 200);
|
|
639
|
-
assert.strictEqual(result.usage.output_tokens, 100);
|
|
640
|
-
});
|
|
641
|
-
|
|
642
|
-
it("should handle missing usage gracefully", () => {
|
|
643
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
644
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
645
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
646
|
-
|
|
647
|
-
const { convertOpenRouterResponseToAnthropic } = require("../src/clients/openrouter-utils");
|
|
648
|
-
|
|
649
|
-
const response = {
|
|
650
|
-
choices: [
|
|
651
|
-
{
|
|
652
|
-
message: { role: "assistant", content: "Response" },
|
|
653
|
-
finish_reason: "stop"
|
|
654
|
-
}
|
|
655
|
-
]
|
|
656
|
-
// No usage field
|
|
657
|
-
};
|
|
658
|
-
|
|
659
|
-
const result = convertOpenRouterResponseToAnthropic(response, "test-model");
|
|
660
|
-
|
|
661
|
-
assert.strictEqual(result.usage.input_tokens, 0);
|
|
662
|
-
assert.strictEqual(result.usage.output_tokens, 0);
|
|
663
|
-
});
|
|
664
|
-
|
|
665
|
-
it("should filter duplicate tool call JSON from content when tool_calls are present", () => {
|
|
666
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
667
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
668
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
669
|
-
|
|
670
|
-
const { convertOpenRouterResponseToAnthropic } = require("../src/clients/openrouter-utils");
|
|
671
|
-
|
|
672
|
-
// Simulate llama.cpp response with BOTH content (as JSON) and tool_calls
|
|
673
|
-
const response = {
|
|
674
|
-
id: "chatcmpl-123",
|
|
675
|
-
choices: [
|
|
676
|
-
{
|
|
677
|
-
message: {
|
|
678
|
-
role: "assistant",
|
|
679
|
-
content: '{"type": "function", "function": {"name": "Write", "parameters": {"file_path": "test.cpp", "content": "int main() {}"}}}',
|
|
680
|
-
tool_calls: [
|
|
681
|
-
{
|
|
682
|
-
id: "call_abc123",
|
|
683
|
-
type: "function",
|
|
684
|
-
function: {
|
|
685
|
-
name: "Write",
|
|
686
|
-
arguments: '{"file_path": "test.cpp", "content": "int main() {}"}'
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
]
|
|
690
|
-
},
|
|
691
|
-
finish_reason: "tool_calls"
|
|
692
|
-
}
|
|
693
|
-
],
|
|
694
|
-
usage: {
|
|
695
|
-
prompt_tokens: 100,
|
|
696
|
-
completion_tokens: 50
|
|
697
|
-
}
|
|
698
|
-
};
|
|
699
|
-
|
|
700
|
-
const result = convertOpenRouterResponseToAnthropic(response, "test-model");
|
|
701
|
-
|
|
702
|
-
// Should have only 1 content block (tool_use), not 2 (text + tool_use)
|
|
703
|
-
assert.strictEqual(result.content.length, 1);
|
|
704
|
-
assert.strictEqual(result.content[0].type, "tool_use");
|
|
705
|
-
assert.strictEqual(result.content[0].name, "Write");
|
|
706
|
-
assert.strictEqual(result.stop_reason, "tool_use");
|
|
707
|
-
|
|
708
|
-
// Verify the JSON text was NOT included as a text block
|
|
709
|
-
const textBlocks = result.content.filter(block => block.type === "text");
|
|
710
|
-
assert.strictEqual(textBlocks.length, 0, "Should not include text block with duplicate JSON");
|
|
711
|
-
});
|
|
712
|
-
|
|
713
|
-
it("should preserve normal text content when tool_calls are NOT present", () => {
|
|
714
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
715
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
716
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
717
|
-
|
|
718
|
-
const { convertOpenRouterResponseToAnthropic } = require("../src/clients/openrouter-utils");
|
|
719
|
-
|
|
720
|
-
const response = {
|
|
721
|
-
id: "chatcmpl-456",
|
|
722
|
-
choices: [
|
|
723
|
-
{
|
|
724
|
-
message: {
|
|
725
|
-
role: "assistant",
|
|
726
|
-
content: "Here is the code you requested.",
|
|
727
|
-
// No tool_calls
|
|
728
|
-
},
|
|
729
|
-
finish_reason: "stop"
|
|
730
|
-
}
|
|
731
|
-
],
|
|
732
|
-
usage: {
|
|
733
|
-
prompt_tokens: 50,
|
|
734
|
-
completion_tokens: 25
|
|
735
|
-
}
|
|
736
|
-
};
|
|
737
|
-
|
|
738
|
-
const result = convertOpenRouterResponseToAnthropic(response, "test-model");
|
|
739
|
-
|
|
740
|
-
// Should have 1 text block
|
|
741
|
-
assert.strictEqual(result.content.length, 1);
|
|
742
|
-
assert.strictEqual(result.content[0].type, "text");
|
|
743
|
-
assert.strictEqual(result.content[0].text, "Here is the code you requested.");
|
|
744
|
-
assert.strictEqual(result.stop_reason, "end_turn");
|
|
745
|
-
});
|
|
746
|
-
|
|
747
|
-
it("should preserve text content with tool_calls when text is NOT JSON", () => {
|
|
748
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
749
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
750
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
751
|
-
|
|
752
|
-
const { convertOpenRouterResponseToAnthropic } = require("../src/clients/openrouter-utils");
|
|
753
|
-
|
|
754
|
-
// Some models include explanatory text before/with tool calls
|
|
755
|
-
const response = {
|
|
756
|
-
id: "chatcmpl-789",
|
|
757
|
-
choices: [
|
|
758
|
-
{
|
|
759
|
-
message: {
|
|
760
|
-
role: "assistant",
|
|
761
|
-
content: "I'll write the file for you now.",
|
|
762
|
-
tool_calls: [
|
|
763
|
-
{
|
|
764
|
-
id: "call_xyz789",
|
|
765
|
-
type: "function",
|
|
766
|
-
function: {
|
|
767
|
-
name: "Write",
|
|
768
|
-
arguments: '{"file_path": "test.cpp", "content": "int main() {}"}'
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
]
|
|
772
|
-
},
|
|
773
|
-
finish_reason: "tool_calls"
|
|
774
|
-
}
|
|
775
|
-
],
|
|
776
|
-
usage: {
|
|
777
|
-
prompt_tokens: 100,
|
|
778
|
-
completion_tokens: 60
|
|
779
|
-
}
|
|
780
|
-
};
|
|
781
|
-
|
|
782
|
-
const result = convertOpenRouterResponseToAnthropic(response, "test-model");
|
|
783
|
-
|
|
784
|
-
// Should have 2 content blocks (text + tool_use)
|
|
785
|
-
assert.strictEqual(result.content.length, 2);
|
|
786
|
-
assert.strictEqual(result.content[0].type, "text");
|
|
787
|
-
assert.strictEqual(result.content[0].text, "I'll write the file for you now.");
|
|
788
|
-
assert.strictEqual(result.content[1].type, "tool_use");
|
|
789
|
-
assert.strictEqual(result.content[1].name, "Write");
|
|
790
|
-
assert.strictEqual(result.stop_reason, "tool_use");
|
|
791
|
-
});
|
|
792
|
-
|
|
793
|
-
it("should filter malformed JSON when model outputs ONLY JSON without tool_calls", () => {
|
|
794
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
795
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
796
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
797
|
-
|
|
798
|
-
const { convertOpenRouterResponseToAnthropic } = require("../src/clients/openrouter-utils");
|
|
799
|
-
|
|
800
|
-
// Simulate llama.cpp model that outputs JSON in content but doesn't provide tool_calls
|
|
801
|
-
// This is a model training/configuration issue - model learned to output JSON
|
|
802
|
-
// but llama.cpp server isn't converting it to structured tool_calls
|
|
803
|
-
const response = {
|
|
804
|
-
id: "chatcmpl-malformed",
|
|
805
|
-
choices: [
|
|
806
|
-
{
|
|
807
|
-
message: {
|
|
808
|
-
role: "assistant",
|
|
809
|
-
content: '{"function": "Write", "parameters": {"file_path": "test.go", "content": "package main"}}',
|
|
810
|
-
// No tool_calls array - model error!
|
|
811
|
-
},
|
|
812
|
-
finish_reason: "stop"
|
|
813
|
-
}
|
|
814
|
-
],
|
|
815
|
-
usage: {
|
|
816
|
-
prompt_tokens: 50,
|
|
817
|
-
completion_tokens: 30
|
|
818
|
-
}
|
|
819
|
-
};
|
|
820
|
-
|
|
821
|
-
const result = convertOpenRouterResponseToAnthropic(response, "test-model");
|
|
822
|
-
|
|
823
|
-
// Should have 1 empty text block (JSON was filtered out)
|
|
824
|
-
assert.strictEqual(result.content.length, 1);
|
|
825
|
-
assert.strictEqual(result.content[0].type, "text");
|
|
826
|
-
assert.strictEqual(result.content[0].text, "");
|
|
827
|
-
assert.strictEqual(result.stop_reason, "end_turn");
|
|
828
|
-
});
|
|
829
|
-
|
|
830
|
-
it("should filter alternative JSON formats without tool_calls", () => {
|
|
831
|
-
process.env.MODEL_PROVIDER = "databricks";
|
|
832
|
-
process.env.DATABRICKS_API_KEY = "test-key";
|
|
833
|
-
process.env.DATABRICKS_API_BASE = "http://test.com";
|
|
834
|
-
|
|
835
|
-
const { convertOpenRouterResponseToAnthropic } = require("../src/clients/openrouter-utils");
|
|
836
|
-
|
|
837
|
-
// Test the other JSON format seen in the wild
|
|
838
|
-
const response = {
|
|
839
|
-
id: "chatcmpl-alt-format",
|
|
840
|
-
choices: [
|
|
841
|
-
{
|
|
842
|
-
message: {
|
|
843
|
-
role: "assistant",
|
|
844
|
-
content: '{"type": "function", "function": {"name": "Read", "arguments": {"file_path": "config.json"}}}',
|
|
845
|
-
},
|
|
846
|
-
finish_reason: "stop"
|
|
847
|
-
}
|
|
848
|
-
],
|
|
849
|
-
usage: {
|
|
850
|
-
prompt_tokens: 40,
|
|
851
|
-
completion_tokens: 25
|
|
852
|
-
}
|
|
853
|
-
};
|
|
854
|
-
|
|
855
|
-
const result = convertOpenRouterResponseToAnthropic(response, "test-model");
|
|
856
|
-
|
|
857
|
-
// Should filter out the JSON
|
|
858
|
-
assert.strictEqual(result.content.length, 1);
|
|
859
|
-
assert.strictEqual(result.content[0].type, "text");
|
|
860
|
-
assert.strictEqual(result.content[0].text, "");
|
|
861
|
-
});
|
|
862
|
-
});
|
|
863
|
-
});
|