Fix Tavily tool SecretRef runtime config

Resolve Tavily dedicated tool credential lookup against the active runtime config snapshot.

PR: https://github.com/openclaw/openclaw/pull/78610
This commit is contained in:
VACInc
2026-05-07 05:40:22 -04:00
committed by GitHub
parent 129b9dad9e
commit 8de5a55317
5 changed files with 118 additions and 7 deletions

View File

@@ -143,6 +143,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Tavily: resolve dedicated `tavily_search` and `tavily_extract` tool credentials from the active runtime config snapshot, so `exec` SecretRef-backed API keys do not reach the tools unresolved. (#78610) Thanks @VACInc.
- Gateway/sessions: clear cached skills snapshots during `/new` and `sessions.reset` so long-lived channel sessions rebuild the visible skill list after skills change. (#78873) Thanks @Evizero.
- fix(auto-reply): gate inline skill tool dispatch [AI]. (#78517) Thanks @pgondhi987.
- Canvas plugin: keep legacy root `canvasHost` configs valid until `openclaw doctor --fix` migrates them into `plugins.entries.canvas.config.host`, move Canvas/A2UI clients to gateway protocol v4 plugin surfaces, and refresh the generated A2UI bundle hash so normal builds stay clean.

View File

@@ -1,4 +1,4 @@
import { definePluginEntry, type AnyAgentTool } from "openclaw/plugin-sdk/plugin-entry";
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createTavilyExtractTool } from "./src/tavily-extract-tool.js";
import { createTavilyWebSearchProvider } from "./src/tavily-search-provider.js";
import { createTavilySearchTool } from "./src/tavily-search-tool.js";
@@ -9,7 +9,7 @@ export default definePluginEntry({
description: "Bundled Tavily search and extract plugin",
register(api) {
api.registerWebSearchProvider(createTavilyWebSearchProvider());
api.registerTool(createTavilySearchTool(api) as AnyAgentTool);
api.registerTool(createTavilyExtractTool(api) as AnyAgentTool);
api.registerTool((ctx) => createTavilySearchTool(api, ctx), { name: "tavily_search" });
api.registerTool((ctx) => createTavilyExtractTool(api, ctx), { name: "tavily_extract" });
},
});

View File

@@ -1,3 +1,5 @@
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
import type { OpenClawPluginToolContext } from "openclaw/plugin-sdk/plugin-entry";
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-runtime";
import {
jsonResult,
@@ -8,6 +10,18 @@ import { Type } from "typebox";
import { runTavilyExtract } from "./tavily-client.js";
import { optionalStringEnum } from "./tavily-tool-schema.js";
type TavilyToolConfigContext = Pick<
OpenClawPluginToolContext,
"config" | "runtimeConfig" | "getRuntimeConfig"
>;
function resolveTavilyToolConfig(
api: OpenClawPluginApi,
ctx?: TavilyToolConfigContext,
): OpenClawConfig {
return ctx?.getRuntimeConfig?.() ?? ctx?.runtimeConfig ?? ctx?.config ?? api.config;
}
const TavilyExtractToolSchema = Type.Object(
{
urls: Type.Array(Type.String(), {
@@ -39,7 +53,7 @@ const TavilyExtractToolSchema = Type.Object(
{ additionalProperties: false },
);
export function createTavilyExtractTool(api: OpenClawPluginApi) {
export function createTavilyExtractTool(api: OpenClawPluginApi, ctx?: TavilyToolConfigContext) {
return {
name: "tavily_extract",
label: "Tavily Extract",
@@ -65,7 +79,7 @@ export function createTavilyExtractTool(api: OpenClawPluginApi) {
return jsonResult(
await runTavilyExtract({
cfg: api.config,
cfg: resolveTavilyToolConfig(api, ctx),
urls,
query,
extractDepth,

View File

@@ -1,3 +1,5 @@
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
import type { OpenClawPluginToolContext } from "openclaw/plugin-sdk/plugin-entry";
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-runtime";
import {
jsonResult,
@@ -8,6 +10,18 @@ import { Type } from "typebox";
import { runTavilySearch } from "./tavily-client.js";
import { optionalStringEnum } from "./tavily-tool-schema.js";
type TavilyToolConfigContext = Pick<
OpenClawPluginToolContext,
"config" | "runtimeConfig" | "getRuntimeConfig"
>;
function resolveTavilyToolConfig(
api: OpenClawPluginApi,
ctx?: TavilyToolConfigContext,
): OpenClawConfig {
return ctx?.getRuntimeConfig?.() ?? ctx?.runtimeConfig ?? ctx?.config ?? api.config;
}
const TavilySearchToolSchema = Type.Object(
{
query: Type.String({ description: "Search query string." }),
@@ -46,7 +60,7 @@ const TavilySearchToolSchema = Type.Object(
{ additionalProperties: false },
);
export function createTavilySearchTool(api: OpenClawPluginApi) {
export function createTavilySearchTool(api: OpenClawPluginApi, ctx?: TavilyToolConfigContext) {
return {
name: "tavily_search",
label: "Tavily Search",
@@ -69,7 +83,7 @@ export function createTavilySearchTool(api: OpenClawPluginApi) {
return jsonResult(
await runTavilySearch({
cfg: api.config,
cfg: resolveTavilyToolConfig(api, ctx),
query,
searchDepth,
topic,

View File

@@ -1,5 +1,6 @@
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-runtime";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import {
DEFAULT_TAVILY_BASE_URL,
@@ -33,6 +34,7 @@ describe("tavily tools", () => {
let createTavilySearchTool: typeof import("./tavily-search-tool.js").createTavilySearchTool;
let createTavilyExtractTool: typeof import("./tavily-extract-tool.js").createTavilyExtractTool;
let tavilyClientTesting: typeof import("./tavily-client.js").__testing;
let tavilyPlugin: typeof import("../index.js").default;
beforeAll(async () => {
({ createTavilyWebSearchProvider } = await import("./tavily-search-provider.js"));
@@ -40,6 +42,7 @@ describe("tavily tools", () => {
({ createTavilyExtractTool } = await import("./tavily-extract-tool.js"));
({ __testing: tavilyClientTesting } =
await vi.importActual<typeof import("./tavily-client.js")>("./tavily-client.js"));
({ default: tavilyPlugin } = await import("../index.js"));
});
beforeEach(() => {
@@ -140,6 +143,85 @@ describe("tavily tools", () => {
});
});
it("late-binds dedicated tools to the resolved runtime config snapshot", async () => {
const rawConfig = {
plugins: {
entries: {
tavily: {
config: {
webSearch: {
apiKey: { source: "exec", provider: "default", id: "printf resolved-key" },
},
},
},
},
},
} as OpenClawConfig;
const runtimeConfig = {
plugins: {
entries: {
tavily: {
config: {
webSearch: {
apiKey: "resolved-key",
},
},
},
},
},
} as OpenClawConfig;
const registeredTools: Array<Parameters<OpenClawPluginApi["registerTool"]>[0]> = [];
const registeredOptions: Array<Parameters<OpenClawPluginApi["registerTool"]>[1]> = [];
const api = createTestPluginApi({
config: rawConfig,
registerTool(tool, opts) {
registeredTools.push(tool);
registeredOptions.push(opts);
},
});
tavilyPlugin.register(api);
const searchFactory = registeredTools.find(
(tool, index) =>
registeredOptions[index]?.name === "tavily_search" && typeof tool === "function",
);
const extractFactory = registeredTools.find(
(tool, index) =>
registeredOptions[index]?.name === "tavily_extract" && typeof tool === "function",
);
if (typeof searchFactory !== "function" || typeof extractFactory !== "function") {
throw new Error("Expected Tavily tools to register as runtime-context factories");
}
const searchTool = searchFactory({
config: rawConfig,
runtimeConfig,
});
const extractTool = extractFactory({
config: rawConfig,
getRuntimeConfig: () => runtimeConfig,
});
if (Array.isArray(searchTool) || !searchTool || Array.isArray(extractTool) || !extractTool) {
throw new Error("Expected single Tavily tool definitions");
}
await searchTool.execute("search-call", { query: "openclaw" });
await extractTool.execute("extract-call", { urls: ["https://example.com"] });
expect(runTavilySearch).toHaveBeenCalledWith(
expect.objectContaining({
cfg: runtimeConfig,
query: "openclaw",
}),
);
expect(runTavilyExtract).toHaveBeenCalledWith(
expect.objectContaining({
cfg: runtimeConfig,
urls: ["https://example.com"],
}),
);
});
it("drops empty domain arrays and forwards query-scoped chunking", async () => {
runTavilySearch.mockImplementationOnce(async (params: Record<string, unknown>) => ({
ok: true,