Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/target
*.log
.context/
15 changes: 8 additions & 7 deletions crates/coco-tui/src/components/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,14 +197,15 @@ impl Default for Inner {
enum ChatState {
#[default]
Ready,
Procesing,
#[serde(alias = "Procesing")]
Processing,
}

impl std::fmt::Display for ChatState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Ready => f.write_str("Ready"),
Self::Procesing => f.write_str("Procesing"),
Self::Processing => f.write_str("Processing"),
}
}
}
Expand Down Expand Up @@ -428,7 +429,7 @@ impl Chat<'static> {
if state.focus == Focus::ShortcutHints {
state.focus = self.prev_focus.clone().unwrap_or_default();
}
if state.state == ChatState::Procesing {
if state.state == ChatState::Processing {
// Persist Ready to avoid restoring a stale processing state.
state.state = ChatState::Ready;
}
Expand Down Expand Up @@ -1230,8 +1231,8 @@ impl Chat<'static> {
}

fn set_processing(&mut self) {
if self.state.state != ChatState::Procesing {
self.state.write().state = ChatState::Procesing;
if self.state.state != ChatState::Processing {
self.state.write().state = ChatState::Processing;
}
}

Expand Down Expand Up @@ -1645,7 +1646,7 @@ impl Chat<'static> {
ChatState::Ready => {
Line::from(Span::styled(format!(" {state} "), theme.ui.status_ready))
}
ChatState::Procesing => Line::from(vec![
ChatState::Processing => Line::from(vec![
Span::raw(" "),
Throbber::default()
.throbber_set(BRAILLE_EIGHT_DOUBLE)
Expand Down Expand Up @@ -2468,7 +2469,7 @@ impl Component for Chat<'static> {
fn on_tick(&mut self) {
self.cancellation_guard.on_trick();

if self.state.state == ChatState::Procesing {
if self.state.state == ChatState::Processing {
self.indicator.calc_next();
global::signal_dirty();
}
Expand Down
5 changes: 3 additions & 2 deletions crates/coco-tui/src/components/chat/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ const CTRL_C_WINDOW: Duration = Duration::from_secs(2);
pub enum ChatState {
#[default]
Ready,
Procesing,
#[serde(alias = "Procesing")]
Processing,
}

impl std::fmt::Display for ChatState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Ready => f.write_str("Ready"),
Self::Procesing => f.write_str("Procesing"),
Self::Processing => f.write_str("Processing"),
}
}
}
Expand Down
90 changes: 42 additions & 48 deletions src/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,31 @@ impl Agent {
*self.messages.lock().await = messages.to_vec();
}

async fn build_chat_response_from_blocks(
&mut self,
blocks: Vec<Block>,
stop_reason: Option<StopReason>,
usage: Option<crate::provider::UsageStats>,
request_options: &RequestOptions,
) -> ChatResponse {
let message = if blocks.is_empty() {
Message::assistant(Content::Multiple(Vec::default()))
} else {
let mut msg = Message::assistant(Content::Multiple(blocks));
if request_options.stringify_nested_tool_inputs {
parse_stringified_tool_inputs_in_message(&mut msg, &self.executor);
}
self.messages.lock().await.push(msg.clone());
msg
};
self.mark_thinking_cleanup_pending(stop_reason.as_ref());
ChatResponse {
message,
stop_reason,
usage,
}
}

pub async fn chat(&mut self, message: Message) -> Result<ChatResponse> {
let request_options = self.request_options_for_current_model();
let (_, client) = self.pick_provider()?;
Expand Down Expand Up @@ -370,24 +395,15 @@ impl Agent {
})
.whatever_context_display("failed to send messages")?;

let stop_reason = response.stop_reason.clone();
let usage = response.usage.clone();
let message = if response.content.is_empty() {
Message::assistant(Content::Multiple(Vec::default()))
} else {
let mut msg = Message::assistant(Content::Multiple(response.content));
if request_options.stringify_nested_tool_inputs {
parse_stringified_tool_inputs_in_message(&mut msg, &self.executor);
}
self.messages.lock().await.push(msg.clone());
msg
};
self.mark_thinking_cleanup_pending(stop_reason.as_ref());
Ok(ChatResponse {
message,
let crate::provider::MessagesResponse {
content,
stop_reason,
usage,
})
..
} = response;
Ok(self
.build_chat_response_from_blocks(content, stop_reason, usage, &request_options)
.await)
}

pub async fn chat_with_history(&mut self) -> Result<ChatResponse> {
Expand All @@ -412,24 +428,15 @@ impl Agent {
})
.whatever_context_display("failed to send messages")?;

let stop_reason = response.stop_reason.clone();
let usage = response.usage.clone();
let message = if response.content.is_empty() {
Message::assistant(Content::Multiple(Vec::default()))
} else {
let mut msg = Message::assistant(Content::Multiple(response.content));
if request_options.stringify_nested_tool_inputs {
parse_stringified_tool_inputs_in_message(&mut msg, &self.executor);
}
self.messages.lock().await.push(msg.clone());
msg
};
self.mark_thinking_cleanup_pending(stop_reason.as_ref());
Ok(ChatResponse {
message,
let crate::provider::MessagesResponse {
content,
stop_reason,
usage,
})
..
} = response;
Ok(self
.build_chat_response_from_blocks(content, stop_reason, usage, &request_options)
.await)
}

pub async fn chat_stream<F>(
Expand Down Expand Up @@ -574,25 +581,12 @@ impl Agent {
}

let (blocks, stop_reason, usage) = accumulator.finish();
let message = if blocks.is_empty() {
Message::assistant(Content::Multiple(Vec::default()))
} else {
let mut msg = Message::assistant(Content::Multiple(blocks));
if request_options.stringify_nested_tool_inputs {
parse_stringified_tool_inputs_in_message(&mut msg, &self.executor);
}
self.messages.lock().await.push(msg.clone());
msg
};
self.mark_thinking_cleanup_pending(stop_reason.as_ref());
if retried {
notify_stream_retry_finished(request_options, true);
}
return Ok(ChatResponse {
message,
stop_reason,
usage,
});
return Ok(self
.build_chat_response_from_blocks(blocks, stop_reason, usage, request_options)
.await);
}
}

Expand Down
68 changes: 32 additions & 36 deletions src/agent/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,34 @@ pub fn substitute_template(template: &str, args: &HashMap<String, String>) -> St
result
}

fn append_prompt_content(current_prompt: &mut String, content: &str) {
if content.trim().is_empty() {
return;
}
if !current_prompt.trim().is_empty() {
current_prompt.push_str("\n\n");
}
current_prompt.push_str(content);
}

fn append_prompt_file_if_exists(current_prompt: &mut String, path: &Path) {
if !path.exists() {
return;
}
if let Ok(content) = std::fs::read_to_string(path) {
append_prompt_content(current_prompt, &content);
}
}

async fn append_prompt_file_if_exists_async(current_prompt: &mut String, path: &Path) {
if !path.exists() {
return;
}
if let Ok(content) = tokio::fs::read_to_string(path).await {
append_prompt_content(current_prompt, &content);
}
}

/// Build system prompt from SystemPromptConfig.
///
/// Combines agent.toml system_prompt with AGENTS.md files from multiple layers:
Expand Down Expand Up @@ -201,27 +229,11 @@ pub fn build_system_prompt_from_config(

// 2. Append: global AGENTS.md
let global_agents_md = config_dir.join("AGENTS.md");
if global_agents_md.exists()
&& let Ok(content) = std::fs::read_to_string(&global_agents_md)
&& !content.trim().is_empty()
{
if !current_prompt.trim().is_empty() {
current_prompt.push_str("\n\n");
}
current_prompt.push_str(&content);
}
append_prompt_file_if_exists(&mut current_prompt, &global_agents_md);

// 3. Append: workspace AGENTS.md
let workspace_agents_md = workspace_dir.join("AGENTS.md");
if workspace_agents_md.exists()
&& let Ok(content) = std::fs::read_to_string(&workspace_agents_md)
&& !content.trim().is_empty()
{
if !current_prompt.trim().is_empty() {
current_prompt.push_str("\n\n");
}
current_prompt.push_str(&content);
}
append_prompt_file_if_exists(&mut current_prompt, &workspace_agents_md);

current_prompt
}
Expand Down Expand Up @@ -259,27 +271,11 @@ pub async fn build_system_prompt_from_config_async(

// 2. Append: global AGENTS.md
let global_agents_md = config_dir.join("AGENTS.md");
if global_agents_md.exists()
&& let Ok(content) = tokio::fs::read_to_string(&global_agents_md).await
&& !content.trim().is_empty()
{
if !current_prompt.trim().is_empty() {
current_prompt.push_str("\n\n");
}
current_prompt.push_str(&content);
}
append_prompt_file_if_exists_async(&mut current_prompt, &global_agents_md).await;

// 3. Append: workspace AGENTS.md
let workspace_agents_md = workspace_dir.join("AGENTS.md");
if workspace_agents_md.exists()
&& let Ok(content) = tokio::fs::read_to_string(&workspace_agents_md).await
&& !content.trim().is_empty()
{
if !current_prompt.trim().is_empty() {
current_prompt.push_str("\n\n");
}
current_prompt.push_str(&content);
}
append_prompt_file_if_exists_async(&mut current_prompt, &workspace_agents_md).await;

current_prompt
}
Expand Down
Loading