Staging#513
Conversation
Brainstormed spec for the finalized per-action credit pricing table — metered live-voice/agent-loop billing, new embedding/summarize costs, and the multi-row FIFO port needed in cloud-agent to support >1-credit spends.
- generateReply: spend lives in chargeForReply(), which has no access to tools; grounded/standard split needs a signature plumb, not a one-line change at the spend site. - cloud-agent spendCredit ripple: correct the caller inventory — tools/browserAction.ts is the real browser_action spend site (was omitted); wsBrowserAgentHandler.ts is not a caller (was wrongly listed).
11 tasks, dependency-ordered: cloud-agent multi-row credit allocator + ripple, per-loop agent-turn billing with hard stop at 5, live-voice 5-credit tick + gate, client gate, functions callable cost bumps and two new billing sites (summarizeText, generateEmbedding), generateReply grounded/standard split, docs, and consumer copy.
Meter agent turns per internal tool-loop on HTTP and WebSocket with a zero-balance pre-flight gate, raise live voice to 5 credits/tick, port the multi-row FIFO allocator, bill summarize/embedding, and split generateReply into grounded (3) vs standard (1) pricing. Co-authored-by: Cursor <cursoragent@cursor.com>
- Guard WebSocket sends so a failed send (client already disconnected) can no longer be mistaken for an ADK processing error and trigger an unwarranted credit refund. - Emit onToolEnd for the active tool before breaking the loop on abort, insufficient credits, or hitting MAX_LOOP_ITERATIONS, so consumers don't get stuck showing a tool as still running. - Run the zero-credit preflight check before queryWikiContext so 402 responses skip the embedding/DB work. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
feat(billing): July 2026 credit economy repricing
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 2 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughReworks credit handling to use allocation arrays and shared agent billing helpers, updates live-voice gating to 5 credits, changes per-action pricing in Functions, and refreshes related tests, client copy, and billing docs. ChangesCredit economy repricing
Estimated code review effort: 4 (Complex) | ~75 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant Index
participant assertAgentTurnCredits
participant runAgentReal
participant consumeAgentEvents
participant CreditService
Client->>Index: POST /agent/run
Index->>assertAgentTurnCredits: verify balance
assertAgentTurnCredits-->>Index: ok or insufficient
Index->>runAgentReal: creditService + ADK events
runAgentReal->>consumeAgentEvents: consume stream
consumeAgentEvents->>CreditService: spendCredit/refundCredit
consumeAgentEvents-->>runAgentReal: reply/toolCalls/metadata
runAgentReal-->>Client: response payload
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (6)
cloud-agent/src/tools/browserAction.test.ts (1)
29-29: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winCapture and assert refund allocations in these mocks.
These tests now model allocation-based spends, but the refund mocks still accept no args. The refund-path tests should assert the exact
{ transactionId, amount }[]is passed through.Also applies to: 158-158, 196-196
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cloud-agent/src/tools/browserAction.test.ts` at line 29, The refund mocks in browserAction.test.ts still ignore the allocation payload, so update the creditService.refundCredit mocks to accept and assert the exact { transactionId, amount }[] argument passed through by the refund flow. Adjust the affected test cases around the browserAction refund paths to verify the expected allocation array in the mock implementation, using the creditService object and its spendCredit/refundCredit methods as the main symbols to locate the changes.cloud-agent/src/handlers/schedulerTriggerHandler.test.ts (1)
27-28: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winAssert the refund allocation payload, not only the call count.
The updated mock returns allocations, but
refundCreditstill ignores its args. Existing timeout/setup refund tests would pass even if the handler refunded[]or the wrong transaction.Also applies to: 67-68
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cloud-agent/src/handlers/schedulerTriggerHandler.test.ts` around lines 27 - 28, The refund tests in schedulerTriggerHandler.test.ts only verify that refundCredit was called, so they can miss incorrect refund payloads. Update the affected tests to assert the actual arguments passed to refundCredit, especially the allocations returned by the updated spendCredit mock, using the relevant handler/test setup symbols so the test fails if the handler refunds an empty or wrong transaction list.cloud-agent/src/handlers/wsLiveAgentHandler.ts (1)
315-320: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider a shared constant for the live-voice credit rate.
The connect-gate threshold (
balance < 5, Line 316) and the per-tick spend amount (cs.spendCredit(userId!, 5), Line 339) are both hardcoded literals that happen to match today. They express related-but-distinct concepts (minimum balance to start vs. cost per tick) and could silently diverge in a future edit since nothing ties them together.♻️ Suggested refactor
+const LIVE_VOICE_MIN_CREDITS_TO_CONNECT = 5 +const LIVE_VOICE_CREDITS_PER_TICK = 5 + ... - if (balance < 5) { + if (balance < LIVE_VOICE_MIN_CREDITS_TO_CONNECT) { ... - await cs.spendCredit(userId!, 5) + await cs.spendCredit(userId!, LIVE_VOICE_CREDITS_PER_TICK)Also applies to: 339-339
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cloud-agent/src/handlers/wsLiveAgentHandler.ts` around lines 315 - 320, The live-voice credit threshold and per-tick spend are hardcoded separately in wsLiveAgentHandler, which can drift apart. Introduce shared named constants for the minimum balance required in the connect gate and the credit cost consumed in the tick loop, and use them in both the balance check and cs.spendCredit call so the relationship is explicit and maintained in one place.docs/superpowers/specs/2026-07-01-credit-economy-repricing-design.md (1)
55-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winEscape the unescaped
||inside thegenerateReplytable row.The literal
`!tools || tools.length === 0`inside this table cell contains an unescaped double-pipe, which Markdown table parsers split on regardless of the surrounding backticks — this is what's producing the "Actual: 5" column-count warning from markdownlint and will render as a broken table.📝 Suggested fix
-computed at the call site as `!tools || tools.length === 0` — and spend `isGrounded ? 3 : 1`. +computed at the call site as `!tools \|\| tools.length === 0` — and spend `isGrounded ? 3 : 1`.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/superpowers/specs/2026-07-01-credit-economy-repricing-design.md` at line 55, The generateReply spec table row contains an unescaped double-pipe in the inline expression used to describe the grounded check, which breaks Markdown table parsing and causes the column-count warning. Update the text in the generateReply row so the `||` inside the `chargeForReply`/`isGrounded` description is escaped or rewritten in a table-safe form, while keeping the existing references to `generateReply.ts`, `buildToolsForRequest`, and `chargeForReply` intact.Source: Linters/SAST tools
functions/src/generateImage.ts (1)
123-132: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winAvoid duplicating the credit cost literal across spend, log, and response.
2is hardcoded independently inchargeForImage(Line 127), the success log (Line 351), and the returnedcreditsSpent(Line 366). If the price changes again, these can drift out of sync and misreport the actual amount charged to the user.Derive the displayed/logged value from
spendAllocations(sum ofamount) or a single named constant, rather than repeating the literal.♻️ Proposed fix using a single source of truth
+const IMAGE_GENERATION_CREDIT_COST = 2; + async function chargeForImage( userId: string, credits: Pick<typeof creditService, 'spendCredits'> ): Promise<CreditSpendAllocation[]> { - const spendAllocations = await credits.spendCredits(userId, 2); + const spendAllocations = await credits.spendCredits(userId, IMAGE_GENERATION_CREDIT_COST); if (spendAllocations === null) { throw new HttpsError("failed-precondition", "Insufficient credits."); } return spendAllocations; }+ const creditsSpent = spendAllocations.reduce((sum, a) => sum + a.amount, 0); logger.info("generateImage succeeded", { firebaseUid: request.auth.uid, userId: user.id, - creditsSpent: 2, + creditsSpent, ... }); return { imageBase64: imageResult.imageBase64, mimeType: normalizedMimeType, - creditsSpent: 2, + creditsSpent, ... };Also applies to: 348-351, 363-366
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@functions/src/generateImage.ts` around lines 123 - 132, The image credit charge amount is hardcoded in multiple places, so the spend call, success log, and returned creditsSpent can drift out of sync; update chargeForImage and the related success/response handling to use a single source of truth from the spendAllocations result (sum of amount) or a shared constant, and reuse that derived value in the log and returned payload instead of repeating the literal.functions/src/summarizeText.test.ts (1)
127-162: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winAssert the refund receives the original allocation.
These tests would still pass if the handler refunded the wrong user or an empty allocation. Capture
userIdandallocationsso the new allocation refund contract is covered.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@functions/src/summarizeText.test.ts` around lines 127 - 162, The refund-path tests in summarizeTextHandler only verify that refundCreditImpl is called, so they miss whether the original allocation is actually passed through. Update the tests around summarizeTextHandler to capture the userId and allocations from the initial credit allocation and assert refundCreditImpl receives those same values when generateSummary fails or returns an empty result. Use the existing summarizeTextHandler, buildAuth, and makeOptions helpers to verify the refund contract end-to-end.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cloud-agent/src/handlers/schedulerTriggerHandler.ts`:
- Around line 215-216: The refund rollback path in schedulerTriggerHandler is
swallowing failures, so update the refund handling around refundCredit in the
scheduler trigger flow to surface errors instead of relying on the inaccurate
“/* logged */” comment. In the try/catch blocks near the allocation rollback
logic, catch the error, log it with enough context (such as userId and
allocations), and make sure the failure is recorded consistently in the same
rollback path used by the other occurrence in this handler.
In `@cloud-agent/src/handlers/wsAgentHandler.ts`:
- Around line 64-73: The auth flow in wsAgentHandler still bypasses the new
safeSend wrapper, so disconnected clients can trigger raw ws.send exceptions
during token verification or DB lookup. Update the auth failure/success response
paths in the handler to use safeSend instead of ws.send, especially in the
branches covered by the auth logic around the WebSocket session setup, so close
handling can continue normally without throwing.
In `@cloud-agent/src/services/agentEventLoop.ts`:
- Around line 116-119: The tool lifecycle handling in agentEventLoop is missing
an onToolEnd for the previously active tool when a new function call with a
different name arrives, which leaves earlier tools active in WebSocket clients.
Update the logic around the consecutive function-call handling in agentEventLoop
so that before calling hooks.onToolStart for a new fc.name, you first end the
currently tracked lastToolName via hooks.onToolEnd and then reset/update
lastToolName; apply the same transition handling in the other function-call path
referenced by the diff so every started tool is always ended before switching to
the next one.
In `@cloud-agent/src/services/creditService.ts`:
- Around line 17-18: Validate credit amounts before any balance mutation in
CreditService: add input checks in spendCredit and refundCredit to reject zero,
negative, fractional, NaN, or non-finite amounts, and ensure allocation.amount
values used by refundCredit are sane before updating credits. Also harden the
internal allocation handling path in CreditService methods (including the
allocation creation and refund logic around the referenced blocks) so duplicate
or malformed allocations cannot be applied twice or corrupt balances, returning
a clear validation error instead of proceeding.
In `@cloud-agent/src/tools/browserAction.ts`:
- Around line 111-112: The refundCredit calls in browserAction’s allocation
cleanup are swallowing failures without any operational trail. Update the refund
paths in browserAction to catch the thrown error and log a clear failure message
with the relevant allocation/user context before continuing, using the existing
deps.creditService.refundCredit flow so reconciliation can identify stranded
refunds. Apply the same fix to both refund locations in this file, keeping the
logging consistent and tied to the refund attempt.
In `@functions/src/summarizeText.ts`:
- Around line 166-176: Move the `getSummaryGenerator()` initialization into the
same refund-protected `try` block in `summarizeText` so any synchronous setup
failure is also covered by the refund path. Keep the `spendCredits` call before
the block, but wrap both generator creation and the subsequent
`generateSummary(buildPrompt(...))` call inside the existing `try/catch` so
`spendAllocations` can be refunded if initialization throws.
- Around line 160-164: The user bootstrap in summarizeText.ts is passing an
empty string into getOrCreateUserByFirebaseIdentity when decoded.email is
missing, which should be rejected earlier. Update the logic around
request.auth.uid and users.getOrCreateUserByFirebaseIdentity to require a
normalized, non-empty email from decoded.email before calling the repository,
and fail fast with a clear error or return path when the Firebase token has no
usable email. Keep the guard close to the existing email normalization so the
user creation flow only proceeds with valid identity data.
---
Nitpick comments:
In `@cloud-agent/src/handlers/schedulerTriggerHandler.test.ts`:
- Around line 27-28: The refund tests in schedulerTriggerHandler.test.ts only
verify that refundCredit was called, so they can miss incorrect refund payloads.
Update the affected tests to assert the actual arguments passed to refundCredit,
especially the allocations returned by the updated spendCredit mock, using the
relevant handler/test setup symbols so the test fails if the handler refunds an
empty or wrong transaction list.
In `@cloud-agent/src/handlers/wsLiveAgentHandler.ts`:
- Around line 315-320: The live-voice credit threshold and per-tick spend are
hardcoded separately in wsLiveAgentHandler, which can drift apart. Introduce
shared named constants for the minimum balance required in the connect gate and
the credit cost consumed in the tick loop, and use them in both the balance
check and cs.spendCredit call so the relationship is explicit and maintained in
one place.
In `@cloud-agent/src/tools/browserAction.test.ts`:
- Line 29: The refund mocks in browserAction.test.ts still ignore the allocation
payload, so update the creditService.refundCredit mocks to accept and assert the
exact { transactionId, amount }[] argument passed through by the refund flow.
Adjust the affected test cases around the browserAction refund paths to verify
the expected allocation array in the mock implementation, using the
creditService object and its spendCredit/refundCredit methods as the main
symbols to locate the changes.
In `@docs/superpowers/specs/2026-07-01-credit-economy-repricing-design.md`:
- Line 55: The generateReply spec table row contains an unescaped double-pipe in
the inline expression used to describe the grounded check, which breaks Markdown
table parsing and causes the column-count warning. Update the text in the
generateReply row so the `||` inside the `chargeForReply`/`isGrounded`
description is escaped or rewritten in a table-safe form, while keeping the
existing references to `generateReply.ts`, `buildToolsForRequest`, and
`chargeForReply` intact.
In `@functions/src/generateImage.ts`:
- Around line 123-132: The image credit charge amount is hardcoded in multiple
places, so the spend call, success log, and returned creditsSpent can drift out
of sync; update chargeForImage and the related success/response handling to use
a single source of truth from the spendAllocations result (sum of amount) or a
shared constant, and reuse that derived value in the log and returned payload
instead of repeating the literal.
In `@functions/src/summarizeText.test.ts`:
- Around line 127-162: The refund-path tests in summarizeTextHandler only verify
that refundCreditImpl is called, so they miss whether the original allocation is
actually passed through. Update the tests around summarizeTextHandler to capture
the userId and allocations from the initial credit allocation and assert
refundCreditImpl receives those same values when generateSummary fails or
returns an empty result. Use the existing summarizeTextHandler, buildAuth, and
makeOptions helpers to verify the refund contract end-to-end.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 8f705a29-fe8d-4eaf-821a-71e3c7c1521c
📒 Files selected for processing (38)
__tests__/useLiveVoiceChat.test.tsxapp/index.web.tsxcloud-agent/src/agent.live.test.tscloud-agent/src/handlers/schedulerTriggerHandler.test.tscloud-agent/src/handlers/schedulerTriggerHandler.tscloud-agent/src/handlers/wsAgentHandler.test.tscloud-agent/src/handlers/wsAgentHandler.tscloud-agent/src/handlers/wsLiveAgentHandler.test.tscloud-agent/src/handlers/wsLiveAgentHandler.tscloud-agent/src/index.test.tscloud-agent/src/index.tscloud-agent/src/integration.test.tscloud-agent/src/services/agentEventLoop.test.tscloud-agent/src/services/agentEventLoop.tscloud-agent/src/services/creditService.test.tscloud-agent/src/services/creditService.tscloud-agent/src/tools/browserAction.test.tscloud-agent/src/tools/browserAction.tsdocs/billing-and-credits.mddocs/superpowers/plans/2026-06-29-mv3-bridge-phase1.mddocs/superpowers/plans/2026-06-29-phase2-browser-bridge-stateful-actions.mddocs/superpowers/plans/2026-06-29-phase3-proactive-scheduler.mddocs/superpowers/plans/2026-06-30-cws-extension-readiness.mddocs/superpowers/plans/2026-07-01-billing-hardening.mddocs/superpowers/plans/2026-07-01-credit-improvements.mddocs/superpowers/plans/2026-07-01-live-voice-credit-reconciliation.mddocs/superpowers/specs/2026-07-01-credit-economy-repricing-design.mdfunctions/src/convertDocumentText.tsfunctions/src/generateEmbedding.test.tsfunctions/src/generateEmbedding.tsfunctions/src/generateImage.test.tsfunctions/src/generateImage.tsfunctions/src/generateReply.test.tsfunctions/src/generateReply.tsfunctions/src/summarizeText.test.tsfunctions/src/summarizeText.tssrc/components/LandingPage/FeaturesSection.tsxsrc/hooks/useLiveVoiceChat.ts
💤 Files with no reviewable changes (5)
- docs/superpowers/plans/2026-06-29-phase2-browser-bridge-stateful-actions.md
- docs/superpowers/plans/2026-07-01-live-voice-credit-reconciliation.md
- docs/superpowers/plans/2026-06-30-cws-extension-readiness.md
- docs/superpowers/plans/2026-07-01-credit-improvements.md
- docs/superpowers/plans/2026-06-29-phase3-proactive-scheduler.md
- Log (don't silently swallow) refund rollback failures in schedulerTriggerHandler and browserAction - Use safeSend in the WS auth path so a closed socket can't throw - End the previously active tool before starting a differently-named one in the ADK event loop - Validate credit spend/refund amounts are positive safe integers - Guard summarizeText against a missing Firebase email and move generator init inside the refund-protected try block Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
/fix-pr follow-upCommit: Review resolution
Verification
|
There was a problem hiding this comment.
Pull request overview
Implements the July 2026 credit-economy repricing across the client, Firebase Functions, and cloud-agent so that billed actions align with the new per-action costs and the cloud-agent’s text/voice paths use metered billing with updated connect gates and documentation.
Changes:
- Updated client gates/copy for live voice to require ≥ 5 credits and reflect 5 credits/minute pricing.
- Added/adjusted Functions-side billing for summarizeText, embeddings, grounded vs standard generateReply, image generation, and document conversion (with test updates).
- Refactored cloud-agent billing to support multi-row spends, per-loop metering (max 5) for agent turns, and 5-credits/tick live-voice billing (with new event-loop utility + tests), plus refreshed billing docs and added the repricing design spec.
Reviewed changes
Copilot reviewed 36 out of 38 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/hooks/useLiveVoiceChat.ts | Raises live-voice minimum credits gate to 5. |
| src/components/LandingPage/FeaturesSection.tsx | Updates landing-page voice pricing copy to 5 credits/minute. |
| functions/src/summarizeText.ts | Adds 1-credit billing with refund-on-failure for summarizeText. |
| functions/src/summarizeText.test.ts | Adds spend/refund test coverage for summarizeText billing. |
| functions/src/generateReply.ts | Charges 3 credits for grounded default vs 1 for explicit-tools path. |
| functions/src/generateReply.test.ts | Updates/expands tests to cover grounded vs standard costs. |
| functions/src/generateImage.ts | Raises image generation cost to 2 credits and updates reporting. |
| functions/src/generateImage.test.ts | Updates tests to assert 2-credit image billing and refunds. |
| functions/src/generateEmbedding.ts | Adds embedding billing (Math.ceil(chars/50k)) + refund-on-failure. |
| functions/src/generateEmbedding.test.ts | Adds billing/refund unit tests and cost function tests. |
| functions/src/convertDocumentText.ts | Raises document conversion cost from 1 to 2 credits. |
| docs/superpowers/specs/2026-07-01-credit-economy-repricing-design.md | Adds the repricing design spec documenting the new costs and rollout. |
| docs/superpowers/plans/2026-07-01-live-voice-credit-reconciliation.md | Removes completed/obsolete implementation plan doc. |
| docs/superpowers/plans/2026-07-01-credit-improvements.md | Removes completed/obsolete implementation plan doc. |
| docs/superpowers/plans/2026-06-30-cws-extension-readiness.md | Removes completed/obsolete implementation plan doc. |
| docs/superpowers/plans/2026-06-29-phase3-proactive-scheduler.md | Removes completed/obsolete implementation plan doc. |
| docs/billing-and-credits.md | Updates consumption table for new per-action pricing and gates. |
| cloud-agent/src/tools/browserAction.ts | Updates browser_action billing/refund to allocation-array spend API. |
| cloud-agent/src/tools/browserAction.test.ts | Updates mocks/assertions for allocation-based spend/refund. |
| cloud-agent/src/services/creditService.ts | Implements multi-row FIFO spending with variable-amount support. |
| cloud-agent/src/services/creditService.test.ts | Adds tests for multi-row spend/refund and default-amount behavior. |
| cloud-agent/src/services/agentEventLoop.ts | Adds shared per-loop credit metering + graceful degrade logic (max 5). |
| cloud-agent/src/services/agentEventLoop.test.ts | Adds unit tests for per-loop billing, caps, refunds, and preflight gating. |
| cloud-agent/src/integration.test.ts | Updates creditService mocks for allocation-array spend/refund API. |
| cloud-agent/src/index.ts | Switches HTTP /agent/run to preflight gate + per-loop billing via shared event loop. |
| cloud-agent/src/index.test.ts | Updates tests for new preflight behavior and allocation-based credit mocks. |
| cloud-agent/src/handlers/wsLiveAgentHandler.ts | Raises live-voice connect gate to ≥ 5 and charges 5 credits/tick. |
| cloud-agent/src/handlers/wsLiveAgentHandler.test.ts | Adds/updates tests for new ≥ 5 gate and updated spend behavior. |
| cloud-agent/src/handlers/wsAgentHandler.ts | Switches WS agent path to preflight gate + per-loop billing + safeSend. |
| cloud-agent/src/handlers/wsAgentHandler.test.ts | Updates insufficient-credit test to reflect preflight balance gating. |
| cloud-agent/src/handlers/schedulerTriggerHandler.ts | Updates scheduler trigger billing to allocation-array spend/refund API. |
| cloud-agent/src/handlers/schedulerTriggerHandler.test.ts | Updates scheduler-trigger credit mocks to allocation-array shape. |
| cloud-agent/src/agent.live.test.ts | Updates live agent test wiring to supply creditService spend/refund hooks. |
| app/index.web.tsx | Updates web meta description voice pricing to 5 credits/minute. |
| tests/useLiveVoiceChat.test.tsx | Adds test ensuring client gate blocks starting call below 5 credits. |
Comments suppressed due to low confidence (1)
cloud-agent/src/index.ts:231
- The HTTP /agent/run handler performs bulkInsertUnsynced writes before the pre-flight 402 gate (assertAgentTurnCredits). This means a user with 0 credits can still trigger DB writes before being rejected, which is inconsistent with the WS path (wsAgentHandler) and undermines the intent of the pre-flight credit gate.
Move assertAgentTurnCredits above the bulkInsertUnsynced block so insufficient-credit requests fail fast before any non-essential writes.
if (unsyncedHistory.length > 0) {
try {
await bulkInsertUnsynced(db, userId, characterId, unsyncedHistory, embedText)
} catch (err) {
// Swallow sync errors so the agent can still respond (matches Firebase generateReply behavior)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Empty-string email was passed straight into getOrCreateUserByFirebaseIdentity, which throws an opaque internal error for email-less auth tokens instead of a clear failed-precondition. Same guard already applied to summarizeText. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Pushed f9c6da5. Fixed missing-email guard in generateEmbedding.ts; other two findings |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
cloud-agent/src/services/agentEventLoop.ts (2)
185-193: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winEnd the active tool when the stream errors.
If an error is thrown after
onToolStartand before a non-function event, the catch path refunds and rethrows without emittingonToolEnd, leaving WebSocket clients with stale active-tool state.🧹 Proposed fix
} catch (err) { + try { + endActiveTool() + } catch (hookErr) { + console.error('onToolEnd cleanup failed:', hookErr) + } if (spentAllocations.length > 0) { try { await creditService.refundCredit(userId, spentAllocations)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cloud-agent/src/services/agentEventLoop.ts` around lines 185 - 193, The error path in agentEventLoop’s catch block refunds credits and rethrows, but never closes the currently active tool after onToolStart. Update the catch handling around the stream processing in agentEventLoop so that any started tool is ended before rethrowing when a stream error occurs, using the existing onToolEnd flow/state that is paired with onToolStart. Make sure this cleanup runs even when refundCredit fails, so WebSocket clients don’t retain stale active-tool state.
110-128: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winBill every function-call part, not just the containing event.
This loop records each
fc.name, but Line 127 spends only once for the whole event. If an event contains multiplefunctionCallparts, those tool calls are undercharged despite being tracked individually.💳 Proposed fix
if (eventHasFunctionCall(event)) { + let billableFunctionCalls = 0 for (const part of event.content!.parts!) { if ('functionCall' in part) { const fc = (part as { functionCall?: { name?: string } }).functionCall if (fc?.name) { + billableFunctionCalls += 1 toolCalls.push(fc.name) if (fc.name !== lastToolName) { endActiveTool() hooks?.onToolStart?.(fc.name) lastToolName = fc.name @@ loopCount += 1 try { - const allocations = await creditService.spendCredit(userId) - spentAllocations.push(...allocations) + if (billableFunctionCalls > 0) { + const allocations = await creditService.spendCredit(userId, billableFunctionCalls) + spentAllocations.push(...allocations) + } } catch (creditErr) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cloud-agent/src/services/agentEventLoop.ts` around lines 110 - 128, The billing in agentEventLoop’s function-call handling only charges once per event even though multiple functionCall parts can be recorded in toolCalls. Update the event processing loop in eventHasFunctionCall so each part with a functionCall and valid fc.name is billed individually, while still preserving the existing onToolStart/endActiveTool behavior and lastToolName tracking. Use the existing creditService.spendCredit and spentAllocations flow, but move the charge inside the per-part handling so every function-call part is accounted for.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@cloud-agent/src/services/agentEventLoop.ts`:
- Around line 185-193: The error path in agentEventLoop’s catch block refunds
credits and rethrows, but never closes the currently active tool after
onToolStart. Update the catch handling around the stream processing in
agentEventLoop so that any started tool is ended before rethrowing when a stream
error occurs, using the existing onToolEnd flow/state that is paired with
onToolStart. Make sure this cleanup runs even when refundCredit fails, so
WebSocket clients don’t retain stale active-tool state.
- Around line 110-128: The billing in agentEventLoop’s function-call handling
only charges once per event even though multiple functionCall parts can be
recorded in toolCalls. Update the event processing loop in eventHasFunctionCall
so each part with a functionCall and valid fc.name is billed individually, while
still preserving the existing onToolStart/endActiveTool behavior and
lastToolName tracking. Use the existing creditService.spendCredit and
spentAllocations flow, but move the charge inside the per-part handling so every
function-call part is accounted for.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 368486d3-dc9c-4b6f-8a1c-b425e204fb14
📒 Files selected for processing (7)
cloud-agent/src/handlers/schedulerTriggerHandler.tscloud-agent/src/handlers/wsAgentHandler.tscloud-agent/src/services/agentEventLoop.tscloud-agent/src/services/creditService.tscloud-agent/src/tools/browserAction.tsfunctions/src/generateEmbedding.tsfunctions/src/summarizeText.ts
🚧 Files skipped from review as they are similar to previous changes (6)
- cloud-agent/src/tools/browserAction.ts
- functions/src/summarizeText.ts
- cloud-agent/src/handlers/schedulerTriggerHandler.ts
- functions/src/generateEmbedding.ts
- cloud-agent/src/handlers/wsAgentHandler.ts
- cloud-agent/src/services/creditService.ts
consumeAgentEvents only spent a credit inside the functionCall branch, so a plain conversational reply with no tool call cost 0 credits — a real billing gap, not the per-loop metering the spec intended. Extract the spend+cap logic into chargeLoopIteration and call it for both the tool-call turn and the no-tool-call/final-synthesis turn, so every completed model turn bills 1 credit (still capped at MAX_LOOP_ITERATIONS total). Update the design spec to document the corrected behavior and add test coverage for the previously-free no-tool-call reply. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
/fix-pr follow-upCommit: Review resolutionNo new unresolved review threads found (all 8 prior threads — 7 CodeRabbit + 1 Copilot — were already fixed and resolved in earlier passes). This pass addresses a billing gap found during manual re-review of the diff, at the user's direction:
Verification
|
Description
Ships the July 2026 credit economy repricing: metered agent-turn billing on the primary WebSocket path and HTTP fallback, live voice at 5 credits/tick with a connect gate of 5, updated Firebase callable costs, and refreshed billing/docs copy.
Design spec: docs/superpowers/specs/2026-07-01-credit-economy-repricing-design.md
Type of Change
Related Issue
Fixes #
Changes Made
consumeAgentEventsfor per-loop billing (max 5); WebSocket/agent/streamuses shared metering; HTTPPOST /agent/runpre-flight 402 when balance is zero; live voice 5 credits/tick and connect gate ≥ 5generateReplygrounded (3) vs standard (1);generateImage/convertDocumentText→ 2 credits; billsummarizeText(1) andgenerateEmbedding(Math.ceil(chars/50_000))MIN_CREDITS_FOR_CALLraised to 5; landing/meta copy updated to 5 credits/minutebilling-and-credits.mdconsumption table; added repricing design spec; removed completed ephemeral plan filesTesting
Platforms Tested
Test Steps
cd cloud-agent && npm run typecheck && npm testcd functions && npm run typecheck && npm testnpm test -- __tests__/useLiveVoiceChat.test.tsxScreenshots
Before
N/A — billing/pricing change
After
N/A — billing/pricing change
Checklist
Additional Notes
Hard cutover — no rate versioning.
LOW_CREDIT_THRESHOLDon the talk screen stays at 5 (less runway at the new live-voice rate; accepted per spec).Made with Cursor