mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-16 23:10:43 +00:00
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:
@@ -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.
|
||||
|
||||
@@ -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" });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user