Skip to content

Commit adec8b2

Browse files
keskuobviyus
andauthored
alphabetize web search providers (#40259)
Merged via squash. Prepared head SHA: be6350e Co-authored-by: kesku <[email protected]> Co-authored-by: obviyus <[email protected]> Reviewed-by: @obviyus
1 parent e3df943 commit adec8b2

File tree

9 files changed

+82
-78
lines changed

9 files changed

+82
-78
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Docs: https://docs.openclaw.ai
1515
- 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.
1616
- 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.
1717
- 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.
18+
- 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.
1819

1920
### Breaking
2021

docs/tools/web.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ The table above is alphabetical. If no `provider` is explicitly set, runtime aut
4343

4444
1. **Brave**`BRAVE_API_KEY` env var or `tools.web.search.apiKey` config
4545
2. **Gemini**`GEMINI_API_KEY` env var or `tools.web.search.gemini.apiKey` config
46-
3. **Kimi**`KIMI_API_KEY` / `MOONSHOT_API_KEY` env var or `tools.web.search.kimi.apiKey` config
47-
4. **Perplexity**`PERPLEXITY_API_KEY`, `OPENROUTER_API_KEY`, or `tools.web.search.perplexity.apiKey` config
48-
5. **Grok**`XAI_API_KEY` env var or `tools.web.search.grok.apiKey` config
46+
3. **Grok**`XAI_API_KEY` env var or `tools.web.search.grok.apiKey` config
47+
4. **Kimi**`KIMI_API_KEY` / `MOONSHOT_API_KEY` env var or `tools.web.search.kimi.apiKey` config
48+
5. **Perplexity**`PERPLEXITY_API_KEY`, `OPENROUTER_API_KEY`, or `tools.web.search.perplexity.apiKey` config
4949

5050
If no keys are found, it falls back to Brave (you'll get a missing-key error prompting you to configure one).
5151

@@ -212,10 +212,10 @@ Search the web using your configured provider.
212212
- `tools.web.search.enabled` must not be `false` (default: enabled)
213213
- API key for your chosen provider:
214214
- **Brave**: `BRAVE_API_KEY` or `tools.web.search.apiKey`
215-
- **Perplexity**: `PERPLEXITY_API_KEY`, `OPENROUTER_API_KEY`, or `tools.web.search.perplexity.apiKey`
216215
- **Gemini**: `GEMINI_API_KEY` or `tools.web.search.gemini.apiKey`
217216
- **Grok**: `XAI_API_KEY` or `tools.web.search.grok.apiKey`
218217
- **Kimi**: `KIMI_API_KEY`, `MOONSHOT_API_KEY`, or `tools.web.search.kimi.apiKey`
218+
- **Perplexity**: `PERPLEXITY_API_KEY`, `OPENROUTER_API_KEY`, or `tools.web.search.perplexity.apiKey`
219219

220220
### Config
221221

src/agents/tools/web-search.ts

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
writeCache,
2222
} from "./web-shared.js";
2323

24-
const SEARCH_PROVIDERS = ["brave", "perplexity", "grok", "gemini", "kimi"] as const;
24+
const SEARCH_PROVIDERS = ["brave", "gemini", "grok", "kimi", "perplexity"] as const;
2525
const DEFAULT_SEARCH_COUNT = 5;
2626
const MAX_SEARCH_COUNT = 10;
2727

@@ -492,27 +492,26 @@ function resolveSearchApiKey(search?: WebSearchConfig): string | undefined {
492492
}
493493

494494
function missingSearchKeyPayload(provider: (typeof SEARCH_PROVIDERS)[number]) {
495-
if (provider === "perplexity") {
495+
if (provider === "brave") {
496496
return {
497-
error: "missing_perplexity_api_key",
498-
message:
499-
"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.",
497+
error: "missing_brave_api_key",
498+
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.`,
500499
docs: "https://docs.openclaw.ai/tools/web",
501500
};
502501
}
503-
if (provider === "grok") {
502+
if (provider === "gemini") {
504503
return {
505-
error: "missing_xai_api_key",
504+
error: "missing_gemini_api_key",
506505
message:
507-
"web_search (grok) needs an xAI API key. Set XAI_API_KEY in the Gateway environment, or configure tools.web.search.grok.apiKey.",
506+
"web_search (gemini) needs an API key. Set GEMINI_API_KEY in the Gateway environment, or configure tools.web.search.gemini.apiKey.",
508507
docs: "https://docs.openclaw.ai/tools/web",
509508
};
510509
}
511-
if (provider === "gemini") {
510+
if (provider === "grok") {
512511
return {
513-
error: "missing_gemini_api_key",
512+
error: "missing_xai_api_key",
514513
message:
515-
"web_search (gemini) needs an API key. Set GEMINI_API_KEY in the Gateway environment, or configure tools.web.search.gemini.apiKey.",
514+
"web_search (grok) needs an xAI API key. Set XAI_API_KEY in the Gateway environment, or configure tools.web.search.grok.apiKey.",
516515
docs: "https://docs.openclaw.ai/tools/web",
517516
};
518517
}
@@ -525,8 +524,9 @@ function missingSearchKeyPayload(provider: (typeof SEARCH_PROVIDERS)[number]) {
525524
};
526525
}
527526
return {
528-
error: "missing_brave_api_key",
529-
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.`,
527+
error: "missing_perplexity_api_key",
528+
message:
529+
"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.",
530530
docs: "https://docs.openclaw.ai/tools/web",
531531
};
532532
}
@@ -536,48 +536,56 @@ function resolveSearchProvider(search?: WebSearchConfig): (typeof SEARCH_PROVIDE
536536
search && "provider" in search && typeof search.provider === "string"
537537
? search.provider.trim().toLowerCase()
538538
: "";
539-
if (raw === "perplexity") {
540-
return "perplexity";
541-
}
542-
if (raw === "grok") {
543-
return "grok";
539+
if (raw === "brave") {
540+
return "brave";
544541
}
545542
if (raw === "gemini") {
546543
return "gemini";
547544
}
545+
if (raw === "grok") {
546+
return "grok";
547+
}
548548
if (raw === "kimi") {
549549
return "kimi";
550550
}
551-
if (raw === "brave") {
552-
return "brave";
551+
if (raw === "perplexity") {
552+
return "perplexity";
553553
}
554554

555-
// Auto-detect provider from available API keys (priority order)
555+
// Auto-detect provider from available API keys (alphabetical order)
556556
if (raw === "") {
557-
// 1. Brave
557+
// Brave
558558
if (resolveSearchApiKey(search)) {
559559
logVerbose(
560560
'web_search: no provider configured, auto-detected "brave" from available API keys',
561561
);
562562
return "brave";
563563
}
564-
// 2. Gemini
564+
// Gemini
565565
const geminiConfig = resolveGeminiConfig(search);
566566
if (resolveGeminiApiKey(geminiConfig)) {
567567
logVerbose(
568568
'web_search: no provider configured, auto-detected "gemini" from available API keys',
569569
);
570570
return "gemini";
571571
}
572-
// 3. Kimi
572+
// Grok
573+
const grokConfig = resolveGrokConfig(search);
574+
if (resolveGrokApiKey(grokConfig)) {
575+
logVerbose(
576+
'web_search: no provider configured, auto-detected "grok" from available API keys',
577+
);
578+
return "grok";
579+
}
580+
// Kimi
573581
const kimiConfig = resolveKimiConfig(search);
574582
if (resolveKimiApiKey(kimiConfig)) {
575583
logVerbose(
576584
'web_search: no provider configured, auto-detected "kimi" from available API keys',
577585
);
578586
return "kimi";
579587
}
580-
// 4. Perplexity
588+
// Perplexity
581589
const perplexityConfig = resolvePerplexityConfig(search);
582590
const { apiKey: perplexityKey } = resolvePerplexityApiKey(perplexityConfig);
583591
if (perplexityKey) {
@@ -586,14 +594,6 @@ function resolveSearchProvider(search?: WebSearchConfig): (typeof SEARCH_PROVIDE
586594
);
587595
return "perplexity";
588596
}
589-
// 5. Grok
590-
const grokConfig = resolveGrokConfig(search);
591-
if (resolveGrokApiKey(grokConfig)) {
592-
logVerbose(
593-
'web_search: no provider configured, auto-detected "grok" from available API keys',
594-
);
595-
return "grok";
596-
}
597597
}
598598

599599
return "brave";

src/commands/configure.wizard.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,10 @@ async function promptWebToolsConfig(
188188
if (stored && SEARCH_PROVIDER_OPTIONS.some((e) => e.value === stored)) {
189189
return stored;
190190
}
191-
return SEARCH_PROVIDER_OPTIONS.find((e) => hasKeyForProvider(e.value))?.value ?? "brave";
191+
return (
192+
SEARCH_PROVIDER_OPTIONS.find((e) => hasKeyForProvider(e.value))?.value ??
193+
SEARCH_PROVIDER_OPTIONS[0].value
194+
);
192195
})();
193196

194197
note(

src/commands/onboard-search.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type { RuntimeEnv } from "../runtime.js";
1010
import type { WizardPrompter } from "../wizard/prompts.js";
1111
import type { SecretInputMode } from "./onboard-types.js";
1212

13-
export type SearchProvider = "perplexity" | "brave" | "gemini" | "grok" | "kimi";
13+
export type SearchProvider = "brave" | "gemini" | "grok" | "kimi" | "perplexity";
1414

1515
type SearchProviderEntry = {
1616
value: SearchProvider;
@@ -73,14 +73,14 @@ function rawKeyValue(config: OpenClawConfig, provider: SearchProvider): unknown
7373
switch (provider) {
7474
case "brave":
7575
return search?.apiKey;
76-
case "perplexity":
77-
return search?.perplexity?.apiKey;
7876
case "gemini":
7977
return search?.gemini?.apiKey;
8078
case "grok":
8179
return search?.grok?.apiKey;
8280
case "kimi":
8381
return search?.kimi?.apiKey;
82+
case "perplexity":
83+
return search?.perplexity?.apiKey;
8484
}
8585
}
8686

@@ -132,9 +132,6 @@ export function applySearchKey(
132132
case "brave":
133133
search.apiKey = key;
134134
break;
135-
case "perplexity":
136-
search.perplexity = { ...search.perplexity, apiKey: key };
137-
break;
138135
case "gemini":
139136
search.gemini = { ...search.gemini, apiKey: key };
140137
break;
@@ -144,6 +141,9 @@ export function applySearchKey(
144141
case "kimi":
145142
search.kimi = { ...search.kimi, apiKey: key };
146143
break;
144+
case "perplexity":
145+
search.perplexity = { ...search.perplexity, apiKey: key };
146+
break;
147147
}
148148
return {
149149
...config,
@@ -222,7 +222,7 @@ export async function setupSearch(
222222
if (detected) {
223223
return detected.value;
224224
}
225-
return "brave";
225+
return SEARCH_PROVIDER_OPTIONS[0].value;
226226
})();
227227

228228
type PickerValue = SearchProvider | "__skip__";

src/config/config.web-search-provider.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,26 +142,26 @@ describe("web search provider auto-detection", () => {
142142
expect(resolveSearchProvider({})).toBe("kimi");
143143
});
144144

145-
it("follows priority order — brave wins when multiple keys available", () => {
145+
it("follows alphabetical order — brave wins when multiple keys available", () => {
146146
process.env.BRAVE_API_KEY = "test-brave-key"; // pragma: allowlist secret
147147
process.env.GEMINI_API_KEY = "test-gemini-key"; // pragma: allowlist secret
148148
process.env.PERPLEXITY_API_KEY = "test-perplexity-key"; // pragma: allowlist secret
149149
process.env.XAI_API_KEY = "test-xai-key"; // pragma: allowlist secret
150150
expect(resolveSearchProvider({})).toBe("brave");
151151
});
152152

153-
it("gemini wins over perplexity and grok when brave unavailable", () => {
153+
it("gemini wins over grok, kimi, and perplexity when brave unavailable", () => {
154154
process.env.GEMINI_API_KEY = "test-gemini-key"; // pragma: allowlist secret
155155
process.env.PERPLEXITY_API_KEY = "test-perplexity-key"; // pragma: allowlist secret
156156
process.env.XAI_API_KEY = "test-xai-key"; // pragma: allowlist secret
157157
expect(resolveSearchProvider({})).toBe("gemini");
158158
});
159159

160-
it("brave wins over gemini and grok when perplexity unavailable", () => {
161-
process.env.BRAVE_API_KEY = "test-brave-key"; // pragma: allowlist secret
162-
process.env.GEMINI_API_KEY = "test-gemini-key"; // pragma: allowlist secret
160+
it("grok wins over kimi and perplexity when brave and gemini unavailable", () => {
163161
process.env.XAI_API_KEY = "test-xai-key"; // pragma: allowlist secret
164-
expect(resolveSearchProvider({})).toBe("brave");
162+
process.env.KIMI_API_KEY = "test-kimi-key"; // pragma: allowlist secret
163+
process.env.PERPLEXITY_API_KEY = "test-perplexity-key"; // pragma: allowlist secret
164+
expect(resolveSearchProvider({})).toBe("grok");
165165
});
166166

167167
it("explicit provider always wins regardless of keys", () => {

src/config/schema.help.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -649,11 +649,13 @@ export const FIELD_HELP: Record<string, string> = {
649649
"tools.message.broadcast.enabled": "Enable broadcast action (default: true).",
650650
"tools.web.search.enabled": "Enable the web_search tool (requires a provider API key).",
651651
"tools.web.search.provider":
652-
'Search provider ("brave", "perplexity", "grok", "gemini", or "kimi"). Auto-detected from available API keys if omitted.',
652+
'Search provider ("brave", "gemini", "grok", "kimi", or "perplexity"). Auto-detected from available API keys if omitted.',
653653
"tools.web.search.apiKey": "Brave Search API key (fallback: BRAVE_API_KEY env var).",
654-
"tools.web.search.maxResults": "Default number of results to return (1-10).",
654+
"tools.web.search.maxResults": "Number of results to return (1-10).",
655655
"tools.web.search.timeoutSeconds": "Timeout in seconds for web_search requests.",
656656
"tools.web.search.cacheTtlMinutes": "Cache TTL in minutes for web_search results.",
657+
"tools.web.search.brave.mode":
658+
'Brave Search mode: "web" (URL results) or "llm-context" (pre-extracted page content for LLM grounding).',
657659
"tools.web.search.gemini.apiKey":
658660
"Gemini API key for Google Search grounding (fallback: GEMINI_API_KEY env var).",
659661
"tools.web.search.gemini.model": 'Gemini model override (default: "gemini-2.5-flash").',
@@ -670,8 +672,6 @@ export const FIELD_HELP: Record<string, string> = {
670672
"Optional Perplexity/OpenRouter chat-completions base URL override. Setting this opts Perplexity into the legacy Sonar/OpenRouter compatibility path.",
671673
"tools.web.search.perplexity.model":
672674
'Optional Sonar/OpenRouter model override (default: "perplexity/sonar-pro"). Setting this opts Perplexity into the legacy chat-completions compatibility path.',
673-
"tools.web.search.brave.mode":
674-
'Brave Search mode: "web" (URL results) or "llm-context" (pre-extracted page content for LLM grounding).',
675675
"tools.web.fetch.enabled": "Enable the web_fetch tool (lightweight HTTP fetch).",
676676
"tools.web.fetch.maxChars": "Max characters returned by web_fetch (truncated).",
677677
"tools.web.fetch.maxCharsCap":

src/config/schema.labels.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -218,17 +218,17 @@ export const FIELD_LABELS: Record<string, string> = {
218218
"tools.web.search.maxResults": "Web Search Max Results",
219219
"tools.web.search.timeoutSeconds": "Web Search Timeout (sec)",
220220
"tools.web.search.cacheTtlMinutes": "Web Search Cache TTL (min)",
221-
"tools.web.search.perplexity.apiKey": "Perplexity API Key", // pragma: allowlist secret
222-
"tools.web.search.perplexity.baseUrl": "Perplexity Base URL",
223-
"tools.web.search.perplexity.model": "Perplexity Model",
221+
"tools.web.search.brave.mode": "Brave Search Mode",
224222
"tools.web.search.gemini.apiKey": "Gemini Search API Key", // pragma: allowlist secret
225223
"tools.web.search.gemini.model": "Gemini Search Model",
226224
"tools.web.search.grok.apiKey": "Grok Search API Key", // pragma: allowlist secret
227225
"tools.web.search.grok.model": "Grok Search Model",
228-
"tools.web.search.brave.mode": "Brave Search Mode",
229226
"tools.web.search.kimi.apiKey": "Kimi Search API Key", // pragma: allowlist secret
230227
"tools.web.search.kimi.baseUrl": "Kimi Search Base URL",
231228
"tools.web.search.kimi.model": "Kimi Search Model",
229+
"tools.web.search.perplexity.apiKey": "Perplexity API Key", // pragma: allowlist secret
230+
"tools.web.search.perplexity.baseUrl": "Perplexity Base URL",
231+
"tools.web.search.perplexity.model": "Perplexity Model",
232232
"tools.web.fetch.enabled": "Enable Web Fetch Tool",
233233
"tools.web.fetch.maxChars": "Web Fetch Max Chars",
234234
"tools.web.fetch.maxCharsCap": "Web Fetch Hard Max Chars",

src/config/types.tools.ts

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -441,8 +441,8 @@ export type ToolsConfig = {
441441
search?: {
442442
/** Enable web search tool (default: true when API key is present). */
443443
enabled?: boolean;
444-
/** Search provider ("brave", "perplexity", "grok", "gemini", or "kimi"). */
445-
provider?: "brave" | "perplexity" | "grok" | "gemini" | "kimi";
444+
/** Search provider ("brave", "gemini", "grok", "kimi", or "perplexity"). */
445+
provider?: "brave" | "gemini" | "grok" | "kimi" | "perplexity";
446446
/** Brave Search API key (optional; defaults to BRAVE_API_KEY env var). */
447447
apiKey?: SecretInput;
448448
/** Default search results count (1-10). */
@@ -451,13 +451,16 @@ export type ToolsConfig = {
451451
timeoutSeconds?: number;
452452
/** Cache TTL in minutes for search results. */
453453
cacheTtlMinutes?: number;
454-
/** Perplexity-specific configuration (used when provider="perplexity"). */
455-
perplexity?: {
456-
/** API key for Perplexity (defaults to PERPLEXITY_API_KEY env var). */
454+
/** Brave-specific configuration (used when provider="brave"). */
455+
brave?: {
456+
/** Brave Search mode: "web" (standard results) or "llm-context" (pre-extracted page content). Default: "web". */
457+
mode?: "web" | "llm-context";
458+
};
459+
/** Gemini-specific configuration (used when provider="gemini"). */
460+
gemini?: {
461+
/** Gemini API key (defaults to GEMINI_API_KEY env var). */
457462
apiKey?: SecretInput;
458-
/** @deprecated Legacy Sonar/OpenRouter field. Ignored by Search API. */
459-
baseUrl?: string;
460-
/** @deprecated Legacy Sonar/OpenRouter field. Ignored by Search API. */
463+
/** Model to use for grounded search (defaults to "gemini-2.5-flash"). */
461464
model?: string;
462465
};
463466
/** Grok-specific configuration (used when provider="grok"). */
@@ -469,13 +472,6 @@ export type ToolsConfig = {
469472
/** Include inline citations in response text as markdown links (default: false). */
470473
inlineCitations?: boolean;
471474
};
472-
/** Gemini-specific configuration (used when provider="gemini"). */
473-
gemini?: {
474-
/** Gemini API key (defaults to GEMINI_API_KEY env var). */
475-
apiKey?: SecretInput;
476-
/** Model to use for grounded search (defaults to "gemini-2.5-flash"). */
477-
model?: string;
478-
};
479475
/** Kimi-specific configuration (used when provider="kimi"). */
480476
kimi?: {
481477
/** Moonshot/Kimi API key (defaults to KIMI_API_KEY or MOONSHOT_API_KEY env var). */
@@ -485,10 +481,14 @@ export type ToolsConfig = {
485481
/** Model to use (defaults to "moonshot-v1-128k"). */
486482
model?: string;
487483
};
488-
/** Brave-specific configuration (used when provider="brave"). */
489-
brave?: {
490-
/** Brave Search mode: "web" (standard results) or "llm-context" (pre-extracted page content). Default: "web". */
491-
mode?: "web" | "llm-context";
484+
/** Perplexity-specific configuration (used when provider="perplexity"). */
485+
perplexity?: {
486+
/** API key for Perplexity (defaults to PERPLEXITY_API_KEY env var). */
487+
apiKey?: SecretInput;
488+
/** @deprecated Legacy Sonar/OpenRouter field. Ignored by Search API. */
489+
baseUrl?: string;
490+
/** @deprecated Legacy Sonar/OpenRouter field. Ignored by Search API. */
491+
model?: string;
492492
};
493493
};
494494
fetch?: {

0 commit comments

Comments
 (0)