Skip to content

feat(models): add discount field - #894

Merged
steebchen merged 7 commits into
mainfrom
terragon/add-discount-field-routeway-models
Sep 21, 2025
Merged

steebchen merged 7 commits into
mainfrom
terragon/add-discount-field-routeway-models

Conversation

@steebchen

@steebchen steebchen commented Sep 19, 2025

Copy link
Copy Markdown
Member

Summary

  • Introduces a discount field to the cost calculation and logging system
  • Applies discount multipliers for Routeway provider models based on environment variable
  • Updates UI to display discount information in the dashboard log card
  • Adds tests to verify discount application and absence when not applicable

Changes

Core Functionality

  • Added discount field to cost calculation results in calculateCosts function
  • Multiplied input, output, cached input, and request costs by discount factor
  • Included discount field in database schema for logs
  • Updated model definitions to include optional discount multiplier
  • Refactored Anthropics models to use discount field instead of price adjustment functions

API and Chat Layer

  • Extended chat response objects to include discount field

UI Components

  • Enhanced LogCard component to show discount percentage when applicable

Testing

  • Added unit tests in costs.spec.ts to verify discount application for Routeway models
  • Confirmed no discount field is present when discount is not applied

Test plan

  • Run existing and new unit tests for cost calculations
  • Verify discount appears correctly in logs and UI when environment variable is set
  • Confirm no discount field or UI display when discount is not applied
  • Manual testing of chat completions to ensure discount field is included in responses

🌿 Generated by Terry


ℹ️ Tag @terragon-labs to ask questions and address PR feedback

📎 Task: https://www.terragonlabs.com/task/c3981849-cc09-4ea9-8fb2-ddd3a1bbc8d5

Summary by CodeRabbit

  • New Features

    • Discount-aware pricing and cost calculations across the app.
    • Logs now include discount when applicable; UI displays “Discount Applied” with percentage on log cards.
    • Cheapest provider/model selection now factors in provider discounts.
  • Tests

    • Added tests covering discount handling in pricing, selection logic, and cost computations.
  • Chores

    • Database updated to store discount on log entries.
    • Migration metadata added; minor formatting adjustments to build scripts.

- Add discount field to cost calculation results and apply discount multiplier to input, output, cached input, and request costs.
- Include discount field in database schema for logs.
- Update anthropic models to include discount from environment variable ROUTEWAY_PAID_DISCOUNT.
- Display discount information in UI log card component when applicable.
- Add tests to verify discount application and absence when no discount is applied.
- Refactor cost calculation to handle discount properly and default to undefined when no discount.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Sep 19, 2025

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@steebchen has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 10 minutes and 31 seconds before requesting another review.

⌛ How to resolve this issue?

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

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

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

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

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 1956530 and 54717f3.

📒 Files selected for processing (5)
  • packages/db/migrations/1758490715_groovy_bromley.sql (1 hunks)
  • packages/db/migrations/meta/1758490715_snapshot.json (1 hunks)
  • packages/db/migrations/meta/_journal.json (1 hunks)
  • packages/db/src/schema.ts (1 hunks)
  • packages/models/src/get-cheapest-model-for-provider.ts (2 hunks)

Walkthrough

Adds a discount multiplier across pricing, cost calculation, logging, DB schema, and UI. Costs now factor provider/model discounts; logs persist discount; UI conditionally displays it. Cheapest-model/provider selection incorporates discount. Database gains a discount column and snapshot/journal updates. Minor formatting change in conductor.json. New tests cover discount behavior.

Changes

Cohort / File(s) Summary
Gateway costs & logging
apps/gateway/src/chat/chat.ts, apps/gateway/src/lib/costs.ts, apps/gateway/src/lib/costs.spec.ts
Propagate discount through cost computation and log payloads; include discount in streaming/non-streaming and error/cancel paths (often null/undefined when 1). Tests added for discount math; note duplicate discount test block in spec.
Models pricing & selection
packages/models/src/models.ts, packages/models/src/types.ts, packages/models/src/models/anthropic.ts, packages/models/src/get-cheapest-from-available-providers.ts, packages/models/src/get-cheapest-model-for-provider.ts, packages/models/src/models.spec.ts
Introduce optional discount on provider mappings; apply discount in cheapest-model/provider calculations; refactor Anthropics pricing to explicit base prices plus discount field; add tests validating discount-aware selection.
Database schema & metadata
packages/db/src/schema.ts, packages/db/migrations/1758325232_happy_bloodstrike.sql, packages/db/migrations/meta/1758325232_snapshot.json, packages/db/migrations/meta/_journal.json
Add real column discount to log table; update schema, snapshot, and journal to reflect new migration/tag.
UI log display
apps/ui/src/components/dashboard/log-card.tsx
Conditionally render “Discount Applied” with percentage when log.discount exists and != 1.
Config formatting
conductor.json
Indentation-only formatting change in scripts block; no content changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant Gateway as Gateway Chat
  participant Costs as Costs Calculator
  participant DB as DB (log table)
  participant UI as Dashboard UI

  Client->>Gateway: Chat request
  Gateway->>Costs: Compute costs (input/output/cached) + discount
  Costs-->>Gateway: Cost object { ..., discount }
  Gateway->>DB: Create log { costs..., discount }
  Gateway-->>Client: Response (stream/non-stream)

  Note over UI,DB: Later, UI fetches log
  UI->>DB: Get log entry
  DB-->>UI: { ..., discount }
  UI-->>UI: If discount && != 1 → show "Discount Applied" %
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

auto-merge

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat(models): add discount field" is concise, follows conventional commit style, and accurately signals the primary change (adding a discount field) present in the diff; it is therefore clearly related to the changeset. It could be slightly narrower about scope since the PR also updates cost calculations, logging, the DB schema, and UI, but it remains a valid, informative title for reviewers scanning history.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot changed the title Add discount field support for Routeway models and cost calculations feat(models): add discount field Sep 19, 2025
- Add tests to validate discount logic without relying on environment variables.
- Replace model-specific tests with generic calculation validation.
- Ensure proper handling of discount fields and costs when applicable.
Update `providerId` values to use `routeway-discount` for consistency
with discount-support changes in Anthropic models.
- Add `discount` field to `ProviderModelMapping` type.
- Update pricing logic to account for discounts in cost calculations.
- Enhance test coverage to validate discount behavior across providers.
…models

Resolved conflicts by:
- Keeping discount field approach for routeway models
- Adding missing cachedInputPrice fields
- Combining cached token logic with discount support
- Merging both test suites for costs functionality
@steebchen
steebchen marked this pull request as ready for review September 21, 2025 21:23

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

Actionable comments posted: 1

🧹 Nitpick comments (10)
conductor.json (1)

3-3: Setup script can overwrite local .env; guard and make copy safer.

Consider ensuring CONDUCTOR_ROOT_PATH is set and prevent accidental overwrite of an existing .env.

-    "setup": "cp \"$CONDUCTOR_ROOT_PATH/.env\" .\n\npnpm install"
+    "setup": "if [ -z \"${CONDUCTOR_ROOT_PATH:-}\" ]; then echo 'CONDUCTOR_ROOT_PATH not set' >&2; exit 1; fi\ncp -n \"$CONDUCTOR_ROOT_PATH/.env\" .\npnpm install"
packages/models/src/types.ts (1)

253-261: Add discount field to ModelWithPricing providers — LGTM

Type shape aligns with ProviderModelMapping. Consider documenting that this is a multiplier in [0, 1], where 1 = no discount.

packages/db/migrations/1758325232_happy_bloodstrike.sql (1)

1-1: Migration adds log.discount — verify it was generated, and consider a range constraint

  • Confirm this .sql came from the standard generator (per guidelines).
  • Optionally enforce a CHECK (0 ≤ discount ≤ 1) via schema so future migrations carry it.

Would you like me to add a Drizzle check constraint in schema.ts and regenerate the migration?

packages/models/src/models.ts (1)

46-49: Clarify discount semantics in docs

Recommend tightening the comment to state allowed range and default behavior (omit or 1 = no discount).

Apply this diff to improve the doc:

-  /**
-   * Discount multiplier (0-1), where 0.5 = 50% off
-   */
+  /**
+   * Discount multiplier in [0, 1]. Use 1 or omit for no discount (e.g., 0.5 = 50% off).
+   */
packages/models/src/get-cheapest-model-for-provider.ts (1)

29-36: Cheapest computation ignores requestPrice/image/cached components

The function’s doc says “based on input + output” so this may be intentional; just flagging that providers with non‑zero requestPrice won’t be reflected here while other helpers may factor them in.

Do you want this helper to remain “avg(io) only”, or to mirror the fuller price heuristic used elsewhere?

packages/db/src/schema.ts (1)

371-372: Schema column for discount — LGTM

Nullable real fits the logging use (only present when discount ≠ 1). If you later want enforcement, consider a CHECK constraint in schema and regen migrations.

apps/gateway/src/chat/chat.ts (1)

2732-2732: Coalesce to null to avoid inserting undefined

Small robustness tweak so the column is explicitly null when no discount applies.

Apply this diff:

-          discount: costs.discount,
+          discount: costs.discount ?? null,

Please confirm calculateCosts only returns a value when multiplier ≠ 1 and clamps to [0,1].

Also applies to: 3124-3124

apps/ui/src/components/dashboard/log-card.tsx (1)

288-297: Don’t hide 0 (100% off); avoid truthy check

The current condition log.discount && ... suppresses display when discount === 0. Use explicit null/undefined checks and clamp percentage.

Apply this diff:

-                {log.discount && log.discount !== 1 && (
+                {log.discount !== null && log.discount !== undefined && log.discount !== 1 && (
                   <>
                     <div className="text-muted-foreground">
                       Discount Applied
                     </div>
                     <div className="font-medium text-green-600">
-                      {((1 - log.discount) * 100).toFixed(0)}% off
+                      {Math.max(0, Math.min(100, (1 - log.discount) * 100)).toFixed(0)}% off
                     </div>
                   </>
                 )}
apps/gateway/src/lib/costs.spec.ts (1)

135-173: Comprehensive discount testing in cost calculations

The test validates discount functionality thoroughly:

  • Verifies that models without discount have undefined discount field
  • Tests expected cost calculations with discount multipliers
  • Validates that discount field is properly excluded when not applicable

However, there appears to be some test duplication and complexity due to environment variable timing issues.

Consider simplifying the test by creating mock provider data with explicit discount values instead of relying on environment variables, which can make tests more predictable and easier to understand.

packages/models/src/models/anthropic.ts (1)

52-52: Consider adding validation for the discount environment variable

While the current implementation with parseFloat() and fallback to "1" is functional, consider adding validation to ensure the discount value is within expected bounds (e.g., 0 < discount ≤ 1) and handle potential parsing errors more explicitly.

Consider adding validation logic such as:

const rawDiscount = process.env.ROUTEWAY_PAID_DISCOUNT;
const parsedDiscount = rawDiscount ? parseFloat(rawDiscount) : 1;
const discount = !isNaN(parsedDiscount) && parsedDiscount > 0 && parsedDiscount <= 1 
  ? parsedDiscount 
  : 1;

This would provide better error handling and ensure discount values are within sensible bounds.

Also applies to: 89-89, 170-170, 208-208, 247-247

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b9dc721 and 1956530.

📒 Files selected for processing (15)
  • apps/gateway/src/chat/chat.ts (8 hunks)
  • apps/gateway/src/lib/costs.spec.ts (1 hunks)
  • apps/gateway/src/lib/costs.ts (5 hunks)
  • apps/ui/src/components/dashboard/log-card.tsx (1 hunks)
  • conductor.json (1 hunks)
  • packages/db/migrations/1758325232_happy_bloodstrike.sql (1 hunks)
  • packages/db/migrations/meta/1758325232_snapshot.json (1 hunks)
  • packages/db/migrations/meta/_journal.json (1 hunks)
  • packages/db/src/schema.ts (1 hunks)
  • packages/models/src/get-cheapest-from-available-providers.ts (2 hunks)
  • packages/models/src/get-cheapest-model-for-provider.ts (2 hunks)
  • packages/models/src/models.spec.ts (2 hunks)
  • packages/models/src/models.ts (1 hunks)
  • packages/models/src/models/anthropic.ts (5 hunks)
  • packages/models/src/types.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (9)
{apps/api,apps/gateway,apps/ui,apps/docs,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Always use top-level import; never use require() or dynamic imports (e.g., import(), next/dynamic)

Files:

  • packages/models/src/types.ts
  • packages/models/src/get-cheapest-model-for-provider.ts
  • packages/db/src/schema.ts
  • apps/ui/src/components/dashboard/log-card.tsx
  • packages/models/src/models.spec.ts
  • apps/gateway/src/chat/chat.ts
  • packages/models/src/get-cheapest-from-available-providers.ts
  • packages/models/src/models.ts
  • apps/gateway/src/lib/costs.spec.ts
  • apps/gateway/src/lib/costs.ts
  • packages/models/src/models/anthropic.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Never use any or as any in this TypeScript project unless absolutely necessary
Always use top-level import; never use require or dynamic imports

Files:

  • packages/models/src/types.ts
  • packages/models/src/get-cheapest-model-for-provider.ts
  • packages/db/src/schema.ts
  • apps/ui/src/components/dashboard/log-card.tsx
  • packages/models/src/models.spec.ts
  • apps/gateway/src/chat/chat.ts
  • packages/models/src/get-cheapest-from-available-providers.ts
  • packages/models/src/models.ts
  • apps/gateway/src/lib/costs.spec.ts
  • apps/gateway/src/lib/costs.ts
  • packages/models/src/models/anthropic.ts
{apps/api,apps/gateway,packages/db}/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

{apps/api,apps/gateway,packages/db}/**/*.ts: Use Drizzle ORM with the latest object syntax for database access
For reads, use db().query.

.findMany() or db().query.
.findFirst()

Files:

  • packages/db/src/schema.ts
  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/lib/costs.spec.ts
  • apps/gateway/src/lib/costs.ts
packages/db/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Drizzle ORM with the latest object syntax for schema and DB utilities

Files:

  • packages/db/src/schema.ts
apps/ui/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use next/link for links and next/navigation’s router for programmatic navigation

apps/ui/**/*.{ts,tsx}: Use next/link for links and next/navigation's router for programmatic navigation in the UI
Use cookies for user settings not saved in the database to ensure SSR works

Files:

  • apps/ui/src/components/dashboard/log-card.tsx
**/*.spec.ts

📄 CodeRabbit inference engine (AGENTS.md)

Place unit tests in files named *.spec.ts

Unit test files must be named with the .spec.ts suffix

Files:

  • packages/models/src/models.spec.ts
  • apps/gateway/src/lib/costs.spec.ts
{apps/api,apps/gateway}/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

For reads, use db().query.

.findMany() or db().query.
.findFirst()

Files:

  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/lib/costs.spec.ts
  • apps/gateway/src/lib/costs.ts
packages/db/**/*.sql

📄 CodeRabbit inference engine (AGENTS.md)

Do not write migrations manually; avoid adding .sql files (use pnpm run setup to generate/sync)

Files:

  • packages/db/migrations/1758325232_happy_bloodstrike.sql
packages/db/migrations/**/*.sql

📄 CodeRabbit inference engine (CLAUDE.md)

Do not write migrations manually; use pnpm run setup which generates .sql files

Files:

  • packages/db/migrations/1758325232_happy_bloodstrike.sql
🧬 Code graph analysis (4)
packages/models/src/get-cheapest-model-for-provider.ts (1)
packages/models/src/models.ts (1)
  • ProviderModelMapping (23-104)
apps/ui/src/components/dashboard/log-card.tsx (1)
packages/db/src/schema.ts (1)
  • log (324-389)
packages/models/src/models.spec.ts (3)
packages/models/src/models.ts (2)
  • models (163-179)
  • ProviderModelMapping (23-104)
packages/models/src/get-cheapest-model-for-provider.ts (1)
  • getCheapestModelForProvider (9-43)
packages/models/src/get-cheapest-from-available-providers.ts (1)
  • getCheapestFromAvailableProviders (7-34)
packages/models/src/get-cheapest-from-available-providers.ts (1)
packages/models/src/models.ts (1)
  • ProviderModelMapping (23-104)
🔇 Additional comments (18)
conductor.json (1)

2-4: Formatting-only change; verify tab indentation matches repo conventions.

No behavioral impact. Please confirm tabs align with your Prettier/EditorConfig settings to avoid future whitespace-only churn. If the repo standard is spaces, consider reverting to auto-formatted spacing.

packages/models/src/get-cheapest-model-for-provider.ts (1)

3-3: Type-only import — no issues

packages/db/migrations/meta/_journal.json (1)

354-361: Journal entry looks consistent

New tag recorded as expected.

apps/gateway/src/chat/chat.ts (2)

1440-1440: Set discount to null in cached/error/canceled logs — LGTM

Consistent with “no cost on these paths.” No action needed.

Also applies to: 1534-1534, 1748-1748, 1878-1878, 2857-2857, 2960-2960


800-906: Auto routing cost heuristic doesn’t factor discount here

Within auto selection, totalPrice uses only raw input/output averages; discount is only applied later in getCheapestFromAvailableProviders. That’s fine if intentional, but it can bias the first-stage filter.

Would you like me to align the early filter with discount as well?

Also applies to: 869-894

packages/models/src/get-cheapest-from-available-providers.ts (2)

1-1: LGTM: Import addition for discount support

The import of ProviderModelMapping is correctly added to support the new discount functionality.


21-25: LGTM: Discount calculation implementation

The discount logic is correctly implemented:

  • Reads discount from provider info, defaulting to 1 (no discount)
  • Applies discount multiplier to the total price calculation
  • Maintains existing comparison logic for cheapest provider selection
packages/models/src/models.spec.ts (3)

3-3: LGTM: Test imports for discount functionality

The imports for getCheapestFromAvailableProviders and ProviderModelMapping are correctly added to support the new discount testing.

Also applies to: 8-8


336-407: Comprehensive discount testing for cheapest model selection

The test correctly validates that discount multipliers are properly considered when selecting the cheapest model for a provider. The test logic appropriately:

  • Finds models with discount providers
  • Compares regular vs. discounted pricing
  • Validates that discounted providers are cheaper when expected
  • Tests both getCheapestModelForProvider and getCheapestFromAvailableProviders functions

410-509: Thorough testing of cheapest provider selection with discounts

The test suite comprehensively validates discount handling in provider selection:

  • Tests basic cheapest provider selection
  • Validates discount application by comparing regular vs. discounted providers
  • Correctly calculates expected discounted prices and verifies selection logic
  • Includes edge case testing with empty provider lists
apps/gateway/src/lib/costs.ts (4)

52-52: Correct discount field handling in error paths

The discount: undefined field is correctly added to all error return paths, ensuring consistent API response structure when model info is not found or token counts are missing.

Also applies to: 116-116, 136-136


144-144: LGTM: Discount extraction from provider info

The discount value is correctly extracted from providerInfo.discount with a sensible default of 1 (no discount).


153-158: Proper discount application to all cost components

The discount multiplier is correctly applied to all relevant cost calculations:

  • Input cost (uncached prompt tokens)
  • Output cost (completion tokens)
  • Cached input cost
  • Request cost

This ensures consistent discount application across all pricing components.


171-171: Clean discount field in response object

The discount field is properly included in the response only when different from 1, keeping the API clean by not including redundant information for non-discounted pricing.

packages/db/migrations/meta/1758325232_snapshot.json (1)

817-822: Database schema correctly updated for discount column

The migration snapshot correctly adds the discount column to the log table:

  • Type: real (appropriate for discount multipliers)
  • Nullable: true (allows for logs without discount information)
  • Positioned logically among other cost-related fields
packages/models/src/models/anthropic.ts (3)

49-50: Clean refactor from computed to explicit pricing

The change from computed discount pricing to explicit inputPrice/outputPrice values with a separate discount field is a good architectural improvement:

  • Makes pricing more transparent and easier to understand
  • Separates concerns between base pricing and discount application
  • The discount field correctly uses environment variable parsing with fallback

Also applies to: 52-52


86-87: Consistent discount implementation across models

The same refactoring pattern is consistently applied to the Claude 3.7 Sonnet model, maintaining uniformity across all routeway-discount providers.

Also applies to: 89-89


167-168: Uniform discount handling across all Anthropic models

The refactoring is consistently applied across all models with routeway-discount providers:

  • Claude Sonnet 4
  • Claude Opus 4
  • Claude Opus 4.1

This maintains consistency and makes the discount logic explicit and transparent.

Also applies to: 170-170, 205-206, 208-208, 244-245, 247-247

Comment thread packages/models/src/get-cheapest-model-for-provider.ts Outdated
@steebchen
steebchen added this pull request to the merge queue Sep 21, 2025
Merged via the queue into main with commit 522ee57 Sep 21, 2025
13 checks passed
@steebchen
steebchen deleted the terragon/add-discount-field-routeway-models branch September 21, 2025 21:48
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.

1 participant