feat: add provider usage adapters for openai and placeholders for others
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import json
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Any, Dict
|
||||
from typing import Any, Dict, Tuple
|
||||
|
||||
import requests
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -66,21 +66,69 @@ def test_provider_connection(provider: str, credential: str):
|
||||
return False, str(e)
|
||||
|
||||
|
||||
def _openai_usage(credential: str) -> Tuple[str, Dict[str, Any]]:
|
||||
# Uses legacy billing endpoints; may be disabled in some orgs.
|
||||
headers = _provider_headers('openai', credential)
|
||||
today = _now().date()
|
||||
start = (today - timedelta(days=7)).isoformat()
|
||||
end = today.isoformat()
|
||||
usage_url = f'https://api.openai.com/v1/dashboard/billing/usage?start_date={start}&end_date={end}'
|
||||
sub_url = 'https://api.openai.com/v1/dashboard/billing/subscription'
|
||||
u = requests.get(usage_url, headers=headers, timeout=12)
|
||||
s = requests.get(sub_url, headers=headers, timeout=12)
|
||||
if u.status_code != 200 or s.status_code != 200:
|
||||
return 'error', {'error': f'usage:{u.status_code}, subscription:{s.status_code}'}
|
||||
usage = u.json()
|
||||
sub = s.json()
|
||||
total_usage = usage.get('total_usage')
|
||||
hard_limit = sub.get('hard_limit_usd')
|
||||
reset_at = sub.get('billing_cycle_anchor')
|
||||
if reset_at:
|
||||
reset_at = datetime.fromtimestamp(reset_at, tz=timezone.utc).isoformat()
|
||||
usage_pct = None
|
||||
if total_usage is not None and hard_limit:
|
||||
usage_pct = round(total_usage / hard_limit * 100, 2)
|
||||
return 'ok', {
|
||||
'window_label': '7d',
|
||||
'used': total_usage,
|
||||
'limit': hard_limit,
|
||||
'usage_pct': usage_pct,
|
||||
'reset_at': reset_at,
|
||||
'raw': {'usage': usage, 'subscription': sub},
|
||||
}
|
||||
|
||||
|
||||
def _anthropic_usage(credential: str) -> Tuple[str, Dict[str, Any]]:
|
||||
# Anthropic usage endpoints are enterprise-specific; keep placeholder.
|
||||
# We return accepted status; detail will be filled once API confirmed.
|
||||
return 'unsupported', {'error': 'anthropic usage API not configured'}
|
||||
|
||||
|
||||
def refresh_provider_usage_once(db: Session):
|
||||
accounts = db.query(ProviderAccount).filter(ProviderAccount.is_enabled == True).all()
|
||||
now = _now()
|
||||
for a in accounts:
|
||||
ok, msg = test_provider_connection(a.provider, a.credential)
|
||||
status = 'pending'
|
||||
payload: Dict[str, Any] = {}
|
||||
if a.provider == 'openai':
|
||||
status, payload = _openai_usage(a.credential)
|
||||
elif a.provider == 'anthropic':
|
||||
status, payload = _anthropic_usage(a.credential)
|
||||
else:
|
||||
ok, msg = test_provider_connection(a.provider, a.credential)
|
||||
status = 'ok' if ok else 'error'
|
||||
payload = {'error': None if ok else msg}
|
||||
|
||||
snap = ProviderUsageSnapshot(
|
||||
account_id=a.id,
|
||||
window_label='provider-default',
|
||||
used=None,
|
||||
limit=None,
|
||||
usage_pct=None,
|
||||
reset_at=None,
|
||||
status='ok' if ok else 'error',
|
||||
error=None if ok else msg,
|
||||
raw_payload=json.dumps({'message': msg}, ensure_ascii=False),
|
||||
window_label=payload.get('window_label'),
|
||||
used=payload.get('used'),
|
||||
limit=payload.get('limit'),
|
||||
usage_pct=payload.get('usage_pct'),
|
||||
reset_at=payload.get('reset_at'),
|
||||
status=status,
|
||||
error=payload.get('error'),
|
||||
raw_payload=json.dumps(payload.get('raw') or payload, ensure_ascii=False),
|
||||
fetched_at=now,
|
||||
)
|
||||
db.add(snap)
|
||||
@@ -100,7 +148,7 @@ def get_provider_usage_view(db: Session):
|
||||
'usage_pct': snap.usage_pct if snap else None,
|
||||
'used': snap.used if snap else None,
|
||||
'limit': snap.limit if snap else None,
|
||||
'reset_at': snap.reset_at.isoformat() if snap and snap.reset_at else None,
|
||||
'reset_at': snap.reset_at if snap else None,
|
||||
'status': snap.status if snap else 'pending',
|
||||
'error': snap.error if snap else None,
|
||||
'fetched_at': snap.fetched_at.isoformat() if snap and snap.fetched_at else None,
|
||||
|
||||
Reference in New Issue
Block a user