fix(billing): split image input tokens for gpt-image-2 - #2176
Conversation
Bill `input_tokens_details.image_tokens` from OpenAI/Azure gpt-image-2 responses against `imageInputPrice` ($8/M) and the text remainder against `inputPrice` ($5/M, corrected from $8/M). Plumb the upstream-reported image_tokens / output image_tokens counts through `calculateCosts` and prefer them over the count*estimate fallback. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 59c4efbaea
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| webSearchCount: number | null = null, | ||
| organizationId: string | null = null, |
There was a problem hiding this comment.
Pass image token details when billing cached responses
Because the new split pricing depends on reportedImageInputTokens/reportedImageOutputTokens, cached non-streaming responses still get billed incorrectly: the cache-hit call at apps/gateway/src/chat/chat.ts:3716 recalculates from cachedResponse.usage but never passes prompt_tokens_details.image_tokens or completion_tokens_details.image_tokens (and uses outputImageCount = 0). For cached gpt-image-2 edits/generations, this means image input tokens fall back to the new $5/M text rate and image output tokens are not charged at the $30/M image rate, even though those details are stored in the transformed cached response.
Useful? React with 👍 / 👎.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds optional upstream-reported image input/output token parameters to calculateCosts, prefers those values over estimates, splits GPT-Image-2 pricing between text and image input rates, updates chat call sites to pass new args, and adds tests for split billing and Azure discount behavior. ChangesImage Token Reporting & Billing
Sequence DiagramsequenceDiagram
participant Client
participant Gateway.Chat
participant Gateway.Costs
participant ModelsData
Client->>Gateway.Chat: request (may include image reports)
Gateway.Chat->>Gateway.Costs: calculateCosts(..., reportedImageInputTokens?, reportedImageOutputTokens?)
Gateway.Costs->>ModelsData: lookup model prices (inputPrice, imageInputPrice, imageOutputPrice, discount)
Gateway.Costs-->>Gateway.Chat: cost breakdown
Gateway.Chat-->>Client: stream response + usage/cost chunk
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Pull request overview
This PR updates the gateway’s billing logic for OpenAI/Azure gpt-image-2 to correctly price image input tokens separately from text input tokens, aligning internal pricing with published provider rates and avoiding double-billing image tokens at the text rate.
Changes:
- Updates
gpt-image-2provider mappings to setinputPriceto the text-input rate and introduce a new per-tokenimageInputPrice. - Extends
calculateCoststo accept upstream-reported image token counts and uses them for image-output models; subtracts image input tokens from billable text prompt tokens for OpenAI/Azure/xAI. - Adds unit tests covering split image/text input billing (including Azure discount behavior).
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| packages/models/src/models/openai.ts | Adjusts gpt-image-2 pricing and introduces imageInputPrice for OpenAI/Azure mappings. |
| apps/gateway/src/lib/costs.ts | Uses upstream image token counts for image-output models and prevents double-billing by subtracting image tokens from text prompt billing. |
| apps/gateway/src/lib/costs.spec.ts | Updates expectations and adds tests for split image/text input billing for gpt-image-2 (OpenAI + Azure). |
| apps/gateway/src/chat/chat.ts | Plumbs parsed imageInputTokens / imageOutputTokens into calculateCosts for non-streaming responses. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export async function calculateCosts( | ||
| model: string, | ||
| provider: string, | ||
| promptTokens: number | null, | ||
| completionTokens: number | null, | ||
| cachedTokens: number | null = null, | ||
| fullOutput?: { | ||
| messages?: ChatMessage[]; | ||
| prompt?: string; | ||
| completion?: string; | ||
| toolResults?: ToolCall[]; | ||
| }, | ||
| reasoningTokens: number | null = null, | ||
| outputImageCount = 0, | ||
| imageSize?: string, | ||
| inputImageCount = 0, | ||
| webSearchCount: number | null = null, | ||
| organizationId: string | null = null, | ||
| imageQuality?: string, | ||
| reportedImageInputTokens: number | null = null, | ||
| reportedImageOutputTokens: number | null = null, | ||
| ) { |
| reportedImageInputTokens && | ||
| reportedImageInputTokens > 0 |
| imageOutputTokens = | ||
| imageOutputTokensPerImage !== undefined | ||
| ? outputImageCount * imageOutputTokensPerImage | ||
| : totalOutputTokens > 0 | ||
| ? totalOutputTokens | ||
| : outputImageCount * LEGACY_DEFAULT_TOKENS_PER_IMAGE; | ||
| isImageOutputModel && | ||
| reportedImageOutputTokens && | ||
| reportedImageOutputTokens > 0 | ||
| ? reportedImageOutputTokens | ||
| : imageOutputTokensPerImage !== undefined | ||
| ? outputImageCount * imageOutputTokensPerImage | ||
| : totalOutputTokens > 0 | ||
| ? totalOutputTokens | ||
| : outputImageCount * LEGACY_DEFAULT_TOKENS_PER_IMAGE; |
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 (1)
apps/gateway/src/chat/chat.ts (1)
9304-9324:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPropagate reported image tokens through the cached billing path too.
This fixes the live non-streaming path, but the cached non-streaming branch still calls
calculateCosts(...)withoutusage.prompt_tokens_details.image_tokens/usage.completion_tokens_details.image_tokensfrom the cached response. That means a cachedgpt-image-2response can be billed differently from the original uncached response, and image output can fall back to the wrong pricing path because Line 3724 still passes0foroutputImageCount.🤖 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 `@apps/gateway/src/chat/chat.ts` around lines 9304 - 9324, The cached non-streaming billing path is calling calculateCosts(...) without propagating image token/details from the cached response; update the cached-response branch that builds the calculateCosts(...) call to extract usage.prompt_tokens_details.image_tokens and usage.completion_tokens_details.image_tokens (and cached output image count) from the cached response and pass them into the calculateCosts parameters (imageInputTokens, imageOutputTokens and outputImageCount) instead of hardcoding 0/omitting them so cached gpt-image-2 responses are billed the same as live responses.
🧹 Nitpick comments (1)
apps/gateway/src/lib/costs.ts (1)
308-326: 💤 Low valueDefensive: clamp
reportedImageInputTokensagainstuncachedPromptTokens.If the upstream ever reports
image_tokenslarger thanprompt_tokens - cached_tokens(e.g., transient provider bug, or cached tokens that overlap with image tokens),billableTextPromptTokensfloors to 0 butimageInputCost = reportedImageInputTokens * imageInputPriceis still billed in full — over-billing the customer for tokens the provider never reported as input. A small clamp would make the gate symmetric with theMath.max(0, …)already used at line 343.♻️ Suggested clamp
if ( imageInputPricePerToken && isImageOutputModel && reportedImageInputTokens && reportedImageInputTokens > 0 ) { - imageInputTokens = reportedImageInputTokens; + const uncachedForImageCap = cachedTokens + ? calculatedPromptTokens - cachedTokens + : calculatedPromptTokens; + imageInputTokens = Math.min( + reportedImageInputTokens, + Math.max(0, uncachedForImageCap), + ); imageInputCost = new Decimal(imageInputTokens) .times(imageInputPricePerToken) .times(discountMultiplier); } else if (imageInputPricePerToken && inputImageCount > 0) {Also applies to: 341-349
🤖 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 `@apps/gateway/src/lib/costs.ts` around lines 308 - 326, Clamp reportedImageInputTokens to not exceed the available uncached prompt tokens before using it for billing: replace direct use of reportedImageInputTokens when computing imageInputTokens/imageInputCost with a bounded value like clampedImageTokens = Math.min(reportedImageInputTokens, Math.max(0, uncachedPromptTokens)) (or equivalent), then use clampedImageTokens in place of reportedImageInputTokens; do the same defensive clamp wherever reportedImageInputTokens is used (also update the similar block that handles the legacy-tokens path).
🤖 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 `@apps/gateway/src/chat/chat.ts`:
- Around line 9304-9324: The cached non-streaming billing path is calling
calculateCosts(...) without propagating image token/details from the cached
response; update the cached-response branch that builds the calculateCosts(...)
call to extract usage.prompt_tokens_details.image_tokens and
usage.completion_tokens_details.image_tokens (and cached output image count)
from the cached response and pass them into the calculateCosts parameters
(imageInputTokens, imageOutputTokens and outputImageCount) instead of hardcoding
0/omitting them so cached gpt-image-2 responses are billed the same as live
responses.
---
Nitpick comments:
In `@apps/gateway/src/lib/costs.ts`:
- Around line 308-326: Clamp reportedImageInputTokens to not exceed the
available uncached prompt tokens before using it for billing: replace direct use
of reportedImageInputTokens when computing imageInputTokens/imageInputCost with
a bounded value like clampedImageTokens = Math.min(reportedImageInputTokens,
Math.max(0, uncachedPromptTokens)) (or equivalent), then use clampedImageTokens
in place of reportedImageInputTokens; do the same defensive clamp wherever
reportedImageInputTokens is used (also update the similar block that handles the
legacy-tokens path).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: ddf0801f-428d-4c68-b6e5-97cad39cfa71
📒 Files selected for processing (4)
apps/gateway/src/chat/chat.tsapps/gateway/src/lib/costs.spec.tsapps/gateway/src/lib/costs.tspackages/models/src/models/openai.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Resolved merge conflicts with origin/main: Conflict 1 — Conflict 2 — Conflict 3 — Also fixed 5 other |
So the gpt-image-2 azure billing test still passes if the discount value is changed later. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 44729dea14
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| imageInputCost = new Decimal(imageInputTokens) | ||
| .times(imageInputPricePerToken) | ||
| .times(discountMultiplier); |
There was a problem hiding this comment.
Avoid charging cached image tokens at full input rate
When OpenAI/Azure returns input_tokens_details.cached_tokens together with image_tokens, cached input is subtracted from the text prompt path but imageInputCost still multiplies the full reported image-token count by the full image input rate; those cached image tokens are then also charged again via cachedInputCost. For repeated gpt-image-2 image edits where the cached tokens include image input tokens, this overcharges instead of applying the cached-input rate, so allocate/subtract cached tokens from the image-token portion before multiplying by imageInputPrice.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
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 `@apps/gateway/src/lib/costs.spec.ts`:
- Around line 520-565: The Azure image test "should apply azure discount on top
of split image/text input pricing for gpt-image-2" is missing assertions for
imageOutputTokens, imageOutputCost, and totalCost; update the spec to assert
that result.imageOutputTokens equals reportedImageOutputTokens,
result.imageOutputCost is close to expectedImageOutput (use the same
discountMultiplier and per-token rate used to compute expectedImageOutputCost),
and result.totalCost equals the sum of inputCost and outputCost (or assert
closeTo expectedTextInputCost + expectedImageInputCost +
expectedImageOutputCost). Reference the test name, calculateCosts call, and
result properties imageOutputTokens, imageOutputCost, and totalCost when adding
these assertions.
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 5d84783c-cb76-43ed-b398-c69f90c23870
📒 Files selected for processing (1)
apps/gateway/src/lib/costs.spec.ts
| it("should apply azure discount on top of split image/text input pricing for gpt-image-2", async () => { | ||
| const promptTokens = 524; | ||
| const reportedImageInputTokens = 512; | ||
| const completionTokens = 196; | ||
| const reportedImageOutputTokens = 196; | ||
|
|
||
| const result = await calculateCosts( | ||
| "gpt-image-2", | ||
| "azure", | ||
| promptTokens, | ||
| completionTokens, | ||
| null, | ||
| undefined, | ||
| null, | ||
| 1, | ||
| "1024x1024", | ||
| 0, | ||
| null, | ||
| null, | ||
| "low", | ||
| reportedImageInputTokens, | ||
| reportedImageOutputTokens, | ||
| ); | ||
|
|
||
| // Read discount from the model definition so the test stays correct | ||
| // even if the azure discount value changes. | ||
| const azureProvider = models | ||
| .find((m) => m.id === "gpt-image-2") | ||
| ?.providers.find((p) => p.providerId === "azure"); | ||
| const discountMultiplier = 1 - (azureProvider?.discount ?? 0); | ||
| const expectedTextInputCost = | ||
| (promptTokens - reportedImageInputTokens) * | ||
| (5 / 1e6) * | ||
| discountMultiplier; | ||
| const expectedImageInputCost = | ||
| reportedImageInputTokens * (8 / 1e6) * discountMultiplier; | ||
| const expectedImageOutputCost = | ||
| reportedImageOutputTokens * (30 / 1e6) * discountMultiplier; | ||
|
|
||
| expect(result.imageInputTokens).toBe(reportedImageInputTokens); | ||
| expect(result.imageInputCost).toBeCloseTo(expectedImageInputCost); | ||
| expect(result.inputCost).toBeCloseTo( | ||
| expectedTextInputCost + expectedImageInputCost, | ||
| ); | ||
| expect(result.outputCost).toBeCloseTo(expectedImageOutputCost); | ||
| }); |
There was a problem hiding this comment.
Azure test is missing imageOutputTokens, imageOutputCost, and totalCost assertions.
The companion OpenAI test (lines 507–517) checks all six cost breakdown fields plus totalCost, but the Azure variant only checks imageInputTokens, imageInputCost, inputCost, and outputCost. This leaves the discount-applied imageOutputCost and the aggregate totalCost unverified — which is exactly what the PR description says this test exists to confirm ("the gpt-image-2 image output portion uses reportedImageOutputTokens for its discounted image-output cost").
🛡️ Proposed additions
expect(result.imageInputTokens).toBe(reportedImageInputTokens);
expect(result.imageInputCost).toBeCloseTo(expectedImageInputCost);
expect(result.inputCost).toBeCloseTo(
expectedTextInputCost + expectedImageInputCost,
);
expect(result.outputCost).toBeCloseTo(expectedImageOutputCost);
+ expect(result.imageOutputTokens).toBe(reportedImageOutputTokens);
+ expect(result.imageOutputCost).toBeCloseTo(expectedImageOutputCost);
+ expect(result.totalCost).toBeCloseTo(
+ expectedTextInputCost + expectedImageInputCost + expectedImageOutputCost,
+ );
});🤖 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 `@apps/gateway/src/lib/costs.spec.ts` around lines 520 - 565, The Azure image
test "should apply azure discount on top of split image/text input pricing for
gpt-image-2" is missing assertions for imageOutputTokens, imageOutputCost, and
totalCost; update the spec to assert that result.imageOutputTokens equals
reportedImageOutputTokens, result.imageOutputCost is close to
expectedImageOutput (use the same discountMultiplier and per-token rate used to
compute expectedImageOutputCost), and result.totalCost equals the sum of
inputCost and outputCost (or assert closeTo expectedTextInputCost +
expectedImageInputCost + expectedImageOutputCost). Reference the test name,
calculateCosts call, and result properties imageOutputTokens, imageOutputCost,
and totalCost when adding these assertions.
OpenAI's official pricing table lists no text-output price for gpt-image-2 — the model only emits image output. The stale 15/1M was a holdover; set to 0 to match the published rate card. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add a new `cachedImageInputPrice` field on `ProviderModelMapping`. For providers whose `prompt_tokens` already includes image tokens (OpenAI/Azure/xAI on image-output models), apportion the upstream `cached_tokens` count between text and image by the overall image:text ratio in `prompt_tokens`, then bill each portion at its own rate. Fixes double-billing of image-cached tokens (previously charged at both `imageInputPrice` and `cachedInputPrice`). For gpt-image-2 (openai+azure): cachedInputPrice $1.25/M (text-cached, was $2) cachedImageInputPrice $2/M (image-cached, new field) Also updates the "free flag" sanity test to recognise image-output models that set `outputPrice=0` but bill via `imageOutputPrice`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
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 `@apps/gateway/src/lib/costs.spec.ts`:
- Around line 546-549: The test currently computes discountMultiplier from a
possibly undefined azureProvider, which yields a false-positive when the lookup
fails; update the test around the models lookup so you assert that the Azure
mapping was found (e.g., assert/expect that azureProvider is defined) before
computing discountMultiplier, and fail the test if it's missing so the test
genuinely verifies Azure-discount behavior (reference the azureProvider, models
lookup, and discountMultiplier identifiers when making the change).
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: afe423ad-20a0-44e7-9d80-7bf69c03e3f0
📒 Files selected for processing (5)
apps/gateway/src/lib/costs.spec.tsapps/gateway/src/lib/costs.tspackages/actions/src/models.spec.tspackages/models/src/models.tspackages/models/src/models/openai.ts
✅ Files skipped from review due to trivial changes (1)
- packages/actions/src/models.spec.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/models/src/models/openai.ts
- apps/gateway/src/lib/costs.ts
| const azureProvider = models | ||
| .find((m) => m.id === "gpt-image-2") | ||
| ?.providers.find((p) => p.providerId === "azure"); | ||
| const discountMultiplier = 1 - (azureProvider?.discount ?? 0); |
There was a problem hiding this comment.
Guard against false positives by asserting Azure model mapping exists.
If the lookup fails, discountMultiplier becomes 1 and this test no longer proves Azure-discount behavior.
💡 Proposed test hardening
const azureProvider = models
.find((m) => m.id === "gpt-image-2")
?.providers.find((p) => p.providerId === "azure");
+ expect(azureProvider).toBeDefined();
const discountMultiplier = 1 - (azureProvider?.discount ?? 0);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const azureProvider = models | |
| .find((m) => m.id === "gpt-image-2") | |
| ?.providers.find((p) => p.providerId === "azure"); | |
| const discountMultiplier = 1 - (azureProvider?.discount ?? 0); | |
| const azureProvider = models | |
| .find((m) => m.id === "gpt-image-2") | |
| ?.providers.find((p) => p.providerId === "azure"); | |
| expect(azureProvider).toBeDefined(); | |
| const discountMultiplier = 1 - (azureProvider?.discount ?? 0); |
🤖 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 `@apps/gateway/src/lib/costs.spec.ts` around lines 546 - 549, The test
currently computes discountMultiplier from a possibly undefined azureProvider,
which yields a false-positive when the lookup fails; update the test around the
models lookup so you assert that the Azure mapping was found (e.g.,
assert/expect that azureProvider is defined) before computing
discountMultiplier, and fail the test if it's missing so the test genuinely
verifies Azure-discount behavior (reference the azureProvider, models lookup,
and discountMultiplier identifiers when making the change).
Type fix after merging main: the new aws-bedrock cache write test was calling calculateCosts without nulls for the reportedImageInputTokens / reportedImageOutputTokens slots that were inserted before the options object on this branch. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Summary
input_tokens_details.image_tokensfrom OpenAI/Azuregpt-image-2responses against a newimageInputPriceof $8/M, and correctinputPricefrom $8/M to $5/M (the actual text-input rate). Together this stops billing image input tokens at the text rate and aligns gateway pricing with OpenAI's published rates.imageInputTokens/imageOutputTokensfrom the parsed response throughcalculateCosts, and prefer those over the legacyinputImageCount * 560estimate when the model hasoutput: \[\"image\"].inputPriceso the image portion isn't double-billed at both the text and image rates.gpt-image-1isn't defined in this repo's model catalog (onlygpt-image-2), so there's nothing to change there.Verified pricing (per 1M tokens)
Sources: OpenAI developer docs pricing table, the existing research note in `docs/providers/openai/gpt-image-2/openai-gpt-image-2.md`.
Why scoped to image-output models
isImageOutputModel = modelInfo.output?.includes(\"image\")gates the new "use reported tokens" path. Without this gate,gpt-4o's legacy per-imageimageInputPrice: 0.00553(which is encoded as a flat per-image fee, not per-token) would get applied to thousands of vision tokens and over-bill catastrophically. That field is dormant today (becauseinputImageCountonly fires for Gemini image-preview models) and a separate cleanup PR can remove it.Test plan
apps/gateway/src/lib/costs.spec.ts— 27/27 pass, including two new tests covering OpenAI and Azure gpt-image-2 image-edit billingapps/gateway/src/{lib,chat}— 518/518 passpackages/actions— 94/94 passpnpm build:corecleanapps/gateway/src/images.e2e.ts(gpt-image-2edits) when run with live OpenAI/Azure credentials🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Tests