Refactor API integration and enhance command handling
- Removed unused modules and updated module paths for clarity. - Added autocomplete functionality for command input in InputState. - Updated AppStateRest to include a method for checking if a turn is in flight. - Refactored subagent engine to use new API client structure. - Changed default provider from "openrouter" to "zen" with updated API keys and models. - Implemented tests for memory management and edit log functionalities. - Enhanced error handling in API requests and improved response parsing. - Updated UI components to reflect new API provider and status indicators.
This commit is contained in:
+1
-1
@@ -1,2 +1,2 @@
|
||||
pub mod openrouter;
|
||||
pub mod provider;
|
||||
pub mod oauth;
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::dto::chat::message::ChatMessage;
|
||||
use crate::dto::openrouter::request::ToolDef;
|
||||
|
||||
pub struct OpenRouterClient {
|
||||
pub client: reqwest::blocking::Client,
|
||||
pub api_key: String,
|
||||
pub base_url: String,
|
||||
pub model: String,
|
||||
}
|
||||
|
||||
impl OpenRouterClient {
|
||||
pub fn new(api_key: String, model: String) -> Self {
|
||||
OpenRouterClient {
|
||||
client: reqwest::blocking::Client::new(),
|
||||
api_key,
|
||||
base_url: "https://openrouter.ai/api/v1".to_string(),
|
||||
model,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chat(&self, messages: &[ChatMessage]) -> Result<String> {
|
||||
let response = self.chat_with_tools(messages, None)?;
|
||||
Ok(response.content.unwrap_or_default())
|
||||
}
|
||||
|
||||
pub fn chat_with_tools(
|
||||
&self,
|
||||
messages: &[ChatMessage],
|
||||
tools: Option<Vec<ToolDef>>,
|
||||
) -> Result<ChatMessage> {
|
||||
let req = crate::dto::openrouter::request::ChatRequest {
|
||||
model: self.model.clone(),
|
||||
messages: messages.to_vec(),
|
||||
max_tokens: Some(4096),
|
||||
temperature: Some(0.7),
|
||||
tools,
|
||||
stream: Some(false),
|
||||
top_p: None,
|
||||
stop: None,
|
||||
};
|
||||
|
||||
let resp = self.client
|
||||
.post(format!("{}/chat/completions", self.base_url))
|
||||
.header("Authorization", format!("Bearer {}", self.api_key))
|
||||
.header("Content-Type", "application/json")
|
||||
.json(&req)
|
||||
.send()?;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
let status = resp.status();
|
||||
let body = resp.text().unwrap_or_default();
|
||||
anyhow::bail!("OpenRouter API error {}: {}", status, body);
|
||||
}
|
||||
|
||||
let data: crate::dto::openrouter::response::ChatResponse = resp.json()?;
|
||||
let message = data
|
||||
.choices
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|c| c.message)
|
||||
.ok_or_else(|| anyhow::anyhow!("OpenRouter response had no choices"))?;
|
||||
Ok(message)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
use std::time::Duration;
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::dto::chat::message::ChatMessage;
|
||||
use crate::dto::provider::request::ToolDef;
|
||||
|
||||
const DEFAULT_BASE_URL: &str = "https://opencode.ai/zen/v1";
|
||||
const DEFAULT_MODEL: &str = "deepseek-v4-flash-free";
|
||||
const CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
const REQUEST_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
|
||||
pub struct LlmClient {
|
||||
pub client: reqwest::blocking::Client,
|
||||
pub api_key: String,
|
||||
pub base_url: String,
|
||||
pub model: String,
|
||||
}
|
||||
|
||||
impl LlmClient {
|
||||
pub fn new(api_key: String, model: String) -> Self {
|
||||
let model = if model.is_empty() {
|
||||
DEFAULT_MODEL.to_string()
|
||||
} else {
|
||||
model
|
||||
};
|
||||
let client = reqwest::blocking::Client::builder()
|
||||
.timeout(REQUEST_TIMEOUT)
|
||||
.connect_timeout(CONNECT_TIMEOUT)
|
||||
.build()
|
||||
.unwrap_or_else(|_| reqwest::blocking::Client::new());
|
||||
LlmClient {
|
||||
client,
|
||||
api_key,
|
||||
base_url: DEFAULT_BASE_URL.to_string(),
|
||||
model,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chat(&self, messages: &[ChatMessage]) -> Result<String> {
|
||||
let response = self.chat_with_tools(messages, None)?;
|
||||
Ok(response.content.unwrap_or_default())
|
||||
}
|
||||
|
||||
pub fn chat_with_tools(
|
||||
&self,
|
||||
messages: &[ChatMessage],
|
||||
tools: Option<Vec<ToolDef>>,
|
||||
) -> Result<ChatMessage> {
|
||||
let req = crate::dto::provider::request::ChatRequest {
|
||||
model: self.model.clone(),
|
||||
messages: messages.to_vec(),
|
||||
max_tokens: Some(4096),
|
||||
temperature: Some(0.7),
|
||||
tools,
|
||||
stream: Some(false),
|
||||
top_p: None,
|
||||
stop: None,
|
||||
};
|
||||
|
||||
let url = format!("{}/chat/completions", self.base_url);
|
||||
let mut http_req = self.client
|
||||
.post(&url)
|
||||
.header("Content-Type", "application/json");
|
||||
|
||||
if !self.api_key.is_empty() {
|
||||
http_req = http_req.header("Authorization", format!("Bearer {}", self.api_key));
|
||||
}
|
||||
|
||||
let resp = http_req.json(&req).send().map_err(|e| {
|
||||
if e.is_timeout() {
|
||||
anyhow::anyhow!("API request timed out after {:?}. Check your network or try again.", REQUEST_TIMEOUT)
|
||||
} else if e.is_connect() {
|
||||
anyhow::anyhow!("Could not connect to {}. Is the URL correct and is the service reachable?", self.base_url)
|
||||
} else {
|
||||
anyhow::anyhow!("API request failed: {}", e)
|
||||
}
|
||||
})?;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
let status = resp.status();
|
||||
let body = resp.text().unwrap_or_default();
|
||||
anyhow::bail!("API error {} from {}: {}", status, self.base_url, body);
|
||||
}
|
||||
|
||||
let data: crate::dto::provider::response::ChatResponse = resp.json()?;
|
||||
let message = data
|
||||
.choices
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|c| c.message)
|
||||
.ok_or_else(|| anyhow::anyhow!("API response had no choices"))?;
|
||||
Ok(message)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user