From 3ad39ba3a585e90e5bd7ef6b46e0e36c0e04cdf9 Mon Sep 17 00:00:00 2001 From: Connor Lindsey Date: Mon, 21 Jul 2025 11:58:40 -0700 Subject: [PATCH 1/2] feat: add broadcasts tool for list, get, upsert, send, and cancel --- package-lock.json | 4 +- src/lib/tools/broadcasts.ts | 238 ++++++++++++++++++++++++++++++++++++ src/lib/tools/index.ts | 7 ++ 3 files changed, 247 insertions(+), 2 deletions(-) create mode 100644 src/lib/tools/broadcasts.ts diff --git a/package-lock.json b/package-lock.json index a5a8c0e..188fa6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@knocklabs/agent-toolkit", - "version": "0.2.0", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@knocklabs/agent-toolkit", - "version": "0.2.0", + "version": "0.3.0", "license": "MIT", "dependencies": { "@knocklabs/mgmt": "^0.2.0", diff --git a/src/lib/tools/broadcasts.ts b/src/lib/tools/broadcasts.ts new file mode 100644 index 0000000..0906b59 --- /dev/null +++ b/src/lib/tools/broadcasts.ts @@ -0,0 +1,238 @@ +import { WorkflowStep } from "@knocklabs/mgmt/resources/index.js"; +import { z } from "zod"; + +import { KnockTool } from "../knock-tool.js"; + +/** + * A slimmed down version of the Broadcast resource that is easier to work with in the LLM. + */ +export type SerializedBroadcast = { + key: string; + name: string; + status: string; + description: string | undefined; + categories: string[] | undefined; +}; + +export function serializeBroadcastResponse( + broadcast: any +): SerializedBroadcast { + return { + key: broadcast.key, + name: broadcast.name, + status: broadcast.status, + description: broadcast.description, + categories: broadcast.categories, + }; +} + +export function serializeFullBroadcastResponse( + broadcast: any +): SerializedBroadcast & { steps: WorkflowStep[] } { + return { + key: broadcast.key, + name: broadcast.name, + status: broadcast.status, + description: broadcast.description, + categories: broadcast.categories, + steps: broadcast.steps, + }; +} + +const listBroadcasts = KnockTool({ + method: "list_broadcasts", + name: "List broadcasts", + description: ` + List all broadcasts available for the given environment. Returns structural information about the broadcasts, including the key, name, description, categories, and status. + + Use this tool when you need to understand which broadcasts are available to be managed. + `, + parameters: z.object({ + environment: z + .string() + .optional() + .describe( + "(string): The environment to list broadcasts for. Defaults to `development`." + ), + }), + execute: (knockClient, config) => async (params) => { + const allBroadcasts: SerializedBroadcast[] = []; + const listParams = { + environment: params.environment ?? config.environment ?? "development", + }; + + for await (const broadcast of knockClient.broadcasts.list(listParams)) { + allBroadcasts.push(serializeBroadcastResponse(broadcast)); + } + + return allBroadcasts; + }, +}); + +const getBroadcast = KnockTool({ + method: "get_broadcast", + name: "Get broadcast", + description: ` + Get a broadcast by key. Returns complete information about the broadcast, including the key, name, description, categories, and status. + `, + parameters: z.object({ + environment: z + .string() + .optional() + .describe( + "(string): The environment to get the broadcast for. Defaults to `development`." + ), + broadcastKey: z + .string() + .describe("(string): The key of the broadcast to get."), + }), + execute: (knockClient, config) => async (params) => { + const broadcast = await knockClient.broadcasts.retrieve( + params.broadcastKey, + { + environment: params.environment ?? config.environment ?? "development", + } + ); + + return serializeFullBroadcastResponse(broadcast); + }, +}); + +const upsertBroadcast = KnockTool({ + method: "upsert_broadcast", + name: "Upsert broadcast", + description: ` + Create or update a broadcast. Use this tool when you need to create a new broadcast or modify an existing one. + + Broadcasts are used to send one-time messages to a target audience. They support channel, branch, and delay steps. + `, + parameters: z.object({ + environment: z + .string() + .optional() + .describe( + "(string): The environment to create/update the broadcast in. Defaults to `development`." + ), + broadcastKey: z + .string() + .describe( + "(string): The key of the broadcast to create/update. Only use a kebab-case string with no spaces or special characters." + ), + name: z.string().describe("(string): The name of the broadcast."), + description: z + .string() + .optional() + .describe("(string): The description of the broadcast."), + categories: z + .array(z.string()) + .optional() + .describe("(array): The categories to add to the broadcast."), + targetAudienceKey: z + .string() + .optional() + .describe( + "(string): The key of the audience to target for this broadcast." + ), + steps: z + .array(z.record(z.any())) + .describe( + "(array): The steps in the broadcast. Broadcasts only support channel, branch, and delay steps." + ), + settings: z + .object({ + overridePreferences: z.boolean().optional(), + isCommercial: z.boolean().optional(), + }) + .optional() + .describe("(object): Broadcast settings."), + }), + execute: (knockClient, config) => async (params) => { + const result = await knockClient.broadcasts.upsert(params.broadcastKey, { + environment: params.environment ?? config.environment ?? "development", + broadcast: { + name: params.name, + description: params.description, + categories: params.categories ?? [], + target_audience_key: params.targetAudienceKey, + steps: params.steps ?? [], + settings: params.settings, + }, + }); + + return serializeFullBroadcastResponse(result.broadcast); + }, +}); + +const sendBroadcast = KnockTool({ + method: "send_broadcast", + name: "Send broadcast", + description: ` + Send a broadcast immediately or schedule it to send at a future time. Use this tool when you need to send a broadcast to its target audience. + + If sendAt is provided, the broadcast will be scheduled to send at that time. If not provided, the broadcast will be sent immediately. + `, + parameters: z.object({ + environment: z + .string() + .optional() + .describe( + "(string): The environment to send the broadcast in. Defaults to `development`." + ), + broadcastKey: z + .string() + .describe("(string): The key of the broadcast to send."), + sendAt: z + .string() + .optional() + .describe( + "(string): When to send the broadcast. Must be in ISO 8601 UTC format. If not provided, the broadcast will be sent immediately." + ), + }), + execute: (knockClient, config) => async (params) => { + const result = await knockClient.broadcasts.send(params.broadcastKey, { + environment: params.environment ?? config.environment ?? "development", + send_at: params.sendAt, + }); + + return serializeFullBroadcastResponse(result.broadcast); + }, +}); + +const cancelBroadcast = KnockTool({ + method: "cancel_broadcast", + name: "Cancel broadcast", + description: ` + Cancel a scheduled broadcast. The broadcast will return to draft status. Use this tool when you need to cancel a broadcast that has been scheduled but not yet sent. + `, + parameters: z.object({ + environment: z + .string() + .optional() + .describe( + "(string): The environment to cancel the broadcast in. Defaults to `development`." + ), + broadcastKey: z + .string() + .describe("(string): The key of the broadcast to cancel."), + }), + execute: (knockClient, config) => async (params) => { + const result = await knockClient.broadcasts.cancel(params.broadcastKey, { + environment: params.environment ?? config.environment ?? "development", + }); + + return serializeFullBroadcastResponse(result.broadcast); + }, +}); + +export const broadcasts = { + listBroadcasts, + getBroadcast, + upsertBroadcast, + sendBroadcast, + cancelBroadcast, +}; + +export const permissions = { + read: ["listBroadcasts", "getBroadcast"], + manage: ["upsertBroadcast", "sendBroadcast", "cancelBroadcast"], +}; diff --git a/src/lib/tools/index.ts b/src/lib/tools/index.ts index 32213e7..e8223e1 100644 --- a/src/lib/tools/index.ts +++ b/src/lib/tools/index.ts @@ -1,3 +1,7 @@ +import { + broadcasts, + permissions as broadcastsPermissions, +} from "./broadcasts.js"; import { channels, permissions as channelsPermissions } from "./channels.js"; import { commits, permissions as commitsPermissions } from "./commits.js"; import { @@ -25,6 +29,7 @@ import { users, permissions as usersPermissions } from "./users.js"; import { workflows, permissions as workflowsPermissions } from "./workflows.js"; export const tools = { + broadcasts, channels, commits, documentation, @@ -41,6 +46,7 @@ export const tools = { }; export const allTools = { + ...broadcasts, ...channels, ...commits, ...documentation, @@ -57,6 +63,7 @@ export const allTools = { }; export const toolPermissions = { + broadcasts: broadcastsPermissions, channels: channelsPermissions, commits: commitsPermissions, documentation: documentationPermissions, From 7a7b66a617f2c44bf704808be09c7db86326aee7 Mon Sep 17 00:00:00 2001 From: Connor Lindsey Date: Tue, 22 Jul 2025 09:29:48 -0700 Subject: [PATCH 2/2] add changeset --- .changeset/long-worlds-invent.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/long-worlds-invent.md diff --git a/.changeset/long-worlds-invent.md b/.changeset/long-worlds-invent.md new file mode 100644 index 0000000..172e622 --- /dev/null +++ b/.changeset/long-worlds-invent.md @@ -0,0 +1,5 @@ +--- +"@knocklabs/agent-toolkit": minor +--- + +feat: add broadcast tools