feat(03-01): backend API endpoints — channels, billing, usage, and audit logger enhancement

- Create channels.py: HMAC-signed OAuth state generation/verification, Slack OAuth install/callback, WhatsApp manual connect, test message endpoint
- Create billing.py: Stripe Checkout session, billing portal session, webhook handler with idempotency (StripeEvent table), subscription lifecycle management
- Update usage.py: add _aggregate_rows_by_agent and _aggregate_rows_by_provider helpers (unit-testable without DB), complete usage endpoints
- Fix audit.py: rename 'metadata' attribute to 'event_metadata' (SQLAlchemy 2.0 DeclarativeBase reserves 'metadata')
- Enhance runner.py: audit log now includes prompt_tokens, completion_tokens, total_tokens, cost_usd, provider in LLM call metadata
- Update api/__init__.py to export all new routers
- All 27 unit tests passing
This commit is contained in:
2026-03-23 21:24:08 -06:00
parent 215e67a7eb
commit 4cbf192fa5
9 changed files with 1297 additions and 3 deletions

View File

@@ -182,6 +182,19 @@ async def run_agent(
input_summary = _get_last_user_message(loop_messages)
output_summary = response_content or f"[{len(response_tool_calls)} tool calls]"
try:
# Extract token usage from LLM pool response
usage_data = data.get("usage", {}) or {}
prompt_tokens = int(usage_data.get("prompt_tokens", 0))
completion_tokens = int(usage_data.get("completion_tokens", 0))
total_tokens = prompt_tokens + completion_tokens
# Extract cost from response or estimate from usage
cost_usd = float(data.get("cost_usd", 0.0))
# Extract provider from model string (e.g. "anthropic/claude-sonnet-4" → "anthropic")
model_str: str = data.get("model", agent.model_preference) or ""
provider = model_str.split("/")[0] if "/" in model_str else model_str
await audit_logger.log_llm_call(
tenant_id=tenant_id,
agent_id=agent_uuid,
@@ -190,9 +203,14 @@ async def run_agent(
output_summary=output_summary,
latency_ms=call_latency_ms,
metadata={
"model": data.get("model", agent.model_preference),
"model": model_str,
"provider": provider,
"iteration": iteration,
"tool_calls_count": len(response_tool_calls),
"prompt_tokens": prompt_tokens,
"completion_tokens": completion_tokens,
"total_tokens": total_tokens,
"cost_usd": cost_usd,
},
)
except Exception: