|
1 | 1 | import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types"; |
2 | 2 | import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-runtime"; |
| 3 | +import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api"; |
3 | 4 | import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; |
4 | 5 | import { |
5 | 6 | DEFAULT_TAVILY_BASE_URL, |
@@ -33,13 +34,15 @@ describe("tavily tools", () => { |
33 | 34 | let createTavilySearchTool: typeof import("./tavily-search-tool.js").createTavilySearchTool; |
34 | 35 | let createTavilyExtractTool: typeof import("./tavily-extract-tool.js").createTavilyExtractTool; |
35 | 36 | let tavilyClientTesting: typeof import("./tavily-client.js").__testing; |
| 37 | + let tavilyPlugin: typeof import("../index.js").default; |
36 | 38 |
|
37 | 39 | beforeAll(async () => { |
38 | 40 | ({ createTavilyWebSearchProvider } = await import("./tavily-search-provider.js")); |
39 | 41 | ({ createTavilySearchTool } = await import("./tavily-search-tool.js")); |
40 | 42 | ({ createTavilyExtractTool } = await import("./tavily-extract-tool.js")); |
41 | 43 | ({ __testing: tavilyClientTesting } = |
42 | 44 | await vi.importActual<typeof import("./tavily-client.js")>("./tavily-client.js")); |
| 45 | + ({ default: tavilyPlugin } = await import("../index.js")); |
43 | 46 | }); |
44 | 47 |
|
45 | 48 | beforeEach(() => { |
@@ -140,6 +143,85 @@ describe("tavily tools", () => { |
140 | 143 | }); |
141 | 144 | }); |
142 | 145 |
|
| 146 | + it("late-binds dedicated tools to the resolved runtime config snapshot", async () => { |
| 147 | + const rawConfig = { |
| 148 | + plugins: { |
| 149 | + entries: { |
| 150 | + tavily: { |
| 151 | + config: { |
| 152 | + webSearch: { |
| 153 | + apiKey: { source: "exec", provider: "default", id: "printf resolved-key" }, |
| 154 | + }, |
| 155 | + }, |
| 156 | + }, |
| 157 | + }, |
| 158 | + }, |
| 159 | + } as OpenClawConfig; |
| 160 | + const runtimeConfig = { |
| 161 | + plugins: { |
| 162 | + entries: { |
| 163 | + tavily: { |
| 164 | + config: { |
| 165 | + webSearch: { |
| 166 | + apiKey: "resolved-key", |
| 167 | + }, |
| 168 | + }, |
| 169 | + }, |
| 170 | + }, |
| 171 | + }, |
| 172 | + } as OpenClawConfig; |
| 173 | + const registeredTools: Array<Parameters<OpenClawPluginApi["registerTool"]>[0]> = []; |
| 174 | + const registeredOptions: Array<Parameters<OpenClawPluginApi["registerTool"]>[1]> = []; |
| 175 | + const api = createTestPluginApi({ |
| 176 | + config: rawConfig, |
| 177 | + registerTool(tool, opts) { |
| 178 | + registeredTools.push(tool); |
| 179 | + registeredOptions.push(opts); |
| 180 | + }, |
| 181 | + }); |
| 182 | + |
| 183 | + tavilyPlugin.register(api); |
| 184 | + const searchFactory = registeredTools.find( |
| 185 | + (tool, index) => |
| 186 | + registeredOptions[index]?.name === "tavily_search" && typeof tool === "function", |
| 187 | + ); |
| 188 | + const extractFactory = registeredTools.find( |
| 189 | + (tool, index) => |
| 190 | + registeredOptions[index]?.name === "tavily_extract" && typeof tool === "function", |
| 191 | + ); |
| 192 | + if (typeof searchFactory !== "function" || typeof extractFactory !== "function") { |
| 193 | + throw new Error("Expected Tavily tools to register as runtime-context factories"); |
| 194 | + } |
| 195 | + |
| 196 | + const searchTool = searchFactory({ |
| 197 | + config: rawConfig, |
| 198 | + runtimeConfig, |
| 199 | + }); |
| 200 | + const extractTool = extractFactory({ |
| 201 | + config: rawConfig, |
| 202 | + getRuntimeConfig: () => runtimeConfig, |
| 203 | + }); |
| 204 | + if (Array.isArray(searchTool) || !searchTool || Array.isArray(extractTool) || !extractTool) { |
| 205 | + throw new Error("Expected single Tavily tool definitions"); |
| 206 | + } |
| 207 | + |
| 208 | + await searchTool.execute("search-call", { query: "openclaw" }); |
| 209 | + await extractTool.execute("extract-call", { urls: ["https://example.com"] }); |
| 210 | + |
| 211 | + expect(runTavilySearch).toHaveBeenCalledWith( |
| 212 | + expect.objectContaining({ |
| 213 | + cfg: runtimeConfig, |
| 214 | + query: "openclaw", |
| 215 | + }), |
| 216 | + ); |
| 217 | + expect(runTavilyExtract).toHaveBeenCalledWith( |
| 218 | + expect.objectContaining({ |
| 219 | + cfg: runtimeConfig, |
| 220 | + urls: ["https://example.com"], |
| 221 | + }), |
| 222 | + ); |
| 223 | + }); |
| 224 | + |
143 | 225 | it("drops empty domain arrays and forwards query-scoped chunking", async () => { |
144 | 226 | runTavilySearch.mockImplementationOnce(async (params: Record<string, unknown>) => ({ |
145 | 227 | ok: true, |
|
0 commit comments