@@ -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"
@@ -632,6 +636,7 @@ func New(options *Options) *API {
632
636
api .PortSharer .Store (& portsharing .DefaultPortSharer )
633
637
api .PrebuildsClaimer .Store (& prebuilds .DefaultClaimer )
634
638
api .PrebuildsReconciler .Store (& prebuilds .DefaultReconciler )
639
+ api .AIBridgeDaemon .Store (& aibridged .DefaultServer )
635
640
buildInfo := codersdk.BuildInfoResponse {
636
641
ExternalURL : buildinfo .ExternalURL (),
637
642
Version : buildinfo .Version (),
@@ -1766,6 +1771,8 @@ type API struct {
1766
1771
// dbRolluper rolls up template usage stats from raw agent and app
1767
1772
// stats. This is used to provide insights in the WebUI.
1768
1773
dbRolluper * dbrollup.Rolluper
1774
+
1775
+ AIBridgeDaemon atomic.Pointer [aibridged.Server ]
1769
1776
}
1770
1777
1771
1778
// Close waits for all WebSocket connections to drain before returning.
@@ -1824,6 +1831,10 @@ func (api *API) Close() error {
1824
1831
(* current ).Stop (ctx , nil )
1825
1832
}
1826
1833
1834
+ if current := api .AIBridgeDaemon .Load (); current != nil {
1835
+ _ = (* current ).Close ()
1836
+ }
1837
+
1827
1838
return nil
1828
1839
}
1829
1840
@@ -1997,6 +2008,76 @@ func (api *API) CreateInMemoryTaggedProvisionerDaemon(dialCtx context.Context, n
1997
2008
return proto .NewDRPCProvisionerDaemonClient (clientSession ), nil
1998
2009
}
1999
2010
2011
+ func (api * API ) CreateInMemoryAIBridgeDaemon (dialCtx context.Context ) (client aibridged.DRPCClient , err error ) {
2012
+ // TODO(dannyk): implement options.
2013
+ // TODO(dannyk): implement tracing.
2014
+ // TODO(dannyk): implement API versioning.
2015
+
2016
+ clientSession , serverSession := drpcsdk .MemTransportPipe ()
2017
+ defer func () {
2018
+ if err != nil {
2019
+ _ = clientSession .Close ()
2020
+ _ = serverSession .Close ()
2021
+ }
2022
+ }()
2023
+
2024
+ mux := drpcmux .New ()
2025
+ api .Logger .Debug (dialCtx , "starting in-memory aibridge daemon" )
2026
+ logger := api .Logger .Named ("inmem-aibridged" )
2027
+ srv , err := aibridgedserver .NewServer (api .ctx , api .Database , logger ,
2028
+ api .DeploymentValues .AccessURL .String (), api .ExternalAuthConfigs )
2029
+ if err != nil {
2030
+ return nil , err
2031
+ }
2032
+ err = aibridgedproto .DRPCRegisterRecorder (mux , srv )
2033
+ if err != nil {
2034
+ return nil , xerrors .Errorf ("register recorder service: %w" , err )
2035
+ }
2036
+ err = aibridgedproto .DRPCRegisterMCPConfigurator (mux , srv )
2037
+ if err != nil {
2038
+ return nil , xerrors .Errorf ("register MCP configurator service: %w" , err )
2039
+ }
2040
+ err = aibridgedproto .DRPCRegisterAuthenticator (mux , srv )
2041
+ if err != nil {
2042
+ return nil , xerrors .Errorf ("register authenticator service: %w" , err )
2043
+ }
2044
+ server := drpcserver .NewWithOptions (& tracing.DRPCHandler {Handler : mux },
2045
+ drpcserver.Options {
2046
+ Manager : drpcsdk .DefaultDRPCOptions (nil ),
2047
+ Log : func (err error ) {
2048
+ if xerrors .Is (err , io .EOF ) {
2049
+ return
2050
+ }
2051
+ logger .Debug (dialCtx , "drpc server error" , slog .Error (err ))
2052
+ },
2053
+ },
2054
+ )
2055
+ // in-mem pipes aren't technically "websockets" but they have the same properties as far as the
2056
+ // API is concerned: they are long-lived connections that we need to close before completing
2057
+ // shutdown of the API.
2058
+ api .WebsocketWaitMutex .Lock ()
2059
+ api .WebsocketWaitGroup .Add (1 )
2060
+ api .WebsocketWaitMutex .Unlock ()
2061
+ go func () {
2062
+ defer api .WebsocketWaitGroup .Done ()
2063
+ // Here we pass the background context, since we want the server to keep serving until the
2064
+ // client hangs up. The aibridged is local, in-mem, so there isn't a danger of losing contact with it and
2065
+ // having a dead connection we don't know the status of.
2066
+ err := server .Serve (context .Background (), serverSession )
2067
+ logger .Info (dialCtx , "aibridge daemon disconnected" , slog .Error (err ))
2068
+ // Close the sessions, so we don't leak goroutines serving them.
2069
+ _ = clientSession .Close ()
2070
+ _ = serverSession .Close ()
2071
+ }()
2072
+
2073
+ return & aibridged.Client {
2074
+ Conn : clientSession ,
2075
+ DRPCRecorderClient : aibridgedproto .NewDRPCRecorderClient (clientSession ),
2076
+ DRPCMCPConfiguratorClient : aibridgedproto .NewDRPCMCPConfiguratorClient (clientSession ),
2077
+ DRPCAuthenticatorClient : aibridgedproto .NewDRPCAuthenticatorClient (clientSession ),
2078
+ }, nil
2079
+ }
2080
+
2000
2081
func (api * API ) DERPMap () * tailcfg.DERPMap {
2001
2082
fn := api .DERPMapper .Load ()
2002
2083
if fn != nil {
0 commit comments