From 12dd2a95513a2fccb052cb9ef4b2871501b49c98 Mon Sep 17 00:00:00 2001 From: hahahafafa <94551263+hahahafafa@users.noreply.github.com> Date: Tue, 14 Nov 2023 18:51:20 -0500 Subject: [PATCH 01/46] outlook integration 1. outlookintegration.ts created 2. implement outlook api for read and send message 3. export the function to index.ts --- langchain/src/tools/index.ts | 1 + langchain/src/tools/outlookIntegration.ts | 94 +++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 langchain/src/tools/outlookIntegration.ts diff --git a/langchain/src/tools/index.ts b/langchain/src/tools/index.ts index f29955016343..3996ea21c267 100644 --- a/langchain/src/tools/index.ts +++ b/langchain/src/tools/index.ts @@ -47,3 +47,4 @@ export { formatToOpenAIFunction, formatToOpenAITool, } from "./convert_to_openai.js"; +export { OutlookIntegration } from "./outlookIntegration.js"; \ No newline at end of file diff --git a/langchain/src/tools/outlookIntegration.ts b/langchain/src/tools/outlookIntegration.ts new file mode 100644 index 000000000000..56f125b4c80f --- /dev/null +++ b/langchain/src/tools/outlookIntegration.ts @@ -0,0 +1,94 @@ +import { Tool, type ToolParams } from "./base.js"; +import fetch from 'node-fetch'; + +export interface Email { + sender: string; + subject: string; + // Add other properties as needed +} + +export class OutlookIntegration extends Tool { + accessToken: string; // Store the OAuth2 access token + + constructor(params: ToolParams, accessToken: string) { + super(params); + this.accessToken = accessToken; // Initialize with an OAuth2 access token + } + + async readEmails(): Promise { + try { + const response = await fetch("https://graph.microsoft.com/v1.0/me/mailFolders('Inbox')/messages?$select=sender,subject", { + headers: { + Authorization: `Bearer ${this.accessToken}`, + }, + }); + + if (!response.ok) { + throw new Error(`Error: ${response.status}`); + } + + const data = await response.json(); + return data.value; // Assuming 'value' contains the array of emails + } catch (error) { + console.error("Failed to read emails:", error); + throw error; + } + } + + async sendEmail(to: string, subject: string, content: string): Promise { + const message = { + message: { + subject: subject, + body: { + contentType: "Text", + content: content, + }, + toRecipients: [ + { + emailAddress: { + address: to, + }, + }, + ], + }, + }; + + try { + const response = await fetch("https://graph.microsoft.com/v1.0/me/sendMail", { + method: "POST", + headers: { + Authorization: `Bearer ${this.accessToken}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(message), + }); + + if (!response.ok) { + throw new Error(`Error: ${response.status}`); + } + + console.log("Email sent successfully"); + } catch (error) { + console.error("Failed to send email:", error); + throw error; + } + } + + // You can add more methods for other features like managing contacts, calendar, etc. + +} + + +// import fetch from "node-fetch"; + +// const accessToken = "YOUR_ACCESS_TOKEN"; + +// const response = await fetch("https://graph.microsoft.com/v1.0/me/messages", { +// headers: { +// Authorization: `Bearer ${accessToken}`, +// }, +// }); + +// const data = await response.json(); + +// console.log(data); \ No newline at end of file From 3388d62bd0c9bb867c00f17bfc7be96e1358c934 Mon Sep 17 00:00:00 2001 From: SimonLi1020 Date: Wed, 15 Nov 2023 08:48:02 -0500 Subject: [PATCH 02/46] Update outlookIntegration.ts Add description, name, and _call() --- langchain/src/tools/outlookIntegration.ts | 44 +++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/langchain/src/tools/outlookIntegration.ts b/langchain/src/tools/outlookIntegration.ts index 56f125b4c80f..eee6571022a3 100644 --- a/langchain/src/tools/outlookIntegration.ts +++ b/langchain/src/tools/outlookIntegration.ts @@ -1,20 +1,48 @@ import { Tool, type ToolParams } from "./base.js"; import fetch from 'node-fetch'; + export interface Email { sender: string; subject: string; // Add other properties as needed } +interface SendEmailParams { + to: string; + subject: string; + content: string; +} + +interface ReadEmailParams { + // Define parameters for reading emails if needed +} + +// ... other parameter types for different actions + + + +/** + * The OutlookIntegration class is a tool used to send and + * read emails. It extends the base Tool class. + */ export class OutlookIntegration extends Tool { accessToken: string; // Store the OAuth2 access token + static lc_name() { + return "Outlook Integration"; + } + name = "outlook-integration"; + description = `Useful for sending and reading emails. The input to this tool should be a valid email address.`; + constructor(params: ToolParams, accessToken: string) { super(params); this.accessToken = accessToken; // Initialize with an OAuth2 access token + this. } + + async readEmails(): Promise { try { const response = await fetch("https://graph.microsoft.com/v1.0/me/mailFolders('Inbox')/messages?$select=sender,subject", { @@ -74,6 +102,22 @@ export class OutlookIntegration extends Tool { } } + // Implement the abstract _call method + async _call(action: string, params: SendEmailParams | ReadEmailParams): Promise { + switch (action) { + case "sendEmail": + const { to, subject, content } = params as SendEmailParams; + await this.sendEmail(to, subject, content); + break; + case "readEmails": + await this.readEmails(); + break; + default: + throw new Error(`Action ${action} is not supported`); + } + } + + // You can add more methods for other features like managing contacts, calendar, etc. } From 884bab74972b65c536bddf1f437585e6717ecc56 Mon Sep 17 00:00:00 2001 From: hahahafafa <94551263+hahahafafa@users.noreply.github.com> Date: Tue, 21 Nov 2023 21:14:24 -0500 Subject: [PATCH 03/46] get message tips integrated outlook getMessageTips integrated --- langchain/src/tools/outlookIntegration.ts | 42 ++++++++++++++++++++--- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/langchain/src/tools/outlookIntegration.ts b/langchain/src/tools/outlookIntegration.ts index eee6571022a3..fe7525663607 100644 --- a/langchain/src/tools/outlookIntegration.ts +++ b/langchain/src/tools/outlookIntegration.ts @@ -19,7 +19,10 @@ interface ReadEmailParams { } // ... other parameter types for different actions - +interface GetMailTipsParams { + emailAddresses: string[]; // Array of email addresses to get mail tips for + mailTipTypes: string[]; // Types of mail tips you want to retrieve (e.g., "automaticReplies", "mailboxFullStatus") +} /** @@ -101,9 +104,36 @@ export class OutlookIntegration extends Tool { throw error; } } - - // Implement the abstract _call method - async _call(action: string, params: SendEmailParams | ReadEmailParams): Promise { + async getMailTips(params: GetMailTipsParams): Promise { + const payload = { + EmailAddresses: params.emailAddresses, + MailTipsOptions: params.mailTipTypes.join(','), + }; + + try { + const response = await fetch("https://graph.microsoft.com/v1.0/me/getMailTips", { + method: "POST", + headers: { + Authorization: `Bearer ${this.accessToken}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); + + if (!response.ok) { + throw new Error(`Error: ${response.status}`); + } + + const data = await response.json(); + return data.value; // Assuming 'value' contains the mail tips + } catch (error) { + console.error("Failed to get mail tips:", error); + throw error; + } + } + + // Combined _call method with all cases + async _call(action: string, params: SendEmailParams | ReadEmailParams | GetMailTipsParams): Promise { switch (action) { case "sendEmail": const { to, subject, content } = params as SendEmailParams; @@ -112,10 +142,14 @@ export class OutlookIntegration extends Tool { case "readEmails": await this.readEmails(); break; + case "getMailTips": + await this.getMailTips(params as GetMailTipsParams); + break; default: throw new Error(`Action ${action} is not supported`); } } + // You can add more methods for other features like managing contacts, calendar, etc. From 929f9f81a8f1feaf2103461345285f9021e1e016 Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Mon, 27 Nov 2023 13:26:43 -0500 Subject: [PATCH 04/46] added some tests and added getToken for getting Access token --- langchain/package.json | 1 + langchain/src/tools/outlookIntegration.ts | 64 +++++++++++++++++-- .../tools/tests/outlookIntegration.test.ts | 24 +++++++ yarn.lock | 19 ++++++ 4 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 langchain/src/tools/tests/outlookIntegration.test.ts diff --git a/langchain/package.json b/langchain/package.json index 171f6aa9442b..4de44f84a185 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -1359,6 +1359,7 @@ }, "dependencies": { "@anthropic-ai/sdk": "^0.6.2", + "@azure/msal-node": "^2.5.1", "ansi-styles": "^5.0.0", "binary-extensions": "^2.2.0", "camelcase": "6", diff --git a/langchain/src/tools/outlookIntegration.ts b/langchain/src/tools/outlookIntegration.ts index fe7525663607..e46bb7172ee1 100644 --- a/langchain/src/tools/outlookIntegration.ts +++ b/langchain/src/tools/outlookIntegration.ts @@ -1,5 +1,6 @@ import { Tool, type ToolParams } from "./base.js"; -import fetch from 'node-fetch'; +import { getEnvironmentVariable } from "../util/env.js"; +import * as msal from '@azure/msal-node'; export interface Email { @@ -38,13 +39,66 @@ export class OutlookIntegration extends Tool { name = "outlook-integration"; description = `Useful for sending and reading emails. The input to this tool should be a valid email address.`; - constructor(params: ToolParams, accessToken: string) { + constructor(params: ToolParams) { super(params); - this.accessToken = accessToken; // Initialize with an OAuth2 access token - this. } - + async getToken(): Promise { + if (this.accessToken) { + return this.accessToken; + } + + const AAD_ENDPOINT = "https://login.microsoftonline.com/"; + const GRAPH_ENDPOINT = "https://graph.microsoft.com/"; + + const clientId = getEnvironmentVariable("CLIENT_ID"); + const tenantId = getEnvironmentVariable("TENANT_ID"); + const clientSecret = getEnvironmentVariable("CLIENT_SECRET"); + + if (!clientId || !tenantId || !clientSecret) { + throw new Error("Missing environment variables"); + } + + const msalConfig = { + auth: { + clientId: clientId, + authority: AAD_ENDPOINT + tenantId, + clientSecret: clientSecret, + } + } + const cca = new msal.ConfidentialClientApplication(msalConfig); + const tokenRequest = { + scopes: [GRAPH_ENDPOINT + '.default'], // e.g. 'https://graph.microsoft.com/.default' + }; + const result = await cca.acquireTokenByClientCredential(tokenRequest); + if (result === null) { + throw new Error('Failed to obtain access token'); + } else { + this.accessToken = result.accessToken; + return this.accessToken; + } + } + + + async readme(): Promise { + try { + const response = await fetch("https://graph.microsoft.com/v1.0/me", { + headers: { + Authorization: `Bearer ${this.accessToken}`, + }, + }); + + if (!response.ok) { + throw new Error(`Error: ${response.status}`); + } + + const data = await response.json(); + return data.mail; // Assuming 'value' contains the array of emails + } catch (error) { + console.error("Failed to read me:", error); + throw error; + } + } async readEmails(): Promise { try { diff --git a/langchain/src/tools/tests/outlookIntegration.test.ts b/langchain/src/tools/tests/outlookIntegration.test.ts new file mode 100644 index 000000000000..bdf5dd8bc914 --- /dev/null +++ b/langchain/src/tools/tests/outlookIntegration.test.ts @@ -0,0 +1,24 @@ +import { OutlookIntegration } from '../outlookIntegration.js'; + +const toolParams = {}; + +const outlookTool = new OutlookIntegration(toolParams); + +describe("outlook integration test suite", () => { + test("Test get token", async () => { + const token = await outlookTool.getToken(); + console.log(token); + expect(token).toBeDefined(); + }); + + test("Test read me", async () => { + const email = await outlookTool.readme(); + console.log(email); + expect(email).toBeDefined(); + }); + + test("Test read emails", async () => { + const emails = await outlookTool.readEmails(); + expect(emails).toBeDefined(); + }); +}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 3d395fadd094..de9ce5421463 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4131,6 +4131,24 @@ __metadata: languageName: node linkType: hard +"@azure/msal-common@npm:14.4.0": + version: 14.4.0 + resolution: "@azure/msal-common@npm:14.4.0" + checksum: 63c63fac0915b4dbd026add260e2527138a02ba6f9ccd8d92d2641fc3e2b5d59c6943415d470e40af2f34c55e77ccfc2d3f90e8a2befc1eeae3a9896ec65ade8 + languageName: node + linkType: hard + +"@azure/msal-node@npm:^2.5.1": + version: 2.5.1 + resolution: "@azure/msal-node@npm:2.5.1" + dependencies: + "@azure/msal-common": 14.4.0 + jsonwebtoken: ^9.0.0 + uuid: ^8.3.0 + checksum: f2e5cfbfeea010e88885d19d1fca22aa70fbdc488ec58e8c76b8da8a632be002ffacddd54671c7e58162bce3f5f1ddfcb440c330ddd627aa74134b6a81d1753e + languageName: node + linkType: hard + "@azure/storage-blob@npm:^12.15.0": version: 12.15.0 resolution: "@azure/storage-blob@npm:12.15.0" @@ -22199,6 +22217,7 @@ __metadata: "@aws-sdk/client-sfn": ^3.362.0 "@aws-sdk/credential-provider-node": ^3.388.0 "@aws-sdk/types": ^3.357.0 + "@azure/msal-node": ^2.5.1 "@azure/storage-blob": ^12.15.0 "@clickhouse/client": ^0.0.14 "@cloudflare/ai": ^1.0.12 From 44928c15b73b1190c17172c72c832bb50f747dc2 Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Wed, 29 Nov 2023 06:46:24 -0500 Subject: [PATCH 05/46] move our integration to outlook folder, extract authentication to other module --- langchain/package.json | 2 + langchain/src/tools/index.ts | 2 +- langchain/src/tools/outlook/authFlowBase.ts | 13 ++ langchain/src/tools/outlook/authFlowREST.ts | 141 ++++++++++++++++++ .../tools/{ => outlook}/outlookIntegration.ts | 130 ++++++---------- .../tools/tests/outlookIntegration.test.ts | 36 +++-- yarn.lock | 18 +++ 7 files changed, 243 insertions(+), 99 deletions(-) create mode 100644 langchain/src/tools/outlook/authFlowBase.ts create mode 100644 langchain/src/tools/outlook/authFlowREST.ts rename langchain/src/tools/{ => outlook}/outlookIntegration.ts (55%) diff --git a/langchain/package.json b/langchain/package.json index 4de44f84a185..c11661a7f604 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -879,6 +879,7 @@ "@types/jsdom": "^21.1.1", "@types/lodash": "^4", "@types/mozilla-readability": "^0.2.1", + "@types/openurl": "^1", "@types/pdf-parse": "^1.1.1", "@types/pg": "^8", "@types/pg-copy-streams": "^1.2.2", @@ -1374,6 +1375,7 @@ "ml-distance": "^4.0.0", "openai": "^4.17.0", "openapi-types": "^12.1.3", + "openurl": "^1.1.1", "p-queue": "^6.6.2", "p-retry": "4", "uuid": "^9.0.0", diff --git a/langchain/src/tools/index.ts b/langchain/src/tools/index.ts index 3996ea21c267..01b981690fd5 100644 --- a/langchain/src/tools/index.ts +++ b/langchain/src/tools/index.ts @@ -47,4 +47,4 @@ export { formatToOpenAIFunction, formatToOpenAITool, } from "./convert_to_openai.js"; -export { OutlookIntegration } from "./outlookIntegration.js"; \ No newline at end of file +export { OutlookIntegration } from "./outlook/outlookIntegration.js"; \ No newline at end of file diff --git a/langchain/src/tools/outlook/authFlowBase.ts b/langchain/src/tools/outlook/authFlowBase.ts new file mode 100644 index 000000000000..5e3da2ffe9a4 --- /dev/null +++ b/langchain/src/tools/outlook/authFlowBase.ts @@ -0,0 +1,13 @@ +export abstract class AuthFlowBase { + protected clientId: string; + protected clientSecret: string; + protected redirectUri: string; + + constructor(clientId: string, clientSecret: string, redirectUri: string) { + this.clientId = clientId; + this.clientSecret = clientSecret; + this.redirectUri = redirectUri; + } + + public abstract getAccessToken(): Promise; +} \ No newline at end of file diff --git a/langchain/src/tools/outlook/authFlowREST.ts b/langchain/src/tools/outlook/authFlowREST.ts new file mode 100644 index 000000000000..5fc5c9a54718 --- /dev/null +++ b/langchain/src/tools/outlook/authFlowREST.ts @@ -0,0 +1,141 @@ +import * as http from 'http'; +import * as url from 'url'; +import * as openurl from 'openurl'; +import { AuthFlowBase } from './authFlowBase.js'; + +interface AccessTokenResponse { + access_token: string; +} + +export class AuthFlowREST extends AuthFlowBase { + + private port: number; + private pathname: string; + + constructor(clientId: string, clientSecret: string, redirectUri: string) { + super(clientId, clientSecret, redirectUri); + const parsedUrl = new URL(this.redirectUri); + this.port = parsedUrl.port ? parseInt(parsedUrl.port) : 3000; + this.pathname = parsedUrl.pathname || ''; + } + + // Function to construct the OAuth URL + private openAuthUrl(): string { + const loginEndpoint = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize'; + const clientId = this.clientId; // client ID regestered in Azure + const response_type = 'code'; + const response_mode = 'query'; + const redirectUri = encodeURIComponent(this.redirectUri); // redirect URI regestered in Azure + const scope = encodeURIComponent('openid offline_access https://graph.microsoft.com/.default'); + const state = '12345'; + + const url = [ + `${loginEndpoint}?client_id=${clientId}`, + `&response_type=${response_type}`, + `&response_mode=${response_mode}`, + `&redirect_uri=${redirectUri}`, + `&scope=${scope}`, + `&state=${state}` + ].join(''); + openurl.open(url); + return url; + } + + // Function to start the server + private startServer(): Promise { + return new Promise((resolve, reject) => { + const server = http.createServer(); + + server.listen(this.port, () => { + console.log(`Server listening at http://localhost:${this.port}`); + resolve(server); + }); + + server.on('error', (err) => { + reject(err); + }); + }); + } + + // Function to listen for the authorization code + private async listenForCode(server: http.Server): Promise { + return new Promise((resolve, reject) => { + server.on('request', (req, res) => { + try { + const reqUrl = url.parse(req.url || '', true); + + if (reqUrl.pathname === this.pathname) { + const authCode = reqUrl.query.code as string; + + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end('Authorization code received. You can close this window.'); + + server.close(); + resolve(authCode); // Resolve the Promise with the authorization code + } else { + res.writeHead(404); + res.end("404 Not Found"); + } + } catch (err) { + res.writeHead(500); + res.end('Server error'); + reject(err); + } + }); + }); + } + + // Main function to run the auth flow + private async getCode(): Promise { + // check credentials + if (!this.clientId || !this.redirectUri) { + throw new Error('Missing clientId or redirectUri.'); + } + try { + const server = await this.startServer(); + this.openAuthUrl(); + const code = await this.listenForCode(server); + return code; + } catch (err) { + console.error('Error during authentication:', err); + return ''; + } + } + + // Function to get the token using the code and client credentials + public async getAccessToken(): Promise { + // check credentials + if (!this.clientId || !this.clientSecret || !this.redirectUri) { + throw new Error('Missing clientId, clientSecret or redirectUri.'); + } + try { + const code = await this.getCode(); + const req_body = + `client_id=${encodeURIComponent(this.clientId)}&` + + `client_secret=${encodeURIComponent(this.clientSecret)}&` + + `scope=${encodeURIComponent('https://graph.microsoft.com/.default')}&` + + `redirect_uri=${encodeURIComponent(this.redirectUri)}&` + + `grant_type=authorization_code&` + + `code=${encodeURIComponent(code)}`; + + const response = await fetch('https://login.microsoftonline.com/common/oauth2/v2.0/token', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: req_body + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const json = await response.json() as AccessTokenResponse; + return json.access_token; + } catch (error) { + console.error('Error getting access token:', error); + return ''; + } + } +} + diff --git a/langchain/src/tools/outlookIntegration.ts b/langchain/src/tools/outlook/outlookIntegration.ts similarity index 55% rename from langchain/src/tools/outlookIntegration.ts rename to langchain/src/tools/outlook/outlookIntegration.ts index e46bb7172ee1..bbb3fe1ff95f 100644 --- a/langchain/src/tools/outlookIntegration.ts +++ b/langchain/src/tools/outlook/outlookIntegration.ts @@ -1,6 +1,6 @@ -import { Tool, type ToolParams } from "./base.js"; -import { getEnvironmentVariable } from "../util/env.js"; -import * as msal from '@azure/msal-node'; +// import { Tool, type ToolParams } from "../base.js"; +// import { getEnvironmentVariable } from "../../util/env.js"; +// import * as msal from '@azure/msal-node'; export interface Email { @@ -9,15 +9,15 @@ export interface Email { // Add other properties as needed } -interface SendEmailParams { - to: string; - subject: string; - content: string; -} +// interface SendEmailParams { +// to: string; +// subject: string; +// content: string; +// } -interface ReadEmailParams { - // Define parameters for reading emails if needed -} +// interface ReadEmailParams { +// // Define parameters for reading emails if needed +// } // ... other parameter types for different actions interface GetMailTipsParams { @@ -30,8 +30,8 @@ interface GetMailTipsParams { * The OutlookIntegration class is a tool used to send and * read emails. It extends the base Tool class. */ -export class OutlookIntegration extends Tool { - accessToken: string; // Store the OAuth2 access token +export class OutlookIntegration { + private accessToken: string; // Store the OAuth2 access token static lc_name() { return "Outlook Integration"; @@ -39,44 +39,10 @@ export class OutlookIntegration extends Tool { name = "outlook-integration"; description = `Useful for sending and reading emails. The input to this tool should be a valid email address.`; - constructor(params: ToolParams) { - super(params); - } - - async getToken(): Promise { - if (this.accessToken) { - return this.accessToken; - } - - const AAD_ENDPOINT = "https://login.microsoftonline.com/"; - const GRAPH_ENDPOINT = "https://graph.microsoft.com/"; - - const clientId = getEnvironmentVariable("CLIENT_ID"); - const tenantId = getEnvironmentVariable("TENANT_ID"); - const clientSecret = getEnvironmentVariable("CLIENT_SECRET"); - - if (!clientId || !tenantId || !clientSecret) { - throw new Error("Missing environment variables"); - } - - const msalConfig = { - auth: { - clientId: clientId, - authority: AAD_ENDPOINT + tenantId, - clientSecret: clientSecret, - } - } - const cca = new msal.ConfidentialClientApplication(msalConfig); - const tokenRequest = { - scopes: [GRAPH_ENDPOINT + '.default'], // e.g. 'https://graph.microsoft.com/.default' - }; - const result = await cca.acquireTokenByClientCredential(tokenRequest); - if (result === null) { - throw new Error('Failed to obtain access token'); - } else { - this.accessToken = result.accessToken; - return this.accessToken; - } + // constructor(params: ToolParams) { + // super(params); + constructor(access_token: string) { + this.accessToken = access_token; } @@ -93,7 +59,7 @@ export class OutlookIntegration extends Tool { } const data = await response.json(); - return data.mail; // Assuming 'value' contains the array of emails + return data; } catch (error) { console.error("Failed to read me:", error); throw error; @@ -104,7 +70,8 @@ export class OutlookIntegration extends Tool { try { const response = await fetch("https://graph.microsoft.com/v1.0/me/mailFolders('Inbox')/messages?$select=sender,subject", { headers: { - Authorization: `Bearer ${this.accessToken}`, + // Authorization: `Bearer ${this.accessToken}`, + Authorization: `${this.accessToken}`, }, }); @@ -120,18 +87,19 @@ export class OutlookIntegration extends Tool { } } - async sendEmail(to: string, subject: string, content: string): Promise { + // async sendEmail(to: string, subject: string, content: string): Promise { + async sendEmail(): Promise { const message = { message: { - subject: subject, + subject: "subject", body: { contentType: "Text", - content: content, + content: "content", }, toRecipients: [ { emailAddress: { - address: to, + address: "oscar17chen@gmail.com", }, }, ], @@ -158,6 +126,7 @@ export class OutlookIntegration extends Tool { throw error; } } + async getMailTips(params: GetMailTipsParams): Promise { const payload = { EmailAddresses: params.emailAddresses, @@ -187,40 +156,27 @@ export class OutlookIntegration extends Tool { } // Combined _call method with all cases - async _call(action: string, params: SendEmailParams | ReadEmailParams | GetMailTipsParams): Promise { - switch (action) { - case "sendEmail": - const { to, subject, content } = params as SendEmailParams; - await this.sendEmail(to, subject, content); - break; - case "readEmails": - await this.readEmails(); - break; - case "getMailTips": - await this.getMailTips(params as GetMailTipsParams); - break; - default: - throw new Error(`Action ${action} is not supported`); - } - } + // async _call(action: string, params: SendEmailParams | ReadEmailParams | GetMailTipsParams): Promise { + // switch (action) { + // case "sendEmail": + // const { to, subject, content } = params as SendEmailParams; + // await this.sendEmail(to, subject, content); + // break; + // case "readEmails": + // await this.readEmails(); + // break; + // case "getMailTips": + // await this.getMailTips(params as GetMailTipsParams); + // break; + // default: + // throw new Error(`Action ${action} is not supported`); + // } + // } +} // You can add more methods for other features like managing contacts, calendar, etc. -} - - -// import fetch from "node-fetch"; - -// const accessToken = "YOUR_ACCESS_TOKEN"; - -// const response = await fetch("https://graph.microsoft.com/v1.0/me/messages", { -// headers: { -// Authorization: `Bearer ${accessToken}`, -// }, -// }); -// const data = await response.json(); -// console.log(data); \ No newline at end of file diff --git a/langchain/src/tools/tests/outlookIntegration.test.ts b/langchain/src/tools/tests/outlookIntegration.test.ts index bdf5dd8bc914..a9150fbfe92f 100644 --- a/langchain/src/tools/tests/outlookIntegration.test.ts +++ b/langchain/src/tools/tests/outlookIntegration.test.ts @@ -1,24 +1,38 @@ -import { OutlookIntegration } from '../outlookIntegration.js'; +import { OutlookIntegration } from '../outlook/outlookIntegration.js'; +import { AuthFlowREST } from '../outlook/authFlowREST.js'; -const toolParams = {}; - -const outlookTool = new OutlookIntegration(toolParams); +// const toolParams = {}; +let outlookTool: OutlookIntegration; describe("outlook integration test suite", () => { - test("Test get token", async () => { - const token = await outlookTool.getToken(); - console.log(token); - expect(token).toBeDefined(); + beforeAll(async() => { + const clientId = '6e6fad12-297a-4f81-9472-11b2d07831be'; + const clientSecret = 'ACm8Q~ErjhV4weyW5uBBUoDQprkWpP.rgwwI1c-y'; + const redirectUri = 'http://localhost:3000/oauth-callback'; + const authflow = new AuthFlowREST(clientId, clientSecret, redirectUri) + const access_token = await authflow.getAccessToken(); + outlookTool = new OutlookIntegration(access_token); }); + // test("Pass", async () => { + // expect(true).toBe(true); + // }); + test("Test read me", async () => { - const email = await outlookTool.readme(); - console.log(email); - expect(email).toBeDefined(); + const res = await outlookTool.readme(); + console.log(res); + expect(res).toBeDefined(); }); test("Test read emails", async () => { const emails = await outlookTool.readEmails(); expect(emails).toBeDefined(); }); + + test("Test send email", async () => { + const res = await outlookTool.sendEmail(); + console.log(res); + expect(res).toBeDefined(); + }); + }); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index de9ce5421463..9a549ff07e33 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11503,6 +11503,15 @@ __metadata: languageName: node linkType: hard +"@types/openurl@npm:^1": + version: 1.0.3 + resolution: "@types/openurl@npm:1.0.3" + dependencies: + "@types/node": "*" + checksum: a5454ca5cc08bc857231742aecd118e600cb7bd284ba72301bb46c4e55875b6ddc58ed32776b9d71843d1180aa7430cca7eb555a3f7b536aa4b54ebcdcc2a28b + languageName: node + linkType: hard + "@types/pad-left@npm:2.1.1": version: 2.1.1 resolution: "@types/pad-left@npm:2.1.1" @@ -22260,6 +22269,7 @@ __metadata: "@types/jsdom": ^21.1.1 "@types/lodash": ^4 "@types/mozilla-readability": ^0.2.1 + "@types/openurl": ^1 "@types/pdf-parse": ^1.1.1 "@types/pg": ^8 "@types/pg-copy-streams": ^1.2.2 @@ -22331,6 +22341,7 @@ __metadata: notion-to-md: ^3.1.0 openai: ^4.17.0 openapi-types: ^12.1.3 + openurl: ^1.1.1 p-queue: ^6.6.2 p-retry: 4 pdf-parse: 1.1.1 @@ -24982,6 +24993,13 @@ __metadata: languageName: node linkType: hard +"openurl@npm:^1.1.1": + version: 1.1.1 + resolution: "openurl@npm:1.1.1" + checksum: c90f2f065bc5950f1402aff67a3ce4b5fb0e4475cb07b5ff84247686f7436fbc5bc2d0e38bda4ebc9cf8aea866788424e07f25a68f7e97502d412527964351a9 + languageName: node + linkType: hard + "option@npm:~0.2.1": version: 0.2.4 resolution: "option@npm:0.2.4" From ba5266660a89a58d202f1518ae54696e32300cf1 Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Wed, 29 Nov 2023 08:14:44 -0500 Subject: [PATCH 06/46] add get credentials from env, remove unused package --- langchain/package.json | 1 - langchain/src/tools/outlook/authFlowBase.ts | 6 +----- langchain/src/tools/outlook/authFlowREST.ts | 17 +++++++++++++++-- .../src/tools/outlook/outlookIntegration.ts | 2 -- .../tools/tests/outlookIntegration.test.ts | 5 +---- yarn.lock | 19 ------------------- 6 files changed, 17 insertions(+), 33 deletions(-) diff --git a/langchain/package.json b/langchain/package.json index c11661a7f604..f77b92456bd8 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -1360,7 +1360,6 @@ }, "dependencies": { "@anthropic-ai/sdk": "^0.6.2", - "@azure/msal-node": "^2.5.1", "ansi-styles": "^5.0.0", "binary-extensions": "^2.2.0", "camelcase": "6", diff --git a/langchain/src/tools/outlook/authFlowBase.ts b/langchain/src/tools/outlook/authFlowBase.ts index 5e3da2ffe9a4..d34be14c1bfc 100644 --- a/langchain/src/tools/outlook/authFlowBase.ts +++ b/langchain/src/tools/outlook/authFlowBase.ts @@ -1,12 +1,8 @@ export abstract class AuthFlowBase { protected clientId: string; - protected clientSecret: string; - protected redirectUri: string; - constructor(clientId: string, clientSecret: string, redirectUri: string) { + constructor(clientId: string) { this.clientId = clientId; - this.clientSecret = clientSecret; - this.redirectUri = redirectUri; } public abstract getAccessToken(): Promise; diff --git a/langchain/src/tools/outlook/authFlowREST.ts b/langchain/src/tools/outlook/authFlowREST.ts index 5fc5c9a54718..e5e62a6b03b7 100644 --- a/langchain/src/tools/outlook/authFlowREST.ts +++ b/langchain/src/tools/outlook/authFlowREST.ts @@ -2,6 +2,7 @@ import * as http from 'http'; import * as url from 'url'; import * as openurl from 'openurl'; import { AuthFlowBase } from './authFlowBase.js'; +import { getEnvironmentVariable } from '../../util/env.js'; interface AccessTokenResponse { access_token: string; @@ -9,11 +10,23 @@ interface AccessTokenResponse { export class AuthFlowREST extends AuthFlowBase { + private clientSecret: string; + private redirectUri: string; private port: number; private pathname: string; - constructor(clientId: string, clientSecret: string, redirectUri: string) { - super(clientId, clientSecret, redirectUri); + constructor(clientId?: string, clientSecret?: string, redirectUri?: string) { + if (!clientId || !clientSecret || !redirectUri) { + clientId = getEnvironmentVariable('CLIENT_ID'); + clientSecret = getEnvironmentVariable('CLIENT_SECRET'); + redirectUri = getEnvironmentVariable('REDIRECT_URI'); + } + if (!clientId || !clientSecret || !redirectUri) { + throw new Error('Missing clientId, clientSecret or redirectUri.'); + } + super(clientId); + this.clientSecret = clientSecret; + this.redirectUri = redirectUri; const parsedUrl = new URL(this.redirectUri); this.port = parsedUrl.port ? parseInt(parsedUrl.port) : 3000; this.pathname = parsedUrl.pathname || ''; diff --git a/langchain/src/tools/outlook/outlookIntegration.ts b/langchain/src/tools/outlook/outlookIntegration.ts index bbb3fe1ff95f..3f314f52a035 100644 --- a/langchain/src/tools/outlook/outlookIntegration.ts +++ b/langchain/src/tools/outlook/outlookIntegration.ts @@ -1,6 +1,4 @@ // import { Tool, type ToolParams } from "../base.js"; -// import { getEnvironmentVariable } from "../../util/env.js"; -// import * as msal from '@azure/msal-node'; export interface Email { diff --git a/langchain/src/tools/tests/outlookIntegration.test.ts b/langchain/src/tools/tests/outlookIntegration.test.ts index a9150fbfe92f..f02c535c487a 100644 --- a/langchain/src/tools/tests/outlookIntegration.test.ts +++ b/langchain/src/tools/tests/outlookIntegration.test.ts @@ -6,10 +6,7 @@ let outlookTool: OutlookIntegration; describe("outlook integration test suite", () => { beforeAll(async() => { - const clientId = '6e6fad12-297a-4f81-9472-11b2d07831be'; - const clientSecret = 'ACm8Q~ErjhV4weyW5uBBUoDQprkWpP.rgwwI1c-y'; - const redirectUri = 'http://localhost:3000/oauth-callback'; - const authflow = new AuthFlowREST(clientId, clientSecret, redirectUri) + const authflow = new AuthFlowREST() const access_token = await authflow.getAccessToken(); outlookTool = new OutlookIntegration(access_token); }); diff --git a/yarn.lock b/yarn.lock index 9a549ff07e33..296ed6437b1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4131,24 +4131,6 @@ __metadata: languageName: node linkType: hard -"@azure/msal-common@npm:14.4.0": - version: 14.4.0 - resolution: "@azure/msal-common@npm:14.4.0" - checksum: 63c63fac0915b4dbd026add260e2527138a02ba6f9ccd8d92d2641fc3e2b5d59c6943415d470e40af2f34c55e77ccfc2d3f90e8a2befc1eeae3a9896ec65ade8 - languageName: node - linkType: hard - -"@azure/msal-node@npm:^2.5.1": - version: 2.5.1 - resolution: "@azure/msal-node@npm:2.5.1" - dependencies: - "@azure/msal-common": 14.4.0 - jsonwebtoken: ^9.0.0 - uuid: ^8.3.0 - checksum: f2e5cfbfeea010e88885d19d1fca22aa70fbdc488ec58e8c76b8da8a632be002ffacddd54671c7e58162bce3f5f1ddfcb440c330ddd627aa74134b6a81d1753e - languageName: node - linkType: hard - "@azure/storage-blob@npm:^12.15.0": version: 12.15.0 resolution: "@azure/storage-blob@npm:12.15.0" @@ -22226,7 +22208,6 @@ __metadata: "@aws-sdk/client-sfn": ^3.362.0 "@aws-sdk/credential-provider-node": ^3.388.0 "@aws-sdk/types": ^3.357.0 - "@azure/msal-node": ^2.5.1 "@azure/storage-blob": ^12.15.0 "@clickhouse/client": ^0.0.14 "@cloudflare/ai": ^1.0.12 From 106868d8896869701afbc73732b9aa0adc76dad5 Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Wed, 29 Nov 2023 11:17:08 -0500 Subject: [PATCH 07/46] create entrypoints for outlook, create read and send tool extending base tool, auth using authFlowREST --- langchain/scripts/create-entrypoints.js | 1 + langchain/src/tools/outlook/base.ts | 70 +++++++++++++++++++ langchain/src/tools/outlook/descriptions.ts | 3 + langchain/src/tools/outlook/index.ts | 3 + langchain/src/tools/outlook/readMail.ts | 37 +++++++++++ langchain/src/tools/outlook/sendMail.ts | 74 +++++++++++++++++++++ 6 files changed, 188 insertions(+) create mode 100644 langchain/src/tools/outlook/base.ts create mode 100644 langchain/src/tools/outlook/descriptions.ts create mode 100644 langchain/src/tools/outlook/index.ts create mode 100644 langchain/src/tools/outlook/readMail.ts create mode 100644 langchain/src/tools/outlook/sendMail.ts diff --git a/langchain/scripts/create-entrypoints.js b/langchain/scripts/create-entrypoints.js index d02bc22c85c2..514657ece940 100644 --- a/langchain/scripts/create-entrypoints.js +++ b/langchain/scripts/create-entrypoints.js @@ -36,6 +36,7 @@ const entrypoints = { "tools/sql": "tools/sql", "tools/webbrowser": "tools/webbrowser", "tools/google_calendar": "tools/google_calendar/index", + "tools/outlook": "tools/outlook/index", // chains chains: "chains/index", "chains/combine_documents/reduce": "chains/combine_documents/reduce", diff --git a/langchain/src/tools/outlook/base.ts b/langchain/src/tools/outlook/base.ts new file mode 100644 index 000000000000..e418461ab1a6 --- /dev/null +++ b/langchain/src/tools/outlook/base.ts @@ -0,0 +1,70 @@ +import { Tool } from "../base.js"; +import { getEnvironmentVariable } from "../../util/env.js"; +import { AuthFlowREST } from "./authFlowREST.js"; + +export interface OutlookCredentials { + credentials?: { + clientId?: string; + clientSecret?: string; + redirectUri?: string; + }; +} + +export class OutlookBase extends Tool { + name = "Outlook"; + + description = + "A tool to send or read emails or do other features from Outlook."; + + protected clientId: string; + protected clientSecret: string; + protected redirectUri: string; + + constructor( + fields: OutlookCredentials = { + credentials: { + clientId: getEnvironmentVariable("OUTLOOK_CLIENT_ID"), + clientSecret: getEnvironmentVariable("OUTLOOK_CLIENT_SECRET"), + redirectUri: getEnvironmentVariable("OUTLOOK_REDIRECT_URI"), + } + } + ) { + super(...arguments); + + if (!fields.credentials) { + throw new Error("Missing credentials to authenticate to Outlook"); + } + + if (!fields.credentials.clientId) { + throw new Error( + "Missing OUTLOOK_CLIENT_ID to interact with Outlook" + ); + } + + if (!fields.credentials.clientSecret) { + throw new Error( + "Missing OUTLOOK_CLIENT_SECRET to interact with Outlook" + ); + } + + if (!fields.credentials.redirectUri) { + throw new Error( + "Missing OUTLOOK_REDIRECT_URI to interact with Outlook" + ); + } + + this.clientId = fields.credentials.clientId; + this.clientSecret = fields.credentials.clientSecret; + this.redirectUri = fields.credentials.redirectUri; + } + + async getAuth() { + const authflow = new AuthFlowREST(this.clientId, this.clientSecret, this.redirectUri); + const accessToken = await authflow.getAccessToken(); + return accessToken; + } + + async _call(input: string) { + return input; + } +} diff --git a/langchain/src/tools/outlook/descriptions.ts b/langchain/src/tools/outlook/descriptions.ts new file mode 100644 index 000000000000..538df05f61f1 --- /dev/null +++ b/langchain/src/tools/outlook/descriptions.ts @@ -0,0 +1,3 @@ +export const SEND_MAIL_TOOL_DESCRIPTION = `A tool for sending emails.`; + +export const READ_MAIL_TOOL_DESCRIPTION = `A tool for reading emails.`; \ No newline at end of file diff --git a/langchain/src/tools/outlook/index.ts b/langchain/src/tools/outlook/index.ts new file mode 100644 index 000000000000..12d8ff841210 --- /dev/null +++ b/langchain/src/tools/outlook/index.ts @@ -0,0 +1,3 @@ +export { OutlookSendMailTool } from "./sendMail.js"; +export { OutlookReadMailTool } from "./readMail.js"; +export type { OutlookCredentials } from "./base.js"; \ No newline at end of file diff --git a/langchain/src/tools/outlook/readMail.ts b/langchain/src/tools/outlook/readMail.ts new file mode 100644 index 000000000000..da915cadcccb --- /dev/null +++ b/langchain/src/tools/outlook/readMail.ts @@ -0,0 +1,37 @@ +import { OutlookBase, OutlookCredentials } from "./base.js"; +import { READ_MAIL_TOOL_DESCRIPTION } from "./descriptions.js"; + +export class OutlookReadMailTool extends OutlookBase { + name = "outlook_read_mail"; + + description = READ_MAIL_TOOL_DESCRIPTION; + + constructor(fields: OutlookCredentials) { + super(fields); + } + + async _call(query: string) { + console.log("query: ", query); + const accessToken = await this.getAuth(); + try { + const response = await fetch("https://graph.microsoft.com/v1.0/me/messages", { + method: "GET", + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }); + + if (!response.ok) { + throw new Error(`Error: ${response.status}`); + } + + + const data = await response.json(); + const emails = data.value.map((email: any) => { JSON.stringify(email); }); + return emails; + } catch (error) { + return `Failed to send email: ${error}`; + } + + } +} \ No newline at end of file diff --git a/langchain/src/tools/outlook/sendMail.ts b/langchain/src/tools/outlook/sendMail.ts new file mode 100644 index 000000000000..2c504b7dd0d3 --- /dev/null +++ b/langchain/src/tools/outlook/sendMail.ts @@ -0,0 +1,74 @@ +import { OutlookBase, OutlookCredentials } from "./base.js"; +import { SEND_MAIL_TOOL_DESCRIPTION } from "./descriptions.js"; + +export class OutlookSendMailTool extends OutlookBase { + name = "outlook_send_mail"; + + description = SEND_MAIL_TOOL_DESCRIPTION; + + constructor(fields: OutlookCredentials) { + super(fields); + } + + async _call(message: string) { + const accessToken = await this.getAuth(); + let newMessage; + try { + const { subject, content, to, cc } = JSON.parse(message); + + const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/; + + if (!Array.isArray(to) || !to.every((item: string) => emailRegex.test(item))) { + return 'To must be an array of strings'; + } + + if (cc && (!Array.isArray(cc) || !cc.every((item: string) => emailRegex.test(item)))) { + return 'CC must be an array of strings'; + } + + newMessage = { + message: { + subject: String(subject), + body: { + contentType: "Text", + content: String(content), + }, + toRecipients: to.map((address: string) => ({ + emailAddress: { + address, + }, + })), + ...(cc && { + ccRecipients: cc.map((address: string) => ({ + emailAddress: { + address, + }, + })), + }), + }, + saveToSentItems: "true", + }; + } catch (e) { + return 'Invalid JSON format'; + } + try { + const response = await fetch("https://graph.microsoft.com/v1.0/me/sendMail", { + method: "POST", + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(newMessage), + }); + + if (!response.ok) { + throw new Error(`Error: ${response.status}`); + } + + return 'Email sent'; + } catch (error) { + return `Failed to send email: ${error}`; + } + + } +} \ No newline at end of file From 3b54b6ff229af7bae1f214440b2295ab167f7782 Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Wed, 29 Nov 2023 16:01:52 -0500 Subject: [PATCH 08/46] Update index.ts --- langchain/src/tools/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/langchain/src/tools/index.ts b/langchain/src/tools/index.ts index 01b981690fd5..763dff6eb8d3 100644 --- a/langchain/src/tools/index.ts +++ b/langchain/src/tools/index.ts @@ -47,4 +47,5 @@ export { formatToOpenAIFunction, formatToOpenAITool, } from "./convert_to_openai.js"; -export { OutlookIntegration } from "./outlook/outlookIntegration.js"; \ No newline at end of file +// export { OutlookIntegration } from "./outlook/outlookIntegration.js"; +export { OutlookSendMailTool, OutlookReadMailTool } from "./outlook/index.js"; \ No newline at end of file From 5c259c7baea4bf1e72c620dccddee05c80128000 Mon Sep 17 00:00:00 2001 From: Qi123123Li <94654984+Qi123123Li@users.noreply.github.com> Date: Wed, 29 Nov 2023 21:23:45 -0500 Subject: [PATCH 09/46] reformat the read email --- langchain/src/tools/outlook/readMail.ts | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/langchain/src/tools/outlook/readMail.ts b/langchain/src/tools/outlook/readMail.ts index da915cadcccb..c3c3fd3b2290 100644 --- a/langchain/src/tools/outlook/readMail.ts +++ b/langchain/src/tools/outlook/readMail.ts @@ -1,6 +1,12 @@ import { OutlookBase, OutlookCredentials } from "./base.js"; import { READ_MAIL_TOOL_DESCRIPTION } from "./descriptions.js"; +interface Email { + subject: string; + body: { content: string; }; + sender: { emailAddress: { name: string; address: string; } }; +} + export class OutlookReadMailTool extends OutlookBase { name = "outlook_read_mail"; @@ -14,7 +20,7 @@ export class OutlookReadMailTool extends OutlookBase { console.log("query: ", query); const accessToken = await this.getAuth(); try { - const response = await fetch("https://graph.microsoft.com/v1.0/me/messages", { + const response = await fetch("https://graph.microsoft.com/v1.0/me/messages?$select=sender,subject,body", { method: "GET", headers: { Authorization: `Bearer ${accessToken}`, @@ -25,12 +31,16 @@ export class OutlookReadMailTool extends OutlookBase { throw new Error(`Error: ${response.status}`); } - - const data = await response.json(); - const emails = data.value.map((email: any) => { JSON.stringify(email); }); - return emails; + const data = await response.json() as { value: Email[] }; + const formattedEmails = data.value.map(email => { + const { subject, body, sender } = email; + const senderInfo = `${sender.emailAddress.name} ${sender.emailAddress.address}`; + return `subject: ${subject}\nsender: ${senderInfo}\nbody: ${body.content}\n`; + }).join("\n"); + + return formattedEmails; } catch (error) { - return `Failed to send email: ${error}`; + return `Failed to read email: ${error}`; } } From 3d123c5dad5bdecc44119bbf5a417d18548e94c6 Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Thu, 30 Nov 2023 02:30:57 -0500 Subject: [PATCH 10/46] now our outlook tools depend on abstract authFlowBase not concrete authFlowREST, resolved user login many times when agent call tool many times --- langchain/src/load/import_map.ts | 1 + langchain/src/tools/outlook/authFlowBase.ts | 2 + langchain/src/tools/outlook/authFlowREST.ts | 100 ++++++---- langchain/src/tools/outlook/base.ts | 71 ++----- langchain/src/tools/outlook/descriptions.ts | 16 +- langchain/src/tools/outlook/index.ts | 4 +- .../src/tools/outlook/outlookIntegration.ts | 180 ------------------ langchain/src/tools/outlook/readMail.ts | 56 +++--- langchain/src/tools/outlook/sendMail.ts | 55 +++--- .../tools/tests/outlookIntegration.test.ts | 5 +- 10 files changed, 163 insertions(+), 327 deletions(-) delete mode 100644 langchain/src/tools/outlook/outlookIntegration.ts diff --git a/langchain/src/load/import_map.ts b/langchain/src/load/import_map.ts index d968a29215d9..487369833f43 100644 --- a/langchain/src/load/import_map.ts +++ b/langchain/src/load/import_map.ts @@ -14,6 +14,7 @@ export * as agents__openai__output_parser from "../agents/openai/output_parser.j export * as base_language from "../base_language/index.js"; export * as tools from "../tools/index.js"; export * as tools__render from "../tools/render.js"; +export * as tools__outlook from "../tools/outlook/index.js"; export * as chains from "../chains/index.js"; export * as chains__combine_documents__reduce from "../chains/combine_documents/reduce.js"; export * as chains__openai_functions from "../chains/openai_functions/index.js"; diff --git a/langchain/src/tools/outlook/authFlowBase.ts b/langchain/src/tools/outlook/authFlowBase.ts index d34be14c1bfc..cc996866935b 100644 --- a/langchain/src/tools/outlook/authFlowBase.ts +++ b/langchain/src/tools/outlook/authFlowBase.ts @@ -1,9 +1,11 @@ export abstract class AuthFlowBase { protected clientId: string; + protected accessToken: string = ""; constructor(clientId: string) { this.clientId = clientId; } public abstract getAccessToken(): Promise; + public abstract refreshAccessToken(): Promise; } \ No newline at end of file diff --git a/langchain/src/tools/outlook/authFlowREST.ts b/langchain/src/tools/outlook/authFlowREST.ts index e5e62a6b03b7..db8fea740b79 100644 --- a/langchain/src/tools/outlook/authFlowREST.ts +++ b/langchain/src/tools/outlook/authFlowREST.ts @@ -6,6 +6,7 @@ import { getEnvironmentVariable } from '../../util/env.js'; interface AccessTokenResponse { access_token: string; + refresh_token: string; } export class AuthFlowREST extends AuthFlowBase { @@ -14,6 +15,7 @@ export class AuthFlowREST extends AuthFlowBase { private redirectUri: string; private port: number; private pathname: string; + private refreshToken: string = ""; constructor(clientId?: string, clientSecret?: string, redirectUri?: string) { if (!clientId || !clientSecret || !redirectUri) { @@ -84,6 +86,7 @@ export class AuthFlowREST extends AuthFlowBase { res.end('Authorization code received. You can close this window.'); server.close(); + console.log("Server closed"); resolve(authCode); // Resolve the Promise with the authorization code } else { res.writeHead(404); @@ -104,51 +107,70 @@ export class AuthFlowREST extends AuthFlowBase { if (!this.clientId || !this.redirectUri) { throw new Error('Missing clientId or redirectUri.'); } - try { - const server = await this.startServer(); - this.openAuthUrl(); - const code = await this.listenForCode(server); - return code; - } catch (err) { - console.error('Error during authentication:', err); - return ''; - } + + const server = await this.startServer(); + this.openAuthUrl(); + const code = await this.listenForCode(server); + return code; } // Function to get the token using the code and client credentials public async getAccessToken(): Promise { - // check credentials - if (!this.clientId || !this.clientSecret || !this.redirectUri) { - throw new Error('Missing clientId, clientSecret or redirectUri.'); + // fetch auth code from user login + const code = await this.getCode(); + // fetch access token using auth code + const req_body = + `client_id=${encodeURIComponent(this.clientId)}&` + + `client_secret=${encodeURIComponent(this.clientSecret)}&` + + `scope=${encodeURIComponent('https://graph.microsoft.com/.default')}&` + + `redirect_uri=${encodeURIComponent(this.redirectUri)}&` + + `grant_type=authorization_code&` + + `code=${encodeURIComponent(code)}`; + + const response = await fetch('https://login.microsoftonline.com/common/oauth2/v2.0/token', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: req_body + }); + + if (!response.ok) { + throw new Error(`fetch token error! response: ${response}`); } - try { - const code = await this.getCode(); - const req_body = - `client_id=${encodeURIComponent(this.clientId)}&` + - `client_secret=${encodeURIComponent(this.clientSecret)}&` + - `scope=${encodeURIComponent('https://graph.microsoft.com/.default')}&` + - `redirect_uri=${encodeURIComponent(this.redirectUri)}&` + - `grant_type=authorization_code&` + - `code=${encodeURIComponent(code)}`; - - const response = await fetch('https://login.microsoftonline.com/common/oauth2/v2.0/token', { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: req_body - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const json = await response.json() as AccessTokenResponse; - return json.access_token; - } catch (error) { - console.error('Error getting access token:', error); - return ''; + // save access token and refresh token + const json = await response.json() as AccessTokenResponse; + this.accessToken = json.access_token; + this.refreshToken = json.refresh_token; + return this.accessToken; + } + + public async refreshAccessToken(): Promise { + // fetch new access token using refresh token + const req_body = + `client_id=${encodeURIComponent(this.clientId)}&` + + `client_secret=${encodeURIComponent(this.clientSecret)}&` + + `scope=${encodeURIComponent('https://graph.microsoft.com/.default')}&` + + `redirect_uri=${encodeURIComponent(this.redirectUri)}&` + + `grant_type=refresh_token&` + + `refresh_token=${encodeURIComponent(this.refreshToken)}`; + + const response = await fetch('https://login.microsoftonline.com/common/oauth2/v2.0/token', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: req_body + }); + + if (!response.ok) { + throw new Error(`fetch token error! response: ${response}`); } + // save new access token + const json = await response.json() as AccessTokenResponse; + this.accessToken = json.access_token; + return this.accessToken; } + } diff --git a/langchain/src/tools/outlook/base.ts b/langchain/src/tools/outlook/base.ts index e418461ab1a6..49ed3640c787 100644 --- a/langchain/src/tools/outlook/base.ts +++ b/langchain/src/tools/outlook/base.ts @@ -1,67 +1,30 @@ import { Tool } from "../base.js"; -import { getEnvironmentVariable } from "../../util/env.js"; -import { AuthFlowREST } from "./authFlowREST.js"; - -export interface OutlookCredentials { - credentials?: { - clientId?: string; - clientSecret?: string; - redirectUri?: string; - }; -} +import { AuthFlowBase } from './authFlowBase.js'; export class OutlookBase extends Tool { name = "Outlook"; - description = - "A tool to send or read emails or do other features from Outlook."; - - protected clientId: string; - protected clientSecret: string; - protected redirectUri: string; - - constructor( - fields: OutlookCredentials = { - credentials: { - clientId: getEnvironmentVariable("OUTLOOK_CLIENT_ID"), - clientSecret: getEnvironmentVariable("OUTLOOK_CLIENT_SECRET"), - redirectUri: getEnvironmentVariable("OUTLOOK_REDIRECT_URI"), - } - } - ) { - super(...arguments); + description = "A tool to send or read emails or do other features from Outlook."; - if (!fields.credentials) { - throw new Error("Missing credentials to authenticate to Outlook"); - } + protected authFlow: AuthFlowBase; + protected accessToken: string = ""; - if (!fields.credentials.clientId) { - throw new Error( - "Missing OUTLOOK_CLIENT_ID to interact with Outlook" - ); - } - - if (!fields.credentials.clientSecret) { - throw new Error( - "Missing OUTLOOK_CLIENT_SECRET to interact with Outlook" - ); - } - - if (!fields.credentials.redirectUri) { - throw new Error( - "Missing OUTLOOK_REDIRECT_URI to interact with Outlook" - ); - } - - this.clientId = fields.credentials.clientId; - this.clientSecret = fields.credentials.clientSecret; - this.redirectUri = fields.credentials.redirectUri; + constructor(authFlow: AuthFlowBase) { + super(); + this.authFlow = authFlow; } async getAuth() { - const authflow = new AuthFlowREST(this.clientId, this.clientSecret, this.redirectUri); - const accessToken = await authflow.getAccessToken(); - return accessToken; + if (!this.accessToken) { + this.accessToken = await this.authFlow.getAccessToken(); + } else { + try { + this.accessToken = await this.authFlow.refreshAccessToken(); + } catch (error) { + this.accessToken = await this.authFlow.getAccessToken(); + } + } + return this.accessToken; } async _call(input: string) { diff --git a/langchain/src/tools/outlook/descriptions.ts b/langchain/src/tools/outlook/descriptions.ts index 538df05f61f1..e33763ed53c7 100644 --- a/langchain/src/tools/outlook/descriptions.ts +++ b/langchain/src/tools/outlook/descriptions.ts @@ -1,3 +1,17 @@ -export const SEND_MAIL_TOOL_DESCRIPTION = `A tool for sending emails.`; +export const SEND_MAIL_TOOL_DESCRIPTION = "A tool for sending emails. \ +input instructions: \ +input a JSON formatted email message with the following four keys:\ +'subject', 'content', 'to', and 'cc'.\ +The 'subject' should be a brief title for the email, \ +'content' should contain the body of the email, \ +'to' should be an array of the recipient's email address, \ +and 'cc' should be an array of any additional recipient's email address. \ +The 'cc' key is optional, just give empty array if no cc. \ +Ensure that the JSON object is correctly formatted and includes all four specified keys.\ +This is an example of a valid JSON object: \ +"; +// {\"subject\":\"Example Subject\",\"content\":\"Example Content\",\"to":[\"team@example.com\"],\"cc\":[]}\ +// "; + export const READ_MAIL_TOOL_DESCRIPTION = `A tool for reading emails.`; \ No newline at end of file diff --git a/langchain/src/tools/outlook/index.ts b/langchain/src/tools/outlook/index.ts index 12d8ff841210..32b949313ca6 100644 --- a/langchain/src/tools/outlook/index.ts +++ b/langchain/src/tools/outlook/index.ts @@ -1,3 +1,5 @@ export { OutlookSendMailTool } from "./sendMail.js"; export { OutlookReadMailTool } from "./readMail.js"; -export type { OutlookCredentials } from "./base.js"; \ No newline at end of file +export { OutlookBase } from "./base.js"; +export { AuthFlowREST } from "./authFlowREST.js"; +export { AuthFlowBase } from "./authFlowBase.js"; \ No newline at end of file diff --git a/langchain/src/tools/outlook/outlookIntegration.ts b/langchain/src/tools/outlook/outlookIntegration.ts deleted file mode 100644 index 3f314f52a035..000000000000 --- a/langchain/src/tools/outlook/outlookIntegration.ts +++ /dev/null @@ -1,180 +0,0 @@ -// import { Tool, type ToolParams } from "../base.js"; - - -export interface Email { - sender: string; - subject: string; - // Add other properties as needed -} - -// interface SendEmailParams { -// to: string; -// subject: string; -// content: string; -// } - -// interface ReadEmailParams { -// // Define parameters for reading emails if needed -// } - -// ... other parameter types for different actions -interface GetMailTipsParams { - emailAddresses: string[]; // Array of email addresses to get mail tips for - mailTipTypes: string[]; // Types of mail tips you want to retrieve (e.g., "automaticReplies", "mailboxFullStatus") -} - - -/** - * The OutlookIntegration class is a tool used to send and - * read emails. It extends the base Tool class. - */ -export class OutlookIntegration { - private accessToken: string; // Store the OAuth2 access token - - static lc_name() { - return "Outlook Integration"; - } - name = "outlook-integration"; - description = `Useful for sending and reading emails. The input to this tool should be a valid email address.`; - - // constructor(params: ToolParams) { - // super(params); - constructor(access_token: string) { - this.accessToken = access_token; - } - - - async readme(): Promise { - try { - const response = await fetch("https://graph.microsoft.com/v1.0/me", { - headers: { - Authorization: `Bearer ${this.accessToken}`, - }, - }); - - if (!response.ok) { - throw new Error(`Error: ${response.status}`); - } - - const data = await response.json(); - return data; - } catch (error) { - console.error("Failed to read me:", error); - throw error; - } - } - - async readEmails(): Promise { - try { - const response = await fetch("https://graph.microsoft.com/v1.0/me/mailFolders('Inbox')/messages?$select=sender,subject", { - headers: { - // Authorization: `Bearer ${this.accessToken}`, - Authorization: `${this.accessToken}`, - }, - }); - - if (!response.ok) { - throw new Error(`Error: ${response.status}`); - } - - const data = await response.json(); - return data.value; // Assuming 'value' contains the array of emails - } catch (error) { - console.error("Failed to read emails:", error); - throw error; - } - } - - // async sendEmail(to: string, subject: string, content: string): Promise { - async sendEmail(): Promise { - const message = { - message: { - subject: "subject", - body: { - contentType: "Text", - content: "content", - }, - toRecipients: [ - { - emailAddress: { - address: "oscar17chen@gmail.com", - }, - }, - ], - }, - }; - - try { - const response = await fetch("https://graph.microsoft.com/v1.0/me/sendMail", { - method: "POST", - headers: { - Authorization: `Bearer ${this.accessToken}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(message), - }); - - if (!response.ok) { - throw new Error(`Error: ${response.status}`); - } - - console.log("Email sent successfully"); - } catch (error) { - console.error("Failed to send email:", error); - throw error; - } - } - - async getMailTips(params: GetMailTipsParams): Promise { - const payload = { - EmailAddresses: params.emailAddresses, - MailTipsOptions: params.mailTipTypes.join(','), - }; - - try { - const response = await fetch("https://graph.microsoft.com/v1.0/me/getMailTips", { - method: "POST", - headers: { - Authorization: `Bearer ${this.accessToken}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(payload), - }); - - if (!response.ok) { - throw new Error(`Error: ${response.status}`); - } - - const data = await response.json(); - return data.value; // Assuming 'value' contains the mail tips - } catch (error) { - console.error("Failed to get mail tips:", error); - throw error; - } - } - - // Combined _call method with all cases - // async _call(action: string, params: SendEmailParams | ReadEmailParams | GetMailTipsParams): Promise { - // switch (action) { - // case "sendEmail": - // const { to, subject, content } = params as SendEmailParams; - // await this.sendEmail(to, subject, content); - // break; - // case "readEmails": - // await this.readEmails(); - // break; - // case "getMailTips": - // await this.getMailTips(params as GetMailTipsParams); - // break; - // default: - // throw new Error(`Action ${action} is not supported`); - // } - // } -} - - - - // You can add more methods for other features like managing contacts, calendar, etc. - - - diff --git a/langchain/src/tools/outlook/readMail.ts b/langchain/src/tools/outlook/readMail.ts index c3c3fd3b2290..a850b13f342f 100644 --- a/langchain/src/tools/outlook/readMail.ts +++ b/langchain/src/tools/outlook/readMail.ts @@ -1,46 +1,54 @@ -import { OutlookBase, OutlookCredentials } from "./base.js"; +import { OutlookBase } from "./base.js"; import { READ_MAIL_TOOL_DESCRIPTION } from "./descriptions.js"; +import { AuthFlowBase } from './authFlowBase.js'; interface Email { subject: string; body: { content: string; }; sender: { emailAddress: { name: string; address: string; } }; } - export class OutlookReadMailTool extends OutlookBase { name = "outlook_read_mail"; description = READ_MAIL_TOOL_DESCRIPTION; - constructor(fields: OutlookCredentials) { - super(fields); + constructor(authFlow: AuthFlowBase) { + super(authFlow); } async _call(query: string) { console.log("query: ", query); - const accessToken = await this.getAuth(); try { - const response = await fetch("https://graph.microsoft.com/v1.0/me/messages?$select=sender,subject,body", { - method: "GET", - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }); - - if (!response.ok) { - throw new Error(`Error: ${response.status}`); - } + await this.getAuth(); + } catch (error) { + return `Failed to get access token: ${error}`; + } + // fetch emails from me/messages + const response = await fetch("https://graph.microsoft.com/v1.0/me/messages?$select=sender,subject,body", { + method: "GET", + headers: { + Authorization: `Bearer ${this.accessToken}`, + }, + }); + + if (!response.ok) { + return `Fetch mail error: ${response.status}`; + } + try { + // parse response + const data = await response.json() as { value: Email[] }; + const formattedEmails = data.value.map(email => { + const subject = email?.subject ?? 'No subject'; + const bodyContent = email?.body?.content ?? 'No content'; + const senderName = email?.sender?.emailAddress?.name ?? 'Unknown Sender'; + const senderAddress = email?.sender?.emailAddress?.address ?? 'No address'; + // Constructing the email string + return `subject: ${subject}\nsender: ${senderName} ${senderAddress}\nbody: ${bodyContent}\n`; + }).join("\n"); - const data = await response.json() as { value: Email[] }; - const formattedEmails = data.value.map(email => { - const { subject, body, sender } = email; - const senderInfo = `${sender.emailAddress.name} ${sender.emailAddress.address}`; - return `subject: ${subject}\nsender: ${senderInfo}\nbody: ${body.content}\n`; - }).join("\n"); - - return formattedEmails; + return formattedEmails; } catch (error) { - return `Failed to read email: ${error}`; + return `Failed to parse response: ${error}`; } } diff --git a/langchain/src/tools/outlook/sendMail.ts b/langchain/src/tools/outlook/sendMail.ts index 2c504b7dd0d3..fe3e26623534 100644 --- a/langchain/src/tools/outlook/sendMail.ts +++ b/langchain/src/tools/outlook/sendMail.ts @@ -1,21 +1,28 @@ -import { OutlookBase, OutlookCredentials } from "./base.js"; +import { OutlookBase } from "./base.js"; import { SEND_MAIL_TOOL_DESCRIPTION } from "./descriptions.js"; +import { AuthFlowBase } from './authFlowBase.js'; export class OutlookSendMailTool extends OutlookBase { name = "outlook_send_mail"; description = SEND_MAIL_TOOL_DESCRIPTION; - constructor(fields: OutlookCredentials) { - super(fields); + constructor(authFlow: AuthFlowBase) { + super(authFlow); } async _call(message: string) { - const accessToken = await this.getAuth(); - let newMessage; try { - const { subject, content, to, cc } = JSON.parse(message); + await this.getAuth(); + } catch (error) { + return `Failed to get access token: ${error}`; + } + let newMessage: string; + try { + // parse message + const { subject, content, to, cc } = JSON.parse(message); + // validate message const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/; if (!Array.isArray(to) || !to.every((item: string) => emailRegex.test(item))) { @@ -25,8 +32,8 @@ export class OutlookSendMailTool extends OutlookBase { if (cc && (!Array.isArray(cc) || !cc.every((item: string) => emailRegex.test(item)))) { return 'CC must be an array of strings'; } - - newMessage = { + // create new message + newMessage = JSON.stringify({ message: { subject: String(subject), body: { @@ -47,28 +54,24 @@ export class OutlookSendMailTool extends OutlookBase { }), }, saveToSentItems: "true", - }; + }); } catch (e) { return 'Invalid JSON format'; } - try { - const response = await fetch("https://graph.microsoft.com/v1.0/me/sendMail", { - method: "POST", - headers: { - Authorization: `Bearer ${accessToken}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(newMessage), - }); - - if (!response.ok) { - throw new Error(`Error: ${response.status}`); - } - return 'Email sent'; - } catch (error) { - return `Failed to send email: ${error}`; + const response = await fetch("https://graph.microsoft.com/v1.0/me/sendMail", { + method: "POST", + headers: { + Authorization: `Bearer ${this.accessToken}`, + 'Content-Type': 'application/json', + }, + body: newMessage, + }); + + if (!response.ok) { + return `Send mail error: ${response.status}`; } - + + return 'Email sent'; } } \ No newline at end of file diff --git a/langchain/src/tools/tests/outlookIntegration.test.ts b/langchain/src/tools/tests/outlookIntegration.test.ts index f02c535c487a..47a3096f72aa 100644 --- a/langchain/src/tools/tests/outlookIntegration.test.ts +++ b/langchain/src/tools/tests/outlookIntegration.test.ts @@ -1,8 +1,9 @@ -import { OutlookIntegration } from '../outlook/outlookIntegration.js'; +// import { OutlookIntegration } from '../outlook/outlookIntegration.js'; +import { OutlookReadMailTool, OutlookSendMailTool } from '../outlook/index.js'; import { AuthFlowREST } from '../outlook/authFlowREST.js'; // const toolParams = {}; -let outlookTool: OutlookIntegration; + describe("outlook integration test suite", () => { beforeAll(async() => { From df52718dc10018822639e1a8249845cc9b1932e1 Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Thu, 30 Nov 2023 05:17:09 -0500 Subject: [PATCH 11/46] added two authentication flow, make outlook tool construct more simple, improve readMail, add test --- langchain/src/tools/outlook/authFlowREST.ts | 6 +- langchain/src/tools/outlook/authFlowToken.ts | 89 +++++++++++++++++++ langchain/src/tools/outlook/base.ts | 18 +++- langchain/src/tools/outlook/descriptions.ts | 27 +++++- langchain/src/tools/outlook/index.ts | 1 + langchain/src/tools/outlook/readMail.ts | 14 +-- langchain/src/tools/outlook/sendMail.ts | 8 +- .../tools/tests/outlookIntegration.test.ts | 69 +++++++++----- 8 files changed, 195 insertions(+), 37 deletions(-) create mode 100644 langchain/src/tools/outlook/authFlowToken.ts diff --git a/langchain/src/tools/outlook/authFlowREST.ts b/langchain/src/tools/outlook/authFlowREST.ts index db8fea740b79..5732f8383663 100644 --- a/langchain/src/tools/outlook/authFlowREST.ts +++ b/langchain/src/tools/outlook/authFlowREST.ts @@ -19,9 +19,9 @@ export class AuthFlowREST extends AuthFlowBase { constructor(clientId?: string, clientSecret?: string, redirectUri?: string) { if (!clientId || !clientSecret || !redirectUri) { - clientId = getEnvironmentVariable('CLIENT_ID'); - clientSecret = getEnvironmentVariable('CLIENT_SECRET'); - redirectUri = getEnvironmentVariable('REDIRECT_URI'); + clientId = getEnvironmentVariable('OUTLOOK_CLIENT_ID'); + clientSecret = getEnvironmentVariable('OUTLOOK_CLIENT_SECRET'); + redirectUri = getEnvironmentVariable('OUTLOOK_REDIRECT_URI'); } if (!clientId || !clientSecret || !redirectUri) { throw new Error('Missing clientId, clientSecret or redirectUri.'); diff --git a/langchain/src/tools/outlook/authFlowToken.ts b/langchain/src/tools/outlook/authFlowToken.ts new file mode 100644 index 000000000000..af9fd3648479 --- /dev/null +++ b/langchain/src/tools/outlook/authFlowToken.ts @@ -0,0 +1,89 @@ +import { AuthFlowBase } from './authFlowBase.js'; +import { getEnvironmentVariable } from '../../util/env.js'; + +interface AccessTokenResponse { + access_token: string; + refresh_token: string; +} + +// if you have the token, and no need to refresh it, warning: token expires in 1 hour +export class AuthFlowToken extends AuthFlowBase { + + constructor(accessToken?: string) { + if (!accessToken) { + accessToken = getEnvironmentVariable('OUTLOOK_ACCESS_TOKEN'); + } + if (!accessToken) { + throw new Error('Missing access_token.'); + } + super(""); + this.accessToken = accessToken; + } + + public async refreshAccessToken(): Promise { + return this.accessToken; + } + + public async getAccessToken(): Promise { + return this.accessToken; + } +} + +// if you have the refresh token and other credentials +export class AuthFlowRefresh extends AuthFlowBase { + + private clientSecret: string; + private redirectUri: string; + private refreshToken: string; + + constructor(clientId?: string, clientSecret?: string, redirectUri?: string, refreshToken?: string) { + if (!clientId || !clientSecret || !redirectUri || !refreshToken) { + clientId = getEnvironmentVariable('OUTLOOK_CLIENT_ID'); + clientSecret = getEnvironmentVariable('OUTLOOK_CLIENT_SECRET'); + redirectUri = getEnvironmentVariable('OUTLOOK_REDIRECT_URI'); + refreshToken = getEnvironmentVariable('OUTLOOK_REFRESH_TOKEN'); + } + if (!clientId || !clientSecret || !redirectUri || !refreshToken) { + throw new Error('Missing clientId, clientSecret, redirectUri or refreshToken.'); + } + super(clientId); + this.clientSecret = clientSecret; + this.redirectUri = redirectUri; + this.refreshToken = refreshToken; + } + + public async refreshAccessToken(): Promise { + // fetch new access token using refresh token + const req_body = + `client_id=${encodeURIComponent(this.clientId)}&` + + `client_secret=${encodeURIComponent(this.clientSecret)}&` + + `scope=${encodeURIComponent('https://graph.microsoft.com/.default')}&` + + `redirect_uri=${encodeURIComponent(this.redirectUri)}&` + + `grant_type=refresh_token&` + + `refresh_token=${encodeURIComponent(this.refreshToken)}`; + + const response = await fetch('https://login.microsoftonline.com/common/oauth2/v2.0/token', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: req_body + }); + + if (!response.ok) { + throw new Error(`fetch token error! response: ${response}`); + } + // save new access token + const json = await response.json() as AccessTokenResponse; + this.accessToken = json.access_token; + return this.accessToken; + } + + // Function to get the token using the code and client credentials + public async getAccessToken(): Promise { + const accessToken = await this.refreshAccessToken(); + this.accessToken = accessToken; + return accessToken; + } +} + diff --git a/langchain/src/tools/outlook/base.ts b/langchain/src/tools/outlook/base.ts index 49ed3640c787..eecf251b4e92 100644 --- a/langchain/src/tools/outlook/base.ts +++ b/langchain/src/tools/outlook/base.ts @@ -1,5 +1,7 @@ import { Tool } from "../base.js"; import { AuthFlowBase } from './authFlowBase.js'; +import { AuthFlowToken, AuthFlowRefresh } from './authFlowToken.js'; +import { AuthFlowREST } from './authFlowREST.js'; export class OutlookBase extends Tool { name = "Outlook"; @@ -9,9 +11,21 @@ export class OutlookBase extends Tool { protected authFlow: AuthFlowBase; protected accessToken: string = ""; - constructor(authFlow: AuthFlowBase) { + constructor(authFlow?: AuthFlowBase, choice?: string) { super(); - this.authFlow = authFlow; + if (authFlow) { + this.authFlow = authFlow; + } else { + if (choice === "token") { + this.authFlow = new AuthFlowToken(); + } else if (choice === "refresh") { + this.authFlow = new AuthFlowRefresh(); + } else if (choice === "rest") { + this.authFlow = new AuthFlowREST(); + } else { + throw new Error("Incorrect choice of built-in authFlow."); + } + } } async getAuth() { diff --git a/langchain/src/tools/outlook/descriptions.ts b/langchain/src/tools/outlook/descriptions.ts index e33763ed53c7..2d3ff1f8ba48 100644 --- a/langchain/src/tools/outlook/descriptions.ts +++ b/langchain/src/tools/outlook/descriptions.ts @@ -8,10 +8,31 @@ The 'subject' should be a brief title for the email, \ and 'cc' should be an array of any additional recipient's email address. \ The 'cc' key is optional, just give empty array if no cc. \ Ensure that the JSON object is correctly formatted and includes all four specified keys.\ -This is an example of a valid JSON object: \ "; -// {\"subject\":\"Example Subject\",\"content\":\"Example Content\",\"to":[\"team@example.com\"],\"cc\":[]}\ +// This is an example of a valid JSON object: \ +// {\"subject\":\"Example Subject\",\"content\":\"Example Content\",\"to\":[\"team@example.com\"],\"cc\":[]}\ // "; -export const READ_MAIL_TOOL_DESCRIPTION = `A tool for reading emails.`; \ No newline at end of file +export const READ_MAIL_TOOL_DESCRIPTION = `A tool for reading emails.\ +You can search messages based on a value in specific message properties. \ +The results of the search are sorted by the date and time that the message was sent.\ +A $search request returns up to 1000 results.\ +If you do a search on messages and specify only a value without specific message properties, \ +the search is carried out on the default search properties of from, subject, and body.\ +Alternatively, you can search messages by specifying message property names in the following table\ +and a value to search for in those properties.\n\ +body: The body of an email message.\n\ +cc: The cc field of an email message, specified as an SMTP address, display name, or alias.\n\ +from: The sender of an email message, specified as an SMTP address, display name, or alias.\n\ +received: The date that an email message was received by a recipient. e.g. 07/23/2018\n\ +recipients: The to, cc, and bcc fields of an email meesage,\n\ +sent: The date that an email message was sent by the sender. e.g. 07/23/2018\n\ +subject: The text in the subject line of an email message.\n\ +to: The to field of an email message,\n\ +INPUT:\n\ +input empty string to get all emails.\n\ +If on default: $search=\"\" \n\ +On specified property: $search=\":\"\n\ +Example: $search=\"sent:07/23/2018\" +`; \ No newline at end of file diff --git a/langchain/src/tools/outlook/index.ts b/langchain/src/tools/outlook/index.ts index 32b949313ca6..f8d344a3edde 100644 --- a/langchain/src/tools/outlook/index.ts +++ b/langchain/src/tools/outlook/index.ts @@ -2,4 +2,5 @@ export { OutlookSendMailTool } from "./sendMail.js"; export { OutlookReadMailTool } from "./readMail.js"; export { OutlookBase } from "./base.js"; export { AuthFlowREST } from "./authFlowREST.js"; +export { AuthFlowToken, AuthFlowRefresh } from "./authFlowToken.js"; export { AuthFlowBase } from "./authFlowBase.js"; \ No newline at end of file diff --git a/langchain/src/tools/outlook/readMail.ts b/langchain/src/tools/outlook/readMail.ts index a850b13f342f..cae4920fe2c3 100644 --- a/langchain/src/tools/outlook/readMail.ts +++ b/langchain/src/tools/outlook/readMail.ts @@ -12,19 +12,23 @@ export class OutlookReadMailTool extends OutlookBase { description = READ_MAIL_TOOL_DESCRIPTION; - constructor(authFlow: AuthFlowBase) { - super(authFlow); + constructor(authFlow?: AuthFlowBase, choice?: string) { + super(authFlow, choice); } async _call(query: string) { - console.log("query: ", query); try { - await this.getAuth(); + await this.getAuth(); } catch (error) { return `Failed to get access token: ${error}`; } + // validate query + const queryRegex = /^\$search="(?:body|cc|from|received|recipients|sent|subject|to)(?::[^"]*)?"$/; + if (query && !queryRegex.test(query)) { + return 'Invalid query format'; + } // fetch emails from me/messages - const response = await fetch("https://graph.microsoft.com/v1.0/me/messages?$select=sender,subject,body", { + const response = await fetch(`https://graph.microsoft.com/v1.0/me/messages?${query}&$top=5`, { method: "GET", headers: { Authorization: `Bearer ${this.accessToken}`, diff --git a/langchain/src/tools/outlook/sendMail.ts b/langchain/src/tools/outlook/sendMail.ts index fe3e26623534..7e4684faa525 100644 --- a/langchain/src/tools/outlook/sendMail.ts +++ b/langchain/src/tools/outlook/sendMail.ts @@ -7,8 +7,8 @@ export class OutlookSendMailTool extends OutlookBase { description = SEND_MAIL_TOOL_DESCRIPTION; - constructor(authFlow: AuthFlowBase) { - super(authFlow); + constructor(authFlow?: AuthFlowBase, choice?: string) { + super(authFlow, choice); } async _call(message: string) { @@ -26,11 +26,11 @@ export class OutlookSendMailTool extends OutlookBase { const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/; if (!Array.isArray(to) || !to.every((item: string) => emailRegex.test(item))) { - return 'To must be an array of strings'; + return 'TO must be an array of valid email in strings'; } if (cc && (!Array.isArray(cc) || !cc.every((item: string) => emailRegex.test(item)))) { - return 'CC must be an array of strings'; + return 'CC must be an array of valid email in strings'; } // create new message newMessage = JSON.stringify({ diff --git a/langchain/src/tools/tests/outlookIntegration.test.ts b/langchain/src/tools/tests/outlookIntegration.test.ts index 47a3096f72aa..5f95aaea3428 100644 --- a/langchain/src/tools/tests/outlookIntegration.test.ts +++ b/langchain/src/tools/tests/outlookIntegration.test.ts @@ -1,36 +1,65 @@ -// import { OutlookIntegration } from '../outlook/outlookIntegration.js'; import { OutlookReadMailTool, OutlookSendMailTool } from '../outlook/index.js'; -import { AuthFlowREST } from '../outlook/authFlowREST.js'; -// const toolParams = {}; +describe("OutlookReadMailTool Test", () => { -describe("outlook integration test suite", () => { - beforeAll(async() => { - const authflow = new AuthFlowREST() - const access_token = await authflow.getAccessToken(); - outlookTool = new OutlookIntegration(access_token); + test("Test read messages", async () => { + const outlookTool = new OutlookReadMailTool(undefined, "refresh"); + const emails = await outlookTool._call(""); + console.log(emails); + expect(true).toBe(true); }); - // test("Pass", async () => { - // expect(true).toBe(true); - // }); + test("Test invalid query format", async () => { + const outlookTool = new OutlookReadMailTool(undefined, "refresh"); + const emails = await outlookTool._call("blah"); + console.log(emails); + expect(emails).toBe("Invalid query format"); + }); + + test("Test query correct format", async () => { + const outlookTool = new OutlookReadMailTool(undefined, "refresh"); + const emails = await outlookTool._call("$search=\"subject:hello\""); + console.log(emails); + expect(true).toBe(true); + }); - test("Test read me", async () => { - const res = await outlookTool.readme(); +}); + +describe("OutlookSendMailTool Test", () => { + + test("Test invalid TO email address", async () => { + const message = JSON.stringify({ + subject: "test", + content: "test", + to: ["testemail"], + cc: [], + }); + const outlookTool = new OutlookSendMailTool(undefined, "refresh"); + const res = await outlookTool._call(message); console.log(res); - expect(res).toBeDefined(); + expect(res).toBe("TO must be an array of valid email in strings"); }); - test("Test read emails", async () => { - const emails = await outlookTool.readEmails(); - expect(emails).toBeDefined(); + test("Test invalid CC email address", async () => { + const message = JSON.stringify({ + subject: "test", + content: "test", + to: ["test@email.com"], + cc: ["blah"], + }); + const outlookTool = new OutlookSendMailTool(undefined, "refresh"); + const res = await outlookTool._call(message); + console.log(res); + expect(res).toBe("CC must be an array of valid email in strings"); }); - test("Test send email", async () => { - const res = await outlookTool.sendEmail(); + test("Test invalid JSON format", async () => { + const message = "blah"; + const outlookTool = new OutlookSendMailTool(undefined, "refresh"); + const res = await outlookTool._call(message); console.log(res); - expect(res).toBeDefined(); + expect(res).toBe("Invalid JSON format"); }); }); \ No newline at end of file From 5fc99722abd7701b1c381f9ccb184797dfede2ac Mon Sep 17 00:00:00 2001 From: Qi123123Li <94654984+Qi123123Li@users.noreply.github.com> Date: Thu, 30 Nov 2023 10:06:54 -0500 Subject: [PATCH 12/46] change the code to fit standard lint rules --- langchain/src/tools/index.ts | 2 +- langchain/src/tools/outlook/authFlowBase.ts | 18 +- langchain/src/tools/outlook/authFlowREST.ts | 333 +++++++++--------- langchain/src/tools/outlook/authFlowToken.ts | 156 ++++---- langchain/src/tools/outlook/base.ts | 12 +- langchain/src/tools/outlook/descriptions.ts | 68 ++-- langchain/src/tools/outlook/index.ts | 2 +- langchain/src/tools/outlook/readMail.ts | 53 +-- langchain/src/tools/outlook/sendMail.ts | 102 +++--- .../tools/tests/outlookIntegration.test.ts | 25 +- 10 files changed, 410 insertions(+), 361 deletions(-) diff --git a/langchain/src/tools/index.ts b/langchain/src/tools/index.ts index 763dff6eb8d3..a5041c5574c9 100644 --- a/langchain/src/tools/index.ts +++ b/langchain/src/tools/index.ts @@ -48,4 +48,4 @@ export { formatToOpenAITool, } from "./convert_to_openai.js"; // export { OutlookIntegration } from "./outlook/outlookIntegration.js"; -export { OutlookSendMailTool, OutlookReadMailTool } from "./outlook/index.js"; \ No newline at end of file +export { OutlookSendMailTool, OutlookReadMailTool } from "./outlook/index.js"; diff --git a/langchain/src/tools/outlook/authFlowBase.ts b/langchain/src/tools/outlook/authFlowBase.ts index cc996866935b..54ff555877ba 100644 --- a/langchain/src/tools/outlook/authFlowBase.ts +++ b/langchain/src/tools/outlook/authFlowBase.ts @@ -1,11 +1,13 @@ export abstract class AuthFlowBase { - protected clientId: string; - protected accessToken: string = ""; + protected clientId: string; - constructor(clientId: string) { - this.clientId = clientId; - } + protected accessToken: ""; - public abstract getAccessToken(): Promise; - public abstract refreshAccessToken(): Promise; -} \ No newline at end of file + constructor(clientId: string) { + this.clientId = clientId; + } + + public abstract getAccessToken(): Promise; + + public abstract refreshAccessToken(): Promise; +} diff --git a/langchain/src/tools/outlook/authFlowREST.ts b/langchain/src/tools/outlook/authFlowREST.ts index 5732f8383663..834944070391 100644 --- a/langchain/src/tools/outlook/authFlowREST.ts +++ b/langchain/src/tools/outlook/authFlowREST.ts @@ -1,176 +1,189 @@ -import * as http from 'http'; -import * as url from 'url'; -import * as openurl from 'openurl'; -import { AuthFlowBase } from './authFlowBase.js'; -import { getEnvironmentVariable } from '../../util/env.js'; +import * as http from "http"; +import * as url from "url"; +import * as openurl from "openurl"; +import { AuthFlowBase } from "./authFlowBase.js"; +import { getEnvironmentVariable } from "../../util/env.js"; interface AccessTokenResponse { - access_token: string; - refresh_token: string; + access_token: string; + refresh_token: string; } export class AuthFlowREST extends AuthFlowBase { + private clientSecret: string; - private clientSecret: string; - private redirectUri: string; - private port: number; - private pathname: string; - private refreshToken: string = ""; - - constructor(clientId?: string, clientSecret?: string, redirectUri?: string) { - if (!clientId || !clientSecret || !redirectUri) { - clientId = getEnvironmentVariable('OUTLOOK_CLIENT_ID'); - clientSecret = getEnvironmentVariable('OUTLOOK_CLIENT_SECRET'); - redirectUri = getEnvironmentVariable('OUTLOOK_REDIRECT_URI'); - } - if (!clientId || !clientSecret || !redirectUri) { - throw new Error('Missing clientId, clientSecret or redirectUri.'); - } - super(clientId); - this.clientSecret = clientSecret; - this.redirectUri = redirectUri; - const parsedUrl = new URL(this.redirectUri); - this.port = parsedUrl.port ? parseInt(parsedUrl.port) : 3000; - this.pathname = parsedUrl.pathname || ''; - } + private redirectUri: string; - // Function to construct the OAuth URL - private openAuthUrl(): string { - const loginEndpoint = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize'; - const clientId = this.clientId; // client ID regestered in Azure - const response_type = 'code'; - const response_mode = 'query'; - const redirectUri = encodeURIComponent(this.redirectUri); // redirect URI regestered in Azure - const scope = encodeURIComponent('openid offline_access https://graph.microsoft.com/.default'); - const state = '12345'; - - const url = [ - `${loginEndpoint}?client_id=${clientId}`, - `&response_type=${response_type}`, - `&response_mode=${response_mode}`, - `&redirect_uri=${redirectUri}`, - `&scope=${scope}`, - `&state=${state}` - ].join(''); - openurl.open(url); - return url; - } + private port: number; - // Function to start the server - private startServer(): Promise { - return new Promise((resolve, reject) => { - const server = http.createServer(); - - server.listen(this.port, () => { - console.log(`Server listening at http://localhost:${this.port}`); - resolve(server); - }); - - server.on('error', (err) => { - reject(err); - }); - }); - } - - // Function to listen for the authorization code - private async listenForCode(server: http.Server): Promise { - return new Promise((resolve, reject) => { - server.on('request', (req, res) => { - try { - const reqUrl = url.parse(req.url || '', true); - - if (reqUrl.pathname === this.pathname) { - const authCode = reqUrl.query.code as string; - - res.writeHead(200, { 'Content-Type': 'text/html' }); - res.end('Authorization code received. You can close this window.'); - - server.close(); - console.log("Server closed"); - resolve(authCode); // Resolve the Promise with the authorization code - } else { - res.writeHead(404); - res.end("404 Not Found"); - } - } catch (err) { - res.writeHead(500); - res.end('Server error'); - reject(err); - } - }); - }); - } + private pathname: string; - // Main function to run the auth flow - private async getCode(): Promise { - // check credentials - if (!this.clientId || !this.redirectUri) { - throw new Error('Missing clientId or redirectUri.'); - } + private refreshToken: ""; - const server = await this.startServer(); - this.openAuthUrl(); - const code = await this.listenForCode(server); - return code; + constructor({ clientId, clientSecret, redirectUri }: { clientId?: string, clientSecret?: string, redirectUri?: string } = {}) { + let id = clientId; + let secret = clientSecret; + let uri = redirectUri; + if (!id || !secret || !uri) { + id = getEnvironmentVariable("OUTLOOK_CLIENT_ID"); + secret = getEnvironmentVariable("OUTLOOK_CLIENT_SECRET"); + uri = getEnvironmentVariable("OUTLOOK_REDIRECT_URI"); } - - // Function to get the token using the code and client credentials - public async getAccessToken(): Promise { - // fetch auth code from user login - const code = await this.getCode(); - // fetch access token using auth code - const req_body = - `client_id=${encodeURIComponent(this.clientId)}&` + - `client_secret=${encodeURIComponent(this.clientSecret)}&` + - `scope=${encodeURIComponent('https://graph.microsoft.com/.default')}&` + - `redirect_uri=${encodeURIComponent(this.redirectUri)}&` + - `grant_type=authorization_code&` + - `code=${encodeURIComponent(code)}`; - - const response = await fetch('https://login.microsoftonline.com/common/oauth2/v2.0/token', { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: req_body - }); - - if (!response.ok) { - throw new Error(`fetch token error! response: ${response}`); - } - // save access token and refresh token - const json = await response.json() as AccessTokenResponse; - this.accessToken = json.access_token; - this.refreshToken = json.refresh_token; - return this.accessToken; + if (!id || !secret || !uri) { + throw new Error("Missing clientId, clientSecret or redirectUri."); } - - public async refreshAccessToken(): Promise { - // fetch new access token using refresh token - const req_body = - `client_id=${encodeURIComponent(this.clientId)}&` + - `client_secret=${encodeURIComponent(this.clientSecret)}&` + - `scope=${encodeURIComponent('https://graph.microsoft.com/.default')}&` + - `redirect_uri=${encodeURIComponent(this.redirectUri)}&` + - `grant_type=refresh_token&` + - `refresh_token=${encodeURIComponent(this.refreshToken)}`; - - const response = await fetch('https://login.microsoftonline.com/common/oauth2/v2.0/token', { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: req_body - }); - - if (!response.ok) { - throw new Error(`fetch token error! response: ${response}`); + super(id); + this.clientSecret = secret; + this.redirectUri = uri; + const parsedUrl = new URL(this.redirectUri); + this.port = parsedUrl.port ? parseInt(parsedUrl.port, 10) : 3000; + this.pathname = parsedUrl.pathname || ""; + } + + // Function to construct the OAuth URL + private openAuthUrl(): string { + const loginEndpoint = + "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"; + const { clientId } = this; // client ID regestered in Azure + const response_type = "code"; + const response_mode = "query"; + const redirectUri = encodeURIComponent(this.redirectUri); // redirect URI regestered in Azure + const scope = encodeURIComponent( + "openid offline_access https://graph.microsoft.com/.default" + ); + const state = "12345"; + + const url = [ + `${loginEndpoint}?client_id=${clientId}`, + `&response_type=${response_type}`, + `&response_mode=${response_mode}`, + `&redirect_uri=${redirectUri}`, + `&scope=${scope}`, + `&state=${state}`, + ].join(""); + openurl.open(url); + return url; + } + + // Function to start the server + private startServer(): Promise { + return new Promise((resolve, reject) => { + const server = http.createServer(); + + server.listen(this.port, () => { + console.log(`Server listening at http://localhost:${this.port}`); + resolve(server); + }); + + server.on("error", (err) => { + reject(err); + }); + }); + } + + // Function to listen for the authorization code + private async listenForCode(server: http.Server): Promise { + return new Promise((resolve, reject) => { + server.on("request", (req, res) => { + try { + const reqUrl = url.parse(req.url || "", true); + + if (reqUrl.pathname === this.pathname) { + const authCode = reqUrl.query.code as string; + + res.writeHead(200, { "Content-Type": "text/html" }); + res.end("Authorization code received. You can close this window."); + + server.close(); + console.log("Server closed"); + resolve(authCode); // Resolve the Promise with the authorization code + } else { + res.writeHead(404); + res.end("404 Not Found"); + } + } catch (err) { + res.writeHead(500); + res.end("Server error"); + reject(err); } - // save new access token - const json = await response.json() as AccessTokenResponse; - this.accessToken = json.access_token; - return this.accessToken; + }); + }); + } + + // Main function to run the auth flow + private async getCode(): Promise { + // check credentials + if (!this.clientId || !this.redirectUri) { + throw new Error("Missing clientId or redirectUri."); } + const server = await this.startServer(); + this.openAuthUrl(); + const code = await this.listenForCode(server); + return code; + } + + // Function to get the token using the code and client credentials + public async getAccessToken(): Promise { + // fetch auth code from user login + const code = await this.getCode(); + // fetch access token using auth code + const req_body = + `client_id=${encodeURIComponent(this.clientId)}&` + + `client_secret=${encodeURIComponent(this.clientSecret)}&` + + `scope=${encodeURIComponent("https://graph.microsoft.com/.default")}&` + + `redirect_uri=${encodeURIComponent(this.redirectUri)}&` + + `grant_type=authorization_code&` + + `code=${encodeURIComponent(code)}`; + + const response = await fetch( + "https://login.microsoftonline.com/common/oauth2/v2.0/token", + { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: req_body, + } + ); + + if (!response.ok) { + throw new Error(`fetch token error! response: ${response}`); + } + // save access token and refresh token + const json = (await response.json()) as AccessTokenResponse; + this.accessToken = json.access_token; + this.refreshToken = json.refresh_token; + return this.accessToken; + } + + public async refreshAccessToken(): Promise { + // fetch new access token using refresh token + const req_body = + `client_id=${encodeURIComponent(this.clientId)}&` + + `client_secret=${encodeURIComponent(this.clientSecret)}&` + + `scope=${encodeURIComponent("https://graph.microsoft.com/.default")}&` + + `redirect_uri=${encodeURIComponent(this.redirectUri)}&` + + `grant_type=refresh_token&` + + `refresh_token=${encodeURIComponent(this.refreshToken)}`; + + const response = await fetch( + "https://login.microsoftonline.com/common/oauth2/v2.0/token", + { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: req_body, + } + ); + + if (!response.ok) { + throw new Error(`fetch token error! response: ${response}`); + } + // save new access token + const json = (await response.json()) as AccessTokenResponse; + this.accessToken = json.access_token; + return this.accessToken; + } } - diff --git a/langchain/src/tools/outlook/authFlowToken.ts b/langchain/src/tools/outlook/authFlowToken.ts index af9fd3648479..c1d370311e34 100644 --- a/langchain/src/tools/outlook/authFlowToken.ts +++ b/langchain/src/tools/outlook/authFlowToken.ts @@ -1,89 +1,103 @@ -import { AuthFlowBase } from './authFlowBase.js'; -import { getEnvironmentVariable } from '../../util/env.js'; +import { AuthFlowBase } from "./authFlowBase.js"; +import { getEnvironmentVariable } from "../../util/env.js"; interface AccessTokenResponse { - access_token: string; - refresh_token: string; + access_token: string; + refresh_token: string; } // if you have the token, and no need to refresh it, warning: token expires in 1 hour export class AuthFlowToken extends AuthFlowBase { - - constructor(accessToken?: string) { - if (!accessToken) { - accessToken = getEnvironmentVariable('OUTLOOK_ACCESS_TOKEN'); - } - if (!accessToken) { - throw new Error('Missing access_token.'); - } - super(""); - this.accessToken = accessToken; - } - - public async refreshAccessToken(): Promise { - return this.accessToken; + constructor(accessToken?: string) { + let token = accessToken; + if (!token) { + token = getEnvironmentVariable("OUTLOOK_ACCESS_TOKEN"); } - - public async getAccessToken(): Promise { - return this.accessToken; + if (!token) { + throw new Error("Missing access_token."); } + super(""); + this.accessToken = token; + } + + public async refreshAccessToken(): Promise { + return this.accessToken; + } + + public async getAccessToken(): Promise { + return this.accessToken; + } } // if you have the refresh token and other credentials export class AuthFlowRefresh extends AuthFlowBase { + private clientSecret: string; + + private redirectUri: string; - private clientSecret: string; - private redirectUri: string; - private refreshToken: string; + private refreshToken: string; - constructor(clientId?: string, clientSecret?: string, redirectUri?: string, refreshToken?: string) { - if (!clientId || !clientSecret || !redirectUri || !refreshToken) { - clientId = getEnvironmentVariable('OUTLOOK_CLIENT_ID'); - clientSecret = getEnvironmentVariable('OUTLOOK_CLIENT_SECRET'); - redirectUri = getEnvironmentVariable('OUTLOOK_REDIRECT_URI'); - refreshToken = getEnvironmentVariable('OUTLOOK_REFRESH_TOKEN'); - } - if (!clientId || !clientSecret || !redirectUri || !refreshToken) { - throw new Error('Missing clientId, clientSecret, redirectUri or refreshToken.'); - } - super(clientId); - this.clientSecret = clientSecret; - this.redirectUri = redirectUri; - this.refreshToken = refreshToken; + constructor( + clientId?: string, + clientSecret?: string, + redirectUri?: string, + refreshToken?: string + ) { + let id = clientId; + let secret = clientSecret; + let uri = redirectUri; + let token = refreshToken; + if (!id || !secret || !uri || !token) { + id = getEnvironmentVariable("OUTLOOK_CLIENT_ID"); + secret = getEnvironmentVariable("OUTLOOK_CLIENT_SECRET"); + uri = getEnvironmentVariable("OUTLOOK_REDIRECT_URI"); + token = getEnvironmentVariable("OUTLOOK_REFRESH_TOKEN"); } - - public async refreshAccessToken(): Promise { - // fetch new access token using refresh token - const req_body = - `client_id=${encodeURIComponent(this.clientId)}&` + - `client_secret=${encodeURIComponent(this.clientSecret)}&` + - `scope=${encodeURIComponent('https://graph.microsoft.com/.default')}&` + - `redirect_uri=${encodeURIComponent(this.redirectUri)}&` + - `grant_type=refresh_token&` + - `refresh_token=${encodeURIComponent(this.refreshToken)}`; - - const response = await fetch('https://login.microsoftonline.com/common/oauth2/v2.0/token', { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: req_body - }); - - if (!response.ok) { - throw new Error(`fetch token error! response: ${response}`); - } - // save new access token - const json = await response.json() as AccessTokenResponse; - this.accessToken = json.access_token; - return this.accessToken; + if (!id || !secret || !uri || !token) { + throw new Error( + "Missing clientId, clientSecret, redirectUri or refreshToken." + ); } - - // Function to get the token using the code and client credentials - public async getAccessToken(): Promise { - const accessToken = await this.refreshAccessToken(); - this.accessToken = accessToken; - return accessToken; + super(id); + this.clientSecret = secret; + this.redirectUri = uri; + this.refreshToken = token; + } + + public async refreshAccessToken(): Promise { + // fetch new access token using refresh token + const req_body = + `client_id=${encodeURIComponent(this.clientId)}&` + + `client_secret=${encodeURIComponent(this.clientSecret)}&` + + `scope=${encodeURIComponent("https://graph.microsoft.com/.default")}&` + + `redirect_uri=${encodeURIComponent(this.redirectUri)}&` + + `grant_type=refresh_token&` + + `refresh_token=${encodeURIComponent(this.refreshToken)}`; + + const response = await fetch( + "https://login.microsoftonline.com/common/oauth2/v2.0/token", + { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: req_body, + } + ); + + if (!response.ok) { + throw new Error(`fetch token error! response: ${response}`); } -} + // save new access token + const json = (await response.json()) as AccessTokenResponse; + this.accessToken = json.access_token; + return this.accessToken; + } + // Function to get the token using the code and client credentials + public async getAccessToken(): Promise { + const accessToken = await this.refreshAccessToken(); + this.accessToken = accessToken; + return accessToken; + } +} diff --git a/langchain/src/tools/outlook/base.ts b/langchain/src/tools/outlook/base.ts index eecf251b4e92..bd247f51d7de 100644 --- a/langchain/src/tools/outlook/base.ts +++ b/langchain/src/tools/outlook/base.ts @@ -1,15 +1,17 @@ import { Tool } from "../base.js"; -import { AuthFlowBase } from './authFlowBase.js'; -import { AuthFlowToken, AuthFlowRefresh } from './authFlowToken.js'; -import { AuthFlowREST } from './authFlowREST.js'; +import { AuthFlowBase } from "./authFlowBase.js"; +import { AuthFlowToken, AuthFlowRefresh } from "./authFlowToken.js"; +import { AuthFlowREST } from "./authFlowREST.js"; export class OutlookBase extends Tool { name = "Outlook"; - description = "A tool to send or read emails or do other features from Outlook."; + description = + "A tool to send or read emails or do other features from Outlook."; protected authFlow: AuthFlowBase; - protected accessToken: string = ""; + + protected accessToken: ""; constructor(authFlow?: AuthFlowBase, choice?: string) { super(); diff --git a/langchain/src/tools/outlook/descriptions.ts b/langchain/src/tools/outlook/descriptions.ts index 2d3ff1f8ba48..a54d146604f2 100644 --- a/langchain/src/tools/outlook/descriptions.ts +++ b/langchain/src/tools/outlook/descriptions.ts @@ -1,38 +1,40 @@ -export const SEND_MAIL_TOOL_DESCRIPTION = "A tool for sending emails. \ -input instructions: \ -input a JSON formatted email message with the following four keys:\ -'subject', 'content', 'to', and 'cc'.\ -The 'subject' should be a brief title for the email, \ -'content' should contain the body of the email, \ -'to' should be an array of the recipient's email address, \ -and 'cc' should be an array of any additional recipient's email address. \ -The 'cc' key is optional, just give empty array if no cc. \ -Ensure that the JSON object is correctly formatted and includes all four specified keys.\ -"; +export const SEND_MAIL_TOOL_DESCRIPTION = ` + A tool for sending emails. + input instructions: + input a JSON formatted email message with the following four keys: + 'subject', 'content', 'to', and 'cc'. + The 'subject' should be a brief title for the email, + 'content' should contain the body of the email, + 'to' should be an array of the recipient's email address, + and 'cc' should be an array of any additional recipient's email address. + The 'cc' key is optional, just give an empty array if no cc. + Ensure that the JSON object is correctly formatted and includes all four specified keys. +`; + // This is an example of a valid JSON object: \ // {\"subject\":\"Example Subject\",\"content\":\"Example Content\",\"to\":[\"team@example.com\"],\"cc\":[]}\ // "; +export const READ_MAIL_TOOL_DESCRIPTION = `A tool for reading emails. +You can search messages based on a value in specific message properties. +The results of the search are sorted by the date and time that the message was sent. +A $search request returns up to 1000 results. +If you do a search on messages and specify only a value without specific message properties, +the search is carried out on the default search properties of from, subject, and body. +Alternatively, you can search messages by specifying message property names in the following table +and a value to search for in those properties. +body: The body of an email message. +cc: The cc field of an email message, specified as an SMTP address, display name, or alias. +from: The sender of an email message, specified as an SMTP address, display name, or alias. +received: The date that an email message was received by a recipient. e.g. 07/23/2018 +recipients: The to, cc, and bcc fields of an email message, +sent: The date that an email message was sent by the sender. e.g. 07/23/2018 +subject: The text in the subject line of an email message. +to: The to field of an email message, +INPUT: +input empty string to get all emails. +If on default: $search="" +On specified property: $search=":" +Example: $search="sent:07/23/2018" +`; -export const READ_MAIL_TOOL_DESCRIPTION = `A tool for reading emails.\ -You can search messages based on a value in specific message properties. \ -The results of the search are sorted by the date and time that the message was sent.\ -A $search request returns up to 1000 results.\ -If you do a search on messages and specify only a value without specific message properties, \ -the search is carried out on the default search properties of from, subject, and body.\ -Alternatively, you can search messages by specifying message property names in the following table\ -and a value to search for in those properties.\n\ -body: The body of an email message.\n\ -cc: The cc field of an email message, specified as an SMTP address, display name, or alias.\n\ -from: The sender of an email message, specified as an SMTP address, display name, or alias.\n\ -received: The date that an email message was received by a recipient. e.g. 07/23/2018\n\ -recipients: The to, cc, and bcc fields of an email meesage,\n\ -sent: The date that an email message was sent by the sender. e.g. 07/23/2018\n\ -subject: The text in the subject line of an email message.\n\ -to: The to field of an email message,\n\ -INPUT:\n\ -input empty string to get all emails.\n\ -If on default: $search=\"\" \n\ -On specified property: $search=\":\"\n\ -Example: $search=\"sent:07/23/2018\" -`; \ No newline at end of file diff --git a/langchain/src/tools/outlook/index.ts b/langchain/src/tools/outlook/index.ts index f8d344a3edde..d1d7dc32a8c0 100644 --- a/langchain/src/tools/outlook/index.ts +++ b/langchain/src/tools/outlook/index.ts @@ -3,4 +3,4 @@ export { OutlookReadMailTool } from "./readMail.js"; export { OutlookBase } from "./base.js"; export { AuthFlowREST } from "./authFlowREST.js"; export { AuthFlowToken, AuthFlowRefresh } from "./authFlowToken.js"; -export { AuthFlowBase } from "./authFlowBase.js"; \ No newline at end of file +export { AuthFlowBase } from "./authFlowBase.js"; diff --git a/langchain/src/tools/outlook/readMail.ts b/langchain/src/tools/outlook/readMail.ts index cae4920fe2c3..afa28797ec7f 100644 --- a/langchain/src/tools/outlook/readMail.ts +++ b/langchain/src/tools/outlook/readMail.ts @@ -1,11 +1,11 @@ import { OutlookBase } from "./base.js"; import { READ_MAIL_TOOL_DESCRIPTION } from "./descriptions.js"; -import { AuthFlowBase } from './authFlowBase.js'; +import { AuthFlowBase } from "./authFlowBase.js"; interface Email { subject: string; - body: { content: string; }; - sender: { emailAddress: { name: string; address: string; } }; + body: { content: string }; + sender: { emailAddress: { name: string; address: string } }; } export class OutlookReadMailTool extends OutlookBase { name = "outlook_read_mail"; @@ -23,37 +23,44 @@ export class OutlookReadMailTool extends OutlookBase { return `Failed to get access token: ${error}`; } // validate query - const queryRegex = /^\$search="(?:body|cc|from|received|recipients|sent|subject|to)(?::[^"]*)?"$/; + const queryRegex = + /^\$search="(?:body|cc|from|received|recipients|sent|subject|to)(?::[^"]*)?"$/; if (query && !queryRegex.test(query)) { - return 'Invalid query format'; + return "Invalid query format"; } // fetch emails from me/messages - const response = await fetch(`https://graph.microsoft.com/v1.0/me/messages?${query}&$top=5`, { - method: "GET", - headers: { - Authorization: `Bearer ${this.accessToken}`, - }, - }); + const response = await fetch( + `https://graph.microsoft.com/v1.0/me/messages?${query}&$top=5`, + { + method: "GET", + headers: { + Authorization: `Bearer ${this.accessToken}`, + }, + } + ); if (!response.ok) { return `Fetch mail error: ${response.status}`; } try { // parse response - const data = await response.json() as { value: Email[] }; - const formattedEmails = data.value.map(email => { - const subject = email?.subject ?? 'No subject'; - const bodyContent = email?.body?.content ?? 'No content'; - const senderName = email?.sender?.emailAddress?.name ?? 'Unknown Sender'; - const senderAddress = email?.sender?.emailAddress?.address ?? 'No address'; - // Constructing the email string - return `subject: ${subject}\nsender: ${senderName} ${senderAddress}\nbody: ${bodyContent}\n`; - }).join("\n"); + const data = (await response.json()) as { value: Email[] }; + const formattedEmails = data.value + .map((email) => { + const subject = email?.subject ?? "No subject"; + const bodyContent = email?.body?.content ?? "No content"; + const senderName = + email?.sender?.emailAddress?.name ?? "Unknown Sender"; + const senderAddress = + email?.sender?.emailAddress?.address ?? "No address"; + // Constructing the email string + return `subject: ${subject}\nsender: ${senderName} ${senderAddress}\nbody: ${bodyContent}\n`; + }) + .join("\n"); return formattedEmails; } catch (error) { - return `Failed to parse response: ${error}`; + return `Failed to parse response: ${error}`; } - } -} \ No newline at end of file +} diff --git a/langchain/src/tools/outlook/sendMail.ts b/langchain/src/tools/outlook/sendMail.ts index 7e4684faa525..89c39c227b52 100644 --- a/langchain/src/tools/outlook/sendMail.ts +++ b/langchain/src/tools/outlook/sendMail.ts @@ -1,6 +1,6 @@ import { OutlookBase } from "./base.js"; import { SEND_MAIL_TOOL_DESCRIPTION } from "./descriptions.js"; -import { AuthFlowBase } from './authFlowBase.js'; +import { AuthFlowBase } from "./authFlowBase.js"; export class OutlookSendMailTool extends OutlookBase { name = "outlook_send_mail"; @@ -20,58 +20,68 @@ export class OutlookSendMailTool extends OutlookBase { let newMessage: string; try { - // parse message - const { subject, content, to, cc } = JSON.parse(message); - // validate message - const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/; + // parse message + const { subject, content, to, cc } = JSON.parse(message); + // validate message + const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/; - if (!Array.isArray(to) || !to.every((item: string) => emailRegex.test(item))) { - return 'TO must be an array of valid email in strings'; - } - - if (cc && (!Array.isArray(cc) || !cc.every((item: string) => emailRegex.test(item)))) { - return 'CC must be an array of valid email in strings'; - } - // create new message - newMessage = JSON.stringify({ - message: { - subject: String(subject), - body: { - contentType: "Text", - content: String(content), - }, - toRecipients: to.map((address: string) => ({ - emailAddress: { - address, - }, - })), - ...(cc && { - ccRecipients: cc.map((address: string) => ({ - emailAddress: { - address, - }, - })), - }), + if ( + !Array.isArray(to) || + !to.every((item: string) => emailRegex.test(item)) + ) { + return "TO must be an array of valid email in strings"; + } + + if ( + cc && + (!Array.isArray(cc) || + !cc.every((item: string) => emailRegex.test(item))) + ) { + return "CC must be an array of valid email in strings"; + } + // create new message + newMessage = JSON.stringify({ + message: { + subject: String(subject), + body: { + contentType: "Text", + content: String(content), + }, + toRecipients: to.map((address: string) => ({ + emailAddress: { + address, + }, + })), + ...(cc && { + ccRecipients: cc.map((address: string) => ({ + emailAddress: { + address, }, - saveToSentItems: "true", - }); + })), + }), + }, + saveToSentItems: "true", + }); } catch (e) { - return 'Invalid JSON format'; + return "Invalid JSON format"; } - - const response = await fetch("https://graph.microsoft.com/v1.0/me/sendMail", { - method: "POST", - headers: { - Authorization: `Bearer ${this.accessToken}`, - 'Content-Type': 'application/json', - }, - body: newMessage, - }); + + const response = await fetch( + "https://graph.microsoft.com/v1.0/me/sendMail", + { + method: "POST", + headers: { + Authorization: `Bearer ${this.accessToken}`, + "Content-Type": "application/json", + }, + body: newMessage, + } + ); if (!response.ok) { return `Send mail error: ${response.status}`; } - return 'Email sent'; + return "Email sent"; } -} \ No newline at end of file +} diff --git a/langchain/src/tools/tests/outlookIntegration.test.ts b/langchain/src/tools/tests/outlookIntegration.test.ts index 5f95aaea3428..a83ed1821858 100644 --- a/langchain/src/tools/tests/outlookIntegration.test.ts +++ b/langchain/src/tools/tests/outlookIntegration.test.ts @@ -1,33 +1,33 @@ -import { OutlookReadMailTool, OutlookSendMailTool } from '../outlook/index.js'; - +import { OutlookReadMailTool, OutlookSendMailTool } from "../outlook/index.js"; +import { AuthFlowToken } from "../outlook/authFlowToken.js"; describe("OutlookReadMailTool Test", () => { + const authFlowToken = new AuthFlowToken(); test("Test read messages", async () => { - const outlookTool = new OutlookReadMailTool(undefined, "refresh"); + const outlookTool = new OutlookReadMailTool(authFlowToken, "refresh"); const emails = await outlookTool._call(""); console.log(emails); expect(true).toBe(true); }); test("Test invalid query format", async () => { - const outlookTool = new OutlookReadMailTool(undefined, "refresh"); + const outlookTool = new OutlookReadMailTool(authFlowToken, "refresh"); const emails = await outlookTool._call("blah"); console.log(emails); expect(emails).toBe("Invalid query format"); }); test("Test query correct format", async () => { - const outlookTool = new OutlookReadMailTool(undefined, "refresh"); - const emails = await outlookTool._call("$search=\"subject:hello\""); + const outlookTool = new OutlookReadMailTool(authFlowToken, "refresh"); + const emails = await outlookTool._call('$search="subject:hello"'); console.log(emails); expect(true).toBe(true); }); - }); describe("OutlookSendMailTool Test", () => { - + const authFlowToken = new AuthFlowToken(); test("Test invalid TO email address", async () => { const message = JSON.stringify({ subject: "test", @@ -35,7 +35,7 @@ describe("OutlookSendMailTool Test", () => { to: ["testemail"], cc: [], }); - const outlookTool = new OutlookSendMailTool(undefined, "refresh"); + const outlookTool = new OutlookSendMailTool(authFlowToken, "refresh"); const res = await outlookTool._call(message); console.log(res); expect(res).toBe("TO must be an array of valid email in strings"); @@ -48,7 +48,7 @@ describe("OutlookSendMailTool Test", () => { to: ["test@email.com"], cc: ["blah"], }); - const outlookTool = new OutlookSendMailTool(undefined, "refresh"); + const outlookTool = new OutlookSendMailTool(authFlowToken, "refresh"); const res = await outlookTool._call(message); console.log(res); expect(res).toBe("CC must be an array of valid email in strings"); @@ -56,10 +56,9 @@ describe("OutlookSendMailTool Test", () => { test("Test invalid JSON format", async () => { const message = "blah"; - const outlookTool = new OutlookSendMailTool(undefined, "refresh"); + const outlookTool = new OutlookSendMailTool(authFlowToken, "refresh"); const res = await outlookTool._call(message); console.log(res); expect(res).toBe("Invalid JSON format"); }); - -}); \ No newline at end of file +}); From 43388c7dbc1c65e89c92569d7362bffd164d8a54 Mon Sep 17 00:00:00 2001 From: Qi123123Li <94654984+Qi123123Li@users.noreply.github.com> Date: Thu, 30 Nov 2023 12:30:40 -0500 Subject: [PATCH 13/46] add test case for send email --- .../tools/tests/outlookIntegration.test.ts | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/langchain/src/tools/tests/outlookIntegration.test.ts b/langchain/src/tools/tests/outlookIntegration.test.ts index a83ed1821858..88565fa2e352 100644 --- a/langchain/src/tools/tests/outlookIntegration.test.ts +++ b/langchain/src/tools/tests/outlookIntegration.test.ts @@ -1,25 +1,23 @@ import { OutlookReadMailTool, OutlookSendMailTool } from "../outlook/index.js"; -import { AuthFlowToken } from "../outlook/authFlowToken.js"; describe("OutlookReadMailTool Test", () => { - const authFlowToken = new AuthFlowToken(); test("Test read messages", async () => { - const outlookTool = new OutlookReadMailTool(authFlowToken, "refresh"); + const outlookTool = new OutlookReadMailTool(undefined, "refresh"); const emails = await outlookTool._call(""); console.log(emails); expect(true).toBe(true); }); test("Test invalid query format", async () => { - const outlookTool = new OutlookReadMailTool(authFlowToken, "refresh"); + const outlookTool = new OutlookReadMailTool(undefined, "refresh"); const emails = await outlookTool._call("blah"); console.log(emails); expect(emails).toBe("Invalid query format"); }); test("Test query correct format", async () => { - const outlookTool = new OutlookReadMailTool(authFlowToken, "refresh"); + const outlookTool = new OutlookReadMailTool(undefined, "refresh"); const emails = await outlookTool._call('$search="subject:hello"'); console.log(emails); expect(true).toBe(true); @@ -27,7 +25,6 @@ describe("OutlookReadMailTool Test", () => { }); describe("OutlookSendMailTool Test", () => { - const authFlowToken = new AuthFlowToken(); test("Test invalid TO email address", async () => { const message = JSON.stringify({ subject: "test", @@ -35,7 +32,7 @@ describe("OutlookSendMailTool Test", () => { to: ["testemail"], cc: [], }); - const outlookTool = new OutlookSendMailTool(authFlowToken, "refresh"); + const outlookTool = new OutlookSendMailTool(undefined, "refresh"); const res = await outlookTool._call(message); console.log(res); expect(res).toBe("TO must be an array of valid email in strings"); @@ -48,7 +45,7 @@ describe("OutlookSendMailTool Test", () => { to: ["test@email.com"], cc: ["blah"], }); - const outlookTool = new OutlookSendMailTool(authFlowToken, "refresh"); + const outlookTool = new OutlookSendMailTool(undefined, "refresh"); const res = await outlookTool._call(message); console.log(res); expect(res).toBe("CC must be an array of valid email in strings"); @@ -56,9 +53,22 @@ describe("OutlookSendMailTool Test", () => { test("Test invalid JSON format", async () => { const message = "blah"; - const outlookTool = new OutlookSendMailTool(authFlowToken, "refresh"); + const outlookTool = new OutlookSendMailTool(undefined, "refresh"); const res = await outlookTool._call(message); console.log(res); expect(res).toBe("Invalid JSON format"); }); + + test("Test valid email address", async () => { + const message = JSON.stringify({ + subject: "test", + content: "test", + to: ["test@email.com"], + cc: [], + }); + const outlookTool = new OutlookSendMailTool(undefined, "refresh"); + const res = await outlookTool._call(message); + console.log(res); + expect(res).toBe("Email sent"); + }); }); From 497e48635b15e5fd29cab49da37f4e1dfa4e8ea3 Mon Sep 17 00:00:00 2001 From: Qi123123Li <94654984+Qi123123Li@users.noreply.github.com> Date: Thu, 30 Nov 2023 13:22:54 -0500 Subject: [PATCH 14/46] fix some small bugs --- langchain/src/tools/outlook/authFlowBase.ts | 2 +- langchain/src/tools/outlook/authFlowREST.ts | 6 +++--- langchain/src/tools/outlook/authFlowToken.ts | 2 +- langchain/src/tools/outlook/base.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/langchain/src/tools/outlook/authFlowBase.ts b/langchain/src/tools/outlook/authFlowBase.ts index 54ff555877ba..97651b8decbe 100644 --- a/langchain/src/tools/outlook/authFlowBase.ts +++ b/langchain/src/tools/outlook/authFlowBase.ts @@ -1,7 +1,7 @@ export abstract class AuthFlowBase { protected clientId: string; - protected accessToken: ""; + protected accessToken = ""; constructor(clientId: string) { this.clientId = clientId; diff --git a/langchain/src/tools/outlook/authFlowREST.ts b/langchain/src/tools/outlook/authFlowREST.ts index 834944070391..c4d6490681bf 100644 --- a/langchain/src/tools/outlook/authFlowREST.ts +++ b/langchain/src/tools/outlook/authFlowREST.ts @@ -18,7 +18,7 @@ export class AuthFlowREST extends AuthFlowBase { private pathname: string; - private refreshToken: ""; + private refreshToken = ""; constructor({ clientId, clientSecret, redirectUri }: { clientId?: string, clientSecret?: string, redirectUri?: string } = {}) { let id = clientId; @@ -148,7 +148,7 @@ export class AuthFlowREST extends AuthFlowBase { ); if (!response.ok) { - throw new Error(`fetch token error! response: ${response}`); + throw new Error(`fetch token error! response: ${response.status}`); } // save access token and refresh token const json = (await response.json()) as AccessTokenResponse; @@ -179,7 +179,7 @@ export class AuthFlowREST extends AuthFlowBase { ); if (!response.ok) { - throw new Error(`fetch token error! response: ${response}`); + throw new Error(`fetch token error! response: ${response.status}`); } // save new access token const json = (await response.json()) as AccessTokenResponse; diff --git a/langchain/src/tools/outlook/authFlowToken.ts b/langchain/src/tools/outlook/authFlowToken.ts index c1d370311e34..546b21bbbe7e 100644 --- a/langchain/src/tools/outlook/authFlowToken.ts +++ b/langchain/src/tools/outlook/authFlowToken.ts @@ -86,7 +86,7 @@ export class AuthFlowRefresh extends AuthFlowBase { ); if (!response.ok) { - throw new Error(`fetch token error! response: ${response}`); + throw new Error(`fetch token error! response: ${response.status}`); } // save new access token const json = (await response.json()) as AccessTokenResponse; diff --git a/langchain/src/tools/outlook/base.ts b/langchain/src/tools/outlook/base.ts index bd247f51d7de..48c502f95372 100644 --- a/langchain/src/tools/outlook/base.ts +++ b/langchain/src/tools/outlook/base.ts @@ -11,7 +11,7 @@ export class OutlookBase extends Tool { protected authFlow: AuthFlowBase; - protected accessToken: ""; + protected accessToken = ""; constructor(authFlow?: AuthFlowBase, choice?: string) { super(); From 11d91fcfa650f3b8cd14c2d8707b74b4f3262543 Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Thu, 30 Nov 2023 14:16:17 -0500 Subject: [PATCH 15/46] add invalid token test --- langchain/src/tools/tests/outlookIntegration.test.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/langchain/src/tools/tests/outlookIntegration.test.ts b/langchain/src/tools/tests/outlookIntegration.test.ts index 88565fa2e352..5994e49a5e9f 100644 --- a/langchain/src/tools/tests/outlookIntegration.test.ts +++ b/langchain/src/tools/tests/outlookIntegration.test.ts @@ -1,7 +1,16 @@ -import { OutlookReadMailTool, OutlookSendMailTool } from "../outlook/index.js"; +import { AuthFlowToken, OutlookReadMailTool, OutlookSendMailTool } from "../outlook/index.js"; + describe("OutlookReadMailTool Test", () => { + test("Test invalid access token", async () => { + const accessToken = "blah"; + const authFlow = new AuthFlowToken(accessToken); + const outlookTool = new OutlookReadMailTool(authFlow); + const emails = await outlookTool._call(""); + expect(emails).toBe("Fetch mail error: 401"); + }); + test("Test read messages", async () => { const outlookTool = new OutlookReadMailTool(undefined, "refresh"); const emails = await outlookTool._call(""); From 70911185efefc1371d8e5337729e7dd4c704ee22 Mon Sep 17 00:00:00 2001 From: SimonLi1020 <55788824+SimonLi1020@users.noreply.github.com> Date: Thu, 30 Nov 2023 14:56:55 -0500 Subject: [PATCH 16/46] Update .env.example --- langchain/.env.example | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/langchain/.env.example b/langchain/.env.example index d6ad749624c8..bcc7e18c1010 100644 --- a/langchain/.env.example +++ b/langchain/.env.example @@ -88,3 +88,8 @@ NEO4J_USERNAME=ADD_YOURS_HERE NEO4J_PASSWORD=ADD_YOURS_HERE CLOSEVECTOR_API_KEY=ADD_YOURS_HERE CLOSEVECTOR_API_SECRET=ADD_YOURS_HERE +OUTLOOK_CLIENT_ID=ADD_YOURS_HERE +OUTLOOK_CLIENT_SECRET=ADD_YOURS_HERE +OUTLOOK_REDIRECT_URI=ADD_YOURS_HERE +OUTLOOK_REFRESH_TOKEN=ADD_YOURS_HERE +OUTLOOK_ACCESS_TOKEN=ADD_YOURS_HERE \ No newline at end of file From d38f9f6a54dbdb2cb384a634cf34407a5a5846c8 Mon Sep 17 00:00:00 2001 From: Qi123123Li <94654984+Qi123123Li@users.noreply.github.com> Date: Thu, 30 Nov 2023 22:15:24 -0500 Subject: [PATCH 17/46] use typedoc --- docs/api_refs/typedoc.json | 1 + environment_tests/test-exports-bun/src/entrypoints.js | 1 + environment_tests/test-exports-cf/src/entrypoints.js | 1 + environment_tests/test-exports-cjs/src/entrypoints.js | 1 + environment_tests/test-exports-esbuild/src/entrypoints.js | 1 + environment_tests/test-exports-esm/src/entrypoints.js | 1 + environment_tests/test-exports-vercel/src/entrypoints.js | 1 + environment_tests/test-exports-vite/src/entrypoints.js | 1 + langchain/.gitignore | 3 +++ langchain/package.json | 8 ++++++++ 10 files changed, 19 insertions(+) diff --git a/docs/api_refs/typedoc.json b/docs/api_refs/typedoc.json index 7ff44c31fe65..879766fa053d 100644 --- a/docs/api_refs/typedoc.json +++ b/docs/api_refs/typedoc.json @@ -42,6 +42,7 @@ "./langchain/src/tools/sql.ts", "./langchain/src/tools/webbrowser.ts", "./langchain/src/tools/google_calendar/index.ts", + "./langchain/src/tools/outlook/index.ts", "./langchain/src/tools/google_places.ts", "./langchain/src/chains/index.ts", "./langchain/src/chains/combine_documents/reduce.ts", diff --git a/environment_tests/test-exports-bun/src/entrypoints.js b/environment_tests/test-exports-bun/src/entrypoints.js index 61b2f0553453..33a8d2a35ffa 100644 --- a/environment_tests/test-exports-bun/src/entrypoints.js +++ b/environment_tests/test-exports-bun/src/entrypoints.js @@ -13,6 +13,7 @@ export * from "langchain/agents/openai/output_parser"; export * from "langchain/base_language"; export * from "langchain/tools"; export * from "langchain/tools/render"; +export * from "langchain/tools/outlook"; export * from "langchain/tools/google_places"; export * from "langchain/chains"; export * from "langchain/chains/combine_documents/reduce"; diff --git a/environment_tests/test-exports-cf/src/entrypoints.js b/environment_tests/test-exports-cf/src/entrypoints.js index 61b2f0553453..33a8d2a35ffa 100644 --- a/environment_tests/test-exports-cf/src/entrypoints.js +++ b/environment_tests/test-exports-cf/src/entrypoints.js @@ -13,6 +13,7 @@ export * from "langchain/agents/openai/output_parser"; export * from "langchain/base_language"; export * from "langchain/tools"; export * from "langchain/tools/render"; +export * from "langchain/tools/outlook"; export * from "langchain/tools/google_places"; export * from "langchain/chains"; export * from "langchain/chains/combine_documents/reduce"; diff --git a/environment_tests/test-exports-cjs/src/entrypoints.js b/environment_tests/test-exports-cjs/src/entrypoints.js index 725b9cd0477c..7ca38613642e 100644 --- a/environment_tests/test-exports-cjs/src/entrypoints.js +++ b/environment_tests/test-exports-cjs/src/entrypoints.js @@ -13,6 +13,7 @@ const agents_openai_output_parser = require("langchain/agents/openai/output_pars const base_language = require("langchain/base_language"); const tools = require("langchain/tools"); const tools_render = require("langchain/tools/render"); +const tools_outlook = require("langchain/tools/outlook"); const tools_google_places = require("langchain/tools/google_places"); const chains = require("langchain/chains"); const chains_combine_documents_reduce = require("langchain/chains/combine_documents/reduce"); diff --git a/environment_tests/test-exports-esbuild/src/entrypoints.js b/environment_tests/test-exports-esbuild/src/entrypoints.js index 9c4a916789c5..61fa4b84577f 100644 --- a/environment_tests/test-exports-esbuild/src/entrypoints.js +++ b/environment_tests/test-exports-esbuild/src/entrypoints.js @@ -13,6 +13,7 @@ import * as agents_openai_output_parser from "langchain/agents/openai/output_par import * as base_language from "langchain/base_language"; import * as tools from "langchain/tools"; import * as tools_render from "langchain/tools/render"; +import * as tools_outlook from "langchain/tools/outlook"; import * as tools_google_places from "langchain/tools/google_places"; import * as chains from "langchain/chains"; import * as chains_combine_documents_reduce from "langchain/chains/combine_documents/reduce"; diff --git a/environment_tests/test-exports-esm/src/entrypoints.js b/environment_tests/test-exports-esm/src/entrypoints.js index 9c4a916789c5..61fa4b84577f 100644 --- a/environment_tests/test-exports-esm/src/entrypoints.js +++ b/environment_tests/test-exports-esm/src/entrypoints.js @@ -13,6 +13,7 @@ import * as agents_openai_output_parser from "langchain/agents/openai/output_par import * as base_language from "langchain/base_language"; import * as tools from "langchain/tools"; import * as tools_render from "langchain/tools/render"; +import * as tools_outlook from "langchain/tools/outlook"; import * as tools_google_places from "langchain/tools/google_places"; import * as chains from "langchain/chains"; import * as chains_combine_documents_reduce from "langchain/chains/combine_documents/reduce"; diff --git a/environment_tests/test-exports-vercel/src/entrypoints.js b/environment_tests/test-exports-vercel/src/entrypoints.js index 61b2f0553453..33a8d2a35ffa 100644 --- a/environment_tests/test-exports-vercel/src/entrypoints.js +++ b/environment_tests/test-exports-vercel/src/entrypoints.js @@ -13,6 +13,7 @@ export * from "langchain/agents/openai/output_parser"; export * from "langchain/base_language"; export * from "langchain/tools"; export * from "langchain/tools/render"; +export * from "langchain/tools/outlook"; export * from "langchain/tools/google_places"; export * from "langchain/chains"; export * from "langchain/chains/combine_documents/reduce"; diff --git a/environment_tests/test-exports-vite/src/entrypoints.js b/environment_tests/test-exports-vite/src/entrypoints.js index 61b2f0553453..33a8d2a35ffa 100644 --- a/environment_tests/test-exports-vite/src/entrypoints.js +++ b/environment_tests/test-exports-vite/src/entrypoints.js @@ -13,6 +13,7 @@ export * from "langchain/agents/openai/output_parser"; export * from "langchain/base_language"; export * from "langchain/tools"; export * from "langchain/tools/render"; +export * from "langchain/tools/outlook"; export * from "langchain/tools/google_places"; export * from "langchain/chains"; export * from "langchain/chains/combine_documents/reduce"; diff --git a/langchain/.gitignore b/langchain/.gitignore index 886cf43390bd..b0afc74a61e2 100644 --- a/langchain/.gitignore +++ b/langchain/.gitignore @@ -70,6 +70,9 @@ tools/webbrowser.d.ts tools/google_calendar.cjs tools/google_calendar.js tools/google_calendar.d.ts +tools/outlook.cjs +tools/outlook.js +tools/outlook.d.ts tools/google_places.cjs tools/google_places.js tools/google_places.d.ts diff --git a/langchain/package.json b/langchain/package.json index c855ae367c8f..2fd52060eef7 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -82,6 +82,9 @@ "tools/google_calendar.cjs", "tools/google_calendar.js", "tools/google_calendar.d.ts", + "tools/outlook.cjs", + "tools/outlook.js", + "tools/outlook.d.ts", "tools/google_places.cjs", "tools/google_places.js", "tools/google_places.d.ts", @@ -1573,6 +1576,11 @@ "import": "./tools/google_calendar.js", "require": "./tools/google_calendar.cjs" }, + "./tools/outlook": { + "types": "./tools/outlook.d.ts", + "import": "./tools/outlook.js", + "require": "./tools/outlook.cjs" + }, "./tools/google_places": { "types": "./tools/google_places.d.ts", "import": "./tools/google_places.js", From 12d160fa8d46da138b5727d5f5c44e72a11e3afa Mon Sep 17 00:00:00 2001 From: Qi123123Li <94654984+Qi123123Li@users.noreply.github.com> Date: Fri, 1 Dec 2023 08:54:27 -0500 Subject: [PATCH 18/46] add http and url in package --- langchain/package.json | 2 ++ yarn.lock | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/langchain/package.json b/langchain/package.json index e235a13ab4e3..06a92245eb22 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -1425,6 +1425,7 @@ "binary-extensions": "^2.2.0", "expr-eval": "^2.0.2", "flat": "^5.0.2", + "http": "^0.0.1-security", "js-tiktoken": "^1.0.7", "js-yaml": "^4.1.0", "jsonpointer": "^5.0.1", @@ -1436,6 +1437,7 @@ "openurl": "^1.1.1", "p-queue": "^6.6.2", "p-retry": "4", + "url": "^0.11.3", "uuid": "^9.0.0", "yaml": "^2.2.1", "zod": "^3.22.3", diff --git a/yarn.lock b/yarn.lock index f72ad9f83e61..4cb5f852ff8a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20373,6 +20373,13 @@ __metadata: languageName: node linkType: hard +"http@npm:^0.0.1-security": + version: 0.0.1-security + resolution: "http@npm:0.0.1-security" + checksum: c45a4e2771b5679b479b24150216e0b423d5bf2f94062c91b4b5e5347ed3f18b8c049e220df6e328c34ac5aab0d020600a05a9ea954bc7a7efc04d8dde7aada0 + languageName: node + linkType: hard + "https-proxy-agent@npm:5, https-proxy-agent@npm:5.0.1, https-proxy-agent@npm:^5.0.0, https-proxy-agent@npm:^5.0.1": version: 5.0.1 resolution: "https-proxy-agent@npm:5.0.1" @@ -22694,6 +22701,7 @@ __metadata: graphql: ^16.6.0 hnswlib-node: ^1.4.2 html-to-text: ^9.0.5 + http: ^0.0.1-security ignore: ^5.2.0 ioredis: ^5.3.2 jest: ^29.5.0 @@ -22742,6 +22750,7 @@ __metadata: typeorm: ^0.3.12 typescript: ^5.0.0 typesense: ^1.5.3 + url: ^0.11.3 usearch: ^1.1.1 uuid: ^9.0.0 vectordb: ^0.1.4 @@ -31551,7 +31560,7 @@ __metadata: languageName: node linkType: hard -"url@npm:^0.11.0": +"url@npm:^0.11.0, url@npm:^0.11.3": version: 0.11.3 resolution: "url@npm:0.11.3" dependencies: From d70da4145048898c3047da73f7ba9d833ab5fd04 Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Mon, 4 Dec 2023 18:43:28 -0500 Subject: [PATCH 19/46] remove optional deps, remove modules using opt depd from index entrypoint --- langchain/package.json | 4 --- langchain/src/tools/index.ts | 7 ----- langchain/src/tools/outlook/authFlowREST.ts | 6 ++++- langchain/src/tools/outlook/authFlowToken.ts | 2 +- langchain/src/tools/outlook/descriptions.ts | 1 - .../tools/tests/outlookIntegration.test.ts | 8 +++--- yarn.lock | 27 ------------------- 7 files changed, 11 insertions(+), 44 deletions(-) diff --git a/langchain/package.json b/langchain/package.json index 3be086ce57dd..4bcf4d583722 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -924,7 +924,6 @@ "@types/jsdom": "^21.1.1", "@types/lodash": "^4", "@types/mozilla-readability": "^0.2.1", - "@types/openurl": "^1", "@types/pdf-parse": "^1.1.1", "@types/pg": "^8", "@types/pg-copy-streams": "^1.2.2", @@ -1428,7 +1427,6 @@ "binary-extensions": "^2.2.0", "expr-eval": "^2.0.2", "flat": "^5.0.2", - "http": "^0.0.1-security", "js-tiktoken": "^1.0.7", "js-yaml": "^4.1.0", "jsonpointer": "^5.0.1", @@ -1437,8 +1435,6 @@ "ml-distance": "^4.0.0", "openai": "^4.19.0", "openapi-types": "^12.1.3", - "openurl": "^1.1.1", - "p-queue": "^6.6.2", "p-retry": "4", "url": "^0.11.3", "uuid": "^9.0.0", diff --git a/langchain/src/tools/index.ts b/langchain/src/tools/index.ts index 29fca40d6b74..f29955016343 100644 --- a/langchain/src/tools/index.ts +++ b/langchain/src/tools/index.ts @@ -47,10 +47,3 @@ export { formatToOpenAIFunction, formatToOpenAITool, } from "./convert_to_openai.js"; - -export { OutlookSendMailTool, OutlookReadMailTool } from "./outlook/index.js"; -export { - GooglePlacesAPI, - type GooglePlacesAPIParams, -} from "./google_places.js"; - diff --git a/langchain/src/tools/outlook/authFlowREST.ts b/langchain/src/tools/outlook/authFlowREST.ts index c4d6490681bf..f587d46c3510 100644 --- a/langchain/src/tools/outlook/authFlowREST.ts +++ b/langchain/src/tools/outlook/authFlowREST.ts @@ -20,7 +20,11 @@ export class AuthFlowREST extends AuthFlowBase { private refreshToken = ""; - constructor({ clientId, clientSecret, redirectUri }: { clientId?: string, clientSecret?: string, redirectUri?: string } = {}) { + constructor({ + clientId, + clientSecret, + redirectUri, + }: { clientId?: string; clientSecret?: string; redirectUri?: string } = {}) { let id = clientId; let secret = clientSecret; let uri = redirectUri; diff --git a/langchain/src/tools/outlook/authFlowToken.ts b/langchain/src/tools/outlook/authFlowToken.ts index 546b21bbbe7e..aba1a431fb1c 100644 --- a/langchain/src/tools/outlook/authFlowToken.ts +++ b/langchain/src/tools/outlook/authFlowToken.ts @@ -32,7 +32,7 @@ export class AuthFlowToken extends AuthFlowBase { // if you have the refresh token and other credentials export class AuthFlowRefresh extends AuthFlowBase { private clientSecret: string; - + private redirectUri: string; private refreshToken: string; diff --git a/langchain/src/tools/outlook/descriptions.ts b/langchain/src/tools/outlook/descriptions.ts index a54d146604f2..c7b082c1416f 100644 --- a/langchain/src/tools/outlook/descriptions.ts +++ b/langchain/src/tools/outlook/descriptions.ts @@ -37,4 +37,3 @@ If on default: $search="" On specified property: $search=":" Example: $search="sent:07/23/2018" `; - diff --git a/langchain/src/tools/tests/outlookIntegration.test.ts b/langchain/src/tools/tests/outlookIntegration.test.ts index 5994e49a5e9f..f62f843009af 100644 --- a/langchain/src/tools/tests/outlookIntegration.test.ts +++ b/langchain/src/tools/tests/outlookIntegration.test.ts @@ -1,8 +1,10 @@ -import { AuthFlowToken, OutlookReadMailTool, OutlookSendMailTool } from "../outlook/index.js"; - +import { + AuthFlowToken, + OutlookReadMailTool, + OutlookSendMailTool, +} from "../outlook/index.js"; describe("OutlookReadMailTool Test", () => { - test("Test invalid access token", async () => { const accessToken = "blah"; const authFlow = new AuthFlowToken(accessToken); diff --git a/yarn.lock b/yarn.lock index 55a05d151936..fa0730309a2e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11646,15 +11646,6 @@ __metadata: languageName: node linkType: hard -"@types/openurl@npm:^1": - version: 1.0.3 - resolution: "@types/openurl@npm:1.0.3" - dependencies: - "@types/node": "*" - checksum: a5454ca5cc08bc857231742aecd118e600cb7bd284ba72301bb46c4e55875b6ddc58ed32776b9d71843d1180aa7430cca7eb555a3f7b536aa4b54ebcdcc2a28b - languageName: node - linkType: hard - "@types/pad-left@npm:2.1.1": version: 2.1.1 resolution: "@types/pad-left@npm:2.1.1" @@ -20373,13 +20364,6 @@ __metadata: languageName: node linkType: hard -"http@npm:^0.0.1-security": - version: 0.0.1-security - resolution: "http@npm:0.0.1-security" - checksum: c45a4e2771b5679b479b24150216e0b423d5bf2f94062c91b4b5e5347ed3f18b8c049e220df6e328c34ac5aab0d020600a05a9ea954bc7a7efc04d8dde7aada0 - languageName: node - linkType: hard - "https-proxy-agent@npm:5, https-proxy-agent@npm:5.0.1, https-proxy-agent@npm:^5.0.0, https-proxy-agent@npm:^5.0.1": version: 5.0.1 resolution: "https-proxy-agent@npm:5.0.1" @@ -22653,7 +22637,6 @@ __metadata: "@types/jsdom": ^21.1.1 "@types/lodash": ^4 "@types/mozilla-readability": ^0.2.1 - "@types/openurl": ^1 "@types/pdf-parse": ^1.1.1 "@types/pg": ^8 "@types/pg-copy-streams": ^1.2.2 @@ -22701,7 +22684,6 @@ __metadata: graphql: ^16.6.0 hnswlib-node: ^1.4.2 html-to-text: ^9.0.5 - http: ^0.0.1-security ignore: ^5.2.0 ioredis: ^5.3.2 jest: ^29.5.0 @@ -22725,8 +22707,6 @@ __metadata: officeparser: ^4.0.4 openai: ^4.19.0 openapi-types: ^12.1.3 - openurl: ^1.1.1 - p-queue: ^6.6.2 p-retry: 4 pdf-parse: 1.1.1 peggy: ^3.0.2 @@ -25425,13 +25405,6 @@ __metadata: languageName: node linkType: hard -"openurl@npm:^1.1.1": - version: 1.1.1 - resolution: "openurl@npm:1.1.1" - checksum: c90f2f065bc5950f1402aff67a3ce4b5fb0e4475cb07b5ff84247686f7436fbc5bc2d0e38bda4ebc9cf8aea866788424e07f25a68f7e97502d412527964351a9 - languageName: node - linkType: hard - "option@npm:~0.2.1": version: 0.2.4 resolution: "option@npm:0.2.4" From 43ee152f6d349a4600e8c86ec84c8494d4475565 Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Tue, 5 Dec 2023 17:07:50 -0500 Subject: [PATCH 20/46] add to create-entrypoints --- langchain/scripts/create-entrypoints.js | 1 + langchain/src/load/import_constants.ts | 1 + langchain/src/load/import_map.ts | 1 - langchain/src/load/import_type.d.ts | 3 +++ 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/langchain/scripts/create-entrypoints.js b/langchain/scripts/create-entrypoints.js index 5559914dc19f..3bfcbeed3070 100644 --- a/langchain/scripts/create-entrypoints.js +++ b/langchain/scripts/create-entrypoints.js @@ -359,6 +359,7 @@ const requiresOptionalDependency = [ "tools/webbrowser", "tools/google_calendar", "tools/gmail", + "tools/outlook", "callbacks/handlers/llmonitor", "chains/load", "chains/sql_db", diff --git a/langchain/src/load/import_constants.ts b/langchain/src/load/import_constants.ts index d45a9c6d6227..bc89e5ac136b 100644 --- a/langchain/src/load/import_constants.ts +++ b/langchain/src/load/import_constants.ts @@ -11,6 +11,7 @@ export const optionalImportEntrypoints = [ "langchain/tools/webbrowser", "langchain/tools/gmail", "langchain/tools/google_calendar", + "langchain/tools/outlook", "langchain/chains/load", "langchain/chains/query_constructor", "langchain/chains/query_constructor/ir", diff --git a/langchain/src/load/import_map.ts b/langchain/src/load/import_map.ts index 04261333593f..fa6ef233e228 100644 --- a/langchain/src/load/import_map.ts +++ b/langchain/src/load/import_map.ts @@ -16,7 +16,6 @@ export * as base_language from "../base_language/index.js"; export * as tools from "../tools/index.js"; export * as tools__connery from "../tools/connery.js"; export * as tools__render from "../tools/render.js"; -export * as tools__outlook from "../tools/outlook/index.js"; export * as tools__google_places from "../tools/google_places.js"; export * as chains from "../chains/index.js"; export * as chains__combine_documents__reduce from "../chains/combine_documents/reduce.js"; diff --git a/langchain/src/load/import_type.d.ts b/langchain/src/load/import_type.d.ts index 9b2b3abbde95..56108a10febd 100644 --- a/langchain/src/load/import_type.d.ts +++ b/langchain/src/load/import_type.d.ts @@ -31,6 +31,9 @@ export interface OptionalImportMap { "langchain/tools/google_calendar"?: | typeof import("../tools/google_calendar/index.js") | Promise; + "langchain/tools/outlook"?: + | typeof import("../tools/outlook/index.js") + | Promise; "langchain/chains/load"?: | typeof import("../chains/load.js") | Promise; From d4bf536e690870d320b88554a948c0bf03d22b87 Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Tue, 5 Dec 2023 17:40:23 -0500 Subject: [PATCH 21/46] change from openurl to open package --- environment_tests/test-exports-bun/src/entrypoints.js | 1 - environment_tests/test-exports-cf/src/entrypoints.js | 1 - environment_tests/test-exports-cjs/src/entrypoints.js | 1 - environment_tests/test-exports-esbuild/src/entrypoints.js | 1 - environment_tests/test-exports-esm/src/entrypoints.js | 1 - environment_tests/test-exports-vercel/src/entrypoints.js | 1 - environment_tests/test-exports-vite/src/entrypoints.js | 1 - langchain/package.json | 5 +++++ langchain/src/tools/outlook/authFlowREST.ts | 6 ++++-- yarn.lock | 4 ++++ 10 files changed, 13 insertions(+), 9 deletions(-) diff --git a/environment_tests/test-exports-bun/src/entrypoints.js b/environment_tests/test-exports-bun/src/entrypoints.js index c1f7e5acf570..3f823c50181f 100644 --- a/environment_tests/test-exports-bun/src/entrypoints.js +++ b/environment_tests/test-exports-bun/src/entrypoints.js @@ -15,7 +15,6 @@ export * from "langchain/base_language"; export * from "langchain/tools"; export * from "langchain/tools/connery"; export * from "langchain/tools/render"; -export * from "langchain/tools/outlook"; export * from "langchain/tools/google_places"; export * from "langchain/chains"; export * from "langchain/chains/combine_documents/reduce"; diff --git a/environment_tests/test-exports-cf/src/entrypoints.js b/environment_tests/test-exports-cf/src/entrypoints.js index c1f7e5acf570..3f823c50181f 100644 --- a/environment_tests/test-exports-cf/src/entrypoints.js +++ b/environment_tests/test-exports-cf/src/entrypoints.js @@ -15,7 +15,6 @@ export * from "langchain/base_language"; export * from "langchain/tools"; export * from "langchain/tools/connery"; export * from "langchain/tools/render"; -export * from "langchain/tools/outlook"; export * from "langchain/tools/google_places"; export * from "langchain/chains"; export * from "langchain/chains/combine_documents/reduce"; diff --git a/environment_tests/test-exports-cjs/src/entrypoints.js b/environment_tests/test-exports-cjs/src/entrypoints.js index af5fe4fbd16c..48ed405030ab 100644 --- a/environment_tests/test-exports-cjs/src/entrypoints.js +++ b/environment_tests/test-exports-cjs/src/entrypoints.js @@ -15,7 +15,6 @@ const base_language = require("langchain/base_language"); const tools = require("langchain/tools"); const tools_connery = require("langchain/tools/connery"); const tools_render = require("langchain/tools/render"); -const tools_outlook = require("langchain/tools/outlook"); const tools_google_places = require("langchain/tools/google_places"); const chains = require("langchain/chains"); const chains_combine_documents_reduce = require("langchain/chains/combine_documents/reduce"); diff --git a/environment_tests/test-exports-esbuild/src/entrypoints.js b/environment_tests/test-exports-esbuild/src/entrypoints.js index f822c08e737c..b8a0473cfcc2 100644 --- a/environment_tests/test-exports-esbuild/src/entrypoints.js +++ b/environment_tests/test-exports-esbuild/src/entrypoints.js @@ -15,7 +15,6 @@ import * as base_language from "langchain/base_language"; import * as tools from "langchain/tools"; import * as tools_connery from "langchain/tools/connery"; import * as tools_render from "langchain/tools/render"; -import * as tools_outlook from "langchain/tools/outlook"; import * as tools_google_places from "langchain/tools/google_places"; import * as chains from "langchain/chains"; import * as chains_combine_documents_reduce from "langchain/chains/combine_documents/reduce"; diff --git a/environment_tests/test-exports-esm/src/entrypoints.js b/environment_tests/test-exports-esm/src/entrypoints.js index f822c08e737c..b8a0473cfcc2 100644 --- a/environment_tests/test-exports-esm/src/entrypoints.js +++ b/environment_tests/test-exports-esm/src/entrypoints.js @@ -15,7 +15,6 @@ import * as base_language from "langchain/base_language"; import * as tools from "langchain/tools"; import * as tools_connery from "langchain/tools/connery"; import * as tools_render from "langchain/tools/render"; -import * as tools_outlook from "langchain/tools/outlook"; import * as tools_google_places from "langchain/tools/google_places"; import * as chains from "langchain/chains"; import * as chains_combine_documents_reduce from "langchain/chains/combine_documents/reduce"; diff --git a/environment_tests/test-exports-vercel/src/entrypoints.js b/environment_tests/test-exports-vercel/src/entrypoints.js index c1f7e5acf570..3f823c50181f 100644 --- a/environment_tests/test-exports-vercel/src/entrypoints.js +++ b/environment_tests/test-exports-vercel/src/entrypoints.js @@ -15,7 +15,6 @@ export * from "langchain/base_language"; export * from "langchain/tools"; export * from "langchain/tools/connery"; export * from "langchain/tools/render"; -export * from "langchain/tools/outlook"; export * from "langchain/tools/google_places"; export * from "langchain/chains"; export * from "langchain/chains/combine_documents/reduce"; diff --git a/environment_tests/test-exports-vite/src/entrypoints.js b/environment_tests/test-exports-vite/src/entrypoints.js index c1f7e5acf570..3f823c50181f 100644 --- a/environment_tests/test-exports-vite/src/entrypoints.js +++ b/environment_tests/test-exports-vite/src/entrypoints.js @@ -15,7 +15,6 @@ export * from "langchain/base_language"; export * from "langchain/tools"; export * from "langchain/tools/connery"; export * from "langchain/tools/render"; -export * from "langchain/tools/outlook"; export * from "langchain/tools/google_places"; export * from "langchain/chains"; export * from "langchain/chains/combine_documents/reduce"; diff --git a/langchain/package.json b/langchain/package.json index 83aee66778c8..67ec5a2161f6 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -995,6 +995,7 @@ "node-llama-cpp": "2.7.3", "notion-to-md": "^3.1.0", "officeparser": "^4.0.4", + "open": "9.1.0", "pdf-parse": "1.1.1", "peggy": "^3.0.2", "pg": "^8.11.0", @@ -1104,6 +1105,7 @@ "node-llama-cpp": "*", "notion-to-md": "^3.1.0", "officeparser": "^4.0.4", + "open": "9.1.0", "pdf-parse": "1.1.1", "peggy": "^3.0.2", "pg": "^8.11.0", @@ -1363,6 +1365,9 @@ "officeparser": { "optional": true }, + "open": { + "optional": true + }, "pdf-parse": { "optional": true }, diff --git a/langchain/src/tools/outlook/authFlowREST.ts b/langchain/src/tools/outlook/authFlowREST.ts index f587d46c3510..044872652ea9 100644 --- a/langchain/src/tools/outlook/authFlowREST.ts +++ b/langchain/src/tools/outlook/authFlowREST.ts @@ -1,6 +1,6 @@ import * as http from "http"; import * as url from "url"; -import * as openurl from "openurl"; +import open from "open"; import { AuthFlowBase } from "./authFlowBase.js"; import { getEnvironmentVariable } from "../../util/env.js"; @@ -65,7 +65,9 @@ export class AuthFlowREST extends AuthFlowBase { `&scope=${scope}`, `&state=${state}`, ].join(""); - openurl.open(url); + open(url).catch((error) => { + console.error("Error opening URL: ", error); + }); return url; } diff --git a/yarn.lock b/yarn.lock index 4a7269ac69d2..6db3e4658cc2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22747,6 +22747,7 @@ __metadata: node-llama-cpp: 2.7.3 notion-to-md: ^3.1.0 officeparser: ^4.0.4 + open: 9.1.0 openai: ^4.19.0 openapi-types: ^12.1.3 p-retry: 4 @@ -22863,6 +22864,7 @@ __metadata: node-llama-cpp: "*" notion-to-md: ^3.1.0 officeparser: ^4.0.4 + open: 9.1.0 pdf-parse: 1.1.1 peggy: ^3.0.2 pg: ^8.11.0 @@ -23043,6 +23045,8 @@ __metadata: optional: true officeparser: optional: true + open: + optional: true pdf-parse: optional: true peggy: From 960d0822dc95ecc8237c78a6947c6f8d79a1d3db Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Tue, 5 Dec 2023 20:22:56 -0500 Subject: [PATCH 22/46] remove url from dependencies --- langchain/package.json | 1 - yarn.lock | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/langchain/package.json b/langchain/package.json index 4e549c62f10d..fc0a699c501e 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -1453,7 +1453,6 @@ "openai": "^4.19.0", "openapi-types": "^12.1.3", "p-retry": "4", - "url": "^0.11.3", "uuid": "^9.0.0", "yaml": "^2.2.1", "zod": "^3.22.3", diff --git a/yarn.lock b/yarn.lock index a5808ba975aa..db22c978ae5f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22782,7 +22782,6 @@ __metadata: typeorm: ^0.3.12 typescript: ^5.0.0 typesense: ^1.5.3 - url: ^0.11.3 usearch: ^1.1.1 uuid: ^9.0.0 vectordb: ^0.1.4 @@ -31590,7 +31589,7 @@ __metadata: languageName: node linkType: hard -"url@npm:^0.11.0, url@npm:^0.11.3": +"url@npm:^0.11.0": version: 0.11.3 resolution: "url@npm:0.11.3" dependencies: From f4861fefc869753ec10c79babc18a4663bb48c13 Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Tue, 5 Dec 2023 21:33:33 -0500 Subject: [PATCH 23/46] log the url for manually open instead of using open package --- langchain/package.json | 5 ----- langchain/src/tools/outlook/authFlowREST.ts | 22 +++++++++++++++------ yarn.lock | 4 ---- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/langchain/package.json b/langchain/package.json index fc0a699c501e..b1d388cc0b3e 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -995,7 +995,6 @@ "node-llama-cpp": "2.7.3", "notion-to-md": "^3.1.0", "officeparser": "^4.0.4", - "open": "9.1.0", "pdf-parse": "1.1.1", "peggy": "^3.0.2", "pg": "^8.11.0", @@ -1105,7 +1104,6 @@ "node-llama-cpp": "*", "notion-to-md": "^3.1.0", "officeparser": "^4.0.4", - "open": "9.1.0", "pdf-parse": "1.1.1", "peggy": "^3.0.2", "pg": "^8.11.0", @@ -1365,9 +1363,6 @@ "officeparser": { "optional": true }, - "open": { - "optional": true - }, "pdf-parse": { "optional": true }, diff --git a/langchain/src/tools/outlook/authFlowREST.ts b/langchain/src/tools/outlook/authFlowREST.ts index 044872652ea9..ccbe5560f0b6 100644 --- a/langchain/src/tools/outlook/authFlowREST.ts +++ b/langchain/src/tools/outlook/authFlowREST.ts @@ -1,6 +1,5 @@ import * as http from "http"; import * as url from "url"; -import open from "open"; import { AuthFlowBase } from "./authFlowBase.js"; import { getEnvironmentVariable } from "../../util/env.js"; @@ -9,6 +8,9 @@ interface AccessTokenResponse { refresh_token: string; } +// this class uses your clientId, clientSecret and redirectUri +// to get access token and refresh token, if you already have them, +// you can use AuthFlowToken or AuthFlowRefresh instead export class AuthFlowREST extends AuthFlowBase { private clientSecret: string; @@ -44,7 +46,7 @@ export class AuthFlowREST extends AuthFlowBase { this.pathname = parsedUrl.pathname || ""; } - // Function to construct the OAuth URL + // Function to open or print the login url for user to login private openAuthUrl(): string { const loginEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"; @@ -65,9 +67,9 @@ export class AuthFlowREST extends AuthFlowBase { `&scope=${scope}`, `&state=${state}`, ].join(""); - open(url).catch((error) => { - console.error("Error opening URL: ", error); - }); + + console.log("Please open the following url to login:"); + console.log(url); return url; } @@ -90,6 +92,12 @@ export class AuthFlowREST extends AuthFlowBase { // Function to listen for the authorization code private async listenForCode(server: http.Server): Promise { return new Promise((resolve, reject) => { + // Set timeout in case the user fails to login + const timeout = setTimeout(() => { + server.close(); + reject(new Error("Timeout")); + }, 180000); + server.on("request", (req, res) => { try { const reqUrl = url.parse(req.url || "", true); @@ -100,6 +108,7 @@ export class AuthFlowREST extends AuthFlowBase { res.writeHead(200, { "Content-Type": "text/html" }); res.end("Authorization code received. You can close this window."); + clearTimeout(timeout); server.close(); console.log("Server closed"); resolve(authCode); // Resolve the Promise with the authorization code @@ -108,6 +117,7 @@ export class AuthFlowREST extends AuthFlowBase { res.end("404 Not Found"); } } catch (err) { + clearTimeout(timeout); res.writeHead(500); res.end("Server error"); reject(err); @@ -116,7 +126,7 @@ export class AuthFlowREST extends AuthFlowBase { }); } - // Main function to run the auth flow + // get authentication code from user login private async getCode(): Promise { // check credentials if (!this.clientId || !this.redirectUri) { diff --git a/yarn.lock b/yarn.lock index db22c978ae5f..1d547abb963e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22756,7 +22756,6 @@ __metadata: node-llama-cpp: 2.7.3 notion-to-md: ^3.1.0 officeparser: ^4.0.4 - open: 9.1.0 openai: ^4.19.0 openapi-types: ^12.1.3 p-retry: 4 @@ -22872,7 +22871,6 @@ __metadata: node-llama-cpp: "*" notion-to-md: ^3.1.0 officeparser: ^4.0.4 - open: 9.1.0 pdf-parse: 1.1.1 peggy: ^3.0.2 pg: ^8.11.0 @@ -23053,8 +23051,6 @@ __metadata: optional: true officeparser: optional: true - open: - optional: true pdf-parse: optional: true peggy: From c518e9fe0fa5191d600318b18233ac40619d7139 Mon Sep 17 00:00:00 2001 From: Qi123123Li <94654984+Qi123123Li@users.noreply.github.com> Date: Wed, 6 Dec 2023 16:08:15 -0500 Subject: [PATCH 24/46] Update langchain/src/tools/outlook/authFlowREST.ts Co-authored-by: Brace Sproul --- langchain/src/tools/outlook/authFlowREST.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/langchain/src/tools/outlook/authFlowREST.ts b/langchain/src/tools/outlook/authFlowREST.ts index ccbe5560f0b6..40fd53328b35 100644 --- a/langchain/src/tools/outlook/authFlowREST.ts +++ b/langchain/src/tools/outlook/authFlowREST.ts @@ -31,9 +31,9 @@ export class AuthFlowREST extends AuthFlowBase { let secret = clientSecret; let uri = redirectUri; if (!id || !secret || !uri) { - id = getEnvironmentVariable("OUTLOOK_CLIENT_ID"); - secret = getEnvironmentVariable("OUTLOOK_CLIENT_SECRET"); - uri = getEnvironmentVariable("OUTLOOK_REDIRECT_URI"); + id = id ?? getEnvironmentVariable("OUTLOOK_CLIENT_ID"); + secret = secret ?? getEnvironmentVariable("OUTLOOK_CLIENT_SECRET"); + uri = uri ?? getEnvironmentVariable("OUTLOOK_REDIRECT_URI"); } if (!id || !secret || !uri) { throw new Error("Missing clientId, clientSecret or redirectUri."); From 3212a3cd7119d7a0791dad0bea532d223992fa35 Mon Sep 17 00:00:00 2001 From: Qi123123Li <94654984+Qi123123Li@users.noreply.github.com> Date: Wed, 6 Dec 2023 18:43:43 -0500 Subject: [PATCH 25/46] Update langchain/src/tools/outlook/descriptions.ts Co-authored-by: Brace Sproul --- langchain/src/tools/outlook/descriptions.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/langchain/src/tools/outlook/descriptions.ts b/langchain/src/tools/outlook/descriptions.ts index c7b082c1416f..17af03000a55 100644 --- a/langchain/src/tools/outlook/descriptions.ts +++ b/langchain/src/tools/outlook/descriptions.ts @@ -11,10 +11,6 @@ export const SEND_MAIL_TOOL_DESCRIPTION = ` Ensure that the JSON object is correctly formatted and includes all four specified keys. `; -// This is an example of a valid JSON object: \ -// {\"subject\":\"Example Subject\",\"content\":\"Example Content\",\"to\":[\"team@example.com\"],\"cc\":[]}\ -// "; - export const READ_MAIL_TOOL_DESCRIPTION = `A tool for reading emails. You can search messages based on a value in specific message properties. The results of the search are sorted by the date and time that the message was sent. From 767acbccdfdcd66b3f35be842f5d912b4452201c Mon Sep 17 00:00:00 2001 From: Qi123123Li <94654984+Qi123123Li@users.noreply.github.com> Date: Wed, 6 Dec 2023 18:48:17 -0500 Subject: [PATCH 26/46] Update langchain/src/tools/outlook/descriptions.ts Co-authored-by: Brace Sproul --- langchain/src/tools/outlook/descriptions.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/langchain/src/tools/outlook/descriptions.ts b/langchain/src/tools/outlook/descriptions.ts index 17af03000a55..321452789945 100644 --- a/langchain/src/tools/outlook/descriptions.ts +++ b/langchain/src/tools/outlook/descriptions.ts @@ -1,14 +1,14 @@ export const SEND_MAIL_TOOL_DESCRIPTION = ` - A tool for sending emails. - input instructions: - input a JSON formatted email message with the following four keys: - 'subject', 'content', 'to', and 'cc'. - The 'subject' should be a brief title for the email, - 'content' should contain the body of the email, - 'to' should be an array of the recipient's email address, - and 'cc' should be an array of any additional recipient's email address. - The 'cc' key is optional, just give an empty array if no cc. - Ensure that the JSON object is correctly formatted and includes all four specified keys. +A tool for sending emails. +input instructions: +input a JSON formatted email message with the following four keys: +'subject', 'content', 'to', and 'cc'. +The 'subject' should be a brief title for the email, +'content' should contain the body of the email, +'to' should be an array of the recipient's email address, +and 'cc' should be an array of any additional recipient's email address. +The 'cc' key is optional, just give an empty array if no cc. +Ensure that the JSON object is correctly formatted and includes all four specified keys. `; export const READ_MAIL_TOOL_DESCRIPTION = `A tool for reading emails. From 216c3cc96370cd2deac44095cb6aed6c93e2e8e8 Mon Sep 17 00:00:00 2001 From: Qi123123Li <94654984+Qi123123Li@users.noreply.github.com> Date: Wed, 6 Dec 2023 18:48:41 -0500 Subject: [PATCH 27/46] Update langchain/src/tools/tests/outlookIntegration.test.ts Co-authored-by: Brace Sproul --- langchain/src/tools/tests/outlookIntegration.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langchain/src/tools/tests/outlookIntegration.test.ts b/langchain/src/tools/tests/outlookIntegration.test.ts index f62f843009af..22c4e8b7aba7 100644 --- a/langchain/src/tools/tests/outlookIntegration.test.ts +++ b/langchain/src/tools/tests/outlookIntegration.test.ts @@ -9,7 +9,7 @@ describe("OutlookReadMailTool Test", () => { const accessToken = "blah"; const authFlow = new AuthFlowToken(accessToken); const outlookTool = new OutlookReadMailTool(authFlow); - const emails = await outlookTool._call(""); + const emails = await outlookTool.call(""); expect(emails).toBe("Fetch mail error: 401"); }); From c94886eab746f4486a14d7bca4c8947b56fd1a6f Mon Sep 17 00:00:00 2001 From: Qi123123Li <94654984+Qi123123Li@users.noreply.github.com> Date: Wed, 6 Dec 2023 19:14:57 -0500 Subject: [PATCH 28/46] fix the format --- langchain/src/tools/outlook/authFlowREST.ts | 37 ++++++++++++-------- langchain/src/tools/outlook/authFlowToken.ts | 25 +++++++------ 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/langchain/src/tools/outlook/authFlowREST.ts b/langchain/src/tools/outlook/authFlowREST.ts index 40fd53328b35..d59adc2f954d 100644 --- a/langchain/src/tools/outlook/authFlowREST.ts +++ b/langchain/src/tools/outlook/authFlowREST.ts @@ -50,7 +50,7 @@ export class AuthFlowREST extends AuthFlowBase { private openAuthUrl(): string { const loginEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"; - const { clientId } = this; // client ID regestered in Azure + const clientId = this.clientId; // client ID regestered in Azure const response_type = "code"; const response_mode = "query"; const redirectUri = encodeURIComponent(this.redirectUri); // redirect URI regestered in Azure @@ -144,13 +144,16 @@ export class AuthFlowREST extends AuthFlowBase { // fetch auth code from user login const code = await this.getCode(); // fetch access token using auth code - const req_body = - `client_id=${encodeURIComponent(this.clientId)}&` + - `client_secret=${encodeURIComponent(this.clientSecret)}&` + - `scope=${encodeURIComponent("https://graph.microsoft.com/.default")}&` + - `redirect_uri=${encodeURIComponent(this.redirectUri)}&` + - `grant_type=authorization_code&` + - `code=${encodeURIComponent(code)}`; + const params = new URLSearchParams({ + client_id: this.clientId, + client_secret: this.clientSecret, + scope: "https://graph.microsoft.com/.default", + redirect_uri: this.redirectUri, + grant_type: "authorization_code", + code: code, + }); + + const req_body = params.toString(); const response = await fetch( "https://login.microsoftonline.com/common/oauth2/v2.0/token", @@ -175,13 +178,17 @@ export class AuthFlowREST extends AuthFlowBase { public async refreshAccessToken(): Promise { // fetch new access token using refresh token - const req_body = - `client_id=${encodeURIComponent(this.clientId)}&` + - `client_secret=${encodeURIComponent(this.clientSecret)}&` + - `scope=${encodeURIComponent("https://graph.microsoft.com/.default")}&` + - `redirect_uri=${encodeURIComponent(this.redirectUri)}&` + - `grant_type=refresh_token&` + - `refresh_token=${encodeURIComponent(this.refreshToken)}`; + const params = new URLSearchParams({ + client_id: this.clientId, + client_secret: this.clientSecret, + scope: "https://graph.microsoft.com/.default", + redirect_uri: this.redirectUri, + grant_type: "refresh_token", + refresh_token: this.refreshToken, + }); + + const req_body = params.toString(); + const response = await fetch( "https://login.microsoftonline.com/common/oauth2/v2.0/token", diff --git a/langchain/src/tools/outlook/authFlowToken.ts b/langchain/src/tools/outlook/authFlowToken.ts index aba1a431fb1c..c9b0ba4b636b 100644 --- a/langchain/src/tools/outlook/authFlowToken.ts +++ b/langchain/src/tools/outlook/authFlowToken.ts @@ -48,10 +48,10 @@ export class AuthFlowRefresh extends AuthFlowBase { let uri = redirectUri; let token = refreshToken; if (!id || !secret || !uri || !token) { - id = getEnvironmentVariable("OUTLOOK_CLIENT_ID"); - secret = getEnvironmentVariable("OUTLOOK_CLIENT_SECRET"); - uri = getEnvironmentVariable("OUTLOOK_REDIRECT_URI"); - token = getEnvironmentVariable("OUTLOOK_REFRESH_TOKEN"); + id = id ?? getEnvironmentVariable("OUTLOOK_CLIENT_ID"); + secret = secret ?? getEnvironmentVariable("OUTLOOK_CLIENT_SECRET"); + uri = uri ?? getEnvironmentVariable("OUTLOOK_REDIRECT_URI"); + token = token ?? getEnvironmentVariable("OUTLOOK_REFRESH_TOKEN"); } if (!id || !secret || !uri || !token) { throw new Error( @@ -66,13 +66,16 @@ export class AuthFlowRefresh extends AuthFlowBase { public async refreshAccessToken(): Promise { // fetch new access token using refresh token - const req_body = - `client_id=${encodeURIComponent(this.clientId)}&` + - `client_secret=${encodeURIComponent(this.clientSecret)}&` + - `scope=${encodeURIComponent("https://graph.microsoft.com/.default")}&` + - `redirect_uri=${encodeURIComponent(this.redirectUri)}&` + - `grant_type=refresh_token&` + - `refresh_token=${encodeURIComponent(this.refreshToken)}`; + const params = new URLSearchParams({ + client_id: this.clientId, + client_secret: this.clientSecret, + scope: "https://graph.microsoft.com/.default", + redirect_uri: this.redirectUri, + grant_type: "refresh_token", + refresh_token: this.refreshToken, + }); + + const req_body = params.toString(); const response = await fetch( "https://login.microsoftonline.com/common/oauth2/v2.0/token", From c3f6c74a6479e41b41388a28bee71354a73c2a45 Mon Sep 17 00:00:00 2001 From: Qi123123Li <94654984+Qi123123Li@users.noreply.github.com> Date: Wed, 6 Dec 2023 19:21:50 -0500 Subject: [PATCH 29/46] add the jsdoc --- langchain/src/tools/outlook/authFlowREST.ts | 93 ++++++++++++++++---- langchain/src/tools/outlook/authFlowToken.ts | 63 ++++++++++--- 2 files changed, 129 insertions(+), 27 deletions(-) diff --git a/langchain/src/tools/outlook/authFlowREST.ts b/langchain/src/tools/outlook/authFlowREST.ts index d59adc2f954d..3315f7de412e 100644 --- a/langchain/src/tools/outlook/authFlowREST.ts +++ b/langchain/src/tools/outlook/authFlowREST.ts @@ -3,25 +3,59 @@ import * as url from "url"; import { AuthFlowBase } from "./authFlowBase.js"; import { getEnvironmentVariable } from "../../util/env.js"; +/** + * Represents the response structure for an access token. + * @interface + */ interface AccessTokenResponse { access_token: string; refresh_token: string; } -// this class uses your clientId, clientSecret and redirectUri -// to get access token and refresh token, if you already have them, -// you can use AuthFlowToken or AuthFlowRefresh instead +/** + * Implements the RESTful authentication flow for Microsoft Graph API. + * Extends AuthFlowBase class. + * @class + */ export class AuthFlowREST extends AuthFlowBase { + /** + * The client secret used for authentication. + * @private + */ private clientSecret: string; + /** + * The redirect URI for the authentication flow. + * @private + */ private redirectUri: string; + /** + * The port on which the local server is running for handling authentication. + * @private + */ private port: number; + /** + * The pathname component of the redirect URI. + * @private + */ private pathname: string; + /** + * The refresh token obtained during authentication. + * @private + */ private refreshToken = ""; + /** + * Creates an instance of AuthFlowREST. + * @constructor + * @param {Object} options - Configuration options for the authentication flow. + * @param {string} options.clientId - The client ID for authentication. + * @param {string} options.clientSecret - The client secret for authentication. + * @param {string} options.redirectUri - The redirect URI for authentication. + */ constructor({ clientId, clientSecret, @@ -36,7 +70,7 @@ export class AuthFlowREST extends AuthFlowBase { uri = uri ?? getEnvironmentVariable("OUTLOOK_REDIRECT_URI"); } if (!id || !secret || !uri) { - throw new Error("Missing clientId, clientSecret or redirectUri."); + throw new Error("Missing clientId, clientSecret, or redirectUri."); } super(id); this.clientSecret = secret; @@ -46,14 +80,18 @@ export class AuthFlowREST extends AuthFlowBase { this.pathname = parsedUrl.pathname || ""; } - // Function to open or print the login url for user to login + /** + * Opens the authentication URL and returns it. + * @private + * @returns {string} The authentication URL. + */ private openAuthUrl(): string { const loginEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"; - const clientId = this.clientId; // client ID regestered in Azure + const clientId = this.clientId; // client ID registered in Azure const response_type = "code"; const response_mode = "query"; - const redirectUri = encodeURIComponent(this.redirectUri); // redirect URI regestered in Azure + const redirectUri = encodeURIComponent(this.redirectUri); // redirect URI registered in Azure const scope = encodeURIComponent( "openid offline_access https://graph.microsoft.com/.default" ); @@ -68,12 +106,16 @@ export class AuthFlowREST extends AuthFlowBase { `&state=${state}`, ].join(""); - console.log("Please open the following url to login:"); + console.log("Please open the following URL to login:"); console.log(url); return url; } - // Function to start the server + /** + * Starts a local server for handling the authorization code. + * @private + * @returns {Promise} A Promise resolving to the created server. + */ private startServer(): Promise { return new Promise((resolve, reject) => { const server = http.createServer(); @@ -89,7 +131,12 @@ export class AuthFlowREST extends AuthFlowBase { }); } - // Function to listen for the authorization code + /** + * Listens for the authorization code on the local server. + * @private + * @param {http.Server} server - The server instance. + * @returns {Promise} A Promise resolving to the authorization code. + */ private async listenForCode(server: http.Server): Promise { return new Promise((resolve, reject) => { // Set timeout in case the user fails to login @@ -126,7 +173,11 @@ export class AuthFlowREST extends AuthFlowBase { }); } - // get authentication code from user login + /** + * Gets the authorization code from the user login. + * @private + * @returns {Promise} A Promise resolving to the authorization code. + */ private async getCode(): Promise { // check credentials if (!this.clientId || !this.redirectUri) { @@ -139,7 +190,11 @@ export class AuthFlowREST extends AuthFlowBase { return code; } - // Function to get the token using the code and client credentials + /** + * Gets the access token using the authorization code and client credentials. + * @public + * @returns {Promise} A Promise resolving to the access token. + */ public async getAccessToken(): Promise { // fetch auth code from user login const code = await this.getCode(); @@ -152,7 +207,7 @@ export class AuthFlowREST extends AuthFlowBase { grant_type: "authorization_code", code: code, }); - + const req_body = params.toString(); const response = await fetch( @@ -167,7 +222,7 @@ export class AuthFlowREST extends AuthFlowBase { ); if (!response.ok) { - throw new Error(`fetch token error! response: ${response.status}`); + throw new Error(`Fetch token error! Response: ${response.status}`); } // save access token and refresh token const json = (await response.json()) as AccessTokenResponse; @@ -176,6 +231,11 @@ export class AuthFlowREST extends AuthFlowBase { return this.accessToken; } + /** + * Refreshes the access token using the refresh token. + * @public + * @returns {Promise} A Promise resolving to the refreshed access token. + */ public async refreshAccessToken(): Promise { // fetch new access token using refresh token const params = new URLSearchParams({ @@ -186,9 +246,8 @@ export class AuthFlowREST extends AuthFlowBase { grant_type: "refresh_token", refresh_token: this.refreshToken, }); - + const req_body = params.toString(); - const response = await fetch( "https://login.microsoftonline.com/common/oauth2/v2.0/token", @@ -202,7 +261,7 @@ export class AuthFlowREST extends AuthFlowBase { ); if (!response.ok) { - throw new Error(`fetch token error! response: ${response.status}`); + throw new Error(`Fetch token error! Response: ${response.status}`); } // save new access token const json = (await response.json()) as AccessTokenResponse; diff --git a/langchain/src/tools/outlook/authFlowToken.ts b/langchain/src/tools/outlook/authFlowToken.ts index c9b0ba4b636b..63b2f3a5dea7 100644 --- a/langchain/src/tools/outlook/authFlowToken.ts +++ b/langchain/src/tools/outlook/authFlowToken.ts @@ -1,13 +1,27 @@ import { AuthFlowBase } from "./authFlowBase.js"; import { getEnvironmentVariable } from "../../util/env.js"; +/** + * Response structure for access token. + * @interface AccessTokenResponse + */ interface AccessTokenResponse { access_token: string; refresh_token: string; } -// if you have the token, and no need to refresh it, warning: token expires in 1 hour +/** + * Authentication flow with a provided access token. + * If the token is not provided, it falls back to an environment variable. + * @class AuthFlowToken + * @extends AuthFlowBase + */ export class AuthFlowToken extends AuthFlowBase { + /** + * Constructor for AuthFlowToken. + * @param {string} [accessToken] - Optional access token. + * @throws Will throw an error if access token is missing. + */ constructor(accessToken?: string) { let token = accessToken; if (!token) { @@ -20,23 +34,43 @@ export class AuthFlowToken extends AuthFlowBase { this.accessToken = token; } + /** + * Refreshes the access token. + * @async + * @returns {Promise} - The refreshed access token. + */ public async refreshAccessToken(): Promise { return this.accessToken; } + /** + * Gets the access token. + * @async + * @returns {Promise} - The access token. + */ public async getAccessToken(): Promise { return this.accessToken; } } -// if you have the refresh token and other credentials +/** + * Authentication flow with a refresh token and other credentials. + * @class AuthFlowRefresh + * @extends AuthFlowBase + */ export class AuthFlowRefresh extends AuthFlowBase { private clientSecret: string; - private redirectUri: string; - private refreshToken: string; + /** + * Constructor for AuthFlowRefresh. + * @param {string} [clientId] - Optional client ID. + * @param {string} [clientSecret] - Optional client secret. + * @param {string} [redirectUri] - Optional redirect URI. + * @param {string} [refreshToken] - Optional refresh token. + * @throws Will throw an error if any required parameter is missing. + */ constructor( clientId?: string, clientSecret?: string, @@ -55,7 +89,7 @@ export class AuthFlowRefresh extends AuthFlowBase { } if (!id || !secret || !uri || !token) { throw new Error( - "Missing clientId, clientSecret, redirectUri or refreshToken." + "Missing clientId, clientSecret, redirectUri, or refreshToken." ); } super(id); @@ -64,8 +98,13 @@ export class AuthFlowRefresh extends AuthFlowBase { this.refreshToken = token; } + /** + * Refreshes the access token using the refresh token. + * @async + * @returns {Promise} - The refreshed access token. + * @throws Will throw an error if the token fetch fails. + */ public async refreshAccessToken(): Promise { - // fetch new access token using refresh token const params = new URLSearchParams({ client_id: this.clientId, client_secret: this.clientSecret, @@ -74,7 +113,7 @@ export class AuthFlowRefresh extends AuthFlowBase { grant_type: "refresh_token", refresh_token: this.refreshToken, }); - + const req_body = params.toString(); const response = await fetch( @@ -89,15 +128,19 @@ export class AuthFlowRefresh extends AuthFlowBase { ); if (!response.ok) { - throw new Error(`fetch token error! response: ${response.status}`); + throw new Error(`Fetch token error! Response: ${response.status}`); } - // save new access token + const json = (await response.json()) as AccessTokenResponse; this.accessToken = json.access_token; return this.accessToken; } - // Function to get the token using the code and client credentials + /** + * Gets the access token by refreshing it. + * @async + * @returns {Promise} - The access token. + */ public async getAccessToken(): Promise { const accessToken = await this.refreshAccessToken(); this.accessToken = accessToken; From 3cec11e5398973df4c09662e3bbe9f4df1da1e60 Mon Sep 17 00:00:00 2001 From: Qi123123Li <94654984+Qi123123Li@users.noreply.github.com> Date: Wed, 6 Dec 2023 19:32:30 -0500 Subject: [PATCH 30/46] add jsdoc --- langchain/src/tools/outlook/authFlowBase.ts | 39 +++++++++++++++++++++ langchain/src/tools/outlook/base.ts | 39 +++++++++++++++++++++ langchain/src/tools/outlook/readMail.ts | 39 ++++++++++++++++++--- langchain/src/tools/outlook/sendMail.ts | 24 ++++++++++++- 4 files changed, 135 insertions(+), 6 deletions(-) diff --git a/langchain/src/tools/outlook/authFlowBase.ts b/langchain/src/tools/outlook/authFlowBase.ts index 97651b8decbe..ec7609fec6b3 100644 --- a/langchain/src/tools/outlook/authFlowBase.ts +++ b/langchain/src/tools/outlook/authFlowBase.ts @@ -1,13 +1,52 @@ +/** + * Base class for handling authentication flows. + * + * @class AuthFlowBase + */ export abstract class AuthFlowBase { + /** + * The client ID used for authentication. + * + * @protected + * @type {string} + * @memberof AuthFlowBase + */ protected clientId: string; + /** + * The access token obtained through authentication. + * + * @protected + * @type {string} + * @memberof AuthFlowBase + */ protected accessToken = ""; + /** + * Creates an instance of AuthFlowBase. + * + * @param {string} clientId - The client ID for authentication. + * @memberof AuthFlowBase + */ constructor(clientId: string) { this.clientId = clientId; } + /** + * Abstract method to get the access token. + * + * @abstract + * @returns {Promise} A promise that resolves to the access token. + * @memberof AuthFlowBase + */ public abstract getAccessToken(): Promise; + /** + * Abstract method to refresh the access token. + * + * @abstract + * @returns {Promise} A promise that resolves to the refreshed access token. + * @memberof AuthFlowBase + */ public abstract refreshAccessToken(): Promise; } diff --git a/langchain/src/tools/outlook/base.ts b/langchain/src/tools/outlook/base.ts index 48c502f95372..122350512dc1 100644 --- a/langchain/src/tools/outlook/base.ts +++ b/langchain/src/tools/outlook/base.ts @@ -3,16 +3,44 @@ import { AuthFlowBase } from "./authFlowBase.js"; import { AuthFlowToken, AuthFlowRefresh } from "./authFlowToken.js"; import { AuthFlowREST } from "./authFlowREST.js"; +/** + * Tool for interacting with Outlook, allowing actions such as sending or reading emails. + * @extends Tool + */ export class OutlookBase extends Tool { + /** + * The name of the Outlook tool. + * @type {string} + */ name = "Outlook"; + /** + * Description of the Outlook tool. + * @type {string} + */ description = "A tool to send or read emails or do other features from Outlook."; + /** + * The authentication flow used for obtaining access tokens. + * @type {AuthFlowBase} + * @protected + */ protected authFlow: AuthFlowBase; + /** + * The access token obtained after successful authentication. + * @type {string} + * @protected + */ protected accessToken = ""; + /** + * Constructs an instance of the OutlookBase tool. + * @param {AuthFlowBase} authFlow - The authentication flow to use. + * @param {string} choice - The choice of authentication flow (token, refresh, rest). + * @throws {Error} Throws an error if an incorrect choice of built-in authFlow is provided. + */ constructor(authFlow?: AuthFlowBase, choice?: string) { super(); if (authFlow) { @@ -30,6 +58,11 @@ export class OutlookBase extends Tool { } } + /** + * Retrieves and returns the authentication token. + * If the token is not available, it first attempts to refresh the token, and if that fails, it obtains a new token. + * @returns {Promise} A promise that resolves to the authentication token. + */ async getAuth() { if (!this.accessToken) { this.accessToken = await this.authFlow.getAccessToken(); @@ -43,6 +76,12 @@ export class OutlookBase extends Tool { return this.accessToken; } + /** + * Placeholder method for making calls related to Outlook. + * @param {string} input - The input for the Outlook call. + * @returns {Promise} A promise that resolves to the result of the Outlook call. + * @protected + */ async _call(input: string) { return input; } diff --git a/langchain/src/tools/outlook/readMail.ts b/langchain/src/tools/outlook/readMail.ts index afa28797ec7f..477ec6023e1d 100644 --- a/langchain/src/tools/outlook/readMail.ts +++ b/langchain/src/tools/outlook/readMail.ts @@ -2,33 +2,58 @@ import { OutlookBase } from "./base.js"; import { READ_MAIL_TOOL_DESCRIPTION } from "./descriptions.js"; import { AuthFlowBase } from "./authFlowBase.js"; +/** + * Represents an email retrieved from Outlook. + * @interface + */ interface Email { subject: string; body: { content: string }; sender: { emailAddress: { name: string; address: string } }; } + +/** + * Class for interacting with the Outlook API to read emails. + * @extends OutlookBase + */ export class OutlookReadMailTool extends OutlookBase { + /** The name of the Outlook Read Mail tool. */ name = "outlook_read_mail"; + /** The description of the Outlook Read Mail tool. */ description = READ_MAIL_TOOL_DESCRIPTION; + /** + * Constructor for the OutlookReadMailTool class. + * @param {AuthFlowBase} [authFlow] - The authentication flow for the tool. + * @param {string} [choice] - Additional choice parameter. + */ constructor(authFlow?: AuthFlowBase, choice?: string) { super(authFlow, choice); } - async _call(query: string) { + /** + * Calls the Outlook API to fetch emails based on the provided query. + * @param {string} query - The query string to filter emails. + * @returns {Promise} - A formatted string containing email details. + */ + async _call(query: string): Promise { try { + // Ensure authentication is completed before making the API call. await this.getAuth(); } catch (error) { + // Handle authentication error. return `Failed to get access token: ${error}`; } - // validate query + + // Validate the format of the query string. const queryRegex = /^\$search="(?:body|cc|from|received|recipients|sent|subject|to)(?::[^"]*)?"$/; if (query && !queryRegex.test(query)) { return "Invalid query format"; } - // fetch emails from me/messages + + // Fetch emails from the Outlook API. const response = await fetch( `https://graph.microsoft.com/v1.0/me/messages?${query}&$top=5`, { @@ -40,10 +65,12 @@ export class OutlookReadMailTool extends OutlookBase { ); if (!response.ok) { + // Handle API call error. return `Fetch mail error: ${response.status}`; } + try { - // parse response + // Parse the API response and format email details. const data = (await response.json()) as { value: Email[] }; const formattedEmails = data.value .map((email) => { @@ -53,13 +80,15 @@ export class OutlookReadMailTool extends OutlookBase { email?.sender?.emailAddress?.name ?? "Unknown Sender"; const senderAddress = email?.sender?.emailAddress?.address ?? "No address"; - // Constructing the email string + + // Constructing the email string. return `subject: ${subject}\nsender: ${senderName} ${senderAddress}\nbody: ${bodyContent}\n`; }) .join("\n"); return formattedEmails; } catch (error) { + // Handle response parsing error. return `Failed to parse response: ${error}`; } } diff --git a/langchain/src/tools/outlook/sendMail.ts b/langchain/src/tools/outlook/sendMail.ts index 89c39c227b52..931fae619bdc 100644 --- a/langchain/src/tools/outlook/sendMail.ts +++ b/langchain/src/tools/outlook/sendMail.ts @@ -2,16 +2,38 @@ import { OutlookBase } from "./base.js"; import { SEND_MAIL_TOOL_DESCRIPTION } from "./descriptions.js"; import { AuthFlowBase } from "./authFlowBase.js"; +/** + * Class representing an Outlook send mail tool. + * @extends OutlookBase + */ export class OutlookSendMailTool extends OutlookBase { + /** + * The name of the send mail tool. + * @type {string} + */ name = "outlook_send_mail"; + /** + * The description of the send mail tool. + * @type {string} + */ description = SEND_MAIL_TOOL_DESCRIPTION; + /** + * Creates an instance of OutlookSendMailTool. + * @param {AuthFlowBase} [authFlow] - The authentication flow instance. + * @param {string} [choice] - The choice string. + */ constructor(authFlow?: AuthFlowBase, choice?: string) { super(authFlow, choice); } - async _call(message: string) { + /** + * Sends an email using the Outlook API. + * @param {string} message - The JSON string representing the email message. + * @returns {Promise} A promise that resolves with the result of the email sending process. + */ + async _call(message: string): Promise { try { await this.getAuth(); } catch (error) { From 9b1d3f41166014edfa70c7e3660ad8e539bad084 Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Sun, 10 Dec 2023 01:27:42 -0500 Subject: [PATCH 31/46] change OutlookBase to abstract; remove `state` in rest; apply camel case; improve test --- langchain/src/tools/outlook/authFlowREST.ts | 17 ++++++------- langchain/src/tools/outlook/authFlowToken.ts | 6 +++-- langchain/src/tools/outlook/base.ts | 12 +-------- .../tools/tests/outlookIntegration.test.ts | 25 +++++++------------ 4 files changed, 21 insertions(+), 39 deletions(-) diff --git a/langchain/src/tools/outlook/authFlowREST.ts b/langchain/src/tools/outlook/authFlowREST.ts index 3315f7de412e..7f3bbb14a832 100644 --- a/langchain/src/tools/outlook/authFlowREST.ts +++ b/langchain/src/tools/outlook/authFlowREST.ts @@ -66,7 +66,7 @@ export class AuthFlowREST extends AuthFlowBase { let uri = redirectUri; if (!id || !secret || !uri) { id = id ?? getEnvironmentVariable("OUTLOOK_CLIENT_ID"); - secret = secret ?? getEnvironmentVariable("OUTLOOK_CLIENT_SECRET"); + secret = secret ?? getEnvironmentVariable("OUTLOOK_CLIENT_SECRET"); uri = uri ?? getEnvironmentVariable("OUTLOOK_REDIRECT_URI"); } if (!id || !secret || !uri) { @@ -88,22 +88,19 @@ export class AuthFlowREST extends AuthFlowBase { private openAuthUrl(): string { const loginEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"; - const clientId = this.clientId; // client ID registered in Azure - const response_type = "code"; - const response_mode = "query"; + const responseType = "code"; + const responseMode = "query"; const redirectUri = encodeURIComponent(this.redirectUri); // redirect URI registered in Azure const scope = encodeURIComponent( "openid offline_access https://graph.microsoft.com/.default" ); - const state = "12345"; const url = [ - `${loginEndpoint}?client_id=${clientId}`, - `&response_type=${response_type}`, - `&response_mode=${response_mode}`, + `${loginEndpoint}?client_id=${this.clientId}`, + `&response_type=${responseType}`, + `&response_mode=${responseMode}`, `&redirect_uri=${redirectUri}`, `&scope=${scope}`, - `&state=${state}`, ].join(""); console.log("Please open the following URL to login:"); @@ -205,7 +202,7 @@ export class AuthFlowREST extends AuthFlowBase { scope: "https://graph.microsoft.com/.default", redirect_uri: this.redirectUri, grant_type: "authorization_code", - code: code, + code, }); const req_body = params.toString(); diff --git a/langchain/src/tools/outlook/authFlowToken.ts b/langchain/src/tools/outlook/authFlowToken.ts index 63b2f3a5dea7..bfaafec5a0c3 100644 --- a/langchain/src/tools/outlook/authFlowToken.ts +++ b/langchain/src/tools/outlook/authFlowToken.ts @@ -60,7 +60,9 @@ export class AuthFlowToken extends AuthFlowBase { */ export class AuthFlowRefresh extends AuthFlowBase { private clientSecret: string; + private redirectUri: string; + private refreshToken: string; /** @@ -114,7 +116,7 @@ export class AuthFlowRefresh extends AuthFlowBase { refresh_token: this.refreshToken, }); - const req_body = params.toString(); + const reqBody = params.toString(); const response = await fetch( "https://login.microsoftonline.com/common/oauth2/v2.0/token", @@ -123,7 +125,7 @@ export class AuthFlowRefresh extends AuthFlowBase { headers: { "Content-Type": "application/x-www-form-urlencoded", }, - body: req_body, + body: reqBody, } ); diff --git a/langchain/src/tools/outlook/base.ts b/langchain/src/tools/outlook/base.ts index 122350512dc1..98999aaaf2e1 100644 --- a/langchain/src/tools/outlook/base.ts +++ b/langchain/src/tools/outlook/base.ts @@ -7,7 +7,7 @@ import { AuthFlowREST } from "./authFlowREST.js"; * Tool for interacting with Outlook, allowing actions such as sending or reading emails. * @extends Tool */ -export class OutlookBase extends Tool { +export abstract class OutlookBase extends Tool { /** * The name of the Outlook tool. * @type {string} @@ -75,14 +75,4 @@ export class OutlookBase extends Tool { } return this.accessToken; } - - /** - * Placeholder method for making calls related to Outlook. - * @param {string} input - The input for the Outlook call. - * @returns {Promise} A promise that resolves to the result of the Outlook call. - * @protected - */ - async _call(input: string) { - return input; - } } diff --git a/langchain/src/tools/tests/outlookIntegration.test.ts b/langchain/src/tools/tests/outlookIntegration.test.ts index 22c4e8b7aba7..1e1966126e40 100644 --- a/langchain/src/tools/tests/outlookIntegration.test.ts +++ b/langchain/src/tools/tests/outlookIntegration.test.ts @@ -15,23 +15,20 @@ describe("OutlookReadMailTool Test", () => { test("Test read messages", async () => { const outlookTool = new OutlookReadMailTool(undefined, "refresh"); - const emails = await outlookTool._call(""); - console.log(emails); - expect(true).toBe(true); + const emails = await outlookTool.call(""); + expect(emails.substring(0, 7)).toBe("subject"); }); test("Test invalid query format", async () => { const outlookTool = new OutlookReadMailTool(undefined, "refresh"); - const emails = await outlookTool._call("blah"); - console.log(emails); + const emails = await outlookTool.call("blah"); expect(emails).toBe("Invalid query format"); }); test("Test query correct format", async () => { const outlookTool = new OutlookReadMailTool(undefined, "refresh"); - const emails = await outlookTool._call('$search="subject:hello"'); - console.log(emails); - expect(true).toBe(true); + const emails = await outlookTool.call('$search="subject:hello"'); + expect(emails.substring(0, 7)).toBe("subject"); }); }); @@ -44,8 +41,7 @@ describe("OutlookSendMailTool Test", () => { cc: [], }); const outlookTool = new OutlookSendMailTool(undefined, "refresh"); - const res = await outlookTool._call(message); - console.log(res); + const res = await outlookTool.call(message); expect(res).toBe("TO must be an array of valid email in strings"); }); @@ -57,16 +53,14 @@ describe("OutlookSendMailTool Test", () => { cc: ["blah"], }); const outlookTool = new OutlookSendMailTool(undefined, "refresh"); - const res = await outlookTool._call(message); - console.log(res); + const res = await outlookTool.call(message); expect(res).toBe("CC must be an array of valid email in strings"); }); test("Test invalid JSON format", async () => { const message = "blah"; const outlookTool = new OutlookSendMailTool(undefined, "refresh"); - const res = await outlookTool._call(message); - console.log(res); + const res = await outlookTool.call(message); expect(res).toBe("Invalid JSON format"); }); @@ -78,8 +72,7 @@ describe("OutlookSendMailTool Test", () => { cc: [], }); const outlookTool = new OutlookSendMailTool(undefined, "refresh"); - const res = await outlookTool._call(message); - console.log(res); + const res = await outlookTool.call(message); expect(res).toBe("Email sent"); }); }); From da61a071bdcc8f4609a130777723bb54ea304ebe Mon Sep 17 00:00:00 2001 From: jacoblee93 Date: Tue, 12 Dec 2023 14:04:23 -0800 Subject: [PATCH 32/46] Merge --- docs/api_refs/typedoc.json | 1 - langchain/.gitignore | 3 --- langchain/package.json | 8 -------- langchain/scripts/create-entrypoints.js | 2 -- langchain/src/load/import_constants.ts | 1 - langchain/src/load/import_type.d.ts | 3 --- libs/langchain-community/.gitignore | 3 +++ libs/langchain-community/package.json | 8 ++++++++ libs/langchain-community/scripts/create-entrypoints.js | 2 ++ libs/langchain-community/src/load/import_constants.ts | 1 + libs/langchain-community/src/load/import_type.d.ts | 3 +++ .../src/tools/outlook/authFlowBase.ts | 0 .../src/tools/outlook/authFlowREST.ts | 6 +++--- .../src/tools/outlook/authFlowToken.ts | 2 +- .../langchain-community}/src/tools/outlook/base.ts | 2 +- .../src/tools/outlook/descriptions.ts | 0 .../langchain-community}/src/tools/outlook/index.ts | 0 .../langchain-community}/src/tools/outlook/readMail.ts | 0 .../langchain-community}/src/tools/outlook/sendMail.ts | 0 .../src/tools/tests/outlook.int.test.ts | 0 20 files changed, 22 insertions(+), 23 deletions(-) rename {langchain => libs/langchain-community}/src/tools/outlook/authFlowBase.ts (100%) rename {langchain => libs/langchain-community}/src/tools/outlook/authFlowREST.ts (98%) rename {langchain => libs/langchain-community}/src/tools/outlook/authFlowToken.ts (98%) rename {langchain => libs/langchain-community}/src/tools/outlook/base.ts (98%) rename {langchain => libs/langchain-community}/src/tools/outlook/descriptions.ts (100%) rename {langchain => libs/langchain-community}/src/tools/outlook/index.ts (100%) rename {langchain => libs/langchain-community}/src/tools/outlook/readMail.ts (100%) rename {langchain => libs/langchain-community}/src/tools/outlook/sendMail.ts (100%) rename langchain/src/tools/tests/outlookIntegration.test.ts => libs/langchain-community/src/tools/tests/outlook.int.test.ts (100%) diff --git a/docs/api_refs/typedoc.json b/docs/api_refs/typedoc.json index e5d3231ef790..378c86f4d173 100644 --- a/docs/api_refs/typedoc.json +++ b/docs/api_refs/typedoc.json @@ -45,7 +45,6 @@ "./langchain/src/tools/webbrowser.ts", "./langchain/src/tools/gmail/index.ts", "./langchain/src/tools/google_calendar/index.ts", - "./langchain/src/tools/outlook/index.ts", "./langchain/src/tools/google_places.ts", "./langchain/src/chains/index.ts", "./langchain/src/chains/combine_documents/reduce.ts", diff --git a/langchain/.gitignore b/langchain/.gitignore index 2db842d8e5a4..635d41db8711 100644 --- a/langchain/.gitignore +++ b/langchain/.gitignore @@ -79,9 +79,6 @@ tools/gmail.d.ts tools/google_calendar.cjs tools/google_calendar.js tools/google_calendar.d.ts -tools/outlook.cjs -tools/outlook.js -tools/outlook.d.ts tools/google_places.cjs tools/google_places.js tools/google_places.d.ts diff --git a/langchain/package.json b/langchain/package.json index f964e8532cdb..461c08488333 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -91,9 +91,6 @@ "tools/google_calendar.cjs", "tools/google_calendar.js", "tools/google_calendar.d.ts", - "tools/outlook.cjs", - "tools/outlook.js", - "tools/outlook.d.ts", "tools/google_places.cjs", "tools/google_places.js", "tools/google_places.d.ts", @@ -1349,11 +1346,6 @@ "import": "./tools/google_calendar.js", "require": "./tools/google_calendar.cjs" }, - "./tools/outlook": { - "types": "./tools/outlook.d.ts", - "import": "./tools/outlook.js", - "require": "./tools/outlook.cjs" - }, "./tools/google_places": { "types": "./tools/google_places.d.ts", "import": "./tools/google_places.js", diff --git a/langchain/scripts/create-entrypoints.js b/langchain/scripts/create-entrypoints.js index 27578fb3bbf9..e5bb0b7c3b2e 100644 --- a/langchain/scripts/create-entrypoints.js +++ b/langchain/scripts/create-entrypoints.js @@ -40,7 +40,6 @@ const entrypoints = { "tools/webbrowser": "tools/webbrowser", "tools/gmail": "tools/gmail/index", "tools/google_calendar": "tools/google_calendar/index", - "tools/outlook": "tools/outlook/index", "tools/google_places": "tools/google_places", // chains chains: "chains/index", @@ -360,7 +359,6 @@ const requiresOptionalDependency = [ "tools/webbrowser", "tools/google_calendar", "tools/gmail", - "tools/outlook", "callbacks/handlers/llmonitor", "chains/load", "chains/sql_db", diff --git a/langchain/src/load/import_constants.ts b/langchain/src/load/import_constants.ts index bc89e5ac136b..d45a9c6d6227 100644 --- a/langchain/src/load/import_constants.ts +++ b/langchain/src/load/import_constants.ts @@ -11,7 +11,6 @@ export const optionalImportEntrypoints = [ "langchain/tools/webbrowser", "langchain/tools/gmail", "langchain/tools/google_calendar", - "langchain/tools/outlook", "langchain/chains/load", "langchain/chains/query_constructor", "langchain/chains/query_constructor/ir", diff --git a/langchain/src/load/import_type.d.ts b/langchain/src/load/import_type.d.ts index 3523c641dead..050171f77ecd 100644 --- a/langchain/src/load/import_type.d.ts +++ b/langchain/src/load/import_type.d.ts @@ -31,9 +31,6 @@ export interface OptionalImportMap { "langchain/tools/google_calendar"?: | typeof import("../tools/google_calendar/index.js") | Promise; - "langchain/tools/outlook"?: - | typeof import("../tools/outlook/index.js") - | Promise; "langchain/chains/load"?: | typeof import("../chains/load.js") | Promise; diff --git a/libs/langchain-community/.gitignore b/libs/langchain-community/.gitignore index 9475e00e7a15..5d846ac2612b 100644 --- a/libs/langchain-community/.gitignore +++ b/libs/langchain-community/.gitignore @@ -43,6 +43,9 @@ tools/google_places.d.ts tools/ifttt.cjs tools/ifttt.js tools/ifttt.d.ts +tools/outlook.cjs +tools/outlook.js +tools/outlook.d.ts tools/searchapi.cjs tools/searchapi.js tools/searchapi.d.ts diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index ed71a2c5fc58..efb6184863bb 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -541,6 +541,11 @@ "import": "./tools/ifttt.js", "require": "./tools/ifttt.cjs" }, + "./tools/outlook": { + "types": "./tools/outlook.d.ts", + "import": "./tools/outlook.js", + "require": "./tools/outlook.cjs" + }, "./tools/searchapi": { "types": "./tools/searchapi.d.ts", "import": "./tools/searchapi.js", @@ -1260,6 +1265,9 @@ "tools/ifttt.cjs", "tools/ifttt.js", "tools/ifttt.d.ts", + "tools/outlook.cjs", + "tools/outlook.js", + "tools/outlook.d.ts", "tools/searchapi.cjs", "tools/searchapi.js", "tools/searchapi.d.ts", diff --git a/libs/langchain-community/scripts/create-entrypoints.js b/libs/langchain-community/scripts/create-entrypoints.js index 0a75fbb878eb..9087585b7104 100644 --- a/libs/langchain-community/scripts/create-entrypoints.js +++ b/libs/langchain-community/scripts/create-entrypoints.js @@ -24,6 +24,7 @@ const entrypoints = { "tools/google_custom_search": "tools/google_custom_search", "tools/google_places": "tools/google_places", "tools/ifttt": "tools/ifttt", + "tools/outlook": "tools/outlook/index", "tools/searchapi": "tools/searchapi", "tools/searxng_search": "tools/searxng_search", "tools/serpapi": "tools/serpapi", @@ -185,6 +186,7 @@ const requiresOptionalDependency = [ "tools/aws_sfn", "tools/aws_lambda", "tools/gmail", + "tools/outlook", "agents/toolkits/aws_sfn", "callbacks/handlers/llmonitor", "embeddings/bedrock", diff --git a/libs/langchain-community/src/load/import_constants.ts b/libs/langchain-community/src/load/import_constants.ts index 943495e663dd..e09ba9519296 100644 --- a/libs/langchain-community/src/load/import_constants.ts +++ b/libs/langchain-community/src/load/import_constants.ts @@ -4,6 +4,7 @@ export const optionalImportEntrypoints = [ "langchain_community/tools/aws_lambda", "langchain_community/tools/aws_sfn", "langchain_community/tools/gmail", + "langchain_community/tools/outlook", "langchain_community/agents/toolkits/aws_sfn", "langchain_community/embeddings/bedrock", "langchain_community/embeddings/cloudflare_workersai", diff --git a/libs/langchain-community/src/load/import_type.d.ts b/libs/langchain-community/src/load/import_type.d.ts index 5d226c55af0a..31210a1c14a7 100644 --- a/libs/langchain-community/src/load/import_type.d.ts +++ b/libs/langchain-community/src/load/import_type.d.ts @@ -10,6 +10,9 @@ export interface OptionalImportMap { "@langchain/community/tools/gmail"?: | typeof import("../tools/gmail/index.js") | Promise; + "@langchain/community/tools/outlook"?: + | typeof import("../tools/outlook/index.js") + | Promise; "@langchain/community/agents/toolkits/aws_sfn"?: | typeof import("../agents/toolkits/aws_sfn.js") | Promise; diff --git a/langchain/src/tools/outlook/authFlowBase.ts b/libs/langchain-community/src/tools/outlook/authFlowBase.ts similarity index 100% rename from langchain/src/tools/outlook/authFlowBase.ts rename to libs/langchain-community/src/tools/outlook/authFlowBase.ts diff --git a/langchain/src/tools/outlook/authFlowREST.ts b/libs/langchain-community/src/tools/outlook/authFlowREST.ts similarity index 98% rename from langchain/src/tools/outlook/authFlowREST.ts rename to libs/langchain-community/src/tools/outlook/authFlowREST.ts index 7f3bbb14a832..de4afb68a77e 100644 --- a/langchain/src/tools/outlook/authFlowREST.ts +++ b/libs/langchain-community/src/tools/outlook/authFlowREST.ts @@ -1,7 +1,7 @@ -import * as http from "http"; -import * as url from "url"; +import * as http from "node:http"; +import * as url from "node:url"; +import { getEnvironmentVariable } from "@langchain/core/utils/env"; import { AuthFlowBase } from "./authFlowBase.js"; -import { getEnvironmentVariable } from "../../util/env.js"; /** * Represents the response structure for an access token. diff --git a/langchain/src/tools/outlook/authFlowToken.ts b/libs/langchain-community/src/tools/outlook/authFlowToken.ts similarity index 98% rename from langchain/src/tools/outlook/authFlowToken.ts rename to libs/langchain-community/src/tools/outlook/authFlowToken.ts index bfaafec5a0c3..cf0ea7deb635 100644 --- a/langchain/src/tools/outlook/authFlowToken.ts +++ b/libs/langchain-community/src/tools/outlook/authFlowToken.ts @@ -1,5 +1,5 @@ +import { getEnvironmentVariable } from "@langchain/core/utils/env"; import { AuthFlowBase } from "./authFlowBase.js"; -import { getEnvironmentVariable } from "../../util/env.js"; /** * Response structure for access token. diff --git a/langchain/src/tools/outlook/base.ts b/libs/langchain-community/src/tools/outlook/base.ts similarity index 98% rename from langchain/src/tools/outlook/base.ts rename to libs/langchain-community/src/tools/outlook/base.ts index 98999aaaf2e1..99a534cf30bc 100644 --- a/langchain/src/tools/outlook/base.ts +++ b/libs/langchain-community/src/tools/outlook/base.ts @@ -1,4 +1,4 @@ -import { Tool } from "../base.js"; +import { Tool } from "@langchain/core/tools"; import { AuthFlowBase } from "./authFlowBase.js"; import { AuthFlowToken, AuthFlowRefresh } from "./authFlowToken.js"; import { AuthFlowREST } from "./authFlowREST.js"; diff --git a/langchain/src/tools/outlook/descriptions.ts b/libs/langchain-community/src/tools/outlook/descriptions.ts similarity index 100% rename from langchain/src/tools/outlook/descriptions.ts rename to libs/langchain-community/src/tools/outlook/descriptions.ts diff --git a/langchain/src/tools/outlook/index.ts b/libs/langchain-community/src/tools/outlook/index.ts similarity index 100% rename from langchain/src/tools/outlook/index.ts rename to libs/langchain-community/src/tools/outlook/index.ts diff --git a/langchain/src/tools/outlook/readMail.ts b/libs/langchain-community/src/tools/outlook/readMail.ts similarity index 100% rename from langchain/src/tools/outlook/readMail.ts rename to libs/langchain-community/src/tools/outlook/readMail.ts diff --git a/langchain/src/tools/outlook/sendMail.ts b/libs/langchain-community/src/tools/outlook/sendMail.ts similarity index 100% rename from langchain/src/tools/outlook/sendMail.ts rename to libs/langchain-community/src/tools/outlook/sendMail.ts diff --git a/langchain/src/tools/tests/outlookIntegration.test.ts b/libs/langchain-community/src/tools/tests/outlook.int.test.ts similarity index 100% rename from langchain/src/tools/tests/outlookIntegration.test.ts rename to libs/langchain-community/src/tools/tests/outlook.int.test.ts From ed41abeaf5149f8be60b6523d1d1766a8f6b006b Mon Sep 17 00:00:00 2001 From: jacoblee93 Date: Tue, 12 Dec 2023 14:08:17 -0800 Subject: [PATCH 33/46] Skip test --- libs/langchain-community/src/tools/tests/outlook.int.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/langchain-community/src/tools/tests/outlook.int.test.ts b/libs/langchain-community/src/tools/tests/outlook.int.test.ts index 1e1966126e40..1cb8e3f8e59f 100644 --- a/libs/langchain-community/src/tools/tests/outlook.int.test.ts +++ b/libs/langchain-community/src/tools/tests/outlook.int.test.ts @@ -4,7 +4,7 @@ import { OutlookSendMailTool, } from "../outlook/index.js"; -describe("OutlookReadMailTool Test", () => { +describe.skip("OutlookReadMailTool Test", () => { test("Test invalid access token", async () => { const accessToken = "blah"; const authFlow = new AuthFlowToken(accessToken); @@ -32,7 +32,7 @@ describe("OutlookReadMailTool Test", () => { }); }); -describe("OutlookSendMailTool Test", () => { +describe.skip("OutlookSendMailTool Test", () => { test("Test invalid TO email address", async () => { const message = JSON.stringify({ subject: "test", From 562df12d9eff61e682de252dbe99178bb9cd43a1 Mon Sep 17 00:00:00 2001 From: jacoblee93 Date: Thu, 21 Dec 2023 18:57:48 -0800 Subject: [PATCH 34/46] Move/rename files around --- .../tools/outlook/{authFlowBase.ts => auth/base.ts} | 0 .../tools/outlook/{authFlowREST.ts => auth/rest.ts} | 2 +- .../tools/outlook/{authFlowToken.ts => auth/token.ts} | 2 +- libs/langchain-community/src/tools/outlook/base.ts | 6 +++--- libs/langchain-community/src/tools/outlook/index.ts | 10 +++++----- .../src/tools/outlook/{readMail.ts => read_mail.ts} | 2 +- .../src/tools/outlook/{sendMail.ts => send_mail.ts} | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) rename libs/langchain-community/src/tools/outlook/{authFlowBase.ts => auth/base.ts} (100%) rename libs/langchain-community/src/tools/outlook/{authFlowREST.ts => auth/rest.ts} (99%) rename libs/langchain-community/src/tools/outlook/{authFlowToken.ts => auth/token.ts} (98%) rename libs/langchain-community/src/tools/outlook/{readMail.ts => read_mail.ts} (98%) rename libs/langchain-community/src/tools/outlook/{sendMail.ts => send_mail.ts} (98%) diff --git a/libs/langchain-community/src/tools/outlook/authFlowBase.ts b/libs/langchain-community/src/tools/outlook/auth/base.ts similarity index 100% rename from libs/langchain-community/src/tools/outlook/authFlowBase.ts rename to libs/langchain-community/src/tools/outlook/auth/base.ts diff --git a/libs/langchain-community/src/tools/outlook/authFlowREST.ts b/libs/langchain-community/src/tools/outlook/auth/rest.ts similarity index 99% rename from libs/langchain-community/src/tools/outlook/authFlowREST.ts rename to libs/langchain-community/src/tools/outlook/auth/rest.ts index de4afb68a77e..8cc11c928713 100644 --- a/libs/langchain-community/src/tools/outlook/authFlowREST.ts +++ b/libs/langchain-community/src/tools/outlook/auth/rest.ts @@ -1,7 +1,7 @@ import * as http from "node:http"; import * as url from "node:url"; import { getEnvironmentVariable } from "@langchain/core/utils/env"; -import { AuthFlowBase } from "./authFlowBase.js"; +import { AuthFlowBase } from "./base.js"; /** * Represents the response structure for an access token. diff --git a/libs/langchain-community/src/tools/outlook/authFlowToken.ts b/libs/langchain-community/src/tools/outlook/auth/token.ts similarity index 98% rename from libs/langchain-community/src/tools/outlook/authFlowToken.ts rename to libs/langchain-community/src/tools/outlook/auth/token.ts index cf0ea7deb635..2cc7a837c8fa 100644 --- a/libs/langchain-community/src/tools/outlook/authFlowToken.ts +++ b/libs/langchain-community/src/tools/outlook/auth/token.ts @@ -1,5 +1,5 @@ import { getEnvironmentVariable } from "@langchain/core/utils/env"; -import { AuthFlowBase } from "./authFlowBase.js"; +import { AuthFlowBase } from "./base.js"; /** * Response structure for access token. diff --git a/libs/langchain-community/src/tools/outlook/base.ts b/libs/langchain-community/src/tools/outlook/base.ts index 99a534cf30bc..497e8fd342ca 100644 --- a/libs/langchain-community/src/tools/outlook/base.ts +++ b/libs/langchain-community/src/tools/outlook/base.ts @@ -1,7 +1,7 @@ import { Tool } from "@langchain/core/tools"; -import { AuthFlowBase } from "./authFlowBase.js"; -import { AuthFlowToken, AuthFlowRefresh } from "./authFlowToken.js"; -import { AuthFlowREST } from "./authFlowREST.js"; +import { AuthFlowBase } from "./auth/base.js"; +import { AuthFlowToken, AuthFlowRefresh } from "./auth/token.js"; +import { AuthFlowREST } from "./auth/rest.js"; /** * Tool for interacting with Outlook, allowing actions such as sending or reading emails. diff --git a/libs/langchain-community/src/tools/outlook/index.ts b/libs/langchain-community/src/tools/outlook/index.ts index d1d7dc32a8c0..f404f03ffcd0 100644 --- a/libs/langchain-community/src/tools/outlook/index.ts +++ b/libs/langchain-community/src/tools/outlook/index.ts @@ -1,6 +1,6 @@ -export { OutlookSendMailTool } from "./sendMail.js"; -export { OutlookReadMailTool } from "./readMail.js"; +export { OutlookSendMailTool } from "./send_mail.js"; +export { OutlookReadMailTool } from "./read_mail.js"; export { OutlookBase } from "./base.js"; -export { AuthFlowREST } from "./authFlowREST.js"; -export { AuthFlowToken, AuthFlowRefresh } from "./authFlowToken.js"; -export { AuthFlowBase } from "./authFlowBase.js"; +export { AuthFlowREST } from "./auth/rest.js"; +export { AuthFlowToken, AuthFlowRefresh } from "./auth/token.js"; +export { AuthFlowBase } from "./auth/base.js"; diff --git a/libs/langchain-community/src/tools/outlook/readMail.ts b/libs/langchain-community/src/tools/outlook/read_mail.ts similarity index 98% rename from libs/langchain-community/src/tools/outlook/readMail.ts rename to libs/langchain-community/src/tools/outlook/read_mail.ts index 477ec6023e1d..e95f254fcf2e 100644 --- a/libs/langchain-community/src/tools/outlook/readMail.ts +++ b/libs/langchain-community/src/tools/outlook/read_mail.ts @@ -1,6 +1,6 @@ import { OutlookBase } from "./base.js"; import { READ_MAIL_TOOL_DESCRIPTION } from "./descriptions.js"; -import { AuthFlowBase } from "./authFlowBase.js"; +import { AuthFlowBase } from "./auth/base.js"; /** * Represents an email retrieved from Outlook. diff --git a/libs/langchain-community/src/tools/outlook/sendMail.ts b/libs/langchain-community/src/tools/outlook/send_mail.ts similarity index 98% rename from libs/langchain-community/src/tools/outlook/sendMail.ts rename to libs/langchain-community/src/tools/outlook/send_mail.ts index 931fae619bdc..957a50fab76c 100644 --- a/libs/langchain-community/src/tools/outlook/sendMail.ts +++ b/libs/langchain-community/src/tools/outlook/send_mail.ts @@ -1,6 +1,6 @@ import { OutlookBase } from "./base.js"; import { SEND_MAIL_TOOL_DESCRIPTION } from "./descriptions.js"; -import { AuthFlowBase } from "./authFlowBase.js"; +import { AuthFlowBase } from "./auth/base.js"; /** * Class representing an Outlook send mail tool. From a9ba5491d6ffdda742fd22ecd70abe63fc00b287 Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Thu, 21 Dec 2023 23:59:33 -0500 Subject: [PATCH 35/46] add a docs page for outlook toolkit --- .../docs/integrations/toolkits/outlook.mdx | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 docs/core_docs/docs/integrations/toolkits/outlook.mdx diff --git a/docs/core_docs/docs/integrations/toolkits/outlook.mdx b/docs/core_docs/docs/integrations/toolkits/outlook.mdx new file mode 100644 index 000000000000..30a3fa5ed2ad --- /dev/null +++ b/docs/core_docs/docs/integrations/toolkits/outlook.mdx @@ -0,0 +1,135 @@ +# Outlook Toolkit + +## Overview + +Using this toolkit, you can integrate Microsoft Graph API functionalities into your applications to manage email interactions. + +## Learn Microsoft Graph + +Microsoft Graph is a RESTful web API that enables you to access Microsoft Cloud service resources. With Microsoft Graph, you can integrate various Microsoft services and data, including users' emails, calendar, contacts, and more, into your applications. + +After you register your app and get authentication tokens for a user or service, you can make requests to the Microsoft Graph API. + +How to get authentication tokens for a user: +https://learn.microsoft.com/en-us/graph/auth-v2-user + +## App register and set environment variables +Azure App Registration for Microsoft Outlook API Integration + +1. Register an Application: + - Go to Azure Portal. + - Navigate to Azure Active Directory -> App registrations. + - Register a new application, note down the Client ID. + +2. Generate Client Secret: + - In the application settings, go to Certificates & Secrets. + - Create a new client secret and note down the Client Secret. + +3. Configure Redirect URI: + - In the application settings, go to Authentication. + - Add a Redirect URI and note down the Redirect URI. + +4. Permissions and Admin Consent: + - In API permissions, grant necessary permissions for Microsoft Graph API or Outlook API. + - Grant admin consent for the added permissions. + +6. Environment Variables: + - Set the following environment variables: + + ```env + OUTLOOK_CLIENT_ID=your_client_id + OUTLOOK_CLIENT_SECRET=your_client_secret + OUTLOOK_REDIRECT_URI=your_redirect_uri + OUTLOOK_ACCESS_TOKEN=your_access_token + OUTLOOK_REFRESH_TOKEN=your_refresh_token + + ``` + + **Keep these values secure and avoid exposing them in public repositories.** + +## Setup + +Basically you only need the accessToken to use the Outlook Toolkit, but in expires in an hour. + +If you only have a valid accessToken, the following two ways creates a Outlook tool instance. +```typescript +const authFlow = new AuthFlowToken(accessToken); +// if you have accessToken stored in .env +const authFlow = new AuthFlowToken(); +const outlookReadMail = new OutlookReadMailTool(authFlow=authFlow); +``` +```typescript +// you must have accessToken stored in .env +const outlookReadMail = new OutlookReadMailTool(choice="token"); +``` +But if you do not have a valid accessToken, the `AuthFlowREST` class obtains the accessToken for you. A temporary server will be opened on your `redirectUri` and you need to manually open the printed link to login on behalf of the user. For every `AuthFlowREST` instance, you only need to login once. + +Credentials you need to provide:`clientId`, `clientSecret`, `redirectUri` +You can either pass them to the constructor or put them in the environment. +```typescript +const authFlow = new AuthFlowREST(); +const outlookReadMail = new OutlookReadMailTool(authFlow=authFlow); +``` +```typescript +const outlookReadMail = new OutlookReadMailTool(choice="rest"); +``` +Usually when you obtain a accessToken you also get a refreshToken used to refresh expired accessToken. The `AuthFlowREST` class needs the following credentials: `clientId`, `clientSecret`, `redirectUri` and `refreshToken`. In this way you won't need to login, no server will be open. +```typescript +const authFlow = new AuthFlowRefresh(); +const outlookReadMail = new OutlookReadMailTool(authFlow=authFlow); +``` +```typescript +const outlookReadMail = new OutlookReadMailTool(choice="refresh"); +``` + +## Usage +This example shows how to read emails by using agent. +```typescript +import { ChatOpenAI } from 'langchain/chat_models/openai'; +import { initializeAgentExecutorWithOptions } from 'langchain/agents'; +import { OutlookReadMailTool, AuthFlowREST, AuthFlowToken} from 'langchain_community/tools/outlook'; + +async function createAndRunAgent() { + const gpt4 = new ChatOpenAI({ modelName: 'gpt-4', openAIApiKey: getEnvironmentVariable("OPENAI_API_KEY")}); + + const outlookReadMail = new OutlookReadMailTool("token"); + + const executor = await initializeAgentExecutorWithOptions([outlookReadMail], gpt4, { + agentType: "openai-functions", + verbose: true, + }); + + const input = 'show emails sent to YOUR_EMAIL'; + const result = await executor.invoke({ input }); + + console.log(result); +} + +createAndRunAgent(); +``` + +This example shows how to send emails by using agent. +```typescript +import { ChatOpenAI } from 'langchain/chat_models/openai'; +import { initializeAgentExecutorWithOptions } from 'langchain/agents'; +import { OutlookSendMailTool, AuthFlowREST } from 'langchain_community/tools/outlook'; + + +async function createAndRunAgent() { + const gpt4 = new ChatOpenAI({ modelName: 'gpt-4', openAIApiKey: getEnvironmentVariable("OPENAI_API_KEY")}); + + const sendMailTool = new OutlookSendMailTool("token"); + + const executor = await initializeAgentExecutorWithOptions([sendMailTool], gpt4, { + agentType: "openai-functions", + verbose: true, + }); + + const input = 'send an email to YOUR_EMAIL and say Hello'; + const result = await executor.invoke({ input }); + + console.log(result); +} + +createAndRunAgent(); +``` \ No newline at end of file From 7fa8d85e31759c2e0c74819319dde53e187b1363 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Fri, 22 Dec 2023 17:09:28 -0800 Subject: [PATCH 36/46] Update outlook.mdx --- .../docs/integrations/toolkits/outlook.mdx | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/core_docs/docs/integrations/toolkits/outlook.mdx b/docs/core_docs/docs/integrations/toolkits/outlook.mdx index 30a3fa5ed2ad..6d50bc08150c 100644 --- a/docs/core_docs/docs/integrations/toolkits/outlook.mdx +++ b/docs/core_docs/docs/integrations/toolkits/outlook.mdx @@ -49,9 +49,10 @@ Azure App Registration for Microsoft Outlook API Integration ## Setup -Basically you only need the accessToken to use the Outlook Toolkit, but in expires in an hour. +You only need an `accessToken` to use the Outlook Toolkit, but they expire after an hour. + +If you only have a valid `accessToken`, the following two ways can create an Outlook tool instance. -If you only have a valid accessToken, the following two ways creates a Outlook tool instance. ```typescript const authFlow = new AuthFlowToken(accessToken); // if you have accessToken stored in .env @@ -62,18 +63,22 @@ const outlookReadMail = new OutlookReadMailTool(authFlow=authFlow); // you must have accessToken stored in .env const outlookReadMail = new OutlookReadMailTool(choice="token"); ``` -But if you do not have a valid accessToken, the `AuthFlowREST` class obtains the accessToken for you. A temporary server will be opened on your `redirectUri` and you need to manually open the printed link to login on behalf of the user. For every `AuthFlowREST` instance, you only need to login once. -Credentials you need to provide:`clientId`, `clientSecret`, `redirectUri` +If you do not have a valid accessToken, the `AuthFlowREST` class obtains the accessToken for you. A temporary server will be opened on your `redirectUri` and you need to manually open the printed link to login on behalf of the user. For every `AuthFlowREST` instance, you only need to login once. + +You'll need to provide a `clientId`, `clientSecret`, and `redirectUri`. You can either pass them to the constructor or put them in the environment. ```typescript const authFlow = new AuthFlowREST(); const outlookReadMail = new OutlookReadMailTool(authFlow=authFlow); ``` + ```typescript const outlookReadMail = new OutlookReadMailTool(choice="rest"); ``` + Usually when you obtain a accessToken you also get a refreshToken used to refresh expired accessToken. The `AuthFlowREST` class needs the following credentials: `clientId`, `clientSecret`, `redirectUri` and `refreshToken`. In this way you won't need to login, no server will be open. + ```typescript const authFlow = new AuthFlowRefresh(); const outlookReadMail = new OutlookReadMailTool(authFlow=authFlow); @@ -83,7 +88,9 @@ const outlookReadMail = new OutlookReadMailTool(choice="refresh"); ``` ## Usage + This example shows how to read emails by using agent. + ```typescript import { ChatOpenAI } from 'langchain/chat_models/openai'; import { initializeAgentExecutorWithOptions } from 'langchain/agents'; @@ -109,6 +116,7 @@ createAndRunAgent(); ``` This example shows how to send emails by using agent. + ```typescript import { ChatOpenAI } from 'langchain/chat_models/openai'; import { initializeAgentExecutorWithOptions } from 'langchain/agents'; @@ -132,4 +140,4 @@ async function createAndRunAgent() { } createAndRunAgent(); -``` \ No newline at end of file +``` From c2a9eba3429d3af7b518848ce140f1f20ca9a676 Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Fri, 22 Dec 2023 21:52:27 -0500 Subject: [PATCH 37/46] Update outlook.mdx --- docs/core_docs/docs/integrations/toolkits/outlook.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/core_docs/docs/integrations/toolkits/outlook.mdx b/docs/core_docs/docs/integrations/toolkits/outlook.mdx index 6d50bc08150c..6188a8144fd9 100644 --- a/docs/core_docs/docs/integrations/toolkits/outlook.mdx +++ b/docs/core_docs/docs/integrations/toolkits/outlook.mdx @@ -40,7 +40,9 @@ Azure App Registration for Microsoft Outlook API Integration OUTLOOK_CLIENT_ID=your_client_id OUTLOOK_CLIENT_SECRET=your_client_secret OUTLOOK_REDIRECT_URI=your_redirect_uri + // for AuthFlowRefresh OUTLOOK_ACCESS_TOKEN=your_access_token + // for AuthFlowToken OUTLOOK_REFRESH_TOKEN=your_refresh_token ``` From c7eb5c6548015c5733d86c997b95db33a7459814 Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Sat, 23 Dec 2023 02:53:30 -0500 Subject: [PATCH 38/46] Update outlook.mdx --- .../docs/integrations/toolkits/outlook.mdx | 106 ++++++++++++------ 1 file changed, 74 insertions(+), 32 deletions(-) diff --git a/docs/core_docs/docs/integrations/toolkits/outlook.mdx b/docs/core_docs/docs/integrations/toolkits/outlook.mdx index 6188a8144fd9..baa49cefe51f 100644 --- a/docs/core_docs/docs/integrations/toolkits/outlook.mdx +++ b/docs/core_docs/docs/integrations/toolkits/outlook.mdx @@ -2,19 +2,30 @@ ## Overview -Using this toolkit, you can integrate Microsoft Graph API functionalities into your applications to manage email interactions. +Using this toolkit, you can integrate Microsoft Graph API functionalities into +your applications to manage email interactions. ## Learn Microsoft Graph -Microsoft Graph is a RESTful web API that enables you to access Microsoft Cloud service resources. With Microsoft Graph, you can integrate various Microsoft services and data, including users' emails, calendar, contacts, and more, into your applications. +Microsoft Graph is a RESTful web API that enables you to access Microsoft +Cloud service resources. With Microsoft Graph, you can integrate various +Microsoft services and data, including users' emails, calendar, contacts, +and more, into your applications. -After you register your app and get authentication tokens for a user or service, you can make requests to the Microsoft Graph API. +After you register your app and get authentication tokens for a user or service, +you can make requests to the Microsoft Graph API. How to get authentication tokens for a user: https://learn.microsoft.com/en-us/graph/auth-v2-user -## App register and set environment variables -Azure App Registration for Microsoft Outlook API Integration +## Set up + +An `accessToken` is needed to use the Outlook Toolkit. + +You need to first register an application in Azure Portal to obtain +necessary credentials. + +### App register 1. Register an Application: - Go to Azure Portal. @@ -30,62 +41,93 @@ Azure App Registration for Microsoft Outlook API Integration - Add a Redirect URI and note down the Redirect URI. 4. Permissions and Admin Consent: - - In API permissions, grant necessary permissions for Microsoft Graph API or Outlook API. + - In API permissions, grant necessary permissions for Microsoft Graph API or + Outlook API. - Grant admin consent for the added permissions. -6. Environment Variables: - - Set the following environment variables: +At this point, you should have these three credentials: +clientId, clientSecret, redirectUri + +Set them as environment variables: + + ```env + OUTLOOK_CLIENT_ID=your_client_id + OUTLOOK_CLIENT_SECRET=your_client_secret + OUTLOOK_REDIRECT_URI=your_redirect_uri + ``` + **Keep these values secure and avoid exposing them in public repositories.** + +The following link from Microsoft is a good reference for the above steps: +https://learn.microsoft.com/en-us/graph/auth-v2-user + +This video may also be helpful: +https://www.youtube.com/watch?v=NAtiNpwnivI&t=309s + - ```env - OUTLOOK_CLIENT_ID=your_client_id - OUTLOOK_CLIENT_SECRET=your_client_secret - OUTLOOK_REDIRECT_URI=your_redirect_uri - // for AuthFlowRefresh OUTLOOK_ACCESS_TOKEN=your_access_token // for AuthFlowToken OUTLOOK_REFRESH_TOKEN=your_refresh_token ``` - **Keep these values secure and avoid exposing them in public repositories.** +### Get an accessToken -## Setup +Now you are able to use the above credentials to get an `accessToken`. -You only need an `accessToken` to use the Outlook Toolkit, but they expire after an hour. +The `AuthFlowREST` class implements the OAuth 2.0 authorization code flow. +You can use the `getAccessToken()` which will open a temporary server on +your `redirectUri` and you need to manually open the printed link to login +on behalf of the user. For every `AuthFlowREST` instance, you only need to +login once. The next time you can run the `refreshAccessToken()` method, it will +make use of the `refreshToken` obtained together with the first `accessToken` + to get a new `accessToken` without the need of the server and login. -If you only have a valid `accessToken`, the following two ways can create an Outlook tool instance. +Having the above credentials, you can either pass them to the constructor +or put them in the environment. ```typescript -const authFlow = new AuthFlowToken(accessToken); -// if you have accessToken stored in .env -const authFlow = new AuthFlowToken(); -const outlookReadMail = new OutlookReadMailTool(authFlow=authFlow); -``` -```typescript -// you must have accessToken stored in .env -const outlookReadMail = new OutlookReadMailTool(choice="token"); +const authFlow = new AuthFlowREST(); +const authFlow = new AuthFlowREST(clientId, clientSecret, redirectUri); +// Get accessToken +const accessToken = await authFlow.getAccessToken(); +// Refresh accessToken +const accessToken = await authFlow.refreshAccessToken(); ``` -If you do not have a valid accessToken, the `AuthFlowREST` class obtains the accessToken for you. A temporary server will be opened on your `redirectUri` and you need to manually open the printed link to login on behalf of the user. For every `AuthFlowREST` instance, you only need to login once. +### Provide accessToken to Outlook Toolkit + +The Outlook Toolkit needs `AuthFlow` abstarct class to provide accessToken. -You'll need to provide a `clientId`, `clientSecret`, and `redirectUri`. -You can either pass them to the constructor or put them in the environment. ```typescript const authFlow = new AuthFlowREST(); const outlookReadMail = new OutlookReadMailTool(authFlow=authFlow); +// or simply this when you have the credentials in .env +const outlookReadMail = new OutlookReadMailTool(choice="rest"); ``` +### Other classes to provide accessToken + +If you only have a valid `accessToken` in your env: + ```typescript -const outlookReadMail = new OutlookReadMailTool(choice="rest"); +const authFlow = new AuthFlowToken(accessToken); +// if you have accessToken stored in .env +const authFlow = new AuthFlowToken(); +const outlookReadMail = new OutlookReadMailTool(authFlow=authFlow); +// or simply this when you have accessToken in .env +const outlookReadMail = new OutlookReadMailTool(choice="token"); ``` -Usually when you obtain a accessToken you also get a refreshToken used to refresh expired accessToken. The `AuthFlowREST` class needs the following credentials: `clientId`, `clientSecret`, `redirectUri` and `refreshToken`. In this way you won't need to login, no server will be open. +Usually when you obtain a accessToken you also get a refreshToken +used to refresh expired accessToken. The `AuthFlowREST` class needs +the following credentials: `clientId`, `clientSecret`, `redirectUri` +and `refreshToken`. In this way you won't need to login, no server +will be open. ```typescript const authFlow = new AuthFlowRefresh(); const outlookReadMail = new OutlookReadMailTool(authFlow=authFlow); -``` -```typescript +// or simply this when you have the credentials in .env const outlookReadMail = new OutlookReadMailTool(choice="refresh"); ``` From 67186693e27fe01083e21baac9fd9d6f49ea11ac Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Sat, 23 Dec 2023 03:05:26 -0500 Subject: [PATCH 39/46] add getRefreshToken for AuthFlowREST --- docs/core_docs/docs/integrations/toolkits/outlook.mdx | 2 ++ libs/langchain-community/src/tools/outlook/auth/rest.ts | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/docs/core_docs/docs/integrations/toolkits/outlook.mdx b/docs/core_docs/docs/integrations/toolkits/outlook.mdx index baa49cefe51f..bd5b8c928425 100644 --- a/docs/core_docs/docs/integrations/toolkits/outlook.mdx +++ b/docs/core_docs/docs/integrations/toolkits/outlook.mdx @@ -90,6 +90,8 @@ const authFlow = new AuthFlowREST(); const authFlow = new AuthFlowREST(clientId, clientSecret, redirectUri); // Get accessToken const accessToken = await authFlow.getAccessToken(); +// After the above line, you can get the refreshToken +const refreshToken = authFlow.getRefreshToken(); // Refresh accessToken const accessToken = await authFlow.refreshAccessToken(); ``` diff --git a/libs/langchain-community/src/tools/outlook/auth/rest.ts b/libs/langchain-community/src/tools/outlook/auth/rest.ts index 8cc11c928713..e849dadc85da 100644 --- a/libs/langchain-community/src/tools/outlook/auth/rest.ts +++ b/libs/langchain-community/src/tools/outlook/auth/rest.ts @@ -228,6 +228,15 @@ export class AuthFlowREST extends AuthFlowBase { return this.accessToken; } + /** + * return the refresh token + * @public + * @returns {string} The refresh token. + */ + public getRefreshToken(): string { + return this.refreshToken; + } + /** * Refreshes the access token using the refresh token. * @public From 1fb53e07f576db3a46c35d877aa3b0a442acb036 Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Thu, 28 Dec 2023 20:49:52 -0500 Subject: [PATCH 40/46] move getAuth from outlook tool to authFLow Also updated docs examples, make description for readMail tool shorter --- .../docs/integrations/toolkits/outlook.mdx | 19 ++++++------ .../src/tools/outlook/auth/base.ts | 9 ------ .../src/tools/outlook/auth/rest.ts | 30 ++++++++++++++++--- .../src/tools/outlook/auth/token.ts | 9 ------ .../src/tools/outlook/base.ts | 18 ----------- .../src/tools/outlook/descriptions.ts | 22 +++++--------- .../src/tools/outlook/read_mail.ts | 2 +- .../src/tools/outlook/send_mail.ts | 2 +- 8 files changed, 45 insertions(+), 66 deletions(-) diff --git a/docs/core_docs/docs/integrations/toolkits/outlook.mdx b/docs/core_docs/docs/integrations/toolkits/outlook.mdx index bd5b8c928425..15b77a24bde0 100644 --- a/docs/core_docs/docs/integrations/toolkits/outlook.mdx +++ b/docs/core_docs/docs/integrations/toolkits/outlook.mdx @@ -140,19 +140,20 @@ This example shows how to read emails by using agent. ```typescript import { ChatOpenAI } from 'langchain/chat_models/openai'; import { initializeAgentExecutorWithOptions } from 'langchain/agents'; -import { OutlookReadMailTool, AuthFlowREST, AuthFlowToken} from 'langchain_community/tools/outlook'; +import { OutlookReadMailTool } from '@langchain/community/tools/outlook'; +require('dotenv').config(); async function createAndRunAgent() { - const gpt4 = new ChatOpenAI({ modelName: 'gpt-4', openAIApiKey: getEnvironmentVariable("OPENAI_API_KEY")}); + const gpt4 = new ChatOpenAI({ modelName: 'gpt-3.5-turbo-1106', openAIApiKey: process.env.OPENAI_API_KEY}); - const outlookReadMail = new OutlookReadMailTool("token"); + const outlookReadMail = new OutlookReadMailTool(undefined, "token"); const executor = await initializeAgentExecutorWithOptions([outlookReadMail], gpt4, { agentType: "openai-functions", verbose: true, }); - const input = 'show emails sent to YOUR_EMAIL'; + const input = 'show my emails'; const result = await executor.invoke({ input }); console.log(result); @@ -166,20 +167,20 @@ This example shows how to send emails by using agent. ```typescript import { ChatOpenAI } from 'langchain/chat_models/openai'; import { initializeAgentExecutorWithOptions } from 'langchain/agents'; -import { OutlookSendMailTool, AuthFlowREST } from 'langchain_community/tools/outlook'; - +import { OutlookSendMailTool } from '@langchain/community/tools/outlook'; +require('dotenv').config(); async function createAndRunAgent() { - const gpt4 = new ChatOpenAI({ modelName: 'gpt-4', openAIApiKey: getEnvironmentVariable("OPENAI_API_KEY")}); + const gpt4 = new ChatOpenAI({ modelName: 'gpt-4', openAIApiKey: process.env.OPENAI_API_KEY}); - const sendMailTool = new OutlookSendMailTool("token"); + const sendMailTool = new OutlookSendMailTool(undefined, "token"); const executor = await initializeAgentExecutorWithOptions([sendMailTool], gpt4, { agentType: "openai-functions", verbose: true, }); - const input = 'send an email to YOUR_EMAIL and say Hello'; + const input = 'send an email to YOUR_EMAIL and invite him to a meeting at 3pm tomorrow'; const result = await executor.invoke({ input }); console.log(result); diff --git a/libs/langchain-community/src/tools/outlook/auth/base.ts b/libs/langchain-community/src/tools/outlook/auth/base.ts index ec7609fec6b3..1b3e3e3b2f2a 100644 --- a/libs/langchain-community/src/tools/outlook/auth/base.ts +++ b/libs/langchain-community/src/tools/outlook/auth/base.ts @@ -40,13 +40,4 @@ export abstract class AuthFlowBase { * @memberof AuthFlowBase */ public abstract getAccessToken(): Promise; - - /** - * Abstract method to refresh the access token. - * - * @abstract - * @returns {Promise} A promise that resolves to the refreshed access token. - * @memberof AuthFlowBase - */ - public abstract refreshAccessToken(): Promise; } diff --git a/libs/langchain-community/src/tools/outlook/auth/rest.ts b/libs/langchain-community/src/tools/outlook/auth/rest.ts index e849dadc85da..9569935f072b 100644 --- a/libs/langchain-community/src/tools/outlook/auth/rest.ts +++ b/libs/langchain-community/src/tools/outlook/auth/rest.ts @@ -188,11 +188,11 @@ export class AuthFlowREST extends AuthFlowBase { } /** - * Gets the access token using the authorization code and client credentials. + * Login to get the access and refresh token by user login. * @public - * @returns {Promise} A Promise resolving to the access token. + * @returns {Promise<{ accessToken: string; refreshToken: string }>} A Promise resolving to the access and refresh tokens. */ - public async getAccessToken(): Promise { + public async getTokens(): Promise<{ accessToken: string; refreshToken: string }> { // fetch auth code from user login const code = await this.getCode(); // fetch access token using auth code @@ -225,7 +225,7 @@ export class AuthFlowREST extends AuthFlowBase { const json = (await response.json()) as AccessTokenResponse; this.accessToken = json.access_token; this.refreshToken = json.refresh_token; - return this.accessToken; + return { accessToken: this.accessToken, refreshToken: this.refreshToken }; } /** @@ -274,4 +274,26 @@ export class AuthFlowREST extends AuthFlowBase { this.accessToken = json.access_token; return this.accessToken; } + + /** + * returns a valid access token. + * @public + * @returns {Promise} A Promise resolving to the access token. + */ + public async getAccessToken(): Promise { + if (!this.accessToken) { + const { accessToken, refreshToken } = await this.getTokens(); + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } else { + try { + this.accessToken = await this.refreshAccessToken(); + } catch (error) { + const { accessToken, refreshToken } = await this.getTokens(); + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } + } + return this.accessToken; + } } diff --git a/libs/langchain-community/src/tools/outlook/auth/token.ts b/libs/langchain-community/src/tools/outlook/auth/token.ts index 2cc7a837c8fa..11e3cba6c678 100644 --- a/libs/langchain-community/src/tools/outlook/auth/token.ts +++ b/libs/langchain-community/src/tools/outlook/auth/token.ts @@ -34,15 +34,6 @@ export class AuthFlowToken extends AuthFlowBase { this.accessToken = token; } - /** - * Refreshes the access token. - * @async - * @returns {Promise} - The refreshed access token. - */ - public async refreshAccessToken(): Promise { - return this.accessToken; - } - /** * Gets the access token. * @async diff --git a/libs/langchain-community/src/tools/outlook/base.ts b/libs/langchain-community/src/tools/outlook/base.ts index 497e8fd342ca..bc76def26d9b 100644 --- a/libs/langchain-community/src/tools/outlook/base.ts +++ b/libs/langchain-community/src/tools/outlook/base.ts @@ -57,22 +57,4 @@ export abstract class OutlookBase extends Tool { } } } - - /** - * Retrieves and returns the authentication token. - * If the token is not available, it first attempts to refresh the token, and if that fails, it obtains a new token. - * @returns {Promise} A promise that resolves to the authentication token. - */ - async getAuth() { - if (!this.accessToken) { - this.accessToken = await this.authFlow.getAccessToken(); - } else { - try { - this.accessToken = await this.authFlow.refreshAccessToken(); - } catch (error) { - this.accessToken = await this.authFlow.getAccessToken(); - } - } - return this.accessToken; - } } diff --git a/libs/langchain-community/src/tools/outlook/descriptions.ts b/libs/langchain-community/src/tools/outlook/descriptions.ts index 321452789945..35452d6c8dc1 100644 --- a/libs/langchain-community/src/tools/outlook/descriptions.ts +++ b/libs/langchain-community/src/tools/outlook/descriptions.ts @@ -12,24 +12,16 @@ Ensure that the JSON object is correctly formatted and includes all four specifi `; export const READ_MAIL_TOOL_DESCRIPTION = `A tool for reading emails. -You can search messages based on a value in specific message properties. -The results of the search are sorted by the date and time that the message was sent. -A $search request returns up to 1000 results. -If you do a search on messages and specify only a value without specific message properties, +1. You can do a search on messages and specify only a value without specific message properties, the search is carried out on the default search properties of from, subject, and body. -Alternatively, you can search messages by specifying message property names in the following table +2. You can search messages by specifying message property names in the following table and a value to search for in those properties. -body: The body of an email message. -cc: The cc field of an email message, specified as an SMTP address, display name, or alias. -from: The sender of an email message, specified as an SMTP address, display name, or alias. -received: The date that an email message was received by a recipient. e.g. 07/23/2018 -recipients: The to, cc, and bcc fields of an email message, -sent: The date that an email message was sent by the sender. e.g. 07/23/2018 -subject: The text in the subject line of an email message. -to: The to field of an email message, +Properties: body, cc, from, received (date e.g.07/23/2018), recipients (to, cc, and bcc), +sent (date e.g. 07/23/2018), subject, to. + INPUT: -input empty string to get all emails. -If on default: $search="" +input empty string to get all emails (up to 5). +Search on default properties: $search="" On specified property: $search=":" Example: $search="sent:07/23/2018" `; diff --git a/libs/langchain-community/src/tools/outlook/read_mail.ts b/libs/langchain-community/src/tools/outlook/read_mail.ts index e95f254fcf2e..6aaf70392d32 100644 --- a/libs/langchain-community/src/tools/outlook/read_mail.ts +++ b/libs/langchain-community/src/tools/outlook/read_mail.ts @@ -40,7 +40,7 @@ export class OutlookReadMailTool extends OutlookBase { async _call(query: string): Promise { try { // Ensure authentication is completed before making the API call. - await this.getAuth(); + this.accessToken = await this.authFlow.getAccessToken(); } catch (error) { // Handle authentication error. return `Failed to get access token: ${error}`; diff --git a/libs/langchain-community/src/tools/outlook/send_mail.ts b/libs/langchain-community/src/tools/outlook/send_mail.ts index 957a50fab76c..043273ba3470 100644 --- a/libs/langchain-community/src/tools/outlook/send_mail.ts +++ b/libs/langchain-community/src/tools/outlook/send_mail.ts @@ -35,7 +35,7 @@ export class OutlookSendMailTool extends OutlookBase { */ async _call(message: string): Promise { try { - await this.getAuth(); + this.accessToken = await this.authFlow.getAccessToken(); } catch (error) { return `Failed to get access token: ${error}`; } From 4719a521c49c8bc73d98096da082134d92a7c56b Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Thu, 28 Dec 2023 20:52:18 -0500 Subject: [PATCH 41/46] format --- libs/langchain-community/src/tools/outlook/auth/rest.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libs/langchain-community/src/tools/outlook/auth/rest.ts b/libs/langchain-community/src/tools/outlook/auth/rest.ts index 9569935f072b..d9d605bb1c74 100644 --- a/libs/langchain-community/src/tools/outlook/auth/rest.ts +++ b/libs/langchain-community/src/tools/outlook/auth/rest.ts @@ -192,7 +192,10 @@ export class AuthFlowREST extends AuthFlowBase { * @public * @returns {Promise<{ accessToken: string; refreshToken: string }>} A Promise resolving to the access and refresh tokens. */ - public async getTokens(): Promise<{ accessToken: string; refreshToken: string }> { + public async getTokens(): Promise<{ + accessToken: string; + refreshToken: string; + }> { // fetch auth code from user login const code = await this.getCode(); // fetch access token using auth code From cdde1d45037bd123ce07a44a9a14675f05963668 Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Thu, 28 Dec 2023 21:20:11 -0500 Subject: [PATCH 42/46] update docs --- docs/api_refs/typedoc.json | 2 + .../docs/integrations/toolkits/outlook.mdx | 45 +++++++++++-------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/docs/api_refs/typedoc.json b/docs/api_refs/typedoc.json index e474c502fff7..67ff0b521038 100644 --- a/docs/api_refs/typedoc.json +++ b/docs/api_refs/typedoc.json @@ -362,6 +362,7 @@ "../../libs/langchain-community/src/tools/google_custom_search.ts", "../../libs/langchain-community/src/tools/google_places.ts", "../../libs/langchain-community/src/tools/ifttt.ts", + "../../libs/langchain-community/src/tools/outlook/index.ts", "../../libs/langchain-community/src/tools/searchapi.ts", "../../libs/langchain-community/src/tools/searxng_search.ts", "../../libs/langchain-community/src/tools/serpapi.ts", @@ -408,6 +409,7 @@ "../../libs/langchain-community/src/llms/writer.ts", "../../libs/langchain-community/src/llms/yandex.ts", "../../libs/langchain-community/src/vectorstores/analyticdb.ts", + "../../libs/langchain-community/src/vectorstores/astradb.ts", "../../libs/langchain-community/src/vectorstores/azure_cosmosdb.ts", "../../libs/langchain-community/src/vectorstores/cassandra.ts", "../../libs/langchain-community/src/vectorstores/chroma.ts", diff --git a/docs/core_docs/docs/integrations/toolkits/outlook.mdx b/docs/core_docs/docs/integrations/toolkits/outlook.mdx index 15b77a24bde0..3429743671f9 100644 --- a/docs/core_docs/docs/integrations/toolkits/outlook.mdx +++ b/docs/core_docs/docs/integrations/toolkits/outlook.mdx @@ -64,12 +64,6 @@ This video may also be helpful: https://www.youtube.com/watch?v=NAtiNpwnivI&t=309s - OUTLOOK_ACCESS_TOKEN=your_access_token - // for AuthFlowToken - OUTLOOK_REFRESH_TOKEN=your_refresh_token - - ``` - ### Get an accessToken Now you are able to use the above credentials to get an `accessToken`. @@ -78,61 +72,74 @@ The `AuthFlowREST` class implements the OAuth 2.0 authorization code flow. You can use the `getAccessToken()` which will open a temporary server on your `redirectUri` and you need to manually open the printed link to login on behalf of the user. For every `AuthFlowREST` instance, you only need to -login once. The next time you can run the `refreshAccessToken()` method, it will +login once. The next time you can run the `getAccessToken()` method, it will make use of the `refreshToken` obtained together with the first `accessToken` - to get a new `accessToken` without the need of the server and login. +to get a new `accessToken` without the need of the server and login. Having the above credentials, you can either pass them to the constructor or put them in the environment. ```typescript +import { AuthFlowREST } from '@langchain/community/tools/outlook'; + const authFlow = new AuthFlowREST(); const authFlow = new AuthFlowREST(clientId, clientSecret, redirectUri); // Get accessToken const accessToken = await authFlow.getAccessToken(); // After the above line, you can get the refreshToken const refreshToken = authFlow.getRefreshToken(); -// Refresh accessToken -const accessToken = await authFlow.refreshAccessToken(); +// Get accessToken again without login +const accessToken = await authFlow.getAccessToken(); +``` + +Now you can set the `accessToken` and `refreshToken` as environment variable: + +```env +OUTLOOK_ACCESS_TOKEN=your_access_token +OUTLOOK_REFRESH_TOKEN=your_refresh_token ``` ### Provide accessToken to Outlook Toolkit -The Outlook Toolkit needs `AuthFlow` abstarct class to provide accessToken. +The Outlook Toolkit needs `AuthFlowBase` abstarct class to provide accessToken. ```typescript const authFlow = new AuthFlowREST(); -const outlookReadMail = new OutlookReadMailTool(authFlow=authFlow); +const outlookReadMail = new OutlookReadMailTool(authFlow); // or simply this when you have the credentials in .env -const outlookReadMail = new OutlookReadMailTool(choice="rest"); +const outlookReadMail = new OutlookReadMailTool(undefined, "rest"); ``` ### Other classes to provide accessToken -If you only have a valid `accessToken` in your env: +If you only have a valid `accessToken` in your env, you can use `AuthFlowToken`: ```typescript const authFlow = new AuthFlowToken(accessToken); // if you have accessToken stored in .env const authFlow = new AuthFlowToken(); -const outlookReadMail = new OutlookReadMailTool(authFlow=authFlow); +const outlookReadMail = new OutlookReadMailTool(authFlow); // or simply this when you have accessToken in .env -const outlookReadMail = new OutlookReadMailTool(choice="token"); +const outlookReadMail = new OutlookReadMailTool(undefined, "token"); ``` Usually when you obtain a accessToken you also get a refreshToken -used to refresh expired accessToken. The `AuthFlowREST` class needs +used to refresh expired accessToken. The `AuthFlowRefresh` class needs the following credentials: `clientId`, `clientSecret`, `redirectUri` and `refreshToken`. In this way you won't need to login, no server will be open. ```typescript const authFlow = new AuthFlowRefresh(); -const outlookReadMail = new OutlookReadMailTool(authFlow=authFlow); +const outlookReadMail = new OutlookReadMailTool(authFlow); // or simply this when you have the credentials in .env -const outlookReadMail = new OutlookReadMailTool(choice="refresh"); +const outlookReadMail = new OutlookReadMailTool(undefined, "refresh"); ``` +### Customize your own authentication flow + +Inherit from `AuthFlowBase` and implement the `getAccessToken()` method. + ## Usage This example shows how to read emails by using agent. From 42189f80acafdf970e9c12ad1d0d9873a313db6e Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Sat, 6 Jan 2024 03:02:29 -0500 Subject: [PATCH 43/46] format outlook.mdx --- .../docs/integrations/toolkits/outlook.mdx | 108 ++++++++++-------- 1 file changed, 63 insertions(+), 45 deletions(-) diff --git a/docs/core_docs/docs/integrations/toolkits/outlook.mdx b/docs/core_docs/docs/integrations/toolkits/outlook.mdx index 3429743671f9..0883270e32e4 100644 --- a/docs/core_docs/docs/integrations/toolkits/outlook.mdx +++ b/docs/core_docs/docs/integrations/toolkits/outlook.mdx @@ -2,20 +2,20 @@ ## Overview -Using this toolkit, you can integrate Microsoft Graph API functionalities into +Using this toolkit, you can integrate Microsoft Graph API functionalities into your applications to manage email interactions. ## Learn Microsoft Graph -Microsoft Graph is a RESTful web API that enables you to access Microsoft -Cloud service resources. With Microsoft Graph, you can integrate various -Microsoft services and data, including users' emails, calendar, contacts, +Microsoft Graph is a RESTful web API that enables you to access Microsoft +Cloud service resources. With Microsoft Graph, you can integrate various +Microsoft services and data, including users' emails, calendar, contacts, and more, into your applications. -After you register your app and get authentication tokens for a user or service, +After you register your app and get authentication tokens for a user or service, you can make requests to the Microsoft Graph API. -How to get authentication tokens for a user: +How to get authentication tokens for a user: https://learn.microsoft.com/en-us/graph/auth-v2-user ## Set up @@ -28,21 +28,24 @@ necessary credentials. ### App register 1. Register an Application: + - Go to Azure Portal. - Navigate to Azure Active Directory -> App registrations. - Register a new application, note down the Client ID. 2. Generate Client Secret: + - In the application settings, go to Certificates & Secrets. - Create a new client secret and note down the Client Secret. 3. Configure Redirect URI: + - In the application settings, go to Authentication. - Add a Redirect URI and note down the Redirect URI. 4. Permissions and Admin Consent: - - In API permissions, grant necessary permissions for Microsoft Graph API or - Outlook API. + - In API permissions, grant necessary permissions for Microsoft Graph API or + Outlook API. - Grant admin consent for the added permissions. At this point, you should have these three credentials: @@ -50,12 +53,13 @@ clientId, clientSecret, redirectUri Set them as environment variables: - ```env - OUTLOOK_CLIENT_ID=your_client_id - OUTLOOK_CLIENT_SECRET=your_client_secret - OUTLOOK_REDIRECT_URI=your_redirect_uri - ``` - **Keep these values secure and avoid exposing them in public repositories.** +```env +OUTLOOK_CLIENT_ID=your_client_id +OUTLOOK_CLIENT_SECRET=your_client_secret +OUTLOOK_REDIRECT_URI=your_redirect_uri +``` + +**Keep these values secure and avoid exposing them in public repositories.** The following link from Microsoft is a good reference for the above steps: https://learn.microsoft.com/en-us/graph/auth-v2-user @@ -63,24 +67,23 @@ https://learn.microsoft.com/en-us/graph/auth-v2-user This video may also be helpful: https://www.youtube.com/watch?v=NAtiNpwnivI&t=309s - ### Get an accessToken Now you are able to use the above credentials to get an `accessToken`. The `AuthFlowREST` class implements the OAuth 2.0 authorization code flow. -You can use the `getAccessToken()` which will open a temporary server on -your `redirectUri` and you need to manually open the printed link to login -on behalf of the user. For every `AuthFlowREST` instance, you only need to +You can use the `getAccessToken()` which will open a temporary server on +your `redirectUri` and you need to manually open the printed link to login +on behalf of the user. For every `AuthFlowREST` instance, you only need to login once. The next time you can run the `getAccessToken()` method, it will make use of the `refreshToken` obtained together with the first `accessToken` to get a new `accessToken` without the need of the server and login. -Having the above credentials, you can either pass them to the constructor +Having the above credentials, you can either pass them to the constructor or put them in the environment. ```typescript -import { AuthFlowREST } from '@langchain/community/tools/outlook'; +import { AuthFlowREST } from "@langchain/community/tools/outlook"; const authFlow = new AuthFlowREST(); const authFlow = new AuthFlowREST(clientId, clientSecret, redirectUri); @@ -123,10 +126,10 @@ const outlookReadMail = new OutlookReadMailTool(authFlow); const outlookReadMail = new OutlookReadMailTool(undefined, "token"); ``` -Usually when you obtain a accessToken you also get a refreshToken -used to refresh expired accessToken. The `AuthFlowRefresh` class needs -the following credentials: `clientId`, `clientSecret`, `redirectUri` -and `refreshToken`. In this way you won't need to login, no server +Usually when you obtain a accessToken you also get a refreshToken +used to refresh expired accessToken. The `AuthFlowRefresh` class needs +the following credentials: `clientId`, `clientSecret`, `redirectUri` +and `refreshToken`. In this way you won't need to login, no server will be open. ```typescript @@ -145,22 +148,29 @@ Inherit from `AuthFlowBase` and implement the `getAccessToken()` method. This example shows how to read emails by using agent. ```typescript -import { ChatOpenAI } from 'langchain/chat_models/openai'; -import { initializeAgentExecutorWithOptions } from 'langchain/agents'; -import { OutlookReadMailTool } from '@langchain/community/tools/outlook'; -require('dotenv').config(); +import { ChatOpenAI } from "langchain/chat_models/openai"; +import { initializeAgentExecutorWithOptions } from "langchain/agents"; +import { OutlookReadMailTool } from "@langchain/community/tools/outlook"; +require("dotenv").config(); async function createAndRunAgent() { - const gpt4 = new ChatOpenAI({ modelName: 'gpt-3.5-turbo-1106', openAIApiKey: process.env.OPENAI_API_KEY}); + const gpt4 = new ChatOpenAI({ + modelName: "gpt-3.5-turbo-1106", + openAIApiKey: process.env.OPENAI_API_KEY, + }); const outlookReadMail = new OutlookReadMailTool(undefined, "token"); - const executor = await initializeAgentExecutorWithOptions([outlookReadMail], gpt4, { - agentType: "openai-functions", - verbose: true, - }); + const executor = await initializeAgentExecutorWithOptions( + [outlookReadMail], + gpt4, + { + agentType: "openai-functions", + verbose: true, + } + ); - const input = 'show my emails'; + const input = "show my emails"; const result = await executor.invoke({ input }); console.log(result); @@ -172,22 +182,30 @@ createAndRunAgent(); This example shows how to send emails by using agent. ```typescript -import { ChatOpenAI } from 'langchain/chat_models/openai'; -import { initializeAgentExecutorWithOptions } from 'langchain/agents'; -import { OutlookSendMailTool } from '@langchain/community/tools/outlook'; -require('dotenv').config(); +import { ChatOpenAI } from "langchain/chat_models/openai"; +import { initializeAgentExecutorWithOptions } from "langchain/agents"; +import { OutlookSendMailTool } from "@langchain/community/tools/outlook"; +require("dotenv").config(); async function createAndRunAgent() { - const gpt4 = new ChatOpenAI({ modelName: 'gpt-4', openAIApiKey: process.env.OPENAI_API_KEY}); + const gpt4 = new ChatOpenAI({ + modelName: "gpt-4", + openAIApiKey: process.env.OPENAI_API_KEY, + }); const sendMailTool = new OutlookSendMailTool(undefined, "token"); - const executor = await initializeAgentExecutorWithOptions([sendMailTool], gpt4, { - agentType: "openai-functions", - verbose: true, - }); - - const input = 'send an email to YOUR_EMAIL and invite him to a meeting at 3pm tomorrow'; + const executor = await initializeAgentExecutorWithOptions( + [sendMailTool], + gpt4, + { + agentType: "openai-functions", + verbose: true, + } + ); + + const input = + "send an email to YOUR_EMAIL and invite him to a meeting at 3pm tomorrow"; const result = await executor.invoke({ input }); console.log(result); From d1e961e23457fb664246b2ce0ff726de015f702b Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Thu, 18 Jan 2024 16:55:30 -0500 Subject: [PATCH 44/46] putting example in the examples/ --- .../docs/integrations/toolkits/outlook.mdx | 69 +------------------ examples/src/tools/outlook.ts | 64 +++++++++++++++++ .../src/load/import_constants.ts | 2 +- .../src/load/import_type.d.ts | 6 +- 4 files changed, 71 insertions(+), 70 deletions(-) create mode 100644 examples/src/tools/outlook.ts diff --git a/docs/core_docs/docs/integrations/toolkits/outlook.mdx b/docs/core_docs/docs/integrations/toolkits/outlook.mdx index 0883270e32e4..e7bdf435f53b 100644 --- a/docs/core_docs/docs/integrations/toolkits/outlook.mdx +++ b/docs/core_docs/docs/integrations/toolkits/outlook.mdx @@ -145,71 +145,8 @@ Inherit from `AuthFlowBase` and implement the `getAccessToken()` method. ## Usage -This example shows how to read emails by using agent. +These two examples show how to readand send emails by using agent. -```typescript -import { ChatOpenAI } from "langchain/chat_models/openai"; -import { initializeAgentExecutorWithOptions } from "langchain/agents"; -import { OutlookReadMailTool } from "@langchain/community/tools/outlook"; -require("dotenv").config(); - -async function createAndRunAgent() { - const gpt4 = new ChatOpenAI({ - modelName: "gpt-3.5-turbo-1106", - openAIApiKey: process.env.OPENAI_API_KEY, - }); - - const outlookReadMail = new OutlookReadMailTool(undefined, "token"); - - const executor = await initializeAgentExecutorWithOptions( - [outlookReadMail], - gpt4, - { - agentType: "openai-functions", - verbose: true, - } - ); - - const input = "show my emails"; - const result = await executor.invoke({ input }); - - console.log(result); -} - -createAndRunAgent(); -``` - -This example shows how to send emails by using agent. +import ToolExample from "@examples/tools/outlook.ts"; -```typescript -import { ChatOpenAI } from "langchain/chat_models/openai"; -import { initializeAgentExecutorWithOptions } from "langchain/agents"; -import { OutlookSendMailTool } from "@langchain/community/tools/outlook"; -require("dotenv").config(); - -async function createAndRunAgent() { - const gpt4 = new ChatOpenAI({ - modelName: "gpt-4", - openAIApiKey: process.env.OPENAI_API_KEY, - }); - - const sendMailTool = new OutlookSendMailTool(undefined, "token"); - - const executor = await initializeAgentExecutorWithOptions( - [sendMailTool], - gpt4, - { - agentType: "openai-functions", - verbose: true, - } - ); - - const input = - "send an email to YOUR_EMAIL and invite him to a meeting at 3pm tomorrow"; - const result = await executor.invoke({ input }); - - console.log(result); -} - -createAndRunAgent(); -``` +{ToolExample} diff --git a/examples/src/tools/outlook.ts b/examples/src/tools/outlook.ts new file mode 100644 index 000000000000..abbb30c2c316 --- /dev/null +++ b/examples/src/tools/outlook.ts @@ -0,0 +1,64 @@ +import { ChatOpenAI } from "@langchain/openai"; +import { AgentExecutor, createOpenAIFunctionsAgent } from "langchain/agents"; +import { + OutlookReadMailTool, + OutlookSendMailTool, +} from "@langchain/community/tools/outlook"; +import { pull } from "langchain/hub"; +import type { ChatPromptTemplate } from "@langchain/core/prompts"; + +async function AgentRead() { + const llm = new ChatOpenAI({ + modelName: "gpt-3.5-turbo-1106", + }); + + const outlookReadMail = new OutlookReadMailTool(undefined, "token"); + const tools = [outlookReadMail]; + const prompt = await pull("hwchase17/openai-tools-agent"); + + const agent = await createOpenAIFunctionsAgent({ + llm, + tools, + prompt, + }); + + const agentExecutor = new AgentExecutor({ + agent, + tools, + }); + + const input = "show my emails"; + const result = await agentExecutor.invoke({ input }); + + console.log(result); +} + +AgentRead(); + +async function AgentSend() { + const llm = new ChatOpenAI({ + modelName: "gpt-4", + }); + + const sendMailTool = new OutlookSendMailTool(undefined, "token"); + const tools = [sendMailTool]; + const prompt = await pull("hwchase17/openai-tools-agent"); + + const agent = await createOpenAIFunctionsAgent({ + llm, + tools, + prompt, + }); + const agentExecutor = new AgentExecutor({ + agent, + tools, + }); + + const input = + "send an email to YOUR_EMAIL and invite him to a meeting at 3pm tomorrow"; + const result = await agentExecutor.invoke({ input }); + + console.log(result); +} + +AgentSend(); diff --git a/libs/langchain-community/src/load/import_constants.ts b/libs/langchain-community/src/load/import_constants.ts index 0db6f725d923..03c3bd35c249 100644 --- a/libs/langchain-community/src/load/import_constants.ts +++ b/libs/langchain-community/src/load/import_constants.ts @@ -5,8 +5,8 @@ export const optionalImportEntrypoints = [ "langchain_community/tools/aws_sfn", "langchain_community/tools/discord", "langchain_community/tools/gmail", - "langchain_community/tools/outlook", "langchain_community/tools/google_calendar", + "langchain_community/tools/outlook", "langchain_community/agents/toolkits/aws_sfn", "langchain_community/embeddings/bedrock", "langchain_community/embeddings/cloudflare_workersai", diff --git a/libs/langchain-community/src/load/import_type.d.ts b/libs/langchain-community/src/load/import_type.d.ts index 13ed10ce4a13..15dd9d5b03fe 100644 --- a/libs/langchain-community/src/load/import_type.d.ts +++ b/libs/langchain-community/src/load/import_type.d.ts @@ -13,12 +13,12 @@ export interface OptionalImportMap { "@langchain/community/tools/gmail"?: | typeof import("../tools/gmail/index.js") | Promise; - "@langchain/community/tools/outlook"?: - | typeof import("../tools/outlook/index.js") - | Promise; "@langchain/community/tools/google_calendar"?: | typeof import("../tools/google_calendar/index.js") | Promise; + "@langchain/community/tools/outlook"?: + | typeof import("../tools/outlook/index.js") + | Promise; "@langchain/community/agents/toolkits/aws_sfn"?: | typeof import("../agents/toolkits/aws_sfn.js") | Promise; From 569b34e2115e46dd69c8b68a32c822afdd90f5e8 Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Thu, 18 Jan 2024 19:24:28 -0500 Subject: [PATCH 45/46] Update outlook.mdx --- docs/core_docs/docs/integrations/toolkits/outlook.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/core_docs/docs/integrations/toolkits/outlook.mdx b/docs/core_docs/docs/integrations/toolkits/outlook.mdx index e7bdf435f53b..5640caf64b5c 100644 --- a/docs/core_docs/docs/integrations/toolkits/outlook.mdx +++ b/docs/core_docs/docs/integrations/toolkits/outlook.mdx @@ -1,3 +1,5 @@ +import CodeBlock from "@theme/CodeBlock"; + # Outlook Toolkit ## Overview From 981242e619045de3720758158947adb0738d59ee Mon Sep 17 00:00:00 2001 From: oscarchen178 <65813281+oscarchen178@users.noreply.github.com> Date: Thu, 18 Jan 2024 21:44:55 -0500 Subject: [PATCH 46/46] Update outlook.ts --- examples/src/tools/outlook.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/src/tools/outlook.ts b/examples/src/tools/outlook.ts index abbb30c2c316..938545dc515b 100644 --- a/examples/src/tools/outlook.ts +++ b/examples/src/tools/outlook.ts @@ -1,11 +1,11 @@ -import { ChatOpenAI } from "@langchain/openai"; -import { AgentExecutor, createOpenAIFunctionsAgent } from "langchain/agents"; import { OutlookReadMailTool, OutlookSendMailTool, } from "@langchain/community/tools/outlook"; -import { pull } from "langchain/hub"; +import { ChatOpenAI } from "@langchain/openai"; import type { ChatPromptTemplate } from "@langchain/core/prompts"; +import { pull } from "langchain/hub"; +import { AgentExecutor, createOpenAIFunctionsAgent } from "langchain/agents"; async function AgentRead() { const llm = new ChatOpenAI({ @@ -14,7 +14,9 @@ async function AgentRead() { const outlookReadMail = new OutlookReadMailTool(undefined, "token"); const tools = [outlookReadMail]; - const prompt = await pull("hwchase17/openai-tools-agent"); + const prompt = await pull( + "hwchase17/openai-functions-agent" + ); const agent = await createOpenAIFunctionsAgent({ llm, @@ -42,7 +44,9 @@ async function AgentSend() { const sendMailTool = new OutlookSendMailTool(undefined, "token"); const tools = [sendMailTool]; - const prompt = await pull("hwchase17/openai-tools-agent"); + const prompt = await pull( + "hwchase17/openai-functions-agent" + ); const agent = await createOpenAIFunctionsAgent({ llm,