mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-20 13:13:06 +00:00
refactor: dedupe optional string readers
This commit is contained in:
@@ -11,7 +11,7 @@ import {
|
||||
} from "../../infra/restart-sentinel.js";
|
||||
import { scheduleGatewaySigusr1Restart } from "../../infra/restart.js";
|
||||
import { createSubsystemLogger } from "../../logging/subsystem.js";
|
||||
import { readStringValue } from "../../shared/string-coerce.js";
|
||||
import { normalizeOptionalString, readStringValue } from "../../shared/string-coerce.js";
|
||||
import { stringEnum } from "../schema/typebox.js";
|
||||
import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js";
|
||||
import { callGatewayTool, readGatewayCallOptions } from "./gateway.js";
|
||||
@@ -163,19 +163,14 @@ export function createGatewayTool(opts?: {
|
||||
throw new Error("Gateway restart is disabled (commands.restart=false).");
|
||||
}
|
||||
const sessionKey =
|
||||
typeof params.sessionKey === "string" && params.sessionKey.trim()
|
||||
? params.sessionKey.trim()
|
||||
: opts?.agentSessionKey?.trim() || undefined;
|
||||
normalizeOptionalString(params.sessionKey) ??
|
||||
normalizeOptionalString(opts?.agentSessionKey);
|
||||
const delayMs =
|
||||
typeof params.delayMs === "number" && Number.isFinite(params.delayMs)
|
||||
? Math.floor(params.delayMs)
|
||||
: undefined;
|
||||
const reason =
|
||||
typeof params.reason === "string" && params.reason.trim()
|
||||
? params.reason.trim().slice(0, 200)
|
||||
: undefined;
|
||||
const note =
|
||||
typeof params.note === "string" && params.note.trim() ? params.note.trim() : undefined;
|
||||
const reason = normalizeOptionalString(params.reason)?.slice(0, 200);
|
||||
const note = normalizeOptionalString(params.note);
|
||||
// Extract channel + threadId for routing after restart.
|
||||
// Uses generic :thread: parsing plus plugin-owned session grammars.
|
||||
const { deliveryContext, threadId } = extractDeliveryInfo(sessionKey);
|
||||
@@ -216,11 +211,9 @@ export function createGatewayTool(opts?: {
|
||||
restartDelayMs: number | undefined;
|
||||
} => {
|
||||
const sessionKey =
|
||||
typeof params.sessionKey === "string" && params.sessionKey.trim()
|
||||
? params.sessionKey.trim()
|
||||
: opts?.agentSessionKey?.trim() || undefined;
|
||||
const note =
|
||||
typeof params.note === "string" && params.note.trim() ? params.note.trim() : undefined;
|
||||
normalizeOptionalString(params.sessionKey) ??
|
||||
normalizeOptionalString(opts?.agentSessionKey);
|
||||
const note = normalizeOptionalString(params.note);
|
||||
const restartDelayMs =
|
||||
typeof params.restartDelayMs === "number" && Number.isFinite(params.restartDelayMs)
|
||||
? Math.floor(params.restartDelayMs)
|
||||
|
||||
@@ -2,6 +2,7 @@ import { type Api, type Model } from "@mariozechner/pi-ai";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import type { AgentModelConfig } from "../../config/types.agents-shared.js";
|
||||
import { getDefaultLocalRoots } from "../../media/web-media.js";
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
import { normalizeProviderId } from "../provider-id.js";
|
||||
import type { ImageModelConfig } from "./image-tool.helpers.js";
|
||||
import {
|
||||
@@ -214,10 +215,8 @@ export function resolvePromptAndModelOverride(
|
||||
prompt: string;
|
||||
modelOverride?: string;
|
||||
} {
|
||||
const prompt =
|
||||
typeof args.prompt === "string" && args.prompt.trim() ? args.prompt.trim() : defaultPrompt;
|
||||
const modelOverride =
|
||||
typeof args.model === "string" && args.model.trim() ? args.model.trim() : undefined;
|
||||
const prompt = normalizeOptionalString(args.prompt) ?? defaultPrompt;
|
||||
const modelOverride = normalizeOptionalString(args.model);
|
||||
return { prompt, modelOverride };
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Type } from "@sinclair/typebox";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { extractPdfContent, type PdfExtractedContent } from "../../media/pdf-extract.js";
|
||||
import { loadWebMediaRaw } from "../../media/web-media.js";
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
import { resolveUserPath } from "../../utils.js";
|
||||
import { type ImageModelConfig } from "./image-tool.helpers.js";
|
||||
import { resolvePdfModelConfigForTool } from "./pdf-tool.model-config.js";
|
||||
@@ -323,8 +324,7 @@ export function createPdfTool(options?: {
|
||||
const maxBytes = Math.floor(maxBytesMb * 1024 * 1024);
|
||||
|
||||
// Parse page range
|
||||
const pagesRaw =
|
||||
typeof record.pages === "string" && record.pages.trim() ? record.pages.trim() : undefined;
|
||||
const pagesRaw = normalizeOptionalString(record.pages);
|
||||
|
||||
const sandboxConfig: SandboxedBridgeMediaPathConfig | null =
|
||||
options?.sandbox && options.sandbox.root.trim()
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
import { resolveSecretInputRef } from "../config/types.secrets.js";
|
||||
import { readTailscaleWhoisIdentity, type TailscaleWhoisIdentity } from "../infra/tailscale.js";
|
||||
import { safeEqualSecret } from "../security/secret-equal.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import {
|
||||
AUTH_RATE_LIMIT_SCOPE_SHARED_SECRET,
|
||||
type AuthRateLimiter,
|
||||
@@ -157,17 +158,17 @@ function getTailscaleUser(req?: IncomingMessage): TailscaleUser | null {
|
||||
if (!req) {
|
||||
return null;
|
||||
}
|
||||
const login = req.headers["tailscale-user-login"];
|
||||
if (typeof login !== "string" || !login.trim()) {
|
||||
const login = normalizeOptionalString(req.headers["tailscale-user-login"]);
|
||||
if (!login) {
|
||||
return null;
|
||||
}
|
||||
const nameRaw = req.headers["tailscale-user-name"];
|
||||
const profilePic = req.headers["tailscale-user-profile-pic"];
|
||||
const name = typeof nameRaw === "string" && nameRaw.trim() ? nameRaw.trim() : login.trim();
|
||||
const name = normalizeOptionalString(nameRaw) ?? login;
|
||||
return {
|
||||
login: login.trim(),
|
||||
login,
|
||||
name,
|
||||
profilePic: typeof profilePic === "string" && profilePic.trim() ? profilePic.trim() : undefined,
|
||||
profilePic: normalizeOptionalString(profilePic),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import type { PromptImageOrderEntry } from "../media/prompt-image-order.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import type { NodeEvent, NodeEventContext } from "./server-node-events-types.js";
|
||||
import {
|
||||
agentCommandFromIngress,
|
||||
@@ -37,28 +38,20 @@ const MAX_RECENT_VOICE_TRANSCRIPTS = 200;
|
||||
|
||||
const recentVoiceTranscripts = new Map<string, { fingerprint: string; ts: number }>();
|
||||
|
||||
function normalizeNonEmptyString(value: unknown): string | null {
|
||||
if (typeof value !== "string") {
|
||||
return null;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : null;
|
||||
}
|
||||
|
||||
function normalizeFiniteInteger(value: unknown): number | null {
|
||||
return typeof value === "number" && Number.isFinite(value) ? Math.trunc(value) : null;
|
||||
}
|
||||
|
||||
function resolveVoiceTranscriptFingerprint(obj: Record<string, unknown>, text: string): string {
|
||||
const eventId =
|
||||
normalizeNonEmptyString(obj.eventId) ??
|
||||
normalizeNonEmptyString(obj.providerEventId) ??
|
||||
normalizeNonEmptyString(obj.transcriptId);
|
||||
normalizeOptionalString(obj.eventId) ??
|
||||
normalizeOptionalString(obj.providerEventId) ??
|
||||
normalizeOptionalString(obj.transcriptId);
|
||||
if (eventId) {
|
||||
return `event:${eventId}`;
|
||||
}
|
||||
|
||||
const callId = normalizeNonEmptyString(obj.providerCallId) ?? normalizeNonEmptyString(obj.callId);
|
||||
const callId = normalizeOptionalString(obj.providerCallId) ?? normalizeOptionalString(obj.callId);
|
||||
const sequence = normalizeFiniteInteger(obj.sequence) ?? normalizeFiniteInteger(obj.seq);
|
||||
if (callId && sequence !== null) {
|
||||
return `call-seq:${callId}:${sequence}`;
|
||||
@@ -428,12 +421,12 @@ export const handleNodeEvent = async (ctx: NodeEventContext, nodeId: string, evt
|
||||
|
||||
const channelRaw = typeof link?.channel === "string" ? link.channel.trim() : "";
|
||||
let channel = normalizeChannelId(channelRaw) ?? undefined;
|
||||
let to = typeof link?.to === "string" && link.to.trim() ? link.to.trim() : undefined;
|
||||
let to = normalizeOptionalString(link?.to);
|
||||
const deliverRequested = Boolean(link?.deliver);
|
||||
const wantsReceipt = Boolean(link?.receipt);
|
||||
const receiptTextRaw = typeof link?.receiptText === "string" ? link.receiptText.trim() : "";
|
||||
const receiptText =
|
||||
receiptTextRaw || "Just received your iOS share + request, working on it.";
|
||||
normalizeOptionalString(link?.receiptText) ||
|
||||
"Just received your iOS share + request, working on it.";
|
||||
|
||||
const now = Date.now();
|
||||
const sessionId = entry?.sessionId ?? randomUUID();
|
||||
@@ -508,24 +501,24 @@ export const handleNodeEvent = async (ctx: NodeEventContext, nodeId: string, evt
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
const change = normalizeNonEmptyString(obj.change)?.toLowerCase();
|
||||
const change = normalizeOptionalString(obj.change)?.toLowerCase();
|
||||
if (change !== "posted" && change !== "removed") {
|
||||
return;
|
||||
}
|
||||
const keyRaw = normalizeNonEmptyString(obj.key);
|
||||
const keyRaw = normalizeOptionalString(obj.key);
|
||||
if (!keyRaw) {
|
||||
return;
|
||||
}
|
||||
const key = sanitizeInboundSystemTags(keyRaw);
|
||||
const sessionKeyRaw = normalizeNonEmptyString(obj.sessionKey) ?? `node-${nodeId}`;
|
||||
const sessionKeyRaw = normalizeOptionalString(obj.sessionKey) ?? `node-${nodeId}`;
|
||||
const { canonicalKey: sessionKey } = loadSessionEntry(sessionKeyRaw);
|
||||
const packageNameRaw = normalizeNonEmptyString(obj.packageName);
|
||||
const packageNameRaw = normalizeOptionalString(obj.packageName);
|
||||
const packageName = packageNameRaw ? sanitizeInboundSystemTags(packageNameRaw) : null;
|
||||
const title = compactNotificationEventText(
|
||||
sanitizeInboundSystemTags(normalizeNonEmptyString(obj.title) ?? ""),
|
||||
sanitizeInboundSystemTags(normalizeOptionalString(obj.title) ?? ""),
|
||||
);
|
||||
const text = compactNotificationEventText(
|
||||
sanitizeInboundSystemTags(normalizeNonEmptyString(obj.text) ?? ""),
|
||||
sanitizeInboundSystemTags(normalizeOptionalString(obj.text) ?? ""),
|
||||
);
|
||||
|
||||
let summary = `Notification ${change} (node=${nodeId} key=${key}`;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
import {
|
||||
INTERNAL_MESSAGE_CHANNEL,
|
||||
isDeliverableMessageChannel,
|
||||
@@ -23,16 +24,13 @@ export function resolveExternalBestEffortDeliveryTarget(params: {
|
||||
normalizedChannel && isDeliverableMessageChannel(normalizedChannel)
|
||||
? normalizedChannel
|
||||
: undefined;
|
||||
const to = typeof params.to === "string" && params.to.trim() ? params.to.trim() : undefined;
|
||||
const to = normalizeOptionalString(params.to);
|
||||
const deliver = Boolean(channel && to);
|
||||
return {
|
||||
deliver,
|
||||
channel: deliver ? channel : undefined,
|
||||
to: deliver ? to : undefined,
|
||||
accountId:
|
||||
deliver && typeof params.accountId === "string" && params.accountId.trim()
|
||||
? params.accountId.trim()
|
||||
: undefined,
|
||||
accountId: deliver ? normalizeOptionalString(params.accountId) : undefined,
|
||||
threadId:
|
||||
deliver && params.threadId != null && params.threadId !== ""
|
||||
? String(params.threadId)
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import type { SessionEntry } from "../config/sessions/types.js";
|
||||
import { stripEnvelope, stripMessageIdHints } from "../shared/chat-envelope.js";
|
||||
import { asFiniteNumber } from "../shared/number-coercion.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import { countToolResults, extractToolCallNames } from "../utils/transcript-tools.js";
|
||||
import { estimateUsageCost, resolveModelCostConfig } from "../utils/usage-format.js";
|
||||
import type {
|
||||
@@ -945,8 +946,7 @@ export async function loadSessionLogs(params: {
|
||||
|
||||
const contentParts: string[] = [];
|
||||
const rawToolName = message.toolName ?? message.tool_name ?? message.name ?? message.tool;
|
||||
const toolName =
|
||||
typeof rawToolName === "string" && rawToolName.trim() ? rawToolName.trim() : undefined;
|
||||
const toolName = normalizeOptionalString(rawToolName);
|
||||
if (role === "tool" || role === "toolResult") {
|
||||
contentParts.push(`[Tool: ${toolName ?? "tool"}]`);
|
||||
contentParts.push("[Tool Result]");
|
||||
|
||||
Reference in New Issue
Block a user