feat: add support for Router provider and enhance command handling
This commit is contained in:
@@ -1,10 +1,11 @@
|
|||||||
use crate::model::settings::Settings;
|
use crate::model::settings::Settings;
|
||||||
|
|
||||||
pub const PROVIDERS: &[&str] = &["Zen API", "OpenAI"];
|
pub const PROVIDERS: &[&str] = &["Zen API", "Router", "OpenAI"];
|
||||||
|
|
||||||
pub fn set_provider(settings: &mut Settings, provider: &str) {
|
pub fn set_provider(settings: &mut Settings, provider: &str) {
|
||||||
settings.provider = match provider {
|
settings.provider = match provider {
|
||||||
"Zen API" => "zen".to_string(),
|
"Zen API" => "zen".to_string(),
|
||||||
|
"Router" => "router".to_string(),
|
||||||
"OpenAI" => "openai".to_string(),
|
"OpenAI" => "openai".to_string(),
|
||||||
_ => "zen".to_string(),
|
_ => "zen".to_string(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -577,7 +577,7 @@ fn run_agent_turn(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut stream_started = false;
|
let mut stream_started = false;
|
||||||
let (response, usage) = tc.client.chat_with_tools_streaming(
|
let (response, usage) = match tc.client.chat_with_tools_streaming(
|
||||||
&wire_msgs,
|
&wire_msgs,
|
||||||
Some(tc.tdefs.clone()),
|
Some(tc.tdefs.clone()),
|
||||||
Some(tc.temperature),
|
Some(tc.temperature),
|
||||||
@@ -602,8 +602,21 @@ fn run_agent_turn(
|
|||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
|
) {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(_stream_err) => {
|
||||||
|
// Provider doesn't support streaming — fall back to non-streaming
|
||||||
|
let (msg, usage_fb) = tc.client.chat_with_tools_non_streaming(
|
||||||
|
&wire_msgs, Some(tc.tdefs.clone()),
|
||||||
)?;
|
)?;
|
||||||
let _ = usage; // already emitted as TurnEvent::Usage inside the on_event callback, if present
|
if let Some((tok_in, tok_out)) = usage_fb {
|
||||||
|
if let Ok(mut q) = events_q.lock() {
|
||||||
|
q.push_back(TurnEvent::Usage { tokens_in: tok_in, tokens_out: tok_out });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(msg, usage_fb)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let has_tool_calls = response.tool_calls.is_some()
|
let has_tool_calls = response.tool_calls.is_some()
|
||||||
&& response.tool_calls.as_ref().is_some_and(|tc| !tc.is_empty());
|
&& response.tool_calls.as_ref().is_some_and(|tc| !tc.is_empty());
|
||||||
|
|||||||
@@ -72,11 +72,21 @@ const COMMANDS: &[&str] = &[
|
|||||||
"/lesson ls",
|
"/lesson ls",
|
||||||
"/lesson export",
|
"/lesson export",
|
||||||
"/lesson import",
|
"/lesson import",
|
||||||
|
"/lesson accept",
|
||||||
|
"/lesson reject",
|
||||||
"/mode chat",
|
"/mode chat",
|
||||||
"/mode bash",
|
"/mode bash",
|
||||||
"/mode help",
|
"/mode help",
|
||||||
"/mode settings",
|
"/mode settings",
|
||||||
|
"/mode effort",
|
||||||
|
"/mode mcp",
|
||||||
|
"/mode rewind",
|
||||||
|
"/mode editor",
|
||||||
"/login",
|
"/login",
|
||||||
|
"/login zen",
|
||||||
|
"/login openai",
|
||||||
|
"/edit",
|
||||||
|
"/mcp add",
|
||||||
];
|
];
|
||||||
|
|
||||||
impl InputState {
|
impl InputState {
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ impl Default for AppConfig {
|
|||||||
api_key_env: Some("API_KEY".to_string()),
|
api_key_env: Some("API_KEY".to_string()),
|
||||||
default_model: Some("deepseek-v4-flash-free".to_string()),
|
default_model: Some("deepseek-v4-flash-free".to_string()),
|
||||||
});
|
});
|
||||||
|
providers.insert("router".to_string(), ProviderConfig {
|
||||||
|
api_base: "https://9router.asepharyana.my.id/v1".to_string(),
|
||||||
|
api_key_env: Some("ROUTER_API_KEY".to_string()),
|
||||||
|
default_model: Some("claude-opus-4-8".to_string()),
|
||||||
|
});
|
||||||
let mut model_roles = HashMap::new();
|
let mut model_roles = HashMap::new();
|
||||||
model_roles.insert("default".to_string(), ModelRole {
|
model_roles.insert("default".to_string(), ModelRole {
|
||||||
provider: "zen".to_string(),
|
provider: "zen".to_string(),
|
||||||
|
|||||||
+15
-3
@@ -48,6 +48,15 @@ impl LlmClient {
|
|||||||
messages: &[ChatMessage],
|
messages: &[ChatMessage],
|
||||||
tools: Option<Vec<ToolDef>>,
|
tools: Option<Vec<ToolDef>>,
|
||||||
) -> Result<ChatMessage> {
|
) -> Result<ChatMessage> {
|
||||||
|
let (msg, _usage) = self.chat_with_tools_non_streaming(messages, tools)?;
|
||||||
|
Ok(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn chat_with_tools_non_streaming(
|
||||||
|
&self,
|
||||||
|
messages: &[ChatMessage],
|
||||||
|
tools: Option<Vec<ToolDef>>,
|
||||||
|
) -> Result<(ChatMessage, Option<(u64, u64)>)> {
|
||||||
let req = ChatRequest {
|
let req = ChatRequest {
|
||||||
model: self.model.clone(),
|
model: self.model.clone(),
|
||||||
messages: messages.to_vec(),
|
messages: messages.to_vec(),
|
||||||
@@ -75,7 +84,7 @@ impl LlmClient {
|
|||||||
http_req = http_req.header("Authorization", format!("Bearer {}", self.api_key));
|
http_req = http_req.header("Authorization", format!("Bearer {}", self.api_key));
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = (|| -> Result<ChatMessage> {
|
let result = (|| -> Result<(ChatMessage, Option<(u64, u64)>)> {
|
||||||
let resp = http_req.json(&req).send().map_err(|e| {
|
let resp = http_req.json(&req).send().map_err(|e| {
|
||||||
if e.is_timeout() {
|
if e.is_timeout() {
|
||||||
anyhow::anyhow!("API request timed out after {:?}. Check your network or try again.", REQUEST_TIMEOUT)
|
anyhow::anyhow!("API request timed out after {:?}. Check your network or try again.", REQUEST_TIMEOUT)
|
||||||
@@ -93,17 +102,20 @@ impl LlmClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let data: crate::dto::provider::response::ChatResponse = resp.json()?;
|
let data: crate::dto::provider::response::ChatResponse = resp.json()?;
|
||||||
|
let usage = data.usage.map(|u| {
|
||||||
|
(u.prompt_tokens.unwrap_or(0) as u64, u.completion_tokens.unwrap_or(0) as u64)
|
||||||
|
});
|
||||||
let message = data
|
let message = data
|
||||||
.choices
|
.choices
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.next()
|
.next()
|
||||||
.map(|c| c.message)
|
.map(|c| c.message)
|
||||||
.ok_or_else(|| anyhow::anyhow!("API response had no choices"))?;
|
.ok_or_else(|| anyhow::anyhow!("API response had no choices"))?;
|
||||||
Ok(message)
|
Ok((message, usage))
|
||||||
})();
|
})();
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(msg) => return Ok(msg),
|
Ok((msg, usage)) => return Ok((msg, usage)),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if attempt >= max_retries {
|
if attempt >= max_retries {
|
||||||
return Err(e);
|
return Err(e);
|
||||||
|
|||||||
Reference in New Issue
Block a user