diff --git a/Dockerfile.coolify b/Dockerfile.coolify index a61519516..8fe22ed43 100644 --- a/Dockerfile.coolify +++ b/Dockerfile.coolify @@ -34,10 +34,8 @@ RUN /usr/local/bin/picoclaw onboard COPY entrypoint-coolify.sh /usr/local/bin/entrypoint-coolify.sh RUN chmod +x /usr/local/bin/entrypoint-coolify.sh -# Default env vars (overridden by Coolify) -ENV PICOCLAW_AGENTS_DEFAULTS_PROVIDER="gemini" -ENV PICOCLAW_AGENTS_DEFAULTS_MODEL="gemini-2.5-flash-lite" -ENV PICOCLAW_PROVIDERS_GEMINI_API_KEY="" +# Default env vars (can be overridden by Coolify) +ENV TZ=UTC ENTRYPOINT ["/usr/local/bin/entrypoint-coolify.sh"] CMD ["gateway"] diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 8ebf24b7c..a1e90f66f 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -12,6 +12,7 @@ import ( "fmt" "os" "path/filepath" + "reflect" "strings" "sync" "sync/atomic" @@ -212,70 +213,45 @@ func (al *AgentLoop) listModelsResponse() string { sb.WriteString(fmt.Sprintf("**Active model:** `%s`\n", al.model)) sb.WriteString(fmt.Sprintf("**Active provider:** `%s`\n\n", al.cfg.Agents.Defaults.Provider)) - // List all configured providers - type providerEntry struct { - Name string - APIBase string - } - providersList := []providerEntry{ - {"gemini", al.cfg.Providers.Gemini.APIBase}, - {"openrouter", al.cfg.Providers.OpenRouter.APIBase}, - {"openai", al.cfg.Providers.OpenAI.APIBase}, - {"anthropic", al.cfg.Providers.Anthropic.APIBase}, - {"vllm", al.cfg.Providers.VLLM.APIBase}, - {"groq", al.cfg.Providers.Groq.APIBase}, - {"deepseek", al.cfg.Providers.DeepSeek.APIBase}, - {"mistral", al.cfg.Providers.Mistral.APIBase}, - {"nvidia", al.cfg.Providers.Nvidia.APIBase}, - {"moonshot", al.cfg.Providers.Moonshot.APIBase}, - {"zhipu", al.cfg.Providers.Zhipu.APIBase}, - } + sb.WriteString("**Configured providers:**\n") + + // Use reflection to iterate over Config.Providers fields + v := reflect.ValueOf(al.cfg.Providers) + t := v.Type() hasConfigured := false - for _, p := range providersList { - // Show providers that have either an API key or API base configured - hasKey := false - switch p.Name { - case "gemini": - hasKey = al.cfg.Providers.Gemini.APIKey != "" - case "openrouter": - hasKey = al.cfg.Providers.OpenRouter.APIKey != "" - case "openai": - hasKey = al.cfg.Providers.OpenAI.APIKey != "" - case "anthropic": - hasKey = al.cfg.Providers.Anthropic.APIKey != "" - case "vllm": - hasKey = al.cfg.Providers.VLLM.APIKey != "" || al.cfg.Providers.VLLM.APIBase != "" - case "groq": - hasKey = al.cfg.Providers.Groq.APIKey != "" - case "deepseek": - hasKey = al.cfg.Providers.DeepSeek.APIKey != "" - case "mistral": - hasKey = al.cfg.Providers.Mistral.APIKey != "" - case "nvidia": - hasKey = al.cfg.Providers.Nvidia.APIKey != "" - case "moonshot": - hasKey = al.cfg.Providers.Moonshot.APIKey != "" - case "zhipu": - hasKey = al.cfg.Providers.Zhipu.APIKey != "" + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + fieldName := strings.ToLower(t.Field(i).Name) + + // Provider name from json tag if available + jsonTag := t.Field(i).Tag.Get("json") + if jsonTag != "" { + fieldName = strings.Split(jsonTag, ",")[0] } - if hasKey { - if !hasConfigured { - sb.WriteString("**Configured providers:**\n") - hasConfigured = true - } + + // Check if provider has API key or Base URL + apiKey := field.FieldByName("APIKey").String() + apiBase := field.FieldByName("APIBase").String() + + if apiKey != "" || apiBase != "" { active := "" - if p.Name == al.cfg.Agents.Defaults.Provider { + if fieldName == al.cfg.Agents.Defaults.Provider { active = " ✅" } - if p.APIBase != "" { - sb.WriteString(fmt.Sprintf("- `%s` → %s%s\n", p.Name, p.APIBase, active)) + if apiBase != "" { + sb.WriteString(fmt.Sprintf("- `%s` → %s%s\n", fieldName, apiBase, active)) } else { - sb.WriteString(fmt.Sprintf("- `%s`%s\n", p.Name, active)) + sb.WriteString(fmt.Sprintf("- `%s`%s\n", fieldName, active)) } + hasConfigured = true } } + if !hasConfigured { + sb.WriteString("_None configured_\n") + } + sb.WriteString("\n_Usage: `/model ` or `/model /`_") return sb.String() } diff --git a/pkg/channels/telegram.go b/pkg/channels/telegram.go index 0ab748bc7..10c87af9a 100644 --- a/pkg/channels/telegram.go +++ b/pkg/channels/telegram.go @@ -27,14 +27,14 @@ type TelegramChannel struct { config config.TelegramConfig chatIDs map[string]int64 transcriber *voice.GroqTranscriber - stopThinking sync.Map // chatID -> typingCancel + typingTasks sync.Map // chatID -> *typingTask } -type thinkingCancel struct { +type typingTask struct { fn context.CancelFunc } -func (c *thinkingCancel) Cancel() { +func (c *typingTask) Cancel() { if c != nil && c.fn != nil { c.fn() } @@ -68,7 +68,7 @@ func NewTelegramChannel(cfg config.TelegramConfig, bus *bus.MessageBus) (*Telegr config: cfg, chatIDs: make(map[string]int64), transcriber: nil, - stopThinking: sync.Map{}, + typingTasks: sync.Map{}, }, nil } @@ -165,11 +165,11 @@ func (c *TelegramChannel) Send(ctx context.Context, msg bus.OutboundMessage) err } // Stop typing indicator goroutine - if stop, ok := c.stopThinking.Load(msg.ChatID); ok { - if cf, ok := stop.(*thinkingCancel); ok && cf != nil { + if stop, ok := c.typingTasks.Load(msg.ChatID); ok { + if cf, ok := stop.(*typingTask); ok && cf != nil { cf.Cancel() } - c.stopThinking.Delete(msg.ChatID) + c.typingTasks.Delete(msg.ChatID) } htmlContent := markdownToTelegramHTML(msg.Content) @@ -331,13 +331,13 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, update telego.Updat // so we re-send every 4s until the response arrives) chatIDStr := fmt.Sprintf("%d", chatID) // Cancel any previous typing goroutine for this chat - if prevStop, ok := c.stopThinking.Load(chatIDStr); ok { - if cf, ok := prevStop.(*thinkingCancel); ok && cf != nil { + if prevStop, ok := c.typingTasks.Load(chatIDStr); ok { + if cf, ok := prevStop.(*typingTask); ok && cf != nil { cf.Cancel() } } typingCtx, typingCancel := context.WithCancel(ctx) - c.stopThinking.Store(chatIDStr, &thinkingCancel{fn: typingCancel}) + c.typingTasks.Store(chatIDStr, &typingTask{fn: typingCancel}) go func() { ticker := time.NewTicker(4 * time.Second) defer ticker.Stop()