diff --git a/cmd/picoclaw/internal/gateway/helpers.go b/cmd/picoclaw/internal/gateway/helpers.go index 77ccd5460..ae9a40d33 100644 --- a/cmd/picoclaw/internal/gateway/helpers.go +++ b/cmd/picoclaw/internal/gateway/helpers.go @@ -295,7 +295,8 @@ func setupAndStartServices( if strings.HasPrefix(webAppURL, "https://") { hostname, tsErr := tailscale.DetectHostname() if tsErr != nil { - logger.ErrorCF("miniapp", "HTTPS URL configured but Tailscale not available", map[string]any{"error": tsErr.Error()}) + logger.ErrorCF("miniapp", "HTTPS URL configured but Tailscale not available", + map[string]any{"error": tsErr.Error()}) } else { certDir := filepath.Join(cfg.WorkspacePath(), "state", "certs") certFile, keyFile, certErr := tailscale.FetchCert(hostname, certDir) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 1f7e32c85..025c03173 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -66,8 +66,6 @@ type AgentLoop struct { mcp mcpRuntime mu sync.RWMutex - // Track active requests for safe provider cleanup - activeRequests sync.WaitGroup providerCache map[string]providers.LLMProvider @@ -2263,69 +2261,6 @@ func (al *AgentLoop) runLLMIteration( return finalContent, iteration, nil } -// selectCandidates returns the model candidates and resolved model name to use -// for a conversation turn. When model routing is configured and the incoming -// message scores below the complexity threshold, it returns the light model -// candidates instead of the primary ones. -// -// The returned (candidates, model) pair is used for all LLM calls within one -// turn — tool follow-up iterations use the same tier as the initial call so -// that a multi-step tool chain doesn't switch models mid-way. -func (al *AgentLoop) selectCandidates( - agent *AgentInstance, - userMsg string, - history []providers.Message, -) (candidates []providers.FallbackCandidate, model string) { - if agent.Router == nil || len(agent.LightCandidates) == 0 { - return agent.Candidates, agent.Model - } - - _, usedLight, score := agent.Router.SelectModel(userMsg, history, agent.Model) - if !usedLight { - logger.DebugCF("agent", "Model routing: primary model selected", - map[string]any{ - "agent_id": agent.ID, - "score": score, - "threshold": agent.Router.Threshold(), - }) - return agent.Candidates, agent.Model - } - - logger.InfoCF("agent", "Model routing: light model selected", - map[string]any{ - "agent_id": agent.ID, - "light_model": agent.Router.LightModel(), - "score": score, - "threshold": agent.Router.Threshold(), - }) - return agent.LightCandidates, agent.Router.LightModel() -} - -// findNearestUserMessage finds the nearest user message to the given index. -// It searches backward first, then forward if no user message is found. -func (al *AgentLoop) findNearestUserMessage(messages []providers.Message, mid int) int { - originalMid := mid - - for mid > 0 && messages[mid].Role != "user" { - mid-- - } - - if messages[mid].Role == "user" { - return mid - } - - mid = originalMid - for mid < len(messages) && messages[mid].Role != "user" { - mid++ - } - - if mid < len(messages) { - return mid - } - - return originalMid -} - // callLLMWithRetry calls the LLM with streaming support, fallback chain, // and retry logic for timeout and context window errors. func (al *AgentLoop) callLLMWithRetry( @@ -2657,42 +2592,6 @@ func (al *AgentLoop) publishToolMedia(ctx context.Context, result *tools.ToolRes }) } -func (al *AgentLoop) buildCommandsRuntime(agent *AgentInstance, opts *processOptions) *commands.Runtime { - registry := al.GetRegistry() - cfg := al.GetConfig() - rt := &commands.Runtime{ - Config: cfg, - ListAgentIDs: registry.ListAgentIDs, - ListDefinitions: al.cmdRegistry.Definitions, - GetEnabledChannels: func() []string { - if al.channelManager == nil { - return nil - } - return al.channelManager.GetEnabledChannels() - }, - SwitchChannel: func(value string) error { - if al.channelManager == nil { - return fmt.Errorf("channel manager not initialized") - } - if _, exists := al.channelManager.GetChannel(value); !exists && value != "cli" { - return fmt.Errorf("channel '%s' not found or not enabled", value) - } - return nil - }, - } - if agent != nil { - rt.GetModelInfo = func() (string, string) { - return agent.Model, cfg.Agents.Defaults.Provider - } - rt.SwitchModel = func(value string) (string, error) { - oldModel := agent.Model - agent.Model = value - return oldModel, nil - } - } - return rt -} - // forceTextResponse makes a final LLM call without tools when max iterations // are exhausted, forcing a text response. func (al *AgentLoop) forceTextResponse(ctx context.Context, agent *AgentInstance, messages []providers.Message) string { diff --git a/pkg/providers/anthropic_messages/provider.go b/pkg/providers/anthropic_messages/provider.go index 7f1b92ce0..e2b75516c 100644 --- a/pkg/providers/anthropic_messages/provider.go +++ b/pkg/providers/anthropic_messages/provider.go @@ -270,7 +270,7 @@ func buildRequestBody( func buildTools(tools []ToolDefinition) []any { result := make([]any, len(tools)) for i, tool := range tools { - // Unmarshal Parameters (json.RawMessage) into map so it serialises + // Unmarshal Parameters (json.RawMessage) into map so it serializes // correctly as a nested JSON object rather than a raw byte string. var schema any if len(tool.Function.Parameters) > 0 { diff --git a/pkg/providers/anthropic_messages/provider_test.go b/pkg/providers/anthropic_messages/provider_test.go index bde2d47d1..e800ee643 100644 --- a/pkg/providers/anthropic_messages/provider_test.go +++ b/pkg/providers/anthropic_messages/provider_test.go @@ -137,7 +137,9 @@ func TestBuildRequestBody(t *testing.T) { Function: ToolFunctionDefinition{ Name: "get_weather", Description: "Get current weather", - Parameters: json.RawMessage(`{"type":"object","properties":{"location":{"type":"string","description":"City name"}}}`), + Parameters: json.RawMessage( + `{"type":"object","properties":{"location":{"type":"string","description":"City name"}}}`, + ), }, }, }, diff --git a/pkg/tools/web.go b/pkg/tools/web.go index 26d6b3b7e..4e5e31f47 100644 --- a/pkg/tools/web.go +++ b/pkg/tools/web.go @@ -89,46 +89,6 @@ func (it *APIKeyIterator) Next() (string, bool) { return key, true } -// createHTTPClient creates an HTTP client with optional proxy support -func createHTTPClient(proxyURL string, timeout time.Duration) (*http.Client, error) { - client := &http.Client{ - Timeout: timeout, - Transport: &http.Transport{ - MaxIdleConns: 10, - - IdleConnTimeout: 30 * time.Second, - - DisableCompression: false, - - TLSHandshakeTimeout: 15 * time.Second, - }, - } - - if proxyURL != "" { - proxy, err := url.Parse(proxyURL) - if err != nil { - return nil, fmt.Errorf("invalid proxy URL: %w", err) - } - scheme := strings.ToLower(proxy.Scheme) - switch scheme { - case "http", "https", "socks5", "socks5h": - default: - return nil, fmt.Errorf( - "unsupported proxy scheme %q (supported: http, https, socks5, socks5h)", - proxy.Scheme, - ) - } - if proxy.Host == "" { - return nil, fmt.Errorf("invalid proxy URL: missing host") - } - client.Transport.(*http.Transport).Proxy = http.ProxyURL(proxy) - } else { - client.Transport.(*http.Transport).Proxy = http.ProxyFromEnvironment - } - - return client, nil -} - type SearchProvider interface { Search(ctx context.Context, query string, count int) (string, error) }