gci-fmt
This commit is contained in:
parent
997c888a5c
commit
c240378200
6 changed files with 525 additions and 75 deletions
7
go.mod
7
go.mod
|
|
@ -94,15 +94,8 @@ require (
|
||||||
github.com/valyala/fastjson v1.6.10 // indirect
|
github.com/valyala/fastjson v1.6.10 // indirect
|
||||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||||
golang.org/x/arch v0.24.0 // indirect
|
golang.org/x/arch v0.24.0 // indirect
|
||||||
<<<<<<< HEAD
|
|
||||||
golang.org/x/crypto v0.49.0
|
golang.org/x/crypto v0.49.0
|
||||||
golang.org/x/net v0.52.0
|
golang.org/x/net v0.52.0
|
||||||
golang.org/x/sync v0.20.0 // indirect
|
golang.org/x/sync v0.20.0 // indirect
|
||||||
golang.org/x/sys v0.42.0 // indirect
|
golang.org/x/sys v0.42.0 // indirect
|
||||||
=======
|
|
||||||
golang.org/x/crypto v0.48.0
|
|
||||||
golang.org/x/net v0.51.0
|
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
|
||||||
>>>>>>> 1038a05 (channel mqtt)
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -158,8 +158,13 @@ func (m *Manager) RecordReactionUndo(channel, chatID string, undo func()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// preSend handles typing stop, reaction undo, and placeholder editing before sending a message.
|
// preSend handles typing stop, reaction undo, and placeholder editing before sending a message.
|
||||||
// Returns true if the message was already delivered (skip Send).
|
// Returns true if the message was edited into a placeholder (skip Send).
|
||||||
func (m *Manager) preSend(ctx context.Context, name string, msg bus.OutboundMessage, ch Channel) bool {
|
func (m *Manager) preSend(
|
||||||
|
ctx context.Context,
|
||||||
|
name string,
|
||||||
|
msg bus.OutboundMessage,
|
||||||
|
ch Channel,
|
||||||
|
) bool {
|
||||||
key := name + ":" + msg.ChatID
|
key := name + ":" + msg.ChatID
|
||||||
|
|
||||||
// 1. Stop typing
|
// 1. Stop typing
|
||||||
|
|
@ -206,7 +211,11 @@ func (m *Manager) preSend(ctx context.Context, name string, msg bus.OutboundMess
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager(cfg *config.Config, messageBus *bus.MessageBus, store media.MediaStore) (*Manager, error) {
|
func NewManager(
|
||||||
|
cfg *config.Config,
|
||||||
|
messageBus *bus.MessageBus,
|
||||||
|
store media.MediaStore,
|
||||||
|
) (*Manager, error) {
|
||||||
m := &Manager{
|
m := &Manager{
|
||||||
channels: make(map[string]Channel),
|
channels: make(map[string]Channel),
|
||||||
workers: make(map[string]*channelWorker),
|
workers: make(map[string]*channelWorker),
|
||||||
|
|
@ -401,7 +410,9 @@ func (m *Manager) initChannels(channels *config.ChannelsConfig) error {
|
||||||
m.initChannel("irc", "IRC")
|
m.initChannel("irc", "IRC")
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.config.Channels.MQTT.Enabled && m.config.Channels.MQTT.Broker != "" && m.config.Channels.MQTT.ClientID != "" && len(m.config.Channels.MQTT.SubscribeTopics) > 0 {
|
if m.config.Channels.MQTT.Enabled && m.config.Channels.MQTT.Broker != "" &&
|
||||||
|
m.config.Channels.MQTT.ClientID != "" &&
|
||||||
|
len(m.config.Channels.MQTT.SubscribeTopics) > 0 {
|
||||||
m.initChannel("mqtt", "MQTT")
|
m.initChannel("mqtt", "MQTT")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -623,7 +634,12 @@ func (m *Manager) runWorker(ctx context.Context, name string, w *channelWorker)
|
||||||
// - ErrNotRunning / ErrSendFailed: permanent, no retry
|
// - ErrNotRunning / ErrSendFailed: permanent, no retry
|
||||||
// - ErrRateLimit: fixed delay retry
|
// - ErrRateLimit: fixed delay retry
|
||||||
// - ErrTemporary / unknown: exponential backoff retry
|
// - ErrTemporary / unknown: exponential backoff retry
|
||||||
func (m *Manager) sendWithRetry(ctx context.Context, name string, w *channelWorker, msg bus.OutboundMessage) {
|
func (m *Manager) sendWithRetry(
|
||||||
|
ctx context.Context,
|
||||||
|
name string,
|
||||||
|
w *channelWorker,
|
||||||
|
msg bus.OutboundMessage,
|
||||||
|
) {
|
||||||
// Rate limit: wait for token
|
// Rate limit: wait for token
|
||||||
if err := w.limiter.Wait(ctx); err != nil {
|
if err := w.limiter.Wait(ctx); err != nil {
|
||||||
// ctx canceled, shutting down
|
// ctx canceled, shutting down
|
||||||
|
|
@ -663,7 +679,10 @@ func (m *Manager) sendWithRetry(ctx context.Context, name string, w *channelWork
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrTemporary or unknown error — exponential backoff
|
// ErrTemporary or unknown error — exponential backoff
|
||||||
backoff := min(time.Duration(float64(baseBackoff)*math.Pow(2, float64(attempt))), maxBackoff)
|
backoff := min(
|
||||||
|
time.Duration(float64(baseBackoff)*math.Pow(2, float64(attempt))),
|
||||||
|
maxBackoff,
|
||||||
|
)
|
||||||
select {
|
select {
|
||||||
case <-time.After(backoff):
|
case <-time.After(backoff):
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
|
@ -788,12 +807,21 @@ func (m *Manager) runMediaWorker(ctx context.Context, name string, w *channelWor
|
||||||
|
|
||||||
// sendMediaWithRetry sends a media message through the channel with rate limiting and
|
// sendMediaWithRetry sends a media message through the channel with rate limiting and
|
||||||
// retry logic. If the channel does not implement MediaSender, it silently skips.
|
// retry logic. If the channel does not implement MediaSender, it silently skips.
|
||||||
func (m *Manager) sendMediaWithRetry(ctx context.Context, name string, w *channelWorker, msg bus.OutboundMediaMessage) {
|
func (m *Manager) sendMediaWithRetry(
|
||||||
|
ctx context.Context,
|
||||||
|
name string,
|
||||||
|
w *channelWorker,
|
||||||
|
msg bus.OutboundMediaMessage,
|
||||||
|
) {
|
||||||
ms, ok := w.ch.(MediaSender)
|
ms, ok := w.ch.(MediaSender)
|
||||||
if !ok {
|
if !ok {
|
||||||
logger.DebugCF("channels", "Channel does not support MediaSender, skipping media", map[string]any{
|
logger.DebugCF(
|
||||||
"channel": name,
|
"channels",
|
||||||
})
|
"Channel does not support MediaSender, skipping media",
|
||||||
|
map[string]any{
|
||||||
|
"channel": name,
|
||||||
|
},
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -830,7 +858,10 @@ func (m *Manager) sendMediaWithRetry(ctx context.Context, name string, w *channe
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrTemporary or unknown error — exponential backoff
|
// ErrTemporary or unknown error — exponential backoff
|
||||||
backoff := min(time.Duration(float64(baseBackoff)*math.Pow(2, float64(attempt))), maxBackoff)
|
backoff := min(
|
||||||
|
time.Duration(float64(baseBackoff)*math.Pow(2, float64(attempt))),
|
||||||
|
maxBackoff,
|
||||||
|
)
|
||||||
select {
|
select {
|
||||||
case <-time.After(backoff):
|
case <-time.After(backoff):
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,17 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
||||||
channels.RegisterFactory("mqtt", func(cfg *config.Config, b *bus.MessageBus) (channels.Channel, error) {
|
channels.RegisterFactory("mqtt", func(cfg *config.Config, b *bus.MessageBus) (channels.Channel, error) {
|
||||||
|
|
||||||
if !cfg.Channels.MQTT.Enabled {
|
if !cfg.Channels.MQTT.Enabled {
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewMQTTChannel(cfg.Channels.MQTT, b)
|
return NewMQTTChannel(cfg.Channels.MQTT, b)
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
mqtt "github.com/eclipse/paho.mqtt.golang"
|
mqtt "github.com/eclipse/paho.mqtt.golang"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/bus"
|
"github.com/sipeed/picoclaw/pkg/bus"
|
||||||
"github.com/sipeed/picoclaw/pkg/channels"
|
"github.com/sipeed/picoclaw/pkg/channels"
|
||||||
"github.com/sipeed/picoclaw/pkg/config"
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
|
|
@ -164,7 +163,7 @@ func (c *MQTTChannel) Start(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.InfoCF("mqtt", "Connected to MQTT broker", map[string]any{
|
logger.InfoCF("mqtt", "Connected to MQTT broker", map[string]any{
|
||||||
"broker": c.config.Broker,
|
"broker": c.config.Broker,
|
||||||
"client_id": c.config.ClientID,
|
"client_id": c.config.ClientID,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -191,7 +190,6 @@ func (c *MQTTChannel) Stop(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// onMessage handles incoming MQTT messages.
|
// onMessage handles incoming MQTT messages.
|
||||||
func (c *MQTTChannel) onMessage(client mqtt.Client, msg mqtt.Message) {
|
func (c *MQTTChannel) onMessage(client mqtt.Client, msg mqtt.Message) {
|
||||||
logger.DebugCF("mqtt", "Received message", map[string]any{
|
logger.DebugCF("mqtt", "Received message", map[string]any{
|
||||||
|
|
@ -277,8 +275,8 @@ func (c *MQTTChannel) onMessage(client mqtt.Client, msg mqtt.Message) {
|
||||||
messageID := fmt.Sprintf("mqtt-%d", time.Now().UnixNano())
|
messageID := fmt.Sprintf("mqtt-%d", time.Now().UnixNano())
|
||||||
|
|
||||||
metadata := map[string]string{
|
metadata := map[string]string{
|
||||||
"platform": "mqtt",
|
"platform": "mqtt",
|
||||||
"topic": msg.Topic(),
|
"topic": msg.Topic(),
|
||||||
"reply_topic": replyTopic,
|
"reply_topic": replyTopic,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -594,26 +594,26 @@ type IRCConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type MQTTConfig struct {
|
type MQTTConfig struct {
|
||||||
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_MQTT_ENABLED"`
|
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_MQTT_ENABLED"`
|
||||||
Broker string `json:"broker" env:"PICOCLAW_CHANNELS_MQTT_BROKER"`
|
Broker string `json:"broker" env:"PICOCLAW_CHANNELS_MQTT_BROKER"`
|
||||||
ClientID string `json:"client_id" env:"PICOCLAW_CHANNELS_MQTT_CLIENT_ID"`
|
ClientID string `json:"client_id" env:"PICOCLAW_CHANNELS_MQTT_CLIENT_ID"`
|
||||||
Username string `json:"username" env:"PICOCLAW_CHANNELS_MQTT_USERNAME"`
|
Username string `json:"username" env:"PICOCLAW_CHANNELS_MQTT_USERNAME"`
|
||||||
Password string `json:"password" env:"PICOCLAW_CHANNELS_MQTT_PASSWORD"`
|
Password string `json:"password" env:"PICOCLAW_CHANNELS_MQTT_PASSWORD"`
|
||||||
SubscribeTopics []string `json:"subscribe_topics" env:"PICOCLAW_CHANNELS_MQTT_SUBSCRIBE_TOPICS"`
|
SubscribeTopics []string `json:"subscribe_topics" env:"PICOCLAW_CHANNELS_MQTT_SUBSCRIBE_TOPICS"`
|
||||||
SubscribeJSONKey *string `json:"subscribe_json_key,omitempty"`
|
SubscribeJSONKey *string `json:"subscribe_json_key,omitempty"`
|
||||||
ReplyTopic string `json:"reply_topic" env:"PICOCLAW_CHANNELS_MQTT_REPLY_TOPIC"`
|
ReplyTopic string `json:"reply_topic" env:"PICOCLAW_CHANNELS_MQTT_REPLY_TOPIC"`
|
||||||
ReplyJSONKey *string `json:"reply_json_key,omitempty"`
|
ReplyJSONKey *string `json:"reply_json_key,omitempty"`
|
||||||
TLS bool `json:"tls" env:"PICOCLAW_CHANNELS_MQTT_TLS"`
|
TLS bool `json:"tls" env:"PICOCLAW_CHANNELS_MQTT_TLS"`
|
||||||
TLSCA string `json:"tls_ca" env:"PICOCLAW_CHANNELS_MQTT_TLS_CA"`
|
TLSCA string `json:"tls_ca" env:"PICOCLAW_CHANNELS_MQTT_TLS_CA"`
|
||||||
TLSCert string `json:"tls_cert" env:"PICOCLAW_CHANNELS_MQTT_TLS_CERT"`
|
TLSCert string `json:"tls_cert" env:"PICOCLAW_CHANNELS_MQTT_TLS_CERT"`
|
||||||
TLSKey string `json:"tls_key" env:"PICOCLAW_CHANNELS_MQTT_TLS_KEY"`
|
TLSKey string `json:"tls_key" env:"PICOCLAW_CHANNELS_MQTT_TLS_KEY"`
|
||||||
QoS int `json:"qos" env:"PICOCLAW_CHANNELS_MQTT_QOS"`
|
QoS int `json:"qos" env:"PICOCLAW_CHANNELS_MQTT_QOS"`
|
||||||
Retain bool `json:"retain" env:"PICOCLAW_CHANNELS_MQTT_RETAIN"`
|
Retain bool `json:"retain" env:"PICOCLAW_CHANNELS_MQTT_RETAIN"`
|
||||||
Prefix string `json:"prefix" env:"PICOCLAW_CHANNELS_MQTT_PREFIX"`
|
Prefix string `json:"prefix" env:"PICOCLAW_CHANNELS_MQTT_PREFIX"`
|
||||||
Instruction string `json:"instruction" env:"PICOCLAW_CHANNELS_MQTT_INSTRUCTION"`
|
Instruction string `json:"instruction" env:"PICOCLAW_CHANNELS_MQTT_INSTRUCTION"`
|
||||||
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_MQTT_ALLOW_FROM"`
|
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_MQTT_ALLOW_FROM"`
|
||||||
GroupTrigger GroupTriggerConfig `json:"group_trigger,omitempty"`
|
GroupTrigger GroupTriggerConfig `json:"group_trigger,omitempty"`
|
||||||
ReasoningChannelID string `json:"reasoning_channel_id" env:"PICOCLAW_CHANNELS_MQTT_REASONING_CHANNEL_ID"`
|
ReasoningChannelID string `json:"reasoning_channel_id" env:"PICOCLAW_CHANNELS_MQTT_REASONING_CHANNEL_ID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HeartbeatConfig struct {
|
type HeartbeatConfig struct {
|
||||||
|
|
@ -808,9 +808,9 @@ type SearXNGConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type GLMSearchConfig struct {
|
type GLMSearchConfig struct {
|
||||||
Enabled bool `json:"enabled" env:"PICOCLAW_TOOLS_WEB_GLM_ENABLED"`
|
Enabled bool `json:"enabled" env:"PICOCLAW_TOOLS_WEB_GLM_ENABLED"`
|
||||||
APIKey string `json:"api_key" env:"PICOCLAW_TOOLS_WEB_GLM_API_KEY"`
|
APIKey string `json:"api_key" env:"PICOCLAW_TOOLS_WEB_GLM_API_KEY"`
|
||||||
BaseURL string `json:"base_url" env:"PICOCLAW_TOOLS_WEB_GLM_BASE_URL"`
|
BaseURL string `json:"base_url" env:"PICOCLAW_TOOLS_WEB_GLM_BASE_URL"`
|
||||||
// SearchEngine specifies the search backend: "search_std" (default),
|
// SearchEngine specifies the search backend: "search_std" (default),
|
||||||
// "search_pro", "search_pro_sogou", or "search_pro_quark".
|
// "search_pro", "search_pro_sogou", or "search_pro_quark".
|
||||||
SearchEngine string `json:"search_engine" env:"PICOCLAW_TOOLS_WEB_GLM_SEARCH_ENGINE"`
|
SearchEngine string `json:"search_engine" env:"PICOCLAW_TOOLS_WEB_GLM_SEARCH_ENGINE"`
|
||||||
|
|
@ -825,6 +825,7 @@ type BaiduSearchConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebToolsConfig struct {
|
type WebToolsConfig struct {
|
||||||
|
|
||||||
ToolConfig ` envPrefix:"PICOCLAW_TOOLS_WEB_"`
|
ToolConfig ` envPrefix:"PICOCLAW_TOOLS_WEB_"`
|
||||||
Brave BraveConfig ` json:"brave"`
|
Brave BraveConfig ` json:"brave"`
|
||||||
Tavily TavilyConfig ` json:"tavily"`
|
Tavily TavilyConfig ` json:"tavily"`
|
||||||
|
|
@ -833,18 +834,19 @@ type WebToolsConfig struct {
|
||||||
SearXNG SearXNGConfig ` json:"searxng"`
|
SearXNG SearXNGConfig ` json:"searxng"`
|
||||||
GLMSearch GLMSearchConfig ` json:"glm_search"`
|
GLMSearch GLMSearchConfig ` json:"glm_search"`
|
||||||
BaiduSearch BaiduSearchConfig ` json:"baidu_search"`
|
BaiduSearch BaiduSearchConfig ` json:"baidu_search"`
|
||||||
|
|
||||||
// PreferNative controls whether to use provider-native web search when
|
// PreferNative controls whether to use provider-native web search when
|
||||||
// the active LLM supports it (e.g. OpenAI web_search_preview). When true,
|
// the active LLM supports it (e.g. OpenAI web_search_preview). When true,
|
||||||
// the client-side web_search tool is hidden to avoid duplicate search surfaces,
|
// the client-side web_search tool is hidden to avoid duplicate search surfaces,
|
||||||
// and the provider's built-in search is used instead. Falls back to client-side
|
// and the provider's built-in search is used instead. Falls back to client-side
|
||||||
// search when the provider does not support native search.
|
// search when the provider does not support native search.
|
||||||
PreferNative bool `json:"prefer_native" env:"PICOCLAW_TOOLS_WEB_PREFER_NATIVE"`
|
PreferNative bool ` json:"prefer_native" env:"PICOCLAW_TOOLS_WEB_PREFER_NATIVE"`
|
||||||
// Proxy is an optional proxy URL for web tools (http/https/socks5/socks5h).
|
// Proxy is an optional proxy URL for web tools (http/https/socks5/socks5h).
|
||||||
// For authenticated proxies, prefer HTTP_PROXY/HTTPS_PROXY env vars instead of embedding credentials in config.
|
// For authenticated proxies, prefer HTTP_PROXY/HTTPS_PROXY env vars instead of embedding credentials in config.
|
||||||
Proxy string `json:"proxy,omitempty" env:"PICOCLAW_TOOLS_WEB_PROXY"`
|
Proxy string ` json:"proxy,omitempty" env:"PICOCLAW_TOOLS_WEB_PROXY"`
|
||||||
FetchLimitBytes int64 `json:"fetch_limit_bytes,omitempty" env:"PICOCLAW_TOOLS_WEB_FETCH_LIMIT_BYTES"`
|
FetchLimitBytes int64 ` json:"fetch_limit_bytes,omitempty" env:"PICOCLAW_TOOLS_WEB_FETCH_LIMIT_BYTES"`
|
||||||
Format string `json:"format,omitempty" env:"PICOCLAW_TOOLS_WEB_FORMAT"`
|
Format string ` json:"format,omitempty" env:"PICOCLAW_TOOLS_WEB_FORMAT"`
|
||||||
PrivateHostWhitelist FlexibleStringSlice `json:"private_host_whitelist,omitempty" env:"PICOCLAW_TOOLS_WEB_PRIVATE_HOST_WHITELIST"`
|
PrivateHostWhitelist FlexibleStringSlice ` json:"private_host_whitelist,omitempty" env:"PICOCLAW_TOOLS_WEB_PRIVATE_HOST_WHITELIST"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CronToolsConfig struct {
|
type CronToolsConfig struct {
|
||||||
|
|
@ -959,10 +961,10 @@ type MCPServerConfig struct {
|
||||||
|
|
||||||
// MCPConfig defines configuration for all MCP servers
|
// MCPConfig defines configuration for all MCP servers
|
||||||
type MCPConfig struct {
|
type MCPConfig struct {
|
||||||
ToolConfig ` envPrefix:"PICOCLAW_TOOLS_MCP_"`
|
ToolConfig ` envPrefix:"PICOCLAW_TOOLS_MCP_"`
|
||||||
Discovery ToolDiscoveryConfig ` json:"discovery"`
|
Discovery ToolDiscoveryConfig ` json:"discovery"`
|
||||||
// Servers is a map of server name to server configuration
|
// Servers is a map of server name to server configuration
|
||||||
Servers map[string]MCPServerConfig `json:"servers,omitempty"`
|
Servers map[string]MCPServerConfig ` json:"servers,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadConfig(path string) (*Config, error) {
|
func LoadConfig(path string) (*Config, error) {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue