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.
Files changed (2) hide show
  1. package/dist/index.js +134 -7
  2. 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 [key, scope, user] = entry.trim().split(":");
66542
+ const parts = entry.trim().split(":");
66543
+ const [key, scope, user, rpmStr, tpdStr, maxJobsStr] = parts;
66474
66544
  if (key === token) {
66475
- return { authenticated: true, scope: scope || "admin", user: user || void 0 };
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
- const jobs = listJobs();
66987
- jsonResponse(res, 200, { runs: jobs });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.185.74",
3
+ "version": "0.185.75",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",