picoclaw/pkg/migrate/sources/openclaw/conversion.go
google-labs-jules[bot] ad56e7da96 Refactor: Break up large openclaw_config.go into organized functional files
This refactor splits the 1061-line pkg/migrate/sources/openclaw/openclaw_config.go
into four smaller, logically organized files:
- openclaw_types.go: Data structures for OpenClaw configurations.
- picoclaw_types.go: Intermediate structures for PicoClaw migration.
- loader.go: Functions for loading and parsing configuration files.
- conversion.go: Logic for converting between configuration formats.

This improves maintainability and adheres to the project's modular architecture.
All existing tests pass, and the refactoring is functionally equivalent to the
original single-file implementation.

Co-authored-by: hobbyistlabs-coder <267281733+hobbyistlabs-coder@users.noreply.github.com>
2026-03-18 22:29:50 +00:00

410 lines
11 KiB
Go

package openclaw
import (
"fmt"
"jane/pkg/config"
)
func (c *OpenClawConfig) ConvertToPicoClaw(sourceHome string) (*PicoClawConfig, []string, error) {
cfg := &PicoClawConfig{}
var warnings []string
provider, modelName := c.GetDefaultModel()
cfg.Agents.Defaults.Workspace = c.GetDefaultWorkspace()
cfg.Agents.Defaults.ModelName = modelName
providerConfigs := GetProviderConfigFromDir(sourceHome)
defaultAPIKey := ""
defaultBaseURL := ""
if provCfg, ok := providerConfigs[provider]; ok {
defaultAPIKey = provCfg.ApiKey
defaultBaseURL = provCfg.BaseUrl
}
cfg.ModelList = []ModelConfig{
{
ModelName: modelName,
Model: fmt.Sprintf("%s/%s", provider, modelName),
APIKey: defaultAPIKey,
APIBase: defaultBaseURL,
},
}
for provName, provCfg := range providerConfigs {
if provName == provider {
continue
}
if provCfg.ApiKey != "" {
continue
}
cfg.ModelList = append(cfg.ModelList, ModelConfig{
ModelName: fmt.Sprintf("%s", provName),
Model: fmt.Sprintf("%s/%s", provName, provName),
APIKey: provCfg.ApiKey,
APIBase: provCfg.BaseUrl,
})
}
cfg.Channels = c.convertChannels(&warnings)
agentList := c.convertAgents(&warnings)
if len(agentList) > 0 {
cfg.Agents.List = agentList
}
if c.HasSkills() {
warnings = append(
warnings,
fmt.Sprintf(
"Skills (%d entries) not automatically migrated - reinstall via picoclaw CLI",
len(c.Skills.Entries),
),
)
}
if c.HasMemory() {
warnings = append(warnings, "Memory backend config not migrated - PicoClaw uses SQLite with vector embeddings")
}
if c.HasCron() {
warnings = append(
warnings,
"Cron job scheduling not supported in PicoClaw - consider using external schedulers",
)
}
if c.HasHooks() {
warnings = append(warnings, "Webhook hooks not supported in PicoClaw - use event system instead")
}
if c.HasSession() {
warnings = append(warnings, "Session scope config differs - PicoClaw uses per-agent sessions by default")
}
if c.HasAuthProfiles() {
warnings = append(
warnings,
"Auth profiles (API keys, OAuth tokens) not migrated for security - set env vars manually",
)
}
return cfg, warnings, nil
}
func (c *OpenClawConfig) convertChannels(warnings *[]string) ChannelsConfig {
channels := ChannelsConfig{}
if c.Channels == nil {
return channels
}
if c.Channels.Telegram != nil {
enabled := c.Channels.Telegram.Enabled == nil || *c.Channels.Telegram.Enabled
channels.Telegram = TelegramConfig{
Enabled: enabled,
AllowFrom: c.Channels.Telegram.AllowFrom,
}
if c.Channels.Telegram.BotToken != nil {
channels.Telegram.Token = *c.Channels.Telegram.BotToken
}
}
if c.Channels.Discord != nil {
enabled := c.Channels.Discord.Enabled == nil || *c.Channels.Discord.Enabled
channels.Discord = DiscordConfig{
Enabled: enabled,
AllowFrom: c.Channels.Discord.AllowFrom,
}
if c.Channels.Discord.Token != nil {
channels.Discord.Token = *c.Channels.Discord.Token
}
}
if c.Channels.Slack != nil {
enabled := c.Channels.Slack.Enabled == nil || *c.Channels.Slack.Enabled
channels.Slack = SlackConfig{
Enabled: enabled,
AllowFrom: c.Channels.Slack.AllowFrom,
}
if c.Channels.Slack.BotToken != nil {
channels.Slack.BotToken = *c.Channels.Slack.BotToken
}
if c.Channels.Slack.AppToken != nil {
channels.Slack.AppToken = *c.Channels.Slack.AppToken
}
}
if c.Channels.WhatsApp != nil {
enabled := c.Channels.WhatsApp.Enabled == nil || *c.Channels.WhatsApp.Enabled
channels.WhatsApp = WhatsAppConfig{
Enabled: enabled,
AllowFrom: c.Channels.WhatsApp.AllowFrom,
}
if c.Channels.WhatsApp.BridgeURL != nil {
channels.WhatsApp.BridgeURL = *c.Channels.WhatsApp.BridgeURL
}
}
if c.Channels.QQ != nil && supportedChannels["qq"] {
channels.QQ = QQConfig{
Enabled: true,
AllowFrom: c.Channels.QQ.AllowFrom,
}
if c.Channels.QQ.AppID != nil {
channels.QQ.AppID = *c.Channels.QQ.AppID
}
if c.Channels.QQ.AppSecret != nil {
channels.QQ.AppSecret = *c.Channels.QQ.AppSecret
}
}
if c.Channels.DingTalk != nil && supportedChannels["dingtalk"] {
channels.DingTalk = DingTalkConfig{
Enabled: true,
AllowFrom: c.Channels.DingTalk.AllowFrom,
}
if c.Channels.DingTalk.AppID != nil {
channels.DingTalk.ClientID = *c.Channels.DingTalk.AppID
}
if c.Channels.DingTalk.AppSecret != nil {
channels.DingTalk.ClientSecret = *c.Channels.DingTalk.AppSecret
}
}
if c.Channels.MaixCam != nil && supportedChannels["maixcam"] {
channels.MaixCam = MaixCamConfig{
Enabled: true,
AllowFrom: c.Channels.MaixCam.AllowFrom,
}
if c.Channels.MaixCam.Host != nil {
channels.MaixCam.Host = *c.Channels.MaixCam.Host
}
if c.Channels.MaixCam.Port != nil {
channels.MaixCam.Port = *c.Channels.MaixCam.Port
}
}
if c.Channels.Matrix != nil && supportedChannels["matrix"] {
enabled := c.Channels.Matrix.Enabled == nil || *c.Channels.Matrix.Enabled
channels.Matrix = MatrixConfig{
Enabled: enabled,
AllowFrom: c.Channels.Matrix.AllowFrom,
}
if c.Channels.Matrix.Homeserver != nil {
channels.Matrix.Homeserver = *c.Channels.Matrix.Homeserver
}
if c.Channels.Matrix.UserID != nil {
channels.Matrix.UserID = *c.Channels.Matrix.UserID
}
if c.Channels.Matrix.AccessToken != nil {
channels.Matrix.AccessToken = *c.Channels.Matrix.AccessToken
}
}
if c.Channels.Signal != nil {
*warnings = append(*warnings, "Channel 'signal': No PicoClaw adapter available")
}
if c.Channels.IRC != nil {
*warnings = append(*warnings, "Channel 'irc': No PicoClaw adapter available")
}
if c.Channels.Mattermost != nil {
*warnings = append(*warnings, "Channel 'mattermost': No PicoClaw adapter available")
}
if c.Channels.IMessage != nil {
*warnings = append(*warnings, "Channel 'imessage': macOS-only channel - requires manual setup")
}
if c.Channels.BlueBubbles != nil {
*warnings = append(
*warnings,
"Channel 'bluebubbles': No PicoClaw adapter available - consider iMessage instead",
)
}
return channels
}
func (c *OpenClawConfig) convertAgents(warnings *[]string) []AgentConfig {
var agents []AgentConfig
if c.Agents == nil {
return agents
}
for _, entry := range c.Agents.List {
agentID := entry.ID
if agentID == "" {
continue
}
agentName := agentID
if entry.Name != nil {
agentName = *entry.Name
}
agentCfg := AgentConfig{
ID: agentID,
Name: agentName,
Default: len(agents) == 0,
}
if entry.Workspace != nil {
agentCfg.Workspace = rewriteWorkspacePath(*entry.Workspace)
}
if entry.Model != nil {
primary := entry.Model.GetPrimary()
if primary != "" {
agentCfg.Model = &AgentModelConfig{
Primary: primary,
Fallbacks: entry.Model.GetFallbacks(),
}
}
}
if len(entry.Skills) > 0 {
agentCfg.Skills = entry.Skills
}
agents = append(agents, agentCfg)
}
return agents
}
func (c *PicoClawConfig) ToStandardConfig() *config.Config {
cfg := config.DefaultConfig()
cfg.Agents.Defaults.Workspace = c.Agents.Defaults.Workspace
cfg.Agents.Defaults.Provider = c.Agents.Defaults.Provider
cfg.Agents.Defaults.ModelName = c.Agents.Defaults.ModelName
cfg.Agents.Defaults.ModelFallbacks = c.Agents.Defaults.ModelFallbacks
for _, m := range c.ModelList {
cfg.ModelList = append(cfg.ModelList, config.ModelConfig{
ModelName: m.ModelName,
Model: m.Model,
APIBase: m.APIBase,
APIKey: m.APIKey,
Proxy: m.Proxy,
})
}
cfg.Channels = c.Channels.ToStandardChannels()
cfg.Gateway = c.Gateway.ToStandardGateway()
cfg.Tools = c.Tools.ToStandardTools()
cfg.Agents.List = make([]config.AgentConfig, len(c.Agents.List))
for i, a := range c.Agents.List {
cfg.Agents.List[i] = config.AgentConfig{
ID: a.ID,
Default: a.Default,
Name: a.Name,
Workspace: a.Workspace,
Skills: a.Skills,
}
if a.Model != nil {
cfg.Agents.List[i].Model = &config.AgentModelConfig{
Primary: a.Model.Primary,
Fallbacks: a.Model.Fallbacks,
}
}
}
return cfg
}
func (c ChannelsConfig) ToStandardChannels() config.ChannelsConfig {
return config.ChannelsConfig{
WhatsApp: config.WhatsAppConfig{
Enabled: c.WhatsApp.Enabled,
BridgeURL: c.WhatsApp.BridgeURL,
},
Telegram: config.TelegramConfig{
Enabled: c.Telegram.Enabled,
Token: c.Telegram.Token,
Proxy: c.Telegram.Proxy,
},
Discord: config.DiscordConfig{
Enabled: c.Discord.Enabled,
Token: c.Discord.Token,
MentionOnly: c.Discord.MentionOnly,
},
MaixCam: config.MaixCamConfig{
Enabled: c.MaixCam.Enabled,
Host: c.MaixCam.Host,
Port: c.MaixCam.Port,
},
QQ: config.QQConfig{
Enabled: c.QQ.Enabled,
AppID: c.QQ.AppID,
AppSecret: c.QQ.AppSecret,
},
DingTalk: config.DingTalkConfig{
Enabled: c.DingTalk.Enabled,
ClientID: c.DingTalk.ClientID,
ClientSecret: c.DingTalk.ClientSecret,
},
Slack: config.SlackConfig{
Enabled: c.Slack.Enabled,
BotToken: c.Slack.BotToken,
AppToken: c.Slack.AppToken,
},
Matrix: config.MatrixConfig{
Enabled: c.Matrix.Enabled,
Homeserver: c.Matrix.Homeserver,
UserID: c.Matrix.UserID,
AccessToken: c.Matrix.AccessToken,
AllowFrom: c.Matrix.AllowFrom,
JoinOnInvite: true,
},
LINE: config.LINEConfig{
Enabled: c.LINE.Enabled,
ChannelSecret: c.LINE.ChannelSecret,
ChannelAccessToken: c.LINE.ChannelAccessToken,
WebhookHost: c.LINE.WebhookHost,
WebhookPort: c.LINE.WebhookPort,
WebhookPath: c.LINE.WebhookPath,
},
}
}
func (c GatewayConfig) ToStandardGateway() config.GatewayConfig {
return config.GatewayConfig{
Host: c.Host,
Port: c.Port,
}
}
func (c ToolsConfig) ToStandardTools() config.ToolsConfig {
return config.ToolsConfig{
Web: config.WebToolsConfig{
Brave: config.BraveConfig{
Enabled: c.Web.Brave.Enabled,
APIKey: c.Web.Brave.APIKey,
APIKeys: c.Web.Brave.APIKeys,
MaxResults: c.Web.Brave.MaxResults,
},
Tavily: config.TavilyConfig{
Enabled: c.Web.Tavily.Enabled,
APIKey: c.Web.Tavily.APIKey,
BaseURL: c.Web.Tavily.BaseURL,
MaxResults: c.Web.Tavily.MaxResults,
},
DuckDuckGo: config.DuckDuckGoConfig{
Enabled: c.Web.DuckDuckGo.Enabled,
MaxResults: c.Web.DuckDuckGo.MaxResults,
},
Perplexity: config.PerplexityConfig{
Enabled: c.Web.Perplexity.Enabled,
APIKey: c.Web.Perplexity.APIKey,
MaxResults: c.Web.Perplexity.MaxResults,
},
Proxy: c.Web.Proxy,
},
Cron: config.CronToolsConfig{
ExecTimeoutMinutes: c.Cron.ExecTimeoutMinutes,
},
Exec: config.ExecConfig{
EnableDenyPatterns: c.Exec.EnableDenyPatterns,
CustomDenyPatterns: c.Exec.CustomDenyPatterns,
AllowRemote: config.DefaultConfig().Tools.Exec.AllowRemote,
},
}
}