diff --git a/scripts/bench-web-fetch.ts b/scripts/bench-web-fetch.ts index 132fc1a5c310..1ed2b19241ec 100644 --- a/scripts/bench-web-fetch.ts +++ b/scripts/bench-web-fetch.ts @@ -7,7 +7,7 @@ import { createWebFetchTool } from "../src/agents/tools/web-fetch.js"; import type { OpenClawConfig } from "../src/config/types.openclaw.js"; import type { LookupFn } from "../src/infra/net/ssrf.js"; import { extractReadableContent } from "../src/web-fetch/content-extractors.runtime.js"; -import { stripLeadingPackageManagerSeparator } from "./lib/arg-utils.mts"; +import * as cliArgs from "./lib/arg-utils.mts"; type BenchmarkCaseId = | "tool-create" @@ -68,8 +68,7 @@ const ALL_CASE_IDS = [ "extract-basic-shell", ] as const satisfies readonly BenchmarkCaseId[]; -const BOOLEAN_FLAGS = new Set(["--help", "-h", "--json"]); -const VALUE_FLAGS = new Set(["--case", "--output", "--runs", "--warmup"]); +const SPLIT_VALUE_FLAG_OPTIONS = { allowInline: false, rejectShortOptions: true } as const; class CliArgumentError extends Error { override name = "CliArgumentError"; @@ -125,71 +124,7 @@ const toolConfig: OpenClawConfig = { }, }; -function validateArgs(args: string[]): void { - const seenSingularValueFlags = new Set(); - for (let index = 0; index < args.length; index += 1) { - const arg = args[index] ?? ""; - if (BOOLEAN_FLAGS.has(arg)) { - continue; - } - if (VALUE_FLAGS.has(arg)) { - if (arg !== "--case") { - if (seenSingularValueFlags.has(arg)) { - throw new CliArgumentError(`${arg} was provided more than once`); - } - seenSingularValueFlags.add(arg); - } - const value = args[index + 1]; - if (!value || value.startsWith("-")) { - throw new CliArgumentError(`${arg} requires a value`); - } - index += 1; - continue; - } - throw new CliArgumentError(`Unknown argument: ${arg}`); - } -} - -function parsePositiveInteger(flag: string, fallback: number, args: string[]): number { - const index = args.indexOf(flag); - if (index === -1) { - return fallback; - } - const raw = args[index + 1]; - if (!/^\d+$/u.test(raw ?? "")) { - throw new CliArgumentError(`${flag} must be a positive integer`); - } - const value = Number(raw); - if (!Number.isSafeInteger(value) || value <= 0) { - throw new CliArgumentError(`${flag} must be a positive integer`); - } - return value; -} - -function parseNonNegativeInteger(flag: string, fallback: number, args: string[]): number { - const index = args.indexOf(flag); - if (index === -1) { - return fallback; - } - const raw = args[index + 1]; - if (!/^\d+$/u.test(raw ?? "")) { - throw new CliArgumentError(`${flag} must be a non-negative integer`); - } - const value = Number(raw); - if (!Number.isSafeInteger(value) || value < 0) { - throw new CliArgumentError(`${flag} must be a non-negative integer`); - } - return value; -} - -function parseCases(args: string[]): BenchmarkCaseId[] { - const requested: string[] = []; - for (let index = 0; index < args.length; index += 1) { - if (args[index] === "--case") { - requested.push(args[index + 1] ?? ""); - index += 1; - } - } +function parseCases(requested: string[]): BenchmarkCaseId[] { if (requested.length === 0 || requested.includes("all")) { return [...ALL_CASE_IDS]; } @@ -204,21 +139,51 @@ function parseCases(args: string[]): BenchmarkCaseId[] { return requested as BenchmarkCaseId[]; } -function parseFlagValue(flag: string, args: string[]): string | undefined { - const index = args.indexOf(flag); - return index === -1 ? undefined : args[index + 1]; +function parseBenchmarkCount(raw: string, flag: string, minimum: 0 | 1): number { + const parsed = cliArgs.classifyBoundedUnsignedDecimal(raw, minimum, Number.MAX_SAFE_INTEGER); + if (parsed.kind === "value") { + return parsed.value; + } + const kind = minimum === 0 ? "non-negative" : "positive"; + throw new CliArgumentError(`${flag} must be a ${kind} integer`); } function parseOptions(args = process.argv.slice(2)): Options { - const normalizedArgs = stripLeadingPackageManagerSeparator(args); - validateArgs(normalizedArgs); - return { - cases: parseCases(normalizedArgs), - json: normalizedArgs.includes("--json"), - output: parseFlagValue("--output", normalizedArgs), - runs: parsePositiveInteger("--runs", 20, normalizedArgs), - warmup: parseNonNegativeInteger("--warmup", 3, normalizedArgs), - }; + const normalizedArgs = cliArgs.stripLeadingPackageManagerSeparator(args); + try { + const parsed = cliArgs.parseFlagArgs( + normalizedArgs, + { + cases: [] as string[], + json: false, + output: undefined as string | undefined, + runs: "20", + warmup: "3", + }, + [ + cliArgs.stringListFlag("--case", "cases", SPLIT_VALUE_FLAG_OPTIONS), + cliArgs.stringFlag("--output", "output", SPLIT_VALUE_FLAG_OPTIONS), + cliArgs.stringFlag("--runs", "runs", SPLIT_VALUE_FLAG_OPTIONS), + cliArgs.stringFlag("--warmup", "warmup", SPLIT_VALUE_FLAG_OPTIONS), + cliArgs.booleanFlag("--json", "json", true, { repeatable: true }), + ], + { + ignoreDoubleDash: false, + onUnhandledArg(arg) { + throw new CliArgumentError(`Unknown argument: ${arg}`); + }, + }, + ); + return { + cases: parseCases(parsed.cases), + json: parsed.json, + output: parsed.output, + runs: parseBenchmarkCount(parsed.runs, "--runs", 1), + warmup: parseBenchmarkCount(parsed.warmup, "--warmup", 0), + }; + } catch (error) { + throw new CliArgumentError(error instanceof Error ? error.message : String(error)); + } } function printUsage(): void {