Skip to content

Commit 3872a86

Browse files
authored
fix(xai): make x_search auth plugin-owned (#59691)
* fix(xai): make x_search auth plugin-owned * fix(xai): restore x_search runtime migration fallback * fix(xai): narrow legacy x_search auth migration * fix(secrets): drop legacy x_search target registry entry * fix(xai): no-op knob-only x_search migration fallback
1 parent b6debb4 commit 3872a86

17 files changed

Lines changed: 239 additions & 279 deletions

docs/.generated/config-baseline.json

Lines changed: 6 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -67338,69 +67338,14 @@
6733867338
"tags": [],
6733967339
"hasChildren": true
6734067340
},
67341-
{
67342-
"path": "tools.web.x_search.apiKey",
67343-
"kind": "core",
67344-
"type": [
67345-
"object",
67346-
"string"
67347-
],
67348-
"required": false,
67349-
"deprecated": false,
67350-
"sensitive": true,
67351-
"tags": [
67352-
"auth",
67353-
"security",
67354-
"tools"
67355-
],
67356-
"label": "xAI API Key",
67357-
"help": "xAI API key for X search (fallback: XAI_API_KEY env var).",
67358-
"hasChildren": true
67359-
},
67360-
{
67361-
"path": "tools.web.x_search.apiKey.id",
67362-
"kind": "core",
67363-
"type": "string",
67364-
"required": true,
67365-
"deprecated": false,
67366-
"sensitive": false,
67367-
"tags": [],
67368-
"hasChildren": false
67369-
},
67370-
{
67371-
"path": "tools.web.x_search.apiKey.provider",
67372-
"kind": "core",
67373-
"type": "string",
67374-
"required": true,
67375-
"deprecated": false,
67376-
"sensitive": false,
67377-
"tags": [],
67378-
"hasChildren": false
67379-
},
67380-
{
67381-
"path": "tools.web.x_search.apiKey.source",
67382-
"kind": "core",
67383-
"type": "string",
67384-
"required": true,
67385-
"deprecated": false,
67386-
"sensitive": false,
67387-
"tags": [],
67388-
"hasChildren": false
67389-
},
6739067341
{
6739167342
"path": "tools.web.x_search.cacheTtlMinutes",
6739267343
"kind": "core",
6739367344
"type": "number",
6739467345
"required": false,
6739567346
"deprecated": false,
6739667347
"sensitive": false,
67397-
"tags": [
67398-
"performance",
67399-
"storage",
67400-
"tools"
67401-
],
67402-
"label": "X Search Cache TTL (min)",
67403-
"help": "Cache TTL in minutes for x_search results.",
67348+
"tags": [],
6740467349
"hasChildren": false
6740567350
},
6740667351
{
@@ -67410,11 +67355,7 @@
6741067355
"required": false,
6741167356
"deprecated": false,
6741267357
"sensitive": false,
67413-
"tags": [
67414-
"tools"
67415-
],
67416-
"label": "Enable X Search Tool",
67417-
"help": "Enable the x_search tool (requires XAI_API_KEY or tools.web.x_search.apiKey).",
67358+
"tags": [],
6741867359
"hasChildren": false
6741967360
},
6742067361
{
@@ -67424,11 +67365,7 @@
6742467365
"required": false,
6742567366
"deprecated": false,
6742667367
"sensitive": false,
67427-
"tags": [
67428-
"tools"
67429-
],
67430-
"label": "X Search Inline Citations",
67431-
"help": "Keep inline citations from xAI in x_search responses when available (default: false).",
67368+
"tags": [],
6743267369
"hasChildren": false
6743367370
},
6743467371
{
@@ -67438,12 +67375,7 @@
6743867375
"required": false,
6743967376
"deprecated": false,
6744067377
"sensitive": false,
67441-
"tags": [
67442-
"performance",
67443-
"tools"
67444-
],
67445-
"label": "X Search Max Turns",
67446-
"help": "Optional max internal search/tool turns xAI may use per x_search request. Omit to let xAI choose.",
67378+
"tags": [],
6744767379
"hasChildren": false
6744867380
},
6744967381
{
@@ -67453,12 +67385,7 @@
6745367385
"required": false,
6745467386
"deprecated": false,
6745567387
"sensitive": false,
67456-
"tags": [
67457-
"models",
67458-
"tools"
67459-
],
67460-
"label": "X Search Model",
67461-
"help": "Model to use for X search (default: \"grok-4-1-fast-non-reasoning\").",
67388+
"tags": [],
6746267389
"hasChildren": false
6746367390
},
6746467391
{
@@ -67468,12 +67395,7 @@
6746867395
"required": false,
6746967396
"deprecated": false,
6747067397
"sensitive": false,
67471-
"tags": [
67472-
"performance",
67473-
"tools"
67474-
],
67475-
"label": "X Search Timeout (sec)",
67476-
"help": "Timeout in seconds for x_search requests.",
67398+
"tags": [],
6747767399
"hasChildren": false
6747867400
},
6747967401
{

docs/.generated/config-baseline.jsonl

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5781}
1+
{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5777}
22
{"recordType":"path","path":"acp","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP","help":"ACP runtime controls for enabling dispatch, selecting backends, constraining allowed agent targets, and tuning streamed turn projection behavior.","hasChildren":true}
33
{"recordType":"path","path":"acp.allowedAgents","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"ACP Allowed Agents","help":"Allowlist of ACP target agent ids permitted for ACP runtime sessions. Empty means no additional allowlist restriction.","hasChildren":true}
44
{"recordType":"path","path":"acp.allowedAgents.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -5742,16 +5742,12 @@
57425742
{"recordType":"path","path":"tools.web.search.provider","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Web Search Provider","help":"Search provider id. Auto-detected from available API keys if omitted.","hasChildren":false}
57435743
{"recordType":"path","path":"tools.web.search.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","tools"],"label":"Web Search Timeout (sec)","help":"Timeout in seconds for web_search requests.","hasChildren":false}
57445744
{"recordType":"path","path":"tools.web.x_search","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
5745-
{"recordType":"path","path":"tools.web.x_search.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"label":"xAI API Key","help":"xAI API key for X search (fallback: XAI_API_KEY env var).","hasChildren":true}
5746-
{"recordType":"path","path":"tools.web.x_search.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
5747-
{"recordType":"path","path":"tools.web.x_search.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
5748-
{"recordType":"path","path":"tools.web.x_search.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
5749-
{"recordType":"path","path":"tools.web.x_search.cacheTtlMinutes","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage","tools"],"label":"X Search Cache TTL (min)","help":"Cache TTL in minutes for x_search results.","hasChildren":false}
5750-
{"recordType":"path","path":"tools.web.x_search.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Enable X Search Tool","help":"Enable the x_search tool (requires XAI_API_KEY or tools.web.x_search.apiKey).","hasChildren":false}
5751-
{"recordType":"path","path":"tools.web.x_search.inlineCitations","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"X Search Inline Citations","help":"Keep inline citations from xAI in x_search responses when available (default: false).","hasChildren":false}
5752-
{"recordType":"path","path":"tools.web.x_search.maxTurns","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","tools"],"label":"X Search Max Turns","help":"Optional max internal search/tool turns xAI may use per x_search request. Omit to let xAI choose.","hasChildren":false}
5753-
{"recordType":"path","path":"tools.web.x_search.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models","tools"],"label":"X Search Model","help":"Model to use for X search (default: \"grok-4-1-fast-non-reasoning\").","hasChildren":false}
5754-
{"recordType":"path","path":"tools.web.x_search.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","tools"],"label":"X Search Timeout (sec)","help":"Timeout in seconds for x_search requests.","hasChildren":false}
5745+
{"recordType":"path","path":"tools.web.x_search.cacheTtlMinutes","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
5746+
{"recordType":"path","path":"tools.web.x_search.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
5747+
{"recordType":"path","path":"tools.web.x_search.inlineCitations","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
5748+
{"recordType":"path","path":"tools.web.x_search.maxTurns","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
5749+
{"recordType":"path","path":"tools.web.x_search.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
5750+
{"recordType":"path","path":"tools.web.x_search.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
57555751
{"recordType":"path","path":"ui","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"UI","help":"UI presentation settings for accenting and assistant identity shown in control surfaces. Use this for branding and readability customization without changing runtime behavior.","hasChildren":true}
57565752
{"recordType":"path","path":"ui.assistant","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Assistant Appearance","help":"Assistant display identity settings for name and avatar shown in UI surfaces. Keep these values aligned with your operator-facing persona and support expectations.","hasChildren":true}
57575753
{"recordType":"path","path":"ui.assistant.avatar","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Assistant Avatar","help":"Assistant avatar image source used in UI surfaces (URL, path, or data URI depending on runtime support). Use trusted assets and consistent branding dimensions for clean rendering.","hasChildren":false}

docs/reference/secretref-credential-surface.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ Scope intent:
3939
- `plugins.entries.firecrawl.config.webSearch.apiKey`
4040
- `plugins.entries.tavily.config.webSearch.apiKey`
4141
- `tools.web.search.apiKey`
42-
- `tools.web.x_search.apiKey`
4342
- `gateway.auth.password`
4443
- `gateway.auth.token`
4544
- `gateway.remote.token`

docs/reference/secretref-user-supplied-credentials-matrix.json

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -523,13 +523,6 @@
523523
"path": "tools.web.search.apiKey",
524524
"secretShape": "secret_input",
525525
"optIn": true
526-
},
527-
{
528-
"id": "tools.web.x_search.apiKey",
529-
"configFile": "openclaw.json",
530-
"path": "tools.web.x_search.apiKey",
531-
"secretShape": "secret_input",
532-
"optIn": true
533526
}
534527
]
535528
}

extensions/xai/x-search.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,44 @@ describe("xai x_search tool", () => {
231231
);
232232
});
233233

234+
it("uses migrated runtime auth when the source config still carries legacy x_search apiKey", async () => {
235+
const mockFetch = installXSearchFetch();
236+
const tool = createXSearchTool({
237+
config: {
238+
tools: {
239+
web: {
240+
x_search: {
241+
apiKey: "legacy-x-search-key", // pragma: allowlist secret
242+
enabled: true,
243+
} as Record<string, unknown>,
244+
},
245+
},
246+
},
247+
runtimeConfig: {
248+
plugins: {
249+
entries: {
250+
xai: {
251+
config: {
252+
webSearch: {
253+
apiKey: "migrated-runtime-key", // pragma: allowlist secret
254+
},
255+
},
256+
},
257+
},
258+
},
259+
},
260+
});
261+
262+
await tool?.execute?.("x-search:migrated-runtime-key", {
263+
query: "migrated runtime auth",
264+
});
265+
266+
const request = mockFetch.mock.calls[0]?.[1] as RequestInit | undefined;
267+
expect((request?.headers as Record<string, string> | undefined)?.Authorization).toBe(
268+
"Bearer migrated-runtime-key",
269+
);
270+
});
271+
234272
it("rejects invalid date ordering before calling xAI", async () => {
235273
const mockFetch = installXSearchFetch();
236274
const tool = createXSearchTool({

extensions/xai/x-search.ts

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,6 @@ function resolveFallbackXaiApiKey(cfg?: OpenClawConfig): string | undefined {
7777
return readPluginXaiWebSearchApiKey(cfg) ?? readLegacyGrokApiKey(cfg);
7878
}
7979

80-
function readLegacyXSearchApiKey(cfg?: OpenClawConfig): string | undefined {
81-
const legacyConfig = resolveLegacyXSearchConfig(cfg);
82-
return readConfiguredSecretString(legacyConfig?.apiKey, "tools.web.x_search.apiKey");
83-
}
84-
8580
function resolveXSearchConfig(cfg?: OpenClawConfig): Record<string, unknown> | undefined {
8681
return resolveEffectiveXSearchConfig(cfg);
8782
}
@@ -94,33 +89,19 @@ function resolveXSearchEnabled(params: {
9489
if (params.config?.enabled === false) {
9590
return false;
9691
}
97-
if (
98-
resolveFallbackXaiApiKey(params.runtimeConfig) ||
99-
readLegacyXSearchApiKey(params.runtimeConfig)
100-
) {
92+
if (resolveFallbackXaiApiKey(params.runtimeConfig)) {
10193
return true;
10294
}
103-
return Boolean(
104-
resolveFallbackXaiApiKey(params.cfg) ||
105-
readLegacyXSearchApiKey(params.cfg) ||
106-
readProviderEnvValue(["XAI_API_KEY"]),
107-
);
95+
return Boolean(resolveFallbackXaiApiKey(params.cfg) || readProviderEnvValue(["XAI_API_KEY"]));
10896
}
10997

11098
function resolveXSearchApiKey(params: {
11199
sourceConfig?: OpenClawConfig;
112100
runtimeConfig?: OpenClawConfig;
113101
}): string | undefined {
114-
const sourceXSearchConfig = resolveXSearchConfig(params.sourceConfig);
115-
const runtimeXSearchConfig =
116-
params.runtimeConfig && params.runtimeConfig !== params.sourceConfig
117-
? resolveXSearchConfig(params.runtimeConfig)
118-
: undefined;
119102
return (
120103
resolveFallbackXaiApiKey(params.runtimeConfig) ??
121104
resolveFallbackXaiApiKey(params.sourceConfig) ??
122-
readLegacyXSearchApiKey(params.runtimeConfig) ??
123-
readLegacyXSearchApiKey(params.sourceConfig) ??
124105
readProviderEnvValue(["XAI_API_KEY"])
125106
);
126107
}

src/commands/doctor-legacy-config.migrations.test.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ describe("normalizeCompatibilityConfigValues", () => {
125125
]);
126126
});
127127

128-
it("migrates legacy x_search config into xai plugin-owned config", () => {
128+
it("migrates legacy x_search auth into xai plugin-owned config", () => {
129129
const res = normalizeCompatibilityConfigValues({
130130
tools: {
131131
web: {
@@ -138,25 +138,21 @@ describe("normalizeCompatibilityConfigValues", () => {
138138
},
139139
});
140140

141-
expect(
142-
(res.config.tools?.web as Record<string, unknown> | undefined)?.x_search,
143-
).toBeUndefined();
141+
expect((res.config.tools?.web as Record<string, unknown> | undefined)?.x_search).toEqual({
142+
enabled: true,
143+
model: "grok-4-1-fast",
144+
});
144145
expect(res.config.plugins?.entries?.xai).toEqual({
145146
enabled: true,
146147
config: {
147148
webSearch: {
148149
apiKey: "xai-legacy-key",
149150
},
150-
xSearch: {
151-
enabled: true,
152-
model: "grok-4-1-fast",
153-
},
154151
},
155152
});
156153
expect(res.changes).toEqual(
157154
expect.arrayContaining([
158155
"Moved tools.web.x_search.apiKey → plugins.entries.xai.config.webSearch.apiKey.",
159-
"Moved tools.web.x_search → plugins.entries.xai.config.xSearch.",
160156
]),
161157
);
162158
});

src/config/legacy-migrate.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,38 @@ describe("legacy migrate tts provider shape", () => {
286286
});
287287
});
288288

289+
describe("legacy migrate x_search auth", () => {
290+
it("moves only legacy x_search auth into plugin-owned xai config", () => {
291+
const res = migrateLegacyConfig({
292+
tools: {
293+
web: {
294+
x_search: {
295+
apiKey: "xai-legacy-key",
296+
enabled: true,
297+
model: "grok-4-1-fast",
298+
},
299+
},
300+
},
301+
});
302+
303+
expect((res.config?.tools?.web as Record<string, unknown> | undefined)?.x_search).toEqual({
304+
enabled: true,
305+
model: "grok-4-1-fast",
306+
});
307+
expect(res.config?.plugins?.entries?.xai).toEqual({
308+
enabled: true,
309+
config: {
310+
webSearch: {
311+
apiKey: "xai-legacy-key",
312+
},
313+
},
314+
});
315+
expect(res.changes).toEqual([
316+
"Moved tools.web.x_search.apiKey → plugins.entries.xai.config.webSearch.apiKey.",
317+
]);
318+
});
319+
});
320+
289321
describe("legacy migrate heartbeat config", () => {
290322
it("moves top-level heartbeat into agents.defaults.heartbeat", () => {
291323
const res = migrateLegacyConfig({

0 commit comments

Comments
 (0)