Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions src/commands/artifacts-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
parseCommanderProgram,
} from "./commander-utils.js";
import { setStderrDevLogStorageRootDirectory } from "../runtime/stderr-dev-log.js";
import { parseNonNegativeInt } from "./option-parsers.js";

interface WriteTarget {
write(chunk: string): unknown;
Expand Down Expand Up @@ -391,14 +392,6 @@ function parseHarnessKindOption(value: string): AgentHarnessKind {
);
}

function parseNonNegativeInt(value: string): number {
const parsed = Number.parseInt(value, 10);
if (!Number.isFinite(parsed) || parsed < 0) {
throw new InvalidArgumentError("value must be a non-negative integer");
}
return parsed;
}

function writeJson(stdout: WriteTarget, value: unknown): void {
stdout.write(`${JSON.stringify(value, null, 2)}\n`);
}
16 changes: 16 additions & 0 deletions src/commands/option-parsers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ describe("parsePositiveInt", () => {
it("throws for non-numeric strings", () => {
expect(() => parsePositiveInt("abc")).toThrow("positive integer");
});

it("rejects partially parsed and unsafe integer inputs", () => {
for (const value of ["12px", "1.5", "1e3", "9007199254740992"]) {
expect(() => parsePositiveInt(value)).toThrow("positive integer");
}
});
});

describe("parseNonNegativeInt", () => {
Expand All @@ -45,6 +51,10 @@ describe("parseNonNegativeInt", () => {
it("throws for negative", () => {
expect(() => parseNonNegativeInt("-1")).toThrow("non-negative");
});

it("rejects partially parsed values", () => {
expect(() => parseNonNegativeInt("0items")).toThrow("non-negative");
});
});

describe("parseNumber", () => {
Expand All @@ -55,6 +65,12 @@ describe("parseNumber", () => {
it("throws for non-numeric", () => {
expect(() => parseNumber("xyz")).toThrow("Expected number");
});

it("rejects malformed numeric values", () => {
for (const value of ["3.14ms", "1.2.3", "Infinity"]) {
expect(() => parseNumber(value)).toThrow("Expected number");
}
});
});

describe("parseOperatingMode", () => {
Expand Down
29 changes: 24 additions & 5 deletions src/commands/option-parsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,28 @@ import { MIN_ANTHROPIC_THINKING_BUDGET_TOKENS } from "../bootstrap/config/defaul

export class InvalidArgumentError extends Error {}

const DECIMAL_INTEGER_PATTERN = /^[+-]?\d+$/u;
const DECIMAL_NUMBER_PATTERN = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/iu;

function parseDecimalInteger(value: string): number {
const normalized = value.trim();
if (!DECIMAL_INTEGER_PATTERN.test(normalized)) {
return Number.NaN;
}
return Number(normalized);
}

function parseFiniteNumber(value: string): number {
const normalized = value.trim();
if (!DECIMAL_NUMBER_PATTERN.test(normalized)) {
return Number.NaN;
}
return Number(normalized);
}

export function parsePositiveInt(value: string): number {
const parsed = Number.parseInt(value, 10);
if (!Number.isInteger(parsed) || parsed <= 0) {
const parsed = parseDecimalInteger(value);
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
throw new Error(`Expected positive integer, got: ${value}`);
}
return parsed;
Expand All @@ -32,8 +51,8 @@ export function parseOperatingMode(value: string): AgentOperatingMode {
}

export function parseNonNegativeInt(value: string): number {
const parsed = Number.parseInt(value, 10);
if (!Number.isInteger(parsed) || parsed < 0) {
const parsed = parseDecimalInteger(value);
if (!Number.isSafeInteger(parsed) || parsed < 0) {
throw new Error(`Expected non-negative integer, got: ${value}`);
}
return parsed;
Expand Down Expand Up @@ -107,7 +126,7 @@ export function parseToolSearchIndexProfile(
}

export function parseNumber(value: string): number {
const parsed = Number.parseFloat(value);
const parsed = parseFiniteNumber(value);
if (!Number.isFinite(parsed)) {
throw new Error(`Expected number, got: ${value}`);
}
Expand Down
Loading