fix(security): resolve rebase conflicts and provider regressions

This commit is contained in:
Chummy 2026-02-17 19:01:36 +08:00
parent 5d131a8903
commit 0087bcc496
4 changed files with 23 additions and 44 deletions

View file

@ -281,16 +281,12 @@ fn parse_sse_line(line: &str) -> StreamResult<Option<String>> {
} }
/// Convert SSE byte stream to text chunks. /// Convert SSE byte stream to text chunks.
async fn sse_bytes_to_chunks( fn sse_bytes_to_chunks(
mut response: reqwest::Response, response: reqwest::Response,
count_tokens: bool, count_tokens: bool,
) -> stream::BoxStream<'static, StreamResult<StreamChunk>> { ) -> stream::BoxStream<'static, StreamResult<StreamChunk>> {
use tokio::io::AsyncBufReadExt;
let name = "stream".to_string();
// Create a channel to send chunks // Create a channel to send chunks
let (mut tx, rx) = tokio::sync::mpsc::channel::<StreamResult<StreamChunk>>(100); let (tx, rx) = tokio::sync::mpsc::channel::<StreamResult<StreamChunk>>(100);
tokio::spawn(async move { tokio::spawn(async move {
// Buffer for incomplete lines // Buffer for incomplete lines
@ -341,10 +337,7 @@ async fn sse_bytes_to_chunks(
return; // Receiver dropped return; // Receiver dropped
} }
} }
Ok(None) => { Ok(None) => {}
// Empty line or [DONE] sentinel - continue
continue;
}
Err(e) => { Err(e) => {
let _ = tx.send(Err(e)).await; let _ = tx.send(Err(e)).await;
return; return;
@ -365,10 +358,7 @@ async fn sse_bytes_to_chunks(
// Convert channel receiver to stream // Convert channel receiver to stream
stream::unfold(rx, |mut rx| async { stream::unfold(rx, |mut rx| async {
match rx.recv().await { rx.recv().await.map(|chunk| (chunk, rx))
Some(chunk) => Some((chunk, rx)),
None => None,
}
}) })
.boxed() .boxed()
} }
@ -692,7 +682,7 @@ impl Provider for OpenAiCompatibleProvider {
temperature: f64, temperature: f64,
options: StreamOptions, options: StreamOptions,
) -> stream::BoxStream<'static, StreamResult<StreamChunk>> { ) -> stream::BoxStream<'static, StreamResult<StreamChunk>> {
let api_key = match self.api_key.as_ref() { let credential = match self.credential.as_ref() {
Some(key) => key.clone(), Some(key) => key.clone(),
None => { None => {
let provider_name = self.name.clone(); let provider_name = self.name.clone();
@ -739,10 +729,10 @@ impl Provider for OpenAiCompatibleProvider {
// Apply auth header // Apply auth header
req_builder = match &auth_header { req_builder = match &auth_header {
AuthStyle::Bearer => { AuthStyle::Bearer => {
req_builder.header("Authorization", format!("Bearer {}", api_key)) req_builder.header("Authorization", format!("Bearer {}", credential))
} }
AuthStyle::XApiKey => req_builder.header("x-api-key", &api_key), AuthStyle::XApiKey => req_builder.header("x-api-key", &credential),
AuthStyle::Custom(header) => req_builder.header(header, &api_key), AuthStyle::Custom(header) => req_builder.header(header, &credential),
}; };
// Set accept header for streaming // Set accept header for streaming
@ -771,7 +761,7 @@ impl Provider for OpenAiCompatibleProvider {
} }
// Convert to chunk stream and forward to channel // Convert to chunk stream and forward to channel
let mut chunk_stream = sse_bytes_to_chunks(response, options.count_tokens).await; let mut chunk_stream = sse_bytes_to_chunks(response, options.count_tokens);
while let Some(chunk) = chunk_stream.next().await { while let Some(chunk) = chunk_stream.next().await {
if tx.send(chunk).await.is_err() { if tx.send(chunk).await.is_err() {
break; // Receiver dropped break; // Receiver dropped
@ -781,10 +771,7 @@ impl Provider for OpenAiCompatibleProvider {
// Convert channel receiver to stream // Convert channel receiver to stream
stream::unfold(rx, |mut rx| async move { stream::unfold(rx, |mut rx| async move {
match rx.recv().await { rx.recv().await.map(|chunk| (chunk, rx))
Some(chunk) => Some((chunk, rx)),
None => None,
}
}) })
.boxed() .boxed()
} }

View file

@ -409,7 +409,7 @@ impl Provider for OpenRouterProvider {
model: &str, model: &str,
temperature: f64, temperature: f64,
) -> anyhow::Result<ProviderChatResponse> { ) -> anyhow::Result<ProviderChatResponse> {
let api_key = self.api_key.as_ref().ok_or_else(|| { let credential = self.credential.as_ref().ok_or_else(|| {
anyhow::anyhow!( anyhow::anyhow!(
"OpenRouter API key not set. Run `zeroclaw onboard` or set OPENROUTER_API_KEY env var." "OpenRouter API key not set. Run `zeroclaw onboard` or set OPENROUTER_API_KEY env var."
) )
@ -462,7 +462,7 @@ impl Provider for OpenRouterProvider {
let response = self let response = self
.client .client
.post("https://openrouter.ai/api/v1/chat/completions") .post("https://openrouter.ai/api/v1/chat/completions")
.header("Authorization", format!("Bearer {api_key}")) .header("Authorization", format!("Bearer {credential}"))
.header( .header(
"HTTP-Referer", "HTTP-Referer",
"https://github.com/theonlyhennygod/zeroclaw", "https://github.com/theonlyhennygod/zeroclaw",

View file

@ -329,21 +329,11 @@ pub trait Provider: Send + Sync {
/// Default implementation falls back to stream_chat_with_system with last user message. /// Default implementation falls back to stream_chat_with_system with last user message.
fn stream_chat_with_history( fn stream_chat_with_history(
&self, &self,
messages: &[ChatMessage], _messages: &[ChatMessage],
model: &str, _model: &str,
temperature: f64, _temperature: f64,
options: StreamOptions, _options: StreamOptions,
) -> stream::BoxStream<'static, StreamResult<StreamChunk>> { ) -> stream::BoxStream<'static, StreamResult<StreamChunk>> {
let system = messages
.iter()
.find(|m| m.role == "system")
.map(|m| m.content.clone());
let last_user = messages
.iter()
.rfind(|m| m.role == "user")
.map(|m| m.content.clone())
.unwrap_or_default();
// For default implementation, we need to convert to owned strings // For default implementation, we need to convert to owned strings
// This is a limitation of the default implementation // This is a limitation of the default implementation
let provider_name = "unknown".to_string(); let provider_name = "unknown".to_string();

View file

@ -94,14 +94,16 @@ impl Tool for HardwareMemoryReadTool {
.get("address") .get("address")
.and_then(|v| v.as_str()) .and_then(|v| v.as_str())
.unwrap_or("0x20000000"); .unwrap_or("0x20000000");
let address = parse_hex_address(address_str).unwrap_or(NUCLEO_RAM_BASE); let _address = parse_hex_address(address_str).unwrap_or(NUCLEO_RAM_BASE);
let length = args.get("length").and_then(|v| v.as_u64()).unwrap_or(128) as usize; let requested_length = args.get("length").and_then(|v| v.as_u64()).unwrap_or(128);
let length = length.min(256).max(1); let _length = usize::try_from(requested_length)
.unwrap_or(256)
.clamp(1, 256);
#[cfg(feature = "probe")] #[cfg(feature = "probe")]
{ {
match probe_read_memory(chip.unwrap(), address, length) { match probe_read_memory(chip.unwrap(), _address, _length) {
Ok(output) => { Ok(output) => {
return Ok(ToolResult { return Ok(ToolResult {
success: true, success: true,