diff --git a/docs/.generated/config-baseline.json b/docs/.generated/config-baseline.json index d551b16708b..d9697ae2874 100644 --- a/docs/.generated/config-baseline.json +++ b/docs/.generated/config-baseline.json @@ -60229,6 +60229,124 @@ "tags": [], "hasChildren": false }, + { + "path": "tools.code_execution", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.code_execution.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 remote code execution (fallback: XAI_API_KEY env var).", + "hasChildren": true + }, + { + "path": "tools.code_execution.apiKey.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.code_execution.apiKey.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.code_execution.apiKey.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.code_execution.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Enable Code Execution Tool", + "help": "Enable the code_execution tool (requires XAI_API_KEY or tools.code_execution.apiKey).", + "hasChildren": false + }, + { + "path": "tools.code_execution.maxTurns", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "tools" + ], + "label": "Code Execution Max Turns", + "help": "Optional max internal tool turns xAI may use per code_execution request. Omit to let xAI choose.", + "hasChildren": false + }, + { + "path": "tools.code_execution.model", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "models", + "tools" + ], + "label": "Code Execution Model", + "help": "Model to use for remote code execution (default: \"grok-4-1-fast\").", + "hasChildren": false + }, + { + "path": "tools.code_execution.timeoutSeconds", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "tools" + ], + "label": "Code Execution Timeout (sec)", + "help": "Timeout in seconds for code_execution requests.", + "hasChildren": false + }, { "path": "tools.deny", "kind": "core", @@ -64706,6 +64824,154 @@ "help": "Timeout in seconds for web_search requests.", "hasChildren": false }, + { + "path": "tools.web.x_search", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "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 + }, + { + "path": "tools.web.x_search.apiKey.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.x_search.apiKey.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.x_search.apiKey.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, { "path": "ui", "kind": "core", diff --git a/docs/.generated/config-baseline.jsonl b/docs/.generated/config-baseline.jsonl index 5d7b0ef0126..2f67fa407a2 100644 --- a/docs/.generated/config-baseline.jsonl +++ b/docs/.generated/config-baseline.jsonl @@ -1,4 +1,4 @@ -{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5554} +{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5574} {"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} {"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} {"recordType":"path","path":"acp.allowedAgents.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} @@ -5134,6 +5134,15 @@ {"recordType":"path","path":"tools.byProvider.*.deny","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"tools.byProvider.*.deny.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"tools.byProvider.*.profile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.code_execution","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.code_execution.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 remote code execution (fallback: XAI_API_KEY env var).","hasChildren":true} +{"recordType":"path","path":"tools.code_execution.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.code_execution.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.code_execution.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.code_execution.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Enable Code Execution Tool","help":"Enable the code_execution tool (requires XAI_API_KEY or tools.code_execution.apiKey).","hasChildren":false} +{"recordType":"path","path":"tools.code_execution.maxTurns","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","tools"],"label":"Code Execution Max Turns","help":"Optional max internal tool turns xAI may use per code_execution request. Omit to let xAI choose.","hasChildren":false} +{"recordType":"path","path":"tools.code_execution.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models","tools"],"label":"Code Execution Model","help":"Model to use for remote code execution (default: \"grok-4-1-fast\").","hasChildren":false} +{"recordType":"path","path":"tools.code_execution.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","tools"],"label":"Code Execution Timeout (sec)","help":"Timeout in seconds for code_execution requests.","hasChildren":false} {"recordType":"path","path":"tools.deny","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access","tools"],"label":"Tool Denylist","help":"Global tool denylist that blocks listed tools even when profile or provider rules would allow them. Use deny rules for emergency lockouts and long-term defense-in-depth.","hasChildren":true} {"recordType":"path","path":"tools.deny.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"tools.elevated","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Elevated Tool Access","help":"Elevated tool access controls for privileged command surfaces that should only be reachable from trusted senders. Keep disabled unless operator workflows explicitly require elevated actions.","hasChildren":true} @@ -5525,6 +5534,17 @@ {"recordType":"path","path":"tools.web.search.perplexity.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"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} {"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} +{"recordType":"path","path":"tools.web.x_search","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"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} +{"recordType":"path","path":"tools.web.x_search.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.web.x_search.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.web.x_search.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"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} +{"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} +{"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} +{"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} +{"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} +{"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} {"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} {"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} {"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} diff --git a/docs/docs.json b/docs/docs.json index d5c3f9ae86b..e0cb9ce84b7 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -1146,6 +1146,7 @@ ] }, "tools/btw", + "tools/code-execution", "tools/diffs", "tools/elevated", "tools/exec", diff --git a/docs/providers/xai.md b/docs/providers/xai.md index 72bf91f7fdd..6e98a24bd92 100644 --- a/docs/providers/xai.md +++ b/docs/providers/xai.md @@ -28,7 +28,8 @@ openclaw onboard --auth-choice xai-api-key ``` OpenClaw now uses the xAI Responses API as the bundled xAI transport. The same -`XAI_API_KEY` can also power Grok-backed `web_search` and first-class `x_search`. +`XAI_API_KEY` can also power Grok-backed `web_search`, first-class `x_search`, +and remote `code_execution`. If you store an xAI key under `plugins.entries.xai.config.webSearch.apiKey`, the bundled xAI model provider now reuses that key as a fallback too. @@ -61,5 +62,6 @@ openclaw config set tools.web.search.provider grok ## Notes - OpenClaw applies xAI-specific tool-schema and tool-call compatibility fixes automatically on the shared runner path. -- `web_search` and `x_search` are exposed as OpenClaw tools. OpenClaw enables the specific xAI built-in it needs inside each tool request instead of attaching both search tools to every chat turn. +- `web_search`, `x_search`, and `code_execution` are exposed as OpenClaw tools. OpenClaw enables the specific xAI built-in it needs inside each tool request instead of attaching all native tools to every chat turn. +- `code_execution` is remote xAI sandbox execution, not local [`exec`](/tools/exec). - For the broader provider overview, see [Model providers](/providers/index). diff --git a/docs/tools/code-execution.md b/docs/tools/code-execution.md new file mode 100644 index 00000000000..c47eee153b7 --- /dev/null +++ b/docs/tools/code-execution.md @@ -0,0 +1,83 @@ +--- +summary: "code_execution -- run sandboxed remote Python analysis with xAI" +read_when: + - You want to enable or configure code_execution + - You want remote analysis without local shell access + - You want to combine x_search or web_search with remote Python analysis +title: "Code Execution" +--- + +# Code Execution + +`code_execution` runs sandboxed remote Python analysis on xAI's Responses API. +This is different from local [`exec`](/tools/exec): + +- `exec` runs shell commands on your machine or node +- `code_execution` runs Python in xAI's remote sandbox + +Use `code_execution` for: + +- calculations +- tabulation +- quick statistics +- chart-style analysis +- analyzing data returned by `x_search` or `web_search` + +Do **not** use it when you need local files, your shell, your repo, or paired +devices. Use [`exec`](/tools/exec) for that. + +## Setup + +You need an xAI API key. Any of these work: + +- `tools.code_execution.apiKey` +- `XAI_API_KEY` +- `plugins.entries.xai.config.webSearch.apiKey` + +Example: + +```json5 +{ + tools: { + code_execution: { + enabled: true, + apiKey: "xai-...", + model: "grok-4-1-fast", + maxTurns: 2, + timeoutSeconds: 30, + }, + }, +} +``` + +## How To Use It + +Ask naturally and make the analysis intent explicit: + +```text +Use code_execution to calculate the 7-day moving average for these numbers: ... +``` + +```text +Use x_search to find posts mentioning OpenClaw this week, then use code_execution to count them by day. +``` + +```text +Use web_search to gather the latest AI benchmark numbers, then use code_execution to compare percent changes. +``` + +The tool takes a single `task` parameter internally, so the agent should send +the full analysis request and any inline data in one prompt. + +## Limits + +- This is remote xAI execution, not local process execution. +- It should be treated as ephemeral analysis, not a persistent notebook. +- Do not assume access to local files or your workspace. +- For fresh X data, use [`x_search`](/tools/web#x_search) first. + +## See Also + +- [Web tools](/tools/web) +- [Exec](/tools/exec) +- [xAI](/providers/xai) diff --git a/docs/tools/index.md b/docs/tools/index.md index f5d6b9eaf2d..bb3dc325b9e 100644 --- a/docs/tools/index.md +++ b/docs/tools/index.md @@ -52,19 +52,20 @@ OpenClaw has three layers that work together: These tools ship with OpenClaw and are available without installing any plugins: -| Tool | What it does | Page | -| --------------------------------------- | -------------------------------------------------------- | --------------------------------- | -| `exec` / `process` | Run shell commands, manage background processes | [Exec](/tools/exec) | -| `browser` | Control a Chromium browser (navigate, click, screenshot) | [Browser](/tools/browser) | -| `web_search` / `x_search` / `web_fetch` | Search the web, search X posts, fetch page content | [Web](/tools/web) | -| `read` / `write` / `edit` | File I/O in the workspace | | -| `apply_patch` | Multi-hunk file patches | [Apply Patch](/tools/apply-patch) | -| `message` | Send messages across all channels | [Agent Send](/tools/agent-send) | -| `canvas` | Drive node Canvas (present, eval, snapshot) | | -| `nodes` | Discover and target paired devices | | -| `cron` / `gateway` | Manage scheduled jobs, restart gateway | | -| `image` / `image_generate` | Analyze or generate images | | -| `sessions_*` / `agents_list` | Session management, sub-agents | [Sub-agents](/tools/subagents) | +| Tool | What it does | Page | +| --------------------------------------- | -------------------------------------------------------- | --------------------------------------- | +| `exec` / `process` | Run shell commands, manage background processes | [Exec](/tools/exec) | +| `code_execution` | Run sandboxed remote Python analysis with xAI | [Code Execution](/tools/code-execution) | +| `browser` | Control a Chromium browser (navigate, click, screenshot) | [Browser](/tools/browser) | +| `web_search` / `x_search` / `web_fetch` | Search the web, search X posts, fetch page content | [Web](/tools/web) | +| `read` / `write` / `edit` | File I/O in the workspace | | +| `apply_patch` | Multi-hunk file patches | [Apply Patch](/tools/apply-patch) | +| `message` | Send messages across all channels | [Agent Send](/tools/agent-send) | +| `canvas` | Drive node Canvas (present, eval, snapshot) | | +| `nodes` | Discover and target paired devices | | +| `cron` / `gateway` | Manage scheduled jobs, restart gateway | | +| `image` / `image_generate` | Analyze or generate images | | +| `sessions_*` / `agents_list` | Session management, sub-agents | [Sub-agents](/tools/subagents) | For image work, use `image` for analysis and `image_generate` for generation or editing. If you target `openai/*`, `google/*`, `fal/*`, or another non-default image provider, configure that provider's auth/API key first. @@ -111,7 +112,7 @@ Use `group:*` shorthands in allow/deny lists: | Group | Tools | | ------------------ | --------------------------------------------------------------------------------------------------------- | -| `group:runtime` | exec, bash, process | +| `group:runtime` | exec, bash, process, code_execution | | `group:fs` | read, write, edit, apply_patch | | `group:sessions` | sessions_list, sessions_history, sessions_send, sessions_spawn, sessions_yield, subagents, session_status | | `group:memory` | memory_search, memory_get | diff --git a/extensions/xai/src/code-execution-shared.ts b/extensions/xai/src/code-execution-shared.ts new file mode 100644 index 00000000000..dfd658b1944 --- /dev/null +++ b/extensions/xai/src/code-execution-shared.ts @@ -0,0 +1,129 @@ +import { normalizeXaiModelId } from "openclaw/plugin-sdk/provider-models"; +import { postTrustedWebToolsJson } from "openclaw/plugin-sdk/provider-web-search"; +import { extractXaiWebSearchContent, type XaiWebSearchResponse } from "./web-search-shared.js"; + +export const XAI_CODE_EXECUTION_ENDPOINT = "https://api.x.ai/v1/responses"; +export const XAI_DEFAULT_CODE_EXECUTION_MODEL = "grok-4-1-fast"; + +export type XaiCodeExecutionConfig = { + apiKey?: unknown; + model?: unknown; + maxTurns?: unknown; +}; + +export type XaiCodeExecutionResponse = XaiWebSearchResponse & { + output?: Array<{ + type?: string; + }>; +}; + +export type XaiCodeExecutionResult = { + content: string; + citations: string[]; + usedCodeExecution: boolean; + outputTypes: string[]; +}; + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +export function resolveXaiCodeExecutionConfig( + config?: Record, +): XaiCodeExecutionConfig { + return isRecord(config) ? (config as XaiCodeExecutionConfig) : {}; +} + +export function resolveXaiCodeExecutionModel(config?: Record): string { + const resolved = resolveXaiCodeExecutionConfig(config); + return typeof resolved.model === "string" && resolved.model.trim() + ? normalizeXaiModelId(resolved.model.trim()) + : XAI_DEFAULT_CODE_EXECUTION_MODEL; +} + +export function resolveXaiCodeExecutionMaxTurns( + config?: Record, +): number | undefined { + const raw = resolveXaiCodeExecutionConfig(config).maxTurns; + if (typeof raw !== "number" || !Number.isFinite(raw)) { + return undefined; + } + const normalized = Math.trunc(raw); + return normalized > 0 ? normalized : undefined; +} + +export function buildXaiCodeExecutionPayload(params: { + task: string; + model: string; + tookMs: number; + content: string; + citations: string[]; + usedCodeExecution: boolean; + outputTypes: string[]; +}): Record { + return { + task: params.task, + provider: "xai", + model: params.model, + tookMs: params.tookMs, + content: params.content, + citations: params.citations, + usedCodeExecution: params.usedCodeExecution, + outputTypes: params.outputTypes, + }; +} + +export async function requestXaiCodeExecution(params: { + apiKey: string; + model: string; + timeoutSeconds: number; + maxTurns?: number; + task: string; +}): Promise { + return await postTrustedWebToolsJson( + { + url: XAI_CODE_EXECUTION_ENDPOINT, + timeoutSeconds: params.timeoutSeconds, + apiKey: params.apiKey, + body: { + model: params.model, + input: [{ role: "user", content: params.task }], + tools: [{ type: "code_interpreter" }], + ...(params.maxTurns ? { max_turns: params.maxTurns } : {}), + }, + errorLabel: "xAI", + }, + async (response) => { + const data = (await response.json()) as XaiCodeExecutionResponse; + const { text, annotationCitations } = extractXaiWebSearchContent(data); + const outputTypes = Array.isArray(data.output) + ? [ + ...new Set( + data.output + .map((entry) => entry?.type) + .filter((value): value is string => Boolean(value)), + ), + ] + : []; + const citations = + Array.isArray(data.citations) && data.citations.length > 0 + ? data.citations + : annotationCitations; + return { + content: text ?? "No response", + citations, + usedCodeExecution: outputTypes.includes("code_interpreter_call"), + outputTypes, + }; + }, + ); +} + +export const __testing = { + buildXaiCodeExecutionPayload, + requestXaiCodeExecution, + resolveXaiCodeExecutionConfig, + resolveXaiCodeExecutionMaxTurns, + resolveXaiCodeExecutionModel, + XAI_DEFAULT_CODE_EXECUTION_MODEL, +} as const; diff --git a/src/agents/openclaw-tools.ts b/src/agents/openclaw-tools.ts index 8979a6d4c67..b8f1044d75f 100644 --- a/src/agents/openclaw-tools.ts +++ b/src/agents/openclaw-tools.ts @@ -27,7 +27,12 @@ import { createSessionsSpawnTool } from "./tools/sessions-spawn-tool.js"; import { createSessionsYieldTool } from "./tools/sessions-yield-tool.js"; import { createSubagentsTool } from "./tools/subagents-tool.js"; import { createTtsTool } from "./tools/tts-tool.js"; -import { createWebFetchTool, createWebSearchTool, createXSearchTool } from "./tools/web-tools.js"; +import { + createCodeExecutionTool, + createWebFetchTool, + createWebSearchTool, + createXSearchTool, +} from "./tools/web-tools.js"; import { resolveWorkspaceRoot } from "./workspace-dir.js"; type OpenClawToolsDeps = { @@ -159,6 +164,9 @@ export function createOpenClawTools( config: options?.config, runtimeXSearch: runtimeWebTools?.xSearch, }); + const codeExecutionTool = createCodeExecutionTool({ + config: options?.config, + }); const webFetchTool = createWebFetchTool({ config: options?.config, sandboxed: options?.sandboxed, @@ -256,6 +264,7 @@ export function createOpenClawTools( }), ...(webSearchTool ? [webSearchTool] : []), ...(xSearchTool ? [xSearchTool] : []), + ...(codeExecutionTool ? [codeExecutionTool] : []), ...(webFetchTool ? [webFetchTool] : []), ...(imageTool ? [imageTool] : []), ...(pdfTool ? [pdfTool] : []), diff --git a/src/agents/openclaw-tools.web-runtime.test.ts b/src/agents/openclaw-tools.web-runtime.test.ts index 3f376eedb6c..00b979ffa7e 100644 --- a/src/agents/openclaw-tools.web-runtime.test.ts +++ b/src/agents/openclaw-tools.web-runtime.test.ts @@ -282,4 +282,50 @@ describe("openclaw tools runtime web metadata wiring", () => { "https://x.com/openclaw/status/1", ]); }); + + it("resolves code_execution SecretRef from the active runtime snapshot", async () => { + const snapshot = await prepareAndActivate({ + config: asConfig({ + tools: { + code_execution: { + apiKey: { source: "env", provider: "default", id: "CODE_EXECUTION_RUNTIME_REF" }, + }, + }, + }), + env: { + CODE_EXECUTION_RUNTIME_REF: "code-execution-runtime-key", + }, + }); + + const mockFetch = vi.fn((_input?: unknown, _init?: unknown) => + Promise.resolve({ + ok: true, + json: () => + Promise.resolve({ + output: [ + { type: "code_interpreter_call" }, + { + type: "message", + content: [{ type: "output_text", text: "runtime code execution ok" }], + }, + ], + }), + } as Response), + ); + global.fetch = withFetchPreconnect(mockFetch); + + const codeExecution = findTool("code_execution", snapshot.config); + const result = await codeExecution.execute("call-runtime-code-execution", { + task: "Add 20 + 22", + }); + + expect(mockFetch).toHaveBeenCalled(); + expect(String(mockFetch.mock.calls[0]?.[0])).toContain("api.x.ai/v1/responses"); + const request = mockFetch.mock.calls[0]?.[1] as RequestInit | undefined; + const body = JSON.parse(typeof request?.body === "string" ? request.body : "{}") as { + tools?: Array>; + }; + expect(body.tools).toEqual([{ type: "code_interpreter" }]); + expect((result.details as { usedCodeExecution?: boolean }).usedCodeExecution).toBe(true); + }); }); diff --git a/src/agents/system-prompt.ts b/src/agents/system-prompt.ts index 00fa5fcd0b0..b6f87c4fdda 100644 --- a/src/agents/system-prompt.ts +++ b/src/agents/system-prompt.ts @@ -233,6 +233,8 @@ export function buildAgentSystemPrompt(params: { ls: "List directory contents", exec: "Run shell commands (pty available for TTY-required CLIs)", process: "Manage background exec sessions", + code_execution: + "Run sandboxed remote Python analysis with xAI (no local shell or filesystem access)", web_search: "Search the web", x_search: "Search X (formerly Twitter) posts with xAI, including targeted post or thread lookups; for per-post stats use the exact post URL or status ID when possible", @@ -270,6 +272,7 @@ export function buildAgentSystemPrompt(params: { "ls", "exec", "process", + "code_execution", "web_search", "web_fetch", "browser", diff --git a/src/agents/tool-catalog.test.ts b/src/agents/tool-catalog.test.ts index d328782b0d4..c7f6367e8ff 100644 --- a/src/agents/tool-catalog.test.ts +++ b/src/agents/tool-catalog.test.ts @@ -2,9 +2,10 @@ import { describe, expect, it } from "vitest"; import { resolveCoreToolProfilePolicy } from "./tool-catalog.js"; describe("tool-catalog", () => { - it("includes web_search, x_search, and web_fetch in the coding profile policy", () => { + it("includes code_execution, web_search, x_search, and web_fetch in the coding profile policy", () => { const policy = resolveCoreToolProfilePolicy("coding"); expect(policy).toBeDefined(); + expect(policy!.allow).toContain("code_execution"); expect(policy!.allow).toContain("web_search"); expect(policy!.allow).toContain("x_search"); expect(policy!.allow).toContain("web_fetch"); diff --git a/src/agents/tool-catalog.ts b/src/agents/tool-catalog.ts index 9a11d9a60ed..6587cf8e25b 100644 --- a/src/agents/tool-catalog.ts +++ b/src/agents/tool-catalog.ts @@ -81,6 +81,14 @@ const CORE_TOOL_DEFINITIONS: CoreToolDefinition[] = [ sectionId: "runtime", profiles: ["coding"], }, + { + id: "code_execution", + label: "code_execution", + description: "Run sandboxed remote analysis with xAI", + sectionId: "runtime", + profiles: ["coding"], + includeInOpenClawGroup: true, + }, { id: "web_search", label: "web_search", diff --git a/src/agents/tool-display-overrides.json b/src/agents/tool-display-overrides.json index 590485404ff..98c09c8aba4 100644 --- a/src/agents/tool-display-overrides.json +++ b/src/agents/tool-display-overrides.json @@ -92,6 +92,11 @@ "title": "Web Fetch", "detailKeys": ["url", "extractMode", "maxChars"] }, + "code_execution": { + "emoji": "🧮", + "title": "Code Execution", + "detailKeys": ["task"] + }, "message": { "emoji": "✉️", "title": "Message", diff --git a/src/agents/tools/code-execution.test.ts b/src/agents/tools/code-execution.test.ts new file mode 100644 index 00000000000..f54c72794ce --- /dev/null +++ b/src/agents/tools/code-execution.test.ts @@ -0,0 +1,148 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { withFetchPreconnect } from "../../test-utils/fetch-mock.js"; +import { createCodeExecutionTool } from "./code-execution.js"; + +function installCodeExecutionFetch(payload?: Record) { + const mockFetch = vi.fn((_input?: unknown, _init?: unknown) => + Promise.resolve({ + ok: true, + json: () => + Promise.resolve( + payload ?? { + output: [ + { type: "code_interpreter_call" }, + { + type: "message", + content: [ + { + type: "output_text", + text: "The moving average is 42.", + }, + ], + }, + ], + }, + ), + } as Response), + ); + global.fetch = withFetchPreconnect(mockFetch); + return mockFetch; +} + +function parseFirstRequestBody(mockFetch: ReturnType) { + const request = mockFetch.mock.calls[0]?.[1] as RequestInit | undefined; + const requestBody = request?.body; + return JSON.parse(typeof requestBody === "string" ? requestBody : "{}") as Record< + string, + unknown + >; +} + +afterEach(() => { + vi.restoreAllMocks(); +}); + +describe("code_execution tool", () => { + it("enables code_execution when the xAI plugin web search key is configured", () => { + const tool = createCodeExecutionTool({ + config: { + plugins: { + entries: { + xai: { + config: { + webSearch: { + apiKey: "xai-plugin-key", // pragma: allowlist secret + }, + }, + }, + }, + }, + }, + }); + + expect(tool?.name).toBe("code_execution"); + }); + + it("uses the xAI Responses code_interpreter tool", async () => { + const mockFetch = installCodeExecutionFetch(); + const tool = createCodeExecutionTool({ + config: { + tools: { + code_execution: { + apiKey: "xai-config-test", // pragma: allowlist secret + model: "grok-4-1-fast", + maxTurns: 2, + }, + }, + }, + }); + + const result = await tool?.execute?.("code-exec:1", { + task: "Calculate the average of 40, 42, and 44.", + }); + + expect(mockFetch).toHaveBeenCalled(); + expect(String(mockFetch.mock.calls[0]?.[0])).toContain("api.x.ai/v1/responses"); + const body = parseFirstRequestBody(mockFetch); + expect(body.model).toBe("grok-4-1-fast"); + expect(body.max_turns).toBe(2); + expect(body.tools).toEqual([{ type: "code_interpreter" }]); + expect( + (result?.details as { usedCodeExecution?: boolean } | undefined)?.usedCodeExecution, + ).toBe(true); + }); + + it("reuses the xAI plugin web search key for code_execution requests", async () => { + const mockFetch = installCodeExecutionFetch(); + const tool = createCodeExecutionTool({ + config: { + plugins: { + entries: { + xai: { + config: { + webSearch: { + apiKey: "xai-plugin-key", // pragma: allowlist secret + }, + }, + }, + }, + }, + }, + }); + + await tool?.execute?.("code-exec:plugin-key", { + task: "Sum 1 + 2 + 3.", + }); + + const request = mockFetch.mock.calls[0]?.[1] as RequestInit | undefined; + expect((request?.headers as Record | undefined)?.Authorization).toBe( + "Bearer xai-plugin-key", + ); + }); + + it("reuses the legacy grok web search key for code_execution requests", async () => { + const mockFetch = installCodeExecutionFetch(); + const tool = createCodeExecutionTool({ + config: { + tools: { + web: { + search: { + grok: { + apiKey: "xai-legacy-key", // pragma: allowlist secret + }, + }, + }, + }, + }, + }); + + await tool?.execute?.("code-exec:legacy-key", { + task: "Multiply 6 * 7.", + }); + + const request = mockFetch.mock.calls[0]?.[1] as RequestInit | undefined; + expect((request?.headers as Record | undefined)?.Authorization).toBe( + "Bearer xai-legacy-key", + ); + }); +}); diff --git a/src/agents/tools/code-execution.ts b/src/agents/tools/code-execution.ts new file mode 100644 index 00000000000..a41c4886cce --- /dev/null +++ b/src/agents/tools/code-execution.ts @@ -0,0 +1,133 @@ +import { Type } from "@sinclair/typebox"; +import { + buildXaiCodeExecutionPayload, + requestXaiCodeExecution, + resolveXaiCodeExecutionMaxTurns, + resolveXaiCodeExecutionModel, +} from "../../../extensions/xai/src/code-execution-shared.js"; +import type { OpenClawConfig } from "../../config/config.js"; +import { resolveProviderWebSearchPluginConfig } from "../../plugin-sdk/provider-web-search.js"; +import { jsonResult, readStringParam } from "./common.js"; +import { readConfiguredSecretString, readProviderEnvValue } from "./web-search-provider-common.js"; + +type CodeExecutionConfig = + NonNullable extends infer Tools + ? Tools extends { code_execution?: infer CodeExecution } + ? CodeExecution + : undefined + : undefined; + +function readLegacyGrokApiKey(cfg?: OpenClawConfig): string | undefined { + const search = cfg?.tools?.web?.search; + if (!search || typeof search !== "object") { + return undefined; + } + const grok = (search as Record).grok; + return readConfiguredSecretString( + grok && typeof grok === "object" ? (grok as Record).apiKey : undefined, + "tools.web.search.grok.apiKey", + ); +} + +function readPluginXaiWebSearchApiKey(cfg?: OpenClawConfig): string | undefined { + return readConfiguredSecretString( + resolveProviderWebSearchPluginConfig(cfg as Record | undefined, "xai")?.apiKey, + "plugins.entries.xai.config.webSearch.apiKey", + ); +} + +function resolveFallbackXaiApiKey(cfg?: OpenClawConfig): string | undefined { + return readPluginXaiWebSearchApiKey(cfg) ?? readLegacyGrokApiKey(cfg); +} + +function resolveCodeExecutionConfig(cfg?: OpenClawConfig): CodeExecutionConfig | undefined { + const codeExecution = cfg?.tools?.code_execution; + if (!codeExecution || typeof codeExecution !== "object") { + return undefined; + } + return codeExecution; +} + +function resolveCodeExecutionEnabled(params: { + cfg?: OpenClawConfig; + config?: CodeExecutionConfig; +}): boolean { + if (params.config?.enabled === false) { + return false; + } + const configuredApiKey = readConfiguredSecretString( + params.config?.apiKey, + "tools.code_execution.apiKey", + ); + return Boolean( + configuredApiKey || + resolveFallbackXaiApiKey(params.cfg) || + readProviderEnvValue(["XAI_API_KEY"]), + ); +} + +function resolveCodeExecutionApiKey( + config?: CodeExecutionConfig, + cfg?: OpenClawConfig, +): string | undefined { + return ( + readConfiguredSecretString(config?.apiKey, "tools.code_execution.apiKey") ?? + resolveFallbackXaiApiKey(cfg) ?? + readProviderEnvValue(["XAI_API_KEY"]) + ); +} + +export function createCodeExecutionTool(options?: { config?: OpenClawConfig }) { + const codeExecutionConfig = resolveCodeExecutionConfig(options?.config); + if (!resolveCodeExecutionEnabled({ cfg: options?.config, config: codeExecutionConfig })) { + return null; + } + + return { + label: "Code Execution", + name: "code_execution", + description: + "Run sandboxed Python analysis with xAI. Use for calculations, tabulation, summaries, and chart-style analysis without local machine access.", + parameters: Type.Object({ + task: Type.String({ + description: + "The full analysis task for xAI's remote Python sandbox. Include any data to analyze directly in the task.", + }), + }), + execute: async (_toolCallId: string, args: Record) => { + const apiKey = resolveCodeExecutionApiKey(codeExecutionConfig, options?.config); + if (!apiKey) { + return jsonResult({ + error: "missing_xai_api_key", + message: + "code_execution needs an xAI API key. Set XAI_API_KEY in the Gateway environment, or configure tools.code_execution.apiKey or plugins.entries.xai.config.webSearch.apiKey.", + docs: "https://docs.openclaw.ai/tools/code-execution", + }); + } + + const task = readStringParam(args, "task", { required: true }); + const codeExecutionConfigRecord = codeExecutionConfig as Record | undefined; + const model = resolveXaiCodeExecutionModel(codeExecutionConfigRecord); + const maxTurns = resolveXaiCodeExecutionMaxTurns(codeExecutionConfigRecord); + const startedAt = Date.now(); + const result = await requestXaiCodeExecution({ + apiKey, + model, + timeoutSeconds: codeExecutionConfig?.timeoutSeconds ?? 30, + maxTurns, + task, + }); + return jsonResult( + buildXaiCodeExecutionPayload({ + task, + model, + tookMs: Date.now() - startedAt, + content: result.content, + citations: result.citations, + usedCodeExecution: result.usedCodeExecution, + outputTypes: result.outputTypes, + }), + ); + }, + }; +} diff --git a/src/agents/tools/web-tools.ts b/src/agents/tools/web-tools.ts index 432c84d4771..23dac25a7d1 100644 --- a/src/agents/tools/web-tools.ts +++ b/src/agents/tools/web-tools.ts @@ -1,3 +1,4 @@ export { createWebFetchTool, extractReadableContent, fetchFirecrawlContent } from "./web-fetch.js"; export { createWebSearchTool } from "./web-search.js"; export { createXSearchTool } from "./x-search.js"; +export { createCodeExecutionTool } from "./code-execution.js"; diff --git a/src/cli/command-secret-targets.test.ts b/src/cli/command-secret-targets.test.ts index 0baa8c10870..1d6a0c79189 100644 --- a/src/cli/command-secret-targets.test.ts +++ b/src/cli/command-secret-targets.test.ts @@ -10,6 +10,7 @@ describe("command secret target ids", () => { const ids = getAgentRuntimeCommandSecretTargetIds(); expect(ids.has("agents.defaults.memorySearch.remote.apiKey")).toBe(true); expect(ids.has("agents.list[].memorySearch.remote.apiKey")).toBe(true); + expect(ids.has("tools.code_execution.apiKey")).toBe(true); expect(ids.has("tools.web.fetch.firecrawl.apiKey")).toBe(true); expect(ids.has("tools.web.x_search.apiKey")).toBe(true); }); diff --git a/src/cli/command-secret-targets.ts b/src/cli/command-secret-targets.ts index 0f1ca32ec06..34fd11d1cce 100644 --- a/src/cli/command-secret-targets.ts +++ b/src/cli/command-secret-targets.ts @@ -23,6 +23,7 @@ const COMMAND_SECRET_TARGETS = { "agents.list[].memorySearch.remote.", "skills.entries.", "messages.tts.", + "tools.code_execution", "tools.web.search", "tools.web.fetch.firecrawl.", "tools.web.x_search", diff --git a/src/config/schema.base.generated.ts b/src/config/schema.base.generated.ts index 8e454431031..606c3dd0c6e 100644 --- a/src/config/schema.base.generated.ts +++ b/src/config/schema.base.generated.ts @@ -7164,6 +7164,94 @@ export const GENERATED_BASE_CONFIG_SCHEMA = { }, additionalProperties: false, }, + code_execution: { + type: "object", + properties: { + enabled: { + type: "boolean", + }, + apiKey: { + anyOf: [ + { + type: "string", + }, + { + oneOf: [ + { + type: "object", + properties: { + source: { + type: "string", + const: "env", + }, + provider: { + type: "string", + pattern: "^[a-z][a-z0-9_-]{0,63}$", + }, + id: { + type: "string", + pattern: "^[A-Z][A-Z0-9_]{0,127}$", + }, + }, + required: ["source", "provider", "id"], + additionalProperties: false, + }, + { + type: "object", + properties: { + source: { + type: "string", + const: "file", + }, + provider: { + type: "string", + pattern: "^[a-z][a-z0-9_-]{0,63}$", + }, + id: { + type: "string", + }, + }, + required: ["source", "provider", "id"], + additionalProperties: false, + }, + { + type: "object", + properties: { + source: { + type: "string", + const: "exec", + }, + provider: { + type: "string", + pattern: "^[a-z][a-z0-9_-]{0,63}$", + }, + id: { + type: "string", + }, + }, + required: ["source", "provider", "id"], + additionalProperties: false, + }, + ], + }, + ], + }, + model: { + type: "string", + }, + maxTurns: { + type: "integer", + minimum: -9007199254740991, + maximum: 9007199254740991, + }, + timeoutSeconds: { + type: "integer", + exclusiveMinimum: 0, + maximum: 9007199254740991, + }, + }, + additionalProperties: false, + }, exec: { type: "object", properties: { @@ -12975,6 +13063,32 @@ export const GENERATED_BASE_CONFIG_SCHEMA = { help: "Cache TTL in minutes for x_search results.", tags: ["performance", "storage", "tools"], }, + "tools.code_execution.enabled": { + label: "Enable Code Execution Tool", + help: "Enable the code_execution tool (requires XAI_API_KEY or tools.code_execution.apiKey).", + tags: ["tools"], + }, + "tools.code_execution.apiKey": { + label: "xAI API Key", + help: "xAI API key for remote code execution (fallback: XAI_API_KEY env var).", + tags: ["security", "auth", "tools"], + sensitive: true, + }, + "tools.code_execution.model": { + label: "Code Execution Model", + help: 'Model to use for remote code execution (default: "grok-4-1-fast").', + tags: ["models", "tools"], + }, + "tools.code_execution.maxTurns": { + label: "Code Execution Max Turns", + help: "Optional max internal tool turns xAI may use per code_execution request. Omit to let xAI choose.", + tags: ["performance", "tools"], + }, + "tools.code_execution.timeoutSeconds": { + label: "Code Execution Timeout (sec)", + help: "Timeout in seconds for code_execution requests.", + tags: ["performance", "tools"], + }, "gateway.controlUi.basePath": { label: "Control UI Base Path", help: "Optional URL prefix where the Control UI is served (e.g. /openclaw).", diff --git a/src/config/schema.help.ts b/src/config/schema.help.ts index 1363900c595..72ebdc065ca 100644 --- a/src/config/schema.help.ts +++ b/src/config/schema.help.ts @@ -724,6 +724,15 @@ export const FIELD_HELP: Record = { "Optional max internal search/tool turns xAI may use per x_search request. Omit to let xAI choose.", "tools.web.x_search.timeoutSeconds": "Timeout in seconds for x_search requests.", "tools.web.x_search.cacheTtlMinutes": "Cache TTL in minutes for x_search results.", + "tools.code_execution.enabled": + "Enable the code_execution tool (requires XAI_API_KEY or tools.code_execution.apiKey).", + "tools.code_execution.apiKey": + "xAI API key for remote code execution (fallback: XAI_API_KEY env var).", + "tools.code_execution.model": + 'Model to use for remote code execution (default: "grok-4-1-fast").', + "tools.code_execution.maxTurns": + "Optional max internal tool turns xAI may use per code_execution request. Omit to let xAI choose.", + "tools.code_execution.timeoutSeconds": "Timeout in seconds for code_execution requests.", models: "Model catalog root for provider definitions, merge/replace behavior, and optional Bedrock discovery integration. Keep provider definitions explicit and validated before relying on production failover paths.", "models.mode": diff --git a/src/config/schema.labels.ts b/src/config/schema.labels.ts index 6fd71eb8df7..74840e7e948 100644 --- a/src/config/schema.labels.ts +++ b/src/config/schema.labels.ts @@ -254,6 +254,11 @@ export const FIELD_LABELS: Record = { "tools.web.x_search.maxTurns": "X Search Max Turns", "tools.web.x_search.timeoutSeconds": "X Search Timeout (sec)", "tools.web.x_search.cacheTtlMinutes": "X Search Cache TTL (min)", + "tools.code_execution.enabled": "Enable Code Execution Tool", + "tools.code_execution.apiKey": "xAI API Key", // pragma: allowlist secret + "tools.code_execution.model": "Code Execution Model", + "tools.code_execution.maxTurns": "Code Execution Max Turns", + "tools.code_execution.timeoutSeconds": "Code Execution Timeout (sec)", "gateway.controlUi.basePath": "Control UI Base Path", "gateway.controlUi.root": "Control UI Assets Root", "gateway.controlUi.allowedOrigins": "Control UI Allowed Origins", diff --git a/src/config/types.tools.ts b/src/config/types.tools.ts index 3325ebd59af..a12ba791d54 100644 --- a/src/config/types.tools.ts +++ b/src/config/types.tools.ts @@ -476,6 +476,19 @@ type XSearchToolConfig = { cacheTtlMinutes?: number; }; +type CodeExecutionToolConfig = { + /** Enable remote xAI code execution (default: true when an xAI API key is available). */ + enabled?: boolean; + /** API key for xAI (defaults to XAI_API_KEY env var). Supports SecretRef. */ + apiKey?: SecretInput; + /** Model id to use for remote code execution. */ + model?: string; + /** Optional max internal tool turns for xAI to use. */ + maxTurns?: number; + /** Timeout in seconds for code execution requests. */ + timeoutSeconds?: number; +}; + export type ToolsConfig = { /** Base tool profile applied before allow/deny lists. */ profile?: ToolProfileId; @@ -485,6 +498,8 @@ export type ToolsConfig = { deny?: string[]; /** Optional tool policy overrides keyed by provider id or "provider/model". */ byProvider?: Record; + /** Remote xAI sandboxed code execution. */ + code_execution?: CodeExecutionToolConfig; web?: { search?: { /** Enable web search tool (default: true when API key is present). */ diff --git a/src/config/zod-schema.agent-runtime.ts b/src/config/zod-schema.agent-runtime.ts index fa231127705..088d443e49c 100644 --- a/src/config/zod-schema.agent-runtime.ts +++ b/src/config/zod-schema.agent-runtime.ts @@ -361,6 +361,17 @@ export const ToolsWebXSearchSchema = z .strict() .optional(); +export const ToolCodeExecutionSchema = z + .object({ + enabled: z.boolean().optional(), + apiKey: SecretInputSchema.optional().register(sensitive), + model: z.string().optional(), + maxTurns: z.number().int().optional(), + timeoutSeconds: z.number().int().positive().optional(), + }) + .strict() + .optional(); + export const ToolsWebSchema = z .object({ search: ToolsWebSearchSchema, @@ -854,6 +865,7 @@ export const ToolsSchema = z }) .strict() .optional(), + code_execution: ToolCodeExecutionSchema, exec: ToolExecSchema, fs: ToolFsSchema, subagents: z diff --git a/src/secrets/runtime-config-collectors-core.ts b/src/secrets/runtime-config-collectors-core.ts index ef571b3f54f..9df7138130f 100644 --- a/src/secrets/runtime-config-collectors-core.ts +++ b/src/secrets/runtime-config-collectors-core.ts @@ -292,6 +292,33 @@ function collectMessagesTtsAssignments(params: { }); } +function collectCodeExecutionAssignments(params: { + config: OpenClawConfig; + defaults: SecretDefaults | undefined; + context: ResolverContext; +}): void { + const tools = params.config.tools as Record | undefined; + if (!isRecord(tools)) { + return; + } + const codeExecution = isRecord(tools.code_execution) ? tools.code_execution : undefined; + if (!codeExecution) { + return; + } + collectSecretInputAssignment({ + value: codeExecution.apiKey, + path: "tools.code_execution.apiKey", + expected: "string", + defaults: params.defaults, + context: params.context, + active: codeExecution.enabled !== false, + inactiveReason: "tools.code_execution is disabled.", + apply: (value) => { + codeExecution.apiKey = value; + }, + }); +} + function collectCronAssignments(params: { config: OpenClawConfig; defaults: SecretDefaults | undefined; @@ -425,5 +452,6 @@ export function collectCoreConfigAssignments(params: { collectGatewayAssignments(params); collectSandboxSshAssignments(params); collectMessagesTtsAssignments(params); + collectCodeExecutionAssignments(params); collectCronAssignments(params); } diff --git a/src/secrets/runtime.coverage.test.ts b/src/secrets/runtime.coverage.test.ts index 3cbca0410c0..4f4e9f2b83f 100644 --- a/src/secrets/runtime.coverage.test.ts +++ b/src/secrets/runtime.coverage.test.ts @@ -208,6 +208,9 @@ function buildConfigForOpenClawTarget(entry: SecretRegistryEntry, envId: string) if (entry.id === "plugins.entries.tavily.config.webSearch.apiKey") { setPathCreateStrict(config, ["tools", "web", "search", "provider"], "tavily"); } + if (entry.id === "tools.code_execution.apiKey") { + setPathCreateStrict(config, ["tools", "code_execution", "enabled"], true); + } if (entry.id === "tools.web.x_search.apiKey") { setPathCreateStrict(config, ["tools", "web", "x_search", "enabled"], true); } diff --git a/src/secrets/target-registry-data.ts b/src/secrets/target-registry-data.ts index 1a931c10ef5..711ab4dc67d 100644 --- a/src/secrets/target-registry-data.ts +++ b/src/secrets/target-registry-data.ts @@ -703,6 +703,17 @@ const SECRET_TARGET_REGISTRY: SecretTargetRegistryEntry[] = [ includeInConfigure: true, includeInAudit: true, }, + { + id: "tools.code_execution.apiKey", + targetType: "tools.code_execution.apiKey", + configFile: "openclaw.json", + pathPattern: "tools.code_execution.apiKey", + secretShape: SECRET_INPUT_SHAPE, + expectedResolvedValue: "string", + includeInPlan: true, + includeInConfigure: true, + includeInAudit: true, + }, { id: "tools.web.fetch.firecrawl.apiKey", targetType: "tools.web.fetch.firecrawl.apiKey",