ctxloom-pro 1.3.0 → 1.4.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 +6 -1
- package/apps/dashboard/dist/server/index.js +169 -102
- package/dist/budgetStats-TURA232F.js +116 -0
- package/dist/chunk-5I6CJITG.js +99 -0
- package/dist/{chunk-Q2KTZNNU.js → chunk-7GZVGIQL.js} +1429 -71
- package/dist/eventCollector-QSRBVUDF.js +18 -0
- package/dist/index.js +56 -9
- package/dist/{src-HNXOOOWF.js → src-CH2OSHKF.js} +31 -2
- package/package.json +1 -1
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// packages/core/src/utils/stats.ts
|
|
2
|
+
function percentile(values, p) {
|
|
3
|
+
if (values.length === 0) return null;
|
|
4
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
5
|
+
const idx = Math.floor((sorted.length - 1) * p);
|
|
6
|
+
return sorted[idx];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// packages/core/src/budget/budgetStats.ts
|
|
10
|
+
function summarize(events, windowStart, windowEnd) {
|
|
11
|
+
const byTool = /* @__PURE__ */ new Map();
|
|
12
|
+
for (const e of events) {
|
|
13
|
+
const existing = byTool.get(e.tool);
|
|
14
|
+
if (existing) existing.push(e);
|
|
15
|
+
else byTool.set(e.tool, [e]);
|
|
16
|
+
}
|
|
17
|
+
const fallbackTable = [];
|
|
18
|
+
const distributionTable = [];
|
|
19
|
+
for (const [tool, bucket] of byTool) {
|
|
20
|
+
const fallbackUsed = bucket.filter((e) => e.event === "mcp.fallback.used");
|
|
21
|
+
if (fallbackUsed.length > 0) {
|
|
22
|
+
let skeleton = 0, truncate = 0, error = 0;
|
|
23
|
+
for (const e of fallbackUsed) {
|
|
24
|
+
const mode = typeof e.mode === "string" ? e.mode : "";
|
|
25
|
+
if (mode === "skeleton" || mode === "skeleton+truncate") skeleton++;
|
|
26
|
+
else if (mode === "truncate" || mode === "truncate-fallback") truncate++;
|
|
27
|
+
else if (mode === "error") error++;
|
|
28
|
+
}
|
|
29
|
+
const total = skeleton + truncate + error;
|
|
30
|
+
if (total > 0) {
|
|
31
|
+
fallbackTable.push({
|
|
32
|
+
tool,
|
|
33
|
+
breaches: fallbackUsed.length,
|
|
34
|
+
skeletonPct: Math.round(skeleton / total * 100),
|
|
35
|
+
truncatePct: Math.round(truncate / total * 100),
|
|
36
|
+
errorPct: Math.round(error / total * 100)
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const tokens = bucket.filter((e) => e.event === "mcp.budget.exceeded").map((e) => typeof e.original_tokens === "number" ? e.original_tokens : null).filter((n) => n !== null);
|
|
41
|
+
if (tokens.length > 0) {
|
|
42
|
+
distributionTable.push({
|
|
43
|
+
tool,
|
|
44
|
+
n: tokens.length,
|
|
45
|
+
min: Math.min(...tokens),
|
|
46
|
+
p50: percentile(tokens, 0.5),
|
|
47
|
+
p75: percentile(tokens, 0.75),
|
|
48
|
+
p95: percentile(tokens, 0.95),
|
|
49
|
+
max: Math.max(...tokens)
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
fallbackTable.sort((a, b) => a.tool.localeCompare(b.tool));
|
|
54
|
+
distributionTable.sort((a, b) => a.tool.localeCompare(b.tool));
|
|
55
|
+
return {
|
|
56
|
+
windowStart: windowStart.toISOString(),
|
|
57
|
+
windowEnd: windowEnd.toISOString(),
|
|
58
|
+
totalEvents: events.length,
|
|
59
|
+
fallbackTable,
|
|
60
|
+
distributionTable
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function renderSummary(s) {
|
|
64
|
+
const lines = [];
|
|
65
|
+
const startDate = s.windowStart.slice(0, 10);
|
|
66
|
+
const endDate = s.windowEnd.slice(0, 10);
|
|
67
|
+
lines.push(`Budget event summary (${startDate} \u2192 ${endDate}, ${s.totalEvents} events)`);
|
|
68
|
+
lines.push("");
|
|
69
|
+
if (s.totalEvents === 0) {
|
|
70
|
+
lines.push("No events in window. Either:");
|
|
71
|
+
lines.push(" - No budget breaches occurred (everything fit under budgets)");
|
|
72
|
+
lines.push(` - CTXLOOM_TELEMETRY_LEVEL is not set to "full" in the MCP server's env`);
|
|
73
|
+
lines.push(" - No tool calls have opted into the budget surface yet");
|
|
74
|
+
return lines.join("\n");
|
|
75
|
+
}
|
|
76
|
+
lines.push("Fallback distribution per tool");
|
|
77
|
+
lines.push("");
|
|
78
|
+
if (s.fallbackTable.length === 0) {
|
|
79
|
+
lines.push(" (no fallback events recorded in window)");
|
|
80
|
+
} else {
|
|
81
|
+
lines.push("| Tool | Breaches | Skeleton % | Truncate % | Error % |");
|
|
82
|
+
lines.push("|----------------------------|---------:|-----------:|-----------:|--------:|");
|
|
83
|
+
for (const r of s.fallbackTable) {
|
|
84
|
+
lines.push(
|
|
85
|
+
`| ${r.tool.padEnd(26)} | ${String(r.breaches).padStart(8)} | ${String(r.skeletonPct).padStart(9)}% | ${String(r.truncatePct).padStart(9)}% | ${String(r.errorPct).padStart(6)}% |`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
lines.push("");
|
|
90
|
+
lines.push("Original-token distribution per tool (over-budget calls only)");
|
|
91
|
+
lines.push("");
|
|
92
|
+
if (s.distributionTable.length === 0) {
|
|
93
|
+
lines.push(" (no budget-breach events recorded in window)");
|
|
94
|
+
} else {
|
|
95
|
+
lines.push("| Tool | n | min | p50 | p75 | p95 | max |");
|
|
96
|
+
lines.push("|----------------------------|----:|-------:|-------:|-------:|-------:|-------:|");
|
|
97
|
+
for (const r of s.distributionTable) {
|
|
98
|
+
lines.push(
|
|
99
|
+
`| ${r.tool.padEnd(26)} | ${String(r.n).padStart(3)} | ${fmt(r.min)} | ${fmt(r.p50)} | ${fmt(r.p75)} | ${fmt(r.p95)} | ${fmt(r.max)} |`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
lines.push("");
|
|
103
|
+
lines.push("The **p75** column is the input for per-tool default budget tuning");
|
|
104
|
+
lines.push("(packages/core/src/tools/*.ts \u2192 DEFAULT_MAX_RESPONSE_TOKENS).");
|
|
105
|
+
}
|
|
106
|
+
return lines.join("\n");
|
|
107
|
+
}
|
|
108
|
+
function fmt(n) {
|
|
109
|
+
return n === null ? " \u2014" : String(n).padStart(6);
|
|
110
|
+
}
|
|
111
|
+
export {
|
|
112
|
+
percentile,
|
|
113
|
+
renderSummary,
|
|
114
|
+
summarize
|
|
115
|
+
};
|
|
116
|
+
//# sourceMappingURL=budgetStats-TURA232F.js.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {
|
|
2
|
+
logger
|
|
3
|
+
} from "./chunk-TYDMSHV7.js";
|
|
4
|
+
|
|
5
|
+
// packages/core/src/budget/eventCollector.ts
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import os from "os";
|
|
8
|
+
import path from "path";
|
|
9
|
+
var DEFAULT_TELEMETRY_DIR = path.join(os.homedir(), ".ctxloom", "telemetry");
|
|
10
|
+
function telemetryDir() {
|
|
11
|
+
const raw = process.env.CTXLOOM_TELEMETRY_DIR ?? DEFAULT_TELEMETRY_DIR;
|
|
12
|
+
if (raw.includes("..") || !path.isAbsolute(raw)) {
|
|
13
|
+
if (!telemetryDirWarned) {
|
|
14
|
+
telemetryDirWarned = true;
|
|
15
|
+
logger.warn('CTXLOOM_TELEMETRY_DIR rejected \u2014 must be an absolute path with no ".." segments; using default', {
|
|
16
|
+
rejected: raw,
|
|
17
|
+
fallback: DEFAULT_TELEMETRY_DIR
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return DEFAULT_TELEMETRY_DIR;
|
|
21
|
+
}
|
|
22
|
+
return path.resolve(raw);
|
|
23
|
+
}
|
|
24
|
+
var telemetryDirWarned = false;
|
|
25
|
+
function filenameForDate(date) {
|
|
26
|
+
const y = date.getUTCFullYear();
|
|
27
|
+
const m = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
28
|
+
const d = String(date.getUTCDate()).padStart(2, "0");
|
|
29
|
+
return `budget-events-${y}-${m}-${d}.jsonl`;
|
|
30
|
+
}
|
|
31
|
+
function appendEvent(event, now = /* @__PURE__ */ new Date()) {
|
|
32
|
+
try {
|
|
33
|
+
const dir = telemetryDir();
|
|
34
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
35
|
+
const file = path.join(dir, filenameForDate(now));
|
|
36
|
+
const persisted = { ts: now.toISOString(), ...event };
|
|
37
|
+
fs.appendFileSync(file, JSON.stringify(persisted) + "\n", "utf-8");
|
|
38
|
+
} catch (err) {
|
|
39
|
+
if (!appendFailureWarned) {
|
|
40
|
+
appendFailureWarned = true;
|
|
41
|
+
logger.warn("telemetry sink append failed (further failures suppressed)", {
|
|
42
|
+
error: err instanceof Error ? err.message : String(err)
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
var appendFailureWarned = false;
|
|
48
|
+
function __resetTelemetryWarnFlagsForTests() {
|
|
49
|
+
telemetryDirWarned = false;
|
|
50
|
+
appendFailureWarned = false;
|
|
51
|
+
}
|
|
52
|
+
function readEvents(opts = {}) {
|
|
53
|
+
const until = opts.until ?? /* @__PURE__ */ new Date();
|
|
54
|
+
const since = opts.since ?? new Date(until.getTime() - 14 * 24 * 60 * 60 * 1e3);
|
|
55
|
+
const dir = telemetryDir();
|
|
56
|
+
if (!fs.existsSync(dir)) return [];
|
|
57
|
+
const out = [];
|
|
58
|
+
for (let cursor = new Date(Date.UTC(since.getUTCFullYear(), since.getUTCMonth(), since.getUTCDate())); cursor.getTime() <= until.getTime(); cursor = new Date(cursor.getTime() + 24 * 60 * 60 * 1e3)) {
|
|
59
|
+
const file = path.join(dir, filenameForDate(cursor));
|
|
60
|
+
if (!fs.existsSync(file)) continue;
|
|
61
|
+
const text = fs.readFileSync(file, "utf-8");
|
|
62
|
+
for (const line of text.split("\n")) {
|
|
63
|
+
if (line.trim() === "") continue;
|
|
64
|
+
let parsed;
|
|
65
|
+
try {
|
|
66
|
+
parsed = JSON.parse(line);
|
|
67
|
+
} catch {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (!isPersistedEvent(parsed)) continue;
|
|
71
|
+
const eventTs = new Date(parsed.ts).getTime();
|
|
72
|
+
if (!Number.isFinite(eventTs)) continue;
|
|
73
|
+
if (eventTs < since.getTime() || eventTs > until.getTime()) continue;
|
|
74
|
+
if (opts.tool && parsed.tool !== opts.tool) continue;
|
|
75
|
+
out.push(parsed);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
function isPersistedEvent(v) {
|
|
81
|
+
if (!v || typeof v !== "object") return false;
|
|
82
|
+
const o = v;
|
|
83
|
+
return typeof o.ts === "string" && typeof o.event === "string" && typeof o.tool === "string";
|
|
84
|
+
}
|
|
85
|
+
var diskSink = {
|
|
86
|
+
append(event) {
|
|
87
|
+
appendEvent(event);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export {
|
|
92
|
+
telemetryDir,
|
|
93
|
+
filenameForDate,
|
|
94
|
+
appendEvent,
|
|
95
|
+
__resetTelemetryWarnFlagsForTests,
|
|
96
|
+
readEvents,
|
|
97
|
+
diskSink
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=chunk-5I6CJITG.js.map
|