lynkr 9.11.0 → 9.12.0
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 +46 -11
- package/bin/statusline.js +80 -0
- package/bin/wrap.js +10 -6
- package/package.json +8 -3
- package/public/dashboard.html +325 -1
- package/src/api/openai-router.js +120 -2
- package/src/clients/databricks.js +28 -1
- package/src/dashboard/api.js +78 -2
- package/src/dashboard/recommendations/cache-weakspots.js +88 -0
- package/src/dashboard/recommendations/deadweight.js +82 -0
- package/src/dashboard/recommendations/downsize.js +109 -0
- package/src/dashboard/recommendations/index.js +106 -0
- package/src/dashboard/recommendations/side-requests.js +68 -0
- package/src/dashboard/recommendations/wilson.js +24 -0
- package/src/dashboard/router.js +5 -0
- package/src/orchestrator/sse-transformer.js +9 -3
- package/src/routing/complexity-analyzer.js +30 -9
- package/src/routing/telemetry.js +206 -2
|
@@ -16,6 +16,7 @@ const logger = require('../logger');
|
|
|
16
16
|
const config = require('../config');
|
|
17
17
|
const codeGraph = require('../tools/code-graph');
|
|
18
18
|
const clientProfiles = require('./client-profiles');
|
|
19
|
+
const { compressMessages, shouldCompress } = require('./context-compressor');
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Return the tools the user actually added beyond the client harness's
|
|
@@ -769,13 +770,31 @@ function getThreshold() {
|
|
|
769
770
|
* @returns {Object} Complexity analysis result
|
|
770
771
|
*/
|
|
771
772
|
async function analyzeComplexity(payload, options = {}) {
|
|
772
|
-
|
|
773
|
-
|
|
773
|
+
// Phase 0: Context Compression (TencentDB-inspired)
|
|
774
|
+
// Compress conversation before scoring to account for token reduction
|
|
775
|
+
let workingPayload = payload;
|
|
776
|
+
let compressionStats = null;
|
|
777
|
+
|
|
778
|
+
const useCompression = options.compression ?? config.routing?.compression ?? true;
|
|
779
|
+
if (useCompression && shouldCompress(payload)) {
|
|
780
|
+
const { compressed, stats } = compressMessages(payload.messages);
|
|
781
|
+
compressionStats = stats;
|
|
782
|
+
workingPayload = { ...payload, messages: compressed };
|
|
783
|
+
|
|
784
|
+
logger.debug({
|
|
785
|
+
reduction: stats.reduction,
|
|
786
|
+
originalTokens: stats.original,
|
|
787
|
+
compressedTokens: stats.compressed,
|
|
788
|
+
}, '[complexity] Context compressed');
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
const content = extractContent(workingPayload);
|
|
792
|
+
const messageCount = workingPayload?.messages?.length ?? 0;
|
|
774
793
|
const useWeighted = options.weighted ?? config.routing?.weightedScoring ?? false;
|
|
775
794
|
|
|
776
795
|
// Use weighted scoring if enabled
|
|
777
796
|
if (useWeighted) {
|
|
778
|
-
const weighted = calculateWeightedScore(
|
|
797
|
+
const weighted = calculateWeightedScore(workingPayload, content);
|
|
779
798
|
const threshold = getThreshold();
|
|
780
799
|
const mode = config.smartToolSelection?.mode ?? 'heuristic';
|
|
781
800
|
|
|
@@ -807,6 +826,7 @@ async function analyzeComplexity(payload, options = {}) {
|
|
|
807
826
|
forceReason: taskTypeResult.reason?.startsWith('force_') ? taskTypeResult.reason : null,
|
|
808
827
|
content: content.slice(0, 100) + (content.length > 100 ? '...' : ''),
|
|
809
828
|
graphSignals: null,
|
|
829
|
+
compression: compressionStats, // Add compression stats
|
|
810
830
|
};
|
|
811
831
|
|
|
812
832
|
// Phase 5: Structural Analysis (code-review-graph, optional)
|
|
@@ -841,9 +861,9 @@ async function analyzeComplexity(payload, options = {}) {
|
|
|
841
861
|
return result;
|
|
842
862
|
}
|
|
843
863
|
|
|
844
|
-
// Standard scoring (original logic)
|
|
845
|
-
const tokenScore = scoreTokens(
|
|
846
|
-
const toolScore = scoreTools(
|
|
864
|
+
// Standard scoring (original logic) - use compressed payload
|
|
865
|
+
const tokenScore = scoreTokens(workingPayload);
|
|
866
|
+
const toolScore = scoreTools(workingPayload);
|
|
847
867
|
const taskTypeResult = scoreTaskType(content);
|
|
848
868
|
const codeComplexityResult = scoreCodeComplexity(content);
|
|
849
869
|
const reasoningResult = scoreReasoning(content);
|
|
@@ -881,8 +901,8 @@ async function analyzeComplexity(payload, options = {}) {
|
|
|
881
901
|
mode,
|
|
882
902
|
recommendation,
|
|
883
903
|
breakdown: {
|
|
884
|
-
tokens: { score: tokenScore, estimated: estimateTokens(
|
|
885
|
-
tools: { score: toolScore, count: _effectiveTools(
|
|
904
|
+
tokens: { score: tokenScore, estimated: estimateTokens(workingPayload) },
|
|
905
|
+
tools: { score: toolScore, count: _effectiveTools(workingPayload).length },
|
|
886
906
|
taskType: taskTypeResult,
|
|
887
907
|
codeComplexity: codeComplexityResult,
|
|
888
908
|
reasoning: reasoningResult,
|
|
@@ -890,11 +910,12 @@ async function analyzeComplexity(payload, options = {}) {
|
|
|
890
910
|
},
|
|
891
911
|
content: content.slice(0, 100) + (content.length > 100 ? '...' : ''),
|
|
892
912
|
graphSignals: null,
|
|
913
|
+
compression: compressionStats, // Add compression stats
|
|
893
914
|
};
|
|
894
915
|
|
|
895
916
|
// Phase 5: Structural Analysis (code-review-graph, optional)
|
|
896
917
|
try {
|
|
897
|
-
const filePaths = extractFilePaths(
|
|
918
|
+
const filePaths = extractFilePaths(workingPayload);
|
|
898
919
|
const graphOpts = { filePaths, workspace: options?.workspace };
|
|
899
920
|
const graphAvailable = await codeGraph.isAvailable(graphOpts);
|
|
900
921
|
if (graphAvailable && filePaths.length > 0) {
|
package/src/routing/telemetry.js
CHANGED
|
@@ -170,6 +170,11 @@ function init() {
|
|
|
170
170
|
// projectedSwitchCostUsd, projectedStaySavingsUsd,
|
|
171
171
|
// expectedRemainingTurns} as JSON.
|
|
172
172
|
["cache_decision", "TEXT"],
|
|
173
|
+
// Lens dashboard — per-request cache counters from the provider's
|
|
174
|
+
// usage payload. NULL on rows recorded before capture began (or by
|
|
175
|
+
// providers that report none) — "not measured" is distinct from 0.
|
|
176
|
+
["cache_read_tokens", "INTEGER"],
|
|
177
|
+
["cache_creation_tokens", "INTEGER"],
|
|
173
178
|
];
|
|
174
179
|
for (const [col, type] of additiveCols) {
|
|
175
180
|
if (!existingCols.has(col)) {
|
|
@@ -232,7 +237,7 @@ function record(data) {
|
|
|
232
237
|
retry_count, circuit_breaker_state, quality_score, tokens_per_second,
|
|
233
238
|
cost_efficiency, request_text, response_text,
|
|
234
239
|
base_tier, escalation_source, propensity, candidates, pinned, switch_reason,
|
|
235
|
-
cache_decision
|
|
240
|
+
cache_decision, cache_read_tokens, cache_creation_tokens
|
|
236
241
|
) VALUES (
|
|
237
242
|
@request_id, @session_id, @timestamp, @complexity_score, @tier,
|
|
238
243
|
@agentic_type, @tool_count, @input_tokens, @message_count, @request_type,
|
|
@@ -241,7 +246,7 @@ function record(data) {
|
|
|
241
246
|
@retry_count, @circuit_breaker_state, @quality_score, @tokens_per_second,
|
|
242
247
|
@cost_efficiency, @request_text, @response_text,
|
|
243
248
|
@base_tier, @escalation_source, @propensity, @candidates, @pinned, @switch_reason,
|
|
244
|
-
@cache_decision
|
|
249
|
+
@cache_decision, @cache_read_tokens, @cache_creation_tokens
|
|
245
250
|
)`
|
|
246
251
|
);
|
|
247
252
|
if (!insert) return;
|
|
@@ -292,6 +297,8 @@ function record(data) {
|
|
|
292
297
|
: (typeof data.cache_decision === "string"
|
|
293
298
|
? data.cache_decision
|
|
294
299
|
: JSON.stringify(data.cache_decision)),
|
|
300
|
+
cache_read_tokens: data.cache_read_tokens ?? null,
|
|
301
|
+
cache_creation_tokens: data.cache_creation_tokens ?? null,
|
|
295
302
|
});
|
|
296
303
|
} catch (err) {
|
|
297
304
|
logger.debug({ err: err.message }, "Telemetry record failed");
|
|
@@ -926,6 +933,200 @@ function getExpectedRemainingTurns(currentTurns = 0) {
|
|
|
926
933
|
}
|
|
927
934
|
}
|
|
928
935
|
|
|
936
|
+
// ---------------------------------------------------------------------------
|
|
937
|
+
// Lens dashboard queries
|
|
938
|
+
// ---------------------------------------------------------------------------
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* MEASURED cache savings — backward-looking, auditable against the bill:
|
|
942
|
+
* for rows that carried cache counters, what did cache reads actually save
|
|
943
|
+
* vs. paying full input price? Distinct from the projected figures in
|
|
944
|
+
* getCacheEconomics. Also reports the hit ratio per (provider, model).
|
|
945
|
+
*
|
|
946
|
+
* @param {Object} [opts] {since}
|
|
947
|
+
* @returns {{measuredSavedUsd:number, rowsMeasured:number, perModel:Array}|null}
|
|
948
|
+
*/
|
|
949
|
+
function getMeasuredCacheSavings(opts = {}) {
|
|
950
|
+
if (!init()) return null;
|
|
951
|
+
const since = opts.since ?? Date.now() - 7 * 24 * 60 * 60 * 1000;
|
|
952
|
+
try {
|
|
953
|
+
const rows = db
|
|
954
|
+
.prepare(
|
|
955
|
+
`SELECT provider, model, COUNT(*) n,
|
|
956
|
+
SUM(COALESCE(cache_read_tokens,0)) rd,
|
|
957
|
+
SUM(COALESCE(cache_creation_tokens,0)) cr,
|
|
958
|
+
SUM(COALESCE(input_tokens,0)) inp
|
|
959
|
+
FROM routing_telemetry
|
|
960
|
+
WHERE timestamp > ? AND cache_read_tokens IS NOT NULL
|
|
961
|
+
GROUP BY provider, model`
|
|
962
|
+
)
|
|
963
|
+
.all(since);
|
|
964
|
+
const { resolveCacheEconomics } = require("./cache-economics");
|
|
965
|
+
let saved = 0;
|
|
966
|
+
let measured = 0;
|
|
967
|
+
const perModel = [];
|
|
968
|
+
for (const r of rows) {
|
|
969
|
+
measured += r.n;
|
|
970
|
+
const econ = resolveCacheEconomics(r.provider, r.model);
|
|
971
|
+
const savedUsd = econ.unknownPricing
|
|
972
|
+
? null
|
|
973
|
+
: (r.rd * Math.max(0, econ.inputPerM - econ.cacheReadPerM)) / 1_000_000;
|
|
974
|
+
if (savedUsd != null) saved += savedUsd;
|
|
975
|
+
const total = r.rd + r.cr + r.inp;
|
|
976
|
+
perModel.push({
|
|
977
|
+
provider: r.provider,
|
|
978
|
+
model: r.model,
|
|
979
|
+
requests: r.n,
|
|
980
|
+
cacheReadTokens: r.rd,
|
|
981
|
+
hitRatio: total > 0 ? r.rd / total : null,
|
|
982
|
+
savedUsd,
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
perModel.sort((a, b) => (a.hitRatio ?? 1) - (b.hitRatio ?? 1));
|
|
986
|
+
return { measuredSavedUsd: saved, rowsMeasured: measured, perModel };
|
|
987
|
+
} catch (err) {
|
|
988
|
+
logger.debug({ err: err.message }, "Telemetry getMeasuredCacheSavings failed");
|
|
989
|
+
return null;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
/**
|
|
994
|
+
* Session drill-down: per-model mix, context-growth series, tool totals.
|
|
995
|
+
* @param {string} sessionId
|
|
996
|
+
*/
|
|
997
|
+
function getSessionDetail(sessionId) {
|
|
998
|
+
if (!sessionId || !init()) return null;
|
|
999
|
+
try {
|
|
1000
|
+
const rows = db
|
|
1001
|
+
.prepare(
|
|
1002
|
+
`SELECT timestamp, provider, model, tier, routing_method, input_tokens,
|
|
1003
|
+
output_tokens, cost_usd, latency_ms, tool_count, tool_calls_made,
|
|
1004
|
+
cache_read_tokens, cache_creation_tokens, status_code, error_type,
|
|
1005
|
+
pinned, switch_reason, message_count
|
|
1006
|
+
FROM routing_telemetry WHERE session_id = ? ORDER BY timestamp ASC LIMIT 2000`
|
|
1007
|
+
)
|
|
1008
|
+
.all(sessionId);
|
|
1009
|
+
if (!rows.length) return null;
|
|
1010
|
+
|
|
1011
|
+
const perModel = new Map();
|
|
1012
|
+
let toolCalls = 0;
|
|
1013
|
+
let cost = 0;
|
|
1014
|
+
for (const r of rows) {
|
|
1015
|
+
const key = `${r.provider}:${r.model}`;
|
|
1016
|
+
const m = perModel.get(key) ?? {
|
|
1017
|
+
provider: r.provider, model: r.model, requests: 0,
|
|
1018
|
+
inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, costUsd: 0,
|
|
1019
|
+
};
|
|
1020
|
+
m.requests++;
|
|
1021
|
+
m.inputTokens += r.input_tokens ?? 0;
|
|
1022
|
+
m.outputTokens += r.output_tokens ?? 0;
|
|
1023
|
+
m.cacheReadTokens += r.cache_read_tokens ?? 0;
|
|
1024
|
+
m.costUsd += r.cost_usd ?? 0;
|
|
1025
|
+
perModel.set(key, m);
|
|
1026
|
+
toolCalls += r.tool_calls_made ?? 0;
|
|
1027
|
+
cost += r.cost_usd ?? 0;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
return {
|
|
1031
|
+
sessionId,
|
|
1032
|
+
requests: rows.length,
|
|
1033
|
+
totalCostUsd: cost,
|
|
1034
|
+
toolCalls,
|
|
1035
|
+
firstSeen: rows[0].timestamp,
|
|
1036
|
+
lastSeen: rows[rows.length - 1].timestamp,
|
|
1037
|
+
models: [...perModel.values()].sort((a, b) => b.costUsd - a.costUsd),
|
|
1038
|
+
// Input (context) tokens per call over the session — the distiller
|
|
1039
|
+
// cliff and frozen-block plateau are visible here.
|
|
1040
|
+
contextSeries: rows.map((r) => ({
|
|
1041
|
+
t: r.timestamp,
|
|
1042
|
+
inputTokens: r.input_tokens ?? 0,
|
|
1043
|
+
cacheReadTokens: r.cache_read_tokens ?? null,
|
|
1044
|
+
tier: r.tier,
|
|
1045
|
+
model: r.model,
|
|
1046
|
+
})),
|
|
1047
|
+
rows,
|
|
1048
|
+
};
|
|
1049
|
+
} catch (err) {
|
|
1050
|
+
logger.debug({ err: err.message }, "Telemetry getSessionDetail failed");
|
|
1051
|
+
return null;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// Pivot explorer — every metric/dimension is whitelisted; params never reach
|
|
1056
|
+
// SQL as strings.
|
|
1057
|
+
const ANALYTICS_METRICS = {
|
|
1058
|
+
spend: "SUM(COALESCE(cost_usd,0))",
|
|
1059
|
+
tokens: "SUM(COALESCE(input_tokens,0)+COALESCE(output_tokens,0))",
|
|
1060
|
+
requests: "COUNT(*)",
|
|
1061
|
+
sessions: "COUNT(DISTINCT session_id)",
|
|
1062
|
+
};
|
|
1063
|
+
const ANALYTICS_DIMS = {
|
|
1064
|
+
provider: "provider",
|
|
1065
|
+
model: "model",
|
|
1066
|
+
tier: "tier",
|
|
1067
|
+
request_type: "request_type",
|
|
1068
|
+
routing_method: "routing_method",
|
|
1069
|
+
switch_reason: "switch_reason",
|
|
1070
|
+
day: "date(timestamp/1000, 'unixepoch')",
|
|
1071
|
+
};
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* Generic Metric × Dimension (× Stack) pivot with KPI row and
|
|
1075
|
+
* period-over-period deltas (suppressed when the prior window is thin).
|
|
1076
|
+
*
|
|
1077
|
+
* @param {Object} [opts] {metric, by, stack, since, until}
|
|
1078
|
+
*/
|
|
1079
|
+
function getAnalytics(opts = {}) {
|
|
1080
|
+
if (!init()) return null;
|
|
1081
|
+
const metricExpr = ANALYTICS_METRICS[opts.metric] ?? ANALYTICS_METRICS.spend;
|
|
1082
|
+
const metric = ANALYTICS_METRICS[opts.metric] ? opts.metric : "spend";
|
|
1083
|
+
const byExpr = ANALYTICS_DIMS[opts.by] ?? ANALYTICS_DIMS.provider;
|
|
1084
|
+
const by = ANALYTICS_DIMS[opts.by] ? opts.by : "provider";
|
|
1085
|
+
const stackExpr = opts.stack && ANALYTICS_DIMS[opts.stack] ? ANALYTICS_DIMS[opts.stack] : null;
|
|
1086
|
+
const until = opts.until ?? Date.now();
|
|
1087
|
+
const since = opts.since ?? until - 7 * 24 * 60 * 60 * 1000;
|
|
1088
|
+
|
|
1089
|
+
try {
|
|
1090
|
+
const groupCols = stackExpr ? `${byExpr} AS dim, ${stackExpr} AS stack` : `${byExpr} AS dim`;
|
|
1091
|
+
const groupBy = stackExpr ? "GROUP BY dim, stack" : "GROUP BY dim";
|
|
1092
|
+
const rows = db
|
|
1093
|
+
.prepare(
|
|
1094
|
+
`SELECT ${groupCols}, ${metricExpr} AS value
|
|
1095
|
+
FROM routing_telemetry
|
|
1096
|
+
WHERE timestamp BETWEEN ? AND ?
|
|
1097
|
+
${groupBy} ORDER BY value DESC LIMIT 500`
|
|
1098
|
+
)
|
|
1099
|
+
.all(since, until);
|
|
1100
|
+
|
|
1101
|
+
const kpiRow = (lo, hi) =>
|
|
1102
|
+
db
|
|
1103
|
+
.prepare(
|
|
1104
|
+
`SELECT SUM(COALESCE(cost_usd,0)) spend,
|
|
1105
|
+
SUM(COALESCE(input_tokens,0)+COALESCE(output_tokens,0)) tokens,
|
|
1106
|
+
COUNT(*) requests,
|
|
1107
|
+
COUNT(DISTINCT session_id) sessions
|
|
1108
|
+
FROM routing_telemetry WHERE timestamp BETWEEN ? AND ?`
|
|
1109
|
+
)
|
|
1110
|
+
.get(lo, hi);
|
|
1111
|
+
const kpis = kpiRow(since, until);
|
|
1112
|
+
const prev = kpiRow(since - (until - since), since);
|
|
1113
|
+
// Thin prior window → deltas are noise, suppress rather than mislead.
|
|
1114
|
+
const kpiDeltas = (prev?.requests ?? 0) >= 10
|
|
1115
|
+
? {
|
|
1116
|
+
spend: kpis.spend - (prev.spend ?? 0),
|
|
1117
|
+
tokens: kpis.tokens - (prev.tokens ?? 0),
|
|
1118
|
+
requests: kpis.requests - prev.requests,
|
|
1119
|
+
sessions: kpis.sessions - (prev.sessions ?? 0),
|
|
1120
|
+
}
|
|
1121
|
+
: null;
|
|
1122
|
+
|
|
1123
|
+
return { metric, by, stack: stackExpr ? opts.stack : null, since, until, rows, kpis, kpiDeltas };
|
|
1124
|
+
} catch (err) {
|
|
1125
|
+
logger.debug({ err: err.message }, "Telemetry getAnalytics failed");
|
|
1126
|
+
return null;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
929
1130
|
module.exports = {
|
|
930
1131
|
record,
|
|
931
1132
|
query,
|
|
@@ -936,6 +1137,9 @@ module.exports = {
|
|
|
936
1137
|
getQualityByTierAndType,
|
|
937
1138
|
getExpectedRemainingTurns,
|
|
938
1139
|
getCacheEconomics,
|
|
1140
|
+
getMeasuredCacheSavings,
|
|
1141
|
+
getSessionDetail,
|
|
1142
|
+
getAnalytics,
|
|
939
1143
|
recordSavings,
|
|
940
1144
|
getSavingsSummary,
|
|
941
1145
|
cleanup,
|