refactor: share trimmed string entry normalization

This commit is contained in:
Peter Steinberger
2026-03-07 23:11:04 +00:00
parent 6647d02846
commit d228a62143
12 changed files with 29 additions and 15 deletions

View File

@@ -1,3 +1,4 @@
import { normalizeStringEntries } from "../../shared/string-normalization.js";
import { normalizeSecretInput } from "../../utils/normalize-secret-input.js"; import { normalizeSecretInput } from "../../utils/normalize-secret-input.js";
import { normalizeProviderId, normalizeProviderIdForAuth } from "../model-selection.js"; import { normalizeProviderId, normalizeProviderIdForAuth } from "../model-selection.js";
import { import {
@@ -18,9 +19,7 @@ export async function setAuthProfileOrder(params: {
}): Promise<AuthProfileStore | null> { }): Promise<AuthProfileStore | null> {
const providerKey = normalizeProviderId(params.provider); const providerKey = normalizeProviderId(params.provider);
const sanitized = const sanitized =
params.order && Array.isArray(params.order) params.order && Array.isArray(params.order) ? normalizeStringEntries(params.order) : [];
? params.order.map((entry) => String(entry).trim()).filter(Boolean)
: [];
const deduped = dedupeProfileIds(sanitized); const deduped = dedupeProfileIds(sanitized);
return await updateAuthProfileStoreWithLock({ return await updateAuthProfileStoreWithLock({

View File

@@ -6,6 +6,7 @@ import {
resolveConfigPath, resolveConfigPath,
resolveRuntimePlatform, resolveRuntimePlatform,
} from "../../shared/config-eval.js"; } from "../../shared/config-eval.js";
import { normalizeStringEntries } from "../../shared/string-normalization.js";
import { resolveSkillKey } from "./frontmatter.js"; import { resolveSkillKey } from "./frontmatter.js";
import type { SkillEligibilityContext, SkillEntry } from "./types.js"; import type { SkillEligibilityContext, SkillEntry } from "./types.js";
@@ -42,7 +43,7 @@ function normalizeAllowlist(input: unknown): string[] | undefined {
if (!Array.isArray(input)) { if (!Array.isArray(input)) {
return undefined; return undefined;
} }
const normalized = input.map((entry) => String(entry).trim()).filter(Boolean); const normalized = normalizeStringEntries(input);
return normalized.length > 0 ? normalized : undefined; return normalized.length > 0 ? normalized : undefined;
} }

View File

@@ -1,8 +1,10 @@
import { normalizeStringEntries } from "../../shared/string-normalization.js";
export function normalizeSkillFilter(skillFilter?: ReadonlyArray<unknown>): string[] | undefined { export function normalizeSkillFilter(skillFilter?: ReadonlyArray<unknown>): string[] | undefined {
if (skillFilter === undefined) { if (skillFilter === undefined) {
return undefined; return undefined;
} }
return skillFilter.map((entry) => String(entry).trim()).filter(Boolean); return normalizeStringEntries(skillFilter);
} }
export function normalizeSkillFilterForComparison( export function normalizeSkillFilterForComparison(

View File

@@ -3,6 +3,7 @@ import { getChannelDock, listChannelDocks } from "../channels/dock.js";
import type { ChannelId } from "../channels/plugins/types.js"; import type { ChannelId } from "../channels/plugins/types.js";
import { normalizeAnyChannelId } from "../channels/registry.js"; import { normalizeAnyChannelId } from "../channels/registry.js";
import type { OpenClawConfig } from "../config/config.js"; import type { OpenClawConfig } from "../config/config.js";
import { normalizeStringEntries } from "../shared/string-normalization.js";
import { import {
INTERNAL_MESSAGE_CHANNEL, INTERNAL_MESSAGE_CHANNEL,
isInternalMessageChannel, isInternalMessageChannel,
@@ -85,7 +86,7 @@ function formatAllowFromList(params: {
if (dock?.config?.formatAllowFrom) { if (dock?.config?.formatAllowFrom) {
return dock.config.formatAllowFrom({ cfg, accountId, allowFrom }); return dock.config.formatAllowFrom({ cfg, accountId, allowFrom });
} }
return allowFrom.map((entry) => String(entry).trim()).filter(Boolean); return normalizeStringEntries(allowFrom);
} }
function normalizeAllowFromEntry(params: { function normalizeAllowFromEntry(params: {

View File

@@ -23,6 +23,7 @@ import {
normalizeAccountId, normalizeAccountId,
normalizeOptionalAccountId, normalizeOptionalAccountId,
} from "../../routing/session-key.js"; } from "../../routing/session-key.js";
import { normalizeStringEntries } from "../../shared/string-normalization.js";
import { resolveSignalAccount } from "../../signal/accounts.js"; import { resolveSignalAccount } from "../../signal/accounts.js";
import { resolveSlackAccount } from "../../slack/accounts.js"; import { resolveSlackAccount } from "../../slack/accounts.js";
import { resolveSlackUserAllowlist } from "../../slack/resolve-users.js"; import { resolveSlackUserAllowlist } from "../../slack/resolve-users.js";
@@ -165,7 +166,7 @@ function normalizeAllowFrom(params: {
allowFrom: params.values, allowFrom: params.values,
}); });
} }
return params.values.map((entry) => String(entry).trim()).filter(Boolean); return normalizeStringEntries(params.values);
} }
function formatEntryList(entries: string[], resolved?: Map<string, string>): string { function formatEntryList(entries: string[], resolved?: Map<string, string>): string {

View File

@@ -12,6 +12,7 @@ import { type OpenClawConfig, loadConfig } from "../../config/config.js";
import { applyLinkUnderstanding } from "../../link-understanding/apply.js"; import { applyLinkUnderstanding } from "../../link-understanding/apply.js";
import { applyMediaUnderstanding } from "../../media-understanding/apply.js"; import { applyMediaUnderstanding } from "../../media-understanding/apply.js";
import { defaultRuntime } from "../../runtime.js"; import { defaultRuntime } from "../../runtime.js";
import { normalizeStringEntries } from "../../shared/string-normalization.js";
import { resolveCommandAuthorization } from "../command-auth.js"; import { resolveCommandAuthorization } from "../command-auth.js";
import type { MsgContext } from "../templating.js"; import type { MsgContext } from "../templating.js";
import { SILENT_REPLY_TOKEN } from "../tokens.js"; import { SILENT_REPLY_TOKEN } from "../tokens.js";
@@ -33,7 +34,7 @@ function mergeSkillFilters(channelFilter?: string[], agentFilter?: string[]): st
if (!Array.isArray(list)) { if (!Array.isArray(list)) {
return undefined; return undefined;
} }
return list.map((entry) => String(entry).trim()).filter(Boolean); return normalizeStringEntries(list);
}; };
const channel = normalize(channelFilter); const channel = normalize(channelFilter);
const agent = normalize(agentFilter); const agent = normalize(agentFilter);

View File

@@ -2,6 +2,7 @@ import { resolveAgentConfig } from "../../agents/agent-scope.js";
import { getChannelDock } from "../../channels/dock.js"; import { getChannelDock } from "../../channels/dock.js";
import { normalizeChannelId } from "../../channels/plugins/index.js"; import { normalizeChannelId } from "../../channels/plugins/index.js";
import type { AgentElevatedAllowFromConfig, OpenClawConfig } from "../../config/config.js"; import type { AgentElevatedAllowFromConfig, OpenClawConfig } from "../../config/config.js";
import { normalizeStringEntries } from "../../shared/string-normalization.js";
import type { MsgContext } from "../templating.js"; import type { MsgContext } from "../templating.js";
import { import {
type AllowFromFormatter, type AllowFromFormatter,
@@ -36,7 +37,7 @@ function resolveAllowFromFormatter(params: {
const dock = normalizedProvider ? getChannelDock(normalizedProvider) : undefined; const dock = normalizedProvider ? getChannelDock(normalizedProvider) : undefined;
const formatAllowFrom = dock?.config?.formatAllowFrom; const formatAllowFrom = dock?.config?.formatAllowFrom;
if (!formatAllowFrom) { if (!formatAllowFrom) {
return (values) => values.map((entry) => String(entry).trim()).filter(Boolean); return (values) => normalizeStringEntries(values);
} }
return (values) => return (values) =>
formatAllowFrom({ formatAllowFrom({
@@ -64,7 +65,7 @@ function isApprovedElevatedSender(params: {
return false; return false;
} }
const allowTokens = rawAllow.map((entry) => String(entry).trim()).filter(Boolean); const allowTokens = normalizeStringEntries(rawAllow);
if (allowTokens.length === 0) { if (allowTokens.length === 0) {
return false; return false;
} }

View File

@@ -1,4 +1,5 @@
import type { OpenClawConfig } from "../config/config.js"; import type { OpenClawConfig } from "../config/config.js";
import { normalizeStringEntries } from "../shared/string-normalization.js";
import { projectSafeChannelAccountSnapshotFields } from "./account-snapshot-fields.js"; import { projectSafeChannelAccountSnapshotFields } from "./account-snapshot-fields.js";
import type { ChannelAccountSnapshot } from "./plugins/types.core.js"; import type { ChannelAccountSnapshot } from "./plugins/types.core.js";
import type { ChannelPlugin } from "./plugins/types.plugin.js"; import type { ChannelPlugin } from "./plugins/types.plugin.js";
@@ -34,7 +35,7 @@ export function formatChannelAllowFrom(params: {
allowFrom: params.allowFrom, allowFrom: params.allowFrom,
}); });
} }
return params.allowFrom.map((entry) => String(entry).trim()).filter(Boolean); return normalizeStringEntries(params.allowFrom);
} }
function asRecord(value: unknown): Record<string, unknown> | undefined { function asRecord(value: unknown): Record<string, unknown> | undefined {

View File

@@ -6,6 +6,7 @@ import {
} from "../../agents/auth-profiles.js"; } from "../../agents/auth-profiles.js";
import { normalizeProviderId } from "../../agents/model-selection.js"; import { normalizeProviderId } from "../../agents/model-selection.js";
import type { RuntimeEnv } from "../../runtime.js"; import type { RuntimeEnv } from "../../runtime.js";
import { normalizeStringEntries } from "../../shared/string-normalization.js";
import { shortenHomePath } from "../../utils.js"; import { shortenHomePath } from "../../utils.js";
import { loadModelsConfig } from "./load-config.js"; import { loadModelsConfig } from "./load-config.js";
import { resolveKnownAgentId } from "./shared.js"; import { resolveKnownAgentId } from "./shared.js";
@@ -104,7 +105,7 @@ export async function modelsAuthOrderSetCommand(
allowKeychainPrompt: false, allowKeychainPrompt: false,
}); });
const providerKey = provider; const providerKey = provider;
const requested = (opts.order ?? []).map((entry) => String(entry).trim()).filter(Boolean); const requested = normalizeStringEntries(opts.order ?? []);
if (requested.length === 0) { if (requested.length === 0) {
throw new Error("Missing profile ids. Provide one or more profile ids."); throw new Error("Missing profile ids. Provide one or more profile ids.");
} }

View File

@@ -2,6 +2,7 @@ import { normalizeWhatsAppAllowFromEntries } from "../channels/plugins/normalize
import type { OpenClawConfig } from "../config/config.js"; import type { OpenClawConfig } from "../config/config.js";
import { resolveIMessageAccount } from "../imessage/accounts.js"; import { resolveIMessageAccount } from "../imessage/accounts.js";
import { normalizeAccountId } from "../routing/session-key.js"; import { normalizeAccountId } from "../routing/session-key.js";
import { normalizeStringEntries } from "../shared/string-normalization.js";
import { resolveWhatsAppAccount } from "../web/accounts.js"; import { resolveWhatsAppAccount } from "../web/accounts.js";
export function mapAllowFromEntries( export function mapAllowFromEntries(
@@ -11,7 +12,7 @@ export function mapAllowFromEntries(
} }
export function formatTrimmedAllowFromEntries(allowFrom: Array<string | number>): string[] { export function formatTrimmedAllowFromEntries(allowFrom: Array<string | number>): string[] {
return allowFrom.map((entry) => String(entry).trim()).filter(Boolean); return normalizeStringEntries(allowFrom);
} }
export function resolveOptionalConfigString( export function resolveOptionalConfigString(

View File

@@ -9,6 +9,11 @@ import {
describe("shared/string-normalization", () => { describe("shared/string-normalization", () => {
it("normalizes mixed allow-list entries", () => { it("normalizes mixed allow-list entries", () => {
expect(normalizeStringEntries([" a ", 42, "", " ", "z"])).toEqual(["a", "42", "z"]); expect(normalizeStringEntries([" a ", 42, "", " ", "z"])).toEqual(["a", "42", "z"]);
expect(normalizeStringEntries([" ok ", null, { toString: () => " obj " }])).toEqual([
"ok",
"null",
"obj",
]);
expect(normalizeStringEntries(undefined)).toEqual([]); expect(normalizeStringEntries(undefined)).toEqual([]);
}); });

View File

@@ -1,8 +1,8 @@
export function normalizeStringEntries(list?: Array<string | number>) { export function normalizeStringEntries(list?: ReadonlyArray<unknown>) {
return (list ?? []).map((entry) => String(entry).trim()).filter(Boolean); return (list ?? []).map((entry) => String(entry).trim()).filter(Boolean);
} }
export function normalizeStringEntriesLower(list?: Array<string | number>) { export function normalizeStringEntriesLower(list?: ReadonlyArray<unknown>) {
return normalizeStringEntries(list).map((entry) => entry.toLowerCase()); return normalizeStringEntries(list).map((entry) => entry.toLowerCase());
} }