Skip to content

feat(code-review): show Cloud Agent pricing and compute costs - #10713

Open
eshurakov wants to merge 3 commits into
mainfrom
kilo/green-summit-5z5
Open

eshurakov wants to merge 3 commits into
mainfrom
kilo/green-summit-5z5

Conversation

@eshurakov

@eshurakov eshurakov commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Explain that code reviews use paid Cloud Agent compute for accounts with compute billing enabled.
  • Show the hourly price from the dedicated code review SKU in GitHub, GitLab, and Bitbucket settings.
  • Show recorded compute costs separately from model costs on each review, including charges across retry attempts.
  • Label the existing Cloud Agent session compute total as Compute cost.

The billing service owns the enrollment decision. The new read-only API uses the same decision as admission, including enterprise exemptions.

Verification

  • Opened GitHub and GitLab settings against the local web app and real billing Workers. Confirmed the paid-resource notice and catalog price.
  • Removed the local test account from paid enrollment. Confirmed the API returned disabled and the notice disappeared.
  • Restored enrollment. Confirmed the paid notice returned.
  • Opened a synthetic completed review. Confirmed the page and API showed $0.42 model cost and $0.03 compute cost from local ledger rows.
  • Checked the notice at 390px width. Confirmed wrapping and no horizontal overflow.
  • Rechecked the local settings notice and review detail after changing all user-facing cost wording to compute.

Automated checks

  • Web billing, review router, and session information tests: 183 passed.
  • Session information tests after the wording update: 40 passed.
  • Cloud Agent API tests: 135 passed.
  • Meter and billing configuration tests: 25 passed.
  • PostgreSQL admission, capacity, and organization consumption tests: 54 passed.
  • Web, Cloud Agent, and meter typechecks passed.
  • Changed files formatted; git diff --check passed.
  • Lint passed for all four files in the wording update. Lint for the complete PR did not finish within the sandbox command timeout.

Visual Changes

Screenshots use synthetic local data and a synthetic catalog rate, not production pricing. They show the running application, not mocked browser responses.

Before After
Settings did not disclose compute billing or the hourly rate. Code review settings with paid Cloud Agent compute notice and hourly rate
Review details showed model cost without recorded compute cost. Review details with separate model and compute costs

Reviewer Notes

  • Release the meter read API before the Cloud Agent endpoint and web consumer. No migration, price change, or enrollment change is included.
  • Live container execution was not verified. The local image build failed because the GitLab download certificate was not trusted. Billing API verification used the real Workers with container image builds omitted; screenshot charges came from seeded PostgreSQL ledger rows.
  • The UI follows the Kilo Cloud overlay and voice guidance. It uses the existing Alert primitive; no dedicated alert recipe exists.

<dd>{formatSessionCost(sessionCostMusd / 1_000_000)}</dd>
</div>
)}
{data.containerCostMusd !== null && (

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: Sub-cent container cost renders as "$0.00"

containerCostMusd !== null shows this row for any positive charge, but formatSessionCost rounds to cents. At the code-review container rate ($0.18/hr) a container shorter than ~100 seconds costs less than $0.005 and will display "Container cost $0.00" for a charge that exists. The model-cost row above (line 288) guards the same situation with isRenderableSessionCost(...). Apply the same guard here (or use formatSessionTotalCost to render "<$0.01"), and use != null to match the sibling row.

Suggested change
{data.containerCostMusd !== null && (
{data.containerCostMusd != null && isRenderableSessionCost(data.containerCostMusd / 1_000_000) && (

.from(cloud_billing_sku)
.where(eq(cloud_billing_sku.id, SANDBOX_USAGE_SKUS.SandboxContainersCodeReview))
.limit(1);
return { enabled: true, hourlyUsd: rate ? Number(rate.rate) * 36 : null };

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: Advertised rate ignores SKU availability

readComputeRates selects accepts_new_usage, and container admission rejects starts when the SKU is disabled. This lookup returns the price unconditionally, so if cloud-agent-code-review-2026-07 is disabled while an account is in paid mode, the settings notice advertises an hourly rate even though every code-review start would be refused at admission. Filtering on cloud_billing_sku.accepts_new_usage (and returning hourlyUsd: null when it is false) would keep the notice consistent with admission.

)
.where(
and(
inArray(container_usage_interval.session_id, sessionIds),

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: Add a created_at lower bound to enable partition pruning

compute_usage_charge is partitioned monthly on created_at and its payer indexes are (user_id, created_at) / (organization_id, created_at). Without a created_at predicate this lookup must probe the payer index in every month partition that has ever existed, and it runs on the review detail endpoint the page polls. A review's container charges cannot predate the review (or its attempts), so passing that timestamp as a lower bound on compute_usage_charge.created_at would allow partition pruning.

it.each(['paid', 'shadow'] as const)('reports the admission billing decision: %s', async mode => {
vi.mocked(resolveBillingMode).mockResolvedValueOnce(mode);
await expect(
createMeter().getBillingMode({

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: Test does not assert the forwarded service and subject

This stubs resolveBillingMode and only asserts the resolved mode, so getBillingMode could pass the wrong service or subject and the test would still pass. The core guarantee here is that this read path resolves the same decision as admission, so assert the forwarded arguments, e.g. expect(resolveBillingMode).toHaveBeenCalledWith(expect.anything(), expect.anything(), 'cloud-agent-next-sandbox-containers-code-review', { type: 'user', id: 'oauth/provider/user' }, expect.any(Date)).

@kilo-code-bot

kilo-code-bot Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 6 Issues Found | Recommendation: Address before merge

Executive Summary

The two highest-risk findings are on the billing read paths: a supplementary container-cost read can turn a healthy review page into a permanent "not found" on any transient DB error, and the settings rate lookup advertises a price without the unit/availability checks that admission enforces.

Assumptions: Live end-to-end container-cost linkage was not exercised; the session-id key match is [INFERENCE] from review.session_id/attempt ids, container_usage_interval.session_id, and identity.sessionId. This incremental pass reviewed the wording diff (4 files, 7 lines); findings in unchanged files were re-verified against current HEAD 4887384c.

Overview

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

WARNING

File Line Issue
apps/web/src/routers/code-reviews/code-reviews-router.ts 374 readCodeReviewContainerCost runs inside the get try block; any error becomes failureResult, so CodeReviewDetailClient shows "Code review not found" and disables refetchInterval. The sibling model-cost read catches and returns null, so a supplementary billing lookup can never fail the review read. Move the call into the existing Promise.all and tolerate its failure.
apps/web/src/routers/compute-billing-router.ts 50 codeReviewRate filters on id alone and returns Number(rate.rate) * 36 without unit = 'second' or accepts_new_usage, whereas admission rejects sku_unit_mismatch / sku_not_accepting_new_usage. A disabled or other-unit SKU would still advertise a price for a paid resource.

SUGGESTION

File Line Issue
apps/web/src/app/(app)/code-reviews/[reviewId]/CodeReviewDetailClient.tsx 294 The compute-cost row renders for any non-null sum, but formatSessionCost rounds to cents, so a sub-cent charge (short review) displays $0.00. The model-cost row guards with isRenderableSessionCost; use formatSessionTotalCost (or the same guard) so a positive charge is never shown as zero.
apps/web/src/lib/code-reviews/compute-billing.ts 53 No created_at bound on the monthly-partitioned compute_usage_charge join, so the polled review detail endpoint probes the payer index in every historical partition. A review/attempt timestamp as a lower bound would allow partition pruning.
services/container-usage-meter/src/meter.test.ts 101 The getBillingMode test only asserts the resolved mode; it does not assert the forwarded service/subject, so it does not guard the "same decision as admission" guarantee.
apps/web/src/routers/code-reviews/code-reviews-router.ts 445 The get wiring for containerCostMusd is untested: no test exercises owner-type selection, attempt-session inclusion, or foreign-payer exclusion.
Files Reviewed (8 files)
  • apps/web/src/app/(app)/code-reviews/[reviewId]/CodeReviewDetailClient.tsx - 1 issue
  • apps/web/src/components/cloud-agent-next/ChatHeader.test.ts - 0 issues
  • apps/web/src/components/cloud-agent-next/SessionInfoDialog.tsx - 0 issues
  • apps/web/src/components/code-reviews/CodeReviewComputeNotice.tsx - 0 issues
  • apps/web/src/lib/code-reviews/compute-billing.ts - 1 issue
  • apps/web/src/routers/code-reviews/code-reviews-router.ts - 2 issues
  • apps/web/src/routers/compute-billing-router.ts - 1 issue
  • services/container-usage-meter/src/meter.test.ts - 1 issue

Fix these issues in Kilo Cloud

Previous Review Summary (commit a3ca2c6)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit a3ca2c6)

Status: 4 Issues Found | Recommendation: Address before merge

Overview

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

SUGGESTION

File Line Issue
apps/web/src/app/(app)/code-reviews/[reviewId]/CodeReviewDetailClient.tsx 294 Sub-cent container cost renders as "$0.00"; model-cost row hides unrenderable amounts via isRenderableSessionCost
apps/web/src/routers/compute-billing-router.ts 50 codeReviewRate returns the price without checking accepts_new_usage, unlike readComputeRates, so it can advertise a rate for a disabled SKU that admission rejects
apps/web/src/lib/code-reviews/compute-billing.ts 53 No created_at bound on the monthly-partitioned compute_usage_charge join, preventing partition pruning on the polled review detail endpoint
services/container-usage-meter/src/meter.test.ts 101 getBillingMode test does not assert the forwarded service/subject, so it does not guard the "same decision as admission" guarantee
Files Reviewed (18 files)
  • apps/web/src/app/(app)/code-reviews/[reviewId]/CodeReviewDetailClient.tsx - 1 issue
  • apps/web/src/components/cloud-agent-next/ChatHeader.test.ts - 0 issues
  • apps/web/src/components/cloud-agent-next/SessionInfoDialog.tsx - 0 issues
  • apps/web/src/components/code-reviews/BitbucketReviewConfigForm.tsx - 0 issues
  • apps/web/src/components/code-reviews/CodeReviewComputeNotice.tsx - 0 issues
  • apps/web/src/components/code-reviews/ReviewConfigForm.tsx - 0 issues
  • apps/web/src/lib/code-reviews/compute-billing.ts - 1 issue
  • apps/web/src/routers/code-reviews/code-reviews-router.ts - 0 issues
  • apps/web/src/routers/compute-billing-router.test.ts - 0 issues
  • apps/web/src/routers/compute-billing-router.ts - 1 issue
  • packages/container-usage/src/contracts.ts - 0 issues
  • services/cloud-agent-next/src/server.test.ts - 0 issues
  • services/cloud-agent-next/src/server.ts - 0 issues
  • services/cloud-agent-next/src/types.ts - 0 issues
  • services/container-usage-meter/src/meter.test.ts - 1 issue
  • services/container-usage-meter/src/meter.ts - 0 issues
  • services/container-usage-meter/src/postgres.ts - 0 issues
  • services/kilo-mcp/catalog.json - 0 issues (generated; new entry is a read-only query)

Verified: the rate_cents_per_unit * 36 conversion matches the existing rates path; the segment→charge join cannot double count (unique segment idempotency keys, one charge per segment); payer filters prevent cross-user/org disclosure; personal and organization review detail paths authorize before the new cost lookup; the internal endpoint validates owner type and uses constant-time auth; resolveBillingMode matches admission, including the enterprise exemption. No memory leaks introduced (React Query + cleaned-up abort listeners).

Note: end-to-end container-cost linkage against a live code-review container was not exercised in the PR; the screenshots use seeded ledger rows. The session-id key match is [INFERENCE] from review.session_id/attempt ids, container_usage_interval.session_id, and identity.sessionId = cloudAgentSessionId.

Fix these issues in Kilo Cloud


Reviewed by deepseek-flash-latest · Input: 39.7K · Output: 7.5K · Cached: 194.3K

Review guidance: REVIEW.md from base branch main

@iscekic iscekic left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

bot: Automated code review for this pull request, requested by @eshurakov.

This change adds a read-only billing-status surface for code review compute and shows it to customers. The meter gains getBillingMode, which reuses resolveBillingMode extracted from the admission path (including the unreviewed-enterprise exemption), so the read and admission agree. Cloud Agent exposes a new internal read-only route, gated by the existing timing-safe internal-key check and a Zod-validated {type,id} subject. Web adds computeBilling.codeReviewRate, which authorizes org access, asks the Cloud Agent worker, and prices the dedicated cloud-agent-code-review-2026-07 SKU; a new CodeReviewComputeNotice renders enabled/disabled/price-missing/error-with-retry states in the GitHub, GitLab, and Bitbucket config forms. codeReviews.get now sums recorded container charges for the review's session and every attempt session and the detail page shows that as Container cost, with the existing session compute label renamed to match. The head commit is the generated kilo-mcp catalog refresh; the new catalog entry is a query, not a write, so it does not expand credential or mutation reach.

What I checked: the new query reuses the ledger join shape of readSessionComputeCharges (segment idempotency key -> compute_usage_charge.usage_source_id, one delta charge row per settled segment), the payer filter matches the charge-writing code (user_id for user subjects, organization_id for org subjects), and container_usage_interval.session_id is the same cloud-agent session id stored on the review and attempts that readSessionComputeCharges relies on. The meter RPC parses its own input and uses per-call getContainerUsageDb/this.env, and the endpoint requires the internal secret before touching the meter. The extracted resolveBillingMode preserves the admission decision and its enterprise exemption. I could not run the suites or a live container, so the real-session-ledger match and the production SKU row remain unverified at runtime (the PR says the same).

Act first on the failure isolation in code-reviews-router.ts: the new container-cost read is supplementary, but a single query error currently turns a healthy review page into "Code review not found" with polling switched off. Then fix the two display/price issues: sub-cent charges shown as $0.00, and a rate read without the unit/availability checks admission applies.

E2E: not run.

Not run. The checkout has no node_modules and this job does not install dependencies, so no Jest, Vitest, typecheck, or app run was possible. Review was static: the PR diff plus the surrounding repository sources (routers, meter, admission path, schema, UI). I did parse the changed services/kilo-mcp/catalog.json to confirm it is valid JSON and contains the new read-only query entry.

Every remark is attached to the changed line where GitHub accepts one.

This service never pushes, rebases, forces, closes, labels, or merges, and it posts nothing else on this pull request.

cached: 0,
};

const containerCostMusd = await readCodeReviewContainerCost(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

WARNING: A transient failure of this supplementary read fails the whole review page.

readCodeReviewContainerCost is awaited inside the get try block. Any error from it falls through to the catch below, which returns failureResult(error.message); CodeReviewDetailClient treats !data?.success as "Code review not found" and its refetchInterval returns false for an unsuccessful result, so the user is stranded on the not-found screen with no automatic retry (and other callers of codeReviews.get receive the raw database message in error). The model-cost read beside it has the opposite contract: getSessionUsageFromBilling catches, reports via captureException, and returns null so a supplementary billing lookup can never fail the review read (apps/web/src/lib/code-reviews/db/code-reviews.ts:2629-2635).

Smallest fix: give the container-cost read the same tolerance — wrap the readCodeReviewContainerCost call in try/catch, log with captureException, and leave containerCostMusd null on failure. While there, move the call into the Promise.all above so the 5-second in-flight poll does not add a serial database round trip.

.from(cloud_billing_sku)
.where(eq(cloud_billing_sku.id, SANDBOX_USAGE_SKUS.SandboxContainersCodeReview))
.limit(1);
return { enabled: true, hourlyUsd: rate ? Number(rate.rate) * 36 : null };

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

WARNING: The advertised price skips the SKU checks that admission enforces.

Number(rate.rate) * 36 is only the cents-per-second -> USD-per-hour conversion. Admission reads the same SKU and rejects a row whose unit !== 'second' (sku_unit_mismatch) or whose acceptsNewUsage is false (sku_not_accepting_new_usage) — services/container-usage-meter/src/postgres.ts:546-569. This lookup filters on id alone, so a row in another unit would be multiplied by 36 and shown as an hourly price, and a disabled SKU would still be advertised as the price of a paid resource. readComputeRates in apps/web/src/lib/cloud-agent/compute-billing.ts already filters cloud_billing_sku.unit = 'second' for the same reason.

Smallest fix: filter on unit = 'second' and accepts_new_usage, or select both and return hourlyUsd: null when the row is not usable, so the notice falls back to "Price unavailable" instead of a wrong or unusable rate.

{data.containerCostMusd !== null && (
<div>
<dt className="text-muted-foreground">Container cost</dt>
<dd>{formatSessionCost(data.containerCostMusd / 1_000_000)}</dd>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

WARNING: A real sub-cent container charge renders as $0.00.

formatSessionCost rounds to whole cents (session-cost-breakdown.ts:25-27), while this row renders for any non-null sum. At the code-review container SKU's cents-per-second rate, a review shorter than about 100 seconds is a positive charge below $0.005, so this line reads "Container cost $0.00" and implies nothing was charged. The model-cost row directly above avoids exactly that with isRenderableSessionCost, and SessionInfoDialog renders the same kind of total with formatSessionTotalCost, which produces <$0.01.

Smallest fix: use formatSessionTotalCost(data.containerCostMusd / 1_000_000) (keeping the non-null gate) so a positive charge is never displayed as $0.00.

attempts: visibleAttempts,
tokenUsage,
sessionCostMusd: billingUsage?.totalCostMusd ?? null,
containerCostMusd,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

SUGGESTION: The get wiring for the new field is untested.

readCodeReviewContainerCost itself is unit-tested, but nothing exercises the contract this page depends on: that codeReviews.get passes review.session_id plus every attempt session, picks review.owned_by_organization_id for an org review and the caller for a personal one, and returns the aggregate as containerCostMusd. A wrong owner type or a dropped attempt session would silently show no cost. apps/web/src/routers/code-reviews-router.test.ts already has DB-backed get cost coverage ("returns the billing-derived session cost for a terminal review") to extend.

Smallest addition: seed container_usage_interval + container_usage_segment + compute_usage_charge rows for the review and one attempt session plus a row for another payer, then call get as the owner and as an org member and assert both the amount and that foreign payer rows are excluded; a review with only shadow (no-charge) usage should still return containerCostMusd: null.

@eshurakov eshurakov changed the title feat(code-review): show Cloud Agent pricing and container costs feat(code-review): show Cloud Agent pricing and compute costs Nov 12, 2026

This branch has not been deployed

No deployments
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