├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── src ├── bin │ └── gemini.rs └── lib.rs └── worker ├── Cargo.toml ├── README.md ├── package.json ├── src └── lib.rs └── wrangler.toml /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | 17 | # Added by cargo 18 | 19 | target 20 | worker/target 21 | worker/node_modules 22 | ``` -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gemini" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [features] 9 | 10 | [[bin]] 11 | name = "gemini" 12 | path = "src/bin/gemini.rs" 13 | 14 | [profile.release] 15 | lto = true 16 | strip = true 17 | codegen-units = 1 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 gngpp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gemini 2 | Google Gemini API proxy 3 | 4 | ### Deploy 5 | 6 | - `Cloudflare Worker` 7 | 8 | ```shell 9 | # Clone 10 | git clone https://github.com/gemini.git && cd gemini 11 | 12 | # Deploy to cloudflare worker 13 | cd worker 14 | npm install wrangler --save-dev 15 | npx wrangler publish 16 | ``` 17 | -------------------------------------------------------------------------------- /src/bin/gemini.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "worker")] 2 | pub mod worker; 3 | 4 | /// Example 5 | pub fn add(a: i32, b: i32) -> i32 { 6 | a + b 7 | } 8 | -------------------------------------------------------------------------------- /worker/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gemini" 3 | version = "0.1.0" 4 | edition = "2021" 5 | repository = "https://github.com/gngpp/gemini" 6 | 7 | # https://github.com/rustwasm/wasm-pack/issues/1247 8 | [package.metadata.wasm-pack.profile.release] 9 | wasm-opt = false 10 | 11 | [lib] 12 | crate-type = ["cdylib"] 13 | 14 | [dependencies] 15 | reqwest = { version = "0.11.23", features = ["stream"] } 16 | tokio = { version = "1.35.1", default-features=false, features=['io-util', "sync", 'macros'] } 17 | console_error_panic_hook = { version = "0.1.1" } 18 | wasm-bindgen-futures = { version = "0.4.24" } 19 | worker = { version = "0.0.18" } 20 | async-stream = { version = "0.3.5" } 21 | futures-util = { version = "0.3.30" } 22 | 23 | [profile.release] 24 | opt-level = "s" 25 | lto = true 26 | strip = true 27 | codegen-units = 1 28 | -------------------------------------------------------------------------------- /worker/README.md: -------------------------------------------------------------------------------- 1 | # Template: worker-rust 2 | 3 | [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/templates/tree/main/worker-rust) 4 | 5 | A template for kick starting a Cloudflare worker project using [`workers-rs`](https://github.com/cloudflare/workers-rs). 6 | 7 | This template is designed for compiling Rust to WebAssembly and publishing the resulting worker to Cloudflare's [edge infrastructure](https://www.cloudflare.com/network/). 8 | 9 | ## Setup 10 | 11 | To create a `my-project` directory using this template, run: 12 | 13 | ```sh 14 | $ npx wrangler generate my-project https://github.com/cloudflare/workers-sdk/templates/experimental/worker-rust 15 | # or 16 | $ yarn wrangler generate my-project https://github.com/cloudflare/workers-sdk/templates/experimental/worker-rust 17 | # or 18 | $ pnpm wrangler generate my-project https://github.com/cloudflare/workers-sdk/templates/experimental/worker-rust 19 | ``` 20 | 21 | ## Wrangler 22 | 23 | Wrangler is used to develop, deploy, and configure your Worker via CLI. 24 | 25 | Further documentation for Wrangler can be found [here](https://developers.cloudflare.com/workers/tooling/wrangler). 26 | 27 | ## Usage 28 | 29 | This template starts you off with a `src/lib.rs` file, acting as an entrypoint for requests hitting your Worker. Feel free to add more code in this file, or create Rust modules anywhere else for this project to use. 30 | 31 | With `wrangler`, you can build, test, and deploy your Worker with the following commands: 32 | 33 | ```sh 34 | # run your Worker in an ideal development workflow (with a local server, file watcher & more) 35 | $ npm run dev 36 | 37 | # deploy your Worker globally to the Cloudflare network (update your wrangler.toml file for configuration) 38 | $ npm run deploy 39 | ``` 40 | 41 | Read the latest `worker` crate documentation here: https://docs.rs/worker 42 | 43 | ## Advanced Example 44 | 45 | As this template comprises only the essential setup, we recommend considering our advanced example to leverage its additional functionalities. The advanced example showcases the creation of multiple routes, logging of requests, retrieval of field data from a form, and other features that may prove useful to your project. 46 | The following example has been taken from: [workers-rs](https://github.com/cloudflare/workers-rs). You can learn more about how to use workers with rust by going there. 47 | 48 | ```rust 49 | use worker::*; 50 | 51 | #[event(fetch)] 52 | pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result { 53 | console_log!( 54 | "{} {}, located at: {:?}, within: {}", 55 | req.method().to_string(), 56 | req.path(), 57 | req.cf().coordinates().unwrap_or_default(), 58 | req.cf().region().unwrap_or("unknown region".into()) 59 | ); 60 | 61 | if !matches!(req.method(), Method::Post) { 62 | return Response::error("Method Not Allowed", 405); 63 | } 64 | 65 | if let Some(file) = req.form_data().await?.get("file") { 66 | return match file { 67 | FormEntry::File(buf) => { 68 | Response::ok(&format!("size = {}", buf.bytes().await?.len())) 69 | } 70 | _ => Response::error("`file` part of POST form must be a file", 400), 71 | }; 72 | } 73 | 74 | Response::error("Bad Request", 400) 75 | } 76 | ``` 77 | 78 | ## WebAssembly 79 | 80 | `workers-rs` (the Rust SDK for Cloudflare Workers used in this template) is meant to be executed as compiled WebAssembly, and as such so **must** all the code you write and depend upon. All crates and modules used in Rust-based Workers projects have to compile to the `wasm32-unknown-unknown` triple. 81 | 82 | Read more about this on the [`workers-rs`](https://github.com/cloudflare/workers-rs) project README. 83 | 84 | ## Issues 85 | 86 | If you have any problems with the `worker` crate, please open an issue on the upstream project issue tracker on the [`workers-rs` repository](https://github.com/cloudflare/workers-rs). 87 | -------------------------------------------------------------------------------- /worker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gemini-worker-rust", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "deploy": "wrangler deploy", 7 | "dev": "wrangler dev --local" 8 | }, 9 | "devDependencies": { 10 | "miniflare": "^3.20231030.4", 11 | "wrangler": "^2.20.2" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /worker/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use console_error_panic_hook::set_once as set_panic_hook; 4 | use futures_util::StreamExt; 5 | use reqwest::{ 6 | header::{HeaderMap, HeaderName, HeaderValue}, 7 | Client, 8 | }; 9 | use tokio::sync::OnceCell; 10 | use worker::*; 11 | 12 | static CLIENT: OnceCell = OnceCell::const_new(); 13 | 14 | async fn get_client() -> Client { 15 | CLIENT 16 | .get_or_init(|| async { Client::builder().build().expect("Failed to create client") }) 17 | .await 18 | .clone() 19 | } 20 | 21 | #[event(fetch)] 22 | async fn main(mut req: Request, _env: Env, _ctx: Context) -> worker::Result { 23 | set_panic_hook(); 24 | 25 | // Replace host with the backend host 26 | let mut url = req.url()?; 27 | url.set_host(Some("generativelanguage.googleapis.com"))?; 28 | url.set_scheme("https") 29 | .map_err(|_| worker::Error::RustError(format!("Failed to set scheme")))?; 30 | 31 | // Convert method 32 | let method = match req.method() { 33 | Method::Get => reqwest::Method::GET, 34 | Method::Post => reqwest::Method::POST, 35 | Method::Put => reqwest::Method::PUT, 36 | Method::Delete => reqwest::Method::DELETE, 37 | Method::Head => reqwest::Method::HEAD, 38 | Method::Connect => reqwest::Method::CONNECT, 39 | Method::Options => reqwest::Method::OPTIONS, 40 | Method::Trace => reqwest::Method::TRACE, 41 | Method::Patch => reqwest::Method::PATCH, 42 | }; 43 | 44 | // Convert headers 45 | let mut headers = HeaderMap::new(); 46 | for (k, v) in req.headers().into_iter() { 47 | headers.insert( 48 | HeaderName::from_str(k.as_str()) 49 | .map_err(|_| worker::Error::RustError(format!("Failed to parse header name")))?, 50 | HeaderValue::from_str(v.as_str()) 51 | .map_err(|_| worker::Error::RustError(format!("Failed to parse header value")))?, 52 | ); 53 | } 54 | 55 | // Send request 56 | let resp = get_client() 57 | .await 58 | .request(method, url) 59 | .headers(headers) 60 | .body(req.bytes().await?) 61 | .send() 62 | .await 63 | .map_err(|_| worker::Error::RustError(format!("Failed to send request")))?; 64 | 65 | // Convert response 66 | let status = resp.status().as_u16(); 67 | let mut worker_headers = worker::Headers::new(); 68 | resp.headers().iter().for_each(|(k, v)| { 69 | if let Ok(value) = v.to_str() { 70 | let _ = worker_headers.append(k.as_str(), value); 71 | } 72 | }); 73 | 74 | // Convert stream body 75 | let stream = async_stream::stream! { 76 | let mut bytes_streams = resp.bytes_stream(); 77 | while let Some(s) = bytes_streams.next().await { 78 | yield s.map_err(|_| worker::Error::RustError(format!("Failed to read response"))); 79 | } 80 | }; 81 | 82 | // Return response 83 | let response = Response::from_stream(stream)? 84 | .with_status(status) 85 | .with_headers(worker_headers); 86 | Ok(response) 87 | } 88 | -------------------------------------------------------------------------------- /worker/wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "gemini" 2 | main = "build/worker/shim.mjs" 3 | compatibility_date = "2023-03-22" 4 | 5 | [build] 6 | command = "cargo install -q worker-build && worker-build --release" 7 | --------------------------------------------------------------------------------