neoagent 2.4.2-beta.1 → 2.4.2-beta.3

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.
@@ -110,6 +110,27 @@ router.get('/', (req, res) => {
110
110
  }
111
111
  });
112
112
 
113
+ router.get('/usage', (req, res) => {
114
+ try {
115
+ const userId = req.session.userId;
116
+ const userLimits = db.prepare('SELECT rate_limit_4h, rate_limit_weekly FROM users WHERE id = ?').get(userId);
117
+ const h4Tokens = db.prepare("SELECT COALESCE(SUM(total_tokens), 0) as t FROM agent_runs WHERE user_id = ? AND created_at > datetime('now', '-4 hours')").get(userId).t;
118
+ const weeklyTokens = db.prepare("SELECT COALESCE(SUM(total_tokens), 0) as t FROM agent_runs WHERE user_id = ? AND created_at > datetime('now', '-7 days')").get(userId).t;
119
+ res.json({
120
+ limits: {
121
+ fourHour: userLimits?.rate_limit_4h || null,
122
+ weekly: userLimits?.rate_limit_weekly || null,
123
+ },
124
+ usage: {
125
+ fourHour: h4Tokens,
126
+ weekly: weeklyTokens,
127
+ }
128
+ });
129
+ } catch (err) {
130
+ sendRouteError(res, err);
131
+ }
132
+ });
133
+
113
134
  router.put('/display-name', accountLimiter, (req, res) => {
114
135
  try {
115
136
  const displayName = normalizeDisplayName(req.body?.displayName);
@@ -469,7 +469,7 @@ router.get('/api/users', requireAdminAuth, (req, res) => {
469
469
  try {
470
470
  const sql = `
471
471
  SELECT u.id, u.username, u.display_name, u.email, u.email_verified_at,
472
- u.created_at, u.last_login,
472
+ u.created_at, u.last_login, u.rate_limit_4h, u.rate_limit_weekly,
473
473
  COALESCE(r.run_count, 0) AS run_count,
474
474
  COALESCE(a.storage_bytes,0) AS storage_bytes
475
475
  FROM users u
@@ -618,6 +618,45 @@ router.put('/api/providers', requireAdminAuth, express.json(), (req, res) => {
618
618
  res.json({ ok: true });
619
619
  });
620
620
 
621
+ // --- Models ---
622
+
623
+ router.get('/api/models', requireAdminAuth, async (req, res) => {
624
+ const { getSupportedModels } = require('../services/ai/models');
625
+ try {
626
+ const models = await getSupportedModels(null, null);
627
+ const enabledModelsStr = process.env.NEOAGENT_ENABLED_MODELS || '';
628
+ const enabledModelsList = enabledModelsStr ? enabledModelsStr.split(',').map(s => s.trim()).filter(s => s.length > 0) : [];
629
+ res.json({ models, enabledModels: enabledModelsList });
630
+ } catch (err) {
631
+ res.status(500).json({ error: String(err.message || err) });
632
+ }
633
+ });
634
+
635
+ router.put('/api/models/enabled', requireAdminAuth, express.json(), (req, res) => {
636
+ const { enabledModels } = req.body || {};
637
+ if (!Array.isArray(enabledModels)) return res.status(400).json({ error: 'enabledModels must be an array' });
638
+ const value = enabledModels.join(',');
639
+ upsertEnvValue(ENV_FILE, 'NEOAGENT_ENABLED_MODELS', value);
640
+ process.env.NEOAGENT_ENABLED_MODELS = value;
641
+ res.json({ ok: true });
642
+ });
643
+
644
+ router.put('/api/users/:id/rate-limits', requireAdminAuth, express.json(), (req, res) => {
645
+ const db = require('../db/database');
646
+ const { id } = req.params;
647
+ const { rate_limit_4h, rate_limit_weekly } = req.body || {};
648
+ try {
649
+ db.prepare('UPDATE users SET rate_limit_4h = ?, rate_limit_weekly = ? WHERE id = ?').run(
650
+ rate_limit_4h ?? null,
651
+ rate_limit_weekly ?? null,
652
+ id
653
+ );
654
+ res.json({ ok: true });
655
+ } catch (err) {
656
+ res.status(500).json({ error: String(err.message || err) });
657
+ }
658
+ });
659
+
621
660
  // --- Static files ---
622
661
 
623
662
  router.use(express.static(ADMIN_DIR));
@@ -1214,6 +1214,22 @@ class AgentEngine {
1214
1214
  ensureDefaultAiSettings(userId, agentId);
1215
1215
  const aiSettings = getAiSettings(userId, agentId);
1216
1216
 
1217
+ const userLimits = db.prepare('SELECT rate_limit_4h, rate_limit_weekly FROM users WHERE id = ?').get(userId);
1218
+ if (userLimits) {
1219
+ if (userLimits.rate_limit_4h) {
1220
+ const h4Tokens = db.prepare("SELECT COALESCE(SUM(total_tokens), 0) as t FROM agent_runs WHERE user_id = ? AND created_at > datetime('now', '-4 hours')").get(userId).t;
1221
+ if (h4Tokens >= userLimits.rate_limit_4h) {
1222
+ throw new Error(`Rate limit exceeded: You have used ${h4Tokens} tokens in the last 4 hours (limit: ${userLimits.rate_limit_4h}).`);
1223
+ }
1224
+ }
1225
+ if (userLimits.rate_limit_weekly) {
1226
+ const weeklyTokens = db.prepare("SELECT COALESCE(SUM(total_tokens), 0) as t FROM agent_runs WHERE user_id = ? AND created_at > datetime('now', '-7 days')").get(userId).t;
1227
+ if (weeklyTokens >= userLimits.rate_limit_weekly) {
1228
+ throw new Error(`Rate limit exceeded: You have used ${weeklyTokens} tokens in the last 7 days (limit: ${userLimits.rate_limit_weekly}).`);
1229
+ }
1230
+ }
1231
+ }
1232
+
1217
1233
  const runId = options.runId || uuidv4();
1218
1234
  const conversationId = options.conversationId;
1219
1235
  const app = options.app || this.app;
@@ -402,7 +402,7 @@ async function getSupportedModels(userId, agentId = null) {
402
402
 
403
403
  // Ollama: dynamic list from local server
404
404
  const ollama = providerById.get('ollama');
405
- if (ollama?.enabled) {
405
+ if (ollama?.available) {
406
406
  const dynamicModels = await refreshDynamicModels(ollama.baseUrl);
407
407
  for (const model of dynamicModels) {
408
408
  if (!staticIds.has(model.id)) {
@@ -430,6 +430,9 @@ async function getSupportedModels(userId, agentId = null) {
430
430
  }
431
431
  }
432
432
 
433
+ const globalEnabledStr = process.env.NEOAGENT_ENABLED_MODELS || '';
434
+ const globalEnabledSet = globalEnabledStr ? new Set(globalEnabledStr.split(',').map(s => s.trim()).filter(Boolean)) : null;
435
+
433
436
  return all.map((model) => {
434
437
  const provider = providerById.get(model.provider);
435
438
  // Ollama models are always local/free; all others look up the OpenRouter
@@ -437,10 +440,16 @@ async function getSupportedModels(userId, agentId = null) {
437
440
  const priceTier = model.provider === 'ollama'
438
441
  ? 'free'
439
442
  : (model.priceTier ?? classifyPriceTier(model.id));
443
+
444
+ let available = provider?.available !== false;
445
+ if (globalEnabledSet && !globalEnabledSet.has(model.id)) {
446
+ available = false;
447
+ }
448
+
440
449
  return {
441
450
  ...model,
442
451
  priceTier,
443
- available: provider?.available !== false,
452
+ available,
444
453
  providerStatus: provider?.status || 'unknown',
445
454
  providerStatusLabel: provider?.statusLabel || 'Unknown'
446
455
  };
@@ -10,7 +10,10 @@ class OllamaProvider extends BaseProvider {
10
10
 
11
11
  async listModels() {
12
12
  try {
13
- const res = await fetch(`${this.baseUrl}/api/tags`);
13
+ const controller = new AbortController();
14
+ const timer = setTimeout(() => controller.abort(), 5000);
15
+ const res = await fetch(`${this.baseUrl}/api/tags`, { signal: controller.signal });
16
+ clearTimeout(timer);
14
17
  const data = await res.json();
15
18
  this.models = (data.models || []).map(m => m.name);
16
19
  return this.models;