diff --git a/CHANGELOG.md b/CHANGELOG.md
index bc5d3b02dc6..7e2357e8015 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,7 @@ Docs: https://docs.openclaw.ai
- Plugins/install: add `openclaw plugins install --force` to overwrite existing plugin and hook-pack install targets without using the dangerous-code override flag. (#60544) Thanks @gumadeiras.
- Providers/Anthropic: remove setup-token from new onboarding and auth-command setup paths, keep existing configured legacy token profiles runnable, and steer new Anthropic setup to Claude CLI or API keys.
- Providers/OpenAI Codex: add forward-compat `openai-codex/gpt-5.4-mini` synthesis across provider runtime, model catalog, and model listing so Codex mini works before bundled Pi catalog updates land.
+- Tools/web_search: add a bundled MiniMax Search provider backed by the Coding Plan search API, with region reuse from `MINIMAX_API_HOST` and plugin-owned credential config. (#54648) Thanks @fengmk2.
- Channels/context visibility: add configurable `contextVisibility` per channel (`all`, `allowlist`, `allowlist_quote`) so quoted, threaded, and fetched history context can be filtered by sender allowlists instead of always passing through as received.
- Providers/request overrides: add shared model and media request transport overrides across OpenAI-, Anthropic-, Google-, and compatible provider paths, including headers, auth, proxy, and TLS controls. (#60200)
- Matrix/exec approvals: add Matrix-native exec approval prompts with account-scoped approvers, channel-or-DM delivery, and room-thread aware resolution handling. (#58635) Thanks @gumadeiras.
diff --git a/docs/providers/minimax.md b/docs/providers/minimax.md
index ce0addfda5c..7f03634059a 100644
--- a/docs/providers/minimax.md
+++ b/docs/providers/minimax.md
@@ -10,6 +10,11 @@ title: "MiniMax"
OpenClaw's MiniMax provider defaults to **MiniMax M2.7**.
+MiniMax also provides:
+
+- bundled speech synthesis via T2A v2
+- bundled `web_search` through the MiniMax Coding Plan search API
+
## Model lineup
- `MiniMax-M2.7`: default hosted reasoning model.
@@ -44,8 +49,24 @@ When onboarding or API-key setup writes explicit `models.providers.minimax`
entries, OpenClaw materializes `MiniMax-M2.7` and
`MiniMax-M2.7-highspeed` with `input: ["text", "image"]`.
-The bundled MiniMax provider catalog itself currently advertises those chat
-refs as text-only metadata until explicit provider config is materialized.
+The bundled MiniMax provider catalog also advertises image input on those M2.7
+chat refs, so image-capable routing can use MiniMax without requiring explicit
+provider config first.
+
+## Web search
+
+The MiniMax plugin also registers `web_search` through the MiniMax Coding Plan
+search API.
+
+- Provider id: `minimax`
+- Structured results: titles, URLs, snippets, related queries
+- Preferred env var: `MINIMAX_CODE_PLAN_KEY`
+- Accepted env alias: `MINIMAX_CODING_API_KEY`
+- Compatibility fallback: `MINIMAX_API_KEY` when it already points at a coding-plan token
+- Region reuse: `plugins.entries.minimax.config.webSearch.region`, then `MINIMAX_API_HOST`, then MiniMax provider base URLs
+
+Config lives under `plugins.entries.minimax.config.webSearch.*`.
+See [MiniMax Search](/tools/minimax-search).
## Choose a setup
diff --git a/docs/reference/secretref-credential-surface.md b/docs/reference/secretref-credential-surface.md
index 108e52402a9..6696623a748 100644
--- a/docs/reference/secretref-credential-surface.md
+++ b/docs/reference/secretref-credential-surface.md
@@ -47,6 +47,7 @@ Scope intent:
- `plugins.entries.moonshot.config.webSearch.apiKey`
- `plugins.entries.perplexity.config.webSearch.apiKey`
- `plugins.entries.firecrawl.config.webSearch.apiKey`
+- `plugins.entries.minimax.config.webSearch.apiKey`
- `plugins.entries.tavily.config.webSearch.apiKey`
- `tools.web.search.apiKey`
- `gateway.auth.password`
diff --git a/docs/reference/secretref-user-supplied-credentials-matrix.json b/docs/reference/secretref-user-supplied-credentials-matrix.json
index d8549d644ca..f8b6d27115e 100644
--- a/docs/reference/secretref-user-supplied-credentials-matrix.json
+++ b/docs/reference/secretref-user-supplied-credentials-matrix.json
@@ -540,6 +540,13 @@
"secretShape": "secret_input",
"optIn": true
},
+ {
+ "id": "plugins.entries.minimax.config.webSearch.apiKey",
+ "configFile": "openclaw.json",
+ "path": "plugins.entries.minimax.config.webSearch.apiKey",
+ "secretShape": "secret_input",
+ "optIn": true
+ },
{
"id": "plugins.entries.moonshot.config.webSearch.apiKey",
"configFile": "openclaw.json",
diff --git a/docs/tools/minimax-search.md b/docs/tools/minimax-search.md
new file mode 100644
index 00000000000..e16dd56e5fb
--- /dev/null
+++ b/docs/tools/minimax-search.md
@@ -0,0 +1,95 @@
+---
+summary: "MiniMax Search via the Coding Plan search API"
+read_when:
+ - You want to use MiniMax for web_search
+ - You need a MiniMax Coding Plan key
+ - You want MiniMax CN/global search host guidance
+title: "MiniMax Search"
+---
+
+# MiniMax Search
+
+OpenClaw supports MiniMax as a `web_search` provider through the MiniMax
+Coding Plan search API. It returns structured search results with titles, URLs,
+snippets, and related queries.
+
+## Get a Coding Plan key
+
+
+
+ Create or copy a MiniMax Coding Plan key from
+ [MiniMax Platform](https://platform.minimax.io/user-center/basic-information/interface-key).
+
+
+ Set `MINIMAX_CODE_PLAN_KEY` in the Gateway environment, or configure via:
+
+ ```bash
+ openclaw configure --section web
+ ```
+
+
+
+
+OpenClaw also accepts `MINIMAX_CODING_API_KEY` as an env alias. `MINIMAX_API_KEY`
+is still read as a compatibility fallback when it already points at a coding-plan token.
+
+## Config
+
+```json5
+{
+ plugins: {
+ entries: {
+ minimax: {
+ config: {
+ webSearch: {
+ apiKey: "sk-cp-...", // optional if MINIMAX_CODE_PLAN_KEY is set
+ region: "global", // or "cn"
+ },
+ },
+ },
+ },
+ },
+ tools: {
+ web: {
+ search: {
+ provider: "minimax",
+ },
+ },
+ },
+}
+```
+
+**Environment alternative:** set `MINIMAX_CODE_PLAN_KEY` in the Gateway environment.
+For a gateway install, put it in `~/.openclaw/.env`.
+
+## Region selection
+
+MiniMax Search uses these endpoints:
+
+- Global: `https://api.minimax.io/v1/coding_plan/search`
+- CN: `https://api.minimaxi.com/v1/coding_plan/search`
+
+If `plugins.entries.minimax.config.webSearch.region` is unset, OpenClaw resolves
+the region in this order:
+
+1. `tools.web.search.minimax.region` / plugin-owned `webSearch.region`
+2. `MINIMAX_API_HOST`
+3. `models.providers.minimax.baseUrl`
+4. `models.providers.minimax-portal.baseUrl`
+
+That means CN onboarding or `MINIMAX_API_HOST=https://api.minimaxi.com/...`
+automatically keeps MiniMax Search on the CN host too.
+
+## Supported parameters
+
+MiniMax Search supports:
+
+- `query`
+- `count` (OpenClaw trims the returned result list to the requested count)
+
+Provider-specific filters are not currently supported.
+
+## Related
+
+- [Web Search overview](/tools/web) -- all providers and auto-detection
+- [MiniMax](/providers/minimax) -- model, image, speech, and auth setup
diff --git a/docs/tools/web.md b/docs/tools/web.md
index 3bef2eb2d3f..b1bc2a6c6b9 100644
--- a/docs/tools/web.md
+++ b/docs/tools/web.md
@@ -80,6 +80,9 @@ local while `web_search` and `x_search` can use xAI Responses under the hood.
AI-synthesized answers with citations via Moonshot web search.
+
+ Structured results via the MiniMax Coding Plan search API.
+
Key-free search via your configured Ollama host. Requires `ollama signin`.
@@ -105,6 +108,7 @@ local while `web_search` and `x_search` can use xAI Responses under the hood.
| [Gemini](/tools/gemini-search) | AI-synthesized + citations | -- | `GEMINI_API_KEY` |
| [Grok](/tools/grok-search) | AI-synthesized + citations | -- | `XAI_API_KEY` |
| [Kimi](/tools/kimi-search) | AI-synthesized + citations | -- | `KIMI_API_KEY` / `MOONSHOT_API_KEY` |
+| [MiniMax Search](/tools/minimax-search) | Structured snippets | Region (`global` / `cn`) | `MINIMAX_CODE_PLAN_KEY` / `MINIMAX_CODING_API_KEY` |
| [Ollama Web Search](/tools/ollama-search) | Structured snippets | -- | None by default; `ollama signin` required, can reuse Ollama provider bearer auth |
| [Perplexity](/tools/perplexity-search) | Structured snippets | Country, language, time, domains, content limits | `PERPLEXITY_API_KEY` / `OPENROUTER_API_KEY` |
| [SearXNG](/tools/searxng-search) | Structured snippets | Categories, language | None (self-hosted) |
@@ -158,19 +162,20 @@ first one that is ready:
API-backed providers first:
1. **Brave** -- `BRAVE_API_KEY` or `plugins.entries.brave.config.webSearch.apiKey` (order 10)
-2. **Gemini** -- `GEMINI_API_KEY` or `plugins.entries.google.config.webSearch.apiKey` (order 20)
-3. **Grok** -- `XAI_API_KEY` or `plugins.entries.xai.config.webSearch.apiKey` (order 30)
-4. **Kimi** -- `KIMI_API_KEY` / `MOONSHOT_API_KEY` or `plugins.entries.moonshot.config.webSearch.apiKey` (order 40)
-5. **Perplexity** -- `PERPLEXITY_API_KEY` / `OPENROUTER_API_KEY` or `plugins.entries.perplexity.config.webSearch.apiKey` (order 50)
-6. **Firecrawl** -- `FIRECRAWL_API_KEY` or `plugins.entries.firecrawl.config.webSearch.apiKey` (order 60)
-7. **Exa** -- `EXA_API_KEY` or `plugins.entries.exa.config.webSearch.apiKey` (order 65)
-8. **Tavily** -- `TAVILY_API_KEY` or `plugins.entries.tavily.config.webSearch.apiKey` (order 70)
+2. **MiniMax Search** -- `MINIMAX_CODE_PLAN_KEY` / `MINIMAX_CODING_API_KEY` or `plugins.entries.minimax.config.webSearch.apiKey` (order 15)
+3. **Gemini** -- `GEMINI_API_KEY` or `plugins.entries.google.config.webSearch.apiKey` (order 20)
+4. **Grok** -- `XAI_API_KEY` or `plugins.entries.xai.config.webSearch.apiKey` (order 30)
+5. **Kimi** -- `KIMI_API_KEY` / `MOONSHOT_API_KEY` or `plugins.entries.moonshot.config.webSearch.apiKey` (order 40)
+6. **Perplexity** -- `PERPLEXITY_API_KEY` / `OPENROUTER_API_KEY` or `plugins.entries.perplexity.config.webSearch.apiKey` (order 50)
+7. **Firecrawl** -- `FIRECRAWL_API_KEY` or `plugins.entries.firecrawl.config.webSearch.apiKey` (order 60)
+8. **Exa** -- `EXA_API_KEY` or `plugins.entries.exa.config.webSearch.apiKey` (order 65)
+9. **Tavily** -- `TAVILY_API_KEY` or `plugins.entries.tavily.config.webSearch.apiKey` (order 70)
Key-free fallbacks after that:
-9. **DuckDuckGo** -- key-free HTML fallback with no account or API key (order 100)
-10. **Ollama Web Search** -- key-free fallback via your configured Ollama host; requires Ollama to be reachable and signed in with `ollama signin` and can reuse Ollama provider bearer auth if the host needs it (order 110)
-11. **SearXNG** -- `SEARXNG_BASE_URL` or `plugins.entries.searxng.config.webSearch.baseUrl` (order 200)
+10. **DuckDuckGo** -- key-free HTML fallback with no account or API key (order 100)
+11. **Ollama Web Search** -- key-free fallback via your configured Ollama host; requires Ollama to be reachable and signed in with `ollama signin` and can reuse Ollama provider bearer auth if the host needs it (order 110)
+12. **SearXNG** -- `SEARXNG_BASE_URL` or `plugins.entries.searxng.config.webSearch.baseUrl` (order 200)
If no provider is detected, it falls back to Brave (you will get a missing-key
error prompting you to configure one).
diff --git a/extensions/minimax/index.test.ts b/extensions/minimax/index.test.ts
index 3d4a8881a79..e53ea4fa5ac 100644
--- a/extensions/minimax/index.test.ts
+++ b/extensions/minimax/index.test.ts
@@ -129,4 +129,25 @@ describe("minimax provider hooks", () => {
expect(resolvedApiModelId).toBe("MiniMax-M2.7-highspeed");
expect(resolvedPortalModelId).toBe("MiniMax-M2.7-highspeed");
});
+
+ it("registers the bundled MiniMax web search provider", () => {
+ const webSearchProviders: unknown[] = [];
+
+ minimaxPlugin.register({
+ registerProvider() {},
+ registerMediaUnderstandingProvider() {},
+ registerImageGenerationProvider() {},
+ registerSpeechProvider() {},
+ registerWebSearchProvider(provider: unknown) {
+ webSearchProviders.push(provider);
+ },
+ } as never);
+
+ expect(webSearchProviders).toHaveLength(1);
+ expect(webSearchProviders[0]).toMatchObject({
+ id: "minimax",
+ label: "MiniMax Search",
+ envVars: ["MINIMAX_CODE_PLAN_KEY", "MINIMAX_CODING_API_KEY"],
+ });
+ });
});
diff --git a/extensions/minimax/index.ts b/extensions/minimax/index.ts
index 49d53afa7e5..23dd7c3ebba 100644
--- a/extensions/minimax/index.ts
+++ b/extensions/minimax/index.ts
@@ -27,6 +27,7 @@ import type { MiniMaxRegion } from "./oauth.js";
import { applyMinimaxApiConfig, applyMinimaxApiConfigCn } from "./onboard.js";
import { buildMinimaxPortalProvider, buildMinimaxProvider } from "./provider-catalog.js";
import { buildMinimaxSpeechProvider } from "./speech-provider.js";
+import { createMiniMaxWebSearchProvider } from "./src/minimax-web-search-provider.js";
const API_PROVIDER_ID = "minimax";
const PORTAL_PROVIDER_ID = "minimax-portal";
@@ -237,7 +238,11 @@ export default definePluginEntry({
},
resolveUsageAuth: async (ctx) => {
const apiKey = ctx.resolveApiKeyFromConfigAndStore({
- envDirect: [ctx.env.MINIMAX_CODE_PLAN_KEY, ctx.env.MINIMAX_API_KEY],
+ envDirect: [
+ ctx.env.MINIMAX_CODE_PLAN_KEY,
+ ctx.env.MINIMAX_CODING_API_KEY,
+ ctx.env.MINIMAX_API_KEY,
+ ],
});
return apiKey ? { token: apiKey } : null;
},
@@ -303,5 +308,6 @@ export default definePluginEntry({
api.registerImageGenerationProvider(buildMinimaxImageGenerationProvider());
api.registerImageGenerationProvider(buildMinimaxPortalImageGenerationProvider());
api.registerSpeechProvider(buildMinimaxSpeechProvider());
+ api.registerWebSearchProvider(createMiniMaxWebSearchProvider());
},
});
diff --git a/extensions/minimax/openclaw.plugin.json b/extensions/minimax/openclaw.plugin.json
index 3d6e517b6ce..27570ed2f20 100644
--- a/extensions/minimax/openclaw.plugin.json
+++ b/extensions/minimax/openclaw.plugin.json
@@ -63,11 +63,38 @@
"contracts": {
"speechProviders": ["minimax"],
"mediaUnderstandingProviders": ["minimax", "minimax-portal"],
- "imageGenerationProviders": ["minimax", "minimax-portal"]
+ "imageGenerationProviders": ["minimax", "minimax-portal"],
+ "webSearchProviders": ["minimax"]
+ },
+ "uiHints": {
+ "webSearch.apiKey": {
+ "label": "MiniMax Coding Plan key",
+ "help": "MiniMax Coding Plan key (fallback: MINIMAX_CODE_PLAN_KEY, MINIMAX_CODING_API_KEY, or MINIMAX_API_KEY if it already points at a coding-plan token).",
+ "sensitive": true,
+ "placeholder": "sk-cp-..."
+ },
+ "webSearch.region": {
+ "label": "MiniMax Search Region",
+ "help": "Search endpoint region override. Leave unset to reuse your configured MiniMax host or MINIMAX_API_HOST."
+ }
},
"configSchema": {
"type": "object",
"additionalProperties": false,
- "properties": {}
+ "properties": {
+ "webSearch": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "apiKey": {
+ "type": ["string", "object"]
+ },
+ "region": {
+ "type": "string",
+ "enum": ["global", "cn"]
+ }
+ }
+ }
+ }
}
}
diff --git a/extensions/minimax/src/minimax-web-search-provider.test.ts b/extensions/minimax/src/minimax-web-search-provider.test.ts
new file mode 100644
index 00000000000..3e45303822c
--- /dev/null
+++ b/extensions/minimax/src/minimax-web-search-provider.test.ts
@@ -0,0 +1,153 @@
+import { afterEach, beforeEach, describe, expect, it } from "vitest";
+import { __testing } from "./minimax-web-search-provider.js";
+
+const {
+ MINIMAX_SEARCH_ENDPOINT_GLOBAL,
+ MINIMAX_SEARCH_ENDPOINT_CN,
+ resolveMiniMaxApiKey,
+ resolveMiniMaxEndpoint,
+ resolveMiniMaxRegion,
+} = __testing;
+
+describe("minimax web search provider", () => {
+ const originalApiHost = process.env.MINIMAX_API_HOST;
+ const originalCodePlanKey = process.env.MINIMAX_CODE_PLAN_KEY;
+ const originalCodingApiKey = process.env.MINIMAX_CODING_API_KEY;
+ const originalApiKey = process.env.MINIMAX_API_KEY;
+
+ beforeEach(() => {
+ delete process.env.MINIMAX_API_HOST;
+ delete process.env.MINIMAX_CODE_PLAN_KEY;
+ delete process.env.MINIMAX_CODING_API_KEY;
+ delete process.env.MINIMAX_API_KEY;
+ });
+
+ afterEach(() => {
+ process.env.MINIMAX_API_HOST = originalApiHost;
+ process.env.MINIMAX_CODE_PLAN_KEY = originalCodePlanKey;
+ process.env.MINIMAX_CODING_API_KEY = originalCodingApiKey;
+ process.env.MINIMAX_API_KEY = originalApiKey;
+ });
+
+ describe("resolveMiniMaxRegion", () => {
+ it("returns global by default", () => {
+ expect(resolveMiniMaxRegion()).toBe("global");
+ expect(resolveMiniMaxRegion({})).toBe("global");
+ });
+
+ it("returns cn when explicit region is cn", () => {
+ expect(resolveMiniMaxRegion({ minimax: { region: "cn" } })).toBe("cn");
+ });
+
+ it("returns global when explicit region is not cn", () => {
+ expect(resolveMiniMaxRegion({ minimax: { region: "global" } })).toBe("global");
+ expect(resolveMiniMaxRegion({ minimax: { region: "us" } })).toBe("global");
+ });
+
+ it("infers cn from MINIMAX_API_HOST", () => {
+ process.env.MINIMAX_API_HOST = "https://api.minimaxi.com/anthropic";
+ expect(resolveMiniMaxRegion()).toBe("cn");
+ });
+
+ it("infers cn from model provider base URL", () => {
+ const cnConfig = {
+ models: {
+ providers: {
+ minimax: { baseUrl: "https://api.minimaxi.com/anthropic" },
+ },
+ },
+ };
+ expect(resolveMiniMaxRegion({}, cnConfig)).toBe("cn");
+ });
+
+ it("infers cn from minimax-portal base URL (OAuth CN path)", () => {
+ const cnPortalConfig = {
+ models: {
+ providers: {
+ "minimax-portal": { baseUrl: "https://api.minimaxi.com/anthropic" },
+ },
+ },
+ };
+ expect(resolveMiniMaxRegion({}, cnPortalConfig)).toBe("cn");
+ });
+
+ it("returns global when model provider base URL is global", () => {
+ const globalConfig = {
+ models: {
+ providers: {
+ minimax: { baseUrl: "https://api.minimax.io/anthropic" },
+ },
+ },
+ };
+ expect(resolveMiniMaxRegion({}, globalConfig)).toBe("global");
+ });
+
+ it("explicit search config region takes priority over base URL", () => {
+ const cnConfig = {
+ models: {
+ providers: {
+ minimax: { baseUrl: "https://api.minimaxi.com/anthropic" },
+ },
+ },
+ };
+ // Explicit global region overrides CN base URL
+ expect(resolveMiniMaxRegion({ minimax: { region: "global" } }, cnConfig)).toBe("global");
+ });
+
+ it("handles non-object minimax search config gracefully", () => {
+ expect(resolveMiniMaxRegion({ minimax: "invalid" })).toBe("global");
+ expect(resolveMiniMaxRegion({ minimax: null })).toBe("global");
+ expect(resolveMiniMaxRegion({ minimax: [1, 2] })).toBe("global");
+ });
+ });
+
+ describe("resolveMiniMaxEndpoint", () => {
+ it("returns global endpoint by default", () => {
+ expect(resolveMiniMaxEndpoint()).toBe(MINIMAX_SEARCH_ENDPOINT_GLOBAL);
+ });
+
+ it("returns CN endpoint when region is cn", () => {
+ expect(resolveMiniMaxEndpoint({ minimax: { region: "cn" } })).toBe(
+ MINIMAX_SEARCH_ENDPOINT_CN,
+ );
+ });
+
+ it("returns CN endpoint when inferred from model provider base URL", () => {
+ const cnConfig = {
+ models: {
+ providers: {
+ minimax: { baseUrl: "https://api.minimaxi.com/anthropic" },
+ },
+ },
+ };
+ expect(resolveMiniMaxEndpoint({}, cnConfig)).toBe(MINIMAX_SEARCH_ENDPOINT_CN);
+ });
+ });
+
+ describe("resolveMiniMaxApiKey", () => {
+ it("prefers configured apiKey over env vars", () => {
+ process.env.MINIMAX_CODE_PLAN_KEY = "env-key";
+ expect(resolveMiniMaxApiKey({ apiKey: "configured-key" })).toBe("configured-key");
+ });
+
+ it("accepts MINIMAX_CODING_API_KEY as a coding-plan alias", () => {
+ process.env.MINIMAX_CODING_API_KEY = "coding-key";
+ expect(resolveMiniMaxApiKey()).toBe("coding-key");
+ });
+
+ it("falls back to MINIMAX_API_KEY last", () => {
+ process.env.MINIMAX_API_KEY = "plain-key";
+ expect(resolveMiniMaxApiKey()).toBe("plain-key");
+ });
+ });
+
+ describe("endpoint constants", () => {
+ it("uses correct global endpoint", () => {
+ expect(MINIMAX_SEARCH_ENDPOINT_GLOBAL).toBe("https://api.minimax.io/v1/coding_plan/search");
+ });
+
+ it("uses correct CN endpoint", () => {
+ expect(MINIMAX_SEARCH_ENDPOINT_CN).toBe("https://api.minimaxi.com/v1/coding_plan/search");
+ });
+ });
+});
diff --git a/extensions/minimax/src/minimax-web-search-provider.ts b/extensions/minimax/src/minimax-web-search-provider.ts
new file mode 100644
index 00000000000..e954c245034
--- /dev/null
+++ b/extensions/minimax/src/minimax-web-search-provider.ts
@@ -0,0 +1,305 @@
+import { Type } from "@sinclair/typebox";
+import {
+ DEFAULT_SEARCH_COUNT,
+ MAX_SEARCH_COUNT,
+ buildSearchCacheKey,
+ formatCliCommand,
+ mergeScopedSearchConfig,
+ readCachedSearchPayload,
+ readConfiguredSecretString,
+ readNumberParam,
+ readProviderEnvValue,
+ readStringParam,
+ resolveProviderWebSearchPluginConfig,
+ resolveSearchCacheTtlMs,
+ resolveSearchCount,
+ resolveSearchTimeoutSeconds,
+ resolveSiteName,
+ setProviderWebSearchPluginConfigValue,
+ setTopLevelCredentialValue,
+ withTrustedWebSearchEndpoint,
+ wrapWebContent,
+ writeCachedSearchPayload,
+ type SearchConfigRecord,
+ type WebSearchProviderPlugin,
+ type WebSearchProviderToolDefinition,
+} from "openclaw/plugin-sdk/provider-web-search";
+
+const MINIMAX_SEARCH_ENDPOINT_GLOBAL = "https://api.minimax.io/v1/coding_plan/search";
+const MINIMAX_SEARCH_ENDPOINT_CN = "https://api.minimaxi.com/v1/coding_plan/search";
+const MINIMAX_CODING_PLAN_ENV_VARS = [
+ "MINIMAX_CODE_PLAN_KEY",
+ "MINIMAX_CODING_API_KEY",
+] as const;
+
+type MiniMaxSearchResult = {
+ title?: string;
+ link?: string;
+ snippet?: string;
+ date?: string;
+};
+
+type MiniMaxRelatedSearch = {
+ query?: string;
+};
+
+type MiniMaxSearchResponse = {
+ organic?: MiniMaxSearchResult[];
+ related_searches?: MiniMaxRelatedSearch[];
+ base_resp?: {
+ status_code?: number;
+ status_msg?: string;
+ };
+};
+
+function resolveMiniMaxApiKey(searchConfig?: SearchConfigRecord): string | undefined {
+ return (
+ readConfiguredSecretString(searchConfig?.apiKey, "tools.web.search.apiKey") ??
+ readProviderEnvValue([...MINIMAX_CODING_PLAN_ENV_VARS, "MINIMAX_API_KEY"])
+ );
+}
+
+function isMiniMaxCnHost(value: string | undefined): boolean {
+ const trimmed = value?.trim();
+ if (!trimmed) {
+ return false;
+ }
+ try {
+ return new URL(trimmed).hostname.endsWith("minimaxi.com");
+ } catch {
+ return trimmed.includes("minimaxi.com");
+ }
+}
+
+function resolveMiniMaxRegion(
+ searchConfig?: SearchConfigRecord,
+ config?: Record,
+): "cn" | "global" {
+ // 1. Explicit region in search config takes priority
+ const minimax =
+ typeof searchConfig?.minimax === "object" &&
+ searchConfig.minimax !== null &&
+ !Array.isArray(searchConfig.minimax)
+ ? (searchConfig.minimax as Record)
+ : undefined;
+ if (typeof minimax?.region === "string" && minimax.region.trim()) {
+ return minimax.region === "cn" ? "cn" : "global";
+ }
+
+ // 2. Infer from the shared MiniMax host override.
+ if (isMiniMaxCnHost(process.env.MINIMAX_API_HOST)) {
+ return "cn";
+ }
+
+ // 3. Infer from model provider base URL (set by CN onboarding)
+ const models = config?.models as Record | undefined;
+ const providers = models?.providers as Record | undefined;
+ const minimaxProvider = providers?.minimax as Record | undefined;
+ const portalProvider = providers?.["minimax-portal"] as Record | undefined;
+ const baseUrl = typeof minimaxProvider?.baseUrl === "string" ? minimaxProvider.baseUrl : "";
+ const portalBaseUrl = typeof portalProvider?.baseUrl === "string" ? portalProvider.baseUrl : "";
+ if (isMiniMaxCnHost(baseUrl) || isMiniMaxCnHost(portalBaseUrl)) {
+ return "cn";
+ }
+
+ return "global";
+}
+
+function resolveMiniMaxEndpoint(
+ searchConfig?: SearchConfigRecord,
+ config?: Record,
+): string {
+ return resolveMiniMaxRegion(searchConfig, config) === "cn"
+ ? MINIMAX_SEARCH_ENDPOINT_CN
+ : MINIMAX_SEARCH_ENDPOINT_GLOBAL;
+}
+
+async function runMiniMaxSearch(params: {
+ query: string;
+ count: number;
+ apiKey: string;
+ endpoint: string;
+ timeoutSeconds: number;
+}): Promise<{
+ results: Array>;
+ relatedSearches?: string[];
+}> {
+ return withTrustedWebSearchEndpoint(
+ {
+ url: params.endpoint,
+ timeoutSeconds: params.timeoutSeconds,
+ init: {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${params.apiKey}`,
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ body: JSON.stringify({ q: params.query }),
+ },
+ },
+ async (res) => {
+ if (!res.ok) {
+ const detail = await res.text();
+ throw new Error(`MiniMax Search API error (${res.status}): ${detail || res.statusText}`);
+ }
+
+ const data = (await res.json()) as MiniMaxSearchResponse;
+
+ if (data.base_resp?.status_code && data.base_resp.status_code !== 0) {
+ throw new Error(
+ `MiniMax Search API error (${data.base_resp.status_code}): ${data.base_resp.status_msg || "unknown error"}`,
+ );
+ }
+
+ const organic = Array.isArray(data.organic) ? data.organic : [];
+ const results = organic.slice(0, params.count).map((entry) => {
+ const title = entry.title ?? "";
+ const url = entry.link ?? "";
+ const snippet = entry.snippet ?? "";
+ return {
+ title: title ? wrapWebContent(title, "web_search") : "",
+ url,
+ description: snippet ? wrapWebContent(snippet, "web_search") : "",
+ published: entry.date || undefined,
+ siteName: resolveSiteName(url) || undefined,
+ };
+ });
+
+ const relatedSearches = Array.isArray(data.related_searches)
+ ? data.related_searches
+ .map((r) => r.query)
+ .filter((q): q is string => typeof q === "string" && q.length > 0)
+ .map((q) => wrapWebContent(q, "web_search"))
+ : undefined;
+
+ return { results, relatedSearches };
+ },
+ );
+}
+
+const MiniMaxSearchSchema = Type.Object({
+ query: Type.String({ description: "Search query string." }),
+ count: Type.Optional(
+ Type.Number({
+ description: "Number of results to return (1-10).",
+ minimum: 1,
+ maximum: MAX_SEARCH_COUNT,
+ }),
+ ),
+});
+
+function missingMiniMaxKeyPayload() {
+ return {
+ error: "missing_minimax_api_key",
+ message: `web_search (minimax) needs a MiniMax Coding Plan key. Run \`${formatCliCommand("openclaw configure --section web")}\` to store it, or set MINIMAX_CODE_PLAN_KEY, MINIMAX_CODING_API_KEY, or MINIMAX_API_KEY in the Gateway environment.`,
+ docs: "https://docs.openclaw.ai/tools/web",
+ };
+}
+
+function createMiniMaxToolDefinition(
+ searchConfig?: SearchConfigRecord,
+ config?: Record,
+): WebSearchProviderToolDefinition {
+ return {
+ description:
+ "Search the web using MiniMax Search API. Returns titles, URLs, snippets, and related search suggestions.",
+ parameters: MiniMaxSearchSchema,
+ execute: async (args) => {
+ const apiKey = resolveMiniMaxApiKey(searchConfig);
+ if (!apiKey) {
+ return missingMiniMaxKeyPayload();
+ }
+
+ const params = args as Record;
+ const query = readStringParam(params, "query", { required: true });
+ const count =
+ readNumberParam(params, "count", { integer: true }) ??
+ searchConfig?.maxResults ??
+ undefined;
+
+ const resolvedCount = resolveSearchCount(count, DEFAULT_SEARCH_COUNT);
+ const endpoint = resolveMiniMaxEndpoint(searchConfig, config);
+
+ const cacheKey = buildSearchCacheKey(["minimax", endpoint, query, resolvedCount]);
+ const cached = readCachedSearchPayload(cacheKey);
+ if (cached) {
+ return cached;
+ }
+
+ const start = Date.now();
+ const timeoutSeconds = resolveSearchTimeoutSeconds(searchConfig);
+ const cacheTtlMs = resolveSearchCacheTtlMs(searchConfig);
+
+ const { results, relatedSearches } = await runMiniMaxSearch({
+ query,
+ count: resolvedCount,
+ apiKey,
+ endpoint,
+ timeoutSeconds,
+ });
+
+ const payload: Record = {
+ query,
+ provider: "minimax",
+ count: results.length,
+ tookMs: Date.now() - start,
+ externalContent: {
+ untrusted: true,
+ source: "web_search",
+ provider: "minimax",
+ wrapped: true,
+ },
+ results,
+ };
+
+ if (relatedSearches && relatedSearches.length > 0) {
+ payload.relatedSearches = relatedSearches;
+ }
+
+ writeCachedSearchPayload(cacheKey, payload, cacheTtlMs);
+ return payload;
+ },
+ };
+}
+
+export const __testing = {
+ MINIMAX_SEARCH_ENDPOINT_GLOBAL,
+ MINIMAX_SEARCH_ENDPOINT_CN,
+ resolveMiniMaxApiKey,
+ resolveMiniMaxEndpoint,
+ resolveMiniMaxRegion,
+} as const;
+
+export function createMiniMaxWebSearchProvider(): WebSearchProviderPlugin {
+ return {
+ id: "minimax",
+ label: "MiniMax Search",
+ hint: "Structured results via MiniMax Coding Plan search API",
+ credentialLabel: "MiniMax Coding Plan key",
+ envVars: [...MINIMAX_CODING_PLAN_ENV_VARS],
+ placeholder: "sk-cp-...",
+ signupUrl: "https://platform.minimax.io/user-center/basic-information/interface-key",
+ docsUrl: "https://docs.openclaw.ai/tools/minimax-search",
+ autoDetectOrder: 15,
+ credentialPath: "plugins.entries.minimax.config.webSearch.apiKey",
+ inactiveSecretPaths: ["plugins.entries.minimax.config.webSearch.apiKey"],
+ getCredentialValue: (searchConfig) => searchConfig?.apiKey,
+ setCredentialValue: setTopLevelCredentialValue,
+ getConfiguredCredentialValue: (config) =>
+ resolveProviderWebSearchPluginConfig(config, "minimax")?.apiKey,
+ setConfiguredCredentialValue: (configTarget, value) => {
+ setProviderWebSearchPluginConfigValue(configTarget, "minimax", "apiKey", value);
+ },
+ createTool: (ctx) =>
+ createMiniMaxToolDefinition(
+ mergeScopedSearchConfig(
+ ctx.searchConfig as SearchConfigRecord | undefined,
+ "minimax",
+ resolveProviderWebSearchPluginConfig(ctx.config, "minimax"),
+ { mirrorApiKeyToTopLevel: true },
+ ) as SearchConfigRecord | undefined,
+ ctx.config as Record | undefined,
+ ),
+ };
+}
diff --git a/src/config/config.web-search-provider.test.ts b/src/config/config.web-search-provider.test.ts
index 06f4fbd6b06..99352f9fdfc 100644
--- a/src/config/config.web-search-provider.test.ts
+++ b/src/config/config.web-search-provider.test.ts
@@ -5,6 +5,13 @@ vi.mock("../runtime.js", () => ({
defaultRuntime: { log: vi.fn(), error: vi.fn() },
}));
+vi.mock("../plugin-sdk/telegram-command-config.js", () => ({
+ TELEGRAM_COMMAND_NAME_PATTERN: /^[a-z0-9_]+$/,
+ normalizeTelegramCommandName: (value: string) => value.trim().toLowerCase(),
+ normalizeTelegramCommandDescription: (value: string) => value.trim(),
+ resolveTelegramCustomCommands: () => ({ commands: [], issues: [] }),
+}));
+
const getScopedWebSearchCredential = (key: string) => (search?: Record) =>
(search?.[key] as { apiKey?: unknown } | undefined)?.apiKey;
const getConfiguredPluginWebSearchConfig =
@@ -59,6 +66,13 @@ const mockWebSearchProviders = [
getCredentialValue: getScopedWebSearchCredential("kimi"),
getConfiguredCredentialValue: getConfiguredPluginWebSearchCredential("moonshot"),
},
+ {
+ id: "minimax",
+ envVars: ["MINIMAX_CODE_PLAN_KEY", "MINIMAX_CODING_API_KEY"],
+ credentialPath: "plugins.entries.minimax.config.webSearch.apiKey",
+ getCredentialValue: getScopedWebSearchCredential("minimax"),
+ getConfiguredCredentialValue: getConfiguredPluginWebSearchCredential("minimax"),
+ },
{
id: "perplexity",
envVars: ["PERPLEXITY_API_KEY", "OPENROUTER_API_KEY"],
@@ -91,24 +105,8 @@ vi.mock("../plugins/web-search-providers.js", () => {
};
});
-const secretInputSchema = {
- oneOf: [
- { type: "string" },
- {
- type: "object",
- additionalProperties: false,
- properties: {
- source: { type: "string" },
- provider: { type: "string" },
- id: { type: "string" },
- },
- required: ["source", "provider", "id"],
- },
- ],
-};
-
-function buildWebSearchPluginSchema() {
- return {
+vi.mock("../plugins/manifest-registry.js", () => {
+ const buildSchema = () => ({
type: "object",
additionalProperties: false,
properties: {
@@ -116,34 +114,69 @@ function buildWebSearchPluginSchema() {
type: "object",
additionalProperties: false,
properties: {
- apiKey: secretInputSchema,
- baseUrl: secretInputSchema,
+ apiKey: {
+ oneOf: [
+ { type: "string" },
+ {
+ type: "object",
+ additionalProperties: false,
+ properties: {
+ source: { type: "string" },
+ provider: { type: "string" },
+ id: { type: "string" },
+ },
+ required: ["source", "provider", "id"],
+ },
+ ],
+ },
+ baseUrl: {
+ oneOf: [
+ { type: "string" },
+ {
+ type: "object",
+ additionalProperties: false,
+ properties: {
+ source: { type: "string" },
+ provider: { type: "string" },
+ id: { type: "string" },
+ },
+ required: ["source", "provider", "id"],
+ },
+ ],
+ },
model: { type: "string" },
},
},
},
- };
-}
+ });
-vi.mock("../plugins/manifest-registry.js", () => ({
- loadPluginManifestRegistry: () => ({
- plugins: [
- {
- id: "brave",
- origin: "bundled",
- channels: [],
- providers: [],
- cliBackends: [],
- skills: [],
- hooks: [],
- rootDir: "/tmp/plugins/brave",
- source: "test",
- manifestPath: "/tmp/plugins/brave/openclaw.plugin.json",
- schemaCacheKey: "test:brave",
- configSchema: buildWebSearchPluginSchema(),
- },
- ...["firecrawl", "google", "moonshot", "perplexity", "searxng", "tavily", "xai"].map(
- (id) => ({
+ return {
+ loadPluginManifestRegistry: () => ({
+ plugins: [
+ {
+ id: "brave",
+ origin: "bundled",
+ channels: [],
+ providers: [],
+ cliBackends: [],
+ skills: [],
+ hooks: [],
+ rootDir: "/tmp/plugins/brave",
+ source: "test",
+ manifestPath: "/tmp/plugins/brave/openclaw.plugin.json",
+ schemaCacheKey: "test:brave",
+ configSchema: buildSchema(),
+ },
+ ...[
+ "firecrawl",
+ "google",
+ "minimax",
+ "moonshot",
+ "perplexity",
+ "searxng",
+ "tavily",
+ "xai",
+ ].map((id) => ({
id,
origin: "bundled",
channels: [],
@@ -155,13 +188,13 @@ vi.mock("../plugins/manifest-registry.js", () => ({
source: "test",
manifestPath: `/tmp/plugins/${id}/openclaw.plugin.json`,
schemaCacheKey: `test:${id}`,
- configSchema: buildWebSearchPluginSchema(),
- }),
- ),
- ],
- diagnostics: [],
- }),
-}));
+ configSchema: buildSchema(),
+ })),
+ ],
+ diagnostics: [],
+ }),
+ };
+});
let validateConfigObjectWithPlugins: typeof import("./config.js").validateConfigObjectWithPlugins;
let resolveSearchProvider: typeof import("../agents/tools/web-search.js").__testing.resolveSearchProvider;
@@ -278,6 +311,24 @@ describe("web search provider config", () => {
expect(res.ok).toBe(true);
});
+ it("accepts minimax provider config on the plugin-owned path", () => {
+ const res = validateConfigObjectWithPlugins(
+ buildWebSearchProviderConfig({
+ enabled: true,
+ provider: "minimax",
+ providerConfig: {
+ apiKey: {
+ source: "env",
+ provider: "default",
+ id: "MINIMAX_CODE_PLAN_KEY",
+ },
+ },
+ }),
+ );
+
+ expect(res.ok).toBe(true);
+ });
+
it("accepts searxng provider config on the plugin-owned path", () => {
const res = validateConfigObjectWithPlugins(
buildWebSearchProviderConfig({
@@ -363,6 +414,9 @@ describe("web search provider auto-detection", () => {
delete process.env.FIRECRAWL_API_KEY;
delete process.env.GEMINI_API_KEY;
delete process.env.KIMI_API_KEY;
+ delete process.env.MINIMAX_API_KEY;
+ delete process.env.MINIMAX_CODE_PLAN_KEY;
+ delete process.env.MINIMAX_CODING_API_KEY;
delete process.env.MOONSHOT_API_KEY;
delete process.env.PERPLEXITY_API_KEY;
delete process.env.OPENROUTER_API_KEY;
@@ -412,6 +466,11 @@ describe("web search provider auto-detection", () => {
expect(resolveSearchProvider({})).toBe("kimi");
});
+ it("auto-detects minimax when only MINIMAX_CODE_PLAN_KEY is set", () => {
+ process.env.MINIMAX_CODE_PLAN_KEY = "sk-cp-test";
+ expect(resolveSearchProvider({})).toBe("minimax");
+ });
+
it("auto-detects perplexity when only PERPLEXITY_API_KEY is set", () => {
process.env.PERPLEXITY_API_KEY = "test-perplexity-key"; // pragma: allowlist secret
expect(resolveSearchProvider({})).toBe("perplexity");
diff --git a/src/plugins/bundled-web-search.test.ts b/src/plugins/bundled-web-search.test.ts
index d37f2333594..cba6cf26491 100644
--- a/src/plugins/bundled-web-search.test.ts
+++ b/src/plugins/bundled-web-search.test.ts
@@ -67,6 +67,13 @@ describe("bundled web search helpers", () => {
});
vi.mocked(loadBundledCapabilityRuntimeRegistry).mockReturnValue({
webSearchProviders: [
+ {
+ pluginId: "minimax",
+ provider: createMockedBundledWebSearchProvider({
+ pluginId: "minimax",
+ providerId: "minimax",
+ }),
+ },
{
pluginId: "xai",
provider: createMockedBundledWebSearchProvider({
@@ -116,15 +123,18 @@ describe("bundled web search helpers", () => {
it("maps bundled provider ids back to their owning plugins", () => {
expect(resolveBundledWebSearchPluginId(" gemini ")).toBe("google");
+ expect(resolveBundledWebSearchPluginId(" minimax ")).toBe("minimax");
expect(resolveBundledWebSearchPluginId("missing")).toBeUndefined();
});
it("loads bundled provider entries through the capability runtime registry once", () => {
expect(listBundledWebSearchProviders()).toEqual([
+ expect.objectContaining({ pluginId: "minimax", id: "minimax" }),
expect.objectContaining({ pluginId: "xai", id: "grok" }),
expect.objectContaining({ pluginId: "google", id: "gemini" }),
]);
expect(listBundledWebSearchProviders()).toEqual([
+ expect.objectContaining({ pluginId: "minimax", id: "minimax" }),
expect.objectContaining({ pluginId: "xai", id: "grok" }),
expect.objectContaining({ pluginId: "google", id: "gemini" }),
]);
diff --git a/src/plugins/contracts/bundled-web-search.minimax.contract.test.ts b/src/plugins/contracts/bundled-web-search.minimax.contract.test.ts
new file mode 100644
index 00000000000..f4b5fcd81a2
--- /dev/null
+++ b/src/plugins/contracts/bundled-web-search.minimax.contract.test.ts
@@ -0,0 +1,3 @@
+import { describeBundledWebSearchFastPathContract } from "../../../test/helpers/plugins/bundled-web-search-fast-path-contract.js";
+
+describeBundledWebSearchFastPathContract("minimax");
diff --git a/src/secrets/provider-env-vars.test.ts b/src/secrets/provider-env-vars.test.ts
index 6180a014986..45f0b11ae08 100644
--- a/src/secrets/provider-env-vars.test.ts
+++ b/src/secrets/provider-env-vars.test.ts
@@ -37,7 +37,7 @@ describe("provider env vars", () => {
]),
);
expect(listKnownProviderAuthEnvVarNames()).toEqual(
- expect.arrayContaining(["MINIMAX_CODE_PLAN_KEY"]),
+ expect.arrayContaining(["MINIMAX_CODE_PLAN_KEY", "MINIMAX_CODING_API_KEY"]),
);
expect(listKnownSecretEnvVarNames()).not.toContain("OPENCLAW_API_KEY");
});
diff --git a/src/secrets/provider-env-vars.ts b/src/secrets/provider-env-vars.ts
index 936ff8a4ddc..58aade35ce1 100644
--- a/src/secrets/provider-env-vars.ts
+++ b/src/secrets/provider-env-vars.ts
@@ -44,7 +44,10 @@ export function getProviderEnvVars(providerId: string): string[] {
return Array.isArray(envVars) ? [...envVars] : [];
}
-const EXTRA_PROVIDER_AUTH_ENV_VARS = ["MINIMAX_CODE_PLAN_KEY"] as const;
+const EXTRA_PROVIDER_AUTH_ENV_VARS = [
+ "MINIMAX_CODE_PLAN_KEY",
+ "MINIMAX_CODING_API_KEY",
+] as const;
const KNOWN_SECRET_ENV_VARS = [
...new Set(Object.values(PROVIDER_ENV_VARS).flatMap((keys) => keys)),
diff --git a/src/secrets/runtime.coverage.test.ts b/src/secrets/runtime.coverage.test.ts
index 0ec523b4ac0..67e63aca2c1 100644
--- a/src/secrets/runtime.coverage.test.ts
+++ b/src/secrets/runtime.coverage.test.ts
@@ -42,7 +42,7 @@ vi.mock("../plugins/web-fetch-providers.runtime.js", () => ({
}));
function createTestProvider(params: {
- id: "brave" | "gemini" | "grok" | "kimi" | "perplexity" | "firecrawl" | "tavily";
+ id: "brave" | "gemini" | "grok" | "kimi" | "minimax" | "perplexity" | "firecrawl" | "tavily";
pluginId: string;
order: number;
}): PluginWebSearchProviderEntry {
@@ -100,6 +100,7 @@ function buildTestWebSearchProviders(): PluginWebSearchProviderEntry[] {
createTestProvider({ id: "gemini", pluginId: "google", order: 20 }),
createTestProvider({ id: "grok", pluginId: "xai", order: 30 }),
createTestProvider({ id: "kimi", pluginId: "moonshot", order: 40 }),
+ createTestProvider({ id: "minimax", pluginId: "minimax", order: 15 }),
createTestProvider({ id: "perplexity", pluginId: "perplexity", order: 50 }),
createTestProvider({ id: "firecrawl", pluginId: "firecrawl", order: 60 }),
createTestProvider({ id: "tavily", pluginId: "tavily", order: 70 }),
@@ -260,6 +261,9 @@ function buildConfigForOpenClawTarget(entry: SecretRegistryEntry, envId: string)
if (entry.id === "plugins.entries.firecrawl.config.webSearch.apiKey") {
setPathCreateStrict(config, ["tools", "web", "search", "provider"], "firecrawl");
}
+ if (entry.id === "plugins.entries.minimax.config.webSearch.apiKey") {
+ setPathCreateStrict(config, ["tools", "web", "search", "provider"], "minimax");
+ }
if (entry.id === "plugins.entries.tavily.config.webSearch.apiKey") {
setPathCreateStrict(config, ["tools", "web", "search", "provider"], "tavily");
}
diff --git a/src/secrets/target-registry-data.ts b/src/secrets/target-registry-data.ts
index 7086499d6c1..0301a75268b 100644
--- a/src/secrets/target-registry-data.ts
+++ b/src/secrets/target-registry-data.ts
@@ -422,6 +422,17 @@ const CORE_SECRET_TARGET_REGISTRY: SecretTargetRegistryEntry[] = [
includeInConfigure: true,
includeInAudit: true,
},
+ {
+ id: "plugins.entries.minimax.config.webSearch.apiKey",
+ targetType: "plugins.entries.minimax.config.webSearch.apiKey",
+ configFile: "openclaw.json",
+ pathPattern: "plugins.entries.minimax.config.webSearch.apiKey",
+ secretShape: SECRET_INPUT_SHAPE,
+ expectedResolvedValue: "string",
+ includeInPlan: true,
+ includeInConfigure: true,
+ includeInAudit: true,
+ },
];
const SECRET_TARGET_REGISTRY: SecretTargetRegistryEntry[] = [