feat: add message tool and CLI
This commit is contained in:
@@ -7,6 +7,7 @@ import { createCronTool } from "./tools/cron-tool.js";
|
||||
import { createDiscordTool } from "./tools/discord-tool.js";
|
||||
import { createGatewayTool } from "./tools/gateway-tool.js";
|
||||
import { createImageTool } from "./tools/image-tool.js";
|
||||
import { createMessageTool } from "./tools/message-tool.js";
|
||||
import { createNodesTool } from "./tools/nodes-tool.js";
|
||||
import { createSessionsHistoryTool } from "./tools/sessions-history-tool.js";
|
||||
import { createSessionsListTool } from "./tools/sessions-list-tool.js";
|
||||
@@ -35,6 +36,7 @@ export function createClawdbotTools(options?: {
|
||||
createNodesTool(),
|
||||
createCronTool(),
|
||||
createDiscordTool(),
|
||||
createMessageTool(),
|
||||
createSlackTool({
|
||||
agentAccountId: options?.agentAccountId,
|
||||
config: options?.config,
|
||||
|
||||
@@ -66,7 +66,7 @@ describe("createClawdbotCodingTools", () => {
|
||||
|
||||
it("preserves action enums in normalized schemas", () => {
|
||||
const tools = createClawdbotCodingTools();
|
||||
const toolNames = ["browser", "canvas", "nodes", "cron", "gateway"];
|
||||
const toolNames = ["browser", "canvas", "nodes", "cron", "gateway", "message"];
|
||||
|
||||
const collectActionValues = (
|
||||
schema: unknown,
|
||||
|
||||
@@ -150,6 +150,14 @@
|
||||
"restart": { "label": "restart", "detailKeys": ["reason", "delayMs"] }
|
||||
}
|
||||
},
|
||||
"message": {
|
||||
"emoji": "✉️",
|
||||
"title": "Message",
|
||||
"actions": {
|
||||
"send": { "label": "send", "detailKeys": ["to", "provider", "mediaUrl"] },
|
||||
"poll": { "label": "poll", "detailKeys": ["to", "provider", "question"] }
|
||||
}
|
||||
},
|
||||
"agents_list": {
|
||||
"emoji": "🧭",
|
||||
"title": "Agents",
|
||||
|
||||
112
src/agents/tools/message-tool.ts
Normal file
112
src/agents/tools/message-tool.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { Type } from "@sinclair/typebox";
|
||||
|
||||
import {
|
||||
sendMessage,
|
||||
sendPoll,
|
||||
type MessagePollResult,
|
||||
type MessageSendResult,
|
||||
} from "../../infra/outbound/message.js";
|
||||
import type { AnyAgentTool } from "./common.js";
|
||||
import {
|
||||
jsonResult,
|
||||
readNumberParam,
|
||||
readStringArrayParam,
|
||||
readStringParam,
|
||||
} from "./common.js";
|
||||
|
||||
const MessageToolSchema = Type.Object({
|
||||
action: Type.Union([Type.Literal("send"), Type.Literal("poll")]),
|
||||
to: Type.Optional(Type.String()),
|
||||
content: Type.Optional(Type.String()),
|
||||
mediaUrl: Type.Optional(Type.String()),
|
||||
gifPlayback: Type.Optional(Type.Boolean()),
|
||||
provider: Type.Optional(Type.String()),
|
||||
accountId: Type.Optional(Type.String()),
|
||||
dryRun: Type.Optional(Type.Boolean()),
|
||||
bestEffort: Type.Optional(Type.Boolean()),
|
||||
question: Type.Optional(Type.String()),
|
||||
options: Type.Optional(Type.Array(Type.String())),
|
||||
maxSelections: Type.Optional(Type.Number()),
|
||||
durationHours: Type.Optional(Type.Number()),
|
||||
gatewayUrl: Type.Optional(Type.String()),
|
||||
gatewayToken: Type.Optional(Type.String()),
|
||||
timeoutMs: Type.Optional(Type.Number()),
|
||||
});
|
||||
|
||||
export function createMessageTool(): AnyAgentTool {
|
||||
return {
|
||||
label: "Message",
|
||||
name: "message",
|
||||
description:
|
||||
"Send messages and polls across providers (send/poll). Prefer this for general outbound messaging.",
|
||||
parameters: MessageToolSchema,
|
||||
execute: async (_toolCallId, args) => {
|
||||
const params = args as Record<string, unknown>;
|
||||
const action = readStringParam(params, "action", { required: true });
|
||||
const gateway = {
|
||||
url: readStringParam(params, "gatewayUrl", { trim: false }),
|
||||
token: readStringParam(params, "gatewayToken", { trim: false }),
|
||||
timeoutMs: readNumberParam(params, "timeoutMs"),
|
||||
clientName: "agent" as const,
|
||||
mode: "agent" as const,
|
||||
};
|
||||
const dryRun = Boolean(params.dryRun);
|
||||
|
||||
if (action === "send") {
|
||||
const to = readStringParam(params, "to", { required: true });
|
||||
const content = readStringParam(params, "content", {
|
||||
required: true,
|
||||
allowEmpty: true,
|
||||
});
|
||||
const mediaUrl = readStringParam(params, "mediaUrl", { trim: false });
|
||||
const provider = readStringParam(params, "provider");
|
||||
const accountId = readStringParam(params, "accountId");
|
||||
const gifPlayback =
|
||||
typeof params.gifPlayback === "boolean" ? params.gifPlayback : false;
|
||||
const bestEffort =
|
||||
typeof params.bestEffort === "boolean" ? params.bestEffort : undefined;
|
||||
|
||||
const result: MessageSendResult = await sendMessage({
|
||||
to,
|
||||
content,
|
||||
mediaUrl: mediaUrl || undefined,
|
||||
provider: provider || undefined,
|
||||
accountId: accountId || undefined,
|
||||
gifPlayback,
|
||||
dryRun,
|
||||
bestEffort,
|
||||
gateway,
|
||||
});
|
||||
return jsonResult(result);
|
||||
}
|
||||
|
||||
if (action === "poll") {
|
||||
const to = readStringParam(params, "to", { required: true });
|
||||
const question = readStringParam(params, "question", { required: true });
|
||||
const options =
|
||||
readStringArrayParam(params, "options", { required: true }) ?? [];
|
||||
const maxSelections = readNumberParam(params, "maxSelections", {
|
||||
integer: true,
|
||||
});
|
||||
const durationHours = readNumberParam(params, "durationHours", {
|
||||
integer: true,
|
||||
});
|
||||
const provider = readStringParam(params, "provider");
|
||||
|
||||
const result: MessagePollResult = await sendPoll({
|
||||
to,
|
||||
question,
|
||||
options,
|
||||
maxSelections,
|
||||
durationHours,
|
||||
provider: provider || undefined,
|
||||
dryRun,
|
||||
gateway,
|
||||
});
|
||||
return jsonResult(result);
|
||||
}
|
||||
|
||||
throw new Error(`Unknown action: ${action}`);
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user