@relayplane/proxy 1.9.18 → 1.9.21
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/dist/agent-policy.d.ts +54 -0
- package/dist/agent-policy.d.ts.map +1 -0
- package/dist/agent-policy.js +183 -0
- package/dist/agent-policy.js.map +1 -0
- package/dist/budget.d.ts +81 -0
- package/dist/budget.d.ts.map +1 -1
- package/dist/budget.js +224 -1
- package/dist/budget.js.map +1 -1
- package/dist/cli.js +773 -4
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/policy-analyzer.d.ts +41 -0
- package/dist/policy-analyzer.d.ts.map +1 -0
- package/dist/policy-analyzer.js +268 -0
- package/dist/policy-analyzer.js.map +1 -0
- package/dist/policy-suggestions.d.ts +31 -0
- package/dist/policy-suggestions.d.ts.map +1 -0
- package/dist/policy-suggestions.js +173 -0
- package/dist/policy-suggestions.js.map +1 -0
- package/dist/routing-log.d.ts +47 -0
- package/dist/routing-log.d.ts.map +1 -0
- package/dist/routing-log.js +141 -0
- package/dist/routing-log.js.map +1 -0
- package/dist/standalone-proxy.d.ts.map +1 -1
- package/dist/standalone-proxy.js +139 -0
- package/dist/standalone-proxy.js.map +1 -1
- package/dist/telemetryPinger.d.ts +2 -0
- package/dist/telemetryPinger.d.ts.map +1 -0
- package/dist/telemetryPinger.js +80 -0
- package/dist/telemetryPinger.js.map +1 -0
- package/package.json +4 -2
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Routing Log
|
|
4
|
+
*
|
|
5
|
+
* In-memory ring buffer (1000 entries) with JSONL persistence at
|
|
6
|
+
* ~/.relayplane/routing-log.jsonl. Tracks all routing decisions for
|
|
7
|
+
* observability and debugging.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.LOG_FILE = void 0;
|
|
44
|
+
exports._resetRoutingLog = _resetRoutingLog;
|
|
45
|
+
exports.initRoutingLog = initRoutingLog;
|
|
46
|
+
exports.appendRoutingLog = appendRoutingLog;
|
|
47
|
+
exports.getRoutingLog = getRoutingLog;
|
|
48
|
+
exports.flushRoutingLog = flushRoutingLog;
|
|
49
|
+
const fs = __importStar(require("node:fs"));
|
|
50
|
+
const path = __importStar(require("node:path"));
|
|
51
|
+
const os = __importStar(require("node:os"));
|
|
52
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
53
|
+
const LOG_DIR = path.join(os.homedir(), '.relayplane');
|
|
54
|
+
exports.LOG_FILE = path.join(LOG_DIR, 'routing-log.jsonl');
|
|
55
|
+
const BAK_FILE = path.join(LOG_DIR, 'routing-log.jsonl.bak');
|
|
56
|
+
const MAX_ENTRIES = 1000;
|
|
57
|
+
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
|
|
58
|
+
// ─── State ────────────────────────────────────────────────────────────────────
|
|
59
|
+
let _buffer = [];
|
|
60
|
+
/** Reset for testing */
|
|
61
|
+
function _resetRoutingLog() {
|
|
62
|
+
_buffer = [];
|
|
63
|
+
}
|
|
64
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
65
|
+
/**
|
|
66
|
+
* Initialize routing log by loading the last 1000 lines from file.
|
|
67
|
+
* Called once on proxy startup.
|
|
68
|
+
*/
|
|
69
|
+
function initRoutingLog() {
|
|
70
|
+
if (!fs.existsSync(exports.LOG_FILE))
|
|
71
|
+
return;
|
|
72
|
+
try {
|
|
73
|
+
const content = fs.readFileSync(exports.LOG_FILE, 'utf-8');
|
|
74
|
+
const lines = content.split('\n').filter(l => l.trim().length > 0);
|
|
75
|
+
const last = lines.slice(-MAX_ENTRIES);
|
|
76
|
+
_buffer = [];
|
|
77
|
+
for (const line of last) {
|
|
78
|
+
try {
|
|
79
|
+
const entry = JSON.parse(line);
|
|
80
|
+
_buffer.push(entry);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Skip malformed lines
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Non-critical — start with empty buffer
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Append a routing decision to the in-memory buffer and JSONL file.
|
|
93
|
+
*/
|
|
94
|
+
function appendRoutingLog(entry) {
|
|
95
|
+
// Ring buffer: drop oldest when full
|
|
96
|
+
if (_buffer.length >= MAX_ENTRIES) {
|
|
97
|
+
_buffer.shift();
|
|
98
|
+
}
|
|
99
|
+
_buffer.push(entry);
|
|
100
|
+
// Persist to file
|
|
101
|
+
try {
|
|
102
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
103
|
+
// Rotate if file is too large
|
|
104
|
+
try {
|
|
105
|
+
const stat = fs.statSync(exports.LOG_FILE);
|
|
106
|
+
if (stat.size > MAX_FILE_SIZE) {
|
|
107
|
+
fs.renameSync(exports.LOG_FILE, BAK_FILE);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// File may not exist yet — that's fine
|
|
112
|
+
}
|
|
113
|
+
fs.appendFileSync(exports.LOG_FILE, JSON.stringify(entry) + '\n', 'utf-8');
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// Non-critical — don't break the proxy
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get routing log entries from the in-memory buffer with optional filters.
|
|
121
|
+
*/
|
|
122
|
+
function getRoutingLog(opts) {
|
|
123
|
+
let entries = _buffer.slice();
|
|
124
|
+
if (opts?.agentFingerprint) {
|
|
125
|
+
const fp = opts.agentFingerprint;
|
|
126
|
+
entries = entries.filter(e => e.agentFingerprint === fp || e.agentName === fp);
|
|
127
|
+
}
|
|
128
|
+
if (opts?.taskType) {
|
|
129
|
+
const tt = opts.taskType;
|
|
130
|
+
entries = entries.filter(e => e.taskType === tt);
|
|
131
|
+
}
|
|
132
|
+
const limit = Math.min(opts?.limit ?? 100, MAX_ENTRIES);
|
|
133
|
+
return entries.slice(-limit);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* No-op flush — writes are synchronous. Called for symmetry on shutdown.
|
|
137
|
+
*/
|
|
138
|
+
function flushRoutingLog() {
|
|
139
|
+
// No-op: all writes are synchronous appends
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=routing-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routing-log.js","sourceRoot":"","sources":["../src/routing-log.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"standalone-proxy.d.ts","sourceRoot":"","sources":["../src/standalone-proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAOlC,OAAO,KAAK,EAAE,QAAQ,IAAI,YAAY,EAAY,MAAM,kBAAkB,CAAC;AAE3E,KAAK,QAAQ,GAAG,YAAY,GACxB,YAAY,GACZ,UAAU,GACV,MAAM,GACN,SAAS,GACT,UAAU,GACV,WAAW,GACX,YAAY,GACZ,QAAQ,CAAC;AAOb,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"standalone-proxy.d.ts","sourceRoot":"","sources":["../src/standalone-proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAOlC,OAAO,KAAK,EAAE,QAAQ,IAAI,YAAY,EAAY,MAAM,kBAAkB,CAAC;AAE3E,KAAK,QAAQ,GAAG,YAAY,GACxB,YAAY,GACZ,UAAU,GACV,MAAM,GACN,SAAS,GACT,UAAU,GACV,WAAW,GACX,YAAY,GACZ,QAAQ,CAAC;AAOb,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAsH5C,2DAA2D;AAC3D,eAAO,MAAM,mBAAmB,gBAAuB,CAAC;AA6CxD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAiD9D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAiD/E,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAGrD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,IAAI,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAM7E,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI;IAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CA6CnH;AAsDD;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,EAAE,CAWjD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMvD;AAkBD,KAAK,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAEjD,UAAU,WAAW;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;CAC9B;AAcD,UAAU,aAAa;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,aAAa,GAAG,SAAS,GAAG,OAAO,CAAC;IAChD,cAAc,EAAE,MAAM,CAAC;CACxB;AA6JD,KAAK,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;AA6EpD;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,aAAa,GAAG,KAAK,GAAG,MAAM,CAAC;CAChD;AAuHD,0EAA0E;AAC1E,MAAM,WAAW,kBAAkB;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA4OD;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,WAAW,EAAE,OAAO,GACnB;IAAE,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CA2CjD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,EAAE,OAAO,GAAG,MAAM,CAavG;AA6JD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAe3D;AA2DD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,GAAG,UAAU,CAoDpG;AAED,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,GAAG,OAAO,CAIlG;AA2uCD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,eAAe,CAAC,EAAE,MAAM,GACvB;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA4E9C;AAmxBD;;GAEG;AACH,wBAAsB,UAAU,CAAC,MAAM,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CA83G/E"}
|
package/dist/standalone-proxy.js
CHANGED
|
@@ -84,10 +84,13 @@ const anomaly_js_1 = require("./anomaly.js");
|
|
|
84
84
|
const alerts_js_1 = require("./alerts.js");
|
|
85
85
|
const downgrade_js_1 = require("./downgrade.js");
|
|
86
86
|
const agent_tracker_js_1 = require("./agent-tracker.js");
|
|
87
|
+
const routing_log_js_1 = require("./routing-log.js");
|
|
88
|
+
const agent_policy_js_1 = require("./agent-policy.js");
|
|
87
89
|
const version_status_js_1 = require("./utils/version-status.js");
|
|
88
90
|
const signup_nudge_js_1 = require("./signup-nudge.js");
|
|
89
91
|
const star_nudge_js_1 = require("./star-nudge.js");
|
|
90
92
|
const estimate_js_1 = require("./estimate.js");
|
|
93
|
+
const telemetryPinger_js_1 = require("./telemetryPinger.js");
|
|
91
94
|
// Per-IP rate limit state for /v1/estimate (60 req/min per IP)
|
|
92
95
|
const estimateRateMap = new Map();
|
|
93
96
|
// Fix A: Purge expired rate-limit entries every 5 minutes to prevent memory leak.
|
|
@@ -1088,11 +1091,13 @@ function extractPromptText(messages) {
|
|
|
1088
1091
|
if (!messages || !Array.isArray(messages))
|
|
1089
1092
|
return '';
|
|
1090
1093
|
return messages
|
|
1094
|
+
.filter((msg) => Boolean(msg))
|
|
1091
1095
|
.map((msg) => {
|
|
1092
1096
|
if (typeof msg.content === 'string')
|
|
1093
1097
|
return msg.content;
|
|
1094
1098
|
if (Array.isArray(msg.content)) {
|
|
1095
1099
|
return msg.content
|
|
1100
|
+
.filter((c) => Boolean(c))
|
|
1096
1101
|
.map((c) => {
|
|
1097
1102
|
const part = c;
|
|
1098
1103
|
return part.type === 'text' ? (part.text ?? '') : '';
|
|
@@ -1105,12 +1110,14 @@ function extractPromptText(messages) {
|
|
|
1105
1110
|
}
|
|
1106
1111
|
function extractMessageText(messages) {
|
|
1107
1112
|
return messages
|
|
1113
|
+
.filter((msg) => Boolean(msg))
|
|
1108
1114
|
.map((msg) => {
|
|
1109
1115
|
const content = msg.content;
|
|
1110
1116
|
if (typeof content === 'string')
|
|
1111
1117
|
return content;
|
|
1112
1118
|
if (Array.isArray(content)) {
|
|
1113
1119
|
return content
|
|
1120
|
+
.filter((c) => Boolean(c))
|
|
1114
1121
|
.map((c) => {
|
|
1115
1122
|
const part = c;
|
|
1116
1123
|
return part.type === 'text' ? (part.text ?? '') : '';
|
|
@@ -2627,6 +2634,25 @@ td{padding:8px 12px;border-bottom:1px solid #111318}
|
|
|
2627
2634
|
.rename-btn{background:none;border:none;cursor:pointer;font-size:.75rem;opacity:.5;padding:2px}.rename-btn:hover{opacity:1}
|
|
2628
2635
|
</style></head><body>
|
|
2629
2636
|
<div class="header"><div><h1>⚡ RelayPlane Dashboard</h1></div><div class="meta"><a href="/dashboard/config">Config</a> · <span id="ver"></span><span id="vstat" class="vstat unavailable">Unable to check</span> · up <span id="uptime"></span> · refreshes every 5s</div></div>
|
|
2637
|
+
<div id="policy-nudge" style="display:none;background:#1a1a2e;border:1px solid #4a9eff;border-radius:6px;padding:12px 16px;margin-bottom:16px;display:flex;align-items:center;justify-content:space-between;font-family:monospace;font-size:13px;color:#e0e0e0"><span>You've routed <strong id="nudge-reqs">0</strong> requests across <strong id="nudge-agents">0</strong> detected agent<span id="nudge-plural">s</span>. Run <code>relayplane policy auto</code> to optimize routing. Estimated savings: ~<strong id="nudge-savings">$0</strong>/mo.</span><div style="display:flex;gap:8px;margin-left:16px"><button onclick="fetch('/v1/policy-auto',{method:'POST'}).then(()=>location.reload())" style="background:#4a9eff;color:#000;border:none;padding:4px 12px;border-radius:4px;cursor:pointer;font-size:12px">Run now</button><button onclick="document.getElementById('policy-nudge').style.display='none';localStorage.setItem('nudge-dismissed','1')" style="background:transparent;color:#888;border:1px solid #444;padding:4px 8px;border-radius:4px;cursor:pointer;font-size:12px">Dismiss</button></div></div>
|
|
2638
|
+
<script>
|
|
2639
|
+
(async function(){
|
|
2640
|
+
if(localStorage.getItem('nudge-dismissed'))return;
|
|
2641
|
+
try{
|
|
2642
|
+
const n=await fetch('/v1/policy-nudge').then(r=>r.json());
|
|
2643
|
+
if(n.show){
|
|
2644
|
+
const el=document.getElementById('policy-nudge');
|
|
2645
|
+
if(el){
|
|
2646
|
+
el.style.display='flex';
|
|
2647
|
+
document.getElementById('nudge-reqs').textContent=n.requestCount;
|
|
2648
|
+
document.getElementById('nudge-agents').textContent=n.agentCount;
|
|
2649
|
+
document.getElementById('nudge-plural').textContent=n.agentCount===1?'':'s';
|
|
2650
|
+
document.getElementById('nudge-savings').textContent='$'+n.estimatedMonthlySavings;
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
}catch(e){}
|
|
2654
|
+
})();
|
|
2655
|
+
</script>
|
|
2630
2656
|
<div class="cards">
|
|
2631
2657
|
<div class="card"><div class="label">Requests (7d window, max 10k)</div><div class="value" id="totalReq">—</div><div id="totalReqDetail" style="font-size:.75rem;color:#64748b;margin-top:4px">—</div></div>
|
|
2632
2658
|
<div class="card"><div class="label">Total Cost</div><div class="value" id="totalCost">—</div></div>
|
|
@@ -3202,6 +3228,19 @@ async function startProxy(config = {}) {
|
|
|
3202
3228
|
log(`Budget manager init failed: ${err}`);
|
|
3203
3229
|
}
|
|
3204
3230
|
}
|
|
3231
|
+
// Initialize budget tracker (dailyCapUSD enforcement)
|
|
3232
|
+
const budgetTracker = (0, budget_js_1.getBudgetTracker)(proxyConfig.budget?.dailyCapUSD !== undefined
|
|
3233
|
+
? { dailyCapUSD: proxyConfig.budget.dailyCapUSD, warningThreshold: proxyConfig.budget.warningThreshold }
|
|
3234
|
+
: undefined);
|
|
3235
|
+
try {
|
|
3236
|
+
budgetTracker.init();
|
|
3237
|
+
if (proxyConfig.budget?.dailyCapUSD !== undefined) {
|
|
3238
|
+
log(`Budget tracker initialized: dailyCapUSD=$${proxyConfig.budget.dailyCapUSD}`);
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
catch (err) {
|
|
3242
|
+
log(`Budget tracker init failed: ${err}`);
|
|
3243
|
+
}
|
|
3205
3244
|
// Initialize anomaly detector
|
|
3206
3245
|
const anomalyDetector = (0, anomaly_js_1.getAnomalyDetector)(proxyConfig.anomaly);
|
|
3207
3246
|
// Initialize alert manager
|
|
@@ -3269,6 +3308,16 @@ async function startProxy(config = {}) {
|
|
|
3269
3308
|
(0, downgrade_js_1.applyDowngradeHeaders)(headers, dr);
|
|
3270
3309
|
}
|
|
3271
3310
|
}
|
|
3311
|
+
// BudgetTracker: enforce dailyCapUSD
|
|
3312
|
+
const trackerResult = budgetTracker.check();
|
|
3313
|
+
if (!trackerResult.allowed) {
|
|
3314
|
+
headers['x-relayplane-budget-exceeded'] = 'daily-cap';
|
|
3315
|
+
return { blocked: true, model: finalModel, headers, downgraded: false };
|
|
3316
|
+
}
|
|
3317
|
+
if (trackerResult.warn && trackerResult.cap !== null) {
|
|
3318
|
+
headers['x-relayplane-budget-warning'] =
|
|
3319
|
+
`${((trackerResult.spent / trackerResult.cap) * 100).toFixed(1)}% of daily cap $${trackerResult.cap}`;
|
|
3320
|
+
}
|
|
3272
3321
|
return { blocked: false, model: finalModel, headers, downgraded };
|
|
3273
3322
|
}
|
|
3274
3323
|
/**
|
|
@@ -3277,6 +3326,7 @@ async function startProxy(config = {}) {
|
|
|
3277
3326
|
function postRequestRecord(model, tokensIn, tokensOut, costUsd) {
|
|
3278
3327
|
// Record spend
|
|
3279
3328
|
budgetManager.recordSpend(costUsd, model);
|
|
3329
|
+
budgetTracker.record(costUsd, model);
|
|
3280
3330
|
// Anomaly detection
|
|
3281
3331
|
const anomalyResult = anomalyDetector.recordAndAnalyze({
|
|
3282
3332
|
model,
|
|
@@ -3307,6 +3357,10 @@ async function startProxy(config = {}) {
|
|
|
3307
3357
|
proxyConfig = await loadProxyConfig(configPath, log);
|
|
3308
3358
|
cooldownManager.updateConfig(getCooldownConfig(proxyConfig));
|
|
3309
3359
|
budgetManager.updateConfig({ ...budgetManager.getConfig(), ...(proxyConfig.budget ?? {}) });
|
|
3360
|
+
budgetTracker.updateConfig({
|
|
3361
|
+
dailyCapUSD: proxyConfig.budget?.dailyCapUSD,
|
|
3362
|
+
warningThreshold: proxyConfig.budget?.warningThreshold,
|
|
3363
|
+
});
|
|
3310
3364
|
anomalyDetector.updateConfig({ ...anomalyDetector.getConfig(), ...(proxyConfig.anomaly ?? {}) });
|
|
3311
3365
|
alertManager.updateConfig({ ...alertManager.getConfig(), ...(proxyConfig.alerts ?? {}) });
|
|
3312
3366
|
downgradeConfig = { ...downgrade_js_1.DEFAULT_DOWNGRADE_CONFIG, ...(proxyConfig.downgrade ?? {}) };
|
|
@@ -3385,6 +3439,8 @@ async function startProxy(config = {}) {
|
|
|
3385
3439
|
return;
|
|
3386
3440
|
}
|
|
3387
3441
|
if (req.method === 'GET' && pathname === '/v1/version-status') {
|
|
3442
|
+
// This endpoint is hit by the dashboard, so trigger the dashboard ping.
|
|
3443
|
+
(0, telemetryPinger_js_1.sendPing)('dashboard');
|
|
3388
3444
|
const latest = await getLatestProxyVersion();
|
|
3389
3445
|
const status = (0, version_status_js_1.getVersionStatus)(PROXY_VERSION, latest);
|
|
3390
3446
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'public, max-age=60' });
|
|
@@ -4015,6 +4071,66 @@ async function startProxy(config = {}) {
|
|
|
4015
4071
|
}));
|
|
4016
4072
|
return;
|
|
4017
4073
|
}
|
|
4074
|
+
// === Policy nudge endpoint ===
|
|
4075
|
+
if (req.method === 'GET' && pathname === '/v1/policy-nudge') {
|
|
4076
|
+
const log = (0, routing_log_js_1.getRoutingLog)({ limit: 100 });
|
|
4077
|
+
const agentCount = new Set(log.map(e => e.agentFingerprint).filter(Boolean)).size;
|
|
4078
|
+
const policy = (0, agent_policy_js_1.loadPolicy)();
|
|
4079
|
+
const policyActive = Object.keys(policy.agents ?? {}).length > 0;
|
|
4080
|
+
const show = log.length >= 10 && !policyActive;
|
|
4081
|
+
const totalCost = Object.values((0, agent_tracker_js_1.getAgentRegistry)()).reduce((sum, a) => sum + a.totalCost, 0);
|
|
4082
|
+
const estimatedMonthlySavings = show ? Math.round(totalCost * 0.4 * 30) : 0;
|
|
4083
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4084
|
+
res.end(JSON.stringify({ show, requestCount: log.length, agentCount, estimatedMonthlySavings, policyActive }));
|
|
4085
|
+
return;
|
|
4086
|
+
}
|
|
4087
|
+
// === Policy auto endpoint (from dashboard "Run now" button) ===
|
|
4088
|
+
if (req.method === 'POST' && pathname === '/v1/policy-auto') {
|
|
4089
|
+
try {
|
|
4090
|
+
const { analyzeTraffic } = await import('./policy-analyzer.js');
|
|
4091
|
+
const { detectAvailableProviders, suggestPolicies } = await import('./policy-suggestions.js');
|
|
4092
|
+
const { dump: yamlDumpLocal } = await import('js-yaml');
|
|
4093
|
+
const analyses = await analyzeTraffic({ lookbackDays: 7 });
|
|
4094
|
+
if (analyses.length === 0) {
|
|
4095
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4096
|
+
res.end(JSON.stringify({ success: false, error: 'No traffic data found' }));
|
|
4097
|
+
return;
|
|
4098
|
+
}
|
|
4099
|
+
const providers = detectAvailableProviders();
|
|
4100
|
+
const suggestions = suggestPolicies(analyses, providers);
|
|
4101
|
+
const isoDate = new Date().toISOString().slice(0, 10);
|
|
4102
|
+
const header = `# RelayPlane routing policy\n# Generated by dashboard on ${isoDate}\n# Edit manually or re-run \`relayplane policy auto\` to regenerate.\n\n`;
|
|
4103
|
+
const agents = {};
|
|
4104
|
+
for (let i = 0; i < analyses.length; i++) {
|
|
4105
|
+
const a = analyses[i];
|
|
4106
|
+
const s = suggestions[i];
|
|
4107
|
+
if (s.noSuggestion)
|
|
4108
|
+
continue;
|
|
4109
|
+
const entry = { fingerprint: a.fingerprint, preferred: s.suggestedModel };
|
|
4110
|
+
if (s.escalateTo)
|
|
4111
|
+
entry['escalateTo'] = s.escalateTo;
|
|
4112
|
+
if (s.escalateOn)
|
|
4113
|
+
entry['escalateOn'] = s.escalateOn;
|
|
4114
|
+
if (s.neverDowngrade)
|
|
4115
|
+
entry['neverDowngrade'] = true;
|
|
4116
|
+
agents[a.name] = entry;
|
|
4117
|
+
}
|
|
4118
|
+
const body = yamlDumpLocal({ version: 1, agents }, { lineWidth: 120 });
|
|
4119
|
+
fs.mkdirSync(path.dirname(agent_policy_js_1.POLICY_FILE), { recursive: true });
|
|
4120
|
+
const tmp = agent_policy_js_1.POLICY_FILE + '.tmp';
|
|
4121
|
+
fs.writeFileSync(tmp, header + body, 'utf-8');
|
|
4122
|
+
fs.renameSync(tmp, agent_policy_js_1.POLICY_FILE);
|
|
4123
|
+
const agentsConfigured = Object.keys(agents).length;
|
|
4124
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4125
|
+
res.end(JSON.stringify({ success: true, agentsConfigured }));
|
|
4126
|
+
}
|
|
4127
|
+
catch (err) {
|
|
4128
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4129
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
4130
|
+
res.end(JSON.stringify({ success: false, error: msg }));
|
|
4131
|
+
}
|
|
4132
|
+
return;
|
|
4133
|
+
}
|
|
4018
4134
|
if (req.method === 'GET' && pathname === '/v1/mesh/stats') {
|
|
4019
4135
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4020
4136
|
res.end(JSON.stringify(meshHandle.getStats()));
|
|
@@ -5962,6 +6078,29 @@ async function startProxy(config = {}) {
|
|
|
5962
6078
|
console.log(` Streaming: ✅ Enabled`);
|
|
5963
6079
|
startWatchdog();
|
|
5964
6080
|
log('Health watchdog started (30s interval, sd_notify enabled)');
|
|
6081
|
+
// Fire startup ping.
|
|
6082
|
+
// Use setImmediate to ensure it runs after startup logs are printed.
|
|
6083
|
+
setImmediate(() => (0, telemetryPinger_js_1.sendPing)('startup'));
|
|
6084
|
+
// Policy nudge — check once, 5 seconds after startup, only if nudge conditions met
|
|
6085
|
+
setTimeout(async () => {
|
|
6086
|
+
try {
|
|
6087
|
+
const nudgeLog = (0, routing_log_js_1.getRoutingLog)({ limit: 100 });
|
|
6088
|
+
const nudgePolicy = (0, agent_policy_js_1.loadPolicy)();
|
|
6089
|
+
const nudgePolicyActive = Object.keys(nudgePolicy.agents ?? {}).length > 0;
|
|
6090
|
+
if (nudgeLog.length >= 10 && !nudgePolicyActive) {
|
|
6091
|
+
const agentCount = new Set(nudgeLog.map(e => e.agentFingerprint).filter(Boolean)).size;
|
|
6092
|
+
const totalCost = Object.values((0, agent_tracker_js_1.getAgentRegistry)()).reduce((s, a) => s + a.totalCost, 0);
|
|
6093
|
+
const savings = Math.round(totalCost * 0.4 * 30);
|
|
6094
|
+
console.log('');
|
|
6095
|
+
console.log(' ─────────────────────────────────────────────────────');
|
|
6096
|
+
console.log(` 💡 ${nudgeLog.length} requests routed across ${agentCount} agent${agentCount !== 1 ? 's' : ''}.`);
|
|
6097
|
+
console.log(` Run \`relayplane policy auto\` to optimize routing (~$${savings}/mo savings estimated).`);
|
|
6098
|
+
console.log(' ─────────────────────────────────────────────────────');
|
|
6099
|
+
console.log('');
|
|
6100
|
+
}
|
|
6101
|
+
}
|
|
6102
|
+
catch { /* non-critical */ }
|
|
6103
|
+
}, 5000);
|
|
5965
6104
|
resolve(server);
|
|
5966
6105
|
});
|
|
5967
6106
|
});
|