refactor(domain): one owner each for non-empty text schemas and UTF-8 head excerpts (#10569)

Fifteen domain schema files each declared z.string().min(1), eight of them
as boundedTextSchema although it bounds nothing. They now share
nonEmptyTextSchema from src/domain/textSchemas.ts; the one trimming copy in
reconstructionCoverage.ts is renamed trimmedTextSchema.

Three private UTF-8 prefix truncators (gdb recent MI records, rizin
diagnostics, environment replay) and src/process/boundedUtf8Text.ts are
replaced by utf8HeadExcerpt in src/domain/utf8HeadExcerpt.ts, which encodes
only the retained prefix and returns a slice of the source text.
This commit is contained in:
morluto authored and GitHub committed 2026-11-14 13:28:28 -05:00
1 parent 1ba93b4602
commit eead7a5815
29 files changed
+236 -173

No files matched your search

@@ -1,6 +1,6 @@
import type { ComputerAction } from "../../domain/desktop/computerActions.js";
import type { JsonValue } from "../../domain/jsonValue.js";
import { boundedUtf8Text } from "../../process/boundedUtf8Text.js";
import { utf8HeadExcerpt } from "../../domain/utf8HeadExcerpt.js";
/** Bytes the log keeps of each string in an action's arguments, such as type's text. */
export const LOGGED_TEXT_BYTES = 1024;
@@ -35,7 +35,7 @@ const bounded = (
redact: Redact,
): { readonly value: JsonValue; readonly cut: boolean } => {
if (typeof value === "string") {
const { text, truncated } = boundedUtf8Text(
const { text, truncated } = utf8HeadExcerpt(
redact(value),
LOGGED_TEXT_BYTES,
);
@@ -19,6 +19,7 @@ import {
type EnvironmentSource,
type RecordedExchange,
} from "./environmentSource.js";
import { utf8HeadExcerpt } from "../../domain/utf8HeadExcerpt.js";
/** Caller arguments as the MCP tool receives them. */
export type ReplayArguments = Readonly<Record<string, unknown>>;
@@ -307,13 +308,6 @@ export const selectsRecordedLine = (
return true;
};
/** The longest prefix of text whose UTF-8 encoding fits the byte limit, encoding only that prefix. */
const utf8Prefix = (text: string, maxBytes: number): string => {
// encodeInto stops before the first character that does not fit, so `read` ends on a boundary.
const { read } = new TextEncoder().encodeInto(text, new Uint8Array(maxBytes));
return text.slice(0, read);
};
/** Base64 characters, in whole groups, whose bytes fit the replay limit. */
const BASE64_LIMIT_CHARS = Math.floor(REPLAY_BODY_LIMIT_BYTES / 3) * 4;
@@ -334,7 +328,7 @@ const replayBody = (exchange: RecordedExchange): ReplayBody => {
: { body: body.encoded.slice(0, BASE64_LIMIT_CHARS), truncated: true };
case "cut":
return {
body: utf8Prefix(body.text, REPLAY_BODY_LIMIT_BYTES),
body: utf8HeadExcerpt(body.text, REPLAY_BODY_LIMIT_BYTES).text,
truncated: true,
};
case "json":
@@ -380,15 +374,18 @@ export const replayAnswer = (
exact: boolean,
{ body, truncated }: ReplayBody = replayBody(exchange),
): ReplayResult => {
const recordedPath = utf8Prefix(exchange.path, REPLAY_METADATA_LIMIT_BYTES);
const recordedPath = utf8HeadExcerpt(
exchange.path,
REPLAY_METADATA_LIMIT_BYTES,
).text;
const recordedQuery =
exchange.query === null
? null
: utf8Prefix(exchange.query, REPLAY_METADATA_LIMIT_BYTES);
: utf8HeadExcerpt(exchange.query, REPLAY_METADATA_LIMIT_BYTES).text;
const contentType =
exchange.contentType === null
? null
: utf8Prefix(exchange.contentType, REPLAY_METADATA_LIMIT_BYTES);
: utf8HeadExcerpt(exchange.contentType, REPLAY_METADATA_LIMIT_BYTES).text;
return {
status: exchange.status,
content_type: contentType,