fix(core): centralize non-finite integer options · openclaw/openclaw@30c24bb
steipete
·
2026-05-29
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
|
| 1 | +import { resolveIntegerOption as resolveSharedIntegerOption } from "../shared/number-coercion.js"; |
| 2 | + |
1 | 3 | export function resolveIntegerOption( |
2 | 4 | value: number | undefined, |
3 | 5 | fallback: number, |
4 | 6 | params: { min: number }, |
5 | 7 | ): number { |
6 | | -const candidate = typeof value === "number" && Number.isFinite(value) ? value : fallback; |
7 | | -return Math.max(params.min, Math.floor(candidate)); |
| 8 | +return resolveSharedIntegerOption(value, fallback, params); |
8 | 9 | } |
| Original file line number | Diff line number | Diff line change |
|---|
@@ -199,6 +199,19 @@ describe("installSessionToolResultGuard", () => {
|
199 | 199 | expect(text).toContain("truncated"); |
200 | 200 | }); |
201 | 201 | |
| 202 | +it("falls back to the default tool-result cap for non-finite configured caps", () => { |
| 203 | +const sm = SessionManager.inMemory(); |
| 204 | +installSessionToolResultGuard(sm, { |
| 205 | +maxToolResultChars: Number.NaN, |
| 206 | +}); |
| 207 | + |
| 208 | +appendToolResultText(sm, "x".repeat(80_000)); |
| 209 | + |
| 210 | +const text = getToolResultText(getPersistedMessages(sm)); |
| 211 | +expect(text.length).toBeLessThanOrEqual(16_000); |
| 212 | +expect(text).toContain("truncated"); |
| 213 | +}); |
| 214 | + |
202 | 215 | it("backfills blank toolResult names from pending tool calls", () => { |
203 | 216 | const sm = SessionManager.inMemory(); |
204 | 217 | installSessionToolResultGuard(sm); |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -14,6 +14,7 @@ import type {
|
14 | 14 | PluginHookBeforeMessageWriteResult, |
15 | 15 | } from "../plugins/types.js"; |
16 | 16 | import { emitSessionTranscriptUpdate } from "../sessions/transcript-events.js"; |
| 17 | +import { resolveIntegerOption } from "../shared/number-coercion.js"; |
17 | 18 | import { normalizeOptionalString } from "../shared/string-coerce.js"; |
18 | 19 | import { formatContextLimitTruncationNotice } from "./embedded-agent-runner/context-truncation-notice.js"; |
19 | 20 | import { |
@@ -46,7 +47,9 @@ function capToolResultSize(msg: AgentMessage, maxChars: number): AgentMessage {
|
46 | 47 | } |
47 | 48 | |
48 | 49 | function resolveMaxToolResultChars(opts?: { maxToolResultChars?: number }): number { |
49 | | -return Math.max(1, opts?.maxToolResultChars ?? DEFAULT_MAX_LIVE_TOOL_RESULT_CHARS); |
| 50 | +return resolveIntegerOption(opts?.maxToolResultChars, DEFAULT_MAX_LIVE_TOOL_RESULT_CHARS, { |
| 51 | +min: 1, |
| 52 | +}); |
50 | 53 | } |
51 | 54 | |
52 | 55 | type UserAgentMessage = Extract<AgentMessage, { role: "user" }>; |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -95,6 +95,26 @@ describe("tool image sanitizing", () => {
|
95 | 95 | expect(image.mimeType).toBe("image/jpeg"); |
96 | 96 | }); |
97 | 97 | |
| 98 | +it("uses default image limits for non-finite options", async () => { |
| 99 | +const jpeg = createTinyJpegBuffer(); |
| 100 | + |
| 101 | +const out = await sanitizeContentBlocksImages( |
| 102 | +[ |
| 103 | +{ |
| 104 | +type: "image" as const, |
| 105 | +data: jpeg.toString("base64"), |
| 106 | +mimeType: "image/jpeg", |
| 107 | +}, |
| 108 | +], |
| 109 | +"test", |
| 110 | +{ maxDimensionPx: Number.NaN, maxBytes: Number.NaN }, |
| 111 | +); |
| 112 | + |
| 113 | +const image = getImageBlock(out); |
| 114 | +expect(image.mimeType).toBe("image/jpeg"); |
| 115 | +expect(image.data).toBe(jpeg.toString("base64")); |
| 116 | +}); |
| 117 | + |
98 | 118 | it("drops malformed image base64 payloads", async () => { |
99 | 119 | const blocks = [ |
100 | 120 | { |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -8,6 +8,7 @@ import {
|
8 | 8 | isImageProcessorUnavailableError, |
9 | 9 | resizeToJpeg, |
10 | 10 | } from "../media/media-services.js"; |
| 11 | +import { resolveIntegerOption } from "../shared/number-coercion.js"; |
11 | 12 | import { |
12 | 13 | DEFAULT_IMAGE_MAX_BYTES, |
13 | 14 | DEFAULT_IMAGE_MAX_DIMENSION_PX, |
@@ -289,8 +290,10 @@ export async function sanitizeContentBlocksImages(
|
289 | 290 | label: string, |
290 | 291 | opts: ImageSanitizationLimits = {}, |
291 | 292 | ): Promise<ToolContentBlock[]> { |
292 | | -const maxDimensionPx = Math.max(opts.maxDimensionPx ?? MAX_IMAGE_DIMENSION_PX, 1); |
293 | | -const maxBytes = Math.max(opts.maxBytes ?? MAX_IMAGE_BYTES, 1); |
| 293 | +const maxDimensionPx = resolveIntegerOption(opts.maxDimensionPx, MAX_IMAGE_DIMENSION_PX, { |
| 294 | +min: 1, |
| 295 | +}); |
| 296 | +const maxBytes = resolveIntegerOption(opts.maxBytes, MAX_IMAGE_BYTES, { min: 1 }); |
294 | 297 | const out: ToolContentBlock[] = []; |
295 | 298 | let mediaPathHint: string | undefined; |
296 | 299 | |
|
| Original file line number | Diff line number | Diff line change |
|---|
|
| 1 | +import { |
| 2 | +resolveIntegerOption as resolveSharedIntegerOption, |
| 3 | +resolveNonNegativeIntegerOption as resolveSharedNonNegativeIntegerOption, |
| 4 | +} from "../shared/number-coercion.js"; |
| 5 | + |
1 | 6 | export function resolveNonNegativeIntegerOption(value: number, fallback: number): number { |
2 | | -return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : fallback; |
| 7 | +return resolveSharedNonNegativeIntegerOption(value, fallback); |
3 | 8 | } |
4 | 9 | |
5 | 10 | export function resolveIntegerOption( |
6 | 11 | value: number, |
7 | 12 | fallback: number, |
8 | 13 | params: { min: number }, |
9 | 14 | ): number { |
10 | | -const candidate = Number.isFinite(value) ? value : fallback; |
11 | | -return Math.max(params.min, Math.floor(candidate)); |
| 15 | +return resolveSharedIntegerOption(value, fallback, params); |
12 | 16 | } |
| Original file line number | Diff line number | Diff line change |
|---|
@@ -4,6 +4,8 @@ import {
|
4 | 4 | asFiniteNumberInRange, |
5 | 5 | asSafeIntegerInRange, |
6 | 6 | parseFiniteNumber, |
| 7 | +resolveIntegerOption, |
| 8 | +resolveNonNegativeIntegerOption, |
7 | 9 | parseStrictFiniteNumber, |
8 | 10 | parseStrictInteger, |
9 | 11 | parseStrictNonNegativeInteger, |
@@ -65,4 +67,13 @@ describe("number-coercion", () => {
|
65 | 67 | expect(parseStrictNonNegativeInteger("0")).toBe(0); |
66 | 68 | expect(parseStrictNonNegativeInteger("-1")).toBeUndefined(); |
67 | 69 | }); |
| 70 | + |
| 71 | +test("integer option helpers floor finite values and fall back for non-finite values", () => { |
| 72 | +expect(resolveIntegerOption(7.9, 1, { min: 1, max: 10 })).toBe(7); |
| 73 | +expect(resolveIntegerOption(Number.NaN, 4.9, { min: 1 })).toBe(4); |
| 74 | +expect(resolveIntegerOption(Number.NEGATIVE_INFINITY, 4, { min: 1 })).toBe(4); |
| 75 | +expect(resolveIntegerOption(-4, 1, { min: 0 })).toBe(0); |
| 76 | +expect(resolveIntegerOption(40, 1, { max: 10 })).toBe(10); |
| 77 | +expect(resolveNonNegativeIntegerOption(Number.NaN, 3.9)).toBe(3); |
| 78 | +}); |
68 | 79 | }); |
| Original file line number | Diff line number | Diff line change |
|---|
@@ -93,6 +93,24 @@ export function asPositiveSafeInteger(value: unknown): number | undefined {
|
93 | 93 | return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined; |
94 | 94 | } |
95 | 95 | |
| 96 | +export function resolveIntegerOption( |
| 97 | +value: unknown, |
| 98 | +fallback: number, |
| 99 | +range: { |
| 100 | +min?: number; |
| 101 | +max?: number; |
| 102 | +} = {}, |
| 103 | +): number { |
| 104 | +const candidate = typeof value === "number" && Number.isFinite(value) ? value : fallback; |
| 105 | +const floored = Math.floor(candidate); |
| 106 | +const minBounded = range.min === undefined ? floored : Math.max(range.min, floored); |
| 107 | +return range.max === undefined ? minBounded : Math.min(range.max, minBounded); |
| 108 | +} |
| 109 | + |
| 110 | +export function resolveNonNegativeIntegerOption(value: unknown, fallback: number): number { |
| 111 | +return resolveIntegerOption(value, fallback, { min: 0 }); |
| 112 | +} |
| 113 | + |
96 | 114 | export function parseStrictPositiveInteger(value: unknown): number | undefined { |
97 | 115 | const parsed = parseStrictInteger(value); |
98 | 116 | return parsed !== undefined && parsed > 0 ? parsed : undefined; |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。