diff --git a/api/strategy.go b/api/strategy.go index b9bb912a..fbc2d626 100644 --- a/api/strategy.go +++ b/api/strategy.go @@ -668,6 +668,12 @@ func (s *Server) runRealAITest(userID, modelID, systemPrompt, userPrompt string) case "minimax": aiClient = mcp.NewMiniMaxClient() aiClient.SetAPIKey(apiKey, model.CustomAPIURL, model.CustomModelName) + case "blockrun-base": + aiClient = mcp.NewBlockRunBaseClient() + aiClient.SetAPIKey(apiKey, "", model.CustomModelName) + case "blockrun-sol": + aiClient = mcp.NewBlockRunSolClient() + aiClient.SetAPIKey(apiKey, "", model.CustomModelName) default: // Use generic client aiClient = mcp.NewClient() diff --git a/backtest/ai_client.go b/backtest/ai_client.go index 74c34761..24dd45a6 100644 --- a/backtest/ai_client.go +++ b/backtest/ai_client.go @@ -78,6 +78,20 @@ func configureMCPClient(cfg BacktestConfig, base mcp.AIClient) (mcp.AIClient, er mmC := mcp.NewMiniMaxClientWithOptions() mmC.(*mcp.MiniMaxClient).SetAPIKey(cfg.AICfg.APIKey, cfg.AICfg.BaseURL, cfg.AICfg.Model) return mmC, nil + case "blockrun-base": + if cfg.AICfg.APIKey == "" { + return nil, fmt.Errorf("blockrun-base provider requires wallet private key") + } + brBase := mcp.NewBlockRunBaseClient() + brBase.SetAPIKey(cfg.AICfg.APIKey, "", cfg.AICfg.Model) + return brBase, nil + case "blockrun-sol": + if cfg.AICfg.APIKey == "" { + return nil, fmt.Errorf("blockrun-sol provider requires wallet keypair") + } + brSol := mcp.NewBlockRunSolClient() + brSol.SetAPIKey(cfg.AICfg.APIKey, "", cfg.AICfg.Model) + return brSol, nil case "custom": if cfg.AICfg.BaseURL == "" || cfg.AICfg.APIKey == "" || cfg.AICfg.Model == "" { return nil, fmt.Errorf("custom provider requires base_url, api key and model") diff --git a/debate/engine.go b/debate/engine.go index 9d9dffb1..9e39cca2 100644 --- a/debate/engine.go +++ b/debate/engine.go @@ -99,6 +99,10 @@ func (e *DebateEngine) InitializeClients(participants []*store.DebateParticipant client = mcp.NewKimiClient() case "minimax": client = mcp.NewMiniMaxClient() + case "blockrun-base": + client = mcp.NewBlockRunBaseClient() + case "blockrun-sol": + client = mcp.NewBlockRunSolClient() default: client = mcp.New() } diff --git a/telegram/bot.go b/telegram/bot.go index f37dd29a..e65df3a1 100644 --- a/telegram/bot.go +++ b/telegram/bot.go @@ -266,15 +266,39 @@ func sendMarkdownMsg(bot *tgbotapi.BotAPI, chatID int64, text string) { // ── LLM client ─────────────────────────────────────────────────────────────── func newLLMClient(st *store.Store, userID string) mcp.AIClient { + // 1. Prefer the model explicitly configured for Telegram (Settings → Telegram → AI Model) + if tgCfg, err := st.TelegramConfig().Get(); err == nil && tgCfg.ModelID != "" { + if model, err := st.AIModel().Get(userID, tgCfg.ModelID); err == nil && model.Enabled { + apiKey := string(model.APIKey) + if apiKey != "" { + client := clientForProvider(model.Provider) + client.SetAPIKey(apiKey, model.CustomAPIURL, model.CustomModelName) + if isUSDCProvider(model.Provider) { + logger.Infof("Telegram agent: provider=%s (USDC payment) user=%s", model.Provider, userID) + } else { + logger.Infof("Telegram agent: provider=%s user=%s", model.Provider, userID) + } + return client + } + } + } + + // 2. Fall back to first enabled model if model, err := st.AIModel().GetDefault(userID); err == nil { apiKey := string(model.APIKey) if apiKey != "" { client := clientForProvider(model.Provider) client.SetAPIKey(apiKey, model.CustomAPIURL, model.CustomModelName) - logger.Infof("Telegram agent: provider=%s user=%s", model.Provider, userID) + if isUSDCProvider(model.Provider) { + logger.Infof("Telegram agent: provider=%s (USDC payment) user=%s", model.Provider, userID) + } else { + logger.Infof("Telegram agent: provider=%s user=%s", model.Provider, userID) + } return client } } + + // 3. Environment variable fallback for _, pair := range []struct{ provider, key, url string }{ {"deepseek", os.Getenv("DEEPSEEK_API_KEY"), mcp.DefaultDeepSeekBaseURL}, {"openai", os.Getenv("OPENAI_API_KEY"), ""}, @@ -289,6 +313,11 @@ func newLLMClient(st *store.Store, userID string) mcp.AIClient { return nil } +// isUSDCProvider returns true for providers that pay per call with USDC (x402 protocol). +func isUSDCProvider(provider string) bool { + return provider == "blockrun-base" || provider == "blockrun-sol" +} + func clientForProvider(provider string) mcp.AIClient { switch provider { case "openai": @@ -305,6 +334,12 @@ func clientForProvider(provider string) mcp.AIClient { return mcp.NewGrokClient() case "gemini": return mcp.NewGeminiClient() + case "minimax": + return mcp.NewMiniMaxClient() + case "blockrun-base": + return mcp.NewBlockRunBaseClient() + case "blockrun-sol": + return mcp.NewBlockRunSolClient() default: return mcp.NewDeepSeekClient() }