Skip to content

Commit 9412768

Browse files
committed
fix: stabilize v1.7.0
1 parent 26751bc commit 9412768

File tree

9 files changed

+279
-27
lines changed

9 files changed

+279
-27
lines changed

src-tauri/crates/agent/src/provider_safety.rs

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use aster::conversation::message::{Message, MessageContent};
22
use aster::model::ModelConfig;
33
use aster::providers::base::{
44
LeadWorkerProviderTrait, MessageStream, Provider, ProviderMetadata, ProviderUsage,
5+
SessionNameGenerationExecutionStrategy,
56
};
67
use aster::providers::errors::ProviderError;
78
use aster::providers::RetryConfig;
@@ -201,6 +202,17 @@ impl Provider for ProviderSafety {
201202
self.inner.get_active_model_name()
202203
}
203204

205+
async fn generate_session_name(
206+
&self,
207+
messages: &aster::conversation::Conversation,
208+
) -> Result<String, ProviderError> {
209+
self.inner.generate_session_name(messages).await
210+
}
211+
212+
fn session_name_generation_execution_strategy(&self) -> SessionNameGenerationExecutionStrategy {
213+
self.inner.session_name_generation_execution_strategy()
214+
}
215+
204216
async fn configure_oauth(&self) -> Result<(), ProviderError> {
205217
self.inner.configure_oauth().await
206218
}
@@ -212,8 +224,11 @@ mod tests {
212224
normalize_provider_messages, normalize_provider_model_config, wrap_provider_with_safety,
213225
};
214226
use aster::conversation::message::{Message, MessageContent};
227+
use aster::conversation::Conversation;
215228
use aster::model::ModelConfig;
216-
use aster::providers::base::{Provider, ProviderMetadata, ProviderUsage, Usage};
229+
use aster::providers::base::{
230+
Provider, ProviderMetadata, ProviderUsage, SessionNameGenerationExecutionStrategy, Usage,
231+
};
217232
use aster::providers::errors::ProviderError;
218233
use async_trait::async_trait;
219234
use rmcp::model::{CallToolRequestParam, CallToolResult, ErrorCode, ErrorData, Tool};
@@ -323,6 +338,55 @@ mod tests {
323338
}
324339
}
325340

341+
#[derive(Clone)]
342+
struct SessionNamingProvider {
343+
model_config: ModelConfig,
344+
}
345+
346+
#[async_trait]
347+
impl Provider for SessionNamingProvider {
348+
fn metadata() -> ProviderMetadata
349+
where
350+
Self: Sized,
351+
{
352+
ProviderMetadata::empty()
353+
}
354+
355+
fn get_name(&self) -> &str {
356+
"session-naming"
357+
}
358+
359+
async fn complete_with_model(
360+
&self,
361+
model_config: &ModelConfig,
362+
_system: &str,
363+
_messages: &[Message],
364+
_tools: &[Tool],
365+
) -> Result<(Message, ProviderUsage), ProviderError> {
366+
Ok((
367+
Message::assistant().with_text("ok"),
368+
ProviderUsage::new(model_config.model_name.clone(), Usage::default()),
369+
))
370+
}
371+
372+
fn get_model_config(&self) -> ModelConfig {
373+
self.model_config.clone()
374+
}
375+
376+
async fn generate_session_name(
377+
&self,
378+
_messages: &aster::conversation::Conversation,
379+
) -> Result<String, ProviderError> {
380+
Ok("wrapped-title".to_string())
381+
}
382+
383+
fn session_name_generation_execution_strategy(
384+
&self,
385+
) -> SessionNameGenerationExecutionStrategy {
386+
SessionNameGenerationExecutionStrategy::AfterReply
387+
}
388+
}
389+
326390
#[tokio::test]
327391
async fn wrap_provider_with_safety_should_disable_fast_model_for_complete_fast() {
328392
let seen_models = Arc::new(Mutex::new(Vec::new()));
@@ -370,6 +434,36 @@ mod tests {
370434
);
371435
}
372436

437+
#[tokio::test]
438+
async fn wrap_provider_with_safety_should_forward_session_name_generation() {
439+
let provider = Arc::new(SessionNamingProvider {
440+
model_config: ModelConfig::new("deepseek-r1:latest").expect("create model config"),
441+
});
442+
let wrapped = wrap_provider_with_safety(provider, false);
443+
let messages =
444+
Conversation::new(vec![Message::user().with_text("你好")]).expect("conversation");
445+
446+
let generated = wrapped
447+
.generate_session_name(&messages)
448+
.await
449+
.expect("generate session name");
450+
451+
assert_eq!(generated, "wrapped-title");
452+
}
453+
454+
#[test]
455+
fn wrap_provider_with_safety_should_forward_session_name_strategy() {
456+
let provider = Arc::new(SessionNamingProvider {
457+
model_config: ModelConfig::new("deepseek-r1:latest").expect("create model config"),
458+
});
459+
let wrapped = wrap_provider_with_safety(provider, false);
460+
461+
assert_eq!(
462+
wrapped.session_name_generation_execution_strategy(),
463+
SessionNameGenerationExecutionStrategy::AfterReply
464+
);
465+
}
466+
373467
#[test]
374468
fn normalize_provider_messages_should_remove_orphan_tool_response() {
375469
let messages = vec![

src-tauri/crates/aster-rust/crates/aster/src/agents/agent.rs

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ use crate::model::ModelConfig;
4040
use crate::permission::permission_inspector::PermissionInspector;
4141
use crate::permission::permission_judge::PermissionCheckResult;
4242
use crate::permission::PermissionConfirmation;
43-
use crate::providers::base::Provider;
43+
use crate::providers::base::{Provider, SessionNameGenerationExecutionStrategy};
4444
use crate::providers::errors::ProviderError;
4545
use crate::recipe::{Author, Recipe, Response, Settings, SubRecipe};
4646
use crate::scheduler_trait::SchedulerTrait;
@@ -3330,17 +3330,26 @@ impl Agent {
33303330
let provider = self.provider().await?;
33313331
let session_for_name = session.clone().without_messages();
33323332
let conversation_for_name = conversation.clone();
3333-
tokio::spawn(async move {
3334-
if let Err(e) = SessionManager::maybe_update_name_for_session(
3335-
&session_for_name,
3336-
&conversation_for_name,
3337-
provider,
3338-
)
3339-
.await
3340-
{
3341-
warn!("Failed to generate session description: {}", e);
3342-
}
3343-
});
3333+
let deferred_session_name_generation =
3334+
match provider.session_name_generation_execution_strategy() {
3335+
SessionNameGenerationExecutionStrategy::Background => {
3336+
tokio::spawn(async move {
3337+
if let Err(e) = SessionManager::maybe_update_name_for_session(
3338+
&session_for_name,
3339+
&conversation_for_name,
3340+
provider,
3341+
)
3342+
.await
3343+
{
3344+
warn!("Failed to generate session description: {}", e);
3345+
}
3346+
});
3347+
None
3348+
}
3349+
SessionNameGenerationExecutionStrategy::AfterReply => {
3350+
Some((session_for_name, conversation_for_name, provider))
3351+
}
3352+
};
33443353
let working_dir = session.working_dir.clone();
33453354

33463355
Ok(Box::pin(async_stream::try_stream! {
@@ -3801,6 +3810,22 @@ impl Agent {
38013810

38023811
tokio::task::yield_now().await;
38033812
}
3813+
3814+
if let Some((session_for_name, conversation_for_name, provider)) =
3815+
deferred_session_name_generation
3816+
{
3817+
tokio::spawn(async move {
3818+
if let Err(e) = SessionManager::maybe_update_name_for_session(
3819+
&session_for_name,
3820+
&conversation_for_name,
3821+
provider,
3822+
)
3823+
.await
3824+
{
3825+
warn!("Failed to generate session description: {}", e);
3826+
}
3827+
});
3828+
}
38043829
}))
38053830
}
38063831

src-tauri/crates/aster-rust/crates/aster/src/network/proxy.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,31 @@ pub fn should_bypass_proxy(target_url: &str, no_proxy: &[String]) -> bool {
186186
false
187187
}
188188

189+
/// 检查目标 URL 是否应直接绕过系统代理。
190+
///
191+
/// 主要用于本地 loopback / unspecified 地址,避免本机服务请求被系统代理截流。
192+
pub fn should_bypass_system_proxy_for_url(target_url: &str) -> bool {
193+
let Ok(url) = Url::parse(target_url) else {
194+
return false;
195+
};
196+
197+
let Some(hostname) = url.host_str() else {
198+
return false;
199+
};
200+
201+
if matches!(
202+
hostname,
203+
"localhost" | "127.0.0.1" | "::1" | "0.0.0.0" | "host.docker.internal"
204+
) {
205+
return true;
206+
}
207+
208+
hostname
209+
.parse::<std::net::IpAddr>()
210+
.map(|ip| ip.is_loopback() || ip.is_unspecified())
211+
.unwrap_or(false)
212+
}
213+
189214
/// 获取目标 URL 的代理 URL
190215
pub fn get_proxy_for_url(target_url: &str, config: &ProxyConfig) -> Option<String> {
191216
// 检查是否绕过代理

src-tauri/crates/aster-rust/crates/aster/src/network/tests.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,25 @@ fn test_should_bypass_proxy_all() {
4848
assert!(should_bypass_proxy("http://any.domain.com", &no_proxy));
4949
}
5050

51+
#[test]
52+
fn test_should_bypass_system_proxy_for_loopback_url() {
53+
assert!(should_bypass_system_proxy_for_url(
54+
"http://127.0.0.1:11434/api/chat"
55+
));
56+
assert!(should_bypass_system_proxy_for_url(
57+
"http://localhost:11434/api/tags"
58+
));
59+
assert!(should_bypass_system_proxy_for_url("http://0.0.0.0:3000"));
60+
}
61+
62+
#[test]
63+
fn test_should_not_bypass_system_proxy_for_remote_url() {
64+
assert!(!should_bypass_system_proxy_for_url(
65+
"https://api.openai.com/v1/chat/completions"
66+
));
67+
assert!(!should_bypass_system_proxy_for_url("https://example.com"));
68+
}
69+
5170
#[test]
5271
fn test_timeout_config_default() {
5372
let config = TimeoutConfig::default();

src-tauri/crates/aster-rust/crates/aster/src/providers/api_client.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::network::should_bypass_system_proxy_for_url;
12
use crate::session_context::SESSION_ID_HEADER;
23
use anyhow::Result;
34
use async_trait::async_trait;
@@ -205,6 +206,10 @@ impl ApiClient {
205206

206207
pub fn with_timeout(host: String, auth: AuthMethod, timeout: Duration) -> Result<Self> {
207208
let mut client_builder = Client::builder().timeout(timeout);
209+
if should_bypass_system_proxy_for_url(&host) {
210+
tracing::info!("[ApiClient] 本地地址绕过系统代理: {}", host);
211+
client_builder = client_builder.no_proxy();
212+
}
208213

209214
// Configure TLS if needed
210215
let tls_config = TlsConfig::from_config()?;
@@ -228,6 +233,10 @@ impl ApiClient {
228233
let mut client_builder = Client::builder()
229234
.timeout(self.timeout)
230235
.default_headers(self.default_headers.clone());
236+
if should_bypass_system_proxy_for_url(&self.host) {
237+
tracing::info!("[ApiClient] 重建客户端时绕过系统代理: {}", self.host);
238+
client_builder = client_builder.no_proxy();
239+
}
231240

232241
// Configure TLS if needed
233242
if let Some(ref tls_config) = self.tls_config {
@@ -408,6 +417,23 @@ impl fmt::Debug for ApiClient {
408417
mod tests {
409418
use super::*;
410419

420+
#[test]
421+
fn should_bypass_proxy_for_loopback_host() {
422+
assert!(should_bypass_system_proxy_for_url("http://127.0.0.1:11434"));
423+
assert!(should_bypass_system_proxy_for_url(
424+
"http://localhost:11434/api"
425+
));
426+
assert!(should_bypass_system_proxy_for_url("http://0.0.0.0:1234"));
427+
}
428+
429+
#[test]
430+
fn should_not_bypass_proxy_for_remote_host() {
431+
assert!(!should_bypass_system_proxy_for_url(
432+
"https://api.openai.com/v1"
433+
));
434+
assert!(!should_bypass_system_proxy_for_url("https://example.com"));
435+
}
436+
411437
#[tokio::test]
412438
async fn test_session_id_header_injection() {
413439
let client = ApiClient::new(

src-tauri/crates/aster-rust/crates/aster/src/providers/base.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ pub fn get_current_model() -> Option<String> {
3535

3636
pub static MSG_COUNT_FOR_SESSION_NAME_GENERATION: usize = 3;
3737

38+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39+
pub enum SessionNameGenerationExecutionStrategy {
40+
Background,
41+
AfterReply,
42+
}
43+
3844
/// Information about a model's capabilities
3945
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, PartialEq)]
4046
pub struct ModelInfo {
@@ -577,6 +583,10 @@ pub trait Provider: Send + Sync {
577583
Ok(safe_truncate(&description, 100))
578584
}
579585

586+
fn session_name_generation_execution_strategy(&self) -> SessionNameGenerationExecutionStrategy {
587+
SessionNameGenerationExecutionStrategy::Background
588+
}
589+
580590
// Generate a prompt for a session name based on the conversation history
581591
fn create_session_name_prompt(&self, context: &[String]) -> String {
582592
// Create a prompt for a concise description

src-tauri/crates/aster-rust/crates/aster/src/providers/ollama.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use super::api_client::{ApiClient, AuthMethod};
2-
use super::base::{ConfigKey, MessageStream, Provider, ProviderMetadata, ProviderUsage, Usage};
2+
use super::base::{
3+
ConfigKey, MessageStream, Provider, ProviderMetadata, ProviderUsage,
4+
SessionNameGenerationExecutionStrategy, Usage,
5+
};
36
use super::errors::ProviderError;
47
use super::retry::ProviderRetry;
58
use super::utils::{
@@ -278,6 +281,10 @@ impl Provider for OllamaProvider {
278281
Ok(safe_truncate(&description, 100))
279282
}
280283

284+
fn session_name_generation_execution_strategy(&self) -> SessionNameGenerationExecutionStrategy {
285+
SessionNameGenerationExecutionStrategy::AfterReply
286+
}
287+
281288
fn supports_streaming(&self) -> bool {
282289
self.supports_streaming
283290
}

src-tauri/crates/aster-rust/crates/aster/src/providers/toolshim.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ use super::ollama::OLLAMA_HOST;
3737
use crate::conversation::message::{Message, MessageContent};
3838
use crate::conversation::Conversation;
3939
use crate::model::ModelConfig;
40+
use crate::network::should_bypass_system_proxy_for_url;
4041
use crate::providers::formats::openai::create_request;
4142
use anyhow::Result;
4243
use reqwest::Client;
@@ -76,13 +77,19 @@ impl OllamaInterpreter {
7677
}
7778

7879
pub fn new_with_model(interpreter_model: Option<String>) -> Result<Self, ProviderError> {
79-
let client = Client::builder()
80-
.timeout(Duration::from_secs(600))
80+
let base_url = Self::get_ollama_base_url()?;
81+
let mut client_builder = Client::builder().timeout(Duration::from_secs(600));
82+
if should_bypass_system_proxy_for_url(&base_url) {
83+
tracing::info!(
84+
"[ToolShim] 本地 Ollama 结构化请求绕过系统代理: {}",
85+
base_url
86+
);
87+
client_builder = client_builder.no_proxy();
88+
}
89+
let client = client_builder
8190
.build()
8291
.expect("Failed to create HTTP client");
8392

84-
let base_url = Self::get_ollama_base_url()?;
85-
8693
Ok(Self {
8794
client,
8895
base_url,

0 commit comments

Comments
 (0)