Files
wehub-resource-sync 1b8708893a
Security Scan / tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:12:26 +08:00

179 lines
5.6 KiB
Go

package agentpool
import (
"encoding/json"
"fmt"
"github.com/mudler/LocalAGI/core/agent"
"github.com/mudler/LocalAGI/core/sse"
"github.com/mudler/LocalAGI/core/state"
"github.com/mudler/LocalAI/core/services/agents"
"github.com/mudler/xlog"
)
const (
defaultObservableLimit = 100
)
// distributedAgentConfigBackend wraps PostgreSQL (AgentStore) + NATS for distributed mode.
type distributedAgentConfigBackend struct {
svc *AgentPoolService // back-reference for dispatchChat, eventBridge, etc.
store *agents.AgentStore // concrete store for full access (observables, etc.)
}
func newDistributedAgentConfigBackend(svc *AgentPoolService, store *agents.AgentStore) *distributedAgentConfigBackend {
return &distributedAgentConfigBackend{svc: svc, store: store}
}
func (b *distributedAgentConfigBackend) ListAgents(userID string) map[string]bool {
statuses := map[string]bool{}
configs, err := b.store.ListConfigs(userID)
if err != nil {
xlog.Error("Failed to list agents from database", "error", err)
return statuses
}
for _, cfg := range configs {
statuses[cfg.Name] = cfg.Status == agents.StatusActive
}
return statuses
}
func (b *distributedAgentConfigBackend) GetConfig(userID, name string) *state.AgentConfig {
rec, err := b.store.GetConfig(userID, name)
if err != nil || rec == nil {
return nil
}
var agentCfg state.AgentConfig
if json.Unmarshal([]byte(rec.ConfigJSON), &agentCfg) != nil {
return nil
}
return &agentCfg
}
func (b *distributedAgentConfigBackend) SaveConfig(userID string, cfg *state.AgentConfig) error {
configJSON, err := json.Marshal(cfg)
if err != nil {
return fmt.Errorf("failed to marshal agent config: %w", err)
}
return b.store.SaveConfig(&agents.AgentConfigRecord{
UserID: userID,
Name: cfg.Name,
ConfigJSON: string(configJSON),
Status: agents.StatusActive,
})
}
func (b *distributedAgentConfigBackend) UpdateConfig(userID, name string, cfg *state.AgentConfig) error {
// Check if agent exists
if _, err := b.store.GetConfig(userID, name); err != nil {
return fmt.Errorf("%w: %s", ErrAgentNotFound, name)
}
configJSON, err := json.Marshal(cfg)
if err != nil {
return fmt.Errorf("failed to marshal agent config: %w", err)
}
return b.store.SaveConfig(&agents.AgentConfigRecord{
UserID: userID,
Name: cfg.Name,
ConfigJSON: string(configJSON),
Status: agents.StatusActive,
})
}
func (b *distributedAgentConfigBackend) DeleteConfig(userID, name string) error {
return b.store.DeleteConfig(userID, name)
}
func (b *distributedAgentConfigBackend) ImportConfig(userID string, cfg *state.AgentConfig) error {
return b.SaveConfig(userID, cfg)
}
func (b *distributedAgentConfigBackend) ExportConfig(userID, name string) ([]byte, error) {
rec, err := b.store.GetConfig(userID, name)
if err != nil || rec == nil {
return nil, fmt.Errorf("%w: %s", ErrAgentNotFound, name)
}
// Return the raw config JSON (already properly formatted)
var pretty json.RawMessage
if err := json.Unmarshal([]byte(rec.ConfigJSON), &pretty); err == nil {
return json.MarshalIndent(pretty, "", " ")
}
return []byte(rec.ConfigJSON), nil
}
func (b *distributedAgentConfigBackend) SetStatus(userID, name, status string) error {
return b.store.UpdateStatus(userID, name, status)
}
// GetAgent returns nil in distributed mode — agents don't run in-process.
func (b *distributedAgentConfigBackend) GetAgent(_, _ string) *agent.Agent {
return nil
}
// GetSSEManager returns nil in distributed mode — SSE is handled by EventBridge.
func (b *distributedAgentConfigBackend) GetSSEManager(_, _ string) sse.Manager {
return nil
}
// GetStatus returns nil in distributed mode — status is not tracked in-process.
func (b *distributedAgentConfigBackend) GetStatus(_, _ string) *state.Status {
return nil
}
// GetObservables reads from the PostgreSQL observables table.
func (b *distributedAgentConfigBackend) GetObservables(userID, name string) ([]json.RawMessage, error) {
records, err := b.store.GetObservables(agents.AgentKey(userID, name), defaultObservableLimit)
if err != nil {
return nil, err
}
result := make([]json.RawMessage, 0, len(records))
for _, rec := range records {
result = append(result, json.RawMessage(rec.PayloadJSON))
}
return result, nil
}
// ClearObservables clears observables from the PostgreSQL table.
func (b *distributedAgentConfigBackend) ClearObservables(userID, name string) error {
return b.store.ClearObservables(agents.AgentKey(userID, name))
}
func (b *distributedAgentConfigBackend) ListAllGrouped() map[string][]UserAgentInfo {
result := map[string][]UserAgentInfo{}
configs, err := b.store.ListConfigs("")
if err != nil {
return result
}
for _, cfg := range configs {
result[cfg.UserID] = append(result[cfg.UserID], UserAgentInfo{
Name: cfg.Name,
Active: cfg.Status == agents.StatusActive,
})
}
return result
}
func (b *distributedAgentConfigBackend) GetConfigMeta() AgentConfigMetaResult {
meta := agents.DefaultConfigMeta()
return AgentConfigMetaResult{
Fields: meta.Fields,
Actions: meta.Actions,
Connectors: meta.Connectors,
Filters: meta.Filters,
OutputsDir: "",
Distributed: true,
}
}
// ListAvailableActions returns empty in distributed mode — actions are configured as MCP tools per agent.
func (b *distributedAgentConfigBackend) ListAvailableActions() []string {
return []string{}
}
func (b *distributedAgentConfigBackend) Chat(userID, name, message string) (string, error) {
return b.svc.dispatchChat(userID, name, message)
}
// Stop is a no-op in distributed mode — no in-process pool to stop.
func (b *distributedAgentConfigBackend) Stop() {}