mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
refactor: share web search time filters
This commit is contained in:
@@ -8,8 +8,7 @@ import {
|
||||
DEFAULT_SEARCH_COUNT,
|
||||
formatCliCommand,
|
||||
MAX_SEARCH_COUNT,
|
||||
normalizeFreshness,
|
||||
parseIsoDateRange,
|
||||
parseWebSearchTimeFilters,
|
||||
readCachedSearchPayload,
|
||||
readConfiguredSecretString,
|
||||
readPositiveIntegerParam,
|
||||
@@ -45,6 +44,7 @@ const BRAVE_SEARCH_ENDPOINT_PATH = "/res/v1/web/search";
|
||||
const BRAVE_LLM_CONTEXT_ENDPOINT_PATH = "/res/v1/llm/context";
|
||||
const braveHttpLogger = createSubsystemLogger("brave/http");
|
||||
type BraveEndpointMode = "selfHosted" | "strict";
|
||||
type BraveSearchMode = "llm-context" | "web";
|
||||
|
||||
type BraveSearchResult = {
|
||||
title?: string;
|
||||
@@ -158,6 +158,91 @@ function missingBraveKeyPayload() {
|
||||
};
|
||||
}
|
||||
|
||||
function setBraveSearchUrlParams(
|
||||
url: URL,
|
||||
params: {
|
||||
query: string;
|
||||
country?: string;
|
||||
search_lang?: string;
|
||||
freshness?: string;
|
||||
dateAfter?: string;
|
||||
dateBefore?: string;
|
||||
allowDateBeforeOnly?: boolean;
|
||||
},
|
||||
): void {
|
||||
url.searchParams.set("q", params.query);
|
||||
if (params.country) {
|
||||
url.searchParams.set("country", params.country);
|
||||
}
|
||||
if (params.search_lang) {
|
||||
url.searchParams.set("search_lang", params.search_lang);
|
||||
}
|
||||
if (params.freshness) {
|
||||
url.searchParams.set("freshness", params.freshness);
|
||||
} else if (params.dateAfter && params.dateBefore) {
|
||||
url.searchParams.set("freshness", `${params.dateAfter}to${params.dateBefore}`);
|
||||
} else if (params.dateAfter) {
|
||||
url.searchParams.set(
|
||||
"freshness",
|
||||
`${params.dateAfter}to${new Date().toISOString().slice(0, 10)}`,
|
||||
);
|
||||
} else if (params.allowDateBeforeOnly && params.dateBefore) {
|
||||
url.searchParams.set("freshness", `1970-01-01to${params.dateBefore}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function runBraveJsonRequest<T>(
|
||||
params: {
|
||||
baseUrl: string;
|
||||
endpointPath: string;
|
||||
endpointMode: BraveEndpointMode;
|
||||
mode: BraveSearchMode;
|
||||
apiKey: string;
|
||||
timeoutSeconds: number;
|
||||
diagnostics?: BraveHttpDiagnostics;
|
||||
configureUrl: (url: URL) => void;
|
||||
},
|
||||
errorLabel: string,
|
||||
): Promise<T> {
|
||||
const url = buildBraveEndpointUrl({
|
||||
baseUrl: params.baseUrl,
|
||||
endpointPath: params.endpointPath,
|
||||
});
|
||||
params.configureUrl(url);
|
||||
logBraveHttp(params.diagnostics, "request", {
|
||||
mode: params.mode,
|
||||
...describeBraveRequestUrl(url),
|
||||
});
|
||||
const startedAt = Date.now();
|
||||
const withEndpoint =
|
||||
params.endpointMode === "selfHosted"
|
||||
? withSelfHostedWebSearchEndpoint
|
||||
: withTrustedWebSearchEndpoint;
|
||||
return withEndpoint(
|
||||
{
|
||||
url: url.toString(),
|
||||
timeoutSeconds: params.timeoutSeconds,
|
||||
init: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"X-Subscription-Token": params.apiKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
async (response) => {
|
||||
logBraveHttp(params.diagnostics, "response", {
|
||||
mode: params.mode,
|
||||
status: response.status,
|
||||
ok: response.ok,
|
||||
durationMs: Date.now() - startedAt,
|
||||
});
|
||||
await assertOkOrThrowProviderError(response, errorLabel);
|
||||
return readProviderJsonResponse<T>(response, errorLabel);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async function runBraveLlmContextSearch(params: {
|
||||
baseUrl: string;
|
||||
endpointMode: BraveEndpointMode;
|
||||
@@ -179,65 +264,22 @@ async function runBraveLlmContextSearch(params: {
|
||||
}>;
|
||||
sources?: BraveLlmContextResponse["sources"];
|
||||
}> {
|
||||
const url = buildBraveEndpointUrl({
|
||||
baseUrl: params.baseUrl,
|
||||
endpointPath: BRAVE_LLM_CONTEXT_ENDPOINT_PATH,
|
||||
});
|
||||
url.searchParams.set("q", params.query);
|
||||
if (params.country) {
|
||||
url.searchParams.set("country", params.country);
|
||||
}
|
||||
if (params.search_lang) {
|
||||
url.searchParams.set("search_lang", params.search_lang);
|
||||
}
|
||||
if (params.freshness) {
|
||||
url.searchParams.set("freshness", params.freshness);
|
||||
} else if (params.dateAfter && params.dateBefore) {
|
||||
url.searchParams.set("freshness", `${params.dateAfter}to${params.dateBefore}`);
|
||||
} else if (params.dateAfter) {
|
||||
url.searchParams.set(
|
||||
"freshness",
|
||||
`${params.dateAfter}to${new Date().toISOString().slice(0, 10)}`,
|
||||
);
|
||||
}
|
||||
|
||||
logBraveHttp(params.diagnostics, "request", {
|
||||
mode: "llm-context",
|
||||
...describeBraveRequestUrl(url),
|
||||
});
|
||||
const startedAt = Date.now();
|
||||
const withEndpoint =
|
||||
params.endpointMode === "selfHosted"
|
||||
? withSelfHostedWebSearchEndpoint
|
||||
: withTrustedWebSearchEndpoint;
|
||||
return withEndpoint(
|
||||
const data = await runBraveJsonRequest<BraveLlmContextResponse>(
|
||||
{
|
||||
url: url.toString(),
|
||||
baseUrl: params.baseUrl,
|
||||
endpointPath: BRAVE_LLM_CONTEXT_ENDPOINT_PATH,
|
||||
mode: "llm-context",
|
||||
endpointMode: params.endpointMode,
|
||||
apiKey: params.apiKey,
|
||||
timeoutSeconds: params.timeoutSeconds,
|
||||
init: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"X-Subscription-Token": params.apiKey,
|
||||
},
|
||||
diagnostics: params.diagnostics,
|
||||
configureUrl: (url) => {
|
||||
setBraveSearchUrlParams(url, params);
|
||||
},
|
||||
},
|
||||
async (response) => {
|
||||
logBraveHttp(params.diagnostics, "response", {
|
||||
mode: "llm-context",
|
||||
status: response.status,
|
||||
ok: response.ok,
|
||||
durationMs: Date.now() - startedAt,
|
||||
});
|
||||
await assertOkOrThrowProviderError(response, "Brave LLM Context API error");
|
||||
|
||||
const data = await readProviderJsonResponse<BraveLlmContextResponse>(
|
||||
response,
|
||||
"Brave LLM Context API error",
|
||||
);
|
||||
return { results: mapBraveLlmContextResults(data), sources: data.sources };
|
||||
},
|
||||
"Brave LLM Context API error",
|
||||
);
|
||||
return { results: mapBraveLlmContextResults(data), sources: data.sources };
|
||||
}
|
||||
|
||||
async function runBraveWebSearch(params: {
|
||||
@@ -255,83 +297,41 @@ async function runBraveWebSearch(params: {
|
||||
dateAfter?: string;
|
||||
dateBefore?: string;
|
||||
}): Promise<Array<Record<string, unknown>>> {
|
||||
const url = buildBraveEndpointUrl({
|
||||
baseUrl: params.baseUrl,
|
||||
endpointPath: BRAVE_SEARCH_ENDPOINT_PATH,
|
||||
});
|
||||
url.searchParams.set("q", params.query);
|
||||
url.searchParams.set("count", String(params.count));
|
||||
if (params.country) {
|
||||
url.searchParams.set("country", params.country);
|
||||
}
|
||||
if (params.search_lang) {
|
||||
url.searchParams.set("search_lang", params.search_lang);
|
||||
}
|
||||
if (params.ui_lang) {
|
||||
url.searchParams.set("ui_lang", params.ui_lang);
|
||||
}
|
||||
if (params.freshness) {
|
||||
url.searchParams.set("freshness", params.freshness);
|
||||
} else if (params.dateAfter && params.dateBefore) {
|
||||
url.searchParams.set("freshness", `${params.dateAfter}to${params.dateBefore}`);
|
||||
} else if (params.dateAfter) {
|
||||
url.searchParams.set(
|
||||
"freshness",
|
||||
`${params.dateAfter}to${new Date().toISOString().slice(0, 10)}`,
|
||||
);
|
||||
} else if (params.dateBefore) {
|
||||
url.searchParams.set("freshness", `1970-01-01to${params.dateBefore}`);
|
||||
}
|
||||
|
||||
logBraveHttp(params.diagnostics, "request", {
|
||||
mode: "web",
|
||||
...describeBraveRequestUrl(url),
|
||||
});
|
||||
const startedAt = Date.now();
|
||||
const withEndpoint =
|
||||
params.endpointMode === "selfHosted"
|
||||
? withSelfHostedWebSearchEndpoint
|
||||
: withTrustedWebSearchEndpoint;
|
||||
return withEndpoint(
|
||||
const data = await runBraveJsonRequest<BraveSearchResponse>(
|
||||
{
|
||||
url: url.toString(),
|
||||
baseUrl: params.baseUrl,
|
||||
endpointPath: BRAVE_SEARCH_ENDPOINT_PATH,
|
||||
mode: "web",
|
||||
endpointMode: params.endpointMode,
|
||||
apiKey: params.apiKey,
|
||||
timeoutSeconds: params.timeoutSeconds,
|
||||
init: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"X-Subscription-Token": params.apiKey,
|
||||
},
|
||||
diagnostics: params.diagnostics,
|
||||
configureUrl: (url) => {
|
||||
setBraveSearchUrlParams(url, {
|
||||
...params,
|
||||
allowDateBeforeOnly: true,
|
||||
});
|
||||
url.searchParams.set("count", String(params.count));
|
||||
if (params.ui_lang) {
|
||||
url.searchParams.set("ui_lang", params.ui_lang);
|
||||
}
|
||||
},
|
||||
},
|
||||
async (response) => {
|
||||
logBraveHttp(params.diagnostics, "response", {
|
||||
mode: "web",
|
||||
status: response.status,
|
||||
ok: response.ok,
|
||||
durationMs: Date.now() - startedAt,
|
||||
});
|
||||
await assertOkOrThrowProviderError(response, "Brave Search API error");
|
||||
|
||||
const data = await readProviderJsonResponse<BraveSearchResponse>(
|
||||
response,
|
||||
"Brave Search API error",
|
||||
);
|
||||
const results = Array.isArray(data.web?.results) ? (data.web?.results ?? []) : [];
|
||||
return results.map((entry) => {
|
||||
const description = entry.description ?? "";
|
||||
const title = entry.title ?? "";
|
||||
const url = entry.url ?? "";
|
||||
return {
|
||||
title: title ? wrapWebContent(title, "web_search") : "",
|
||||
url,
|
||||
description: description ? wrapWebContent(description, "web_search") : "",
|
||||
published: entry.age || undefined,
|
||||
siteName: resolveSiteName(url) || undefined,
|
||||
};
|
||||
});
|
||||
},
|
||||
"Brave Search API error",
|
||||
);
|
||||
const results = Array.isArray(data.web?.results) ? (data.web?.results ?? []) : [];
|
||||
return results.map((entry) => {
|
||||
const description = entry.description ?? "";
|
||||
const title = entry.title ?? "";
|
||||
const url = entry.url ?? "";
|
||||
return {
|
||||
title: title ? wrapWebContent(title, "web_search") : "",
|
||||
url,
|
||||
description: description ? wrapWebContent(description, "web_search") : "",
|
||||
published: entry.age || undefined,
|
||||
siteName: resolveSiteName(url) || undefined,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function executeBraveSearch(
|
||||
@@ -392,37 +392,23 @@ export async function executeBraveSearch(
|
||||
}
|
||||
|
||||
const rawFreshness = readStringParam(args, "freshness");
|
||||
const freshness = rawFreshness ? normalizeFreshness(rawFreshness, "brave") : undefined;
|
||||
if (rawFreshness && !freshness) {
|
||||
return {
|
||||
error: "invalid_freshness",
|
||||
message: "freshness must be day, week, month, or year.",
|
||||
docs: "https://docs.openclaw.ai/tools/web",
|
||||
};
|
||||
}
|
||||
|
||||
const rawDateAfter = readStringParam(args, "date_after");
|
||||
const rawDateBefore = readStringParam(args, "date_before");
|
||||
if (rawFreshness && (rawDateAfter || rawDateBefore)) {
|
||||
return {
|
||||
error: "conflicting_time_filters",
|
||||
message:
|
||||
"freshness and date_after/date_before cannot be used together. Use either freshness (day/week/month/year) or a date range (date_after/date_before), not both.",
|
||||
docs: "https://docs.openclaw.ai/tools/web",
|
||||
};
|
||||
}
|
||||
const parsedDateRange = parseIsoDateRange({
|
||||
const parsedTimeFilters = parseWebSearchTimeFilters({
|
||||
rawDateAfter,
|
||||
rawDateBefore,
|
||||
rawFreshness,
|
||||
freshnessProvider: "brave",
|
||||
invalidFreshnessMessage: "freshness must be day, week, month, or year.",
|
||||
invalidDateAfterMessage: "date_after must be YYYY-MM-DD format.",
|
||||
invalidDateBeforeMessage: "date_before must be YYYY-MM-DD format.",
|
||||
invalidDateRangeMessage: "date_after must be before date_before.",
|
||||
});
|
||||
if ("error" in parsedDateRange) {
|
||||
return parsedDateRange;
|
||||
if ("error" in parsedTimeFilters) {
|
||||
return parsedTimeFilters;
|
||||
}
|
||||
|
||||
const { dateAfter, dateBefore } = parsedDateRange;
|
||||
const { freshness, dateAfter, dateBefore } = parsedTimeFilters;
|
||||
if (braveMode === "llm-context") {
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
if (dateAfter && !dateBefore && dateAfter > today) {
|
||||
|
||||
@@ -8,8 +8,7 @@ import {
|
||||
buildUnsupportedSearchFilterResponse,
|
||||
DEFAULT_SEARCH_COUNT,
|
||||
MAX_SEARCH_COUNT,
|
||||
normalizeFreshness,
|
||||
parseIsoDateRange,
|
||||
parseWebSearchTimeFilters,
|
||||
readCachedSearchPayload,
|
||||
readConfiguredSecretString,
|
||||
readPositiveIntegerParam,
|
||||
@@ -114,39 +113,24 @@ function resolveGeminiTimeRangeFilter(
|
||||
docs: string;
|
||||
} {
|
||||
const rawFreshness = readStringParam(args, "freshness");
|
||||
const freshness = rawFreshness
|
||||
? (normalizeFreshness(rawFreshness, "perplexity") as GeminiFreshness | undefined)
|
||||
: undefined;
|
||||
if (rawFreshness && !freshness) {
|
||||
return {
|
||||
error: "invalid_freshness",
|
||||
message: "freshness must be day, week, month, year, or the shortcuts pd, pw, pm, py.",
|
||||
docs: "https://docs.openclaw.ai/tools/web",
|
||||
};
|
||||
}
|
||||
|
||||
const rawDateAfter = readStringParam(args, "date_after");
|
||||
const rawDateBefore = readStringParam(args, "date_before");
|
||||
if (rawFreshness && (rawDateAfter || rawDateBefore)) {
|
||||
return {
|
||||
error: "conflicting_time_filters",
|
||||
message:
|
||||
"freshness and date_after/date_before cannot be used together. Use either freshness (day/week/month/year) or a date range (date_after/date_before), not both.",
|
||||
docs: "https://docs.openclaw.ai/tools/web",
|
||||
};
|
||||
}
|
||||
|
||||
const parsedDateRange = parseIsoDateRange({
|
||||
const parsedTimeFilters = parseWebSearchTimeFilters({
|
||||
rawDateAfter,
|
||||
rawDateBefore,
|
||||
rawFreshness,
|
||||
freshnessProvider: "perplexity",
|
||||
invalidFreshnessMessage:
|
||||
"freshness must be day, week, month, year, or the shortcuts pd, pw, pm, py.",
|
||||
invalidDateAfterMessage: "date_after must be YYYY-MM-DD format.",
|
||||
invalidDateBeforeMessage: "date_before must be YYYY-MM-DD format.",
|
||||
invalidDateRangeMessage: "date_after must be before date_before.",
|
||||
});
|
||||
if ("error" in parsedDateRange) {
|
||||
return parsedDateRange;
|
||||
if ("error" in parsedTimeFilters) {
|
||||
return parsedTimeFilters;
|
||||
}
|
||||
|
||||
const { freshness, dateAfter, dateBefore } = parsedTimeFilters;
|
||||
if (freshness) {
|
||||
return {
|
||||
timeRangeFilter: {
|
||||
@@ -156,7 +140,6 @@ function resolveGeminiTimeRangeFilter(
|
||||
};
|
||||
}
|
||||
|
||||
const { dateAfter, dateBefore } = parsedDateRange;
|
||||
if (!dateAfter && !dateBefore) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -189,6 +189,11 @@ const BRAVE_FRESHNESS_SHORTCUTS = new Set(["pd", "pw", "pm", "py"]);
|
||||
const BRAVE_FRESHNESS_RANGE = /^(\d{4}-\d{2}-\d{2})to(\d{4}-\d{2}-\d{2})$/;
|
||||
const PERPLEXITY_RECENCY_VALUES = new Set(["day", "week", "month", "year"]);
|
||||
|
||||
export type WebSearchFreshnessProvider = "brave" | "perplexity";
|
||||
export type WebSearchRecencyFreshness = "day" | "week" | "month" | "year";
|
||||
export type ParsedWebSearchFreshness<Provider extends WebSearchFreshnessProvider> =
|
||||
Provider extends "perplexity" ? WebSearchRecencyFreshness : string;
|
||||
|
||||
export const FRESHNESS_TO_RECENCY: Record<string, string> = {
|
||||
pd: "day",
|
||||
pw: "week",
|
||||
@@ -289,7 +294,7 @@ export function parseIsoDateRange(params: {
|
||||
|
||||
export function normalizeFreshness(
|
||||
value: string | undefined,
|
||||
provider: "brave" | "perplexity",
|
||||
provider: WebSearchFreshnessProvider,
|
||||
): string | undefined {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
@@ -319,6 +324,74 @@ export function normalizeFreshness(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function parseWebSearchTimeFilters<Provider extends WebSearchFreshnessProvider>(params: {
|
||||
rawFreshness?: string;
|
||||
rawDateAfter?: string;
|
||||
rawDateBefore?: string;
|
||||
freshnessProvider: Provider;
|
||||
invalidFreshnessMessage: string;
|
||||
invalidDateAfterMessage: string;
|
||||
invalidDateBeforeMessage: string;
|
||||
invalidDateRangeMessage: string;
|
||||
conflictingTimeFiltersMessage?: string;
|
||||
docs?: string;
|
||||
}):
|
||||
| {
|
||||
freshness?: ParsedWebSearchFreshness<Provider>;
|
||||
dateAfter?: string;
|
||||
dateBefore?: string;
|
||||
}
|
||||
| {
|
||||
error:
|
||||
| "invalid_freshness"
|
||||
| "invalid_date"
|
||||
| "invalid_date_range"
|
||||
| "conflicting_time_filters";
|
||||
message: string;
|
||||
docs: string;
|
||||
} {
|
||||
const docs = params.docs ?? "https://docs.openclaw.ai/tools/web";
|
||||
const freshness = params.rawFreshness
|
||||
? normalizeFreshness(params.rawFreshness, params.freshnessProvider)
|
||||
: undefined;
|
||||
if (params.rawFreshness && !freshness) {
|
||||
return {
|
||||
error: "invalid_freshness",
|
||||
message: params.invalidFreshnessMessage,
|
||||
docs,
|
||||
};
|
||||
}
|
||||
|
||||
if (params.rawFreshness && (params.rawDateAfter || params.rawDateBefore)) {
|
||||
return {
|
||||
error: "conflicting_time_filters",
|
||||
message:
|
||||
params.conflictingTimeFiltersMessage ??
|
||||
"freshness and date_after/date_before cannot be used together. Use either freshness (day/week/month/year) or a date range (date_after/date_before), not both.",
|
||||
docs,
|
||||
};
|
||||
}
|
||||
|
||||
const parsedDateRange = parseIsoDateRange({
|
||||
rawDateAfter: params.rawDateAfter,
|
||||
rawDateBefore: params.rawDateBefore,
|
||||
invalidDateAfterMessage: params.invalidDateAfterMessage,
|
||||
invalidDateBeforeMessage: params.invalidDateBeforeMessage,
|
||||
invalidDateRangeMessage: params.invalidDateRangeMessage,
|
||||
docs,
|
||||
});
|
||||
if ("error" in parsedDateRange) {
|
||||
return parsedDateRange;
|
||||
}
|
||||
|
||||
return freshness
|
||||
? {
|
||||
freshness: freshness as ParsedWebSearchFreshness<Provider>,
|
||||
...parsedDateRange,
|
||||
}
|
||||
: parsedDateRange;
|
||||
}
|
||||
|
||||
export function readCachedSearchPayload(cacheKey: string): Record<string, unknown> | undefined {
|
||||
const cached = readCache(SEARCH_CACHE, cacheKey);
|
||||
return cached ? { ...cached.value, cached: true } : undefined;
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
isoToPerplexityDate,
|
||||
normalizeToIsoDate,
|
||||
normalizeFreshness,
|
||||
parseWebSearchTimeFilters,
|
||||
} from "./web-search-provider-common.js";
|
||||
import { mergeScopedSearchConfig } from "./web-search-provider-config.js";
|
||||
import { createWebSearchTool } from "./web-search.js";
|
||||
@@ -88,6 +89,52 @@ describe("web_search date normalization", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("web_search time filter parsing", () => {
|
||||
const baseMessages = {
|
||||
invalidFreshnessMessage: "bad freshness",
|
||||
invalidDateAfterMessage: "bad after",
|
||||
invalidDateBeforeMessage: "bad before",
|
||||
invalidDateRangeMessage: "bad range",
|
||||
};
|
||||
|
||||
it("normalizes freshness shortcuts for providers", () => {
|
||||
expect(
|
||||
parseWebSearchTimeFilters({
|
||||
rawFreshness: "pd",
|
||||
freshnessProvider: "perplexity",
|
||||
...baseMessages,
|
||||
}),
|
||||
).toEqual({ freshness: "day" });
|
||||
});
|
||||
|
||||
it("rejects conflicting freshness and date filters", () => {
|
||||
expect(
|
||||
parseWebSearchTimeFilters({
|
||||
rawFreshness: "week",
|
||||
rawDateAfter: "2026-01-01",
|
||||
freshnessProvider: "brave",
|
||||
...baseMessages,
|
||||
}),
|
||||
).toEqual({
|
||||
error: "conflicting_time_filters",
|
||||
message:
|
||||
"freshness and date_after/date_before cannot be used together. Use either freshness (day/week/month/year) or a date range (date_after/date_before), not both.",
|
||||
docs: "https://docs.openclaw.ai/tools/web",
|
||||
});
|
||||
});
|
||||
|
||||
it("parses date bounds through the shared ISO range validator", () => {
|
||||
expect(
|
||||
parseWebSearchTimeFilters({
|
||||
rawDateAfter: "2026-01-01",
|
||||
rawDateBefore: "2026-01-31",
|
||||
freshnessProvider: "brave",
|
||||
...baseMessages,
|
||||
}),
|
||||
).toEqual({ dateAfter: "2026-01-01", dateBefore: "2026-01-31" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("web_search unsupported filter response", () => {
|
||||
it("returns undefined when no unsupported filter is set", () => {
|
||||
expect(buildUnsupportedSearchFilterResponse({ query: "openclaw" }, "gemini")).toBeUndefined();
|
||||
|
||||
@@ -26,6 +26,7 @@ export {
|
||||
normalizeFreshness,
|
||||
normalizeToIsoDate,
|
||||
parseIsoDateRange,
|
||||
parseWebSearchTimeFilters,
|
||||
readCachedSearchPayload,
|
||||
readConfiguredSecretString,
|
||||
readProviderEnvValue,
|
||||
|
||||
Reference in New Issue
Block a user