refactor(commands): use shared /switch model path
This commit is contained in:
parent
47accae0d8
commit
ffb7841572
7 changed files with 49 additions and 287 deletions
|
|
@ -25,7 +25,6 @@ import (
|
|||
_ "github.com/sipeed/picoclaw/pkg/channels/qq"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/slack"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/telegram"
|
||||
tgchannel "github.com/sipeed/picoclaw/pkg/channels/telegram"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/wecom"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/whatsapp"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/whatsapp_native"
|
||||
|
|
@ -144,12 +143,6 @@ func gatewayCmd(debug bool) error {
|
|||
logger.InfoCF("voice", "Transcription enabled (agent-level)", map[string]any{"provider": transcriber.Name()})
|
||||
}
|
||||
|
||||
if telegramCh, ok := channelManager.GetChannel("telegram"); ok {
|
||||
if tc, ok := telegramCh.(*tgchannel.TelegramChannel); ok {
|
||||
tc.SetRegistry(agentLoop.GetRegistry())
|
||||
}
|
||||
}
|
||||
|
||||
enabledChannels := channelManager.GetEnabledChannels()
|
||||
if len(enabledChannels) > 0 {
|
||||
fmt.Printf("✓ Channels enabled: %s\n", enabledChannels)
|
||||
|
|
|
|||
|
|
@ -1812,12 +1812,18 @@ func (al *AgentLoop) buildCommandsRuntime(agent *AgentInstance, opts *processOpt
|
|||
}
|
||||
if agent != nil {
|
||||
rt.GetModelInfo = func() (string, string) {
|
||||
return agent.Model, al.cfg.Agents.Defaults.Provider
|
||||
provider := al.cfg.Agents.Defaults.Provider
|
||||
if len(agent.Candidates) > 0 && agent.Candidates[0].Provider != "" {
|
||||
provider = agent.Candidates[0].Provider
|
||||
}
|
||||
return agent.Model, provider
|
||||
}
|
||||
rt.SwitchModel = func(value string) (string, error) {
|
||||
oldModel := agent.Model
|
||||
agent.Model = value
|
||||
return oldModel, nil
|
||||
if al.registry == nil {
|
||||
return "", fmt.Errorf("agent registry not initialized")
|
||||
}
|
||||
oldModel, _, err := al.registry.SwitchDefaultAgentModel(value)
|
||||
return oldModel, err
|
||||
}
|
||||
|
||||
rt.ClearHistory = func() error {
|
||||
|
|
|
|||
|
|
@ -531,10 +531,23 @@ func TestProcessMessage_SwitchModelShowModelConsistency(t *testing.T) {
|
|||
Workspace: tmpDir,
|
||||
Provider: "openai",
|
||||
Model: "before-switch",
|
||||
ModelName: "test-openai-mini",
|
||||
MaxTokens: 4096,
|
||||
MaxToolIterations: 10,
|
||||
},
|
||||
},
|
||||
ModelList: []config.ModelConfig{
|
||||
{
|
||||
ModelName: "test-openai-mini",
|
||||
Model: "openai/fake-openai-mini",
|
||||
APIKey: "test-openai-key",
|
||||
},
|
||||
{
|
||||
ModelName: "test-qwen-plus",
|
||||
Model: "qwen/fake-qwen-plus",
|
||||
APIKey: "test-qwen-key",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
msgBus := bus.NewMessageBus()
|
||||
|
|
@ -546,13 +559,13 @@ func TestProcessMessage_SwitchModelShowModelConsistency(t *testing.T) {
|
|||
Channel: "telegram",
|
||||
SenderID: "user1",
|
||||
ChatID: "chat1",
|
||||
Content: "/switch model to after-switch",
|
||||
Content: "/switch model to test-qwen-plus",
|
||||
Peer: bus.Peer{
|
||||
Kind: "direct",
|
||||
ID: "user1",
|
||||
},
|
||||
})
|
||||
if !strings.Contains(switchResp, "Switched model from before-switch to after-switch") {
|
||||
if !strings.Contains(switchResp, "Switched model from test-openai-mini to fake-qwen-plus") {
|
||||
t.Fatalf("unexpected /switch reply: %q", switchResp)
|
||||
}
|
||||
|
||||
|
|
@ -566,7 +579,7 @@ func TestProcessMessage_SwitchModelShowModelConsistency(t *testing.T) {
|
|||
ID: "user1",
|
||||
},
|
||||
})
|
||||
if !strings.Contains(showResp, "Current Model: after-switch (Provider: openai)") {
|
||||
if !strings.Contains(showResp, "Current Model: fake-qwen-plus (Provider: qwen)") {
|
||||
t.Fatalf("unexpected /show model reply after switch: %q", showResp)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,13 +40,12 @@ var (
|
|||
|
||||
type TelegramChannel struct {
|
||||
*channels.BaseChannel
|
||||
bot *telego.Bot
|
||||
bh *th.BotHandler
|
||||
commands TelegramCommander
|
||||
config *config.Config
|
||||
chatIDs map[string]int64
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
bot *telego.Bot
|
||||
bh *th.BotHandler
|
||||
config *config.Config
|
||||
chatIDs map[string]int64
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
registerFunc func(context.Context, []commands.Definition) error
|
||||
commandRegCancel context.CancelFunc
|
||||
|
|
@ -96,17 +95,12 @@ func NewTelegramChannel(cfg *config.Config, bus *bus.MessageBus) (*TelegramChann
|
|||
|
||||
return &TelegramChannel{
|
||||
BaseChannel: base,
|
||||
commands: NewTelegramCommands(bot, cfg, nil),
|
||||
bot: bot,
|
||||
config: cfg,
|
||||
chatIDs: make(map[string]int64),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *TelegramChannel) SetRegistry(switcher AgentModelSwitcher) {
|
||||
c.commands = NewTelegramCommands(c.bot, c.config, switcher)
|
||||
}
|
||||
|
||||
func (c *TelegramChannel) Start(ctx context.Context) error {
|
||||
logger.InfoC("telegram", "Starting Telegram bot (polling mode)...")
|
||||
|
||||
|
|
@ -127,25 +121,6 @@ func (c *TelegramChannel) Start(ctx context.Context) error {
|
|||
}
|
||||
c.bh = bh
|
||||
|
||||
bh.HandleMessage(func(ctx *th.Context, message telego.Message) error {
|
||||
return c.commands.Start(ctx, message)
|
||||
}, th.CommandEqual("start"))
|
||||
bh.HandleMessage(func(ctx *th.Context, message telego.Message) error {
|
||||
return c.commands.Help(ctx, message)
|
||||
}, th.CommandEqual("help"))
|
||||
|
||||
bh.HandleMessage(func(ctx *th.Context, message telego.Message) error {
|
||||
return c.commands.Show(ctx, message)
|
||||
}, th.CommandEqual("show"))
|
||||
|
||||
bh.HandleMessage(func(ctx *th.Context, message telego.Message) error {
|
||||
return c.commands.List(ctx, message)
|
||||
}, th.CommandEqual("list"))
|
||||
|
||||
bh.HandleMessage(func(ctx *th.Context, message telego.Message) error {
|
||||
return c.commands.Model(ctx, message)
|
||||
}, th.CommandEqual("model"))
|
||||
|
||||
bh.HandleMessage(func(ctx *th.Context, message telego.Message) error {
|
||||
return c.handleMessage(ctx, &message)
|
||||
}, th.AnyMessage())
|
||||
|
|
@ -155,11 +130,7 @@ func (c *TelegramChannel) Start(ctx context.Context) error {
|
|||
"username": c.bot.Username(),
|
||||
})
|
||||
|
||||
commandDefs := append(commands.BuiltinDefinitions(), commands.Definition{
|
||||
Name: "model",
|
||||
Description: "Show or switch the active model",
|
||||
})
|
||||
c.startCommandRegistration(c.ctx, commandDefs)
|
||||
c.startCommandRegistration(c.ctx, commands.BuiltinDefinitions())
|
||||
|
||||
go func() {
|
||||
if err = bh.Start(); err != nil {
|
||||
|
|
|
|||
|
|
@ -1,234 +0,0 @@
|
|||
package telegram
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mymmrac/telego"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
"github.com/sipeed/picoclaw/pkg/providers"
|
||||
)
|
||||
|
||||
// AgentModelSwitcher is the minimal interface needed to get/set the active model.
|
||||
type AgentModelSwitcher interface {
|
||||
GetDefaultAgentModel() string
|
||||
SwitchDefaultAgentModel(modelName string) (string, string, error)
|
||||
}
|
||||
|
||||
type TelegramCommander interface {
|
||||
Help(ctx context.Context, message telego.Message) error
|
||||
Start(ctx context.Context, message telego.Message) error
|
||||
Show(ctx context.Context, message telego.Message) error
|
||||
List(ctx context.Context, message telego.Message) error
|
||||
Model(ctx context.Context, message telego.Message) error
|
||||
}
|
||||
|
||||
type cmd struct {
|
||||
bot *telego.Bot
|
||||
config *config.Config
|
||||
switcher AgentModelSwitcher
|
||||
}
|
||||
|
||||
func NewTelegramCommands(bot *telego.Bot, cfg *config.Config, switcher AgentModelSwitcher) TelegramCommander {
|
||||
return &cmd{
|
||||
bot: bot,
|
||||
config: cfg,
|
||||
switcher: switcher,
|
||||
}
|
||||
}
|
||||
|
||||
func commandArgs(text string) string {
|
||||
parts := strings.SplitN(text, " ", 2)
|
||||
if len(parts) < 2 {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(parts[1])
|
||||
}
|
||||
|
||||
func (c *cmd) Help(ctx context.Context, message telego.Message) error {
|
||||
msg := `/start - Start the bot
|
||||
/help - Show this help message
|
||||
/show [model|channel] - Show current configuration
|
||||
/list [models|channels] - List available options
|
||||
/model - Show or switch the active model
|
||||
/model - show current model
|
||||
/model list - list available models
|
||||
/model <name> - switch to named model
|
||||
`
|
||||
_, err := c.bot.SendMessage(ctx, &telego.SendMessageParams{
|
||||
ChatID: telego.ChatID{ID: message.Chat.ID},
|
||||
Text: msg,
|
||||
ReplyParameters: &telego.ReplyParameters{
|
||||
MessageID: message.MessageID,
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *cmd) Start(ctx context.Context, message telego.Message) error {
|
||||
_, err := c.bot.SendMessage(ctx, &telego.SendMessageParams{
|
||||
ChatID: telego.ChatID{ID: message.Chat.ID},
|
||||
Text: "Hello! I am PicoClaw 🦞",
|
||||
ReplyParameters: &telego.ReplyParameters{
|
||||
MessageID: message.MessageID,
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *cmd) Show(ctx context.Context, message telego.Message) error {
|
||||
args := commandArgs(message.Text)
|
||||
if args == "" {
|
||||
_, err := c.bot.SendMessage(ctx, &telego.SendMessageParams{
|
||||
ChatID: telego.ChatID{ID: message.Chat.ID},
|
||||
Text: "Usage: /show [model|channel]",
|
||||
ReplyParameters: &telego.ReplyParameters{
|
||||
MessageID: message.MessageID,
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
var response string
|
||||
switch args {
|
||||
case "model":
|
||||
if c.switcher == nil {
|
||||
response = fmt.Sprintf("Current model: %s", c.config.Agents.Defaults.GetModelName())
|
||||
} else {
|
||||
response = fmt.Sprintf("Current model: %s", c.switcher.GetDefaultAgentModel())
|
||||
}
|
||||
case "channel":
|
||||
response = "Current Channel: telegram"
|
||||
default:
|
||||
response = fmt.Sprintf("Unknown parameter: %s. Try 'model' or 'channel'.", args)
|
||||
}
|
||||
|
||||
_, err := c.bot.SendMessage(ctx, &telego.SendMessageParams{
|
||||
ChatID: telego.ChatID{ID: message.Chat.ID},
|
||||
Text: response,
|
||||
ReplyParameters: &telego.ReplyParameters{
|
||||
MessageID: message.MessageID,
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *cmd) List(ctx context.Context, message telego.Message) error {
|
||||
args := commandArgs(message.Text)
|
||||
if args == "" {
|
||||
_, err := c.bot.SendMessage(ctx, &telego.SendMessageParams{
|
||||
ChatID: telego.ChatID{ID: message.Chat.ID},
|
||||
Text: "Usage: /list [models|channels]",
|
||||
ReplyParameters: &telego.ReplyParameters{
|
||||
MessageID: message.MessageID,
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
var response string
|
||||
switch args {
|
||||
case "models":
|
||||
provider := c.config.Agents.Defaults.Provider
|
||||
if provider == "" {
|
||||
provider = "configured default"
|
||||
}
|
||||
response = fmt.Sprintf("Configured Model: %s\nProvider: %s\n\nTo change models, update config.json",
|
||||
c.config.Agents.Defaults.GetModelName(), provider)
|
||||
|
||||
case "channels":
|
||||
var enabled []string
|
||||
if c.config.Channels.Telegram.Enabled {
|
||||
enabled = append(enabled, "telegram")
|
||||
}
|
||||
if c.config.Channels.WhatsApp.Enabled {
|
||||
enabled = append(enabled, "whatsapp")
|
||||
}
|
||||
if c.config.Channels.Feishu.Enabled {
|
||||
enabled = append(enabled, "feishu")
|
||||
}
|
||||
if c.config.Channels.Discord.Enabled {
|
||||
enabled = append(enabled, "discord")
|
||||
}
|
||||
if c.config.Channels.Slack.Enabled {
|
||||
enabled = append(enabled, "slack")
|
||||
}
|
||||
response = fmt.Sprintf("Enabled Channels:\n- %s", strings.Join(enabled, "\n- "))
|
||||
|
||||
default:
|
||||
response = fmt.Sprintf("Unknown parameter: %s. Try 'models' or 'channels'.", args)
|
||||
}
|
||||
|
||||
_, err := c.bot.SendMessage(ctx, &telego.SendMessageParams{
|
||||
ChatID: telego.ChatID{ID: message.Chat.ID},
|
||||
Text: response,
|
||||
ReplyParameters: &telego.ReplyParameters{
|
||||
MessageID: message.MessageID,
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *cmd) Model(ctx context.Context, message telego.Message) error {
|
||||
args := commandArgs(message.Text)
|
||||
|
||||
var response string
|
||||
switch {
|
||||
case args == "":
|
||||
// Show current model
|
||||
if c.switcher == nil {
|
||||
response = "No agent configured."
|
||||
} else {
|
||||
response = fmt.Sprintf("Current model: %s", c.switcher.GetDefaultAgentModel())
|
||||
}
|
||||
|
||||
case args == "list":
|
||||
// List models from config
|
||||
if len(c.config.ModelList) == 0 {
|
||||
response = "No models configured in model_list."
|
||||
} else {
|
||||
currentModel := ""
|
||||
if c.switcher != nil {
|
||||
currentModel = c.switcher.GetDefaultAgentModel()
|
||||
}
|
||||
lines := make([]string, 0, len(c.config.ModelList))
|
||||
for _, m := range c.config.ModelList {
|
||||
_, modelID := providers.ExtractProtocol(m.Model)
|
||||
line := "• " + m.ModelName
|
||||
if modelID == currentModel {
|
||||
line += " (active)"
|
||||
}
|
||||
line += " -> " + m.Model
|
||||
lines = append(lines, line)
|
||||
}
|
||||
response = "Available models:\n" + strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
default:
|
||||
// Switch to the named model
|
||||
modelName := args
|
||||
if c.switcher == nil {
|
||||
response = "No agent configured."
|
||||
} else {
|
||||
oldModel, newModel, err := c.switcher.SwitchDefaultAgentModel(modelName)
|
||||
if err != nil {
|
||||
response = fmt.Sprintf("Failed to switch model: %v", err)
|
||||
} else if oldModel == newModel {
|
||||
response = fmt.Sprintf("Model already active: %s", newModel)
|
||||
} else {
|
||||
response = fmt.Sprintf("Switched model: %s -> %s", oldModel, newModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, err := c.bot.SendMessage(ctx, &telego.SendMessageParams{
|
||||
ChatID: telego.ChatID{ID: message.Chat.ID},
|
||||
Text: response,
|
||||
ReplyParameters: &telego.ReplyParameters{
|
||||
MessageID: message.MessageID,
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
|
@ -27,7 +27,14 @@ func switchCommand() Definition {
|
|||
if err != nil {
|
||||
return req.Reply(err.Error())
|
||||
}
|
||||
return req.Reply(fmt.Sprintf("Switched model from %s to %s", oldModel, value))
|
||||
newModel := value
|
||||
if rt.GetModelInfo != nil {
|
||||
currentModel, _ := rt.GetModelInfo()
|
||||
if currentModel != "" {
|
||||
newModel = currentModel
|
||||
}
|
||||
}
|
||||
return req.Reply(fmt.Sprintf("Switched model from %s to %s", oldModel, newModel))
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ func TestSwitchModel_Success(t *testing.T) {
|
|||
SwitchModel: func(value string) (string, error) {
|
||||
return "old-model", nil
|
||||
},
|
||||
GetModelInfo: func() (string, string) {
|
||||
return "openai/test-model", "openai"
|
||||
},
|
||||
}
|
||||
ex := NewExecutor(NewRegistry(BuiltinDefinitions()), rt)
|
||||
|
||||
|
|
@ -25,7 +28,7 @@ func TestSwitchModel_Success(t *testing.T) {
|
|||
if res.Outcome != OutcomeHandled {
|
||||
t.Fatalf("outcome=%v, want=%v", res.Outcome, OutcomeHandled)
|
||||
}
|
||||
want := "Switched model from old-model to gpt-4"
|
||||
want := "Switched model from old-model to openai/test-model"
|
||||
if reply != want {
|
||||
t.Fatalf("reply=%q, want=%q", reply, want)
|
||||
}
|
||||
|
|
@ -239,6 +242,9 @@ func TestSwitch_BangPrefix(t *testing.T) {
|
|||
SwitchModel: func(value string) (string, error) {
|
||||
return "old", nil
|
||||
},
|
||||
GetModelInfo: func() (string, string) {
|
||||
return "openai/test-model", "openai"
|
||||
},
|
||||
}
|
||||
ex := NewExecutor(NewRegistry(BuiltinDefinitions()), rt)
|
||||
|
||||
|
|
@ -253,7 +259,7 @@ func TestSwitch_BangPrefix(t *testing.T) {
|
|||
if res.Outcome != OutcomeHandled {
|
||||
t.Fatalf("! prefix: outcome=%v, want=%v", res.Outcome, OutcomeHandled)
|
||||
}
|
||||
if reply != "Switched model from old to gpt-4" {
|
||||
if reply != "Switched model from old to openai/test-model" {
|
||||
t.Fatalf("! prefix: reply=%q, want success message", reply)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue