commit 30bc85994863eef19210bf2d8e8d9132d03763a8
Author: cloudflare[bot] <>
Date: Mon Jan 12 05:55:32 2026 +0000
source repo import
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d1f5f56
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+# Local Netlify folder
+.netlify
+.wrangler
+node_modules/
+.vercel
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..516a6b3
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025 Tech Shrimp(技术爬爬虾)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..38c44e4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,186 @@
+# Gemini Balance Lite
+# Gemini API 代理和负载均衡无服务器轻量版(边缘函数)
+
+### 作者:技术爬爬虾
+[B站](https://space.bilibili.com/316183842),[Youtube](https://www.youtube.com/@Tech_Shrimp),抖音,公众号 全网同名。转载请注明作者。
+
+
+## 项目简介
+
+Gemini API 代理, 使用边缘函数把Gemini API免费中转到国内。还可以聚合多个Gemini API Key,随机选取API Key的使用实现负载均衡,使得Gemini API免费成倍增加。
+
+## Vercel部署(推荐)
+[](https://vercel.com/new/clone?repository-url=https://github.com/tech-shrimp/gemini-balance-lite)
+
+
+1. 点击部署按钮⬆️一键部署。
+2. 国内使用需要配置自定义域名
+
+ 配置自定义域名:
+
+ 
+
+3. 去[AIStudio](https://aistudio.google.com)申请一个免费Gemini API Key
+
将API Key与自定义的域名填入AI客户端即可使用,如果有多个API Key用逗号分隔
+
+ 以Cherry Studio为例:
+
+ 
+
+
+
+
+
+## Deno部署
+
+1. [fork](https://github.com/tech-shrimp/gemini-balance-lite/fork)本项目
+2. 登录/注册 https://dash.deno.com/
+3. 创建项目 https://dash.deno.com/new_project
+4. 选择此项目,填写项目名字(请仔细填写项目名字,关系到自动分配的域名)
+5. Entrypoint 填写 `src/deno_index.ts` 其他字段留空
+
+ 如图
+
+ 
+
+6. 点击 Deploy Project
+7. 部署成功后获得域名
+8. 国内使用需要配置自定义域名
+9. 去[AIStudio](https://aistudio.google.com)申请一个免费Gemini API Key
+10. 将API Key与分配的域名填入AI客户端即可使用,如果有多个API Key用逗号分隔
+
+
+以Cherry Studio为例:
+
+
+
+
+
+## Cloudflare Worker 部署
+[](https://deploy.workers.cloudflare.com/?url=https://github.com/tech-shrimp/gemini-balance-lite)
+
+0. CF Worker有可能会分配香港的CDN节点导致无法使用(Gemini不允许香港IP连接)
+0. 广东地区不建议使用Cloudflare Worker 部署
+1. 点击部署按钮
+2. 登录Cloudflare账号
+3. 链接Github账户,部署
+4. 打开dash.cloudflare.com,查看部署后的worker
+6. 国内使用需要配置自定义域名
+
+配置自定义域名:
+
+
+
+
+
+## Netlify部署
+[](https://app.netlify.com/start/deploy?repository=https://github.com/tech-shrimp/gemini-balance-lite)
+
点击部署按钮,登录Github账户即可
+
免费分配域名,国内可直连。
+
但是不稳定
+
+
+将分配的域名复制下来,如图:
+
+
+
+
+去[AIStudio](https://aistudio.google.com)申请一个免费Gemini API Key
+
将API Key与分配的域名填入AI客户端即可使用,如果有多个API Key用逗号分隔
+
+
+以Cherry Studio为例:
+
+
+
+
+
+
+## 打赏
+#### 帮忙点点关注点点赞,谢谢啦~
+B站:[https://space.bilibili.com/316183842](https://space.bilibili.com/316183842)
+Youtube: [https://www.youtube.com/@Tech_Shrimp](https://www.youtube.com/@Tech_Shrimp)
+
+
+## 本地调试
+
+1. 安装NodeJs
+2. npm install -g vercel
+3. cd 项目根目录
+4. vercel dev
+
+## API 说明
+
+
+### Gemini 代理
+
+可以使用 Gemini 的原生 API 格式进行代理请求。
+**Curl 示例:**
+```bash
+curl -X POST --location 'https:///v1beta/models/gemini-2.5-pro:generateContent' \
+--header 'Content-Type: application/json' \
+--header 'x-goog-api-key: ,' \
+--data '{
+ "contents": [
+ {
+ "role": "user",
+ "parts": [
+ {
+ "text": "Hello"
+ }
+ ]
+ }
+ ]
+}'
+```
+**Curl 示例:(流式)**
+```bash
+curl -X POST --location 'https:///v1beta/models/gemini-2.5-pro:generateContent?alt=sse' \
+--header 'Content-Type: application/json' \
+--header 'x-goog-api-key: ,' \
+--data '{
+ "contents": [
+ {
+ "role": "user",
+ "parts": [
+ {
+ "text": "Hello"
+ }
+ ]
+ }
+ ]
+}'
+```
+> 注意: 请将 `` 替换为你的部署域名,并将 `` 替换为你的 Gemini API Ke,如果有多个用逗号分隔
+
+
+### API Key 校验
+
+可以通过向 `/verify` 端点发送请求来校验你的 API Key 是否有效。可以一次性校验多个 Key,用逗号隔开。
+
+**Curl 示例:**
+```bash
+curl -X POST --location 'https:///verify' \
+--header 'x-goog-api-key: ,'
+```
+
+### OpenAI 格式
+
+本项目兼容 OpenAI 的 API 格式,你可以通过 `/chat` 或 `/chat/completions` 端点来发送请求。
+
+**Curl 示例:**
+```bash
+curl -X POST --location 'https:///chat/completions' \
+--header 'Content-Type: application/json' \
+--header 'Authorization: Bearer ' \
+--data '{
+ "model": "gpt-3.5-turbo",
+ "messages": [
+ {
+ "role": "user",
+ "content": "你好"
+ }
+ ]
+}'
+```
+
diff --git a/api/vercel_index.js b/api/vercel_index.js
new file mode 100644
index 0000000..454c8f9
--- /dev/null
+++ b/api/vercel_index.js
@@ -0,0 +1,9 @@
+import { handleRequest } from "../src/handle_request.js";
+
+export const config = {
+ runtime: 'edge' //告诉 Vercel 这是 Edge Function
+};
+
+export default async function handler(req) {
+ return handleRequest(req);
+}
\ No newline at end of file
diff --git a/deno.lock b/deno.lock
new file mode 100644
index 0000000..ad6dd16
--- /dev/null
+++ b/deno.lock
@@ -0,0 +1,18 @@
+{
+ "version": "4",
+ "remote": {
+ "https://edge.netlify.com/": "fd941d61d88673d5f28aab283fb86fcc50f08a3bc80ee5470498fcfa88c65cfb",
+ "https://edge.netlify.com/bootstrap/config.ts": "19dcaa5c4480175295c4dffc9be11f7c280d9391b851d30409f3e6904c34a161",
+ "https://edge.netlify.com/bootstrap/context.ts": "c6e9de479234f3ac500d2fc1544dadf1925e335d0d5551b1a71b7ce14423a683",
+ "https://edge.netlify.com/bootstrap/cookie.ts": "8b0baae708989ca183c6f3b4ab3d029e6abcbc2e43f93edeb0ff447b3bbc3a05",
+ "https://edge.netlify.com/bootstrap/edge_function.ts": "b8253e86aa83c67341f5cfedeba5049d77fbf84dcab7eceff7566b7728ae9b39",
+ "https://edge.netlify.com/bootstrap/globals/types.ts": "eaa6148ded3121d8dee62dd91c86e7fe76601df0f3ca8d7962243a30f4c8935f"
+ },
+ "workspace": {
+ "packageJson": {
+ "dependencies": [
+ "npm:wrangler@^4.23.0"
+ ]
+ }
+ }
+}
diff --git a/docs/images/1.png b/docs/images/1.png
new file mode 100644
index 0000000..8c9cd5a
Binary files /dev/null and b/docs/images/1.png differ
diff --git a/docs/images/2.png b/docs/images/2.png
new file mode 100644
index 0000000..42c4b30
Binary files /dev/null and b/docs/images/2.png differ
diff --git a/docs/images/3.png b/docs/images/3.png
new file mode 100644
index 0000000..2a39693
Binary files /dev/null and b/docs/images/3.png differ
diff --git a/docs/images/4.png b/docs/images/4.png
new file mode 100644
index 0000000..3442841
Binary files /dev/null and b/docs/images/4.png differ
diff --git a/docs/images/5.png b/docs/images/5.png
new file mode 100644
index 0000000..ea9a4d7
Binary files /dev/null and b/docs/images/5.png differ
diff --git a/netlify.toml b/netlify.toml
new file mode 100644
index 0000000..1a3e095
--- /dev/null
+++ b/netlify.toml
@@ -0,0 +1,4 @@
+[[redirects]]
+ from = "/"
+ to = "/.netlify/functions/api"
+ status = 200
\ No newline at end of file
diff --git a/netlify/functions/api.js b/netlify/functions/api.js
new file mode 100644
index 0000000..a30b9f9
--- /dev/null
+++ b/netlify/functions/api.js
@@ -0,0 +1,5 @@
+import { handleRequest } from "../../src/handle_request.js";
+
+export default async (req, context) => {
+ return handleRequest(req);
+};
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..c431a10
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1528 @@
+{
+ "name": "gemini-balance-lite",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "devDependencies": {
+ "wrangler": "^4.23.0"
+ }
+ },
+ "node_modules/@cloudflare/kv-asset-handler": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.0.tgz",
+ "integrity": "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==",
+ "dev": true,
+ "license": "MIT OR Apache-2.0",
+ "dependencies": {
+ "mime": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@cloudflare/unenv-preset": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.3.3.tgz",
+ "integrity": "sha512-/M3MEcj3V2WHIRSW1eAQBPRJ6JnGQHc6JKMAPLkDb7pLs3m6X9ES/+K3ceGqxI6TKeF32AWAi7ls0AYzVxCP0A==",
+ "dev": true,
+ "license": "MIT OR Apache-2.0",
+ "peerDependencies": {
+ "unenv": "2.0.0-rc.17",
+ "workerd": "^1.20250508.0"
+ },
+ "peerDependenciesMeta": {
+ "workerd": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@cloudflare/workerd-darwin-64": {
+ "version": "1.20250617.0",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250617.0.tgz",
+ "integrity": "sha512-toG8JUKVLIks4oOJLe9FeuixE84pDpMZ32ip7mCpE7JaFc5BqGFvevk0YC/db3T71AQlialjRwioH3jS/dzItA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cloudflare/workerd-darwin-arm64": {
+ "version": "1.20250617.0",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250617.0.tgz",
+ "integrity": "sha512-JTX0exbC9/ZtMmQQA8tDZEZFMXZrxOpTUj2hHnsUkErWYkr5SSZH04RBhPg6dU4VL8bXuB5/eJAh7+P9cZAp7g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cloudflare/workerd-linux-64": {
+ "version": "1.20250617.0",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250617.0.tgz",
+ "integrity": "sha512-8jkSoVRJ+1bOx3tuWlZCGaGCV2ew7/jFMl6V3CPXOoEtERUHsZBQLVkQIGKcmC/LKSj7f/mpyBUeu2EPTo2HEg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cloudflare/workerd-linux-arm64": {
+ "version": "1.20250617.0",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250617.0.tgz",
+ "integrity": "sha512-YAzcOyu897z5dQKFzme1oujGWMGEJCR7/Wrrm1nSP6dqutxFPTubRADM8BHn2CV3ij//vaPnAeLmZE3jVwOwig==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cloudflare/workerd-windows-64": {
+ "version": "1.20250617.0",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250617.0.tgz",
+ "integrity": "sha512-XWM/6sagDrO0CYDKhXhPjM23qusvIN1ju9ZEml6gOQs8tNOFnq6Cn6X9FAmnyapRFCGUSEC3HZYJAm7zwVKaMA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cspotcode/source-map-support": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "0.3.9"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz",
+ "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz",
+ "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz",
+ "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz",
+ "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz",
+ "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz",
+ "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz",
+ "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz",
+ "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz",
+ "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz",
+ "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz",
+ "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz",
+ "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz",
+ "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz",
+ "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz",
+ "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz",
+ "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz",
+ "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz",
+ "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz",
+ "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz",
+ "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz",
+ "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz",
+ "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz",
+ "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz",
+ "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz",
+ "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz",
+ "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@fastify/busboy": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
+ "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
+ "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
+ "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
+ "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
+ "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
+ "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
+ "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
+ "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
+ "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
+ "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
+ "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
+ "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.0.5"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
+ "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
+ "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
+ "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
+ "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
+ "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
+ "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.2.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
+ "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
+ "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
+ "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.14.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
+ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
+ "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/as-table": {
+ "version": "1.0.55",
+ "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz",
+ "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "printable-characters": "^1.0.42"
+ }
+ },
+ "node_modules/blake3-wasm": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz",
+ "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/color": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
+ "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1",
+ "color-string": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=12.5.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/color-string": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/data-uri-to-buffer": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz",
+ "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/defu": {
+ "version": "6.1.4",
+ "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
+ "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/detect-libc": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
+ "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz",
+ "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.4",
+ "@esbuild/android-arm": "0.25.4",
+ "@esbuild/android-arm64": "0.25.4",
+ "@esbuild/android-x64": "0.25.4",
+ "@esbuild/darwin-arm64": "0.25.4",
+ "@esbuild/darwin-x64": "0.25.4",
+ "@esbuild/freebsd-arm64": "0.25.4",
+ "@esbuild/freebsd-x64": "0.25.4",
+ "@esbuild/linux-arm": "0.25.4",
+ "@esbuild/linux-arm64": "0.25.4",
+ "@esbuild/linux-ia32": "0.25.4",
+ "@esbuild/linux-loong64": "0.25.4",
+ "@esbuild/linux-mips64el": "0.25.4",
+ "@esbuild/linux-ppc64": "0.25.4",
+ "@esbuild/linux-riscv64": "0.25.4",
+ "@esbuild/linux-s390x": "0.25.4",
+ "@esbuild/linux-x64": "0.25.4",
+ "@esbuild/netbsd-arm64": "0.25.4",
+ "@esbuild/netbsd-x64": "0.25.4",
+ "@esbuild/openbsd-arm64": "0.25.4",
+ "@esbuild/openbsd-x64": "0.25.4",
+ "@esbuild/sunos-x64": "0.25.4",
+ "@esbuild/win32-arm64": "0.25.4",
+ "@esbuild/win32-ia32": "0.25.4",
+ "@esbuild/win32-x64": "0.25.4"
+ }
+ },
+ "node_modules/exit-hook": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz",
+ "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/exsolve": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz",
+ "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/get-source": {
+ "version": "2.0.12",
+ "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz",
+ "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==",
+ "dev": true,
+ "license": "Unlicense",
+ "dependencies": {
+ "data-uri-to-buffer": "^2.0.0",
+ "source-map": "^0.6.1"
+ }
+ },
+ "node_modules/glob-to-regexp": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
+ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
+ "dev": true,
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
+ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/mime": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
+ "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/miniflare": {
+ "version": "4.20250617.5",
+ "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250617.5.tgz",
+ "integrity": "sha512-Qqn30jR6dCjXaKVizT6vH4KOb+GyLccoxLNOJEfu63yBPn8eoXa7PrdiSGTmjs2RY8/tr7eTO8Wu/Yr14k0xVA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@cspotcode/source-map-support": "0.8.1",
+ "acorn": "8.14.0",
+ "acorn-walk": "8.3.2",
+ "exit-hook": "2.2.1",
+ "glob-to-regexp": "0.4.1",
+ "sharp": "^0.33.5",
+ "stoppable": "1.1.0",
+ "undici": "^5.28.5",
+ "workerd": "1.20250617.0",
+ "ws": "8.18.0",
+ "youch": "3.3.4",
+ "zod": "3.22.3"
+ },
+ "bin": {
+ "miniflare": "bootstrap.js"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/mustache": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
+ "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mustache": "bin/mustache"
+ }
+ },
+ "node_modules/ohash": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
+ "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/path-to-regexp": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
+ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/printable-characters": {
+ "version": "1.0.42",
+ "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz",
+ "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==",
+ "dev": true,
+ "license": "Unlicense"
+ },
+ "node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/sharp": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
+ "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "color": "^4.2.3",
+ "detect-libc": "^2.0.3",
+ "semver": "^7.6.3"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.33.5",
+ "@img/sharp-darwin-x64": "0.33.5",
+ "@img/sharp-libvips-darwin-arm64": "1.0.4",
+ "@img/sharp-libvips-darwin-x64": "1.0.4",
+ "@img/sharp-libvips-linux-arm": "1.0.5",
+ "@img/sharp-libvips-linux-arm64": "1.0.4",
+ "@img/sharp-libvips-linux-s390x": "1.0.4",
+ "@img/sharp-libvips-linux-x64": "1.0.4",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.4",
+ "@img/sharp-linux-arm": "0.33.5",
+ "@img/sharp-linux-arm64": "0.33.5",
+ "@img/sharp-linux-s390x": "0.33.5",
+ "@img/sharp-linux-x64": "0.33.5",
+ "@img/sharp-linuxmusl-arm64": "0.33.5",
+ "@img/sharp-linuxmusl-x64": "0.33.5",
+ "@img/sharp-wasm32": "0.33.5",
+ "@img/sharp-win32-ia32": "0.33.5",
+ "@img/sharp-win32-x64": "0.33.5"
+ }
+ },
+ "node_modules/simple-swizzle": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
+ "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.3.1"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stacktracey": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz",
+ "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==",
+ "dev": true,
+ "license": "Unlicense",
+ "dependencies": {
+ "as-table": "^1.0.36",
+ "get-source": "^2.0.12"
+ }
+ },
+ "node_modules/stoppable": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz",
+ "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD",
+ "optional": true
+ },
+ "node_modules/ufo": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz",
+ "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/undici": {
+ "version": "5.29.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
+ "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@fastify/busboy": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.0"
+ }
+ },
+ "node_modules/unenv": {
+ "version": "2.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.17.tgz",
+ "integrity": "sha512-B06u0wXkEd+o5gOCMl/ZHl5cfpYbDZKAT+HWTL+Hws6jWu7dCiqBBXXXzMFcFVJb8D4ytAnYmxJA83uwOQRSsg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "defu": "^6.1.4",
+ "exsolve": "^1.0.4",
+ "ohash": "^2.0.11",
+ "pathe": "^2.0.3",
+ "ufo": "^1.6.1"
+ }
+ },
+ "node_modules/workerd": {
+ "version": "1.20250617.0",
+ "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250617.0.tgz",
+ "integrity": "sha512-Uv6p0PYUHp/W/aWfUPLkZVAoAjapisM27JJlwcX9wCPTfCfnuegGOxFMvvlYpmNaX4YCwEdLCwuNn3xkpSkuZw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "workerd": "bin/workerd"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "optionalDependencies": {
+ "@cloudflare/workerd-darwin-64": "1.20250617.0",
+ "@cloudflare/workerd-darwin-arm64": "1.20250617.0",
+ "@cloudflare/workerd-linux-64": "1.20250617.0",
+ "@cloudflare/workerd-linux-arm64": "1.20250617.0",
+ "@cloudflare/workerd-windows-64": "1.20250617.0"
+ }
+ },
+ "node_modules/wrangler": {
+ "version": "4.23.0",
+ "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.23.0.tgz",
+ "integrity": "sha512-JSeDt3IwA4TEmg/V3tRblImPjdxynBt9PUVO/acQJ83XGlMMSwswDKL1FuwvbFzgX6+JXc3GMHeu7r8AQIxw9w==",
+ "dev": true,
+ "license": "MIT OR Apache-2.0",
+ "dependencies": {
+ "@cloudflare/kv-asset-handler": "0.4.0",
+ "@cloudflare/unenv-preset": "2.3.3",
+ "blake3-wasm": "2.1.5",
+ "esbuild": "0.25.4",
+ "miniflare": "4.20250617.5",
+ "path-to-regexp": "6.3.0",
+ "unenv": "2.0.0-rc.17",
+ "workerd": "1.20250617.0"
+ },
+ "bin": {
+ "wrangler": "bin/wrangler.js",
+ "wrangler2": "bin/wrangler.js"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ },
+ "peerDependencies": {
+ "@cloudflare/workers-types": "^4.20250617.0"
+ },
+ "peerDependenciesMeta": {
+ "@cloudflare/workers-types": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+ "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/youch": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.4.tgz",
+ "integrity": "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^0.7.1",
+ "mustache": "^4.2.0",
+ "stacktracey": "^2.1.8"
+ }
+ },
+ "node_modules/zod": {
+ "version": "3.22.3",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
+ "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..9cfd25e
--- /dev/null
+++ b/package.json
@@ -0,0 +1,5 @@
+{
+ "devDependencies": {
+ "wrangler": "^4.23.0"
+ },"name": "gemini-balance-lite"
+}
diff --git a/src/deno_index.ts b/src/deno_index.ts
new file mode 100644
index 0000000..7c254be
--- /dev/null
+++ b/src/deno_index.ts
@@ -0,0 +1,9 @@
+import { handleRequest } from "./handle_request.js";
+
+async function denoHandleRequest(req: Request): Promise {
+ const url = new URL(req.url);
+ console.log('Request URL:', req.url);
+ return handleRequest(req);
+};
+
+Deno.serve({ port: 80 },denoHandleRequest);
\ No newline at end of file
diff --git a/src/handle_request.js b/src/handle_request.js
new file mode 100644
index 0000000..a1a008e
--- /dev/null
+++ b/src/handle_request.js
@@ -0,0 +1,81 @@
+import { handleVerification } from './verify_keys.js';
+import openai from './openai.mjs';
+
+export async function handleRequest(request) {
+
+ const url = new URL(request.url);
+ const pathname = url.pathname;
+ const search = url.search;
+
+ if (pathname === '/' || pathname === '/index.html') {
+ return new Response('Proxy is Running! More Details: https://github.com/tech-shrimp/gemini-balance-lite', {
+ status: 200,
+ headers: { 'Content-Type': 'text/html' }
+ });
+ }
+
+ if (pathname === '/verify' && request.method === 'POST') {
+ return handleVerification(request);
+ }
+
+ // 处理OpenAI格式请求
+ if (url.pathname.endsWith("/chat/completions") || url.pathname.endsWith("/completions") || url.pathname.endsWith("/embeddings") || url.pathname.endsWith("/models")) {
+ return openai.fetch(request);
+ }
+
+ const targetUrl = `https://generativelanguage.googleapis.com${pathname}${search}`;
+
+ try {
+ const headers = new Headers();
+ for (const [key, value] of request.headers.entries()) {
+ if (key.trim().toLowerCase() === 'x-goog-api-key') {
+ const apiKeys = value.split(',').map(k => k.trim()).filter(k => k);
+ if (apiKeys.length > 0) {
+ const selectedKey = apiKeys[Math.floor(Math.random() * apiKeys.length)];
+ console.log(`Gemini Selected API Key: ${selectedKey}`);
+ headers.set('x-goog-api-key', selectedKey);
+ }
+ } else {
+ if (key.trim().toLowerCase()==='content-type')
+ {
+ headers.set(key, value);
+ }
+ }
+ }
+
+ console.log('Request Sending to Gemini')
+ console.log('targetUrl:'+targetUrl)
+ console.log(headers)
+
+ const response = await fetch(targetUrl, {
+ method: request.method,
+ headers: headers,
+ body: request.body
+ });
+
+ console.log("Call Gemini Success")
+
+ const responseHeaders = new Headers(response.headers);
+
+ console.log('Header from Gemini:')
+ console.log(responseHeaders)
+
+ responseHeaders.delete('transfer-encoding');
+ responseHeaders.delete('connection');
+ responseHeaders.delete('keep-alive');
+ responseHeaders.delete('content-encoding');
+ responseHeaders.set('Referrer-Policy', 'no-referrer');
+
+ return new Response(response.body, {
+ status: response.status,
+ headers: responseHeaders
+ });
+
+ } catch (error) {
+ console.error('Failed to fetch:', error);
+ return new Response('Internal Server Error\n' + error?.stack, {
+ status: 500,
+ headers: { 'Content-Type': 'text/plain' }
+ });
+}
+};
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..52cdae7
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,9 @@
+ import { handleRequest } from "./handle_request.js";
+
+ export default {
+ async fetch (req, env, context) {
+ const url = new URL(req.url);
+ console.log('Request URL:', req.url);
+ return handleRequest(req);
+ }
+ }
\ No newline at end of file
diff --git a/src/openai.mjs b/src/openai.mjs
new file mode 100644
index 0000000..fca7679
--- /dev/null
+++ b/src/openai.mjs
@@ -0,0 +1,681 @@
+//Author: PublicAffairs
+//Project: https://github.com/PublicAffairs/openai-gemini
+//MIT License : https://github.com/PublicAffairs/openai-gemini/blob/main/LICENSE
+
+
+import { Buffer } from "node:buffer";
+
+export default {
+ async fetch (request) {
+ if (request.method === "OPTIONS") {
+ return handleOPTIONS();
+ }
+ const errHandler = (err) => {
+ console.error(err);
+ return new Response(err.message, fixCors({ status: err.status ?? 500 }));
+ };
+ try {
+ const auth = request.headers.get("Authorization");
+ let apiKey = auth?.split(" ")[1];
+ if (apiKey && apiKey.includes(',')) {
+ const apiKeys = apiKey.split(',').map(k => k.trim()).filter(k => k);
+ apiKey = apiKeys[Math.floor(Math.random() * apiKeys.length)];
+ console.log(`OpenAI Selected API Key: ${apiKey}`);
+ }
+ const assert = (success) => {
+ if (!success) {
+ throw new HttpError("The specified HTTP method is not allowed for the requested resource", 400);
+ }
+ };
+ const { pathname } = new URL(request.url);
+ switch (true) {
+ case pathname.endsWith("/chat/completions"):
+ assert(request.method === "POST");
+ return handleCompletions(await request.json(), apiKey)
+ .catch(errHandler);
+ case pathname.endsWith("/embeddings"):
+ assert(request.method === "POST");
+ return handleEmbeddings(await request.json(), apiKey)
+ .catch(errHandler);
+ case pathname.endsWith("/models"):
+ assert(request.method === "GET");
+ return handleModels(apiKey)
+ .catch(errHandler);
+ default:
+ throw new HttpError("404 Not Found", 404);
+ }
+ } catch (err) {
+ return errHandler(err);
+ }
+ }
+};
+
+class HttpError extends Error {
+ constructor(message, status) {
+ super(message);
+ this.name = this.constructor.name;
+ this.status = status;
+ }
+}
+
+const fixCors = ({ headers, status, statusText }) => {
+ headers = new Headers(headers);
+ headers.set("Access-Control-Allow-Origin", "*");
+ return { headers, status, statusText };
+};
+
+const handleOPTIONS = async () => {
+ return new Response(null, {
+ headers: {
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Methods": "*",
+ "Access-Control-Allow-Headers": "*",
+ }
+ });
+};
+
+const BASE_URL = "https://generativelanguage.googleapis.com";
+const API_VERSION = "v1beta";
+
+// https://github.com/google-gemini/generative-ai-js/blob/cf223ff4a1ee5a2d944c53cddb8976136382bee6/src/requests/request.ts#L71
+const API_CLIENT = "genai-js/0.21.0"; // npm view @google/generative-ai version
+const makeHeaders = (apiKey, more) => ({
+ "x-goog-api-client": API_CLIENT,
+ ...(apiKey && { "x-goog-api-key": apiKey }),
+ ...more
+});
+
+async function handleModels (apiKey) {
+ const response = await fetch(`${BASE_URL}/${API_VERSION}/models`, {
+ headers: makeHeaders(apiKey),
+ });
+ let { body } = response;
+ if (response.ok) {
+ const { models } = JSON.parse(await response.text());
+ body = JSON.stringify({
+ object: "list",
+ data: models.map(({ name }) => ({
+ id: name.replace("models/", ""),
+ object: "model",
+ created: 0,
+ owned_by: "",
+ })),
+ }, null, " ");
+ }
+ return new Response(body, fixCors(response));
+}
+
+const DEFAULT_EMBEDDINGS_MODEL = "text-embedding-004";
+async function handleEmbeddings (req, apiKey) {
+ if (typeof req.model !== "string") {
+ throw new HttpError("model is not specified", 400);
+ }
+ let model;
+ if (req.model.startsWith("models/")) {
+ model = req.model;
+ } else {
+ if (!req.model.startsWith("gemini-")) {
+ req.model = DEFAULT_EMBEDDINGS_MODEL;
+ }
+ model = "models/" + req.model;
+ }
+ if (!Array.isArray(req.input)) {
+ req.input = [ req.input ];
+ }
+ const response = await fetch(`${BASE_URL}/${API_VERSION}/${model}:batchEmbedContents`, {
+ method: "POST",
+ headers: makeHeaders(apiKey, { "Content-Type": "application/json" }),
+ body: JSON.stringify({
+ "requests": req.input.map(text => ({
+ model,
+ content: { parts: { text } },
+ outputDimensionality: req.dimensions,
+ }))
+ })
+ });
+ let { body } = response;
+ if (response.ok) {
+ const { embeddings } = JSON.parse(await response.text());
+ body = JSON.stringify({
+ object: "list",
+ data: embeddings.map(({ values }, index) => ({
+ object: "embedding",
+ index,
+ embedding: values,
+ })),
+ model: req.model,
+ }, null, " ");
+ }
+ return new Response(body, fixCors(response));
+}
+
+const DEFAULT_MODEL = "gemini-2.5-flash";
+async function handleCompletions (req, apiKey) {
+ let model = DEFAULT_MODEL;
+ switch (true) {
+ case typeof req.model !== "string":
+ break;
+ case req.model.startsWith("models/"):
+ model = req.model.substring(7);
+ break;
+ case req.model.startsWith("gemini-"):
+ case req.model.startsWith("gemma-"):
+ case req.model.startsWith("learnlm-"):
+ model = req.model;
+ }
+ let body = await transformRequest(req);
+ const extra = req.extra_body?.google
+ if (extra) {
+ if (extra.safety_settings) {
+ body.safetySettings = extra.safety_settings;
+ }
+ if (extra.cached_content) {
+ body.cachedContent = extra.cached_content;
+ }
+ if (extra.thinking_config) {
+ body.generationConfig.thinkingConfig = extra.thinking_config;
+ }
+ }
+ switch (true) {
+ case model.endsWith(":search"):
+ model = model.substring(0, model.length - 7);
+ // eslint-disable-next-line no-fallthrough
+ case req.model.endsWith("-search-preview"):
+ case req.tools?.some(tool => tool.function?.name === 'googleSearch'):
+ body.tools = body.tools || [];
+ body.tools.push({googleSearch: {}});
+ }
+ console.log(body.tools)
+ const TASK = req.stream ? "streamGenerateContent" : "generateContent";
+ let url = `${BASE_URL}/${API_VERSION}/models/${model}:${TASK}`;
+ if (req.stream) { url += "?alt=sse"; }
+ const response = await fetch(url, {
+ method: "POST",
+ headers: makeHeaders(apiKey, { "Content-Type": "application/json" }),
+ body: JSON.stringify(body),
+ });
+
+ body = response.body;
+ if (response.ok) {
+ let id = "chatcmpl-" + generateId(); //"chatcmpl-8pMMaqXMK68B3nyDBrapTDrhkHBQK";
+ const shared = {};
+ if (req.stream) {
+ body = response.body
+ .pipeThrough(new TextDecoderStream())
+ .pipeThrough(new TransformStream({
+ transform: parseStream,
+ flush: parseStreamFlush,
+ buffer: "",
+ shared,
+ }))
+ .pipeThrough(new TransformStream({
+ transform: toOpenAiStream,
+ flush: toOpenAiStreamFlush,
+ streamIncludeUsage: req.stream_options?.include_usage,
+ model, id, last: [],
+ shared,
+ }))
+ .pipeThrough(new TextEncoderStream());
+ } else {
+ body = await response.text();
+ try {
+ body = JSON.parse(body);
+ if (!body.candidates) {
+ throw new Error("Invalid completion object");
+ }
+ } catch (err) {
+ console.error("Error parsing response:", err);
+ return new Response(body, fixCors(response)); // output as is
+ }
+ body = processCompletionsResponse(body, model, id);
+ }
+ }
+ return new Response(body, fixCors(response));
+}
+
+const adjustProps = (schemaPart) => {
+ if (typeof schemaPart !== "object" || schemaPart === null) {
+ return;
+ }
+ if (Array.isArray(schemaPart)) {
+ schemaPart.forEach(adjustProps);
+ } else {
+ if (schemaPart.type === "object" && schemaPart.properties && schemaPart.additionalProperties === false) {
+ delete schemaPart.additionalProperties;
+ }
+ Object.values(schemaPart).forEach(adjustProps);
+ }
+};
+const adjustSchema = (schema) => {
+ const obj = schema[schema.type];
+ delete obj.strict;
+ return adjustProps(schema);
+};
+
+const harmCategory = [
+ "HARM_CATEGORY_HATE_SPEECH",
+ "HARM_CATEGORY_SEXUALLY_EXPLICIT",
+ "HARM_CATEGORY_DANGEROUS_CONTENT",
+ "HARM_CATEGORY_HARASSMENT",
+ "HARM_CATEGORY_CIVIC_INTEGRITY",
+];
+const safetySettings = harmCategory.map(category => ({
+ category,
+ threshold: "BLOCK_NONE",
+}));
+const fieldsMap = {
+ frequency_penalty: "frequencyPenalty",
+ max_completion_tokens: "maxOutputTokens",
+ max_tokens: "maxOutputTokens",
+ n: "candidateCount", // not for streaming
+ presence_penalty: "presencePenalty",
+ seed: "seed",
+ stop: "stopSequences",
+ temperature: "temperature",
+ top_k: "topK", // non-standard
+ top_p: "topP",
+};
+const thinkingBudgetMap = {
+ low: 1024,
+ medium: 8192,
+ high: 24576,
+};
+const transformConfig = (req) => {
+ let cfg = {};
+ //if (typeof req.stop === "string") { req.stop = [req.stop]; } // no need
+ for (let key in req) {
+ const matchedKey = fieldsMap[key];
+ if (matchedKey) {
+ cfg[matchedKey] = req[key];
+ }
+ }
+ if (req.response_format) {
+ switch (req.response_format.type) {
+ case "json_schema":
+ adjustSchema(req.response_format);
+ cfg.responseSchema = req.response_format.json_schema?.schema;
+ if (cfg.responseSchema && "enum" in cfg.responseSchema) {
+ cfg.responseMimeType = "text/x.enum";
+ break;
+ }
+ // eslint-disable-next-line no-fallthrough
+ case "json_object":
+ cfg.responseMimeType = "application/json";
+ break;
+ case "text":
+ cfg.responseMimeType = "text/plain";
+ break;
+ default:
+ throw new HttpError("Unsupported response_format.type", 400);
+ }
+ }
+ if (req.reasoning_effort) {
+ cfg.thinkingConfig = { thinkingBudget: thinkingBudgetMap[req.reasoning_effort] };
+ }
+ return cfg;
+};
+
+const parseImg = async (url) => {
+ let mimeType, data;
+ if (url.startsWith("http://") || url.startsWith("https://")) {
+ try {
+ const response = await fetch(url);
+ if (!response.ok) {
+ throw new Error(`${response.status} ${response.statusText} (${url})`);
+ }
+ mimeType = response.headers.get("content-type");
+ data = Buffer.from(await response.arrayBuffer()).toString("base64");
+ } catch (err) {
+ throw new Error("Error fetching image: " + err.toString());
+ }
+ } else {
+ const match = url.match(/^data:(?.*?)(;base64)?,(?.*)$/);
+ if (!match) {
+ throw new HttpError("Invalid image data: " + url, 400);
+ }
+ ({ mimeType, data } = match.groups);
+ }
+ return {
+ inlineData: {
+ mimeType,
+ data,
+ },
+ };
+};
+
+const transformFnResponse = ({ content, tool_call_id }, parts) => {
+ if (!parts.calls) {
+ throw new HttpError("No function calls found in the previous message", 400);
+ }
+ let response;
+ try {
+ response = JSON.parse(content);
+ } catch (err) {
+ console.error("Error parsing function response content:", err);
+ throw new HttpError("Invalid function response: " + content, 400);
+ }
+ if (typeof response !== "object" || response === null || Array.isArray(response)) {
+ response = { result: response };
+ }
+ if (!tool_call_id) {
+ throw new HttpError("tool_call_id not specified", 400);
+ }
+ const { i, name } = parts.calls[tool_call_id] ?? {};
+ if (!name) {
+ throw new HttpError("Unknown tool_call_id: " + tool_call_id, 400);
+ }
+ if (parts[i]) {
+ throw new HttpError("Duplicated tool_call_id: " + tool_call_id, 400);
+ }
+ parts[i] = {
+ functionResponse: {
+ id: tool_call_id.startsWith("call_") ? null : tool_call_id,
+ name,
+ response,
+ }
+ };
+};
+
+const transformFnCalls = ({ tool_calls }) => {
+ const calls = {};
+ const parts = tool_calls.map(({ function: { arguments: argstr, name }, id, type }, i) => {
+ if (type !== "function") {
+ throw new HttpError(`Unsupported tool_call type: "${type}"`, 400);
+ }
+ let args;
+ try {
+ args = JSON.parse(argstr);
+ } catch (err) {
+ console.error("Error parsing function arguments:", err);
+ throw new HttpError("Invalid function arguments: " + argstr, 400);
+ }
+ calls[id] = {i, name};
+ return {
+ functionCall: {
+ id: id.startsWith("call_") ? null : id,
+ name,
+ args,
+ }
+ };
+ });
+ parts.calls = calls;
+ return parts;
+};
+
+const transformMsg = async ({ content }) => {
+ const parts = [];
+ if (!Array.isArray(content)) {
+ // system, user: string
+ // assistant: string or null (Required unless tool_calls is specified.)
+ parts.push({ text: content });
+ return parts;
+ }
+ // user:
+ // An array of content parts with a defined type.
+ // Supported options differ based on the model being used to generate the response.
+ // Can contain text, image, or audio inputs.
+ for (const item of content) {
+ switch (item.type) {
+ case "text":
+ parts.push({ text: item.text });
+ break;
+ case "image_url":
+ parts.push(await parseImg(item.image_url.url));
+ break;
+ case "input_audio":
+ parts.push({
+ inlineData: {
+ mimeType: "audio/" + item.input_audio.format,
+ data: item.input_audio.data,
+ }
+ });
+ break;
+ default:
+ throw new HttpError(`Unknown "content" item type: "${item.type}"`, 400);
+ }
+ }
+ if (content.every(item => item.type === "image_url")) {
+ parts.push({ text: "" }); // to avoid "Unable to submit request because it must have a text parameter"
+ }
+ return parts;
+};
+
+const transformMessages = async (messages) => {
+ if (!messages) { return; }
+ const contents = [];
+ let system_instruction;
+ for (const item of messages) {
+ switch (item.role) {
+ case "system":
+ system_instruction = { parts: await transformMsg(item) };
+ continue;
+ case "tool":
+ // eslint-disable-next-line no-case-declarations
+ let { role, parts } = contents[contents.length - 1] ?? {};
+ if (role !== "function") {
+ const calls = parts?.calls;
+ parts = []; parts.calls = calls;
+ contents.push({
+ role: "function", // ignored
+ parts
+ });
+ }
+ transformFnResponse(item, parts);
+ continue;
+ case "assistant":
+ item.role = "model";
+ break;
+ case "user":
+ break;
+ default:
+ throw new HttpError(`Unknown message role: "${item.role}"`, 400);
+ }
+ contents.push({
+ role: item.role,
+ parts: item.tool_calls ? transformFnCalls(item) : await transformMsg(item)
+ });
+ }
+ if (system_instruction) {
+ if (!contents[0]?.parts.some(part => part.text)) {
+ contents.unshift({ role: "user", parts: { text: " " } });
+ }
+ }
+ //console.info(JSON.stringify(contents, 2));
+ return { system_instruction, contents };
+};
+
+const transformTools = (req) => {
+ let tools, tool_config;
+ if (req.tools) {
+ const funcs = req.tools.filter(tool => tool.type === "function" && tool.function?.name !== 'googleSearch');
+ if (funcs.length > 0) {
+ funcs.forEach(adjustSchema);
+ tools = [{ function_declarations: funcs.map(schema => schema.function) }];
+ }
+ }
+ if (req.tool_choice) {
+ const allowed_function_names = req.tool_choice?.type === "function" ? [ req.tool_choice?.function?.name ] : undefined;
+ if (allowed_function_names || typeof req.tool_choice === "string") {
+ tool_config = {
+ function_calling_config: {
+ mode: allowed_function_names ? "ANY" : req.tool_choice.toUpperCase(),
+ allowed_function_names
+ }
+ };
+ }
+ }
+ return { tools, tool_config };
+};
+
+const transformRequest = async (req) => ({
+ ...await transformMessages(req.messages),
+ safetySettings,
+ generationConfig: transformConfig(req),
+ ...transformTools(req),
+});
+
+const generateId = () => {
+ const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ const randomChar = () => characters[Math.floor(Math.random() * characters.length)];
+ return Array.from({ length: 29 }, randomChar).join("");
+};
+
+const reasonsMap = { //https://ai.google.dev/api/rest/v1/GenerateContentResponse#finishreason
+ //"FINISH_REASON_UNSPECIFIED": // Default value. This value is unused.
+ "STOP": "stop",
+ "MAX_TOKENS": "length",
+ "SAFETY": "content_filter",
+ "RECITATION": "content_filter",
+ //"OTHER": "OTHER",
+};
+const SEP = "\n\n|>";
+const transformCandidates = (key, cand) => {
+ const message = { role: "assistant", content: [] };
+ for (const part of cand.content?.parts ?? []) {
+ if (part.functionCall) {
+ const fc = part.functionCall;
+ message.tool_calls = message.tool_calls ?? [];
+ message.tool_calls.push({
+ id: fc.id ?? "call_" + generateId(),
+ type: "function",
+ function: {
+ name: fc.name,
+ arguments: JSON.stringify(fc.args),
+ }
+ });
+ } else {
+ message.content.push(part.text);
+ }
+ }
+ message.content = message.content.join(SEP) || null;
+ return {
+ index: cand.index || 0, // 0-index is absent in new -002 models response
+ [key]: message,
+ logprobs: null,
+ finish_reason: message.tool_calls ? "tool_calls" : reasonsMap[cand.finishReason] || cand.finishReason,
+ //original_finish_reason: cand.finishReason,
+ };
+};
+const transformCandidatesMessage = transformCandidates.bind(null, "message");
+const transformCandidatesDelta = transformCandidates.bind(null, "delta");
+
+const transformUsage = (data) => ({
+ completion_tokens: data.candidatesTokenCount,
+ prompt_tokens: data.promptTokenCount,
+ total_tokens: data.totalTokenCount
+});
+
+const checkPromptBlock = (choices, promptFeedback, key) => {
+ if (choices.length) { return; }
+ if (promptFeedback?.blockReason) {
+ console.log("Prompt block reason:", promptFeedback.blockReason);
+ if (promptFeedback.blockReason === "SAFETY") {
+ promptFeedback.safetyRatings
+ .filter(r => r.blocked)
+ .forEach(r => console.log(r));
+ }
+ choices.push({
+ index: 0,
+ [key]: null,
+ finish_reason: "content_filter",
+ //original_finish_reason: data.promptFeedback.blockReason,
+ });
+ }
+ return true;
+};
+
+const processCompletionsResponse = (data, model, id) => {
+ const obj = {
+ id,
+ choices: data.candidates.map(transformCandidatesMessage),
+ created: Math.floor(Date.now()/1000),
+ model: data.modelVersion ?? model,
+ //system_fingerprint: "fp_69829325d0",
+ object: "chat.completion",
+ usage: data.usageMetadata && transformUsage(data.usageMetadata),
+ };
+ if (obj.choices.length === 0 ) {
+ checkPromptBlock(obj.choices, data.promptFeedback, "message");
+ }
+ return JSON.stringify(obj);
+};
+
+const responseLineRE = /^data: (.*)(?:\n\n|\r\r|\r\n\r\n)/;
+function parseStream (chunk, controller) {
+ this.buffer += chunk;
+ do {
+ const match = this.buffer.match(responseLineRE);
+ if (!match) { break; }
+ controller.enqueue(match[1]);
+ this.buffer = this.buffer.substring(match[0].length);
+ } while (true); // eslint-disable-line no-constant-condition
+}
+function parseStreamFlush (controller) {
+ if (this.buffer) {
+ console.error("Invalid data:", this.buffer);
+ controller.enqueue(this.buffer);
+ this.shared.is_buffers_rest = true;
+ }
+}
+
+const delimiter = "\n\n";
+const sseline = (obj) => {
+ obj.created = Math.floor(Date.now()/1000);
+ return "data: " + JSON.stringify(obj) + delimiter;
+};
+function toOpenAiStream (line, controller) {
+ let data;
+ try {
+ data = JSON.parse(line);
+ if (!data.candidates) {
+ throw new Error("Invalid completion chunk object");
+ }
+ } catch (err) {
+ console.error("Error parsing response:", err);
+ if (!this.shared.is_buffers_rest) { line =+ delimiter; }
+ controller.enqueue(line); // output as is
+ return;
+ }
+ const obj = {
+ id: this.id,
+ choices: data.candidates.map(transformCandidatesDelta),
+ //created: Math.floor(Date.now()/1000),
+ model: data.modelVersion ?? this.model,
+ //system_fingerprint: "fp_69829325d0",
+ object: "chat.completion.chunk",
+ usage: data.usageMetadata && this.streamIncludeUsage ? null : undefined,
+ };
+ if (checkPromptBlock(obj.choices, data.promptFeedback, "delta")) {
+ controller.enqueue(sseline(obj));
+ return;
+ }
+ console.assert(data.candidates.length === 1, "Unexpected candidates count: %d", data.candidates.length);
+ const cand = obj.choices[0];
+ cand.index = cand.index || 0; // absent in new -002 models response
+ const finish_reason = cand.finish_reason;
+ cand.finish_reason = null;
+ if (!this.last[cand.index]) { // first
+ controller.enqueue(sseline({
+ ...obj,
+ choices: [{ ...cand, tool_calls: undefined, delta: { role: "assistant", content: "" } }],
+ }));
+ }
+ delete cand.delta.role;
+ if ("content" in cand.delta) { // prevent empty data (e.g. when MAX_TOKENS)
+ controller.enqueue(sseline(obj));
+ }
+ cand.finish_reason = finish_reason;
+ if (data.usageMetadata && this.streamIncludeUsage) {
+ obj.usage = transformUsage(data.usageMetadata);
+ }
+ cand.delta = {};
+ this.last[cand.index] = obj;
+}
+function toOpenAiStreamFlush (controller) {
+ if (this.last.length > 0) {
+ for (const obj of this.last) {
+ controller.enqueue(sseline(obj));
+ }
+ controller.enqueue("data: [DONE]" + delimiter);
+ }
+}
diff --git a/src/verify_keys.js b/src/verify_keys.js
new file mode 100644
index 0000000..1ebc08b
--- /dev/null
+++ b/src/verify_keys.js
@@ -0,0 +1,65 @@
+async function verifyKey(key, controller) {
+ const url = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent';
+ const body = {
+ "contents": [{
+ "role": "user",
+ "parts": [{
+ "text": "Hello"
+ }]
+ }]
+ };
+ let result;
+ try {
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-goog-api-key': key,
+ },
+ body: JSON.stringify(body),
+ });
+ if (response.ok) {
+ await response.text(); // Consume body to release connection
+ result = { key: `${key.slice(0, 7)}......${key.slice(-7)}`, status: 'GOOD' };
+ } else {
+ const errorData = await response.json().catch(() => ({ error: { message: 'Unknown error' } }));
+ result = { key: `${key.slice(0, 7)}......${key.slice(-7)}`, status: 'BAD', error: errorData.error.message };
+ }
+ } catch (e) {
+ result = { key: `${key.slice(0, 7)}......${key.slice(-7)}`, status: 'ERROR', error: e.message };
+ }
+ controller.enqueue(new TextEncoder().encode('data: ' + JSON.stringify(result) + '\n\n'));
+}
+
+export async function handleVerification(request) {
+ try {
+ const authHeader = request.headers.get('x-goog-api-key');
+ if (!authHeader) {
+ return new Response(JSON.stringify({ error: 'Missing x-goog-api-key header.' }), {
+ status: 400,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+ const keys = authHeader.split(',').map(k => k.trim()).filter(Boolean);
+
+ const stream = new ReadableStream({
+ async start(controller) {
+ const verificationPromises = keys.map(key => verifyKey(key, controller));
+ await Promise.all(verificationPromises);
+ controller.close();
+ }
+ });
+
+ return new Response(stream, {
+ status: 200,
+ headers: {
+ 'Content-Type': 'text/event-stream',
+ 'Cache-Control': 'no-cache',
+ 'Connection': 'keep-alive',
+ }
+ });
+
+ } catch (e) {
+ return new Response(JSON.stringify({ error: 'An unexpected error occurred: ' + e.message }), { status: 500, headers: { 'Content-Type': 'application/json' } });
+ }
+}
diff --git a/vercel.json b/vercel.json
new file mode 100644
index 0000000..a81bf0a
--- /dev/null
+++ b/vercel.json
@@ -0,0 +1,5 @@
+{
+ "routes": [
+ { "src": "/(.*)", "dest": "/api/vercel_index.js" }
+ ]
+}
\ No newline at end of file
diff --git a/wrangler.toml b/wrangler.toml
new file mode 100644
index 0000000..f1a94ba
--- /dev/null
+++ b/wrangler.toml
@@ -0,0 +1,5 @@
+name = "gemini-balance-lite"
+# 主入口
+main = "src/index.js"
+compatibility_date = "2025-06-17"
+compatibility_flags = ["nodejs_compat"]