mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-20 13:41:30 +00:00
refactor(xai): move code_execution into plugin
This commit is contained in:
committed by
Peter Steinberger
parent
1617e0218f
commit
b7ab0ddb55
@@ -21600,7 +21600,10 @@
|
||||
{
|
||||
"path": "channels.matrix.accessToken",
|
||||
"kind": "channel",
|
||||
"type": "string",
|
||||
"type": [
|
||||
"object",
|
||||
"string"
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": true,
|
||||
@@ -21611,6 +21614,36 @@
|
||||
"network",
|
||||
"security"
|
||||
],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "channels.matrix.accessToken.id",
|
||||
"kind": "channel",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "channels.matrix.accessToken.provider",
|
||||
"kind": "channel",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "channels.matrix.accessToken.source",
|
||||
"kind": "channel",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@@ -23867,6 +23900,16 @@
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "channels.msteams.blockStreaming",
|
||||
"kind": "channel",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "channels.msteams.blockStreamingCoalesce",
|
||||
"kind": "channel",
|
||||
@@ -44771,6 +44814,26 @@
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "models.providers.*.models.*.compat.unsupportedToolSchemaKeywords",
|
||||
"kind": "core",
|
||||
"type": "array",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "models.providers.*.models.*.compat.unsupportedToolSchemaKeywords.*",
|
||||
"kind": "core",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "models.providers.*.models.*.contextWindow",
|
||||
"kind": "core",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5574}
|
||||
{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5580}
|
||||
{"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}
|
||||
@@ -1921,7 +1921,10 @@
|
||||
{"recordType":"path","path":"channels.line.tokenFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Matrix","help":"open protocol; install the plugin to enable.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.matrix.accessToken","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["access","auth","channels","network","security"],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.accessToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["access","auth","channels","network","security"],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.matrix.accessToken.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.accessToken.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.accessToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.matrix.accounts.*","kind":"channel","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.ackReaction","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -2127,6 +2130,7 @@
|
||||
{"recordType":"path","path":"channels.msteams.appPassword.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.msteams.appPassword.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.msteams.appPassword.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.msteams.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.msteams.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.msteams.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.msteams.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -3950,6 +3954,8 @@
|
||||
{"recordType":"path","path":"models.providers.*.models.*.compat.thinkingFormat","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.compat.toolCallArgumentsEncoding","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.compat.toolSchemaProfile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.compat.unsupportedToolSchemaKeywords","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.compat.unsupportedToolSchemaKeywords.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.contextWindow","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.cost","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.cost.cacheRead","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
|
||||
@@ -32,6 +32,7 @@ OpenClaw now uses the xAI Responses API as the bundled xAI transport. The same
|
||||
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.
|
||||
`code_execution` tuning lives under `plugins.entries.xai.config.codeExecution`.
|
||||
|
||||
## Current bundled model catalog
|
||||
|
||||
@@ -63,5 +64,6 @@ openclaw config set tools.web.search.provider grok
|
||||
|
||||
- OpenClaw applies xAI-specific tool-schema and tool-call compatibility fixes automatically on the shared runner path.
|
||||
- `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.
|
||||
- `x_search` and `code_execution` are owned by the bundled xAI plugin rather than hardcoded into the core model runtime.
|
||||
- `code_execution` is remote xAI sandbox execution, not local [`exec`](/tools/exec).
|
||||
- For the broader provider overview, see [Model providers](/providers/index).
|
||||
|
||||
@@ -30,7 +30,6 @@ devices. Use [`exec`](/tools/exec) for that.
|
||||
|
||||
You need an xAI API key. Any of these work:
|
||||
|
||||
- `tools.code_execution.apiKey`
|
||||
- `XAI_API_KEY`
|
||||
- `plugins.entries.xai.config.webSearch.apiKey`
|
||||
|
||||
@@ -38,13 +37,21 @@ Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
tools: {
|
||||
code_execution: {
|
||||
enabled: true,
|
||||
apiKey: "xai-...",
|
||||
model: "grok-4-1-fast",
|
||||
maxTurns: 2,
|
||||
timeoutSeconds: 30,
|
||||
plugins: {
|
||||
entries: {
|
||||
xai: {
|
||||
config: {
|
||||
webSearch: {
|
||||
apiKey: "xai-...",
|
||||
},
|
||||
codeExecution: {
|
||||
enabled: true,
|
||||
model: "grok-4-1-fast",
|
||||
maxTurns: 2,
|
||||
timeoutSeconds: 30,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ 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) |
|
||||
| `code_execution` | Run sandboxed remote Python analysis with xAI | [Code Execution](/tools/code-execution) |
|
||||
| `code_execution` | Run sandboxed remote Python analysis | [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 | |
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { withFetchPreconnect } from "../../test-utils/fetch-mock.js";
|
||||
import { withFetchPreconnect } from "../../test/helpers/extensions/fetch-mock.js";
|
||||
import { createCodeExecutionTool } from "./code-execution.js";
|
||||
|
||||
function installCodeExecutionFetch(payload?: Record<string, unknown>) {
|
||||
@@ -16,11 +16,13 @@ function installCodeExecutionFetch(payload?: Record<string, unknown>) {
|
||||
content: [
|
||||
{
|
||||
type: "output_text",
|
||||
text: "The moving average is 42.",
|
||||
text: "Mean: 42",
|
||||
annotations: [{ type: "url_citation", url: "https://example.com/data.csv" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
citations: ["https://example.com/data.csv"],
|
||||
},
|
||||
),
|
||||
} as Response),
|
||||
@@ -42,7 +44,7 @@ afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("code_execution tool", () => {
|
||||
describe("xai code_execution tool", () => {
|
||||
it("enables code_execution when the xAI plugin web search key is configured", () => {
|
||||
const tool = createCodeExecutionTool({
|
||||
config: {
|
||||
@@ -67,18 +69,27 @@ describe("code_execution tool", () => {
|
||||
const mockFetch = installCodeExecutionFetch();
|
||||
const tool = createCodeExecutionTool({
|
||||
config: {
|
||||
tools: {
|
||||
code_execution: {
|
||||
apiKey: "xai-config-test", // pragma: allowlist secret
|
||||
model: "grok-4-1-fast",
|
||||
maxTurns: 2,
|
||||
plugins: {
|
||||
entries: {
|
||||
xai: {
|
||||
config: {
|
||||
webSearch: {
|
||||
apiKey: "xai-config-test", // pragma: allowlist secret
|
||||
},
|
||||
codeExecution: {
|
||||
model: "grok-4-1-fast",
|
||||
maxTurns: 2,
|
||||
timeoutSeconds: 45,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await tool?.execute?.("code-exec:1", {
|
||||
task: "Calculate the average of 40, 42, and 44.",
|
||||
const result = await tool?.execute?.("code-execution:1", {
|
||||
task: "Calculate the mean of [40, 42, 44]",
|
||||
});
|
||||
|
||||
expect(mockFetch).toHaveBeenCalled();
|
||||
@@ -110,8 +121,8 @@ describe("code_execution tool", () => {
|
||||
},
|
||||
});
|
||||
|
||||
await tool?.execute?.("code-exec:plugin-key", {
|
||||
task: "Sum 1 + 2 + 3.",
|
||||
await tool?.execute?.("code-execution:plugin-key", {
|
||||
task: "Compute the standard deviation of [1, 2, 3]",
|
||||
});
|
||||
|
||||
const request = mockFetch.mock.calls[0]?.[1] as RequestInit | undefined;
|
||||
@@ -136,8 +147,8 @@ describe("code_execution tool", () => {
|
||||
},
|
||||
});
|
||||
|
||||
await tool?.execute?.("code-exec:legacy-key", {
|
||||
task: "Multiply 6 * 7.",
|
||||
await tool?.execute?.("code-execution:legacy-key", {
|
||||
task: "Count rows in a two-column table",
|
||||
});
|
||||
|
||||
const request = mockFetch.mock.calls[0]?.[1] as RequestInit | undefined;
|
||||
@@ -1,21 +1,33 @@
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { getRuntimeConfigSnapshot } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import {
|
||||
jsonResult,
|
||||
readConfiguredSecretString,
|
||||
readProviderEnvValue,
|
||||
readStringParam,
|
||||
resolveProviderWebSearchPluginConfig,
|
||||
} from "openclaw/plugin-sdk/provider-web-search";
|
||||
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";
|
||||
} from "./src/code-execution-shared.js";
|
||||
|
||||
type CodeExecutionConfig =
|
||||
NonNullable<OpenClawConfig["tools"]> extends infer Tools
|
||||
? Tools extends { code_execution?: infer CodeExecution }
|
||||
? CodeExecution
|
||||
: undefined
|
||||
: undefined;
|
||||
type XaiPluginConfig = NonNullable<
|
||||
NonNullable<OpenClawConfig["plugins"]>["entries"]
|
||||
>["xai"] extends {
|
||||
config?: infer Config;
|
||||
}
|
||||
? Config
|
||||
: undefined;
|
||||
|
||||
type CodeExecutionConfig = XaiPluginConfig extends infer Config
|
||||
? Config extends { codeExecution?: infer CodeExecution }
|
||||
? CodeExecution
|
||||
: undefined
|
||||
: undefined;
|
||||
|
||||
function readLegacyGrokApiKey(cfg?: OpenClawConfig): string | undefined {
|
||||
const search = cfg?.tools?.web?.search;
|
||||
@@ -29,57 +41,66 @@ function readLegacyGrokApiKey(cfg?: OpenClawConfig): string | undefined {
|
||||
);
|
||||
}
|
||||
|
||||
function readPluginXaiWebSearchApiKey(cfg?: OpenClawConfig): string | undefined {
|
||||
return readConfiguredSecretString(
|
||||
resolveProviderWebSearchPluginConfig(cfg as Record<string, unknown> | 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;
|
||||
function readPluginCodeExecutionConfig(cfg?: OpenClawConfig): CodeExecutionConfig | undefined {
|
||||
const entries = cfg?.plugins?.entries;
|
||||
if (!entries || typeof entries !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
const xaiEntry = (entries as Record<string, unknown>).xai;
|
||||
if (!xaiEntry || typeof xaiEntry !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
const config = (xaiEntry as Record<string, unknown>).config;
|
||||
if (!config || typeof config !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
const codeExecution = (config as Record<string, unknown>).codeExecution;
|
||||
if (!codeExecution || typeof codeExecution !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
return codeExecution;
|
||||
return codeExecution as CodeExecutionConfig;
|
||||
}
|
||||
|
||||
function resolveFallbackXaiApiKey(cfg?: OpenClawConfig): string | undefined {
|
||||
return (
|
||||
readConfiguredSecretString(
|
||||
resolveProviderWebSearchPluginConfig(cfg as Record<string, unknown> | undefined, "xai")
|
||||
?.apiKey,
|
||||
"plugins.entries.xai.config.webSearch.apiKey",
|
||||
) ?? readLegacyGrokApiKey(cfg)
|
||||
);
|
||||
}
|
||||
|
||||
function resolveCodeExecutionEnabled(params: {
|
||||
cfg?: OpenClawConfig;
|
||||
sourceConfig?: OpenClawConfig;
|
||||
runtimeConfig?: 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) ||
|
||||
resolveFallbackXaiApiKey(params.runtimeConfig) ??
|
||||
resolveFallbackXaiApiKey(params.sourceConfig) ??
|
||||
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 })) {
|
||||
export function createCodeExecutionTool(options?: {
|
||||
config?: OpenClawConfig;
|
||||
runtimeConfig?: OpenClawConfig | null;
|
||||
}) {
|
||||
const runtimeConfig = options?.runtimeConfig ?? getRuntimeConfigSnapshot();
|
||||
const codeExecutionConfig =
|
||||
readPluginCodeExecutionConfig(runtimeConfig ?? undefined) ??
|
||||
readPluginCodeExecutionConfig(options?.config);
|
||||
if (
|
||||
!resolveCodeExecutionEnabled({
|
||||
sourceConfig: options?.config,
|
||||
runtimeConfig: runtimeConfig ?? undefined,
|
||||
config: codeExecutionConfig,
|
||||
})
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -95,12 +116,15 @@ export function createCodeExecutionTool(options?: { config?: OpenClawConfig }) {
|
||||
}),
|
||||
}),
|
||||
execute: async (_toolCallId: string, args: Record<string, unknown>) => {
|
||||
const apiKey = resolveCodeExecutionApiKey(codeExecutionConfig, options?.config);
|
||||
const apiKey =
|
||||
resolveFallbackXaiApiKey(runtimeConfig ?? undefined) ??
|
||||
resolveFallbackXaiApiKey(options?.config) ??
|
||||
readProviderEnvValue(["XAI_API_KEY"]);
|
||||
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.",
|
||||
"code_execution needs an xAI API key. Set XAI_API_KEY in the Gateway environment, or configure plugins.entries.xai.config.webSearch.apiKey.",
|
||||
docs: "https://docs.openclaw.ai/tools/code-execution",
|
||||
});
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { createToolStreamWrapper } from "openclaw/plugin-sdk/provider-stream";
|
||||
import { resolveProviderWebSearchPluginConfig } from "openclaw/plugin-sdk/provider-web-search";
|
||||
import { normalizeSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
||||
import { applyXaiModelCompat, normalizeXaiModelId } from "./api.js";
|
||||
import { createCodeExecutionTool } from "./code-execution.js";
|
||||
import { applyXaiConfig, XAI_DEFAULT_MODEL_REF } from "./onboard.js";
|
||||
import { buildXaiProvider } from "./provider-catalog.js";
|
||||
import { isModernXaiModel, resolveXaiForwardCompatModel } from "./provider-models.js";
|
||||
@@ -137,6 +138,14 @@ export default defineSingleProviderPluginEntry({
|
||||
},
|
||||
register(api) {
|
||||
api.registerWebSearchProvider(createXaiWebSearchProvider());
|
||||
api.registerTool(
|
||||
(ctx) =>
|
||||
createCodeExecutionTool({
|
||||
config: ctx.config,
|
||||
runtimeConfig: ctx.runtimeConfig,
|
||||
}),
|
||||
{ name: "code_execution" },
|
||||
);
|
||||
api.registerTool(
|
||||
(ctx) =>
|
||||
createXSearchTool({
|
||||
|
||||
@@ -33,11 +33,27 @@
|
||||
"webSearch.inlineCitations": {
|
||||
"label": "Inline Citations",
|
||||
"help": "Include inline markdown citations in Grok responses."
|
||||
},
|
||||
"codeExecution.enabled": {
|
||||
"label": "Enable Code Execution",
|
||||
"help": "Enable the code_execution tool for remote xAI sandbox analysis."
|
||||
},
|
||||
"codeExecution.model": {
|
||||
"label": "Code Execution Model",
|
||||
"help": "xAI model override for code_execution."
|
||||
},
|
||||
"codeExecution.maxTurns": {
|
||||
"label": "Code Execution Max Turns",
|
||||
"help": "Optional max internal tool turns xAI may use for code_execution."
|
||||
},
|
||||
"codeExecution.timeoutSeconds": {
|
||||
"label": "Code Execution Timeout",
|
||||
"help": "Timeout in seconds for code_execution requests."
|
||||
}
|
||||
},
|
||||
"contracts": {
|
||||
"webSearchProviders": ["grok"],
|
||||
"tools": ["x_search"]
|
||||
"tools": ["code_execution", "x_search"]
|
||||
},
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
@@ -57,6 +73,24 @@
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codeExecution": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"model": {
|
||||
"type": "string"
|
||||
},
|
||||
"maxTurns": {
|
||||
"type": "number"
|
||||
},
|
||||
"timeoutSeconds": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ export function createXSearchTool(options?: {
|
||||
label: "X Search",
|
||||
name: "x_search",
|
||||
description:
|
||||
"Search X (formerly Twitter) using xAI. Returns AI-synthesized answers with citations from real-time X post search.",
|
||||
"Search X (formerly Twitter) using xAI, including targeted post or thread lookups. For per-post stats like reposts, replies, bookmarks, or views, prefer the exact post URL or status ID.",
|
||||
parameters: Type.Object({
|
||||
query: Type.String({ description: "X search query string." }),
|
||||
allowed_x_handles: Type.Optional(
|
||||
|
||||
@@ -30,11 +30,7 @@ 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 {
|
||||
createCodeExecutionTool,
|
||||
createWebFetchTool,
|
||||
createWebSearchTool,
|
||||
} from "./tools/web-tools.js";
|
||||
import { createWebFetchTool, createWebSearchTool } from "./tools/web-tools.js";
|
||||
import { resolveWorkspaceRoot } from "./workspace-dir.js";
|
||||
|
||||
type OpenClawToolsDeps = {
|
||||
@@ -163,9 +159,6 @@ export function createOpenClawTools(
|
||||
sandboxed: options?.sandboxed,
|
||||
runtimeWebSearch: runtimeWebTools?.search,
|
||||
});
|
||||
const codeExecutionTool = createCodeExecutionTool({
|
||||
config: options?.config,
|
||||
});
|
||||
const webFetchTool = createWebFetchTool({
|
||||
config: options?.config,
|
||||
sandboxed: options?.sandboxed,
|
||||
@@ -262,7 +255,6 @@ export function createOpenClawTools(
|
||||
sandboxed: options?.sandboxed,
|
||||
}),
|
||||
...(webSearchTool ? [webSearchTool] : []),
|
||||
...(codeExecutionTool ? [codeExecutionTool] : []),
|
||||
...(webFetchTool ? [webFetchTool] : []),
|
||||
...(imageTool ? [imageTool] : []),
|
||||
...(pdfTool ? [pdfTool] : []),
|
||||
|
||||
@@ -233,11 +233,7 @@ 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",
|
||||
web_fetch: "Fetch and extract readable content from a URL",
|
||||
// Channel docking: add login tools here when a channel needs interactive linking.
|
||||
browser: "Control web browser",
|
||||
|
||||
@@ -84,7 +84,7 @@ const CORE_TOOL_DEFINITIONS: CoreToolDefinition[] = [
|
||||
{
|
||||
id: "code_execution",
|
||||
label: "code_execution",
|
||||
description: "Run sandboxed remote analysis with xAI",
|
||||
description: "Run sandboxed remote analysis",
|
||||
sectionId: "runtime",
|
||||
profiles: ["coding"],
|
||||
includeInOpenClawGroup: true,
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export { createCodeExecutionTool } from "./code-execution.js";
|
||||
export { createWebFetchTool, extractReadableContent, fetchFirecrawlContent } from "./web-fetch.js";
|
||||
export { createWebSearchTool } from "./web-search.js";
|
||||
|
||||
@@ -10,7 +10,6 @@ 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);
|
||||
});
|
||||
|
||||
@@ -23,7 +23,6 @@ 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",
|
||||
|
||||
@@ -7164,94 +7164,6 @@ 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: {
|
||||
@@ -13063,32 +12975,6 @@ 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).",
|
||||
|
||||
@@ -724,15 +724,6 @@ export const FIELD_HELP: Record<string, string> = {
|
||||
"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":
|
||||
|
||||
@@ -254,11 +254,6 @@ export const FIELD_LABELS: Record<string, string> = {
|
||||
"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",
|
||||
|
||||
@@ -476,19 +476,6 @@ 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;
|
||||
@@ -498,8 +485,6 @@ export type ToolsConfig = {
|
||||
deny?: string[];
|
||||
/** Optional tool policy overrides keyed by provider id or "provider/model". */
|
||||
byProvider?: Record<string, ToolPolicyConfig>;
|
||||
/** Remote xAI sandboxed code execution. */
|
||||
code_execution?: CodeExecutionToolConfig;
|
||||
web?: {
|
||||
search?: {
|
||||
/** Enable web search tool (default: true when API key is present). */
|
||||
|
||||
@@ -361,17 +361,6 @@ 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,
|
||||
@@ -865,7 +854,6 @@ export const ToolsSchema = z
|
||||
})
|
||||
.strict()
|
||||
.optional(),
|
||||
code_execution: ToolCodeExecutionSchema,
|
||||
exec: ToolExecSchema,
|
||||
fs: ToolFsSchema,
|
||||
subagents: z
|
||||
|
||||
@@ -18525,6 +18525,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [
|
||||
},
|
||||
publicSurfaceArtifacts: [
|
||||
"api.js",
|
||||
"code-execution.js",
|
||||
"model-definitions.js",
|
||||
"model-id.js",
|
||||
"onboard.js",
|
||||
@@ -18532,6 +18533,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [
|
||||
"provider-models.js",
|
||||
"stream.js",
|
||||
"web-search.js",
|
||||
"x-search.js",
|
||||
],
|
||||
packageName: "@openclaw/xai-plugin",
|
||||
packageVersion: "2026.3.27",
|
||||
@@ -18560,6 +18562,24 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [
|
||||
},
|
||||
},
|
||||
},
|
||||
codeExecution: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
},
|
||||
model: {
|
||||
type: "string",
|
||||
},
|
||||
maxTurns: {
|
||||
type: "number",
|
||||
},
|
||||
timeoutSeconds: {
|
||||
type: "number",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
enabledByDefault: true,
|
||||
@@ -18596,10 +18616,26 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [
|
||||
label: "Inline Citations",
|
||||
help: "Include inline markdown citations in Grok responses.",
|
||||
},
|
||||
"codeExecution.enabled": {
|
||||
label: "Enable Code Execution",
|
||||
help: "Enable the code_execution tool for remote xAI sandbox analysis.",
|
||||
},
|
||||
"codeExecution.model": {
|
||||
label: "Code Execution Model",
|
||||
help: "xAI model override for code_execution.",
|
||||
},
|
||||
"codeExecution.maxTurns": {
|
||||
label: "Code Execution Max Turns",
|
||||
help: "Optional max internal tool turns xAI may use for code_execution.",
|
||||
},
|
||||
"codeExecution.timeoutSeconds": {
|
||||
label: "Code Execution Timeout",
|
||||
help: "Timeout in seconds for code_execution requests.",
|
||||
},
|
||||
},
|
||||
contracts: {
|
||||
webSearchProviders: ["grok"],
|
||||
tools: ["x_search"],
|
||||
tools: ["code_execution", "x_search"],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -292,33 +292,6 @@ function collectMessagesTtsAssignments(params: {
|
||||
});
|
||||
}
|
||||
|
||||
function collectCodeExecutionAssignments(params: {
|
||||
config: OpenClawConfig;
|
||||
defaults: SecretDefaults | undefined;
|
||||
context: ResolverContext;
|
||||
}): void {
|
||||
const tools = params.config.tools as Record<string, unknown> | 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;
|
||||
@@ -452,6 +425,5 @@ export function collectCoreConfigAssignments(params: {
|
||||
collectGatewayAssignments(params);
|
||||
collectSandboxSshAssignments(params);
|
||||
collectMessagesTtsAssignments(params);
|
||||
collectCodeExecutionAssignments(params);
|
||||
collectCronAssignments(params);
|
||||
}
|
||||
|
||||
@@ -208,9 +208,6 @@ 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);
|
||||
}
|
||||
|
||||
@@ -703,17 +703,6 @@ 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",
|
||||
|
||||
Reference in New Issue
Block a user