open-agents-ai 0.103.15 → 0.103.16

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 +163 -21
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -26223,6 +26223,13 @@ function cleanForwardHeaders(raw, targetHost) {
26223
26223
  out.host = targetHost;
26224
26224
  return out;
26225
26225
  }
26226
+ function fmtTokens(n) {
26227
+ if (n < 1e3)
26228
+ return String(n);
26229
+ if (n < 1e6)
26230
+ return `${(n / 1e3).toFixed(1)}K`;
26231
+ return `${(n / 1e6).toFixed(1)}M`;
26232
+ }
26226
26233
  function readExposeState(stateDir) {
26227
26234
  try {
26228
26235
  const path = join35(stateDir, STATE_FILE_NAME);
@@ -26347,7 +26354,11 @@ var init_expose = __esm({
26347
26354
  totalRequests: 0,
26348
26355
  activeConnections: 0,
26349
26356
  errors: 0,
26350
- modelUsage: /* @__PURE__ */ new Map()
26357
+ totalTokensIn: 0,
26358
+ totalTokensOut: 0,
26359
+ startedAt: Date.now(),
26360
+ modelUsage: /* @__PURE__ */ new Map(),
26361
+ users: /* @__PURE__ */ new Map()
26351
26362
  };
26352
26363
  get tunnelUrl() {
26353
26364
  return this._tunnelUrl;
@@ -26509,7 +26520,20 @@ var init_expose = __esm({
26509
26520
  totalRequests: this._stats.totalRequests,
26510
26521
  activeConnections: this._stats.activeConnections,
26511
26522
  errors: this._stats.errors,
26512
- modelUsage: Object.fromEntries(this._stats.modelUsage)
26523
+ totalTokensIn: this._stats.totalTokensIn,
26524
+ totalTokensOut: this._stats.totalTokensOut,
26525
+ uptimeSeconds: Math.floor((Date.now() - this._stats.startedAt) / 1e3),
26526
+ modelUsage: Object.fromEntries(this._stats.modelUsage),
26527
+ users: Array.from(this._stats.users.values()).map((u) => ({
26528
+ ip: u.ip,
26529
+ requests: u.requests,
26530
+ tokensIn: u.tokensIn,
26531
+ tokensOut: u.tokensOut,
26532
+ activeRequests: u.activeRequests,
26533
+ firstSeen: new Date(u.firstSeen).toISOString(),
26534
+ lastSeen: new Date(u.lastSeen).toISOString(),
26535
+ models: Object.fromEntries(Array.from(u.models.entries()).map(([m, v]) => [m, { requests: v.requests, tokensIn: v.tokensIn, tokensOut: v.tokensOut }]))
26536
+ }))
26513
26537
  };
26514
26538
  res.writeHead(200, { "Content-Type": "application/json" });
26515
26539
  res.end(JSON.stringify({ ...metrics, gateway: gatewayStats }));
@@ -26519,8 +26543,26 @@ var init_expose = __esm({
26519
26543
  });
26520
26544
  return;
26521
26545
  }
26546
+ const userIp = req.headers["cf-connecting-ip"] ?? req.headers["x-forwarded-for"]?.split(",")[0]?.trim() ?? req.socket.remoteAddress ?? "unknown";
26547
+ let user = this._stats.users.get(userIp);
26548
+ if (!user) {
26549
+ user = {
26550
+ ip: userIp,
26551
+ firstSeen: Date.now(),
26552
+ lastSeen: Date.now(),
26553
+ requests: 0,
26554
+ tokensIn: 0,
26555
+ tokensOut: 0,
26556
+ activeRequests: 0,
26557
+ models: /* @__PURE__ */ new Map()
26558
+ };
26559
+ this._stats.users.set(userIp, user);
26560
+ }
26522
26561
  this._stats.totalRequests++;
26523
26562
  this._stats.activeConnections++;
26563
+ user.requests++;
26564
+ user.activeRequests++;
26565
+ user.lastSeen = Date.now();
26524
26566
  this.emitStats();
26525
26567
  url.searchParams.delete("key");
26526
26568
  const forwardPath = url.pathname + url.search;
@@ -26529,14 +26571,22 @@ var init_expose = __esm({
26529
26571
  req.on("end", () => {
26530
26572
  const body = Buffer.concat(bodyChunks);
26531
26573
  let isStreaming = false;
26574
+ let requestModel = "";
26532
26575
  if (req.method === "POST" && body.length > 0) {
26533
26576
  try {
26534
26577
  const text = body.toString("utf8", 0, Math.min(body.length, 1024));
26535
26578
  const modelMatch = text.match(/"model"\s*:\s*"([^"]+)"/);
26536
26579
  if (modelMatch) {
26537
- const model = modelMatch[1];
26538
- const count = this._stats.modelUsage.get(model) ?? 0;
26539
- this._stats.modelUsage.set(model, count + 1);
26580
+ requestModel = modelMatch[1];
26581
+ const count = this._stats.modelUsage.get(requestModel) ?? 0;
26582
+ this._stats.modelUsage.set(requestModel, count + 1);
26583
+ let mm = user.models.get(requestModel);
26584
+ if (!mm) {
26585
+ mm = { requests: 0, tokensIn: 0, tokensOut: 0, lastUsed: Date.now() };
26586
+ user.models.set(requestModel, mm);
26587
+ }
26588
+ mm.requests++;
26589
+ mm.lastUsed = Date.now();
26540
26590
  }
26541
26591
  isStreaming = /"stream"\s*:\s*true/.test(text);
26542
26592
  } catch {
@@ -26567,6 +26617,50 @@ var init_expose = __esm({
26567
26617
  heartbeatTimer = null;
26568
26618
  }
26569
26619
  };
26620
+ let responseTail = "";
26621
+ const finalizeRequest = () => {
26622
+ user.activeRequests = Math.max(0, user.activeRequests - 1);
26623
+ this._stats.activeConnections = Math.max(0, this._stats.activeConnections - 1);
26624
+ try {
26625
+ const promptEval = responseTail.match(/"prompt_eval_count"\s*:\s*(\d+)/);
26626
+ const evalCount = responseTail.match(/"eval_count"\s*:\s*(\d+)/);
26627
+ if (promptEval || evalCount) {
26628
+ const tIn = parseInt(promptEval?.[1] ?? "0", 10);
26629
+ const tOut = parseInt(evalCount?.[1] ?? "0", 10);
26630
+ this._stats.totalTokensIn += tIn;
26631
+ this._stats.totalTokensOut += tOut;
26632
+ user.tokensIn += tIn;
26633
+ user.tokensOut += tOut;
26634
+ if (requestModel) {
26635
+ const mm = user.models.get(requestModel);
26636
+ if (mm) {
26637
+ mm.tokensIn += tIn;
26638
+ mm.tokensOut += tOut;
26639
+ }
26640
+ }
26641
+ } else {
26642
+ const promptTokens = responseTail.match(/"prompt_tokens"\s*:\s*(\d+)/);
26643
+ const completionTokens = responseTail.match(/"completion_tokens"\s*:\s*(\d+)/);
26644
+ if (promptTokens || completionTokens) {
26645
+ const tIn = parseInt(promptTokens?.[1] ?? "0", 10);
26646
+ const tOut = parseInt(completionTokens?.[1] ?? "0", 10);
26647
+ this._stats.totalTokensIn += tIn;
26648
+ this._stats.totalTokensOut += tOut;
26649
+ user.tokensIn += tIn;
26650
+ user.tokensOut += tOut;
26651
+ if (requestModel) {
26652
+ const mm = user.models.get(requestModel);
26653
+ if (mm) {
26654
+ mm.tokensIn += tIn;
26655
+ mm.tokensOut += tOut;
26656
+ }
26657
+ }
26658
+ }
26659
+ }
26660
+ } catch {
26661
+ }
26662
+ this.emitStats();
26663
+ };
26570
26664
  const proxyReq = httpRequest({
26571
26665
  hostname: target.hostname,
26572
26666
  port: target.port || (target.protocol === "https:" ? 443 : 80),
@@ -26576,8 +26670,14 @@ var init_expose = __esm({
26576
26670
  }, (proxyRes) => {
26577
26671
  cleanupHeartbeat();
26578
26672
  proxyRes.on("error", () => {
26579
- this._stats.activeConnections = Math.max(0, this._stats.activeConnections - 1);
26580
- this.emitStats();
26673
+ finalizeRequest();
26674
+ });
26675
+ proxyRes.on("data", (chunk) => {
26676
+ const text = chunk.toString();
26677
+ responseTail += text;
26678
+ if (responseTail.length > 2048) {
26679
+ responseTail = responseTail.slice(-2048);
26680
+ }
26581
26681
  });
26582
26682
  if (isStreaming) {
26583
26683
  if (proxyRes.statusCode !== 200) {
@@ -26597,8 +26697,7 @@ var init_expose = __esm({
26597
26697
  } catch {
26598
26698
  }
26599
26699
  this._stats.errors++;
26600
- this._stats.activeConnections = Math.max(0, this._stats.activeConnections - 1);
26601
- this.emitStats();
26700
+ finalizeRequest();
26602
26701
  });
26603
26702
  return;
26604
26703
  }
@@ -26608,14 +26707,12 @@ var init_expose = __esm({
26608
26707
  proxyRes.pipe(res);
26609
26708
  }
26610
26709
  proxyRes.on("end", () => {
26611
- this._stats.activeConnections = Math.max(0, this._stats.activeConnections - 1);
26612
- this.emitStats();
26710
+ finalizeRequest();
26613
26711
  });
26614
26712
  });
26615
26713
  proxyReq.on("error", (err) => {
26616
26714
  cleanupHeartbeat();
26617
26715
  this._stats.errors++;
26618
- this._stats.activeConnections = Math.max(0, this._stats.activeConnections - 1);
26619
26716
  if (!res.headersSent) {
26620
26717
  res.writeHead(502, { "Content-Type": "application/json" });
26621
26718
  res.end(JSON.stringify({ error: `Backend unreachable: ${err.message}` }));
@@ -26631,7 +26728,7 @@ var init_expose = __esm({
26631
26728
  } catch {
26632
26729
  }
26633
26730
  }
26634
- this.emitStats();
26731
+ finalizeRequest();
26635
26732
  });
26636
26733
  if (body.length > 0) {
26637
26734
  proxyReq.write(body);
@@ -26639,6 +26736,7 @@ var init_expose = __esm({
26639
26736
  proxyReq.end();
26640
26737
  });
26641
26738
  req.on("error", () => {
26739
+ user.activeRequests = Math.max(0, user.activeRequests - 1);
26642
26740
  this._stats.activeConnections = Math.max(0, this._stats.activeConnections - 1);
26643
26741
  this.emitStats();
26644
26742
  });
@@ -26669,8 +26767,12 @@ var init_expose = __esm({
26669
26767
  });
26670
26768
  this._cloudflaredPid = this.cloudflaredProcess.pid ?? null;
26671
26769
  let urlFound = false;
26770
+ let stderrOutput = "";
26672
26771
  const handleOutput = (data) => {
26673
26772
  const text = data.toString();
26773
+ stderrOutput += text;
26774
+ if (stderrOutput.length > 4096)
26775
+ stderrOutput = stderrOutput.slice(-4096);
26674
26776
  const urlMatch = text.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/);
26675
26777
  if (urlMatch && !urlFound) {
26676
26778
  urlFound = true;
@@ -26690,7 +26792,8 @@ var init_expose = __esm({
26690
26792
  this.cloudflaredProcess.on("close", (code) => {
26691
26793
  if (!urlFound) {
26692
26794
  clearTimeout(timeout);
26693
- reject(new Error(`Cloudflared exited with code ${code} before providing URL`));
26795
+ const hint = stderrOutput.trim().split("\n").slice(-3).join(" | ");
26796
+ reject(new Error(`Cloudflared exited with code ${code}. ${hint ? "Output: " + hint : ""}`));
26694
26797
  }
26695
26798
  if (this._stats.status === "active") {
26696
26799
  this.options.onInfo?.("Cloudflared tunnel died \u2014 restarting...");
@@ -26769,7 +26872,11 @@ ${this.formatConnectionInfo()}`);
26769
26872
  });
26770
26873
  }
26771
26874
  emitStats() {
26772
- this.emit("stats", { ...this._stats, modelUsage: new Map(this._stats.modelUsage) });
26875
+ this.emit("stats", {
26876
+ ...this._stats,
26877
+ modelUsage: new Map(this._stats.modelUsage),
26878
+ users: new Map(this._stats.users)
26879
+ });
26773
26880
  }
26774
26881
  /** Format connection info for display */
26775
26882
  formatConnectionInfo() {
@@ -26795,15 +26902,50 @@ ${this.formatConnectionInfo()}`);
26795
26902
  formatStats() {
26796
26903
  const s = this._stats;
26797
26904
  const lines = [];
26905
+ const uptimeSec = Math.floor((Date.now() - s.startedAt) / 1e3);
26906
+ const uptimeStr = uptimeSec < 60 ? `${uptimeSec}s` : uptimeSec < 3600 ? `${Math.floor(uptimeSec / 60)}m ${uptimeSec % 60}s` : `${Math.floor(uptimeSec / 3600)}h ${Math.floor(uptimeSec % 3600 / 60)}m`;
26798
26907
  lines.push(` ${c2.bold("Expose Gateway Stats")}`);
26799
- lines.push(` ${c2.cyan("Status".padEnd(16))} ${s.status === "active" ? c2.green("active") : s.status === "error" ? c2.red("error") : c2.yellow("standby")}`);
26800
- lines.push(` ${c2.cyan("Total requests".padEnd(16))} ${s.totalRequests}`);
26801
- lines.push(` ${c2.cyan("Active conns".padEnd(16))} ${s.activeConnections}`);
26802
- lines.push(` ${c2.cyan("Errors".padEnd(16))} ${s.errors}`);
26908
+ lines.push(` ${c2.cyan("Status".padEnd(18))} ${s.status === "active" ? c2.green("active") : s.status === "error" ? c2.red("error") : c2.yellow("standby")}`);
26909
+ lines.push(` ${c2.cyan("Uptime".padEnd(18))} ${uptimeStr}`);
26910
+ lines.push(` ${c2.cyan("Total requests".padEnd(18))} ${s.totalRequests}`);
26911
+ lines.push(` ${c2.cyan("Active conns".padEnd(18))} ${s.activeConnections}`);
26912
+ lines.push(` ${c2.cyan("Errors".padEnd(18))} ${s.errors}`);
26913
+ lines.push(` ${c2.cyan("Tokens in".padEnd(18))} ${fmtTokens(s.totalTokensIn)}`);
26914
+ lines.push(` ${c2.cyan("Tokens out".padEnd(18))} ${fmtTokens(s.totalTokensOut)}`);
26803
26915
  if (s.modelUsage.size > 0) {
26804
- lines.push(` ${c2.cyan("Models used:")}`);
26916
+ lines.push("");
26917
+ lines.push(` ${c2.bold("Models")}`);
26805
26918
  for (const [model, count] of s.modelUsage) {
26806
- lines.push(` ${model}: ${count} requests`);
26919
+ let mIn = 0, mOut = 0;
26920
+ for (const user of s.users.values()) {
26921
+ const mm = user.models.get(model);
26922
+ if (mm) {
26923
+ mIn += mm.tokensIn;
26924
+ mOut += mm.tokensOut;
26925
+ }
26926
+ }
26927
+ lines.push(` ${c2.cyan(model.padEnd(30))} ${count} reqs ${c2.dim(`in:${fmtTokens(mIn)} out:${fmtTokens(mOut)}`)}`);
26928
+ }
26929
+ }
26930
+ if (s.users.size > 0) {
26931
+ lines.push("");
26932
+ lines.push(` ${c2.bold("Active Users")} (${s.users.size})`);
26933
+ for (const user of s.users.values()) {
26934
+ const ageSec = Math.floor((Date.now() - user.firstSeen) / 1e3);
26935
+ const ageStr = ageSec < 60 ? `${ageSec}s` : ageSec < 3600 ? `${Math.floor(ageSec / 60)}m` : `${Math.floor(ageSec / 3600)}h ${Math.floor(ageSec % 3600 / 60)}m`;
26936
+ const lastSec = Math.floor((Date.now() - user.lastSeen) / 1e3);
26937
+ const lastStr = lastSec < 5 ? "now" : lastSec < 60 ? `${lastSec}s ago` : `${Math.floor(lastSec / 60)}m ago`;
26938
+ const activeTag = user.activeRequests > 0 ? c2.green(` [${user.activeRequests} active]`) : "";
26939
+ lines.push(` ${c2.bold(user.ip)}${activeTag}`);
26940
+ lines.push(` ${c2.dim("Session:")} ${ageStr} ${c2.dim("Last seen:")} ${lastStr} ${c2.dim("Requests:")} ${user.requests}`);
26941
+ lines.push(` ${c2.dim("Tokens:")} in:${fmtTokens(user.tokensIn)} out:${fmtTokens(user.tokensOut)} ${c2.dim("Total:")} ${fmtTokens(user.tokensIn + user.tokensOut)}`);
26942
+ if (user.models.size > 0) {
26943
+ const modelParts = [];
26944
+ for (const [m, mm] of user.models) {
26945
+ modelParts.push(`${m} (${mm.requests} reqs, ${fmtTokens(mm.tokensIn + mm.tokensOut)} tokens)`);
26946
+ }
26947
+ lines.push(` ${c2.dim("Models:")} ${modelParts.join(", ")}`);
26948
+ }
26807
26949
  }
26808
26950
  }
26809
26951
  return lines.join("\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.103.15",
3
+ "version": "0.103.16",
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",