refactor: replace interface{} with any

This commit is contained in:
yumosx 2026-02-16 00:36:58 +08:00
parent 214b201bfa
commit 1815dd5415
No known key found for this signature in database
GPG key ID: 4533920A18758A91
61 changed files with 572 additions and 572 deletions

View file

@ -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"],

View file

@ -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,

View file

@ -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,
})

View file

@ -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")
}

View file

@ -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
}

View file

@ -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,

View file

@ -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,

View file

@ -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),

View file

@ -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),

View file

@ -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)

View file

@ -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(),
})

View file

@ -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(),
}

View file

@ -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),

View file

@ -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),

View file

@ -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 ""
}

View file

@ -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 ""

View file

@ -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 {

View file

@ -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
}

View file

@ -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(),

View file

@ -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,

View file

@ -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()
}()

View file

@ -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

View file

@ -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)
}

View file

@ -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"})
}

View file

@ -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{}
}

View file

@ -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",
},
},

View file

@ -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{

View file

@ -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"},
},
},
},

View file

@ -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,

View file

@ -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)
}

View file

@ -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,

View file

@ -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)
}

View file

@ -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"`

View file

@ -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

View file

@ -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"`
}

View file

@ -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(),

View file

@ -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,
}

View file

@ -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")

View file

@ -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",
}

View file

@ -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 = "."

View file

@ -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)

View file

@ -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)")

View file

@ -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)")
}

View file

@ -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")
}

View file

@ -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}

View file

@ -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")
}

View file

@ -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",

View file

@ -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)
}

View file

@ -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")

View file

@ -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",
}

View file

@ -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")

View file

@ -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)"

View file

@ -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,

View file

@ -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")
}

View file

@ -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"))

View file

@ -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",
}

View file

@ -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"`
}

View file

@ -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,

View file

@ -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://",
}

View file

@ -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,
})

View file

@ -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
}