mirror of https://github.com/openclaw/openclaw.git
alphabetize web search providers (#40259)
Merged via squash.
Prepared head SHA: be6350e5ae
Co-authored-by: kesku <62210496+kesku@users.noreply.github.com>
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Reviewed-by: @obviyus
This commit is contained in:
parent
e3df94365b
commit
adec8b28bb
|
|
@ -15,6 +15,7 @@ Docs: https://docs.openclaw.ai
|
|||
- CLI/backup: add `openclaw backup create` and `openclaw backup verify` for local state archives, including `--only-config`, `--no-include-workspace`, manifest/payload validation, and backup guidance in destructive flows. (#40163) thanks @shichangs.
|
||||
- CLI/backup: improve archive naming for date sorting, add config-only backup mode, and harden backup planning, publication, and verification edge cases. (#40163) Thanks @gumadeiras.
|
||||
- ACP/Provenance: add optional ACP ingress provenance metadata and visible receipt injection (`openclaw acp --provenance off|meta|meta+receipt`) so OpenClaw agents can retain and report ACP-origin context with session trace IDs. (#40473) thanks @mbelinky.
|
||||
- Tools/web search: alphabetize provider ordering across runtime selection, onboarding/configure pickers, and config metadata, so provider lists stay neutral and multi-key auto-detect now prefers Grok before Kimi. (#40259) thanks @kesku.
|
||||
|
||||
### Breaking
|
||||
|
||||
|
|
|
|||
|
|
@ -43,9 +43,9 @@ The table above is alphabetical. If no `provider` is explicitly set, runtime aut
|
|||
|
||||
1. **Brave** — `BRAVE_API_KEY` env var or `tools.web.search.apiKey` config
|
||||
2. **Gemini** — `GEMINI_API_KEY` env var or `tools.web.search.gemini.apiKey` config
|
||||
3. **Kimi** — `KIMI_API_KEY` / `MOONSHOT_API_KEY` env var or `tools.web.search.kimi.apiKey` config
|
||||
4. **Perplexity** — `PERPLEXITY_API_KEY`, `OPENROUTER_API_KEY`, or `tools.web.search.perplexity.apiKey` config
|
||||
5. **Grok** — `XAI_API_KEY` env var or `tools.web.search.grok.apiKey` config
|
||||
3. **Grok** — `XAI_API_KEY` env var or `tools.web.search.grok.apiKey` config
|
||||
4. **Kimi** — `KIMI_API_KEY` / `MOONSHOT_API_KEY` env var or `tools.web.search.kimi.apiKey` config
|
||||
5. **Perplexity** — `PERPLEXITY_API_KEY`, `OPENROUTER_API_KEY`, or `tools.web.search.perplexity.apiKey` config
|
||||
|
||||
If no keys are found, it falls back to Brave (you'll get a missing-key error prompting you to configure one).
|
||||
|
||||
|
|
@ -212,10 +212,10 @@ Search the web using your configured provider.
|
|||
- `tools.web.search.enabled` must not be `false` (default: enabled)
|
||||
- API key for your chosen provider:
|
||||
- **Brave**: `BRAVE_API_KEY` or `tools.web.search.apiKey`
|
||||
- **Perplexity**: `PERPLEXITY_API_KEY`, `OPENROUTER_API_KEY`, or `tools.web.search.perplexity.apiKey`
|
||||
- **Gemini**: `GEMINI_API_KEY` or `tools.web.search.gemini.apiKey`
|
||||
- **Grok**: `XAI_API_KEY` or `tools.web.search.grok.apiKey`
|
||||
- **Kimi**: `KIMI_API_KEY`, `MOONSHOT_API_KEY`, or `tools.web.search.kimi.apiKey`
|
||||
- **Perplexity**: `PERPLEXITY_API_KEY`, `OPENROUTER_API_KEY`, or `tools.web.search.perplexity.apiKey`
|
||||
|
||||
### Config
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import {
|
|||
writeCache,
|
||||
} from "./web-shared.js";
|
||||
|
||||
const SEARCH_PROVIDERS = ["brave", "perplexity", "grok", "gemini", "kimi"] as const;
|
||||
const SEARCH_PROVIDERS = ["brave", "gemini", "grok", "kimi", "perplexity"] as const;
|
||||
const DEFAULT_SEARCH_COUNT = 5;
|
||||
const MAX_SEARCH_COUNT = 10;
|
||||
|
||||
|
|
@ -492,19 +492,10 @@ function resolveSearchApiKey(search?: WebSearchConfig): string | undefined {
|
|||
}
|
||||
|
||||
function missingSearchKeyPayload(provider: (typeof SEARCH_PROVIDERS)[number]) {
|
||||
if (provider === "perplexity") {
|
||||
if (provider === "brave") {
|
||||
return {
|
||||
error: "missing_perplexity_api_key",
|
||||
message:
|
||||
"web_search (perplexity) needs an API key. Set PERPLEXITY_API_KEY or OPENROUTER_API_KEY in the Gateway environment, or configure tools.web.search.perplexity.apiKey.",
|
||||
docs: "https://docs.openclaw.ai/tools/web",
|
||||
};
|
||||
}
|
||||
if (provider === "grok") {
|
||||
return {
|
||||
error: "missing_xai_api_key",
|
||||
message:
|
||||
"web_search (grok) needs an xAI API key. Set XAI_API_KEY in the Gateway environment, or configure tools.web.search.grok.apiKey.",
|
||||
error: "missing_brave_api_key",
|
||||
message: `web_search (brave) needs a Brave Search API key. Run \`${formatCliCommand("openclaw configure --section web")}\` to store it, or set BRAVE_API_KEY in the Gateway environment.`,
|
||||
docs: "https://docs.openclaw.ai/tools/web",
|
||||
};
|
||||
}
|
||||
|
|
@ -516,6 +507,14 @@ function missingSearchKeyPayload(provider: (typeof SEARCH_PROVIDERS)[number]) {
|
|||
docs: "https://docs.openclaw.ai/tools/web",
|
||||
};
|
||||
}
|
||||
if (provider === "grok") {
|
||||
return {
|
||||
error: "missing_xai_api_key",
|
||||
message:
|
||||
"web_search (grok) needs an xAI API key. Set XAI_API_KEY in the Gateway environment, or configure tools.web.search.grok.apiKey.",
|
||||
docs: "https://docs.openclaw.ai/tools/web",
|
||||
};
|
||||
}
|
||||
if (provider === "kimi") {
|
||||
return {
|
||||
error: "missing_kimi_api_key",
|
||||
|
|
@ -525,8 +524,9 @@ function missingSearchKeyPayload(provider: (typeof SEARCH_PROVIDERS)[number]) {
|
|||
};
|
||||
}
|
||||
return {
|
||||
error: "missing_brave_api_key",
|
||||
message: `web_search needs a Brave Search API key. Run \`${formatCliCommand("openclaw configure --section web")}\` to store it, or set BRAVE_API_KEY in the Gateway environment.`,
|
||||
error: "missing_perplexity_api_key",
|
||||
message:
|
||||
"web_search (perplexity) needs an API key. Set PERPLEXITY_API_KEY or OPENROUTER_API_KEY in the Gateway environment, or configure tools.web.search.perplexity.apiKey.",
|
||||
docs: "https://docs.openclaw.ai/tools/web",
|
||||
};
|
||||
}
|
||||
|
|
@ -536,32 +536,32 @@ function resolveSearchProvider(search?: WebSearchConfig): (typeof SEARCH_PROVIDE
|
|||
search && "provider" in search && typeof search.provider === "string"
|
||||
? search.provider.trim().toLowerCase()
|
||||
: "";
|
||||
if (raw === "perplexity") {
|
||||
return "perplexity";
|
||||
}
|
||||
if (raw === "grok") {
|
||||
return "grok";
|
||||
if (raw === "brave") {
|
||||
return "brave";
|
||||
}
|
||||
if (raw === "gemini") {
|
||||
return "gemini";
|
||||
}
|
||||
if (raw === "grok") {
|
||||
return "grok";
|
||||
}
|
||||
if (raw === "kimi") {
|
||||
return "kimi";
|
||||
}
|
||||
if (raw === "brave") {
|
||||
return "brave";
|
||||
if (raw === "perplexity") {
|
||||
return "perplexity";
|
||||
}
|
||||
|
||||
// Auto-detect provider from available API keys (priority order)
|
||||
// Auto-detect provider from available API keys (alphabetical order)
|
||||
if (raw === "") {
|
||||
// 1. Brave
|
||||
// Brave
|
||||
if (resolveSearchApiKey(search)) {
|
||||
logVerbose(
|
||||
'web_search: no provider configured, auto-detected "brave" from available API keys',
|
||||
);
|
||||
return "brave";
|
||||
}
|
||||
// 2. Gemini
|
||||
// Gemini
|
||||
const geminiConfig = resolveGeminiConfig(search);
|
||||
if (resolveGeminiApiKey(geminiConfig)) {
|
||||
logVerbose(
|
||||
|
|
@ -569,7 +569,15 @@ function resolveSearchProvider(search?: WebSearchConfig): (typeof SEARCH_PROVIDE
|
|||
);
|
||||
return "gemini";
|
||||
}
|
||||
// 3. Kimi
|
||||
// Grok
|
||||
const grokConfig = resolveGrokConfig(search);
|
||||
if (resolveGrokApiKey(grokConfig)) {
|
||||
logVerbose(
|
||||
'web_search: no provider configured, auto-detected "grok" from available API keys',
|
||||
);
|
||||
return "grok";
|
||||
}
|
||||
// Kimi
|
||||
const kimiConfig = resolveKimiConfig(search);
|
||||
if (resolveKimiApiKey(kimiConfig)) {
|
||||
logVerbose(
|
||||
|
|
@ -577,7 +585,7 @@ function resolveSearchProvider(search?: WebSearchConfig): (typeof SEARCH_PROVIDE
|
|||
);
|
||||
return "kimi";
|
||||
}
|
||||
// 4. Perplexity
|
||||
// Perplexity
|
||||
const perplexityConfig = resolvePerplexityConfig(search);
|
||||
const { apiKey: perplexityKey } = resolvePerplexityApiKey(perplexityConfig);
|
||||
if (perplexityKey) {
|
||||
|
|
@ -586,14 +594,6 @@ function resolveSearchProvider(search?: WebSearchConfig): (typeof SEARCH_PROVIDE
|
|||
);
|
||||
return "perplexity";
|
||||
}
|
||||
// 5. Grok
|
||||
const grokConfig = resolveGrokConfig(search);
|
||||
if (resolveGrokApiKey(grokConfig)) {
|
||||
logVerbose(
|
||||
'web_search: no provider configured, auto-detected "grok" from available API keys',
|
||||
);
|
||||
return "grok";
|
||||
}
|
||||
}
|
||||
|
||||
return "brave";
|
||||
|
|
|
|||
|
|
@ -188,7 +188,10 @@ async function promptWebToolsConfig(
|
|||
if (stored && SEARCH_PROVIDER_OPTIONS.some((e) => e.value === stored)) {
|
||||
return stored;
|
||||
}
|
||||
return SEARCH_PROVIDER_OPTIONS.find((e) => hasKeyForProvider(e.value))?.value ?? "brave";
|
||||
return (
|
||||
SEARCH_PROVIDER_OPTIONS.find((e) => hasKeyForProvider(e.value))?.value ??
|
||||
SEARCH_PROVIDER_OPTIONS[0].value
|
||||
);
|
||||
})();
|
||||
|
||||
note(
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import type { RuntimeEnv } from "../runtime.js";
|
|||
import type { WizardPrompter } from "../wizard/prompts.js";
|
||||
import type { SecretInputMode } from "./onboard-types.js";
|
||||
|
||||
export type SearchProvider = "perplexity" | "brave" | "gemini" | "grok" | "kimi";
|
||||
export type SearchProvider = "brave" | "gemini" | "grok" | "kimi" | "perplexity";
|
||||
|
||||
type SearchProviderEntry = {
|
||||
value: SearchProvider;
|
||||
|
|
@ -73,14 +73,14 @@ function rawKeyValue(config: OpenClawConfig, provider: SearchProvider): unknown
|
|||
switch (provider) {
|
||||
case "brave":
|
||||
return search?.apiKey;
|
||||
case "perplexity":
|
||||
return search?.perplexity?.apiKey;
|
||||
case "gemini":
|
||||
return search?.gemini?.apiKey;
|
||||
case "grok":
|
||||
return search?.grok?.apiKey;
|
||||
case "kimi":
|
||||
return search?.kimi?.apiKey;
|
||||
case "perplexity":
|
||||
return search?.perplexity?.apiKey;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -132,9 +132,6 @@ export function applySearchKey(
|
|||
case "brave":
|
||||
search.apiKey = key;
|
||||
break;
|
||||
case "perplexity":
|
||||
search.perplexity = { ...search.perplexity, apiKey: key };
|
||||
break;
|
||||
case "gemini":
|
||||
search.gemini = { ...search.gemini, apiKey: key };
|
||||
break;
|
||||
|
|
@ -144,6 +141,9 @@ export function applySearchKey(
|
|||
case "kimi":
|
||||
search.kimi = { ...search.kimi, apiKey: key };
|
||||
break;
|
||||
case "perplexity":
|
||||
search.perplexity = { ...search.perplexity, apiKey: key };
|
||||
break;
|
||||
}
|
||||
return {
|
||||
...config,
|
||||
|
|
@ -222,7 +222,7 @@ export async function setupSearch(
|
|||
if (detected) {
|
||||
return detected.value;
|
||||
}
|
||||
return "brave";
|
||||
return SEARCH_PROVIDER_OPTIONS[0].value;
|
||||
})();
|
||||
|
||||
type PickerValue = SearchProvider | "__skip__";
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ describe("web search provider auto-detection", () => {
|
|||
expect(resolveSearchProvider({})).toBe("kimi");
|
||||
});
|
||||
|
||||
it("follows priority order — brave wins when multiple keys available", () => {
|
||||
it("follows alphabetical order — brave wins when multiple keys available", () => {
|
||||
process.env.BRAVE_API_KEY = "test-brave-key"; // pragma: allowlist secret
|
||||
process.env.GEMINI_API_KEY = "test-gemini-key"; // pragma: allowlist secret
|
||||
process.env.PERPLEXITY_API_KEY = "test-perplexity-key"; // pragma: allowlist secret
|
||||
|
|
@ -150,18 +150,18 @@ describe("web search provider auto-detection", () => {
|
|||
expect(resolveSearchProvider({})).toBe("brave");
|
||||
});
|
||||
|
||||
it("gemini wins over perplexity and grok when brave unavailable", () => {
|
||||
it("gemini wins over grok, kimi, and perplexity when brave unavailable", () => {
|
||||
process.env.GEMINI_API_KEY = "test-gemini-key"; // pragma: allowlist secret
|
||||
process.env.PERPLEXITY_API_KEY = "test-perplexity-key"; // pragma: allowlist secret
|
||||
process.env.XAI_API_KEY = "test-xai-key"; // pragma: allowlist secret
|
||||
expect(resolveSearchProvider({})).toBe("gemini");
|
||||
});
|
||||
|
||||
it("brave wins over gemini and grok when perplexity unavailable", () => {
|
||||
process.env.BRAVE_API_KEY = "test-brave-key"; // pragma: allowlist secret
|
||||
process.env.GEMINI_API_KEY = "test-gemini-key"; // pragma: allowlist secret
|
||||
it("grok wins over kimi and perplexity when brave and gemini unavailable", () => {
|
||||
process.env.XAI_API_KEY = "test-xai-key"; // pragma: allowlist secret
|
||||
expect(resolveSearchProvider({})).toBe("brave");
|
||||
process.env.KIMI_API_KEY = "test-kimi-key"; // pragma: allowlist secret
|
||||
process.env.PERPLEXITY_API_KEY = "test-perplexity-key"; // pragma: allowlist secret
|
||||
expect(resolveSearchProvider({})).toBe("grok");
|
||||
});
|
||||
|
||||
it("explicit provider always wins regardless of keys", () => {
|
||||
|
|
|
|||
|
|
@ -649,11 +649,13 @@ export const FIELD_HELP: Record<string, string> = {
|
|||
"tools.message.broadcast.enabled": "Enable broadcast action (default: true).",
|
||||
"tools.web.search.enabled": "Enable the web_search tool (requires a provider API key).",
|
||||
"tools.web.search.provider":
|
||||
'Search provider ("brave", "perplexity", "grok", "gemini", or "kimi"). Auto-detected from available API keys if omitted.',
|
||||
'Search provider ("brave", "gemini", "grok", "kimi", or "perplexity"). Auto-detected from available API keys if omitted.',
|
||||
"tools.web.search.apiKey": "Brave Search API key (fallback: BRAVE_API_KEY env var).",
|
||||
"tools.web.search.maxResults": "Default number of results to return (1-10).",
|
||||
"tools.web.search.maxResults": "Number of results to return (1-10).",
|
||||
"tools.web.search.timeoutSeconds": "Timeout in seconds for web_search requests.",
|
||||
"tools.web.search.cacheTtlMinutes": "Cache TTL in minutes for web_search results.",
|
||||
"tools.web.search.brave.mode":
|
||||
'Brave Search mode: "web" (URL results) or "llm-context" (pre-extracted page content for LLM grounding).',
|
||||
"tools.web.search.gemini.apiKey":
|
||||
"Gemini API key for Google Search grounding (fallback: GEMINI_API_KEY env var).",
|
||||
"tools.web.search.gemini.model": 'Gemini model override (default: "gemini-2.5-flash").',
|
||||
|
|
@ -670,8 +672,6 @@ export const FIELD_HELP: Record<string, string> = {
|
|||
"Optional Perplexity/OpenRouter chat-completions base URL override. Setting this opts Perplexity into the legacy Sonar/OpenRouter compatibility path.",
|
||||
"tools.web.search.perplexity.model":
|
||||
'Optional Sonar/OpenRouter model override (default: "perplexity/sonar-pro"). Setting this opts Perplexity into the legacy chat-completions compatibility path.',
|
||||
"tools.web.search.brave.mode":
|
||||
'Brave Search mode: "web" (URL results) or "llm-context" (pre-extracted page content for LLM grounding).',
|
||||
"tools.web.fetch.enabled": "Enable the web_fetch tool (lightweight HTTP fetch).",
|
||||
"tools.web.fetch.maxChars": "Max characters returned by web_fetch (truncated).",
|
||||
"tools.web.fetch.maxCharsCap":
|
||||
|
|
|
|||
|
|
@ -218,17 +218,17 @@ export const FIELD_LABELS: Record<string, string> = {
|
|||
"tools.web.search.maxResults": "Web Search Max Results",
|
||||
"tools.web.search.timeoutSeconds": "Web Search Timeout (sec)",
|
||||
"tools.web.search.cacheTtlMinutes": "Web Search Cache TTL (min)",
|
||||
"tools.web.search.perplexity.apiKey": "Perplexity API Key", // pragma: allowlist secret
|
||||
"tools.web.search.perplexity.baseUrl": "Perplexity Base URL",
|
||||
"tools.web.search.perplexity.model": "Perplexity Model",
|
||||
"tools.web.search.brave.mode": "Brave Search Mode",
|
||||
"tools.web.search.gemini.apiKey": "Gemini Search API Key", // pragma: allowlist secret
|
||||
"tools.web.search.gemini.model": "Gemini Search Model",
|
||||
"tools.web.search.grok.apiKey": "Grok Search API Key", // pragma: allowlist secret
|
||||
"tools.web.search.grok.model": "Grok Search Model",
|
||||
"tools.web.search.brave.mode": "Brave Search Mode",
|
||||
"tools.web.search.kimi.apiKey": "Kimi Search API Key", // pragma: allowlist secret
|
||||
"tools.web.search.kimi.baseUrl": "Kimi Search Base URL",
|
||||
"tools.web.search.kimi.model": "Kimi Search Model",
|
||||
"tools.web.search.perplexity.apiKey": "Perplexity API Key", // pragma: allowlist secret
|
||||
"tools.web.search.perplexity.baseUrl": "Perplexity Base URL",
|
||||
"tools.web.search.perplexity.model": "Perplexity Model",
|
||||
"tools.web.fetch.enabled": "Enable Web Fetch Tool",
|
||||
"tools.web.fetch.maxChars": "Web Fetch Max Chars",
|
||||
"tools.web.fetch.maxCharsCap": "Web Fetch Hard Max Chars",
|
||||
|
|
|
|||
|
|
@ -441,8 +441,8 @@ export type ToolsConfig = {
|
|||
search?: {
|
||||
/** Enable web search tool (default: true when API key is present). */
|
||||
enabled?: boolean;
|
||||
/** Search provider ("brave", "perplexity", "grok", "gemini", or "kimi"). */
|
||||
provider?: "brave" | "perplexity" | "grok" | "gemini" | "kimi";
|
||||
/** Search provider ("brave", "gemini", "grok", "kimi", or "perplexity"). */
|
||||
provider?: "brave" | "gemini" | "grok" | "kimi" | "perplexity";
|
||||
/** Brave Search API key (optional; defaults to BRAVE_API_KEY env var). */
|
||||
apiKey?: SecretInput;
|
||||
/** Default search results count (1-10). */
|
||||
|
|
@ -451,13 +451,16 @@ export type ToolsConfig = {
|
|||
timeoutSeconds?: number;
|
||||
/** Cache TTL in minutes for search results. */
|
||||
cacheTtlMinutes?: number;
|
||||
/** Perplexity-specific configuration (used when provider="perplexity"). */
|
||||
perplexity?: {
|
||||
/** API key for Perplexity (defaults to PERPLEXITY_API_KEY env var). */
|
||||
/** Brave-specific configuration (used when provider="brave"). */
|
||||
brave?: {
|
||||
/** Brave Search mode: "web" (standard results) or "llm-context" (pre-extracted page content). Default: "web". */
|
||||
mode?: "web" | "llm-context";
|
||||
};
|
||||
/** Gemini-specific configuration (used when provider="gemini"). */
|
||||
gemini?: {
|
||||
/** Gemini API key (defaults to GEMINI_API_KEY env var). */
|
||||
apiKey?: SecretInput;
|
||||
/** @deprecated Legacy Sonar/OpenRouter field. Ignored by Search API. */
|
||||
baseUrl?: string;
|
||||
/** @deprecated Legacy Sonar/OpenRouter field. Ignored by Search API. */
|
||||
/** Model to use for grounded search (defaults to "gemini-2.5-flash"). */
|
||||
model?: string;
|
||||
};
|
||||
/** Grok-specific configuration (used when provider="grok"). */
|
||||
|
|
@ -469,13 +472,6 @@ export type ToolsConfig = {
|
|||
/** Include inline citations in response text as markdown links (default: false). */
|
||||
inlineCitations?: boolean;
|
||||
};
|
||||
/** Gemini-specific configuration (used when provider="gemini"). */
|
||||
gemini?: {
|
||||
/** Gemini API key (defaults to GEMINI_API_KEY env var). */
|
||||
apiKey?: SecretInput;
|
||||
/** Model to use for grounded search (defaults to "gemini-2.5-flash"). */
|
||||
model?: string;
|
||||
};
|
||||
/** Kimi-specific configuration (used when provider="kimi"). */
|
||||
kimi?: {
|
||||
/** Moonshot/Kimi API key (defaults to KIMI_API_KEY or MOONSHOT_API_KEY env var). */
|
||||
|
|
@ -485,10 +481,14 @@ export type ToolsConfig = {
|
|||
/** Model to use (defaults to "moonshot-v1-128k"). */
|
||||
model?: string;
|
||||
};
|
||||
/** Brave-specific configuration (used when provider="brave"). */
|
||||
brave?: {
|
||||
/** Brave Search mode: "web" (standard results) or "llm-context" (pre-extracted page content). Default: "web". */
|
||||
mode?: "web" | "llm-context";
|
||||
/** Perplexity-specific configuration (used when provider="perplexity"). */
|
||||
perplexity?: {
|
||||
/** API key for Perplexity (defaults to PERPLEXITY_API_KEY env var). */
|
||||
apiKey?: SecretInput;
|
||||
/** @deprecated Legacy Sonar/OpenRouter field. Ignored by Search API. */
|
||||
baseUrl?: string;
|
||||
/** @deprecated Legacy Sonar/OpenRouter field. Ignored by Search API. */
|
||||
model?: string;
|
||||
};
|
||||
};
|
||||
fetch?: {
|
||||
|
|
|
|||
Loading…
Reference in New Issue