fix: add per-channel markdown table conversion (#1495) (thanks @odysseus0)
This commit is contained in:
60
src/config/markdown-tables.ts
Normal file
60
src/config/markdown-tables.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { normalizeChannelId } from "../channels/plugins/index.js";
|
||||
import { normalizeAccountId } from "../routing/session-key.js";
|
||||
import type { ClawdbotConfig } from "./config.js";
|
||||
import type { MarkdownTableMode } from "./types.base.js";
|
||||
|
||||
type MarkdownConfigEntry = {
|
||||
markdown?: {
|
||||
tables?: MarkdownTableMode;
|
||||
};
|
||||
};
|
||||
|
||||
type MarkdownConfigSection = MarkdownConfigEntry & {
|
||||
accounts?: Record<string, MarkdownConfigEntry>;
|
||||
};
|
||||
|
||||
const DEFAULT_TABLE_MODES = new Map<string, MarkdownTableMode>([
|
||||
["signal", "bullets"],
|
||||
["whatsapp", "bullets"],
|
||||
]);
|
||||
|
||||
const isMarkdownTableMode = (value: unknown): value is MarkdownTableMode =>
|
||||
value === "off" || value === "bullets" || value === "code";
|
||||
|
||||
function resolveMarkdownModeFromSection(
|
||||
section: MarkdownConfigSection | undefined,
|
||||
accountId?: string | null,
|
||||
): MarkdownTableMode | undefined {
|
||||
if (!section) return undefined;
|
||||
const normalizedAccountId = normalizeAccountId(accountId);
|
||||
const accounts = section.accounts;
|
||||
if (accounts && typeof accounts === "object") {
|
||||
const direct = accounts[normalizedAccountId];
|
||||
const directMode = direct?.markdown?.tables;
|
||||
if (isMarkdownTableMode(directMode)) return directMode;
|
||||
const matchKey = Object.keys(accounts).find(
|
||||
(key) => key.toLowerCase() === normalizedAccountId.toLowerCase(),
|
||||
);
|
||||
const match = matchKey ? accounts[matchKey] : undefined;
|
||||
const matchMode = match?.markdown?.tables;
|
||||
if (isMarkdownTableMode(matchMode)) return matchMode;
|
||||
}
|
||||
const sectionMode = section.markdown?.tables;
|
||||
return isMarkdownTableMode(sectionMode) ? sectionMode : undefined;
|
||||
}
|
||||
|
||||
export function resolveMarkdownTableMode(params: {
|
||||
cfg?: Partial<ClawdbotConfig>;
|
||||
channel?: string | null;
|
||||
accountId?: string | null;
|
||||
}): MarkdownTableMode {
|
||||
const channel = normalizeChannelId(params.channel);
|
||||
const defaultMode = channel ? (DEFAULT_TABLE_MODES.get(channel) ?? "code") : "code";
|
||||
if (!channel || !params.cfg) return defaultMode;
|
||||
const channelsConfig = params.cfg.channels as Record<string, unknown> | undefined;
|
||||
const section = (channelsConfig?.[channel] ??
|
||||
(params.cfg as Record<string, unknown> | undefined)?.[channel]) as
|
||||
| MarkdownConfigSection
|
||||
| undefined;
|
||||
return resolveMarkdownModeFromSection(section, params.accountId) ?? defaultMode;
|
||||
}
|
||||
@@ -31,6 +31,13 @@ export type BlockStreamingChunkConfig = {
|
||||
breakPreference?: "paragraph" | "newline" | "sentence";
|
||||
};
|
||||
|
||||
export type MarkdownTableMode = "off" | "bullets" | "code";
|
||||
|
||||
export type MarkdownConfig = {
|
||||
/** Table rendering mode (off|bullets|code). */
|
||||
tables?: MarkdownTableMode;
|
||||
};
|
||||
|
||||
export type HumanDelayConfig = {
|
||||
/** Delay style for block replies (off|natural|custom). */
|
||||
mode?: "off" | "natural" | "custom";
|
||||
|
||||
@@ -2,6 +2,7 @@ import type {
|
||||
BlockStreamingCoalesceConfig,
|
||||
DmPolicy,
|
||||
GroupPolicy,
|
||||
MarkdownConfig,
|
||||
OutboundRetryConfig,
|
||||
ReplyToMode,
|
||||
} from "./types.base.js";
|
||||
@@ -70,6 +71,8 @@ export type DiscordAccountConfig = {
|
||||
name?: string;
|
||||
/** Optional provider capability tags used for agent/runtime guidance. */
|
||||
capabilities?: string[];
|
||||
/** Markdown formatting overrides (tables). */
|
||||
markdown?: MarkdownConfig;
|
||||
/** Override native command registration for Discord (bool or "auto"). */
|
||||
commands?: ProviderCommandsConfig;
|
||||
/** Allow channel-initiated config writes (default: true). */
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import type { BlockStreamingCoalesceConfig, DmPolicy, GroupPolicy } from "./types.base.js";
|
||||
import type {
|
||||
BlockStreamingCoalesceConfig,
|
||||
DmPolicy,
|
||||
GroupPolicy,
|
||||
MarkdownConfig,
|
||||
} from "./types.base.js";
|
||||
import type { DmConfig } from "./types.messages.js";
|
||||
|
||||
export type IMessageAccountConfig = {
|
||||
@@ -6,6 +11,8 @@ export type IMessageAccountConfig = {
|
||||
name?: string;
|
||||
/** Optional provider capability tags used for agent/runtime guidance. */
|
||||
capabilities?: string[];
|
||||
/** Markdown formatting overrides (tables). */
|
||||
markdown?: MarkdownConfig;
|
||||
/** Allow channel-initiated config writes (default: true). */
|
||||
configWrites?: boolean;
|
||||
/** If false, do not start this iMessage account. Default: true. */
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import type { BlockStreamingCoalesceConfig, DmPolicy, GroupPolicy } from "./types.base.js";
|
||||
import type {
|
||||
BlockStreamingCoalesceConfig,
|
||||
DmPolicy,
|
||||
GroupPolicy,
|
||||
MarkdownConfig,
|
||||
} from "./types.base.js";
|
||||
import type { DmConfig } from "./types.messages.js";
|
||||
|
||||
export type MSTeamsWebhookConfig = {
|
||||
@@ -34,6 +39,8 @@ export type MSTeamsConfig = {
|
||||
enabled?: boolean;
|
||||
/** Optional provider capability tags used for agent/runtime guidance. */
|
||||
capabilities?: string[];
|
||||
/** Markdown formatting overrides (tables). */
|
||||
markdown?: MarkdownConfig;
|
||||
/** Allow channel-initiated config writes (default: true). */
|
||||
configWrites?: boolean;
|
||||
/** Azure Bot App ID (from Azure Bot registration). */
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import type { BlockStreamingCoalesceConfig, DmPolicy, GroupPolicy } from "./types.base.js";
|
||||
import type {
|
||||
BlockStreamingCoalesceConfig,
|
||||
DmPolicy,
|
||||
GroupPolicy,
|
||||
MarkdownConfig,
|
||||
} from "./types.base.js";
|
||||
import type { DmConfig } from "./types.messages.js";
|
||||
|
||||
export type SignalReactionNotificationMode = "off" | "own" | "all" | "allowlist";
|
||||
@@ -8,6 +13,8 @@ export type SignalAccountConfig = {
|
||||
name?: string;
|
||||
/** Optional provider capability tags used for agent/runtime guidance. */
|
||||
capabilities?: string[];
|
||||
/** Markdown formatting overrides (tables). */
|
||||
markdown?: MarkdownConfig;
|
||||
/** Allow channel-initiated config writes (default: true). */
|
||||
configWrites?: boolean;
|
||||
/** If false, do not start this Signal account. Default: true. */
|
||||
|
||||
@@ -2,6 +2,7 @@ import type {
|
||||
BlockStreamingCoalesceConfig,
|
||||
DmPolicy,
|
||||
GroupPolicy,
|
||||
MarkdownConfig,
|
||||
ReplyToMode,
|
||||
} from "./types.base.js";
|
||||
import type { DmConfig, ProviderCommandsConfig } from "./types.messages.js";
|
||||
@@ -80,6 +81,8 @@ export type SlackAccountConfig = {
|
||||
webhookPath?: string;
|
||||
/** Optional provider capability tags used for agent/runtime guidance. */
|
||||
capabilities?: string[];
|
||||
/** Markdown formatting overrides (tables). */
|
||||
markdown?: MarkdownConfig;
|
||||
/** Override native command registration for Slack (bool or "auto"). */
|
||||
commands?: ProviderCommandsConfig;
|
||||
/** Allow channel-initiated config writes (default: true). */
|
||||
|
||||
@@ -3,6 +3,7 @@ import type {
|
||||
BlockStreamingCoalesceConfig,
|
||||
DmPolicy,
|
||||
GroupPolicy,
|
||||
MarkdownConfig,
|
||||
OutboundRetryConfig,
|
||||
ReplyToMode,
|
||||
} from "./types.base.js";
|
||||
@@ -35,6 +36,8 @@ export type TelegramAccountConfig = {
|
||||
name?: string;
|
||||
/** Optional provider capability tags used for agent/runtime guidance. */
|
||||
capabilities?: TelegramCapabilitiesConfig;
|
||||
/** Markdown formatting overrides (tables). */
|
||||
markdown?: MarkdownConfig;
|
||||
/** Override native command registration for Telegram (bool or "auto"). */
|
||||
commands?: ProviderCommandsConfig;
|
||||
/** Custom commands to register in Telegram's command menu (merged with native). */
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import type { BlockStreamingCoalesceConfig, DmPolicy, GroupPolicy } from "./types.base.js";
|
||||
import type {
|
||||
BlockStreamingCoalesceConfig,
|
||||
DmPolicy,
|
||||
GroupPolicy,
|
||||
MarkdownConfig,
|
||||
} from "./types.base.js";
|
||||
import type { DmConfig } from "./types.messages.js";
|
||||
|
||||
export type WhatsAppActionConfig = {
|
||||
@@ -12,6 +17,8 @@ export type WhatsAppConfig = {
|
||||
accounts?: Record<string, WhatsAppAccountConfig>;
|
||||
/** Optional provider capability tags used for agent/runtime guidance. */
|
||||
capabilities?: string[];
|
||||
/** Markdown formatting overrides (tables). */
|
||||
markdown?: MarkdownConfig;
|
||||
/** Allow channel-initiated config writes (default: true). */
|
||||
configWrites?: boolean;
|
||||
/** Send read receipts for incoming messages (default true). */
|
||||
@@ -84,6 +91,8 @@ export type WhatsAppAccountConfig = {
|
||||
name?: string;
|
||||
/** Optional provider capability tags used for agent/runtime guidance. */
|
||||
capabilities?: string[];
|
||||
/** Markdown formatting overrides (tables). */
|
||||
markdown?: MarkdownConfig;
|
||||
/** Allow channel-initiated config writes (default: true). */
|
||||
configWrites?: boolean;
|
||||
/** If false, do not start this WhatsApp account provider. Default: true. */
|
||||
|
||||
@@ -133,6 +133,15 @@ export const BlockStreamingChunkSchema = z
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const MarkdownTableModeSchema = z.enum(["off", "bullets", "code"]);
|
||||
|
||||
export const MarkdownConfigSchema = z
|
||||
.object({
|
||||
tables: MarkdownTableModeSchema.optional(),
|
||||
})
|
||||
.strict()
|
||||
.optional();
|
||||
|
||||
export const HumanDelaySchema = z
|
||||
.object({
|
||||
mode: z.union([z.literal("off"), z.literal("natural"), z.literal("custom")]).optional(),
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
DmPolicySchema,
|
||||
ExecutableTokenSchema,
|
||||
GroupPolicySchema,
|
||||
MarkdownConfigSchema,
|
||||
MSTeamsReplyStyleSchema,
|
||||
ProviderCommandsSchema,
|
||||
ReplyToModeSchema,
|
||||
@@ -81,6 +82,7 @@ export const TelegramAccountSchemaBase = z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
capabilities: TelegramCapabilitiesSchema.optional(),
|
||||
markdown: MarkdownConfigSchema,
|
||||
enabled: z.boolean().optional(),
|
||||
commands: ProviderCommandsSchema,
|
||||
customCommands: z.array(TelegramCustomCommandSchema).optional(),
|
||||
@@ -193,6 +195,7 @@ export 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(),
|
||||
@@ -296,6 +299,7 @@ export const SlackAccountSchema = z
|
||||
signingSecret: z.string().optional(),
|
||||
webhookPath: z.string().optional(),
|
||||
capabilities: z.array(z.string()).optional(),
|
||||
markdown: MarkdownConfigSchema,
|
||||
enabled: z.boolean().optional(),
|
||||
commands: ProviderCommandsSchema,
|
||||
configWrites: z.boolean().optional(),
|
||||
@@ -381,6 +385,7 @@ export const SignalAccountSchemaBase = z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
capabilities: z.array(z.string()).optional(),
|
||||
markdown: MarkdownConfigSchema,
|
||||
enabled: z.boolean().optional(),
|
||||
configWrites: z.boolean().optional(),
|
||||
account: z.string().optional(),
|
||||
@@ -435,6 +440,7 @@ export const IMessageAccountSchemaBase = z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
capabilities: z.array(z.string()).optional(),
|
||||
markdown: MarkdownConfigSchema,
|
||||
enabled: z.boolean().optional(),
|
||||
configWrites: z.boolean().optional(),
|
||||
cliPath: ExecutableTokenSchema.optional(),
|
||||
@@ -521,6 +527,7 @@ export const BlueBubblesAccountSchemaBase = z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
capabilities: z.array(z.string()).optional(),
|
||||
markdown: MarkdownConfigSchema,
|
||||
configWrites: z.boolean().optional(),
|
||||
enabled: z.boolean().optional(),
|
||||
serverUrl: z.string().optional(),
|
||||
@@ -585,6 +592,7 @@ export const MSTeamsConfigSchema = z
|
||||
.object({
|
||||
enabled: z.boolean().optional(),
|
||||
capabilities: z.array(z.string()).optional(),
|
||||
markdown: MarkdownConfigSchema,
|
||||
configWrites: z.boolean().optional(),
|
||||
appId: z.string().optional(),
|
||||
appPassword: z.string().optional(),
|
||||
|
||||
@@ -5,12 +5,14 @@ import {
|
||||
DmConfigSchema,
|
||||
DmPolicySchema,
|
||||
GroupPolicySchema,
|
||||
MarkdownConfigSchema,
|
||||
} from "./zod-schema.core.js";
|
||||
|
||||
export const WhatsAppAccountSchema = z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
capabilities: z.array(z.string()).optional(),
|
||||
markdown: MarkdownConfigSchema,
|
||||
configWrites: z.boolean().optional(),
|
||||
enabled: z.boolean().optional(),
|
||||
sendReadReceipts: z.boolean().optional(),
|
||||
@@ -66,6 +68,7 @@ export const WhatsAppConfigSchema = z
|
||||
.object({
|
||||
accounts: z.record(z.string(), WhatsAppAccountSchema.optional()).optional(),
|
||||
capabilities: z.array(z.string()).optional(),
|
||||
markdown: MarkdownConfigSchema,
|
||||
configWrites: z.boolean().optional(),
|
||||
sendReadReceipts: z.boolean().optional(),
|
||||
dmPolicy: DmPolicySchema.optional().default("pairing"),
|
||||
|
||||
Reference in New Issue
Block a user