Skip to content
This repository was archived by the owner on Apr 7, 2026. It is now read-only.

fix: debit credits in SSE streaming path#176

Merged
TSavo merged 1 commit intomainfrom
fix/sse-streaming-debit
Mar 29, 2026
Merged

fix: debit credits in SSE streaming path#176
TSavo merged 1 commit intomainfrom
fix/sse-streaming-debit

Conversation

@TSavo
Copy link
Copy Markdown
Contributor

@TSavo TSavo commented Mar 29, 2026

SSE streams were metering but never debiting credits. Added debitCredits call in streaming.ts flush().

Note

Fix credit debit in SSE streaming path on stream completion

The SSE streaming path was not debiting tenant credits after stream completion. Adds a fire-and-forget call to debitCredits in proxySSEStream using the accumulated stream cost and default margin after metering data is emitted.

Risk: Credit deduction now occurs on every SSE stream completion where it was previously skipped entirely.

Macroscope summarized 8518805.

Summary by CodeRabbit

  • Bug Fixes
    • Improved credit management in streaming operations to ensure proper tracking and deduction after stream completion.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Sorry @TSavo, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

Warning

Rate limit exceeded

@TSavo has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 12 minutes and 29 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 12 minutes and 29 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7a167a0f-9b92-4fa1-a090-4cbe537d3040

📥 Commits

Reviewing files that changed from the base of the PR and between a1a45c7 and 8518805.

📒 Files selected for processing (1)
  • src/gateway/streaming.ts
📝 Walkthrough

Walkthrough

src/gateway/streaming.ts now imports the debitCredits function and invokes it in a fire-and-forget manner within the TransformStream's flush() method upon SSE stream completion, extending post-stream side effects to include credit debiting.

Changes

Cohort / File(s) Summary
Credit Debiting Integration
src/gateway/streaming.ts
Added import of debitCredits and integrated fire-and-forget invocation in TransformStream.flush() to debit credits after SSE stream completion, following the existing meter emission and completion logging.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 A stream completes, the credits flow,
Debiting silently, swift and low,
No wait, no fuss, just set it free,
The tunnel charges what it must be! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding credit debiting functionality to the SSE streaming path, which directly aligns with the PR's objective to fix missing credit debit behavior in streaming.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sse-streaming-debit

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.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 29, 2026

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 87.43% 2080 / 2379
🔵 Statements 87.07% 2197 / 2523
🔵 Functions 89.13% 517 / 580
🔵 Branches 73.63% 1075 / 1460
File CoverageNo changed files found.
Generated in workflow #549 for commit 8518805 by the Vitest Coverage Report Action

@TSavo TSavo force-pushed the fix/sse-streaming-debit branch from a1a45c7 to 8518805 Compare March 29, 2026 03:44
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 29, 2026

Greptile Summary

This PR fixes a billing gap where SSE streaming responses were metering usage (emitting meter events) but never actually debiting the tenant's credit balance. The fix adds a debitCredits call in the TransformStream.flush() handler in streaming.ts, which runs after all SSE chunks have been processed and accumulatedCost is finalized.

The change is correct in intent and the arguments (accumulatedCost in USD, deps.defaultMargin, capability, provider) are consistent with how debitCredits is called throughout the non-streaming paths in proxy.ts. ProxyDeps satisfies CreditGateDeps structurally, so the TypeScript types align.

Key concern:

  • The debitCredits call is unawaited inside an async flush() handler. Because flush() resolves (and the TransformStream signals completion/stream close) before the debit Promise settles, any serverless or edge runtime that terminates the process when the HTTP response commits will silently drop the ledger write — the exact billing gap this PR is meant to close. Adding await costs nothing since flush() is already async and debitCredits handles its own errors internally.

Confidence Score: 3/5

Not safe to merge as-is — the unawaited debit in flush() can silently drop credits in edge/serverless runtimes, leaving the billing gap partially open.

The fix addresses the right problem (missing debit on SSE path) but introduces a reliability risk: debitCredits is not awaited in the async flush() handler. The TransformStream resolves flush — closing the response stream — before the ledger write completes. In any runtime that reclaims the process context after the response is committed (Cloudflare Workers, Lambda, etc.), the pending Promise is dropped and the debit never lands. The one-line fix (await debitCredits(...)) is trivial and should be applied before merge.

src/gateway/streaming.ts — the flush() handler needs await on the debitCredits call.

Important Files Changed

Filename Overview
src/gateway/streaming.ts Adds the missing debitCredits call in flush() to fix SSE billing, but the call is fire-and-forget (unawaited) in an async context — the stream closes before the ledger write completes, which can silently drop debits in edge/serverless runtimes.

Sequence Diagram

sequenceDiagram
    participant Client
    participant TransformStream
    participant flush as flush()
    participant debitCredits
    participant Ledger

    Client->>TransformStream: SSE chunks (transform x N)
    TransformStream->>TransformStream: accumulate cost
    TransformStream->>flush: [DONE] received → flush()
    flush->>flush: meter.emit(cost, charge)
    flush->>debitCredits: debitCredits(...) [NOT awaited ⚠️]
    flush-->>TransformStream: flush() resolves (stream closes)
    Note over Client,TransformStream: Response already committed
    debitCredits->>Ledger: creditLedger.debit() [may be dropped!]
    Ledger-->>debitCredits: ok
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/gateway/streaming.ts
Line: 124-125

Comment:
**Unawaited debit in async `flush()` — credits can be silently dropped**

`debitCredits` returns a `Promise<void>` that is not awaited. Because `flush()` is already `async`, the TransformStream resolves its flush contract (closing the stream) before the debit finishes executing. In serverless or edge runtimes (Cloudflare Workers, Lambda), the isolate may be terminated as soon as the HTTP response is committed, silently dropping the pending debit — meaning the tenant gets the tokens for free.

The non-streaming path shares this pattern, but the risk is higher here: the streaming response _closes_ at the moment `flush()` settles, which is before `debitCredits` awaits the ledger. Since `flush()` is async and the debit already handles its own errors internally, awaiting it adds zero performance cost and guarantees billing integrity before the stream terminates.

```suggestion
      // Debit credits — await to guarantee the ledger write completes before the stream closes
      await debitCredits(deps, tenant.id, accumulatedCost, deps.defaultMargin, capability, provider);
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix: debit credits in SSE streaming path..." | Re-trigger Greptile

Comment thread src/gateway/streaming.ts
Comment on lines +124 to +125
// Debit credits (fire-and-forget, same as non-streaming path)
debitCredits(deps, tenant.id, accumulatedCost, deps.defaultMargin, capability, provider);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Unawaited debit in async flush() — credits can be silently dropped

debitCredits returns a Promise<void> that is not awaited. Because flush() is already async, the TransformStream resolves its flush contract (closing the stream) before the debit finishes executing. In serverless or edge runtimes (Cloudflare Workers, Lambda), the isolate may be terminated as soon as the HTTP response is committed, silently dropping the pending debit — meaning the tenant gets the tokens for free.

The non-streaming path shares this pattern, but the risk is higher here: the streaming response closes at the moment flush() settles, which is before debitCredits awaits the ledger. Since flush() is async and the debit already handles its own errors internally, awaiting it adds zero performance cost and guarantees billing integrity before the stream terminates.

Suggested change
// Debit credits (fire-and-forget, same as non-streaming path)
debitCredits(deps, tenant.id, accumulatedCost, deps.defaultMargin, capability, provider);
// Debit credits — await to guarantee the ledger write completes before the stream closes
await debitCredits(deps, tenant.id, accumulatedCost, deps.defaultMargin, capability, provider);
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/gateway/streaming.ts
Line: 124-125

Comment:
**Unawaited debit in async `flush()` — credits can be silently dropped**

`debitCredits` returns a `Promise<void>` that is not awaited. Because `flush()` is already `async`, the TransformStream resolves its flush contract (closing the stream) before the debit finishes executing. In serverless or edge runtimes (Cloudflare Workers, Lambda), the isolate may be terminated as soon as the HTTP response is committed, silently dropping the pending debit — meaning the tenant gets the tokens for free.

The non-streaming path shares this pattern, but the risk is higher here: the streaming response _closes_ at the moment `flush()` settles, which is before `debitCredits` awaits the ledger. Since `flush()` is async and the debit already handles its own errors internally, awaiting it adds zero performance cost and guarantees billing integrity before the stream terminates.

```suggestion
      // Debit credits — await to guarantee the ledger write completes before the stream closes
      await debitCredits(deps, tenant.id, accumulatedCost, deps.defaultMargin, capability, provider);
```

How can I resolve this? If you propose a fix, please make it concise.

@TSavo TSavo added this pull request to the merge queue Mar 29, 2026
Merged via the queue into main with commit 8e90678 Mar 29, 2026
10 checks passed
@TSavo TSavo deleted the fix/sse-streaming-debit branch March 29, 2026 03:49
@github-actions
Copy link
Copy Markdown

🎉 This PR is included in version 1.75.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant