fix: validate web search count integers · openclaw/openclaw@59d4327
steipete
·
2026-05-29
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
@@ -7,11 +7,12 @@ import {
|
7 | 7 | buildSearchCacheKey, |
8 | 8 | DEFAULT_SEARCH_COUNT, |
9 | 9 | formatCliCommand, |
| 10 | +MAX_SEARCH_COUNT, |
10 | 11 | normalizeFreshness, |
11 | 12 | parseIsoDateRange, |
12 | 13 | readCachedSearchPayload, |
13 | 14 | readConfiguredSecretString, |
14 | | -readNumberParam, |
| 15 | +readPositiveIntegerParam, |
15 | 16 | readProviderEnvValue, |
16 | 17 | readStringParam, |
17 | 18 | resolveSearchCacheTtlMs, |
@@ -351,7 +352,12 @@ export async function executeBraveSearch(
|
351 | 352 | const braveEndpointMode = await validateBraveBaseUrl(braveBaseUrl); |
352 | 353 | const query = readStringParam(args, "query", { required: true }); |
353 | 354 | const count = |
354 | | -readNumberParam(args, "count", { integer: true }) ?? searchConfig?.maxResults ?? undefined; |
| 355 | +readPositiveIntegerParam(args, "count", { |
| 356 | +max: MAX_SEARCH_COUNT, |
| 357 | +message: `count must be an integer from 1 to ${MAX_SEARCH_COUNT}.`, |
| 358 | +}) ?? |
| 359 | +searchConfig?.maxResults ?? |
| 360 | +undefined; |
355 | 361 | const country = normalizeBraveCountry(readStringParam(args, "country")); |
356 | 362 | const language = readStringParam(args, "language"); |
357 | 363 | const search_lang = readStringParam(args, "search_lang"); |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -28,7 +28,7 @@ const BraveSearchSchema = {
|
28 | 28 | properties: { |
29 | 29 | query: { type: "string", description: "Search query string." }, |
30 | 30 | count: { |
31 | | -type: "number", |
| 31 | +type: "integer", |
32 | 32 | description: "Number of results to return (1-10).", |
33 | 33 | minimum: 1, |
34 | 34 | maximum: 10, |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -85,6 +85,24 @@ describe("duckduckgo web search provider", () => {
|
85 | 85 | }); |
86 | 86 | }); |
87 | 87 | |
| 88 | +it("rejects fractional and out-of-range counts before searching", async () => { |
| 89 | +const provider = createDuckDuckGoWebSearchProvider(); |
| 90 | +const tool = provider.createTool({ |
| 91 | +config: { test: true }, |
| 92 | +} as never); |
| 93 | +if (!tool) { |
| 94 | +throw new Error("Expected tool definition"); |
| 95 | +} |
| 96 | + |
| 97 | +await expect(tool.execute({ query: "openclaw docs", count: 4.5 })).rejects.toThrow( |
| 98 | +"count must be an integer from 1 to 10.", |
| 99 | +); |
| 100 | +await expect(tool.execute({ query: "openclaw docs", count: 11 })).rejects.toThrow( |
| 101 | +"count must be an integer from 1 to 10.", |
| 102 | +); |
| 103 | +expect(runDuckDuckGoSearch).not.toHaveBeenCalled(); |
| 104 | +}); |
| 105 | + |
88 | 106 | it("reads region from plugin config and normalizes empty values away", () => { |
89 | 107 | expect( |
90 | 108 | resolveDdgRegion({ |
|
| Original file line number | Diff line number | Diff line change |
|---|
|
1 | | -import { readNumberParam, readStringParam } from "openclaw/plugin-sdk/param-readers"; |
| 1 | +import { readPositiveIntegerParam, readStringParam } from "openclaw/plugin-sdk/param-readers"; |
2 | 2 | import type { WebSearchProviderPlugin } from "openclaw/plugin-sdk/provider-web-search-contract"; |
3 | 3 | import { createDuckDuckGoWebSearchProviderBase } from "./ddg-search-provider.shared.js"; |
4 | 4 | |
@@ -16,7 +16,7 @@ const DuckDuckGoSearchSchema = {
|
16 | 16 | properties: { |
17 | 17 | query: { type: "string", description: "Search query string." }, |
18 | 18 | count: { |
19 | | -type: "number", |
| 19 | +type: "integer", |
20 | 20 | description: "Number of results to return (1-10).", |
21 | 21 | minimum: 1, |
22 | 22 | maximum: 10, |
@@ -45,7 +45,10 @@ export function createDuckDuckGoWebSearchProvider(): WebSearchProviderPlugin {
|
45 | 45 | return await runDuckDuckGoSearch({ |
46 | 46 | config: ctx.config, |
47 | 47 | query: readStringParam(args, "query", { required: true }), |
48 | | -count: readNumberParam(args, "count", { integer: true }), |
| 48 | +count: readPositiveIntegerParam(args, "count", { |
| 49 | +max: 10, |
| 50 | +message: "count must be an integer from 1 to 10.", |
| 51 | +}), |
49 | 52 | region: readStringParam(args, "region"), |
50 | 53 | safeSearch: readStringParam(args, "safeSearch") as |
51 | 54 | | "strict" |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -5,7 +5,7 @@ import {
|
5 | 5 | parseIsoDateRange, |
6 | 6 | readCachedSearchPayload, |
7 | 7 | readConfiguredSecretString, |
8 | | -readNumberParam, |
| 8 | +readPositiveIntegerParam, |
9 | 9 | readProviderEnvValue, |
10 | 10 | readStringParam, |
11 | 11 | resolveProviderWebSearchPluginConfig, |
@@ -474,7 +474,12 @@ export async function executeExaWebSearchProviderTool(
|
474 | 474 | ? (rawType as ExaSearchType) |
475 | 475 | : "auto"; |
476 | 476 | const count = |
477 | | -readNumberParam(params, "count", { integer: true }) ?? searchConfig?.maxResults ?? undefined; |
| 477 | +readPositiveIntegerParam(params, "count", { |
| 478 | +max: EXA_MAX_SEARCH_COUNT, |
| 479 | +message: `count must be an integer from 1 to ${EXA_MAX_SEARCH_COUNT}.`, |
| 480 | +}) ?? |
| 481 | +searchConfig?.maxResults ?? |
| 482 | +undefined; |
478 | 483 | const rawFreshness = readStringParam(params, "freshness"); |
479 | 484 | const freshness = normalizeExaFreshness(rawFreshness); |
480 | 485 | if (rawFreshness && !freshness) { |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -19,7 +19,7 @@ const ExaSearchSchema = {
|
19 | 19 | properties: { |
20 | 20 | query: { type: "string", description: "Search query string." }, |
21 | 21 | count: { |
22 | | -type: "number", |
| 22 | +type: "integer", |
23 | 23 | description: "Number of results to return (1-100, subject to Exa search-type limits).", |
24 | 24 | minimum: 1, |
25 | 25 | maximum: EXA_MAX_SEARCH_COUNT, |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -7,11 +7,12 @@ import {
|
7 | 7 | buildSearchCacheKey, |
8 | 8 | buildUnsupportedSearchFilterResponse, |
9 | 9 | DEFAULT_SEARCH_COUNT, |
| 10 | +MAX_SEARCH_COUNT, |
10 | 11 | normalizeFreshness, |
11 | 12 | parseIsoDateRange, |
12 | 13 | readCachedSearchPayload, |
13 | 14 | readConfiguredSecretString, |
14 | | -readNumberParam, |
| 15 | +readPositiveIntegerParam, |
15 | 16 | readProviderEnvValue, |
16 | 17 | readStringParam, |
17 | 18 | resolveCitationRedirectUrl, |
@@ -318,7 +319,12 @@ export async function executeGeminiSearch(
|
318 | 319 | |
319 | 320 | const query = readStringParam(args, "query", { required: true }); |
320 | 321 | const count = |
321 | | -readNumberParam(args, "count", { integer: true }) ?? searchConfig?.maxResults ?? undefined; |
| 322 | +readPositiveIntegerParam(args, "count", { |
| 323 | +max: MAX_SEARCH_COUNT, |
| 324 | +message: `count must be an integer from 1 to ${MAX_SEARCH_COUNT}.`, |
| 325 | +}) ?? |
| 326 | +searchConfig?.maxResults ?? |
| 327 | +undefined; |
322 | 328 | const model = resolveGeminiModel(geminiConfig); |
323 | 329 | const baseUrl = resolveGeminiBaseUrl(geminiConfig); |
324 | 330 | const cacheKey = buildSearchCacheKey([ |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -30,7 +30,7 @@ const GEMINI_TOOL_PARAMETERS = {
|
30 | 30 | properties: { |
31 | 31 | query: { type: "string", description: "Search query string." }, |
32 | 32 | count: { |
33 | | -type: "number", |
| 33 | +type: "integer", |
34 | 34 | description: "Number of results to return (1-10).", |
35 | 35 | minimum: 1, |
36 | 36 | maximum: 10, |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -10,9 +10,10 @@ import {
|
10 | 10 | mergeScopedSearchConfig, |
11 | 11 | readCachedSearchPayload, |
12 | 12 | readConfiguredSecretString, |
13 | | -readNumberParam, |
| 13 | +readPositiveIntegerParam, |
14 | 14 | readProviderEnvValue, |
15 | 15 | readStringParam, |
| 16 | +MAX_SEARCH_COUNT, |
16 | 17 | resolveProviderWebSearchPluginConfig, |
17 | 18 | resolveSearchCacheTtlMs, |
18 | 19 | resolveSearchCount, |
@@ -214,7 +215,12 @@ export async function executeMiniMaxWebSearchProviderTool(
|
214 | 215 | const params = args; |
215 | 216 | const query = readStringParam(params, "query", { required: true }); |
216 | 217 | const count = |
217 | | -readNumberParam(params, "count", { integer: true }) ?? searchConfig?.maxResults ?? undefined; |
| 218 | +readPositiveIntegerParam(params, "count", { |
| 219 | +max: MAX_SEARCH_COUNT, |
| 220 | +message: `count must be an integer from 1 to ${MAX_SEARCH_COUNT}.`, |
| 221 | +}) ?? |
| 222 | +searchConfig?.maxResults ?? |
| 223 | +undefined; |
218 | 224 | |
219 | 225 | const resolvedCount = resolveSearchCount(count, DEFAULT_SEARCH_COUNT); |
220 | 226 | const endpoint = resolveMiniMaxEndpoint(searchConfig, config); |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -25,7 +25,7 @@ const MiniMaxSearchSchema = {
|
25 | 25 | properties: { |
26 | 26 | query: { type: "string", description: "Search query string." }, |
27 | 27 | count: { |
28 | | -type: "number", |
| 28 | +type: "integer", |
29 | 29 | description: "Number of results to return (1-10).", |
30 | 30 | minimum: 1, |
31 | 31 | maximum: 10, |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。