feat: initialize aibridged & mount API handler (#19798)

Addresses https://github.com/coder/internal/issues/987
This commit is contained in:
Danny Kopping
2025-09-25 16:37:28 +02:00
committed by GitHub
parent 6971f612be
commit 0a79817050
14 changed files with 798 additions and 39 deletions
+47
View File
@@ -0,0 +1,47 @@
//go:build !slim
package cli
import (
"context"
"golang.org/x/xerrors"
"github.com/coder/aibridge"
"github.com/coder/coder/v2/enterprise/coderd"
"github.com/coder/coder/v2/enterprise/x/aibridged"
)
func newAIBridgeDaemon(coderAPI *coderd.API) (*aibridged.Server, error) {
ctx := context.Background()
coderAPI.Logger.Debug(ctx, "starting in-memory aibridge daemon")
logger := coderAPI.Logger.Named("aibridged")
// Setup supported providers.
providers := []aibridge.Provider{
aibridge.NewOpenAIProvider(aibridge.ProviderConfig{
BaseURL: coderAPI.DeploymentValues.AI.BridgeConfig.OpenAI.BaseURL.String(),
Key: coderAPI.DeploymentValues.AI.BridgeConfig.OpenAI.Key.String(),
}),
aibridge.NewAnthropicProvider(aibridge.ProviderConfig{
BaseURL: coderAPI.DeploymentValues.AI.BridgeConfig.Anthropic.BaseURL.String(),
Key: coderAPI.DeploymentValues.AI.BridgeConfig.Anthropic.Key.String(),
}),
}
// Create pool for reusable stateful [aibridge.RequestBridge] instances (one per user).
pool, err := aibridged.NewCachedBridgePool(aibridged.DefaultPoolOptions, providers, logger.Named("pool")) // TODO: configurable.
if err != nil {
return nil, xerrors.Errorf("create request pool: %w", err)
}
// Create daemon.
srv, err := aibridged.New(ctx, pool, func(dialCtx context.Context) (aibridged.DRPCClient, error) {
return coderAPI.CreateInMemoryAIBridgeServer(dialCtx)
}, logger)
if err != nil {
return nil, xerrors.Errorf("start in-memory aibridge daemon: %w", err)
}
return srv, nil
}
+30
View File
@@ -7,6 +7,7 @@ import (
"database/sql"
"encoding/base64"
"errors"
"fmt"
"io"
"net/url"
@@ -15,6 +16,7 @@ import (
"tailscale.com/types/key"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/cryptorand"
"github.com/coder/coder/v2/enterprise/audit"
"github.com/coder/coder/v2/enterprise/audit/backends"
@@ -23,6 +25,7 @@ import (
"github.com/coder/coder/v2/enterprise/coderd/usage"
"github.com/coder/coder/v2/enterprise/dbcrypt"
"github.com/coder/coder/v2/enterprise/trialer"
"github.com/coder/coder/v2/enterprise/x/aibridged"
"github.com/coder/coder/v2/tailnet"
"github.com/coder/quartz"
"github.com/coder/serpent"
@@ -143,6 +146,33 @@ func (r *RootCmd) Server(_ func()) *serpent.Command {
}
closers.Add(publisher)
experiments := agplcoderd.ReadExperiments(options.Logger, options.DeploymentValues.Experiments.Value())
var aibridgeDaemon *aibridged.Server
// In-memory aibridge daemon.
if options.DeploymentValues.AI.BridgeConfig.Enabled {
if experiments.Enabled(codersdk.ExperimentAIBridge) {
aibridgeDaemon, err = newAIBridgeDaemon(api)
if err != nil {
return nil, nil, xerrors.Errorf("create aibridged: %w", err)
}
api.RegisterInMemoryAIBridgedHTTPHandler(aibridgeDaemon)
// When running as an in-memory daemon, the HTTP handler is wired into the
// coderd API and therefore is subject to its context. Calling Close() on
// aibridged will NOT affect in-flight requests but those will be closed once
// the API server is itself shutdown.
closers.Add(aibridgeDaemon)
} else {
api.Logger.Warn(ctx, fmt.Sprintf("CODER_AIBRIDGE_ENABLED=true but experiment %q not enabled", codersdk.ExperimentAIBridge))
}
} else {
if experiments.Enabled(codersdk.ExperimentAIBridge) {
api.Logger.Warn(ctx, "aibridge experiment enabled but CODER_AIBRIDGE_ENABLED=false")
}
}
return api.AGPL, closers, nil
})