diff --git a/extensions/comfy/workflow-runtime.ts b/extensions/comfy/workflow-runtime.ts index bde54fc5729..44aec79f398 100644 --- a/extensions/comfy/workflow-runtime.ts +++ b/extensions/comfy/workflow-runtime.ts @@ -314,7 +314,7 @@ async function uploadInputImage(params: { form.set( "image", new Blob([toBlobBytes(params.image.buffer)], { type: params.image.mimeType }), - params.image.fileName?.trim() || + normalizeOptionalString(params.image.fileName) || `input.${inferFileExtension({ mimeType: params.image.mimeType })}`, ); form.set("type", "input"); @@ -337,7 +337,8 @@ async function uploadInputImage(params: { errorPrefix: "Comfy image upload failed", }); - const uploadedName = payload.filename?.trim() || payload.name?.trim(); + const uploadedName = + normalizeOptionalString(payload.filename) || normalizeOptionalString(payload.name); if (!uploadedName) { throw new Error("Comfy image upload response missing filename"); } @@ -473,15 +474,16 @@ async function downloadOutputFile(params: { mode: ComfyMode; capability: ComfyCapability; }): Promise<{ buffer: Buffer; mimeType: string }> { - const fileName = params.file.filename?.trim() || params.file.name?.trim(); + const fileName = + normalizeOptionalString(params.file.filename) || normalizeOptionalString(params.file.name); if (!fileName) { throw new Error("Comfy output entry missing filename"); } const query = new URLSearchParams({ filename: fileName, - subfolder: params.file.subfolder?.trim() ?? "", - type: params.file.type?.trim() ?? "output", + subfolder: normalizeOptionalString(params.file.subfolder) ?? "", + type: normalizeOptionalString(params.file.type) ?? "output", }); const viewPath = params.mode === "cloud" ? "/api/view" : "/view"; const auditContext = `comfy-${params.capability}-download`; @@ -504,7 +506,7 @@ async function downloadOutputFile(params: { params.mode === "cloud" && [301, 302, 303, 307, 308].includes(firstResponse.response.status) ) { - const redirectUrl = firstResponse.response.headers.get("location")?.trim(); + const redirectUrl = normalizeOptionalString(firstResponse.response.headers.get("location")); if (!redirectUrl) { throw new Error("Comfy cloud output redirect missing location header"); } @@ -520,7 +522,8 @@ async function downloadOutputFile(params: { try { await assertOkOrThrowHttpError(redirected.response, "Comfy output download failed"); const mimeType = - redirected.response.headers.get("content-type")?.trim() || "application/octet-stream"; + normalizeOptionalString(redirected.response.headers.get("content-type")) || + "application/octet-stream"; return { buffer: Buffer.from(await redirected.response.arrayBuffer()), mimeType, @@ -532,7 +535,8 @@ async function downloadOutputFile(params: { await assertOkOrThrowHttpError(firstResponse.response, "Comfy output download failed"); const mimeType = - firstResponse.response.headers.get("content-type")?.trim() || "application/octet-stream"; + normalizeOptionalString(firstResponse.response.headers.get("content-type")) || + "application/octet-stream"; return { buffer: Buffer.from(await firstResponse.response.arrayBuffer()), mimeType, @@ -592,7 +596,7 @@ export async function runComfyWorkflow(params: { readConfigInteger(capabilityConfig, "pollIntervalMs") ?? DEFAULT_POLL_INTERVAL_MS; const timeoutMs = readConfigInteger(capabilityConfig, "timeoutMs") ?? params.timeoutMs ?? DEFAULT_TIMEOUT_MS; - const providerModel = params.model?.trim() || DEFAULT_COMFY_MODEL; + const providerModel = normalizeOptionalString(params.model) || DEFAULT_COMFY_MODEL; setWorkflowInput({ workflow, @@ -687,7 +691,7 @@ export async function runComfyWorkflow(params: { errorPrefix: "Comfy workflow submit failed", }); - const promptId = promptResponse.prompt_id?.trim(); + const promptId = normalizeOptionalString(promptResponse.prompt_id); if (!promptId) { throw new Error("Comfy workflow submit response missing prompt_id"); } @@ -755,7 +759,8 @@ export async function runComfyWorkflow(params: { capability: params.capability, }); assetIndex += 1; - const originalName = output.file.filename?.trim() || output.file.name?.trim(); + const originalName = + normalizeOptionalString(output.file.filename) || normalizeOptionalString(output.file.name); assets.push({ buffer: downloaded.buffer, mimeType: downloaded.mimeType, diff --git a/extensions/feishu/src/comment-shared.ts b/extensions/feishu/src/comment-shared.ts index 8497e78b634..d2139aab9de 100644 --- a/extensions/feishu/src/comment-shared.ts +++ b/extensions/feishu/src/comment-shared.ts @@ -32,36 +32,32 @@ export function extractCommentElementText(element: unknown): string | undefined if (!isRecord(element)) { return undefined; } - const type = readString(element.type)?.trim(); + const type = normalizeString(element.type); if (type === "text_run" && isRecord(element.text_run)) { - return ( - readString(element.text_run.content)?.trim() || - readString(element.text_run.text)?.trim() || - undefined - ); + return normalizeString(element.text_run.content) || normalizeString(element.text_run.text); } if (type === "mention") { const mention = isRecord(element.mention) ? element.mention : undefined; const mentionName = - readString(mention?.name)?.trim() || - readString(mention?.display_name)?.trim() || - readString(element.name)?.trim(); + normalizeString(mention?.name) || + normalizeString(mention?.display_name) || + normalizeString(element.name); return mentionName ? `@${mentionName}` : "@mention"; } if (type === "docs_link") { const docsLink = isRecord(element.docs_link) ? element.docs_link : undefined; return ( - readString(docsLink?.text)?.trim() || - readString(docsLink?.url)?.trim() || - readString(element.text)?.trim() || - readString(element.url)?.trim() || + normalizeString(docsLink?.text) || + normalizeString(docsLink?.url) || + normalizeString(element.text) || + normalizeString(element.url) || undefined ); } return ( - readString(element.text)?.trim() || - readString(element.content)?.trim() || - readString(element.name)?.trim() || + normalizeString(element.text) || + normalizeString(element.content) || + normalizeString(element.name) || undefined ); } diff --git a/extensions/irc/src/setup-surface.ts b/extensions/irc/src/setup-surface.ts index 8ce001d1aaa..65cc35c4ec1 100644 --- a/extensions/irc/src/setup-surface.ts +++ b/extensions/irc/src/setup-surface.ts @@ -11,6 +11,10 @@ import { formatDocsLink, setSetupChannelEnabled, } from "openclaw/plugin-sdk/setup"; +import { + normalizeOptionalString, + normalizeStringifiedOptionalString, +} from "openclaw/plugin-sdk/text-runtime"; import { resolveDefaultIrcAccountId, resolveIrcAccount } from "./accounts.js"; import { isChannelTarget, @@ -96,7 +100,7 @@ async function promptIrcNickServConfig(params: { await params.prompter.text({ message: "NickServ service nick", initialValue: existing?.service || "NickServ", - validate: (value) => (String(value ?? "").trim() ? undefined : "Required"), + validate: (value) => (normalizeStringifiedOptionalString(value) ? undefined : "Required"), }), ).trim(); @@ -139,7 +143,7 @@ async function promptIrcNickServConfig(params: { (params.accountId === DEFAULT_ACCOUNT_ID ? process.env.IRC_NICKSERV_REGISTER_EMAIL : undefined), - validate: (value) => (String(value ?? "").trim() ? undefined : "Required"), + validate: (value) => (normalizeStringifiedOptionalString(value) ? undefined : "Required"), }), ).trim() : undefined; @@ -199,8 +203,8 @@ export const ircSetupWizard: ChannelSetupWizard = { prepare: async ({ cfg, accountId, credentialValues, prompter }) => { const resolved = resolveIrcAccount({ cfg: cfg as CoreConfig, accountId }); const isDefaultAccount = accountId === DEFAULT_ACCOUNT_ID; - const envHost = isDefaultAccount ? process.env.IRC_HOST?.trim() : ""; - const envNick = isDefaultAccount ? process.env.IRC_NICK?.trim() : ""; + const envHost = isDefaultAccount ? (normalizeOptionalString(process.env.IRC_HOST) ?? "") : ""; + const envNick = isDefaultAccount ? (normalizeOptionalString(process.env.IRC_NICK) ?? "") : ""; const envReady = Boolean(envHost && envNick && !resolved.config.host && !resolved.config.nick); if (envReady) { @@ -243,8 +247,8 @@ export const ircSetupWizard: ChannelSetupWizard = { currentValue: ({ cfg, accountId }) => resolveIrcAccount({ cfg: cfg as CoreConfig, accountId }).config.host || undefined, shouldPrompt: ({ credentialValues }) => credentialValues[USE_ENV_FLAG] !== "1", - validate: ({ value }) => (String(value ?? "").trim() ? undefined : "Required"), - normalizeValue: ({ value }) => String(value).trim(), + validate: ({ value }) => (normalizeStringifiedOptionalString(value) ? undefined : "Required"), + normalizeValue: ({ value }) => normalizeStringifiedOptionalString(value) ?? "", applySet: async ({ cfg, accountId, value }) => updateIrcAccountConfig(cfg as CoreConfig, accountId, { enabled: true, @@ -264,7 +268,7 @@ export const ircSetupWizard: ChannelSetupWizard = { return String(defaultPort); }, validate: ({ value }) => { - const parsed = Number.parseInt(String(value ?? "").trim(), 10); + const parsed = Number.parseInt(normalizeStringifiedOptionalString(value) ?? "", 10); return Number.isFinite(parsed) && parsed >= 1 && parsed <= 65535 ? undefined : "Use a port between 1 and 65535"; @@ -282,8 +286,8 @@ export const ircSetupWizard: ChannelSetupWizard = { currentValue: ({ cfg, accountId }) => resolveIrcAccount({ cfg: cfg as CoreConfig, accountId }).config.nick || undefined, shouldPrompt: ({ credentialValues }) => credentialValues[USE_ENV_FLAG] !== "1", - validate: ({ value }) => (String(value ?? "").trim() ? undefined : "Required"), - normalizeValue: ({ value }) => String(value).trim(), + validate: ({ value }) => (normalizeStringifiedOptionalString(value) ? undefined : "Required"), + normalizeValue: ({ value }) => normalizeStringifiedOptionalString(value) ?? "", applySet: async ({ cfg, accountId, value }) => updateIrcAccountConfig(cfg as CoreConfig, accountId, { enabled: true, @@ -300,8 +304,8 @@ export const ircSetupWizard: ChannelSetupWizard = { resolveIrcAccount({ cfg: cfg as CoreConfig, accountId }).config.username || credentialValues.token || "openclaw", - validate: ({ value }) => (String(value ?? "").trim() ? undefined : "Required"), - normalizeValue: ({ value }) => String(value).trim(), + validate: ({ value }) => (normalizeStringifiedOptionalString(value) ? undefined : "Required"), + normalizeValue: ({ value }) => normalizeStringifiedOptionalString(value) ?? "", applySet: async ({ cfg, accountId, value }) => updateIrcAccountConfig(cfg as CoreConfig, accountId, { enabled: true, @@ -316,8 +320,8 @@ export const ircSetupWizard: ChannelSetupWizard = { shouldPrompt: ({ credentialValues }) => credentialValues[USE_ENV_FLAG] !== "1", initialValue: ({ cfg, accountId }) => resolveIrcAccount({ cfg: cfg as CoreConfig, accountId }).config.realname || "OpenClaw", - validate: ({ value }) => (String(value ?? "").trim() ? undefined : "Required"), - normalizeValue: ({ value }) => String(value).trim(), + validate: ({ value }) => (normalizeStringifiedOptionalString(value) ? undefined : "Required"), + normalizeValue: ({ value }) => normalizeStringifiedOptionalString(value) ?? "", applySet: async ({ cfg, accountId, value }) => updateIrcAccountConfig(cfg as CoreConfig, accountId, { enabled: true, diff --git a/extensions/tlon/src/setup-core.ts b/extensions/tlon/src/setup-core.ts index 118937f458e..f23fbddbd26 100644 --- a/extensions/tlon/src/setup-core.ts +++ b/extensions/tlon/src/setup-core.ts @@ -10,6 +10,10 @@ import { type ChannelSetupWizard, type OpenClawConfig, } from "openclaw/plugin-sdk/setup"; +import { + normalizeOptionalString, + normalizeStringifiedOptionalString, +} from "openclaw/plugin-sdk/text-runtime"; import { buildTlonAccountFields } from "./account-fields.js"; import { normalizeShip } from "./targets.js"; import { listTlonAccountIds, resolveTlonAccount, type TlonResolvedAccount } from "./types.js"; @@ -78,8 +82,10 @@ export function createTlonSetupWizardBase(params: TlonSetupWizardBaseParams): Ch message: "Ship name", placeholder: "~sampel-palnet", currentValue: ({ cfg, accountId }) => resolveTlonAccount(cfg, accountId).ship ?? undefined, - validate: ({ value }) => (String(value ?? "").trim() ? undefined : "Required"), - normalizeValue: ({ value }) => normalizeShip(String(value).trim()), + validate: ({ value }) => + normalizeStringifiedOptionalString(value) ? undefined : "Required", + normalizeValue: ({ value }) => + normalizeShip(normalizeStringifiedOptionalString(value) ?? ""), applySet: async ({ cfg, accountId, value }) => applyTlonSetupConfig({ cfg, @@ -99,7 +105,7 @@ export function createTlonSetupWizardBase(params: TlonSetupWizardBaseParams): Ch } return undefined; }, - normalizeValue: ({ value }) => String(value).trim(), + normalizeValue: ({ value }) => normalizeStringifiedOptionalString(value) ?? "", applySet: async ({ cfg, accountId, value }) => applyTlonSetupConfig({ cfg, @@ -112,8 +118,9 @@ export function createTlonSetupWizardBase(params: TlonSetupWizardBaseParams): Ch message: "Login code", placeholder: "lidlut-tabwed-pillex-ridrup", currentValue: ({ cfg, accountId }) => resolveTlonAccount(cfg, accountId).code ?? undefined, - validate: ({ value }) => (String(value ?? "").trim() ? undefined : "Required"), - normalizeValue: ({ value }) => String(value).trim(), + validate: ({ value }) => + normalizeStringifiedOptionalString(value) ? undefined : "Required", + normalizeValue: ({ value }) => normalizeStringifiedOptionalString(value) ?? "", applySet: async ({ cfg, accountId, value }) => applyTlonSetupConfig({ cfg, @@ -206,9 +213,9 @@ export const tlonSetupAdapter: ChannelSetupAdapter = { validateInput: createSetupInputPresenceValidator({ validate: ({ cfg, accountId, input }) => { const resolved = resolveTlonAccount(cfg, accountId ?? undefined); - const ship = input.ship?.trim() || resolved.ship; - const url = input.url?.trim() || resolved.url; - const code = input.code?.trim() || resolved.code; + const ship = normalizeOptionalString(input.ship) || resolved.ship; + const url = normalizeOptionalString(input.url) || resolved.url; + const code = normalizeOptionalString(input.code) || resolved.code; if (!ship) { return "Tlon requires --ship."; }