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

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions coderd/aibridge/prices/prices.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"context"
_ "embed"
"encoding/json"
"sync"

"golang.org/x/xerrors"

Expand Down Expand Up @@ -60,3 +61,32 @@ func parseSeed(data []byte) ([]seedRow, error) {
}
return rows, nil
}

// modelKey identifies a priced model.
type modelKey struct {
provider string
model string
}

// defaultPricedModels indexes the embedded price book by provider and model.
// Built on first use, since a deployment that never sets a price never needs
// it.
var defaultPricedModels = sync.OnceValue(func() map[modelKey]struct{} {
rows, err := parseSeed(seedJSON)
if err != nil {
panic(xerrors.Errorf("parse embedded price seed: %w", err))
Comment thread
ssncferreira marked this conversation as resolved.
}
index := make(map[modelKey]struct{}, len(rows))
for _, row := range rows {
index[modelKey{provider: row.Provider, model: row.Model}] = struct{}{}
}
return index
})

// IsDefaultPriced reports whether the embedded price book already carries a
// price for the model. Coder owns those prices and re-applies them on every
// startup, so an operator price set for one would not survive a restart.
func IsDefaultPriced(provider, model string) bool {

@ssncferreira ssncferreira Aug 12, 2026

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.

Initially, I thought we would need to restore the models removed between 2.35 and 2.36. However, I think this is not required. After the patch lands, the CLI will only be available after 2.36, meaning that whatever was released on the price book is what is considered a model with "default" source (once AIGOV-589 lands), meaning that if operators decide to add custom values for removed models, these will be considered "custom".

So we have 2 paths going forward:

  1. Only consider prices that were available on 2.36 as default, meaning that if an operator changes a model that was available on 2.35 this will be considered a "custom" model.
  2. Consider all prices from when the prices table was first introduced in 2.35. However, if we do decide this route, we need to restore these models in the patch as well. Otherwise, operators might change these models via the CLI, and then once we introduce the previously removed models and the source column, these models would be considered "default" and would overwrite the prices defined by the operator.

Given that cost control was officially released in 2.36 and the cost reporting via the effective group is only available from 2.36 (even though raw interception cost without a group was released in 2.35, but all the data returned by the endpoints always guarantees the effective group constraint), and that it is unlikely that deprecated models will still be used, I would say option 1 is an acceptable approach.

However, I think this brings up another topic on how we should handle "deprecated" models going forward: should we remove them from the price book? Because this would mean that for customers that skip a version, some models might be considered "unpriced", while for customers that do not skip any versions, they would be considered "default" (since we never delete models from the database). In conclusion, this would mean that we could end up with different customers with different default models in their databases. Wdyt?

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.

I think it's acceptable for those models.
However, it might be simpler to just keep deprecated models around so that we are more likely to have a consistent list of prices. It's not a huge amount of data.

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.

Discussed internally and we decided that it is ok to not keep removed models in the price book. With the introduction of custom pricing, customers will be able to introduce models not built-in if needed.

_, ok := defaultPricedModels()[modelKey{provider: provider, model: model}]
return ok
}
53 changes: 53 additions & 0 deletions coderd/aibridge/prices/prices_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,56 @@ func TestSeed(t *testing.T) {
db, _ := dbtestutil.NewDB(t)
require.NoError(t, prices.Seed(ctx, db))
}

// TestIsDefaultPriced reads the real embedded price book, so it uses a model
// the generator injects rather than one that could drift out of upstream.
func TestIsDefaultPriced(t *testing.T) {
t.Parallel()

tests := []struct {
name string
provider string
model string
want bool
}{
{
name: "ModelInThePriceBook",
provider: "anthropic",
model: "claude-opus-5",
want: true,
},
{
name: "ModelNotInThePriceBook",
provider: "anthropic",
model: "not-a-real-model",
want: false,
},
{
// The book is keyed on both columns, so the same model under
// another provider is a different entry.
name: "SameModelUnderAnotherProvider",
provider: "openai",
model: "claude-opus-5",
want: false,
},
{
name: "UnknownProvider",
provider: "unknown-provider",
model: "claude-opus-5",
want: false,
},
{
name: "Empty",
provider: "",
model: "",
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
require.Equal(t, tt.want, prices.IsDefaultPriced(tt.provider, tt.model))
})
}
}
28 changes: 28 additions & 0 deletions coderd/aibridge/prices/providers/providers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package providers

import "github.com/coder/coder/v2/coderd/database"

// Supported lists the providers a model price may be set for.
//
// openai-compat is excluded: it is a generic passthrough, so the upstream
// vendor is unknown and a price cannot be attributed to it. Listed explicitly
// rather than derived from ai_provider_type so a new provider is opt-in.
var Supported = []database.AIProviderType{
database.AIProviderTypeAnthropic,
database.AIProviderTypeAzure,
database.AIProviderTypeBedrock,
database.AIProviderTypeCopilot,
database.AIProviderTypeGoogle,
database.AIProviderTypeOpenai,
database.AIProviderTypeOpenrouter,
database.AIProviderTypeVercel,
}

// SupportedStrings returns the supported providers as plain strings.
func SupportedStrings() []string {
ids := make([]string, len(Supported))
for i, provider := range Supported {
ids[i] = string(provider)
}
return ids
}
144 changes: 144 additions & 0 deletions coderd/apidoc/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading