feat: add AI providers HTTP CRUD handlers (#24894)

This commit is contained in:
Danny Kopping
2026-05-20 10:21:36 +02:00
committed by GitHub
parent fe01efeb21
commit dd3223451b
27 changed files with 3260 additions and 150 deletions
+75
View File
@@ -19,6 +19,7 @@ import (
"tailscale.com/tailcfg"
agentproto "github.com/coder/coder/v2/agent/proto"
aibridgeutils "github.com/coder/coder/v2/aibridge/utils"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/externalauth/gitprovider"
@@ -42,6 +43,80 @@ func APIAllowListTarget(entry rbac.AllowListElement) codersdk.APIAllowListTarget
}
}
// AIProvider converts a database row plus its API keys into the
// codersdk shape. The caller is responsible for ensuring the row and
// keys have been decrypted (i.e. fetched through the dbcrypt-wrapped
// store). Each api_key is masked via aibridge utils.MaskSecret and
// write-only fields on Settings are stripped, so the result is safe
// to echo back in API responses.
func AIProvider(row database.AIProvider, keys []database.AIProviderKey) (codersdk.AIProvider, error) {
display := row.Name
if row.DisplayName.Valid && row.DisplayName.String != "" {
display = row.DisplayName.String
}
out := codersdk.AIProvider{
ID: row.ID,
Type: codersdk.AIProviderType(row.Type),
Name: row.Name,
DisplayName: display,
Enabled: row.Enabled,
BaseURL: row.BaseUrl,
APIKeys: maskAIProviderKeys(keys),
CreatedAt: row.CreatedAt,
UpdatedAt: row.UpdatedAt,
}
s, err := AIProviderSettings(row.Settings)
if err != nil {
return codersdk.AIProvider{}, xerrors.Errorf("decode settings: %w", err)
}
out.Settings = redactAIProviderSettings(s)
return out, nil
}
// AIProviderSettings parses the on-disk JSON form back into a codersdk
// settings value. SQL NULL and the empty string decode to the zero
// value.
func AIProviderSettings(col sql.NullString) (codersdk.AIProviderSettings, error) {
if !col.Valid || col.String == "" {
return codersdk.AIProviderSettings{}, nil
}
var s codersdk.AIProviderSettings
if err := json.Unmarshal([]byte(col.String), &s); err != nil {
return codersdk.AIProviderSettings{}, err
}
return s, nil
}
// maskAIProviderKeys converts the supplied database rows into the
// public-facing AIProviderKey shape, preserving order. Plaintext is
// replaced by a non-reversible mask (see aibridgeutils.MaskSecret) so
// the result is safe to embed in API responses.
func maskAIProviderKeys(keys []database.AIProviderKey) []codersdk.AIProviderKey {
out := make([]codersdk.AIProviderKey, 0, len(keys))
for _, k := range keys {
out = append(out, codersdk.AIProviderKey{
ID: k.ID,
Masked: aibridgeutils.MaskSecret(k.APIKey),
CreatedAt: k.CreatedAt,
})
}
return out
}
// redactAIProviderSettings strips write-only fields from a settings
// value so it can be safely echoed back in API responses.
func redactAIProviderSettings(s codersdk.AIProviderSettings) codersdk.AIProviderSettings {
out := s
if out.Bedrock != nil {
// Deep-copy so we don't mutate the caller's struct.
b := *out.Bedrock
b.AccessKey = nil
b.AccessKeySecret = nil
out.Bedrock = &b
}
return out
}
type ExternalAuthMeta struct {
Authenticated bool
ValidateError string