import { Tool } from "./base.js"; import { renderTemplate } from "../prompts/template.js"; import { AsyncCaller, AsyncCallerParams } from "../util/async_caller.js"; import { getEnvironmentVariable } from "../util/env.js"; import { Serializable } from "../load/serializable.js"; const zapierNLABaseDescription: string = "A wrapper around Zapier NLA actions. " + "The input to this tool is a natural language instruction, " + 'for example "get the latest email from my bank" or ' + '"send a slack message to the #general channel". ' + "Each tool will have params associated with it that are specified as a list. You MUST take into account the params when creating the instruction. " + "For example, if the params are ['Message_Text', 'Channel'], your instruction should be something like 'send a slack message to the #general channel with the text hello world'. " + "Another example: if the params are ['Calendar', 'Search_Term'], your instruction should be something like 'find the meeting in my personal calendar at 3pm'. " + "Do not make up params, they will be explicitly specified in the tool description. " + "If you do not have enough information to fill in the params, just say 'not enough information provided in the instruction, missing '. " + "If you get a none or null response, STOP EXECUTION, do not try to another tool! " + "This tool specifically used for: {zapier_description}, " + "and has params: {params}"; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type ZapierValues = Record; export interface ZapierNLAWrapperParams extends AsyncCallerParams { /** * NLA API Key. Found in the NLA documentation https://nla.zapier.com/docs/authentication/#api-key * Can also be set via the environment variable `ZAPIER_NLA_API_KEY` */ apiKey?: string; /** * NLA OAuth Access Token. Found in the NLA documentation https://nla.zapier.com/docs/authentication/#oauth-credentials * Can also be set via the environment variable `ZAPIER_NLA_OAUTH_ACCESS_TOKEN` */ oauthAccessToken?: string; } export class ZapierNLAWrapper extends Serializable { lc_namespace = ["langchain", "tools", "zapier"]; get lc_secrets(): { [key: string]: string } | undefined { return { apiKey: "ZAPIER_NLA_API_KEY", }; } zapierNlaApiKey?: string; zapierNlaOAuthAccessToken?: string; zapierNlaApiBase = "https://nla.zapier.com/api/v1/"; caller: AsyncCaller; constructor(params?: ZapierNLAWrapperParams) { super(params); const zapierNlaOAuthAccessToken = params?.oauthAccessToken; const zapierNlaApiKey = params?.apiKey; const oauthAccessToken = zapierNlaOAuthAccessToken ?? getEnvironmentVariable("ZAPIER_NLA_OAUTH_ACCESS_TOKEN"); const apiKey = zapierNlaApiKey ?? getEnvironmentVariable("ZAPIER_NLA_API_KEY"); if (!apiKey && !oauthAccessToken) { throw new Error( "Neither ZAPIER_NLA_OAUTH_ACCESS_TOKEN or ZAPIER_NLA_API_KEY are set" ); } if (oauthAccessToken) { this.zapierNlaOAuthAccessToken = oauthAccessToken; } else { this.zapierNlaApiKey = apiKey; } this.caller = new AsyncCaller( typeof params === "string" ? {} : params ?? {} ); } protected _getHeaders(): Record { const headers: { "Content-Type": string; Accept: string; Authorization?: string; "x-api-key"?: string; } = { "Content-Type": "application/json", Accept: "application/json", }; if (this.zapierNlaOAuthAccessToken) { headers.Authorization = `Bearer ${this.zapierNlaOAuthAccessToken}`; } else { headers["x-api-key"] = this.zapierNlaApiKey; } return headers; } protected async _getActionRequest( actionId: string, instructions: string, params?: ZapierValues ): Promise { const data = params ?? {}; data.instructions = instructions; const headers = this._getHeaders(); // add api key to params const resp = await this.caller.call( fetch, `${this.zapierNlaApiBase}exposed/${actionId}/execute/`, { method: "POST", headers, body: JSON.stringify(data), } ); if (!resp.ok) { throw new Error( `Failed to execute action ${actionId} with instructions ${instructions}` ); } const jsonResp = await resp.json(); if (jsonResp.status === "error") { throw new Error(`Error from Zapier: ${jsonResp.error}`); } return jsonResp; } /** * Executes an action that is identified by action_id, must be exposed * (enabled) by the current user (associated with the set api_key or access token). * @param actionId * @param instructions * @param params */ async runAction( actionId: string, instructions: string, params?: ZapierValues ): Promise { const resp = await this._getActionRequest(actionId, instructions, params); return resp.status === "error" ? resp.error : resp.result; } /** * Same as run, but instead of actually executing the action, will * instead return a preview of params that have been guessed by the AI in * case you need to explicitly review before executing. * @param actionId * @param instructions * @param params */ async previewAction( actionId: string, instructions: string, params?: ZapierValues ): Promise { const data = params ?? {}; data.preview_only = true; const resp = await this._getActionRequest(actionId, instructions, data); return resp.input_params; } /** * Returns a list of all exposed (enabled) actions associated with * current user (associated with the set api_key or access token). */ async listActions(): Promise { const headers = this._getHeaders(); const resp = await this.caller.call( fetch, `${this.zapierNlaApiBase}exposed/`, { method: "GET", headers, } ); if (!resp.ok) { if (resp.status === 401) { if (this.zapierNlaOAuthAccessToken) { throw new Error( "A 401 Unauthorized error was returned. Check that your access token is correct and doesn't need to be refreshed." ); } throw new Error( "A 401 Unauthorized error was returned. Check that your API Key is correct." ); } throw new Error("Failed to list actions"); } return (await resp.json()).results; } /** * Same as run, but returns a stringified version of the result. * @param actionId * @param instructions * @param params */ async runAsString( actionId: string, instructions: string, params?: ZapierValues ): Promise { const result = await this.runAction(actionId, instructions, params); return JSON.stringify(result); } /** * Same as preview, but returns a stringified version of the result. * @param actionId * @param instructions * @param params */ async previewAsString( actionId: string, instructions: string, params?: ZapierValues ): Promise { const result = await this.previewAction(actionId, instructions, params); return JSON.stringify(result); } /** * Same as list, but returns a stringified version of the result. */ async listActionsAsString(): Promise { const result = await this.listActions(); return JSON.stringify(result); } } export class ZapierNLARunAction extends Tool { apiWrapper: ZapierNLAWrapper; actionId: string; params?: ZapierValues; name: string; description: string; constructor( apiWrapper: ZapierNLAWrapper, actionId: string, zapierDescription: string, paramsSchema: ZapierValues, params?: ZapierValues ) { super(); this.apiWrapper = apiWrapper; this.actionId = actionId; this.params = params; this.name = zapierDescription; const paramsSchemaWithoutInstructions = { ...paramsSchema }; delete paramsSchemaWithoutInstructions.instructions; const paramsSchemaKeysString = JSON.stringify( Object.keys(paramsSchemaWithoutInstructions) ); this.description = renderTemplate(zapierNLABaseDescription, "f-string", { zapier_description: zapierDescription, params: paramsSchemaKeysString, }); } /** @ignore */ async _call(arg: string): Promise { return this.apiWrapper.runAsString(this.actionId, arg, this.params); } }