@@ -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"
@@ -1007,6 +1011,11 @@ func New(options *Options) *API {
1007
1011
1008
1012
// Experimental routes are not guaranteed to be stable and may change at any time.
1009
1013
r .Route ("/api/experimental" ,func (r chi.Router ) {
1014
+ r .NotFound (func (rw http.ResponseWriter ,_ * http.Request ) {httpapi .RouteNotFound (rw ) })
1015
+
1016
+ // Only this group should be subject to apiKeyMiddleware; aibridged will mount its own
1017
+ // router and handles key validation in a different fashion.
1018
+ // See aibridged/http.go.
1010
1019
r .Group (func (r chi.Router ) {
1011
1020
r .Use (apiKeyMiddleware )
1012
1021
r .Route ("/aitasks" ,func (r chi.Router ) {
@@ -1985,6 +1994,78 @@ func (api *API) CreateInMemoryTaggedProvisionerDaemon(dialCtx context.Context, n
1985
1994
return proto .NewDRPCProvisionerDaemonClient (clientSession ),nil
1986
1995
}
1987
1996
1997
+ // CreateInMemoryAIBridgeServer creates a [aibridged.DRPCServer] and returns a
1998
+ // [aibridged.DRPCClient] to it, connected over an in-memory transport.
1999
+ // This server is responsible for all the Coder-specific functionality that aibridged
2000
+ // requires such as persistence and retrieving configuration.
2001
+ func (api * API )CreateInMemoryAIBridgeServer (dialCtx context.Context ) (client aibridged.DRPCClient ,err error ) {
2002
+ // TODO(dannyk): implement options.
2003
+ // TODO(dannyk): implement tracing.
2004
+ // TODO(dannyk): implement API versioning.
2005
+
2006
+ clientSession ,serverSession := drpcsdk .MemTransportPipe ()
2007
+ defer func () {
2008
+ if err != nil {
2009
+ _ = clientSession .Close ()
2010
+ _ = serverSession .Close ()
2011
+ }
2012
+ }()
2013
+
2014
+ mux := drpcmux .New ()
2015
+ srv ,err := aibridgedserver .NewServer (api .ctx ,api .Database ,api .Logger .Named ("aibridgedserver" ),
2016
+ api .DeploymentValues .AccessURL .String (),api .ExternalAuthConfigs ,api .Experiments )
2017
+ if err != nil {
2018
+ return nil ,err
2019
+ }
2020
+ err = aibridgedproto .DRPCRegisterRecorder (mux ,srv )
2021
+ if err != nil {
2022
+ return nil ,xerrors .Errorf ("register recorder service: %w" ,err )
2023
+ }
2024
+ err = aibridgedproto .DRPCRegisterMCPConfigurator (mux ,srv )
2025
+ if err != nil {
2026
+ return nil ,xerrors .Errorf ("register MCP configurator service: %w" ,err )
2027
+ }
2028
+ err = aibridgedproto .DRPCRegisterAuthorizer (mux ,srv )
2029
+ if err != nil {
2030
+ return nil ,xerrors .Errorf ("register key validator service: %w" ,err )
2031
+ }
2032
+ server := drpcserver .NewWithOptions (& tracing.DRPCHandler {Handler :mux },
2033
+ drpcserver.Options {
2034
+ Manager :drpcsdk .DefaultDRPCOptions (nil ),
2035
+ Log :func (err error ) {
2036
+ if errors .Is (err ,io .EOF ) {
2037
+ return
2038
+ }
2039
+ api .Logger .Debug (dialCtx ,"aibridged drpc server error" ,slog .Error (err ))
2040
+ },
2041
+ },
2042
+ )
2043
+ // in-mem pipes aren't technically "websockets" but they have the same properties as far as the
2044
+ // API is concerned: they are long-lived connections that we need to close before completing
2045
+ // shutdown of the API.
2046
+ api .WebsocketWaitMutex .Lock ()
2047
+ api .WebsocketWaitGroup .Add (1 )
2048
+ api .WebsocketWaitMutex .Unlock ()
2049
+ go func () {
2050
+ defer api .WebsocketWaitGroup .Done ()
2051
+ // Here we pass the background context, since we want the server to keep serving until the
2052
+ // client hangs up. The aibridged is local, in-mem, so there isn't a danger of losing contact with it and
2053
+ // having a dead connection we don't know the status of.
2054
+ err := server .Serve (context .Background (),serverSession )
2055
+ api .Logger .Info (dialCtx ,"aibridge daemon disconnected" ,slog .Error (err ))
2056
+ // Close the sessions, so we don't leak goroutines serving them.
2057
+ _ = clientSession .Close ()
2058
+ _ = serverSession .Close ()
2059
+ }()
2060
+
2061
+ return & aibridged.Client {
2062
+ Conn :clientSession ,
2063
+ DRPCRecorderClient :aibridgedproto .NewDRPCRecorderClient (clientSession ),
2064
+ DRPCMCPConfiguratorClient :aibridgedproto .NewDRPCMCPConfiguratorClient (clientSession ),
2065
+ DRPCAuthorizerClient :aibridgedproto .NewDRPCAuthorizerClient (clientSession ),
2066
+ },nil
2067
+ }
2068
+
1988
2069
func (api * API )DERPMap ()* tailcfg.DERPMap {
1989
2070
fn := api .DERPMapper .Load ()
1990
2071
if fn != nil {