diff --git a/src/agents/tools/web-search.ts b/src/agents/tools/web-search.ts index bf5741490..ae360c865 100644 --- a/src/agents/tools/web-search.ts +++ b/src/agents/tools/web-search.ts @@ -1,4 +1,5 @@ import { Type } from "@sinclair/typebox"; +import { ProxyAgent } from "undici"; import type { OpenClawConfig } from "../../config/config.js"; import { formatCliCommand } from "../../cli/command-format.js"; @@ -122,6 +123,11 @@ function resolveSearchApiKey(search?: WebSearchConfig): string | undefined { return fromConfig || fromEnv || undefined; } +function resolveProxy(search?: WebSearchConfig): string | undefined { + const fromConfig = search && "proxy" in search && typeof search.proxy === "string" ? search.proxy.trim() : ""; + return fromConfig || undefined; +} + function missingSearchKeyPayload(provider: (typeof SEARCH_PROVIDERS)[number]) { if (provider === "perplexity") { return { @@ -271,9 +277,12 @@ async function runPerplexitySearch(params: { baseUrl: string; model: string; timeoutSeconds: number; + proxy?: string; }): Promise<{ content: string; citations: string[] }> { const endpoint = `${params.baseUrl.replace(/\/$/, "")}/chat/completions`; + const dispatcher = params.proxy ? new ProxyAgent(params.proxy) : undefined; + const res = await fetch(endpoint, { method: "POST", headers: { @@ -292,6 +301,7 @@ async function runPerplexitySearch(params: { ], }), signal: withTimeout(undefined, params.timeoutSeconds * 1000), + dispatcher, }); if (!res.ok) { @@ -319,6 +329,7 @@ async function runWebSearch(params: { freshness?: string; perplexityBaseUrl?: string; perplexityModel?: string; + proxy?: string; }): Promise> { const cacheKey = normalizeCacheKey( params.provider === "brave" @@ -337,6 +348,7 @@ async function runWebSearch(params: { baseUrl: params.perplexityBaseUrl ?? DEFAULT_PERPLEXITY_BASE_URL, model: params.perplexityModel ?? DEFAULT_PERPLEXITY_MODEL, timeoutSeconds: params.timeoutSeconds, + proxy: params.proxy, }); const payload = { @@ -371,6 +383,8 @@ async function runWebSearch(params: { url.searchParams.set("freshness", params.freshness); } + const dispatcher = params.proxy ? new ProxyAgent(params.proxy) : undefined; + const res = await fetch(url.toString(), { method: "GET", headers: { @@ -378,6 +392,7 @@ async function runWebSearch(params: { "X-Subscription-Token": params.apiKey, }, signal: withTimeout(undefined, params.timeoutSeconds * 1000), + dispatcher, }); if (!res.ok) { @@ -476,6 +491,7 @@ export function createWebSearchTool(options?: { perplexityAuth?.apiKey, ), perplexityModel: resolvePerplexityModel(perplexityConfig), + proxy: resolveProxy(search), }); return jsonResult(result); }, diff --git a/src/config/zod-schema.agent-runtime.ts b/src/config/zod-schema.agent-runtime.ts index 7e95c3538..6864d3785 100644 --- a/src/config/zod-schema.agent-runtime.ts +++ b/src/config/zod-schema.agent-runtime.ts @@ -170,6 +170,7 @@ export const ToolsWebSearchSchema = z maxResults: z.number().int().positive().optional(), timeoutSeconds: z.number().int().positive().optional(), cacheTtlMinutes: z.number().nonnegative().optional(), + proxy: z.string().optional(), perplexity: z .object({ apiKey: z.string().optional(),