import { z } from "zod"; import { isSafeExecutableValue } from "../infra/exec-safety.js"; export 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"), ]); export 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(); export const ModelDefinitionSchema = z .object({ id: z.string().min(1), name: z.string().min(1), api: ModelApiSchema.optional(), reasoning: z.boolean(), input: z.array(z.union([z.literal("text"), z.literal("image")])), cost: z .object({ input: z.number(), output: z.number(), cacheRead: z.number(), cacheWrite: z.number(), }) .strict(), contextWindow: z.number().positive(), maxTokens: z.number().positive(), headers: z.record(z.string(), z.string()).optional(), compat: ModelCompatSchema, }) .strict(); export const ModelProviderSchema = z .object({ baseUrl: z.string().min(1), apiKey: z.string().optional(), api: ModelApiSchema.optional(), headers: z.record(z.string(), z.string()).optional(), authHeader: z.boolean().optional(), models: z.array(ModelDefinitionSchema), }) .strict(); export const ModelsConfigSchema = z .object({ mode: z.union([z.literal("merge"), z.literal("replace")]).optional(), providers: z.record(z.string(), ModelProviderSchema).optional(), }) .strict() .optional(); export const GroupChatSchema = z .object({ mentionPatterns: z.array(z.string()).optional(), historyLimit: z.number().int().positive().optional(), }) .strict() .optional(); export const DmConfigSchema = z .object({ historyLimit: z.number().int().min(0).optional(), }) .strict(); export const IdentitySchema = z .object({ name: z.string().optional(), theme: z.string().optional(), emoji: z.string().optional(), }) .strict() .optional(); export 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"), ]); export const QueueDropSchema = z.union([ z.literal("old"), z.literal("new"), z.literal("summarize"), ]); export const ReplyToModeSchema = z.union([z.literal("off"), z.literal("first"), z.literal("all")]); // GroupPolicySchema: controls how group messages are handled // Used with .default("allowlist").optional() pattern: // - .optional() allows field omission in input config // - .default("allowlist") ensures runtime always resolves to "allowlist" if not provided export const GroupPolicySchema = z.enum(["open", "disabled", "allowlist"]); export const DmPolicySchema = z.enum(["pairing", "allowlist", "open", "disabled"]); export const BlockStreamingCoalesceSchema = z .object({ minChars: z.number().int().positive().optional(), maxChars: z.number().int().positive().optional(), idleMs: z.number().int().nonnegative().optional(), }) .strict(); export 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(); export 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(); export 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(); export const normalizeAllowFrom = (values?: Array): string[] => (values ?? []).map((v) => String(v).trim()).filter(Boolean); export const requireOpenAllowFrom = (params: { policy?: string; allowFrom?: Array; ctx: z.RefinementCtx; path: Array; message: string; }) => { if (params.policy !== "open") return; const allow = normalizeAllowFrom(params.allowFrom); if (allow.includes("*")) return; params.ctx.addIssue({ code: z.ZodIssueCode.custom, path: params.path, message: params.message, }); }; export const MSTeamsReplyStyleSchema = z.enum(["thread", "top-level"]); export 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(); export const QueueModeBySurfaceSchema = z .object({ whatsapp: QueueModeSchema.optional(), telegram: QueueModeSchema.optional(), discord: QueueModeSchema.optional(), slack: QueueModeSchema.optional(), signal: QueueModeSchema.optional(), imessage: QueueModeSchema.optional(), msteams: QueueModeSchema.optional(), webchat: QueueModeSchema.optional(), }) .strict() .optional(); export const DebounceMsBySurfaceSchema = z .object({ whatsapp: z.number().int().nonnegative().optional(), telegram: z.number().int().nonnegative().optional(), discord: z.number().int().nonnegative().optional(), slack: z.number().int().nonnegative().optional(), signal: z.number().int().nonnegative().optional(), imessage: z.number().int().nonnegative().optional(), msteams: z.number().int().nonnegative().optional(), webchat: z.number().int().nonnegative().optional(), }) .strict() .optional(); export const QueueSchema = z .object({ mode: QueueModeSchema.optional(), byChannel: QueueModeBySurfaceSchema, debounceMs: z.number().int().nonnegative().optional(), cap: z.number().int().positive().optional(), drop: QueueDropSchema.optional(), }) .strict() .optional(); export const InboundDebounceSchema = z .object({ debounceMs: z.number().int().nonnegative().optional(), byChannel: DebounceMsBySurfaceSchema, }) .strict() .optional(); export 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(); export const HexColorSchema = z.string().regex(/^#?[0-9a-fA-F]{6}$/, "expected hex color (RRGGBB)"); export const ExecutableTokenSchema = z .string() .refine(isSafeExecutableValue, "expected safe executable name or path"); export 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(); export const MediaUnderstandingCapabilitiesSchema = z .array(z.union([z.literal("image"), z.literal("audio"), z.literal("video")])) .optional(); export 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(); export 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(); export 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(); export 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(); export const NativeCommandsSettingSchema = z.union([z.boolean(), z.literal("auto")]); export const ProviderCommandsSchema = z .object({ native: NativeCommandsSettingSchema.optional(), nativeSkills: NativeCommandsSettingSchema.optional(), }) .strict() .optional();