refactor: dedupe gateway trimmed readers

This commit is contained in:
Peter Steinberger
2026-04-07 23:40:48 +01:00
parent 65ea8c60f3
commit 134588fc17
10 changed files with 51 additions and 39 deletions

View File

@@ -432,7 +432,7 @@ export const agentHandlers: GatewayRequestHandlers = {
}
}
const agentIdRaw = typeof request.agentId === "string" ? request.agentId.trim() : "";
const agentIdRaw = normalizeOptionalString(request.agentId) ?? "";
const agentId = agentIdRaw ? normalizeAgentId(agentIdRaw) : undefined;
if (agentId) {
const knownAgents = listAgentIds(cfg);
@@ -449,10 +449,7 @@ export const agentHandlers: GatewayRequestHandlers = {
}
}
const requestedSessionKeyRaw =
typeof request.sessionKey === "string" && request.sessionKey.trim()
? request.sessionKey.trim()
: undefined;
const requestedSessionKeyRaw = normalizeOptionalString(request.sessionKey);
if (
requestedSessionKeyRaw &&
classifySessionKeyShape(requestedSessionKeyRaw) === "malformed_agent"
@@ -517,7 +514,7 @@ export const agentHandlers: GatewayRequestHandlers = {
}
requestedSessionKey = resetResult.key;
resolvedSessionId = resetResult.sessionId ?? resolvedSessionId;
const postResetMessage = resetCommandMatch[2]?.trim() ?? "";
const postResetMessage = normalizeOptionalString(resetCommandMatch[2]) ?? "";
if (postResetMessage) {
message = postResetMessage;
} else {
@@ -867,8 +864,8 @@ export const agentHandlers: GatewayRequestHandlers = {
return;
}
const p = params;
const agentIdRaw = typeof p.agentId === "string" ? p.agentId.trim() : "";
const sessionKeyRaw = typeof p.sessionKey === "string" ? p.sessionKey.trim() : "";
const agentIdRaw = normalizeOptionalString(p.agentId) ?? "";
const sessionKeyRaw = normalizeOptionalString(p.sessionKey) ?? "";
let agentId = agentIdRaw ? normalizeAgentId(agentIdRaw) : undefined;
if (sessionKeyRaw) {
if (classifySessionKeyShape(sessionKeyRaw) === "malformed_agent") {

View File

@@ -553,7 +553,7 @@ export const agentsHandlers: GatewayRequestHandlers = {
}
const cfg = loadConfig();
const rawName = String(params.name ?? "").trim();
const rawName = normalizeOptionalString(String(params.name ?? "")) ?? "";
const agentId = normalizeAgentId(rawName);
if (agentId === DEFAULT_AGENT_ID) {
respond(
@@ -573,7 +573,9 @@ export const agentsHandlers: GatewayRequestHandlers = {
return;
}
const workspaceDir = resolveUserPath(String(params.workspace ?? "").trim());
const workspaceDir = resolveUserPath(
normalizeOptionalString(String(params.workspace ?? "")) ?? "",
);
// Resolve agentDir against the config we're about to persist (vs the pre-write config),
// so subsequent resolutions can't disagree about the agent's directory.

View File

@@ -14,6 +14,7 @@ import { applyPluginAutoEnable } from "../../config/plugin-auto-enable.js";
import { getChannelActivity } from "../../infra/channel-activity.js";
import { DEFAULT_ACCOUNT_ID } from "../../routing/session-key.js";
import { defaultRuntime } from "../../runtime.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import {
ErrorCodes,
errorShape,
@@ -39,7 +40,7 @@ export async function logoutChannelAccount(params: {
plugin: ChannelPlugin;
}): Promise<ChannelLogoutPayload> {
const resolvedAccountId =
params.accountId?.trim() ||
normalizeOptionalString(params.accountId) ||
params.plugin.config.defaultAccountId?.(params.cfg) ||
params.plugin.config.listAccountIds(params.cfg)[0] ||
DEFAULT_ACCOUNT_ID;
@@ -270,7 +271,7 @@ export const channelsHandlers: GatewayRequestHandlers = {
return;
}
const accountIdRaw = (params as { accountId?: unknown }).accountId;
const accountId = typeof accountIdRaw === "string" ? accountIdRaw.trim() : undefined;
const accountId = normalizeOptionalString(accountIdRaw);
const snapshot = await readConfigFileSnapshot();
if (!snapshot.valid) {
respond(

View File

@@ -16,6 +16,7 @@ import {
buildSystemRunApprovalEnvBinding,
} from "../../infra/system-run-approval-binding.js";
import { resolveSystemRunApprovalRequestContext } from "../../infra/system-run-approval-context.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import type { ExecApprovalManager } from "../exec-approval-manager.js";
import {
ErrorCodes,
@@ -143,9 +144,9 @@ export function createExecApprovalHandlers(
const twoPhase = p.twoPhase === true;
const timeoutMs =
typeof p.timeoutMs === "number" ? p.timeoutMs : DEFAULT_EXEC_APPROVAL_TIMEOUT_MS;
const explicitId = typeof p.id === "string" && p.id.trim().length > 0 ? p.id.trim() : null;
const host = typeof p.host === "string" ? p.host.trim() : "";
const nodeId = typeof p.nodeId === "string" ? p.nodeId.trim() : "";
const explicitId = normalizeOptionalString(p.id) ?? null;
const host = normalizeOptionalString(p.host) ?? "";
const nodeId = normalizeOptionalString(p.nodeId) ?? "";
const approvalContext = resolveSystemRunApprovalRequestContext({
host,
command: p.command,

View File

@@ -19,7 +19,10 @@ import {
resolveApnsAuthConfigFromEnv,
resolveApnsRelayConfigFromEnv,
} from "../../infra/push-apns.js";
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "../../shared/string-coerce.js";
import {
buildCanvasScopedHostUrl,
CANVAS_CAPABILITY_TTL_MS,
@@ -126,8 +129,8 @@ function isForbiddenBrowserProxyMutation(params: unknown): boolean {
return false;
}
const candidate = params as { method?: unknown; path?: unknown };
const method = typeof candidate.method === "string" ? candidate.method.trim().toUpperCase() : "";
const path = typeof candidate.path === "string" ? candidate.path.trim() : "";
const method = (normalizeOptionalString(candidate.method) ?? "").toUpperCase();
const path = normalizeOptionalString(candidate.path) ?? "";
return Boolean(method && path && isPersistentBrowserProxyMutation(method, path));
}
@@ -715,7 +718,7 @@ export const nodeHandlers: GatewayRequestHandlers = {
return;
}
const { nodeId } = params as { nodeId: string };
const id = String(nodeId ?? "").trim();
const id = normalizeOptionalString(String(nodeId ?? "")) ?? "";
if (!id) {
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "nodeId required"));
return;
@@ -747,7 +750,7 @@ export const nodeHandlers: GatewayRequestHandlers = {
});
return;
}
const baseCanvasHostUrl = client?.canvasHostUrl?.trim() ?? "";
const baseCanvasHostUrl = normalizeOptionalString(client?.canvasHostUrl) ?? "";
if (!baseCanvasHostUrl) {
respond(
false,
@@ -793,7 +796,7 @@ export const nodeHandlers: GatewayRequestHandlers = {
return;
}
const nodeId = client?.connect?.device?.id ?? client?.connect?.client?.id;
const trimmedNodeId = String(nodeId ?? "").trim();
const trimmedNodeId = normalizeOptionalString(String(nodeId ?? "")) ?? "";
if (!trimmedNodeId) {
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "nodeId required"));
return;
@@ -824,13 +827,17 @@ export const nodeHandlers: GatewayRequestHandlers = {
return;
}
const nodeId = client?.connect?.device?.id ?? client?.connect?.client?.id;
const trimmedNodeId = String(nodeId ?? "").trim();
const trimmedNodeId = normalizeOptionalString(String(nodeId ?? "")) ?? "";
if (!trimmedNodeId) {
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "nodeId required"));
return;
}
const ackIds = Array.from(
new Set((params.ids ?? []).map((value) => String(value ?? "").trim()).filter(Boolean)),
new Set(
(params.ids ?? [])
.map((value) => normalizeOptionalString(String(value ?? "")) ?? "")
.filter(Boolean),
),
);
const remaining = ackPendingNodeActions(trimmedNodeId, ackIds);
respond(
@@ -859,8 +866,8 @@ export const nodeHandlers: GatewayRequestHandlers = {
timeoutMs?: number;
idempotencyKey: string;
};
const nodeId = String(p.nodeId ?? "").trim();
const command = String(p.command ?? "").trim();
const nodeId = normalizeOptionalString(String(p.nodeId ?? "")) ?? "";
const command = normalizeOptionalString(String(p.command ?? "")) ?? "";
if (!nodeId || !command) {
respond(
false,

View File

@@ -19,6 +19,7 @@ import { fetchClawHubSkillDetail } from "../../infra/clawhub.js";
import { formatErrorMessage } from "../../infra/errors.js";
import { getRemoteSkillEligibility } from "../../infra/skills-remote.js";
import { normalizeAgentId } from "../../routing/session-key.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import { normalizeSecretInput } from "../../utils/normalize-secret-input.js";
import {
ErrorCodes,
@@ -54,7 +55,7 @@ function collectSkillBins(entries: SkillEntry[]): string[] {
for (const spec of install) {
const specBins = spec?.bins ?? [];
for (const bin of specBins) {
const trimmed = String(bin).trim();
const trimmed = normalizeOptionalString(String(bin)) ?? "";
if (trimmed) {
bins.add(trimmed);
}
@@ -78,7 +79,7 @@ export const skillsHandlers: GatewayRequestHandlers = {
return;
}
const cfg = loadConfig();
const agentIdRaw = typeof params?.agentId === "string" ? params.agentId.trim() : "";
const agentIdRaw = normalizeOptionalString(params?.agentId) ?? "";
const agentId = agentIdRaw ? normalizeAgentId(agentIdRaw) : resolveDefaultAgentId(cfg);
if (agentIdRaw) {
const knownAgents = listAgentIds(cfg);

View File

@@ -1,3 +1,4 @@
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import { ADMIN_SCOPE } from "../method-scopes.js";
import {
ErrorCodes,
@@ -23,7 +24,7 @@ function resolveRequestedAgentIdOrRespondError(params: {
respond: RespondFn;
}) {
const knownAgents = listAgentIds(params.cfg);
const requestedAgentId = typeof params.rawAgentId === "string" ? params.rawAgentId.trim() : "";
const requestedAgentId = normalizeOptionalString(params.rawAgentId) ?? "";
if (!requestedAgentId) {
return undefined;
}

View File

@@ -1,4 +1,5 @@
import { loadConfig } from "../../config/config.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import {
canonicalizeSpeechProviderId,
getSpeechProvider,
@@ -78,7 +79,7 @@ export const ttsHandlers: GatewayRequestHandlers = {
}
},
"tts.convert": async ({ params, respond }) => {
const text = typeof params.text === "string" ? params.text.trim() : "";
const text = normalizeOptionalString(params.text) ?? "";
if (!text) {
respond(
false,
@@ -89,10 +90,10 @@ export const ttsHandlers: GatewayRequestHandlers = {
}
try {
const cfg = loadConfig();
const channel = typeof params.channel === "string" ? params.channel.trim() : undefined;
const providerRaw = typeof params.provider === "string" ? params.provider.trim() : undefined;
const modelId = typeof params.modelId === "string" ? params.modelId.trim() : undefined;
const voiceId = typeof params.voiceId === "string" ? params.voiceId.trim() : undefined;
const channel = normalizeOptionalString(params.channel);
const providerRaw = normalizeOptionalString(params.provider);
const modelId = normalizeOptionalString(params.modelId);
const voiceId = normalizeOptionalString(params.voiceId);
let overrides;
try {
overrides = resolveExplicitTtsOverrides({
@@ -133,7 +134,7 @@ export const ttsHandlers: GatewayRequestHandlers = {
"tts.setProvider": async ({ params, respond }) => {
const cfg = loadConfig();
const provider = canonicalizeSpeechProviderId(
typeof params.provider === "string" ? params.provider.trim() : "",
normalizeOptionalString(params.provider) ?? "",
cfg,
);
if (!provider || !getSpeechProvider(provider, cfg)) {

View File

@@ -1,6 +1,7 @@
import type { OpenClawConfig } from "../config/config.js";
import { loadSessionStore, updateSessionStore } from "../config/sessions.js";
import { parseSessionLabel } from "../sessions/session-label.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import {
ErrorCodes,
type ErrorShape,
@@ -56,11 +57,11 @@ export async function resolveSessionKeyFromResolveParams(params: {
}): Promise<SessionsResolveResult> {
const { cfg, p } = params;
const key = typeof p.key === "string" ? p.key.trim() : "";
const key = normalizeOptionalString(p.key) ?? "";
const hasKey = key.length > 0;
const sessionId = typeof p.sessionId === "string" ? p.sessionId.trim() : "";
const sessionId = normalizeOptionalString(p.sessionId) ?? "";
const hasSessionId = sessionId.length > 0;
const hasLabel = typeof p.label === "string" && p.label.trim().length > 0;
const hasLabel = (normalizeOptionalString(p.label) ?? "").length > 0;
const selectionCount = [hasKey, hasSessionId, hasLabel].filter(Boolean).length;
if (selectionCount > 1) {
return {

View File

@@ -189,7 +189,7 @@ export async function handleToolsInvokeHttpRequest(
}
const body = (bodyUnknown ?? {}) as ToolsInvokeBody;
const toolName = typeof body.tool === "string" ? body.tool.trim() : "";
const toolName = normalizeOptionalString(body.tool) ?? "";
if (!toolName) {
sendInvalidRequest(res, "tools.invoke requires body.tool");
return true;
@@ -212,7 +212,7 @@ export async function handleToolsInvokeHttpRequest(
}
}
const action = typeof body.action === "string" ? body.action.trim() : undefined;
const action = normalizeOptionalString(body.action);
const argsRaw = body.args;
const args =