refactor(perplexity): unify search option validation (#129877)

This commit is contained in:
Peter Steinberger
2026-08-25 22:05:50 -07:00
committed by GitHub
parent aa2643d6d3
commit 87ff446588
2 changed files with 177 additions and 70 deletions
@@ -6,7 +6,7 @@ import {
isoToPerplexityDate,
MAX_SEARCH_COUNT,
normalizeFreshness,
normalizeToIsoDate,
parseWebSearchTimeFilters,
readCachedSearchPayload,
readConfiguredSecretString,
readPositiveIntegerParam,
@@ -361,45 +361,26 @@ export async function executePerplexitySearch(
});
if (!structured) {
if (country) {
return {
error: "unsupported_country",
message:
"country filtering is only supported by the native Perplexity Search API path. Remove Perplexity baseUrl/model overrides or use a direct PERPLEXITY_API_KEY to enable it.",
docs: "https://docs.openclaw.ai/tools/web",
};
}
if (language) {
return {
error: "unsupported_language",
message:
"language filtering is only supported by the native Perplexity Search API path. Remove Perplexity baseUrl/model overrides or use a direct PERPLEXITY_API_KEY to enable it.",
docs: "https://docs.openclaw.ai/tools/web",
};
}
if (rawDateAfter || rawDateBefore) {
return {
error: "unsupported_date_filter",
message:
"date_after/date_before are only supported by the native Perplexity Search API path. Remove Perplexity baseUrl/model overrides or use a direct PERPLEXITY_API_KEY to enable them.",
docs: "https://docs.openclaw.ai/tools/web",
};
}
if (domainFilter?.length) {
return {
error: "unsupported_domain_filter",
message:
"domain_filter is only supported by the native Perplexity Search API path. Remove Perplexity baseUrl/model overrides or use a direct PERPLEXITY_API_KEY to enable it.",
docs: "https://docs.openclaw.ai/tools/web",
};
}
if (maxTokens !== undefined || maxTokensPerPage !== undefined) {
return {
error: "unsupported_content_budget",
message:
"max_tokens and max_tokens_per_page are only supported by the native Perplexity Search API path. Remove Perplexity baseUrl/model overrides or use a direct PERPLEXITY_API_KEY to enable them.",
docs: "https://docs.openclaw.ai/tools/web",
};
const unsupportedOptions = [
[country, "unsupported_country", "country filtering", "it"],
[language, "unsupported_language", "language filtering", "it"],
[rawDateAfter || rawDateBefore, "unsupported_date_filter", "date_after/date_before", "them"],
[domainFilter?.length, "unsupported_domain_filter", "domain_filter", "it"],
[
maxTokens !== undefined || maxTokensPerPage !== undefined,
"unsupported_content_budget",
"max_tokens and max_tokens_per_page",
"them",
],
] as const;
for (const [value, error, option, pronoun] of unsupportedOptions) {
if (value) {
return {
error,
message: `${option} ${pronoun === "them" ? "are" : "is"} only supported by the native Perplexity Search API path. Remove Perplexity baseUrl/model overrides or use a direct PERPLEXITY_API_KEY to enable ${pronoun}.`,
docs: "https://docs.openclaw.ai/tools/web",
};
}
}
}
@@ -410,37 +391,20 @@ export async function executePerplexitySearch(
docs: "https://docs.openclaw.ai/tools/web",
};
}
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 dateAfter = rawDateAfter ? normalizeToIsoDate(rawDateAfter) : undefined;
const dateBefore = rawDateBefore ? normalizeToIsoDate(rawDateBefore) : undefined;
if (rawDateAfter && !dateAfter) {
return {
error: "invalid_date",
message: "date_after must be YYYY-MM-DD format.",
docs: "https://docs.openclaw.ai/tools/web",
};
}
if (rawDateBefore && !dateBefore) {
return {
error: "invalid_date",
message: "date_before must be YYYY-MM-DD format.",
docs: "https://docs.openclaw.ai/tools/web",
};
}
if (dateAfter && dateBefore && dateAfter > dateBefore) {
return {
error: "invalid_date_range",
message: "date_after must be before date_before.",
docs: "https://docs.openclaw.ai/tools/web",
};
const parsedTimeFilters = parseWebSearchTimeFilters({
rawFreshness,
rawDateAfter,
rawDateBefore,
freshnessProvider: "perplexity",
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 parsedTimeFilters) {
return parsedTimeFilters;
}
const { dateAfter, dateBefore } = parsedTimeFilters;
if (domainFilter?.length) {
const hasDeny = domainFilter.some((entry) => entry.startsWith("-"));
const hasAllow = domainFilter.some((entry) => !entry.startsWith("-"));
@@ -69,6 +69,149 @@ describe("perplexity web search provider", () => {
);
});
it.each([
{
name: "country before every other unsupported chat option",
structured: false,
args: {
country: "US",
language: "en",
date_after: "2024-01-01",
domain_filter: ["a.test"],
max_tokens: 1,
},
error: "unsupported_country",
message:
"country filtering is only supported by the native Perplexity Search API path. Remove Perplexity baseUrl/model overrides or use a direct PERPLEXITY_API_KEY to enable it.",
},
{
name: "language before unsupported chat dates, domains, and budget",
structured: false,
args: { language: "en", date_after: "2024-01-01", domain_filter: ["a.test"], max_tokens: 1 },
error: "unsupported_language",
message:
"language filtering is only supported by the native Perplexity Search API path. Remove Perplexity baseUrl/model overrides or use a direct PERPLEXITY_API_KEY to enable it.",
},
{
name: "date before unsupported chat domains and budget",
structured: false,
args: { date_after: "2024-01-01", domain_filter: ["a.test"], max_tokens: 1 },
error: "unsupported_date_filter",
message:
"date_after/date_before are only supported by the native Perplexity Search API path. Remove Perplexity baseUrl/model overrides or use a direct PERPLEXITY_API_KEY to enable them.",
},
{
name: "unsupported chat language before language validation and dates",
structured: false,
args: { language: "invalid", date_after: "2024-01-01" },
error: "unsupported_language",
message:
"language filtering is only supported by the native Perplexity Search API path. Remove Perplexity baseUrl/model overrides or use a direct PERPLEXITY_API_KEY to enable it.",
},
{
name: "unsupported chat date before a valid freshness conflict",
structured: false,
args: { freshness: "day", date_after: "2024-01-01" },
error: "unsupported_date_filter",
message:
"date_after/date_before are only supported by the native Perplexity Search API path. Remove Perplexity baseUrl/model overrides or use a direct PERPLEXITY_API_KEY to enable them.",
},
{
name: "domain before unsupported chat content budget",
structured: false,
args: { domain_filter: ["a.test"], max_tokens: 1 },
error: "unsupported_domain_filter",
message:
"domain_filter is only supported by the native Perplexity Search API path. Remove Perplexity baseUrl/model overrides or use a direct PERPLEXITY_API_KEY to enable it.",
},
{
name: "unsupported chat content budget",
structured: false,
args: { max_tokens_per_page: 1 },
error: "unsupported_content_budget",
message:
"max_tokens and max_tokens_per_page are only supported by the native Perplexity Search API path. Remove Perplexity baseUrl/model overrides or use a direct PERPLEXITY_API_KEY to enable them.",
},
{
name: "invalid freshness before reading an invalid native budget",
structured: true,
args: { freshness: "invalid", max_tokens: 0 },
error: "invalid_freshness",
message: "freshness must be day, week, month, or year.",
},
{
name: "invalid freshness before reading an invalid chat budget",
structured: false,
args: { freshness: "invalid", country: "US", max_tokens: 0 },
error: "invalid_freshness",
message: "freshness must be day, week, month, or year.",
},
{
name: "invalid native language before conflicting date filters",
structured: true,
args: { language: "invalid", freshness: "day", date_after: "invalid" },
error: "invalid_language",
message: "language must be a 2-letter ISO 639-1 code like 'en', 'de', or 'fr'.",
},
{
name: "conflicting freshness before invalid date format",
structured: true,
args: { freshness: "day", date_after: "invalid" },
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.",
},
{
name: "invalid date_after before invalid date_before",
structured: true,
args: { date_after: "invalid", date_before: "also-invalid" },
error: "invalid_date",
message: "date_after must be YYYY-MM-DD format.",
},
{
name: "invalid date_before after valid date_after",
structured: true,
args: { date_after: "2024-01-01", date_before: "invalid" },
error: "invalid_date",
message: "date_before must be YYYY-MM-DD format.",
},
{
name: "invalid chronological date range",
structured: true,
args: { date_after: "2024-06-01", date_before: "2024-01-01" },
error: "invalid_date_range",
message: "date_after must be before date_before.",
},
{
name: "invalid date before mixed native domain filters",
structured: true,
args: { date_after: "invalid", domain_filter: ["allowed.test", "-denied.test"] },
error: "invalid_date",
message: "date_after must be YYYY-MM-DD format.",
},
])(
"preserves provider validation precedence: $name",
async ({ structured, args, error, message }) => {
await expect(
createConfiguredPerplexityTool(structured).execute({ query: "validation", ...args }),
).resolves.toEqual({
error,
message,
docs: "https://docs.openclaw.ai/tools/web",
});
},
);
it("validates chat token budgets before unsupported country precedence", async () => {
await expect(
createConfiguredPerplexityTool(false).execute({
query: "validation",
country: "US",
max_tokens: 0,
}),
).rejects.toThrow("max_tokens must be a positive integer.");
});
it.each([
{ name: "native Search API", webSearch: { apiKey: "pplx-test" } },
{