@@ -41,10 +41,36 @@ var (
4141 serverLogger = logging .DefaultLogger (logging .LogLevelInfo ).WithName ("server" )
4242)
4343
44+ // AIPluginInfo represents information about an AI plugin
45+ type AIPluginInfo struct {
46+ Name string `json:"name"`
47+ Version string `json:"version"`
48+ Description string `json:"description"`
49+ Capabilities []string `json:"capabilities"`
50+ SocketPath string `json:"socketPath"`
51+ Metadata map [string ]string `json:"metadata"`
52+ }
53+
54+ // AIPluginHealth represents the health status of an AI plugin
55+ type AIPluginHealth struct {
56+ Name string `json:"name"`
57+ Status string `json:"status"` // online, offline, error, processing
58+ LastCheckAt time.Time `json:"lastCheckAt"`
59+ ResponseTime time.Duration `json:"responseTime"`
60+ ErrorMessage string `json:"errorMessage,omitempty"`
61+ Metrics map [string ]string `json:"metrics,omitempty"`
62+ }
63+
4464type ExtManager interface {
4565 Start (name , socket string ) (err error )
4666 StopAll () (err error )
4767 WithDownloader (downloader.PlatformAwareOCIDownloader )
68+ // AI Plugin Management
69+ DiscoverAIPlugins () ([]AIPluginInfo , error )
70+ CheckAIPluginHealth (name string ) (* AIPluginHealth , error )
71+ GetAllAIPluginHealth () (map [string ]* AIPluginHealth , error )
72+ RegisterAIPlugin (info AIPluginInfo ) error
73+ UnregisterAIPlugin (name string ) error
4874}
4975
5076type storeExtManager struct {
@@ -57,37 +83,59 @@ type storeExtManager struct {
5783 processChan chan fakeruntime.Process
5884 stopSingal chan struct {}
5985 lock * sync.RWMutex
86+ // AI Plugin Management
87+ aiPluginRegistry map [string ]AIPluginInfo `json:"aiPluginRegistry"`
88+ aiPluginHealthMap map [string ]* AIPluginHealth `json:"aiPluginHealthMap"`
89+ healthCheckTicker * time.Ticker `json:"-"`
90+ healthCheckCtx context.Context `json:"-"`
91+ healthCheckCancel context.CancelFunc `json:"-"`
6092}
6193
6294var ss * storeExtManager
6395
6496func NewStoreExtManager (execer fakeruntime.Execer ) ExtManager {
6597 if ss == nil {
98+ ctx , cancel := context .WithCancel (context .Background ())
6699 ss = & storeExtManager {
67100 processChan : make (chan fakeruntime.Process ),
68101 stopSingal : make (chan struct {}, 1 ),
69102 lock : & sync.RWMutex {},
103+ // AI Plugin Management initialization
104+ aiPluginRegistry : make (map [string ]AIPluginInfo ),
105+ aiPluginHealthMap : make (map [string ]* AIPluginHealth ),
106+ healthCheckCtx : ctx ,
107+ healthCheckCancel : cancel ,
70108 }
71109 ss .execer = execer
72110 ss .socketPrefix = "unix://"
73111 ss .extStatusMap = map [string ]bool {}
74112 ss .processCollect ()
75113 ss .WithDownloader (& nonDownloader {})
114+ // Start AI plugin health monitoring
115+ ss .startAIHealthMonitoring ()
76116 }
77117 return ss
78118}
79119
80120func NewStoreExtManagerInstance (execer fakeruntime.Execer ) ExtManager {
121+ ctx , cancel := context .WithCancel (context .Background ())
81122 ss = & storeExtManager {
82123 processChan : make (chan fakeruntime.Process ),
83124 stopSingal : make (chan struct {}, 1 ),
84125 lock : & sync.RWMutex {},
126+ // AI Plugin Management initialization
127+ aiPluginRegistry : make (map [string ]AIPluginInfo ),
128+ aiPluginHealthMap : make (map [string ]* AIPluginHealth ),
129+ healthCheckCtx : ctx ,
130+ healthCheckCancel : cancel ,
85131 }
86132 ss .execer = execer
87133 ss .socketPrefix = "unix://"
88134 ss .extStatusMap = map [string ]bool {}
89135 ss .processCollect ()
90136 ss .WithDownloader (& nonDownloader {})
137+ // Start AI plugin health monitoring
138+ ss .startAIHealthMonitoring ()
91139 return ss
92140}
93141
@@ -180,21 +228,6 @@ func (s *storeExtManager) startPluginViaHTTP(httpURL, plugin, pluginName string)
180228 return
181229}
182230
183- func (s * storeExtManager ) StopAll () error {
184- serverLogger .Info ("stop" , "extensions" , len (s .processs ))
185- for _ , p := range s .processs {
186- if p != nil {
187- // Use Kill on Windows, Signal on other platforms
188- if isWindows () {
189- p .Kill ()
190- } else {
191- p .Signal (syscall .SIGTERM )
192- }
193- }
194- }
195- s .stopSingal <- struct {}{}
196- return nil
197- }
198231
199232// isWindows returns true if the program is running on Windows OS.
200233func isWindows () bool {
@@ -267,3 +300,235 @@ func (d *nonDownloader) WithContext(context.Context) {}
267300func (n * nonDownloader ) GetTargetFile () string {
268301 return ""
269302}
303+
304+ // AI Plugin Management Implementation
305+
306+ // startAIHealthMonitoring starts the periodic health check for AI plugins
307+ func (s * storeExtManager ) startAIHealthMonitoring () {
308+ s .healthCheckTicker = time .NewTicker (30 * time .Second ) // Health check every 30 seconds
309+
310+ go func () {
311+ for {
312+ select {
313+ case <- s .healthCheckCtx .Done ():
314+ s .healthCheckTicker .Stop ()
315+ return
316+ case <- s .healthCheckTicker .C :
317+ s .performHealthCheck ()
318+ }
319+ }
320+ }()
321+ }
322+
323+ // performHealthCheck performs health checks on all registered AI plugins
324+ func (s * storeExtManager ) performHealthCheck () {
325+ s .lock .RLock ()
326+ plugins := make (map [string ]AIPluginInfo )
327+ for name , info := range s .aiPluginRegistry {
328+ plugins [name ] = info
329+ }
330+ s .lock .RUnlock ()
331+
332+ for name , info := range plugins {
333+ health , err := s .checkSingleAIPlugin (info )
334+ if err != nil {
335+ serverLogger .Error (err , "Failed to check AI plugin health" , "plugin" , name )
336+ health = & AIPluginHealth {
337+ Name : name ,
338+ Status : "error" ,
339+ LastCheckAt : time .Now (),
340+ ErrorMessage : err .Error (),
341+ }
342+ }
343+
344+ s .lock .Lock ()
345+ s .aiPluginHealthMap [name ] = health
346+ s .lock .Unlock ()
347+ }
348+ }
349+
350+ // checkSingleAIPlugin performs health check on a single AI plugin
351+ func (s * storeExtManager ) checkSingleAIPlugin (info AIPluginInfo ) (* AIPluginHealth , error ) {
352+ startTime := time .Now ()
353+
354+ // For now, we'll simulate a health check by checking if the socket file exists
355+ // In a real implementation, this would make a gRPC health check call
356+ _ , err := os .Stat (strings .TrimPrefix (info .SocketPath , "unix://" ))
357+
358+ responseTime := time .Since (startTime )
359+
360+ health := & AIPluginHealth {
361+ Name : info .Name ,
362+ LastCheckAt : time .Now (),
363+ ResponseTime : responseTime ,
364+ Metrics : map [string ]string {
365+ "version" : info .Version ,
366+ "socket_path" : info .SocketPath ,
367+ },
368+ }
369+
370+ if err != nil {
371+ if os .IsNotExist (err ) {
372+ health .Status = "offline"
373+ health .ErrorMessage = "Plugin socket not found"
374+ } else {
375+ health .Status = "error"
376+ health .ErrorMessage = err .Error ()
377+ }
378+ } else {
379+ health .Status = "online"
380+ health .ErrorMessage = ""
381+ }
382+
383+ return health , nil
384+ }
385+
386+ // DiscoverAIPlugins discovers AI-capable plugins in the system
387+ func (s * storeExtManager ) DiscoverAIPlugins () ([]AIPluginInfo , error ) {
388+ s .lock .RLock ()
389+ defer s .lock .RUnlock ()
390+
391+ var plugins []AIPluginInfo
392+ for _ , info := range s .aiPluginRegistry {
393+ plugins = append (plugins , info )
394+ }
395+
396+ return plugins , nil
397+ }
398+
399+ // CheckAIPluginHealth checks the health of a specific AI plugin
400+ func (s * storeExtManager ) CheckAIPluginHealth (name string ) (* AIPluginHealth , error ) {
401+ s .lock .RLock ()
402+ info , exists := s .aiPluginRegistry [name ]
403+ s .lock .RUnlock ()
404+
405+ if ! exists {
406+ return nil , fmt .Errorf ("AI plugin %s not found" , name )
407+ }
408+
409+ health , err := s .checkSingleAIPlugin (info )
410+ if err != nil {
411+ return nil , fmt .Errorf ("failed to check health for AI plugin %s: %w" , name , err )
412+ }
413+
414+ // Update the health cache
415+ s .lock .Lock ()
416+ s .aiPluginHealthMap [name ] = health
417+ s .lock .Unlock ()
418+
419+ return health , nil
420+ }
421+
422+ // GetAllAIPluginHealth returns the health status of all AI plugins
423+ func (s * storeExtManager ) GetAllAIPluginHealth () (map [string ]* AIPluginHealth , error ) {
424+ s .lock .RLock ()
425+ defer s .lock .RUnlock ()
426+
427+ // Return a copy to avoid concurrent access issues
428+ healthMap := make (map [string ]* AIPluginHealth )
429+ for name , health := range s .aiPluginHealthMap {
430+ // Create a copy of the health struct
431+ healthCopy := & AIPluginHealth {
432+ Name : health .Name ,
433+ Status : health .Status ,
434+ LastCheckAt : health .LastCheckAt ,
435+ ResponseTime : health .ResponseTime ,
436+ ErrorMessage : health .ErrorMessage ,
437+ Metrics : make (map [string ]string ),
438+ }
439+
440+ // Copy metrics map
441+ for k , v := range health .Metrics {
442+ healthCopy .Metrics [k ] = v
443+ }
444+
445+ healthMap [name ] = healthCopy
446+ }
447+
448+ return healthMap , nil
449+ }
450+
451+ // RegisterAIPlugin registers a new AI plugin with the system
452+ func (s * storeExtManager ) RegisterAIPlugin (info AIPluginInfo ) error {
453+ if info .Name == "" {
454+ return fmt .Errorf ("plugin name cannot be empty" )
455+ }
456+
457+ if info .SocketPath == "" {
458+ return fmt .Errorf ("plugin socket path cannot be empty" )
459+ }
460+
461+ s .lock .Lock ()
462+ defer s .lock .Unlock ()
463+
464+ // Check if plugin is already registered
465+ if _ , exists := s .aiPluginRegistry [info .Name ]; exists {
466+ serverLogger .Info ("AI plugin already registered, updating info" , "plugin" , info .Name )
467+ }
468+
469+ s .aiPluginRegistry [info .Name ] = info
470+
471+ // Initialize health status
472+ s .aiPluginHealthMap [info .Name ] = & AIPluginHealth {
473+ Name : info .Name ,
474+ Status : "unknown" ,
475+ LastCheckAt : time .Now (),
476+ Metrics : map [string ]string {
477+ "version" : info .Version ,
478+ "socket_path" : info .SocketPath ,
479+ },
480+ }
481+
482+ serverLogger .Info ("AI plugin registered successfully" , "plugin" , info .Name , "version" , info .Version )
483+
484+ return nil
485+ }
486+
487+ // UnregisterAIPlugin removes an AI plugin from the system
488+ func (s * storeExtManager ) UnregisterAIPlugin (name string ) error {
489+ s .lock .Lock ()
490+ defer s .lock .Unlock ()
491+
492+ if _ , exists := s .aiPluginRegistry [name ]; ! exists {
493+ return fmt .Errorf ("AI plugin %s not found" , name )
494+ }
495+
496+ delete (s .aiPluginRegistry , name )
497+ delete (s .aiPluginHealthMap , name )
498+
499+ serverLogger .Info ("AI plugin unregistered successfully" , "plugin" , name )
500+
501+ return nil
502+ }
503+
504+ // StopAll enhanced to also clean up AI plugin monitoring
505+ func (s * storeExtManager ) StopAll () error {
506+ // Stop AI health monitoring
507+ if s .healthCheckCancel != nil {
508+ s .healthCheckCancel ()
509+ }
510+
511+ // Original StopAll implementation
512+ serverLogger .Info ("stop" , "extensions" , len (s .processs ))
513+ for _ , p := range s .processs {
514+ if p != nil {
515+ // Use Kill on Windows, Signal on other platforms
516+ if isWindows () {
517+ p .Kill ()
518+ } else {
519+ p .Signal (syscall .SIGTERM )
520+ }
521+ }
522+ }
523+
524+ for _ , fileToRemove := range s .filesNeedToBeRemoved {
525+ if err := os .RemoveAll (fileToRemove ); err != nil {
526+ serverLogger .Info ("failed to remove" , "file" , fileToRemove , "error" , err )
527+ }
528+ }
529+
530+ // Send stop signal
531+ s .stopSingal <- struct {}{}
532+
533+ return nil
534+ }
0 commit comments