mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 10:16:44 +00:00
fix(usage-bar): validate meter width with strict integer parsing (#110410)
* fix(usage-bar): validate meter width with strict integer parsing * fix(usage-bar): restore canonical meter width range [0, 100] * fix(usage-bar): preserve bounded meter defaults Co-authored-by: sheyanmin <she.yanmin@xydigit.com> --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
co-authored by
Peter Steinberger
parent
242093fb00
commit
ff3ed99cde
@@ -256,6 +256,9 @@ Pipe a value through verbs left to right; a non-verb segment is the fallback.
|
||||
`fixed:N` accepts only a complete decimal integer from 0 through 100. Invalid
|
||||
precision arguments make that interpolation empty.
|
||||
|
||||
`meter:W:SCALE` accepts only a complete decimal integer width from 1 through 100. Leave the width blank to use the default 5 (`meter::braille`); invalid
|
||||
widths make that interpolation empty.
|
||||
|
||||
### Piece forms
|
||||
|
||||
- `{ "text": "📚 {context.max_tokens|num}" }`: literal + interpolation.
|
||||
|
||||
@@ -72,6 +72,21 @@ describe("usage-bar verbs", () => {
|
||||
expect(render([{ text: "{x|meter:1:moon}" }], { x: 100 })).toBe("🌕");
|
||||
});
|
||||
|
||||
it("meter — preserves default and supported explicit widths", () => {
|
||||
expect(render([{ text: "{x|meter::braille}" }], { x: 75 })).toBe("⣿⣿⣿⣧⠐");
|
||||
expect(render([{ text: "{x|meter: :braille}" }], { x: 75 })).toBe("⣿⣿⣿⣧⠐");
|
||||
expect(render([{ text: "{x|meter:+5:braille}" }], { x: 75 })).toBe("⣿⣿⣿⣧⠐");
|
||||
expect(render([{ text: "{x|meter: 5 :braille}" }], { x: 75 })).toBe("⣿⣿⣿⣧⠐");
|
||||
expect(render([{ text: "{x|meter:100:braille}" }], { x: 50 })).toHaveLength(100);
|
||||
});
|
||||
|
||||
it.each(["0", "-1", "2.5", "101", "1e2", "2junk", "abc", "9007199254740992"])(
|
||||
"meter — rejects invalid width %s",
|
||||
(width) => {
|
||||
expect(render([{ text: `{x|meter:${width}:braille}` }], { x: 75 })).toBe("");
|
||||
},
|
||||
);
|
||||
|
||||
it("alias — listed shortens, unlisted echoes through", () => {
|
||||
expect(render([{ text: "{m|alias:models}" }], { m: "claude-opus-4-6" })).toBe("opus46");
|
||||
expect(render([{ text: "{m|alias:models}" }], { m: "some-new-model" })).toBe("some-new-model");
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { expectDefined, parseStrictInteger } from "@openclaw/normalization-core";
|
||||
import {
|
||||
asSafeIntegerInRange,
|
||||
expectDefined,
|
||||
parseStrictInteger,
|
||||
} from "@openclaw/normalization-core";
|
||||
export type UsageBarTemplate = Record<string, unknown>;
|
||||
export type UsageContract = Record<string, unknown>;
|
||||
type Vocab = Record<string, unknown>;
|
||||
@@ -117,9 +121,12 @@ function meter(value: unknown, width: number, scale: unknown): string {
|
||||
|
||||
const VERB_NAMES = new Set(["num", "fixed", "dur", "pct", "inv", "alias", "meter"]);
|
||||
|
||||
function parseFixedDigits(raw: string | undefined): number | undefined {
|
||||
const digits = raw === undefined ? 2 : parseStrictInteger(raw);
|
||||
return digits !== undefined && digits >= 0 && digits <= 100 ? digits : undefined;
|
||||
function parseBoundedIntegerArg(
|
||||
raw: string | undefined,
|
||||
options: { defaultValue: number; min: number; max: number },
|
||||
): number | undefined {
|
||||
const value = raw === undefined ? options.defaultValue : parseStrictInteger(raw);
|
||||
return asSafeIntegerInRange(value, options);
|
||||
}
|
||||
|
||||
function applyVerb(name: string, args: string[], value: unknown, vocab: Vocab): unknown {
|
||||
@@ -127,7 +134,7 @@ function applyVerb(name: string, args: string[], value: unknown, vocab: Vocab):
|
||||
case "num":
|
||||
return num(value);
|
||||
case "fixed": {
|
||||
const digits = parseFixedDigits(args[0]);
|
||||
const digits = parseBoundedIntegerArg(args[0], { defaultValue: 2, min: 0, max: 100 });
|
||||
return digits === undefined ? "" : fixed(value, digits);
|
||||
}
|
||||
case "dur":
|
||||
@@ -148,9 +155,10 @@ function applyVerb(name: string, args: string[], value: unknown, vocab: Vocab):
|
||||
return Object.hasOwn(table, lower) ? table[lower] : value;
|
||||
}
|
||||
case "meter": {
|
||||
const width = args[0] ? Number.parseInt(args[0], 10) || 5 : 5;
|
||||
const rawWidth = args[0]?.trim() ? args[0] : undefined;
|
||||
const width = parseBoundedIntegerArg(rawWidth, { defaultValue: 5, min: 1, max: 100 });
|
||||
const scale = args.length > 1 ? vocab[expectDefined(args[1], "args entry at 1")] : undefined;
|
||||
return meter(value, width, scale);
|
||||
return width === undefined ? "" : meter(value, width, scale);
|
||||
}
|
||||
default:
|
||||
return String(value);
|
||||
|
||||
Reference in New Issue
Block a user