open-agents-ai 0.184.56 → 0.184.58

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 +132 -22
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -62035,6 +62035,38 @@ function getVersion3() {
62035
62035
  }
62036
62036
  return "0.0.0";
62037
62037
  }
62038
+ function getEndpointUsage(label) {
62039
+ let u = endpointUsage.get(label);
62040
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
62041
+ if (!u) {
62042
+ u = { requestTimestamps: [], tokensToday: 0, tokenDate: today };
62043
+ endpointUsage.set(label, u);
62044
+ }
62045
+ if (u.tokenDate !== today) {
62046
+ u.tokensToday = 0;
62047
+ u.tokenDate = today;
62048
+ }
62049
+ return u;
62050
+ }
62051
+ function checkEndpointRateLimit(ep, estimatedTokens) {
62052
+ const rpm = ep.limits?.maxRequestsPerMinute ?? 60;
62053
+ const tpd = ep.limits?.maxTokensPerDay ?? 1e5;
62054
+ const usage = getEndpointUsage(ep.label);
62055
+ const now = Date.now();
62056
+ usage.requestTimestamps = usage.requestTimestamps.filter((t) => now - t < 6e4);
62057
+ if (usage.requestTimestamps.length >= rpm) {
62058
+ return `Rate limit exceeded for ${ep.label}: ${rpm} requests/minute. Try again in ${Math.ceil((usage.requestTimestamps[0] + 6e4 - now) / 1e3)}s.`;
62059
+ }
62060
+ if (estimatedTokens && usage.tokensToday + estimatedTokens > tpd) {
62061
+ return `Daily token limit exceeded for ${ep.label}: ${usage.tokensToday}/${tpd} tokens today.`;
62062
+ }
62063
+ return null;
62064
+ }
62065
+ function recordEndpointUsage(label, tokens) {
62066
+ const usage = getEndpointUsage(label);
62067
+ usage.requestTimestamps.push(Date.now());
62068
+ usage.tokensToday += tokens;
62069
+ }
62038
62070
  async function refreshEndpointRegistry() {
62039
62071
  endpointRegistry.length = 0;
62040
62072
  modelRouteMap.clear();
@@ -62068,6 +62100,10 @@ async function refreshEndpointRegistry() {
62068
62100
  label,
62069
62101
  url: sp.tunnelUrl,
62070
62102
  type: "vllm",
62103
+ limits: {
62104
+ maxRequestsPerMinute: sp.limits?.maxRequestsPerMinute ?? 60,
62105
+ maxTokensPerDay: sp.limits?.maxTokensPerDay ?? 1e5
62106
+ },
62071
62107
  authKey: sp.authKey
62072
62108
  });
62073
62109
  }
@@ -62415,6 +62451,13 @@ async function handleV1ChatCompletions(req, res, ollamaUrl) {
62415
62451
  const targetUrl = route?.endpoint.url ?? ollamaUrl;
62416
62452
  const targetType = route?.endpoint.type ?? loadConfig().backendType ?? "ollama";
62417
62453
  const originalModel = route?.originalId ?? model;
62454
+ if (route?.endpoint) {
62455
+ const limitErr = checkEndpointRateLimit(route.endpoint);
62456
+ if (limitErr) {
62457
+ jsonResponse(res, 429, { error: limitErr });
62458
+ return;
62459
+ }
62460
+ }
62418
62461
  const routedBody = { ...requestBody, model: originalModel };
62419
62462
  if (targetType === "vllm" || targetType === "openai") {
62420
62463
  const payload = JSON.stringify(routedBody);
@@ -62449,8 +62492,11 @@ async function handleV1ChatCompletions(req, res, ollamaUrl) {
62449
62492
  }
62450
62493
  const parsed = JSON.parse(result.body);
62451
62494
  if (parsed.usage) {
62495
+ const totalTok = (parsed.usage.prompt_tokens ?? 0) + (parsed.usage.completion_tokens ?? 0);
62452
62496
  metrics.totalTokensIn += parsed.usage.prompt_tokens ?? 0;
62453
62497
  metrics.totalTokensOut += parsed.usage.completion_tokens ?? 0;
62498
+ if (route?.endpoint)
62499
+ recordEndpointUsage(route.endpoint.label, totalTok);
62454
62500
  }
62455
62501
  jsonResponse(res, 200, parsed);
62456
62502
  } catch (err) {
@@ -63104,6 +63150,19 @@ async function handleRequest(req, res, ollamaUrl, verbose) {
63104
63150
  await handlePutConfigEndpoint(req, res);
63105
63151
  return;
63106
63152
  }
63153
+ if (pathname === "/v1/usage" && method === "GET") {
63154
+ const usageData = {};
63155
+ for (const ep of endpointRegistry) {
63156
+ const u = getEndpointUsage(ep.label);
63157
+ usageData[ep.label] = {
63158
+ requestsLastMinute: u.requestTimestamps.filter((t) => Date.now() - t < 6e4).length,
63159
+ tokensToday: u.tokensToday,
63160
+ limits: ep.limits ?? { maxRequestsPerMinute: "unlimited", maxTokensPerDay: "unlimited" }
63161
+ };
63162
+ }
63163
+ jsonResponse(res, 200, { usage: usageData, totalTokensIn: metrics.totalTokensIn, totalTokensOut: metrics.totalTokensOut });
63164
+ return;
63165
+ }
63107
63166
  if (pathname === "/v1/commands" && method === "GET") {
63108
63167
  handleGetCommands(res);
63109
63168
  return;
@@ -63229,43 +63288,93 @@ function startApiServer(options = {}) {
63229
63288
  }
63230
63289
  });
63231
63290
  });
63291
+ let retried = false;
63232
63292
  server.on("error", (err) => {
63233
- if (err.code === "EADDRINUSE") {
63234
- process.stderr.write(` Port ${port} already in use \u2014 API server not started.
63293
+ if (err.code === "EADDRINUSE" && !retried) {
63294
+ retried = true;
63295
+ process.stderr.write(` Port ${port} in use \u2014 reclaiming...
63296
+ `);
63297
+ try {
63298
+ const { execSync: es } = __require("node:child_process");
63299
+ const pids = es(`lsof -ti :${port} 2>/dev/null || fuser ${port}/tcp 2>/dev/null || true`, { encoding: "utf8" }).trim().split(/[\n\s]+/).filter(Boolean);
63300
+ for (const pid of pids) {
63301
+ const numPid = parseInt(pid, 10);
63302
+ if (numPid && numPid !== process.pid) {
63303
+ try {
63304
+ process.kill(numPid, "SIGTERM");
63305
+ } catch {
63306
+ }
63307
+ setTimeout(() => {
63308
+ try {
63309
+ process.kill(numPid, "SIGKILL");
63310
+ } catch {
63311
+ }
63312
+ }, 500);
63313
+ }
63314
+ }
63315
+ } catch {
63316
+ }
63317
+ setTimeout(() => {
63318
+ server.listen(port, host);
63319
+ }, 2e3);
63320
+ } else if (err.code === "EADDRINUSE" && retried) {
63321
+ process.stderr.write(` Port ${port} still in use after reclaim attempt \u2014 API server not started.
63235
63322
  `);
63236
63323
  } else {
63237
63324
  process.stderr.write(` API server error: ${err.message}
63238
63325
  `);
63239
63326
  }
63240
63327
  });
63241
- refreshEndpointRegistry().catch(() => {
63242
- }).then(() => {
63243
- server.listen(port, host, () => {
63244
- const version = getVersion3();
63245
- process.stderr.write(`
63328
+ endpointRegistry.push({
63329
+ label: (() => {
63330
+ const u = config.backendUrl;
63331
+ if (u.includes("127.0.0.1") || u.includes("localhost"))
63332
+ return "local";
63333
+ try {
63334
+ return new URL(u).hostname.split(".")[0];
63335
+ } catch {
63336
+ return "primary";
63337
+ }
63338
+ })(),
63339
+ url: config.backendUrl,
63340
+ type: config.backendType || "ollama",
63341
+ authKey: config.apiKey
63342
+ });
63343
+ server.listen(port, host, () => {
63344
+ const version = getVersion3();
63345
+ process.stderr.write(`
63246
63346
  open-agents API server v${version}
63247
63347
  `);
63248
- process.stderr.write(` Listening on http://${host}:${port}
63348
+ process.stderr.write(` Listening on http://${host}:${port}
63249
63349
  `);
63250
- process.stderr.write(` Endpoints: ${endpointRegistry.length} registered
63350
+ process.stderr.write(` Primary: ${config.backendUrl} (${config.backendType || "ollama"})
63251
63351
  `);
63252
- for (const ep of endpointRegistry) {
63253
- process.stderr.write(` ${ep.label}: ${ep.url} (${ep.type})${ep.authKey ? " [auth]" : ""}
63352
+ if (process.env["OA_API_KEYS"]) {
63353
+ const keyCount = process.env["OA_API_KEYS"].split(",").length;
63354
+ process.stderr.write(` Auth: ${keyCount} scoped key(s) (read/run/admin)
63254
63355
  `);
63255
- }
63256
- if (process.env["OA_API_KEYS"]) {
63257
- const keyCount = process.env["OA_API_KEYS"].split(",").length;
63258
- process.stderr.write(` Auth: ${keyCount} scoped key(s) (read/run/admin)
63356
+ } else if (process.env["OA_API_KEY"]) {
63357
+ process.stderr.write(` Auth: single Bearer token (admin scope)
63259
63358
  `);
63260
- } else if (process.env["OA_API_KEY"]) {
63261
- process.stderr.write(` Auth: single Bearer token (admin scope)
63359
+ } else {
63360
+ process.stderr.write(` Auth: disabled (set OA_API_KEY or OA_API_KEYS to enable)
63262
63361
  `);
63263
- } else {
63264
- process.stderr.write(` Auth: disabled (set OA_API_KEY or OA_API_KEYS to enable)
63362
+ }
63363
+ process.stderr.write(` Discovering sponsors in background...
63364
+
63265
63365
  `);
63266
- }
63267
- process.stderr.write(`
63366
+ refreshEndpointRegistry().then(() => {
63367
+ if (endpointRegistry.length > 1) {
63368
+ process.stderr.write(` Sponsors: ${endpointRegistry.length - 1} endpoint(s) discovered
63369
+ `);
63370
+ for (const ep of endpointRegistry.slice(1)) {
63371
+ process.stderr.write(` ${ep.label}: ${ep.url} (${ep.type})${ep.authKey ? " [auth]" : ""}
63372
+ `);
63373
+ }
63374
+ process.stderr.write(`
63268
63375
  `);
63376
+ }
63377
+ }).catch(() => {
63269
63378
  });
63270
63379
  });
63271
63380
  const shutdown = () => {
@@ -63296,7 +63405,7 @@ async function apiServeCommand(opts, config) {
63296
63405
  server.on("close", resolve36);
63297
63406
  });
63298
63407
  }
63299
- var endpointRegistry, modelRouteMap, metrics, startedAt, runningProcesses;
63408
+ var endpointRegistry, modelRouteMap, endpointUsage, metrics, startedAt, runningProcesses;
63300
63409
  var init_serve = __esm({
63301
63410
  "packages/cli/dist/api/serve.js"() {
63302
63411
  "use strict";
@@ -63306,6 +63415,7 @@ var init_serve = __esm({
63306
63415
  init_profiles();
63307
63416
  endpointRegistry = [];
63308
63417
  modelRouteMap = /* @__PURE__ */ new Map();
63418
+ endpointUsage = /* @__PURE__ */ new Map();
63309
63419
  metrics = {
63310
63420
  requests: /* @__PURE__ */ new Map(),
63311
63421
  totalTokensIn: 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.184.56",
3
+ "version": "0.184.58",
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",