open-agents-ai 0.185.74 → 0.185.75
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/index.js +134 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -65550,6 +65550,9 @@ body {
|
|
|
65550
65550
|
<div id="agent-events" style="font-size:0.78rem;line-height:1.5"></div>
|
|
65551
65551
|
</div>
|
|
65552
65552
|
<div id="jobs-panel" style="display:none;flex:1;overflow-y:auto;padding:12px 16px">
|
|
65553
|
+
<div id="dashboard-health" style="display:flex;gap:12px;margin-bottom:16px;flex-wrap:wrap"></div>
|
|
65554
|
+
<div id="dashboard-usage" style="margin-bottom:16px"></div>
|
|
65555
|
+
<h3 style="color:#b2920a;font-size:0.7rem;margin-bottom:8px">Job History</h3>
|
|
65553
65556
|
<div id="jobs-list" style="font-size:0.78rem"></div>
|
|
65554
65557
|
</div>
|
|
65555
65558
|
|
|
@@ -65873,7 +65876,40 @@ async function abortAgentTask() {
|
|
|
65873
65876
|
try { await fetch('/v1/runs/' + currentRunId, { method: 'DELETE', headers: headers() }); } catch {}
|
|
65874
65877
|
}
|
|
65875
65878
|
|
|
65879
|
+
async function loadDashboard() {
|
|
65880
|
+
// Health card
|
|
65881
|
+
try {
|
|
65882
|
+
const r = await fetch('/health', { headers: headers() });
|
|
65883
|
+
const d = await r.json();
|
|
65884
|
+
document.getElementById('dashboard-health').innerHTML =
|
|
65885
|
+
'<div style="background:#1e1e22;border:1px solid #2a2a30;border-radius:3px;padding:8px 12px;flex:1;min-width:120px">' +
|
|
65886
|
+
'<div style="color:#555;font-size:0.6rem">STATUS</div>' +
|
|
65887
|
+
'<div style="color:#4ec94e;font-size:0.8rem">' + d.status + '</div></div>' +
|
|
65888
|
+
'<div style="background:#1e1e22;border:1px solid #2a2a30;border-radius:3px;padding:8px 12px;flex:1;min-width:120px">' +
|
|
65889
|
+
'<div style="color:#555;font-size:0.6rem">UPTIME</div>' +
|
|
65890
|
+
'<div style="color:#b2920a;font-size:0.8rem">' + Math.floor(d.uptime_s/60) + 'm</div></div>' +
|
|
65891
|
+
'<div style="background:#1e1e22;border:1px solid #2a2a30;border-radius:3px;padding:8px 12px;flex:1;min-width:120px">' +
|
|
65892
|
+
'<div style="color:#555;font-size:0.6rem">VERSION</div>' +
|
|
65893
|
+
'<div style="color:#b0b0b0;font-size:0.8rem">' + d.version + '</div></div>';
|
|
65894
|
+
} catch {}
|
|
65895
|
+
// Usage
|
|
65896
|
+
try {
|
|
65897
|
+
const r = await fetch('/v1/usage', { headers: headers() });
|
|
65898
|
+
const d = await r.json();
|
|
65899
|
+
document.getElementById('dashboard-usage').innerHTML =
|
|
65900
|
+
'<div style="display:flex;gap:12px;flex-wrap:wrap">' +
|
|
65901
|
+
'<div style="background:#1e1e22;border:1px solid #2a2a30;border-radius:3px;padding:8px 12px;flex:1">' +
|
|
65902
|
+
'<div style="color:#555;font-size:0.6rem">TOKENS IN</div>' +
|
|
65903
|
+
'<div style="color:#b2920a;font-size:0.8rem">' + (d.totalTokensIn || 0).toLocaleString() + '</div></div>' +
|
|
65904
|
+
'<div style="background:#1e1e22;border:1px solid #2a2a30;border-radius:3px;padding:8px 12px;flex:1">' +
|
|
65905
|
+
'<div style="color:#555;font-size:0.6rem">TOKENS OUT</div>' +
|
|
65906
|
+
'<div style="color:#b2920a;font-size:0.8rem">' + (d.totalTokensOut || 0).toLocaleString() + '</div></div>' +
|
|
65907
|
+
'</div>';
|
|
65908
|
+
} catch {}
|
|
65909
|
+
}
|
|
65910
|
+
|
|
65876
65911
|
async function loadJobs() {
|
|
65912
|
+
loadDashboard();
|
|
65877
65913
|
const list = document.getElementById('jobs-list');
|
|
65878
65914
|
try {
|
|
65879
65915
|
const r = await fetch('/v1/runs', { headers: headers() });
|
|
@@ -66462,6 +66498,39 @@ function listJobs() {
|
|
|
66462
66498
|
}
|
|
66463
66499
|
return jobs;
|
|
66464
66500
|
}
|
|
66501
|
+
function getKeyUsage(user) {
|
|
66502
|
+
if (!perKeyUsage.has(user)) {
|
|
66503
|
+
perKeyUsage.set(user, { requestTimestamps: [], tokensToday: 0, tokenDate: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), activeJobs: 0 });
|
|
66504
|
+
}
|
|
66505
|
+
const u = perKeyUsage.get(user);
|
|
66506
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
66507
|
+
if (u.tokenDate !== today) {
|
|
66508
|
+
u.tokensToday = 0;
|
|
66509
|
+
u.tokenDate = today;
|
|
66510
|
+
}
|
|
66511
|
+
return u;
|
|
66512
|
+
}
|
|
66513
|
+
function checkKeyRateLimit(auth) {
|
|
66514
|
+
if (!auth.user || !auth.rpm)
|
|
66515
|
+
return null;
|
|
66516
|
+
const usage = getKeyUsage(auth.user);
|
|
66517
|
+
const now = Date.now();
|
|
66518
|
+
usage.requestTimestamps = usage.requestTimestamps.filter((t) => now - t < 6e4);
|
|
66519
|
+
if (auth.rpm && usage.requestTimestamps.length >= auth.rpm) {
|
|
66520
|
+
return `Rate limit exceeded for ${auth.user}: ${auth.rpm} RPM`;
|
|
66521
|
+
}
|
|
66522
|
+
if (auth.tpd && usage.tokensToday >= auth.tpd) {
|
|
66523
|
+
return `Daily token limit exceeded for ${auth.user}: ${usage.tokensToday}/${auth.tpd}`;
|
|
66524
|
+
}
|
|
66525
|
+
return null;
|
|
66526
|
+
}
|
|
66527
|
+
function recordKeyUsage(user, tokens) {
|
|
66528
|
+
if (!user)
|
|
66529
|
+
return;
|
|
66530
|
+
const usage = getKeyUsage(user);
|
|
66531
|
+
usage.requestTimestamps.push(Date.now());
|
|
66532
|
+
usage.tokensToday += tokens;
|
|
66533
|
+
}
|
|
66465
66534
|
function resolveAuth(req) {
|
|
66466
66535
|
const authHeader = req.headers["authorization"];
|
|
66467
66536
|
const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
|
|
@@ -66470,9 +66539,17 @@ function resolveAuth(req) {
|
|
|
66470
66539
|
if (!token)
|
|
66471
66540
|
return { authenticated: false, scope: "read" };
|
|
66472
66541
|
for (const entry of multiKeys.split(",")) {
|
|
66473
|
-
const
|
|
66542
|
+
const parts = entry.trim().split(":");
|
|
66543
|
+
const [key, scope, user, rpmStr, tpdStr, maxJobsStr] = parts;
|
|
66474
66544
|
if (key === token) {
|
|
66475
|
-
return {
|
|
66545
|
+
return {
|
|
66546
|
+
authenticated: true,
|
|
66547
|
+
scope: scope || "admin",
|
|
66548
|
+
user: user || void 0,
|
|
66549
|
+
rpm: rpmStr ? parseInt(rpmStr, 10) : void 0,
|
|
66550
|
+
tpd: tpdStr ? parseInt(tpdStr, 10) : void 0,
|
|
66551
|
+
maxJobs: maxJobsStr ? parseInt(maxJobsStr, 10) : void 0
|
|
66552
|
+
};
|
|
66476
66553
|
}
|
|
66477
66554
|
}
|
|
66478
66555
|
return { authenticated: false, scope: "read" };
|
|
@@ -66982,9 +67059,17 @@ async function handleV1Run(req, res) {
|
|
|
66982
67059
|
jsonResponse(res, 202, { run_id: id, status: "running", pid: job.pid });
|
|
66983
67060
|
}
|
|
66984
67061
|
}
|
|
66985
|
-
function handleV1Runs(res) {
|
|
66986
|
-
|
|
66987
|
-
|
|
67062
|
+
function handleV1Runs(res, url) {
|
|
67063
|
+
let jobs = listJobs();
|
|
67064
|
+
const statusFilter = url?.searchParams.get("status");
|
|
67065
|
+
if (statusFilter)
|
|
67066
|
+
jobs = jobs.filter((j) => j.status === statusFilter);
|
|
67067
|
+
jobs.sort((a, b) => new Date(b.startedAt ?? 0).getTime() - new Date(a.startedAt ?? 0).getTime());
|
|
67068
|
+
const limit = parseInt(url?.searchParams.get("limit") ?? "50", 10);
|
|
67069
|
+
const offset = parseInt(url?.searchParams.get("offset") ?? "0", 10);
|
|
67070
|
+
const total = jobs.length;
|
|
67071
|
+
jobs = jobs.slice(offset, offset + limit);
|
|
67072
|
+
jsonResponse(res, 200, { runs: jobs, total, limit, offset });
|
|
66988
67073
|
}
|
|
66989
67074
|
function handleV1RunsById(res, id) {
|
|
66990
67075
|
const job = loadJob(id);
|
|
@@ -67208,6 +67293,23 @@ async function handleRequest(req, res, ollamaUrl, verbose) {
|
|
|
67208
67293
|
status = 401;
|
|
67209
67294
|
return;
|
|
67210
67295
|
}
|
|
67296
|
+
const auth = resolveAuth(req);
|
|
67297
|
+
const rateLimitMsg = checkKeyRateLimit(auth);
|
|
67298
|
+
if (rateLimitMsg) {
|
|
67299
|
+
status = 429;
|
|
67300
|
+
jsonResponse(res, 429, { error: "Too Many Requests", message: rateLimitMsg });
|
|
67301
|
+
return;
|
|
67302
|
+
}
|
|
67303
|
+
if (auth.user)
|
|
67304
|
+
recordKeyUsage(auth.user, 0);
|
|
67305
|
+
}
|
|
67306
|
+
if ((method === "POST" || method === "PUT" || method === "PATCH") && req.headers["content-length"]) {
|
|
67307
|
+
const bodySize = parseInt(req.headers["content-length"], 10);
|
|
67308
|
+
if (bodySize > 1048576) {
|
|
67309
|
+
status = 413;
|
|
67310
|
+
jsonResponse(res, 413, { error: "Payload Too Large", message: "Request body exceeds 1MB limit" });
|
|
67311
|
+
return;
|
|
67312
|
+
}
|
|
67211
67313
|
}
|
|
67212
67314
|
if (pathname === "/v1/models" && method === "GET") {
|
|
67213
67315
|
await handleV1Models(res, ollamaUrl);
|
|
@@ -67226,7 +67328,7 @@ async function handleRequest(req, res, ollamaUrl, verbose) {
|
|
|
67226
67328
|
return;
|
|
67227
67329
|
}
|
|
67228
67330
|
if (pathname === "/v1/runs" && method === "GET") {
|
|
67229
|
-
handleV1Runs(res);
|
|
67331
|
+
handleV1Runs(res, urlObj);
|
|
67230
67332
|
return;
|
|
67231
67333
|
}
|
|
67232
67334
|
const runsMatch = pathname.match(/^\/v1\/runs\/([a-zA-Z0-9_-]+)$/);
|
|
@@ -67425,6 +67527,30 @@ function startApiServer(options = {}) {
|
|
|
67425
67527
|
const ollamaUrl = options.ollamaUrl ?? config.backendUrl;
|
|
67426
67528
|
const cwd4 = process.cwd();
|
|
67427
67529
|
initAuditLog(join71(cwd4, ".oa"));
|
|
67530
|
+
const retentionDays = parseInt(process.env["OA_JOB_RETENTION_DAYS"] ?? "30", 10);
|
|
67531
|
+
if (retentionDays > 0) {
|
|
67532
|
+
try {
|
|
67533
|
+
const jobsDir3 = join71(cwd4, ".oa", "jobs");
|
|
67534
|
+
if (existsSync54(jobsDir3)) {
|
|
67535
|
+
const cutoff = Date.now() - retentionDays * 864e5;
|
|
67536
|
+
for (const f of readdirSync21(jobsDir3)) {
|
|
67537
|
+
if (!f.endsWith(".json"))
|
|
67538
|
+
continue;
|
|
67539
|
+
try {
|
|
67540
|
+
const jobPath = join71(jobsDir3, f);
|
|
67541
|
+
const job = JSON.parse(readFileSync43(jobPath, "utf-8"));
|
|
67542
|
+
const jobTime = new Date(job.startedAt ?? job.completedAt ?? 0).getTime();
|
|
67543
|
+
if (jobTime > 0 && jobTime < cutoff && job.status !== "running") {
|
|
67544
|
+
const { unlinkSync: unlinkSync13 } = __require("node:fs");
|
|
67545
|
+
unlinkSync13(jobPath);
|
|
67546
|
+
}
|
|
67547
|
+
} catch {
|
|
67548
|
+
}
|
|
67549
|
+
}
|
|
67550
|
+
}
|
|
67551
|
+
} catch {
|
|
67552
|
+
}
|
|
67553
|
+
}
|
|
67428
67554
|
const tlsCert = process.env["OA_TLS_CERT"];
|
|
67429
67555
|
const tlsKey = process.env["OA_TLS_KEY"];
|
|
67430
67556
|
const useTls = !!(tlsCert && tlsKey);
|
|
@@ -67573,7 +67699,7 @@ async function apiServeCommand(opts, config) {
|
|
|
67573
67699
|
server.on("close", resolve36);
|
|
67574
67700
|
});
|
|
67575
67701
|
}
|
|
67576
|
-
var endpointRegistry, modelRouteMap, endpointUsage, metrics, startedAt, _corsOrigins, _corsLocalOnly, runningProcesses;
|
|
67702
|
+
var endpointRegistry, modelRouteMap, endpointUsage, metrics, startedAt, _corsOrigins, _corsLocalOnly, runningProcesses, perKeyUsage;
|
|
67577
67703
|
var init_serve = __esm({
|
|
67578
67704
|
"packages/cli/dist/api/serve.js"() {
|
|
67579
67705
|
"use strict";
|
|
@@ -67597,6 +67723,7 @@ var init_serve = __esm({
|
|
|
67597
67723
|
_corsOrigins = (process.env["OA_CORS_ORIGINS"] || "").split(",").filter(Boolean);
|
|
67598
67724
|
_corsLocalOnly = _corsOrigins.length === 0;
|
|
67599
67725
|
runningProcesses = /* @__PURE__ */ new Map();
|
|
67726
|
+
perKeyUsage = /* @__PURE__ */ new Map();
|
|
67600
67727
|
}
|
|
67601
67728
|
});
|
|
67602
67729
|
|
package/package.json
CHANGED