mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-22 20:01:09 +00:00
* refactor(plugin-sdk): narrow wildcard barrels to explicit used exports * refactor(tools): delete dead tool-planning module exposed by barrel narrowing * fix(plugin-sdk): restore deprecation tag on OpenClawSchemaType alias * test(agents): drop test for deleted runtime proxy module * refactor(tools): trim descriptor types to cache consumers * refactor(deadcode): harvest exports orphaned by barrel narrowing * refactor(deadcode): harvest exports orphaned by barrel narrowing (rest) * fix(agents): restore sdk imports and test markers via public predicate * fix(plugin-sdk): named type re-exports in plugin-entry; trim types barrel precisely * chore(plugin-sdk): account unmasked deprecated provider types in budgets * fix(plugins): name star-only type rows for dts bundling * fix(plugins): restore host-hook surface; unexport internal api compositions * fix(plugins): named type imports for api composition; restore needed source exports * fix(plugins): knip-visible type imports for registry surfaces * test: adapt tests to privatized media and command internals * fix(qa-lab): re-export snapshot conversation type * style: format sessions sdk imports * fix(plugins): restore smoke entry export; pin budgets to exact actuals * fix(plugins): canonical smoke-entry import; drop orphaned root shims * fix(plugins): allowlist manifest probe, repoint qa web import, drop dead browser barrels * fix(plugin-sdk): pin codex auth marker and scaffold provider type * fix(qa-lab): keep web-facing model-selection shim within boundary rules * fix(plugin-sdk): preserve merged contracts through narrowed barrels * chore(plugin-sdk): pin post-rebase surface budgets
73 lines
2.5 KiB
TypeScript
73 lines
2.5 KiB
TypeScript
// Duration parser shared by CLI flags, command directives, and config-backed timing values.
|
|
import {
|
|
normalizeLowercaseStringOrEmpty,
|
|
normalizeOptionalString,
|
|
} from "@openclaw/normalization-core/string-coerce";
|
|
import milliseconds from "ms";
|
|
|
|
/** Options for choosing the unit used by bare numeric duration values. */
|
|
type DurationMsParseOptions = {
|
|
defaultUnit?: "ms" | "s" | "m" | "h" | "d";
|
|
};
|
|
|
|
function invalidDuration(raw: string, reason?: string): Error {
|
|
const value = raw.trim() ? `"${raw}"` : "empty value";
|
|
const prefix = reason ? `Invalid duration (${reason}): ${value}.` : `Invalid duration: ${value}.`;
|
|
return new Error(`${prefix} Use values like 500ms, 30s, 5m, 2h, or 1h30m.`);
|
|
}
|
|
|
|
function parseDurationToken(raw: string, value: string, unit: string): number {
|
|
const parsed = milliseconds(`${value}${unit}` as Parameters<typeof milliseconds>[0]);
|
|
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
throw invalidDuration(raw);
|
|
}
|
|
return parsed;
|
|
}
|
|
|
|
function roundSafeDurationMs(raw: string, value: number): number {
|
|
const ms = Math.round(value);
|
|
if (!Number.isSafeInteger(ms)) {
|
|
throw invalidDuration(raw);
|
|
}
|
|
return ms;
|
|
}
|
|
|
|
/** Parse a non-negative duration into milliseconds, supporting single and composite units. */
|
|
export function parseDurationMs(raw: string, opts?: DurationMsParseOptions): number {
|
|
const trimmed = normalizeLowercaseStringOrEmpty(normalizeOptionalString(raw) ?? "");
|
|
if (!trimmed) {
|
|
throw invalidDuration(raw, "empty");
|
|
}
|
|
|
|
// Fast path for a single token (supports default unit for bare numbers).
|
|
const single = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)?$/.exec(trimmed);
|
|
if (single) {
|
|
const value = single[1] ?? "";
|
|
const unit = (single[2] ?? opts?.defaultUnit ?? "ms") as "ms" | "s" | "m" | "h" | "d";
|
|
return roundSafeDurationMs(raw, parseDurationToken(raw, value, unit));
|
|
}
|
|
|
|
// Composite form (e.g. "1h30m", "2m500ms"); each token must include a unit.
|
|
let totalMs = 0;
|
|
let consumed = 0;
|
|
const tokenRe = /(\d+(?:\.\d+)?)(ms|s|m|h|d)/g;
|
|
for (const match of trimmed.matchAll(tokenRe)) {
|
|
const [full, valueRaw, unitRaw] = match;
|
|
const index = match.index ?? -1;
|
|
if (!full || !valueRaw || !unitRaw || index < 0) {
|
|
throw invalidDuration(raw);
|
|
}
|
|
if (index !== consumed) {
|
|
throw invalidDuration(raw, "each composite segment needs a unit");
|
|
}
|
|
totalMs += parseDurationToken(raw, valueRaw, unitRaw);
|
|
consumed += full.length;
|
|
}
|
|
|
|
if (consumed !== trimmed.length || consumed === 0) {
|
|
throw invalidDuration(raw);
|
|
}
|
|
|
|
return roundSafeDurationMs(raw, totalMs);
|
|
}
|