Skip to content

chore(api): promote test → main — credit-spend digest cron (#634) - #637

Merged
sweetmantech merged 1 commit into
mainfrom
test
Jun 3, 2026
Merged

chore(api): promote test → main — credit-spend digest cron (#634)#637
sweetmantech merged 1 commit into
mainfrom
test

Conversation

@sweetmantech

@sweetmantechsweetmantech commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Promotes #634 (credit-spend visibility digest cron) from test to main. Net delta vs main is only #634's files.

Verified end-to-end on preview (auth gate, rpc with data + empty no-op, Telegram send) — see #634. DB function get_credit_spend_digest already live in prod (recoupable/database#28). Ensure CRON_SECRET has a real value in Production before the first cron run. Implements the digest item from recoupable/app#1767.


Summary by cubic

Promotes the credit-spend digest cron to production. Adds a cron-triggered internal API that summarizes the last 10 minutes of credit spend and posts a top-spenders digest to Telegram.

  • New Features

    • Vercel Cron runs every 10m via vercel.jsonGET /api/internal/credit-spend-digest.
    • Gated by CRON_SECRET bearer; unauthorized calls return 401.
    • Aggregates via Postgres get_credit_spend_digest and sends a Telegram digest.
    • Digest shows total spend, turns, tokens (cached noted), tool calls, main vs. subagent split, and per-model spend.
    • Top 10 accounts; empty windows are a no-op.
  • Migration

    • Set a real CRON_SECRET in Production before the first run.
    • The DB function get_credit_spend_digest is already live; no schema changes needed.

Written for commit cd4df8b. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added credit spend digest API endpoint with request validation
    • Added formatting utilities for currency amounts and token counts
    • Added credit spend aggregation and reporting functionality

* feat(api): add credit-spend visibility digest cron
Vercel Cron (*/10) hits GET /api/internal/credit-spend-digest, which
aggregates usage_events spend over the last 10 minutes via the
get_credit_spend_digest Postgres function and posts a top-spenders
summary to Telegram. Per account: total spend, turns, tokens, tool
calls, main-vs-subagent split, and per-model breakdown. Empty window is
a no-op (no ping). Cron-only via CRON_SECRET bearer check.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(api): trigger fresh preview build to pick up CRON_SECRET (#1767)
Empty commit so the Vercel git integration rebuilds the preview with the
newly-added CRON_SECRET env var (existing build predated it). Verifies the
credit-spend-digest auth gate (500 "misconfigured" -> 401) before merge.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(api): address review on credit-spend digest
- Move digest handler/validator/formatter from lib/internal/credit-spend-digest
to lib/credits (clearer domain home; "internal" was vague).
- SRP: split formatUsd/formatTokens into formatCentsAsUsd + formatCompactTokens.
- Surface cached_input_tokens as an informational parenthetical instead of
adding it to the token total — cached input is a subset of input_tokens
(priced as input via getCreditUsage), so adding it would double-count.
- Standardize the misconfig 500 body to "Internal server error".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(api): clean git build to verify CRON_SECRET injection (#1767)
Empty commit to get a git-integration preview that receives the Sensitive
CRON_SECRET (CLI deploys can't read Sensitive vars and were bouncing the
branch alias to no-secret builds).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(api): redeploy to pick up corrected CRON_SECRET value (#1767)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Sweets Sweetman <sweetmantech@gmail.com>
@vercel

vercelBot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
apiBuildingBuildingPreviewJun 3, 2026 9:38pm

Request Review

@coderabbitai

coderabbitaiBot commented Jun 3, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 83e589f4-e890-4737-a27b-3f117ece40e5

📥 Commits

Reviewing files that changed from the base of the PR and between 31c9d58 and cd4df8b.

⛔ Files ignored due to path filters (4)
  • lib/credits/__tests__/formatCreditSpendDigest.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/credits/__tests__/getCreditSpendDigestHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/credits/__tests__/validateGetCreditSpendDigestRequest.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • vercel.json is excluded by none and included by none
📒 Files selected for processing (7)
  • app/api/internal/credit-spend-digest/route.ts
  • lib/credits/formatCentsAsUsd.ts
  • lib/credits/formatCompactTokens.ts
  • lib/credits/formatCreditSpendDigest.ts
  • lib/credits/getCreditSpendDigestHandler.ts
  • lib/credits/validateGetCreditSpendDigestRequest.ts
  • lib/supabase/usage_events/getCreditSpendDigest.ts

📝 Walkthrough

Walkthrough

Adds a Vercel cron endpoint that fetches and formats credit spend digests from the last 10 minutes of usage events, posts ranked account summaries to Telegram via bearer token validation, and returns metadata about how many accounts were included.

Changes

Credit Spend Digest Cron

Layer / File(s)Summary
Data contract and Supabase RPC
lib/supabase/usage_events/getCreditSpendDigest.ts
CreditSpendDigestRow interface models aggregated spend with account metadata, token counts, and per-model breakdown. getCreditSpendDigest calls the get_credit_spend_digest Postgres RPC with explicit typing, error handling, and null-to-empty-array normalization.
Formatting utilities
lib/credits/formatCentsAsUsd.ts, lib/credits/formatCompactTokens.ts, lib/credits/formatCreditSpendDigest.ts
Three formatters build the digest message: formatCentsAsUsd converts cents to two-decimal USD strings; formatCompactTokens abbreviates token counts with M/K suffixes; formatCreditSpendDigest composes the full Telegram message from rows with optional subagent and per-model spend breakdowns.
Request validation
lib/credits/validateGetCreditSpendDigestRequest.ts
Enforces CRON_SECRET bearer token authentication: returns 500 when the secret is unset, 401 on mismatch, and null to proceed when authorized.
Handler orchestration
lib/credits/getCreditSpendDigestHandler.ts
Validates the request, derives a 10-minute lookback timestamp, fetches top spenders, returns early with no message when rows are empty, otherwise formats and sends a Telegram digest and returns sent status with account count. Error handling wraps the flow in try/catch with console logging and standardized 500 responses.
Route entry point
app/api/internal/credit-spend-digest/route.ts
Next.js route handler forces dynamic rendering and no-store caching, then delegates to getCreditSpendDigestHandler and returns its response.

Sequence Diagram(s)

sequenceDiagram
participant VercelCron as Vercel Cron
participant Route as GET Handler
participant Validator as validateGetCreditSpendDigestRequest
participant DB as Supabase RPC
participant Formatter as formatCreditSpendDigest
participant Telegram as sendMessage
VercelCron->>Route: POST with CRON_SECRET bearer token
Route->>Validator: Validate authorization
Validator-->>Route: null (authorized)
Route->>DB: getCreditSpendDigest(sinceIso, TOP_N)
DB-->>Route: CreditSpendDigestRow[] or empty
alt rows exist
Route->>Formatter: formatCreditSpendDigest(rows, WINDOW_MINUTES)
Formatter-->>Route: digest message string
Route->>Telegram: sendMessage(digest)
Telegram-->>Route: sent
Route-->>VercelCron: {sent: true, accounts: N}
else no rows
Route-->>VercelCron: {sent: false, accounts: 0}
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • recoupable/api#634: Overlapping implementation of the same credit-spend-digest cron route, handler, validator, and formatter across identical files.
  • recoupable/api#570: Establishes the usage_events rows and credits_deducted_cents that this PR's digest reads and aggregates via the Supabase RPC.

Suggested reviewers

  • cubic-dev-ai

Poem

📊 Ten minutes of credits distilled,
Ranked accounts in a message filled,
Bearer tokens guard the cron's gate,
Telegram learns who spend with weight. 🚀

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sweetmantech
sweetmantech merged commit 8b97b51 into mainJun 3, 2026
4 of 6 checks passed

@cubic-dev-aicubic-dev-aiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 11 files

Confidence score: 5/5

  • This looks low risk overall: the reported issue is low severity (3/10) and appears limited to output ordering noise rather than incorrect spend totals or broken functionality.
  • The main concern is in lib/credits/formatCreditSpendDigest.ts, where equal spend values can sort non-deterministically and cause digest sections to reorder between runs, which may create confusing diffs or presentation churn.
  • This PR is likely safe to merge, with a follow-up improvement to make tie-breaking deterministic for stable digest output.
  • Pay close attention to lib/credits/formatCreditSpendDigest.ts - non-deterministic tie sorting can cause noisy digest reordering.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="lib/credits/formatCreditSpendDigest.ts">
<violation number="1" location="lib/credits/formatCreditSpendDigest.ts:55">
P3: Model breakdown sort is non-deterministic for equal spend values, which can cause noisy digest reordering between runs.</violation>
</file>
Architecture diagram
sequenceDiagram
participant Vercel as Vercel Cron Scheduler
participant API as Next.js API Route
participant Validator as validateGetCreditSpendDigestRequest
participant Handler as getCreditSpendDigestHandler
participant DB as Postgres (get_credit_spend_digest)
participant Formatter as formatCreditSpendDigest
participant Telegram as Telegram Bot API
Note over Vercel,Telegram: Credit-Spend Digest Cron (every 10 minutes)
Vercel->>API: GET /api/internal/credit-spend-digest
Note over Vercel,API: Authorization: Bearer ${CRON_SECRET}
API->>Validator: validateGetCreditSpendDigestRequest(request)
alt Missing CRON_SECRET env var
Validator-->>API: 500 Internal Server Error
API-->>Vercel: 500 (error: misconfigured)
else Authorization header mismatch
Validator-->>API: 401 Unauthorized
API-->>Vercel: 401 (unauthorized)
else Valid bearer token
Validator-->>API: null (authorized)
API->>Handler: getCreditSpendDigestHandler(request)
Handler->>Handler: Compute sinceIso = now - 10 minutes
Handler->>DB: get_credit_spend_digest(p_since, p_limit=10)
Note over Handler,DB: RPC call to Postgres function
alt Database error
DB-->>Handler: Error
Handler-->>API: 500 Internal Server Error
API-->>Vercel: 500
else Empty result set (no spend in window)
DB-->>Handler: [] (empty array)
Handler-->>API: 200 { status: "ok", sent: false, accounts: 0 }
API-->>Vercel: 200 (no-op)
else Spend data found
DB-->>Handler: CreditSpendDigestRow[] (ranked by total_cents desc)
Handler->>Formatter: formatCreditSpendDigest(rows, windowMinutes=10)
Note over Formatter: Builds Telegram message with:<br/>- Total spend<br/>- Turn/token/tool counts<br/>- Main/subagent split<br/>- Per-model breakdown
Formatter-->>Handler: Formatted digest string
Handler->>Telegram: sendMessage(formattedDigest)
Note over Handler,Telegram: Telegram bot sends digest to configured chat
alt Send succeeds
Telegram-->>Handler: OK
Handler-->>API: 200 { status: "ok", sent: true, accounts: N }
else Send fails
Telegram-->>Handler: Error
Handler-->>API: 500 Internal Server Error
end
API-->>Vercel: Response
end
end
Note over Vercel,Telegram: Key behaviors:<br/>- Empty window = silent no-op (no Telegram ping)<br/>- Top 10 accounts only<br/>- Cached tokens shown as informational, not added to total<br/>- Stateless window (no offset tracking)
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

);
}

const models = Object.entries(row.by_model).sort((a, b) => b[1] - a[1]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Model breakdown sort is non-deterministic for equal spend values, which can cause noisy digest reordering between runs.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/credits/formatCreditSpendDigest.ts, line 55:
<comment>Model breakdown sort is non-deterministic for equal spend values, which can cause noisy digest reordering between runs.</comment>
<file context>
@@ -0,0 +1,66 @@
+ );
+ }
+
+ const models = Object.entries(row.by_model).sort((a, b) => b[1] - a[1]);
+ if (models.length > 0) {
+ lines.push(
</file context>

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@sweetmantech@arpitgupta1214