From 0e44eedf8dc497fa8980c23bbeb47669c90de5b2 Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Thu, 8 Jun 2023 11:00:40 -0400 Subject: [PATCH 1/6] 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: {} + } + } +} From 1874c25e41869f56fa234c6b32628ba1d79ec3c8 Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Thu, 8 Jun 2023 11:11:22 -0400 Subject: [PATCH 2/6] docs: update screenshot and add empty line --- legacy/docs/novu.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/legacy/docs/novu.md b/legacy/docs/novu.md index b97316aa..3d98da6f 100644 --- a/legacy/docs/novu.md +++ b/legacy/docs/novu.md @@ -22,9 +22,10 @@ Otherwise, you can pass it in as an argument to the `Novu` constructor. 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) +![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-06-08/b1036717-015e-491a-8e98-ad959c3d1e4e/user_cropped_screenshot.jpeg?tl_px=164,0&br_px=1284,630&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,83) 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/ From f15a8b9c6b79831852818bdcf52a523da12bcf5e Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Thu, 8 Jun 2023 12:16:00 -0400 Subject: [PATCH 3/6] docs: add steps for creating Novu notification template --- legacy/docs/novu.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/legacy/docs/novu.md b/legacy/docs/novu.md index 3d98da6f..7e05f9d5 100644 --- a/legacy/docs/novu.md +++ b/legacy/docs/novu.md @@ -29,3 +29,40 @@ Otherwise, you can pass it in as an argument to the `Novu` constructor. ![](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/ + +### Create a Notification Template + +For each notification type you want to send, you need to create a template in Novu. This is a one-time setup step that you can do on the Novu web interface. + +It is possible to customize the notification content at each invocation via handlebars-style placeholders. For example, you can create a template for an email notification that looks like this: + +``` +Hello {{name}}, +{{content}} +``` + +The placeholders will be replaced with the actual values of the `payload` object you pass to the `send` method. + +To create a template, follow these steps: + +1. Open https://web.novu.co and sign in with your Novu account credentials + +2. Click "Notifications" + +![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-06-08/18f9a014-9f47-473f-9ef9-acc25d15ee29/ascreenshot.jpeg?tl_px=0,87&br_px=1120,717&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=61,139) + +3. Click "Create Workflow" on the top-right + +![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-06-08/07362d5c-1823-46ee-a6b2-17a363da74d9/user_cropped_screenshot.jpeg?tl_px=317,0&br_px=1437,404&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=415,82) + +4. Double-click the "notification name" field and enter the name of the template. This is the event name with which the respective notification may be triggered from the API. + +![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-06-08/ec09f2f6-b79d-4b51-a00e-fa7314fbc62b/ascreenshot.jpeg?tl_px=64,0&br_px=1184,630&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,84) + +5. To create, for example, a SMS notification template, click and hold "SMS" and drag it to the left underneath the trigger. + +![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-06-08/09474128-3c2e-4bec-916f-23236d88b933/ascreenshot.jpeg?tl_px=803,250&br_px=1923,880&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=437,139) + +6. Click inside the "SMS message content" text field on the right and enter the content of your SMS, e.g. a handlebars placeholder such as `{{content}}`. + +![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-06-08/8cf3d995-5ef4-465c-9a25-43c680e67f8c/ascreenshot.jpeg?tl_px=745,177&br_px=1865,807&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) From e3511f3a259dba897ce9b80672bd92cba29ad591 Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Thu, 8 Jun 2023 12:33:34 -0400 Subject: [PATCH 4/6] feat: export Novu tool and service --- legacy/src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/legacy/src/index.ts b/legacy/src/index.ts index 0bfce3e5..fc51a433 100644 --- a/legacy/src/index.ts +++ b/legacy/src/index.ts @@ -8,4 +8,6 @@ export * from './human-feedback' export * from './services/metaphor' export * from './services/serpapi' +export * from './services/novu' export * from './tools/metaphor' +export * from './tools/novu' From 657214bd70a74a55080d02a1bb88c142418e243d Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Thu, 8 Jun 2023 12:34:26 -0400 Subject: [PATCH 5/6] docs: update Novu instructions --- legacy/docs/novu.md | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/legacy/docs/novu.md b/legacy/docs/novu.md index 7e05f9d5..76b6f4fd 100644 --- a/legacy/docs/novu.md +++ b/legacy/docs/novu.md @@ -28,8 +28,6 @@ Otherwise, you can pass it in as an argument to the `Novu` constructor. ![](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/ - ### Create a Notification Template For each notification type you want to send, you need to create a template in Novu. This is a one-time setup step that you can do on the Novu web interface. @@ -55,7 +53,7 @@ To create a template, follow these steps: ![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-06-08/07362d5c-1823-46ee-a6b2-17a363da74d9/user_cropped_screenshot.jpeg?tl_px=317,0&br_px=1437,404&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=415,82) -4. Double-click the "notification name" field and enter the name of the template. This is the event name with which the respective notification may be triggered from the API. +4. Double-click the "notification name" field and enter the name of the template. For this example, we choose the name `send-sms`. This is the event name with which the respective notification may be triggered from the API. ![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-06-08/ec09f2f6-b79d-4b51-a00e-fa7314fbc62b/ascreenshot.jpeg?tl_px=64,0&br_px=1184,630&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,84) @@ -66,3 +64,23 @@ To create a template, follow these steps: 6. Click inside the "SMS message content" text field on the right and enter the content of your SMS, e.g. a handlebars placeholder such as `{{content}}`. ![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-06-08/8cf3d995-5ef4-465c-9a25-43c680e67f8c/ascreenshot.jpeg?tl_px=745,177&br_px=1865,807&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) + +7. When you're done, click "Update" on the top-right. You are now ready to send SMS notifications via the API. For example, to manually trigger a notification via the Agentic Novu Service client: + +```ts +import { NovuClient } from '@agentic/core' + +const client = new NovuClient() + +client.triggerEvent('send-sms', { content: 'Hello World!' }, [{ + subscriberId: '1', + name: 'Jane Doe', + email: 'jane.doe-123@hotmail.com' + phone: '+11234567890' +}]) +``` + +The `subscriberId` is a required field with the ID of the subscriber in Novu. If a subscriber with a provided `subscriberId` does not exist yet in Novu, a new subscriber will be created before the trigger will be executed synchronously. You can find more information about subscribers [in the official Novu documentation][novu-subscribers]. + +[novu]: https://novu.co/ +[novu-subscribers]: https://docs.novu.co/platform/subscribers From 18fab41cadbdc91db4608ac8ef2708890494bc6b Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Thu, 8 Jun 2023 17:10:46 -0400 Subject: [PATCH 6/6] feat: refactor signature and add tests --- legacy/src/services/novu.ts | 23 +++++++++++++---------- legacy/src/tools/novu.ts | 6 +----- legacy/test/novu.test.ts | 27 +++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 15 deletions(-) create mode 100644 legacy/test/novu.test.ts diff --git a/legacy/src/services/novu.ts b/legacy/src/services/novu.ts index 0c1ce0af..e02c0746 100644 --- a/legacy/src/services/novu.ts +++ b/legacy/src/services/novu.ts @@ -34,24 +34,27 @@ export class NovuClient { this.baseUrl = baseUrl } - async triggerEvent( - name: string, - payload: Record, + async triggerEvent({ + name, + payload, + to + }: { + 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 + json: { + name, + payload, + to + } }) return response.json() } diff --git a/legacy/src/tools/novu.ts b/legacy/src/tools/novu.ts index 6cdb8a14..ff80eb3d 100644 --- a/legacy/src/tools/novu.ts +++ b/legacy/src/tools/novu.ts @@ -69,11 +69,7 @@ export class NovuNotificationTool extends BaseTask< // TODO: handle errors gracefully input = this.inputSchema.parse(input) - const result = await this._novuClient.triggerEvent( - input.name, - input.payload, - input.to - ) + const result = await this._novuClient.triggerEvent(input) return { result, metadata: {} diff --git a/legacy/test/novu.test.ts b/legacy/test/novu.test.ts new file mode 100644 index 00000000..5e48496b --- /dev/null +++ b/legacy/test/novu.test.ts @@ -0,0 +1,27 @@ +import test from 'ava' + +import { NovuClient } from '../src/services/novu' +import './_utils' + +test('NovuClient.triggerEvent', async (t) => { + if (!process.env.NOVU_API_KEY) { + return t.pass() + } + + t.timeout(2 * 60 * 1000) + const client = new NovuClient() + + const result = await client.triggerEvent({ + name: 'send-email', + payload: { + content: 'Hello World!' + }, + to: [ + { + subscriberId: '123456', + email: 'pburckhardt@outlook.com' + } + ] + }) + t.truthy(result) +})