From d4aed7badd612e068d4e4e4a7c49b5ff75b9d007 Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Wed, 5 Jun 2024 20:53:31 -0500 Subject: [PATCH] feat: add novu client --- readme.md | 5 +- src/services/index.ts | 1 + src/services/novu-client.ts | 161 ++++++++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 src/services/novu-client.ts diff --git a/readme.md b/readme.md index 643ad1a..dac3ae3 100644 --- a/readme.md +++ b/readme.md @@ -128,8 +128,9 @@ The SDK-specific imports are all isolated to keep the main `@agentic/stdlib` as - dexa - diffbot - exa -- firecrawl (WIP) -- midjourney +- firecrawl +- midjourney (unofficial API) +- novu - people data labs (WIP) - perigon - predict leads diff --git a/src/services/index.ts b/src/services/index.ts index c27130a..004b57b 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -5,6 +5,7 @@ export * from './diffbot-client.js' export * from './exa-client.js' export * from './firecrawl-client.js' export * from './midjourney-client.js' +export * from './novu-client.js' export * from './people-data-labs-client.js' export * from './perigon-client.js' export * from './predict-leads-client.js' diff --git a/src/services/novu-client.ts b/src/services/novu-client.ts new file mode 100644 index 0000000..7d8c80f --- /dev/null +++ b/src/services/novu-client.ts @@ -0,0 +1,161 @@ +import defaultKy, { type KyInstance } from 'ky' +import { z } from 'zod' + +import { aiFunction, AIFunctionsProvider } from '../fns.js' +import { assert, getEnv } from '../utils.js' + +export namespace novu { + export const API_BASE_URL = 'https://api.novu.co/v1' + + /** + * Novu subscriber object. + */ + export type Subscriber = { + /** + * Unique identifier for the subscriber. This can be any value that is meaningful to your application such as a user ID stored in your database or a unique email address. + */ + subscriberId: string + + /** + * Email address of the subscriber. + */ + email?: string + + /** + * First name of the subscriber. + */ + firstName?: string + + /** + * Last name of the subscriber. + */ + lastName?: string + + /** + * Phone number of the subscriber. + */ + phone?: string + } + + /** + * Response from the Novu API when triggering an event. + * + * @see {@link https://docs.novu.co/api/client-libraries#trigger-event} + */ + export type TriggerEventResponse = { + /** + * Data about the triggered event. + */ + data: { + /** + * Whether the trigger was acknowledged or not. + */ + acknowledged?: boolean + + /** + * Status for trigger. + */ + status?: string + + /** + * Transaction id for trigger. + */ + transactionId?: string + + /** + * In case of an error, this field will contain the error message. + */ + error?: Array + } + } + + /** + * Options for triggering an event in Novu. + */ + export type TriggerOptions = { + /** + * Name of the event to trigger. This should match the name of an existing notification template in Novu. + */ + name: string + + /** + * Payload to use for the event. This will be used to populate any handlebars placeholders in the notification template. + */ + payload: Record + + /** + * List of subscribers to send the notification to. Each subscriber must at least have a unique `subscriberId` to identify them in Novu and, if not already known to Novu, an `email` address or `phone` number depending on the notification template being used. + */ + to: Subscriber[] + } +} + +/** + * Client for interacting with the Novu API, a router for sending notifications + * across different channels like Email, SMS, Chat, In-App, and Push. + * + * @see https://novu.co + */ +export class NovuClient extends AIFunctionsProvider { + protected readonly ky: KyInstance + protected readonly apiKey: string + protected readonly apiBaseUrl: string + + constructor({ + apiKey = getEnv('NOVU_API_KEY'), + apiBaseUrl = novu.API_BASE_URL, + ky = defaultKy + }: { + apiKey?: string + apiBaseUrl?: string + ky?: KyInstance + } = {}) { + assert( + apiKey, + 'NovuClient missing required "apiKey" (defaults to "NOVU_API_KEY")' + ) + super() + + this.apiKey = apiKey + this.apiBaseUrl = apiBaseUrl + + this.ky = ky.extend({ + prefixUrl: this.apiBaseUrl, + headers: { + Authorization: `ApiKey ${this.apiKey}` + } + }) + } + + /** + * Triggers an event in Novu. + * + * @see https://docs.novu.co/api-reference/events/trigger-event + */ + @aiFunction({ + name: 'novu_trigger_event', + description: + 'Sends a notification to a person given their novu `subscriberId` and an `email` or `phone` number. Useful for sending emails or SMS text messages to people.', + inputSchema: z.object({ + name: z.string(), + // TODO: make this more + payload: z.record(z.any()), + to: z.array( + z.object({ + subscriberId: z.string(), + email: z.string().optional(), + firstName: z.string().optional(), + lastName: z.string().optional(), + phone: z.string().optional() + }) + ) + }) + }) + async triggerEvent(options: novu.TriggerOptions) { + return this.ky + .post('events/trigger', { + json: options + }) + .json() + } +}