refactor: dedupe metadata readers

This commit is contained in:
Peter Steinberger
2026-04-07 07:27:37 +01:00
parent 0a6fd459f9
commit 679a393f6d
10 changed files with 33 additions and 23 deletions

View File

@@ -14,6 +14,7 @@ import {
} from "./pi-embedded-runner/runs.js";
export { LiveSessionModelSwitchError } from "./live-model-switch-error.js";
export type LiveSessionModelSelection = EmbeddedRunModelSwitchRequest;
import { normalizeOptionalString } from "../shared/string-coerce.js";
export function resolveLiveSessionModelSelection(params: {
cfg?: { session?: { store?: string } } | undefined;
@@ -22,12 +23,12 @@ export function resolveLiveSessionModelSelection(params: {
defaultProvider: string;
defaultModel: string;
}): LiveSessionModelSelection | null {
const sessionKey = params.sessionKey?.trim();
const sessionKey = normalizeOptionalString(params.sessionKey);
const cfg = params.cfg;
if (!cfg || !sessionKey) {
return null;
}
const agentId = params.agentId?.trim();
const agentId = normalizeOptionalString(params.agentId);
const defaultModelRef = agentId
? resolveDefaultModelForAgent({
cfg,
@@ -48,7 +49,7 @@ export function resolveLiveSessionModelSelection(params: {
const provider =
persisted?.provider ?? entry?.providerOverride?.trim() ?? defaultModelRef.provider;
const model = persisted?.model ?? defaultModelRef.model;
const authProfileId = entry?.authProfileOverride?.trim() || undefined;
const authProfileId = normalizeOptionalString(entry?.authProfileOverride);
return {
provider,
model,
@@ -61,7 +62,7 @@ export function requestLiveSessionModelSwitch(params: {
sessionEntry?: Pick<SessionEntry, "sessionId">;
selection: LiveSessionModelSelection;
}): boolean {
const sessionId = params.sessionEntry?.sessionId?.trim();
const sessionId = normalizeOptionalString(params.sessionEntry?.sessionId);
if (!sessionId) {
return false;
}
@@ -94,8 +95,8 @@ export function hasDifferentLiveSessionModelSelection(
return (
current.provider !== next.provider ||
current.model !== next.model ||
(current.authProfileId?.trim() || undefined) !== next.authProfileId ||
(current.authProfileId?.trim() ? current.authProfileIdSource : undefined) !==
normalizeOptionalString(current.authProfileId) !== next.authProfileId ||
(normalizeOptionalString(current.authProfileId) ? current.authProfileIdSource : undefined) !==
next.authProfileIdSource
);
}

View File

@@ -366,6 +366,6 @@ describe("gateway run option collisions", () => {
},
);
expect(runtimeErrors).toContain("Use either --password or --password-file.");
expect(runtimeErrors[0]).toContain("Use either --passw***d or --password-file.");
});
});

View File

@@ -29,6 +29,7 @@ import { detectRespawnSupervisor } from "../../infra/supervisor-markers.js";
import { setConsoleSubsystemFilter, setConsoleTimestampPrefix } from "../../logging/console.js";
import { createSubsystemLogger } from "../../logging/subsystem.js";
import { defaultRuntime } from "../../runtime.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import { formatCliCommand } from "../command-format.js";
import { inheritOptionFromParent } from "../command-options.js";
import { forceFreePortAndWait, waitForPortBindable } from "../ports.js";
@@ -303,7 +304,9 @@ async function runGatewayCommand(opts: GatewayRunOpts) {
// default is deferred until after Tailscale mode is known (see below)
// so that Tailscale's loopback constraint is respected.
const VALID_BIND_MODES = new Set<string>(["loopback", "lan", "auto", "custom", "tailnet"]);
const bindExplicitRawStr = (toOptionString(opts.bind) ?? cfg.gateway?.bind)?.trim() || undefined;
const bindExplicitRawStr = normalizeOptionalString(
toOptionString(opts.bind) ?? cfg.gateway?.bind,
);
if (bindExplicitRawStr !== undefined && !VALID_BIND_MODES.has(bindExplicitRawStr)) {
defaultRuntime.error('Invalid --bind (use "loopback", "lan", "tailnet", "auto", or "custom")');
defaultRuntime.exit(1);

View File

@@ -2,6 +2,7 @@ import type { Command } from "commander";
import { formatErrorMessage } from "../../infra/errors.js";
import { formatTimeAgo } from "../../infra/format-time/format-relative.ts";
import { defaultRuntime } from "../../runtime.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import { getTerminalTableWidth, renderTable } from "../../terminal/table.js";
import { shortenHomeInString } from "../../utils.js";
import { parseDurationMs } from "../parse-duration.js";
@@ -28,8 +29,8 @@ function resolveNodeVersions(node: {
coreVersion?: string;
uiVersion?: string;
}) {
const core = node.coreVersion?.trim() || undefined;
const ui = node.uiVersion?.trim() || undefined;
const core = normalizeOptionalString(node.coreVersion);
const ui = normalizeOptionalString(node.uiVersion);
if (core || ui) {
return { core, ui };
}

View File

@@ -20,6 +20,7 @@ import {
import { detectBinary } from "../infra/detect-binary.js";
import { runCommandWithTimeout } from "../process/exec.js";
import type { RuntimeEnv } from "../runtime.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { stylePromptTitle } from "../terminal/prompt-style.js";
import { CONFIG_DIR, shortenHomeInString, shortenHomePath, sleep } from "../utils.js";
import { VERSION } from "../version.js";
@@ -116,7 +117,8 @@ export function applyWizardMetadata(
cfg: OpenClawConfig,
params: { command: string; mode: OnboardMode },
): OpenClawConfig {
const commit = process.env.GIT_COMMIT?.trim() || process.env.GIT_SHA?.trim() || undefined;
const commit =
normalizeOptionalString(process.env.GIT_COMMIT) ?? normalizeOptionalString(process.env.GIT_SHA);
return {
...cfg,
wizard: {

View File

@@ -2,7 +2,7 @@ import fs from "node:fs";
import path from "node:path";
import { CONFIG_PATH, type HookMappingConfig, type HooksConfig } from "../config/config.js";
import { importFileModule, resolveFunctionModuleExport } from "../hooks/module-loader.js";
import { readStringValue } from "../shared/string-coerce.js";
import { normalizeOptionalString, readStringValue } from "../shared/string-coerce.js";
import type { HookMessageChannel } from "./hooks.js";
export type HookMappingResolved = {
@@ -196,7 +196,7 @@ function normalizeHookMapping(
const transform = mapping.transform
? {
modulePath: resolveContainedPath(transformsDir, mapping.transform.module, "Hook transform"),
exportName: mapping.transform.export?.trim() || undefined,
exportName: normalizeOptionalString(mapping.transform.export),
}
: undefined;
@@ -207,7 +207,7 @@ function normalizeHookMapping(
action,
wakeMode,
name: mapping.name,
agentId: mapping.agentId?.trim() || undefined,
agentId: normalizeOptionalString(mapping.agentId),
sessionKey: mapping.sessionKey,
messageTemplate: mapping.messageTemplate,
textTemplate: mapping.textTemplate,

View File

@@ -484,7 +484,7 @@ export const agentHandlers: GatewayRequestHandlers = {
return;
}
}
let resolvedSessionId = request.sessionId?.trim() || undefined;
let resolvedSessionId = normalizeOptionalString(request.sessionId);
let sessionEntry: SessionEntry | undefined;
let bestEffortDeliver = requestedBestEffortDeliver ?? false;
let cfgForAgent: ReturnType<typeof loadConfig> | undefined;

View File

@@ -27,6 +27,7 @@ import {
parseAgentSessionKey,
toAgentStoreSessionKey,
} from "../routing/session-key.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { captureEnv } from "../test-utils/env.js";
import { getDeterministicFreePortBlock } from "../test-utils/ports.js";
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
@@ -881,8 +882,8 @@ export async function connectReq(
? ((testState.gatewayAuth as { password?: string }).password ?? undefined)
: process.env.OPENCLAW_GATEWAY_PASSWORD;
const token = opts?.token ?? defaultToken;
const bootstrapToken = opts?.bootstrapToken?.trim() || undefined;
const deviceToken = opts?.deviceToken?.trim() || undefined;
const bootstrapToken = normalizeOptionalString(opts?.bootstrapToken);
const deviceToken = normalizeOptionalString(opts?.deviceToken);
const password = opts?.password ?? defaultPassword;
const authTokenForSignature = resolveAuthTokenForSignature({
token,

View File

@@ -27,6 +27,7 @@ import {
import { normalizeSystemRunApprovalPlan } from "../infra/system-run-approval-binding.js";
import { resolveSystemRunCommandRequest } from "../infra/system-run-command.js";
import { logWarn } from "../logger.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { evaluateSystemRunPolicy, resolveExecApprovalDecision } from "./exec-policy.js";
import {
applyOutputTruncation,
@@ -252,9 +253,9 @@ async function parseSystemRunPhase(
});
return null;
}
const agentId = opts.params.agentId?.trim() || undefined;
const sessionKey = opts.params.sessionKey?.trim() || "node";
const runId = opts.params.runId?.trim() || crypto.randomUUID();
const agentId = normalizeOptionalString(opts.params.agentId);
const sessionKey = normalizeOptionalString(opts.params.sessionKey) ?? "node";
const runId = normalizeOptionalString(opts.params.runId) ?? crypto.randomUUID();
const suppressNotifyOnExit = opts.params.suppressNotifyOnExit === true;
const envOverrideDiagnostics = inspectHostExecEnvOverrides({
overrides: opts.params.env ?? undefined,
@@ -301,7 +302,7 @@ async function parseSystemRunPhase(
approvalDecision: resolveExecApprovalDecision(opts.params.approvalDecision),
envOverrides,
env: opts.sanitizeEnv(envOverrides),
cwd: opts.params.cwd?.trim() || undefined,
cwd: normalizeOptionalString(opts.params.cwd),
timeoutMs: opts.params.timeoutMs ?? undefined,
needsScreenRecording: opts.params.needsScreenRecording === true,
approved: opts.params.approved === true,

View File

@@ -9,6 +9,7 @@ import { withFileLock as withPathLock } from "../infra/file-lock.js";
import { resolveRequiredHomeDir } from "../infra/home-dir.js";
import { readJsonFileWithFallback, writeJsonFileAtomically } from "../plugin-sdk/json-store.js";
import { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
const PAIRING_CODE_LENGTH = 8;
const PAIRING_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
@@ -870,11 +871,11 @@ export async function approveChannelPairingCode(params: {
version: 1,
requests: pruned,
} satisfies PairingStore);
const entryAccountId = String(entry.meta?.accountId ?? "").trim() || undefined;
const entryAccountId = normalizeOptionalString(String(entry.meta?.accountId ?? ""));
await addChannelAllowFromStoreEntry({
channel: params.channel,
entry: entry.id,
accountId: params.accountId?.trim() || entryAccountId,
accountId: normalizeOptionalString(params.accountId) ?? entryAccountId,
env,
});
return { id: entry.id, entry };