fix(web-search): share unsupported filter handling

This commit is contained in:
Vincent Koc
2026-03-19 23:39:45 -07:00
parent ce878a9eb1
commit 6c7526f8a0
6 changed files with 101 additions and 48 deletions

View File

@@ -1,6 +1,7 @@
import { Type } from "@sinclair/typebox";
import {
buildSearchCacheKey,
buildUnsupportedSearchFilterResponse,
DEFAULT_SEARCH_COUNT,
MAX_SEARCH_COUNT,
readCachedSearchPayload,
@@ -177,22 +178,9 @@ function createGeminiToolDefinition(
parameters: createGeminiSchema(),
execute: async (args) => {
const params = args as Record<string, unknown>;
for (const name of ["country", "language", "freshness", "date_after", "date_before"]) {
if (readStringParam(params, name)) {
const label =
name === "country"
? "country filtering"
: name === "language"
? "language filtering"
: name === "freshness"
? "freshness filtering"
: "date_after/date_before filtering";
return {
error: name.startsWith("date_") ? "unsupported_date_filter" : `unsupported_${name}`,
message: `${label} is not supported by the gemini provider. Only Brave and Perplexity support ${name === "country" ? "country filtering" : name === "language" ? "language filtering" : name === "freshness" ? "freshness" : "date filtering"}.`,
docs: "https://docs.openclaw.ai/tools/web",
};
}
const unsupportedResponse = buildUnsupportedSearchFilterResponse(params, "gemini");
if (unsupportedResponse) {
return unsupportedResponse;
}
const geminiConfig = resolveGeminiConfig(searchConfig);

View File

@@ -1,6 +1,7 @@
import { Type } from "@sinclair/typebox";
import {
buildSearchCacheKey,
buildUnsupportedSearchFilterResponse,
DEFAULT_SEARCH_COUNT,
MAX_SEARCH_COUNT,
readCachedSearchPayload,
@@ -246,22 +247,9 @@ function createKimiToolDefinition(
parameters: createKimiSchema(),
execute: async (args) => {
const params = args as Record<string, unknown>;
for (const name of ["country", "language", "freshness", "date_after", "date_before"]) {
if (readStringParam(params, name)) {
const label =
name === "country"
? "country filtering"
: name === "language"
? "language filtering"
: name === "freshness"
? "freshness filtering"
: "date_after/date_before filtering";
return {
error: name.startsWith("date_") ? "unsupported_date_filter" : `unsupported_${name}`,
message: `${label} is not supported by the kimi provider. Only Brave and Perplexity support ${name === "country" ? "country filtering" : name === "language" ? "language filtering" : name === "freshness" ? "freshness" : "date filtering"}.`,
docs: "https://docs.openclaw.ai/tools/web",
};
}
const unsupportedResponse = buildUnsupportedSearchFilterResponse(params, "kimi");
if (unsupportedResponse) {
return unsupportedResponse;
}
const kimiConfig = resolveKimiConfig(searchConfig);

View File

@@ -1,6 +1,7 @@
import { Type } from "@sinclair/typebox";
import {
buildSearchCacheKey,
buildUnsupportedSearchFilterResponse,
DEFAULT_SEARCH_COUNT,
MAX_SEARCH_COUNT,
readCachedSearchPayload,
@@ -188,22 +189,9 @@ function createGrokToolDefinition(
parameters: createGrokSchema(),
execute: async (args) => {
const params = args as Record<string, unknown>;
for (const name of ["country", "language", "freshness", "date_after", "date_before"]) {
if (readStringParam(params, name)) {
const label =
name === "country"
? "country filtering"
: name === "language"
? "language filtering"
: name === "freshness"
? "freshness filtering"
: "date_after/date_before filtering";
return {
error: name.startsWith("date_") ? "unsupported_date_filter" : `unsupported_${name}`,
message: `${label} is not supported by the grok provider. Only Brave and Perplexity support ${name === "country" ? "country filtering" : name === "language" ? "language filtering" : name === "freshness" ? "freshness" : "date filtering"}.`,
docs: "https://docs.openclaw.ai/tools/web",
};
}
const unsupportedResponse = buildUnsupportedSearchFilterResponse(params, "grok");
if (unsupportedResponse) {
return unsupportedResponse;
}
const grokConfig = resolveGrokConfig(searchConfig);

View File

@@ -21,6 +21,13 @@ export type SearchConfigRecord = (NonNullable<OpenClawConfig["tools"]>["web"] ex
: never) &
Record<string, unknown>;
type UnsupportedWebSearchFilterName =
| "country"
| "language"
| "freshness"
| "date_after"
| "date_before";
export const DEFAULT_SEARCH_COUNT = 5;
export const MAX_SEARCH_COUNT = 10;
@@ -210,3 +217,59 @@ export function writeCachedSearchPayload(
): void {
writeCache(SEARCH_CACHE, cacheKey, payload, ttlMs);
}
function readUnsupportedSearchFilter(
params: Record<string, unknown>,
): UnsupportedWebSearchFilterName | undefined {
for (const name of ["country", "language", "freshness", "date_after", "date_before"] as const) {
const value = params[name];
if (typeof value === "string" && value.trim()) {
return name;
}
}
return undefined;
}
function describeUnsupportedSearchFilter(name: UnsupportedWebSearchFilterName): string {
switch (name) {
case "country":
return "country filtering";
case "language":
return "language filtering";
case "freshness":
return "freshness filtering";
case "date_after":
case "date_before":
return "date_after/date_before filtering";
}
}
export function buildUnsupportedSearchFilterResponse(
params: Record<string, unknown>,
provider: string,
docs = "https://docs.openclaw.ai/tools/web",
):
| {
error: string;
message: string;
docs: string;
}
| undefined {
const unsupported = readUnsupportedSearchFilter(params);
if (!unsupported) {
return undefined;
}
const label = describeUnsupportedSearchFilter(unsupported);
const supportedLabel =
unsupported === "date_after" || unsupported === "date_before" ? "date filtering" : label;
return {
error: unsupported.startsWith("date_")
? "unsupported_date_filter"
: `unsupported_${unsupported}`,
message: `${label} is not supported by the ${provider} provider. Only Brave and Perplexity support ${supportedLabel}.`,
docs,
};
}

View File

@@ -3,6 +3,7 @@ import { __testing as braveTesting } from "../../../extensions/brave/src/brave-w
import { __testing as moonshotTesting } from "../../../extensions/moonshot/src/kimi-web-search-provider.js";
import { __testing as perplexityTesting } from "../../../extensions/perplexity/web-search-provider.js";
import { __testing as xaiTesting } from "../../../extensions/xai/src/grok-web-search-provider.js";
import { buildUnsupportedSearchFilterResponse } from "../../plugin-sdk/provider-web-search.js";
import { withEnv } from "../../test-utils/env.js";
const {
inferPerplexityBaseUrlFromApiKey,
@@ -198,6 +199,30 @@ describe("web_search date normalization", () => {
});
});
describe("web_search unsupported filter response", () => {
it("returns undefined when no unsupported filter is set", () => {
expect(buildUnsupportedSearchFilterResponse({ query: "openclaw" }, "gemini")).toBeUndefined();
});
it("maps non-date filters to provider-specific unsupported errors", () => {
expect(buildUnsupportedSearchFilterResponse({ country: "us" }, "grok")).toEqual({
error: "unsupported_country",
message:
"country filtering is not supported by the grok provider. Only Brave and Perplexity support country filtering.",
docs: "https://docs.openclaw.ai/tools/web",
});
});
it("collapses date filters to unsupported_date_filter", () => {
expect(buildUnsupportedSearchFilterResponse({ date_before: "2026-03-19" }, "kimi")).toEqual({
error: "unsupported_date_filter",
message:
"date_after/date_before filtering is not supported by the kimi provider. Only Brave and Perplexity support date filtering.",
docs: "https://docs.openclaw.ai/tools/web",
});
});
});
describe("web_search kimi config resolution", () => {
it("uses config apiKey when provided", () => {
expect(resolveKimiApiKey({ apiKey: "kimi-test-key" })).toBe("kimi-test-key");

View File

@@ -9,6 +9,7 @@ export { readNumberParam, readStringArrayParam, readStringParam } from "../agent
export { resolveCitationRedirectUrl } from "../agents/tools/web-search-citation-redirect.js";
export {
buildSearchCacheKey,
buildUnsupportedSearchFilterResponse,
DEFAULT_SEARCH_COUNT,
FRESHNESS_TO_RECENCY,
isoToPerplexityDate,