make gateway aware of config.json change
This commit is contained in:
parent
7df7e0495c
commit
a4198d000e
4 changed files with 187 additions and 21 deletions
|
|
@ -3,10 +3,10 @@ package gateway
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/cmd/picoclaw/internal"
|
"github.com/sipeed/picoclaw/cmd/picoclaw/internal"
|
||||||
|
|
@ -45,6 +45,7 @@ func gatewayCmd(debug bool) error {
|
||||||
fmt.Println("🔍 Debug mode enabled")
|
fmt.Println("🔍 Debug mode enabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configPath := internal.GetConfigPath()
|
||||||
cfg, err := internal.LoadConfig()
|
cfg, err := internal.LoadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error loading config: %w", err)
|
return fmt.Errorf("error loading config: %w", err)
|
||||||
|
|
@ -190,11 +191,18 @@ func gatewayCmd(debug bool) error {
|
||||||
|
|
||||||
go agentLoop.Run(ctx)
|
go agentLoop.Run(ctx)
|
||||||
|
|
||||||
|
// Setup config file watcher for hot reload
|
||||||
|
configReloadChan, stopWatch := setupConfigWatcherPolling(configPath, debug)
|
||||||
|
defer stopWatch()
|
||||||
|
|
||||||
sigChan := make(chan os.Signal, 1)
|
sigChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigChan, os.Interrupt)
|
signal.Notify(sigChan, os.Interrupt)
|
||||||
<-sigChan
|
|
||||||
|
|
||||||
fmt.Println("\nShutting down...")
|
// Main event loop - wait for signals or config changes
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-sigChan:
|
||||||
|
logger.Info("Shutting down...")
|
||||||
if cp, ok := provider.(providers.StatefulProvider); ok {
|
if cp, ok := provider.(providers.StatefulProvider); ok {
|
||||||
cp.Close()
|
cp.Close()
|
||||||
}
|
}
|
||||||
|
|
@ -212,9 +220,143 @@ func gatewayCmd(debug bool) error {
|
||||||
cronService.Stop()
|
cronService.Stop()
|
||||||
mediaStore.Stop()
|
mediaStore.Stop()
|
||||||
agentLoop.Stop()
|
agentLoop.Stop()
|
||||||
fmt.Println("✓ Gateway stopped")
|
logger.Info("✓ Gateway stopped")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
case newCfg := <-configReloadChan:
|
||||||
|
logger.Info("🔄 Config file changed, reloading...")
|
||||||
|
|
||||||
|
newModel := newCfg.Agents.Defaults.ModelName
|
||||||
|
if newModel == "" {
|
||||||
|
newModel = newCfg.Agents.Defaults.Model
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof(" New model is '%s', recreating provider...", newModel)
|
||||||
|
if cp, ok := provider.(providers.StatefulProvider); ok {
|
||||||
|
cp.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new provider from updated config
|
||||||
|
// This will use the correct API key and settings from newCfg.ModelList
|
||||||
|
newProvider, newModelID, err := providers.CreateProvider(newCfg)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf(" ⚠ Error creating new provider: %v", err)
|
||||||
|
logger.Warn(" Continuing with old provider")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
provider = newProvider
|
||||||
|
if newModelID != "" {
|
||||||
|
newCfg.Agents.Defaults.ModelName = newModelID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update agent loop provider and models
|
||||||
|
agentLoop.SetProvider(provider, newCfg)
|
||||||
|
|
||||||
|
logger.Info(" ✓ Provider and agents updated successfully")
|
||||||
|
|
||||||
|
// Update the config reference for other operations
|
||||||
|
// Note: Some changes (like channel configs) may require restart to take full effect
|
||||||
|
cfg = newCfg
|
||||||
|
logger.Info(" ✓ Configuration reloaded successfully")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupConfigWatcherPolling sets up a simple polling-based config file watcher
|
||||||
|
// Returns a channel for config updates and a stop function
|
||||||
|
func setupConfigWatcherPolling(configPath string, debug bool) (chan *config.Config, func()) {
|
||||||
|
configChan := make(chan *config.Config, 1)
|
||||||
|
stop := make(chan struct{})
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
// Get initial file info
|
||||||
|
lastModTime := getFileModTime(configPath)
|
||||||
|
lastSize := getFileSize(configPath)
|
||||||
|
|
||||||
|
ticker := time.NewTicker(2 * time.Second) // Check every 2 seconds
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
currentModTime := getFileModTime(configPath)
|
||||||
|
currentSize := getFileSize(configPath)
|
||||||
|
|
||||||
|
// Check if file changed (modification time or size changed)
|
||||||
|
if currentModTime.After(lastModTime) || currentSize != lastSize {
|
||||||
|
if debug {
|
||||||
|
logger.DebugSF("🔍 Config file change detected")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debounce - wait a bit to ensure file write is complete
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
|
// Validate and load new config
|
||||||
|
newCfg, err := config.LoadConfig(configPath)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("⚠ Error loading new config: %v", err)
|
||||||
|
logger.Warn(" Using previous valid config")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the new config
|
||||||
|
if err := newCfg.ValidateModelList(); err != nil {
|
||||||
|
logger.Errorf(" ⚠ New config validation failed: %v", err)
|
||||||
|
logger.Warn(" Using previous valid config")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("✓ Config file validated and loaded")
|
||||||
|
|
||||||
|
// Update last known state
|
||||||
|
lastModTime = currentModTime
|
||||||
|
lastSize = currentSize
|
||||||
|
|
||||||
|
// Send new config to main loop (non-blocking)
|
||||||
|
select {
|
||||||
|
case configChan <- newCfg:
|
||||||
|
default:
|
||||||
|
// Channel full, skip this update
|
||||||
|
logger.Warn("⚠ Previous config reload still in progress, skipping")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-stop:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
stopFunc := func() {
|
||||||
|
close(stop)
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
return configChan, stopFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFileModTime returns the modification time of a file, or zero time if file doesn't exist
|
||||||
|
func getFileModTime(path string) time.Time {
|
||||||
|
info, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
return info.ModTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFileSize returns the size of a file, or 0 if file doesn't exist
|
||||||
|
func getFileSize(path string) int64 {
|
||||||
|
info, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return info.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupCronTool(
|
func setupCronTool(
|
||||||
|
|
@ -236,7 +378,7 @@ func setupCronTool(
|
||||||
var err error
|
var err error
|
||||||
cronTool, err = tools.NewCronTool(cronService, agentLoop, msgBus, workspace, restrict, execTimeout, cfg)
|
cronTool, err = tools.NewCronTool(cronService, agentLoop, msgBus, workspace, restrict, execTimeout, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Critical error during CronTool initialization: %v", err)
|
logger.Fatalf("Critical error during CronTool initialization: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
agentLoop.RegisterTool(cronTool)
|
agentLoop.RegisterTool(cronTool)
|
||||||
|
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -11,6 +11,7 @@ require (
|
||||||
github.com/gdamore/tcell/v2 v2.13.8
|
github.com/gdamore/tcell/v2 v2.13.8
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
|
github.com/h2non/filetype v1.1.3
|
||||||
github.com/larksuite/oapi-sdk-go/v3 v3.5.3
|
github.com/larksuite/oapi-sdk-go/v3 v3.5.3
|
||||||
github.com/mdp/qrterminal/v3 v3.2.1
|
github.com/mdp/qrterminal/v3 v3.2.1
|
||||||
github.com/modelcontextprotocol/go-sdk v1.3.0
|
github.com/modelcontextprotocol/go-sdk v1.3.0
|
||||||
|
|
@ -37,7 +38,6 @@ require (
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect
|
github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect
|
||||||
github.com/gdamore/encoding v1.0.1 // indirect
|
github.com/gdamore/encoding v1.0.1 // indirect
|
||||||
github.com/h2non/filetype v1.1.3 // indirect
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
|
|
|
||||||
|
|
@ -368,6 +368,14 @@ func (al *AgentLoop) SetChannelManager(cm *channels.Manager) {
|
||||||
al.channelManager = cm
|
al.channelManager = cm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetProvider updates the LLM provider for all agents in the registry
|
||||||
|
// and updates their model configurations.
|
||||||
|
func (al *AgentLoop) SetProvider(provider providers.LLMProvider, cfg *config.Config) {
|
||||||
|
al.cfg = cfg
|
||||||
|
registry := NewAgentRegistry(cfg, provider)
|
||||||
|
al.registry = registry
|
||||||
|
}
|
||||||
|
|
||||||
// SetMediaStore injects a MediaStore for media lifecycle management.
|
// SetMediaStore injects a MediaStore for media lifecycle management.
|
||||||
func (al *AgentLoop) SetMediaStore(s media.MediaStore) {
|
func (al *AgentLoop) SetMediaStore(s media.MediaStore) {
|
||||||
al.mediaStore = s
|
al.mediaStore = s
|
||||||
|
|
|
||||||
|
|
@ -168,6 +168,10 @@ func DebugC(component string, message string) {
|
||||||
logMessage(DEBUG, component, message, nil)
|
logMessage(DEBUG, component, message, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DebugSF(message string, ss ...any) {
|
||||||
|
logMessage(DEBUG, "", fmt.Sprintf(message, ss...), nil)
|
||||||
|
}
|
||||||
|
|
||||||
func DebugF(message string, fields map[string]any) {
|
func DebugF(message string, fields map[string]any) {
|
||||||
logMessage(DEBUG, "", message, fields)
|
logMessage(DEBUG, "", message, fields)
|
||||||
}
|
}
|
||||||
|
|
@ -188,6 +192,10 @@ func InfoF(message string, fields map[string]any) {
|
||||||
logMessage(INFO, "", message, fields)
|
logMessage(INFO, "", message, fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Infof(message string, ss ...any) {
|
||||||
|
logMessage(INFO, "", fmt.Sprintf(message, ss...), nil)
|
||||||
|
}
|
||||||
|
|
||||||
func InfoCF(component string, message string, fields map[string]any) {
|
func InfoCF(component string, message string, fields map[string]any) {
|
||||||
logMessage(INFO, component, message, fields)
|
logMessage(INFO, component, message, fields)
|
||||||
}
|
}
|
||||||
|
|
@ -216,6 +224,10 @@ func ErrorC(component string, message string) {
|
||||||
logMessage(ERROR, component, message, nil)
|
logMessage(ERROR, component, message, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Errorf(message string, ss ...any) {
|
||||||
|
logMessage(ERROR, "", fmt.Sprintf(message, ss...), nil)
|
||||||
|
}
|
||||||
|
|
||||||
func ErrorF(message string, fields map[string]any) {
|
func ErrorF(message string, fields map[string]any) {
|
||||||
logMessage(ERROR, "", message, fields)
|
logMessage(ERROR, "", message, fields)
|
||||||
}
|
}
|
||||||
|
|
@ -232,6 +244,10 @@ func FatalC(component string, message string) {
|
||||||
logMessage(FATAL, component, message, nil)
|
logMessage(FATAL, component, message, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Fatalf(message string, ss ...any) {
|
||||||
|
logMessage(FATAL, "", fmt.Sprintf(message, ss...), nil)
|
||||||
|
}
|
||||||
|
|
||||||
func FatalF(message string, fields map[string]any) {
|
func FatalF(message string, fields map[string]any) {
|
||||||
logMessage(FATAL, "", message, fields)
|
logMessage(FATAL, "", message, fields)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue