refactor: dedupe tlon and voice-call lowercase helpers

This commit is contained in:
Peter Steinberger
2026-04-07 13:19:05 +01:00
parent 88b394ba1b
commit a93a94788a
10 changed files with 32 additions and 15 deletions

View File

@@ -1,7 +1,10 @@
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "openclaw/plugin-sdk/text-runtime";
import {
clearDeviceBootstrapTokens,
definePluginEntry,
@@ -563,7 +566,7 @@ export default definePluginEntry({
handler: async (ctx) => {
const args = normalizeOptionalString(ctx.args) ?? "";
const tokens = args.split(/\s+/).filter(Boolean);
const action = tokens[0]?.toLowerCase() ?? "";
const action = normalizeLowercaseStringOrEmpty(tokens[0]);
const gatewayClientScopes = Array.isArray(ctx.gatewayClientScopes)
? ctx.gatewayClientScopes
: undefined;
@@ -583,7 +586,7 @@ export default definePluginEntry({
}
if (action === "notify") {
const notifyAction = normalizeOptionalString(tokens[1])?.toLowerCase() ?? "status";
const notifyAction = normalizeLowercaseStringOrEmpty(tokens[1]) || "status";
return await handleNotifyCommand({
api,
ctx,

View File

@@ -1,4 +1,7 @@
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "openclaw/plugin-sdk/text-runtime";
import { approveDevicePairing, listDevicePairing } from "./api.js";
import { formatPendingRequests } from "./notify.js";
@@ -31,7 +34,7 @@ export function selectPendingApprovalRequest(params: {
: { reply: buildMultiplePendingApprovalReply(params.pending) };
}
if (params.requested.toLowerCase() === "latest") {
if (normalizeLowercaseStringOrEmpty(params.requested) === "latest") {
return {
pending: [...params.pending].toSorted((a, b) => (b.ts ?? 0) - (a.ts ?? 0))[0],
};

View File

@@ -7,6 +7,7 @@
// Extensions cannot import core internals directly, so use node:crypto here.
import { randomBytes } from "node:crypto";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import type { PendingApproval } from "../settings.js";
export type { PendingApproval };
@@ -106,7 +107,7 @@ export type ApprovalResponse = {
* - "block" permanently blocks the ship via Tlon's native blocking
*/
export function parseApprovalResponse(text: string): ApprovalResponse | null {
const trimmed = text.trim().toLowerCase();
const trimmed = normalizeLowercaseStringOrEmpty(text);
// Match "approve", "deny", or "block" optionally followed by an ID
const match = trimmed.match(/^(approve|deny|block)(?:\s+(.+))?$/);
@@ -125,7 +126,7 @@ export function parseApprovalResponse(text: string): ApprovalResponse | null {
* Used to determine if we should intercept the message before normal processing.
*/
export function isApprovalResponse(text: string): boolean {
const trimmed = text.trim().toLowerCase();
const trimmed = normalizeLowercaseStringOrEmpty(text);
return trimmed.startsWith("approve") || trimmed.startsWith("deny") || trimmed.startsWith("block");
}

View File

@@ -7,6 +7,7 @@ import {
MAX_IMAGE_BYTES,
saveMediaBuffer,
} from "openclaw/plugin-sdk/media-runtime";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { getDefaultSsrFPolicy } from "../urbit/context.js";
const MAX_IMAGES_PER_MESSAGE = 8;
@@ -136,7 +137,7 @@ function getExtensionFromUrl(url: string): string | null {
try {
const pathname = new URL(url).pathname;
const match = pathname.match(/\.([a-z0-9]+)$/i);
return match ? match[1].toLowerCase() : null;
return match ? normalizeLowercaseStringOrEmpty(match[1]) : null;
} catch {
return null;
}

View File

@@ -1,6 +1,7 @@
import crypto from "node:crypto";
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { authenticate } from "./urbit/auth.js";
import { scryUrbitPath } from "./urbit/channel-ops.js";
import { ssrfPolicyFromDangerouslyAllowPrivateNetwork } from "./urbit/context.js";
@@ -72,7 +73,7 @@ function getExtensionFromMimeType(mimeType?: string): string {
if (!mimeType) {
return ".jpg";
}
return mimeToExt[mimeType.toLowerCase()] || ".jpg";
return mimeToExt[normalizeLowercaseStringOrEmpty(mimeType)] || ".jpg";
}
function hasCustomS3Creds(

View File

@@ -1,4 +1,5 @@
import crypto from "node:crypto";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import type {
EndReason,
GetCallStatusInput,
@@ -170,7 +171,7 @@ export class MockProvider implements VoiceCallProvider {
}
async getCallStatus(input: GetCallStatusInput): Promise<GetCallStatusResult> {
const id = input.providerCallId.toLowerCase();
const id = normalizeLowercaseStringOrEmpty(input.providerCallId);
if (id.includes("stale") || id.includes("ended") || id.includes("completed")) {
return { status: "completed", isTerminal: true };
}

View File

@@ -1,4 +1,5 @@
import crypto from "node:crypto";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import type { PlivoConfig, WebhookSecurityConfig } from "../config.js";
import { getHeader } from "../http-headers.js";
import type {
@@ -480,7 +481,7 @@ export class PlivoProvider implements VoiceCallProvider {
private static normalizeNumber(numberOrSip: string): string {
const trimmed = numberOrSip.trim();
if (trimmed.toLowerCase().startsWith("sip:")) {
if (normalizeLowercaseStringOrEmpty(trimmed).startsWith("sip:")) {
return trimmed;
}
return trimmed.replace(/[^\d+]/g, "");

View File

@@ -4,6 +4,7 @@
*/
import crypto from "node:crypto";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import type { SessionEntry } from "../api.js";
import type { VoiceCallConfig } from "./config.js";
import type { CoreAgentDeps, CoreConfig } from "./core-bridge.js";
@@ -95,7 +96,7 @@ function tryParseSpokenJson(text: string): string | null {
}
function isLikelyMetaReasoningParagraph(paragraph: string): boolean {
const lower = paragraph.toLowerCase();
const lower = normalizeLowercaseStringOrEmpty(paragraph);
if (!lower) {
return false;
}

View File

@@ -1,5 +1,8 @@
import type { MsgContext } from "../auto-reply/templating.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "../shared/string-coerce.js";
import { normalizeChatType } from "./chat-type.js";
function extractConversationId(from?: string): string | undefined {
@@ -60,7 +63,7 @@ export function resolveConversationLabel(ctx: MsgContext): string | undefined {
if (base.includes(id)) {
return base;
}
if (base.toLowerCase().includes(" id:")) {
if (normalizeLowercaseStringOrEmpty(base).includes(" id:")) {
return base;
}
if (base.startsWith("#") || base.startsWith("@")) {

View File

@@ -1,3 +1,5 @@
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
export type ServicePrefix<TService extends string> = { prefix: string; service: TService };
export type ChatTargetPrefixesParams = {
@@ -94,7 +96,7 @@ export function resolveServicePrefixedTarget<TService extends string, TTarget>(p
if (!remainder) {
throw new Error(`${prefix} target is required`);
}
const remainderLower = remainder.toLowerCase();
const remainderLower = normalizeLowercaseStringOrEmpty(remainder);
if (params.isChatTarget(remainderLower)) {
return params.parseTarget(remainder);
}