Channels: add message action capabilities
This commit is contained in:
parent
69a85325c3
commit
92bea9704e
@ -106,8 +106,10 @@ export const discordMessageActions: ChannelMessageActionAdapter = {
|
||||
}
|
||||
return Array.from(actions);
|
||||
},
|
||||
supportsInteractive: ({ cfg }) =>
|
||||
listTokenSourcedAccounts(listEnabledDiscordAccounts(cfg)).length > 0,
|
||||
getCapabilities: ({ cfg }) =>
|
||||
listTokenSourcedAccounts(listEnabledDiscordAccounts(cfg)).length > 0
|
||||
? (["interactive", "components"] as const)
|
||||
: [],
|
||||
extractToolSend: ({ args }) => {
|
||||
const action = typeof args.action === "string" ? args.action.trim() : "";
|
||||
if (action === "sendMessage") {
|
||||
|
||||
@ -219,11 +219,11 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
|
||||
}
|
||||
return Array.from(actions);
|
||||
},
|
||||
supportsCards: ({ cfg }) => {
|
||||
return (
|
||||
cfg.channels?.feishu?.enabled !== false &&
|
||||
getCapabilities: ({ cfg }) => {
|
||||
return cfg.channels?.feishu?.enabled !== false &&
|
||||
Boolean(resolveFeishuCredentials(cfg.channels?.feishu as FeishuConfig | undefined))
|
||||
);
|
||||
? (["cards"] as const)
|
||||
: [];
|
||||
},
|
||||
handleAction: async (ctx) => {
|
||||
const account = resolveFeishuAccount({ cfg: ctx.cfg, accountId: ctx.accountId ?? undefined });
|
||||
|
||||
@ -71,11 +71,11 @@ const mattermostMessageActions: ChannelMessageActionAdapter = {
|
||||
supportsAction: ({ action }) => {
|
||||
return action === "send" || action === "react";
|
||||
},
|
||||
supportsButtons: ({ cfg }) => {
|
||||
getCapabilities: ({ cfg }) => {
|
||||
const accounts = listMattermostAccountIds(cfg)
|
||||
.map((id) => resolveMattermostAccount({ cfg, accountId: id }))
|
||||
.filter((a) => a.enabled && a.botToken?.trim() && a.baseUrl?.trim());
|
||||
return accounts.length > 0;
|
||||
return accounts.length > 0 ? (["buttons"] as const) : [];
|
||||
},
|
||||
handleAction: async ({ action, params, cfg, accountId }) => {
|
||||
if (action === "react") {
|
||||
|
||||
@ -366,11 +366,11 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
|
||||
}
|
||||
return ["poll"] satisfies ChannelMessageActionName[];
|
||||
},
|
||||
supportsCards: ({ cfg }) => {
|
||||
return (
|
||||
cfg.channels?.msteams?.enabled !== false &&
|
||||
getCapabilities: ({ cfg }) => {
|
||||
return cfg.channels?.msteams?.enabled !== false &&
|
||||
Boolean(resolveMSTeamsCredentials(cfg.channels?.msteams))
|
||||
);
|
||||
? (["cards"] as const)
|
||||
: [];
|
||||
},
|
||||
handleAction: async (ctx) => {
|
||||
// Handle send action with card parameter
|
||||
|
||||
@ -292,6 +292,16 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
|
||||
},
|
||||
actions: {
|
||||
listActions: ({ cfg }) => listSlackMessageActions(cfg),
|
||||
getCapabilities: ({ cfg }) => {
|
||||
const capabilities = new Set<"interactive" | "blocks">();
|
||||
if (listSlackMessageActions(cfg).includes("send")) {
|
||||
capabilities.add("blocks");
|
||||
}
|
||||
if (isSlackInteractiveRepliesEnabled({ cfg })) {
|
||||
capabilities.add("interactive");
|
||||
}
|
||||
return Array.from(capabilities);
|
||||
},
|
||||
extractToolSend: ({ args }) => extractSlackToolSend(args),
|
||||
handleAction: async (ctx) =>
|
||||
await handleSlackMessageAction({
|
||||
|
||||
@ -24,8 +24,8 @@ import {
|
||||
listEnabledTelegramAccounts,
|
||||
resolveTelegramPollActionGateState,
|
||||
} from "./accounts.js";
|
||||
import { buildTelegramInteractiveButtons } from "./button-types.js";
|
||||
import { isTelegramInlineButtonsEnabled } from "./inline-buttons.js";
|
||||
import { buildTelegramInteractiveButtons } from "./shared-interactive.js";
|
||||
|
||||
const providerId = "telegram";
|
||||
|
||||
@ -124,23 +124,15 @@ export const telegramMessageActions: ChannelMessageActionAdapter = {
|
||||
}
|
||||
return Array.from(actions);
|
||||
},
|
||||
supportsInteractive: ({ cfg }) => {
|
||||
getCapabilities: ({ cfg }) => {
|
||||
const accounts = listTokenSourcedAccounts(listEnabledTelegramAccounts(cfg));
|
||||
if (accounts.length === 0) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
return accounts.some((account) =>
|
||||
isTelegramInlineButtonsEnabled({ cfg, accountId: account.accountId }),
|
||||
);
|
||||
},
|
||||
supportsButtons: ({ cfg }) => {
|
||||
const accounts = listTokenSourcedAccounts(listEnabledTelegramAccounts(cfg));
|
||||
if (accounts.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return accounts.some((account) =>
|
||||
const buttonsEnabled = accounts.some((account) =>
|
||||
isTelegramInlineButtonsEnabled({ cfg, accountId: account.accountId }),
|
||||
);
|
||||
return buttonsEnabled ? (["interactive", "buttons"] as const) : [];
|
||||
},
|
||||
extractToolSend: ({ args }) => {
|
||||
return extractToolSend(args, "sendMessage");
|
||||
|
||||
@ -24,7 +24,7 @@ export const zaloMessageActions: ChannelMessageActionAdapter = {
|
||||
const actions = new Set<ChannelMessageActionName>(["send"]);
|
||||
return Array.from(actions);
|
||||
},
|
||||
supportsButtons: () => false,
|
||||
getCapabilities: () => [],
|
||||
extractToolSend: ({ args }) => extractToolSend(args, "sendMessage"),
|
||||
handleAction: async ({ action, params, cfg, accountId }) => {
|
||||
if (action === "send") {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { ChannelMessageCapability } from "../../channels/plugins/message-capabilities.js";
|
||||
import type { ChannelMessageActionName, ChannelPlugin } from "../../channels/plugins/types.js";
|
||||
import type { MessageActionRunResult } from "../../infra/outbound/message-action-runner.js";
|
||||
import { setActivePluginRegistry } from "../../plugins/runtime.js";
|
||||
@ -47,9 +48,10 @@ function createChannelPlugin(params: {
|
||||
blurb: string;
|
||||
actions?: ChannelMessageActionName[];
|
||||
listActions?: NonNullable<NonNullable<ChannelPlugin["actions"]>["listActions"]>;
|
||||
supportsButtons?: boolean;
|
||||
capabilities?: readonly ChannelMessageCapability[];
|
||||
messaging?: ChannelPlugin["messaging"];
|
||||
}): ChannelPlugin {
|
||||
const actionCapabilities = params.capabilities;
|
||||
return {
|
||||
id: params.id as ChannelPlugin["id"],
|
||||
meta: {
|
||||
@ -71,7 +73,9 @@ function createChannelPlugin(params: {
|
||||
(() => {
|
||||
return (params.actions ?? []) as never;
|
||||
}),
|
||||
...(params.supportsButtons ? { supportsButtons: () => true } : {}),
|
||||
...(actionCapabilities
|
||||
? { getCapabilities: (_params: { cfg: unknown }) => actionCapabilities }
|
||||
: {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -145,7 +149,7 @@ describe("message tool schema scoping", () => {
|
||||
docsPath: "/channels/telegram",
|
||||
blurb: "Telegram test plugin.",
|
||||
actions: ["send", "react", "poll"],
|
||||
supportsButtons: true,
|
||||
capabilities: ["interactive", "buttons"],
|
||||
});
|
||||
|
||||
const discordPlugin = createChannelPlugin({
|
||||
@ -154,6 +158,16 @@ describe("message tool schema scoping", () => {
|
||||
docsPath: "/channels/discord",
|
||||
blurb: "Discord test plugin.",
|
||||
actions: ["send", "poll", "poll-vote"],
|
||||
capabilities: ["interactive", "components"],
|
||||
});
|
||||
|
||||
const slackPlugin = createChannelPlugin({
|
||||
id: "slack",
|
||||
label: "Slack",
|
||||
docsPath: "/channels/slack",
|
||||
blurb: "Slack test plugin.",
|
||||
actions: ["send", "react"],
|
||||
capabilities: ["interactive", "blocks"],
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@ -164,6 +178,7 @@ describe("message tool schema scoping", () => {
|
||||
{
|
||||
provider: "telegram",
|
||||
expectComponents: false,
|
||||
expectBlocks: false,
|
||||
expectButtons: true,
|
||||
expectButtonStyle: true,
|
||||
expectTelegramPollExtras: true,
|
||||
@ -172,16 +187,27 @@ describe("message tool schema scoping", () => {
|
||||
{
|
||||
provider: "discord",
|
||||
expectComponents: true,
|
||||
expectBlocks: false,
|
||||
expectButtons: false,
|
||||
expectButtonStyle: false,
|
||||
expectTelegramPollExtras: true,
|
||||
expectedActions: ["send", "poll", "poll-vote", "react"],
|
||||
},
|
||||
{
|
||||
provider: "slack",
|
||||
expectComponents: false,
|
||||
expectBlocks: true,
|
||||
expectButtons: false,
|
||||
expectButtonStyle: false,
|
||||
expectTelegramPollExtras: true,
|
||||
expectedActions: ["send", "react", "poll", "poll-vote"],
|
||||
},
|
||||
])(
|
||||
"scopes schema fields for $provider",
|
||||
({
|
||||
provider,
|
||||
expectComponents,
|
||||
expectBlocks,
|
||||
expectButtons,
|
||||
expectButtonStyle,
|
||||
expectTelegramPollExtras,
|
||||
@ -191,6 +217,7 @@ describe("message tool schema scoping", () => {
|
||||
createTestRegistry([
|
||||
{ pluginId: "telegram", source: "test", plugin: telegramPlugin },
|
||||
{ pluginId: "discord", source: "test", plugin: discordPlugin },
|
||||
{ pluginId: "slack", source: "test", plugin: slackPlugin },
|
||||
]),
|
||||
);
|
||||
|
||||
@ -206,6 +233,11 @@ describe("message tool schema scoping", () => {
|
||||
} else {
|
||||
expect(properties.components).toBeUndefined();
|
||||
}
|
||||
if (expectBlocks) {
|
||||
expect(properties.blocks).toBeDefined();
|
||||
} else {
|
||||
expect(properties.blocks).toBeUndefined();
|
||||
}
|
||||
if (expectButtons) {
|
||||
expect(properties.buttons).toBeDefined();
|
||||
} else {
|
||||
@ -263,7 +295,7 @@ describe("message tool schema scoping", () => {
|
||||
.channels?.telegram;
|
||||
return telegramCfg?.actions?.poll === false ? ["send", "react"] : ["send", "react", "poll"];
|
||||
},
|
||||
supportsButtons: true,
|
||||
capabilities: ["interactive", "buttons"],
|
||||
});
|
||||
|
||||
setActivePluginRegistry(
|
||||
|
||||
@ -2,14 +2,11 @@ import { Type } from "@sinclair/typebox";
|
||||
import { BLUEBUBBLES_GROUP_ACTIONS } from "../../channels/plugins/bluebubbles-actions.js";
|
||||
import { listChannelPlugins } from "../../channels/plugins/index.js";
|
||||
import {
|
||||
supportsChannelMessageInteractive,
|
||||
supportsChannelMessageInteractiveForChannel,
|
||||
channelSupportsMessageCapability,
|
||||
channelSupportsMessageCapabilityForChannel,
|
||||
listChannelMessageActions,
|
||||
supportsChannelMessageButtons,
|
||||
supportsChannelMessageButtonsForChannel,
|
||||
supportsChannelMessageCards,
|
||||
supportsChannelMessageCardsForChannel,
|
||||
} from "../../channels/plugins/message-actions.js";
|
||||
import type { ChannelMessageCapability } from "../../channels/plugins/message-capabilities.js";
|
||||
import {
|
||||
CHANNEL_MESSAGE_ACTION_NAMES,
|
||||
type ChannelMessageActionName,
|
||||
@ -199,6 +196,7 @@ function buildSendSchema(options: {
|
||||
includeButtons: boolean;
|
||||
includeCards: boolean;
|
||||
includeComponents: boolean;
|
||||
includeBlocks: boolean;
|
||||
}) {
|
||||
const props: Record<string, unknown> = {
|
||||
message: Type.Optional(Type.String()),
|
||||
@ -265,6 +263,17 @@ function buildSendSchema(options: {
|
||||
),
|
||||
),
|
||||
components: Type.Optional(discordComponentMessageSchema),
|
||||
blocks: Type.Optional(
|
||||
Type.Array(
|
||||
Type.Object(
|
||||
{},
|
||||
{
|
||||
additionalProperties: true,
|
||||
description: "Slack Block Kit payload blocks (Slack only).",
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
};
|
||||
if (!options.includeButtons) {
|
||||
delete props.buttons;
|
||||
@ -278,6 +287,9 @@ function buildSendSchema(options: {
|
||||
if (!options.includeComponents) {
|
||||
delete props.components;
|
||||
}
|
||||
if (!options.includeBlocks) {
|
||||
delete props.blocks;
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
@ -487,6 +499,7 @@ function buildMessageToolSchemaProps(options: {
|
||||
includeButtons: boolean;
|
||||
includeCards: boolean;
|
||||
includeComponents: boolean;
|
||||
includeBlocks: boolean;
|
||||
includeTelegramPollExtras: boolean;
|
||||
}) {
|
||||
return {
|
||||
@ -513,6 +526,7 @@ function buildMessageToolSchemaFromActions(
|
||||
includeButtons: boolean;
|
||||
includeCards: boolean;
|
||||
includeComponents: boolean;
|
||||
includeBlocks: boolean;
|
||||
includeTelegramPollExtras: boolean;
|
||||
},
|
||||
) {
|
||||
@ -528,6 +542,7 @@ const MessageToolSchema = buildMessageToolSchemaFromActions(AllMessageActions, {
|
||||
includeButtons: true,
|
||||
includeCards: true,
|
||||
includeComponents: true,
|
||||
includeBlocks: true,
|
||||
includeTelegramPollExtras: true,
|
||||
});
|
||||
|
||||
@ -578,30 +593,59 @@ function resolveMessageToolSchemaActions(params: {
|
||||
return actions.length > 0 ? actions : ["send"];
|
||||
}
|
||||
|
||||
function resolveIncludeCapability(
|
||||
params: {
|
||||
cfg: OpenClawConfig;
|
||||
currentChannelProvider?: string;
|
||||
},
|
||||
capability: ChannelMessageCapability,
|
||||
): boolean {
|
||||
const currentChannel = normalizeMessageChannel(params.currentChannelProvider);
|
||||
if (currentChannel) {
|
||||
return channelSupportsMessageCapabilityForChannel(
|
||||
{
|
||||
cfg: params.cfg,
|
||||
channel: currentChannel,
|
||||
},
|
||||
capability,
|
||||
);
|
||||
}
|
||||
return channelSupportsMessageCapability(params.cfg, capability);
|
||||
}
|
||||
|
||||
function resolveIncludeComponents(params: {
|
||||
cfg: OpenClawConfig;
|
||||
currentChannelProvider?: string;
|
||||
}): boolean {
|
||||
const currentChannel = normalizeMessageChannel(params.currentChannelProvider);
|
||||
if (currentChannel) {
|
||||
return currentChannel === "discord";
|
||||
}
|
||||
// Components are currently Discord-specific.
|
||||
return listChannelSupportedActions({ cfg: params.cfg, channel: "discord" }).length > 0;
|
||||
return resolveIncludeCapability(params, "components");
|
||||
}
|
||||
|
||||
function resolveIncludeInteractive(params: {
|
||||
cfg: OpenClawConfig;
|
||||
currentChannelProvider?: string;
|
||||
}): boolean {
|
||||
const currentChannel = normalizeMessageChannel(params.currentChannelProvider);
|
||||
if (currentChannel) {
|
||||
return supportsChannelMessageInteractiveForChannel({
|
||||
cfg: params.cfg,
|
||||
channel: currentChannel,
|
||||
});
|
||||
}
|
||||
return supportsChannelMessageInteractive(params.cfg);
|
||||
return resolveIncludeCapability(params, "interactive");
|
||||
}
|
||||
|
||||
function resolveIncludeButtons(params: {
|
||||
cfg: OpenClawConfig;
|
||||
currentChannelProvider?: string;
|
||||
}): boolean {
|
||||
return resolveIncludeCapability(params, "buttons");
|
||||
}
|
||||
|
||||
function resolveIncludeCards(params: {
|
||||
cfg: OpenClawConfig;
|
||||
currentChannelProvider?: string;
|
||||
}): boolean {
|
||||
return resolveIncludeCapability(params, "cards");
|
||||
}
|
||||
|
||||
function resolveIncludeBlocks(params: {
|
||||
cfg: OpenClawConfig;
|
||||
currentChannelProvider?: string;
|
||||
}): boolean {
|
||||
return resolveIncludeCapability(params, "blocks");
|
||||
}
|
||||
|
||||
function resolveIncludeTelegramPollExtras(params: {
|
||||
@ -619,22 +663,19 @@ function buildMessageToolSchema(params: {
|
||||
currentChannelProvider?: string;
|
||||
currentChannelId?: string;
|
||||
}) {
|
||||
const currentChannel = normalizeMessageChannel(params.currentChannelProvider);
|
||||
const actions = resolveMessageToolSchemaActions(params);
|
||||
const includeInteractive = resolveIncludeInteractive(params);
|
||||
const includeButtons = currentChannel
|
||||
? supportsChannelMessageButtonsForChannel({ cfg: params.cfg, channel: currentChannel })
|
||||
: supportsChannelMessageButtons(params.cfg);
|
||||
const includeCards = currentChannel
|
||||
? supportsChannelMessageCardsForChannel({ cfg: params.cfg, channel: currentChannel })
|
||||
: supportsChannelMessageCards(params.cfg);
|
||||
const includeButtons = resolveIncludeButtons(params);
|
||||
const includeCards = resolveIncludeCards(params);
|
||||
const includeComponents = resolveIncludeComponents(params);
|
||||
const includeBlocks = resolveIncludeBlocks(params);
|
||||
const includeTelegramPollExtras = resolveIncludeTelegramPollExtras(params);
|
||||
return buildMessageToolSchemaFromActions(actions.length > 0 ? actions : ["send"], {
|
||||
includeInteractive,
|
||||
includeButtons,
|
||||
includeCards,
|
||||
includeComponents,
|
||||
includeBlocks,
|
||||
includeTelegramPollExtras,
|
||||
});
|
||||
}
|
||||
|
||||
@ -6,22 +6,19 @@ import {
|
||||
createTestRegistry,
|
||||
} from "../../test-utils/channel-plugins.js";
|
||||
import {
|
||||
supportsChannelMessageButtons,
|
||||
supportsChannelMessageButtonsForChannel,
|
||||
supportsChannelMessageCards,
|
||||
supportsChannelMessageCardsForChannel,
|
||||
supportsChannelMessageInteractive,
|
||||
supportsChannelMessageInteractiveForChannel,
|
||||
channelSupportsMessageCapability,
|
||||
channelSupportsMessageCapabilityForChannel,
|
||||
listChannelMessageCapabilities,
|
||||
listChannelMessageCapabilitiesForChannel,
|
||||
} from "./message-actions.js";
|
||||
import type { ChannelMessageCapability } from "./message-capabilities.js";
|
||||
import type { ChannelPlugin } from "./types.js";
|
||||
|
||||
const emptyRegistry = createTestRegistry([]);
|
||||
|
||||
function createMessageActionsPlugin(params: {
|
||||
id: "discord" | "telegram";
|
||||
supportsInteractive: boolean;
|
||||
supportsButtons: boolean;
|
||||
supportsCards: boolean;
|
||||
capabilities: readonly ChannelMessageCapability[];
|
||||
}): ChannelPlugin {
|
||||
return {
|
||||
...createChannelTestPluginBase({
|
||||
@ -34,25 +31,19 @@ function createMessageActionsPlugin(params: {
|
||||
}),
|
||||
actions: {
|
||||
listActions: () => ["send"],
|
||||
supportsInteractive: () => params.supportsInteractive,
|
||||
supportsButtons: () => params.supportsButtons,
|
||||
supportsCards: () => params.supportsCards,
|
||||
getCapabilities: () => params.capabilities,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const buttonsPlugin = createMessageActionsPlugin({
|
||||
id: "discord",
|
||||
supportsInteractive: true,
|
||||
supportsButtons: true,
|
||||
supportsCards: false,
|
||||
capabilities: ["interactive", "buttons"],
|
||||
});
|
||||
|
||||
const cardsPlugin = createMessageActionsPlugin({
|
||||
id: "telegram",
|
||||
supportsInteractive: false,
|
||||
supportsButtons: false,
|
||||
supportsCards: true,
|
||||
capabilities: ["cards"],
|
||||
});
|
||||
|
||||
function activateMessageActionTestRegistry() {
|
||||
@ -69,38 +60,66 @@ describe("message action capability checks", () => {
|
||||
setActivePluginRegistry(emptyRegistry);
|
||||
});
|
||||
|
||||
it("aggregates buttons/card support across plugins", () => {
|
||||
it("aggregates capabilities across plugins", () => {
|
||||
activateMessageActionTestRegistry();
|
||||
|
||||
expect(supportsChannelMessageInteractive({} as OpenClawConfig)).toBe(true);
|
||||
expect(supportsChannelMessageButtons({} as OpenClawConfig)).toBe(true);
|
||||
expect(supportsChannelMessageCards({} as OpenClawConfig)).toBe(true);
|
||||
expect(listChannelMessageCapabilities({} as OpenClawConfig).toSorted()).toEqual([
|
||||
"buttons",
|
||||
"cards",
|
||||
"interactive",
|
||||
]);
|
||||
expect(channelSupportsMessageCapability({} as OpenClawConfig, "interactive")).toBe(true);
|
||||
expect(channelSupportsMessageCapability({} as OpenClawConfig, "buttons")).toBe(true);
|
||||
expect(channelSupportsMessageCapability({} as OpenClawConfig, "cards")).toBe(true);
|
||||
});
|
||||
|
||||
it("checks per-channel capabilities", () => {
|
||||
activateMessageActionTestRegistry();
|
||||
|
||||
expect(
|
||||
supportsChannelMessageInteractiveForChannel({
|
||||
listChannelMessageCapabilitiesForChannel({
|
||||
cfg: {} as OpenClawConfig,
|
||||
channel: "discord",
|
||||
}),
|
||||
).toBe(true);
|
||||
).toEqual(["interactive", "buttons"]);
|
||||
expect(
|
||||
supportsChannelMessageInteractiveForChannel({
|
||||
listChannelMessageCapabilitiesForChannel({
|
||||
cfg: {} as OpenClawConfig,
|
||||
channel: "telegram",
|
||||
}),
|
||||
).toBe(false);
|
||||
).toEqual(["cards"]);
|
||||
expect(
|
||||
supportsChannelMessageButtonsForChannel({ cfg: {} as OpenClawConfig, channel: "discord" }),
|
||||
channelSupportsMessageCapabilityForChannel(
|
||||
{ cfg: {} as OpenClawConfig, channel: "discord" },
|
||||
"interactive",
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
supportsChannelMessageButtonsForChannel({ cfg: {} as OpenClawConfig, channel: "telegram" }),
|
||||
channelSupportsMessageCapabilityForChannel(
|
||||
{ cfg: {} as OpenClawConfig, channel: "telegram" },
|
||||
"interactive",
|
||||
),
|
||||
).toBe(false);
|
||||
expect(
|
||||
supportsChannelMessageCardsForChannel({ cfg: {} as OpenClawConfig, channel: "telegram" }),
|
||||
channelSupportsMessageCapabilityForChannel(
|
||||
{ cfg: {} as OpenClawConfig, channel: "discord" },
|
||||
"buttons",
|
||||
),
|
||||
).toBe(true);
|
||||
expect(supportsChannelMessageCardsForChannel({ cfg: {} as OpenClawConfig })).toBe(false);
|
||||
expect(
|
||||
channelSupportsMessageCapabilityForChannel(
|
||||
{ cfg: {} as OpenClawConfig, channel: "telegram" },
|
||||
"buttons",
|
||||
),
|
||||
).toBe(false);
|
||||
expect(
|
||||
channelSupportsMessageCapabilityForChannel(
|
||||
{ cfg: {} as OpenClawConfig, channel: "telegram" },
|
||||
"cards",
|
||||
),
|
||||
).toBe(true);
|
||||
expect(channelSupportsMessageCapabilityForChannel({ cfg: {} as OpenClawConfig }, "cards")).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { getChannelPlugin, listChannelPlugins } from "./index.js";
|
||||
import type { ChannelMessageCapability } from "./message-capabilities.js";
|
||||
import type { ChannelMessageActionContext, ChannelMessageActionName } from "./types.js";
|
||||
|
||||
const trustedRequesterRequiredByChannel: Readonly<
|
||||
@ -30,72 +31,52 @@ export function listChannelMessageActions(cfg: OpenClawConfig): ChannelMessageAc
|
||||
return Array.from(actions);
|
||||
}
|
||||
|
||||
export function supportsChannelMessageButtons(cfg: OpenClawConfig): boolean {
|
||||
return supportsMessageFeature(cfg, (actions) => actions?.supportsButtons?.({ cfg }) === true);
|
||||
}
|
||||
|
||||
export function supportsChannelMessageInteractive(cfg: OpenClawConfig): boolean {
|
||||
return supportsMessageFeature(cfg, (actions) => actions?.supportsInteractive?.({ cfg }) === true);
|
||||
}
|
||||
|
||||
export function supportsChannelMessageButtonsForChannel(params: {
|
||||
cfg: OpenClawConfig;
|
||||
channel?: string;
|
||||
}): boolean {
|
||||
return supportsMessageFeatureForChannel(
|
||||
params,
|
||||
(actions) => actions.supportsButtons?.(params) === true,
|
||||
);
|
||||
}
|
||||
|
||||
export function supportsChannelMessageInteractiveForChannel(params: {
|
||||
cfg: OpenClawConfig;
|
||||
channel?: string;
|
||||
}): boolean {
|
||||
return supportsMessageFeatureForChannel(
|
||||
params,
|
||||
(actions) => actions.supportsInteractive?.(params) === true,
|
||||
);
|
||||
}
|
||||
|
||||
export function supportsChannelMessageCards(cfg: OpenClawConfig): boolean {
|
||||
return supportsMessageFeature(cfg, (actions) => actions?.supportsCards?.({ cfg }) === true);
|
||||
}
|
||||
|
||||
export function supportsChannelMessageCardsForChannel(params: {
|
||||
cfg: OpenClawConfig;
|
||||
channel?: string;
|
||||
}): boolean {
|
||||
return supportsMessageFeatureForChannel(
|
||||
params,
|
||||
(actions) => actions.supportsCards?.(params) === true,
|
||||
);
|
||||
}
|
||||
|
||||
function supportsMessageFeature(
|
||||
function listCapabilities(
|
||||
actions: ChannelActions,
|
||||
cfg: OpenClawConfig,
|
||||
check: (actions: ChannelActions) => boolean,
|
||||
): boolean {
|
||||
): readonly ChannelMessageCapability[] {
|
||||
return actions.getCapabilities?.({ cfg }) ?? [];
|
||||
}
|
||||
|
||||
export function listChannelMessageCapabilities(cfg: OpenClawConfig): ChannelMessageCapability[] {
|
||||
const capabilities = new Set<ChannelMessageCapability>();
|
||||
for (const plugin of listChannelPlugins()) {
|
||||
if (plugin.actions && check(plugin.actions)) {
|
||||
return true;
|
||||
if (!plugin.actions) {
|
||||
continue;
|
||||
}
|
||||
for (const capability of listCapabilities(plugin.actions, cfg)) {
|
||||
capabilities.add(capability);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return Array.from(capabilities);
|
||||
}
|
||||
|
||||
function supportsMessageFeatureForChannel(
|
||||
export function listChannelMessageCapabilitiesForChannel(params: {
|
||||
cfg: OpenClawConfig;
|
||||
channel?: string;
|
||||
}): ChannelMessageCapability[] {
|
||||
if (!params.channel) {
|
||||
return [];
|
||||
}
|
||||
const plugin = getChannelPlugin(params.channel as Parameters<typeof getChannelPlugin>[0]);
|
||||
return plugin?.actions ? Array.from(listCapabilities(plugin.actions, params.cfg)) : [];
|
||||
}
|
||||
|
||||
export function channelSupportsMessageCapability(
|
||||
cfg: OpenClawConfig,
|
||||
capability: ChannelMessageCapability,
|
||||
): boolean {
|
||||
return listChannelMessageCapabilities(cfg).includes(capability);
|
||||
}
|
||||
|
||||
export function channelSupportsMessageCapabilityForChannel(
|
||||
params: {
|
||||
cfg: OpenClawConfig;
|
||||
channel?: string;
|
||||
},
|
||||
check: (actions: ChannelActions) => boolean,
|
||||
capability: ChannelMessageCapability,
|
||||
): boolean {
|
||||
if (!params.channel) {
|
||||
return false;
|
||||
}
|
||||
const plugin = getChannelPlugin(params.channel as Parameters<typeof getChannelPlugin>[0]);
|
||||
return plugin?.actions ? check(plugin.actions) : false;
|
||||
return listChannelMessageCapabilitiesForChannel(params).includes(capability);
|
||||
}
|
||||
|
||||
export async function dispatchChannelMessageAction(
|
||||
|
||||
9
src/channels/plugins/message-capabilities.ts
Normal file
9
src/channels/plugins/message-capabilities.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export const CHANNEL_MESSAGE_CAPABILITIES = [
|
||||
"interactive",
|
||||
"buttons",
|
||||
"cards",
|
||||
"components",
|
||||
"blocks",
|
||||
] as const;
|
||||
|
||||
export type ChannelMessageCapability = (typeof CHANNEL_MESSAGE_CAPABILITIES)[number];
|
||||
@ -11,7 +11,16 @@ import type { ChannelMessageActionAdapter } from "./types.js";
|
||||
export function createSlackActions(providerId: string): ChannelMessageActionAdapter {
|
||||
return {
|
||||
listActions: ({ cfg }) => listSlackMessageActions(cfg),
|
||||
supportsInteractive: ({ cfg }) => isSlackInteractiveRepliesEnabled({ cfg }),
|
||||
getCapabilities: ({ cfg }) => {
|
||||
const capabilities = new Set<"interactive" | "blocks">();
|
||||
if (listSlackMessageActions(cfg).includes("send")) {
|
||||
capabilities.add("blocks");
|
||||
}
|
||||
if (isSlackInteractiveRepliesEnabled({ cfg })) {
|
||||
capabilities.add("interactive");
|
||||
}
|
||||
return Array.from(capabilities);
|
||||
},
|
||||
extractToolSend: ({ args }) => extractSlackToolSend(args),
|
||||
handleAction: async (ctx) => {
|
||||
return await handleSlackMessageAction({
|
||||
|
||||
@ -7,6 +7,7 @@ import type { GatewayClientMode, GatewayClientName } from "../../utils/message-c
|
||||
import type { ChatType } from "../chat-type.js";
|
||||
import type { ChatChannelId } from "../registry.js";
|
||||
import type { ChannelMessageActionName as ChannelMessageActionNameFromList } from "./message-action-names.js";
|
||||
import type { ChannelMessageCapability } from "./message-capabilities.js";
|
||||
|
||||
export type ChannelId = ChatChannelId | (string & {});
|
||||
|
||||
@ -372,9 +373,7 @@ export type ChannelMessageActionAdapter = {
|
||||
*/
|
||||
listActions?: (params: { cfg: OpenClawConfig }) => ChannelMessageActionName[];
|
||||
supportsAction?: (params: { action: ChannelMessageActionName }) => boolean;
|
||||
supportsInteractive?: (params: { cfg: OpenClawConfig }) => boolean;
|
||||
supportsButtons?: (params: { cfg: OpenClawConfig }) => boolean;
|
||||
supportsCards?: (params: { cfg: OpenClawConfig }) => boolean;
|
||||
getCapabilities?: (params: { cfg: OpenClawConfig }) => readonly ChannelMessageCapability[];
|
||||
extractToolSend?: (params: { args: Record<string, unknown> }) => ChannelToolSend | null;
|
||||
handleAction?: (ctx: ChannelMessageActionContext) => Promise<AgentToolResult<unknown>>;
|
||||
};
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import type { ChannelMessageActionName as ChannelMessageActionNameFromList } from "./message-action-names.js";
|
||||
|
||||
export { CHANNEL_MESSAGE_ACTION_NAMES } from "./message-action-names.js";
|
||||
export { CHANNEL_MESSAGE_CAPABILITIES } from "./message-capabilities.js";
|
||||
|
||||
export type ChannelMessageActionName = ChannelMessageActionNameFromList;
|
||||
export type { ChannelMessageCapability } from "./message-capabilities.js";
|
||||
|
||||
export type {
|
||||
ChannelAuthAdapter,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user