{"version":3,"file":"dataforseo_api_search.cjs","names":["Tool"],"sources":["../../src/tools/dataforseo_api_search.ts"],"sourcesContent":["import { getEnvironmentVariable } from \"@langchain/core/utils/env\";\nimport { Tool } from \"@langchain/core/tools\";\n\n/**\n * @interface DataForSeoApiConfig\n * @description Represents the configuration object used to set up a DataForSeoAPISearch instance.\n */\nexport interface DataForSeoApiConfig {\n  /**\n   * @property apiLogin\n   * @type {string}\n   * @description The API login credential for DataForSEO. If not provided, it will be fetched from environment variables.\n   */\n  apiLogin?: string;\n\n  /**\n   * @property apiPassword\n   * @type {string}\n   * @description The API password credential for DataForSEO. If not provided, it will be fetched from environment variables.\n   */\n  apiPassword?: string;\n\n  /**\n   * @property params\n   * @type {Record<string, string | number | boolean>}\n   * @description Additional parameters to customize the API request.\n   */\n  params?: Record<string, string | number | boolean>;\n\n  /**\n   * @property useJsonOutput\n   * @type {boolean}\n   * @description Determines if the output should be in JSON format.\n   */\n  useJsonOutput?: boolean;\n\n  /**\n   * @property jsonResultTypes\n   * @type {Array<string>}\n   * @description Specifies the types of results to include in the output.\n   */\n  jsonResultTypes?: Array<string>;\n\n  /**\n   * @property jsonResultFields\n   * @type {Array<string>}\n   * @description Specifies the fields to include in each result object.\n   */\n  jsonResultFields?: Array<string>;\n\n  /**\n   * @property topCount\n   * @type {number}\n   * @description Specifies the maximum number of results to return.\n   */\n  topCount?: number;\n}\n\n/**\n * Represents a task in the API response.\n */\ntype Task = {\n  id: string;\n  status_code: number;\n  status_message: string;\n  time: string;\n  result: Result[];\n};\n\n/**\n * Represents a result in the API response.\n */\ntype Result = {\n  keyword: string;\n  check_url: string;\n  datetime: string;\n  spell?: string;\n  item_types: string[];\n  se_results_count: number;\n  items_count: number;\n  // oxlint-disable-next-line typescript/no-explicit-any\n  items: any[];\n};\n\n/**\n * Represents the API response.\n */\ntype ApiResponse = {\n  status_code: number;\n  status_message: string;\n  tasks: Task[];\n};\n\n/**\n * @class DataForSeoAPISearch\n * @extends {Tool}\n * @description Represents a wrapper class to work with DataForSEO SERP API.\n */\nexport class DataForSeoAPISearch extends Tool {\n  static lc_name() {\n    return \"DataForSeoAPISearch\";\n  }\n\n  name = \"dataforseo-api-wrapper\";\n\n  description =\n    \"A robust Google Search API provided by DataForSeo. This tool is handy when you need information about trending topics or current events.\";\n\n  protected apiLogin: string;\n\n  protected apiPassword: string;\n\n  /**\n   * @property defaultParams\n   * @type {Record<string, string | number | boolean>}\n   * @description These are the default parameters to be used when making an API request.\n   */\n  protected defaultParams: Record<string, string | number | boolean> = {\n    location_name: \"United States\",\n    language_code: \"en\",\n    depth: 10,\n    se_name: \"google\",\n    se_type: \"organic\",\n  };\n\n  protected params: Record<string, string | number | boolean> = {};\n\n  protected jsonResultTypes: Array<string> | undefined;\n\n  protected jsonResultFields: Array<string> | undefined;\n\n  protected topCount: number | undefined;\n\n  protected useJsonOutput = false;\n\n  /**\n   * @constructor\n   * @param {DataForSeoApiConfig} config\n   * @description Sets up the class, throws an error if the API login/password isn't provided.\n   */\n  constructor(config: DataForSeoApiConfig = {}) {\n    super();\n    const apiLogin =\n      config.apiLogin ?? getEnvironmentVariable(\"DATAFORSEO_LOGIN\");\n    const apiPassword =\n      config.apiPassword ?? getEnvironmentVariable(\"DATAFORSEO_PASSWORD\");\n    const params = config.params ?? {};\n    if (!apiLogin || !apiPassword) {\n      throw new Error(\n        \"DataForSEO login or password not set. You can set it as DATAFORSEO_LOGIN and DATAFORSEO_PASSWORD in your .env file, or pass it to DataForSeoAPISearch.\"\n      );\n    }\n    this.params = { ...this.defaultParams, ...params };\n    this.apiLogin = apiLogin;\n    this.apiPassword = apiPassword;\n    this.jsonResultTypes = config.jsonResultTypes;\n    this.jsonResultFields = config.jsonResultFields;\n    this.useJsonOutput = config.useJsonOutput ?? false;\n    this.topCount = config.topCount;\n  }\n\n  /**\n   * @method _call\n   * @param {string} keyword\n   * @returns {Promise<string>}\n   * @description Initiates a call to the API and processes the response.\n   */\n  async _call(keyword: string): Promise<string> {\n    return this.useJsonOutput\n      ? JSON.stringify(await this.results(keyword))\n      : this.processResponse(await this.getResponseJson(keyword));\n  }\n\n  /**\n   * @method results\n   * @param {string} keyword\n   * @returns {Promise<Array<any>>}\n   * @description Fetches the results from the API for the given keyword.\n   */\n  // oxlint-disable-next-line typescript/no-explicit-any\n  async results(keyword: string): Promise<Array<any>> {\n    const res = await this.getResponseJson(keyword);\n    return this.filterResults(res, this.jsonResultTypes);\n  }\n\n  /**\n   * @method prepareRequest\n   * @param {string} keyword\n   * @returns {{url: string; headers: HeadersInit; data: BodyInit}}\n   * @description Prepares the request details for the API call.\n   */\n  protected prepareRequest(keyword: string): {\n    url: string;\n    headers: HeadersInit;\n    data: BodyInit;\n  } {\n    if (this.apiLogin === undefined || this.apiPassword === undefined) {\n      throw new Error(\"api_login or api_password is not provided\");\n    }\n\n    const credentials = Buffer.from(\n      `${this.apiLogin}:${this.apiPassword}`,\n      \"utf-8\"\n    ).toString(\"base64\");\n    const headers = {\n      Authorization: `Basic ${credentials}`,\n      \"Content-Type\": \"application/json\",\n    };\n\n    const params = { ...this.params };\n    params.keyword ??= keyword;\n    const data = [params];\n\n    return {\n      url: `https://api.dataforseo.com/v3/serp/${params.se_name}/${params.se_type}/live/advanced`,\n      headers,\n      data: JSON.stringify(data),\n    };\n  }\n\n  /**\n   * @method getResponseJson\n   * @param {string} keyword\n   * @returns {Promise<ApiResponse>}\n   * @description Executes a POST request to the provided URL and returns a parsed JSON response.\n   */\n  protected async getResponseJson(keyword: string): Promise<ApiResponse> {\n    const requestDetails = this.prepareRequest(keyword);\n    const response = await fetch(requestDetails.url, {\n      method: \"POST\",\n      headers: requestDetails.headers,\n      body: requestDetails.data,\n    });\n\n    if (!response.ok) {\n      throw new Error(\n        `Got ${response.status} error from DataForSEO: ${response.statusText}`\n      );\n    }\n\n    const result: ApiResponse = await response.json();\n    return this.checkResponse(result);\n  }\n\n  /**\n   * @method checkResponse\n   * @param {ApiResponse} response\n   * @returns {ApiResponse}\n   * @description Checks the response status code.\n   */\n  private checkResponse(response: ApiResponse): ApiResponse {\n    if (response.status_code !== 20000) {\n      throw new Error(\n        `Got error from DataForSEO SERP API: ${response.status_message}`\n      );\n    }\n    for (const task of response.tasks) {\n      if (task.status_code !== 20000) {\n        throw new Error(\n          `Got error from DataForSEO SERP API: ${task.status_message}`\n        );\n      }\n    }\n    return response;\n  }\n\n  /* oxlint-disable typescript/no-explicit-any */\n  /**\n   * @method filterResults\n   * @param {ApiResponse} res\n   * @param {Array<string> | undefined} types\n   * @returns {Array<any>}\n   * @description Filters the results based on the specified result types.\n   */\n  private filterResults(\n    res: ApiResponse,\n    types: Array<string> | undefined\n  ): Array<any> {\n    const output: Array<any> = [];\n    for (const task of res.tasks || []) {\n      for (const result of task.result || []) {\n        for (const item of result.items || []) {\n          if (\n            types === undefined ||\n            types.length === 0 ||\n            types.includes(item.type)\n          ) {\n            const newItem = this.cleanupUnnecessaryItems(item);\n            if (Object.keys(newItem).length !== 0) {\n              output.push(newItem);\n            }\n          }\n          if (this.topCount !== undefined && output.length >= this.topCount) {\n            break;\n          }\n        }\n      }\n    }\n    return output;\n  }\n\n  /* oxlint-disable typescript/no-explicit-any */\n  /**\n   * @method cleanupUnnecessaryItems\n   * @param {any} d\n   * @description Removes unnecessary items from the response.\n   */\n  private cleanupUnnecessaryItems(d: any): any {\n    if (Array.isArray(d)) {\n      return d.map((item) => this.cleanupUnnecessaryItems(item));\n    }\n\n    const toRemove = [\"xpath\", \"position\", \"rectangle\"];\n    if (typeof d === \"object\" && d !== null) {\n      return Object.keys(d).reduce((newObj: any, key: string) => {\n        if (\n          (this.jsonResultFields === undefined ||\n            this.jsonResultFields.includes(key)) &&\n          !toRemove.includes(key)\n        ) {\n          if (typeof d[key] === \"object\" && d[key] !== null) {\n            newObj[key] = this.cleanupUnnecessaryItems(d[key]);\n          } else {\n            newObj[key] = d[key];\n          }\n        }\n        return newObj;\n      }, {});\n    }\n\n    return d;\n  }\n\n  /**\n   * @method processResponse\n   * @param {ApiResponse} res\n   * @returns {string}\n   * @description Processes the response to extract meaningful data.\n   */\n  protected processResponse(res: ApiResponse): string {\n    let returnValue = \"No good search result found\";\n    for (const task of res.tasks || []) {\n      for (const result of task.result || []) {\n        const { item_types } = result;\n        const items = result.items || [];\n        if (item_types.includes(\"answer_box\")) {\n          returnValue = items.find(\n            (item: { type: string; text: string }) => item.type === \"answer_box\"\n          ).text;\n        } else if (item_types.includes(\"knowledge_graph\")) {\n          returnValue = items.find(\n            (item: { type: string; description: string }) =>\n              item.type === \"knowledge_graph\"\n          ).description;\n        } else if (item_types.includes(\"featured_snippet\")) {\n          returnValue = items.find(\n            (item: { type: string; description: string }) =>\n              item.type === \"featured_snippet\"\n          ).description;\n        } else if (item_types.includes(\"shopping\")) {\n          returnValue = items.find(\n            (item: { type: string; price: string }) => item.type === \"shopping\"\n          ).price;\n        } else if (item_types.includes(\"organic\")) {\n          returnValue = items.find(\n            (item: { type: string; description: string }) =>\n              item.type === \"organic\"\n          ).description;\n        }\n        if (returnValue) {\n          break;\n        }\n      }\n    }\n    return returnValue;\n  }\n}\n"],"mappings":"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"}