Skip to content
Open
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
14 changes: 12 additions & 2 deletions src-tauri/src/proxy/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,19 @@ pub fn update_global_system_prompt_config(config: GlobalSystemPromptConfig) {
if let Ok(mut cfg) = lock.write() {
*cfg = config.clone();
tracing::info!(
"[Global-System-Prompt] Config updated: enabled={}, content_len={}",
"[Global-System-Prompt] Config updated: enabled={}, include_default_prompt={}, content_len={}",
config.enabled,
config.include_default_prompt,
config.content.len()
);
}
} else {
// 首次初始化
let _ = GLOBAL_SYSTEM_PROMPT_CONFIG.set(RwLock::new(config.clone()));
tracing::info!(
"[Global-System-Prompt] Config initialized: enabled={}, content_len={}",
"[Global-System-Prompt] Config initialized: enabled={}, include_default_prompt={}, content_len={}",
config.enabled,
config.include_default_prompt,
config.content.len()
);
}
Expand Down Expand Up @@ -130,13 +132,21 @@ pub struct GlobalSystemPromptConfig {
/// 系统提示词内容
#[serde(default)]
pub content: String,
/// 是否注入内置 Antigravity 身份提示词
#[serde(default = "default_include_default_prompt")]
pub include_default_prompt: bool,
}

fn default_include_default_prompt() -> bool {
true
}

impl Default for GlobalSystemPromptConfig {
fn default() -> Self {
Self {
enabled: false,
content: String::new(),
include_default_prompt: true,
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/proxy/handlers/claude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,7 @@ pub async fn handle_messages(
// 成功
if status.is_success() {
// [智能限流] 请求成功,重置该账号的连续失败计数
token_manager.mark_account_success(&email);
token_manager.mark_account_success(&account_id);

// Determine context limit based on model
let context_limit = crate::proxy::mappers::claude::utils::get_context_limit_for_model(&request_with_mapped.model);
Expand Down
2 changes: 2 additions & 0 deletions src-tauri/src/proxy/handlers/gemini.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ pub async fn handle_generate(
.map(|s| s.to_string());

if status.is_success() {
token_manager.mark_account_success(&account_id);

// 6. 响应处理
if is_stream {
use axum::body::Body;
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/proxy/handlers/openai.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1286,7 +1286,7 @@ pub async fn handle_completions(
let status = response.status();
if status.is_success() {
// [智能限流] 请求成功,重置该账号的连续失败计数
token_manager.mark_account_success(&email);
token_manager.mark_account_success(&account_id);

if list_response {
use axum::body::Body;
Expand Down
76 changes: 66 additions & 10 deletions src-tauri/src/proxy/mappers/claude/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -841,13 +841,16 @@ fn build_system_instruction(
}
}

// 如果用户没有提供 Antigravity 身份,则注入
if !user_has_antigravity {
let global_prompt_config = crate::proxy::config::get_global_system_prompt();
let should_inject_default_prompt =
global_prompt_config.include_default_prompt && !user_has_antigravity;

// 如果用户没有提供 Antigravity 身份,且配置允许,则注入
if should_inject_default_prompt {
parts.push(json!({"text": antigravity_identity}));
}

// [NEW] 注入全局系统提示词 (紧跟 Antigravity 身份之后)
let global_prompt_config = crate::proxy::config::get_global_system_prompt();
if global_prompt_config.enabled && !global_prompt_config.content.trim().is_empty() {
parts.push(json!({"text": global_prompt_config.content}));
}
Expand Down Expand Up @@ -884,15 +887,19 @@ fn build_system_instruction(
parts.push(json!({"text": mcp_xml_prompt}));
}

// 如果用户没有提供任何系统提示词,添加结束标记
if !user_has_antigravity {
// 只有注入了内置默认提示词时才添加结束标记
if should_inject_default_prompt {
parts.push(json!({"text": "\n--- [SYSTEM_PROMPT_END] ---"}));
}

Some(json!({
"role": "user",
"parts": parts
}))
if parts.is_empty() {
None
} else {
Some(json!({
"role": "user",
"parts": parts
}))
}
}

/// 构建 Contents (Messages)
Expand Down Expand Up @@ -2053,7 +2060,9 @@ fn is_model_compatible(cached: &str, target: &str) -> bool {
mod tests {
use super::*;
use crate::proxy::common::json_schema::clean_json_schema;
use crate::proxy::config::{ThinkingBudgetConfig, update_thinking_budget_config};
use crate::proxy::config::{
update_global_system_prompt_config, GlobalSystemPromptConfig, ThinkingBudgetConfig,
};

#[test]
fn test_ephemeral_injection_debug() {
Expand Down Expand Up @@ -2123,6 +2132,53 @@ mod tests {
assert!(body["requestId"].as_str().unwrap().starts_with("agent/"));
}

#[test]
fn test_default_system_prompt_can_be_disabled() {
update_global_system_prompt_config(GlobalSystemPromptConfig {
enabled: false,
content: String::new(),
include_default_prompt: false,
});

let req = ClaudeRequest {
model: "claude-sonnet-4-6".to_string(),
messages: vec![Message {
role: "user".to_string(),
content: MessageContent::String("Hello".to_string()),
}],
system: Some(SystemPrompt::String("Custom system only".to_string())),
tools: None,
stream: false,
max_tokens: None,
temperature: None,
top_p: None,
top_k: None,
thinking: None,
metadata: None,
output_config: None,
size: None,
quality: None,
};

let body =
transform_claude_request_in(&req, "test-project", false, None, "test_session", None)
.unwrap();
let parts = body["request"]["systemInstruction"]["parts"]
.as_array()
.unwrap();
let system_text = parts
.iter()
.filter_map(|part| part.get("text").and_then(|text| text.as_str()))
.collect::<Vec<_>>()
.join("\n");

assert!(!system_text.contains("You are Antigravity"));
assert!(!system_text.contains("SYSTEM_PROMPT_END"));
assert!(system_text.contains("Custom system only"));

update_global_system_prompt_config(GlobalSystemPromptConfig::default());
}

#[test]
fn test_clean_json_schema() {
let mut schema = json!({
Expand Down
65 changes: 55 additions & 10 deletions src-tauri/src/proxy/mappers/gemini/wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,8 @@ pub fn wrap_request(
**Proactiveness**"
};

let global_prompt_config = crate::proxy::config::get_global_system_prompt();

// [HYBRID] 检查是否已有 systemInstruction
if let Some(system_instruction) = inner_request.get_mut("systemInstruction") {
// [NEW] 补全 role: user
Expand All @@ -487,18 +489,24 @@ pub fn wrap_request(
.map(|s| s.contains("You are Antigravity"))
.unwrap_or(false);

if !has_antigravity {
let should_inject_default_prompt =
global_prompt_config.include_default_prompt && !has_antigravity;

if should_inject_default_prompt {
// 在前面插入 Antigravity 身份
parts_array.insert(0, json!({"text": antigravity_identity}));
}

// [NEW] 注入全局系统提示词 (紧跟 Antigravity 身份之后,用户指令之前)
let global_prompt_config = crate::proxy::config::get_global_system_prompt();
if global_prompt_config.enabled
&& !global_prompt_config.content.trim().is_empty()
{
// 插入位置:Antigravity 身份之后 (index 1)
let insert_pos = if has_antigravity { 1 } else { 1 };
// 插入位置:内置或用户已有 Antigravity 身份之后,否则放在最前
let insert_pos = if should_inject_default_prompt || has_antigravity {
1
} else {
0
};
if insert_pos <= parts_array.len() {
parts_array
.insert(insert_pos, json!({"text": global_prompt_config.content}));
Expand All @@ -510,16 +518,20 @@ pub fn wrap_request(
}
} else {
// 没有 systemInstruction,创建一个新的
let mut parts = vec![json!({"text": antigravity_identity})];
let mut parts = Vec::new();
if global_prompt_config.include_default_prompt {
parts.push(json!({"text": antigravity_identity}));
}
// [NEW] 注入全局系统提示词
let global_prompt_config = crate::proxy::config::get_global_system_prompt();
if global_prompt_config.enabled && !global_prompt_config.content.trim().is_empty() {
parts.push(json!({"text": global_prompt_config.content}));
}
inner_request["systemInstruction"] = json!({
"role": "user",
"parts": parts
});
if !parts.is_empty() {
inner_request["systemInstruction"] = json!({
"role": "user",
"parts": parts
});
}
}
}

Expand Down Expand Up @@ -673,6 +685,7 @@ pub fn inject_ids_to_response(response: &mut Value, model_name: &str) {
#[cfg(test)]
mod tests {
use super::*;
use crate::proxy::config::{update_global_system_prompt_config, GlobalSystemPromptConfig};
use serde_json::json;

#[test]
Expand Down Expand Up @@ -718,6 +731,38 @@ mod tests {
.unwrap();
}

#[test]
fn test_default_system_prompt_can_be_disabled_gemini_native() {
update_global_system_prompt_config(GlobalSystemPromptConfig {
enabled: false,
content: String::new(),
include_default_prompt: false,
});

let body = json!({
"model": "gemini-pro",
"systemInstruction": {
"parts": [{"text": "Custom system only"}]
},
"contents": [{"role": "user", "parts": [{"text": "Hello"}]}]
});

let result = wrap_request(&body, "test-proj", "gemini-pro", None, None, None);
let parts = result["request"]["systemInstruction"]["parts"]
.as_array()
.unwrap();
let system_text = parts
.iter()
.filter_map(|part| part.get("text").and_then(|text| text.as_str()))
.collect::<Vec<_>>()
.join("\n");

assert!(!system_text.contains("You are Antigravity"));
assert!(system_text.contains("Custom system only"));

update_global_system_prompt_config(GlobalSystemPromptConfig::default());
}

#[test]
fn test_gemini_flash_thinking_budget_capping() {
// Ensure default config (Auto mode)
Expand Down
65 changes: 58 additions & 7 deletions src-tauri/src/proxy/mappers/openai/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -682,14 +682,16 @@ pub fn transform_openai_request(
.any(|s| s.contains("You are Antigravity"));

let mut parts = Vec::new();
let global_prompt_config = crate::proxy::config::get_global_system_prompt();
let should_inject_default_prompt =
global_prompt_config.include_default_prompt && !user_has_antigravity;

// 1. Antigravity 身份 (如果需要, 作为独立 Part 插入)
if !user_has_antigravity {
if should_inject_default_prompt {
parts.push(json!({"text": antigravity_identity}));
}

// 2. [NEW] 注入全局系统提示词 (紧跟 Antigravity 身份之后)
let global_prompt_config = crate::proxy::config::get_global_system_prompt();
if global_prompt_config.enabled && !global_prompt_config.content.trim().is_empty() {
parts.push(json!({"text": global_prompt_config.content}));
}
Expand All @@ -699,10 +701,12 @@ pub fn transform_openai_request(
parts.push(json!({"text": inst}));
}

inner_request["systemInstruction"] = json!({
"role": "user",
"parts": parts
});
if !parts.is_empty() {
inner_request["systemInstruction"] = json!({
"role": "user",
"parts": parts
});
}

if config.inject_google_search {
crate::proxy::mappers::common_utils::inject_google_search_tool(&mut inner_request, Some(mapped_model));
Expand Down Expand Up @@ -769,9 +773,56 @@ fn enforce_uppercase_types(value: &mut Value) {
#[cfg(test)]
mod tests {
use super::*;
use crate::proxy::mappers::openai::models::*;
use crate::proxy::config::{update_global_system_prompt_config, GlobalSystemPromptConfig};

#[test]
fn test_default_system_prompt_can_be_disabled_openai() {
update_global_system_prompt_config(GlobalSystemPromptConfig {
enabled: false,
content: String::new(),
include_default_prompt: false,
});

let req = OpenAIRequest {
model: "gpt-4".to_string(),
messages: vec![
OpenAIMessage {
role: "system".to_string(),
content: Some(OpenAIContent::String("Custom system only".to_string())),
reasoning_content: None,
tool_calls: None,
tool_call_id: None,
name: None,
},
OpenAIMessage {
role: "user".to_string(),
content: Some(OpenAIContent::String("Hello".to_string())),
reasoning_content: None,
tool_calls: None,
tool_call_id: None,
name: None,
},
],
..Default::default()
};

let (result, _, _) =
transform_openai_request(&req, "test-project", "gemini-2.0-flash", None);
let parts = result["request"]["systemInstruction"]["parts"]
.as_array()
.unwrap();
let system_text = parts
.iter()
.filter_map(|part| part.get("text").and_then(|text| text.as_str()))
.collect::<Vec<_>>()
.join("\n");

assert!(!system_text.contains("You are Antigravity"));
assert!(system_text.contains("Custom system only"));

update_global_system_prompt_config(GlobalSystemPromptConfig::default());
}

#[test]
fn test_issue_1592_gemini_3_pro_budget_capping() {
// [FIX #1592] Regression test for gemini-3-pro thinking budget capping
Expand Down
Loading