mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-03 21:40:20 +00:00
refactor: collapse zod setup validators
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
createZodSetupInputValidator,
|
||||
createSetupInputPresenceValidator,
|
||||
createTopLevelChannelDmPolicySetter,
|
||||
normalizeAccountId,
|
||||
patchScopedAccountConfig,
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
type DmPolicy,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import { z } from "zod";
|
||||
import { applyBlueBubblesConnectionConfig } from "./config-apply.js";
|
||||
|
||||
const channel = "bluebubbles" as const;
|
||||
@@ -16,13 +15,6 @@ const setBlueBubblesTopLevelDmPolicy = createTopLevelChannelDmPolicySetter({
|
||||
channel,
|
||||
});
|
||||
|
||||
const BlueBubblesSetupInputSchema = z
|
||||
.object({
|
||||
httpUrl: z.string().optional(),
|
||||
password: z.string().optional(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
export function setBlueBubblesDmPolicy(cfg: OpenClawConfig, dmPolicy: DmPolicy): OpenClawConfig {
|
||||
return setBlueBubblesTopLevelDmPolicy(cfg, dmPolicy);
|
||||
}
|
||||
@@ -51,8 +43,7 @@ export const blueBubblesSetupAdapter: ChannelSetupAdapter = {
|
||||
accountId,
|
||||
name,
|
||||
}),
|
||||
validateInput: createZodSetupInputValidator({
|
||||
schema: BlueBubblesSetupInputSchema,
|
||||
validateInput: createSetupInputPresenceValidator({
|
||||
validate: ({ input }) => {
|
||||
if (!input.httpUrl && !input.password) {
|
||||
return "BlueBubbles requires --http-url and --password.";
|
||||
|
||||
@@ -1,33 +1,22 @@
|
||||
import {
|
||||
createPatchedAccountSetupAdapter,
|
||||
createZodSetupInputValidator,
|
||||
createSetupInputPresenceValidator,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import { z } from "zod";
|
||||
|
||||
const channel = "googlechat" as const;
|
||||
|
||||
const GoogleChatSetupInputSchema = z
|
||||
.object({
|
||||
useEnv: z.boolean().optional(),
|
||||
token: z.string().optional(),
|
||||
tokenFile: z.string().optional(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
export const googlechatSetupAdapter = createPatchedAccountSetupAdapter({
|
||||
channelKey: channel,
|
||||
validateInput: createZodSetupInputValidator({
|
||||
schema: GoogleChatSetupInputSchema,
|
||||
validate: ({ accountId, input }) => {
|
||||
if (input.useEnv && accountId !== DEFAULT_ACCOUNT_ID) {
|
||||
return "GOOGLE_CHAT_SERVICE_ACCOUNT env vars can only be used for the default account.";
|
||||
}
|
||||
if (!input.useEnv && !input.token && !input.tokenFile) {
|
||||
return "Google Chat requires --token (service account JSON) or --token-file.";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
validateInput: createSetupInputPresenceValidator({
|
||||
defaultAccountOnlyEnvError:
|
||||
"GOOGLE_CHAT_SERVICE_ACCOUNT env vars can only be used for the default account.",
|
||||
whenNotUseEnv: [
|
||||
{
|
||||
someOf: ["token", "tokenFile"],
|
||||
message: "Google Chat requires --token (service account JSON) or --token-file.",
|
||||
},
|
||||
],
|
||||
}),
|
||||
buildPatch: (input) => {
|
||||
const patch = input.useEnv
|
||||
|
||||
@@ -3,12 +3,11 @@ import type { DmPolicy } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { normalizeAccountId } from "openclaw/plugin-sdk/routing";
|
||||
import {
|
||||
applyAccountNameToChannelSection,
|
||||
createZodSetupInputValidator,
|
||||
createSetupInputPresenceValidator,
|
||||
createTopLevelChannelAllowFromSetter,
|
||||
createTopLevelChannelDmPolicySetter,
|
||||
patchScopedAccountConfig,
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import { z } from "zod";
|
||||
import type { CoreConfig, IrcAccountConfig, IrcNickServConfig } from "./types.js";
|
||||
|
||||
const channel = "irc" as const;
|
||||
@@ -30,13 +29,6 @@ type IrcSetupInput = ChannelSetupInput & {
|
||||
password?: string;
|
||||
};
|
||||
|
||||
const IrcSetupInputSchema = z
|
||||
.object({
|
||||
host: z.string().trim().min(1, "IRC requires host."),
|
||||
nick: z.string().trim().min(1, "IRC requires nick."),
|
||||
})
|
||||
.passthrough() as z.ZodType<IrcSetupInput>;
|
||||
|
||||
export function parsePort(raw: string, fallback: number): number {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) {
|
||||
@@ -110,7 +102,12 @@ export const ircSetupAdapter: ChannelSetupAdapter = {
|
||||
accountId,
|
||||
name,
|
||||
}),
|
||||
validateInput: createZodSetupInputValidator({ schema: IrcSetupInputSchema }),
|
||||
validateInput: createSetupInputPresenceValidator({
|
||||
whenNotUseEnv: [
|
||||
{ someOf: ["host"], message: "IRC requires host." },
|
||||
{ someOf: ["nick"], message: "IRC requires nick." },
|
||||
],
|
||||
}),
|
||||
applyAccountConfig: ({ cfg, accountId, input }) => {
|
||||
const setupInput = input as IrcSetupInput;
|
||||
const namedConfig = applyAccountNameToChannelSection({
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { ChannelSetupAdapter, OpenClawConfig } from "openclaw/plugin-sdk/setup";
|
||||
import { createZodSetupInputValidator } from "openclaw/plugin-sdk/setup";
|
||||
import { z } from "zod";
|
||||
import { createSetupInputPresenceValidator } from "openclaw/plugin-sdk/setup";
|
||||
import { hasLineCredentials, parseLineAllowFromId } from "./account-helpers.js";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
@@ -12,16 +11,6 @@ import {
|
||||
|
||||
const channel = "line" as const;
|
||||
|
||||
const LineSetupInputSchema = z
|
||||
.object({
|
||||
useEnv: z.boolean().optional(),
|
||||
channelAccessToken: z.string().optional(),
|
||||
channelSecret: z.string().optional(),
|
||||
tokenFile: z.string().optional(),
|
||||
secretFile: z.string().optional(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
export function patchLineAccountConfig(params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId: string;
|
||||
@@ -92,20 +81,19 @@ export const lineSetupAdapter: ChannelSetupAdapter = {
|
||||
accountId,
|
||||
patch: name?.trim() ? { name: name.trim() } : {},
|
||||
}),
|
||||
validateInput: createZodSetupInputValidator({
|
||||
schema: LineSetupInputSchema,
|
||||
validate: ({ accountId, input }) => {
|
||||
if (input.useEnv && accountId !== DEFAULT_ACCOUNT_ID) {
|
||||
return "LINE_CHANNEL_ACCESS_TOKEN can only be used for the default account.";
|
||||
}
|
||||
if (!input.useEnv && !input.channelAccessToken && !input.tokenFile) {
|
||||
return "LINE requires channelAccessToken or --token-file (or --use-env).";
|
||||
}
|
||||
if (!input.useEnv && !input.channelSecret && !input.secretFile) {
|
||||
return "LINE requires channelSecret or --secret-file (or --use-env).";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
validateInput: createSetupInputPresenceValidator({
|
||||
defaultAccountOnlyEnvError:
|
||||
"LINE_CHANNEL_ACCESS_TOKEN can only be used for the default account.",
|
||||
whenNotUseEnv: [
|
||||
{
|
||||
someOf: ["channelAccessToken", "tokenFile"],
|
||||
message: "LINE requires channelAccessToken or --token-file (or --use-env).",
|
||||
},
|
||||
{
|
||||
someOf: ["channelSecret", "secretFile"],
|
||||
message: "LINE requires channelSecret or --secret-file (or --use-env).",
|
||||
},
|
||||
],
|
||||
}),
|
||||
applyAccountConfig: ({ cfg, accountId, input }) => {
|
||||
const typedInput = input as {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { ChannelSetupAdapter } from "openclaw/plugin-sdk/channel-setup";
|
||||
import { createZodSetupInputValidator } from "openclaw/plugin-sdk/setup";
|
||||
import { z } from "zod";
|
||||
import { createSetupInputPresenceValidator } from "openclaw/plugin-sdk/setup";
|
||||
import { resolveMattermostAccount, type ResolvedMattermostAccount } from "./mattermost/accounts.js";
|
||||
import { normalizeMattermostBaseUrl } from "./mattermost/client.js";
|
||||
import {
|
||||
@@ -15,15 +14,6 @@ import { hasConfiguredSecretInput } from "./secret-input.js";
|
||||
|
||||
const channel = "mattermost" as const;
|
||||
|
||||
const MattermostSetupInputSchema = z
|
||||
.object({
|
||||
useEnv: z.boolean().optional(),
|
||||
botToken: z.string().optional(),
|
||||
token: z.string().optional(),
|
||||
httpUrl: z.string().optional(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
export function isMattermostConfigured(account: ResolvedMattermostAccount): boolean {
|
||||
const tokenConfigured =
|
||||
Boolean(account.botToken?.trim()) || hasConfiguredSecretInput(account.config.botToken);
|
||||
@@ -47,14 +37,21 @@ export const mattermostSetupAdapter: ChannelSetupAdapter = {
|
||||
accountId,
|
||||
name,
|
||||
}),
|
||||
validateInput: createZodSetupInputValidator({
|
||||
schema: MattermostSetupInputSchema,
|
||||
validateInput: createSetupInputPresenceValidator({
|
||||
defaultAccountOnlyEnvError: "Mattermost env vars can only be used for the default account.",
|
||||
whenNotUseEnv: [
|
||||
{
|
||||
someOf: ["botToken", "token"],
|
||||
message: "Mattermost requires --bot-token and --http-url (or --use-env).",
|
||||
},
|
||||
{
|
||||
someOf: ["httpUrl"],
|
||||
message: "Mattermost requires --bot-token and --http-url (or --use-env).",
|
||||
},
|
||||
],
|
||||
validate: ({ accountId, input }) => {
|
||||
const token = input.botToken ?? input.token;
|
||||
const baseUrl = normalizeMattermostBaseUrl(input.httpUrl);
|
||||
if (input.useEnv && accountId !== DEFAULT_ACCOUNT_ID) {
|
||||
return "Mattermost env vars can only be used for the default account.";
|
||||
}
|
||||
if (!input.useEnv && (!token || !baseUrl)) {
|
||||
return "Mattermost requires --bot-token and --http-url (or --use-env).";
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/routing";
|
||||
import {
|
||||
applyAccountNameToChannelSection,
|
||||
createZodSetupInputValidator,
|
||||
createSetupInputPresenceValidator,
|
||||
patchScopedAccountConfig,
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import {
|
||||
@@ -17,7 +17,6 @@ import type { ChannelSetupDmPolicy } from "openclaw/plugin-sdk/setup";
|
||||
import { type ChannelSetupWizard } from "openclaw/plugin-sdk/setup";
|
||||
import { formatDocsLink } from "openclaw/plugin-sdk/setup";
|
||||
import type { WizardPrompter } from "openclaw/plugin-sdk/setup";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
listNextcloudTalkAccountIds,
|
||||
resolveDefaultNextcloudTalkAccountId,
|
||||
@@ -34,15 +33,6 @@ type NextcloudSetupInput = ChannelSetupInput & {
|
||||
};
|
||||
type NextcloudTalkSection = NonNullable<CoreConfig["channels"]>["nextcloud-talk"];
|
||||
|
||||
const NextcloudSetupInputSchema = z
|
||||
.object({
|
||||
useEnv: z.boolean().optional(),
|
||||
baseUrl: z.string().optional(),
|
||||
secret: z.string().optional(),
|
||||
secretFile: z.string().optional(),
|
||||
})
|
||||
.passthrough() as z.ZodType<NextcloudSetupInput>;
|
||||
|
||||
export function normalizeNextcloudTalkBaseUrl(value: string | undefined): string {
|
||||
return value?.trim().replace(/\/+$/, "") ?? "";
|
||||
}
|
||||
@@ -192,12 +182,10 @@ export const nextcloudTalkSetupAdapter: ChannelSetupAdapter = {
|
||||
accountId,
|
||||
name,
|
||||
}),
|
||||
validateInput: createZodSetupInputValidator({
|
||||
schema: NextcloudSetupInputSchema,
|
||||
validateInput: createSetupInputPresenceValidator({
|
||||
defaultAccountOnlyEnvError:
|
||||
"NEXTCLOUD_TALK_BOT_SECRET can only be used for the default account.",
|
||||
validate: ({ accountId, input }) => {
|
||||
if (input.useEnv && accountId !== DEFAULT_ACCOUNT_ID) {
|
||||
return "NEXTCLOUD_TALK_BOT_SECRET can only be used for the default account.";
|
||||
}
|
||||
if (!input.useEnv && !input.secret && !input.secretFile) {
|
||||
return "Nextcloud Talk requires bot secret or --secret-file (or --use-env).";
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
createDelegatedSetupWizardProxy,
|
||||
createDelegatedTextInputShouldPrompt,
|
||||
createPatchedAccountSetupAdapter,
|
||||
createZodSetupInputValidator,
|
||||
createSetupInputPresenceValidator,
|
||||
createTopLevelChannelDmPolicy,
|
||||
normalizeE164,
|
||||
parseSetupEntriesAllowingWildcard,
|
||||
@@ -19,7 +19,6 @@ import type {
|
||||
ChannelSetupWizardTextInput,
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import { formatCliCommand, formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
listSignalAccountIds,
|
||||
resolveDefaultSignalAccountId,
|
||||
@@ -33,16 +32,6 @@ const DIGITS_ONLY = /^\d+$/;
|
||||
const INVALID_SIGNAL_ACCOUNT_ERROR =
|
||||
"Invalid E.164 phone number (must start with + and country code, e.g. +15555550123)";
|
||||
|
||||
const SignalSetupInputSchema = z
|
||||
.object({
|
||||
signalNumber: z.string().optional(),
|
||||
cliPath: z.string().optional(),
|
||||
httpUrl: z.string().optional(),
|
||||
httpHost: z.string().optional(),
|
||||
httpPort: z.string().optional(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
export function normalizeSignalAccountInput(value: string | null | undefined): string | null {
|
||||
const trimmed = value?.trim();
|
||||
if (!trimmed) {
|
||||
@@ -196,8 +185,7 @@ export const signalCompletionNote = {
|
||||
|
||||
export const signalSetupAdapter: ChannelSetupAdapter = createPatchedAccountSetupAdapter({
|
||||
channelKey: channel,
|
||||
validateInput: createZodSetupInputValidator({
|
||||
schema: SignalSetupInputSchema,
|
||||
validateInput: createSetupInputPresenceValidator({
|
||||
validate: ({ input }) => {
|
||||
if (
|
||||
!input.signalNumber &&
|
||||
|
||||
@@ -4,13 +4,12 @@ import {
|
||||
normalizeAccountId,
|
||||
patchScopedAccountConfig,
|
||||
prepareScopedSetupConfig,
|
||||
createZodSetupInputValidator,
|
||||
createSetupInputPresenceValidator,
|
||||
type ChannelSetupAdapter,
|
||||
type ChannelSetupInput,
|
||||
type ChannelSetupWizard,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import { z } from "zod";
|
||||
import { buildTlonAccountFields } from "./account-fields.js";
|
||||
import { normalizeShip } from "./targets.js";
|
||||
import { listTlonAccountIds, resolveTlonAccount, type TlonResolvedAccount } from "./types.js";
|
||||
@@ -31,14 +30,6 @@ export type TlonSetupInput = ChannelSetupInput & {
|
||||
ownerShip?: string;
|
||||
};
|
||||
|
||||
const TlonSetupInputSchema = z
|
||||
.object({
|
||||
ship: z.string().optional(),
|
||||
url: z.string().optional(),
|
||||
code: z.string().optional(),
|
||||
})
|
||||
.passthrough() as z.ZodType<TlonSetupInput>;
|
||||
|
||||
function isConfigured(account: TlonResolvedAccount): boolean {
|
||||
return Boolean(account.ship && account.url && account.code);
|
||||
}
|
||||
@@ -196,8 +187,7 @@ export const tlonSetupAdapter: ChannelSetupAdapter = {
|
||||
accountId,
|
||||
name,
|
||||
}),
|
||||
validateInput: createZodSetupInputValidator({
|
||||
schema: TlonSetupInputSchema,
|
||||
validateInput: createSetupInputPresenceValidator({
|
||||
validate: ({ cfg, accountId, input }) => {
|
||||
const resolved = resolveTlonAccount(cfg, accountId ?? undefined);
|
||||
const ship = input.ship?.trim() || resolved.ship;
|
||||
|
||||
@@ -1,33 +1,21 @@
|
||||
import {
|
||||
createPatchedAccountSetupAdapter,
|
||||
createZodSetupInputValidator,
|
||||
createSetupInputPresenceValidator,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import { z } from "zod";
|
||||
|
||||
const channel = "zalo" as const;
|
||||
|
||||
const ZaloSetupInputSchema = z
|
||||
.object({
|
||||
useEnv: z.boolean().optional(),
|
||||
token: z.string().optional(),
|
||||
tokenFile: z.string().optional(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
export const zaloSetupAdapter = createPatchedAccountSetupAdapter({
|
||||
channelKey: channel,
|
||||
validateInput: createZodSetupInputValidator({
|
||||
schema: ZaloSetupInputSchema,
|
||||
validate: ({ accountId, input }) => {
|
||||
if (input.useEnv && accountId !== DEFAULT_ACCOUNT_ID) {
|
||||
return "ZALO_BOT_TOKEN can only be used for the default account.";
|
||||
}
|
||||
if (!input.useEnv && !input.token && !input.tokenFile) {
|
||||
return "Zalo requires token or --token-file (or --use-env).";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
validateInput: createSetupInputPresenceValidator({
|
||||
defaultAccountOnlyEnvError: "ZALO_BOT_TOKEN can only be used for the default account.",
|
||||
whenNotUseEnv: [
|
||||
{
|
||||
someOf: ["token", "tokenFile"],
|
||||
message: "Zalo requires token or --token-file (or --use-env).",
|
||||
},
|
||||
],
|
||||
}),
|
||||
buildPatch: (input) =>
|
||||
input.useEnv
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ZodType } from "zod";
|
||||
import { z, type ZodType } from "zod";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js";
|
||||
import type { ChannelSetupAdapter } from "./types.adapters.js";
|
||||
@@ -224,6 +224,57 @@ export function createZodSetupInputValidator<T extends ChannelSetupInput>(params
|
||||
};
|
||||
}
|
||||
|
||||
const GenericSetupInputSchema = z
|
||||
.object({
|
||||
useEnv: z.boolean().optional(),
|
||||
})
|
||||
.passthrough() as ZodType<ChannelSetupInput>;
|
||||
|
||||
type SetupInputPresenceRequirement = {
|
||||
someOf: string[];
|
||||
message: string;
|
||||
};
|
||||
|
||||
function hasPresentSetupValue(value: unknown): boolean {
|
||||
if (typeof value === "string") {
|
||||
return value.trim().length > 0;
|
||||
}
|
||||
return value !== undefined && value !== null;
|
||||
}
|
||||
|
||||
export function createSetupInputPresenceValidator(params: {
|
||||
defaultAccountOnlyEnvError?: string;
|
||||
whenNotUseEnv?: SetupInputPresenceRequirement[];
|
||||
validate?: (params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId: string;
|
||||
input: ChannelSetupInput;
|
||||
}) => string | null;
|
||||
}): NonNullable<ChannelSetupAdapter["validateInput"]> {
|
||||
return createZodSetupInputValidator({
|
||||
schema: GenericSetupInputSchema,
|
||||
validate: (inputParams) => {
|
||||
if (
|
||||
params.defaultAccountOnlyEnvError &&
|
||||
inputParams.input.useEnv &&
|
||||
inputParams.accountId !== DEFAULT_ACCOUNT_ID
|
||||
) {
|
||||
return params.defaultAccountOnlyEnvError;
|
||||
}
|
||||
if (!inputParams.input.useEnv) {
|
||||
const inputRecord = inputParams.input as Record<string, unknown>;
|
||||
for (const requirement of params.whenNotUseEnv ?? []) {
|
||||
if (requirement.someOf.some((key) => hasPresentSetupValue(inputRecord[key]))) {
|
||||
continue;
|
||||
}
|
||||
return requirement.message;
|
||||
}
|
||||
}
|
||||
return params.validate?.(inputParams) ?? null;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function createEnvPatchedAccountSetupAdapter(params: {
|
||||
channelKey: string;
|
||||
alwaysUseAccounts?: boolean;
|
||||
|
||||
@@ -28,6 +28,7 @@ export {
|
||||
applyAccountNameToChannelSection,
|
||||
applySetupAccountConfigPatch,
|
||||
createEnvPatchedAccountSetupAdapter,
|
||||
createSetupInputPresenceValidator,
|
||||
createPatchedAccountSetupAdapter,
|
||||
createZodSetupInputValidator,
|
||||
migrateBaseNameToDefaultAccount,
|
||||
|
||||
Reference in New Issue
Block a user