refactor: dedupe provider channel readers

This commit is contained in:
Peter Steinberger
2026-04-07 07:59:44 +01:00
parent fca8ff5748
commit 90a45a4907
25 changed files with 99 additions and 59 deletions

View File

@@ -1,4 +1,5 @@
import { Type } from "@sinclair/typebox";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
export type BraveConfig = {
mode?: string;
@@ -174,8 +175,8 @@ export function normalizeBraveLanguageParams(params: { search_lang?: string; ui_
ui_lang?: string;
invalidField?: "search_lang" | "ui_lang";
} {
const rawSearchLang = params.search_lang?.trim() || undefined;
const rawUiLang = params.ui_lang?.trim() || undefined;
const rawSearchLang = normalizeOptionalString(params.search_lang);
const rawUiLang = normalizeOptionalString(params.ui_lang);
let searchLangCandidate = rawSearchLang;
let uiLangCandidate = rawUiLang;

View File

@@ -7,7 +7,7 @@ import {
import { buildOauthProviderAuthResult } from "openclaw/plugin-sdk/provider-auth";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
import { loginChutes } from "openclaw/plugin-sdk/provider-auth-login";
import { readStringValue } from "openclaw/plugin-sdk/text-runtime";
import { normalizeOptionalString, readStringValue } from "openclaw/plugin-sdk/text-runtime";
import {
CHUTES_DEFAULT_MODEL_REF,
applyChutesApiKeyConfig,
@@ -31,7 +31,7 @@ async function runChutesOAuth(ctx: ProviderAuthContext): Promise<ProviderAuthRes
validate: (value: string) => (value?.trim() ? undefined : "Required"),
}),
).trim();
const clientSecret = process.env.CHUTES_CLIENT_SECRET?.trim() || undefined;
const clientSecret = normalizeOptionalString(process.env.CHUTES_CLIENT_SECRET);
await ctx.prompter.note(
isRemote

View File

@@ -1,6 +1,7 @@
import fs from "node:fs/promises";
import { Static, Type } from "@sinclair/typebox";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import type { AnyAgentTool, OpenClawPluginApi, OpenClawPluginToolContext } from "../api.js";
import { PlaywrightDiffScreenshotter, type DiffScreenshotter } from "./browser.js";
import { resolveDiffImageRenderOptions } from "./config.js";
@@ -469,9 +470,9 @@ function normalizeDiffInput(params: DiffsToolParams): DiffInput {
}
assertMaxBytes(before, "before", MAX_BEFORE_AFTER_BYTES);
assertMaxBytes(after, "after", MAX_BEFORE_AFTER_BYTES);
const path = params.path?.trim() || undefined;
const lang = params.lang?.trim() || undefined;
const title = params.title?.trim() || undefined;
const path = normalizeOptionalString(params.path);
const lang = normalizeOptionalString(params.lang);
const title = normalizeOptionalString(params.title);
if (path) {
assertMaxBytes(path, "path", MAX_PATH_BYTES);
}

View File

@@ -6,6 +6,7 @@ import {
readStringParam,
} from "openclaw/plugin-sdk/agent-runtime";
import type { ChannelMessageActionContext } from "openclaw/plugin-sdk/channel-contract";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { handleDiscordAction } from "../../action-runtime-api.js";
import {
isDiscordModerationAction,
@@ -360,7 +361,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
integer: true,
}),
});
const senderUserId = ctx.requesterSenderId?.trim() || undefined;
const senderUserId = normalizeOptionalString(ctx.requesterSenderId);
return await handleDiscordAction(
{
action: moderation.action,

View File

@@ -1,6 +1,7 @@
import { createRunStateMachine } from "openclaw/plugin-sdk/channel-lifecycle";
import { KeyedAsyncQueue } from "openclaw/plugin-sdk/core";
import { danger, formatDurationSeconds } from "openclaw/plugin-sdk/runtime-env";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { materializeDiscordInboundJob, type DiscordInboundJob } from "./inbound-job.js";
import type { RuntimeEnv } from "./message-handler.preflight.types.js";
import { processDiscordMessage } from "./message-handler.process.js";
@@ -63,8 +64,8 @@ async function processDiscordInboundJob(params: {
finalReplyStarted = true;
},
onReplyPlanResolved: (resolved) => {
createdThreadId = resolved.createdThreadId?.trim() || undefined;
sessionKey = resolved.sessionKey?.trim() || undefined;
createdThreadId = normalizeOptionalString(resolved.createdThreadId);
sessionKey = normalizeOptionalString(resolved.sessionKey);
},
});
},

View File

@@ -1,6 +1,7 @@
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import * as conversationRuntime from "openclaw/plugin-sdk/conversation-binding-runtime";
import type { ResolvedAgentRoute } from "openclaw/plugin-sdk/routing";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import {
resolveDiscordBoundConversationRoute,
resolveDiscordEffectiveRoute,
@@ -63,9 +64,9 @@ export async function resolveDiscordNativeInteractionRouteState(params: {
})
: null;
const configuredBinding = configuredRoute?.bindingResolution ?? null;
const configuredBoundSessionKey = configuredRoute?.boundSessionKey?.trim() || undefined;
const configuredBoundSessionKey = normalizeOptionalString(configuredRoute?.boundSessionKey);
const boundSessionKey =
params.threadBinding?.targetSessionKey?.trim() || configuredBoundSessionKey;
normalizeOptionalString(params.threadBinding?.targetSessionKey) ?? configuredBoundSessionKey;
const effectiveRoute = resolveDiscordEffectiveRoute({
route,
boundSessionKey,

View File

@@ -12,6 +12,7 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/config-runtime";
import { danger } from "openclaw/plugin-sdk/runtime-env";
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { createDiscordRequestClient } from "../proxy-request-client.js";
import type { DiscordGuildEntryResolved } from "./allow-list.js";
import { createDiscordAutoPresenceController } from "./auto-presence.js";
@@ -166,7 +167,8 @@ export async function fetchDiscordBotIdentity(params: {
try {
const botUser = await params.client.fetchUser("@me");
const botUserId = botUser?.id;
const botUserName = botUser?.username?.trim() || botUser?.globalName?.trim() || undefined;
const botUserName =
normalizeOptionalString(botUser?.username) ?? normalizeOptionalString(botUser?.globalName);
params.logStartupPhase(
"fetch-bot-identity:done",
`botUserId=${botUserId ?? "<missing>"} botUserName=${botUserName ?? "<missing>"}`,

View File

@@ -17,7 +17,7 @@ import {
type RetryRunner,
} from "openclaw/plugin-sdk/retry-runtime";
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
import { convertMarkdownTables } from "openclaw/plugin-sdk/text-runtime";
import { convertMarkdownTables, normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { resolveDiscordAccount } from "../accounts.js";
import { chunkDiscordTextWithMode } from "../chunk.js";
import { isLikelyDiscordVideoMedia } from "../media-detection.js";
@@ -228,7 +228,7 @@ function createPayloadReplyToResolver(params: {
replyToMode: ReplyToMode;
resolveFallbackReplyTo: () => string | undefined;
}): () => string | undefined {
const payloadReplyTo = params.payload.replyToId?.trim() || undefined;
const payloadReplyTo = normalizeOptionalString(params.payload.replyToId);
const allowExplicitReplyWhenOff = Boolean(
payloadReplyTo && (params.payload.replyToTag || params.payload.replyToCurrent),
);
@@ -370,7 +370,7 @@ export async function deliverDiscordReply(params: {
threadBindings?: DiscordThreadBindingLookup;
mediaLocalRoots?: readonly string[];
}) {
const replyTo = params.replyToId?.trim() || undefined;
const replyTo = normalizeOptionalString(params.replyToId);
const replyToMode = params.replyToMode ?? "all";
const replyOnce = isSingleUseReplyToMode(replyToMode);
let replyUsed = false;

View File

@@ -1,4 +1,5 @@
import type { User } from "@buape/carbon";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import type { PluralKitMessageInfo } from "../pluralkit.js";
import { formatDiscordUserTag } from "./format.js";
@@ -48,13 +49,13 @@ export function resolveDiscordSenderIdentity(params: {
return {
id: memberId,
name: memberName,
tag: pkMember?.name?.trim() || undefined,
tag: normalizeOptionalString(pkMember?.name),
label,
isPluralKit: true,
pluralkit: {
memberId,
memberName,
systemId: pkSystem?.id?.trim() || undefined,
systemId: normalizeOptionalString(pkSystem?.id),
systemName,
},
};

View File

@@ -13,6 +13,7 @@ import {
type OpenClawConfig,
} from "openclaw/plugin-sdk/runtime-config-snapshot";
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { createDiscordRestClient } from "../client.js";
import {
createThreadForBinding,
@@ -452,11 +453,13 @@ export function createThreadBindingManager(
threadId,
targetKind,
targetSessionKey,
agentId: bindParams.agentId?.trim() || resolveAgentIdFromSessionKey(targetSessionKey),
label: bindParams.label?.trim() || undefined,
agentId:
normalizeOptionalString(bindParams.agentId) ??
resolveAgentIdFromSessionKey(targetSessionKey),
label: normalizeOptionalString(bindParams.label),
webhookId: webhookId || undefined,
webhookToken: webhookToken || undefined,
boundBy: bindParams.boundBy?.trim() || "system",
boundBy: normalizeOptionalString(bindParams.boundBy) || "system",
boundAt: now,
lastActivityAt: now,
idleTimeoutMs,
@@ -598,16 +601,22 @@ export function createThreadBindingManager(
typeof metadata.label === "string" ? metadata.label.trim() || undefined : undefined;
const threadName =
typeof metadata.threadName === "string"
? metadata.threadName.trim() || undefined
? normalizeOptionalString(metadata.threadName)
: undefined;
const introText =
typeof metadata.introText === "string" ? metadata.introText.trim() || undefined : undefined;
typeof metadata.introText === "string"
? normalizeOptionalString(metadata.introText)
: undefined;
const boundBy =
typeof metadata.boundBy === "string" ? metadata.boundBy.trim() || undefined : undefined;
typeof metadata.boundBy === "string"
? normalizeOptionalString(metadata.boundBy)
: undefined;
const agentId =
typeof metadata.agentId === "string" ? metadata.agentId.trim() || undefined : undefined;
typeof metadata.agentId === "string"
? normalizeOptionalString(metadata.agentId)
: undefined;
let threadId: string | undefined;
let channelId = input.conversation.parentConversationId?.trim() || undefined;
let channelId = normalizeOptionalString(input.conversation.parentConversationId);
let createThread = false;
if (placement === "child") {

View File

@@ -13,6 +13,7 @@ import {
sendPayloadMediaSequenceOrFallback,
sendTextMediaPayload,
} from "openclaw/plugin-sdk/reply-payload";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import type { DiscordComponentMessageSpec } from "./components.js";
import { getThreadBindingManager, type ThreadBindingRecord } from "./monitor/thread-bindings.js";
import { normalizeDiscordOutboundTarget } from "./normalize.js";
@@ -67,10 +68,10 @@ function resolveDiscordWebhookIdentity(params: {
identity?: OutboundIdentity;
binding: ThreadBindingRecord;
}): { username?: string; avatarUrl?: string } {
const usernameRaw = params.identity?.name?.trim();
const fallbackUsername = params.binding.label?.trim() || params.binding.agentId;
const usernameRaw = normalizeOptionalString(params.identity?.name);
const fallbackUsername = normalizeOptionalString(params.binding.label) ?? params.binding.agentId;
const username = (usernameRaw || fallbackUsername || "").slice(0, 80) || undefined;
const avatarUrl = params.identity?.avatarUrl?.trim() || undefined;
const avatarUrl = normalizeOptionalString(params.identity?.avatarUrl);
return { username, avatarUrl };
}

View File

@@ -1,3 +1,4 @@
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { fetchDiscord } from "./api.js";
import { listGuilds, type DiscordGuildSummary } from "./guilds.js";
import {
@@ -156,7 +157,9 @@ export async function resolveDiscordUserAllowlist(params: {
if (best) {
const user = best.member.user;
const name =
best.member.nick?.trim() || user.global_name?.trim() || user.username?.trim() || undefined;
normalizeOptionalString(best.member.nick) ??
normalizeOptionalString(user.global_name) ??
normalizeOptionalString(user.username);
results.push({
input,
resolved: true,

View File

@@ -13,7 +13,7 @@ import type { PollInput } from "openclaw/plugin-sdk/media-runtime";
import { resolveChunkMode } from "openclaw/plugin-sdk/reply-chunking";
import type { RetryConfig } from "openclaw/plugin-sdk/retry-runtime";
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
import { convertMarkdownTables } from "openclaw/plugin-sdk/text-runtime";
import { convertMarkdownTables, normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { loadWebMediaRaw } from "openclaw/plugin-sdk/web-media";
import { resolveDiscordAccount } from "./accounts.js";
import { resolveDiscordClientAccountContext } from "./client.js";
@@ -391,8 +391,8 @@ export async function sendWebhookMessageDiscord(
},
body: JSON.stringify({
content: rewrittenText,
username: opts.username?.trim() || undefined,
avatar_url: opts.avatarUrl?.trim() || undefined,
username: normalizeOptionalString(opts.username),
avatar_url: normalizeOptionalString(opts.avatarUrl),
...(messageReference ? { message_reference: messageReference } : {}),
}),
},

View File

@@ -17,6 +17,7 @@ import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
import { parseTtsDirectives } from "openclaw/plugin-sdk/speech";
import { formatErrorMessage } from "openclaw/plugin-sdk/ssrf-runtime";
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { formatMention } from "../mentions.js";
import { normalizeDiscordSlug, resolveDiscordOwnerAccess } from "../monitor/allow-list.js";
import { formatDiscordUserTag } from "../monitor/format.js";
@@ -300,7 +301,7 @@ async function transcribeAudio(params: {
agentDir: resolveAgentDir(params.cfg, params.agentId),
mime: "audio/wav",
});
return result.text?.trim() || undefined;
return normalizeOptionalString(result.text);
}
export class DiscordVoiceManager {

View File

@@ -311,9 +311,9 @@ export async function listElevenLabsVoices(params: {
? json.voices
.map((voice) => ({
id: voice.voice_id?.trim() ?? "",
name: voice.name?.trim() || undefined,
category: voice.category?.trim() || undefined,
description: voice.description?.trim() || undefined,
name: trimToUndefined(voice.name),
category: trimToUndefined(voice.category),
description: trimToUndefined(voice.description),
}))
.filter((voice) => voice.id.length > 0)
: [];

View File

@@ -19,6 +19,7 @@ import {
resolveOpenProviderRuntimeGroupPolicy,
warnMissingProviderGroupPolicyFallbackOnce,
} from "openclaw/plugin-sdk/runtime-group-policy";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { resolveFeishuRuntimeAccount } from "./accounts.js";
import {
checkBotMentioned,
@@ -344,7 +345,7 @@ export async function handleFeishuMessage(params: {
let ctx = parseFeishuMessageEvent(event, botOpenId, botName);
const isGroup = ctx.chatType === "group";
const isDirect = !isGroup;
const senderUserId = event.sender.sender_id.user_id?.trim() || undefined;
const senderUserId = normalizeOptionalString(event.sender.sender_id.user_id);
// Handle merge_forward messages: fetch full message via API then expand sub-messages
if (event.message.message_type === "merge_forward") {
@@ -1060,7 +1061,7 @@ export async function handleFeishuMessage(params: {
CommandAuthorized: commandAuthorized,
OriginatingChannel: "feishu" as const,
OriginatingTo: feishuTo,
GroupSystemPrompt: isGroup ? groupConfig?.systemPrompt?.trim() || undefined : undefined,
GroupSystemPrompt: isGroup ? normalizeOptionalString(groupConfig?.systemPrompt) : undefined,
...mediaPayload,
});
};

View File

@@ -5,6 +5,7 @@ import { basename } from "node:path";
import type * as Lark from "@larksuiteoapi/node-sdk";
import { Type } from "@sinclair/typebox";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import type { OpenClawPluginApi } from "../runtime-api.js";
import { listEnabledFeishuAccounts } from "./accounts.js";
import { FeishuDocSchema, type FeishuDocParams } from "./doc-schema.js";
@@ -1393,7 +1394,9 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
(ctx) => {
const defaultAccountId = ctx.agentAccountId;
const trustedRequesterOpenId =
ctx.messageChannel === "feishu" ? ctx.requesterSenderId?.trim() || undefined : undefined;
ctx.messageChannel === "feishu"
? normalizeOptionalString(ctx.requesterSenderId)
: undefined;
return {
name: "feishu_doc",
label: "Feishu Doc",

View File

@@ -1,3 +1,4 @@
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import type { RuntimeEnv } from "../runtime-api.js";
import { waitForAbortableDelay } from "./async.js";
import { fetchBotIdentityForMonitor, type FeishuMonitorBotIdentity } from "./monitor.startup.js";
@@ -12,8 +13,8 @@ export function applyBotIdentityState(
accountId: string,
identity: FeishuMonitorBotIdentity,
): { botOpenId?: string; botName?: string } {
const botOpenId = identity.botOpenId?.trim() || undefined;
const botName = identity.botName?.trim() || undefined;
const botOpenId = normalizeOptionalString(identity.botOpenId);
const botName = normalizeOptionalString(identity.botName);
botOpenIds.set(accountId, botOpenId ?? "");
if (botName) {

View File

@@ -3,7 +3,13 @@ import type { ClawdbotConfig } from "../runtime-api.js";
import { resolveFeishuAccount } from "./accounts.js";
import { raceWithTimeoutAndAbort } from "./async.js";
import { createFeishuClient } from "./client.js";
import { encodeQuery, extractReplyText, isRecord, readString } from "./comment-shared.js";
import {
encodeQuery,
extractReplyText,
isRecord,
normalizeString,
readString,
} from "./comment-shared.js";
import { normalizeCommentFileType, type CommentFileType } from "./comment-target.js";
import type { ResolvedFeishuAccount } from "./types.js";
@@ -415,10 +421,10 @@ async function fetchDriveCommentContext(params: {
const meta = metaResponse?.code === 0 ? metaResponse.data?.metas?.[0] : undefined;
return {
documentTitle: meta?.title?.trim() || undefined,
documentUrl: meta?.url?.trim() || undefined,
documentTitle: normalizeString(meta?.title),
documentUrl: normalizeString(meta?.url),
isWholeComment: commentCard?.is_whole,
quoteText: commentCard?.quote?.trim() || undefined,
quoteText: normalizeString(commentCard?.quote),
rootCommentText: extractReplyText(rootReply),
targetReplyText: extractReplyText(targetReply),
};
@@ -537,7 +543,7 @@ async function resolveDriveCommentEventCore(params: ResolveDriveCommentEventPara
const fileToken = event.notice_meta?.file_token?.trim();
const fileType = normalizeCommentFileType(event.notice_meta?.file_type);
const senderId = event.notice_meta?.from_user_id?.open_id?.trim();
const senderUserId = event.notice_meta?.from_user_id?.user_id?.trim() || undefined;
const senderUserId = normalizeString(event.notice_meta?.from_user_id?.user_id);
if (!eventId || !commentId || !noticeType || !fileToken || !fileType || !senderId) {
logger?.(
`feishu[${accountId}]: drive comment notice missing required fields event=${eventId ?? "unknown"} comment=${commentId ?? "unknown"}`,

View File

@@ -1,3 +1,4 @@
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import type { OpenClawPluginApi } from "../runtime-api.js";
import { buildFeishuConversationId, parseFeishuConversationId } from "./conversation-id.js";
import { normalizeFeishuTarget } from "./targets.js";
@@ -221,8 +222,8 @@ function resolveMatchingChildBinding(params: {
(entry) =>
entry.accountId === requesterConversation.accountId &&
entry.conversationId === requesterConversation.conversationId &&
(entry.parentConversationId?.trim() || undefined) ===
(requesterConversation.parentConversationId?.trim() || undefined),
normalizeOptionalString(entry.parentConversationId) ===
normalizeOptionalString(requesterConversation.parentConversationId),
);
if (matched) {
return matched;

View File

@@ -10,6 +10,7 @@ import {
type SessionBindingRecord,
} from "openclaw/plugin-sdk/conversation-runtime";
import { normalizeAccountId, resolveAgentIdFromSessionKey } from "openclaw/plugin-sdk/routing";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
type FeishuBindingTargetKind = "subagent" | "acp";
@@ -165,7 +166,7 @@ export function createFeishuThreadBindingManager(params: {
const record: FeishuThreadBindingRecord = {
accountId,
conversationId: normalizedConversationId,
parentConversationId: parentConversationId?.trim() || undefined,
parentConversationId: normalizeOptionalString(parentConversationId),
deliveryTo:
typeof metadata?.deliveryTo === "string" && metadata.deliveryTo.trim()
? metadata.deliveryTo.trim()

View File

@@ -144,11 +144,11 @@ export async function listMicrosoftVoices(): Promise<SpeechVoiceOption[]> {
? voices
.map((voice) => ({
id: voice.ShortName?.trim() ?? "",
name: voice.FriendlyName?.trim() || voice.ShortName?.trim() || undefined,
name: trimToUndefined(voice.FriendlyName) ?? trimToUndefined(voice.ShortName),
category: voice.VoiceTag?.ContentCategories?.find((value) => value.trim().length > 0),
description: formatMicrosoftVoiceDescription(voice),
locale: voice.Locale?.trim() || undefined,
gender: voice.Gender?.trim() || undefined,
locale: trimToUndefined(voice.Locale),
gender: trimToUndefined(voice.Gender),
personalities: voice.VoiceTag?.VoicePersonalities?.filter(
(value): value is string => value.trim().length > 0,
),

View File

@@ -991,7 +991,7 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount, ProbeMSTeamsRe
},
threading: {
buildToolContext: ({ context, hasRepliedRef }) => ({
currentChannelId: context.To?.trim() || undefined,
currentChannelId: normalizeOptionalString(context.To),
currentThreadTs: context.ReplyToId,
hasRepliedRef,
}),

View File

@@ -10,7 +10,7 @@ import {
buildProviderReplayFamilyHooks,
type ModelProviderConfig,
} from "openclaw/plugin-sdk/provider-model-shared";
import { readStringValue } from "openclaw/plugin-sdk/text-runtime";
import { normalizeOptionalString, readStringValue } from "openclaw/plugin-sdk/text-runtime";
import {
buildOllamaProvider,
configureOllamaNonInteractive,
@@ -51,8 +51,8 @@ function resolveOllamaDiscoveryApiKey(params: {
resolvedApiKey?: string;
}): string {
const envApiKey = params.env.OLLAMA_API_KEY?.trim() ? "OLLAMA_API_KEY" : undefined;
const explicitApiKey = params.explicitApiKey?.trim() || undefined;
const resolvedApiKey = params.resolvedApiKey?.trim() || undefined;
const explicitApiKey = normalizeOptionalString(params.explicitApiKey);
const resolvedApiKey = normalizeOptionalString(params.resolvedApiKey);
return envApiKey ?? explicitApiKey ?? resolvedApiKey ?? DEFAULT_API_KEY;
}

View File

@@ -24,7 +24,12 @@ import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-pay
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
import { isVerbose, logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/sandbox";
import { CONFIG_DIR, resolveUserPath, stripMarkdown } from "openclaw/plugin-sdk/text-runtime";
import {
CONFIG_DIR,
normalizeOptionalString,
resolveUserPath,
stripMarkdown,
} from "openclaw/plugin-sdk/text-runtime";
import {
canonicalizeSpeechProviderId,
getSpeechProvider,
@@ -324,7 +329,7 @@ export function resolveTtsConfig(cfg: OpenClawConfig): ResolvedTtsConfig {
normalizeConfiguredSpeechProviderId(raw.provider) ??
(providerSource === "config" ? raw.provider?.trim().toLowerCase() || "" : ""),
providerSource,
summaryModel: raw.summaryModel?.trim() || undefined,
summaryModel: normalizeOptionalString(raw.summaryModel),
modelOverrides: resolveModelOverridePolicy(raw.modelOverrides),
providerConfigs: collectDirectProviderConfigEntries(raw),
prefsPath: raw.prefsPath,