Slack: add new slack connection
This commit is contained in:
committed by
Peter Steinberger
parent
4b3ca29404
commit
bf3d120f8c
163
src/agents/tools/slack-actions.ts
Normal file
163
src/agents/tools/slack-actions.ts
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||||
|
|
||||||
|
import type { ClawdisConfig, SlackActionConfig } from "../../config/config.js";
|
||||||
|
import {
|
||||||
|
deleteSlackMessage,
|
||||||
|
editSlackMessage,
|
||||||
|
getSlackMemberInfo,
|
||||||
|
listSlackEmojis,
|
||||||
|
listSlackPins,
|
||||||
|
listSlackReactions,
|
||||||
|
pinSlackMessage,
|
||||||
|
reactSlackMessage,
|
||||||
|
readSlackMessages,
|
||||||
|
sendSlackMessage,
|
||||||
|
unpinSlackMessage,
|
||||||
|
} from "../../slack/actions.js";
|
||||||
|
import { jsonResult, readStringParam } from "./common.js";
|
||||||
|
|
||||||
|
const messagingActions = new Set([
|
||||||
|
"sendMessage",
|
||||||
|
"editMessage",
|
||||||
|
"deleteMessage",
|
||||||
|
"readMessages",
|
||||||
|
]);
|
||||||
|
|
||||||
|
const reactionsActions = new Set(["react", "reactions"]);
|
||||||
|
const pinActions = new Set(["pinMessage", "unpinMessage", "listPins"]);
|
||||||
|
|
||||||
|
type ActionGate = (
|
||||||
|
key: keyof SlackActionConfig,
|
||||||
|
defaultValue?: boolean,
|
||||||
|
) => boolean;
|
||||||
|
|
||||||
|
export async function handleSlackAction(
|
||||||
|
params: Record<string, unknown>,
|
||||||
|
cfg: ClawdisConfig,
|
||||||
|
): Promise<AgentToolResult<unknown>> {
|
||||||
|
const action = readStringParam(params, "action", { required: true });
|
||||||
|
const isActionEnabled: ActionGate = (key, defaultValue = true) => {
|
||||||
|
const value = cfg.slack?.actions?.[key];
|
||||||
|
if (value === undefined) return defaultValue;
|
||||||
|
return value !== false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (reactionsActions.has(action)) {
|
||||||
|
if (!isActionEnabled("reactions")) {
|
||||||
|
throw new Error("Slack reactions are disabled.");
|
||||||
|
}
|
||||||
|
const channelId = readStringParam(params, "channelId", { required: true });
|
||||||
|
const messageId = readStringParam(params, "messageId", { required: true });
|
||||||
|
if (action === "react") {
|
||||||
|
const emoji = readStringParam(params, "emoji", { required: true });
|
||||||
|
await reactSlackMessage(channelId, messageId, emoji);
|
||||||
|
return jsonResult({ ok: true });
|
||||||
|
}
|
||||||
|
const reactions = await listSlackReactions(channelId, messageId);
|
||||||
|
return jsonResult({ ok: true, reactions });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messagingActions.has(action)) {
|
||||||
|
if (!isActionEnabled("messages")) {
|
||||||
|
throw new Error("Slack messages are disabled.");
|
||||||
|
}
|
||||||
|
switch (action) {
|
||||||
|
case "sendMessage": {
|
||||||
|
const to = readStringParam(params, "to", { required: true });
|
||||||
|
const content = readStringParam(params, "content", { required: true });
|
||||||
|
const mediaUrl = readStringParam(params, "mediaUrl");
|
||||||
|
const replyTo = readStringParam(params, "replyTo");
|
||||||
|
const result = await sendSlackMessage(to, content, {
|
||||||
|
mediaUrl: mediaUrl ?? undefined,
|
||||||
|
replyTo: replyTo ?? undefined,
|
||||||
|
});
|
||||||
|
return jsonResult({ ok: true, result });
|
||||||
|
}
|
||||||
|
case "editMessage": {
|
||||||
|
const channelId = readStringParam(params, "channelId", {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
const messageId = readStringParam(params, "messageId", {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
const content = readStringParam(params, "content", {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
await editSlackMessage(channelId, messageId, content);
|
||||||
|
return jsonResult({ ok: true });
|
||||||
|
}
|
||||||
|
case "deleteMessage": {
|
||||||
|
const channelId = readStringParam(params, "channelId", {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
const messageId = readStringParam(params, "messageId", {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
await deleteSlackMessage(channelId, messageId);
|
||||||
|
return jsonResult({ ok: true });
|
||||||
|
}
|
||||||
|
case "readMessages": {
|
||||||
|
const channelId = readStringParam(params, "channelId", {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
const limitRaw = params.limit;
|
||||||
|
const limit =
|
||||||
|
typeof limitRaw === "number" && Number.isFinite(limitRaw)
|
||||||
|
? limitRaw
|
||||||
|
: undefined;
|
||||||
|
const before = readStringParam(params, "before");
|
||||||
|
const after = readStringParam(params, "after");
|
||||||
|
const result = await readSlackMessages(channelId, {
|
||||||
|
limit,
|
||||||
|
before: before ?? undefined,
|
||||||
|
after: after ?? undefined,
|
||||||
|
});
|
||||||
|
return jsonResult({ ok: true, ...result });
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pinActions.has(action)) {
|
||||||
|
if (!isActionEnabled("pins")) {
|
||||||
|
throw new Error("Slack pins are disabled.");
|
||||||
|
}
|
||||||
|
const channelId = readStringParam(params, "channelId", { required: true });
|
||||||
|
if (action === "pinMessage") {
|
||||||
|
const messageId = readStringParam(params, "messageId", {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
await pinSlackMessage(channelId, messageId);
|
||||||
|
return jsonResult({ ok: true });
|
||||||
|
}
|
||||||
|
if (action === "unpinMessage") {
|
||||||
|
const messageId = readStringParam(params, "messageId", {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
await unpinSlackMessage(channelId, messageId);
|
||||||
|
return jsonResult({ ok: true });
|
||||||
|
}
|
||||||
|
const pins = await listSlackPins(channelId);
|
||||||
|
return jsonResult({ ok: true, pins });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === "memberInfo") {
|
||||||
|
if (!isActionEnabled("memberInfo")) {
|
||||||
|
throw new Error("Slack member info is disabled.");
|
||||||
|
}
|
||||||
|
const userId = readStringParam(params, "userId", { required: true });
|
||||||
|
const info = await getSlackMemberInfo(userId);
|
||||||
|
return jsonResult({ ok: true, info });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === "emojiList") {
|
||||||
|
if (!isActionEnabled("emojiList")) {
|
||||||
|
throw new Error("Slack emoji list is disabled.");
|
||||||
|
}
|
||||||
|
const emojis = await listSlackEmojis();
|
||||||
|
return jsonResult({ ok: true, emojis });
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unknown action: ${action}`);
|
||||||
|
}
|
||||||
61
src/agents/tools/slack-schema.ts
Normal file
61
src/agents/tools/slack-schema.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { Type } from "@sinclair/typebox";
|
||||||
|
|
||||||
|
export const SlackToolSchema = Type.Union([
|
||||||
|
Type.Object({
|
||||||
|
action: Type.Literal("react"),
|
||||||
|
channelId: Type.String(),
|
||||||
|
messageId: Type.String(),
|
||||||
|
emoji: Type.String(),
|
||||||
|
}),
|
||||||
|
Type.Object({
|
||||||
|
action: Type.Literal("reactions"),
|
||||||
|
channelId: Type.String(),
|
||||||
|
messageId: Type.String(),
|
||||||
|
}),
|
||||||
|
Type.Object({
|
||||||
|
action: Type.Literal("sendMessage"),
|
||||||
|
to: Type.String(),
|
||||||
|
content: Type.String(),
|
||||||
|
mediaUrl: Type.Optional(Type.String()),
|
||||||
|
replyTo: Type.Optional(Type.String()),
|
||||||
|
}),
|
||||||
|
Type.Object({
|
||||||
|
action: Type.Literal("editMessage"),
|
||||||
|
channelId: Type.String(),
|
||||||
|
messageId: Type.String(),
|
||||||
|
content: Type.String(),
|
||||||
|
}),
|
||||||
|
Type.Object({
|
||||||
|
action: Type.Literal("deleteMessage"),
|
||||||
|
channelId: Type.String(),
|
||||||
|
messageId: Type.String(),
|
||||||
|
}),
|
||||||
|
Type.Object({
|
||||||
|
action: Type.Literal("readMessages"),
|
||||||
|
channelId: Type.String(),
|
||||||
|
limit: Type.Optional(Type.Number()),
|
||||||
|
before: Type.Optional(Type.String()),
|
||||||
|
after: Type.Optional(Type.String()),
|
||||||
|
}),
|
||||||
|
Type.Object({
|
||||||
|
action: Type.Literal("pinMessage"),
|
||||||
|
channelId: Type.String(),
|
||||||
|
messageId: Type.String(),
|
||||||
|
}),
|
||||||
|
Type.Object({
|
||||||
|
action: Type.Literal("unpinMessage"),
|
||||||
|
channelId: Type.String(),
|
||||||
|
messageId: Type.String(),
|
||||||
|
}),
|
||||||
|
Type.Object({
|
||||||
|
action: Type.Literal("listPins"),
|
||||||
|
channelId: Type.String(),
|
||||||
|
}),
|
||||||
|
Type.Object({
|
||||||
|
action: Type.Literal("memberInfo"),
|
||||||
|
userId: Type.String(),
|
||||||
|
}),
|
||||||
|
Type.Object({
|
||||||
|
action: Type.Literal("emojiList"),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
18
src/agents/tools/slack-tool.ts
Normal file
18
src/agents/tools/slack-tool.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { loadConfig } from "../../config/config.js";
|
||||||
|
import type { AnyAgentTool } from "./common.js";
|
||||||
|
import { handleSlackAction } from "./slack-actions.js";
|
||||||
|
import { SlackToolSchema } from "./slack-schema.js";
|
||||||
|
|
||||||
|
export function createSlackTool(): AnyAgentTool {
|
||||||
|
return {
|
||||||
|
label: "Slack",
|
||||||
|
name: "slack",
|
||||||
|
description: "Manage Slack messages, reactions, and pins.",
|
||||||
|
parameters: SlackToolSchema,
|
||||||
|
execute: async (_toolCallId, args) => {
|
||||||
|
const params = args as Record<string, unknown>;
|
||||||
|
const cfg = loadConfig();
|
||||||
|
return await handleSlackAction(params, cfg);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ const BLOCK_CHUNK_SURFACES = new Set<TextChunkSurface>([
|
|||||||
"whatsapp",
|
"whatsapp",
|
||||||
"telegram",
|
"telegram",
|
||||||
"discord",
|
"discord",
|
||||||
|
"slack",
|
||||||
"signal",
|
"signal",
|
||||||
"imessage",
|
"imessage",
|
||||||
"webchat",
|
"webchat",
|
||||||
|
|||||||
Reference in New Issue
Block a user