feat(kiwi): add /orch-usage proxy endpoint for frontend budget display

This commit is contained in:
pyr0ball 2026-04-14 15:42:58 -07:00
parent 1a6898324c
commit 006582b179
4 changed files with 110 additions and 1 deletions

24
.gitleaks.toml Normal file
View file

@ -0,0 +1,24 @@
# Kiwi gitleaks config — extends base CircuitForge config with local rules
[extend]
path = "/Library/Development/CircuitForge/circuitforge-hooks/gitleaks.toml"
# ── Test fixture allowlists ───────────────────────────────────────────────────
[[rules]]
id = "cf-generic-env-token"
description = "Generic KEY=<token> in env-style assignment — catches FORGEJO_API_TOKEN=hex etc."
regex = '''(?i)(token|secret|key|password|passwd|pwd|api_key)\s*[=:]\s*['"]?[A-Za-z0-9\-_]{20,}['"]?'''
[rules.allowlist]
paths = [
'.*test.*',
]
regexes = [
'api_key:\s*ollama',
'api_key:\s*any',
'your-[a-z\-]+-here',
'replace-with-',
'xxxx',
'test-fixture-',
'CFG-KIWI-TEST-',
]

View file

@ -0,0 +1,27 @@
"""Proxy endpoint: exposes cf-orch call budget to the Kiwi frontend.
Only lifetime/founders users have a license_key subscription and free
users receive null (no budget UI shown).
"""
from __future__ import annotations
from fastapi import APIRouter, Depends
from app.cloud_session import CloudUser, get_session
from app.services.heimdall_orch import get_orch_usage
router = APIRouter()
@router.get("")
async def orch_usage_endpoint(
session: CloudUser = Depends(get_session),
) -> dict | None:
"""Return the current period's orch usage for the authenticated user.
Returns null if the user has no lifetime/founders license key (i.e. they
are on a subscription or free plan no budget cap applies to them).
"""
if session.license_key is None:
return None
return get_orch_usage(session.license_key, "kiwi")

View file

@ -1,5 +1,5 @@
from fastapi import APIRouter
from app.api.endpoints import health, receipts, export, inventory, ocr, recipes, settings, staples, feedback, household, saved_recipes, imitate, meal_plans
from app.api.endpoints import health, receipts, export, inventory, ocr, recipes, settings, staples, feedback, household, saved_recipes, imitate, meal_plans, orch_usage
from app.api.endpoints.community import router as community_router
api_router = APIRouter()
@ -17,4 +17,5 @@ api_router.include_router(feedback.router, prefix="/feedback", tags=
api_router.include_router(household.router, prefix="/household", tags=["household"])
api_router.include_router(imitate.router, prefix="/imitate", tags=["imitate"])
api_router.include_router(meal_plans.router, prefix="/meal-plans", tags=["meal-plans"])
api_router.include_router(orch_usage.router, prefix="/orch-usage", tags=["orch-usage"])
api_router.include_router(community_router)

View file

@ -0,0 +1,57 @@
"""Tests for the /orch-usage proxy endpoint."""
from __future__ import annotations
from pathlib import Path
from unittest.mock import patch
import pytest
from fastapi.testclient import TestClient
from app.cloud_session import CloudUser, get_session
from app.main import app
def _make_session(license_key=None, tier="paid"):
return CloudUser(
user_id="user-1",
tier=tier,
db=Path("/tmp/kiwi_test.db"),
has_byok=False,
license_key=license_key,
)
def test_orch_usage_returns_data_for_lifetime_user():
"""GET /orch-usage with a lifetime key returns usage data."""
app.dependency_overrides[get_session] = lambda: _make_session(
license_key="CFG-KIWI-TEST-0000-0000"
)
client = TestClient(app)
with patch("app.api.endpoints.orch_usage.get_orch_usage") as mock_usage:
mock_usage.return_value = {
"calls_used": 10,
"topup_calls": 0,
"calls_total": 60,
"period_start": "2026-04-14",
"resets_on": "2026-05-14",
}
resp = client.get("/api/v1/orch-usage")
app.dependency_overrides.clear()
assert resp.status_code == 200
data = resp.json()
assert data["calls_used"] == 10
assert data["calls_total"] == 60
def test_orch_usage_returns_null_for_subscription_user():
"""GET /orch-usage with no license_key returns null."""
app.dependency_overrides[get_session] = lambda: _make_session(
license_key=None, tier="paid"
)
client = TestClient(app)
resp = client.get("/api/v1/orch-usage")
app.dependency_overrides.clear()
assert resp.status_code == 200
assert resp.json() is None