open-agents-ai 0.184.2 → 0.184.4

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 +85 -2
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -35580,6 +35580,14 @@ var init_expose = __esm({
35580
35580
  /** Debounce tracking for tunnel restarts */
35581
35581
  _lastRestartAttempt = 0;
35582
35582
  _consecutiveFailures = 0;
35583
+ // ── Sponsor rate limiting ──
35584
+ /** Per-IP sliding window: IP → array of request timestamps */
35585
+ _rateLimitWindows = /* @__PURE__ */ new Map();
35586
+ /** Global token counter for daily budget */
35587
+ _dailyTokensUsed = 0;
35588
+ _dailyTokensResetAt = 0;
35589
+ /** Sponsor rate limits (set via setSponsorLimits) */
35590
+ _sponsorLimits = null;
35583
35591
  _authKey;
35584
35592
  _targetUrl;
35585
35593
  _kind;
@@ -35614,6 +35622,48 @@ var init_expose = __esm({
35614
35622
  get isActive() {
35615
35623
  return this._stats.status === "active";
35616
35624
  }
35625
+ /** Set sponsor rate limits — enables rate limiting middleware in the proxy */
35626
+ setSponsorLimits(limits) {
35627
+ this._sponsorLimits = limits;
35628
+ }
35629
+ /** Check rate limits for a request. Returns null if OK, or error message string if blocked. */
35630
+ checkRateLimit(userIp, model) {
35631
+ if (!this._sponsorLimits)
35632
+ return null;
35633
+ const lim = this._sponsorLimits;
35634
+ if (lim.allowedModels !== "all" && model && !lim.allowedModels.includes(model)) {
35635
+ return `Model '${model}' is not available on this sponsored endpoint. Available: ${lim.allowedModels.join(", ")}`;
35636
+ }
35637
+ if (this._stats.activeConnections >= lim.maxConcurrent) {
35638
+ return `Too many concurrent requests (${this._stats.activeConnections}/${lim.maxConcurrent}). Try again shortly.`;
35639
+ }
35640
+ const now = Date.now();
35641
+ const windowMs = 6e4;
35642
+ let window = this._rateLimitWindows.get(userIp);
35643
+ if (!window) {
35644
+ window = [];
35645
+ this._rateLimitWindows.set(userIp, window);
35646
+ }
35647
+ while (window.length > 0 && window[0] < now - windowMs)
35648
+ window.shift();
35649
+ if (window.length >= lim.maxRequestsPerMinute) {
35650
+ const retryAfterMs = window[0] + windowMs - now;
35651
+ return `Rate limited (${lim.maxRequestsPerMinute} req/min). Retry in ${Math.ceil(retryAfterMs / 1e3)}s.`;
35652
+ }
35653
+ window.push(now);
35654
+ if (this._dailyTokensResetAt < now) {
35655
+ this._dailyTokensUsed = 0;
35656
+ this._dailyTokensResetAt = now + 864e5;
35657
+ }
35658
+ if (this._dailyTokensUsed >= lim.maxTokensPerDay) {
35659
+ return `Daily token budget exhausted (${fmtTokens(lim.maxTokensPerDay)}). Resets in ${Math.ceil((this._dailyTokensResetAt - now) / 36e5)}h.`;
35660
+ }
35661
+ return null;
35662
+ }
35663
+ /** Track token usage from a completed response */
35664
+ trackTokenUsage(tokensIn, tokensOut) {
35665
+ this._dailyTokensUsed += tokensIn + tokensOut;
35666
+ }
35617
35667
  constructor(options) {
35618
35668
  super();
35619
35669
  this.options = options;
@@ -35833,6 +35883,15 @@ var init_expose = __esm({
35833
35883
  user.activeRequests++;
35834
35884
  user.lastSeen = Date.now();
35835
35885
  this.emitStats();
35886
+ const preRateLimitCheck = this.checkRateLimit(userIp, "");
35887
+ if (preRateLimitCheck) {
35888
+ this._stats.activeConnections--;
35889
+ user.activeRequests--;
35890
+ this._stats.errors++;
35891
+ res.writeHead(429, { "Content-Type": "application/json", "Retry-After": "60" });
35892
+ res.end(JSON.stringify({ error: preRateLimitCheck }));
35893
+ return;
35894
+ }
35836
35895
  url.searchParams.delete("key");
35837
35896
  const forwardPath = url.pathname + url.search;
35838
35897
  if (!this._fullAccess) {
@@ -35873,6 +35932,17 @@ var init_expose = __esm({
35873
35932
  } catch {
35874
35933
  }
35875
35934
  }
35935
+ if (requestModel && this._sponsorLimits) {
35936
+ const modelCheck = this.checkRateLimit(userIp, requestModel);
35937
+ if (modelCheck) {
35938
+ this._stats.activeConnections--;
35939
+ user.activeRequests--;
35940
+ this._stats.errors++;
35941
+ res.writeHead(429, { "Content-Type": "application/json", "Retry-After": "60" });
35942
+ res.end(JSON.stringify({ error: modelCheck }));
35943
+ return;
35944
+ }
35945
+ }
35876
35946
  const forwardHeaders = cleanForwardHeaders(req.headers, target.host);
35877
35947
  if (body.length > 0) {
35878
35948
  forwardHeaders["content-length"] = String(body.length);
@@ -35910,6 +35980,7 @@ var init_expose = __esm({
35910
35980
  const tOut2 = parseInt(evalCount?.[1] ?? "0", 10);
35911
35981
  this._stats.totalTokensIn += tIn2;
35912
35982
  this._stats.totalTokensOut += tOut2;
35983
+ this.trackTokenUsage(tIn2, tOut2);
35913
35984
  user.tokensIn += tIn2;
35914
35985
  user.tokensOut += tOut2;
35915
35986
  if (requestModel) {
@@ -35927,6 +35998,7 @@ var init_expose = __esm({
35927
35998
  const tOut2 = parseInt(completionTokens?.[1] ?? "0", 10);
35928
35999
  this._stats.totalTokensIn += tIn2;
35929
36000
  this._stats.totalTokensOut += tOut2;
36001
+ this.trackTokenUsage(tIn2, tOut2);
35930
36002
  user.tokensIn += tIn2;
35931
36003
  user.tokensOut += tOut2;
35932
36004
  if (requestModel) {
@@ -47100,8 +47172,14 @@ async function handleSlashCommand(input, ctx) {
47100
47172
  if (config.transport.cloudflared && ctx.exposeStart) {
47101
47173
  try {
47102
47174
  const url = await ctx.exposeStart("ollama", void 0, "tunnel");
47103
- if (url)
47175
+ if (url) {
47104
47176
  renderInfo(`Tunnel: ${url}`);
47177
+ const gateway = ctx.getExposeGateway?.();
47178
+ if (gateway && "setSponsorLimits" in gateway) {
47179
+ gateway.setSponsorLimits(config.rateLimits);
47180
+ renderInfo(`Rate limits active: ${config.rateLimits.maxRequestsPerMinute} req/min, ${config.rateLimits.maxTokensPerDay.toLocaleString()} tokens/day`);
47181
+ }
47182
+ }
47105
47183
  } catch (err) {
47106
47184
  renderError(`Tunnel start failed: ${err instanceof Error ? err.message : String(err)}`);
47107
47185
  }
@@ -47109,8 +47187,13 @@ async function handleSlashCommand(input, ctx) {
47109
47187
  if (config.transport.libp2p && ctx.exposeStart) {
47110
47188
  try {
47111
47189
  const url = await ctx.exposeStart("ollama", void 0, "libp2p");
47112
- if (url)
47190
+ if (url) {
47113
47191
  renderInfo(`P2P: ${url}`);
47192
+ const gateway = ctx.getExposeGateway?.();
47193
+ if (gateway && "setSponsorLimits" in gateway) {
47194
+ gateway.setSponsorLimits(config.rateLimits);
47195
+ }
47196
+ }
47114
47197
  } catch (err) {
47115
47198
  renderError(`P2P start failed: ${err instanceof Error ? err.message : String(err)}`);
47116
47199
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.184.2",
3
+ "version": "0.184.4",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) \u2014 interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",