mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:50:43 +00:00
fix(web-fetch): scope fallback cache by provider
This commit is contained in:
@@ -63,6 +63,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Web fetch: scope provider fallback cache entries by the selected fetch provider so config reloads cannot reuse another provider's cached fallback payload. Thanks @vincentkoc.
|
||||
- Web search: honor late-bound `tools.web.search.enabled: false` during tool execution so config reloads cannot leave an already-created `web_search` tool runnable. Thanks @vincentkoc.
|
||||
- Plugins/packages: reject inferred built runtime entries that exist but fail package-boundary checks instead of falling back to TypeScript source for installed packages. Thanks @vincentkoc.
|
||||
- Plugins/loader: do not retry native-loaded JavaScript plugin modules through the source transformer after native evaluation has already reached a missing dependency, avoiding duplicate top-level side effects. Thanks @vincentkoc.
|
||||
|
||||
@@ -221,4 +221,77 @@ describe("web_fetch provider fallback normalization", () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("scopes provider fallback cache entries by the late-bound provider", async () => {
|
||||
global.fetch = withFetchPreconnect(
|
||||
vi.fn(async () => {
|
||||
throw new Error("network failed");
|
||||
}),
|
||||
);
|
||||
resolveWebFetchDefinitionMock.mockImplementation(
|
||||
({ runtimeWebFetch }: { runtimeWebFetch?: { selectedProvider?: string } }) => {
|
||||
const providerId = runtimeWebFetch?.selectedProvider ?? "unknown";
|
||||
return {
|
||||
provider: { id: providerId },
|
||||
definition: {
|
||||
description: providerId,
|
||||
parameters: {},
|
||||
execute: async () => ({
|
||||
text: `${providerId} fallback body`,
|
||||
}),
|
||||
},
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const executeWithProvider = async (providerId: string) => {
|
||||
runtimeState.activeSecretsRuntimeSnapshot = {
|
||||
config: {
|
||||
tools: {
|
||||
web: {
|
||||
fetch: {
|
||||
provider: providerId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
runtimeState.activeRuntimeWebToolsMetadata = {
|
||||
fetch: {
|
||||
providerConfigured: providerId,
|
||||
providerSource: "configured",
|
||||
selectedProvider: providerId,
|
||||
selectedProviderKeySource: "config",
|
||||
diagnostics: [],
|
||||
},
|
||||
diagnostics: [],
|
||||
};
|
||||
const tool = createWebFetchTool({
|
||||
config: {} as OpenClawConfig,
|
||||
sandboxed: false,
|
||||
lateBindRuntimeConfig: true,
|
||||
});
|
||||
return tool?.execute?.("call-provider-fallback", {
|
||||
url: "https://example.com/provider-cache-scope",
|
||||
});
|
||||
};
|
||||
|
||||
const first = await executeWithProvider("firecrawl");
|
||||
const second = await executeWithProvider("perplexity-fetch");
|
||||
const firstDetails = first?.details as {
|
||||
externalContent?: { provider?: string };
|
||||
text?: string;
|
||||
};
|
||||
const secondDetails = second?.details as {
|
||||
cached?: boolean;
|
||||
externalContent?: { provider?: string };
|
||||
text?: string;
|
||||
};
|
||||
|
||||
expect(firstDetails.externalContent?.provider).toBe("firecrawl");
|
||||
expect(firstDetails.text).toContain("firecrawl fallback body");
|
||||
expect(secondDetails.externalContent?.provider).toBe("perplexity-fetch");
|
||||
expect(secondDetails.text).toContain("perplexity-fetch fallback body");
|
||||
expect(secondDetails.cached).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -283,6 +283,7 @@ type WebFetchRuntimeParams = {
|
||||
allowRfc2544BenchmarkRange?: boolean;
|
||||
allowIpv6UniqueLocalRange?: boolean;
|
||||
};
|
||||
providerCacheKey?: string;
|
||||
lookupFn?: LookupFn;
|
||||
resolveProviderFallback: () => Promise<WebFetchProviderFallback>;
|
||||
};
|
||||
@@ -407,7 +408,7 @@ async function runWebFetch(params: WebFetchRuntimeParams): Promise<Record<string
|
||||
}
|
||||
: undefined;
|
||||
const cacheKey = normalizeCacheKey(
|
||||
`fetch:${params.url}:${params.extractMode}:${params.maxChars}${allowRfc2544BenchmarkRange ? ":allow-rfc2544" : ""}${allowIpv6UniqueLocalRange ? ":allow-ipv6-ula" : ""}${useTrustedEnvProxy ? ":trusted-env-proxy" : ""}`,
|
||||
`fetch:${params.url}:${params.extractMode}:${params.maxChars}${params.providerCacheKey ? `:provider:${params.providerCacheKey}` : ""}${allowRfc2544BenchmarkRange ? ":allow-rfc2544" : ""}${allowIpv6UniqueLocalRange ? ":allow-ipv6-ula" : ""}${useTrustedEnvProxy ? ":trusted-env-proxy" : ""}`,
|
||||
);
|
||||
const cached = readCache(FETCH_CACHE, cacheKey);
|
||||
if (cached) {
|
||||
@@ -641,6 +642,12 @@ export function createWebFetchTool(options?: {
|
||||
if (!resolveFetchEnabled({ fetch: executionFetch, sandboxed: options?.sandboxed })) {
|
||||
throw new Error("web_fetch is disabled.");
|
||||
}
|
||||
const providerCacheKey =
|
||||
normalizeOptionalLowercaseString(runtimeWebFetch?.selectedProvider) ??
|
||||
normalizeOptionalLowercaseString(runtimeWebFetch?.providerConfigured) ??
|
||||
(executionFetch && "provider" in executionFetch
|
||||
? normalizeOptionalLowercaseString(executionFetch.provider)
|
||||
: undefined);
|
||||
const readabilityEnabled = resolveFetchReadabilityEnabled(executionFetch);
|
||||
const userAgent =
|
||||
(executionFetch &&
|
||||
@@ -692,6 +699,7 @@ export function createWebFetchTool(options?: {
|
||||
config,
|
||||
useTrustedEnvProxy: resolveFetchUseTrustedEnvProxy(executionFetch),
|
||||
ssrfPolicy: executionFetch?.ssrfPolicy,
|
||||
...(providerCacheKey ? { providerCacheKey } : {}),
|
||||
lookupFn: options?.lookupFn,
|
||||
resolveProviderFallback,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user