mirror of
https://github.com/coder/coder.git
synced 2025-07-12 00:14:10 +00:00
feat: add database tables and API routes for agentic chat feature (#17570)
Backend portion of experimental `AgenticChat` feature: - Adds database tables for chats and chat messages - Adds functionality to stream messages from LLM providers using `kylecarbs/aisdk-go` - Adds API routes with relevant functionality (list, create, update chats, insert chat message) - Adds experiment `codersdk.AgenticChat` --------- Co-authored-by: Kyle Carberry <kyle@carberry.com>
This commit is contained in:
@ -61,6 +61,7 @@ import (
|
||||
"github.com/coder/serpent"
|
||||
"github.com/coder/wgtunnel/tunnelsdk"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/ai"
|
||||
"github.com/coder/coder/v2/coderd/entitlements"
|
||||
"github.com/coder/coder/v2/coderd/notifications/reports"
|
||||
"github.com/coder/coder/v2/coderd/runtimeconfig"
|
||||
@ -610,6 +611,22 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
)
|
||||
}
|
||||
|
||||
aiProviders, err := ReadAIProvidersFromEnv(os.Environ())
|
||||
if err != nil {
|
||||
return xerrors.Errorf("read ai providers from env: %w", err)
|
||||
}
|
||||
vals.AI.Value.Providers = append(vals.AI.Value.Providers, aiProviders...)
|
||||
for _, provider := range aiProviders {
|
||||
logger.Debug(
|
||||
ctx, "loaded ai provider",
|
||||
slog.F("type", provider.Type),
|
||||
)
|
||||
}
|
||||
languageModels, err := ai.ModelsFromConfig(ctx, vals.AI.Value.Providers)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create language models: %w", err)
|
||||
}
|
||||
|
||||
realIPConfig, err := httpmw.ParseRealIPConfig(vals.ProxyTrustedHeaders, vals.ProxyTrustedOrigins)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("parse real ip config: %w", err)
|
||||
@ -640,6 +657,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
CacheDir: cacheDir,
|
||||
GoogleTokenValidator: googleTokenValidator,
|
||||
ExternalAuthConfigs: externalAuthConfigs,
|
||||
LanguageModels: languageModels,
|
||||
RealIPConfig: realIPConfig,
|
||||
SSHKeygenAlgorithm: sshKeygenAlgorithm,
|
||||
TracerProvider: tracerProvider,
|
||||
@ -2621,6 +2639,77 @@ func redirectHTTPToHTTPSDeprecation(ctx context.Context, logger slog.Logger, inv
|
||||
}
|
||||
}
|
||||
|
||||
func ReadAIProvidersFromEnv(environ []string) ([]codersdk.AIProviderConfig, error) {
|
||||
// The index numbers must be in-order.
|
||||
sort.Strings(environ)
|
||||
|
||||
var providers []codersdk.AIProviderConfig
|
||||
for _, v := range serpent.ParseEnviron(environ, "CODER_AI_PROVIDER_") {
|
||||
tokens := strings.SplitN(v.Name, "_", 2)
|
||||
if len(tokens) != 2 {
|
||||
return nil, xerrors.Errorf("invalid env var: %s", v.Name)
|
||||
}
|
||||
|
||||
providerNum, err := strconv.Atoi(tokens[0])
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("parse number: %s", v.Name)
|
||||
}
|
||||
|
||||
var provider codersdk.AIProviderConfig
|
||||
switch {
|
||||
case len(providers) < providerNum:
|
||||
return nil, xerrors.Errorf(
|
||||
"provider num %v skipped: %s",
|
||||
len(providers),
|
||||
v.Name,
|
||||
)
|
||||
case len(providers) == providerNum:
|
||||
// At the next next provider.
|
||||
providers = append(providers, provider)
|
||||
case len(providers) == providerNum+1:
|
||||
// At the current provider.
|
||||
provider = providers[providerNum]
|
||||
}
|
||||
|
||||
key := tokens[1]
|
||||
switch key {
|
||||
case "TYPE":
|
||||
provider.Type = v.Value
|
||||
case "API_KEY":
|
||||
provider.APIKey = v.Value
|
||||
case "BASE_URL":
|
||||
provider.BaseURL = v.Value
|
||||
case "MODELS":
|
||||
provider.Models = strings.Split(v.Value, ",")
|
||||
}
|
||||
providers[providerNum] = provider
|
||||
}
|
||||
for _, envVar := range environ {
|
||||
tokens := strings.SplitN(envVar, "=", 2)
|
||||
if len(tokens) != 2 {
|
||||
continue
|
||||
}
|
||||
switch tokens[0] {
|
||||
case "OPENAI_API_KEY":
|
||||
providers = append(providers, codersdk.AIProviderConfig{
|
||||
Type: "openai",
|
||||
APIKey: tokens[1],
|
||||
})
|
||||
case "ANTHROPIC_API_KEY":
|
||||
providers = append(providers, codersdk.AIProviderConfig{
|
||||
Type: "anthropic",
|
||||
APIKey: tokens[1],
|
||||
})
|
||||
case "GOOGLE_API_KEY":
|
||||
providers = append(providers, codersdk.AIProviderConfig{
|
||||
Type: "google",
|
||||
APIKey: tokens[1],
|
||||
})
|
||||
}
|
||||
}
|
||||
return providers, nil
|
||||
}
|
||||
|
||||
// ReadExternalAuthProvidersFromEnv is provided for compatibility purposes with
|
||||
// the viper CLI.
|
||||
func ReadExternalAuthProvidersFromEnv(environ []string) ([]codersdk.ExternalAuthConfig, error) {
|
||||
|
Reference in New Issue
Block a user