diff --git a/docs/user-docs/configuration.md b/docs/user-docs/configuration.md index 8e09c136d..68b0d95f7 100644 --- a/docs/user-docs/configuration.md +++ b/docs/user-docs/configuration.md @@ -65,6 +65,7 @@ This opens an interactive wizard showing which keys are configured and which are | Tool | Environment Variable | Purpose | Get a key | |------|---------------------|---------|-----------| | Tavily Search | `TAVILY_API_KEY` | Web search for non-Anthropic models | [tavily.com/app/api-keys](https://tavily.com/app/api-keys) | +| MiniMax Search | `MINIMAX_API_KEY` (`MINIMAX_CODE_PLAN_KEY` and `MINIMAX_CODING_API_KEY` also accepted) | Web search for non-Anthropic models | MiniMax key | | Brave Search | `BRAVE_API_KEY` | Web search for non-Anthropic models | [brave.com/search/api](https://brave.com/search/api) | | Context7 Docs | `CONTEXT7_API_KEY` | Library documentation lookup | [context7.com/dashboard](https://context7.com/dashboard) | diff --git a/src/resources/extensions/search-the-web/command-search-provider.ts b/src/resources/extensions/search-the-web/command-search-provider.ts index ae95d4a15..9f7ffe398 100644 --- a/src/resources/extensions/search-the-web/command-search-provider.ts +++ b/src/resources/extensions/search-the-web/command-search-provider.ts @@ -1,9 +1,9 @@ /** * /search-provider slash command. * - * Lets users switch between tavily, brave, serper, exa, ollama, combosearch, and auto search backends. + * Lets users switch between tavily, minimax, brave, serper, exa, ollama, combosearch, and auto search backends. * Supports direct arg (`/search-provider tavily`) or interactive select UI. - * Tab completion provides the three valid options with key status. + * Tab completion provides the valid options with key status. * * All provider logic lives in provider.ts (S01) — this is pure UI wiring. */ @@ -13,6 +13,7 @@ import type { AutocompleteItem } from "@singularity-forge/pi-tui"; import { getBraveApiKey, getExaApiKey, + getMiniMaxSearchApiKey, getOllamaApiKey, getSearchProviderPreference, getSerperApiKey, @@ -24,6 +25,7 @@ import { const VALID_PREFERENCES: SearchProviderPreference[] = [ "tavily", + "minimax", "brave", "serper", "exa", @@ -33,9 +35,10 @@ const VALID_PREFERENCES: SearchProviderPreference[] = [ ]; function keyStatus( - provider: "tavily" | "brave" | "serper" | "exa" | "ollama", + provider: "tavily" | "minimax" | "brave" | "serper" | "exa" | "ollama", ): string { if (provider === "tavily") return getTavilyApiKey() ? "✓" : "✗"; + if (provider === "minimax") return getMiniMaxSearchApiKey() ? "✓" : "✗"; if (provider === "serper") return getSerperApiKey() ? "✓" : "✗"; if (provider === "exa") return getExaApiKey() ? "✓" : "✗"; if (provider === "ollama") return getOllamaApiKey() ? "✓" : "✗"; @@ -45,6 +48,7 @@ function keyStatus( function comboStatus(): string { const available = [ getTavilyApiKey() ? "tavily" : null, + getMiniMaxSearchApiKey() ? "minimax" : null, getBraveApiKey() ? "brave" : null, getSerperApiKey() ? "serper" : null, getExaApiKey() ? "exa" : null, @@ -58,6 +62,7 @@ function comboStatus(): string { function buildSelectOptions(): string[] { return [ `tavily (key: ${keyStatus("tavily")})`, + `minimax (key: ${keyStatus("minimax")})`, `brave (key: ${keyStatus("brave")})`, `serper (key: ${keyStatus("serper")})`, `exa (key: ${keyStatus("exa")})`, @@ -69,6 +74,7 @@ function buildSelectOptions(): string[] { function parseSelectChoice(choice: string): SearchProviderPreference { if (choice.startsWith("tavily")) return "tavily"; + if (choice.startsWith("minimax")) return "minimax"; if (choice.startsWith("brave")) return "brave"; if (choice.startsWith("serper")) return "serper"; if (choice.startsWith("exa")) return "exa"; @@ -80,14 +86,14 @@ function parseSelectChoice(choice: string): SearchProviderPreference { export function registerSearchProviderCommand(pi: ExtensionAPI): void { pi.registerCommand("search-provider", { description: - "Switch search provider (tavily, brave, serper, exa, ollama, combosearch, auto)", + "Switch search provider (tavily, minimax, brave, serper, exa, ollama, combosearch, auto)", getArgumentCompletions(prefix: string): AutocompleteItem[] | null { const trimmed = prefix.trim().toLowerCase(); return VALID_PREFERENCES.filter((p) => p.startsWith(trimmed)).map((p) => { let description: string; if (p === "auto") { - description = `Auto-select (tavily: ${keyStatus("tavily")}, brave: ${keyStatus("brave")}, serper: ${keyStatus("serper")}, exa: ${keyStatus("exa")}, ollama: ${keyStatus("ollama")})`; + description = `Auto-select (tavily: ${keyStatus("tavily")}, minimax: ${keyStatus("minimax")}, brave: ${keyStatus("brave")}, serper: ${keyStatus("serper")}, exa: ${keyStatus("exa")}, ollama: ${keyStatus("ollama")})`; } else if (p === "combosearch") { description = `fan-out aggregator (${comboStatus()})`; } else { diff --git a/src/resources/extensions/search-the-web/extension-manifest.json b/src/resources/extensions/search-the-web/extension-manifest.json index c55c596d4..600480f93 100644 --- a/src/resources/extensions/search-the-web/extension-manifest.json +++ b/src/resources/extensions/search-the-web/extension-manifest.json @@ -2,7 +2,7 @@ "id": "search-the-web", "name": "Web Search", "version": "1.0.0", - "description": "Web search via Brave and page extraction via Jina Reader", + "description": "Web search via Tavily, MiniMax, Serper, Exa, Ollama, or Brave plus page extraction", "tier": "bundled", "requires": { "platform": ">=2.29.0" }, "provides": { diff --git a/src/resources/extensions/search-the-web/native-search.ts b/src/resources/extensions/search-the-web/native-search.ts index 85f0cd30b..1f47d1ecb 100644 --- a/src/resources/extensions/search-the-web/native-search.ts +++ b/src/resources/extensions/search-the-web/native-search.ts @@ -37,6 +37,7 @@ export function preferBraveSearch(): boolean { if ( prefsPref === "brave" || prefsPref === "tavily" || + prefsPref === "minimax" || prefsPref === "serper" || prefsPref === "exa" || prefsPref === "ollama" || @@ -116,6 +117,9 @@ export function registerNativeSearchHooks(pi: NativeSearchPI): { const hasSearchKey = !!( process.env.BRAVE_API_KEY || process.env.TAVILY_API_KEY || + process.env.MINIMAX_CODE_PLAN_KEY || + process.env.MINIMAX_CODING_API_KEY || + process.env.MINIMAX_API_KEY || process.env.SERPER_API_KEY || process.env.EXA_API_KEY || process.env.OLLAMA_API_KEY @@ -156,7 +160,7 @@ export function registerNativeSearchHooks(pi: NativeSearchPI): { ctx.ui.notify("Brave search active (PREFER_BRAVE_SEARCH)", "info"); } else if (!isAnthropicProvider && !hasSearchKey) { ctx.ui.notify( - "Web search: Set BRAVE_API_KEY, TAVILY_API_KEY, SERPER_API_KEY, EXA_API_KEY, or OLLAMA_API_KEY, or use an Anthropic model for built-in search", + "Web search: Set BRAVE_API_KEY, TAVILY_API_KEY, MINIMAX_CODE_PLAN_KEY, SERPER_API_KEY, EXA_API_KEY, or OLLAMA_API_KEY, or use an Anthropic model for built-in search", "warning", ); } diff --git a/src/resources/extensions/search-the-web/provider.ts b/src/resources/extensions/search-the-web/provider.ts index 54b043057..fd722ece6 100644 --- a/src/resources/extensions/search-the-web/provider.ts +++ b/src/resources/extensions/search-the-web/provider.ts @@ -5,7 +5,7 @@ * Reads API keys from process.env at call time (not module load time) so * hot-reloaded keys work. Preference is stored in auth.json under the * synthetic provider key `search_provider` as - * { type: "api_key", key: "tavily" | "brave" | "serper" | "exa" | "ollama" | "combosearch" | "auto" }. + * { type: "api_key", key: "tavily" | "minimax" | "brave" | "serper" | "exa" | "ollama" | "combosearch" | "auto" }. * * @see S01-RESEARCH.md for the storage decision rationale (D002). */ @@ -23,6 +23,7 @@ const authFilePath = join(sfHome, "agent", "auth.json"); export type SearchProvider = | "tavily" + | "minimax" | "brave" | "serper" | "exa" @@ -32,6 +33,7 @@ export type SearchProviderPreference = SearchProvider | "auto"; const VALID_PREFERENCES = new Set([ "tavily", + "minimax", "brave", "serper", "exa", @@ -65,6 +67,16 @@ export function getOllamaApiKey(): string { return process.env.OLLAMA_API_KEY || ""; } +/** Returns the MiniMax Coding Plan search key, accepting documented aliases. */ +export function getMiniMaxSearchApiKey(): string { + return ( + process.env.MINIMAX_CODE_PLAN_KEY || + process.env.MINIMAX_CODING_API_KEY || + process.env.MINIMAX_API_KEY || + "" + ); +} + /** Returns the Serper API key from the environment, or empty string if not set. */ export function getSerperApiKey(): string { return process.env.SERPER_API_KEY || ""; @@ -129,17 +141,25 @@ export function resolveSearchProvider( overridePreference?: string, ): SearchProvider | null { const tavilyKey = getTavilyApiKey(); + const minimaxKey = getMiniMaxSearchApiKey(); const braveKey = getBraveApiKey(); const serperKey = getSerperApiKey(); const exaKey = getExaApiKey(); const ollamaKey = getOllamaApiKey(); const hasTavily = tavilyKey.length > 0; + const hasMiniMax = minimaxKey.length > 0; const hasBrave = braveKey.length > 0; const hasSerper = serperKey.length > 0; const hasExa = exaKey.length > 0; const hasOllama = ollamaKey.length > 0; - const hasAny = hasTavily || hasBrave || hasSerper || hasExa || hasOllama; + const hasAny = + hasTavily || + hasMiniMax || + hasBrave || + hasSerper || + hasExa || + hasOllama; // Determine effective preference let pref: SearchProviderPreference; @@ -163,6 +183,7 @@ export function resolveSearchProvider( // Resolve based on preference if (pref === "auto") { if (hasTavily) return "tavily"; + if (hasMiniMax) return "minimax"; if (hasBrave) return "brave"; if (hasSerper) return "serper"; if (hasExa) return "exa"; @@ -175,6 +196,17 @@ export function resolveSearchProvider( } if (pref === "tavily") { + if (hasTavily) return "tavily"; + if (hasMiniMax) return "minimax"; + if (hasBrave) return "brave"; + if (hasSerper) return "serper"; + if (hasExa) return "exa"; + if (hasOllama) return "ollama"; + return null; + } + + if (pref === "minimax") { + if (hasMiniMax) return "minimax"; if (hasTavily) return "tavily"; if (hasBrave) return "brave"; if (hasSerper) return "serper"; @@ -186,6 +218,7 @@ export function resolveSearchProvider( if (pref === "brave") { if (hasBrave) return "brave"; if (hasTavily) return "tavily"; + if (hasMiniMax) return "minimax"; if (hasSerper) return "serper"; if (hasExa) return "exa"; if (hasOllama) return "ollama"; @@ -195,6 +228,7 @@ export function resolveSearchProvider( if (pref === "serper") { if (hasSerper) return "serper"; if (hasTavily) return "tavily"; + if (hasMiniMax) return "minimax"; if (hasBrave) return "brave"; if (hasExa) return "exa"; if (hasOllama) return "ollama"; @@ -205,6 +239,7 @@ export function resolveSearchProvider( if (hasExa) return "exa"; if (hasSerper) return "serper"; if (hasTavily) return "tavily"; + if (hasMiniMax) return "minimax"; if (hasBrave) return "brave"; if (hasOllama) return "ollama"; return null; @@ -213,6 +248,7 @@ export function resolveSearchProvider( if (pref === "ollama") { if (hasOllama) return "ollama"; if (hasTavily) return "tavily"; + if (hasMiniMax) return "minimax"; if (hasBrave) return "brave"; if (hasSerper) return "serper"; if (hasExa) return "exa"; diff --git a/src/resources/extensions/search-the-web/tool-llm-context.ts b/src/resources/extensions/search-the-web/tool-llm-context.ts index a35760c00..b2347f388 100644 --- a/src/resources/extensions/search-the-web/tool-llm-context.ts +++ b/src/resources/extensions/search-the-web/tool-llm-context.ts @@ -7,9 +7,11 @@ * * Supports multiple backends: * - Tavily: POST-based, client-side token budgeting via budgetContent() + * - MiniMax: POST-based search snippets with client-side token budgeting * - Brave: GET-based LLM Context API with server-side budgeting * - Serper: search API + Jina Reader extraction * - Exa: search API with built-in extracted contents + * - Ollama: POST-based web search with client-side token budgeting * * Provider is selected by resolveSearchProvider() — same as tool-search.ts. * @@ -43,6 +45,7 @@ import { braveHeaders, getBraveApiKey, getExaApiKey, + getMiniMaxSearchApiKey, getOllamaApiKey, getSerperApiKey, getTavilyApiKey, @@ -105,7 +108,14 @@ interface LLMContextDetails { errorKind?: string; error?: string; retryAfterMs?: number; - provider?: "tavily" | "brave" | "serper" | "exa" | "ollama" | "combosearch"; + provider?: + | "tavily" + | "minimax" + | "brave" + | "serper" + | "exa" + | "ollama" + | "combosearch"; } interface SerperOrganicResult { @@ -130,6 +140,21 @@ interface ExaLLMContextResponse { results?: ExaLLMContextResult[]; } +interface MiniMaxSearchResult { + title?: string; + link?: string; + snippet?: string; + date?: string; +} + +interface MiniMaxSearchResponse { + organic?: MiniMaxSearchResult[]; + base_resp?: { + status_code?: number; + status_msg?: string; + }; +} + // ============================================================================= // Cache // ============================================================================= @@ -362,6 +387,63 @@ async function executeOllamaLLMContext( return { cached, latencyMs: timed.latencyMs, rateLimit: timed.rateLimit }; } +/** + * Execute a search_and_read query against the MiniMax Coding Plan search API. + * + * MiniMax currently returns search snippets rather than full fetched pages, so + * this path exposes those snippets through the same LLM context formatter. + */ +async function executeMiniMaxLLMContext( + params: { query: string; maxTokens: number; threshold: string }, + signal?: AbortSignal, +): Promise<{ + cached: CachedLLMContext; + latencyMs: number; + rateLimit?: RateLimitInfo; +}> { + const scoreThreshold = THRESHOLD_TO_SCORE[params.threshold] ?? 0.5; + + const timed = await fetchWithRetryTimed( + "https://api.minimax.io/v1/coding_plan/search", + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${getMiniMaxSearchApiKey()}`, + "MM-API-Source": "SF", + }, + body: JSON.stringify({ q: params.query }), + signal, + }, + 2, + ); + + const data: MiniMaxSearchResponse = await timed.response.json(); + if (data.base_resp?.status_code && data.base_resp.status_code !== 0) { + throw new Error( + `MiniMax search failed: ${data.base_resp.status_msg ?? data.base_resp.status_code}`, + ); + } + + const tavilyLikeResults: TavilyResult[] = (data.organic || []) + .filter((r) => typeof r.link === "string" && r.link.length > 0) + .map((r) => ({ + title: r.title || "(untitled)", + url: r.link as string, + content: r.snippet || "", + published_date: r.date, + score: 1.0, + })); + + const cached = budgetContent( + tavilyLikeResults, + params.maxTokens, + scoreThreshold, + ); + + return { cached, latencyMs: timed.latencyMs, rateLimit: timed.rateLimit }; +} + async function executeBraveLLMContext( params: { query: string; @@ -634,10 +716,13 @@ async function executeExaLLMContext( } function availableComboProviders(): Array< - "tavily" | "brave" | "serper" | "exa" | "ollama" + "tavily" | "minimax" | "brave" | "serper" | "exa" | "ollama" > { - const providers: Array<"tavily" | "brave" | "serper" | "exa" | "ollama"> = []; + const providers: Array< + "tavily" | "minimax" | "brave" | "serper" | "exa" | "ollama" + > = []; if (getTavilyApiKey()) providers.push("tavily"); + if (getMiniMaxSearchApiKey()) providers.push("minimax"); if (getBraveApiKey()) providers.push("brave"); if (getSerperApiKey()) providers.push("serper"); if (getExaApiKey()) providers.push("exa"); @@ -695,6 +780,16 @@ async function executeComboLLMContext( if (provider === "tavily") { return executeTavilyLLMContext(params, signal); } + if (provider === "minimax") { + return executeMiniMaxLLMContext( + { + query: params.query, + maxTokens: params.maxTokens, + threshold: params.threshold, + }, + signal, + ); + } if (provider === "ollama") { return executeOllamaLLMContext( { @@ -837,7 +932,7 @@ export function registerLLMContextTool(pi: ExtensionAPI) { content: [ { type: "text", - text: "search_and_read unavailable: No search API key is set. Use secure_env_collect to set TAVILY_API_KEY, BRAVE_API_KEY, SERPER_API_KEY, EXA_API_KEY, or OLLAMA_API_KEY.", + text: "search_and_read unavailable: No search API key is set. Use secure_env_collect to set TAVILY_API_KEY, MINIMAX_CODE_PLAN_KEY, BRAVE_API_KEY, SERPER_API_KEY, EXA_API_KEY, or OLLAMA_API_KEY.", }, ], isError: true, @@ -944,6 +1039,14 @@ export function registerLLMContextTool(pi: ExtensionAPI) { result = ollamaResult.cached; latencyMs = ollamaResult.latencyMs; rateLimit = ollamaResult.rateLimit; + } else if (provider === "minimax") { + const minimaxResult = await executeMiniMaxLLMContext( + { query: params.query, maxTokens, threshold }, + signal, + ); + result = minimaxResult.cached; + latencyMs = minimaxResult.latencyMs; + rateLimit = minimaxResult.rateLimit; } else if (provider === "serper") { const serperResult = await executeSerperLLMContext( { query: params.query, maxTokens, maxUrls, threshold, count }, diff --git a/src/resources/extensions/search-the-web/tool-search.ts b/src/resources/extensions/search-the-web/tool-search.ts index 6667b47fb..4f8ed4212 100644 --- a/src/resources/extensions/search-the-web/tool-search.ts +++ b/src/resources/extensions/search-the-web/tool-search.ts @@ -1,5 +1,5 @@ /** - * search-the-web tool — Rich web search with full Brave API support. + * search-the-web tool — Rich web search with Tavily, MiniMax, Ollama, Serper, Exa, and legacy Brave support. * * v3 improvements: * - Structured error taxonomy (auth_error, rate_limited, network_error, etc.) @@ -37,6 +37,7 @@ import { braveHeaders, getBraveApiKey, getExaApiKey, + getMiniMaxSearchApiKey, getOllamaApiKey, getSerperApiKey, getTavilyApiKey, @@ -124,7 +125,14 @@ interface SearchDetails { errorKind?: string; error?: string; retryAfterMs?: number; - provider?: "tavily" | "brave" | "serper" | "exa" | "ollama" | "combosearch"; + provider?: + | "tavily" + | "minimax" + | "brave" + | "serper" + | "exa" + | "ollama" + | "combosearch"; } // ============================================================================= @@ -356,6 +364,22 @@ interface ExaSearchResponse { results?: ExaSearchResult[]; } +interface MiniMaxSearchResult { + title?: string; + link?: string; + snippet?: string; + date?: string; +} + +interface MiniMaxSearchResponse { + organic?: MiniMaxSearchResult[]; + related_searches?: Array<{ query?: string }>; + base_resp?: { + status_code?: number; + status_msg?: string; + }; +} + /** * Execute a search against the Ollama web_search API. * Returns a CachedSearchResult with normalized, deduplicated results. @@ -401,6 +425,60 @@ async function executeOllamaSearch( }; } +/** + * Execute a search against the MiniMax Coding Plan search API. + */ +async function executeMiniMaxSearch( + params: { query: string }, + signal?: AbortSignal, +): Promise<{ + results: CachedSearchResult; + latencyMs: number; + rateLimit?: RateLimitInfo; +}> { + const timed = await fetchWithRetryTimed( + "https://api.minimax.io/v1/coding_plan/search", + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${getMiniMaxSearchApiKey()}`, + "MM-API-Source": "SF", + }, + body: JSON.stringify({ q: params.query }), + signal, + }, + 2, + ); + + const data: MiniMaxSearchResponse = await timed.response.json(); + if (data.base_resp?.status_code && data.base_resp.status_code !== 0) { + throw new Error( + `MiniMax search failed: ${data.base_resp.status_msg ?? data.base_resp.status_code}`, + ); + } + + const normalized: SearchResultFormatted[] = (data.organic || []) + .filter((r) => typeof r.link === "string" && r.link.length > 0) + .map((r) => ({ + title: r.title || "(untitled)", + url: r.link as string, + description: r.snippet || "", + age: r.date || undefined, + })); + const deduplicated = deduplicateResults(normalized); + + return { + results: { + results: deduplicated, + queryCorrected: false, + moreResultsAvailable: (data.related_searches?.length ?? 0) > 0, + }, + latencyMs: timed.latencyMs, + rateLimit: timed.rateLimit, + }; +} + async function executeSerperSearch( params: { query: string; domain?: string; count: number }, signal?: AbortSignal, @@ -599,10 +677,13 @@ async function executeBraveSearch( } function availableComboProviders(): Array< - "tavily" | "brave" | "serper" | "exa" | "ollama" + "tavily" | "minimax" | "brave" | "serper" | "exa" | "ollama" > { - const providers: Array<"tavily" | "brave" | "serper" | "exa" | "ollama"> = []; + const providers: Array< + "tavily" | "minimax" | "brave" | "serper" | "exa" | "ollama" + > = []; if (getTavilyApiKey()) providers.push("tavily"); + if (getMiniMaxSearchApiKey()) providers.push("minimax"); if (getBraveApiKey()) providers.push("brave"); if (getSerperApiKey()) providers.push("serper"); if (getExaApiKey()) providers.push("exa"); @@ -637,6 +718,9 @@ async function executeComboSearch( signal, ); } + if (provider === "minimax") { + return executeMiniMaxSearch({ query: params.query }, signal); + } if (provider === "ollama") { return executeOllamaSearch( { query: params.query, count: Math.max(10, params.count) }, @@ -749,7 +833,8 @@ export function registerSearchTool(pi: ExtensionAPI) { name: "search-the-web", label: "Web Search", description: - "Search the web using Brave Search API. Returns top results with titles, URLs, descriptions, " + + "Search the web using Tavily, MiniMax, Ollama, Serper, Exa, or an existing Brave Search API key. " + + "Returns top results with titles, URLs, descriptions, " + "extra contextual snippets, result ages, and optional AI summary. " + "Supports freshness filtering, domain filtering, and auto-detects recency-sensitive queries.", promptSnippet: "Search the web for information", @@ -810,7 +895,7 @@ export function registerSearchTool(pi: ExtensionAPI) { content: [ { type: "text", - text: "Web search unavailable: No search API key is set. Use secure_env_collect to set TAVILY_API_KEY, BRAVE_API_KEY, SERPER_API_KEY, EXA_API_KEY, or OLLAMA_API_KEY.", + text: "Web search unavailable: No search API key is set. Use secure_env_collect to set TAVILY_API_KEY, MINIMAX_CODE_PLAN_KEY, BRAVE_API_KEY, SERPER_API_KEY, EXA_API_KEY, or OLLAMA_API_KEY.", }, ], isError: true, @@ -1013,6 +1098,14 @@ export function registerSearchTool(pi: ExtensionAPI) { searchResult = ollamaResult.results; latencyMs = ollamaResult.latencyMs; rateLimit = ollamaResult.rateLimit; + } else if (provider === "minimax") { + const minimaxResult = await executeMiniMaxSearch( + { query: params.query }, + signal, + ); + searchResult = minimaxResult.results; + latencyMs = minimaxResult.latencyMs; + rateLimit = minimaxResult.rateLimit; } else if (provider === "serper") { const serperResult = await executeSerperSearch( { query: params.query, domain: params.domain, count: 10 }, diff --git a/src/resources/extensions/sf/preferences-types.ts b/src/resources/extensions/sf/preferences-types.ts index e51102b77..2721fdf78 100644 --- a/src/resources/extensions/sf/preferences-types.ts +++ b/src/resources/extensions/sf/preferences-types.ts @@ -389,10 +389,11 @@ export interface SFPreferences { verification_commands?: string[]; verification_auto_fix?: boolean; verification_max_retries?: number; - /** Search provider preference. "brave"/"tavily"/"serper"/"exa"/"ollama"/"combosearch" force that backend and disable native Anthropic search. "native" forces native only. "auto" = current default behavior. */ + /** Search provider preference. "brave"/"tavily"/"minimax"/"serper"/"exa"/"ollama"/"combosearch" force that backend and disable native Anthropic search. "native" forces native only. "auto" = current default behavior. */ search_provider?: | "brave" | "tavily" + | "minimax" | "serper" | "exa" | "ollama" diff --git a/src/resources/extensions/sf/preferences-validation.ts b/src/resources/extensions/sf/preferences-validation.ts index a10767b14..ad4c0ccf4 100644 --- a/src/resources/extensions/sf/preferences-validation.ts +++ b/src/resources/extensions/sf/preferences-validation.ts @@ -467,6 +467,7 @@ export function validatePreferences(preferences: SFPreferences): { const validSearchProviders = new Set([ "brave", "tavily", + "minimax", "serper", "exa", "ollama", @@ -482,7 +483,7 @@ export function validatePreferences(preferences: SFPreferences): { preferences.search_provider as SFPreferences["search_provider"]; } else { errors.push( - `search_provider must be one of: brave, tavily, serper, exa, ollama, combosearch, native, auto`, + `search_provider must be one of: brave, tavily, minimax, serper, exa, ollama, combosearch, native, auto`, ); } }