refactor(agent): introduce interfaces for MessageBus and ChannelManager
Phase 2 of loop.go refactor — dependency inversion using adapter pattern. - Add interfaces.MessageBus and interfaces.ChannelManager interfaces - Create adapters/messagebus.go wrapping *bus.MessageBus - Create adapters/channelmanager.go wrapping *channels.Manager - Update AgentLoop to use interfaces instead of concrete types - Update registerSharedTools to accept interfaces.MessageBus Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b0d3f19a6a
commit
a81ee35d34
6 changed files with 137 additions and 7 deletions
45
pkg/agent/adapters/channelmanager.go
Normal file
45
pkg/agent/adapters/channelmanager.go
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// PicoClaw - Ultra-lightweight personal AI agent
|
||||
|
||||
package adapters
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/agent/interfaces"
|
||||
"github.com/sipeed/picoclaw/pkg/bus"
|
||||
"github.com/sipeed/picoclaw/pkg/channels"
|
||||
)
|
||||
|
||||
// channelManagerAdapter wraps *channels.Manager to implement interfaces.ChannelManager.
|
||||
type channelManagerAdapter struct {
|
||||
inner *channels.Manager
|
||||
}
|
||||
|
||||
// NewChannelManager creates an adapter for *channels.Manager.
|
||||
func NewChannelManager(inner *channels.Manager) interfaces.ChannelManager {
|
||||
return &channelManagerAdapter{inner: inner}
|
||||
}
|
||||
|
||||
func (a *channelManagerAdapter) GetChannel(name string) (channels.Channel, bool) {
|
||||
return a.inner.GetChannel(name)
|
||||
}
|
||||
|
||||
func (a *channelManagerAdapter) GetEnabledChannels() []string {
|
||||
return a.inner.GetEnabledChannels()
|
||||
}
|
||||
|
||||
func (a *channelManagerAdapter) InvokeTypingStop(channel, chatID string) {
|
||||
a.inner.InvokeTypingStop(channel, chatID)
|
||||
}
|
||||
|
||||
func (a *channelManagerAdapter) SendMessage(ctx context.Context, msg bus.OutboundMessage) error {
|
||||
return a.inner.SendMessage(ctx, msg)
|
||||
}
|
||||
|
||||
func (a *channelManagerAdapter) SendMedia(ctx context.Context, msg bus.OutboundMediaMessage) error {
|
||||
return a.inner.SendMedia(ctx, msg)
|
||||
}
|
||||
|
||||
func (a *channelManagerAdapter) SendPlaceholder(ctx context.Context, channel, chatID string) bool {
|
||||
return a.inner.SendPlaceholder(ctx, channel, chatID)
|
||||
}
|
||||
36
pkg/agent/adapters/messagebus.go
Normal file
36
pkg/agent/adapters/messagebus.go
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// PicoClaw - Ultra-lightweight personal AI agent
|
||||
|
||||
package adapters
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/agent/interfaces"
|
||||
"github.com/sipeed/picoclaw/pkg/bus"
|
||||
)
|
||||
|
||||
// messageBusAdapter wraps *bus.MessageBus to implement interfaces.MessageBus.
|
||||
type messageBusAdapter struct {
|
||||
inner *bus.MessageBus
|
||||
}
|
||||
|
||||
// NewMessageBus creates an adapter for *bus.MessageBus.
|
||||
func NewMessageBus(inner *bus.MessageBus) interfaces.MessageBus {
|
||||
return &messageBusAdapter{inner: inner}
|
||||
}
|
||||
|
||||
func (a *messageBusAdapter) PublishInbound(ctx context.Context, msg bus.InboundMessage) error {
|
||||
return a.inner.PublishInbound(ctx, msg)
|
||||
}
|
||||
|
||||
func (a *messageBusAdapter) PublishOutbound(ctx context.Context, msg bus.OutboundMessage) error {
|
||||
return a.inner.PublishOutbound(ctx, msg)
|
||||
}
|
||||
|
||||
func (a *messageBusAdapter) PublishOutboundMedia(ctx context.Context, msg bus.OutboundMediaMessage) error {
|
||||
return a.inner.PublishOutboundMedia(ctx, msg)
|
||||
}
|
||||
|
||||
func (a *messageBusAdapter) InboundChan() <-chan bus.InboundMessage {
|
||||
return a.inner.InboundChan()
|
||||
}
|
||||
|
|
@ -709,9 +709,10 @@ func TestAgentLoop_HookRespond_MediaError(t *testing.T) {
|
|||
t.Fatalf("MountHook failed: %v", err)
|
||||
}
|
||||
|
||||
al.channelManager = newStartedTestChannelManager(t, al.bus, al.mediaStore, "discord", &errorMediaChannel{
|
||||
sendErr: errors.New("channel unavailable"),
|
||||
})
|
||||
al.channelManager = newStartedTestChannelManager(t,
|
||||
al.bus.(*bus.MessageBus), al.mediaStore, "discord", &errorMediaChannel{
|
||||
sendErr: errors.New("channel unavailable"),
|
||||
})
|
||||
|
||||
sub := al.SubscribeEvents(16)
|
||||
defer al.UnsubscribeEvents(sub.ID)
|
||||
|
|
|
|||
47
pkg/agent/interfaces/interfaces.go
Normal file
47
pkg/agent/interfaces/interfaces.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// PicoClaw - Ultra-lightweight personal AI agent
|
||||
|
||||
package interfaces
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/bus"
|
||||
"github.com/sipeed/picoclaw/pkg/channels"
|
||||
)
|
||||
|
||||
// MessageBus publishes inbound and outbound messages.
|
||||
// It is the primary communication channel for the agent loop.
|
||||
type MessageBus interface {
|
||||
// PublishInbound sends an inbound message to be processed.
|
||||
PublishInbound(ctx context.Context, msg bus.InboundMessage) error
|
||||
|
||||
// PublishOutbound sends an outbound message to the appropriate channel.
|
||||
PublishOutbound(ctx context.Context, msg bus.OutboundMessage) error
|
||||
|
||||
// PublishOutboundMedia sends an outbound media message.
|
||||
PublishOutboundMedia(ctx context.Context, msg bus.OutboundMediaMessage) error
|
||||
|
||||
// InboundChan returns the channel for receiving inbound messages.
|
||||
InboundChan() <-chan bus.InboundMessage
|
||||
}
|
||||
|
||||
// ChannelManager manages channel lifecycle and provides channel access.
|
||||
type ChannelManager interface {
|
||||
// GetChannel returns the channel with the given name.
|
||||
GetChannel(name string) (channels.Channel, bool)
|
||||
|
||||
// GetEnabledChannels returns the list of enabled channel names.
|
||||
GetEnabledChannels() []string
|
||||
|
||||
// InvokeTypingStop signals that typing has stopped.
|
||||
InvokeTypingStop(channel, chatID string)
|
||||
|
||||
// SendMessage sends a text message to the specified channel and chat.
|
||||
SendMessage(ctx context.Context, msg bus.OutboundMessage) error
|
||||
|
||||
// SendMedia sends a media message to the specified channel and chat.
|
||||
SendMedia(ctx context.Context, msg bus.OutboundMediaMessage) error
|
||||
|
||||
// SendPlaceholder sends a placeholder message (e.g., for audio transcription).
|
||||
SendPlaceholder(ctx context.Context, channel, chatID string) bool
|
||||
}
|
||||
|
|
@ -15,9 +15,9 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/agent/interfaces"
|
||||
"github.com/sipeed/picoclaw/pkg/audio/asr"
|
||||
"github.com/sipeed/picoclaw/pkg/bus"
|
||||
"github.com/sipeed/picoclaw/pkg/channels"
|
||||
"github.com/sipeed/picoclaw/pkg/commands"
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
"github.com/sipeed/picoclaw/pkg/constants"
|
||||
|
|
@ -32,7 +32,7 @@ import (
|
|||
|
||||
type AgentLoop struct {
|
||||
// Core dependencies
|
||||
bus *bus.MessageBus
|
||||
bus interfaces.MessageBus
|
||||
cfg *config.Config
|
||||
registry *AgentRegistry
|
||||
state *state.Manager
|
||||
|
|
@ -45,7 +45,7 @@ type AgentLoop struct {
|
|||
running atomic.Bool
|
||||
contextManager ContextManager
|
||||
fallback *providers.FallbackChain
|
||||
channelManager *channels.Manager
|
||||
channelManager interfaces.ChannelManager
|
||||
mediaStore media.MediaStore
|
||||
transcriber asr.Transcriber
|
||||
cmdRegistry *commands.Registry
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/agent/interfaces"
|
||||
"github.com/sipeed/picoclaw/pkg/audio/tts"
|
||||
"github.com/sipeed/picoclaw/pkg/bus"
|
||||
"github.com/sipeed/picoclaw/pkg/channels"
|
||||
|
|
@ -79,7 +80,7 @@ func NewAgentLoop(
|
|||
func registerSharedTools(
|
||||
al *AgentLoop,
|
||||
cfg *config.Config,
|
||||
msgBus *bus.MessageBus,
|
||||
msgBus interfaces.MessageBus,
|
||||
registry *AgentRegistry,
|
||||
provider providers.LLMProvider,
|
||||
) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue