mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add AI providers HTTP CRUD handlers (#24894)
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user