lynkr 9.14.16 → 9.14.18
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 +2 -0
- package/bin/cli.js +2 -0
- package/bin/lynkr-audit.js +58 -0
- package/bin/lynkr-route.js +76 -0
- package/config/complexity-exemplars.json +366 -0
- package/config/routing.example.json +186 -0
- package/config/routing.json +93 -0
- package/package.json +2 -2
- package/scripts/build-complexity-exemplars.js +84 -0
- package/scripts/build-routing-corpus.js +55 -0
- package/scripts/cache-probe.js +66 -0
- package/scripts/calibrate-capabilities.js +433 -0
- package/scripts/eval-complexity-signal.js +65 -0
- package/scripts/validate-grounding.js +63 -0
- package/src/api/router.js +53 -4
- package/src/cache/embeddings.js +22 -3
- package/src/clients/cursor-utils.js +1 -1
- package/src/clients/databricks.js +258 -124
- package/src/context/output-format-guard.js +1 -0
- package/src/orchestrator/index.js +11 -2
- package/src/routing/agentic-detector.js +6 -1
- package/src/routing/client-profiles.js +22 -0
- package/src/routing/complexity-analyzer.js +7 -0
- package/src/routing/decisions.js +100 -0
- package/src/routing/grounding.js +118 -0
- package/src/routing/harness-envelope.js +71 -1
- package/src/routing/index.js +104 -7
- package/src/routing/intent-score.js +5 -0
- package/src/routing/outcomes.js +136 -0
- package/src/routing/routing-config.js +154 -0
- package/src/routing/shortfall.js +90 -1
- package/src/routing/signals.js +179 -0
- package/src/routing/switch-gate.js +90 -0
- package/src/routing/telemetry.js +66 -3
|
@@ -8,12 +8,33 @@ const { getMetricsCollector } = require("../observability/metrics");
|
|
|
8
8
|
const { getHealthTracker } = require("../observability/health-tracker");
|
|
9
9
|
const { createBulkhead } = require("./resilience");
|
|
10
10
|
const logger = require("../logger");
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
function clientStructuredOutput(body) {
|
|
13
|
+
if (process.env.LYNKR_FORWARD_RESPONSE_FORMAT === "false") return null;
|
|
14
|
+
const of = body?.output_format;
|
|
15
|
+
if (of && typeof of === "object") {
|
|
16
|
+
if (of.type === "json_schema" && of.schema) return { kind: "json_schema", schema: of.schema, name: of.name || of.schema?.title || "response" };
|
|
17
|
+
if (of.type === "json_object") return { kind: "json_object" };
|
|
18
|
+
}
|
|
19
|
+
const rf = body?.response_format;
|
|
20
|
+
if (rf && typeof rf === "object") {
|
|
21
|
+
if (rf.type === "json_schema" && rf.json_schema?.schema) return { kind: "json_schema", schema: rf.json_schema.schema, name: rf.json_schema.name || "response" };
|
|
22
|
+
if (rf.type === "json_object") return { kind: "json_object" };
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
/** OpenAI-style response_format for a provider; schemaSupported=false → json_object. */
|
|
27
|
+
function openaiResponseFormat(body, schemaSupported) {
|
|
28
|
+
const so = clientStructuredOutput(body);
|
|
29
|
+
if (!so) return null;
|
|
30
|
+
if (so.kind === "json_schema" && schemaSupported) return { type: "json_schema", json_schema: { name: String(so.name).slice(0, 64), schema: so.schema } };
|
|
31
|
+
return { type: "json_object" };
|
|
32
|
+
}
|
|
12
33
|
const { convertAnthropicToolsToOpenRouter } = require("./openrouter-utils");
|
|
13
34
|
|
|
14
35
|
/**
|
|
15
36
|
* Capability-boundary guard (issue #114): a caller that sets
|
|
16
|
-
* tool_choice "none" grants no tools. Substituting
|
|
37
|
+
* tool_choice "none" grants no tools. Substituting server-side tools would widen
|
|
17
38
|
* the grant to shell execution with zero caller-visible signal, so every
|
|
18
39
|
* injection site below must consult this first. Matches the convention
|
|
19
40
|
* already used by the OpenRouter ingress path.
|
|
@@ -214,17 +235,7 @@ async function invokeDatabricks(body, _incomingHeaders = {}) {
|
|
|
214
235
|
// Create a copy of body to avoid mutating the original
|
|
215
236
|
const databricksBody = { ...body };
|
|
216
237
|
|
|
217
|
-
|
|
218
|
-
// Never when the caller declined tools (issue #114).
|
|
219
|
-
if (!toolsDeclined(body) && (!Array.isArray(databricksBody.tools) || databricksBody.tools.length === 0)) {
|
|
220
|
-
databricksBody.tools = STANDARD_TOOLS;
|
|
221
|
-
logger.debug({
|
|
222
|
-
injectedToolCount: STANDARD_TOOLS.length,
|
|
223
|
-
injectedToolNames: STANDARD_TOOL_NAMES,
|
|
224
|
-
reason: "Client did not send tools (passthrough mode)"
|
|
225
|
-
}, "=== INJECTING STANDARD TOOLS (Databricks) ===");
|
|
226
|
-
}
|
|
227
|
-
|
|
238
|
+
|
|
228
239
|
// Convert Anthropic format tools to OpenAI format (Databricks uses OpenAI format)
|
|
229
240
|
if (Array.isArray(databricksBody.tools) && databricksBody.tools.length > 0) {
|
|
230
241
|
// Check if tools are already in OpenAI format (have type: "function")
|
|
@@ -560,9 +571,6 @@ async function invokeOllama(body, _incomingHeaders = {}) {
|
|
|
560
571
|
|
|
561
572
|
if (!supportsTools) {
|
|
562
573
|
toolsToSend = null;
|
|
563
|
-
} else if (!toolsDeclined(body) && injectToolsOllama && (!Array.isArray(toolsToSend) || toolsToSend.length === 0)) {
|
|
564
|
-
toolsToSend = STANDARD_TOOLS;
|
|
565
|
-
toolsInjected = true;
|
|
566
574
|
}
|
|
567
575
|
|
|
568
576
|
// Consolidated tool injection log
|
|
@@ -841,6 +849,63 @@ async function invokeOpenRouter(body, _incomingHeaders = {}) {
|
|
|
841
849
|
stream: body.stream ?? false
|
|
842
850
|
};
|
|
843
851
|
|
|
852
|
+
{
|
|
853
|
+
// Per-model pin: OPENROUTER_PROVIDER_ORDER_MAP="deepseek-v4.1-flash=DeepSeek;glm-5.3-flash=Novita,DeepInfra"
|
|
854
|
+
// (';' between models, ',' between providers; substring match on model id). Global knob is the default.
|
|
855
|
+
let _orderSrc = process.env.OPENROUTER_PROVIDER_ORDER || "";
|
|
856
|
+
for (const entry of (process.env.OPENROUTER_PROVIDER_ORDER_MAP || "").split(";")) {
|
|
857
|
+
const eq = entry.indexOf("=");
|
|
858
|
+
if (eq < 0) continue;
|
|
859
|
+
const k = entry.slice(0, eq).trim(), v = entry.slice(eq + 1).trim();
|
|
860
|
+
if (k && v && String(openRouterBody.model).includes(k)) { _orderSrc = v; break; }
|
|
861
|
+
}
|
|
862
|
+
const _order = _orderSrc.split(",").map((x) => x.trim()).filter(Boolean);
|
|
863
|
+
if (_order.length) {
|
|
864
|
+
openRouterBody.provider = { order: _order, allow_fallbacks: process.env.OPENROUTER_ALLOW_FALLBACKS === "true" };
|
|
865
|
+
}
|
|
866
|
+
openRouterBody._pinnedProviders = _order;
|
|
867
|
+
let _effort = body._effort || process.env.OPENROUTER_REASONING_EFFORT || null;
|
|
868
|
+
const _map = body._effort ? "" : (process.env.OPENROUTER_REASONING_EFFORT_MAP || "");
|
|
869
|
+
for (const pair of _map.split(",")) {
|
|
870
|
+
const [k, v] = pair.split("=").map((x) => (x || "").trim());
|
|
871
|
+
if (k && v && String(openRouterBody.model).includes(k)) { _effort = v; break; }
|
|
872
|
+
}
|
|
873
|
+
if (body.thinking?.type === "enabled" && body.thinking.budget_tokens) {
|
|
874
|
+
openRouterBody.reasoning = { max_tokens: body.thinking.budget_tokens };
|
|
875
|
+
} else if (_effort && _effort !== "none") {
|
|
876
|
+
openRouterBody.reasoning = { effort: _effort };
|
|
877
|
+
} else if (_effort === "none") {
|
|
878
|
+
openRouterBody.reasoning = { enabled: false };
|
|
879
|
+
}
|
|
880
|
+
// Per-model map lookup helper: "k1=v1,k2=v2" (substring match on model id).
|
|
881
|
+
const _mapLookup = (envName) => {
|
|
882
|
+
for (const pair of (process.env[envName] || "").split(",")) {
|
|
883
|
+
const [k, v] = pair.split("=").map((x) => (x || "").trim());
|
|
884
|
+
if (k && v && String(openRouterBody.model).includes(k)) return v;
|
|
885
|
+
}
|
|
886
|
+
return null;
|
|
887
|
+
};
|
|
888
|
+
const _cap = parseInt(_mapLookup("OPENROUTER_MAX_TOKENS_MAP") || "", 10);
|
|
889
|
+
if (_cap > 0 && openRouterBody.max_tokens > _cap) openRouterBody.max_tokens = _cap;
|
|
890
|
+
// Schema enforcement only where the host supports it (Novita / DeepSeek
|
|
891
|
+
// official reject json_schema; Friendli, Parasail, Together, Xiaomi honour it).
|
|
892
|
+
// OPENROUTER_SCHEMA_PROVIDERS="Friendli,Parasail,Together,Xiaomi"
|
|
893
|
+
const _schemaProviders = (process.env.OPENROUTER_SCHEMA_PROVIDERS || "").split(",").map((x) => x.trim().toLowerCase()).filter(Boolean);
|
|
894
|
+
const _schemaOk = (prov) => !!prov && _schemaProviders.includes(String(prov).toLowerCase());
|
|
895
|
+
const _globalSchema = process.env.OPENROUTER_RESPONSE_FORMAT_SCHEMA === "true";
|
|
896
|
+
const _rf = openaiResponseFormat(body, _globalSchema || _schemaOk(_order[0]));
|
|
897
|
+
if (_rf) {
|
|
898
|
+
if (_rf.type === "json_schema") { _rf.json_schema.strict = true; if (openRouterBody.provider) openRouterBody.provider.require_parameters = true; }
|
|
899
|
+
openRouterBody.response_format = _rf;
|
|
900
|
+
}
|
|
901
|
+
openRouterBody._schemaOk = _schemaOk;
|
|
902
|
+
// Upstream timeout (Lynkr had none → waited 846s on a stalled host).
|
|
903
|
+
// OPENROUTER_TIMEOUT_MS=150000 OPENROUTER_TIMEOUT_MS_MAP="glm-5.3-flash=90000"
|
|
904
|
+
openRouterBody._timeoutMs = parseInt(_mapLookup("OPENROUTER_TIMEOUT_MS_MAP") || process.env.OPENROUTER_TIMEOUT_MS || "150000", 10);
|
|
905
|
+
// Ask for provider-side usage (cached prompt tokens, reasoning tokens, cost).
|
|
906
|
+
openRouterBody.usage = { include: true };
|
|
907
|
+
}
|
|
908
|
+
|
|
844
909
|
// Issue #95: forward the Lynkr session id so OpenRouter's meta-routers
|
|
845
910
|
// (auto/pareto) can apply session stickiness — without it every call in a
|
|
846
911
|
// pinned session re-ranks fresh and one agentic task can span 6+ models.
|
|
@@ -852,16 +917,7 @@ async function invokeOpenRouter(body, _incomingHeaders = {}) {
|
|
|
852
917
|
let toolsToSend = body.tools;
|
|
853
918
|
let toolsInjected = false;
|
|
854
919
|
|
|
855
|
-
|
|
856
|
-
toolsToSend = STANDARD_TOOLS;
|
|
857
|
-
toolsInjected = true;
|
|
858
|
-
logger.debug({
|
|
859
|
-
injectedToolCount: STANDARD_TOOLS.length,
|
|
860
|
-
injectedToolNames: STANDARD_TOOL_NAMES,
|
|
861
|
-
reason: "Client did not send tools (passthrough mode)"
|
|
862
|
-
}, "=== INJECTING STANDARD TOOLS (OpenRouter) ===");
|
|
863
|
-
}
|
|
864
|
-
|
|
920
|
+
|
|
865
921
|
if (Array.isArray(toolsToSend) && toolsToSend.length > 0) {
|
|
866
922
|
openRouterBody.tools = convertAnthropicToolsToOpenRouter(toolsToSend);
|
|
867
923
|
logger.debug({
|
|
@@ -877,11 +933,50 @@ async function invokeOpenRouter(body, _incomingHeaders = {}) {
|
|
|
877
933
|
|
|
878
934
|
// Same pre-return 429 check as invokeMoonshot/invokeBaidu — see the comment
|
|
879
935
|
// on invokeOpenAI's equivalent check for why this matters for streaming.
|
|
880
|
-
const
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
936
|
+
const _pinnedProviders = openRouterBody._pinnedProviders; delete openRouterBody._pinnedProviders;
|
|
937
|
+
const _schemaOkFn = openRouterBody._schemaOk; delete openRouterBody._schemaOk;
|
|
938
|
+
const _timeoutMs = openRouterBody._timeoutMs; delete openRouterBody._timeoutMs;
|
|
939
|
+
const _isTimeout = (e) => e && (e.name === "TimeoutError" || e.name === "AbortError" || e.cause?.name === "TimeoutError" || e.code === "UND_ERR_HEADERS_TIMEOUT");
|
|
940
|
+
let response, _lastErr = null;
|
|
941
|
+
const _orders = _pinnedProviders && _pinnedProviders.length > 1
|
|
942
|
+
? _pinnedProviders.map((_, i) => _pinnedProviders.slice(i))
|
|
943
|
+
: [_pinnedProviders || []];
|
|
944
|
+
for (let ai = 0; ai < _orders.length; ai++) {
|
|
945
|
+
const ord = _orders[ai];
|
|
946
|
+
if (ord.length) {
|
|
947
|
+
openRouterBody.provider = { ...(openRouterBody.provider || {}), order: ord, allow_fallbacks: process.env.OPENROUTER_ALLOW_FALLBACKS === "true" };
|
|
948
|
+
const wantSchema = !!(_schemaOkFn && _schemaOkFn(ord[0])) || process.env.OPENROUTER_RESPONSE_FORMAT_SCHEMA === "true";
|
|
949
|
+
const rf = openaiResponseFormat(body, wantSchema);
|
|
950
|
+
if (rf) { if (rf.type === "json_schema") rf.json_schema.strict = true; openRouterBody.response_format = rf; }
|
|
951
|
+
openRouterBody.provider.require_parameters = !!(rf && rf.type === "json_schema");
|
|
952
|
+
}
|
|
953
|
+
const t0 = Date.now();
|
|
954
|
+
try {
|
|
955
|
+
response = await performJsonRequest(endpoint, {
|
|
956
|
+
headers,
|
|
957
|
+
body: openRouterBody,
|
|
958
|
+
retryableStatusesOverride: [500, 502, 503, 504],
|
|
959
|
+
timeoutMs: _timeoutMs,
|
|
960
|
+
}, "OpenRouter");
|
|
961
|
+
_lastErr = null;
|
|
962
|
+
} catch (e) {
|
|
963
|
+
_lastErr = e; response = null;
|
|
964
|
+
}
|
|
965
|
+
const failedSoft = response && !response.ok && (response.status >= 500 || response.status === 404 || response.status === 429);
|
|
966
|
+
if ((_lastErr || failedSoft) && ai < _orders.length - 1) {
|
|
967
|
+
logger.warn({
|
|
968
|
+
model: openRouterBody.model, failedProvider: ord[0], nextProviders: _orders[ai + 1],
|
|
969
|
+
reason: _lastErr ? (_isTimeout(_lastErr) ? `timeout after ${Date.now() - t0}ms (limit ${_timeoutMs})` : _lastErr.message) : `HTTP ${response.status} ${String(response.json?.error?.message || "").slice(0, 100)}`,
|
|
970
|
+
}, "[Failover] OpenRouter provider failed — trying next pinned provider");
|
|
971
|
+
continue;
|
|
972
|
+
}
|
|
973
|
+
break;
|
|
974
|
+
}
|
|
975
|
+
if (_lastErr) {
|
|
976
|
+
if (_isTimeout(_lastErr)) { const err = new Error(`OpenRouter upstream timeout (${_timeoutMs}ms) on ${openRouterBody.model} via ${(_pinnedProviders || []).join(",") || "auto"}`); err.status = 504; err.code = "UPSTREAM_TIMEOUT"; throw err; }
|
|
977
|
+
throw _lastErr;
|
|
978
|
+
}
|
|
979
|
+
openRouterBody._pinnedProviders = _pinnedProviders;
|
|
885
980
|
|
|
886
981
|
if (!response.ok && response.status === 429) {
|
|
887
982
|
const err = new Error(`OpenRouter rate-limited: ${String(response.json?.error?.message || '').slice(0, 120)}`);
|
|
@@ -889,6 +984,36 @@ async function invokeOpenRouter(body, _incomingHeaders = {}) {
|
|
|
889
984
|
throw err;
|
|
890
985
|
}
|
|
891
986
|
|
|
987
|
+
// Streaming: hand the raw SSE stream to the orchestrator's stream branch.
|
|
988
|
+
if (response?.stream) {
|
|
989
|
+
return response;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
if (response?.ok && response?.json?.choices) {
|
|
993
|
+
const raw = response.json;
|
|
994
|
+
const anthropicJson = convertOpenAIToAnthropic(raw);
|
|
995
|
+
if (raw.model) anthropicJson._providerModel = raw.model;
|
|
996
|
+
if (raw.provider) anthropicJson._providerName = raw.provider;
|
|
997
|
+
const _pinned = (openRouterBody._pinnedProviders || []).map((x) => String(x).toLowerCase());
|
|
998
|
+
if (_pinned.length && raw.provider && !_pinned.includes(String(raw.provider).toLowerCase())) {
|
|
999
|
+
logger.warn({ pinned: _pinned, served: raw.provider, model: raw.model }, "[ProviderCheck] OpenRouter served a provider outside OPENROUTER_PROVIDER_ORDER");
|
|
1000
|
+
}
|
|
1001
|
+
logger.info({
|
|
1002
|
+
model: raw.model, provider: raw.provider,
|
|
1003
|
+
prompt_tokens: raw.usage?.prompt_tokens, cached_tokens: raw.usage?.prompt_tokens_details?.cached_tokens,
|
|
1004
|
+
completion_tokens: raw.usage?.completion_tokens, reasoning_tokens: raw.usage?.completion_tokens_details?.reasoning_tokens,
|
|
1005
|
+
cost_usd: raw.usage?.cost,
|
|
1006
|
+
}, "=== OpenRouter USAGE ===");
|
|
1007
|
+
return {
|
|
1008
|
+
ok: response.ok,
|
|
1009
|
+
status: response.status,
|
|
1010
|
+
json: anthropicJson,
|
|
1011
|
+
text: JSON.stringify(anthropicJson),
|
|
1012
|
+
contentType: "application/json",
|
|
1013
|
+
headers: response.headers,
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
|
|
892
1017
|
return response;
|
|
893
1018
|
}
|
|
894
1019
|
|
|
@@ -933,16 +1058,7 @@ async function invokeEdenAI(body, _incomingHeaders = {}) {
|
|
|
933
1058
|
let toolsToSend = body.tools;
|
|
934
1059
|
let toolsInjected = false;
|
|
935
1060
|
|
|
936
|
-
|
|
937
|
-
toolsToSend = STANDARD_TOOLS;
|
|
938
|
-
toolsInjected = true;
|
|
939
|
-
logger.debug({
|
|
940
|
-
injectedToolCount: STANDARD_TOOLS.length,
|
|
941
|
-
injectedToolNames: STANDARD_TOOL_NAMES,
|
|
942
|
-
reason: "Client did not send tools (passthrough mode)"
|
|
943
|
-
}, "=== INJECTING STANDARD TOOLS (Eden AI) ===");
|
|
944
|
-
}
|
|
945
|
-
|
|
1061
|
+
|
|
946
1062
|
if (Array.isArray(toolsToSend) && toolsToSend.length > 0) {
|
|
947
1063
|
edenAIBody.tools = convertAnthropicToolsToOpenRouter(toolsToSend);
|
|
948
1064
|
logger.debug({
|
|
@@ -1042,16 +1158,7 @@ async function invokeAzureOpenAI(body, _incomingHeaders = {}) {
|
|
|
1042
1158
|
let toolsToSend = body.tools;
|
|
1043
1159
|
let toolsInjected = false;
|
|
1044
1160
|
|
|
1045
|
-
|
|
1046
|
-
toolsToSend = STANDARD_TOOLS;
|
|
1047
|
-
toolsInjected = true;
|
|
1048
|
-
logger.debug({
|
|
1049
|
-
injectedToolCount: STANDARD_TOOLS.length,
|
|
1050
|
-
injectedToolNames: STANDARD_TOOL_NAMES,
|
|
1051
|
-
reason: "Client did not send tools (passthrough mode)"
|
|
1052
|
-
}, "=== INJECTING STANDARD TOOLS ===");
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1161
|
+
|
|
1055
1162
|
if (Array.isArray(toolsToSend) && toolsToSend.length > 0) {
|
|
1056
1163
|
azureBody.tools = convertAnthropicToolsToOpenRouter(toolsToSend);
|
|
1057
1164
|
azureBody.parallel_tool_calls = true;
|
|
@@ -1485,21 +1592,19 @@ async function invokeOpenAI(body, _incomingHeaders = {}) {
|
|
|
1485
1592
|
top_p: body.top_p ?? 1.0,
|
|
1486
1593
|
stream: body.stream ?? false
|
|
1487
1594
|
};
|
|
1595
|
+
if (process.env.OPENAI_REASONING_EFFORT && (!body.thinking || body.thinking?.type === "disabled")) {
|
|
1596
|
+
openAIBody.reasoning_effort = process.env.OPENAI_REASONING_EFFORT;
|
|
1597
|
+
}
|
|
1598
|
+
{
|
|
1599
|
+
const _rf = openaiResponseFormat(body, process.env.OPENAI_RESPONSE_FORMAT_SCHEMA !== "false");
|
|
1600
|
+
if (_rf) openAIBody.response_format = _rf;
|
|
1601
|
+
}
|
|
1488
1602
|
|
|
1489
1603
|
// Add tools - inject standard tools if client didn't send any (passthrough mode)
|
|
1490
1604
|
let toolsToSend = body.tools;
|
|
1491
1605
|
let toolsInjected = false;
|
|
1492
1606
|
|
|
1493
|
-
|
|
1494
|
-
toolsToSend = STANDARD_TOOLS;
|
|
1495
|
-
toolsInjected = true;
|
|
1496
|
-
logger.debug({
|
|
1497
|
-
injectedToolCount: STANDARD_TOOLS.length,
|
|
1498
|
-
injectedToolNames: STANDARD_TOOL_NAMES,
|
|
1499
|
-
reason: "Client did not send tools (passthrough mode)"
|
|
1500
|
-
}, "=== INJECTING STANDARD TOOLS (OpenAI) ===");
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1607
|
+
|
|
1503
1608
|
if (Array.isArray(toolsToSend) && toolsToSend.length > 0) {
|
|
1504
1609
|
openAIBody.tools = convertAnthropicToolsToOpenRouter(toolsToSend);
|
|
1505
1610
|
openAIBody.parallel_tool_calls = false; // Disable parallel tool calls - GPT often makes duplicate calls
|
|
@@ -1571,11 +1676,7 @@ async function invokeAtlas(body) {
|
|
|
1571
1676
|
|
|
1572
1677
|
let toolsToSend = body.tools;
|
|
1573
1678
|
let toolsInjected = false;
|
|
1574
|
-
|
|
1575
|
-
toolsToSend = STANDARD_TOOLS;
|
|
1576
|
-
toolsInjected = true;
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1679
|
+
|
|
1579
1680
|
if (toolsToSend.length > 0) {
|
|
1580
1681
|
atlasBody.tools = convertAnthropicToolsToOpenRouter(toolsToSend);
|
|
1581
1682
|
atlasBody.parallel_tool_calls = false;
|
|
@@ -1709,22 +1810,9 @@ async function invokeLlamaCpp(body, _incomingHeaders = {}) {
|
|
|
1709
1810
|
stream: body.stream ?? false
|
|
1710
1811
|
};
|
|
1711
1812
|
|
|
1712
|
-
// Inject standard tools if client didn't send any
|
|
1713
1813
|
let toolsToSend = body.tools;
|
|
1714
|
-
|
|
1814
|
+
const toolsInjected = false;
|
|
1715
1815
|
|
|
1716
|
-
const injectToolsLlamacpp = process.env.INJECT_TOOLS_LLAMACPP !== "false";
|
|
1717
|
-
if (!toolsDeclined(body) && injectToolsLlamacpp && (!Array.isArray(toolsToSend) || toolsToSend.length === 0)) {
|
|
1718
|
-
toolsToSend = STANDARD_TOOLS;
|
|
1719
|
-
toolsInjected = true;
|
|
1720
|
-
logger.debug({
|
|
1721
|
-
injectedToolCount: STANDARD_TOOLS.length,
|
|
1722
|
-
injectedToolNames: STANDARD_TOOL_NAMES,
|
|
1723
|
-
reason: "Client did not send tools (passthrough mode)"
|
|
1724
|
-
}, "=== INJECTING STANDARD TOOLS (llama.cpp) ===");
|
|
1725
|
-
} else if (!injectToolsLlamacpp) {
|
|
1726
|
-
logger.debug({}, "Tool injection disabled for llama.cpp (INJECT_TOOLS_LLAMACPP=false)");
|
|
1727
|
-
}
|
|
1728
1816
|
|
|
1729
1817
|
if (Array.isArray(toolsToSend) && toolsToSend.length > 0) {
|
|
1730
1818
|
llamacppBody.tools = convertAnthropicToolsToOpenRouter(toolsToSend);
|
|
@@ -1808,20 +1896,10 @@ async function invokeLMStudio(body, _incomingHeaders = {}) {
|
|
|
1808
1896
|
stream: body.stream ?? false
|
|
1809
1897
|
};
|
|
1810
1898
|
|
|
1811
|
-
// Inject standard tools if client didn't send any
|
|
1812
1899
|
let toolsToSend = body.tools;
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
if (!toolsDeclined(body) && (!Array.isArray(toolsToSend) || toolsToSend.length === 0)) {
|
|
1816
|
-
toolsToSend = STANDARD_TOOLS;
|
|
1817
|
-
toolsInjected = true;
|
|
1818
|
-
logger.debug({
|
|
1819
|
-
injectedToolCount: STANDARD_TOOLS.length,
|
|
1820
|
-
injectedToolNames: STANDARD_TOOL_NAMES,
|
|
1821
|
-
reason: "Client did not send tools (passthrough mode)"
|
|
1822
|
-
}, "=== INJECTING STANDARD TOOLS (LM Studio) ===");
|
|
1823
|
-
}
|
|
1900
|
+
const toolsInjected = false;
|
|
1824
1901
|
|
|
1902
|
+
|
|
1825
1903
|
if (Array.isArray(toolsToSend) && toolsToSend.length > 0) {
|
|
1826
1904
|
lmstudioBody.tools = convertAnthropicToolsToOpenRouter(toolsToSend);
|
|
1827
1905
|
lmstudioBody.tool_choice = forwardToolChoice(body);
|
|
@@ -1971,15 +2049,7 @@ async function invokeBedrock(body, _incomingHeaders = {}) {
|
|
|
1971
2049
|
// Inject standard tools if needed
|
|
1972
2050
|
let toolsToSend = body.tools;
|
|
1973
2051
|
|
|
1974
|
-
|
|
1975
|
-
toolsToSend = STANDARD_TOOLS;
|
|
1976
|
-
logger.debug({
|
|
1977
|
-
injectedToolCount: STANDARD_TOOLS.length,
|
|
1978
|
-
injectedToolNames: STANDARD_TOOL_NAMES,
|
|
1979
|
-
reason: "Client did not send tools (passthrough mode)"
|
|
1980
|
-
}, "=== INJECTING STANDARD TOOLS (Bedrock) ===");
|
|
1981
|
-
}
|
|
1982
|
-
|
|
2052
|
+
|
|
1983
2053
|
// Normalize away cache_control / array shapes that prompt-cache injection
|
|
1984
2054
|
// may have applied: the Converse API expects plain-string system and
|
|
1985
2055
|
// message content, not Anthropic cache_control blocks.
|
|
@@ -2162,7 +2232,7 @@ async function invokeZai(body, _incomingHeaders = {}) {
|
|
|
2162
2232
|
};
|
|
2163
2233
|
|
|
2164
2234
|
const requestedModel = body._tierModel || body.model || config.zai.model;
|
|
2165
|
-
let mappedModel = modelMap[requestedModel] || config.zai.model || "glm-4.7";
|
|
2235
|
+
let mappedModel = modelMap[requestedModel] || (body._tierModel ? requestedModel : null) || config.zai.model || "glm-4.7";
|
|
2166
2236
|
mappedModel = mappedModel.toLowerCase();
|
|
2167
2237
|
|
|
2168
2238
|
let zaiBody;
|
|
@@ -2227,6 +2297,11 @@ async function invokeZai(body, _incomingHeaders = {}) {
|
|
|
2227
2297
|
temperature: body.temperature ?? 0.7,
|
|
2228
2298
|
stream: body.stream,
|
|
2229
2299
|
};
|
|
2300
|
+
{
|
|
2301
|
+
// OpenAI-compat branch → response_format (Z.AI accepts json_schema here).
|
|
2302
|
+
const _rf = openaiResponseFormat(body, true);
|
|
2303
|
+
if (_rf) zaiBody.response_format = _rf;
|
|
2304
|
+
}
|
|
2230
2305
|
|
|
2231
2306
|
if (tools && tools.length > 0) {
|
|
2232
2307
|
zaiBody.tools = tools;
|
|
@@ -2263,17 +2338,7 @@ async function invokeZai(body, _incomingHeaders = {}) {
|
|
|
2263
2338
|
// that's a different bug — add zai to DEFAULT_OPENAI_SSE_PROVIDERS then,
|
|
2264
2339
|
// don't reintroduce this override.
|
|
2265
2340
|
|
|
2266
|
-
|
|
2267
|
-
// Never when the caller declined tools (issue #114).
|
|
2268
|
-
if (!toolsDeclined(body) && (!Array.isArray(zaiBody.tools) || zaiBody.tools.length === 0)) {
|
|
2269
|
-
zaiBody.tools = STANDARD_TOOLS;
|
|
2270
|
-
logger.debug({
|
|
2271
|
-
injectedToolCount: STANDARD_TOOLS.length,
|
|
2272
|
-
injectedToolNames: STANDARD_TOOL_NAMES,
|
|
2273
|
-
reason: "Client did not send tools (passthrough mode)"
|
|
2274
|
-
}, "=== INJECTING STANDARD TOOLS (Z.AI Anthropic) ===");
|
|
2275
|
-
}
|
|
2276
|
-
|
|
2341
|
+
|
|
2277
2342
|
// Normalize to Anthropic tool shape — the /anthropic/v1/messages endpoint
|
|
2278
2343
|
// rejects OpenAI-nested tools ({type:"function", function:{name,...}}) with
|
|
2279
2344
|
// 422 missing body.tools[0].name. Callers that stayed OpenAI-format upstream
|
|
@@ -2576,7 +2641,7 @@ async function invokeBaidu(body, _incomingHeaders = {}) {
|
|
|
2576
2641
|
const baiduBody = {
|
|
2577
2642
|
model: mappedModel,
|
|
2578
2643
|
messages,
|
|
2579
|
-
max_tokens: body.max_tokens ||
|
|
2644
|
+
max_tokens: Math.min(body.max_tokens || 12288, 12288),
|
|
2580
2645
|
temperature: body.temperature ?? 0.7,
|
|
2581
2646
|
top_p: body.top_p ?? 1.0,
|
|
2582
2647
|
// glm-5.2 emits verbose reasoning_content by default, sharing the same
|
|
@@ -2727,6 +2792,26 @@ async function invokeFireworks(body, _incomingHeaders = {}) {
|
|
|
2727
2792
|
// path below regardless.
|
|
2728
2793
|
stream: body.stream ?? false,
|
|
2729
2794
|
};
|
|
2795
|
+
const _keepThinkingRe = new RegExp(process.env.FIREWORKS_THINKING_MODELS || "glm-5p3|glm-5\\.3|deepseek-v4p1-flash", "i");
|
|
2796
|
+
if (_keepThinkingRe.test(String(fireworksBody.model)) && fireworksBody.thinking?.type === "disabled") {
|
|
2797
|
+
delete fireworksBody.thinking;
|
|
2798
|
+
}
|
|
2799
|
+
{
|
|
2800
|
+
let _effort = body._effort || process.env.FIREWORKS_REASONING_EFFORT || null;
|
|
2801
|
+
const _map = body._effort ? "" : (process.env.FIREWORKS_REASONING_EFFORT_MAP || "");
|
|
2802
|
+
for (const pair of _map.split(",")) {
|
|
2803
|
+
const [k, v] = pair.split("=").map((x) => (x || "").trim());
|
|
2804
|
+
if (k && v && String(fireworksBody.model).includes(k)) { _effort = v; break; }
|
|
2805
|
+
}
|
|
2806
|
+
if (_effort && (!body.thinking || body.thinking?.type === "disabled") && !fireworksBody.reasoning_effort) {
|
|
2807
|
+
fireworksBody.reasoning_effort = _effort;
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
{
|
|
2811
|
+
// Fireworks 400s on json_schema with $ref; json_object is universally accepted.
|
|
2812
|
+
const _rf = openaiResponseFormat(body, process.env.FIREWORKS_RESPONSE_FORMAT_SCHEMA === "true");
|
|
2813
|
+
if (_rf) fireworksBody.response_format = _rf;
|
|
2814
|
+
}
|
|
2730
2815
|
|
|
2731
2816
|
if (Array.isArray(body.tools) && body.tools.length > 0) {
|
|
2732
2817
|
fireworksBody.tools = convertAnthropicToolsToOpenRouter(body.tools);
|
|
@@ -2771,6 +2856,7 @@ async function invokeFireworks(body, _incomingHeaders = {}) {
|
|
|
2771
2856
|
|
|
2772
2857
|
if (response?.ok && response?.json) {
|
|
2773
2858
|
const anthropicJson = convertOpenAIToAnthropic(response.json);
|
|
2859
|
+
if (response.json?.model) anthropicJson._providerModel = response.json.model;
|
|
2774
2860
|
return {
|
|
2775
2861
|
ok: response.ok,
|
|
2776
2862
|
status: response.status,
|
|
@@ -2797,7 +2883,9 @@ function convertOpenAIToAnthropic(response) {
|
|
|
2797
2883
|
const content = [];
|
|
2798
2884
|
|
|
2799
2885
|
// Extract tool calls embedded as XML/text in content (Minimax, Qwen, GLM, etc.)
|
|
2800
|
-
|
|
2886
|
+
const _mc = typeof message.content === "string" ? message.content.trim() : "";
|
|
2887
|
+
const _isJsonObject = _mc.startsWith("{") && _mc.endsWith("}");
|
|
2888
|
+
if (!_isJsonObject && !message.tool_calls?.length && typeof message.content === "string" && message.content.trim()) {
|
|
2801
2889
|
const { extractToolCallsFromText } = require("./xml-tool-extractor");
|
|
2802
2890
|
const extracted = extractToolCallsFromText(message.content);
|
|
2803
2891
|
if (extracted.toolCalls.length > 0) {
|
|
@@ -2879,14 +2967,22 @@ function convertOpenAIToAnthropic(response) {
|
|
|
2879
2967
|
model: response.model,
|
|
2880
2968
|
stop_reason: stopReason,
|
|
2881
2969
|
stop_sequence: null,
|
|
2882
|
-
usage: {
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
//
|
|
2886
|
-
|
|
2970
|
+
usage: (() => {
|
|
2971
|
+
// Anthropic semantics: input_tokens EXCLUDES cache reads. OpenAI's
|
|
2972
|
+
// prompt_tokens INCLUDES them, so subtract here — downstream rebuilders
|
|
2973
|
+
// (openai-format.js finalUsage/convertUsage) add cache_read back when
|
|
2974
|
+
// emitting OpenAI shape. Copying prompt_tokens verbatim double-counted
|
|
2975
|
+
// cached tokens (2× reported prompt, cache % halved — exp2 2026-09-29).
|
|
2976
|
+
const cacheRead = response.usage?.cache_read_input_tokens
|
|
2887
2977
|
?? response.usage?.prompt_tokens_details?.cached_tokens
|
|
2888
|
-
?? null
|
|
2889
|
-
|
|
2978
|
+
?? null;
|
|
2979
|
+
return {
|
|
2980
|
+
input_tokens: Math.max(0, (response.usage?.prompt_tokens || 0) - (cacheRead || 0)),
|
|
2981
|
+
output_tokens: response.usage?.completion_tokens || 0,
|
|
2982
|
+
// Provider-side prompt-cache hits — telemetry reads this field name.
|
|
2983
|
+
cache_read_input_tokens: cacheRead,
|
|
2984
|
+
};
|
|
2985
|
+
})()
|
|
2890
2986
|
};
|
|
2891
2987
|
}
|
|
2892
2988
|
|
|
@@ -3661,7 +3757,7 @@ const PROVIDER_INVOKERS = {
|
|
|
3661
3757
|
orcarouter: invokeOrcaRouter,
|
|
3662
3758
|
};
|
|
3663
3759
|
|
|
3664
|
-
function invokeProvider(provider, body, incomingHeaders) {
|
|
3760
|
+
async function invokeProvider(provider, body, incomingHeaders) {
|
|
3665
3761
|
const invoke = PROVIDER_INVOKERS[provider];
|
|
3666
3762
|
if (!invoke) {
|
|
3667
3763
|
// databricks itself, or an unknown name. Loud, not silent: under tier
|
|
@@ -3671,9 +3767,19 @@ function invokeProvider(provider, body, incomingHeaders) {
|
|
|
3671
3767
|
}
|
|
3672
3768
|
return invokeDatabricks(body, incomingHeaders);
|
|
3673
3769
|
}
|
|
3674
|
-
|
|
3770
|
+
const res = await invoke(body, incomingHeaders);
|
|
3771
|
+
try {
|
|
3772
|
+
const wanted = body?._tierModel ? String(body._tierModel).split("/").pop().toLowerCase() : null;
|
|
3773
|
+
const _rawServed = res?.json?._providerModel || res?.json?.model;
|
|
3774
|
+
const served = _rawServed ? String(_rawServed).split("/").pop().toLowerCase() : null;
|
|
3775
|
+
if (wanted && served && served !== wanted && !served.startsWith(wanted) && !wanted.startsWith(served)) {
|
|
3776
|
+
logger.warn({ provider, requested: body._tierModel, served: _rawServed }, "[ModelCheck] provider served a DIFFERENT model than requested");
|
|
3777
|
+
}
|
|
3778
|
+
} catch { /* never block */ }
|
|
3779
|
+
return res;
|
|
3675
3780
|
}
|
|
3676
3781
|
|
|
3782
|
+
|
|
3677
3783
|
async function invokeModel(body, options = {}) {
|
|
3678
3784
|
const { determineProviderSmart, isFallbackEnabled, getFallbackProvider } = require("./routing");
|
|
3679
3785
|
const metricsCollector = getMetricsCollector();
|
|
@@ -3711,6 +3817,11 @@ async function invokeModel(body, options = {}) {
|
|
|
3711
3817
|
// TaskBand stamp for the continuation telemetry columns
|
|
3712
3818
|
// (telemetry.taskbandFields reads routingResult.taskband).
|
|
3713
3819
|
taskband: body._taskband ?? null,
|
|
3820
|
+
// Jev verdict (telemetry.jevFields reads routingResult.jev).
|
|
3821
|
+
jev: body._jev ?? null,
|
|
3822
|
+
engine: body._engine ?? null,
|
|
3823
|
+
prev_outcome: body._prevOutcome ?? null,
|
|
3824
|
+
gate: body._gate ?? null,
|
|
3714
3825
|
// WS4 — off-policy evaluation from telemetry alone requires
|
|
3715
3826
|
// propensity + candidates on every row. Deterministic default is
|
|
3716
3827
|
// 1.0 with a single-entry candidate list matching the served pair.
|
|
@@ -3836,6 +3947,24 @@ async function invokeModel(body, options = {}) {
|
|
|
3836
3947
|
return await invokeProvider(initialProvider, body, incomingHeaders);
|
|
3837
3948
|
});
|
|
3838
3949
|
|
|
3950
|
+
// Grounding check on completion claims (observe mode: telemetry + log).
|
|
3951
|
+
// Only runs when the reply claims the task is done; ~25 ms/claim on CPU.
|
|
3952
|
+
if (process.env.LYNKR_GROUNDING && process.env.LYNKR_GROUNDING !== 'off' && result?.ok !== false && Array.isArray(result?.json?.content)) {
|
|
3953
|
+
try {
|
|
3954
|
+
const grounding = require('../routing/grounding');
|
|
3955
|
+
const replyText = result.json.content.filter((b) => b && b.type === 'text' && typeof b.text === 'string').map((b) => b.text).join('\n');
|
|
3956
|
+
if (grounding.extractClaims(replyText).claimsDone) {
|
|
3957
|
+
const g = await grounding.check({ replyText, messages: body.messages });
|
|
3958
|
+
routingResult.grounding = { verdict: g.verdict, maxContradiction: g.maxContradiction ?? null, reason: g.reason ?? null };
|
|
3959
|
+
if (g.verdict === 'contradicted' || g.verdict === 'unverified') {
|
|
3960
|
+
logger.info({ provider: initialProvider, model: routingResult.model, verdict: g.verdict, contradiction: g.maxContradiction, pairs: (g.pairs || []).length }, '[Grounding] completion claim not supported by evidence');
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
} catch (err) {
|
|
3964
|
+
logger.debug({ err: err.message }, '[Grounding] check failed (ignored)');
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
|
|
3839
3968
|
const latency = Date.now() - startTime;
|
|
3840
3969
|
metricsCollector.recordProviderSuccess(initialProvider, latency);
|
|
3841
3970
|
metricsCollector.recordDatabricksRequest(true, retries);
|
|
@@ -3928,6 +4057,7 @@ async function invokeModel(body, options = {}) {
|
|
|
3928
4057
|
pinned: routingResult.pinned ? 1 : 0,
|
|
3929
4058
|
switch_reason: routingResult.switch_reason ?? null,
|
|
3930
4059
|
...telemetry.jevFields(routingResult),
|
|
4060
|
+
...telemetry.engineFields(routingResult),
|
|
3931
4061
|
...telemetry.taskbandFields(routingResult),
|
|
3932
4062
|
cache_decision: routingResult._cacheDecision ?? null,
|
|
3933
4063
|
cache_read_tokens: result.json?.usage?.cache_read_input_tokens ?? null,
|
|
@@ -4165,6 +4295,7 @@ async function invokeModel(body, options = {}) {
|
|
|
4165
4295
|
pinned: routingResult.pinned ? 1 : 0,
|
|
4166
4296
|
switch_reason: routingResult.switch_reason ?? null,
|
|
4167
4297
|
...telemetry.jevFields(routingResult),
|
|
4298
|
+
...telemetry.engineFields(routingResult),
|
|
4168
4299
|
...telemetry.taskbandFields(routingResult),
|
|
4169
4300
|
cache_decision: routingResult._cacheDecision ?? null,
|
|
4170
4301
|
});
|
|
@@ -4280,6 +4411,7 @@ async function invokeModel(body, options = {}) {
|
|
|
4280
4411
|
pinned: routingResult.pinned ? 1 : 0,
|
|
4281
4412
|
switch_reason: routingResult.switch_reason ?? null,
|
|
4282
4413
|
...telemetry.jevFields(routingResult),
|
|
4414
|
+
...telemetry.engineFields(routingResult),
|
|
4283
4415
|
...telemetry.taskbandFields(routingResult),
|
|
4284
4416
|
cache_decision: routingResult._cacheDecision ?? null,
|
|
4285
4417
|
});
|
|
@@ -4379,6 +4511,7 @@ async function invokeModel(body, options = {}) {
|
|
|
4379
4511
|
pinned: routingResult.pinned ? 1 : 0,
|
|
4380
4512
|
switch_reason: routingResult.switch_reason ?? null,
|
|
4381
4513
|
...telemetry.jevFields(routingResult),
|
|
4514
|
+
...telemetry.engineFields(routingResult),
|
|
4382
4515
|
...telemetry.taskbandFields(routingResult),
|
|
4383
4516
|
cache_decision: routingResult._cacheDecision ?? null,
|
|
4384
4517
|
cache_read_tokens: fallbackResult.json?.usage?.cache_read_input_tokens ?? null,
|
|
@@ -4442,6 +4575,7 @@ async function invokeModel(body, options = {}) {
|
|
|
4442
4575
|
pinned: routingResult.pinned ? 1 : 0,
|
|
4443
4576
|
switch_reason: routingResult.switch_reason ?? null,
|
|
4444
4577
|
...telemetry.jevFields(routingResult),
|
|
4578
|
+
...telemetry.engineFields(routingResult),
|
|
4445
4579
|
...telemetry.taskbandFields(routingResult),
|
|
4446
4580
|
cache_decision: routingResult._cacheDecision ?? null,
|
|
4447
4581
|
});
|
|
@@ -82,6 +82,7 @@ function appendToSystem(system, text) {
|
|
|
82
82
|
*/
|
|
83
83
|
function injectFormatGuard(body, opts = {}) {
|
|
84
84
|
if (!body) return body;
|
|
85
|
+
if (process.env.FMT_GUARD_ENABLED === "false") return body;
|
|
85
86
|
const { provider, model } = opts;
|
|
86
87
|
if (producesCleanMarkdown(provider, model)) return body;
|
|
87
88
|
|
|
@@ -2312,6 +2312,8 @@ IMPORTANT TOOL USAGE RULES:
|
|
|
2312
2312
|
const { extractToolCallsFromText } = require("../clients/xml-tool-extractor");
|
|
2313
2313
|
for (const block of contentArray) {
|
|
2314
2314
|
if (block?.type === "text" && block?.text) {
|
|
2315
|
+
const _bt = block.text.trim();
|
|
2316
|
+
if (_bt.startsWith("{") && _bt.endsWith("}")) continue;
|
|
2315
2317
|
const extracted = extractToolCallsFromText(block.text);
|
|
2316
2318
|
if (extracted.toolCalls.length > 0) {
|
|
2317
2319
|
toolCalls = extracted.toolCalls;
|
|
@@ -2338,7 +2340,8 @@ IMPORTANT TOOL USAGE RULES:
|
|
|
2338
2340
|
toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
|
|
2339
2341
|
|
|
2340
2342
|
// Extract tool calls embedded as XML/text in content (Minimax, Qwen, GLM, Llama, etc.)
|
|
2341
|
-
if (toolCalls.length === 0 && typeof message.content === "string" && message.content.trim()
|
|
2343
|
+
if (toolCalls.length === 0 && typeof message.content === "string" && message.content.trim()
|
|
2344
|
+
&& !(message.content.trim().startsWith("{") && message.content.trim().endsWith("}"))) {
|
|
2342
2345
|
const { extractToolCallsFromText } = require("../clients/xml-tool-extractor");
|
|
2343
2346
|
const extracted = extractToolCallsFromText(message.content);
|
|
2344
2347
|
if (extracted.toolCalls.length > 0) {
|
|
@@ -2539,6 +2542,10 @@ IMPORTANT TOOL USAGE RULES:
|
|
|
2539
2542
|
if (Array.isArray(anthropicPayload?.content)) {
|
|
2540
2543
|
anthropicPayload.content = policy.sanitiseContent(anthropicPayload.content);
|
|
2541
2544
|
}
|
|
2545
|
+
} else if ((actualProvider === "openrouter" || actualProvider === "edenai")
|
|
2546
|
+
&& databricksResponse.json?.type === "message" && Array.isArray(databricksResponse.json?.content)) {
|
|
2547
|
+
anthropicPayload = databricksResponse.json;
|
|
2548
|
+
anthropicPayload.content = policy.sanitiseContent(anthropicPayload.content);
|
|
2542
2549
|
} else if (actualProvider === "openrouter" || actualProvider === "edenai") {
|
|
2543
2550
|
const { convertOpenRouterResponseToAnthropic } = require("../clients/openrouter-utils");
|
|
2544
2551
|
|
|
@@ -3126,7 +3133,9 @@ async function processMessage({ payload, headers, session, cwd, options = {} })
|
|
|
3126
3133
|
|
|
3127
3134
|
let cacheKey = null;
|
|
3128
3135
|
let cachedResponse = null;
|
|
3129
|
-
|
|
3136
|
+
const _structuredAgentRequest = !!(cleanPayload?.output_format || cleanPayload?.response_format)
|
|
3137
|
+
&& process.env.LYNKR_CACHE_BYPASS_STRUCTURED !== "false";
|
|
3138
|
+
if (promptCache.isEnabled() && !_structuredAgentRequest) {
|
|
3130
3139
|
// cleanPayload is already a deep clone from sanitizePayload, no need to clone again
|
|
3131
3140
|
const { key, entry } = promptCache.lookup(cleanPayload);
|
|
3132
3141
|
pTimer.mark("cacheCheck");
|