This commit is contained in:
pavel 2026-02-11 17:45:04 +01:00
commit beab32894b
3 changed files with 128 additions and 132 deletions

View file

@ -1,4 +1,4 @@
use crate::domain::agent::api::{ChatRequest, ChatResponse, Message};
use crate::domain::agent::api::Message;
use crate::error::AppError;
use crate::server::AppState;
use axum::{Json, extract::State};
@ -22,109 +22,36 @@ pub async fn chat_handler(
let msg_count = payload.messages.len();
tracing::info!("Received chat request with {} messages", msg_count);
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(60))
.build()
.map_err(|e| {
let err = format!("Failed to build HTTP client: {}", e);
tracing::error!("{}", err);
AppError::Internal(err)
})?;
let url = "https://opencode.ai/zen/v1/chat/completions";
let mut messages = payload.messages;
let tools = crate::domain::agent::tools::get_tools();
let max_turns = 10;
let mut turns = 0;
// Inject current date awareness if not already present or as a fresh system message
let now = chrono::Local::now();
let date_str = now.format("%A, %B %e, %Y at %l:%M %P").to_string();
messages.insert(
0,
Message {
role: "system".to_string(),
content: Some(format!(
"The current date and time is {}. Today is {}. You are in interactive chat mode.",
date_str,
now.format("%Y-%m-%d")
)),
tool_calls: None,
tool_call_id: None,
},
);
loop {
turns += 1;
if turns > max_turns {
return Err(AppError::Internal("Chat turn limit exceeded".into()));
}
let mut agent = crate::domain::agent::Agent::with_messages(
state.db.clone(),
state.config.zen_api_key.clone(),
state.config.tavily_api_key.clone(),
messages,
)?;
let request = ChatRequest {
model: "big-pickle".to_string(),
messages: messages.clone(),
tools: Some(tools.clone()),
};
tracing::info!("Starting interactive agent turn");
let assistant_message = agent.execute_turn().await?;
let mut request_builder = client.post(url).json(&request);
if let Some(key) = &state.config.zen_api_key {
request_builder = request_builder.header("Authorization", format!("Bearer {}", key));
}
let response = request_builder.send().await.map_err(|e| {
tracing::error!("Network error during chat completion: {}", e);
AppError::Network(e)
})?;
if !response.status().is_success() {
let status = response.status();
let error_text = response
.text()
.await
.unwrap_or_else(|_| "Unknown error".into());
let err = format!("API request failed: {} - {}", status, error_text);
tracing::error!("{}", err);
return Err(AppError::Internal(err));
}
let chat_response: ChatResponse = response.json().await.map_err(|e| {
let err = format!("Failed to parse LLM response: {}", e);
tracing::error!("{}", err);
AppError::Internal(err)
})?;
let assistant_message = chat_response
.choices
.get(0)
.ok_or_else(|| {
let err = "Missing assistant response choices";
tracing::error!("{}", err);
AppError::Internal(err.into())
})?
.message
.clone();
messages.push(assistant_message.clone());
if let Some(tool_calls) = &assistant_message.tool_calls {
for tool_call in tool_calls {
tracing::info!("Chat agent calling tool: {}", tool_call.function.name);
let (tool_message, is_final, answer) =
crate::domain::agent::tools::handle_tool_call(
tool_call,
&state.config.tavily_api_key,
&state.db,
)
.await
.map_err(|e| AppError::Internal(format!("Tool execution failed: {}", e)))?;
messages.push(tool_message.clone());
if is_final {
tracing::info!("Chat agent finished via tool");
return Ok(Json(ChatResult {
message: Message {
role: "assistant".to_string(),
content: answer.or(tool_message.content),
tool_calls: None,
tool_call_id: None,
},
}));
}
}
// After tool calls, we loop back to get another assistant response
continue;
}
// If no tool calls, it's a final response for this turn
tracing::info!("Chat completion successful");
return Ok(Json(ChatResult {
message: assistant_message,
}));
}
Ok(Json(ChatResult {
message: assistant_message,
}))
}