refactor: dedupe channel extension readers

This commit is contained in:
Peter Steinberger
2026-04-07 07:45:05 +01:00
parent c19f322ff9
commit ce19b6bf6a
18 changed files with 75 additions and 49 deletions

View File

@@ -4,6 +4,7 @@ import {
resolveNonEnvSecretRefApiKeyMarker,
} from "openclaw/plugin-sdk/provider-auth";
import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import {
buildCloudflareAiGatewayModelDefinition,
resolveCloudflareAiGatewayBaseUrl,
@@ -21,12 +22,11 @@ export function resolveCloudflareAiGatewayApiKey(
}
const keyRef = coerceSecretRef(cred.keyRef);
if (keyRef && keyRef.id.trim()) {
return keyRef.source === "env"
? keyRef.id.trim()
: resolveNonEnvSecretRefApiKeyMarker(keyRef.source);
const keyRefId = normalizeOptionalString(keyRef?.id);
if (keyRef && keyRefId) {
return keyRef.source === "env" ? keyRefId : resolveNonEnvSecretRefApiKeyMarker(keyRef.source);
}
return cred.key?.trim() || undefined;
return normalizeOptionalString(cred.key);
}
export function resolveCloudflareAiGatewayMetadata(cred: CloudflareAiGatewayCredential): {
@@ -37,8 +37,8 @@ export function resolveCloudflareAiGatewayMetadata(cred: CloudflareAiGatewayCred
return {};
}
return {
accountId: cred.metadata?.accountId?.trim() || undefined,
gatewayId: cred.metadata?.gatewayId?.trim() || undefined,
accountId: normalizeOptionalString(cred.metadata?.accountId),
gatewayId: normalizeOptionalString(cred.metadata?.gatewayId),
};
}
@@ -46,7 +46,9 @@ export function buildCloudflareAiGatewayCatalogProvider(params: {
credential: CloudflareAiGatewayCredential;
envApiKey?: string;
}): ModelProviderConfig | null {
const apiKey = params.envApiKey?.trim() || resolveCloudflareAiGatewayApiKey(params.credential);
const apiKey =
normalizeOptionalString(params.envApiKey) ??
resolveCloudflareAiGatewayApiKey(params.credential);
if (!apiKey) {
return null;
}

View File

@@ -11,6 +11,7 @@ import {
upsertAuthProfile,
validateApiKeyInput,
} from "openclaw/plugin-sdk/provider-auth";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { buildCloudflareAiGatewayCatalogProvider } from "./catalog-provider.js";
import { CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF } from "./models.js";
import { applyCloudflareAiGatewayConfig, buildCloudflareAiGatewayConfigPatch } from "./onboard.js";
@@ -33,8 +34,8 @@ async function resolveCloudflareGatewayMetadataInteractive(ctx: {
}) => Promise<unknown>;
};
}) {
let accountId = ctx.accountId?.trim() ?? "";
let gatewayId = ctx.gatewayId?.trim() ?? "";
let accountId = normalizeOptionalString(ctx.accountId) ?? "";
let gatewayId = normalizeOptionalString(ctx.gatewayId) ?? "";
if (!accountId) {
const value = await ctx.prompter.text({
message: "Enter Cloudflare Account ID",
@@ -136,10 +137,12 @@ export default definePluginEntry({
const storedMetadata =
authStore.profiles[PROFILE_ID]?.type === "api_key"
? {
accountId:
authStore.profiles[PROFILE_ID]?.metadata?.accountId?.trim() || undefined,
gatewayId:
authStore.profiles[PROFILE_ID]?.metadata?.gatewayId?.trim() || undefined,
accountId: normalizeOptionalString(
authStore.profiles[PROFILE_ID]?.metadata?.accountId,
),
gatewayId: normalizeOptionalString(
authStore.profiles[PROFILE_ID]?.metadata?.gatewayId,
),
}
: {};
const accountId =
@@ -194,7 +197,9 @@ export default definePluginEntry({
const authStore = ensureAuthProfileStore(ctx.agentDir, {
allowKeychainPrompt: false,
});
const envManagedApiKey = ctx.env[PROVIDER_ENV_VAR]?.trim() ? PROVIDER_ENV_VAR : undefined;
const envManagedApiKey = normalizeOptionalString(ctx.env[PROVIDER_ENV_VAR])
? PROVIDER_ENV_VAR
: undefined;
for (const profileId of listProfilesForProvider(authStore, PROVIDER_ID)) {
const provider = buildCloudflareAiGatewayCatalogProvider({
credential: authStore.profiles[profileId],

View File

@@ -8,6 +8,7 @@ import {
} from "openclaw/plugin-sdk/account-resolution";
import { safeParseJsonWithSchema, safeParseWithSchema } from "openclaw/plugin-sdk/extension-shared";
import { isSecretRef } from "openclaw/plugin-sdk/secret-input";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { z } from "zod";
import type { GoogleChatAccountConfig } from "./types.config.js";
@@ -103,7 +104,7 @@ function resolveCredentialsFromConfig(params: {
);
}
const file = account.serviceAccountFile?.trim();
const file = normalizeOptionalString(account.serviceAccountFile);
if (file) {
return { credentialsFile: file, source: "file" };
}
@@ -114,7 +115,7 @@ function resolveCredentialsFromConfig(params: {
if (envInline) {
return { credentials: envInline, source: "env" };
}
const envFile = process.env[ENV_SERVICE_ACCOUNT_FILE]?.trim();
const envFile = normalizeOptionalString(process.env[ENV_SERVICE_ACCOUNT_FILE]);
if (envFile) {
return { credentialsFile: envFile, source: "env" };
}
@@ -138,7 +139,7 @@ export function resolveGoogleChatAccount(params: {
return {
accountId,
name: merged.name?.trim() || undefined,
name: normalizeOptionalString(merged.name),
enabled,
config: merged,
credentialSource: credentials.source,

View File

@@ -1,4 +1,5 @@
import { resolveInboundMentionDecision } from "openclaw/plugin-sdk/channel-inbound";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import {
GROUP_POLICY_BLOCKED_LABEL,
createChannelPairingController,
@@ -397,6 +398,6 @@ export async function applyGoogleChatInboundAccessPolicy(params: {
ok: true,
commandAuthorized,
effectiveWasMentioned,
groupSystemPrompt: groupEntry?.systemPrompt?.trim() || undefined,
groupSystemPrompt: normalizeOptionalString(groupEntry?.systemPrompt),
};
}

View File

@@ -7,6 +7,7 @@ import {
createChannelNativeOriginTargetResolver,
} from "openclaw/plugin-sdk/approval-native-runtime";
import type { ExecApprovalRequest, PluginApprovalRequest } from "openclaw/plugin-sdk/infra-runtime";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { listSlackAccountIds } from "./accounts.js";
import { isSlackApprovalAuthorizedSender } from "./approval-auth.js";
import {
@@ -138,7 +139,8 @@ export const slackApprovalCapability = createApproverRestrictedNativeApprovalCap
resolveSlackExecApprovalTarget({ cfg, accountId }),
requireMatchingTurnSourceChannel: true,
resolveSuppressionAccountId: ({ target, request }) =>
target.accountId?.trim() || request.request.turnSourceAccountId?.trim() || undefined,
normalizeOptionalString(target.accountId) ??
normalizeOptionalString(request.request.turnSourceAccountId),
resolveOriginTarget: resolveSlackOriginTarget,
resolveApproverDmTargets: resolveSlackApproverDmTargets,
notifyOriginWhenDmOnly: true,

View File

@@ -28,6 +28,7 @@ import {
createDefaultChannelRuntimeState,
} from "openclaw/plugin-sdk/status-helpers";
import { resolveTargetsWithOptionalToken } from "openclaw/plugin-sdk/target-resolver-runtime";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import {
resolveDefaultSlackAccountId,
resolveSlackAccount,
@@ -97,8 +98,8 @@ function getTokenForOperation(
account: ResolvedSlackAccount,
operation: "read" | "write",
): string | undefined {
const userToken = account.config.userToken?.trim() || undefined;
const botToken = account.botToken?.trim();
const userToken = normalizeOptionalString(account.config.userToken);
const botToken = normalizeOptionalString(account.botToken);
const allowUserWrites = account.config.userTokenReadOnly === false;
if (operation === "read") {
return userToken ?? botToken;

View File

@@ -25,6 +25,7 @@ import { resolveAgentRoute } from "openclaw/plugin-sdk/routing";
import { resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
import { logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
import { resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { resolveSlackReplyToMode, type ResolvedSlackAccount } from "../../accounts.js";
import { reactSlackMessage } from "../../actions.js";
import { hasSlackThreadParticipation } from "../../sent-thread-cache.js";
@@ -395,14 +396,14 @@ export async function prepareSlackMessage(params: {
),
];
let resolvedSenderName = message.username?.trim() || undefined;
let resolvedSenderName = normalizeOptionalString(message.username);
const resolveSenderName = async (): Promise<string> => {
if (resolvedSenderName) {
return resolvedSenderName;
}
if (message.user) {
const sender = await ctx.resolveUserName(message.user);
const normalized = sender?.name?.trim();
const normalized = normalizeOptionalString(sender?.name);
if (normalized) {
resolvedSenderName = normalized;
return resolvedSenderName;

View File

@@ -17,6 +17,7 @@ import {
sendPayloadMediaSequenceAndFinalize,
sendTextMediaPayload,
} from "openclaw/plugin-sdk/reply-payload";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { resolveSlackAccount } from "./accounts.js";
import { parseSlackBlocksInput } from "./blocks-input.js";
import { buildSlackInteractiveBlocks, type SlackBlock } from "./blocks-render.js";
@@ -48,9 +49,9 @@ function resolveSlackSendIdentity(identity?: OutboundIdentity): SlackSendIdentit
if (!identity) {
return undefined;
}
const username = identity.name?.trim() || undefined;
const iconUrl = identity.avatarUrl?.trim() || undefined;
const rawEmoji = identity.emoji?.trim();
const username = normalizeOptionalString(identity.name);
const iconUrl = normalizeOptionalString(identity.avatarUrl);
const rawEmoji = normalizeOptionalString(identity.emoji);
const iconEmoji = !iconUrl && rawEmoji && /^:[^:\s]+:$/.test(rawEmoji) ? rawEmoji : undefined;
if (!username && !iconUrl && !iconEmoji) {
return undefined;

View File

@@ -1,4 +1,5 @@
import type { WebClient } from "@slack/web-api";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { createSlackWebClient } from "./client.js";
import {
collectSlackCursorItems,
@@ -74,8 +75,8 @@ async function listSlackUsers(client: WebClient): Promise<SlackUserLookup[]> {
collectPageItems: (res) =>
(res.members ?? [])
.map((member) => {
const id = member.id?.trim();
const name = member.name?.trim();
const id = normalizeOptionalString(member.id);
const name = normalizeOptionalString(member.name);
if (!id || !name) {
return null;
}
@@ -83,9 +84,11 @@ async function listSlackUsers(client: WebClient): Promise<SlackUserLookup[]> {
return {
id,
name,
displayName: profile.display_name?.trim() || undefined,
realName: profile.real_name?.trim() || member.real_name?.trim() || undefined,
email: profile.email?.trim()?.toLowerCase() || undefined,
displayName: normalizeOptionalString(profile.display_name),
realName:
normalizeOptionalString(profile.real_name) ??
normalizeOptionalString(member.real_name),
email: normalizeOptionalString(profile.email)?.toLowerCase(),
deleted: Boolean(member.deleted),
isBot: Boolean(member.is_bot),
isAppUser: Boolean(member.is_app_user),

View File

@@ -16,6 +16,7 @@ import {
type OpenClawConfig,
} from "openclaw/plugin-sdk/setup-runtime";
import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { inspectSlackAccount } from "./account-inspect.js";
import { resolveSlackAccount } from "./accounts.js";
import {
@@ -97,10 +98,10 @@ function createSlackTokenCredential(params: {
return {
accountConfigured: Boolean(resolvedValue) || hasConfiguredSecretInput(configuredValue),
hasConfiguredValue: hasConfiguredSecretInput(configuredValue),
resolvedValue: resolvedValue?.trim() || undefined,
resolvedValue: normalizeOptionalString(resolvedValue),
envValue:
accountId === DEFAULT_ACCOUNT_ID
? process.env[params.preferredEnvVar]?.trim()
? normalizeOptionalString(process.env[params.preferredEnvVar])
: undefined,
};
},

View File

@@ -3,6 +3,7 @@ import type {
ChannelThreadingToolContext,
} from "openclaw/plugin-sdk/channel-contract";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { resolveSlackAccount, resolveSlackReplyToMode } from "./accounts.js";
export function buildSlackThreadingToolContext(params: {
@@ -24,7 +25,7 @@ export function buildSlackThreadingToolContext(params: {
// to NativeChannelId (the raw Slack channel id, e.g. "D…").
const currentChannelId = params.context.To?.startsWith("channel:")
? params.context.To.slice("channel:".length)
: params.context.NativeChannelId?.trim() || undefined;
: normalizeOptionalString(params.context.NativeChannelId);
return {
currentChannelId,
currentThreadTs: threadId != null ? String(threadId) : undefined,

View File

@@ -8,6 +8,7 @@ import {
} from "openclaw/plugin-sdk/approval-native-runtime";
import type { ChannelApprovalCapability } from "openclaw/plugin-sdk/channel-contract";
import type { ExecApprovalRequest, PluginApprovalRequest } from "openclaw/plugin-sdk/infra-runtime";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { listTelegramAccountIds } from "./accounts.js";
import {
getTelegramExecApprovalApprovers,
@@ -110,7 +111,8 @@ const telegramNativeApprovalCapability = createApproverRestrictedNativeApprovalC
resolveTelegramExecApprovalTarget({ cfg, accountId }),
requireMatchingTurnSourceChannel: true,
resolveSuppressionAccountId: ({ target, request }) =>
target.accountId?.trim() || request.request.turnSourceAccountId?.trim() || undefined,
normalizeOptionalString(target.accountId) ??
normalizeOptionalString(request.request.turnSourceAccountId),
resolveOriginTarget: resolveTelegramOriginTarget,
resolveApproverDmTargets: resolveTelegramApproverDmTargets,
});

View File

@@ -20,6 +20,7 @@ import { danger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtim
import { getChildLogger } from "openclaw/plugin-sdk/runtime-env";
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
import { createNonExitingRuntime, type RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { resolveTelegramAccount } from "./accounts.js";
import { defaultTelegramBotDeps, type TelegramBotDeps } from "./bot-deps.js";
import { registerTelegramHandlers } from "./bot-handlers.js";
@@ -258,7 +259,7 @@ export function createTelegramBot(opts: TelegramBotOptions): TelegramBotInstance
typeof telegramCfg?.timeoutSeconds === "number" && Number.isFinite(telegramCfg.timeoutSeconds)
? Math.max(1, Math.floor(telegramCfg.timeoutSeconds))
: undefined;
const apiRoot = telegramCfg.apiRoot?.trim() || undefined;
const apiRoot = normalizeOptionalString(telegramCfg.apiRoot);
const client: ApiClientOptions | undefined =
finalFetch || timeoutSeconds || apiRoot
? {

View File

@@ -186,7 +186,7 @@ export type TelegramForwardedContext = {
function normalizeForwardedUserLabel(user: User) {
const name = [user.first_name, user.last_name].filter(Boolean).join(" ").trim();
const username = user.username?.trim() || undefined;
const username = normalizeOptionalString(user.username);
const id = String(user.id);
const display =
(name && username
@@ -196,8 +196,8 @@ function normalizeForwardedUserLabel(user: User) {
}
function normalizeForwardedChatLabel(chat: Chat, fallbackKind: "chat" | "channel") {
const title = chat.title?.trim() || undefined;
const username = chat.username?.trim() || undefined;
const title = normalizeOptionalString(chat.title);
const username = normalizeOptionalString(chat.username);
const id = String(chat.id);
const display = title || (username ? `@${username}` : undefined) || `${fallbackKind}:${id}`;
return { display, title, username, id };
@@ -251,9 +251,9 @@ function buildForwardedContextFromChat(params: {
if (!display) {
return null;
}
const signature = params.signature?.trim() || undefined;
const signature = normalizeOptionalString(params.signature);
const from = signature ? `${display} (${signature})` : display;
const chatType = (params.chat.type?.trim() || undefined) as Chat["type"] | undefined;
const chatType = normalizeOptionalString(params.chat.type) as Chat["type"] | undefined;
return {
from,
date: params.date,

View File

@@ -32,6 +32,7 @@ import {
createComputedAccountStatusAdapter,
createDefaultChannelRuntimeState,
} from "openclaw/plugin-sdk/status-helpers";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { resolveTelegramAccount, type ResolvedTelegramAccount } from "./accounts.js";
import { resolveTelegramAutoThreadId } from "./action-threading.js";
import { lookupTelegramChatId } from "./api-fetch.js";
@@ -331,7 +332,7 @@ function resolveTelegramInboundConversation(params: {
parsedTarget.messageThreadId != null
? String(parsedTarget.messageThreadId)
: params.threadId != null
? String(params.threadId).trim() || undefined
? normalizeOptionalString(String(params.threadId))
: undefined;
if (threadId) {
const parsedTopic = parseTelegramTopicConversation({

View File

@@ -7,7 +7,7 @@ import { recordChannelActivity } from "openclaw/plugin-sdk/infra-runtime";
import { createTelegramRetryRunner, type RetryConfig } from "openclaw/plugin-sdk/retry-runtime";
import { createSubsystemLogger, logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { formatErrorMessage } from "openclaw/plugin-sdk/ssrf-runtime";
import { redactSensitiveText } from "openclaw/plugin-sdk/text-runtime";
import { normalizeOptionalString, redactSensitiveText } from "openclaw/plugin-sdk/text-runtime";
import { type ResolvedTelegramAccount, resolveTelegramAccount } from "./accounts.js";
import { withTelegramApiErrorLogging } from "./api-logging.js";
import { buildTelegramThreadParams, buildTypingThreadParams } from "./bot/helpers.js";
@@ -265,9 +265,9 @@ function resolveTelegramClientOptions(
return telegramClientOptionsCache.get(cacheKey);
}
const proxyUrl = account.config.proxy?.trim();
const proxyUrl = normalizeOptionalString(account.config.proxy);
const proxyFetch = proxyUrl ? makeProxyFetch(proxyUrl) : undefined;
const apiRoot = account.config.apiRoot?.trim() || undefined;
const apiRoot = normalizeOptionalString(account.config.apiRoot);
const fetchImpl = resolveTelegramFetch(proxyFetch, {
network: account.config.network,
});

View File

@@ -3,13 +3,14 @@ import type {
ChannelThreadingToolContext,
} from "openclaw/plugin-sdk/channel-contract";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { parseTelegramTarget } from "./targets.js";
function resolveTelegramToolContextThreadId(context: ChannelThreadingContext): string | undefined {
if (context.MessageThreadId != null) {
return String(context.MessageThreadId);
}
const currentChannelId = context.To?.trim();
const currentChannelId = normalizeOptionalString(context.To);
if (!currentChannelId) {
return undefined;
}
@@ -27,7 +28,7 @@ export function buildTelegramThreadingToolContext(params: {
void params.accountId;
return {
currentChannelId: params.context.To?.trim() || undefined,
currentChannelId: normalizeOptionalString(params.context.To),
currentThreadTs: resolveTelegramToolContextThreadId(params.context),
hasRepliedRef: params.hasRepliedRef,
};

View File

@@ -1,3 +1,5 @@
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
type AutoSelectableProvider = {
autoSelectOrder?: number;
};
@@ -11,7 +13,7 @@ export function selectConfiguredOrAutoProvider<TProvider extends AutoSelectableP
missingConfiguredProvider: boolean;
provider: TProvider | undefined;
} {
const configuredProviderId = params.configuredProviderId?.trim() || undefined;
const configuredProviderId = normalizeOptionalString(params.configuredProviderId);
const configuredProvider = params.getConfiguredProvider(configuredProviderId);
if (configuredProviderId && !configuredProvider) {