



























@@ -12,12 +12,39 @@ type TestPluginWebSearchConfig = {
1212};
1313};
141415-const { resolvePluginWebSearchProvidersMock, resolveRuntimeWebSearchProvidersMock } = vi.hoisted(
16-() => ({
17-resolvePluginWebSearchProvidersMock: vi.fn<() => PluginWebSearchProviderEntry[]>(() => []),
18-resolveRuntimeWebSearchProvidersMock: vi.fn<() => PluginWebSearchProviderEntry[]>(() => []),
19-}),
20-);
15+type WebSearchProviderResolverParams = {
16+bundledAllowlistCompat?: boolean;
17+config?: OpenClawConfig;
18+onlyPluginIds?: readonly string[];
19+origin?: string;
20+};
21+22+type ManifestContractOwnerParams = {
23+config?: OpenClawConfig;
24+contract?: string;
25+origin?: string;
26+value?: string;
27+};
28+29+const {
30+ resolveManifestContractOwnerPluginIdMock,
31+ resolvePluginWebSearchProvidersMock,
32+ resolveRuntimeWebSearchProvidersMock,
33+} = vi.hoisted(() => ({
34+resolveManifestContractOwnerPluginIdMock: vi.fn(
35+(_params: ManifestContractOwnerParams): string | undefined => undefined,
36+),
37+resolvePluginWebSearchProvidersMock: vi.fn(
38+(_params?: WebSearchProviderResolverParams): PluginWebSearchProviderEntry[] => [],
39+),
40+resolveRuntimeWebSearchProvidersMock: vi.fn(
41+(_params?: WebSearchProviderResolverParams): PluginWebSearchProviderEntry[] => [],
42+),
43+}));
44+45+vi.mock("../plugins/plugin-registry-contributions.js", () => ({
46+resolveManifestContractOwnerPluginId: resolveManifestContractOwnerPluginIdMock,
47+}));
21482249vi.mock("../plugins/web-search-providers.runtime.js", () => ({
2350resolvePluginWebSearchProviders: resolvePluginWebSearchProvidersMock,
@@ -108,8 +135,10 @@ describe("web search runtime", () => {
108135});
109136110137beforeEach(() => {
138+resolveManifestContractOwnerPluginIdMock.mockReset();
111139resolvePluginWebSearchProvidersMock.mockReset();
112140resolveRuntimeWebSearchProvidersMock.mockReset();
141+resolveManifestContractOwnerPluginIdMock.mockReturnValue(undefined);
113142resolvePluginWebSearchProvidersMock.mockReturnValue([]);
114143resolveRuntimeWebSearchProvidersMock.mockReturnValue([]);
115144});
@@ -533,6 +562,110 @@ describe("web search runtime", () => {
533562).rejects.toThrow("google aborted");
534563});
535564565+it("scopes runtime provider loading to the configured bundled web_search provider", async () => {
566+resolveManifestContractOwnerPluginIdMock.mockImplementation(({ value }) =>
567+value === "duckduckgo" ? "duckduckgo" : undefined,
568+);
569+resolveRuntimeWebSearchProvidersMock.mockReturnValue([createDuckDuckGoSearchProvider()]);
570+571+await expect(
572+runWebSearch({
573+config: {
574+tools: {
575+web: {
576+search: {
577+provider: "duckduckgo",
578+},
579+},
580+},
581+},
582+args: { query: "configured-duck" },
583+}),
584+).resolves.toMatchObject({
585+provider: "duckduckgo",
586+});
587+588+expect(resolveManifestContractOwnerPluginIdMock).toHaveBeenCalledWith(
589+expect.objectContaining({
590+contract: "webSearchProviders",
591+origin: "bundled",
592+value: "duckduckgo",
593+}),
594+);
595+expect(resolveRuntimeWebSearchProvidersMock).toHaveBeenCalledWith(
596+expect.objectContaining({
597+onlyPluginIds: ["duckduckgo"],
598+}),
599+);
600+});
601+602+it("scopes runtime provider loading through manifest ownership when provider id differs from plugin id", async () => {
603+resolveManifestContractOwnerPluginIdMock.mockImplementation(({ value }) =>
604+value === "gemini" ? "google" : undefined,
605+);
606+resolveRuntimeWebSearchProvidersMock.mockReturnValue([
607+createGoogleSearchProvider({
608+id: "gemini",
609+pluginId: "google",
610+}),
611+]);
612+613+await expect(
614+runWebSearch({
615+config: {},
616+runtimeWebSearch: {
617+providerConfigured: "gemini",
618+selectedProvider: "gemini",
619+providerSource: "configured",
620+diagnostics: [],
621+},
622+args: { query: "configured-gemini" },
623+}),
624+).resolves.toMatchObject({
625+provider: "gemini",
626+});
627+628+expect(resolveRuntimeWebSearchProvidersMock).toHaveBeenCalledWith(
629+expect.objectContaining({
630+onlyPluginIds: ["google"],
631+}),
632+);
633+});
634+635+it("keeps runtime provider loading unscoped when configured provider ownership is unknown", async () => {
636+resolveManifestContractOwnerPluginIdMock.mockReturnValue(undefined);
637+resolveRuntimeWebSearchProvidersMock.mockReturnValue([
638+createCustomSearchProvider({
639+id: "external-search",
640+pluginId: "external-search",
641+requiresCredential: false,
642+}),
643+]);
644+645+await expect(
646+runWebSearch({
647+config: {
648+tools: {
649+web: {
650+search: {
651+provider: "external-search",
652+},
653+},
654+},
655+},
656+args: { query: "external-provider" },
657+}),
658+).resolves.toMatchObject({
659+provider: "external-search",
660+});
661+662+expect(resolveRuntimeWebSearchProvidersMock).toHaveBeenCalledWith(
663+expect.not.objectContaining({
664+onlyPluginIds: expect.anything(),
665+}),
666+);
667+});
668+536669it("does not fall back when the caller explicitly selects a provider", async () => {
537670resolveRuntimeWebSearchProvidersMock.mockReturnValue([
538671createGoogleSearchProvider({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。