From 1815dd54150e0387c09c7e67ba56d3c1166fb943 Mon Sep 17 00:00:00 2001 From: yumosx Date: Mon, 16 Feb 2026 00:36:58 +0800 Subject: [PATCH] refactor: replace interface{} with any --- cmd/picoclaw/main.go | 14 +-- pkg/agent/context.go | 12 +-- pkg/agent/loop.go | 42 ++++----- pkg/agent/loop_test.go | 26 +++--- pkg/auth/oauth.go | 4 +- pkg/auth/oauth_test.go | 8 +- pkg/channels/base.go | 4 +- pkg/channels/dingtalk.go | 6 +- pkg/channels/feishu_64.go | 6 +- pkg/channels/line.go | 36 ++++---- pkg/channels/maixcam.go | 26 +++--- pkg/channels/manager.go | 40 ++++----- pkg/channels/onebot.go | 62 ++++++------- pkg/channels/qq.go | 10 +-- pkg/channels/slack.go | 18 ++-- pkg/channels/telegram.go | 22 ++--- pkg/channels/whatsapp.go | 8 +- pkg/config/config.go | 2 +- pkg/cron/service.go | 4 +- pkg/devices/service.go | 8 +- pkg/devices/sources/usb_linux.go | 2 +- pkg/heartbeat/service.go | 2 +- pkg/logger/logger.go | 36 ++++---- pkg/logger/logger_test.go | 10 +-- pkg/migrate/config.go | 36 ++++---- pkg/migrate/migrate_test.go | 102 +++++++++++----------- pkg/providers/claude_cli_provider.go | 4 +- pkg/providers/claude_cli_provider_test.go | 6 +- pkg/providers/claude_provider.go | 10 +-- pkg/providers/claude_provider_test.go | 28 +++--- pkg/providers/codex_provider.go | 8 +- pkg/providers/codex_provider_test.go | 32 +++---- pkg/providers/github_copilot_provider.go | 2 +- pkg/providers/http_provider.go | 6 +- pkg/providers/types.go | 18 ++-- pkg/tools/base.go | 10 +-- pkg/tools/cron.go | 32 +++---- pkg/tools/edit.go | 26 +++--- pkg/tools/edit_test.go | 22 ++--- pkg/tools/filesystem.go | 32 +++---- pkg/tools/filesystem_test.go | 20 ++--- pkg/tools/i2c.go | 28 +++--- pkg/tools/i2c_linux.go | 12 +-- pkg/tools/i2c_other.go | 6 +- pkg/tools/message.go | 14 +-- pkg/tools/message_test.go | 20 ++--- pkg/tools/registry.go | 24 ++--- pkg/tools/result_test.go | 2 +- pkg/tools/shell.go | 12 +-- pkg/tools/shell_test.go | 18 ++-- pkg/tools/spawn.go | 12 +-- pkg/tools/spi.go | 28 +++--- pkg/tools/spi_linux.go | 10 +-- pkg/tools/spi_other.go | 4 +- pkg/tools/subagent.go | 12 +-- pkg/tools/subagent_tool_test.go | 20 ++--- pkg/tools/types.go | 18 ++-- pkg/tools/web.go | 28 +++--- pkg/tools/web_test.go | 20 ++--- pkg/utils/media.go | 14 +-- pkg/voice/transcriber.go | 40 ++++----- 61 files changed, 572 insertions(+), 572 deletions(-) diff --git a/cmd/picoclaw/main.go b/cmd/picoclaw/main.go index 2129662d7..e4f168cda 100644 --- a/cmd/picoclaw/main.go +++ b/cmd/picoclaw/main.go @@ -409,10 +409,10 @@ func agentCmd() { // Print agent startup info (only for interactive mode) startupInfo := agentLoop.GetStartupInfo() logger.InfoCF("agent", "Agent initialized", - map[string]interface{}{ - "tools_count": startupInfo["tools"].(map[string]interface{})["count"], - "skills_total": startupInfo["skills"].(map[string]interface{})["total"], - "skills_available": startupInfo["skills"].(map[string]interface{})["available"], + map[string]any{ + "tools_count": startupInfo["tools"].(map[string]any)["count"], + "skills_total": startupInfo["skills"].(map[string]any)["total"], + "skills_available": startupInfo["skills"].(map[string]any)["available"], }) if message != "" { @@ -544,8 +544,8 @@ func gatewayCmd() { // Print agent startup info fmt.Println("\n📦 Agent Status:") startupInfo := agentLoop.GetStartupInfo() - toolsInfo := startupInfo["tools"].(map[string]interface{}) - skillsInfo := startupInfo["skills"].(map[string]interface{}) + toolsInfo := startupInfo["tools"].(map[string]any) + skillsInfo := startupInfo["skills"].(map[string]any) fmt.Printf(" • Tools: %d loaded\n", toolsInfo["count"]) fmt.Printf(" • Skills: %d/%d available\n", skillsInfo["available"], @@ -553,7 +553,7 @@ func gatewayCmd() { // Log to file as well logger.InfoCF("agent", "Agent initialized", - map[string]interface{}{ + map[string]any{ "tools_count": toolsInfo["count"], "skills_total": skillsInfo["total"], "skills_available": skillsInfo["available"], diff --git a/pkg/agent/context.go b/pkg/agent/context.go index cf5ce2913..1d7a84a92 100644 --- a/pkg/agent/context.go +++ b/pkg/agent/context.go @@ -169,7 +169,7 @@ func (cb *ContextBuilder) BuildMessages(history []providers.Message, summary str // Log system prompt summary for debugging (debug mode only) logger.DebugCF("agent", "System prompt built", - map[string]interface{}{ + map[string]any{ "total_chars": len(systemPrompt), "total_lines": strings.Count(systemPrompt, "\n") + 1, "section_count": strings.Count(systemPrompt, "\n\n---\n\n") + 1, @@ -181,7 +181,7 @@ func (cb *ContextBuilder) BuildMessages(history []providers.Message, summary str preview = preview[:500] + "... (truncated)" } logger.DebugCF("agent", "System prompt preview", - map[string]interface{}{ + map[string]any{ "preview": preview, }) @@ -194,7 +194,7 @@ func (cb *ContextBuilder) BuildMessages(history []providers.Message, summary str //Diegox-17 for len(history) > 0 && (history[0].Role == "tool") { logger.DebugCF("agent", "Removing orphaned tool message from history to prevent LLM error", - map[string]interface{}{"role": history[0].Role}) + map[string]any{"role": history[0].Role}) history = history[1:] } //Diegox-17 @@ -224,7 +224,7 @@ func (cb *ContextBuilder) AddToolResult(messages []providers.Message, toolCallID return messages } -func (cb *ContextBuilder) AddAssistantMessage(messages []providers.Message, content string, toolCalls []map[string]interface{}) []providers.Message { +func (cb *ContextBuilder) AddAssistantMessage(messages []providers.Message, content string, toolCalls []map[string]any) []providers.Message { msg := providers.Message{ Role: "assistant", Content: content, @@ -254,13 +254,13 @@ func (cb *ContextBuilder) loadSkills() string { } // GetSkillsInfo returns information about loaded skills. -func (cb *ContextBuilder) GetSkillsInfo() map[string]interface{} { +func (cb *ContextBuilder) GetSkillsInfo() map[string]any { allSkills := cb.skillsLoader.ListSkills() skillNames := make([]string, 0, len(allSkills)) for _, s := range allSkills { skillNames = append(skillNames, s.Name) } - return map[string]interface{}{ + return map[string]any{ "total": len(allSkills), "available": len(allSkills), "names": skillNames, diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index f3dd94090..9a3ee4a53 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -251,7 +251,7 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage) logContent = utils.Truncate(msg.Content, 80) } logger.InfoCF("agent", fmt.Sprintf("Processing message from %s:%s: %s", msg.Channel, msg.SenderID, logContent), - map[string]interface{}{ + map[string]any{ "channel": msg.Channel, "chat_id": msg.ChatID, "sender_id": msg.SenderID, @@ -282,7 +282,7 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe } logger.InfoCF("agent", "Processing system message", - map[string]interface{}{ + map[string]any{ "sender_id": msg.SenderID, "chat_id": msg.ChatID, }) @@ -306,7 +306,7 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe // Skip internal channels - only log, don't send to user if constants.IsInternalChannel(originChannel) { logger.InfoCF("agent", "Subagent completed (internal channel)", - map[string]interface{}{ + map[string]any{ "sender_id": msg.SenderID, "content_len": len(content), "channel": originChannel, @@ -317,7 +317,7 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe // Agent acts as dispatcher only - subagent handles user interaction via message tool // Don't forward result here, subagent should use message tool to communicate with user logger.InfoCF("agent", "Subagent completed", - map[string]interface{}{ + map[string]any{ "sender_id": msg.SenderID, "channel": originChannel, "content_len": len(content), @@ -336,7 +336,7 @@ func (al *AgentLoop) runAgentLoop(ctx context.Context, opts processOptions) (str if !constants.IsInternalChannel(opts.Channel) { channelKey := fmt.Sprintf("%s:%s", opts.Channel, opts.ChatID) if err := al.RecordLastChannel(channelKey); err != nil { - logger.WarnCF("agent", "Failed to record last channel: %v", map[string]interface{}{"error": err.Error()}) + logger.WarnCF("agent", "Failed to record last channel: %v", map[string]any{"error": err.Error()}) } } } @@ -398,7 +398,7 @@ func (al *AgentLoop) runAgentLoop(ctx context.Context, opts processOptions) (str // 9. Log response responsePreview := utils.Truncate(finalContent, 120) logger.InfoCF("agent", fmt.Sprintf("Response: %s", responsePreview), - map[string]interface{}{ + map[string]any{ "session_key": opts.SessionKey, "iterations": iteration, "final_length": len(finalContent), @@ -417,7 +417,7 @@ func (al *AgentLoop) runLLMIteration(ctx context.Context, messages []providers.M iteration++ logger.DebugCF("agent", "LLM iteration", - map[string]interface{}{ + map[string]any{ "iteration": iteration, "max": al.maxIterations, }) @@ -427,7 +427,7 @@ func (al *AgentLoop) runLLMIteration(ctx context.Context, messages []providers.M // Log LLM request details logger.DebugCF("agent", "LLM request", - map[string]interface{}{ + map[string]any{ "iteration": iteration, "model": al.model, "messages_count": len(messages), @@ -439,21 +439,21 @@ func (al *AgentLoop) runLLMIteration(ctx context.Context, messages []providers.M // Log full messages (detailed) logger.DebugCF("agent", "Full LLM request", - map[string]interface{}{ + map[string]any{ "iteration": iteration, "messages_json": formatMessagesForLog(messages), "tools_json": formatToolsForLog(providerToolDefs), }) // Call LLM - response, err := al.provider.Chat(ctx, messages, providerToolDefs, al.model, map[string]interface{}{ + response, err := al.provider.Chat(ctx, messages, providerToolDefs, al.model, map[string]any{ "max_tokens": 8192, "temperature": 0.7, }) if err != nil { logger.ErrorCF("agent", "LLM call failed", - map[string]interface{}{ + map[string]any{ "iteration": iteration, "error": err.Error(), }) @@ -464,7 +464,7 @@ func (al *AgentLoop) runLLMIteration(ctx context.Context, messages []providers.M if len(response.ToolCalls) == 0 { finalContent = response.Content logger.InfoCF("agent", "LLM response without tool calls (direct answer)", - map[string]interface{}{ + map[string]any{ "iteration": iteration, "content_chars": len(finalContent), }) @@ -477,7 +477,7 @@ func (al *AgentLoop) runLLMIteration(ctx context.Context, messages []providers.M toolNames = append(toolNames, tc.Name) } logger.InfoCF("agent", "LLM requested tool calls", - map[string]interface{}{ + map[string]any{ "tools": toolNames, "count": len(response.ToolCalls), "iteration": iteration, @@ -510,7 +510,7 @@ func (al *AgentLoop) runLLMIteration(ctx context.Context, messages []providers.M argsJSON, _ := json.Marshal(tc.Arguments) argsPreview := utils.Truncate(string(argsJSON), 200) logger.InfoCF("agent", fmt.Sprintf("Tool call: %s(%s)", tc.Name, argsPreview), - map[string]interface{}{ + map[string]any{ "tool": tc.Name, "iteration": iteration, }) @@ -524,7 +524,7 @@ func (al *AgentLoop) runLLMIteration(ctx context.Context, messages []providers.M // The agent will handle user notification via processSystemMessage if !result.Silent && result.ForUser != "" { logger.InfoCF("agent", "Async tool completed, agent will handle notification", - map[string]interface{}{ + map[string]any{ "tool": tc.Name, "content_len": len(result.ForUser), }) @@ -541,7 +541,7 @@ func (al *AgentLoop) runLLMIteration(ctx context.Context, messages []providers.M Content: toolResult.ForUser, }) logger.DebugCF("agent", "Sent tool result to user", - map[string]interface{}{ + map[string]any{ "tool": tc.Name, "content_len": len(toolResult.ForUser), }) @@ -605,12 +605,12 @@ func (al *AgentLoop) maybeSummarize(sessionKey string) { } // GetStartupInfo returns information about loaded tools and skills for logging. -func (al *AgentLoop) GetStartupInfo() map[string]interface{} { - info := make(map[string]interface{}) +func (al *AgentLoop) GetStartupInfo() map[string]any { + info := make(map[string]any) // Tools info tools := al.tools.List() - info["tools"] = map[string]interface{}{ + info["tools"] = map[string]any{ "count": len(tools), "names": tools, } @@ -723,7 +723,7 @@ func (al *AgentLoop) summarizeSession(sessionKey string) { // Merge them mergePrompt := fmt.Sprintf("Merge these two conversation summaries into one cohesive summary:\n\n1: %s\n\n2: %s", s1, s2) - resp, err := al.provider.Chat(ctx, []providers.Message{{Role: "user", Content: mergePrompt}}, nil, al.model, map[string]interface{}{ + resp, err := al.provider.Chat(ctx, []providers.Message{{Role: "user", Content: mergePrompt}}, nil, al.model, map[string]any{ "max_tokens": 1024, "temperature": 0.3, }) @@ -758,7 +758,7 @@ func (al *AgentLoop) summarizeBatch(ctx context.Context, batch []providers.Messa prompt += fmt.Sprintf("%s: %s\n", m.Role, m.Content) } - response, err := al.provider.Chat(ctx, []providers.Message{{Role: "user", Content: prompt}}, nil, al.model, map[string]interface{}{ + response, err := al.provider.Chat(ctx, []providers.Message{{Role: "user", Content: prompt}}, nil, al.model, map[string]any{ "max_tokens": 1024, "temperature": 0.3, }) diff --git a/pkg/agent/loop_test.go b/pkg/agent/loop_test.go index c18220258..ff7686211 100644 --- a/pkg/agent/loop_test.go +++ b/pkg/agent/loop_test.go @@ -16,7 +16,7 @@ import ( // mockProvider is a simple mock LLM provider for testing type mockProvider struct{} -func (m *mockProvider) Chat(ctx context.Context, messages []providers.Message, tools []providers.ToolDefinition, model string, opts map[string]interface{}) (*providers.LLMResponse, error) { +func (m *mockProvider) Chat(ctx context.Context, messages []providers.Message, tools []providers.ToolDefinition, model string, opts map[string]any) (*providers.LLMResponse, error) { return &providers.LLMResponse{ Content: "Mock response", ToolCalls: []providers.ToolCall{}, @@ -184,7 +184,7 @@ func TestToolRegistry_ToolRegistration(t *testing.T) { // Verify tool is registered by checking it doesn't panic on GetStartupInfo // (actual tool retrieval is tested in tools package tests) info := al.GetStartupInfo() - toolsInfo := info["tools"].(map[string]interface{}) + toolsInfo := info["tools"].(map[string]any) toolsList := toolsInfo["names"].([]string) // Check that our custom tool name is in the list @@ -259,7 +259,7 @@ func TestToolRegistry_GetDefinitions(t *testing.T) { al.RegisterTool(testTool) info := al.GetStartupInfo() - toolsInfo := info["tools"].(map[string]interface{}) + toolsInfo := info["tools"].(map[string]any) toolsList := toolsInfo["names"].([]string) // Check that our custom tool name is in the list @@ -306,7 +306,7 @@ func TestAgentLoop_GetStartupInfo(t *testing.T) { t.Fatal("Expected 'tools' key in startup info") } - toolsMap, ok := toolsInfo.(map[string]interface{}) + toolsMap, ok := toolsInfo.(map[string]any) if !ok { t.Fatal("Expected 'tools' to be a map") } @@ -362,7 +362,7 @@ type simpleMockProvider struct { response string } -func (m *simpleMockProvider) Chat(ctx context.Context, messages []providers.Message, tools []providers.ToolDefinition, model string, opts map[string]interface{}) (*providers.LLMResponse, error) { +func (m *simpleMockProvider) Chat(ctx context.Context, messages []providers.Message, tools []providers.ToolDefinition, model string, opts map[string]any) (*providers.LLMResponse, error) { return &providers.LLMResponse{ Content: m.response, ToolCalls: []providers.ToolCall{}, @@ -384,14 +384,14 @@ func (m *mockCustomTool) Description() string { return "Mock custom tool for testing" } -func (m *mockCustomTool) Parameters() map[string]interface{} { - return map[string]interface{}{ +func (m *mockCustomTool) Parameters() map[string]any { + return map[string]any{ "type": "object", - "properties": map[string]interface{}{}, + "properties": map[string]any{}, } } -func (m *mockCustomTool) Execute(ctx context.Context, args map[string]interface{}) *tools.ToolResult { +func (m *mockCustomTool) Execute(ctx context.Context, args map[string]any) *tools.ToolResult { return tools.SilentResult("Custom tool executed") } @@ -409,14 +409,14 @@ func (m *mockContextualTool) Description() string { return "Mock contextual tool" } -func (m *mockContextualTool) Parameters() map[string]interface{} { - return map[string]interface{}{ +func (m *mockContextualTool) Parameters() map[string]any { + return map[string]any{ "type": "object", - "properties": map[string]interface{}{}, + "properties": map[string]any{}, } } -func (m *mockContextualTool) Execute(ctx context.Context, args map[string]interface{}) *tools.ToolResult { +func (m *mockContextualTool) Execute(ctx context.Context, args map[string]any) *tools.ToolResult { return tools.SilentResult("Contextual tool executed") } diff --git a/pkg/auth/oauth.go b/pkg/auth/oauth.go index 1a6589641..467d6bc0e 100644 --- a/pkg/auth/oauth.go +++ b/pkg/auth/oauth.go @@ -386,12 +386,12 @@ func extractAccountID(accessToken string) string { return "" } - var claims map[string]interface{} + var claims map[string]any if err := json.Unmarshal(decoded, &claims); err != nil { return "" } - if authClaim, ok := claims["https://api.openai.com/auth"].(map[string]interface{}); ok { + if authClaim, ok := claims["https://api.openai.com/auth"].(map[string]any); ok { if accountID, ok := authClaim["chatgpt_account_id"].(string); ok { return accountID } diff --git a/pkg/auth/oauth_test.go b/pkg/auth/oauth_test.go index 0d2ccc9a5..514c47f37 100644 --- a/pkg/auth/oauth_test.go +++ b/pkg/auth/oauth_test.go @@ -54,7 +54,7 @@ func TestBuildAuthorizeURL(t *testing.T) { } func TestParseTokenResponse(t *testing.T) { - resp := map[string]interface{}{ + resp := map[string]any{ "access_token": "test-access-token", "refresh_token": "test-refresh-token", "expires_in": 3600, @@ -94,7 +94,7 @@ func TestParseTokenResponseNoAccessToken(t *testing.T) { func TestParseTokenResponseAccountIDFromIDToken(t *testing.T) { idToken := makeJWTWithAccountID("acc-from-id") - resp := map[string]interface{}{ + resp := map[string]any{ "access_token": "not-a-jwt", "refresh_token": "test-refresh-token", "expires_in": 3600, @@ -135,7 +135,7 @@ func TestExchangeCodeForTokens(t *testing.T) { return } - resp := map[string]interface{}{ + resp := map[string]any{ "access_token": "mock-access-token", "refresh_token": "mock-refresh-token", "expires_in": 3600, @@ -174,7 +174,7 @@ func TestRefreshAccessToken(t *testing.T) { return } - resp := map[string]interface{}{ + resp := map[string]any{ "access_token": "refreshed-access-token", "refresh_token": "refreshed-refresh-token", "expires_in": 3600, diff --git a/pkg/channels/base.go b/pkg/channels/base.go index 8d2d9a65b..acef005da 100644 --- a/pkg/channels/base.go +++ b/pkg/channels/base.go @@ -18,14 +18,14 @@ type Channel interface { } type BaseChannel struct { - config interface{} + config any bus *bus.MessageBus running bool name string allowList []string } -func NewBaseChannel(name string, config interface{}, bus *bus.MessageBus, allowList []string) *BaseChannel { +func NewBaseChannel(name string, config any, bus *bus.MessageBus, allowList []string) *BaseChannel { return &BaseChannel{ config: config, bus: bus, diff --git a/pkg/channels/dingtalk.go b/pkg/channels/dingtalk.go index 263785c0c..f53e20c40 100644 --- a/pkg/channels/dingtalk.go +++ b/pkg/channels/dingtalk.go @@ -108,7 +108,7 @@ func (c *DingTalkChannel) Send(ctx context.Context, msg bus.OutboundMessage) err return fmt.Errorf("invalid session_webhook type for chat %s", msg.ChatID) } - logger.DebugCF("dingtalk", "Sending message", map[string]interface{}{ + logger.DebugCF("dingtalk", "Sending message", map[string]any{ "chat_id": msg.ChatID, "preview": utils.Truncate(msg.Content, 100), }) @@ -125,7 +125,7 @@ func (c *DingTalkChannel) onChatBotMessageReceived(ctx context.Context, data *ch content := data.Text.Content if content == "" { // Try to extract from Content interface{} if Text is empty - if contentMap, ok := data.Content.(map[string]interface{}); ok { + if contentMap, ok := data.Content.(map[string]any); ok { if textContent, ok := contentMap["content"].(string); ok { content = textContent } @@ -155,7 +155,7 @@ func (c *DingTalkChannel) onChatBotMessageReceived(ctx context.Context, data *ch "session_webhook": data.SessionWebhook, } - logger.DebugCF("dingtalk", "Received message", map[string]interface{}{ + logger.DebugCF("dingtalk", "Received message", map[string]any{ "sender_nick": senderNick, "sender_id": senderID, "preview": utils.Truncate(content, 50), diff --git a/pkg/channels/feishu_64.go b/pkg/channels/feishu_64.go index 39dc40ac1..29d4001cb 100644 --- a/pkg/channels/feishu_64.go +++ b/pkg/channels/feishu_64.go @@ -65,7 +65,7 @@ func (c *FeishuChannel) Start(ctx context.Context) error { go func() { if err := wsClient.Start(runCtx); err != nil { - logger.ErrorCF("feishu", "Feishu websocket stopped with error", map[string]interface{}{ + logger.ErrorCF("feishu", "Feishu websocket stopped with error", map[string]any{ "error": err.Error(), }) } @@ -121,7 +121,7 @@ func (c *FeishuChannel) Send(ctx context.Context, msg bus.OutboundMessage) error return fmt.Errorf("feishu api error: code=%d msg=%s", resp.Code, resp.Msg) } - logger.DebugCF("feishu", "Feishu message sent", map[string]interface{}{ + logger.DebugCF("feishu", "Feishu message sent", map[string]any{ "chat_id": msg.ChatID, }) @@ -165,7 +165,7 @@ func (c *FeishuChannel) handleMessageReceive(_ context.Context, event *larkim.P2 metadata["tenant_key"] = *sender.TenantKey } - logger.InfoCF("feishu", "Feishu message received", map[string]interface{}{ + logger.InfoCF("feishu", "Feishu message received", map[string]any{ "sender_id": senderID, "chat_id": chatID, "preview": utils.Truncate(content, 80), diff --git a/pkg/channels/line.go b/pkg/channels/line.go index ffb5533e8..f7ca98c92 100644 --- a/pkg/channels/line.go +++ b/pkg/channels/line.go @@ -75,11 +75,11 @@ func (c *LINEChannel) Start(ctx context.Context) error { // Fetch bot profile to get bot's userId for mention detection if err := c.fetchBotInfo(); err != nil { - logger.WarnCF("line", "Failed to fetch bot info (mention detection disabled)", map[string]interface{}{ + logger.WarnCF("line", "Failed to fetch bot info (mention detection disabled)", map[string]any{ "error": err.Error(), }) } else { - logger.InfoCF("line", "Bot info fetched", map[string]interface{}{ + logger.InfoCF("line", "Bot info fetched", map[string]any{ "bot_user_id": c.botUserID, "basic_id": c.botBasicID, "display_name": c.botDisplayName, @@ -100,12 +100,12 @@ func (c *LINEChannel) Start(ctx context.Context) error { } go func() { - logger.InfoCF("line", "LINE webhook server listening", map[string]interface{}{ + logger.InfoCF("line", "LINE webhook server listening", map[string]any{ "addr": addr, "path": path, }) if err := c.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { - logger.ErrorCF("line", "Webhook server error", map[string]interface{}{ + logger.ErrorCF("line", "Webhook server error", map[string]any{ "error": err.Error(), }) } @@ -162,7 +162,7 @@ func (c *LINEChannel) Stop(ctx context.Context) error { shutdownCtx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() if err := c.httpServer.Shutdown(shutdownCtx); err != nil { - logger.ErrorCF("line", "Webhook server shutdown error", map[string]interface{}{ + logger.ErrorCF("line", "Webhook server shutdown error", map[string]any{ "error": err.Error(), }) } @@ -182,7 +182,7 @@ func (c *LINEChannel) webhookHandler(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { - logger.ErrorCF("line", "Failed to read request body", map[string]interface{}{ + logger.ErrorCF("line", "Failed to read request body", map[string]any{ "error": err.Error(), }) http.Error(w, "Bad request", http.StatusBadRequest) @@ -200,7 +200,7 @@ func (c *LINEChannel) webhookHandler(w http.ResponseWriter, r *http.Request) { Events []lineEvent `json:"events"` } if err := json.Unmarshal(body, &payload); err != nil { - logger.ErrorCF("line", "Failed to parse webhook payload", map[string]interface{}{ + logger.ErrorCF("line", "Failed to parse webhook payload", map[string]any{ "error": err.Error(), }) http.Error(w, "Bad request", http.StatusBadRequest) @@ -266,7 +266,7 @@ type lineMentionee struct { func (c *LINEChannel) processEvent(event lineEvent) { if event.Type != "message" { - logger.DebugCF("line", "Ignoring non-message event", map[string]interface{}{ + logger.DebugCF("line", "Ignoring non-message event", map[string]any{ "type": event.Type, }) return @@ -278,7 +278,7 @@ func (c *LINEChannel) processEvent(event lineEvent) { var msg lineMessage if err := json.Unmarshal(event.Message, &msg); err != nil { - logger.ErrorCF("line", "Failed to parse message", map[string]interface{}{ + logger.ErrorCF("line", "Failed to parse message", map[string]any{ "error": err.Error(), }) return @@ -286,7 +286,7 @@ func (c *LINEChannel) processEvent(event lineEvent) { // In group chats, only respond when the bot is mentioned if isGroup && !c.isBotMentioned(msg) { - logger.DebugCF("line", "Ignoring group message without mention", map[string]interface{}{ + logger.DebugCF("line", "Ignoring group message without mention", map[string]any{ "chat_id": chatID, }) return @@ -312,7 +312,7 @@ func (c *LINEChannel) processEvent(event lineEvent) { defer func() { for _, file := range localFiles { if err := os.Remove(file); err != nil { - logger.DebugCF("line", "Failed to cleanup temp file", map[string]interface{}{ + logger.DebugCF("line", "Failed to cleanup temp file", map[string]any{ "file": file, "error": err.Error(), }) @@ -366,7 +366,7 @@ func (c *LINEChannel) processEvent(event lineEvent) { "message_id": msg.ID, } - logger.DebugCF("line", "Received message", map[string]interface{}{ + logger.DebugCF("line", "Received message", map[string]any{ "sender_id": senderID, "chat_id": chatID, "message_type": msg.Type, @@ -497,7 +497,7 @@ func (c *LINEChannel) Send(ctx context.Context, msg bus.OutboundMessage) error { tokenEntry := entry.(replyTokenEntry) if time.Since(tokenEntry.timestamp) < lineReplyTokenMaxAge { if err := c.sendReply(ctx, tokenEntry.token, msg.Content, quoteToken); err == nil { - logger.DebugCF("line", "Message sent via Reply API", map[string]interface{}{ + logger.DebugCF("line", "Message sent via Reply API", map[string]any{ "chat_id": msg.ChatID, "quoted": quoteToken != "", }) @@ -525,7 +525,7 @@ func buildTextMessage(content, quoteToken string) map[string]string { // sendReply sends a message using the LINE Reply API. func (c *LINEChannel) sendReply(ctx context.Context, replyToken, content, quoteToken string) error { - payload := map[string]interface{}{ + payload := map[string]any{ "replyToken": replyToken, "messages": []map[string]string{buildTextMessage(content, quoteToken)}, } @@ -535,7 +535,7 @@ func (c *LINEChannel) sendReply(ctx context.Context, replyToken, content, quoteT // sendPush sends a message using the LINE Push API. func (c *LINEChannel) sendPush(ctx context.Context, to, content, quoteToken string) error { - payload := map[string]interface{}{ + payload := map[string]any{ "to": to, "messages": []map[string]string{buildTextMessage(content, quoteToken)}, } @@ -545,19 +545,19 @@ func (c *LINEChannel) sendPush(ctx context.Context, to, content, quoteToken stri // sendLoading sends a loading animation indicator to the chat. func (c *LINEChannel) sendLoading(chatID string) { - payload := map[string]interface{}{ + payload := map[string]any{ "chatId": chatID, "loadingSeconds": 60, } if err := c.callAPI(c.ctx, lineLoadingEndpoint, payload); err != nil { - logger.DebugCF("line", "Failed to send loading indicator", map[string]interface{}{ + logger.DebugCF("line", "Failed to send loading indicator", map[string]any{ "error": err.Error(), }) } } // callAPI makes an authenticated POST request to the LINE API. -func (c *LINEChannel) callAPI(ctx context.Context, endpoint string, payload interface{}) error { +func (c *LINEChannel) callAPI(ctx context.Context, endpoint string, payload any) error { body, err := json.Marshal(payload) if err != nil { return fmt.Errorf("failed to marshal payload: %w", err) diff --git a/pkg/channels/maixcam.go b/pkg/channels/maixcam.go index 5fc19adbe..39c81cf83 100644 --- a/pkg/channels/maixcam.go +++ b/pkg/channels/maixcam.go @@ -22,10 +22,10 @@ type MaixCamChannel struct { } type MaixCamMessage struct { - Type string `json:"type"` - Tips string `json:"tips"` - Timestamp float64 `json:"timestamp"` - Data map[string]interface{} `json:"data"` + Type string `json:"type"` + Tips string `json:"tips"` + Timestamp float64 `json:"timestamp"` + Data map[string]any `json:"data"` } func NewMaixCamChannel(cfg config.MaixCamConfig, bus *bus.MessageBus) (*MaixCamChannel, error) { @@ -51,7 +51,7 @@ func (c *MaixCamChannel) Start(ctx context.Context) error { c.listener = listener c.setRunning(true) - logger.InfoCF("maixcam", "MaixCam server listening", map[string]interface{}{ + logger.InfoCF("maixcam", "MaixCam server listening", map[string]any{ "host": c.config.Host, "port": c.config.Port, }) @@ -73,14 +73,14 @@ func (c *MaixCamChannel) acceptConnections(ctx context.Context) { conn, err := c.listener.Accept() if err != nil { if c.running { - logger.ErrorCF("maixcam", "Failed to accept connection", map[string]interface{}{ + logger.ErrorCF("maixcam", "Failed to accept connection", map[string]any{ "error": err.Error(), }) } return } - logger.InfoCF("maixcam", "New connection from MaixCam device", map[string]interface{}{ + logger.InfoCF("maixcam", "New connection from MaixCam device", map[string]any{ "remote_addr": conn.RemoteAddr().String(), }) @@ -114,7 +114,7 @@ func (c *MaixCamChannel) handleConnection(conn net.Conn, ctx context.Context) { var msg MaixCamMessage if err := decoder.Decode(&msg); err != nil { if err.Error() != "EOF" { - logger.ErrorCF("maixcam", "Failed to decode message", map[string]interface{}{ + logger.ErrorCF("maixcam", "Failed to decode message", map[string]any{ "error": err.Error(), }) } @@ -135,14 +135,14 @@ func (c *MaixCamChannel) processMessage(msg MaixCamMessage, conn net.Conn) { case "status": c.handleStatusUpdate(msg) default: - logger.WarnCF("maixcam", "Unknown message type", map[string]interface{}{ + logger.WarnCF("maixcam", "Unknown message type", map[string]any{ "type": msg.Type, }) } } func (c *MaixCamChannel) handlePersonDetection(msg MaixCamMessage) { - logger.InfoCF("maixcam", "", map[string]interface{}{ + logger.InfoCF("maixcam", "", map[string]any{ "timestamp": msg.Timestamp, "data": msg.Data, }) @@ -178,7 +178,7 @@ func (c *MaixCamChannel) handlePersonDetection(msg MaixCamMessage) { } func (c *MaixCamChannel) handleStatusUpdate(msg MaixCamMessage) { - logger.InfoCF("maixcam", "Status update from MaixCam", map[string]interface{}{ + logger.InfoCF("maixcam", "Status update from MaixCam", map[string]any{ "status": msg.Data, }) } @@ -216,7 +216,7 @@ func (c *MaixCamChannel) Send(ctx context.Context, msg bus.OutboundMessage) erro return fmt.Errorf("no connected MaixCam devices") } - response := map[string]interface{}{ + response := map[string]any{ "type": "command", "timestamp": float64(0), "message": msg.Content, @@ -231,7 +231,7 @@ func (c *MaixCamChannel) Send(ctx context.Context, msg bus.OutboundMessage) erro var sendErr error for conn := range c.clients { if _, err := conn.Write(data); err != nil { - logger.ErrorCF("maixcam", "Failed to send to client", map[string]interface{}{ + logger.ErrorCF("maixcam", "Failed to send to client", map[string]any{ "client": conn.RemoteAddr().String(), "error": err.Error(), }) diff --git a/pkg/channels/manager.go b/pkg/channels/manager.go index 15f8c6037..776dae40c 100644 --- a/pkg/channels/manager.go +++ b/pkg/channels/manager.go @@ -50,7 +50,7 @@ func (m *Manager) initChannels() error { logger.DebugC("channels", "Attempting to initialize Telegram channel") telegram, err := NewTelegramChannel(m.config.Channels.Telegram, m.bus) if err != nil { - logger.ErrorCF("channels", "Failed to initialize Telegram channel", map[string]interface{}{ + logger.ErrorCF("channels", "Failed to initialize Telegram channel", map[string]any{ "error": err.Error(), }) } else { @@ -63,7 +63,7 @@ func (m *Manager) initChannels() error { logger.DebugC("channels", "Attempting to initialize WhatsApp channel") whatsapp, err := NewWhatsAppChannel(m.config.Channels.WhatsApp, m.bus) if err != nil { - logger.ErrorCF("channels", "Failed to initialize WhatsApp channel", map[string]interface{}{ + logger.ErrorCF("channels", "Failed to initialize WhatsApp channel", map[string]any{ "error": err.Error(), }) } else { @@ -76,7 +76,7 @@ func (m *Manager) initChannels() error { logger.DebugC("channels", "Attempting to initialize Feishu channel") feishu, err := NewFeishuChannel(m.config.Channels.Feishu, m.bus) if err != nil { - logger.ErrorCF("channels", "Failed to initialize Feishu channel", map[string]interface{}{ + logger.ErrorCF("channels", "Failed to initialize Feishu channel", map[string]any{ "error": err.Error(), }) } else { @@ -89,7 +89,7 @@ func (m *Manager) initChannels() error { logger.DebugC("channels", "Attempting to initialize Discord channel") discord, err := NewDiscordChannel(m.config.Channels.Discord, m.bus) if err != nil { - logger.ErrorCF("channels", "Failed to initialize Discord channel", map[string]interface{}{ + logger.ErrorCF("channels", "Failed to initialize Discord channel", map[string]any{ "error": err.Error(), }) } else { @@ -102,7 +102,7 @@ func (m *Manager) initChannels() error { logger.DebugC("channels", "Attempting to initialize MaixCam channel") maixcam, err := NewMaixCamChannel(m.config.Channels.MaixCam, m.bus) if err != nil { - logger.ErrorCF("channels", "Failed to initialize MaixCam channel", map[string]interface{}{ + logger.ErrorCF("channels", "Failed to initialize MaixCam channel", map[string]any{ "error": err.Error(), }) } else { @@ -115,7 +115,7 @@ func (m *Manager) initChannels() error { logger.DebugC("channels", "Attempting to initialize QQ channel") qq, err := NewQQChannel(m.config.Channels.QQ, m.bus) if err != nil { - logger.ErrorCF("channels", "Failed to initialize QQ channel", map[string]interface{}{ + logger.ErrorCF("channels", "Failed to initialize QQ channel", map[string]any{ "error": err.Error(), }) } else { @@ -128,7 +128,7 @@ func (m *Manager) initChannels() error { logger.DebugC("channels", "Attempting to initialize DingTalk channel") dingtalk, err := NewDingTalkChannel(m.config.Channels.DingTalk, m.bus) if err != nil { - logger.ErrorCF("channels", "Failed to initialize DingTalk channel", map[string]interface{}{ + logger.ErrorCF("channels", "Failed to initialize DingTalk channel", map[string]any{ "error": err.Error(), }) } else { @@ -141,7 +141,7 @@ func (m *Manager) initChannels() error { logger.DebugC("channels", "Attempting to initialize Slack channel") slackCh, err := NewSlackChannel(m.config.Channels.Slack, m.bus) if err != nil { - logger.ErrorCF("channels", "Failed to initialize Slack channel", map[string]interface{}{ + logger.ErrorCF("channels", "Failed to initialize Slack channel", map[string]any{ "error": err.Error(), }) } else { @@ -154,7 +154,7 @@ func (m *Manager) initChannels() error { logger.DebugC("channels", "Attempting to initialize LINE channel") line, err := NewLINEChannel(m.config.Channels.LINE, m.bus) if err != nil { - logger.ErrorCF("channels", "Failed to initialize LINE channel", map[string]interface{}{ + logger.ErrorCF("channels", "Failed to initialize LINE channel", map[string]any{ "error": err.Error(), }) } else { @@ -167,7 +167,7 @@ func (m *Manager) initChannels() error { logger.DebugC("channels", "Attempting to initialize OneBot channel") onebot, err := NewOneBotChannel(m.config.Channels.OneBot, m.bus) if err != nil { - logger.ErrorCF("channels", "Failed to initialize OneBot channel", map[string]interface{}{ + logger.ErrorCF("channels", "Failed to initialize OneBot channel", map[string]any{ "error": err.Error(), }) } else { @@ -176,7 +176,7 @@ func (m *Manager) initChannels() error { } } - logger.InfoCF("channels", "Channel initialization completed", map[string]interface{}{ + logger.InfoCF("channels", "Channel initialization completed", map[string]any{ "enabled_channels": len(m.channels), }) @@ -200,11 +200,11 @@ func (m *Manager) StartAll(ctx context.Context) error { go m.dispatchOutbound(dispatchCtx) for name, channel := range m.channels { - logger.InfoCF("channels", "Starting channel", map[string]interface{}{ + logger.InfoCF("channels", "Starting channel", map[string]any{ "channel": name, }) if err := channel.Start(ctx); err != nil { - logger.ErrorCF("channels", "Failed to start channel", map[string]interface{}{ + logger.ErrorCF("channels", "Failed to start channel", map[string]any{ "channel": name, "error": err.Error(), }) @@ -227,11 +227,11 @@ func (m *Manager) StopAll(ctx context.Context) error { } for name, channel := range m.channels { - logger.InfoCF("channels", "Stopping channel", map[string]interface{}{ + logger.InfoCF("channels", "Stopping channel", map[string]any{ "channel": name, }) if err := channel.Stop(ctx); err != nil { - logger.ErrorCF("channels", "Error stopping channel", map[string]interface{}{ + logger.ErrorCF("channels", "Error stopping channel", map[string]any{ "channel": name, "error": err.Error(), }) @@ -266,14 +266,14 @@ func (m *Manager) dispatchOutbound(ctx context.Context) { m.mu.RUnlock() if !exists { - logger.WarnCF("channels", "Unknown channel for outbound message", map[string]interface{}{ + logger.WarnCF("channels", "Unknown channel for outbound message", map[string]any{ "channel": msg.Channel, }) continue } if err := channel.Send(ctx, msg); err != nil { - logger.ErrorCF("channels", "Error sending message to channel", map[string]interface{}{ + logger.ErrorCF("channels", "Error sending message to channel", map[string]any{ "channel": msg.Channel, "error": err.Error(), }) @@ -289,13 +289,13 @@ func (m *Manager) GetChannel(name string) (Channel, bool) { return channel, ok } -func (m *Manager) GetStatus() map[string]interface{} { +func (m *Manager) GetStatus() map[string]any { m.mu.RLock() defer m.mu.RUnlock() - status := make(map[string]interface{}) + status := make(map[string]any) for name, channel := range m.channels { - status[name] = map[string]interface{}{ + status[name] = map[string]any{ "enabled": true, "running": channel.IsRunning(), } diff --git a/pkg/channels/onebot.go b/pkg/channels/onebot.go index 5d97fab9c..959ad188e 100644 --- a/pkg/channels/onebot.go +++ b/pkg/channels/onebot.go @@ -76,9 +76,9 @@ type oneBotEvent struct { } type oneBotAPIRequest struct { - Action string `json:"action"` - Params interface{} `json:"params"` - Echo string `json:"echo,omitempty"` + Action string `json:"action"` + Params any `json:"params"` + Echo string `json:"echo,omitempty"` } type oneBotSendPrivateMsgParams struct { @@ -109,14 +109,14 @@ func (c *OneBotChannel) Start(ctx context.Context) error { return fmt.Errorf("OneBot ws_url not configured") } - logger.InfoCF("onebot", "Starting OneBot channel", map[string]interface{}{ + logger.InfoCF("onebot", "Starting OneBot channel", map[string]any{ "ws_url": c.config.WSUrl, }) c.ctx, c.cancel = context.WithCancel(ctx) if err := c.connect(); err != nil { - logger.WarnCF("onebot", "Initial connection failed, will retry in background", map[string]interface{}{ + logger.WarnCF("onebot", "Initial connection failed, will retry in background", map[string]any{ "error": err.Error(), }) } else { @@ -178,7 +178,7 @@ func (c *OneBotChannel) reconnectLoop() { if conn == nil { logger.InfoC("onebot", "Attempting to reconnect...") if err := c.connect(); err != nil { - logger.ErrorCF("onebot", "Reconnect failed", map[string]interface{}{ + logger.ErrorCF("onebot", "Reconnect failed", map[string]any{ "error": err.Error(), }) } else { @@ -246,7 +246,7 @@ func (c *OneBotChannel) Send(ctx context.Context, msg bus.OutboundMessage) error c.writeMu.Unlock() if err != nil { - logger.ErrorCF("onebot", "Failed to send message", map[string]interface{}{ + logger.ErrorCF("onebot", "Failed to send message", map[string]any{ "error": err.Error(), }) return err @@ -255,7 +255,7 @@ func (c *OneBotChannel) Send(ctx context.Context, msg bus.OutboundMessage) error return nil } -func (c *OneBotChannel) buildSendRequest(msg bus.OutboundMessage) (string, interface{}, error) { +func (c *OneBotChannel) buildSendRequest(msg bus.OutboundMessage) (string, any, error) { chatID := msg.ChatID if len(chatID) > 6 && chatID[:6] == "group:" { @@ -308,7 +308,7 @@ func (c *OneBotChannel) listen() { _, message, err := conn.ReadMessage() if err != nil { - logger.ErrorCF("onebot", "WebSocket read error", map[string]interface{}{ + logger.ErrorCF("onebot", "WebSocket read error", map[string]any{ "error": err.Error(), }) c.mu.Lock() @@ -320,14 +320,14 @@ func (c *OneBotChannel) listen() { return } - logger.DebugCF("onebot", "Raw WebSocket message received", map[string]interface{}{ + logger.DebugCF("onebot", "Raw WebSocket message received", map[string]any{ "length": len(message), "payload": string(message), }) var raw oneBotRawEvent if err := json.Unmarshal(message, &raw); err != nil { - logger.WarnCF("onebot", "Failed to unmarshal raw event", map[string]interface{}{ + logger.WarnCF("onebot", "Failed to unmarshal raw event", map[string]any{ "error": err.Error(), "payload": string(message), }) @@ -335,14 +335,14 @@ func (c *OneBotChannel) listen() { } if raw.Echo != "" || raw.Status.Online || raw.Status.Good { - logger.DebugCF("onebot", "Received API response, skipping", map[string]interface{}{ + logger.DebugCF("onebot", "Received API response, skipping", map[string]any{ "echo": raw.Echo, "status": raw.Status, }) continue } - logger.DebugCF("onebot", "Parsed raw event", map[string]interface{}{ + logger.DebugCF("onebot", "Parsed raw event", map[string]any{ "post_type": raw.PostType, "message_type": raw.MessageType, "sub_type": raw.SubType, @@ -407,14 +407,14 @@ func parseMessageContentEx(raw json.RawMessage, selfID int64) parseMessageResult return parseMessageResult{Text: s, IsBotMentioned: mentioned} } - var segments []map[string]interface{} + var segments []map[string]any if err := json.Unmarshal(raw, &segments); err == nil { var text string mentioned := false selfIDStr := strconv.FormatInt(selfID, 10) for _, seg := range segments { segType, _ := seg["type"].(string) - data, _ := seg["data"].(map[string]interface{}) + data, _ := seg["data"].(map[string]any) switch segType { case "text": if data != nil { @@ -441,7 +441,7 @@ func (c *OneBotChannel) handleRawEvent(raw *oneBotRawEvent) { case "message": evt, err := c.normalizeMessageEvent(raw) if err != nil { - logger.WarnCF("onebot", "Failed to normalize message event", map[string]interface{}{ + logger.WarnCF("onebot", "Failed to normalize message event", map[string]any{ "error": err.Error(), }) return @@ -450,20 +450,20 @@ func (c *OneBotChannel) handleRawEvent(raw *oneBotRawEvent) { case "meta_event": c.handleMetaEvent(raw) case "notice": - logger.DebugCF("onebot", "Notice event received", map[string]interface{}{ + logger.DebugCF("onebot", "Notice event received", map[string]any{ "sub_type": raw.SubType, }) case "request": - logger.DebugCF("onebot", "Request event received", map[string]interface{}{ + logger.DebugCF("onebot", "Request event received", map[string]any{ "sub_type": raw.SubType, }) case "": - logger.DebugCF("onebot", "Event with empty post_type (possibly API response)", map[string]interface{}{ + logger.DebugCF("onebot", "Event with empty post_type (possibly API response)", map[string]any{ "echo": raw.Echo, "status": raw.Status, }) default: - logger.DebugCF("onebot", "Unknown post_type", map[string]interface{}{ + logger.DebugCF("onebot", "Unknown post_type", map[string]any{ "post_type": raw.PostType, }) } @@ -498,14 +498,14 @@ func (c *OneBotChannel) normalizeMessageEvent(raw *oneBotRawEvent) (*oneBotEvent var sender oneBotSender if len(raw.Sender) > 0 { if err := json.Unmarshal(raw.Sender, &sender); err != nil { - logger.WarnCF("onebot", "Failed to parse sender", map[string]interface{}{ + logger.WarnCF("onebot", "Failed to parse sender", map[string]any{ "error": err.Error(), "sender": string(raw.Sender), }) } } - logger.DebugCF("onebot", "Normalized message event", map[string]interface{}{ + logger.DebugCF("onebot", "Normalized message event", map[string]any{ "message_type": raw.MessageType, "user_id": userID, "group_id": groupID, @@ -534,13 +534,13 @@ func (c *OneBotChannel) normalizeMessageEvent(raw *oneBotRawEvent) (*oneBotEvent func (c *OneBotChannel) handleMetaEvent(raw *oneBotRawEvent) { switch raw.MetaEventType { case "lifecycle": - logger.InfoCF("onebot", "Lifecycle event", map[string]interface{}{ + logger.InfoCF("onebot", "Lifecycle event", map[string]any{ "sub_type": raw.SubType, }) case "heartbeat": logger.DebugC("onebot", "Heartbeat received") default: - logger.DebugCF("onebot", "Unknown meta_event_type", map[string]interface{}{ + logger.DebugCF("onebot", "Unknown meta_event_type", map[string]any{ "meta_event_type": raw.MetaEventType, }) } @@ -548,7 +548,7 @@ func (c *OneBotChannel) handleMetaEvent(raw *oneBotRawEvent) { func (c *OneBotChannel) handleMessage(evt *oneBotEvent) { if c.isDuplicate(evt.MessageID) { - logger.DebugCF("onebot", "Duplicate message, skipping", map[string]interface{}{ + logger.DebugCF("onebot", "Duplicate message, skipping", map[string]any{ "message_id": evt.MessageID, }) return @@ -556,7 +556,7 @@ func (c *OneBotChannel) handleMessage(evt *oneBotEvent) { content := evt.Content if content == "" { - logger.DebugCF("onebot", "Received empty message, ignoring", map[string]interface{}{ + logger.DebugCF("onebot", "Received empty message, ignoring", map[string]any{ "message_id": evt.MessageID, }) return @@ -572,7 +572,7 @@ func (c *OneBotChannel) handleMessage(evt *oneBotEvent) { switch evt.MessageType { case "private": chatID = "private:" + senderID - logger.InfoCF("onebot", "Received private message", map[string]interface{}{ + logger.InfoCF("onebot", "Received private message", map[string]any{ "sender": senderID, "message_id": evt.MessageID, "length": len(content), @@ -597,7 +597,7 @@ func (c *OneBotChannel) handleMessage(evt *oneBotEvent) { triggered, strippedContent := c.checkGroupTrigger(content, evt.IsBotMentioned) if !triggered { - logger.DebugCF("onebot", "Group message ignored (no trigger)", map[string]interface{}{ + logger.DebugCF("onebot", "Group message ignored (no trigger)", map[string]any{ "sender": senderID, "group": groupIDStr, "is_mentioned": evt.IsBotMentioned, @@ -607,7 +607,7 @@ func (c *OneBotChannel) handleMessage(evt *oneBotEvent) { } content = strippedContent - logger.InfoCF("onebot", "Received group message", map[string]interface{}{ + logger.InfoCF("onebot", "Received group message", map[string]any{ "sender": senderID, "group": groupIDStr, "message_id": evt.MessageID, @@ -617,7 +617,7 @@ func (c *OneBotChannel) handleMessage(evt *oneBotEvent) { }) default: - logger.WarnCF("onebot", "Unknown message type, cannot route", map[string]interface{}{ + logger.WarnCF("onebot", "Unknown message type, cannot route", map[string]any{ "type": evt.MessageType, "message_id": evt.MessageID, "user_id": evt.UserID, @@ -629,7 +629,7 @@ func (c *OneBotChannel) handleMessage(evt *oneBotEvent) { metadata["nickname"] = evt.Sender.Nickname } - logger.DebugCF("onebot", "Forwarding message to bus", map[string]interface{}{ + logger.DebugCF("onebot", "Forwarding message to bus", map[string]any{ "sender_id": senderID, "chat_id": chatID, "content": truncate(content, 100), diff --git a/pkg/channels/qq.go b/pkg/channels/qq.go index 18b4ca0e0..055498797 100644 --- a/pkg/channels/qq.go +++ b/pkg/channels/qq.go @@ -77,7 +77,7 @@ func (c *QQChannel) Start(ctx context.Context) error { return fmt.Errorf("failed to get websocket info: %w", err) } - logger.InfoCF("qq", "Got WebSocket info", map[string]interface{}{ + logger.InfoCF("qq", "Got WebSocket info", map[string]any{ "shards": wsInfo.Shards, }) @@ -87,7 +87,7 @@ func (c *QQChannel) Start(ctx context.Context) error { // 在 goroutine 中启动 WebSocket 连接,避免阻塞 go func() { if err := c.sessionManager.Start(wsInfo, c.tokenSource, &intent); err != nil { - logger.ErrorCF("qq", "WebSocket session error", map[string]interface{}{ + logger.ErrorCF("qq", "WebSocket session error", map[string]any{ "error": err.Error(), }) c.setRunning(false) @@ -124,7 +124,7 @@ func (c *QQChannel) Send(ctx context.Context, msg bus.OutboundMessage) error { // C2C 消息发送 _, err := c.api.PostC2CMessage(ctx, msg.ChatID, msgToCreate) if err != nil { - logger.ErrorCF("qq", "Failed to send C2C message", map[string]interface{}{ + logger.ErrorCF("qq", "Failed to send C2C message", map[string]any{ "error": err.Error(), }) return err @@ -157,7 +157,7 @@ func (c *QQChannel) handleC2CMessage() event.C2CMessageEventHandler { return nil } - logger.InfoCF("qq", "Received C2C message", map[string]interface{}{ + logger.InfoCF("qq", "Received C2C message", map[string]any{ "sender": senderID, "length": len(content), }) @@ -197,7 +197,7 @@ func (c *QQChannel) handleGroupATMessage() event.GroupATMessageEventHandler { return nil } - logger.InfoCF("qq", "Received group AT message", map[string]interface{}{ + logger.InfoCF("qq", "Received group AT message", map[string]any{ "sender": senderID, "group": data.GroupID, "length": len(content), diff --git a/pkg/channels/slack.go b/pkg/channels/slack.go index d86d08a9d..44619eacc 100644 --- a/pkg/channels/slack.go +++ b/pkg/channels/slack.go @@ -73,7 +73,7 @@ func (c *SlackChannel) Start(ctx context.Context) error { } c.botUserID = authResp.UserID - logger.InfoCF("slack", "Slack bot connected", map[string]interface{}{ + logger.InfoCF("slack", "Slack bot connected", map[string]any{ "bot_user_id": c.botUserID, "team": authResp.Team, }) @@ -83,7 +83,7 @@ func (c *SlackChannel) Start(ctx context.Context) error { go func() { if err := c.socketClient.RunContext(c.ctx); err != nil { if c.ctx.Err() == nil { - logger.ErrorCF("slack", "Socket Mode connection error", map[string]interface{}{ + logger.ErrorCF("slack", "Socket Mode connection error", map[string]any{ "error": err.Error(), }) } @@ -138,7 +138,7 @@ func (c *SlackChannel) Send(ctx context.Context, msg bus.OutboundMessage) error }) } - logger.DebugCF("slack", "Message sent", map[string]interface{}{ + logger.DebugCF("slack", "Message sent", map[string]any{ "channel_id": channelID, "thread_ts": threadTS, }) @@ -200,7 +200,7 @@ func (c *SlackChannel) handleMessageEvent(ev *slackevents.MessageEvent) { // 检查白名单,避免为被拒绝的用户下载附件 if !c.IsAllowed(ev.User) { - logger.DebugCF("slack", "Message rejected by allowlist", map[string]interface{}{ + logger.DebugCF("slack", "Message rejected by allowlist", map[string]any{ "user_id": ev.User, }) return @@ -236,7 +236,7 @@ func (c *SlackChannel) handleMessageEvent(ev *slackevents.MessageEvent) { defer func() { for _, file := range localFiles { if err := os.Remove(file); err != nil { - logger.DebugCF("slack", "Failed to cleanup temp file", map[string]interface{}{ + logger.DebugCF("slack", "Failed to cleanup temp file", map[string]any{ "file": file, "error": err.Error(), }) @@ -259,7 +259,7 @@ func (c *SlackChannel) handleMessageEvent(ev *slackevents.MessageEvent) { result, err := c.transcriber.Transcribe(ctx, localPath) if err != nil { - logger.ErrorCF("slack", "Voice transcription failed", map[string]interface{}{"error": err.Error()}) + logger.ErrorCF("slack", "Voice transcription failed", map[string]any{"error": err.Error()}) content += fmt.Sprintf("\n[audio: %s (transcription failed)]", file.Name) } else { content += fmt.Sprintf("\n[voice transcription: %s]", result.Text) @@ -281,7 +281,7 @@ func (c *SlackChannel) handleMessageEvent(ev *slackevents.MessageEvent) { "platform": "slack", } - logger.DebugCF("slack", "Received message", map[string]interface{}{ + logger.DebugCF("slack", "Received message", map[string]any{ "sender_id": senderID, "chat_id": chatID, "preview": utils.Truncate(content, 50), @@ -361,7 +361,7 @@ func (c *SlackChannel) handleSlashCommand(event socketmode.Event) { "trigger_id": cmd.TriggerID, } - logger.DebugCF("slack", "Slash command received", map[string]interface{}{ + logger.DebugCF("slack", "Slash command received", map[string]any{ "sender_id": senderID, "command": cmd.Command, "text": utils.Truncate(content, 50), @@ -376,7 +376,7 @@ func (c *SlackChannel) downloadSlackFile(file slack.File) string { downloadURL = file.URLPrivate } if downloadURL == "" { - logger.ErrorCF("slack", "No download URL for file", map[string]interface{}{"file_id": file.ID}) + logger.ErrorCF("slack", "No download URL for file", map[string]any{"file_id": file.ID}) return "" } diff --git a/pkg/channels/telegram.go b/pkg/channels/telegram.go index b14b1632e..4895bc059 100644 --- a/pkg/channels/telegram.go +++ b/pkg/channels/telegram.go @@ -89,7 +89,7 @@ func (c *TelegramChannel) Start(ctx context.Context) error { } c.setRunning(true) - logger.InfoCF("telegram", "Telegram bot connected", map[string]interface{}{ + logger.InfoCF("telegram", "Telegram bot connected", map[string]any{ "username": c.bot.Username(), }) @@ -155,7 +155,7 @@ func (c *TelegramChannel) Send(ctx context.Context, msg bus.OutboundMessage) err tgMsg.ParseMode = telego.ModeHTML if _, err = c.bot.SendMessage(ctx, tgMsg); err != nil { - logger.ErrorCF("telegram", "HTML parse failed, falling back to plain text", map[string]interface{}{ + logger.ErrorCF("telegram", "HTML parse failed, falling back to plain text", map[string]any{ "error": err.Error(), }) tgMsg.ParseMode = "" @@ -185,7 +185,7 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, update telego.Updat // 检查白名单,避免为被拒绝的用户下载附件 if !c.IsAllowed(userID) && !c.IsAllowed(senderID) { - logger.DebugCF("telegram", "Message rejected by allowlist", map[string]interface{}{ + logger.DebugCF("telegram", "Message rejected by allowlist", map[string]any{ "user_id": userID, "username": user.Username, }) @@ -203,7 +203,7 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, update telego.Updat defer func() { for _, file := range localFiles { if err := os.Remove(file); err != nil { - logger.DebugCF("telegram", "Failed to cleanup temp file", map[string]interface{}{ + logger.DebugCF("telegram", "Failed to cleanup temp file", map[string]any{ "file": file, "error": err.Error(), }) @@ -248,14 +248,14 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, update telego.Updat result, err := c.transcriber.Transcribe(ctx, voicePath) if err != nil { - logger.ErrorCF("telegram", "Voice transcription failed", map[string]interface{}{ + logger.ErrorCF("telegram", "Voice transcription failed", map[string]any{ "error": err.Error(), "path": voicePath, }) transcribedText = fmt.Sprintf("[voice (transcription failed)]") } else { transcribedText = fmt.Sprintf("[voice transcription: %s]", result.Text) - logger.InfoCF("telegram", "Voice transcribed successfully", map[string]interface{}{ + logger.InfoCF("telegram", "Voice transcribed successfully", map[string]any{ "text": result.Text, }) } @@ -298,7 +298,7 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, update telego.Updat content = "[empty message]" } - logger.DebugCF("telegram", "Received message", map[string]interface{}{ + logger.DebugCF("telegram", "Received message", map[string]any{ "sender_id": senderID, "chat_id": fmt.Sprintf("%d", chatID), "preview": utils.Truncate(content, 50), @@ -307,7 +307,7 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, update telego.Updat // Thinking indicator err := c.bot.SendChatAction(ctx, tu.ChatAction(tu.ID(chatID), telego.ChatActionTyping)) if err != nil { - logger.ErrorCF("telegram", "Failed to send chat action", map[string]interface{}{ + logger.ErrorCF("telegram", "Failed to send chat action", map[string]any{ "error": err.Error(), }) } @@ -344,7 +344,7 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, update telego.Updat func (c *TelegramChannel) downloadPhoto(ctx context.Context, fileID string) string { file, err := c.bot.GetFile(ctx, &telego.GetFileParams{FileID: fileID}) if err != nil { - logger.ErrorCF("telegram", "Failed to get photo file", map[string]interface{}{ + logger.ErrorCF("telegram", "Failed to get photo file", map[string]any{ "error": err.Error(), }) return "" @@ -359,7 +359,7 @@ func (c *TelegramChannel) downloadFileWithInfo(file *telego.File, ext string) st } url := c.bot.FileDownloadURL(file.FilePath) - logger.DebugCF("telegram", "File URL", map[string]interface{}{"url": url}) + logger.DebugCF("telegram", "File URL", map[string]any{"url": url}) // Use FilePath as filename for better identification filename := file.FilePath + ext @@ -371,7 +371,7 @@ func (c *TelegramChannel) downloadFileWithInfo(file *telego.File, ext string) st func (c *TelegramChannel) downloadFile(ctx context.Context, fileID, ext string) string { file, err := c.bot.GetFile(ctx, &telego.GetFileParams{FileID: fileID}) if err != nil { - logger.ErrorCF("telegram", "Failed to get file", map[string]interface{}{ + logger.ErrorCF("telegram", "Failed to get file", map[string]any{ "error": err.Error(), }) return "" diff --git a/pkg/channels/whatsapp.go b/pkg/channels/whatsapp.go index c95e59578..6634f2722 100644 --- a/pkg/channels/whatsapp.go +++ b/pkg/channels/whatsapp.go @@ -86,7 +86,7 @@ func (c *WhatsAppChannel) Send(ctx context.Context, msg bus.OutboundMessage) err return fmt.Errorf("whatsapp connection not established") } - payload := map[string]interface{}{ + payload := map[string]any{ "type": "message", "to": msg.ChatID, "content": msg.Content, @@ -126,7 +126,7 @@ func (c *WhatsAppChannel) listen(ctx context.Context) { continue } - var msg map[string]interface{} + var msg map[string]any if err := json.Unmarshal(message, &msg); err != nil { log.Printf("Failed to unmarshal WhatsApp message: %v", err) continue @@ -144,7 +144,7 @@ func (c *WhatsAppChannel) listen(ctx context.Context) { } } -func (c *WhatsAppChannel) handleIncomingMessage(msg map[string]interface{}) { +func (c *WhatsAppChannel) handleIncomingMessage(msg map[string]any) { senderID, ok := msg["from"].(string) if !ok { return @@ -161,7 +161,7 @@ func (c *WhatsAppChannel) handleIncomingMessage(msg map[string]interface{}) { } var mediaPaths []string - if mediaData, ok := msg["media"].([]interface{}); ok { + if mediaData, ok := msg["media"].([]any); ok { mediaPaths = make([]string, 0, len(mediaData)) for _, m := range mediaData { if path, ok := m.(string); ok { diff --git a/pkg/config/config.go b/pkg/config/config.go index d76ec8095..9be8d7d9f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -23,7 +23,7 @@ func (f *FlexibleStringSlice) UnmarshalJSON(data []byte) error { } // Try []interface{} to handle mixed types - var raw []interface{} + var raw []any if err := json.Unmarshal(data, &raw); err != nil { return err } diff --git a/pkg/cron/service.go b/pkg/cron/service.go index ddd680e74..9c511d88b 100644 --- a/pkg/cron/service.go +++ b/pkg/cron/service.go @@ -465,7 +465,7 @@ func (cs *CronService) ListJobs(includeDisabled bool) []CronJob { return enabled } -func (cs *CronService) Status() map[string]interface{} { +func (cs *CronService) Status() map[string]any { cs.mu.RLock() defer cs.mu.RUnlock() @@ -476,7 +476,7 @@ func (cs *CronService) Status() map[string]interface{} { } } - return map[string]interface{}{ + return map[string]any{ "enabled": cs.running, "jobs": len(cs.store.Jobs), "nextWakeAtMS": cs.getNextWakeMS(), diff --git a/pkg/devices/service.go b/pkg/devices/service.go index 05a254729..1541d3c57 100644 --- a/pkg/devices/service.go +++ b/pkg/devices/service.go @@ -63,14 +63,14 @@ func (s *Service) Start(ctx context.Context) error { for _, src := range s.sources { eventCh, err := src.Start(s.ctx) if err != nil { - logger.ErrorCF("devices", "Failed to start source", map[string]interface{}{ + logger.ErrorCF("devices", "Failed to start source", map[string]any{ "kind": src.Kind(), "error": err.Error(), }) continue } go s.handleEvents(src.Kind(), eventCh) - logger.InfoCF("devices", "Device source started", map[string]interface{}{ + logger.InfoCF("devices", "Device source started", map[string]any{ "kind": src.Kind(), }) } @@ -115,7 +115,7 @@ func (s *Service) sendNotification(ev *events.DeviceEvent) { lastChannel := s.state.GetLastChannel() if lastChannel == "" { - logger.DebugCF("devices", "No last channel, skipping notification", map[string]interface{}{ + logger.DebugCF("devices", "No last channel, skipping notification", map[string]any{ "event": ev.FormatMessage(), }) return @@ -133,7 +133,7 @@ func (s *Service) sendNotification(ev *events.DeviceEvent) { Content: msg, }) - logger.InfoCF("devices", "Device notification sent", map[string]interface{}{ + logger.InfoCF("devices", "Device notification sent", map[string]any{ "kind": ev.Kind, "action": ev.Action, "to": platform, diff --git a/pkg/devices/sources/usb_linux.go b/pkg/devices/sources/usb_linux.go index 1f6c068b3..be0193cfb 100644 --- a/pkg/devices/sources/usb_linux.go +++ b/pkg/devices/sources/usb_linux.go @@ -115,7 +115,7 @@ func (m *USBMonitor) Start(ctx context.Context) (<-chan *events.DeviceEvent, err } if err := scanner.Err(); err != nil { - logger.ErrorCF("devices", "udevadm scan error", map[string]interface{}{"error": err.Error()}) + logger.ErrorCF("devices", "udevadm scan error", map[string]any{"error": err.Error()}) } cmd.Wait() }() diff --git a/pkg/heartbeat/service.go b/pkg/heartbeat/service.go index dfdaef58b..122eae041 100644 --- a/pkg/heartbeat/service.go +++ b/pkg/heartbeat/service.go @@ -193,7 +193,7 @@ func (hs *HeartbeatService) executeHeartbeat() { if result.Async { hs.logInfo("Async task started: %s", result.ForLLM) logger.InfoCF("heartbeat", "Async heartbeat task started", - map[string]interface{}{ + map[string]any{ "message": result.ForLLM, }) return diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 22f66829f..e241f2645 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -41,12 +41,12 @@ type Logger struct { } type LogEntry struct { - Level string `json:"level"` - Timestamp string `json:"timestamp"` - Component string `json:"component,omitempty"` - Message string `json:"message"` - Fields map[string]interface{} `json:"fields,omitempty"` - Caller string `json:"caller,omitempty"` + Level string `json:"level"` + Timestamp string `json:"timestamp"` + Component string `json:"component,omitempty"` + Message string `json:"message"` + Fields map[string]any `json:"fields,omitempty"` + Caller string `json:"caller,omitempty"` } func init() { @@ -96,7 +96,7 @@ func DisableFileLogging() { } } -func logMessage(level LogLevel, component string, message string, fields map[string]interface{}) { +func logMessage(level LogLevel, component string, message string, fields map[string]any) { if level < currentLevel { return } @@ -150,7 +150,7 @@ func formatComponent(component string) string { return fmt.Sprintf(" %s:", component) } -func formatFields(fields map[string]interface{}) string { +func formatFields(fields map[string]any) string { var parts []string for k, v := range fields { parts = append(parts, fmt.Sprintf("%s=%v", k, v)) @@ -166,11 +166,11 @@ func DebugC(component string, message string) { logMessage(DEBUG, component, message, nil) } -func DebugF(message string, fields map[string]interface{}) { +func DebugF(message string, fields map[string]any) { logMessage(DEBUG, "", message, fields) } -func DebugCF(component string, message string, fields map[string]interface{}) { +func DebugCF(component string, message string, fields map[string]any) { logMessage(DEBUG, component, message, fields) } @@ -182,11 +182,11 @@ func InfoC(component string, message string) { logMessage(INFO, component, message, nil) } -func InfoF(message string, fields map[string]interface{}) { +func InfoF(message string, fields map[string]any) { logMessage(INFO, "", message, fields) } -func InfoCF(component string, message string, fields map[string]interface{}) { +func InfoCF(component string, message string, fields map[string]any) { logMessage(INFO, component, message, fields) } @@ -198,11 +198,11 @@ func WarnC(component string, message string) { logMessage(WARN, component, message, nil) } -func WarnF(message string, fields map[string]interface{}) { +func WarnF(message string, fields map[string]any) { logMessage(WARN, "", message, fields) } -func WarnCF(component string, message string, fields map[string]interface{}) { +func WarnCF(component string, message string, fields map[string]any) { logMessage(WARN, component, message, fields) } @@ -214,11 +214,11 @@ func ErrorC(component string, message string) { logMessage(ERROR, component, message, nil) } -func ErrorF(message string, fields map[string]interface{}) { +func ErrorF(message string, fields map[string]any) { logMessage(ERROR, "", message, fields) } -func ErrorCF(component string, message string, fields map[string]interface{}) { +func ErrorCF(component string, message string, fields map[string]any) { logMessage(ERROR, component, message, fields) } @@ -230,10 +230,10 @@ func FatalC(component string, message string) { logMessage(FATAL, component, message, nil) } -func FatalF(message string, fields map[string]interface{}) { +func FatalF(message string, fields map[string]any) { logMessage(FATAL, "", message, fields) } -func FatalCF(component string, message string, fields map[string]interface{}) { +func FatalCF(component string, message string, fields map[string]any) { logMessage(FATAL, component, message, fields) } diff --git a/pkg/logger/logger_test.go b/pkg/logger/logger_test.go index 9b9c96820..6e6f8dfa8 100644 --- a/pkg/logger/logger_test.go +++ b/pkg/logger/logger_test.go @@ -54,11 +54,11 @@ func TestLoggerWithComponent(t *testing.T) { name string component string message string - fields map[string]interface{} + fields map[string]any }{ {"Simple message", "test", "Hello, world!", nil}, {"Message with component", "discord", "Discord message", nil}, - {"Message with fields", "telegram", "Telegram message", map[string]interface{}{ + {"Message with fields", "telegram", "Telegram message", map[string]any{ "user_id": "12345", "count": 42, }}, @@ -128,12 +128,12 @@ func TestLoggerHelperFunctions(t *testing.T) { Error("This should log") InfoC("test", "Component message") - InfoF("Fields message", map[string]interface{}{"key": "value"}) + InfoF("Fields message", map[string]any{"key": "value"}) WarnC("test", "Warning with component") - ErrorF("Error with fields", map[string]interface{}{"error": "test"}) + ErrorF("Error with fields", map[string]any{"error": "test"}) SetLevel(DEBUG) DebugC("test", "Debug with component") - WarnF("Warning with fields", map[string]interface{}{"key": "value"}) + WarnF("Warning with fields", map[string]any{"key": "value"}) } diff --git a/pkg/migrate/config.go b/pkg/migrate/config.go index 9c1e36359..6d2e9f508 100644 --- a/pkg/migrate/config.go +++ b/pkg/migrate/config.go @@ -44,26 +44,26 @@ func findOpenClawConfig(openclawHome string) (string, error) { return "", fmt.Errorf("no config file found in %s (tried openclaw.json, config.json)", openclawHome) } -func LoadOpenClawConfig(configPath string) (map[string]interface{}, error) { +func LoadOpenClawConfig(configPath string) (map[string]any, error) { data, err := os.ReadFile(configPath) if err != nil { return nil, fmt.Errorf("reading OpenClaw config: %w", err) } - var raw map[string]interface{} + var raw map[string]any if err := json.Unmarshal(data, &raw); err != nil { return nil, fmt.Errorf("parsing OpenClaw config: %w", err) } converted := convertKeysToSnake(raw) - result, ok := converted.(map[string]interface{}) + result, ok := converted.(map[string]any) if !ok { return nil, fmt.Errorf("unexpected config format") } return result, nil } -func ConvertConfig(data map[string]interface{}) (*config.Config, []string, error) { +func ConvertConfig(data map[string]any) (*config.Config, []string, error) { cfg := config.DefaultConfig() var warnings []string @@ -89,7 +89,7 @@ func ConvertConfig(data map[string]interface{}) (*config.Config, []string, error if providers, ok := getMap(data, "providers"); ok { for name, val := range providers { - pMap, ok := val.(map[string]interface{}) + pMap, ok := val.(map[string]any) if !ok { continue } @@ -125,7 +125,7 @@ func ConvertConfig(data map[string]interface{}) (*config.Config, []string, error if channels, ok := getMap(data, "channels"); ok { for name, val := range channels { - cMap, ok := val.(map[string]interface{}) + cMap, ok := val.(map[string]any) if !ok { continue } @@ -303,16 +303,16 @@ func camelToSnake(s string) string { return result.String() } -func convertKeysToSnake(data interface{}) interface{} { +func convertKeysToSnake(data any) any { switch v := data.(type) { - case map[string]interface{}: - result := make(map[string]interface{}, len(v)) + case map[string]any: + result := make(map[string]any, len(v)) for key, val := range v { result[camelToSnake(key)] = convertKeysToSnake(val) } return result - case []interface{}: - result := make([]interface{}, len(v)) + case []any: + result := make([]any, len(v)) for i, val := range v { result[i] = convertKeysToSnake(val) } @@ -327,16 +327,16 @@ func rewriteWorkspacePath(path string) string { return path } -func getMap(data map[string]interface{}, key string) (map[string]interface{}, bool) { +func getMap(data map[string]any, key string) (map[string]any, bool) { v, ok := data[key] if !ok { return nil, false } - m, ok := v.(map[string]interface{}) + m, ok := v.(map[string]any) return m, ok } -func getString(data map[string]interface{}, key string) (string, bool) { +func getString(data map[string]any, key string) (string, bool) { v, ok := data[key] if !ok { return "", false @@ -345,7 +345,7 @@ func getString(data map[string]interface{}, key string) (string, bool) { return s, ok } -func getFloat(data map[string]interface{}, key string) (float64, bool) { +func getFloat(data map[string]any, key string) (float64, bool) { v, ok := data[key] if !ok { return 0, false @@ -354,7 +354,7 @@ func getFloat(data map[string]interface{}, key string) (float64, bool) { return f, ok } -func getBool(data map[string]interface{}, key string) (bool, bool) { +func getBool(data map[string]any, key string) (bool, bool) { v, ok := data[key] if !ok { return false, false @@ -363,12 +363,12 @@ func getBool(data map[string]interface{}, key string) (bool, bool) { return b, ok } -func getStringSlice(data map[string]interface{}, key string) []string { +func getStringSlice(data map[string]any, key string) []string { v, ok := data[key] if !ok { return []string{} } - arr, ok := v.([]interface{}) + arr, ok := v.([]any) if !ok { return []string{} } diff --git a/pkg/migrate/migrate_test.go b/pkg/migrate/migrate_test.go index be2360aac..e438f5a32 100644 --- a/pkg/migrate/migrate_test.go +++ b/pkg/migrate/migrate_test.go @@ -40,20 +40,20 @@ func TestCamelToSnake(t *testing.T) { } func TestConvertKeysToSnake(t *testing.T) { - input := map[string]interface{}{ + input := map[string]any{ "apiKey": "test-key", "apiBase": "https://example.com", - "nested": map[string]interface{}{ + "nested": map[string]any{ "maxTokens": float64(8192), - "allowFrom": []interface{}{"user1", "user2"}, - "deeperLevel": map[string]interface{}{ + "allowFrom": []any{"user1", "user2"}, + "deeperLevel": map[string]any{ "clientId": "abc", }, }, } result := convertKeysToSnake(input) - m, ok := result.(map[string]interface{}) + m, ok := result.(map[string]any) if !ok { t.Fatal("expected map[string]interface{}") } @@ -65,7 +65,7 @@ func TestConvertKeysToSnake(t *testing.T) { t.Error("expected key 'api_base' after conversion") } - nested, ok := m["nested"].(map[string]interface{}) + nested, ok := m["nested"].(map[string]any) if !ok { t.Fatal("expected nested map") } @@ -76,7 +76,7 @@ func TestConvertKeysToSnake(t *testing.T) { t.Error("expected key 'allow_from' in nested map") } - deeper, ok := nested["deeper_level"].(map[string]interface{}) + deeper, ok := nested["deeper_level"].(map[string]any) if !ok { t.Fatal("expected deeper_level map") } @@ -89,15 +89,15 @@ func TestLoadOpenClawConfig(t *testing.T) { tmpDir := t.TempDir() configPath := filepath.Join(tmpDir, "openclaw.json") - openclawConfig := map[string]interface{}{ - "providers": map[string]interface{}{ - "anthropic": map[string]interface{}{ + openclawConfig := map[string]any{ + "providers": map[string]any{ + "anthropic": map[string]any{ "apiKey": "sk-ant-test123", "apiBase": "https://api.anthropic.com", }, }, - "agents": map[string]interface{}{ - "defaults": map[string]interface{}{ + "agents": map[string]any{ + "defaults": map[string]any{ "maxTokens": float64(4096), "model": "claude-3-opus", }, @@ -117,11 +117,11 @@ func TestLoadOpenClawConfig(t *testing.T) { t.Fatalf("LoadOpenClawConfig: %v", err) } - providers, ok := result["providers"].(map[string]interface{}) + providers, ok := result["providers"].(map[string]any) if !ok { t.Fatal("expected providers map") } - anthropic, ok := providers["anthropic"].(map[string]interface{}) + anthropic, ok := providers["anthropic"].(map[string]any) if !ok { t.Fatal("expected anthropic map") } @@ -129,11 +129,11 @@ func TestLoadOpenClawConfig(t *testing.T) { t.Errorf("api_key = %v, want sk-ant-test123", anthropic["api_key"]) } - agents, ok := result["agents"].(map[string]interface{}) + agents, ok := result["agents"].(map[string]any) if !ok { t.Fatal("expected agents map") } - defaults, ok := agents["defaults"].(map[string]interface{}) + defaults, ok := agents["defaults"].(map[string]any) if !ok { t.Fatal("expected defaults map") } @@ -144,16 +144,16 @@ func TestLoadOpenClawConfig(t *testing.T) { func TestConvertConfig(t *testing.T) { t.Run("providers mapping", func(t *testing.T) { - data := map[string]interface{}{ - "providers": map[string]interface{}{ - "anthropic": map[string]interface{}{ + data := map[string]any{ + "providers": map[string]any{ + "anthropic": map[string]any{ "api_key": "sk-ant-test", "api_base": "https://api.anthropic.com", }, - "openrouter": map[string]interface{}{ + "openrouter": map[string]any{ "api_key": "sk-or-test", }, - "groq": map[string]interface{}{ + "groq": map[string]any{ "api_key": "gsk-test", }, }, @@ -178,9 +178,9 @@ func TestConvertConfig(t *testing.T) { }) t.Run("unsupported provider warning", func(t *testing.T) { - data := map[string]interface{}{ - "providers": map[string]interface{}{ - "deepseek": map[string]interface{}{ + data := map[string]any{ + "providers": map[string]any{ + "deepseek": map[string]any{ "api_key": "sk-deep-test", }, }, @@ -199,14 +199,14 @@ func TestConvertConfig(t *testing.T) { }) t.Run("channels mapping", func(t *testing.T) { - data := map[string]interface{}{ - "channels": map[string]interface{}{ - "telegram": map[string]interface{}{ + data := map[string]any{ + "channels": map[string]any{ + "telegram": map[string]any{ "enabled": true, "token": "tg-token-123", - "allow_from": []interface{}{"user1"}, + "allow_from": []any{"user1"}, }, - "discord": map[string]interface{}{ + "discord": map[string]any{ "enabled": true, "token": "disc-token-456", }, @@ -232,9 +232,9 @@ func TestConvertConfig(t *testing.T) { }) t.Run("unsupported channel warning", func(t *testing.T) { - data := map[string]interface{}{ - "channels": map[string]interface{}{ - "email": map[string]interface{}{ + data := map[string]any{ + "channels": map[string]any{ + "email": map[string]any{ "enabled": true, }, }, @@ -253,9 +253,9 @@ func TestConvertConfig(t *testing.T) { }) t.Run("agent defaults", func(t *testing.T) { - data := map[string]interface{}{ - "agents": map[string]interface{}{ - "defaults": map[string]interface{}{ + data := map[string]any{ + "agents": map[string]any{ + "defaults": map[string]any{ "model": "claude-3-opus", "max_tokens": float64(4096), "temperature": 0.5, @@ -284,7 +284,7 @@ func TestConvertConfig(t *testing.T) { }) t.Run("empty config", func(t *testing.T) { - data := map[string]interface{}{} + data := map[string]any{} cfg, warnings, err := ConvertConfig(data) if err != nil { @@ -576,9 +576,9 @@ func TestRunDryRun(t *testing.T) { os.WriteFile(filepath.Join(wsDir, "SOUL.md"), []byte("# Soul"), 0644) os.WriteFile(filepath.Join(wsDir, "AGENTS.md"), []byte("# Agents"), 0644) - configData := map[string]interface{}{ - "providers": map[string]interface{}{ - "anthropic": map[string]interface{}{ + configData := map[string]any{ + "providers": map[string]any{ + "anthropic": map[string]any{ "apiKey": "test-key", }, }, @@ -622,17 +622,17 @@ func TestRunFullMigration(t *testing.T) { os.MkdirAll(memDir, 0755) os.WriteFile(filepath.Join(memDir, "MEMORY.md"), []byte("# Memory notes"), 0644) - configData := map[string]interface{}{ - "providers": map[string]interface{}{ - "anthropic": map[string]interface{}{ + configData := map[string]any{ + "providers": map[string]any{ + "anthropic": map[string]any{ "apiKey": "sk-ant-migrate-test", }, - "openrouter": map[string]interface{}{ + "openrouter": map[string]any{ "apiKey": "sk-or-migrate-test", }, }, - "channels": map[string]interface{}{ - "telegram": map[string]interface{}{ + "channels": map[string]any{ + "telegram": map[string]any{ "enabled": true, "token": "tg-migrate-test", }, @@ -777,9 +777,9 @@ func TestRunConfigOnly(t *testing.T) { os.MkdirAll(wsDir, 0755) os.WriteFile(filepath.Join(wsDir, "SOUL.md"), []byte("# Soul"), 0644) - configData := map[string]interface{}{ - "providers": map[string]interface{}{ - "anthropic": map[string]interface{}{ + configData := map[string]any{ + "providers": map[string]any{ + "anthropic": map[string]any{ "apiKey": "sk-config-only", }, }, @@ -817,9 +817,9 @@ func TestRunWorkspaceOnly(t *testing.T) { os.MkdirAll(wsDir, 0755) os.WriteFile(filepath.Join(wsDir, "SOUL.md"), []byte("# Soul"), 0644) - configData := map[string]interface{}{ - "providers": map[string]interface{}{ - "anthropic": map[string]interface{}{ + configData := map[string]any{ + "providers": map[string]any{ + "anthropic": map[string]any{ "apiKey": "sk-ws-only", }, }, diff --git a/pkg/providers/claude_cli_provider.go b/pkg/providers/claude_cli_provider.go index a91795715..2be3fab0a 100644 --- a/pkg/providers/claude_cli_provider.go +++ b/pkg/providers/claude_cli_provider.go @@ -24,7 +24,7 @@ func NewClaudeCliProvider(workspace string) *ClaudeCliProvider { } // Chat implements LLMProvider.Chat by executing the claude CLI. -func (p *ClaudeCliProvider) Chat(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]interface{}) (*LLMResponse, error) { +func (p *ClaudeCliProvider) Chat(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]any) (*LLMResponse, error) { systemPrompt := p.buildSystemPrompt(messages, tools) prompt := p.messagesToPrompt(messages) @@ -202,7 +202,7 @@ func (p *ClaudeCliProvider) extractToolCalls(text string) []ToolCall { var result []ToolCall for _, tc := range wrapper.ToolCalls { - var args map[string]interface{} + var args map[string]any json.Unmarshal([]byte(tc.Function.Arguments), &args) result = append(result, ToolCall{ diff --git a/pkg/providers/claude_cli_provider_test.go b/pkg/providers/claude_cli_provider_test.go index 063530deb..35e264e06 100644 --- a/pkg/providers/claude_cli_provider_test.go +++ b/pkg/providers/claude_cli_provider_test.go @@ -611,10 +611,10 @@ func TestBuildSystemPrompt_WithTools(t *testing.T) { Function: ToolFunctionDefinition{ Name: "get_weather", Description: "Get weather for a location", - Parameters: map[string]interface{}{ + Parameters: map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "location": map[string]interface{}{"type": "string"}, + "properties": map[string]any{ + "location": map[string]any{"type": "string"}, }, }, }, diff --git a/pkg/providers/claude_provider.go b/pkg/providers/claude_provider.go index ae6aca96d..d0e62c839 100644 --- a/pkg/providers/claude_provider.go +++ b/pkg/providers/claude_provider.go @@ -29,7 +29,7 @@ func NewClaudeProviderWithTokenSource(token string, tokenSource func() (string, return p } -func (p *ClaudeProvider) Chat(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]interface{}) (*LLMResponse, error) { +func (p *ClaudeProvider) Chat(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]any) (*LLMResponse, error) { var opts []option.RequestOption if p.tokenSource != nil { tok, err := p.tokenSource() @@ -56,7 +56,7 @@ func (p *ClaudeProvider) GetDefaultModel() string { return "claude-sonnet-4-5-20250929" } -func buildClaudeParams(messages []Message, tools []ToolDefinition, model string, options map[string]interface{}) (anthropic.MessageNewParams, error) { +func buildClaudeParams(messages []Message, tools []ToolDefinition, model string, options map[string]any) (anthropic.MessageNewParams, error) { var system []anthropic.TextBlockParam var anthropicMessages []anthropic.MessageParam @@ -134,7 +134,7 @@ func translateToolsForClaude(tools []ToolDefinition) []anthropic.ToolUnionParam if desc := t.Function.Description; desc != "" { tool.Description = anthropic.String(desc) } - if req, ok := t.Function.Parameters["required"].([]interface{}); ok { + if req, ok := t.Function.Parameters["required"].([]any); ok { required := make([]string, 0, len(req)) for _, r := range req { if s, ok := r.(string); ok { @@ -159,9 +159,9 @@ func parseClaudeResponse(resp *anthropic.Message) *LLMResponse { content += tb.Text case "tool_use": tu := block.AsToolUse() - var args map[string]interface{} + var args map[string]any if err := json.Unmarshal(tu.Input, &args); err != nil { - args = map[string]interface{}{"raw": string(tu.Input)} + args = map[string]any{"raw": string(tu.Input)} } toolCalls = append(toolCalls, ToolCall{ ID: tu.ID, diff --git a/pkg/providers/claude_provider_test.go b/pkg/providers/claude_provider_test.go index bbad2d269..36184fe71 100644 --- a/pkg/providers/claude_provider_test.go +++ b/pkg/providers/claude_provider_test.go @@ -14,7 +14,7 @@ func TestBuildClaudeParams_BasicMessage(t *testing.T) { messages := []Message{ {Role: "user", Content: "Hello"}, } - params, err := buildClaudeParams(messages, nil, "claude-sonnet-4-5-20250929", map[string]interface{}{ + params, err := buildClaudeParams(messages, nil, "claude-sonnet-4-5-20250929", map[string]any{ "max_tokens": 1024, }) if err != nil { @@ -36,7 +36,7 @@ func TestBuildClaudeParams_SystemMessage(t *testing.T) { {Role: "system", Content: "You are helpful"}, {Role: "user", Content: "Hi"}, } - params, err := buildClaudeParams(messages, nil, "claude-sonnet-4-5-20250929", map[string]interface{}{}) + params, err := buildClaudeParams(messages, nil, "claude-sonnet-4-5-20250929", map[string]any{}) if err != nil { t.Fatalf("buildClaudeParams() error: %v", err) } @@ -61,13 +61,13 @@ func TestBuildClaudeParams_ToolCallMessage(t *testing.T) { { ID: "call_1", Name: "get_weather", - Arguments: map[string]interface{}{"city": "SF"}, + Arguments: map[string]any{"city": "SF"}, }, }, }, {Role: "tool", Content: `{"temp": 72}`, ToolCallID: "call_1"}, } - params, err := buildClaudeParams(messages, nil, "claude-sonnet-4-5-20250929", map[string]interface{}{}) + params, err := buildClaudeParams(messages, nil, "claude-sonnet-4-5-20250929", map[string]any{}) if err != nil { t.Fatalf("buildClaudeParams() error: %v", err) } @@ -83,17 +83,17 @@ func TestBuildClaudeParams_WithTools(t *testing.T) { Function: ToolFunctionDefinition{ Name: "get_weather", Description: "Get weather for a city", - Parameters: map[string]interface{}{ + Parameters: map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "city": map[string]interface{}{"type": "string"}, + "properties": map[string]any{ + "city": map[string]any{"type": "string"}, }, - "required": []interface{}{"city"}, + "required": []any{"city"}, }, }, }, } - params, err := buildClaudeParams([]Message{{Role: "user", Content: "Hi"}}, tools, "claude-sonnet-4-5-20250929", map[string]interface{}{}) + params, err := buildClaudeParams([]Message{{Role: "user", Content: "Hi"}}, tools, "claude-sonnet-4-5-20250929", map[string]any{}) if err != nil { t.Fatalf("buildClaudeParams() error: %v", err) } @@ -153,19 +153,19 @@ func TestClaudeProvider_ChatRoundTrip(t *testing.T) { return } - var reqBody map[string]interface{} + var reqBody map[string]any json.NewDecoder(r.Body).Decode(&reqBody) - resp := map[string]interface{}{ + resp := map[string]any{ "id": "msg_test", "type": "message", "role": "assistant", "model": reqBody["model"], "stop_reason": "end_turn", - "content": []map[string]interface{}{ + "content": []map[string]any{ {"type": "text", "text": "Hello! How can I help you?"}, }, - "usage": map[string]interface{}{ + "usage": map[string]any{ "input_tokens": 15, "output_tokens": 8, }, @@ -179,7 +179,7 @@ func TestClaudeProvider_ChatRoundTrip(t *testing.T) { provider.client = createAnthropicTestClient(server.URL, "test-token") messages := []Message{{Role: "user", Content: "Hello"}} - resp, err := provider.Chat(t.Context(), messages, nil, "claude-sonnet-4-5-20250929", map[string]interface{}{"max_tokens": 1024}) + resp, err := provider.Chat(t.Context(), messages, nil, "claude-sonnet-4-5-20250929", map[string]any{"max_tokens": 1024}) if err != nil { t.Fatalf("Chat() error: %v", err) } diff --git a/pkg/providers/codex_provider.go b/pkg/providers/codex_provider.go index c0b10bd5b..0ddf981da 100644 --- a/pkg/providers/codex_provider.go +++ b/pkg/providers/codex_provider.go @@ -41,7 +41,7 @@ func NewCodexProviderWithTokenSource(token, accountID string, tokenSource func() return p } -func (p *CodexProvider) Chat(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]interface{}) (*LLMResponse, error) { +func (p *CodexProvider) Chat(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]any) (*LLMResponse, error) { var opts []option.RequestOption if p.tokenSource != nil { tok, accID, err := p.tokenSource() @@ -68,7 +68,7 @@ func (p *CodexProvider) GetDefaultModel() string { return "gpt-4o" } -func buildCodexParams(messages []Message, tools []ToolDefinition, model string, options map[string]interface{}) responses.ResponseNewParams { +func buildCodexParams(messages []Message, tools []ToolDefinition, model string, options map[string]any) responses.ResponseNewParams { var inputItems responses.ResponseInputParam var instructions string @@ -189,9 +189,9 @@ func parseCodexResponse(resp *responses.Response) *LLMResponse { } } case "function_call": - var args map[string]interface{} + var args map[string]any if err := json.Unmarshal([]byte(item.Arguments), &args); err != nil { - args = map[string]interface{}{"raw": item.Arguments} + args = map[string]any{"raw": item.Arguments} } toolCalls = append(toolCalls, ToolCall{ ID: item.CallID, diff --git a/pkg/providers/codex_provider_test.go b/pkg/providers/codex_provider_test.go index 1a5a8cafa..3d034e8b3 100644 --- a/pkg/providers/codex_provider_test.go +++ b/pkg/providers/codex_provider_test.go @@ -15,7 +15,7 @@ func TestBuildCodexParams_BasicMessage(t *testing.T) { messages := []Message{ {Role: "user", Content: "Hello"}, } - params := buildCodexParams(messages, nil, "gpt-4o", map[string]interface{}{ + params := buildCodexParams(messages, nil, "gpt-4o", map[string]any{ "max_tokens": 2048, }) if params.Model != "gpt-4o" { @@ -34,7 +34,7 @@ func TestBuildCodexParams_SystemAsInstructions(t *testing.T) { {Role: "system", Content: "You are helpful"}, {Role: "user", Content: "Hi"}, } - params := buildCodexParams(messages, nil, "gpt-4o", map[string]interface{}{}) + params := buildCodexParams(messages, nil, "gpt-4o", map[string]any{}) if !params.Instructions.Valid() { t.Fatal("Instructions should be set") } @@ -49,12 +49,12 @@ func TestBuildCodexParams_ToolCallConversation(t *testing.T) { { Role: "assistant", ToolCalls: []ToolCall{ - {ID: "call_1", Name: "get_weather", Arguments: map[string]interface{}{"city": "SF"}}, + {ID: "call_1", Name: "get_weather", Arguments: map[string]any{"city": "SF"}}, }, }, {Role: "tool", Content: `{"temp": 72}`, ToolCallID: "call_1"}, } - params := buildCodexParams(messages, nil, "gpt-4o", map[string]interface{}{}) + params := buildCodexParams(messages, nil, "gpt-4o", map[string]any{}) if params.Input.OfInputItemList == nil { t.Fatal("Input.OfInputItemList should not be nil") } @@ -70,16 +70,16 @@ func TestBuildCodexParams_WithTools(t *testing.T) { Function: ToolFunctionDefinition{ Name: "get_weather", Description: "Get weather", - Parameters: map[string]interface{}{ + Parameters: map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "city": map[string]interface{}{"type": "string"}, + "properties": map[string]any{ + "city": map[string]any{"type": "string"}, }, }, }, }, } - params := buildCodexParams([]Message{{Role: "user", Content: "Hi"}}, tools, "gpt-4o", map[string]interface{}{}) + params := buildCodexParams([]Message{{Role: "user", Content: "Hi"}}, tools, "gpt-4o", map[string]any{}) if len(params.Tools) != 1 { t.Fatalf("len(Tools) = %d, want 1", len(params.Tools)) } @@ -92,7 +92,7 @@ func TestBuildCodexParams_WithTools(t *testing.T) { } func TestBuildCodexParams_StoreIsFalse(t *testing.T) { - params := buildCodexParams([]Message{{Role: "user", Content: "Hi"}}, nil, "gpt-4o", map[string]interface{}{}) + params := buildCodexParams([]Message{{Role: "user", Content: "Hi"}}, nil, "gpt-4o", map[string]any{}) if !params.Store.Valid() || params.Store.Or(true) != false { t.Error("Store should be explicitly set to false") } @@ -203,27 +203,27 @@ func TestCodexProvider_ChatRoundTrip(t *testing.T) { return } - resp := map[string]interface{}{ + resp := map[string]any{ "id": "resp_test", "object": "response", "status": "completed", - "output": []map[string]interface{}{ + "output": []map[string]any{ { "id": "msg_1", "type": "message", "role": "assistant", "status": "completed", - "content": []map[string]interface{}{ + "content": []map[string]any{ {"type": "output_text", "text": "Hi from Codex!"}, }, }, }, - "usage": map[string]interface{}{ + "usage": map[string]any{ "input_tokens": 12, "output_tokens": 6, "total_tokens": 18, - "input_tokens_details": map[string]interface{}{"cached_tokens": 0}, - "output_tokens_details": map[string]interface{}{"reasoning_tokens": 0}, + "input_tokens_details": map[string]any{"cached_tokens": 0}, + "output_tokens_details": map[string]any{"reasoning_tokens": 0}, }, } w.Header().Set("Content-Type", "application/json") @@ -235,7 +235,7 @@ func TestCodexProvider_ChatRoundTrip(t *testing.T) { provider.client = createOpenAITestClient(server.URL, "test-token", "acc-123") messages := []Message{{Role: "user", Content: "Hello"}} - resp, err := provider.Chat(t.Context(), messages, nil, "gpt-4o", map[string]interface{}{"max_tokens": 1024}) + resp, err := provider.Chat(t.Context(), messages, nil, "gpt-4o", map[string]any{"max_tokens": 1024}) if err != nil { t.Fatalf("Chat() error: %v", err) } diff --git a/pkg/providers/github_copilot_provider.go b/pkg/providers/github_copilot_provider.go index 5058819f5..4aa630098 100644 --- a/pkg/providers/github_copilot_provider.go +++ b/pkg/providers/github_copilot_provider.go @@ -49,7 +49,7 @@ func NewGitHubCopilotProvider(uri string, connectMode string, model string) (*Gi } // Chat sends a chat request to GitHub Copilot -func (p *GitHubCopilotProvider) Chat(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]interface{}) (*LLMResponse, error) { +func (p *GitHubCopilotProvider) Chat(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]any) (*LLMResponse, error) { type tempMessage struct { Role string `json:"role"` Content string `json:"content"` diff --git a/pkg/providers/http_provider.go b/pkg/providers/http_provider.go index 17eb6214c..cbd3ba8f8 100644 --- a/pkg/providers/http_provider.go +++ b/pkg/providers/http_provider.go @@ -48,7 +48,7 @@ func NewHTTPProvider(apiKey, apiBase, proxy string) *HTTPProvider { } } -func (p *HTTPProvider) Chat(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]interface{}) (*LLMResponse, error) { +func (p *HTTPProvider) Chat(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]any) (*LLMResponse, error) { if p.apiBase == "" { return nil, fmt.Errorf("API base not configured") } @@ -61,7 +61,7 @@ func (p *HTTPProvider) Chat(ctx context.Context, messages []Message, tools []Too } } - requestBody := map[string]interface{}{ + requestBody := map[string]any{ "model": model, "messages": messages, } @@ -157,7 +157,7 @@ func (p *HTTPProvider) parseResponse(body []byte) (*LLMResponse, error) { toolCalls := make([]ToolCall, 0, len(choice.Message.ToolCalls)) for _, tc := range choice.Message.ToolCalls { - arguments := make(map[string]interface{}) + arguments := make(map[string]any) name := "" // Handle OpenAI format with nested function object diff --git a/pkg/providers/types.go b/pkg/providers/types.go index 88b62e975..76b82da0c 100644 --- a/pkg/providers/types.go +++ b/pkg/providers/types.go @@ -3,11 +3,11 @@ package providers import "context" type ToolCall struct { - ID string `json:"id"` - Type string `json:"type,omitempty"` - Function *FunctionCall `json:"function,omitempty"` - Name string `json:"name,omitempty"` - Arguments map[string]interface{} `json:"arguments,omitempty"` + ID string `json:"id"` + Type string `json:"type,omitempty"` + Function *FunctionCall `json:"function,omitempty"` + Name string `json:"name,omitempty"` + Arguments map[string]any `json:"arguments,omitempty"` } type FunctionCall struct { @@ -36,7 +36,7 @@ type Message struct { } type LLMProvider interface { - Chat(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]interface{}) (*LLMResponse, error) + Chat(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]any) (*LLMResponse, error) GetDefaultModel() string } @@ -46,7 +46,7 @@ type ToolDefinition struct { } type ToolFunctionDefinition struct { - Name string `json:"name"` - Description string `json:"description"` - Parameters map[string]interface{} `json:"parameters"` + Name string `json:"name"` + Description string `json:"description"` + Parameters map[string]any `json:"parameters"` } diff --git a/pkg/tools/base.go b/pkg/tools/base.go index b13174633..770d8cb04 100644 --- a/pkg/tools/base.go +++ b/pkg/tools/base.go @@ -6,8 +6,8 @@ import "context" type Tool interface { Name() string Description() string - Parameters() map[string]interface{} - Execute(ctx context.Context, args map[string]interface{}) *ToolResult + Parameters() map[string]any + Execute(ctx context.Context, args map[string]any) *ToolResult } // ContextualTool is an optional interface that tools can implement @@ -69,10 +69,10 @@ type AsyncTool interface { SetCallback(cb AsyncCallback) } -func ToolToSchema(tool Tool) map[string]interface{} { - return map[string]interface{}{ +func ToolToSchema(tool Tool) map[string]any { + return map[string]any{ "type": "function", - "function": map[string]interface{}{ + "function": map[string]any{ "name": tool.Name(), "description": tool.Description(), "parameters": tool.Parameters(), diff --git a/pkg/tools/cron.go b/pkg/tools/cron.go index 0ef745e2b..f41ae94bf 100644 --- a/pkg/tools/cron.go +++ b/pkg/tools/cron.go @@ -48,40 +48,40 @@ func (t *CronTool) Description() string { } // Parameters returns the tool parameters schema -func (t *CronTool) Parameters() map[string]interface{} { - return map[string]interface{}{ +func (t *CronTool) Parameters() map[string]any { + return map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "action": map[string]interface{}{ + "properties": map[string]any{ + "action": map[string]any{ "type": "string", "enum": []string{"add", "list", "remove", "enable", "disable"}, "description": "Action to perform. Use 'add' when user wants to schedule a reminder or task.", }, - "message": map[string]interface{}{ + "message": map[string]any{ "type": "string", "description": "The reminder/task message to display when triggered. If 'command' is used, this describes what the command does.", }, - "command": map[string]interface{}{ + "command": map[string]any{ "type": "string", "description": "Optional: Shell command to execute directly (e.g., 'df -h'). If set, the agent will run this command and report output instead of just showing the message. 'deliver' will be forced to false for commands.", }, - "at_seconds": map[string]interface{}{ + "at_seconds": map[string]any{ "type": "integer", "description": "One-time reminder: seconds from now when to trigger (e.g., 600 for 10 minutes later). Use this for one-time reminders like 'remind me in 10 minutes'.", }, - "every_seconds": map[string]interface{}{ + "every_seconds": map[string]any{ "type": "integer", "description": "Recurring interval in seconds (e.g., 3600 for every hour). Use this ONLY for recurring tasks like 'every 2 hours' or 'daily reminder'.", }, - "cron_expr": map[string]interface{}{ + "cron_expr": map[string]any{ "type": "string", "description": "Cron expression for complex recurring schedules (e.g., '0 9 * * *' for daily at 9am). Use this for complex recurring schedules.", }, - "job_id": map[string]interface{}{ + "job_id": map[string]any{ "type": "string", "description": "Job ID (for remove/enable/disable)", }, - "deliver": map[string]interface{}{ + "deliver": map[string]any{ "type": "boolean", "description": "If true, send message directly to channel. If false, let agent process message (for complex tasks). Default: true", }, @@ -99,7 +99,7 @@ func (t *CronTool) SetContext(channel, chatID string) { } // Execute runs the tool with the given arguments -func (t *CronTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult { +func (t *CronTool) Execute(ctx context.Context, args map[string]any) *ToolResult { action, ok := args["action"].(string) if !ok { return ErrorResult("action is required") @@ -121,7 +121,7 @@ func (t *CronTool) Execute(ctx context.Context, args map[string]interface{}) *To } } -func (t *CronTool) addJob(args map[string]interface{}) *ToolResult { +func (t *CronTool) addJob(args map[string]any) *ToolResult { t.mu.RLock() channel := t.channel chatID := t.chatID @@ -229,7 +229,7 @@ func (t *CronTool) listJobs() *ToolResult { return SilentResult(result) } -func (t *CronTool) removeJob(args map[string]interface{}) *ToolResult { +func (t *CronTool) removeJob(args map[string]any) *ToolResult { jobID, ok := args["job_id"].(string) if !ok || jobID == "" { return ErrorResult("job_id is required for remove") @@ -241,7 +241,7 @@ func (t *CronTool) removeJob(args map[string]interface{}) *ToolResult { return ErrorResult(fmt.Sprintf("Job %s not found", jobID)) } -func (t *CronTool) enableJob(args map[string]interface{}, enable bool) *ToolResult { +func (t *CronTool) enableJob(args map[string]any, enable bool) *ToolResult { jobID, ok := args["job_id"].(string) if !ok || jobID == "" { return ErrorResult("job_id is required for enable/disable") @@ -275,7 +275,7 @@ func (t *CronTool) ExecuteJob(ctx context.Context, job *cron.CronJob) string { // Execute command if present if job.Payload.Command != "" { - args := map[string]interface{}{ + args := map[string]any{ "command": job.Payload.Command, } diff --git a/pkg/tools/edit.go b/pkg/tools/edit.go index 1e7c33b45..6445b70ce 100644 --- a/pkg/tools/edit.go +++ b/pkg/tools/edit.go @@ -30,19 +30,19 @@ func (t *EditFileTool) Description() string { return "Edit a file by replacing old_text with new_text. The old_text must exist exactly in the file." } -func (t *EditFileTool) Parameters() map[string]interface{} { - return map[string]interface{}{ +func (t *EditFileTool) Parameters() map[string]any { + return map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "path": map[string]interface{}{ + "properties": map[string]any{ + "path": map[string]any{ "type": "string", "description": "The file path to edit", }, - "old_text": map[string]interface{}{ + "old_text": map[string]any{ "type": "string", "description": "The exact text to find and replace", }, - "new_text": map[string]interface{}{ + "new_text": map[string]any{ "type": "string", "description": "The text to replace with", }, @@ -51,7 +51,7 @@ func (t *EditFileTool) Parameters() map[string]interface{} { } } -func (t *EditFileTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult { +func (t *EditFileTool) Execute(ctx context.Context, args map[string]any) *ToolResult { path, ok := args["path"].(string) if !ok { return ErrorResult("path is required") @@ -118,15 +118,15 @@ func (t *AppendFileTool) Description() string { return "Append content to the end of a file" } -func (t *AppendFileTool) Parameters() map[string]interface{} { - return map[string]interface{}{ +func (t *AppendFileTool) Parameters() map[string]any { + return map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "path": map[string]interface{}{ + "properties": map[string]any{ + "path": map[string]any{ "type": "string", "description": "The file path to append to", }, - "content": map[string]interface{}{ + "content": map[string]any{ "type": "string", "description": "The content to append", }, @@ -135,7 +135,7 @@ func (t *AppendFileTool) Parameters() map[string]interface{} { } } -func (t *AppendFileTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult { +func (t *AppendFileTool) Execute(ctx context.Context, args map[string]any) *ToolResult { path, ok := args["path"].(string) if !ok { return ErrorResult("path is required") diff --git a/pkg/tools/edit_test.go b/pkg/tools/edit_test.go index c4c02772d..d597e74e8 100644 --- a/pkg/tools/edit_test.go +++ b/pkg/tools/edit_test.go @@ -16,7 +16,7 @@ func TestEditTool_EditFile_Success(t *testing.T) { tool := NewEditFileTool(tmpDir, true) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "path": testFile, "old_text": "World", "new_text": "Universe", @@ -60,7 +60,7 @@ func TestEditTool_EditFile_NotFound(t *testing.T) { tool := NewEditFileTool(tmpDir, true) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "path": testFile, "old_text": "old", "new_text": "new", @@ -87,7 +87,7 @@ func TestEditTool_EditFile_OldTextNotFound(t *testing.T) { tool := NewEditFileTool(tmpDir, true) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "path": testFile, "old_text": "Goodbye", "new_text": "Hello", @@ -114,7 +114,7 @@ func TestEditTool_EditFile_MultipleMatches(t *testing.T) { tool := NewEditFileTool(tmpDir, true) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "path": testFile, "old_text": "test", "new_text": "done", @@ -142,7 +142,7 @@ func TestEditTool_EditFile_OutsideAllowedDir(t *testing.T) { tool := NewEditFileTool(tmpDir, true) // Restrict to tmpDir ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "path": testFile, "old_text": "content", "new_text": "new", @@ -165,7 +165,7 @@ func TestEditTool_EditFile_OutsideAllowedDir(t *testing.T) { func TestEditTool_EditFile_MissingPath(t *testing.T) { tool := NewEditFileTool("", false) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "old_text": "old", "new_text": "new", } @@ -182,7 +182,7 @@ func TestEditTool_EditFile_MissingPath(t *testing.T) { func TestEditTool_EditFile_MissingOldText(t *testing.T) { tool := NewEditFileTool("", false) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "path": "/tmp/test.txt", "new_text": "new", } @@ -199,7 +199,7 @@ func TestEditTool_EditFile_MissingOldText(t *testing.T) { func TestEditTool_EditFile_MissingNewText(t *testing.T) { tool := NewEditFileTool("", false) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "path": "/tmp/test.txt", "old_text": "old", } @@ -220,7 +220,7 @@ func TestEditTool_AppendFile_Success(t *testing.T) { tool := NewAppendFileTool("", false) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "path": testFile, "content": "\nAppended content", } @@ -260,7 +260,7 @@ func TestEditTool_AppendFile_Success(t *testing.T) { func TestEditTool_AppendFile_MissingPath(t *testing.T) { tool := NewAppendFileTool("", false) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "content": "test", } @@ -276,7 +276,7 @@ func TestEditTool_AppendFile_MissingPath(t *testing.T) { func TestEditTool_AppendFile_MissingContent(t *testing.T) { tool := NewAppendFileTool("", false) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "path": "/tmp/test.txt", } diff --git a/pkg/tools/filesystem.go b/pkg/tools/filesystem.go index 237687734..5ba97f33f 100644 --- a/pkg/tools/filesystem.go +++ b/pkg/tools/filesystem.go @@ -53,11 +53,11 @@ func (t *ReadFileTool) Description() string { return "Read the contents of a file" } -func (t *ReadFileTool) Parameters() map[string]interface{} { - return map[string]interface{}{ +func (t *ReadFileTool) Parameters() map[string]any { + return map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "path": map[string]interface{}{ + "properties": map[string]any{ + "path": map[string]any{ "type": "string", "description": "Path to the file to read", }, @@ -66,7 +66,7 @@ func (t *ReadFileTool) Parameters() map[string]interface{} { } } -func (t *ReadFileTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult { +func (t *ReadFileTool) Execute(ctx context.Context, args map[string]any) *ToolResult { path, ok := args["path"].(string) if !ok { return ErrorResult("path is required") @@ -102,15 +102,15 @@ func (t *WriteFileTool) Description() string { return "Write content to a file" } -func (t *WriteFileTool) Parameters() map[string]interface{} { - return map[string]interface{}{ +func (t *WriteFileTool) Parameters() map[string]any { + return map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "path": map[string]interface{}{ + "properties": map[string]any{ + "path": map[string]any{ "type": "string", "description": "Path to the file to write", }, - "content": map[string]interface{}{ + "content": map[string]any{ "type": "string", "description": "Content to write to the file", }, @@ -119,7 +119,7 @@ func (t *WriteFileTool) Parameters() map[string]interface{} { } } -func (t *WriteFileTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult { +func (t *WriteFileTool) Execute(ctx context.Context, args map[string]any) *ToolResult { path, ok := args["path"].(string) if !ok { return ErrorResult("path is required") @@ -164,11 +164,11 @@ func (t *ListDirTool) Description() string { return "List files and directories in a path" } -func (t *ListDirTool) Parameters() map[string]interface{} { - return map[string]interface{}{ +func (t *ListDirTool) Parameters() map[string]any { + return map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "path": map[string]interface{}{ + "properties": map[string]any{ + "path": map[string]any{ "type": "string", "description": "Path to list", }, @@ -177,7 +177,7 @@ func (t *ListDirTool) Parameters() map[string]interface{} { } } -func (t *ListDirTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult { +func (t *ListDirTool) Execute(ctx context.Context, args map[string]any) *ToolResult { path, ok := args["path"].(string) if !ok { path = "." diff --git a/pkg/tools/filesystem_test.go b/pkg/tools/filesystem_test.go index 2707f29b5..5f69a29fa 100644 --- a/pkg/tools/filesystem_test.go +++ b/pkg/tools/filesystem_test.go @@ -16,7 +16,7 @@ func TestFilesystemTool_ReadFile_Success(t *testing.T) { tool := &ReadFileTool{} ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "path": testFile, } @@ -43,7 +43,7 @@ func TestFilesystemTool_ReadFile_Success(t *testing.T) { func TestFilesystemTool_ReadFile_NotFound(t *testing.T) { tool := &ReadFileTool{} ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "path": "/nonexistent_file_12345.txt", } @@ -64,7 +64,7 @@ func TestFilesystemTool_ReadFile_NotFound(t *testing.T) { func TestFilesystemTool_ReadFile_MissingPath(t *testing.T) { tool := &ReadFileTool{} ctx := context.Background() - args := map[string]interface{}{} + args := map[string]any{} result := tool.Execute(ctx, args) @@ -86,7 +86,7 @@ func TestFilesystemTool_WriteFile_Success(t *testing.T) { tool := &WriteFileTool{} ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "path": testFile, "content": "hello world", } @@ -125,7 +125,7 @@ func TestFilesystemTool_WriteFile_CreateDir(t *testing.T) { tool := &WriteFileTool{} ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "path": testFile, "content": "test", } @@ -151,7 +151,7 @@ func TestFilesystemTool_WriteFile_CreateDir(t *testing.T) { func TestFilesystemTool_WriteFile_MissingPath(t *testing.T) { tool := &WriteFileTool{} ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "content": "test", } @@ -167,7 +167,7 @@ func TestFilesystemTool_WriteFile_MissingPath(t *testing.T) { func TestFilesystemTool_WriteFile_MissingContent(t *testing.T) { tool := &WriteFileTool{} ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "path": "/tmp/test.txt", } @@ -193,7 +193,7 @@ func TestFilesystemTool_ListDir_Success(t *testing.T) { tool := &ListDirTool{} ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "path": tmpDir, } @@ -217,7 +217,7 @@ func TestFilesystemTool_ListDir_Success(t *testing.T) { func TestFilesystemTool_ListDir_NotFound(t *testing.T) { tool := &ListDirTool{} ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "path": "/nonexistent_directory_12345", } @@ -238,7 +238,7 @@ func TestFilesystemTool_ListDir_NotFound(t *testing.T) { func TestFilesystemTool_ListDir_DefaultPath(t *testing.T) { tool := &ListDirTool{} ctx := context.Background() - args := map[string]interface{}{} + args := map[string]any{} result := tool.Execute(ctx, args) diff --git a/pkg/tools/i2c.go b/pkg/tools/i2c.go index abca5ec1e..32b720718 100644 --- a/pkg/tools/i2c.go +++ b/pkg/tools/i2c.go @@ -24,37 +24,37 @@ func (t *I2CTool) Description() string { return "Interact with I2C bus devices for reading sensors and controlling peripherals. Actions: detect (list buses), scan (find devices on a bus), read (read bytes from device), write (send bytes to device). Linux only." } -func (t *I2CTool) Parameters() map[string]interface{} { - return map[string]interface{}{ +func (t *I2CTool) Parameters() map[string]any { + return map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "action": map[string]interface{}{ + "properties": map[string]any{ + "action": map[string]any{ "type": "string", "enum": []string{"detect", "scan", "read", "write"}, "description": "Action to perform: detect (list available I2C buses), scan (find devices on a bus), read (read bytes from a device), write (send bytes to a device)", }, - "bus": map[string]interface{}{ + "bus": map[string]any{ "type": "string", "description": "I2C bus number (e.g. \"1\" for /dev/i2c-1). Required for scan/read/write.", }, - "address": map[string]interface{}{ + "address": map[string]any{ "type": "integer", "description": "7-bit I2C device address (0x03-0x77). Required for read/write.", }, - "register": map[string]interface{}{ + "register": map[string]any{ "type": "integer", "description": "Register address to read from or write to. If set, sends register byte before read/write.", }, - "data": map[string]interface{}{ + "data": map[string]any{ "type": "array", - "items": map[string]interface{}{"type": "integer"}, + "items": map[string]any{"type": "integer"}, "description": "Bytes to write (0-255 each). Required for write action.", }, - "length": map[string]interface{}{ + "length": map[string]any{ "type": "integer", "description": "Number of bytes to read (1-256). Default: 1. Used with read action.", }, - "confirm": map[string]interface{}{ + "confirm": map[string]any{ "type": "boolean", "description": "Must be true for write operations. Safety guard to prevent accidental writes.", }, @@ -63,7 +63,7 @@ func (t *I2CTool) Parameters() map[string]interface{} { } } -func (t *I2CTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult { +func (t *I2CTool) Execute(ctx context.Context, args map[string]any) *ToolResult { if runtime.GOOS != "linux" { return ErrorResult("I2C is only supported on Linux. This tool requires /dev/i2c-* device files.") } @@ -122,7 +122,7 @@ func isValidBusID(id string) bool { } // parseI2CAddress extracts and validates an I2C address from args -func parseI2CAddress(args map[string]interface{}) (int, *ToolResult) { +func parseI2CAddress(args map[string]any) (int, *ToolResult) { addrFloat, ok := args["address"].(float64) if !ok { return 0, ErrorResult("address is required (e.g. 0x38 for AHT20)") @@ -135,7 +135,7 @@ func parseI2CAddress(args map[string]interface{}) (int, *ToolResult) { } // parseI2CBus extracts and validates an I2C bus from args -func parseI2CBus(args map[string]interface{}) (string, *ToolResult) { +func parseI2CBus(args map[string]any) (string, *ToolResult) { bus, ok := args["bus"].(string) if !ok || bus == "" { return "", ErrorResult("bus is required (e.g. \"1\" for /dev/i2c-1)") diff --git a/pkg/tools/i2c_linux.go b/pkg/tools/i2c_linux.go index 294f7ecbc..c7ea46433 100644 --- a/pkg/tools/i2c_linux.go +++ b/pkg/tools/i2c_linux.go @@ -74,7 +74,7 @@ func smbusProbe(fd int, addr int, hasQuick bool) bool { // scan probes valid 7-bit addresses on a bus for connected devices. // Uses the same hybrid probe strategy as i2cdetect's MODE_AUTO: // SMBus Quick Write for most addresses, SMBus Read Byte for EEPROM ranges. -func (t *I2CTool) scan(args map[string]interface{}) *ToolResult { +func (t *I2CTool) scan(args map[string]any) *ToolResult { bus, errResult := parseI2CBus(args) if errResult != nil { return errResult @@ -133,7 +133,7 @@ func (t *I2CTool) scan(args map[string]interface{}) *ToolResult { return SilentResult(fmt.Sprintf("No devices found on %s. Check wiring and pull-up resistors.", devPath)) } - result, _ := json.MarshalIndent(map[string]interface{}{ + result, _ := json.MarshalIndent(map[string]any{ "bus": devPath, "devices": found, "count": len(found), @@ -142,7 +142,7 @@ func (t *I2CTool) scan(args map[string]interface{}) *ToolResult { } // readDevice reads bytes from an I2C device, optionally at a specific register -func (t *I2CTool) readDevice(args map[string]interface{}) *ToolResult { +func (t *I2CTool) readDevice(args map[string]any) *ToolResult { bus, errResult := parseI2CBus(args) if errResult != nil { return errResult @@ -201,7 +201,7 @@ func (t *I2CTool) readDevice(args map[string]interface{}) *ToolResult { intBytes[i] = int(buf[i]) } - result, _ := json.MarshalIndent(map[string]interface{}{ + result, _ := json.MarshalIndent(map[string]any{ "bus": devPath, "address": fmt.Sprintf("0x%02x", addr), "bytes": intBytes, @@ -212,7 +212,7 @@ func (t *I2CTool) readDevice(args map[string]interface{}) *ToolResult { } // writeDevice writes bytes to an I2C device, optionally at a specific register -func (t *I2CTool) writeDevice(args map[string]interface{}) *ToolResult { +func (t *I2CTool) writeDevice(args map[string]any) *ToolResult { confirm, _ := args["confirm"].(bool) if !confirm { return ErrorResult("write operations require confirm: true. Please confirm with the user before writing to I2C devices, as incorrect writes can misconfigure hardware.") @@ -228,7 +228,7 @@ func (t *I2CTool) writeDevice(args map[string]interface{}) *ToolResult { return errResult } - dataRaw, ok := args["data"].([]interface{}) + dataRaw, ok := args["data"].([]any) if !ok || len(dataRaw) == 0 { return ErrorResult("data is required for write (array of byte values 0-255)") } diff --git a/pkg/tools/i2c_other.go b/pkg/tools/i2c_other.go index d1d581348..7becf8339 100644 --- a/pkg/tools/i2c_other.go +++ b/pkg/tools/i2c_other.go @@ -3,16 +3,16 @@ package tools // scan is a stub for non-Linux platforms. -func (t *I2CTool) scan(args map[string]interface{}) *ToolResult { +func (t *I2CTool) scan(args map[string]any) *ToolResult { return ErrorResult("I2C is only supported on Linux") } // readDevice is a stub for non-Linux platforms. -func (t *I2CTool) readDevice(args map[string]interface{}) *ToolResult { +func (t *I2CTool) readDevice(args map[string]any) *ToolResult { return ErrorResult("I2C is only supported on Linux") } // writeDevice is a stub for non-Linux platforms. -func (t *I2CTool) writeDevice(args map[string]interface{}) *ToolResult { +func (t *I2CTool) writeDevice(args map[string]any) *ToolResult { return ErrorResult("I2C is only supported on Linux") } diff --git a/pkg/tools/message.go b/pkg/tools/message.go index abedb1316..15ef4ff73 100644 --- a/pkg/tools/message.go +++ b/pkg/tools/message.go @@ -26,19 +26,19 @@ func (t *MessageTool) Description() string { return "Send a message to user on a chat channel. Use this when you want to communicate something." } -func (t *MessageTool) Parameters() map[string]interface{} { - return map[string]interface{}{ +func (t *MessageTool) Parameters() map[string]any { + return map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "content": map[string]interface{}{ + "properties": map[string]any{ + "content": map[string]any{ "type": "string", "description": "The message content to send", }, - "channel": map[string]interface{}{ + "channel": map[string]any{ "type": "string", "description": "Optional: target channel (telegram, whatsapp, etc.)", }, - "chat_id": map[string]interface{}{ + "chat_id": map[string]any{ "type": "string", "description": "Optional: target chat/user ID", }, @@ -62,7 +62,7 @@ func (t *MessageTool) SetSendCallback(callback SendCallback) { t.sendCallback = callback } -func (t *MessageTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult { +func (t *MessageTool) Execute(ctx context.Context, args map[string]any) *ToolResult { content, ok := args["content"].(string) if !ok { return &ToolResult{ForLLM: "content is required", IsError: true} diff --git a/pkg/tools/message_test.go b/pkg/tools/message_test.go index 4bedbe79b..717c1117b 100644 --- a/pkg/tools/message_test.go +++ b/pkg/tools/message_test.go @@ -19,7 +19,7 @@ func TestMessageTool_Execute_Success(t *testing.T) { }) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "content": "Hello, world!", } @@ -70,7 +70,7 @@ func TestMessageTool_Execute_WithCustomChannel(t *testing.T) { }) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "content": "Test message", "channel": "custom-channel", "chat_id": "custom-chat-id", @@ -104,7 +104,7 @@ func TestMessageTool_Execute_SendFailure(t *testing.T) { }) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "content": "Test message", } @@ -136,7 +136,7 @@ func TestMessageTool_Execute_MissingContent(t *testing.T) { tool.SetContext("test-channel", "test-chat-id") ctx := context.Background() - args := map[string]interface{}{} // content missing + args := map[string]any{} // content missing result := tool.Execute(ctx, args) @@ -158,7 +158,7 @@ func TestMessageTool_Execute_NoTargetChannel(t *testing.T) { }) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "content": "Test message", } @@ -179,7 +179,7 @@ func TestMessageTool_Execute_NotConfigured(t *testing.T) { // No SetSendCallback called ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "content": "Test message", } @@ -219,7 +219,7 @@ func TestMessageTool_Parameters(t *testing.T) { t.Error("Expected type 'object'") } - props, ok := params["properties"].(map[string]interface{}) + props, ok := params["properties"].(map[string]any) if !ok { t.Fatal("Expected properties to be a map") } @@ -231,7 +231,7 @@ func TestMessageTool_Parameters(t *testing.T) { } // Check content property - contentProp, ok := props["content"].(map[string]interface{}) + contentProp, ok := props["content"].(map[string]any) if !ok { t.Error("Expected 'content' property") } @@ -240,7 +240,7 @@ func TestMessageTool_Parameters(t *testing.T) { } // Check channel property (optional) - channelProp, ok := props["channel"].(map[string]interface{}) + channelProp, ok := props["channel"].(map[string]any) if !ok { t.Error("Expected 'channel' property") } @@ -249,7 +249,7 @@ func TestMessageTool_Parameters(t *testing.T) { } // Check chat_id property (optional) - chatIDProp, ok := props["chat_id"].(map[string]interface{}) + chatIDProp, ok := props["chat_id"].(map[string]any) if !ok { t.Error("Expected 'chat_id' property") } diff --git a/pkg/tools/registry.go b/pkg/tools/registry.go index c8cf92863..26372792f 100644 --- a/pkg/tools/registry.go +++ b/pkg/tools/registry.go @@ -34,16 +34,16 @@ func (r *ToolRegistry) Get(name string) (Tool, bool) { return tool, ok } -func (r *ToolRegistry) Execute(ctx context.Context, name string, args map[string]interface{}) *ToolResult { +func (r *ToolRegistry) Execute(ctx context.Context, name string, args map[string]any) *ToolResult { return r.ExecuteWithContext(ctx, name, args, "", "", nil) } // ExecuteWithContext executes a tool with channel/chatID context and optional async callback. // If the tool implements AsyncTool and a non-nil callback is provided, // the callback will be set on the tool before execution. -func (r *ToolRegistry) ExecuteWithContext(ctx context.Context, name string, args map[string]interface{}, channel, chatID string, asyncCallback AsyncCallback) *ToolResult { +func (r *ToolRegistry) ExecuteWithContext(ctx context.Context, name string, args map[string]any, channel, chatID string, asyncCallback AsyncCallback) *ToolResult { logger.InfoCF("tool", "Tool execution started", - map[string]interface{}{ + map[string]any{ "tool": name, "args": args, }) @@ -51,7 +51,7 @@ func (r *ToolRegistry) ExecuteWithContext(ctx context.Context, name string, args tool, ok := r.Get(name) if !ok { logger.ErrorCF("tool", "Tool not found", - map[string]interface{}{ + map[string]any{ "tool": name, }) return ErrorResult(fmt.Sprintf("tool %q not found", name)).WithError(fmt.Errorf("tool not found")) @@ -66,7 +66,7 @@ func (r *ToolRegistry) ExecuteWithContext(ctx context.Context, name string, args if asyncTool, ok := tool.(AsyncTool); ok && asyncCallback != nil { asyncTool.SetCallback(asyncCallback) logger.DebugCF("tool", "Async callback injected", - map[string]interface{}{ + map[string]any{ "tool": name, }) } @@ -78,20 +78,20 @@ func (r *ToolRegistry) ExecuteWithContext(ctx context.Context, name string, args // Log based on result type if result.IsError { logger.ErrorCF("tool", "Tool execution failed", - map[string]interface{}{ + map[string]any{ "tool": name, "duration": duration.Milliseconds(), "error": result.ForLLM, }) } else if result.Async { logger.InfoCF("tool", "Tool started (async)", - map[string]interface{}{ + map[string]any{ "tool": name, "duration": duration.Milliseconds(), }) } else { logger.InfoCF("tool", "Tool execution completed", - map[string]interface{}{ + map[string]any{ "tool": name, "duration_ms": duration.Milliseconds(), "result_length": len(result.ForLLM), @@ -101,11 +101,11 @@ func (r *ToolRegistry) ExecuteWithContext(ctx context.Context, name string, args return result } -func (r *ToolRegistry) GetDefinitions() []map[string]interface{} { +func (r *ToolRegistry) GetDefinitions() []map[string]any { r.mu.RLock() defer r.mu.RUnlock() - definitions := make([]map[string]interface{}, 0, len(r.tools)) + definitions := make([]map[string]any, 0, len(r.tools)) for _, tool := range r.tools { definitions = append(definitions, ToolToSchema(tool)) } @@ -123,14 +123,14 @@ func (r *ToolRegistry) ToProviderDefs() []providers.ToolDefinition { schema := ToolToSchema(tool) // Safely extract nested values with type checks - fn, ok := schema["function"].(map[string]interface{}) + fn, ok := schema["function"].(map[string]any) if !ok { continue } name, _ := fn["name"].(string) desc, _ := fn["description"].(string) - params, _ := fn["parameters"].(map[string]interface{}) + params, _ := fn["parameters"].(map[string]any) definitions = append(definitions, providers.ToolDefinition{ Type: "function", diff --git a/pkg/tools/result_test.go b/pkg/tools/result_test.go index bc798cd70..a234e33f3 100644 --- a/pkg/tools/result_test.go +++ b/pkg/tools/result_test.go @@ -192,7 +192,7 @@ func TestToolResultJSONStructure(t *testing.T) { } // Verify JSON structure - var parsed map[string]interface{} + var parsed map[string]any if err := json.Unmarshal(data, &parsed); err != nil { t.Fatalf("Failed to parse JSON: %v", err) } diff --git a/pkg/tools/shell.go b/pkg/tools/shell.go index 1ca3fc35a..7a47a9c06 100644 --- a/pkg/tools/shell.go +++ b/pkg/tools/shell.go @@ -50,15 +50,15 @@ func (t *ExecTool) Description() string { return "Execute a shell command and return its output. Use with caution." } -func (t *ExecTool) Parameters() map[string]interface{} { - return map[string]interface{}{ +func (t *ExecTool) Parameters() map[string]any { + return map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "command": map[string]interface{}{ + "properties": map[string]any{ + "command": map[string]any{ "type": "string", "description": "The shell command to execute", }, - "working_dir": map[string]interface{}{ + "working_dir": map[string]any{ "type": "string", "description": "Optional working directory for the command", }, @@ -67,7 +67,7 @@ func (t *ExecTool) Parameters() map[string]interface{} { } } -func (t *ExecTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult { +func (t *ExecTool) Execute(ctx context.Context, args map[string]any) *ToolResult { command, ok := args["command"].(string) if !ok { return ErrorResult("command is required") diff --git a/pkg/tools/shell_test.go b/pkg/tools/shell_test.go index c06468a39..5db36e598 100644 --- a/pkg/tools/shell_test.go +++ b/pkg/tools/shell_test.go @@ -14,7 +14,7 @@ func TestShellTool_Success(t *testing.T) { tool := NewExecTool("", false) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "command": "echo 'hello world'", } @@ -41,7 +41,7 @@ func TestShellTool_Failure(t *testing.T) { tool := NewExecTool("", false) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "command": "ls /nonexistent_directory_12345", } @@ -69,7 +69,7 @@ func TestShellTool_Timeout(t *testing.T) { tool.SetTimeout(100 * time.Millisecond) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "command": "sleep 10", } @@ -96,7 +96,7 @@ func TestShellTool_WorkingDir(t *testing.T) { tool := NewExecTool("", false) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "command": "cat test.txt", "working_dir": tmpDir, } @@ -117,7 +117,7 @@ func TestShellTool_DangerousCommand(t *testing.T) { tool := NewExecTool("", false) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "command": "rm -rf /", } @@ -138,7 +138,7 @@ func TestShellTool_MissingCommand(t *testing.T) { tool := NewExecTool("", false) ctx := context.Background() - args := map[string]interface{}{} + args := map[string]any{} result := tool.Execute(ctx, args) @@ -153,7 +153,7 @@ func TestShellTool_StderrCapture(t *testing.T) { tool := NewExecTool("", false) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "command": "sh -c 'echo stdout; echo stderr >&2'", } @@ -174,7 +174,7 @@ func TestShellTool_OutputTruncation(t *testing.T) { ctx := context.Background() // Generate long output (>10000 chars) - args := map[string]interface{}{ + args := map[string]any{ "command": "python3 -c \"print('x' * 20000)\" || echo " + strings.Repeat("x", 20000), } @@ -193,7 +193,7 @@ func TestShellTool_RestrictToWorkspace(t *testing.T) { tool.SetRestrictToWorkspace(true) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "command": "cat ../../etc/passwd", } diff --git a/pkg/tools/spawn.go b/pkg/tools/spawn.go index 42dd36a33..6b4360a67 100644 --- a/pkg/tools/spawn.go +++ b/pkg/tools/spawn.go @@ -33,15 +33,15 @@ func (t *SpawnTool) Description() string { return "Spawn a subagent to handle a task in the background. Use this for complex or time-consuming tasks that can run independently. The subagent will complete the task and report back when done." } -func (t *SpawnTool) Parameters() map[string]interface{} { - return map[string]interface{}{ +func (t *SpawnTool) Parameters() map[string]any { + return map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "task": map[string]interface{}{ + "properties": map[string]any{ + "task": map[string]any{ "type": "string", "description": "The task for subagent to complete", }, - "label": map[string]interface{}{ + "label": map[string]any{ "type": "string", "description": "Optional short label for the task (for display)", }, @@ -55,7 +55,7 @@ func (t *SpawnTool) SetContext(channel, chatID string) { t.originChatID = chatID } -func (t *SpawnTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult { +func (t *SpawnTool) Execute(ctx context.Context, args map[string]any) *ToolResult { task, ok := args["task"].(string) if !ok { return ErrorResult("task is required") diff --git a/pkg/tools/spi.go b/pkg/tools/spi.go index 4805d6a35..a3d8ba20b 100644 --- a/pkg/tools/spi.go +++ b/pkg/tools/spi.go @@ -24,41 +24,41 @@ func (t *SPITool) Description() string { return "Interact with SPI bus devices for high-speed peripheral communication. Actions: list (find SPI devices), transfer (full-duplex send/receive), read (receive bytes). Linux only." } -func (t *SPITool) Parameters() map[string]interface{} { - return map[string]interface{}{ +func (t *SPITool) Parameters() map[string]any { + return map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "action": map[string]interface{}{ + "properties": map[string]any{ + "action": map[string]any{ "type": "string", "enum": []string{"list", "transfer", "read"}, "description": "Action to perform: list (find available SPI devices), transfer (full-duplex send/receive), read (receive bytes by sending zeros)", }, - "device": map[string]interface{}{ + "device": map[string]any{ "type": "string", "description": "SPI device identifier (e.g. \"2.0\" for /dev/spidev2.0). Required for transfer/read.", }, - "speed": map[string]interface{}{ + "speed": map[string]any{ "type": "integer", "description": "SPI clock speed in Hz. Default: 1000000 (1 MHz).", }, - "mode": map[string]interface{}{ + "mode": map[string]any{ "type": "integer", "description": "SPI mode (0-3). Default: 0. Mode sets CPOL and CPHA: 0=0,0 1=0,1 2=1,0 3=1,1.", }, - "bits": map[string]interface{}{ + "bits": map[string]any{ "type": "integer", "description": "Bits per word. Default: 8.", }, - "data": map[string]interface{}{ + "data": map[string]any{ "type": "array", - "items": map[string]interface{}{"type": "integer"}, + "items": map[string]any{"type": "integer"}, "description": "Bytes to send (0-255 each). Required for transfer action.", }, - "length": map[string]interface{}{ + "length": map[string]any{ "type": "integer", "description": "Number of bytes to read (1-4096). Required for read action.", }, - "confirm": map[string]interface{}{ + "confirm": map[string]any{ "type": "boolean", "description": "Must be true for transfer operations. Safety guard to prevent accidental writes.", }, @@ -67,7 +67,7 @@ func (t *SPITool) Parameters() map[string]interface{} { } } -func (t *SPITool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult { +func (t *SPITool) Execute(ctx context.Context, args map[string]any) *ToolResult { if runtime.GOOS != "linux" { return ErrorResult("SPI is only supported on Linux. This tool requires /dev/spidev* device files.") } @@ -118,7 +118,7 @@ func (t *SPITool) list() *ToolResult { } // parseSPIArgs extracts and validates common SPI parameters -func parseSPIArgs(args map[string]interface{}) (device string, speed uint32, mode uint8, bits uint8, errMsg string) { +func parseSPIArgs(args map[string]any) (device string, speed uint32, mode uint8, bits uint8, errMsg string) { dev, ok := args["device"].(string) if !ok || dev == "" { return "", 0, 0, 0, "device is required (e.g. \"2.0\" for /dev/spidev2.0)" diff --git a/pkg/tools/spi_linux.go b/pkg/tools/spi_linux.go index 12b696007..8ef678160 100644 --- a/pkg/tools/spi_linux.go +++ b/pkg/tools/spi_linux.go @@ -66,7 +66,7 @@ func configureSPI(devPath string, mode uint8, bits uint8, speed uint32) (int, *T } // transfer performs a full-duplex SPI transfer -func (t *SPITool) transfer(args map[string]interface{}) *ToolResult { +func (t *SPITool) transfer(args map[string]any) *ToolResult { confirm, _ := args["confirm"].(bool) if !confirm { return ErrorResult("transfer operations require confirm: true. Please confirm with the user before sending data to SPI devices.") @@ -77,7 +77,7 @@ func (t *SPITool) transfer(args map[string]interface{}) *ToolResult { return ErrorResult(errMsg) } - dataRaw, ok := args["data"].([]interface{}) + dataRaw, ok := args["data"].([]any) if !ok || len(dataRaw) == 0 { return ErrorResult("data is required for transfer (array of byte values 0-255)") } @@ -130,7 +130,7 @@ func (t *SPITool) transfer(args map[string]interface{}) *ToolResult { intBytes[i] = int(b) } - result, _ := json.MarshalIndent(map[string]interface{}{ + result, _ := json.MarshalIndent(map[string]any{ "device": devPath, "sent": len(txBuf), "received": intBytes, @@ -140,7 +140,7 @@ func (t *SPITool) transfer(args map[string]interface{}) *ToolResult { } // readDevice reads bytes from SPI by sending zeros (read-only, no confirm needed) -func (t *SPITool) readDevice(args map[string]interface{}) *ToolResult { +func (t *SPITool) readDevice(args map[string]any) *ToolResult { dev, speed, mode, bits, errMsg := parseSPIArgs(args) if errMsg != "" { return ErrorResult(errMsg) @@ -186,7 +186,7 @@ func (t *SPITool) readDevice(args map[string]interface{}) *ToolResult { intBytes[i] = int(b) } - result, _ := json.MarshalIndent(map[string]interface{}{ + result, _ := json.MarshalIndent(map[string]any{ "device": devPath, "bytes": intBytes, "hex": hexBytes, diff --git a/pkg/tools/spi_other.go b/pkg/tools/spi_other.go index 6dfc86fd1..5d078ac3f 100644 --- a/pkg/tools/spi_other.go +++ b/pkg/tools/spi_other.go @@ -3,11 +3,11 @@ package tools // transfer is a stub for non-Linux platforms. -func (t *SPITool) transfer(args map[string]interface{}) *ToolResult { +func (t *SPITool) transfer(args map[string]any) *ToolResult { return ErrorResult("SPI is only supported on Linux") } // readDevice is a stub for non-Linux platforms. -func (t *SPITool) readDevice(args map[string]interface{}) *ToolResult { +func (t *SPITool) readDevice(args map[string]any) *ToolResult { return ErrorResult("SPI is only supported on Linux") } diff --git a/pkg/tools/subagent.go b/pkg/tools/subagent.go index efa1d33aa..d0f2567d3 100644 --- a/pkg/tools/subagent.go +++ b/pkg/tools/subagent.go @@ -230,15 +230,15 @@ func (t *SubagentTool) Description() string { return "Execute a subagent task synchronously and return the result. Use this for delegating specific tasks to an independent agent instance. Returns execution summary to user and full details to LLM." } -func (t *SubagentTool) Parameters() map[string]interface{} { - return map[string]interface{}{ +func (t *SubagentTool) Parameters() map[string]any { + return map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "task": map[string]interface{}{ + "properties": map[string]any{ + "task": map[string]any{ "type": "string", "description": "The task for subagent to complete", }, - "label": map[string]interface{}{ + "label": map[string]any{ "type": "string", "description": "Optional short label for the task (for display)", }, @@ -252,7 +252,7 @@ func (t *SubagentTool) SetContext(channel, chatID string) { t.originChatID = chatID } -func (t *SubagentTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult { +func (t *SubagentTool) Execute(ctx context.Context, args map[string]any) *ToolResult { task, ok := args["task"].(string) if !ok { return ErrorResult("task is required").WithError(fmt.Errorf("task parameter is required")) diff --git a/pkg/tools/subagent_tool_test.go b/pkg/tools/subagent_tool_test.go index 8a7d22f24..19af7244f 100644 --- a/pkg/tools/subagent_tool_test.go +++ b/pkg/tools/subagent_tool_test.go @@ -12,7 +12,7 @@ import ( // MockLLMProvider is a test implementation of LLMProvider type MockLLMProvider struct{} -func (m *MockLLMProvider) Chat(ctx context.Context, messages []providers.Message, tools []providers.ToolDefinition, model string, options map[string]interface{}) (*providers.LLMResponse, error) { +func (m *MockLLMProvider) Chat(ctx context.Context, messages []providers.Message, tools []providers.ToolDefinition, model string, options map[string]any) (*providers.LLMResponse, error) { // Find the last user message to generate a response for i := len(messages) - 1; i >= 0; i-- { if messages[i].Role == "user" { @@ -79,13 +79,13 @@ func TestSubagentTool_Parameters(t *testing.T) { } // Check properties - props, ok := params["properties"].(map[string]interface{}) + props, ok := params["properties"].(map[string]any) if !ok { t.Fatal("Properties should be a map") } // Verify task parameter - task, ok := props["task"].(map[string]interface{}) + task, ok := props["task"].(map[string]any) if !ok { t.Fatal("Task parameter should exist") } @@ -94,7 +94,7 @@ func TestSubagentTool_Parameters(t *testing.T) { } // Verify label parameter - label, ok := props["label"].(map[string]interface{}) + label, ok := props["label"].(map[string]any) if !ok { t.Fatal("Label parameter should exist") } @@ -134,7 +134,7 @@ func TestSubagentTool_Execute_Success(t *testing.T) { tool.SetContext("telegram", "chat-123") ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "task": "Write a haiku about coding", "label": "haiku-task", } @@ -189,7 +189,7 @@ func TestSubagentTool_Execute_NoLabel(t *testing.T) { tool := NewSubagentTool(manager) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "task": "Test task without label", } @@ -212,7 +212,7 @@ func TestSubagentTool_Execute_MissingTask(t *testing.T) { tool := NewSubagentTool(manager) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "label": "test", } @@ -239,7 +239,7 @@ func TestSubagentTool_Execute_NilManager(t *testing.T) { tool := NewSubagentTool(nil) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "task": "test task", } @@ -268,7 +268,7 @@ func TestSubagentTool_Execute_ContextPassing(t *testing.T) { tool.SetContext(channel, chatID) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "task": "Test context passing", } @@ -295,7 +295,7 @@ func TestSubagentTool_ForUserTruncation(t *testing.T) { // Create a task that will generate long response longTask := strings.Repeat("This is a very long task description. ", 100) - args := map[string]interface{}{ + args := map[string]any{ "task": longTask, "label": "long-test", } diff --git a/pkg/tools/types.go b/pkg/tools/types.go index f8205b8bd..8e8b379a9 100644 --- a/pkg/tools/types.go +++ b/pkg/tools/types.go @@ -10,11 +10,11 @@ type Message struct { } type ToolCall struct { - ID string `json:"id"` - Type string `json:"type"` - Function *FunctionCall `json:"function,omitempty"` - Name string `json:"name,omitempty"` - Arguments map[string]interface{} `json:"arguments,omitempty"` + ID string `json:"id"` + Type string `json:"type"` + Function *FunctionCall `json:"function,omitempty"` + Name string `json:"name,omitempty"` + Arguments map[string]any `json:"arguments,omitempty"` } type FunctionCall struct { @@ -36,7 +36,7 @@ type UsageInfo struct { } type LLMProvider interface { - Chat(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]interface{}) (*LLMResponse, error) + Chat(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]any) (*LLMResponse, error) GetDefaultModel() string } @@ -46,7 +46,7 @@ type ToolDefinition struct { } type ToolFunctionDefinition struct { - Name string `json:"name"` - Description string `json:"description"` - Parameters map[string]interface{} `json:"parameters"` + Name string `json:"name"` + Description string `json:"description"` + Parameters map[string]any `json:"parameters"` } diff --git a/pkg/tools/web.go b/pkg/tools/web.go index ccd995842..d36bf3751 100644 --- a/pkg/tools/web.go +++ b/pkg/tools/web.go @@ -222,15 +222,15 @@ func (t *WebSearchTool) Description() string { return "Search the web for current information. Returns titles, URLs, and snippets from search results." } -func (t *WebSearchTool) Parameters() map[string]interface{} { - return map[string]interface{}{ +func (t *WebSearchTool) Parameters() map[string]any { + return map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "query": map[string]interface{}{ + "properties": map[string]any{ + "query": map[string]any{ "type": "string", "description": "Search query", }, - "count": map[string]interface{}{ + "count": map[string]any{ "type": "integer", "description": "Number of results (1-10)", "minimum": 1.0, @@ -241,7 +241,7 @@ func (t *WebSearchTool) Parameters() map[string]interface{} { } } -func (t *WebSearchTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult { +func (t *WebSearchTool) Execute(ctx context.Context, args map[string]any) *ToolResult { query, ok := args["query"].(string) if !ok { return ErrorResult("query is required") @@ -286,15 +286,15 @@ func (t *WebFetchTool) Description() string { return "Fetch a URL and extract readable content (HTML to text). Use this to get weather info, news, articles, or any web content." } -func (t *WebFetchTool) Parameters() map[string]interface{} { - return map[string]interface{}{ +func (t *WebFetchTool) Parameters() map[string]any { + return map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "url": map[string]interface{}{ + "properties": map[string]any{ + "url": map[string]any{ "type": "string", "description": "URL to fetch", }, - "maxChars": map[string]interface{}{ + "maxChars": map[string]any{ "type": "integer", "description": "Maximum characters to extract", "minimum": 100.0, @@ -304,7 +304,7 @@ func (t *WebFetchTool) Parameters() map[string]interface{} { } } -func (t *WebFetchTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult { +func (t *WebFetchTool) Execute(ctx context.Context, args map[string]any) *ToolResult { urlStr, ok := args["url"].(string) if !ok { return ErrorResult("url is required") @@ -369,7 +369,7 @@ func (t *WebFetchTool) Execute(ctx context.Context, args map[string]interface{}) var text, extractor string if strings.Contains(contentType, "application/json") { - var jsonData interface{} + var jsonData any if err := json.Unmarshal(body, &jsonData); err == nil { formatted, _ := json.MarshalIndent(jsonData, "", " ") text = string(formatted) @@ -392,7 +392,7 @@ func (t *WebFetchTool) Execute(ctx context.Context, args map[string]interface{}) text = text[:maxChars] } - result := map[string]interface{}{ + result := map[string]any{ "url": urlStr, "status": resp.StatusCode, "extractor": extractor, diff --git a/pkg/tools/web_test.go b/pkg/tools/web_test.go index 988eada16..161b3e9cf 100644 --- a/pkg/tools/web_test.go +++ b/pkg/tools/web_test.go @@ -20,7 +20,7 @@ func TestWebTool_WebFetch_Success(t *testing.T) { tool := NewWebFetchTool(50000) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "url": server.URL, } @@ -56,7 +56,7 @@ func TestWebTool_WebFetch_JSON(t *testing.T) { tool := NewWebFetchTool(50000) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "url": server.URL, } @@ -77,7 +77,7 @@ func TestWebTool_WebFetch_JSON(t *testing.T) { func TestWebTool_WebFetch_InvalidURL(t *testing.T) { tool := NewWebFetchTool(50000) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "url": "not-a-valid-url", } @@ -98,7 +98,7 @@ func TestWebTool_WebFetch_InvalidURL(t *testing.T) { func TestWebTool_WebFetch_UnsupportedScheme(t *testing.T) { tool := NewWebFetchTool(50000) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "url": "ftp://example.com/file.txt", } @@ -119,7 +119,7 @@ func TestWebTool_WebFetch_UnsupportedScheme(t *testing.T) { func TestWebTool_WebFetch_MissingURL(t *testing.T) { tool := NewWebFetchTool(50000) ctx := context.Background() - args := map[string]interface{}{} + args := map[string]any{} result := tool.Execute(ctx, args) @@ -147,7 +147,7 @@ func TestWebTool_WebFetch_Truncation(t *testing.T) { tool := NewWebFetchTool(1000) // Limit to 1000 chars ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "url": server.URL, } @@ -159,7 +159,7 @@ func TestWebTool_WebFetch_Truncation(t *testing.T) { } // ForUser should contain truncated content (not the full 20000 chars) - resultMap := make(map[string]interface{}) + resultMap := make(map[string]any) json.Unmarshal([]byte(result.ForUser), &resultMap) if text, ok := resultMap["text"].(string); ok { if len(text) > 1100 { // Allow some margin @@ -187,7 +187,7 @@ func TestWebTool_WebSearch_NoApiKey(t *testing.T) { func TestWebTool_WebSearch_MissingQuery(t *testing.T) { tool := NewWebSearchTool(WebSearchToolOptions{BraveAPIKey: "test-key", BraveMaxResults: 5, BraveEnabled: true}) ctx := context.Background() - args := map[string]interface{}{} + args := map[string]any{} result := tool.Execute(ctx, args) @@ -208,7 +208,7 @@ func TestWebTool_WebFetch_HTMLExtraction(t *testing.T) { tool := NewWebFetchTool(50000) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "url": server.URL, } @@ -234,7 +234,7 @@ func TestWebTool_WebFetch_HTMLExtraction(t *testing.T) { func TestWebTool_WebFetch_MissingDomain(t *testing.T) { tool := NewWebFetchTool(50000) ctx := context.Background() - args := map[string]interface{}{ + args := map[string]any{ "url": "https://", } diff --git a/pkg/utils/media.go b/pkg/utils/media.go index 6345da8fc..482049315 100644 --- a/pkg/utils/media.go +++ b/pkg/utils/media.go @@ -66,7 +66,7 @@ func DownloadFile(url, filename string, opts DownloadOptions) string { mediaDir := filepath.Join(os.TempDir(), "picoclaw_media") if err := os.MkdirAll(mediaDir, 0700); err != nil { - logger.ErrorCF(opts.LoggerPrefix, "Failed to create media directory", map[string]interface{}{ + logger.ErrorCF(opts.LoggerPrefix, "Failed to create media directory", map[string]any{ "error": err.Error(), }) return "" @@ -80,7 +80,7 @@ func DownloadFile(url, filename string, opts DownloadOptions) string { // Create HTTP request req, err := http.NewRequest("GET", url, nil) if err != nil { - logger.ErrorCF(opts.LoggerPrefix, "Failed to create download request", map[string]interface{}{ + logger.ErrorCF(opts.LoggerPrefix, "Failed to create download request", map[string]any{ "error": err.Error(), }) return "" @@ -94,7 +94,7 @@ func DownloadFile(url, filename string, opts DownloadOptions) string { client := &http.Client{Timeout: opts.Timeout} resp, err := client.Do(req) if err != nil { - logger.ErrorCF(opts.LoggerPrefix, "Failed to download file", map[string]interface{}{ + logger.ErrorCF(opts.LoggerPrefix, "Failed to download file", map[string]any{ "error": err.Error(), "url": url, }) @@ -103,7 +103,7 @@ func DownloadFile(url, filename string, opts DownloadOptions) string { defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - logger.ErrorCF(opts.LoggerPrefix, "File download returned non-200 status", map[string]interface{}{ + logger.ErrorCF(opts.LoggerPrefix, "File download returned non-200 status", map[string]any{ "status": resp.StatusCode, "url": url, }) @@ -112,7 +112,7 @@ func DownloadFile(url, filename string, opts DownloadOptions) string { out, err := os.Create(localPath) if err != nil { - logger.ErrorCF(opts.LoggerPrefix, "Failed to create local file", map[string]interface{}{ + logger.ErrorCF(opts.LoggerPrefix, "Failed to create local file", map[string]any{ "error": err.Error(), }) return "" @@ -122,13 +122,13 @@ func DownloadFile(url, filename string, opts DownloadOptions) string { if _, err := io.Copy(out, resp.Body); err != nil { out.Close() os.Remove(localPath) - logger.ErrorCF(opts.LoggerPrefix, "Failed to write file", map[string]interface{}{ + logger.ErrorCF(opts.LoggerPrefix, "Failed to write file", map[string]any{ "error": err.Error(), }) return "" } - logger.DebugCF(opts.LoggerPrefix, "File downloaded successfully", map[string]interface{}{ + logger.DebugCF(opts.LoggerPrefix, "File downloaded successfully", map[string]any{ "path": localPath, }) diff --git a/pkg/voice/transcriber.go b/pkg/voice/transcriber.go index 9af2ea6bb..ad8767d40 100644 --- a/pkg/voice/transcriber.go +++ b/pkg/voice/transcriber.go @@ -29,7 +29,7 @@ type TranscriptionResponse struct { } func NewGroqTranscriber(apiKey string) *GroqTranscriber { - logger.DebugCF("voice", "Creating Groq transcriber", map[string]interface{}{"has_api_key": apiKey != ""}) + logger.DebugCF("voice", "Creating Groq transcriber", map[string]any{"has_api_key": apiKey != ""}) apiBase := "https://api.groq.com/openai/v1" return &GroqTranscriber{ @@ -42,22 +42,22 @@ func NewGroqTranscriber(apiKey string) *GroqTranscriber { } func (t *GroqTranscriber) Transcribe(ctx context.Context, audioFilePath string) (*TranscriptionResponse, error) { - logger.InfoCF("voice", "Starting transcription", map[string]interface{}{"audio_file": audioFilePath}) + logger.InfoCF("voice", "Starting transcription", map[string]any{"audio_file": audioFilePath}) audioFile, err := os.Open(audioFilePath) if err != nil { - logger.ErrorCF("voice", "Failed to open audio file", map[string]interface{}{"path": audioFilePath, "error": err}) + logger.ErrorCF("voice", "Failed to open audio file", map[string]any{"path": audioFilePath, "error": err}) return nil, fmt.Errorf("failed to open audio file: %w", err) } defer audioFile.Close() fileInfo, err := audioFile.Stat() if err != nil { - logger.ErrorCF("voice", "Failed to get file info", map[string]interface{}{"path": audioFilePath, "error": err}) + logger.ErrorCF("voice", "Failed to get file info", map[string]any{"path": audioFilePath, "error": err}) return nil, fmt.Errorf("failed to get file info: %w", err) } - logger.DebugCF("voice", "Audio file details", map[string]interface{}{ + logger.DebugCF("voice", "Audio file details", map[string]any{ "size_bytes": fileInfo.Size(), "file_name": filepath.Base(audioFilePath), }) @@ -67,44 +67,44 @@ func (t *GroqTranscriber) Transcribe(ctx context.Context, audioFilePath string) part, err := writer.CreateFormFile("file", filepath.Base(audioFilePath)) if err != nil { - logger.ErrorCF("voice", "Failed to create form file", map[string]interface{}{"error": err}) + logger.ErrorCF("voice", "Failed to create form file", map[string]any{"error": err}) return nil, fmt.Errorf("failed to create form file: %w", err) } copied, err := io.Copy(part, audioFile) if err != nil { - logger.ErrorCF("voice", "Failed to copy file content", map[string]interface{}{"error": err}) + logger.ErrorCF("voice", "Failed to copy file content", map[string]any{"error": err}) return nil, fmt.Errorf("failed to copy file content: %w", err) } - logger.DebugCF("voice", "File copied to request", map[string]interface{}{"bytes_copied": copied}) + logger.DebugCF("voice", "File copied to request", map[string]any{"bytes_copied": copied}) if err := writer.WriteField("model", "whisper-large-v3"); err != nil { - logger.ErrorCF("voice", "Failed to write model field", map[string]interface{}{"error": err}) + logger.ErrorCF("voice", "Failed to write model field", map[string]any{"error": err}) return nil, fmt.Errorf("failed to write model field: %w", err) } if err := writer.WriteField("response_format", "json"); err != nil { - logger.ErrorCF("voice", "Failed to write response_format field", map[string]interface{}{"error": err}) + logger.ErrorCF("voice", "Failed to write response_format field", map[string]any{"error": err}) return nil, fmt.Errorf("failed to write response_format field: %w", err) } if err := writer.Close(); err != nil { - logger.ErrorCF("voice", "Failed to close multipart writer", map[string]interface{}{"error": err}) + logger.ErrorCF("voice", "Failed to close multipart writer", map[string]any{"error": err}) return nil, fmt.Errorf("failed to close multipart writer: %w", err) } url := t.apiBase + "/audio/transcriptions" req, err := http.NewRequestWithContext(ctx, "POST", url, &requestBody) if err != nil { - logger.ErrorCF("voice", "Failed to create request", map[string]interface{}{"error": err}) + logger.ErrorCF("voice", "Failed to create request", map[string]any{"error": err}) return nil, fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Content-Type", writer.FormDataContentType()) req.Header.Set("Authorization", "Bearer "+t.apiKey) - logger.DebugCF("voice", "Sending transcription request to Groq API", map[string]interface{}{ + logger.DebugCF("voice", "Sending transcription request to Groq API", map[string]any{ "url": url, "request_size_bytes": requestBody.Len(), "file_size_bytes": fileInfo.Size(), @@ -112,37 +112,37 @@ func (t *GroqTranscriber) Transcribe(ctx context.Context, audioFilePath string) resp, err := t.httpClient.Do(req) if err != nil { - logger.ErrorCF("voice", "Failed to send request", map[string]interface{}{"error": err}) + logger.ErrorCF("voice", "Failed to send request", map[string]any{"error": err}) return nil, fmt.Errorf("failed to send request: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { - logger.ErrorCF("voice", "Failed to read response", map[string]interface{}{"error": err}) + logger.ErrorCF("voice", "Failed to read response", map[string]any{"error": err}) return nil, fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode != http.StatusOK { - logger.ErrorCF("voice", "API error", map[string]interface{}{ + logger.ErrorCF("voice", "API error", map[string]any{ "status_code": resp.StatusCode, "response": string(body), }) return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body)) } - logger.DebugCF("voice", "Received response from Groq API", map[string]interface{}{ + logger.DebugCF("voice", "Received response from Groq API", map[string]any{ "status_code": resp.StatusCode, "response_size_bytes": len(body), }) var result TranscriptionResponse if err := json.Unmarshal(body, &result); err != nil { - logger.ErrorCF("voice", "Failed to unmarshal response", map[string]interface{}{"error": err}) + logger.ErrorCF("voice", "Failed to unmarshal response", map[string]any{"error": err}) return nil, fmt.Errorf("failed to unmarshal response: %w", err) } - logger.InfoCF("voice", "Transcription completed successfully", map[string]interface{}{ + logger.InfoCF("voice", "Transcription completed successfully", map[string]any{ "text_length": len(result.Text), "language": result.Language, "duration_seconds": result.Duration, @@ -154,6 +154,6 @@ func (t *GroqTranscriber) Transcribe(ctx context.Context, audioFilePath string) func (t *GroqTranscriber) IsAvailable() bool { available := t.apiKey != "" - logger.DebugCF("voice", "Checking transcriber availability", map[string]interface{}{"available": available}) + logger.DebugCF("voice", "Checking transcriber availability", map[string]any{"available": available}) return available }