Skip to content

Commit 2c6fa1e

Browse files
authored
Merge pull request #364 from openlegion-ai/fix/dashboard-monthly-budget
fix: add monthly budget support to dashboard
2 parents b076c20 + 6c11919 commit 2c6fa1e

File tree

3 files changed

+49
-24
lines changed

3 files changed

+49
-24
lines changed

src/dashboard/server.py

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,19 @@ def _verify_dashboard_auth(request: Request) -> None:
5959
raise HTTPException(401, error)
6060

6161

62+
def _parse_positive_float(value: Any, field: str, fallback: float) -> float:
63+
"""Validate *value* as a positive float, returning *fallback* if None."""
64+
if value is None:
65+
return fallback
66+
try:
67+
result = float(value)
68+
if result <= 0:
69+
raise ValueError
70+
except (ValueError, TypeError):
71+
raise HTTPException(status_code=400, detail=f"{field} must be a positive number")
72+
return result
73+
74+
6275
def create_dashboard_router(
6376
blackboard: Blackboard,
6477
health_monitor: HealthMonitor | None,
@@ -552,16 +565,14 @@ async def api_update_agent_config(agent_id: str, request: Request) -> dict:
552565
if "budget" in body:
553566
budget_val = body["budget"]
554567
if isinstance(budget_val, dict):
555-
daily = budget_val.get("daily_usd")
556-
if daily is not None:
557-
try:
558-
daily = float(daily)
559-
if daily <= 0:
560-
raise ValueError
561-
except (ValueError, TypeError):
562-
raise HTTPException(status_code=400, detail="Budget must be a positive number")
563-
_update_agent_field(agent_id, "budget", {"daily_usd": daily})
564-
cost_tracker.set_budget(agent_id, daily_usd=daily)
568+
raw_daily = budget_val.get("daily_usd")
569+
raw_monthly = budget_val.get("monthly_usd")
570+
if raw_daily is not None or raw_monthly is not None:
571+
current = cost_tracker.check_budget(agent_id)
572+
daily = _parse_positive_float(raw_daily, "daily_usd", current.get("daily_limit", 10.0))
573+
monthly = _parse_positive_float(raw_monthly, "monthly_usd", current.get("monthly_limit", 200.0))
574+
_update_agent_field(agent_id, "budget", {"daily_usd": daily, "monthly_usd": monthly})
575+
cost_tracker.set_budget(agent_id, daily_usd=daily, monthly_usd=monthly)
565576
updated.append("budget")
566577

567578
if "thinking" in body:
@@ -633,17 +644,17 @@ async def api_update_budget(agent_id: str, request: Request) -> dict:
633644
if agent_id not in agent_registry:
634645
raise HTTPException(status_code=404, detail="Agent not found")
635646
body = await request.json()
636-
daily_usd = body.get("daily_usd")
637-
try:
638-
daily_usd = float(daily_usd)
639-
if daily_usd <= 0:
640-
raise ValueError
641-
except (ValueError, TypeError):
642-
raise HTTPException(status_code=400, detail="daily_usd must be a positive number")
643-
cost_tracker.set_budget(agent_id, daily_usd=daily_usd)
647+
raw_daily = body.get("daily_usd")
648+
raw_monthly = body.get("monthly_usd")
649+
if raw_daily is None and raw_monthly is None:
650+
raise HTTPException(status_code=400, detail="Provide daily_usd and/or monthly_usd")
651+
current = cost_tracker.check_budget(agent_id)
652+
daily_usd = _parse_positive_float(raw_daily, "daily_usd", current.get("daily_limit", 10.0))
653+
monthly_usd = _parse_positive_float(raw_monthly, "monthly_usd", current.get("monthly_limit", 200.0))
654+
cost_tracker.set_budget(agent_id, daily_usd=daily_usd, monthly_usd=monthly_usd)
644655
from src.cli.config import _update_agent_field
645-
_update_agent_field(agent_id, "budget", {"daily_usd": daily_usd})
646-
return {"updated": True, "agent": agent_id, "daily_usd": daily_usd}
656+
_update_agent_field(agent_id, "budget", {"daily_usd": daily_usd, "monthly_usd": monthly_usd})
657+
return {"updated": True, "agent": agent_id, "daily_usd": daily_usd, "monthly_usd": monthly_usd}
647658

648659
@api_router.get("/api/agents/{agent_id}/permissions")
649660
async def api_agent_permissions(agent_id: str) -> dict:

src/dashboard/static/js/app.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1485,6 +1485,7 @@ function dashboard() {
14851485
avatar: cfg.avatar || 1,
14861486
_showAvatarPicker: false,
14871487
budget_daily: cfg.budget?.daily_usd || '',
1488+
budget_monthly: cfg.budget?.monthly_usd || '',
14881489
allowed_credentials: credsStr,
14891490
_credMode: credMode,
14901491
};
@@ -1515,8 +1516,14 @@ function dashboard() {
15151516
if (this.editForm.model && this.editForm.model !== cfg.model) body.model = this.editForm.model;
15161517
if (this.editForm.role !== undefined && this.editForm.role !== cfg.role) body.role = this.editForm.role;
15171518
if (this.editForm.avatar && this.editForm.avatar !== (cfg.avatar || 1)) body.avatar = this.editForm.avatar;
1518-
if (this.editForm.budget_daily && parseFloat(this.editForm.budget_daily) > 0) {
1519-
body.budget = { daily_usd: parseFloat(this.editForm.budget_daily) };
1519+
if ((this.editForm.budget_daily && parseFloat(this.editForm.budget_daily) > 0) ||
1520+
(this.editForm.budget_monthly && parseFloat(this.editForm.budget_monthly) > 0)) {
1521+
const budget = {};
1522+
if (this.editForm.budget_daily && parseFloat(this.editForm.budget_daily) > 0)
1523+
budget.daily_usd = parseFloat(this.editForm.budget_daily);
1524+
if (this.editForm.budget_monthly && parseFloat(this.editForm.budget_monthly) > 0)
1525+
budget.monthly_usd = parseFloat(this.editForm.budget_monthly);
1526+
body.budget = budget;
15201527
}
15211528
// Handle allowed_credentials via the permissions endpoint
15221529
const newCreds = (this.editForm.allowed_credentials || '').split(',').map(s => s.trim()).filter(Boolean);
@@ -1596,11 +1603,14 @@ function dashboard() {
15961603
}, true);
15971604
},
15981605

1599-
async updateBudget(agentId, dailyUsd) {
1606+
async updateBudget(agentId, dailyUsd, monthlyUsd) {
16001607
try {
1608+
const body = {};
1609+
if (dailyUsd != null) body.daily_usd = parseFloat(dailyUsd);
1610+
if (monthlyUsd != null) body.monthly_usd = parseFloat(monthlyUsd);
16011611
const resp = await fetch(`${window.__config.apiBase}/agents/${agentId}/budget`, {
16021612
method: 'PUT', headers: {'Content-Type': 'application/json'},
1603-
body: JSON.stringify({ daily_usd: parseFloat(dailyUsd) }),
1613+
body: JSON.stringify(body),
16041614
});
16051615
if (resp.ok) this.showToast(`Budget updated for ${agentId}`);
16061616
} catch (e) { console.warn('updateBudget failed:', e); }

src/dashboard/templates/index.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,6 +1407,10 @@ <h2 class="text-xl font-bold" x-text="agentDetail.id"></h2>
14071407
<label class="text-xs text-gray-500 mb-1 block">Budget ($/day)</label>
14081408
<input type="number" step="0.5" min="0.1" x-model="editForm.budget_daily" class="dash-input w-full" placeholder="10.0">
14091409
</div>
1410+
<div>
1411+
<label class="text-xs text-gray-500 mb-1 block">Budget ($/month)</label>
1412+
<input type="number" step="1" min="1" x-model="editForm.budget_monthly" class="dash-input w-full" placeholder="200.0">
1413+
</div>
14101414
<div class="md:col-span-2">
14111415
<label class="text-xs text-gray-500 mb-1.5 block">Credential Access</label>
14121416
<div class="flex items-center gap-3 mb-2">

0 commit comments

Comments
 (0)