Skip to content

Commit b813183

Browse files
committed
fix(web-search): support provider base url overrides
1 parent 6b1821b commit b813183

21 files changed

Lines changed: 370 additions & 28 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Docs: https://docs.openclaw.ai
2929
- Slack/message tool: let `read` fetch an exact Slack message timestamp, including a specific thread reply when paired with `threadId`, instead of returning only the parent thread or recent channel history. Fixes #53943. Thanks @zomars.
3030
- Web search: point missing-key errors to `web_fetch` for known URLs and the browser tool for interactive pages. Thanks @zhaoyang97.
3131
- Web search: late-bind managed agent `web_search` calls to the current runtime config snapshot, so existing sessions do not keep stale unresolved SecretRefs after secrets reload. Fixes #75420. Thanks @richardmqq.
32+
- Web search: honor `baseUrl` overrides for Gemini, Grok, and x_search provider-owned config, so proxy-backed search tools no longer dial hardcoded public endpoints. Supersedes #61972. Thanks @Lanfei.
3233
- Heartbeat: strip legacy `[TOOL_CALL]...[/TOOL_CALL]` and `[TOOL_RESULT]...[/TOOL_RESULT]` pseudo-call blocks from heartbeat replies before channel delivery. Fixes #54138. Thanks @Deniable9570.
3334
- macOS/Voice Wake: send wake-word and Push-to-Talk transcripts through the selected macOS session target instead of always falling back to main WebChat. Fixes #51040. Thanks @carl-jeffrolc.
3435
- Providers/xAI: give Grok `web_search` a 60s default timeout, harden malformed xAI Responses parsing, and return structured timeout errors instead of aborting the tool call. Fixes #58063 and #58733. Thanks @dnishimura, @marvcasasola-svg, and @Nanako0129.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
1d9157a39ad18841d666af90c58e0539d6427cbd2ad0c1ce29047a5a2131ba7e config-baseline.json
1+
6666a7f876a31658b3e2f2a6564619cfaf2b282104fd6d7799656389431eb996 config-baseline.json
22
80e6e8dce647aef2d1310de55a81d27de52cca47fc24bd7ad81b80f43a72b84c config-baseline.core.json
33
1cec599c3d27c258b9df3446baa547cb164e502afa9b30c052bba8737183f551 config-baseline.channel.json
4-
8346667910d2b3a3884efce8f96591adebc4f7ea99ce18337b80e4d70bf8e4d2 config-baseline.plugin.json
4+
1b2cb7fec6752245bc2a3da4a835f0bf9d31e6a468e777a5bdb91820398f44d0 config-baseline.plugin.json

docs/providers/google.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,33 @@ Choose your preferred auth method and follow the setup steps.
144144
| Thinking/reasoning | Yes (Gemini 2.5+ / Gemini 3+) |
145145
| Gemma 4 models | Yes |
146146

147+
## Web search
148+
149+
The bundled `gemini` web-search provider uses Gemini Google Search grounding.
150+
Configure it under `plugins.entries.google.config.webSearch`:
151+
152+
```json5
153+
{
154+
plugins: {
155+
entries: {
156+
google: {
157+
config: {
158+
webSearch: {
159+
apiKey: "AIza...", // optional if GEMINI_API_KEY is set
160+
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
161+
model: "gemini-2.5-flash",
162+
},
163+
},
164+
},
165+
},
166+
},
167+
}
168+
```
169+
170+
`webSearch.baseUrl` is optional and exists for operator proxies or compatible
171+
Gemini API endpoints. See [Gemini search](/tools/gemini-search) for the
172+
provider-specific tool behavior.
173+
147174
<Tip>
148175
Gemini 3 models use `thinkingLevel` rather than `thinkingBudget`. OpenClaw maps
149176
Gemini 3, Gemini 3.1, and `gemini-*-latest` alias reasoning controls to

docs/providers/xai.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ OpenClaw uses the xAI Responses API as the bundled xAI transport. The same
3737
and remote `code_execution`.
3838
If you store an xAI key under `plugins.entries.xai.config.webSearch.apiKey`,
3939
the bundled xAI model provider reuses that key as a fallback too.
40+
Set `plugins.entries.xai.config.webSearch.baseUrl` to route Grok `web_search`
41+
and, by default, `x_search` through an operator xAI Responses proxy.
4042
`code_execution` tuning lives under `plugins.entries.xai.config.codeExecution`.
4143
</Note>
4244

@@ -343,6 +345,7 @@ Legacy aliases still normalize to the canonical bundled ids:
343345
| ------------------ | ------- | ------------------ | ------------------------------------ |
344346
| `enabled` | boolean | — | Enable or disable x_search |
345347
| `model` | string | `grok-4-1-fast` | Model used for x_search requests |
348+
| `baseUrl` | string | — | xAI Responses base URL override |
346349
| `inlineCitations` | boolean | — | Include inline citations in results |
347350
| `maxTurns` | number | — | Maximum conversation turns |
348351
| `timeoutSeconds` | number | — | Request timeout in seconds |
@@ -357,6 +360,7 @@ Legacy aliases still normalize to the canonical bundled ids:
357360
xSearch: {
358361
enabled: true,
359362
model: "grok-4-1-fast",
363+
baseUrl: "https://api.x.ai/v1",
360364
inlineCitations: true,
361365
},
362366
},
@@ -429,6 +433,9 @@ Legacy aliases still normalize to the canonical bundled ids:
429433
- `web_search`, `x_search`, and `code_execution` are exposed as OpenClaw
430434
tools. OpenClaw enables the specific xAI built-in it needs inside each tool
431435
request instead of attaching all native tools to every chat turn.
436+
- Grok `web_search` reads `plugins.entries.xai.config.webSearch.baseUrl`.
437+
`x_search` reads `plugins.entries.xai.config.xSearch.baseUrl`, then
438+
falls back to the Grok web-search base URL.
432439
- `x_search` and `code_execution` are owned by the bundled xAI plugin rather
433440
than hardcoded into the core model runtime.
434441
- `code_execution` is remote xAI sandbox execution, not local

docs/tools/gemini-search.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ citations.
3939
config: {
4040
webSearch: {
4141
apiKey: "AIza...", // optional if GEMINI_API_KEY is set
42+
baseUrl: "https://generativelanguage.googleapis.com/v1beta", // optional proxy/base URL override
4243
model: "gemini-2.5-flash", // default
4344
},
4445
},
@@ -89,6 +90,14 @@ The default model is `gemini-2.5-flash` (fast and cost-effective). Any Gemini
8990
model that supports grounding can be used via
9091
`plugins.entries.google.config.webSearch.model`.
9192

93+
## Base URL overrides
94+
95+
Set `plugins.entries.google.config.webSearch.baseUrl` when Gemini web search
96+
must route through an operator proxy or custom Gemini-compatible endpoint. A
97+
plain `https://generativelanguage.googleapis.com` value is normalized to
98+
`https://generativelanguage.googleapis.com/v1beta`; custom proxy paths are kept
99+
as provided after trimming trailing slashes.
100+
92101
## Related
93102

94103
- [Web Search overview](/tools/web) -- all providers and auto-detection

docs/tools/grok-search.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ If you skip it, you can enable or change `x_search` later in config.
6161
config: {
6262
webSearch: {
6363
apiKey: "xai-...", // optional if XAI_API_KEY is set
64+
baseUrl: "https://api.x.ai/v1", // optional Responses API proxy/base URL override
6465
},
6566
},
6667
},
@@ -97,6 +98,14 @@ Grok uses a provider-specific 60 second default timeout because xAI Responses
9798
web-grounded searches can run longer than the shared `web_search` default. Set
9899
`tools.web.search.timeoutSeconds` to override it.
99100

101+
## Base URL overrides
102+
103+
Set `plugins.entries.xai.config.webSearch.baseUrl` when Grok web search should
104+
route through an operator proxy or xAI-compatible Responses endpoint. OpenClaw
105+
posts to `<baseUrl>/responses` after trimming trailing slashes. `x_search`
106+
uses the same `webSearch.baseUrl` fallback unless
107+
`plugins.entries.xai.config.xSearch.baseUrl` is set.
108+
100109
## Related
101110

102111
- [Web Search overview](/tools/web) -- all providers and auto-detection

docs/tools/web.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,13 +334,15 @@ tool on the request that serves this tool call.
334334
xSearch: {
335335
enabled: true,
336336
model: "grok-4-1-fast-non-reasoning",
337+
baseUrl: "https://api.x.ai/v1", // optional, overrides webSearch.baseUrl
337338
inlineCitations: false,
338339
maxTurns: 2,
339340
timeoutSeconds: 30,
340341
cacheTtlMinutes: 15,
341342
},
342343
webSearch: {
343344
apiKey: "xai-...", // optional if XAI_API_KEY is set
345+
baseUrl: "https://api.x.ai/v1", // optional shared xAI Responses base URL
344346
},
345347
},
346348
},
@@ -349,6 +351,11 @@ tool on the request that serves this tool call.
349351
}
350352
```
351353

354+
`x_search` posts to `<baseUrl>/responses` when
355+
`plugins.entries.xai.config.xSearch.baseUrl` is set. If that field is omitted,
356+
it falls back to `plugins.entries.xai.config.webSearch.baseUrl`, then the
357+
legacy `tools.web.search.grok.baseUrl`, and finally the public xAI endpoint.
358+
352359
### x_search parameters
353360

354361
| Parameter | Description |

extensions/google/openclaw.plugin.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@
133133
"webSearch.model": {
134134
"label": "Gemini Search Model",
135135
"help": "Gemini model override for web search grounding."
136+
},
137+
"webSearch.baseUrl": {
138+
"label": "Gemini Search Base URL",
139+
"help": "Optional Gemini API base URL for web search grounding proxies."
136140
}
137141
},
138142
"contracts": {
@@ -177,6 +181,9 @@
177181
},
178182
"model": {
179183
"type": "string"
184+
},
185+
"baseUrl": {
186+
"type": "string"
180187
}
181188
}
182189
}

extensions/google/src/gemini-web-search-provider.runtime.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,13 @@ import {
2020
wrapWebContent,
2121
writeCachedSearchPayload,
2222
} from "openclaw/plugin-sdk/provider-web-search";
23-
import { DEFAULT_GOOGLE_API_BASE_URL } from "../api.js";
2423
import {
2524
resolveGeminiConfig,
25+
resolveGeminiBaseUrl,
2626
resolveGeminiModel,
2727
type GeminiConfig,
2828
} from "./gemini-web-search-provider.shared.js";
2929

30-
const GEMINI_API_BASE = DEFAULT_GOOGLE_API_BASE_URL;
31-
3230
type GeminiGroundingResponse = {
3331
candidates?: Array<{
3432
content?: {
@@ -62,10 +60,11 @@ export function resolveGeminiRuntimeApiKey(gemini?: GeminiConfig): string | unde
6260
async function runGeminiSearch(params: {
6361
query: string;
6462
apiKey: string;
63+
baseUrl: string;
6564
model: string;
6665
timeoutSeconds: number;
6766
}): Promise<{ content: string; citations: Array<{ url: string; title?: string }> }> {
68-
const endpoint = `${GEMINI_API_BASE}/models/${params.model}:generateContent`;
67+
const endpoint = `${params.baseUrl}/models/${params.model}:generateContent`;
6968

7069
return withTrustedWebSearchEndpoint(
7170
{
@@ -161,10 +160,12 @@ export async function executeGeminiSearch(
161160
const count =
162161
readNumberParam(args, "count", { integer: true }) ?? searchConfig?.maxResults ?? undefined;
163162
const model = resolveGeminiModel(geminiConfig);
163+
const baseUrl = resolveGeminiBaseUrl(geminiConfig);
164164
const cacheKey = buildSearchCacheKey([
165165
"gemini",
166166
query,
167167
resolveSearchCount(count, DEFAULT_SEARCH_COUNT),
168+
baseUrl,
168169
model,
169170
]);
170171
const cached = readCachedSearchPayload(cacheKey);
@@ -176,6 +177,7 @@ export async function executeGeminiSearch(
176177
const result = await runGeminiSearch({
177178
query,
178179
apiKey,
180+
baseUrl,
179181
model,
180182
timeoutSeconds: resolveSearchTimeoutSeconds(searchConfig),
181183
});

extensions/google/src/gemini-web-search-provider.shared.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import { normalizeGoogleApiBaseUrl } from "../api.js";
2+
13
const DEFAULT_GEMINI_WEB_SEARCH_MODEL = "gemini-2.5-flash";
24

35
export type GeminiConfig = {
46
apiKey?: unknown;
7+
baseUrl?: unknown;
58
model?: unknown;
69
};
710

@@ -28,3 +31,7 @@ export function resolveGeminiApiKey(
2831
export function resolveGeminiModel(gemini?: GeminiConfig): string {
2932
return trimToUndefined(gemini?.model) ?? DEFAULT_GEMINI_WEB_SEARCH_MODEL;
3033
}
34+
35+
export function resolveGeminiBaseUrl(gemini?: GeminiConfig): string {
36+
return normalizeGoogleApiBaseUrl(trimToUndefined(gemini?.baseUrl));
37+
}

0 commit comments

Comments
 (0)