From 0e44eedf8dc497fa8980c23bbeb47669c90de5b2 Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Thu, 8 Jun 2023 11:00:40 -0400 Subject: [PATCH] feat: add Novu service and tool --- legacy/docs/novu.md | 30 ++++++++++++++ legacy/src/services/novu.ts | 58 ++++++++++++++++++++++++++ legacy/src/tools/novu.ts | 82 +++++++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 legacy/docs/novu.md create mode 100644 legacy/src/services/novu.ts create mode 100644 legacy/src/tools/novu.ts diff --git a/legacy/docs/novu.md b/legacy/docs/novu.md new file mode 100644 index 00000000..b97316aa --- /dev/null +++ b/legacy/docs/novu.md @@ -0,0 +1,30 @@ +

Novu Agentic Service

+ +## Intro + +[Novu][novu] provides open-source notification infrastructure for all communication channels in one place: Email, SMS, Direct, and Push. It integrates with almost all major email providers (Mailgun, Sendgrid, Postmark, etc.), SMS providers (e.g., Twilio or Plivo), and a large selection of push and chat providers (such as OneSignal or Slack) while providing a unified API for sending notifications. + +## Pre-requisites + +Ensure the following environment variable is set: + +- `NOVU_API_KEY` - Novu API key. + +Otherwise, you can pass it in as an argument to the `Novu` constructor. + +### How to Retrieve API Key on Novu.co + +1. Open https://web.novu.co and sign in with your existing Novu account credentials (create a new account with your email address and a password or sign in with GitHub if you don't have an account yet) + +2. Navigate to "Settings" + +![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-06-08/bc87f12a-6f3b-48ef-af7f-b8f876baf912/ascreenshot.jpeg?tl_px=0,588&br_px=1120,1218&sharp=0.8&width=560&wat_scale=50&wat=1&wat_opacity=0.7&wat_gravity=northwest&wat_url=https://colony-labs-public.s3.us-east-2.amazonaws.com/images/watermarks/watermark_default.png&wat_pad=82,139) + +3. Click "API Keys" + +![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-06-08/281e025b-9e96-4973-b5eb-caf64fe659ba/ascreenshot.jpeg?tl_px=164,13&br_px=1284,643&sharp=0.8&width=560&wat_scale=50&wat=1&wat_opacity=0.7&wat_gravity=northwest&wat_url=https://colony-labs-public.s3.us-east-2.amazonaws.com/images/watermarks/watermark_default.png&wat_pad=262,139) + +4. Click here to copy your API key to your clipboard +![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-06-08/43c0f398-5896-46dd-aa63-7c4418dc0ea1/user_cropped_screenshot.jpeg?tl_px=461,47&br_px=1581,677&sharp=0.8&width=560&wat_scale=50&wat=1&wat_opacity=0.7&wat_gravity=northwest&wat_url=https://colony-labs-public.s3.us-east-2.amazonaws.com/images/watermarks/watermark_default.png&wat_pad=452,199) + +[novu]: https://novu.co/ diff --git a/legacy/src/services/novu.ts b/legacy/src/services/novu.ts new file mode 100644 index 00000000..0c1ce0af --- /dev/null +++ b/legacy/src/services/novu.ts @@ -0,0 +1,58 @@ +import ky from 'ky' + +export type NovuSubscriber = { + subscriberId: string + email?: string + firstName?: string + lastName?: string + phone?: string +} + +export type NovuTriggerEventResponse = { + data: { + acknowledged?: boolean + status?: string + transactionId?: string + } +} + +export class NovuClient { + apiKey: string + baseUrl: string + + constructor({ + apiKey = process.env.NOVU_API_KEY, + baseUrl = 'https://api.novu.co/v1' + }: { + apiKey?: string + baseUrl?: string + } = {}) { + if (!apiKey) { + throw new Error(`Error NovuClient missing required "apiKey"`) + } + this.apiKey = apiKey + this.baseUrl = baseUrl + } + + async triggerEvent( + name: string, + payload: Record, + to: NovuSubscriber[] + ) { + const url = `${this.baseUrl}/events/trigger` + const headers = { + Authorization: `ApiKey ${this.apiKey}`, + 'Content-Type': 'application/json' + } + const body = JSON.stringify({ + name, + payload, + to + }) + const response = await ky.post(url, { + headers, + body + }) + return response.json() + } +} diff --git a/legacy/src/tools/novu.ts b/legacy/src/tools/novu.ts new file mode 100644 index 00000000..6cdb8a14 --- /dev/null +++ b/legacy/src/tools/novu.ts @@ -0,0 +1,82 @@ +import { z } from 'zod' + +import * as types from '../types' +import { Agentic } from '../agentic' +import { NovuClient } from '../services/novu' +import { BaseTask } from '../task' + +export const NovuNotificationToolInputSchema = z.object({ + name: z.string(), + payload: z.record(z.unknown()), + to: z.array( + z.object({ + subscriberId: z.string(), + email: z.string().optional(), + firstName: z.string().optional(), + lastName: z.string().optional(), + phone: z.string().optional() + }) + ) +}) + +export type NovuNotificationToolInput = z.infer< + typeof NovuNotificationToolInputSchema +> + +export const NovuNotificationToolOutputSchema = z.object({ + data: z.object({ + acknowledged: z.boolean().optional(), + status: z.string().optional(), + transactionId: z.string().optional() + }) +}) + +export type NovuNotificationToolOutput = z.infer< + typeof NovuNotificationToolOutputSchema +> + +export class NovuNotificationTool extends BaseTask< + typeof NovuNotificationToolInputSchema, + typeof NovuNotificationToolOutputSchema +> { + _novuClient: NovuClient + + constructor({ + agentic, + novuClient = new NovuClient() + }: { + agentic: Agentic + novuClient?: NovuClient + }) { + super({ + agentic + }) + + this._novuClient = novuClient + } + + public override get inputSchema() { + return NovuNotificationToolInputSchema + } + + public override get outputSchema() { + return NovuNotificationToolOutputSchema + } + + protected override async _call( + input: NovuNotificationToolInput + ): Promise> { + // TODO: handle errors gracefully + input = this.inputSchema.parse(input) + + const result = await this._novuClient.triggerEvent( + input.name, + input.payload, + input.to + ) + return { + result, + metadata: {} + } + } +}