fix(irc): normalize channel names from flexible config
This commit is contained in:
parent
ba83a5d256
commit
6bf87d19f4
5 changed files with 1535 additions and 1563 deletions
|
|
@ -29,7 +29,7 @@ func (f *FlexibleStringSlice) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accept a single string so config patch APIs can submit comma-separated values.
|
// Accept a comma-separated string for channels and allow_from style fields.
|
||||||
var s string
|
var s string
|
||||||
if err := json.Unmarshal(data, &s); err == nil {
|
if err := json.Unmarshal(data, &s); err == nil {
|
||||||
*f = parseFlexibleStringSlice(s)
|
*f = parseFlexibleStringSlice(s)
|
||||||
|
|
@ -64,11 +64,13 @@ func (f *FlexibleStringSlice) UnmarshalText(text []byte) error {
|
||||||
*f = nil
|
*f = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
*f = parseFlexibleStringSlice(string(text))
|
*f = parseFlexibleStringSlice(string(text))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFlexibleStringSlice(s string) FlexibleStringSlice {
|
func parseFlexibleStringSlice(s string) FlexibleStringSlice {
|
||||||
|
// Replace Chinese comma with English comma, then split
|
||||||
s = strings.ReplaceAll(s, ",", ",")
|
s = strings.ReplaceAll(s, ",", ",")
|
||||||
parts := strings.Split(s, ",")
|
parts := strings.Split(s, ",")
|
||||||
|
|
||||||
|
|
@ -230,8 +232,8 @@ type AgentDefaults struct {
|
||||||
RestrictToWorkspace bool `json:"restrict_to_workspace" env:"PICOCLAW_AGENTS_DEFAULTS_RESTRICT_TO_WORKSPACE"`
|
RestrictToWorkspace bool `json:"restrict_to_workspace" env:"PICOCLAW_AGENTS_DEFAULTS_RESTRICT_TO_WORKSPACE"`
|
||||||
AllowReadOutsideWorkspace bool `json:"allow_read_outside_workspace" env:"PICOCLAW_AGENTS_DEFAULTS_ALLOW_READ_OUTSIDE_WORKSPACE"`
|
AllowReadOutsideWorkspace bool `json:"allow_read_outside_workspace" env:"PICOCLAW_AGENTS_DEFAULTS_ALLOW_READ_OUTSIDE_WORKSPACE"`
|
||||||
Provider string `json:"provider" env:"PICOCLAW_AGENTS_DEFAULTS_PROVIDER"`
|
Provider string `json:"provider" env:"PICOCLAW_AGENTS_DEFAULTS_PROVIDER"`
|
||||||
ModelName string `json:"model_name,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL_NAME"`
|
ModelName string `json:"model_name" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL_NAME"`
|
||||||
Model string `json:"model" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL"` // Deprecated: use model_name instead
|
Model string `json:"model,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL"` // Deprecated: use model_name instead
|
||||||
ModelFallbacks []string `json:"model_fallbacks,omitempty"`
|
ModelFallbacks []string `json:"model_fallbacks,omitempty"`
|
||||||
ImageModel string `json:"image_model,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_IMAGE_MODEL"`
|
ImageModel string `json:"image_model,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_IMAGE_MODEL"`
|
||||||
ImageModelFallbacks []string `json:"image_model_fallbacks,omitempty"`
|
ImageModelFallbacks []string `json:"image_model_fallbacks,omitempty"`
|
||||||
|
|
@ -536,6 +538,7 @@ type ProvidersConfig struct {
|
||||||
Avian ProviderConfig `json:"avian"`
|
Avian ProviderConfig `json:"avian"`
|
||||||
Minimax ProviderConfig `json:"minimax"`
|
Minimax ProviderConfig `json:"minimax"`
|
||||||
LongCat ProviderConfig `json:"longcat"`
|
LongCat ProviderConfig `json:"longcat"`
|
||||||
|
ModelScope ProviderConfig `json:"modelscope"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsEmpty checks if all provider configs are empty (no API keys or API bases set)
|
// IsEmpty checks if all provider configs are empty (no API keys or API bases set)
|
||||||
|
|
@ -563,7 +566,8 @@ func (p ProvidersConfig) IsEmpty() bool {
|
||||||
p.Mistral.APIKey == "" && p.Mistral.APIBase == "" &&
|
p.Mistral.APIKey == "" && p.Mistral.APIBase == "" &&
|
||||||
p.Avian.APIKey == "" && p.Avian.APIBase == "" &&
|
p.Avian.APIKey == "" && p.Avian.APIBase == "" &&
|
||||||
p.Minimax.APIKey == "" && p.Minimax.APIBase == "" &&
|
p.Minimax.APIKey == "" && p.Minimax.APIBase == "" &&
|
||||||
p.LongCat.APIKey == "" && p.LongCat.APIBase == ""
|
p.LongCat.APIKey == "" && p.LongCat.APIBase == "" &&
|
||||||
|
p.ModelScope.APIKey == "" && p.ModelScope.APIBase == ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements custom JSON marshaling for ProvidersConfig
|
// MarshalJSON implements custom JSON marshaling for ProvidersConfig
|
||||||
|
|
@ -719,6 +723,7 @@ type ExecConfig struct {
|
||||||
type SkillsToolsConfig struct {
|
type SkillsToolsConfig struct {
|
||||||
ToolConfig ` envPrefix:"PICOCLAW_TOOLS_SKILLS_"`
|
ToolConfig ` envPrefix:"PICOCLAW_TOOLS_SKILLS_"`
|
||||||
Registries SkillsRegistriesConfig ` json:"registries"`
|
Registries SkillsRegistriesConfig ` json:"registries"`
|
||||||
|
Github SkillsGithubConfig ` json:"github"`
|
||||||
MaxConcurrentSearches int ` json:"max_concurrent_searches" env:"PICOCLAW_TOOLS_SKILLS_MAX_CONCURRENT_SEARCHES"`
|
MaxConcurrentSearches int ` json:"max_concurrent_searches" env:"PICOCLAW_TOOLS_SKILLS_MAX_CONCURRENT_SEARCHES"`
|
||||||
SearchCache SearchCacheConfig ` json:"search_cache"`
|
SearchCache SearchCacheConfig ` json:"search_cache"`
|
||||||
}
|
}
|
||||||
|
|
@ -768,6 +773,11 @@ type SkillsRegistriesConfig struct {
|
||||||
ClawHub ClawHubRegistryConfig `json:"clawhub"`
|
ClawHub ClawHubRegistryConfig `json:"clawhub"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SkillsGithubConfig struct {
|
||||||
|
Token string `json:"token,omitempty" env:"PICOCLAW_TOOLS_SKILLS_GITHUB_AUTH_TOKEN"`
|
||||||
|
Proxy string `json:"proxy,omitempty" env:"PICOCLAW_TOOLS_SKILLS_GITHUB_PROXY"`
|
||||||
|
}
|
||||||
|
|
||||||
type ClawHubRegistryConfig struct {
|
type ClawHubRegistryConfig struct {
|
||||||
Enabled bool `json:"enabled" env:"PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_ENABLED"`
|
Enabled bool `json:"enabled" env:"PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_ENABLED"`
|
||||||
BaseURL string `json:"base_url" env:"PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_BASE_URL"`
|
BaseURL string `json:"base_url" env:"PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_BASE_URL"`
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/config"
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
|
|
@ -87,40 +86,3 @@ func TestHandleUpdateConfig_DoesNotInheritDefaultModelFields(t *testing.T) {
|
||||||
t.Fatalf("model_list[0].api_base = %q, want empty string", got)
|
t.Fatalf("model_list[0].api_base = %q, want empty string", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandlePatchConfig_AcceptsFlexibleStringSliceString(t *testing.T) {
|
|
||||||
configPath, cleanup := setupOAuthTestEnv(t)
|
|
||||||
defer cleanup()
|
|
||||||
|
|
||||||
h := NewHandler(configPath)
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
h.RegisterRoutes(mux)
|
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodPatch, "/api/config", bytes.NewBufferString(`{
|
|
||||||
"channels": {
|
|
||||||
"irc": {
|
|
||||||
"enabled": true,
|
|
||||||
"server": "irc.example.com:6667",
|
|
||||||
"nick": "testbot",
|
|
||||||
"channels": "general, #ops,dev"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
mux.ServeHTTP(rec, req)
|
|
||||||
if rec.Code != http.StatusOK {
|
|
||||||
t.Fatalf("status = %d, want %d, body=%s", rec.Code, http.StatusOK, rec.Body.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err := config.LoadConfig(configPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("LoadConfig() error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
want := config.FlexibleStringSlice{"general", "#ops", "dev"}
|
|
||||||
if !reflect.DeepEqual(cfg.Channels.IRC.Channels, want) {
|
|
||||||
t.Fatalf("channels.irc.channels = %#v, want %#v", cfg.Channels.IRC.Channels, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue