Skip to content

fix(proxy): preserve hedge loser Codex priority billing#1255

Merged
ding113 merged 2 commits into
ding113:devfrom
Brisbanehuang:codex/fix-hedge-loser-priority-billing
Jun 11, 2026
Merged

fix(proxy): preserve hedge loser Codex priority billing#1255
ding113 merged 2 commits into
ding113:devfrom
Brisbanehuang:codex/fix-hedge-loser-priority-billing

Conversation

@Brisbanehuang

@Brisbanehuang Brisbanehuang commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Preserve the initial Codex hedge loser's requested service_tier before the shared session is synced to the winning attempt
  • Use the loser's provider and captured requested tier when resolving priority billing for hedge loser cost calculation
  • Add a regression test covering an initial Codex priority loser after winner-session sync pollution

Root cause

finalizeHedgeLoserBilling already accepts a pre-sync billing context so the initial provider loser is priced with its own model and multipliers. However, Codex priority billing still called resolveCodexPriorityBillingDecision(loserSession, ...), which reads service_tier from the session after syncWinningAttemptSession may have overwritten it with the winner request. In a hedge race where the initial Codex attempt requested priority but the winner did not, the loser could be under-billed at default-tier rates.

Related PRs:

Changes

Core Changes

  • forwarder.ts: Capture requestedServiceTier from the loser's request message into billingSnapshot before the winner session can overwrite it
  • response-handler.ts: Thread provider and requestedServiceTier from billingContext through resolveCodexPriorityBillingDecision so priority billing uses the loser's own tier, not the winner's
  • response-handler.ts: Extend getRequestedCodexServiceTier to accept an optional provider override

Supporting Changes

  • StreamingHedgeAttempt.billingSnapshot type: Add requestedServiceTier: string | null field
  • finalizeHedgeLoserBilling params: Add requestedServiceTier to billingContext type

Testing

Automated Tests

  • Unit test added: tests/unit/proxy/response-handler-hedge-loser-priority.test.ts (187 lines) — verifies that an initial Codex loser with priority service tier is correctly billed at priority rates after winner session sync

Validation

  • bun run typecheck
  • bun run test tests/unit/proxy/response-handler-hedge-loser-priority.test.ts tests/unit/proxy/proxy-forwarder-hedge-first-byte.test.ts tests/integration/billing-model-source.test.ts
  • bunx biome check src/app/v1/_lib/proxy/response-handler.ts src/app/v1/_lib/proxy/forwarder.ts tests/unit/proxy/response-handler-hedge-loser-priority.test.ts

Description enhanced by Claude AI

Greptile Summary

This PR fixes an under-billing bug in the Codex hedge-loser path where syncWinningAttemptSession could overwrite the shared session's service_tier before resolveCodexPriorityBillingDecision read it, causing an initial Codex loser that requested priority to be billed at default rates.

  • forwarder.ts: Captures requestedServiceTier from the loser's request message into billingSnapshot before the winner session sync, extending the existing snapshot mechanism that already preserved model and cost-multiplier state.
  • response-handler.ts: Threads provider and the captured requestedServiceTier through resolveCodexPriorityBillingDecision and trackCostToRedis so both the DB cost record and Redis rate-limit counters use the loser's pre-sync tier, not the winner's.
  • Test: Adds a focused regression test verifying that an initial Codex loser with requestedServiceTier: \"priority\" in its billing snapshot is billed at priority rates even after the shared session has been polluted with the winner's default-tier request.

Confidence Score: 5/5

Safe to merge — the change is a narrow, well-scoped billing snapshot extension with no mutations to the hot path and a dedicated regression test.

The fix captures requestedServiceTier into the billing snapshot before session-sync can overwrite it, then consistently threads both the provider and the captured tier through cost calculation and Redis tracking. Shadow-session losers preserve existing behavior, and the tests verify the arithmetic end-to-end.

No files require special attention.

Important Files Changed

Filename Overview
src/app/v1/_lib/proxy/forwarder.ts Adds requestedServiceTier to the billing snapshot captured before syncWinningAttemptSession; snapshot logic and the type definition are correctly extended in lockstep.
src/app/v1/_lib/proxy/response-handler.ts Correctly threads provider and requestedServiceTier through resolveCodexPriorityBillingDecision and trackCostToRedis; both the DB cost and Redis counters now consistently use the loser's pre-sync billing parameters.
tests/unit/proxy/response-handler-hedge-loser-priority.test.ts New regression tests cover the priority-tier snapshot path and verify both DB cost (priority x costMultiplier x groupCostMultiplier) and Redis trackCost call. Session-pollution simulation is accurate.

Sequence Diagram

sequenceDiagram
    participant FW as forwarder.ts (commitWinner)
    participant SS as syncWinningAttemptSession
    participant FHL as finalizeHedgeLoserBilling
    participant RCPBD as resolveCodexPriorityBillingDecision
    participant TCR as trackCostToRedis

    FW->>FW: Capture billingSnapshot
    FW->>SS: syncWinningAttemptSession(session, winner.session)
    Note over SS: Overwrites session.request.message with winner's tier
    FW->>FHL: "finalizeHedgeLoserBilling({billingContext: billingSnapshot})"
    FHL->>RCPBD: "resolveCodexPriorityBillingDecision(loserSession, actualTier, {provider, requestedServiceTier})"
    Note over RCPBD: Uses captured tier instead of polluted session value
    RCPBD-->>FHL: "{effectivePriority: true}"
    FHL->>TCR: "trackCostToRedis(..., {provider, context1mApplied, groupCostMultiplier})"
    Note over TCR: Redis counters use loser's cost parameters
Loading

Reviews (2): Last reviewed commit: "fix(proxy): track hedge loser Redis cost..." | Re-trigger Greptile

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

在流式 hedge 的输家计费路径中新增并传播 requestedServiceTier 字段:forwarder 在 commitWinner 时回填,response-handler 将其纳入 billingContext,用于 Codex 优先级决策,并在 trackCostToRedis 中使用覆盖值计算与记录成本;新增单测覆盖该逻辑分支。

变更详情

Hedge Loser 计费中的请求服务等级传播

层级 / 文件 摘要
计费上下文类型与字段定义
src/app/v1/_lib/proxy/forwarder.ts, src/app/v1/_lib/proxy/response-handler.ts
StreamingHedgeAttempt["billingSnapshot"]finalizeHedgeLoserBillingbillingContext 参数中新增 requestedServiceTier?: string | null 字段,用于在计费流程中携带请求侧 Codex service tier。
服务等级解析与决策逻辑参数化
src/app/v1/_lib/proxy/response-handler.ts
getRequestedCodexServiceTier 扩展为接收可选 provider 参数;resolveCodexPriorityBillingDecision 新增 options 参数以支持显式传入 providerrequestedServiceTier,使 loser 计费能独立应用请求侧优先级。
Loser 计费快照中的服务等级回填
src/app/v1/_lib/proxy/forwarder.ts
commitWinner() 时为落败尝试的 billingSnapshot 回填 requestedServiceTier,从落败对应的原始请求消息中读取 service_tier(仅在为字符串时)。
Loser 计费决策中的优先级应用
src/app/v1/_lib/proxy/response-handler.ts
finalizeHedgeLoserBilling 中使用扩展的 resolveCodexPriorityBillingDecision,并在 billingContext 存在时将 requestedServiceTier 透传作为决策输入。
成本写入的计费覆盖与实现
src/app/v1/_lib/proxy/response-handler.ts
trackCostToRedis 新增 TrackCostBillingOverridesbillingOverrides 参数,成本计算(buildCostCalculationOptions)改为优先使用 overrides 中的 context1mAppliedgroupCostMultiplier,并在调用时将 provider 与倍率显式传入,保证影子/共享 session 场景下成本与限流计数一致。
Hedge Loser 优先级计费的测试覆盖
tests/unit/proxy/response-handler-hedge-loser-priority.test.ts
新增综合测试文件,验证 finalizeHedgeLoserBilling 在传入 requestedServiceTier billingContext 时的计费结果和调用行为;通过广泛 mock 依赖和工厂函数构造完整场景。

评估代码审查工作量

🎯 3 (中等) | ⏱️ ~20 分钟

可能相关的 PRs

  • ding113/claude-code-hub#1247:与本 PR 在 hedge-loser 计费路径(forwarder → finalizeHedgeLoserBilling → trackCostToRedis)有直接代码级重叠。

建议审阅者

  • ding113
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 标题准确概括了主要变更:在 hedge 场景中保留 Codex loser 的优先级计费信息,避免 winner 会话同步污染。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description clearly relates to the changeset, explaining the root cause of an under-billing bug in Codex hedge-loser billing and detailing all three modified files and the test additions.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai
coderabbitai Bot requested a review from ding113 June 8, 2026 16:38
@github-actions github-actions Bot added bug Something isn't working area:core labels Jun 8, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request introduces support for capturing and preserving the requested service tier (requestedServiceTier) in the billing snapshot of hedge loser attempts. This ensures that when finalizing billing for a hedge loser, the correct service tier is used for resolving Codex priority billing decisions. Unit tests have been added to verify this behavior. The review feedback suggests a more robust check for requestedServiceTier in the options object of resolveCodexPriorityBillingDecision to avoid inconsistent behavior when the property is explicitly set to undefined.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +265 to +268
const requestedServiceTier =
options && "requestedServiceTier" in options
? (options.requestedServiceTier ?? null)
: getRequestedCodexServiceTier(session, provider);

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.

medium

Using the "requestedServiceTier" in options check can lead to inconsistent behavior in TypeScript/JavaScript depending on whether the property is omitted or explicitly set to undefined (e.g., { provider } vs { provider, requestedServiceTier: undefined }).

A more robust and idiomatic pattern is to check if the property is not undefined. This consistently falls back to getRequestedCodexServiceTier when the value is not provided or is undefined, while correctly preserving explicit string or null values.

Suggested change
const requestedServiceTier =
options && "requestedServiceTier" in options
? (options.requestedServiceTier ?? null)
: getRequestedCodexServiceTier(session, provider);
const requestedServiceTier =
options?.requestedServiceTier !== undefined
? options.requestedServiceTier
: getRequestedCodexServiceTier(session, provider);

@github-actions github-actions Bot added size/S Small PR (< 200 lines) size/M Medium PR (< 500 lines) and removed size/S Small PR (< 200 lines) labels Jun 8, 2026

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 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 `@src/app/v1/_lib/proxy/response-handler.ts`:
- Around line 3851-3855: The Redis billing call still reads provider and
multiplier from the mutated loserSession, causing Redis to be billed with the
winner's context; extract the billing fields from loserSession immediately after
determining the initial loser (e.g. provider, session.getContext1mApplied(),
session.getGroupCostMultiplier(), and requestedServiceTier if present) and pass
that explicit billing context into trackCostToRedis instead of letting
trackCostToRedis read from loserSession, and ensure
resolveCodexPriorityBillingDecision continues to receive
provider/requestedServiceTier as before; update calls to trackCostToRedis(...)
to accept and use this extracted loserBillingContext so Redis accounting remains
decoupled from the shared session mutations.
🪄 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: c17a0a27-7031-4d09-9099-1f51840b9423

📥 Commits

Reviewing files that changed from the base of the PR and between 52a3f3b and 2d3a485.

📒 Files selected for processing (3)
  • src/app/v1/_lib/proxy/forwarder.ts
  • src/app/v1/_lib/proxy/response-handler.ts
  • tests/unit/proxy/response-handler-hedge-loser-priority.test.ts

Comment thread src/app/v1/_lib/proxy/response-handler.ts

@github-actions github-actions Bot left a comment

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.

Code Review Summary

This is a well-targeted bug fix that captures the initial service_tier from the loser's request before syncWinningAttemptSession overwrites the shared session, then threads it through resolveCodexPriorityBillingDecision for accurate priority billing. The change is backward-compatible, properly typed, and includes a focused regression test.

PR Size: M

  • Lines changed: 221 (214 additions, 7 deletions; 187 of which are the new test file)
  • Files changed: 3

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 0 0 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 0 0
Simplification 0 0 0 0

Review Coverage

  • Logic and correctness - Correct capture-before-sync pattern; backward-compatible optional parameter; all 4 existing call sites of resolveCodexPriorityBillingDecision verified
  • Security (OWASP Top 10) - No user input handling; internal billing path only
  • Error handling - Consistent with surrounding code; no new try/catch blocks introduced
  • Type safety - requestedServiceTier: string | null appropriately typed in both billingSnapshot and billingContext; optional options parameter preserves existing signatures
  • Documentation accuracy - Existing comments accurately describe the pre-sync capture pattern
  • Test coverage - Regression test covers the exact scenario (initial Codex priority loser after winner session sync); verifies priority pricing rates are applied (cost=400 from input_cost_per_token_priority=2, output_cost_per_token_priority=20 with 100/10 tokens)
  • Code clarity - Focused change with clear intent

Automated review by Claude AI

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

Labels

area:core bug Something isn't working size/M Medium PR (< 500 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants