Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat: distinguish default and custom AI model prices - #28204

Merged
ssncferreira merged 6 commits into
mainfrom
ssncf/ai-model-price-source
Aug 20, 2026
Merged

feat: distinguish default and custom AI model prices#28204
ssncferreira merged 6 commits into
mainfrom
ssncf/ai-model-price-source

Conversation

@ssncferreira

@ssncferreira ssncferreira commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Description

Prices in ai_model_prices come from two places: the price book embedded in each Coder release, and prices set through the experimental CLI (#27926). Until now, they were indistinguishable, so the startup seeder re-applied the book over every row, and a price someone set would silently revert on the next restart.

This adds a source column with values default and custom, and keeps a row for each. Readers resolve a model to its custom price when one exists and fall back to the book, including the cost path that prices an interception.

Keeping both rows means the price book stays current underneath an override, so a future delete can restore it.

Note: the endpoint still only accepts models the price book does not cover. Overriding a price the book already carries is handled by the follow-up issue https://linear.app/codercom/issue/AIGOV-594.

Changes

  • Add a migration for the ai_model_price_source enum and a source column, backfilled to default and then NOT NULL. source joins the primary key, so a model can hold one row per source.
  • Take a source parameter on UpsertAIModelPrices. Each source keeps its own row, so the price book and a custom price never overwrite each other.
  • Resolve to the custom price in GetAIModelPriceByProviderModel and GetAIModelPrices, so both the interception cost snapshot and the list endpoint use it.
  • Seed the price book as default, and write prices set through the endpoint as custom.

Closes https://linear.app/codercom/issue/AIGOV-592

Note

Initially generated by Claude Opus 5, modified and reviewed by @ssncferreira

ssncferreira commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

@ssncferreira
ssncferreira force-pushed the ssncf/ai-model-price-source branch 5 times, most recently from beab408 to c2143fa Compare August 19, 2026 16:23
@linear-code

linear-code Bot commented Aug 19, 2026

Copy link
Copy Markdown

AIGOV-592

@ssncferreira
ssncferreira force-pushed the ssncf/ai-model-price-source branch from c2143fa to 29a2d26 Compare August 19, 2026 16:31
@ssncferreira
ssncferreira marked this pull request as ready for review August 19, 2026 16:31
Comment on lines +28 to +46
WHERE CASE
-- A custom price claims any row, including one the price book wrote.
WHEN @source::ai_model_price_source = 'custom' THEN true
-- The price book leaves custom rows alone.
ELSE ai_model_prices.source <> 'custom'
END
AND (
ai_model_prices.input_price,
ai_model_prices.output_price,
ai_model_prices.cache_read_price,
ai_model_prices.cache_write_price,
ai_model_prices.source
) IS DISTINCT FROM (
EXCLUDED.input_price,
EXCLUDED.output_price,
EXCLUDED.cache_read_price,
EXCLUDED.cache_write_price,
EXCLUDED.source
);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We could alternatively keep both rows with the custom and default source and then join / filter when querying? This would allow an administrator to delete their custom price and revert to the default from the price book.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is actually a great idea! 🏆 This way we won't lose the default price book! We just need to guarantee that the read queries resolve the model row: if custom is present return it, otherwise fallback to default, which will be used by the CLI/API as well as when calculating the interception cost. Will make these changes 👍

We currently don't have a way of deleting custom prices, but we can add a delete command to the CLI, for instance 🤔

@johnstcn johnstcn Aug 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We don't need to add the delete command now. For the sorting, you can do something like ORDER BY CASE WHEN ... THEN 1 ELSE 0

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think I prefer to do it now because this changes the logic for the new column source which will also need to be considered as part of the PK for ai_model_prices. Should be easy to do 👍

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 4af28ec

@ssncferreira
ssncferreira marked this pull request as draft August 19, 2026 17:20
@ssncferreira
ssncferreira force-pushed the ssncf/ai-model-price-source branch from e6affe7 to 4af28ec Compare August 19, 2026 19:20
@ssncferreira ssncferreira changed the title feat: add source column to ai_model_prices feat: distinguish default and custom AI model prices Aug 19, 2026
@ssncferreira
ssncferreira marked this pull request as ready for review August 19, 2026 19:46
@ssncferreira
ssncferreira requested a review from johnstcn August 19, 2026 19:46
Comment thread coderd/database/queries/aicostcontrol.sql Outdated
@ssncferreira
ssncferreira force-pushed the ssncf/ai-model-price-source branch from 350bc71 to 8e53141 Compare August 20, 2026 10:10

ssncferreira commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Merge activity

  • Aug 20, 10:36 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Aug 20, 10:36 AM UTC: @ssncferreira merged this pull request with Graphite.

@ssncferreira
ssncferreira merged commit 9390e51 into main Aug 20, 2026
25 checks passed
@ssncferreira
ssncferreira deleted the ssncf/ai-model-price-source branch August 20, 2026 10:37
ssncferreira added a commit that referenced this pull request Aug 20, 2026
## Description

The experimental CLI could only price models the price book does not cover, so an operator with a negotiated rate for a model Coder already prices had no way to record it. The restriction existed because a single row served both the price book and prices set through the API, and the startup seeder would overwrite an override on the next restart.

Each source now keeps its own row (#28204), so that no longer applies. This removes the restriction: a price set through the CLI takes effect over the price book for any model, and the book stays underneath it.

## Changes

- Remove the validation that rejected models the price book covers.
- Delete `prices.IsDefaultPriced` and the index behind it, which existed only for that check.
- Generalize the `coder exp ai-model-prices update` help.
- Document that a custom price overrides the price book and does not follow its updates.
- Rework the spend approximation callout around what a custom price can and cannot correct.

Closes https://linear.app/codercom/issue/AIGOV-594

> [!NOTE]
> Initially generated by Claude Opus 5, modified and reviewed by @ssncferreira
aslilac pushed a commit that referenced this pull request Aug 24, 2026
## Description

Prices in `ai_model_prices` come from two places: the price book embedded in each Coder release, and prices set through the experimental CLI (#27926). Until now, they were indistinguishable, so the startup seeder re-applied the book over every row, and a price someone set would silently revert on the next restart.

This adds a `source` column with values `default` and `custom`, and keeps a row for each. Readers resolve a model to its custom price when one exists and fall back to the book, including the cost path that prices an interception.

Keeping both rows means the price book stays current underneath an override, so a future delete can restore it.

Note: the endpoint still only accepts models the price book does not cover. Overriding a price the book already carries is handled by the follow-up issue https://linear.app/codercom/issue/AIGOV-594.

## Changes

- Add a migration for the `ai_model_price_source` enum and a `source` column, backfilled to `default` and then `NOT NULL`. `source` joins the primary key, so a model can hold one row per source.
- Take a `source` parameter on `UpsertAIModelPrices`. Each source keeps its own row, so the price book and a custom price never overwrite each other.
- Resolve to the custom price in `GetAIModelPriceByProviderModel` and `GetAIModelPrices`, so both the interception cost snapshot and the list endpoint use it.
- Seed the price book as `default`, and write prices set through the endpoint as `custom`.

Closes https://linear.app/codercom/issue/AIGOV-592

> [!NOTE]
> Initially generated by Claude Opus 5, modified and reviewed by @ssncferreira
aslilac pushed a commit that referenced this pull request Aug 24, 2026
## Description

The experimental CLI could only price models the price book does not cover, so an operator with a negotiated rate for a model Coder already prices had no way to record it. The restriction existed because a single row served both the price book and prices set through the API, and the startup seeder would overwrite an override on the next restart.

Each source now keeps its own row (#28204), so that no longer applies. This removes the restriction: a price set through the CLI takes effect over the price book for any model, and the book stays underneath it.

## Changes

- Remove the validation that rejected models the price book covers.
- Delete `prices.IsDefaultPriced` and the index behind it, which existed only for that check.
- Generalize the `coder exp ai-model-prices update` help.
- Document that a custom price overrides the price book and does not follow its updates.
- Rework the spend approximation callout around what a custom price can and cannot correct.

Closes https://linear.app/codercom/issue/AIGOV-594

> [!NOTE]
> Initially generated by Claude Opus 5, modified and reviewed by @ssncferreira
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.

3 participants