Skip to content

Commit 3e4de95

Browse files
authored
!refactor(xai): move x_search config behind plugin boundary (#59674)
* refactor(xai): move x_search config behind plugin boundary * chore(changelog): note x_search config migration * fix(xai): include x_search migration helpers
1 parent ef7c553 commit 3e4de95

34 files changed

Lines changed: 788 additions & 420 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai
77
### Breaking
88

99
- Plugins/web fetch: move Firecrawl `web_fetch` config from the legacy core `tools.web.fetch.firecrawl.*` path to the plugin-owned `plugins.entries.firecrawl.config.webFetch.*` path, route `web_fetch` fallback through the new fetch-provider boundary instead of a Firecrawl-only core branch, and migrate legacy config with `openclaw doctor --fix`. Thanks @vincentkoc.
10+
- Plugins/xAI: move `x_search` settings from the legacy core `tools.web.x_search.*` path to the plugin-owned `plugins.entries.xai.config.xSearch.*` path, standardize `x_search` auth on `plugins.entries.xai.config.webSearch.apiKey` / `XAI_API_KEY`, and migrate legacy config with `openclaw doctor --fix`. Thanks @vincentkoc.
1011

1112
### Changes
1213

docs/.generated/config-baseline.json

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60193,6 +60193,101 @@
6019360193
"help": "Grok model override for web search.",
6019460194
"hasChildren": false
6019560195
},
60196+
{
60197+
"path": "plugins.entries.xai.config.xSearch",
60198+
"kind": "plugin",
60199+
"type": "object",
60200+
"required": false,
60201+
"deprecated": false,
60202+
"sensitive": false,
60203+
"tags": [],
60204+
"hasChildren": true
60205+
},
60206+
{
60207+
"path": "plugins.entries.xai.config.xSearch.cacheTtlMinutes",
60208+
"kind": "plugin",
60209+
"type": "number",
60210+
"required": false,
60211+
"deprecated": false,
60212+
"sensitive": false,
60213+
"tags": [
60214+
"performance",
60215+
"storage"
60216+
],
60217+
"label": "X Search Cache TTL",
60218+
"help": "Cache TTL in minutes for x_search results.",
60219+
"hasChildren": false
60220+
},
60221+
{
60222+
"path": "plugins.entries.xai.config.xSearch.enabled",
60223+
"kind": "plugin",
60224+
"type": "boolean",
60225+
"required": false,
60226+
"deprecated": false,
60227+
"sensitive": false,
60228+
"tags": [
60229+
"advanced"
60230+
],
60231+
"label": "Enable X Search",
60232+
"help": "Enable the x_search tool for searching X posts with xAI.",
60233+
"hasChildren": false
60234+
},
60235+
{
60236+
"path": "plugins.entries.xai.config.xSearch.inlineCitations",
60237+
"kind": "plugin",
60238+
"type": "boolean",
60239+
"required": false,
60240+
"deprecated": false,
60241+
"sensitive": false,
60242+
"tags": [
60243+
"advanced"
60244+
],
60245+
"label": "X Search Inline Citations",
60246+
"help": "Keep inline markdown citations from xAI in x_search responses when available.",
60247+
"hasChildren": false
60248+
},
60249+
{
60250+
"path": "plugins.entries.xai.config.xSearch.maxTurns",
60251+
"kind": "plugin",
60252+
"type": "number",
60253+
"required": false,
60254+
"deprecated": false,
60255+
"sensitive": false,
60256+
"tags": [
60257+
"performance"
60258+
],
60259+
"label": "X Search Max Turns",
60260+
"help": "Optional max internal tool turns xAI may use per x_search request.",
60261+
"hasChildren": false
60262+
},
60263+
{
60264+
"path": "plugins.entries.xai.config.xSearch.model",
60265+
"kind": "plugin",
60266+
"type": "string",
60267+
"required": false,
60268+
"deprecated": false,
60269+
"sensitive": false,
60270+
"tags": [
60271+
"models"
60272+
],
60273+
"label": "X Search Model",
60274+
"help": "xAI model override for x_search.",
60275+
"hasChildren": false
60276+
},
60277+
{
60278+
"path": "plugins.entries.xai.config.xSearch.timeoutSeconds",
60279+
"kind": "plugin",
60280+
"type": "number",
60281+
"required": false,
60282+
"deprecated": false,
60283+
"sensitive": false,
60284+
"tags": [
60285+
"performance"
60286+
],
60287+
"label": "X Search Timeout",
60288+
"help": "Timeout in seconds for x_search requests.",
60289+
"hasChildren": false
60290+
},
6019660291
{
6019760292
"path": "plugins.entries.xai.enabled",
6019860293
"kind": "plugin",
@@ -66799,6 +66894,116 @@
6679966894
"help": "Enable the web_fetch tool (lightweight HTTP fetch).",
6680066895
"hasChildren": false
6680166896
},
66897+
{
66898+
"path": "tools.web.fetch.firecrawl",
66899+
"kind": "core",
66900+
"type": "object",
66901+
"required": false,
66902+
"deprecated": false,
66903+
"sensitive": false,
66904+
"tags": [],
66905+
"hasChildren": true
66906+
},
66907+
{
66908+
"path": "tools.web.fetch.firecrawl.apiKey",
66909+
"kind": "core",
66910+
"type": [
66911+
"object",
66912+
"string"
66913+
],
66914+
"required": false,
66915+
"deprecated": false,
66916+
"sensitive": true,
66917+
"tags": [
66918+
"auth",
66919+
"security",
66920+
"tools"
66921+
],
66922+
"hasChildren": true
66923+
},
66924+
{
66925+
"path": "tools.web.fetch.firecrawl.apiKey.id",
66926+
"kind": "core",
66927+
"type": "string",
66928+
"required": true,
66929+
"deprecated": false,
66930+
"sensitive": false,
66931+
"tags": [],
66932+
"hasChildren": false
66933+
},
66934+
{
66935+
"path": "tools.web.fetch.firecrawl.apiKey.provider",
66936+
"kind": "core",
66937+
"type": "string",
66938+
"required": true,
66939+
"deprecated": false,
66940+
"sensitive": false,
66941+
"tags": [],
66942+
"hasChildren": false
66943+
},
66944+
{
66945+
"path": "tools.web.fetch.firecrawl.apiKey.source",
66946+
"kind": "core",
66947+
"type": "string",
66948+
"required": true,
66949+
"deprecated": false,
66950+
"sensitive": false,
66951+
"tags": [],
66952+
"hasChildren": false
66953+
},
66954+
{
66955+
"path": "tools.web.fetch.firecrawl.baseUrl",
66956+
"kind": "core",
66957+
"type": "string",
66958+
"required": false,
66959+
"deprecated": false,
66960+
"sensitive": false,
66961+
"tags": [
66962+
"tools",
66963+
"url-secret"
66964+
],
66965+
"hasChildren": false
66966+
},
66967+
{
66968+
"path": "tools.web.fetch.firecrawl.enabled",
66969+
"kind": "core",
66970+
"type": "boolean",
66971+
"required": false,
66972+
"deprecated": false,
66973+
"sensitive": false,
66974+
"tags": [],
66975+
"hasChildren": false
66976+
},
66977+
{
66978+
"path": "tools.web.fetch.firecrawl.maxAgeMs",
66979+
"kind": "core",
66980+
"type": "integer",
66981+
"required": false,
66982+
"deprecated": false,
66983+
"sensitive": false,
66984+
"tags": [],
66985+
"hasChildren": false
66986+
},
66987+
{
66988+
"path": "tools.web.fetch.firecrawl.onlyMainContent",
66989+
"kind": "core",
66990+
"type": "boolean",
66991+
"required": false,
66992+
"deprecated": false,
66993+
"sensitive": false,
66994+
"tags": [],
66995+
"hasChildren": false
66996+
},
66997+
{
66998+
"path": "tools.web.fetch.firecrawl.timeoutSeconds",
66999+
"kind": "core",
67000+
"type": "integer",
67001+
"required": false,
67002+
"deprecated": false,
67003+
"sensitive": false,
67004+
"tags": [],
67005+
"hasChildren": false
67006+
},
6680267007
{
6680367008
"path": "tools.web.fetch.maxChars",
6680467009
"kind": "core",

docs/.generated/config-baseline.jsonl

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5764}
1+
{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5781}
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}
@@ -5142,6 +5142,13 @@
51425142
{"recordType":"path","path":"plugins.entries.xai.config.webSearch.apiKey","kind":"plugin","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"label":"Grok Search API Key","help":"xAI API key for Grok web search (fallback: XAI_API_KEY env var).","hasChildren":false}
51435143
{"recordType":"path","path":"plugins.entries.xai.config.webSearch.inlineCitations","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Inline Citations","help":"Include inline markdown citations in Grok responses.","hasChildren":false}
51445144
{"recordType":"path","path":"plugins.entries.xai.config.webSearch.model","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Grok Search Model","help":"Grok model override for web search.","hasChildren":false}
5145+
{"recordType":"path","path":"plugins.entries.xai.config.xSearch","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
5146+
{"recordType":"path","path":"plugins.entries.xai.config.xSearch.cacheTtlMinutes","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"X Search Cache TTL","help":"Cache TTL in minutes for x_search results.","hasChildren":false}
5147+
{"recordType":"path","path":"plugins.entries.xai.config.xSearch.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable X Search","help":"Enable the x_search tool for searching X posts with xAI.","hasChildren":false}
5148+
{"recordType":"path","path":"plugins.entries.xai.config.xSearch.inlineCitations","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"X Search Inline Citations","help":"Keep inline markdown citations from xAI in x_search responses when available.","hasChildren":false}
5149+
{"recordType":"path","path":"plugins.entries.xai.config.xSearch.maxTurns","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"X Search Max Turns","help":"Optional max internal tool turns xAI may use per x_search request.","hasChildren":false}
5150+
{"recordType":"path","path":"plugins.entries.xai.config.xSearch.model","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"X Search Model","help":"xAI model override for x_search.","hasChildren":false}
5151+
{"recordType":"path","path":"plugins.entries.xai.config.xSearch.timeoutSeconds","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"X Search Timeout","help":"Timeout in seconds for x_search requests.","hasChildren":false}
51455152
{"recordType":"path","path":"plugins.entries.xai.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/xai-plugin","hasChildren":false}
51465153
{"recordType":"path","path":"plugins.entries.xai.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
51475154
{"recordType":"path","path":"plugins.entries.xai.hooks.allowPromptInjection","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Prompt Injection Hooks","help":"Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.","hasChildren":false}
@@ -5700,6 +5707,16 @@
57005707
{"recordType":"path","path":"tools.web.fetch","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
57015708
{"recordType":"path","path":"tools.web.fetch.cacheTtlMinutes","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage","tools"],"label":"Web Fetch Cache TTL (min)","help":"Cache TTL in minutes for web_fetch results.","hasChildren":false}
57025709
{"recordType":"path","path":"tools.web.fetch.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Enable Web Fetch Tool","help":"Enable the web_fetch tool (lightweight HTTP fetch).","hasChildren":false}
5710+
{"recordType":"path","path":"tools.web.fetch.firecrawl","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
5711+
{"recordType":"path","path":"tools.web.fetch.firecrawl.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"hasChildren":true}
5712+
{"recordType":"path","path":"tools.web.fetch.firecrawl.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
5713+
{"recordType":"path","path":"tools.web.fetch.firecrawl.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
5714+
{"recordType":"path","path":"tools.web.fetch.firecrawl.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
5715+
{"recordType":"path","path":"tools.web.fetch.firecrawl.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools","url-secret"],"hasChildren":false}
5716+
{"recordType":"path","path":"tools.web.fetch.firecrawl.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
5717+
{"recordType":"path","path":"tools.web.fetch.firecrawl.maxAgeMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
5718+
{"recordType":"path","path":"tools.web.fetch.firecrawl.onlyMainContent","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
5719+
{"recordType":"path","path":"tools.web.fetch.firecrawl.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
57035720
{"recordType":"path","path":"tools.web.fetch.maxChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","tools"],"label":"Web Fetch Max Chars","help":"Max characters returned by web_fetch (truncated).","hasChildren":false}
57045721
{"recordType":"path","path":"tools.web.fetch.maxCharsCap","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","tools"],"label":"Web Fetch Hard Max Chars","help":"Hard cap for web_fetch maxChars (applies to config and tool calls).","hasChildren":false}
57055722
{"recordType":"path","path":"tools.web.fetch.maxRedirects","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage","tools"],"label":"Web Fetch Max Redirects","help":"Maximum redirects allowed for web_fetch (default: 3).","hasChildren":false}

extensions/xai/openclaw.plugin.json

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,30 @@
3838
"label": "Enable Code Execution",
3939
"help": "Enable the code_execution tool for remote xAI sandbox analysis."
4040
},
41+
"xSearch.enabled": {
42+
"label": "Enable X Search",
43+
"help": "Enable the x_search tool for searching X posts with xAI."
44+
},
45+
"xSearch.model": {
46+
"label": "X Search Model",
47+
"help": "xAI model override for x_search."
48+
},
49+
"xSearch.inlineCitations": {
50+
"label": "X Search Inline Citations",
51+
"help": "Keep inline markdown citations from xAI in x_search responses when available."
52+
},
53+
"xSearch.maxTurns": {
54+
"label": "X Search Max Turns",
55+
"help": "Optional max internal tool turns xAI may use per x_search request."
56+
},
57+
"xSearch.timeoutSeconds": {
58+
"label": "X Search Timeout",
59+
"help": "Timeout in seconds for x_search requests."
60+
},
61+
"xSearch.cacheTtlMinutes": {
62+
"label": "X Search Cache TTL",
63+
"help": "Cache TTL in minutes for x_search results."
64+
},
4165
"codeExecution.model": {
4266
"label": "Code Execution Model",
4367
"help": "xAI model override for code_execution."
@@ -74,6 +98,30 @@
7498
}
7599
}
76100
},
101+
"xSearch": {
102+
"type": "object",
103+
"additionalProperties": false,
104+
"properties": {
105+
"enabled": {
106+
"type": "boolean"
107+
},
108+
"model": {
109+
"type": "string"
110+
},
111+
"inlineCitations": {
112+
"type": "boolean"
113+
},
114+
"maxTurns": {
115+
"type": "number"
116+
},
117+
"timeoutSeconds": {
118+
"type": "number"
119+
},
120+
"cacheTtlMinutes": {
121+
"type": "number"
122+
}
123+
}
124+
},
77125
"codeExecution": {
78126
"type": "object",
79127
"additionalProperties": false,
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import type { OpenClawConfig } from "openclaw/plugin-sdk/plugin-entry";
2+
3+
type JsonRecord = Record<string, unknown>;
4+
5+
function isRecord(value: unknown): value is JsonRecord {
6+
return typeof value === "object" && value !== null && !Array.isArray(value);
7+
}
8+
9+
function cloneRecord<T extends JsonRecord | undefined>(value: T): T {
10+
if (!value) {
11+
return value;
12+
}
13+
return { ...value } as T;
14+
}
15+
16+
export function resolveLegacyXSearchConfig(config?: OpenClawConfig): JsonRecord | undefined {
17+
const web = config?.tools?.web as Record<string, unknown> | undefined;
18+
const xSearch = web?.x_search;
19+
return isRecord(xSearch) ? cloneRecord(xSearch) : undefined;
20+
}
21+
22+
export function resolvePluginXSearchConfig(config?: OpenClawConfig): JsonRecord | undefined {
23+
const pluginConfig = config?.plugins?.entries?.xai?.config;
24+
if (!isRecord(pluginConfig?.xSearch)) {
25+
return undefined;
26+
}
27+
return cloneRecord(pluginConfig.xSearch);
28+
}
29+
30+
export function resolveEffectiveXSearchConfig(config?: OpenClawConfig): JsonRecord | undefined {
31+
const legacy = resolveLegacyXSearchConfig(config);
32+
const pluginOwned = resolvePluginXSearchConfig(config);
33+
if (!legacy) {
34+
return pluginOwned;
35+
}
36+
if (!pluginOwned) {
37+
return legacy;
38+
}
39+
return {
40+
...legacy,
41+
...pluginOwned,
42+
};
43+
}
44+
45+
export function setPluginXSearchConfigValue(
46+
configTarget: OpenClawConfig,
47+
key: string,
48+
value: unknown,
49+
): void {
50+
const plugins = (configTarget.plugins ??= {}) as { entries?: Record<string, unknown> };
51+
const entries = (plugins.entries ??= {});
52+
const entry = (entries.xai ??= {}) as { config?: Record<string, unknown> };
53+
const config = (entry.config ??= {});
54+
const xSearch = (config.xSearch ??= {}) as Record<string, unknown>;
55+
xSearch[key] = value;
56+
}

0 commit comments

Comments
 (0)