fix: improve Perplexity web_search defaults (#1131) (thanks @CMLKevin)

This commit is contained in:
Peter Steinberger
2026-01-18 01:15:28 +00:00
parent 9fc3926607
commit afefcdf8fb
12 changed files with 400 additions and 60 deletions

View File

@@ -6,6 +6,7 @@ Docs: https://docs.clawd.bot
### Changes
- Tools: allow `sessions_spawn` to override thinking level for sub-agent runs.
- Tools: add Perplexity Sonar provider for `web_search` with direct Perplexity/OpenRouter support. (#1131) — thanks @CMLKevin.
### Fixes
- Memory: apply OpenAI batch defaults even without explicit remote config.

55
docs/perplexity.md Normal file
View File

@@ -0,0 +1,55 @@
---
summary: "Perplexity Sonar setup for web_search"
read_when:
- You want to use Perplexity Sonar for web search
- You need PERPLEXITY_API_KEY or OpenRouter setup
---
# Perplexity Sonar
Clawdbot can use Perplexity Sonar for the `web_search` tool. You can connect
through Perplexitys direct API or via OpenRouter.
## API options
### Perplexity (direct)
- Base URL: https://api.perplexity.ai
- Environment variable: `PERPLEXITY_API_KEY`
### OpenRouter (alternative)
- Base URL: https://openrouter.ai/api/v1
- Environment variable: `OPENROUTER_API_KEY`
- Supports prepaid/crypto credits.
## Config example
```json5
{
tools: {
web: {
search: {
provider: "perplexity",
perplexity: {
apiKey: "pplx-...",
baseUrl: "https://api.perplexity.ai",
model: "perplexity/sonar-pro"
}
}
}
}
}
```
If both `PERPLEXITY_API_KEY` and `OPENROUTER_API_KEY` are set, set
`tools.web.search.perplexity.baseUrl` (or `tools.web.search.perplexity.apiKey`)
to disambiguate.
## Models
- `perplexity/sonar` — fast Q&A with web search
- `perplexity/sonar-pro` (default) — multi-step reasoning + web search
- `perplexity/sonar-reasoning-pro` — deep research
See [Web tools](/tools/web) for the full web_search configuration.

View File

@@ -1,5 +1,5 @@
---
summary: "Web search + fetch tools (Brave Search API, Perplexity via OpenRouter)"
summary: "Web search + fetch tools (Brave Search API, Perplexity Sonar)"
read_when:
- You want to enable web_search or web_fetch
- You need Brave Search API key setup
@@ -10,7 +10,7 @@ read_when:
Clawdbot ships two lightweight web tools:
- `web_search` — Search the web via Brave Search API (default) or Perplexity Sonar (via OpenRouter).
- `web_search` — Search the web via Brave Search API (default) or Perplexity Sonar (direct or via OpenRouter).
- `web_fetch` — HTTP fetch + readable extraction (HTML → markdown/text).
These are **not** browser automation. For JS-heavy sites or logins, use the
@@ -28,10 +28,12 @@ These are **not** browser automation. For JS-heavy sites or logins, use the
## Choosing a search provider
Provider docs: [Brave Search API](https://brave.com/search/api/) and [Perplexity Sonar](/perplexity).
| Provider | Pros | Cons | API Key |
|----------|------|------|---------|
| **Brave** (default) | Fast, structured results, free tier | Traditional search results | `BRAVE_API_KEY` |
| **Perplexity** | AI-synthesized answers, citations, real-time | Requires OpenRouter credits | `OPENROUTER_API_KEY` or `PERPLEXITY_API_KEY` |
| **Perplexity** | AI-synthesized answers, citations, real-time | Requires Perplexity or OpenRouter credits | `PERPLEXITY_API_KEY` or `OPENROUTER_API_KEY` |
Set the provider in config:
@@ -65,13 +67,22 @@ current limits and pricing.
environment. For a daemon install, put it in `~/.clawdbot/.env` (or your
service environment). See [Env vars](/start/faq#how-does-clawdbot-load-environment-variables).
## Using Perplexity (via OpenRouter)
## Using Perplexity Sonar
Perplexity Sonar models have built-in web search capabilities and return AI-synthesized
answers with citations. You can use them via OpenRouter (no credit card required - supports
crypto/prepaid).
answers with citations. You can use them either directly via Perplexitys API or via OpenRouter
(supports crypto/prepaid).
### Getting an OpenRouter API key
See [Perplexity Sonar](/perplexity) for a dedicated setup guide.
### Getting an API key
**Perplexity (direct):**
1) Create a Perplexity account and generate an API key.
2) Set `PERPLEXITY_API_KEY` in the Gateway environment or store the key in config.
**OpenRouter (alternative):**
1) Create an account at https://openrouter.ai/
2) Add credits (supports crypto, prepaid, or credit card)
@@ -88,9 +99,11 @@ crypto/prepaid).
provider: "perplexity",
perplexity: {
// API key (optional if OPENROUTER_API_KEY or PERPLEXITY_API_KEY is set)
apiKey: "sk-or-v1-...",
// Base URL (defaults to OpenRouter)
baseUrl: "https://openrouter.ai/api/v1",
apiKey: "pplx-... or sk-or-v1-...",
// Base URL:
// - https://api.perplexity.ai (default when PERPLEXITY_API_KEY is set)
// - https://openrouter.ai/api/v1 (default when only OPENROUTER_API_KEY is set)
baseUrl: "https://api.perplexity.ai",
// Model (defaults to perplexity/sonar-pro)
model: "perplexity/sonar-pro"
}
@@ -100,8 +113,9 @@ crypto/prepaid).
}
```
**Environment alternative:** set `OPENROUTER_API_KEY` or `PERPLEXITY_API_KEY` in the Gateway
environment. For a daemon install, put it in `~/.clawdbot/.env`.
**Environment alternative:** set `PERPLEXITY_API_KEY` or `OPENROUTER_API_KEY` in the Gateway
environment. For a daemon install, put it in `~/.clawdbot/.env`. If you set both keys, set
`tools.web.search.perplexity.baseUrl` (or `tools.web.search.perplexity.apiKey`) to disambiguate.
### Available Perplexity models
@@ -120,7 +134,7 @@ 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**: `OPENROUTER_API_KEY`, `PERPLEXITY_API_KEY`, or `tools.web.search.perplexity.apiKey`
- **Perplexity**: `PERPLEXITY_API_KEY` (direct), `OPENROUTER_API_KEY` (OpenRouter), or `tools.web.search.perplexity.apiKey`
### Config

View File

@@ -168,7 +168,7 @@ export function buildAgentSystemPrompt(params: {
ls: "List directory contents",
exec: "Run shell commands (pty available for TTY-required CLIs)",
process: "Manage background exec sessions",
web_search: "Search the web (Brave API)",
web_search: "Search the web (Brave or Perplexity)",
web_fetch: "Fetch and extract readable content from a URL",
// Channel docking: add login tools here when a channel needs interactive linking.
browser: "Control web browser",

View File

@@ -89,3 +89,102 @@ describe("web_search country and language parameters", () => {
expect(url.searchParams.get("ui_lang")).toBe("de");
});
});
describe("web_search Perplexity provider configuration", () => {
const priorFetch = global.fetch;
beforeEach(() => {
vi.stubEnv("PERPLEXITY_API_KEY", "pplx-test");
vi.stubEnv("OPENROUTER_API_KEY", "sk-or-test");
});
afterEach(() => {
vi.unstubAllEnvs();
// @ts-expect-error global fetch cleanup
global.fetch = priorFetch;
});
it("defaults to Perplexity base URL when PERPLEXITY_API_KEY is set", async () => {
const mockFetch = vi.fn((_input: RequestInfo, _init?: RequestInit) =>
Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
choices: [{ message: { content: "ok" } }],
citations: [],
}),
} as Response),
);
// @ts-expect-error mock fetch
global.fetch = mockFetch;
const tool = createWebSearchTool({
config: { tools: { web: { search: { provider: "perplexity" } } } },
sandboxed: true,
});
await tool?.execute?.(1, { query: "test" });
expect(mockFetch).toHaveBeenCalled();
const [url, init] = mockFetch.mock.calls[0] as [string, RequestInit?];
expect(url).toBe("https://api.perplexity.ai/chat/completions");
const headers = (init?.headers ?? {}) as Record<string, string>;
expect(headers.Authorization).toBe("Bearer pplx-test");
});
it("uses OpenRouter base URL when configured, even with PERPLEXITY_API_KEY set", async () => {
const mockFetch = vi.fn((_input: RequestInfo, _init?: RequestInit) =>
Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
choices: [{ message: { content: "ok" } }],
citations: [],
}),
} as Response),
);
// @ts-expect-error mock fetch
global.fetch = mockFetch;
const tool = createWebSearchTool({
config: {
tools: {
web: {
search: { provider: "perplexity", perplexity: { baseUrl: "https://openrouter.ai/api/v1" } },
},
},
},
sandboxed: true,
});
await tool?.execute?.(1, { query: "test" });
expect(mockFetch).toHaveBeenCalled();
const [url, init] = mockFetch.mock.calls[0] as [string, RequestInit?];
expect(url).toBe("https://openrouter.ai/api/v1/chat/completions");
const headers = (init?.headers ?? {}) as Record<string, string>;
expect(headers.Authorization).toBe("Bearer sk-or-test");
});
it("returns missing key when OpenRouter base URL is configured without OPENROUTER_API_KEY", async () => {
vi.stubEnv("OPENROUTER_API_KEY", "");
const mockFetch = vi.fn();
// @ts-expect-error mock fetch
global.fetch = mockFetch;
const tool = createWebSearchTool({
config: {
tools: {
web: {
search: { provider: "perplexity", perplexity: { baseUrl: "https://openrouter.ai/api/v1" } },
},
},
},
sandboxed: true,
});
const result = await tool?.execute?.(1, { query: "test" });
expect(mockFetch).not.toHaveBeenCalled();
expect(result?.details).toMatchObject({ error: "missing_openrouter_api_key" });
});
});

View File

@@ -20,7 +20,8 @@ const DEFAULT_FETCH_USER_AGENT =
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36";
const BRAVE_SEARCH_ENDPOINT = "https://api.search.brave.com/res/v1/web/search";
const DEFAULT_PERPLEXITY_BASE_URL = "https://openrouter.ai/api/v1";
const DEFAULT_OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
const DEFAULT_PERPLEXITY_BASE_URL = "https://api.perplexity.ai";
const DEFAULT_PERPLEXITY_MODEL = "perplexity/sonar-pro";
type WebSearchConfig = NonNullable<ClawdbotConfig["tools"]>["web"] extends infer Web
@@ -198,8 +199,27 @@ function resolveFirecrawlMaxAgeMsOrDefault(firecrawl?: FirecrawlFetchConfig): nu
return DEFAULT_FIRECRAWL_MAX_AGE_MS;
}
function missingSearchKeyPayload(provider: (typeof SEARCH_PROVIDERS)[number]) {
function missingSearchKeyPayload(
provider: (typeof SEARCH_PROVIDERS)[number],
perplexityBaseUrl?: string,
) {
if (provider === "perplexity") {
if (perplexityBaseUrl && isOpenRouterBaseUrl(perplexityBaseUrl)) {
return {
error: "missing_openrouter_api_key",
message:
"web_search (perplexity via OpenRouter) needs an API key. Set OPENROUTER_API_KEY in the Gateway environment, or configure tools.web.search.perplexity.apiKey.",
docs: "https://docs.clawd.bot/tools/web",
};
}
if (perplexityBaseUrl && isPerplexityBaseUrl(perplexityBaseUrl)) {
return {
error: "missing_perplexity_api_key",
message:
"web_search (perplexity direct) needs an API key. Set PERPLEXITY_API_KEY in the Gateway environment, or configure tools.web.search.perplexity.apiKey.",
docs: "https://docs.clawd.bot/tools/web",
};
}
return {
error: "missing_perplexity_api_key",
message:
@@ -231,6 +251,32 @@ type PerplexityConfig = {
model?: string;
};
function normalizeBaseUrl(value: string): string {
return value.trim().replace(/\/+$/, "");
}
function isPerplexityBaseUrl(value: string): boolean {
try {
return new URL(value).hostname === "api.perplexity.ai";
} catch {
return false;
}
}
function isOpenRouterBaseUrl(value: string): boolean {
try {
return new URL(value).hostname === "openrouter.ai";
} catch {
return false;
}
}
function inferPerplexityBaseUrlFromKey(apiKey: string): string | undefined {
if (apiKey.startsWith("pplx-")) return DEFAULT_PERPLEXITY_BASE_URL;
if (apiKey.startsWith("sk-or-")) return DEFAULT_OPENROUTER_BASE_URL;
return undefined;
}
function resolvePerplexityConfig(search?: WebSearchConfig): PerplexityConfig {
if (!search || typeof search !== "object") return {};
const perplexity = "perplexity" in search ? search.perplexity : undefined;
@@ -238,14 +284,24 @@ function resolvePerplexityConfig(search?: WebSearchConfig): PerplexityConfig {
return perplexity as PerplexityConfig;
}
function resolvePerplexityApiKey(perplexity?: PerplexityConfig): string | undefined {
function resolvePerplexityApiKey(
perplexity: PerplexityConfig | undefined,
baseUrl: string,
): string | undefined {
const fromConfig =
perplexity && "apiKey" in perplexity && typeof perplexity.apiKey === "string"
? perplexity.apiKey.trim()
: "";
if (fromConfig) return fromConfig;
const fromEnvPerplexity = (process.env.PERPLEXITY_API_KEY ?? "").trim();
const fromEnvOpenRouter = (process.env.OPENROUTER_API_KEY ?? "").trim();
return fromConfig || fromEnvPerplexity || fromEnvOpenRouter || undefined;
if (isPerplexityBaseUrl(baseUrl)) {
return fromEnvPerplexity || undefined;
}
if (isOpenRouterBaseUrl(baseUrl)) {
return fromEnvOpenRouter || undefined;
}
return fromEnvPerplexity || fromEnvOpenRouter || undefined;
}
function resolvePerplexityBaseUrl(perplexity?: PerplexityConfig): string {
@@ -253,7 +309,20 @@ function resolvePerplexityBaseUrl(perplexity?: PerplexityConfig): string {
perplexity && "baseUrl" in perplexity && typeof perplexity.baseUrl === "string"
? perplexity.baseUrl.trim()
: "";
return fromConfig || DEFAULT_PERPLEXITY_BASE_URL;
if (fromConfig) return normalizeBaseUrl(fromConfig);
const fromConfigKey =
perplexity && "apiKey" in perplexity && typeof perplexity.apiKey === "string"
? perplexity.apiKey.trim()
: "";
const inferredFromConfigKey = fromConfigKey
? inferPerplexityBaseUrlFromKey(fromConfigKey)
: undefined;
if (inferredFromConfigKey) return inferredFromConfigKey;
const fromEnvPerplexity = (process.env.PERPLEXITY_API_KEY ?? "").trim();
const fromEnvOpenRouter = (process.env.OPENROUTER_API_KEY ?? "").trim();
if (fromEnvPerplexity) return DEFAULT_PERPLEXITY_BASE_URL;
if (fromEnvOpenRouter) return DEFAULT_OPENROUTER_BASE_URL;
return DEFAULT_OPENROUTER_BASE_URL;
}
function resolvePerplexityModel(perplexity?: PerplexityConfig): string {
@@ -545,6 +614,13 @@ type PerplexitySearchResponse = {
citations?: string[];
};
function resolvePerplexityEndpoint(baseUrl: string): string {
const trimmed = normalizeBaseUrl(baseUrl);
if (!trimmed) return `${DEFAULT_OPENROUTER_BASE_URL}/chat/completions`;
if (trimmed.endsWith("/chat/completions")) return trimmed;
return `${trimmed}/chat/completions`;
}
async function runPerplexitySearch(params: {
query: string;
apiKey: string;
@@ -552,7 +628,7 @@ async function runPerplexitySearch(params: {
model: string;
timeoutSeconds: number;
}): Promise<{ content: string; citations: string[] }> {
const endpoint = `${params.baseUrl.replace(/\/$/, "")}/chat/completions`;
const endpoint = resolvePerplexityEndpoint(params.baseUrl);
const res = await fetch(endpoint, {
method: "POST",
@@ -599,8 +675,12 @@ async function runWebSearch(params: {
perplexityBaseUrl?: string;
perplexityModel?: string;
}): Promise<Record<string, unknown>> {
const providerKey =
params.provider === "perplexity"
? `${params.provider}:${params.perplexityModel ?? DEFAULT_PERPLEXITY_MODEL}:${params.perplexityBaseUrl ?? DEFAULT_OPENROUTER_BASE_URL}`
: params.provider;
const cacheKey = normalizeCacheKey(
`${params.provider}:${params.query}:${params.count}:${params.country || "default"}:${params.search_lang || "default"}:${params.ui_lang || "default"}`,
`${providerKey}:${params.query}:${params.count}:${params.country || "default"}:${params.search_lang || "default"}:${params.ui_lang || "default"}`,
);
const cached = readCache(SEARCH_CACHE, cacheKey);
if (cached) return { ...cached.value, cached: true };
@@ -899,11 +979,13 @@ export function createWebSearchTool(options?: {
const provider = resolveSearchProvider(search);
const perplexityConfig = resolvePerplexityConfig(search);
const perplexityBaseUrl = resolvePerplexityBaseUrl(perplexityConfig);
const perplexityModel = resolvePerplexityModel(perplexityConfig);
// Determine description based on provider
const description =
provider === "perplexity"
? "Search the web using Perplexity Sonar (via OpenRouter). Returns AI-synthesized answers with citations from real-time web search."
? "Search the web using Perplexity Sonar (direct API or via OpenRouter). Returns AI-synthesized answers with citations from real-time web search."
: "Search the web using Brave Search API. Supports region-specific and localized search via country and language parameters. Returns titles, URLs, and snippets for fast research.";
return {
@@ -915,11 +997,11 @@ export function createWebSearchTool(options?: {
// Resolve API key based on provider
const apiKey =
provider === "perplexity"
? resolvePerplexityApiKey(perplexityConfig)
? resolvePerplexityApiKey(perplexityConfig, perplexityBaseUrl)
: resolveSearchApiKey(search);
if (!apiKey) {
return jsonResult(missingSearchKeyPayload(provider));
return jsonResult(missingSearchKeyPayload(provider, perplexityBaseUrl));
}
const params = args as Record<string, unknown>;
const query = readStringParam(params, "query", { required: true });
@@ -938,8 +1020,8 @@ export function createWebSearchTool(options?: {
country,
search_lang,
ui_lang,
perplexityBaseUrl: resolvePerplexityBaseUrl(perplexityConfig),
perplexityModel: resolvePerplexityModel(perplexityConfig),
perplexityBaseUrl,
perplexityModel,
});
return jsonResult(result);
},

View File

@@ -97,12 +97,15 @@ async function promptWebToolsConfig(
): Promise<ClawdbotConfig> {
const existingSearch = nextConfig.tools?.web?.search;
const existingFetch = nextConfig.tools?.web?.fetch;
const hasSearchKey = Boolean(existingSearch?.apiKey);
const existingProvider = existingSearch?.provider === "perplexity" ? "perplexity" : "brave";
const hasBraveKey = Boolean(existingSearch?.apiKey);
const hasPerplexityKey = Boolean(existingSearch?.perplexity?.apiKey);
const hasSearchKey = existingProvider === "perplexity" ? hasPerplexityKey : hasBraveKey;
note(
[
"Web search lets your agent look things up online using the `web_search` tool.",
"It requires a Brave Search API key (you can store it in the config or set BRAVE_API_KEY in the Gateway environment).",
"Choose Brave (structured results) or Perplexity Sonar (answers + citations).",
"Docs: https://docs.clawd.bot/tools/web",
].join("\n"),
"Web search",
@@ -110,7 +113,7 @@ async function promptWebToolsConfig(
const enableSearch = guardCancel(
await confirm({
message: "Enable web_search (Brave Search)?",
message: "Enable web_search?",
initialValue: existingSearch?.enabled ?? hasSearchKey,
}),
runtime,
@@ -122,27 +125,84 @@ async function promptWebToolsConfig(
};
if (enableSearch) {
const keyInput = guardCancel(
await text({
message: hasSearchKey
? "Brave Search API key (leave blank to keep current or use BRAVE_API_KEY)"
: "Brave Search API key (paste it here; leave blank to use BRAVE_API_KEY)",
placeholder: hasSearchKey ? "Leave blank to keep current" : "BSA...",
const provider = guardCancel(
await select<"brave" | "perplexity">({
message: "Web search provider",
options: [
{ value: "brave", label: "Brave Search (structured results)" },
{ value: "perplexity", label: "Perplexity Sonar (answers + citations)" },
],
initialValue: existingProvider,
}),
runtime,
);
const key = String(keyInput ?? "").trim();
if (key) {
nextSearch = { ...nextSearch, apiKey: key };
} else if (!hasSearchKey) {
note(
[
"No key stored yet, so web_search will stay unavailable.",
"Store a key here or set BRAVE_API_KEY in the Gateway environment.",
"Docs: https://docs.clawd.bot/tools/web",
].join("\n"),
"Web search",
nextSearch = { ...nextSearch, provider };
if (provider === "brave") {
const keyInput = guardCancel(
await text({
message: hasBraveKey
? "Brave Search API key (leave blank to keep current or use BRAVE_API_KEY)"
: "Brave Search API key (paste it here; leave blank to use BRAVE_API_KEY)",
placeholder: hasBraveKey ? "Leave blank to keep current" : "BSA...",
}),
runtime,
);
const key = String(keyInput ?? "").trim();
if (key) {
nextSearch = { ...nextSearch, apiKey: key };
} else if (!hasBraveKey) {
note(
[
"No key stored yet, so web_search will stay unavailable.",
"Store a key here or set BRAVE_API_KEY in the Gateway environment.",
"Docs: https://docs.clawd.bot/tools/web",
].join("\n"),
"Web search",
);
}
} else {
const existingPerplexity = existingSearch?.perplexity ?? {};
const keyInput = guardCancel(
await text({
message: hasPerplexityKey
? "Perplexity/OpenRouter API key (leave blank to keep current or use PERPLEXITY_API_KEY/OPENROUTER_API_KEY)"
: "Perplexity/OpenRouter API key (paste it here; leave blank to use PERPLEXITY_API_KEY/OPENROUTER_API_KEY)",
placeholder: hasPerplexityKey ? "Leave blank to keep current" : "pplx-... or sk-or-...",
}),
runtime,
);
const key = String(keyInput ?? "").trim();
let nextPerplexity = { ...existingPerplexity };
if (key) {
nextPerplexity = { ...nextPerplexity, apiKey: key };
}
const baseUrlInput = guardCancel(
await text({
message: "Perplexity base URL (optional; leave blank for default)",
placeholder: existingPerplexity.baseUrl ?? "https://api.perplexity.ai",
}),
runtime,
);
const baseUrl = String(baseUrlInput ?? "").trim();
if (baseUrl) {
nextPerplexity = { ...nextPerplexity, baseUrl };
}
nextSearch = { ...nextSearch, perplexity: nextPerplexity };
if (!key && !hasPerplexityKey) {
note(
[
"No key stored yet, so web_search will stay unavailable.",
"Store a key here or set PERPLEXITY_API_KEY/OPENROUTER_API_KEY in the Gateway environment.",
"Docs: https://docs.clawd.bot/tools/web",
].join("\n"),
"Web search",
);
}
}
}

View File

@@ -123,7 +123,7 @@ export async function runNonInteractiveOnboardingLocal(params: {
if (!opts.json) {
runtime.log(
"Tip: run `clawdbot configure --section web` to store your Brave API key for web_search. Docs: https://docs.clawd.bot/tools/web",
"Tip: run `clawdbot configure --section web` to choose Brave or Perplexity and store your API key for web_search. Docs: https://docs.clawd.bot/tools/web",
);
}
}

View File

@@ -45,7 +45,7 @@ export async function runNonInteractiveOnboardingRemote(params: {
runtime.log(`Remote gateway: ${remoteUrl}`);
runtime.log(`Auth: ${payload.auth}`);
runtime.log(
"Tip: run `clawdbot configure --section web` to store your Brave API key for web_search. Docs: https://docs.clawd.bot/tools/web",
"Tip: run `clawdbot configure --section web` to choose Brave or Perplexity and store your API key for web_search. Docs: https://docs.clawd.bot/tools/web",
);
}
}

View File

@@ -315,9 +315,15 @@ const FIELD_HELP: Record<string, string> = {
"tools.message.crossContext.marker.suffix":
'Text suffix for cross-context markers (supports "{channel}").',
"tools.message.broadcast.enabled": "Enable broadcast action (default: true).",
"tools.web.search.enabled": "Enable the web_search tool (requires Brave API key).",
"tools.web.search.provider": 'Search provider (only "brave" supported today).',
"tools.web.search.enabled": "Enable the web_search tool (requires provider API key).",
"tools.web.search.provider": 'Search provider ("brave" or "perplexity").',
"tools.web.search.apiKey": "Brave Search API key (fallback: BRAVE_API_KEY env var).",
"tools.web.search.perplexity.apiKey":
"Perplexity/OpenRouter API key (fallback: PERPLEXITY_API_KEY or OPENROUTER_API_KEY env vars).",
"tools.web.search.perplexity.baseUrl":
"Perplexity/OpenRouter base URL for web_search (defaults to Perplexity when PERPLEXITY_API_KEY is set, otherwise OpenRouter).",
"tools.web.search.perplexity.model":
'Perplexity Sonar model to use for web_search (default: "perplexity/sonar-pro").',
"tools.web.search.maxResults": "Default 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.",

View File

@@ -236,7 +236,7 @@ export type ToolsConfig = {
perplexity?: {
/** API key for Perplexity or OpenRouter (defaults to PERPLEXITY_API_KEY or OPENROUTER_API_KEY env var). */
apiKey?: string;
/** Base URL for API requests (defaults to OpenRouter: https://openrouter.ai/api/v1). */
/** Base URL for API requests (defaults to Perplexity if PERPLEXITY_API_KEY is set, otherwise OpenRouter). */
baseUrl?: string;
/** Model to use (defaults to "perplexity/sonar-pro"). */
model?: string;

View File

@@ -375,29 +375,52 @@ export async function finalizeOnboardingWizard(options: FinalizeOnboardingOption
);
}
const webSearchKey = (nextConfig.tools?.web?.search?.apiKey ?? "").trim();
const webSearchEnv = (process.env.BRAVE_API_KEY ?? "").trim();
const hasWebSearchKey = Boolean(webSearchKey || webSearchEnv);
const webSearchProvider =
nextConfig.tools?.web?.search?.provider === "perplexity" ? "perplexity" : "brave";
const braveKey = (nextConfig.tools?.web?.search?.apiKey ?? "").trim();
const braveEnv = (process.env.BRAVE_API_KEY ?? "").trim();
const perplexityKey = (nextConfig.tools?.web?.search?.perplexity?.apiKey ?? "").trim();
const perplexityEnv = (process.env.PERPLEXITY_API_KEY ?? "").trim();
const openRouterEnv = (process.env.OPENROUTER_API_KEY ?? "").trim();
const perplexityBaseUrl = (nextConfig.tools?.web?.search?.perplexity?.baseUrl ?? "").trim();
const hasWebSearchKey =
webSearchProvider === "perplexity"
? Boolean(perplexityKey || perplexityEnv || openRouterEnv)
: Boolean(braveKey || braveEnv);
await prompter.note(
hasWebSearchKey
? [
"Web search is enabled, so your agent can look things up online when needed.",
"",
webSearchKey
? "API key: stored in config (tools.web.search.apiKey)."
: "API key: provided via BRAVE_API_KEY env var (Gateway environment).",
webSearchProvider === "perplexity"
? perplexityKey
? "API key: stored in config (tools.web.search.perplexity.apiKey)."
: perplexityBaseUrl.includes("openrouter.ai")
? "API key: provided via OPENROUTER_API_KEY env var (Gateway environment)."
: perplexityBaseUrl.includes("api.perplexity.ai")
? "API key: provided via PERPLEXITY_API_KEY env var (Gateway environment)."
: perplexityEnv
? "API key: provided via PERPLEXITY_API_KEY env var (Gateway environment)."
: "API key: provided via OPENROUTER_API_KEY env var (Gateway environment)."
: braveKey
? "API key: stored in config (tools.web.search.apiKey)."
: "API key: provided via BRAVE_API_KEY env var (Gateway environment).",
"Docs: https://docs.clawd.bot/tools/web",
].join("\n")
: [
"If you want your agent to be able to search the web, youll need an API key.",
"",
"Clawdbot uses Brave Search for the `web_search` tool. Without a Brave Search API key, web search wont work.",
webSearchProvider === "perplexity"
? "Clawdbot can use Perplexity Sonar for the `web_search` tool. Without a Perplexity/OpenRouter API key, web search wont work."
: "Clawdbot uses Brave Search for the `web_search` tool. Without a Brave Search API key, web search wont work.",
"",
"Set it up interactively:",
"- Run: clawdbot configure --section web",
"- Enable web_search and paste your Brave Search API key",
"- Enable web_search and paste your API key",
"",
"Alternative: set BRAVE_API_KEY in the Gateway environment (no config changes).",
webSearchProvider === "perplexity"
? "Alternative: set PERPLEXITY_API_KEY or OPENROUTER_API_KEY in the Gateway environment (no config changes)."
: "Alternative: set BRAVE_API_KEY in the Gateway environment (no config changes).",
"Docs: https://docs.clawd.bot/tools/web",
].join("\n"),
"Web search (optional)",