Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ SWITCH_ON_USES=40
# Set to 0 to disable failure-based switching
FAILURE_THRESHOLD=3

# Default Gemini safetySettings threshold used only when the incoming request does not provide safetySettings
# Supported values: HARM_BLOCK_THRESHOLD_UNSPECIFIED, BLOCK_LOW_AND_ABOVE, BLOCK_MEDIUM_AND_ABOVE, BLOCK_ONLY_HIGH, BLOCK_NONE, OFF
SAFETY_SETTINGS_THRESHOLD=OFF

# HTTP status codes that trigger immediate account switching (comma-separated)
# Common values: 429 (Too Many Requests), 503 (Service Unavailable)
# Leave empty to disable this feature
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ services:
| `RETRY_DELAY` | 两次重试之间的间隔(毫秒)。 | `2000` |
| `SWITCH_ON_USES` | 自动切换帐户前允许的请求次数(设为 `0` 禁用)。 | `40` |
| `FAILURE_THRESHOLD` | 切换帐户前允许的连续失败次数(设为 `0` 禁用)。 | `3` |
| `SAFETY_SETTINGS_THRESHOLD` | 安全设置的等级。官方说明:[`Safety settings`](https://ai.google.dev/gemini-api/docs/safety-settings?hl=zh-cn) | `OFF` |
| `IMMEDIATE_SWITCH_STATUS_CODES` | 触发立即切换帐户的 HTTP 状态码(逗号分隔,设为空值以禁用)。 | `429,503` |
| `MAX_CONTEXTS` | 最大同时登录的账号数量。同时登录的账号切换更快,无需重新登录。数值越大内存消耗越高(约:1 个账号 ~700MB,2 个账号 ~950MB,3 个账号 ~1100MB)。设为 `0` 表示无限制。 | `1` |
| `HTTP_PROXY` | 用于访问 Google 服务的 HTTP 代理地址。 | 无 |
Expand Down
1 change: 1 addition & 0 deletions README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ This endpoint forwards requests to the official Gemini API format endpoint.
| `RETRY_DELAY` | Delay between retries in milliseconds. | `2000` |
| `SWITCH_ON_USES` | Number of requests before automatically switching accounts (`0` to disable). | `40` |
| `FAILURE_THRESHOLD` | Number of consecutive failures before switching accounts (`0` to disable). | `3` |
| `SAFETY_SETTINGS_THRESHOLD` | Safety settings level. Official docs: [`Safety settings`](https://ai.google.dev/gemini-api/docs/safety-settings) | `OFF` |
| `IMMEDIATE_SWITCH_STATUS_CODES` | HTTP status codes that trigger immediate account switching (comma-separated, set to empty to disable). | `429,503` |
| `MAX_CONTEXTS` | Maximum number of accounts that can be logged in simultaneously. Accounts logged in simultaneously can switch faster without re-login. Higher values consume more memory (approx: 1 account ~700MB, 2 accounts ~950MB, 3 accounts ~1100MB). Set to `0` for unlimited. | `1` |
| `HTTP_PROXY` | HTTP proxy address for accessing Google services. | None |
Expand Down
17 changes: 11 additions & 6 deletions src/core/FormatConverter.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,16 @@ class FormatConverter {
this.serverSystem = serverSystem;
}

getDefaultSafetySettings() {
const threshold = this.serverSystem?.config?.safetySettingsThreshold || "OFF";
return [
{ category: "HARM_CATEGORY_HARASSMENT", threshold },
{ category: "HARM_CATEGORY_HATE_SPEECH", threshold },
{ category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold },
{ category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold },
];
Comment on lines +140 to +147
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getDefaultSafetySettings() treats "OFF" as a literal threshold value and will send it to Gemini for each harm category. If the intent of "OFF" is to disable safety settings (i.e., let the API default apply), this should instead return null / undefined (and callers should skip setting safetySettings) or map "OFF" to a documented threshold value. Otherwise, clarify in docs that "OFF" is forwarded verbatim.

Copilot uses AI. Check for mistakes.
}

normalizeImageUrl(imageSource) {
if (typeof imageSource === "string") {
return imageSource;
Expand Down Expand Up @@ -988,12 +998,7 @@ class FormatConverter {
this.ensureServerSideToolInvocations(googleRequest);

// Safety settings
googleRequest.safetySettings = [
{ category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" },
{ category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE" },
{ category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE" },
{ category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" },
];
googleRequest.safetySettings = this.getDefaultSafetySettings();

this.logger.debug(`[Adapter] Debug: Final Gemini Request = ${JSON.stringify(googleRequest, null, 2)}`);
}
Expand Down
7 changes: 1 addition & 6 deletions src/core/RequestHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -4068,12 +4068,7 @@ class RequestHandler {

// Apply safety settings for native Google requests (only if not already provided)
if (req.method === "POST" && bodyObj && bodyObj.contents && !bodyObj.safetySettings) {
bodyObj.safetySettings = [
{ category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" },
{ category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE" },
{ category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE" },
{ category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" },
];
bodyObj.safetySettings = this.formatConverter.getDefaultSafetySettings();
}

this.logger.debug(`[Proxy] Debug: Final Gemini Request (Google Native) = ${JSON.stringify(bodyObj, null, 2)}`);
Expand Down
20 changes: 20 additions & 0 deletions src/utils/ConfigLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class ConfigLoader {
maxContexts: 1,
maxRetries: 3,
retryDelay: 2000,
safetySettingsThreshold: "OFF",
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default safety threshold is now "OFF" (via config default + docs), whereas the previous behavior hard-coded "BLOCK_NONE" for all categories. This is a backward-incompatible default change and may alter moderation behavior (or even cause request failures if "OFF" isn't accepted by the upstream API). Consider keeping the default at the prior value ("BLOCK_NONE") and making the env var override optional; if you want an 'OFF' mode, handle it explicitly (e.g., by omitting safetySettings injection entirely).

Suggested change
safetySettingsThreshold: "OFF",
safetySettingsThreshold: "BLOCK_NONE",

Copilot uses AI. Check for mistakes.
streamingMode: "real",
switchOnUses: 40,
wsPort: 9998,
Expand Down Expand Up @@ -83,6 +84,24 @@ class ConfigLoader {
if (process.env.FORCE_WEB_SEARCH) config.forceWebSearch = process.env.FORCE_WEB_SEARCH.toLowerCase() === "true";
if (process.env.FORCE_URL_CONTEXT)
config.forceUrlContext = process.env.FORCE_URL_CONTEXT.toLowerCase() === "true";
if (process.env.SAFETY_SETTINGS_THRESHOLD) {
const rawThreshold = String(process.env.SAFETY_SETTINGS_THRESHOLD).trim().toUpperCase();
const allowedThresholds = new Set([
"HARM_BLOCK_THRESHOLD_UNSPECIFIED",
Comment on lines +87 to +90
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is marked as fixing #100, but the issue request specifically asks for a new config option named FORCE_BLOCK_LEVEL. Right now only SAFETY_SETTINGS_THRESHOLD is supported. To avoid leaving the issue partially addressed (and to preserve backward/forward compatibility with users following the issue text), consider supporting FORCE_BLOCK_LEVEL as an alias or updating the issue/PR description to reflect the chosen final name.

Copilot uses AI. Check for mistakes.
"BLOCK_LOW_AND_ABOVE",
"BLOCK_MEDIUM_AND_ABOVE",
"BLOCK_ONLY_HIGH",
"BLOCK_NONE",
"OFF",
]);
if (allowedThresholds.has(rawThreshold)) {
config.safetySettingsThreshold = rawThreshold;
} else {
this.logger.warn(
`[Config] Invalid SAFETY_SETTINGS_THRESHOLD "${process.env.SAFETY_SETTINGS_THRESHOLD}", falling back to ${config.safetySettingsThreshold}.`
);
}
}
if (process.env.ENABLE_AUTH_UPDATE)
config.enableAuthUpdate = process.env.ENABLE_AUTH_UPDATE.toLowerCase() !== "false";
if (process.env.ENABLE_USAGE_STATS)
Expand Down Expand Up @@ -163,6 +182,7 @@ class ConfigLoader {
this.logger.info(` Force Thinking: ${config.forceThinking}`);
this.logger.info(` Force Web Search: ${config.forceWebSearch}`);
this.logger.info(` Force URL Context: ${config.forceUrlContext}`);
this.logger.info(` Default Safety Threshold: ${config.safetySettingsThreshold}`);
this.logger.info(` Auto Update Auth: ${config.enableAuthUpdate}`);
this.logger.info(` Usage Stats: ${config.enableUsageStats}`);
this.logger.info(` Max Contexts: ${config.maxContexts === 0 ? "Unlimited" : config.maxContexts}`);
Expand Down