refactor: dedupe extension trimmed readers

This commit is contained in:
Peter Steinberger
2026-04-08 01:07:11 +01:00
parent 08cee3316d
commit bf03babd2b
4 changed files with 60 additions and 48 deletions

View File

@@ -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,

View File

@@ -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
);
}

View File

@@ -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,

View File

@@ -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.";
}