Plugin SDK: add legacy message discovery helper

This commit is contained in:
Gustavo Madeira Santana 2026-03-18 02:06:29 +00:00
parent d3fc6c0cc7
commit 4c36436fb4
No known key found for this signature in database
5 changed files with 152 additions and 109 deletions

View File

@ -1,8 +1,14 @@
import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from";
import { mapAllowFromEntries } from "openclaw/plugin-sdk/channel-config-helpers";
import { collectAllowlistProviderRestrictSendersWarnings } from "openclaw/plugin-sdk/channel-policy";
import { createMessageToolCardSchema } from "openclaw/plugin-sdk/channel-runtime";
import type { ChannelMessageActionAdapter } from "openclaw/plugin-sdk/channel-runtime";
import {
createLegacyMessageToolDiscoveryMethods,
createMessageToolCardSchema,
} from "openclaw/plugin-sdk/channel-runtime";
import type {
ChannelMessageActionAdapter,
ChannelMessageToolDiscovery,
} from "openclaw/plugin-sdk/channel-runtime";
import type { ChannelMeta, ChannelPlugin, ClawdbotConfig } from "openclaw/plugin-sdk/feishu";
import {
buildChannelConfigSchema,
@ -49,6 +55,56 @@ const loadFeishuChannelRuntime = createLazyRuntimeNamedExport(
"feishuChannelRuntime",
);
function describeFeishuMessageTool({
cfg,
}: Parameters<
NonNullable<ChannelMessageActionAdapter["describeMessageTool"]>
>[0]): ChannelMessageToolDiscovery {
const enabled =
cfg.channels?.feishu?.enabled !== false &&
Boolean(resolveFeishuCredentials(cfg.channels?.feishu as FeishuConfig | undefined));
if (listEnabledFeishuAccounts(cfg).length === 0) {
return {
actions: [],
capabilities: enabled ? ["cards"] : [],
schema: enabled
? {
properties: {
card: createMessageToolCardSchema(),
},
}
: null,
};
}
const actions = new Set<ChannelMessageActionName>([
"send",
"read",
"edit",
"thread-reply",
"pin",
"list-pins",
"unpin",
"member-info",
"channel-info",
"channel-list",
]);
if (areAnyFeishuReactionActionsEnabled(cfg)) {
actions.add("react");
actions.add("reactions");
}
return {
actions: Array.from(actions),
capabilities: enabled ? ["cards"] : [],
schema: enabled
? {
properties: {
card: createMessageToolCardSchema(),
},
}
: null,
};
}
function setFeishuNamedAccountEnabled(
cfg: ClawdbotConfig,
accountId: string,
@ -396,53 +452,8 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
formatAllowFrom: ({ allowFrom }) => formatAllowFromLowercase({ allowFrom }),
},
actions: {
describeMessageTool: ({
cfg,
}: Parameters<NonNullable<ChannelMessageActionAdapter["describeMessageTool"]>>[0]) => {
const enabled =
cfg.channels?.feishu?.enabled !== false &&
Boolean(resolveFeishuCredentials(cfg.channels?.feishu as FeishuConfig | undefined));
if (listEnabledFeishuAccounts(cfg).length === 0) {
return {
actions: [],
capabilities: enabled ? ["cards"] : [],
schema: enabled
? {
properties: {
card: createMessageToolCardSchema(),
},
}
: null,
};
}
const actions = new Set<ChannelMessageActionName>([
"send",
"read",
"edit",
"thread-reply",
"pin",
"list-pins",
"unpin",
"member-info",
"channel-info",
"channel-list",
]);
if (areAnyFeishuReactionActionsEnabled(cfg)) {
actions.add("react");
actions.add("reactions");
}
return {
actions: Array.from(actions),
capabilities: enabled ? ["cards"] : [],
schema: enabled
? {
properties: {
card: createMessageToolCardSchema(),
},
}
: null,
};
},
describeMessageTool: describeFeishuMessageTool,
...createLegacyMessageToolDiscoveryMethods(describeFeishuMessageTool),
handleAction: async (ctx) => {
const account = resolveFeishuAccount({ cfg: ctx.cfg, accountId: ctx.accountId ?? undefined });
if (

View File

@ -4,7 +4,11 @@ import {
buildAccountScopedDmSecurityPolicy,
collectAllowlistProviderRestrictSendersWarnings,
} from "openclaw/plugin-sdk/channel-policy";
import { createMessageToolButtonsSchema } from "openclaw/plugin-sdk/channel-runtime";
import {
createLegacyMessageToolDiscoveryMethods,
createMessageToolButtonsSchema,
} from "openclaw/plugin-sdk/channel-runtime";
import type { ChannelMessageToolDiscovery } from "openclaw/plugin-sdk/channel-runtime";
import {
buildComputedAccountStatusSnapshot,
buildChannelConfigSchema,
@ -42,46 +46,49 @@ import { getMattermostRuntime } from "./runtime.js";
import { mattermostSetupAdapter } from "./setup-core.js";
import { mattermostSetupWizard } from "./setup-surface.js";
function describeMattermostMessageTool({
cfg,
}: Parameters<
NonNullable<ChannelMessageActionAdapter["describeMessageTool"]>
>[0]): ChannelMessageToolDiscovery {
const enabledAccounts = listMattermostAccountIds(cfg)
.map((accountId) => resolveMattermostAccount({ cfg, accountId }))
.filter((account) => account.enabled)
.filter((account) => Boolean(account.botToken?.trim() && account.baseUrl?.trim()));
const actions: ChannelMessageActionName[] = [];
if (enabledAccounts.length > 0) {
actions.push("send");
}
const actionsConfig = cfg.channels?.mattermost?.actions as { reactions?: boolean } | undefined;
const baseReactions = actionsConfig?.reactions;
const hasReactionCapableAccount = enabledAccounts.some((account) => {
const accountActions = account.config.actions as { reactions?: boolean } | undefined;
return (accountActions?.reactions ?? baseReactions ?? true) !== false;
});
if (hasReactionCapableAccount) {
actions.push("react");
}
return {
actions,
capabilities: enabledAccounts.length > 0 ? ["buttons"] : [],
schema:
enabledAccounts.length > 0
? {
properties: {
buttons: createMessageToolButtonsSchema(),
},
}
: null,
};
}
const mattermostMessageActions: ChannelMessageActionAdapter = {
describeMessageTool: ({
cfg,
}: Parameters<NonNullable<ChannelMessageActionAdapter["describeMessageTool"]>>[0]) => {
const enabledAccounts = listMattermostAccountIds(cfg)
.map((accountId) => resolveMattermostAccount({ cfg, accountId }))
.filter((account) => account.enabled)
.filter((account) => Boolean(account.botToken?.trim() && account.baseUrl?.trim()));
const actions: ChannelMessageActionName[] = [];
// Send (buttons) is available whenever there's at least one enabled account
if (enabledAccounts.length > 0) {
actions.push("send");
}
// React requires per-account reactions config check
const actionsConfig = cfg.channels?.mattermost?.actions as { reactions?: boolean } | undefined;
const baseReactions = actionsConfig?.reactions;
const hasReactionCapableAccount = enabledAccounts.some((account) => {
const accountActions = account.config.actions as { reactions?: boolean } | undefined;
return (accountActions?.reactions ?? baseReactions ?? true) !== false;
});
if (hasReactionCapableAccount) {
actions.push("react");
}
return {
actions,
capabilities: enabledAccounts.length > 0 ? ["buttons"] : [],
schema:
enabledAccounts.length > 0
? {
properties: {
buttons: createMessageToolButtonsSchema(),
},
}
: null,
};
},
describeMessageTool: describeMattermostMessageTool,
...createLegacyMessageToolDiscoveryMethods(describeMattermostMessageTool),
supportsAction: ({ action }) => {
return action === "send" || action === "react";
},

View File

@ -1,7 +1,13 @@
import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from";
import { collectAllowlistProviderRestrictSendersWarnings } from "openclaw/plugin-sdk/channel-policy";
import { createMessageToolCardSchema } from "openclaw/plugin-sdk/channel-runtime";
import type { ChannelMessageActionAdapter } from "openclaw/plugin-sdk/channel-runtime";
import {
createLegacyMessageToolDiscoveryMethods,
createMessageToolCardSchema,
} from "openclaw/plugin-sdk/channel-runtime";
import type {
ChannelMessageActionAdapter,
ChannelMessageToolDiscovery,
} from "openclaw/plugin-sdk/channel-runtime";
import { createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime";
import type {
ChannelMessageActionName,
@ -64,6 +70,27 @@ const loadMSTeamsChannelRuntime = createLazyRuntimeNamedExport(
"msTeamsChannelRuntime",
);
function describeMSTeamsMessageTool({
cfg,
}: Parameters<
NonNullable<ChannelMessageActionAdapter["describeMessageTool"]>
>[0]): ChannelMessageToolDiscovery {
const enabled =
cfg.channels?.msteams?.enabled !== false &&
Boolean(resolveMSTeamsCredentials(cfg.channels?.msteams));
return {
actions: enabled ? (["poll"] satisfies ChannelMessageActionName[]) : [],
capabilities: enabled ? ["cards"] : [],
schema: enabled
? {
properties: {
card: createMessageToolCardSchema(),
},
}
: null,
};
}
export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
id: "msteams",
meta: {
@ -370,24 +397,8 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
},
},
actions: {
describeMessageTool: ({
cfg,
}: Parameters<NonNullable<ChannelMessageActionAdapter["describeMessageTool"]>>[0]) => {
const enabled =
cfg.channels?.msteams?.enabled !== false &&
Boolean(resolveMSTeamsCredentials(cfg.channels?.msteams));
return {
actions: enabled ? (["poll"] satisfies ChannelMessageActionName[]) : [],
capabilities: enabled ? ["cards"] : [],
schema: enabled
? {
properties: {
card: createMessageToolCardSchema(),
},
}
: null,
};
},
describeMessageTool: describeMSTeamsMessageTool,
...createLegacyMessageToolDiscoveryMethods(describeMSTeamsMessageTool),
handleAction: async (ctx) => {
// Handle send action with card parameter
if (ctx.action === "send" && ctx.params.card) {

View File

@ -0,0 +1,13 @@
import type { ChannelMessageActionAdapter } from "./types.js";
export function createLegacyMessageToolDiscoveryMethods(
describeMessageTool: NonNullable<ChannelMessageActionAdapter["describeMessageTool"]>,
): Pick<ChannelMessageActionAdapter, "listActions" | "getCapabilities" | "getToolSchema"> {
const describe = (ctx: Parameters<typeof describeMessageTool>[0]) =>
describeMessageTool(ctx) ?? null;
return {
listActions: (ctx) => [...(describe(ctx)?.actions ?? [])],
getCapabilities: (ctx) => [...(describe(ctx)?.capabilities ?? [])],
getToolSchema: (ctx) => describe(ctx)?.schema ?? null,
};
}

View File

@ -34,6 +34,7 @@ export type * from "../channels/plugins/types.js";
export * from "../channels/plugins/config-writes.js";
export * from "../channels/plugins/directory-config.js";
export * from "../channels/plugins/media-payload.js";
export * from "../channels/plugins/message-tool-legacy.js";
export * from "../channels/plugins/message-tool-schema.js";
export * from "../channels/plugins/normalize/signal.js";
export * from "../channels/plugins/normalize/whatsapp.js";