@@ -20,6 +20,8 @@ import (
20
20
"sync/atomic"
21
21
"time"
22
22
23
+ "github.com/coder/coder/v2/aibridged"
24
+ aibridgedproto"github.com/coder/coder/v2/aibridged/proto"
23
25
"github.com/coder/coder/v2/coderd/oauth2provider"
24
26
"github.com/coder/coder/v2/coderd/pproflabel"
25
27
"github.com/coder/coder/v2/coderd/prebuilds"
@@ -44,6 +46,9 @@ import (
44
46
"tailscale.com/types/key"
45
47
"tailscale.com/util/singleflight"
46
48
49
+ "github.com/coder/coder/v2/coderd/aibridgedserver"
50
+ "github.com/coder/coder/v2/provisionerd/proto"
51
+
47
52
"cdr.dev/slog"
48
53
"github.com/coder/quartz"
49
54
"github.com/coder/serpent"
@@ -95,7 +100,6 @@ import (
95
100
"github.com/coder/coder/v2/coderd/workspacestats"
96
101
"github.com/coder/coder/v2/codersdk"
97
102
"github.com/coder/coder/v2/codersdk/healthsdk"
98
- "github.com/coder/coder/v2/provisionerd/proto"
99
103
"github.com/coder/coder/v2/provisionersdk"
100
104
"github.com/coder/coder/v2/site"
101
105
"github.com/coder/coder/v2/tailnet"
@@ -999,6 +1003,11 @@ func New(options *Options) *API {
999
1003
1000
1004
// Experimental routes are not guaranteed to be stable and may change at any time.
1001
1005
r .Route ("/api/experimental" ,func (r chi.Router ) {
1006
+ r .NotFound (func (rw http.ResponseWriter ,_ * http.Request ) {httpapi .RouteNotFound (rw ) })
1007
+
1008
+ // Only this group should be subject to apiKeyMiddleware; aibridged will mount its own
1009
+ // router and handles key validation in a different fashion.
1010
+ // See aibridged/http.go.
1002
1011
r .Group (func (r chi.Router ) {
1003
1012
r .Use (apiKeyMiddleware )
1004
1013
r .Route ("/aitasks" ,func (r chi.Router ) {
@@ -1977,6 +1986,78 @@ func (api *API) CreateInMemoryTaggedProvisionerDaemon(dialCtx context.Context, n
1977
1986
return proto .NewDRPCProvisionerDaemonClient (clientSession ),nil
1978
1987
}
1979
1988
1989
+ // CreateInMemoryAIBridgeServer creates a [aibridged.DRPCServer] and returns a
1990
+ // [aibridged.DRPCClient] to it, connected over an in-memory transport.
1991
+ // This server is responsible for all the Coder-specific functionality that aibridged
1992
+ // requires such as persistence and retrieving configuration.
1993
+ func (api * API )CreateInMemoryAIBridgeServer (dialCtx context.Context ) (client aibridged.DRPCClient ,err error ) {
1994
+ // TODO(dannyk): implement options.
1995
+ // TODO(dannyk): implement tracing.
1996
+ // TODO(dannyk): implement API versioning.
1997
+
1998
+ clientSession ,serverSession := drpcsdk .MemTransportPipe ()
1999
+ defer func () {
2000
+ if err != nil {
2001
+ _ = clientSession .Close ()
2002
+ _ = serverSession .Close ()
2003
+ }
2004
+ }()
2005
+
2006
+ mux := drpcmux .New ()
2007
+ srv ,err := aibridgedserver .NewServer (api .ctx ,api .Database ,api .Logger .Named ("aibridgedserver" ),
2008
+ api .DeploymentValues .AccessURL .String (),api .ExternalAuthConfigs ,api .Experiments )
2009
+ if err != nil {
2010
+ return nil ,err
2011
+ }
2012
+ err = aibridgedproto .DRPCRegisterRecorder (mux ,srv )
2013
+ if err != nil {
2014
+ return nil ,xerrors .Errorf ("register recorder service: %w" ,err )
2015
+ }
2016
+ err = aibridgedproto .DRPCRegisterMCPConfigurator (mux ,srv )
2017
+ if err != nil {
2018
+ return nil ,xerrors .Errorf ("register MCP configurator service: %w" ,err )
2019
+ }
2020
+ err = aibridgedproto .DRPCRegisterAuthorizer (mux ,srv )
2021
+ if err != nil {
2022
+ return nil ,xerrors .Errorf ("register key validator service: %w" ,err )
2023
+ }
2024
+ server := drpcserver .NewWithOptions (& tracing.DRPCHandler {Handler :mux },
2025
+ drpcserver.Options {
2026
+ Manager :drpcsdk .DefaultDRPCOptions (nil ),
2027
+ Log :func (err error ) {
2028
+ if errors .Is (err ,io .EOF ) {
2029
+ return
2030
+ }
2031
+ api .Logger .Debug (dialCtx ,"aibridged drpc server error" ,slog .Error (err ))
2032
+ },
2033
+ },
2034
+ )
2035
+ // in-mem pipes aren't technically "websockets" but they have the same properties as far as the
2036
+ // API is concerned: they are long-lived connections that we need to close before completing
2037
+ // shutdown of the API.
2038
+ api .WebsocketWaitMutex .Lock ()
2039
+ api .WebsocketWaitGroup .Add (1 )
2040
+ api .WebsocketWaitMutex .Unlock ()
2041
+ go func () {
2042
+ defer api .WebsocketWaitGroup .Done ()
2043
+ // Here we pass the background context, since we want the server to keep serving until the
2044
+ // client hangs up. The aibridged is local, in-mem, so there isn't a danger of losing contact with it and
2045
+ // having a dead connection we don't know the status of.
2046
+ err := server .Serve (context .Background (),serverSession )
2047
+ api .Logger .Info (dialCtx ,"aibridge daemon disconnected" ,slog .Error (err ))
2048
+ // Close the sessions, so we don't leak goroutines serving them.
2049
+ _ = clientSession .Close ()
2050
+ _ = serverSession .Close ()
2051
+ }()
2052
+
2053
+ return & aibridged.Client {
2054
+ Conn :clientSession ,
2055
+ DRPCRecorderClient :aibridgedproto .NewDRPCRecorderClient (clientSession ),
2056
+ DRPCMCPConfiguratorClient :aibridgedproto .NewDRPCMCPConfiguratorClient (clientSession ),
2057
+ DRPCAuthorizerClient :aibridgedproto .NewDRPCAuthorizerClient (clientSession ),
2058
+ },nil
2059
+ }
2060
+
1980
2061
func (api * API )DERPMap ()* tailcfg.DERPMap {
1981
2062
fn := api .DERPMapper .Load ()
1982
2063
if fn != nil {