From 209607764d4b8e4cfca9e83f264e06ce0c1a4e5f Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Mon, 18 Nov 2024 22:50:16 +0800 Subject: [PATCH] Proxy support (experimental) (#153) Support using a HTTP(s) proxy. Feature is experimental, and may not be working for all users currently. Technical side: Defined a custom `fetch` function that completely replaces the previous one. All requests now go through this (with the exception of curl), which allows us to apply the HTTP agent. --- package-lock.json | 7 +--- package.json | 11 ++++- src/api/Providers/ai4chat.js | 2 +- src/api/Providers/blackbox.js | 9 ++-- src/api/Providers/darkai.js | 2 +- src/api/Providers/deepinfra.js | 2 +- src/api/Providers/deprecated/ecosia.jsx | 2 +- src/api/Providers/deprecated/sambanova.jsx | 2 +- .../dev/blackbox/blackbox_engineer.js | 2 +- src/api/Providers/duckduckgo.js | 2 +- src/api/Providers/google_gemini.js | 2 +- src/api/Providers/metaAI.js | 2 +- src/api/Providers/mhystical.js | 2 +- src/api/Providers/nexra.js | 2 +- src/api/Providers/pizzagpt.js | 2 +- src/api/Providers/replicate.js | 2 +- src/api/Providers/rocks.js | 2 +- src/api/Providers/special/custom_openai.jsx | 2 +- src/api/Providers/special/g4f_local.jsx | 2 +- src/api/Providers/special/ollama_local.jsx | 2 +- src/api/fetch.js | 41 +++++++++++++++++++ src/genImage.jsx | 2 +- src/helpers/update.jsx | 2 +- 23 files changed, 78 insertions(+), 28 deletions(-) create mode 100644 src/api/fetch.js diff --git a/package-lock.json b/package-lock.json index 132c30e..91e04ef 100755 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "fetch-to-curl": "^0.6.0", "g4f-image": "^1.3.3", "gemini-g4f": "^12.3.10-beta.1", + "http-proxy-agent": "^7.0.2", "lodash.throttle": "^4.1.1", "node-fetch": "^3.3.2", "untruncate-json": "^0.0.1" @@ -603,7 +604,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dev": true, "dependencies": { "debug": "^4.3.4" }, @@ -976,7 +976,6 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, "dependencies": { "ms": "^2.1.3" }, @@ -1730,7 +1729,6 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" @@ -2052,8 +2050,7 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/natural-compare": { "version": "1.4.0", diff --git a/package.json b/package.json index 0ad3233..2ebd3e5 100755 --- a/package.json +++ b/package.json @@ -467,8 +467,16 @@ "default": "off" }, { - "name": "smartChatNaming", + "name": "proxyURL", + "title": "Proxy URL (Experimental)", + "description": "Optional. HTTP(s) proxy URL to use for requests.", + "required": false, + "type": "textfield", + "default": "" + }, + { "title": "Features", + "name": "smartChatNaming", "label": "Enable Smart Chat Naming", "description": "Automatically rename chat sessions based on the messages you send.", "required": false, @@ -813,6 +821,7 @@ "fetch-to-curl": "^0.6.0", "g4f-image": "^1.3.3", "gemini-g4f": "^12.3.10-beta.1", + "http-proxy-agent": "^7.0.2", "lodash.throttle": "^4.1.1", "node-fetch": "^3.3.2", "untruncate-json": "^0.0.1" diff --git a/src/api/Providers/ai4chat.js b/src/api/Providers/ai4chat.js index dcb412c..9b0fb82 100644 --- a/src/api/Providers/ai4chat.js +++ b/src/api/Providers/ai4chat.js @@ -1,4 +1,4 @@ -import fetch from "node-fetch"; +import fetch from "#root/src/api/fetch.js"; import { format_chat_to_prompt } from "../../classes/message.js"; diff --git a/src/api/Providers/blackbox.js b/src/api/Providers/blackbox.js index e0d8510..ebfa79d 100644 --- a/src/api/Providers/blackbox.js +++ b/src/api/Providers/blackbox.js @@ -1,4 +1,4 @@ -import fetch from "node-fetch"; +import fetch from "#root/src/api/fetch.js"; import { format_chat_to_prompt } from "../../classes/message.js"; import { randomBytes, randomUUID } from "crypto"; import { Storage } from "../storage.js"; @@ -133,8 +133,11 @@ export const BlackboxProvider = { // Update 7/11/24: if not validated properly, blackbox now returns an advertisement instead // of the actual response. if we detect such a message, we attempt to revalidate the token. - if (max_retries > 0 && (chunk.includes("BLACKBOX.AI") || chunk.includes("https://www.blackbox.ai"))) { - await initValidatedToken({ forceUpdate: true }); + if (chunk.includes("BLACKBOX.AI") || chunk.includes("https://www.blackbox.ai")) { + if (max_retries > 0 && max_retries <= 3) { + // only retry once + await initValidatedToken({ forceUpdate: true }); + } yield* this.generate(chat, options, { max_retries: max_retries - 3 }); return; } diff --git a/src/api/Providers/darkai.js b/src/api/Providers/darkai.js index 5b3644a..6b71620 100644 --- a/src/api/Providers/darkai.js +++ b/src/api/Providers/darkai.js @@ -1,4 +1,4 @@ -import fetch from "node-fetch"; +import fetch from "#root/src/api/fetch.js"; import { format_chat_to_prompt } from "../../classes/message.js"; diff --git a/src/api/Providers/deepinfra.js b/src/api/Providers/deepinfra.js index f7e18cf..dc2be81 100644 --- a/src/api/Providers/deepinfra.js +++ b/src/api/Providers/deepinfra.js @@ -1,4 +1,4 @@ -import fetch from "node-fetch"; +import fetch from "#root/src/api/fetch.js"; import fs from "fs"; import { messages_to_json } from "../../classes/message.js"; diff --git a/src/api/Providers/deprecated/ecosia.jsx b/src/api/Providers/deprecated/ecosia.jsx index d02aee8..d68cbc9 100644 --- a/src/api/Providers/deprecated/ecosia.jsx +++ b/src/api/Providers/deprecated/ecosia.jsx @@ -1,5 +1,5 @@ export const EcosiaProvider = "EcosiaProvider"; -import fetch from "node-fetch"; +import fetch from "#root/src/api/fetch.js"; import { messages_to_json } from "../../../classes/message.js"; const api_url = "https://api.ecosia.org/v2/chat/?sp=productivity"; diff --git a/src/api/Providers/deprecated/sambanova.jsx b/src/api/Providers/deprecated/sambanova.jsx index aec290d..2082e46 100644 --- a/src/api/Providers/deprecated/sambanova.jsx +++ b/src/api/Providers/deprecated/sambanova.jsx @@ -1,4 +1,4 @@ -import fetch from "node-fetch"; +import fetch from "#root/src/api/fetch.js"; import { messages_to_json } from "../../../classes/message.js"; const api_url = "https://cloud.sambanova.ai/api/completion"; diff --git a/src/api/Providers/dev/blackbox/blackbox_engineer.js b/src/api/Providers/dev/blackbox/blackbox_engineer.js index 937ebe7..ffcd23c 100644 --- a/src/api/Providers/dev/blackbox/blackbox_engineer.js +++ b/src/api/Providers/dev/blackbox/blackbox_engineer.js @@ -1,4 +1,4 @@ -import fetch from "node-fetch"; +import fetch from "#root/src/api/fetch.js"; let puppeteer; diff --git a/src/api/Providers/duckduckgo.js b/src/api/Providers/duckduckgo.js index 683ceb1..819db72 100644 --- a/src/api/Providers/duckduckgo.js +++ b/src/api/Providers/duckduckgo.js @@ -1,4 +1,4 @@ -import fetch from "node-fetch"; +import fetch from "#root/src/api/fetch.js"; import { format_chat_to_prompt } from "../../classes/message.js"; import { sleep } from "../../helpers/helper.js"; diff --git a/src/api/Providers/google_gemini.js b/src/api/Providers/google_gemini.js index aa1850c..6706eaa 100644 --- a/src/api/Providers/google_gemini.js +++ b/src/api/Providers/google_gemini.js @@ -1,5 +1,5 @@ import Gemini, { messageToParts } from "gemini-g4f"; -import fetch from "node-fetch"; +import fetch from "#root/src/api/fetch.js"; import fs from "fs"; import { Preferences } from "../preferences.js"; diff --git a/src/api/Providers/metaAI.js b/src/api/Providers/metaAI.js index fec296c..6061027 100644 --- a/src/api/Providers/metaAI.js +++ b/src/api/Providers/metaAI.js @@ -1,4 +1,4 @@ -import fetch from "node-fetch"; +import fetch from "#root/src/api/fetch.js"; import { randomUUID } from "crypto"; import { format_chat_to_prompt } from "../../classes/message.js"; import { sleep } from "../../helpers/helper.js"; diff --git a/src/api/Providers/mhystical.js b/src/api/Providers/mhystical.js index c03a8b9..e290b41 100644 --- a/src/api/Providers/mhystical.js +++ b/src/api/Providers/mhystical.js @@ -1,4 +1,4 @@ -import fetch from "node-fetch"; +import fetch from "#root/src/api/fetch.js"; import { messages_to_json } from "#root/src/classes/message.js"; const api_url = "https://api.mhystical.cc/v1/completions"; diff --git a/src/api/Providers/nexra.js b/src/api/Providers/nexra.js index c0a933b..e126008 100644 --- a/src/api/Providers/nexra.js +++ b/src/api/Providers/nexra.js @@ -1,4 +1,4 @@ -import fetch from "node-fetch"; +import fetch from "#root/src/api/fetch.js"; import { format_chat_to_prompt, messages_to_json } from "../../classes/message.js"; import { sleep } from "#root/src/helpers/helper.js"; diff --git a/src/api/Providers/pizzagpt.js b/src/api/Providers/pizzagpt.js index ca5bb6a..68dbd3b 100644 --- a/src/api/Providers/pizzagpt.js +++ b/src/api/Providers/pizzagpt.js @@ -1,4 +1,4 @@ -import fetch from "node-fetch"; +import fetch from "#root/src/api/fetch.js"; import { format_chat_to_prompt } from "../../classes/message.js"; const url = "https://www.pizzagpt.it"; diff --git a/src/api/Providers/replicate.js b/src/api/Providers/replicate.js index 3320830..38f1081 100644 --- a/src/api/Providers/replicate.js +++ b/src/api/Providers/replicate.js @@ -1,4 +1,4 @@ -import fetch from "node-fetch"; +import fetch from "#root/src/api/fetch.js"; import { format_chat_to_prompt } from "../../classes/message.js"; // Implementation ported from gpt4free Replicate provider. diff --git a/src/api/Providers/rocks.js b/src/api/Providers/rocks.js index 5e379a4..795c89a 100644 --- a/src/api/Providers/rocks.js +++ b/src/api/Providers/rocks.js @@ -1,4 +1,4 @@ -import fetch from "node-fetch"; +import fetch from "#root/src/api/fetch.js"; const api_url = "https://api.airforce/chat/completions"; diff --git a/src/api/Providers/special/custom_openai.jsx b/src/api/Providers/special/custom_openai.jsx index b69818e..a336b3d 100644 --- a/src/api/Providers/special/custom_openai.jsx +++ b/src/api/Providers/special/custom_openai.jsx @@ -1,7 +1,7 @@ import { getCustomAPIInfo } from "./g4f_local"; import { messages_to_json } from "../../../classes/message.js"; -import fetch from "node-fetch"; +import fetch from "#root/src/api/fetch.js"; const DEFAULT_API_URL = "https://api.openai.com/v1/chat/completions"; const DEFAULT_MODEL = "gpt-4"; diff --git a/src/api/Providers/special/g4f_local.jsx b/src/api/Providers/special/g4f_local.jsx index 4cb56d4..071f5d2 100644 --- a/src/api/Providers/special/g4f_local.jsx +++ b/src/api/Providers/special/g4f_local.jsx @@ -2,7 +2,7 @@ // Read more here: https://github.com/xtekky/gpt4free/blob/main/docs/interference.md import { exec } from "child_process"; -import fetch from "node-fetch"; +import fetch from "#root/src/api/fetch.js"; import { Storage } from "../../storage.js"; import { messages_to_json } from "../../../classes/message.js"; diff --git a/src/api/Providers/special/ollama_local.jsx b/src/api/Providers/special/ollama_local.jsx index 7c112da..5964b84 100644 --- a/src/api/Providers/special/ollama_local.jsx +++ b/src/api/Providers/special/ollama_local.jsx @@ -1,7 +1,7 @@ // This module allows communication and requests to the local Ollama API. // Read more here: https://github.com/ollama/ollama/blob/main/docs/api.md -import fetch from "node-fetch"; +import fetch from "#root/src/api/fetch.js"; import { Storage } from "../../storage.js"; import { messages_to_json } from "../../../classes/message.js"; diff --git a/src/api/fetch.js b/src/api/fetch.js new file mode 100644 index 0000000..5df59cd --- /dev/null +++ b/src/api/fetch.js @@ -0,0 +1,41 @@ +/// This is the custom fetch function used in this extension. +/// It is a wrapper around the node-fetch library, with added support for proxies. + +import default_fetch from "node-fetch"; +import { HttpProxyAgent } from "http-proxy-agent"; +import { Preferences } from "./preferences.js"; + +const devMode = Preferences["devMode"] || false; + +/** + * Custom fetch function with optional proxy support + * @param {string} url - The URL to fetch + * @param {Object} [options={}] - Fetch options + * @param {string|null} [proxy=null] - Proxy URL + * @returns {Promise} - Fetch response + */ +async function fetchWithProxy(url, options = {}, proxy = null) { + if (proxy) { + options.agent = new HttpProxyAgent(proxy); + } + if (devMode) { + console.log(`${options.method || "GET"} : ${url}, proxy: ${proxy}`); + } + + const response = await default_fetch(url, options); + if (devMode) { + console.log(`response:\n ${response}, status: ${response.status} : ${response.statusText}`); + if (!response.ok) { + console.log(`error: ${await response.text()}`); + } + } + + return response; +} + +async function fetch(url, options = {}) { + let proxy = Preferences["proxyURL"] || null; // load proxy from preferences + return fetchWithProxy(url, options, proxy); +} + +export default fetch; diff --git a/src/genImage.jsx b/src/genImage.jsx index 04e8650..22b3d58 100644 --- a/src/genImage.jsx +++ b/src/genImage.jsx @@ -13,7 +13,7 @@ import { } from "@raycast/api"; import { useEffect, useState } from "react"; import fs from "fs"; -import fetch from "node-fetch"; +import fetch from "#root/src/api/fetch.js"; import { Storage } from "./api/storage.js"; import { Preferences } from "./api/preferences.js"; diff --git a/src/helpers/update.jsx b/src/helpers/update.jsx index acd5fc6..06b9baa 100644 --- a/src/helpers/update.jsx +++ b/src/helpers/update.jsx @@ -1,6 +1,6 @@ import { version } from "../../package.json"; -import fetch from "node-fetch"; +import fetch from "#root/src/api/fetch.js"; import { exec } from "child_process"; import { popToRoot, showToast, Toast, confirmAlert, Icon } from "@raycast/api";