Skip to content

feat(abuse): implement cost reporting for spend-based heuristics#22

Merged
jrf0110 merged 2 commits intomainfrom
abuse-cost
Feb 5, 2026
Merged

feat(abuse): implement cost reporting for spend-based heuristics#22
jrf0110 merged 2 commits intomainfrom
abuse-cost

Conversation

@jrf0110
Copy link
Copy Markdown
Contributor

@jrf0110 jrf0110 commented Feb 4, 2026

Add functionality to report actual request costs back to the abuse service. This enables the abuse service to track real-time spend and trigger heuristics like free tier exhaustion.

Comment thread src/app/api/openrouter/[...path]/route.ts Outdated
Comment thread src/lib/abuse-service.ts
@kilo-code-bot
Copy link
Copy Markdown
Contributor

kilo-code-bot Bot commented Feb 4, 2026

Code Review Summary

Status: 1 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
src/app/api/openrouter/[...path]/route.ts 387 Promise.race can throw if classifyPromise rejects (should stay fail-open)
Files Reviewed (4 files)
  • src/app/api/openrouter/[...path]/route.ts - 1 issue
  • src/lib/abuse-service.ts
  • src/lib/processUsage.ts
  • src/lib/config.server.ts

Fix these issues in Kilo Cloud

Comment thread src/lib/processUsage.ts Outdated
Comment thread src/app/api/openrouter/[...path]/route.ts Outdated
@jrf0110 jrf0110 force-pushed the abuse-cost branch 2 times, most recently from 210f9a1 to 86a2067 Compare February 4, 2026 23:48
@@ -205,26 +205,15 @@ export async function POST(request: NextRequest): Promise<NextResponseType<unkno
);
console.debug(`Routing request to ${provider.id}`);

// Fire-and-forget abuse classification as early as possible
void classifyAbuse(request, requestBodyParsed, {
// Start abuse classification early (non-blocking) - we'll await it before creating usage context
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

SUGGESTION: Misleading comment about when abuse classification is awaited

classifyPromise is awaited after the upstream response (via Promise.race([...])), not before creating usageContext. Updating this comment avoids confusion for future maintainers.

Suggested change
// Start abuse classification early (non-blocking) - we'll await it before creating usage context
// Start abuse classification early (non-blocking) - we'll await it later to attach request_id to usageContext

// Await abuse classification (with timeout) to get request_id for cost tracking correlation
const classifyResult = await Promise.race([
classifyPromise,
new Promise<null>(resolve => setTimeout(() => resolve(null), 2000)),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WARNING: setTimeout in Promise.race isn't cleared when classifyPromise resolves first

If classification returns quickly, the setTimeout is still scheduled and will fire later. In serverless/edge runtimes this can keep extra work queued and may extend the invocation unnecessarily. Consider storing the timeout id and clearTimeout when classifyPromise settles (or use an abortable timeout helper).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ah this is true. Let me fix this

@@ -392,6 +381,25 @@ export async function POST(request: NextRequest): Promise<NextResponseType<unkno

const clonedReponse = response.clone(); // reading from body is side-effectful

// Await abuse classification (with timeout) to get request_id for cost tracking correlation
const classifyResult = await Promise.race([
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WARNING: Awaiting abuse classification adds up to 2s latency on the critical path

await Promise.race([...]) runs before accountForMicrodollarUsage(...) and before returning the proxied response, so each request can incur up to ~2s extra tail latency. Consider moving the timeout-await into the fire-and-forget usage accounting path (e.g., have accountForMicrodollarUsage await classifyPromise internally), so client response timing isn't affected while still capturing request_id when available.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@jrf0110 i guess this is true? could we fire this after so that we can get request_id eventually (if ever) and then call countAndStoreUsage ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The p99's for the requests to the abuse service are way less than 2s, so in practice, the classify response is going to be resolved already. I didn't want to have to pass in the promise into accountForMicrodollarUsage to resolve it there as it seemed like that would be some mega-mixed concerns programming.

Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Oh and to clarify, the abuse classify promise is resolved after the await to the openrouter. If we look at requests to the completions endpoint:

Image

It's pretty clear that 99.999% of the time, the abuse promise will resolve prior to the openrouter response coming back

Copy link
Copy Markdown
Contributor

@pandemicsyn pandemicsyn left a comment

Choose a reason for hiding this comment

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

One inline where kilo flagged something. But pre-approving because im not super confident.

Add functionality to report actual request costs back to the abuse
service. This enables the abuse service to track real-time spend and
trigger heuristics like free tier exhaustion.
Remove the ABUSE_SERVICE_SECRET and its associated X-Service-Secret
header. Authentication is now managed via Cloudflare Access headers.
Comment thread src/app/api/openrouter/[...path]/route.ts
@jrf0110 jrf0110 merged commit 16a0cc7 into main Feb 5, 2026
11 checks passed
@jrf0110 jrf0110 deleted the abuse-cost branch February 5, 2026 16:17
jrf0110 added a commit that referenced this pull request Apr 22, 2026
Executed via three sub-agent runs:

1. Browse (Flow 2) — sub-agent verified browse-direct matches upstream count
2. Full lifecycle (Flows 3, 4, 6, 7) — sub-agent verified post -> claim -> done
   -> accept on item w-870be07fbc through PRs #8-11. Stamp s-35e8a923...
   issued with author=jrf0110, subject=jfawcett.
3. Branches (Flows 5, 8, 9) — sub-agent verified unclaim (item
   w-68aa4ab1dd, PR #14), reject (w-d2cf6acf6a, PR #18), and close
   (w-89e6720ca4, PR #22) on fresh items.

Only Flow 10 (disconnect) remains — it is a pure gastown operation and
doesn't touch upstream DoltHub state, so lower priority.

Total: 19 PRs merged, 4 rigs in play (jfawcett contributor, jrf0110
maintainer), full lifecycle graph exercised.
Sign up for free to 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