UNPKG

@gguf/claw

Version:

WhatsApp gateway CLI (Baileys web) with Pi RPC agent

1,502 lines (1,488 loc) 808 kB
import "./pi-model-discovery-Dw3A6oXH.js"; import { createRequire } from "node:module"; import { z } from "zod"; import os, { homedir } from "node:os"; import path from "node:path"; import fs, { createWriteStream, existsSync, statSync } from "node:fs"; import { Logger } from "tslog"; import json5 from "json5"; import chalk, { Chalk } from "chalk"; import fs$1 from "node:fs/promises"; import { execFile, execFileSync, spawn } from "node:child_process"; import { promisify } from "node:util"; import { fileURLToPath } from "node:url"; import crypto, { X509Certificate, randomUUID } from "node:crypto"; import "proper-lockfile"; import { getEnvApiKey, getOAuthProviders } from "@mariozechner/pi-ai"; import { BedrockClient, ListFoundationModelsCommand } from "@aws-sdk/client-bedrock"; import AjvPkg from "ajv"; import "@mariozechner/pi-coding-agent"; import { Type } from "@sinclair/typebox"; import { fileTypeFromBuffer } from "file-type"; import { request } from "node:https"; import { pipeline } from "node:stream/promises"; import { lookup } from "node:dns"; import { lookup as lookup$1 } from "node:dns/promises"; import { Agent } from "undici"; import { PermissionFlagsBits } from "discord-api-types/v10"; import { RequestClient } from "@buape/carbon"; import "discord-api-types/payloads/v10"; import "markdown-it"; import "@clack/prompts"; import { WebSocket } from "ws"; import { Buffer as Buffer$1 } from "node:buffer"; import { WebClient } from "@slack/web-api"; import "yaml"; import "express"; import "node-edge-tts"; import "jiti"; import "chokidar"; import { Bot } from "grammy"; import "osc-progress"; import "@buape/carbon/gateway"; import { messagingApi } from "@line/bot-sdk"; import SlackBolt from "@slack/bolt"; import "@grammyjs/runner"; import "@grammyjs/transformer-throttler"; import { DisconnectReason, fetchLatestBaileysVersion, makeCacheableSignalKeyStore, makeWASocket, useMultiFileAuthState } from "@whiskeysockets/baileys"; import "qrcode-terminal/vendor/QRCode/index.js"; import "qrcode-terminal/vendor/QRCode/QRErrorCorrectLevel.js"; import qrcode from "qrcode-terminal"; import { EventEmitter } from "node:events"; //#region src/channels/plugins/message-action-names.ts const CHANNEL_MESSAGE_ACTION_NAMES = [ "send", "broadcast", "poll", "react", "reactions", "read", "edit", "unsend", "reply", "sendWithEffect", "renameGroup", "setGroupIcon", "addParticipant", "removeParticipant", "leaveGroup", "sendAttachment", "delete", "pin", "unpin", "list-pins", "permissions", "thread-create", "thread-list", "thread-reply", "search", "sticker", "sticker-search", "member-info", "role-info", "emoji-list", "emoji-upload", "sticker-upload", "role-add", "role-remove", "channel-info", "channel-list", "channel-create", "channel-edit", "channel-delete", "channel-move", "category-create", "category-edit", "category-delete", "voice-status", "event-list", "event-create", "timeout", "kick", "ban", "set-presence" ]; //#endregion //#region src/channels/plugins/bluebubbles-actions.ts const BLUEBUBBLES_ACTIONS = { react: { gate: "reactions" }, edit: { gate: "edit", unsupportedOnMacOS26: true }, unsend: { gate: "unsend" }, reply: { gate: "reply" }, sendWithEffect: { gate: "sendWithEffect" }, renameGroup: { gate: "renameGroup", groupOnly: true }, setGroupIcon: { gate: "setGroupIcon", groupOnly: true }, addParticipant: { gate: "addParticipant", groupOnly: true }, removeParticipant: { gate: "removeParticipant", groupOnly: true }, leaveGroup: { gate: "leaveGroup", groupOnly: true }, sendAttachment: { gate: "sendAttachment" } }; const BLUEBUBBLES_ACTION_SPECS = BLUEBUBBLES_ACTIONS; const BLUEBUBBLES_ACTION_NAMES = Object.keys(BLUEBUBBLES_ACTIONS); const BLUEBUBBLES_GROUP_ACTIONS = new Set(BLUEBUBBLES_ACTION_NAMES.filter((action) => BLUEBUBBLES_ACTION_SPECS[action]?.groupOnly)); //#endregion //#region src/plugins/http-path.ts function normalizePluginHttpPath(path, fallback) { const trimmed = path?.trim(); if (!trimmed) { const fallbackTrimmed = fallback?.trim(); if (!fallbackTrimmed) return null; return fallbackTrimmed.startsWith("/") ? fallbackTrimmed : `/${fallbackTrimmed}`; } return trimmed.startsWith("/") ? trimmed : `/${trimmed}`; } //#endregion //#region src/plugins/runtime.ts const createEmptyRegistry = () => ({ plugins: [], tools: [], hooks: [], typedHooks: [], channels: [], providers: [], gatewayHandlers: {}, httpHandlers: [], httpRoutes: [], cliRegistrars: [], services: [], commands: [], diagnostics: [] }); const REGISTRY_STATE = Symbol.for("openclaw.pluginRegistryState"); const state = (() => { const globalState = globalThis; if (!globalState[REGISTRY_STATE]) globalState[REGISTRY_STATE] = { registry: createEmptyRegistry(), key: null }; return globalState[REGISTRY_STATE]; })(); function getActivePluginRegistry() { return state.registry; } function requireActivePluginRegistry() { if (!state.registry) state.registry = createEmptyRegistry(); return state.registry; } //#endregion //#region src/plugins/http-registry.ts function registerPluginHttpRoute(params) { const registry = params.registry ?? requireActivePluginRegistry(); const routes = registry.httpRoutes ?? []; registry.httpRoutes = routes; const normalizedPath = normalizePluginHttpPath(params.path, params.fallbackPath); const suffix = params.accountId ? ` for account "${params.accountId}"` : ""; if (!normalizedPath) { params.log?.(`plugin: webhook path missing${suffix}`); return () => {}; } if (routes.some((entry) => entry.path === normalizedPath)) { const pluginHint = params.pluginId ? ` (${params.pluginId})` : ""; params.log?.(`plugin: webhook path ${normalizedPath} already registered${suffix}${pluginHint}`); return () => {}; } const entry = { path: normalizedPath, handler: params.handler, pluginId: params.pluginId, source: params.source }; routes.push(entry); return () => { const index = routes.indexOf(entry); if (index >= 0) routes.splice(index, 1); }; } //#endregion //#region src/plugins/config-schema.ts function error(message) { return { success: false, error: { issues: [{ path: [], message }] } }; } function emptyPluginConfigSchema() { return { safeParse(value) { if (value === void 0) return { success: true, data: void 0 }; if (!value || typeof value !== "object" || Array.isArray(value)) return error("expected config object"); if (Object.keys(value).length > 0) return error("config must be empty"); return { success: true, data: value }; }, jsonSchema: { type: "object", additionalProperties: false, properties: {} } }; } //#endregion //#region src/channels/registry.ts const CHAT_CHANNEL_ORDER = [ "telegram", "whatsapp", "discord", "googlechat", "slack", "signal", "imessage" ]; const CHANNEL_IDS = [...CHAT_CHANNEL_ORDER]; const CHAT_CHANNEL_META = { telegram: { id: "telegram", label: "Telegram", selectionLabel: "Telegram (Bot API)", detailLabel: "Telegram Bot", docsPath: "/channels/telegram", docsLabel: "telegram", blurb: "simplest way to get started — register a bot with @BotFather and get going.", systemImage: "paperplane", selectionDocsPrefix: "", selectionDocsOmitLabel: true, selectionExtras: ["https://openclaw.ai"] }, whatsapp: { id: "whatsapp", label: "WhatsApp", selectionLabel: "WhatsApp (QR link)", detailLabel: "WhatsApp Web", docsPath: "/channels/whatsapp", docsLabel: "whatsapp", blurb: "works with your own number; recommend a separate phone + eSIM.", systemImage: "message" }, discord: { id: "discord", label: "Discord", selectionLabel: "Discord (Bot API)", detailLabel: "Discord Bot", docsPath: "/channels/discord", docsLabel: "discord", blurb: "very well supported right now.", systemImage: "bubble.left.and.bubble.right" }, googlechat: { id: "googlechat", label: "Google Chat", selectionLabel: "Google Chat (Chat API)", detailLabel: "Google Chat", docsPath: "/channels/googlechat", docsLabel: "googlechat", blurb: "Google Workspace Chat app with HTTP webhook.", systemImage: "message.badge" }, slack: { id: "slack", label: "Slack", selectionLabel: "Slack (Socket Mode)", detailLabel: "Slack Bot", docsPath: "/channels/slack", docsLabel: "slack", blurb: "supported (Socket Mode).", systemImage: "number" }, signal: { id: "signal", label: "Signal", selectionLabel: "Signal (signal-cli)", detailLabel: "Signal REST", docsPath: "/channels/signal", docsLabel: "signal", blurb: "signal-cli linked device; more setup (David Reagans: \"Hop on Discord.\").", systemImage: "antenna.radiowaves.left.and.right" }, imessage: { id: "imessage", label: "iMessage", selectionLabel: "iMessage (imsg)", detailLabel: "iMessage", docsPath: "/channels/imessage", docsLabel: "imessage", blurb: "this is still a work in progress.", systemImage: "message.fill" } }; const CHAT_CHANNEL_ALIASES = { imsg: "imessage", "google-chat": "googlechat", gchat: "googlechat" }; const normalizeChannelKey = (raw) => { return raw?.trim().toLowerCase() || void 0; }; function getChatChannelMeta(id) { return CHAT_CHANNEL_META[id]; } function normalizeChatChannelId(raw) { const normalized = normalizeChannelKey(raw); if (!normalized) return null; const resolved = CHAT_CHANNEL_ALIASES[normalized] ?? normalized; return CHAT_CHANNEL_ORDER.includes(resolved) ? resolved : null; } function normalizeAnyChannelId(raw) { const key = normalizeChannelKey(raw); if (!key) return null; return requireActivePluginRegistry().channels.find((entry) => { const id = String(entry.plugin.id ?? "").trim().toLowerCase(); if (id && id === key) return true; return (entry.plugin.meta.aliases ?? []).some((alias) => alias.trim().toLowerCase() === key); })?.plugin.id ?? null; } //#endregion //#region src/config/telegram-custom-commands.ts const TELEGRAM_COMMAND_NAME_PATTERN = /^[a-z0-9_]{1,32}$/; function normalizeTelegramCommandName(value) { const trimmed = value.trim(); if (!trimmed) return ""; return (trimmed.startsWith("/") ? trimmed.slice(1) : trimmed).trim().toLowerCase(); } function normalizeTelegramCommandDescription(value) { return value.trim(); } function resolveTelegramCustomCommands(params) { const entries = Array.isArray(params.commands) ? params.commands : []; const reserved = params.reservedCommands ?? /* @__PURE__ */ new Set(); const checkReserved = params.checkReserved !== false; const checkDuplicates = params.checkDuplicates !== false; const seen = /* @__PURE__ */ new Set(); const resolved = []; const issues = []; for (let index = 0; index < entries.length; index += 1) { const entry = entries[index]; const normalized = normalizeTelegramCommandName(String(entry?.command ?? "")); if (!normalized) { issues.push({ index, field: "command", message: "Telegram custom command is missing a command name." }); continue; } if (!TELEGRAM_COMMAND_NAME_PATTERN.test(normalized)) { issues.push({ index, field: "command", message: `Telegram custom command "/${normalized}" is invalid (use a-z, 0-9, underscore; max 32 chars).` }); continue; } if (checkReserved && reserved.has(normalized)) { issues.push({ index, field: "command", message: `Telegram custom command "/${normalized}" conflicts with a native command.` }); continue; } if (checkDuplicates && seen.has(normalized)) { issues.push({ index, field: "command", message: `Telegram custom command "/${normalized}" is duplicated.` }); continue; } const description = normalizeTelegramCommandDescription(String(entry?.description ?? "")); if (!description) { issues.push({ index, field: "description", message: `Telegram custom command "/${normalized}" is missing a description.` }); continue; } if (checkDuplicates) seen.add(normalized); resolved.push({ command: normalized, description }); } return { commands: resolved, issues }; } //#endregion //#region src/cli/parse-duration.ts function parseDurationMs(raw, opts) { const trimmed = String(raw ?? "").trim().toLowerCase(); if (!trimmed) throw new Error("invalid duration (empty)"); const m = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)?$/.exec(trimmed); if (!m) throw new Error(`invalid duration: ${raw}`); const value = Number(m[1]); if (!Number.isFinite(value) || value < 0) throw new Error(`invalid duration: ${raw}`); const unit = m[2] ?? opts?.defaultUnit ?? "ms"; const multiplier = unit === "ms" ? 1 : unit === "s" ? 1e3 : unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5; const ms = Math.round(value * multiplier); if (!Number.isFinite(ms)) throw new Error(`invalid duration: ${raw}`); return ms; } //#endregion //#region src/infra/exec-safety.ts const SHELL_METACHARS = /[;&|`$<>]/; const CONTROL_CHARS = /[\r\n]/; const QUOTE_CHARS = /["']/; const BARE_NAME_PATTERN = /^[A-Za-z0-9._+-]+$/; function isLikelyPath(value) { if (value.startsWith(".") || value.startsWith("~")) return true; if (value.includes("/") || value.includes("\\")) return true; return /^[A-Za-z]:[\\/]/.test(value); } function isSafeExecutableValue(value) { if (!value) return false; const trimmed = value.trim(); if (!trimmed) return false; if (trimmed.includes("\0")) return false; if (CONTROL_CHARS.test(trimmed)) return false; if (SHELL_METACHARS.test(trimmed)) return false; if (QUOTE_CHARS.test(trimmed)) return false; if (isLikelyPath(trimmed)) return true; if (trimmed.startsWith("-")) return false; return BARE_NAME_PATTERN.test(trimmed); } //#endregion //#region src/config/zod-schema.core.ts const ModelApiSchema = z.union([ z.literal("openai-completions"), z.literal("openai-responses"), z.literal("anthropic-messages"), z.literal("google-generative-ai"), z.literal("github-copilot"), z.literal("bedrock-converse-stream") ]); const ModelCompatSchema = z.object({ supportsStore: z.boolean().optional(), supportsDeveloperRole: z.boolean().optional(), supportsReasoningEffort: z.boolean().optional(), maxTokensField: z.union([z.literal("max_completion_tokens"), z.literal("max_tokens")]).optional() }).strict().optional(); const ModelDefinitionSchema = z.object({ id: z.string().min(1), name: z.string().min(1), api: ModelApiSchema.optional(), reasoning: z.boolean().optional(), input: z.array(z.union([z.literal("text"), z.literal("image")])).optional(), cost: z.object({ input: z.number().optional(), output: z.number().optional(), cacheRead: z.number().optional(), cacheWrite: z.number().optional() }).strict().optional(), contextWindow: z.number().positive().optional(), maxTokens: z.number().positive().optional(), headers: z.record(z.string(), z.string()).optional(), compat: ModelCompatSchema }).strict(); const ModelProviderSchema = z.object({ baseUrl: z.string().min(1), apiKey: z.string().optional(), auth: z.union([ z.literal("api-key"), z.literal("aws-sdk"), z.literal("oauth"), z.literal("token") ]).optional(), api: ModelApiSchema.optional(), headers: z.record(z.string(), z.string()).optional(), authHeader: z.boolean().optional(), models: z.array(ModelDefinitionSchema) }).strict(); const BedrockDiscoverySchema = z.object({ enabled: z.boolean().optional(), region: z.string().optional(), providerFilter: z.array(z.string()).optional(), refreshInterval: z.number().int().nonnegative().optional(), defaultContextWindow: z.number().int().positive().optional(), defaultMaxTokens: z.number().int().positive().optional() }).strict().optional(); const ModelsConfigSchema = z.object({ mode: z.union([z.literal("merge"), z.literal("replace")]).optional(), providers: z.record(z.string(), ModelProviderSchema).optional(), bedrockDiscovery: BedrockDiscoverySchema }).strict().optional(); const GroupChatSchema = z.object({ mentionPatterns: z.array(z.string()).optional(), historyLimit: z.number().int().positive().optional() }).strict().optional(); const DmConfigSchema = z.object({ historyLimit: z.number().int().min(0).optional() }).strict(); const IdentitySchema = z.object({ name: z.string().optional(), theme: z.string().optional(), emoji: z.string().optional(), avatar: z.string().optional() }).strict().optional(); const QueueModeSchema = z.union([ z.literal("steer"), z.literal("followup"), z.literal("collect"), z.literal("steer-backlog"), z.literal("steer+backlog"), z.literal("queue"), z.literal("interrupt") ]); const QueueDropSchema = z.union([ z.literal("old"), z.literal("new"), z.literal("summarize") ]); const ReplyToModeSchema = z.union([ z.literal("off"), z.literal("first"), z.literal("all") ]); const GroupPolicySchema = z.enum([ "open", "disabled", "allowlist" ]); const DmPolicySchema = z.enum([ "pairing", "allowlist", "open", "disabled" ]); const BlockStreamingCoalesceSchema = z.object({ minChars: z.number().int().positive().optional(), maxChars: z.number().int().positive().optional(), idleMs: z.number().int().nonnegative().optional() }).strict(); const BlockStreamingChunkSchema = z.object({ minChars: z.number().int().positive().optional(), maxChars: z.number().int().positive().optional(), breakPreference: z.union([ z.literal("paragraph"), z.literal("newline"), z.literal("sentence") ]).optional() }).strict(); const MarkdownTableModeSchema = z.enum([ "off", "bullets", "code" ]); const MarkdownConfigSchema = z.object({ tables: MarkdownTableModeSchema.optional() }).strict().optional(); const TtsProviderSchema = z.enum([ "elevenlabs", "openai", "edge" ]); const TtsModeSchema = z.enum(["final", "all"]); const TtsAutoSchema = z.enum([ "off", "always", "inbound", "tagged" ]); const TtsConfigSchema = z.object({ auto: TtsAutoSchema.optional(), enabled: z.boolean().optional(), mode: TtsModeSchema.optional(), provider: TtsProviderSchema.optional(), summaryModel: z.string().optional(), modelOverrides: z.object({ enabled: z.boolean().optional(), allowText: z.boolean().optional(), allowProvider: z.boolean().optional(), allowVoice: z.boolean().optional(), allowModelId: z.boolean().optional(), allowVoiceSettings: z.boolean().optional(), allowNormalization: z.boolean().optional(), allowSeed: z.boolean().optional() }).strict().optional(), elevenlabs: z.object({ apiKey: z.string().optional(), baseUrl: z.string().optional(), voiceId: z.string().optional(), modelId: z.string().optional(), seed: z.number().int().min(0).max(4294967295).optional(), applyTextNormalization: z.enum([ "auto", "on", "off" ]).optional(), languageCode: z.string().optional(), voiceSettings: z.object({ stability: z.number().min(0).max(1).optional(), similarityBoost: z.number().min(0).max(1).optional(), style: z.number().min(0).max(1).optional(), useSpeakerBoost: z.boolean().optional(), speed: z.number().min(.5).max(2).optional() }).strict().optional() }).strict().optional(), openai: z.object({ apiKey: z.string().optional(), model: z.string().optional(), voice: z.string().optional() }).strict().optional(), edge: z.object({ enabled: z.boolean().optional(), voice: z.string().optional(), lang: z.string().optional(), outputFormat: z.string().optional(), pitch: z.string().optional(), rate: z.string().optional(), volume: z.string().optional(), saveSubtitles: z.boolean().optional(), proxy: z.string().optional(), timeoutMs: z.number().int().min(1e3).max(12e4).optional() }).strict().optional(), prefsPath: z.string().optional(), maxTextLength: z.number().int().min(1).optional(), timeoutMs: z.number().int().min(1e3).max(12e4).optional() }).strict().optional(); const HumanDelaySchema = z.object({ mode: z.union([ z.literal("off"), z.literal("natural"), z.literal("custom") ]).optional(), minMs: z.number().int().nonnegative().optional(), maxMs: z.number().int().nonnegative().optional() }).strict(); const CliBackendSchema = z.object({ command: z.string(), args: z.array(z.string()).optional(), output: z.union([ z.literal("json"), z.literal("text"), z.literal("jsonl") ]).optional(), resumeOutput: z.union([ z.literal("json"), z.literal("text"), z.literal("jsonl") ]).optional(), input: z.union([z.literal("arg"), z.literal("stdin")]).optional(), maxPromptArgChars: z.number().int().positive().optional(), env: z.record(z.string(), z.string()).optional(), clearEnv: z.array(z.string()).optional(), modelArg: z.string().optional(), modelAliases: z.record(z.string(), z.string()).optional(), sessionArg: z.string().optional(), sessionArgs: z.array(z.string()).optional(), resumeArgs: z.array(z.string()).optional(), sessionMode: z.union([ z.literal("always"), z.literal("existing"), z.literal("none") ]).optional(), sessionIdFields: z.array(z.string()).optional(), systemPromptArg: z.string().optional(), systemPromptMode: z.union([z.literal("append"), z.literal("replace")]).optional(), systemPromptWhen: z.union([ z.literal("first"), z.literal("always"), z.literal("never") ]).optional(), imageArg: z.string().optional(), imageMode: z.union([z.literal("repeat"), z.literal("list")]).optional(), serialize: z.boolean().optional() }).strict(); const normalizeAllowFrom = (values) => (values ?? []).map((v) => String(v).trim()).filter(Boolean); const requireOpenAllowFrom = (params) => { if (params.policy !== "open") return; if (normalizeAllowFrom(params.allowFrom).includes("*")) return; params.ctx.addIssue({ code: z.ZodIssueCode.custom, path: params.path, message: params.message }); }; const MSTeamsReplyStyleSchema = z.enum(["thread", "top-level"]); const RetryConfigSchema = z.object({ attempts: z.number().int().min(1).optional(), minDelayMs: z.number().int().min(0).optional(), maxDelayMs: z.number().int().min(0).optional(), jitter: z.number().min(0).max(1).optional() }).strict().optional(); const QueueModeBySurfaceSchema = z.object({ whatsapp: QueueModeSchema.optional(), telegram: QueueModeSchema.optional(), discord: QueueModeSchema.optional(), slack: QueueModeSchema.optional(), mattermost: QueueModeSchema.optional(), signal: QueueModeSchema.optional(), imessage: QueueModeSchema.optional(), msteams: QueueModeSchema.optional(), webchat: QueueModeSchema.optional() }).strict().optional(); const DebounceMsBySurfaceSchema = z.record(z.string(), z.number().int().nonnegative()).optional(); const QueueSchema = z.object({ mode: QueueModeSchema.optional(), byChannel: QueueModeBySurfaceSchema, debounceMs: z.number().int().nonnegative().optional(), debounceMsByChannel: DebounceMsBySurfaceSchema, cap: z.number().int().positive().optional(), drop: QueueDropSchema.optional() }).strict().optional(); const InboundDebounceSchema = z.object({ debounceMs: z.number().int().nonnegative().optional(), byChannel: DebounceMsBySurfaceSchema }).strict().optional(); const TranscribeAudioSchema = z.object({ command: z.array(z.string()).superRefine((value, ctx) => { const executable = value[0]; if (!isSafeExecutableValue(executable)) ctx.addIssue({ code: z.ZodIssueCode.custom, path: [0], message: "expected safe executable name or path" }); }), timeoutSeconds: z.number().int().positive().optional() }).strict().optional(); const HexColorSchema = z.string().regex(/^#?[0-9a-fA-F]{6}$/, "expected hex color (RRGGBB)"); const ExecutableTokenSchema = z.string().refine(isSafeExecutableValue, "expected safe executable name or path"); const MediaUnderstandingScopeSchema = z.object({ default: z.union([z.literal("allow"), z.literal("deny")]).optional(), rules: z.array(z.object({ action: z.union([z.literal("allow"), z.literal("deny")]), match: z.object({ channel: z.string().optional(), chatType: z.union([ z.literal("direct"), z.literal("group"), z.literal("channel") ]).optional(), keyPrefix: z.string().optional() }).strict().optional() }).strict()).optional() }).strict().optional(); const MediaUnderstandingCapabilitiesSchema = z.array(z.union([ z.literal("image"), z.literal("audio"), z.literal("video") ])).optional(); const MediaUnderstandingAttachmentsSchema = z.object({ mode: z.union([z.literal("first"), z.literal("all")]).optional(), maxAttachments: z.number().int().positive().optional(), prefer: z.union([ z.literal("first"), z.literal("last"), z.literal("path"), z.literal("url") ]).optional() }).strict().optional(); const DeepgramAudioSchema = z.object({ detectLanguage: z.boolean().optional(), punctuate: z.boolean().optional(), smartFormat: z.boolean().optional() }).strict().optional(); const ProviderOptionValueSchema = z.union([ z.string(), z.number(), z.boolean() ]); const ProviderOptionsSchema = z.record(z.string(), z.record(z.string(), ProviderOptionValueSchema)).optional(); const MediaUnderstandingModelSchema = z.object({ provider: z.string().optional(), model: z.string().optional(), capabilities: MediaUnderstandingCapabilitiesSchema, type: z.union([z.literal("provider"), z.literal("cli")]).optional(), command: z.string().optional(), args: z.array(z.string()).optional(), prompt: z.string().optional(), maxChars: z.number().int().positive().optional(), maxBytes: z.number().int().positive().optional(), timeoutSeconds: z.number().int().positive().optional(), language: z.string().optional(), providerOptions: ProviderOptionsSchema, deepgram: DeepgramAudioSchema, baseUrl: z.string().optional(), headers: z.record(z.string(), z.string()).optional(), profile: z.string().optional(), preferredProfile: z.string().optional() }).strict().optional(); const ToolsMediaUnderstandingSchema = z.object({ enabled: z.boolean().optional(), scope: MediaUnderstandingScopeSchema, maxBytes: z.number().int().positive().optional(), maxChars: z.number().int().positive().optional(), prompt: z.string().optional(), timeoutSeconds: z.number().int().positive().optional(), language: z.string().optional(), providerOptions: ProviderOptionsSchema, deepgram: DeepgramAudioSchema, baseUrl: z.string().optional(), headers: z.record(z.string(), z.string()).optional(), attachments: MediaUnderstandingAttachmentsSchema, models: z.array(MediaUnderstandingModelSchema).optional() }).strict().optional(); const ToolsMediaSchema = z.object({ models: z.array(MediaUnderstandingModelSchema).optional(), concurrency: z.number().int().positive().optional(), image: ToolsMediaUnderstandingSchema.optional(), audio: ToolsMediaUnderstandingSchema.optional(), video: ToolsMediaUnderstandingSchema.optional() }).strict().optional(); const LinkModelSchema = z.object({ type: z.literal("cli").optional(), command: z.string().min(1), args: z.array(z.string()).optional(), timeoutSeconds: z.number().int().positive().optional() }).strict(); const ToolsLinksSchema = z.object({ enabled: z.boolean().optional(), scope: MediaUnderstandingScopeSchema, maxLinks: z.number().int().positive().optional(), timeoutSeconds: z.number().int().positive().optional(), models: z.array(LinkModelSchema).optional() }).strict().optional(); const NativeCommandsSettingSchema = z.union([z.boolean(), z.literal("auto")]); const ProviderCommandsSchema = z.object({ native: NativeCommandsSettingSchema.optional(), nativeSkills: NativeCommandsSettingSchema.optional() }).strict().optional(); //#endregion //#region src/config/zod-schema.agent-runtime.ts const HeartbeatSchema = z.object({ every: z.string().optional(), activeHours: z.object({ start: z.string().optional(), end: z.string().optional(), timezone: z.string().optional() }).strict().optional(), model: z.string().optional(), session: z.string().optional(), includeReasoning: z.boolean().optional(), target: z.string().optional(), to: z.string().optional(), accountId: z.string().optional(), prompt: z.string().optional(), ackMaxChars: z.number().int().nonnegative().optional() }).strict().superRefine((val, ctx) => { if (!val.every) return; try { parseDurationMs(val.every, { defaultUnit: "m" }); } catch { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["every"], message: "invalid duration (use ms, s, m, h)" }); } const active = val.activeHours; if (!active) return; const timePattern = /^([01]\d|2[0-3]|24):([0-5]\d)$/; const validateTime = (raw, opts, path) => { if (!raw) return; if (!timePattern.test(raw)) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["activeHours", path], message: "invalid time (use \"HH:MM\" 24h format)" }); return; } const [hourStr, minuteStr] = raw.split(":"); const hour = Number(hourStr); if (hour === 24 && Number(minuteStr) !== 0) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["activeHours", path], message: "invalid time (24:00 is the only allowed 24:xx value)" }); return; } if (hour === 24 && !opts.allow24) ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["activeHours", path], message: "invalid time (start cannot be 24:00)" }); }; validateTime(active.start, { allow24: false }, "start"); validateTime(active.end, { allow24: true }, "end"); }).optional(); const SandboxDockerSchema = z.object({ image: z.string().optional(), containerPrefix: z.string().optional(), workdir: z.string().optional(), readOnlyRoot: z.boolean().optional(), tmpfs: z.array(z.string()).optional(), network: z.string().optional(), user: z.string().optional(), capDrop: z.array(z.string()).optional(), env: z.record(z.string(), z.string()).optional(), setupCommand: z.string().optional(), pidsLimit: z.number().int().positive().optional(), memory: z.union([z.string(), z.number()]).optional(), memorySwap: z.union([z.string(), z.number()]).optional(), cpus: z.number().positive().optional(), ulimits: z.record(z.string(), z.union([ z.string(), z.number(), z.object({ soft: z.number().int().nonnegative().optional(), hard: z.number().int().nonnegative().optional() }).strict() ])).optional(), seccompProfile: z.string().optional(), apparmorProfile: z.string().optional(), dns: z.array(z.string()).optional(), extraHosts: z.array(z.string()).optional(), binds: z.array(z.string()).optional() }).strict().optional(); const SandboxBrowserSchema = z.object({ enabled: z.boolean().optional(), image: z.string().optional(), containerPrefix: z.string().optional(), cdpPort: z.number().int().positive().optional(), vncPort: z.number().int().positive().optional(), noVncPort: z.number().int().positive().optional(), headless: z.boolean().optional(), enableNoVnc: z.boolean().optional(), allowHostControl: z.boolean().optional(), autoStart: z.boolean().optional(), autoStartTimeoutMs: z.number().int().positive().optional() }).strict().optional(); const SandboxPruneSchema = z.object({ idleHours: z.number().int().nonnegative().optional(), maxAgeDays: z.number().int().nonnegative().optional() }).strict().optional(); const ToolPolicyBaseSchema = z.object({ allow: z.array(z.string()).optional(), alsoAllow: z.array(z.string()).optional(), deny: z.array(z.string()).optional() }).strict(); const ToolPolicySchema = ToolPolicyBaseSchema.superRefine((value, ctx) => { if (value.allow && value.allow.length > 0 && value.alsoAllow && value.alsoAllow.length > 0) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "tools policy cannot set both allow and alsoAllow in the same scope (merge alsoAllow into allow, or remove allow and use profile + alsoAllow)" }); }).optional(); const ToolsWebSearchSchema = z.object({ enabled: z.boolean().optional(), provider: z.union([z.literal("brave"), z.literal("perplexity")]).optional(), apiKey: z.string().optional(), maxResults: z.number().int().positive().optional(), timeoutSeconds: z.number().int().positive().optional(), cacheTtlMinutes: z.number().nonnegative().optional(), perplexity: z.object({ apiKey: z.string().optional(), baseUrl: z.string().optional(), model: z.string().optional() }).strict().optional() }).strict().optional(); const ToolsWebFetchSchema = z.object({ enabled: z.boolean().optional(), maxChars: z.number().int().positive().optional(), maxCharsCap: z.number().int().positive().optional(), timeoutSeconds: z.number().int().positive().optional(), cacheTtlMinutes: z.number().nonnegative().optional(), maxRedirects: z.number().int().nonnegative().optional(), userAgent: z.string().optional() }).strict().optional(); const ToolsWebSchema = z.object({ search: ToolsWebSearchSchema, fetch: ToolsWebFetchSchema }).strict().optional(); const ToolProfileSchema = z.union([ z.literal("minimal"), z.literal("coding"), z.literal("messaging"), z.literal("full") ]).optional(); const ToolPolicyWithProfileSchema = z.object({ allow: z.array(z.string()).optional(), alsoAllow: z.array(z.string()).optional(), deny: z.array(z.string()).optional(), profile: ToolProfileSchema }).strict().superRefine((value, ctx) => { if (value.allow && value.allow.length > 0 && value.alsoAllow && value.alsoAllow.length > 0) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "tools.byProvider policy cannot set both allow and alsoAllow in the same scope (merge alsoAllow into allow, or remove allow and use profile + alsoAllow)" }); }); const ElevatedAllowFromSchema = z.record(z.string(), z.array(z.union([z.string(), z.number()]))).optional(); const AgentSandboxSchema = z.object({ mode: z.union([ z.literal("off"), z.literal("non-main"), z.literal("all") ]).optional(), workspaceAccess: z.union([ z.literal("none"), z.literal("ro"), z.literal("rw") ]).optional(), sessionToolsVisibility: z.union([z.literal("spawned"), z.literal("all")]).optional(), scope: z.union([ z.literal("session"), z.literal("agent"), z.literal("shared") ]).optional(), perSession: z.boolean().optional(), workspaceRoot: z.string().optional(), docker: SandboxDockerSchema, browser: SandboxBrowserSchema, prune: SandboxPruneSchema }).strict().optional(); const AgentToolsSchema = z.object({ profile: ToolProfileSchema, allow: z.array(z.string()).optional(), alsoAllow: z.array(z.string()).optional(), deny: z.array(z.string()).optional(), byProvider: z.record(z.string(), ToolPolicyWithProfileSchema).optional(), elevated: z.object({ enabled: z.boolean().optional(), allowFrom: ElevatedAllowFromSchema }).strict().optional(), exec: z.object({ host: z.enum([ "sandbox", "gateway", "node" ]).optional(), security: z.enum([ "deny", "allowlist", "full" ]).optional(), ask: z.enum([ "off", "on-miss", "always" ]).optional(), node: z.string().optional(), pathPrepend: z.array(z.string()).optional(), safeBins: z.array(z.string()).optional(), backgroundMs: z.number().int().positive().optional(), timeoutSec: z.number().int().positive().optional(), approvalRunningNoticeMs: z.number().int().nonnegative().optional(), cleanupMs: z.number().int().positive().optional(), notifyOnExit: z.boolean().optional(), applyPatch: z.object({ enabled: z.boolean().optional(), allowModels: z.array(z.string()).optional() }).strict().optional() }).strict().optional(), sandbox: z.object({ tools: ToolPolicySchema }).strict().optional() }).strict().superRefine((value, ctx) => { if (value.allow && value.allow.length > 0 && value.alsoAllow && value.alsoAllow.length > 0) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "agent tools cannot set both allow and alsoAllow in the same scope (merge alsoAllow into allow, or remove allow and use profile + alsoAllow)" }); }).optional(); const MemorySearchSchema$1 = z.object({ enabled: z.boolean().optional(), sources: z.array(z.union([z.literal("memory"), z.literal("sessions")])).optional(), extraPaths: z.array(z.string()).optional(), experimental: z.object({ sessionMemory: z.boolean().optional() }).strict().optional(), provider: z.union([ z.literal("openai"), z.literal("local"), z.literal("gemini") ]).optional(), remote: z.object({ baseUrl: z.string().optional(), apiKey: z.string().optional(), headers: z.record(z.string(), z.string()).optional(), batch: z.object({ enabled: z.boolean().optional(), wait: z.boolean().optional(), concurrency: z.number().int().positive().optional(), pollIntervalMs: z.number().int().nonnegative().optional(), timeoutMinutes: z.number().int().positive().optional() }).strict().optional() }).strict().optional(), fallback: z.union([ z.literal("openai"), z.literal("gemini"), z.literal("local"), z.literal("none") ]).optional(), model: z.string().optional(), local: z.object({ modelPath: z.string().optional(), modelCacheDir: z.string().optional() }).strict().optional(), store: z.object({ driver: z.literal("sqlite").optional(), path: z.string().optional(), vector: z.object({ enabled: z.boolean().optional(), extensionPath: z.string().optional() }).strict().optional() }).strict().optional(), chunking: z.object({ tokens: z.number().int().positive().optional(), overlap: z.number().int().nonnegative().optional() }).strict().optional(), sync: z.object({ onSessionStart: z.boolean().optional(), onSearch: z.boolean().optional(), watch: z.boolean().optional(), watchDebounceMs: z.number().int().nonnegative().optional(), intervalMinutes: z.number().int().nonnegative().optional(), sessions: z.object({ deltaBytes: z.number().int().nonnegative().optional(), deltaMessages: z.number().int().nonnegative().optional() }).strict().optional() }).strict().optional(), query: z.object({ maxResults: z.number().int().positive().optional(), minScore: z.number().min(0).max(1).optional(), hybrid: z.object({ enabled: z.boolean().optional(), vectorWeight: z.number().min(0).max(1).optional(), textWeight: z.number().min(0).max(1).optional(), candidateMultiplier: z.number().int().positive().optional() }).strict().optional() }).strict().optional(), cache: z.object({ enabled: z.boolean().optional(), maxEntries: z.number().int().positive().optional() }).strict().optional() }).strict().optional(); const AgentModelSchema = z.union([z.string(), z.object({ primary: z.string().optional(), fallbacks: z.array(z.string()).optional() }).strict()]); const AgentEntrySchema = z.object({ id: z.string(), default: z.boolean().optional(), name: z.string().optional(), workspace: z.string().optional(), agentDir: z.string().optional(), model: AgentModelSchema.optional(), skills: z.array(z.string()).optional(), memorySearch: MemorySearchSchema$1, humanDelay: HumanDelaySchema.optional(), heartbeat: HeartbeatSchema, identity: IdentitySchema, groupChat: GroupChatSchema, subagents: z.object({ allowAgents: z.array(z.string()).optional(), model: z.union([z.string(), z.object({ primary: z.string().optional(), fallbacks: z.array(z.string()).optional() }).strict()]).optional(), thinking: z.string().optional() }).strict().optional(), sandbox: AgentSandboxSchema, tools: AgentToolsSchema }).strict(); const ToolsSchema = z.object({ profile: ToolProfileSchema, allow: z.array(z.string()).optional(), alsoAllow: z.array(z.string()).optional(), deny: z.array(z.string()).optional(), byProvider: z.record(z.string(), ToolPolicyWithProfileSchema).optional(), web: ToolsWebSchema, media: ToolsMediaSchema, links: ToolsLinksSchema, message: z.object({ allowCrossContextSend: z.boolean().optional(), crossContext: z.object({ allowWithinProvider: z.boolean().optional(), allowAcrossProviders: z.boolean().optional(), marker: z.object({ enabled: z.boolean().optional(), prefix: z.string().optional(), suffix: z.string().optional() }).strict().optional() }).strict().optional(), broadcast: z.object({ enabled: z.boolean().optional() }).strict().optional() }).strict().optional(), agentToAgent: z.object({ enabled: z.boolean().optional(), allow: z.array(z.string()).optional() }).strict().optional(), elevated: z.object({ enabled: z.boolean().optional(), allowFrom: ElevatedAllowFromSchema }).strict().optional(), exec: z.object({ host: z.enum([ "sandbox", "gateway", "node" ]).optional(), security: z.enum([ "deny", "allowlist", "full" ]).optional(), ask: z.enum([ "off", "on-miss", "always" ]).optional(), node: z.string().optional(), pathPrepend: z.array(z.string()).optional(), safeBins: z.array(z.string()).optional(), backgroundMs: z.number().int().positive().optional(), timeoutSec: z.number().int().positive().optional(), cleanupMs: z.number().int().positive().optional(), notifyOnExit: z.boolean().optional(), applyPatch: z.object({ enabled: z.boolean().optional(), allowModels: z.array(z.string()).optional() }).strict().optional() }).strict().optional(), subagents: z.object({ tools: ToolPolicySchema }).strict().optional(), sandbox: z.object({ tools: ToolPolicySchema }).strict().optional() }).strict().superRefine((value, ctx) => { if (value.allow && value.allow.length > 0 && value.alsoAllow && value.alsoAllow.length > 0) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "tools cannot set both allow and alsoAllow in the same scope (merge alsoAllow into allow, or remove allow and use profile + alsoAllow)" }); }).optional(); //#endregion //#region src/config/zod-schema.channels.ts const ChannelHeartbeatVisibilitySchema = z.object({ showOk: z.boolean().optional(), showAlerts: z.boolean().optional(), useIndicator: z.boolean().optional() }).strict().optional(); //#endregion //#region src/config/zod-schema.providers-core.ts const ToolPolicyBySenderSchema$1 = z.record(z.string(), ToolPolicySchema).optional(); const TelegramInlineButtonsScopeSchema = z.enum([ "off", "dm", "group", "all", "allowlist" ]); const TelegramCapabilitiesSchema = z.union([z.array(z.string()), z.object({ inlineButtons: TelegramInlineButtonsScopeSchema.optional() }).strict()]); const TelegramTopicSchema = z.object({ requireMention: z.boolean().optional(), groupPolicy: GroupPolicySchema.optional(), skills: z.array(z.string()).optional(), enabled: z.boolean().optional(), allowFrom: z.array(z.union([z.string(), z.number()])).optional(), systemPrompt: z.string().optional() }).strict(); const TelegramGroupSchema = z.object({ requireMention: z.boolean().optional(), groupPolicy: GroupPolicySchema.optional(), tools: ToolPolicySchema, toolsBySender: ToolPolicyBySenderSchema$1, skills: z.array(z.string()).optional(), enabled: z.boolean().optional(), allowFrom: z.array(z.union([z.string(), z.number()])).optional(), systemPrompt: z.string().optional(), topics: z.record(z.string(), TelegramTopicSchema.optional()).optional() }).strict(); const TelegramCustomCommandSchema = z.object({ command: z.string().transform(normalizeTelegramCommandName), description: z.string().transform(normalizeTelegramCommandDescription) }).strict(); const validateTelegramCustomCommands = (value, ctx) => { if (!value.customCommands || value.customCommands.length === 0) return; const { issues } = resolveTelegramCustomCommands({ commands: value.customCommands, checkReserved: false, checkDuplicates: false }); for (const issue of issues) ctx.addIssue({ code: z.ZodIssueCode.custom, path: [ "customCommands", issue.index, issue.field ], message: issue.message }); }; const TelegramAccountSchemaBase = z.object({ name: z.string().optional(), capabilities: TelegramCapabilitiesSchema.optional(), markdown: MarkdownConfigSchema, enabled: z.boolean().optional(), commands: ProviderCommandsSchema, customCommands: z.array(TelegramCustomCommandSchema).optional(), configWrites: z.boolean().optional(), dmPolicy: DmPolicySchema.optional().default("pairing"), botToken: z.string().optional(), tokenFile: z.string().optional(), replyToMode: ReplyToModeSchema.optional(), groups: z.record(z.string(), TelegramGroupSchema.optional()).optional(), allowFrom: z.array(z.union([z.string(), z.number()])).optional(), groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(), groupPolicy: GroupPolicySchema.optional().default("allowlist"), historyLimit: z.number().int().min(0).optional(), dmHistoryLimit: z.number().int().min(0).optional(), dms: z.record(z.string(), DmConfigSchema.optional()).optional(), textChunkLimit: z.number().int().positive().optional(), chunkMode: z.enum(["length", "newline"]).optional(), blockStreaming: z.boolean().optional(), draftChunk: BlockStreamingChunkSchema.optional(), blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(), streamMode: z.enum([ "off", "partial", "block" ]).optional().default("partial"), mediaMaxMb: z.number().positive().optional(), timeoutSeconds: z.number().int().positive().optional(), retry: RetryConfigSchema, network: z.object({ autoSelectFamily: z.boolean().optional() }).strict().optional(), proxy: z.string().optional(), webhookUrl: z.string().optional(), webhookSecret: z.string().optional(), webhookPath: z.string().optional(), actions: z.object({ reactions: z.boolean().optional(), sendMessage: z.boolean().optional(), deleteMessage: z.boolean().optional(), sticker: z.boolean().optional() }).strict().optional(), reactionNotifications: z.enum([ "off", "own", "all" ]).optional(), reactionLevel: z.enum([ "off", "ack", "minimal", "extensive" ]).optional(), heartbeat: ChannelHeartbeatVisibilitySchema, linkPreview: z.boolean().optional(), responsePrefix: z.string().optional() }).strict(); const TelegramAccountSchema = TelegramAccountSchemaBase.superRefine((value, ctx) => { requireOpenAllowFrom({ policy: value.dmPolicy, allowFrom: value.allowFrom, ctx, path: ["allowFrom"], message: "channels.telegram.dmPolicy=\"open\" requires channels.telegram.allowFrom to include \"*\"" }); validateTelegramCustomCommands(value, ctx); }); const TelegramConfigSchema = TelegramAccountSchemaBase.extend({ accounts: z.record(z.string(), TelegramAccountSchema.optional()).optional() }).superRefine((value, ctx) => { requireOpenAllowFrom({ policy: value.dmPolicy, allowFrom: value.allowFrom, ctx, path: ["allowFrom"], message: "channels.telegram.dmPolicy=\"open\" requires channels.telegram.allowFrom to include \"*\"" }); validateTelegramCustomCommands(value, ctx); const baseWebhookUrl = typeof value.webhookUrl === "string" ? value.webhookUrl.trim() : ""; const baseWebhookSecret = typeof value.webhookSecret === "string" ? value.webhookSecret.trim() : ""; if (baseWebhookUrl && !baseWebhookSecret) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "channels.telegram.webhookUrl requires channels.telegram.webhookSecret", path: ["webhookSecret"] }); if (!value.accounts) return; for (const [accountId, account] of Object.entries(value.accounts)) { if (!account) continue; if (account.enabled === false) continue; if (!(typeof account.webhookUrl === "string" ? account.webhookUrl.trim() : "")) continue; if (!(typeof account.webhookSecret === "string" ? account.webhookSecret.trim() : "") && !baseWebhookSecret) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "channels.telegram.accounts.*.webhookUrl requires channels.telegram.webhookSecret or channels.telegram.accounts.*.webhookSecret", path: [ "accounts", accountId, "webhookSecret" ] }); } }); const DiscordDmSchema = z.object({ enabled: z.boolean().optional(), policy: DmPolicySchema.optional().default("pairing"), allowFrom: z.array(z.union([z.string(), z.number()])).optional(), groupEnabled: z.boolean().optional(), groupChannels: z.array(z.union([z.string(), z.number()])).optional() }).strict().superRefine((value, ctx) => { requireOpenAllowFrom({ policy: value.policy, allowFrom: value.allowFrom, ctx, path: ["allowFrom"], message: "channels.discord.dm.policy=\"open\" requires channels.discord.dm.allowFrom to include \"*\"" }); }); const DiscordGuildChannelSchema = z.object({ allow: z.boolean().optional(), requireMention: z.boolean().optional(), tools: ToolPolicySchema, toolsBySender: ToolPolicyBySenderSchema$1, skills: z.array(z.string()).optional(), enabled: z.boolean().optional(), users: z.array(z.union([z.string(), z.number()])).optional(), systemPrompt: z.string().optional(), includeThreadStarter: z.boolean().optional(), autoThread: z.boolean().optional() }).strict(); const DiscordGuildSchema = z.object({ slug: z.string().optional(), requireMention: z.boolean().optional(), tools: ToolPolicySchema, toolsBySender: ToolPolicyBySenderSchema$1, reactionNotifications: z.enum([ "off", "own", "all", "allowlist" ]).optional(), users: z.array(z.union([z.string(), z.number()])).optional(), channels: z.record(z.string(), DiscordGuildChannelSchema.optional()).optional() }).strict(); const DiscordAccountSchema = z.object({ name: z.string().optional(), capabilities: z.array(z.string()).optional(), markdown: MarkdownConfigSchema, enabled: z.boolean().optional(), commands: ProviderCommandsSchema, configWrites: z.boolean().optional(), token: z.string().optional(), allowBots: z.boolean().optional(), groupPolicy: GroupPolicySchema.optional().default("allowlist"), historyLimit: z.number().int().min(0).optional(), dmHistoryLimit: z.number().int().min(0).optional(), dms: z.record(z.string(), DmConfigSchema.optional()).optional(), textChunkLimit: z.number().int().positive().optional(), chunkMode: z.enum(["length", "newline"]).optional(), blockStreaming: z.boolean().optional(), blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(), maxLinesPerMessage: z.number().int().positive().optional(), mediaMaxMb: z.number().positive().optional(), retry: RetryConfigSchema, actions: z.object({ reactions: z.boolean().optional(), stickers: z.boolean().optional(), emojiUploads: z.boolean().optional(), stickerUploads: z.boolean().optional(), polls: z.boolean().optional(), permissions: z.boolean().optional(), messages: z.boolean().optional(), threads: z.boolean().optional(), pins: z.boolean().optional(), search: z.boolean().optional(), memberInfo: z.bool