Skip to content
Draft
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
9 changes: 3 additions & 6 deletions crates/aion-agent/src/commands/clear.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ impl SlashCommand for ClearCommand {
mod tests {
use std::sync::Arc;

use aion_providers::{LlmProvider, ProviderError};
use aion_types::llm::{LlmEvent, LlmRequest};
use aion_providers::{LlmProvider, ProviderFailure, ProviderStreamReceiver};
use aion_types::llm::LlmRequest;
use aion_types::message::{ContentBlock, Message, Role};

use super::*;
Expand All @@ -42,10 +42,7 @@ mod tests {
struct NullProvider;
#[async_trait::async_trait]
impl LlmProvider for NullProvider {
async fn stream(
&self,
_: &LlmRequest,
) -> Result<tokio::sync::mpsc::Receiver<LlmEvent>, ProviderError> {
async fn stream(&self, _: &LlmRequest) -> Result<ProviderStreamReceiver, ProviderFailure> {
let (_tx, rx) = tokio::sync::mpsc::channel(1);
Ok(rx)
}
Expand Down
9 changes: 3 additions & 6 deletions crates/aion-agent/src/commands/compact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ impl SlashCommand for CompactCommand {
mod tests {
use std::sync::Arc;

use aion_providers::{LlmProvider, ProviderError};
use aion_types::llm::{LlmEvent, LlmRequest};
use aion_providers::{LlmProvider, ProviderFailure, ProviderStreamReceiver};
use aion_types::llm::LlmRequest;
use aion_types::message::{ContentBlock, Message, Role};

use super::*;
Expand All @@ -95,10 +95,7 @@ mod tests {
struct NullProvider;
#[async_trait::async_trait]
impl LlmProvider for NullProvider {
async fn stream(
&self,
_: &LlmRequest,
) -> Result<tokio::sync::mpsc::Receiver<LlmEvent>, ProviderError> {
async fn stream(&self, _: &LlmRequest) -> Result<ProviderStreamReceiver, ProviderFailure> {
let (_tx, rx) = tokio::sync::mpsc::channel(1);
Ok(rx)
}
Expand Down
9 changes: 3 additions & 6 deletions crates/aion-agent/src/commands/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ impl SlashCommand for HelpCommand {
mod tests {
use std::sync::{Arc, Mutex};

use aion_providers::{LlmProvider, ProviderError};
use aion_types::llm::{LlmEvent, LlmRequest};
use aion_providers::{LlmProvider, ProviderFailure, ProviderStreamReceiver};
use aion_types::llm::LlmRequest;
use aion_types::message::Message;

use super::*;
Expand Down Expand Up @@ -81,10 +81,7 @@ mod tests {
struct NullProvider;
#[async_trait::async_trait]
impl LlmProvider for NullProvider {
async fn stream(
&self,
_: &LlmRequest,
) -> Result<tokio::sync::mpsc::Receiver<LlmEvent>, ProviderError> {
async fn stream(&self, _: &LlmRequest) -> Result<ProviderStreamReceiver, ProviderFailure> {
let (_tx, rx) = tokio::sync::mpsc::channel(1);
Ok(rx)
}
Expand Down
21 changes: 11 additions & 10 deletions crates/aion-agent/src/compact/auto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
//! summary. A circuit breaker prevents runaway retries.

use aion_config::compact::CompactConfig;
use aion_providers::{LlmProvider, ProviderError};
use aion_providers::{LlmProvider, ProviderFailure, ProviderFailureKind, ProviderStreamReceiver};
use aion_types::compact::{CompactMetadata, CompactTrigger};
use aion_types::llm::{LlmEvent, LlmRequest, ThinkingConfig};
use aion_types::message::{ContentBlock, Message, Role, TokenUsage};
use tokio::sync::mpsc;

use super::prompt::{
COMPACT_MAX_OUTPUT_TOKENS, COMPACT_SYSTEM_PROMPT, build_compact_prompt, build_summary_content,
Expand Down Expand Up @@ -42,13 +41,11 @@ pub struct CompactResult {
#[derive(Debug, thiserror::Error)]
pub enum CompactError {
#[error("LLM provider error: {0}")]
Provider(#[from] ProviderError),
Provider(#[from] ProviderFailure),
#[error("Prompt too long after {attempts} retries")]
PromptTooLong { attempts: u32 },
#[error("Empty response from LLM")]
EmptyResponse,
#[error("Stream error: {0}")]
StreamError(String),
#[error("Circuit breaker tripped after {failures} consecutive failures")]
CircuitBroken { failures: u32 },
}
Expand Down Expand Up @@ -130,7 +127,7 @@ pub async fn autocompact(
return Err(e);
}
},
Err(ProviderError::PromptTooLong(_)) if ptl_attempts < MAX_PTL_RETRIES => {
Err(e) if is_context_too_large(&e) && ptl_attempts < MAX_PTL_RETRIES => {
ptl_attempts += 1;
// Remove the summary prompt (last msg), truncate, re-add prompt
let conversation_part = &conv_messages[..conv_messages.len() - 1];
Expand All @@ -152,7 +149,7 @@ pub async fn autocompact(
}
}
}
Err(ProviderError::PromptTooLong(_)) => {
Err(e) if is_context_too_large(&e) => {
state.record_failure();
return Err(CompactError::PromptTooLong {
attempts: ptl_attempts,
Expand Down Expand Up @@ -212,15 +209,15 @@ pub async fn autocompact(

/// Collect all text from a streaming LLM response.
async fn collect_stream_text(
mut rx: mpsc::Receiver<LlmEvent>,
mut rx: ProviderStreamReceiver,
) -> Result<(String, TokenUsage), CompactError> {
let mut text = String::new();

while let Some(event) = rx.recv().await {
while let Some(item) = rx.recv().await {
let event = item?;
match event {
LlmEvent::TextDelta(delta) => text.push_str(&delta),
LlmEvent::Done { usage, .. } => return Ok((text, usage)),
LlmEvent::Error(e) => return Err(CompactError::StreamError(e)),
// Ignore thinking deltas and tool calls (shouldn't happen in compact)
_ => {}
}
Expand All @@ -230,6 +227,10 @@ async fn collect_stream_text(
Err(CompactError::EmptyResponse)
}

fn is_context_too_large(error: &ProviderFailure) -> bool {
matches!(error.kind, ProviderFailureKind::ContextTooLarge)
}

/// Truncate the oldest ~20% of messages for PTL retry.
///
/// Returns `None` if there are too few messages to truncate meaningfully.
Expand Down
37 changes: 21 additions & 16 deletions crates/aion-agent/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::cache_diagnostics::{CacheBreakDetector, CacheDiagnostic, CacheStats};
use crate::compact::state::CompactState;
use crate::compact::{auto, emergency, estimate, micro};
use crate::confirm::ToolConfirmer;
use crate::error::AgentError;
use crate::error::{AgentError, CommandFailure};
use crate::orchestration::{
ExecutionControl, execute_tool_calls, execute_tool_calls_with_approval,
};
Expand Down Expand Up @@ -466,7 +466,9 @@ impl AgentEngine {
}
Err(e) => {
tracing::error!(command = cmd_name, error = %e, "Slash command failed");
Err(AgentError::ApiError(e.to_string()))
Err(AgentError::Command(CommandFailure::Failed {
command: cmd_name.to_string(),
}))
}
};
}
Expand Down Expand Up @@ -543,7 +545,8 @@ impl AgentEngine {
let mut stop_reason = StopReason::EndTurn;
let mut turn_usage = TokenUsage::default();

while let Some(event) = rx.recv().await {
while let Some(item) = rx.recv().await {
let event = item?;
match event {
LlmEvent::TextDelta(text) => {
self.output.emit_text_delta(&text, &self.current_msg_id);
Expand Down Expand Up @@ -592,9 +595,6 @@ impl AgentEngine {
stop_reason = sr;
turn_usage = usage;
}
LlmEvent::Error(e) => {
return Err(AgentError::ApiError(e));
}
}
}

Expand Down Expand Up @@ -1084,7 +1084,7 @@ impl AgentEngine {
mod tests_set_config {
use std::sync::{Arc, Mutex};

use aion_providers::error::ProviderError;
use aion_providers::error::ProviderFailure;
use aion_providers::provider::LlmProvider;
use aion_tools::registry::ToolRegistry;
use aion_types::llm::{LlmEvent, LlmRequest};
Expand All @@ -1110,7 +1110,8 @@ mod tests_set_config {
async fn stream(
&self,
_: &LlmRequest,
) -> Result<tokio::sync::mpsc::Receiver<LlmEvent>, ProviderError> {
) -> Result<tokio::sync::mpsc::Receiver<Result<LlmEvent, ProviderFailure>>, ProviderFailure>
{
let (_tx, rx) = tokio::sync::mpsc::channel(1);
Ok(rx)
}
Expand Down Expand Up @@ -1413,7 +1414,7 @@ mod tests_set_config {
mod tests_phase6 {
use std::sync::{Arc, Mutex};

use aion_providers::error::ProviderError;
use aion_providers::error::ProviderFailure;
use aion_providers::provider::LlmProvider;
use aion_tools::registry::ToolRegistry;
use aion_types::llm::{LlmEvent, LlmRequest};
Expand All @@ -1440,7 +1441,8 @@ mod tests_phase6 {
async fn stream(
&self,
_: &LlmRequest,
) -> Result<tokio::sync::mpsc::Receiver<LlmEvent>, ProviderError> {
) -> Result<tokio::sync::mpsc::Receiver<Result<LlmEvent, ProviderFailure>>, ProviderFailure>
{
let (_tx, rx) = tokio::sync::mpsc::channel(1);
Ok(rx)
}
Expand Down Expand Up @@ -1612,7 +1614,7 @@ mod tests_compact {
use std::sync::{Arc, Mutex};

use aion_config::compact::CompactConfig;
use aion_providers::error::ProviderError;
use aion_providers::error::ProviderFailure;
use aion_providers::provider::LlmProvider;
use aion_tools::registry::ToolRegistry;
use aion_types::llm::{LlmEvent, LlmRequest};
Expand Down Expand Up @@ -1664,7 +1666,8 @@ mod tests_compact {
async fn stream(
&self,
_: &LlmRequest,
) -> Result<tokio::sync::mpsc::Receiver<LlmEvent>, ProviderError> {
) -> Result<tokio::sync::mpsc::Receiver<Result<LlmEvent, ProviderFailure>>, ProviderFailure>
{
let (_tx, rx) = tokio::sync::mpsc::channel(1);
Ok(rx)
}
Expand Down Expand Up @@ -1995,7 +1998,7 @@ mod tests_plan_mode {
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};

use aion_providers::error::ProviderError;
use aion_providers::error::ProviderFailure;
use aion_providers::provider::LlmProvider;
use aion_tools::registry::ToolRegistry;
use aion_types::llm::{LlmEvent, LlmRequest};
Expand Down Expand Up @@ -2024,7 +2027,8 @@ mod tests_plan_mode {
async fn stream(
&self,
_: &LlmRequest,
) -> Result<tokio::sync::mpsc::Receiver<LlmEvent>, ProviderError> {
) -> Result<tokio::sync::mpsc::Receiver<Result<LlmEvent, ProviderFailure>>, ProviderFailure>
{
let (_tx, rx) = tokio::sync::mpsc::channel(1);
Ok(rx)
}
Expand Down Expand Up @@ -2211,7 +2215,7 @@ mod tests_plan_mode {
mod tests_handle_command {
use std::sync::{Arc, Mutex};

use aion_providers::error::ProviderError;
use aion_providers::error::ProviderFailure;
use aion_providers::provider::LlmProvider;
use aion_tools::registry::ToolRegistry;
use aion_types::llm::{LlmEvent, LlmRequest};
Expand Down Expand Up @@ -2239,7 +2243,8 @@ mod tests_handle_command {
async fn stream(
&self,
_: &LlmRequest,
) -> Result<tokio::sync::mpsc::Receiver<LlmEvent>, ProviderError> {
) -> Result<tokio::sync::mpsc::Receiver<Result<LlmEvent, ProviderFailure>>, ProviderFailure>
{
let (_tx, rx) = tokio::sync::mpsc::channel(1);
Ok(rx)
}
Expand Down
26 changes: 21 additions & 5 deletions crates/aion-agent/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
use aion_providers::error::ProviderError;
use aion_providers::error::ProviderFailure;

#[derive(Debug, thiserror::Error)]
pub enum AgentError {
#[error("API error: {0}")]
ApiError(String),
pub enum AgentFailure {
#[error(
"provider repeatedly returned malformed tool calls ({count}/{limit}); stopped to avoid wasting tokens"
)]
RepeatedMalformedToolCall { count: usize, limit: usize },
#[error("Provider error: {0}")]
Provider(#[from] ProviderError),
Provider(#[from] ProviderFailure),
#[error("User aborted the session")]
UserAborted,
#[error("Context window nearly full ({input_tokens} tokens used, limit {limit})")]
ContextTooLong { input_tokens: u64, limit: usize },
#[error("Command error: {0}")]
Command(CommandFailure),
#[error("Internal error: {0}")]
Internal(InternalFailure),
}

pub type AgentError = AgentFailure;

#[derive(Debug, thiserror::Error)]
pub enum CommandFailure {
#[error("slash command failed: {command}")]
Failed { command: String },
}

#[derive(Debug, thiserror::Error)]
pub enum InternalFailure {
#[error("{message}")]
Unexpected { message: String },
}
1 change: 1 addition & 0 deletions crates/aion-agent/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod error;
pub mod orchestration;
pub mod output;
pub mod plan;
pub mod public_error;
pub mod session;
pub mod skill_tool;
pub mod spawn_tool;
Expand Down
5 changes: 5 additions & 0 deletions crates/aion-agent/src/output/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod null_sink;
pub mod protocol_sink;
pub mod terminal;

use aion_protocol::events::PublicError;
use crossterm::execute;
use crossterm::style::{Attribute, Color, Print, ResetColor, SetAttribute, SetForegroundColor};
use std::io::{self, Write};
Expand Down Expand Up @@ -30,6 +31,10 @@ pub trait OutputSink: Send + Sync {
);
/// Display error
fn emit_error(&self, msg: &str);
/// Display a structured public error.
fn emit_public_error(&self, error: &PublicError) {
self.emit_error(&error.message);
}
/// Display informational message
fn emit_info(&self, msg: &str);
}
Expand Down
16 changes: 13 additions & 3 deletions crates/aion-agent/src/output/protocol_sink.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use std::sync::Arc;

use aion_config::compat::ProviderCompat;
use aion_protocol::events::{Capabilities, ErrorInfo, ProtocolEvent, Usage};
use aion_protocol::events::{
Capabilities, ErrorInfo, ErrorOwnership, ProtocolEvent, PublicError, PublicErrorCode, Usage,
};
use aion_protocol::writer::{ProtocolEmitter, ProtocolWriter};

use super::OutputSink;
Expand Down Expand Up @@ -132,13 +134,21 @@ impl OutputSink for ProtocolSink {
let _ = self.writer.emit(&ProtocolEvent::Error {
msg_id: None,
error: ErrorInfo {
code: "engine_error".to_string(),
code: PublicErrorCode::InternalError,
message: msg.to_string(),
retryable: false,
ownership: ErrorOwnership::Aionrs,
details: vec![],
},
});
}

fn emit_public_error(&self, error: &PublicError) {
let _ = self.writer.emit(&ProtocolEvent::Error {
msg_id: None,
error: error.clone(),
});
}

fn emit_info(&self, msg: &str) {
let _ = self.writer.emit(&ProtocolEvent::Info {
msg_id: String::new(),
Expand Down
Loading
Loading