All files / dingtalk-openclaw-connector/src/config schema.ts

0% Statements 0/101
0% Branches 0/1
0% Functions 0/1
0% Lines 0/101

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130                                                                                                                                                                                                                                                                   
import { normalizeAccountId } from "../sdk/helpers.ts";
import { z } from "zod";
export { z };
import { buildSecretInputSchema, hasConfiguredSecretInput } from "../secret-input.ts";
 
const DmPolicySchema = z.enum(["open", "pairing", "allowlist"]);
const GroupPolicySchema = z.enum(["open", "allowlist", "disabled"]);
 
const ToolPolicySchema = z
  .object({
    allow: z.array(z.string()).optional(),
    deny: z.array(z.string()).optional(),
  })
  .strict()
  .optional();
 
/**
 * Group session scope for routing DingTalk group messages.
 * - "group" (default): one session per group chat
 * - "group_sender": one session per (group + sender)
 */
const GroupSessionScopeSchema = z
  .enum(["group", "group_sender"])
  .optional();
 
/**
 * Dingtalk tools configuration.
 * Controls which tool categories are enabled.
 */
const DingtalkToolsConfigSchema = z
  .object({
    docs: z.boolean().optional(), // Document operations (default: true)
    media: z.boolean().optional(), // Media upload operations (default: true)
  })
  .strict()
  .optional();
 
export const DingtalkGroupSchema = z
  .object({
    requireMention: z.boolean().optional(),
    tools: ToolPolicySchema,
    enabled: z.boolean().optional(),
    allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
    systemPrompt: z.string().optional(),
    groupSessionScope: GroupSessionScopeSchema,
  })
  .strict();
 
const DingtalkSharedConfigShape = {
  dmPolicy: DmPolicySchema.optional(),
  allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
  groupPolicy: GroupPolicySchema.optional(),
  groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
  requireMention: z.boolean().optional(),
  groups: z.record(z.string(), DingtalkGroupSchema.optional()).optional(),
  historyLimit: z.number().int().min(0).optional(),
  dmHistoryLimit: z.number().int().min(0).optional(),
  textChunkLimit: z.number().int().positive().optional(),
  mediaMaxMb: z.number().positive().optional(),
  tools: DingtalkToolsConfigSchema,
  typingIndicator: z.boolean().optional(),
  resolveSenderNames: z.boolean().optional(),
  separateSessionByConversation: z.boolean().optional(),
  groupSessionScope: GroupSessionScopeSchema,
  asyncMode: z.boolean().optional(),
  ackText: z.string().optional(),
  endpoint: z.string().optional(), // DWClient gateway endpoint
  debug: z.boolean().optional(), // DWClient debug mode
};
 
/**
 * Per-account configuration.
 * All fields are optional - missing fields inherit from top-level config.
 */
export const DingtalkAccountConfigSchema = z
  .object({
    enabled: z.boolean().optional(),
    name: z.string().optional(), // Display name for this account
    clientId: z.union([z.string(), z.number()]).optional(),
    clientSecret: buildSecretInputSchema().optional(),
    ...DingtalkSharedConfigShape,
  })
  .strict();
 
export const DingtalkConfigSchema = z
  .object({
    enabled: z.boolean().optional(),
    defaultAccount: z.string().optional(),
    // Top-level credentials (backward compatible for single-account mode)
    clientId: z.union([z.string(), z.number()]).optional(),
    clientSecret: buildSecretInputSchema().optional(),
    enableMediaUpload: z.boolean().optional(),
    systemPrompt: z.string().optional(),
    ...DingtalkSharedConfigShape,
    dmPolicy: DmPolicySchema.optional().default("open"),
    groupPolicy: GroupPolicySchema.optional().default("open"),
    requireMention: z.boolean().optional().default(true),
    separateSessionByConversation: z.boolean().optional().default(true),
    groupSessionScope: GroupSessionScopeSchema.optional().default("group"),
    // Multi-account configuration
    accounts: z.record(z.string(), DingtalkAccountConfigSchema.optional()).optional(),
  })
  .strict()
  .superRefine((value, ctx) => {
    const defaultAccount = value.defaultAccount?.trim();
    if (defaultAccount && value.accounts && Object.keys(value.accounts).length > 0) {
      const normalizedDefaultAccount = normalizeAccountId(defaultAccount);
      if (!Object.prototype.hasOwnProperty.call(value.accounts, normalizedDefaultAccount)) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          path: ["defaultAccount"],
          message: `channels.dingtalk-connector.defaultAccount="${defaultAccount}" does not match a configured account key`,
        });
      }
    }
 
    // Validate dmPolicy and allowFrom consistency
    if (value.dmPolicy === "allowlist") {
      const allowFrom = value.allowFrom ?? [];
      if (allowFrom.length === 0) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          path: ["allowFrom"],
          message:
            'channels.dingtalk-connector.dmPolicy="allowlist" requires channels.dingtalk-connector.allowFrom to contain at least one entry',
        });
      }
    }
  });