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
7 changes: 6 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/zeph-channels/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ repository.workspace = true
anyhow.workspace = true
pulldown-cmark.workspace = true
teloxide.workspace = true
thiserror.workspace = true
tokio = { workspace = true, features = ["sync", "rt"] }
tracing.workspace = true
zeph-core.workspace = true
Expand Down
13 changes: 13 additions & 0 deletions crates/zeph-channels/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//! Error types for zeph-channels.

/// Errors that can occur in channel operations.
#[derive(Debug, thiserror::Error)]
pub enum ChannelError {
/// Telegram API error.
#[error("Telegram API error: {0}")]
Telegram(String),

/// No active chat available for operation.
#[error("no active chat")]
NoActiveChat,
}
3 changes: 3 additions & 0 deletions crates/zeph-channels/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
//! Channel implementations for the Zeph agent.

pub mod error;
pub mod markdown;
pub mod telegram;

pub use error::ChannelError;
5 changes: 3 additions & 2 deletions crates/zeph-channels/src/telegram.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use teloxide::types::{ChatAction, MessageId, ParseMode};
use tokio::sync::mpsc;
use zeph_core::channel::{Channel, ChannelMessage};

use crate::error::ChannelError;
use crate::markdown::markdown_to_telegram;

const MAX_MESSAGE_LEN: usize = 4096;
Expand Down Expand Up @@ -119,7 +120,7 @@ impl TelegramChannel {

async fn send_or_edit(&mut self) -> anyhow::Result<()> {
let Some(chat_id) = self.chat_id else {
anyhow::bail!("no active chat to send message to");
return Err(ChannelError::NoActiveChat.into());
};

let text = if self.accumulated.is_empty() {
Expand Down Expand Up @@ -247,7 +248,7 @@ impl Channel for TelegramChannel {

async fn send(&mut self, text: &str) -> anyhow::Result<()> {
let Some(chat_id) = self.chat_id else {
anyhow::bail!("no active chat to send message to");
return Err(ChannelError::NoActiveChat.into());
};

let formatted_text = markdown_to_telegram(text);
Expand Down
2 changes: 1 addition & 1 deletion crates/zeph-core/src/agent/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ impl<P: LlmProvider + Clone + 'static, C: Channel, T: ToolExecutor> Agent<P, C,
.match_skills(&all_meta, query, self.max_active_skills, |text| {
let owned = text.to_owned();
let p = provider.clone();
Box::pin(async move { p.embed(&owned).await })
Box::pin(async move { p.embed(&owned).await.map_err(Into::into) })
})
.await
} else {
Expand Down
2 changes: 1 addition & 1 deletion crates/zeph-core/src/agent/learning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ impl<P: LlmProvider + Clone + 'static, C: Channel, T: ToolExecutor> Agent<P, C,
},
];

self.provider.chat(&messages).await
self.provider.chat(&messages).await.map_err(Into::into)
}

#[cfg(feature = "self-learning")]
Expand Down
4 changes: 2 additions & 2 deletions crates/zeph-core/src/agent/mcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ impl<P: LlmProvider + Clone + 'static, C: Channel, T: ToolExecutor> Agent<P, C,
.search(query, self.max_active_skills, |text| {
let owned = text.to_owned();
let p = provider.clone();
Box::pin(async move { p.embed(&owned).await })
Box::pin(async move { p.embed(&owned).await.map_err(Into::into) })
})
.await
}
Expand All @@ -259,7 +259,7 @@ impl<P: LlmProvider + Clone + 'static, C: Channel, T: ToolExecutor> Agent<P, C,
let embed_fn = |text: &str| -> zeph_mcp::registry::EmbedFuture {
let owned = text.to_owned();
let p = provider.clone();
Box::pin(async move { p.embed(&owned).await })
Box::pin(async move { p.embed(&owned).await.map_err(Into::into) })
};
if let Err(e) = registry
.sync(&self.mcp_tools, &self.embedding_model, embed_fn)
Expand Down
17 changes: 10 additions & 7 deletions crates/zeph-core/src/agent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -715,7 +715,7 @@ impl<P: LlmProvider + Clone + 'static, C: Channel, T: ToolExecutor> Agent<P, C,
let embed_fn = |text: &str| -> zeph_skills::matcher::EmbedFuture {
let owned = text.to_owned();
let p = provider.clone();
Box::pin(async move { p.embed(&owned).await })
Box::pin(async move { p.embed(&owned).await.map_err(Into::into) })
};

let needs_inmemory_rebuild = !self
Expand Down Expand Up @@ -922,7 +922,7 @@ impl<P: LlmProvider + Clone + 'static, C: Channel, T: ToolExecutor> Agent<P, C,
self.channel.send(&display).await?;
Ok(Some(resp))
}
Ok(Err(e)) => Err(e),
Ok(Err(e)) => Err(e.into()),
Err(_) => {
self.channel
.send("LLM request timed out. Please try again.")
Expand Down Expand Up @@ -1265,9 +1265,9 @@ pub(super) mod agent_tests {
}

impl LlmProvider for MockProvider {
async fn chat(&self, _messages: &[Message]) -> anyhow::Result<String> {
async fn chat(&self, _messages: &[Message]) -> Result<String, zeph_llm::LlmError> {
if self.fail_chat {
anyhow::bail!("mock LLM error");
return Err(zeph_llm::LlmError::Other("mock LLM error".into()));
}
let mut responses = self.responses.lock().unwrap();
if responses.is_empty() {
Expand All @@ -1277,7 +1277,10 @@ pub(super) mod agent_tests {
}
}

async fn chat_stream(&self, messages: &[Message]) -> anyhow::Result<ChatStream> {
async fn chat_stream(
&self,
messages: &[Message],
) -> Result<ChatStream, zeph_llm::LlmError> {
let response = self.chat(messages).await?;
let chunks: Vec<_> = response.chars().map(|c| c.to_string()).map(Ok).collect();
Ok(Box::pin(tokio_stream::iter(chunks)))
Expand All @@ -1287,11 +1290,11 @@ pub(super) mod agent_tests {
self.streaming
}

async fn embed(&self, _text: &str) -> anyhow::Result<Vec<f32>> {
async fn embed(&self, _text: &str) -> Result<Vec<f32>, zeph_llm::LlmError> {
if self.embeddings {
Ok(vec![0.1, 0.2, 0.3])
} else {
anyhow::bail!("embeddings not supported")
Err(zeph_llm::LlmError::EmbedUnsupported { provider: "mock" })
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/zeph-index/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ lang-go = ["dep:tree-sitter-go"]
lang-config = ["dep:tree-sitter-bash", "dep:tree-sitter-toml-ng", "dep:tree-sitter-json", "dep:tree-sitter-md"]

[dependencies]
anyhow.workspace = true
blake3.workspace = true
ignore.workspace = true
notify.workspace = true
Expand All @@ -24,6 +23,7 @@ qdrant-client = { workspace = true, features = ["serde"] }
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
sqlx = { workspace = true, features = ["runtime-tokio", "sqlite", "migrate"] }
thiserror.workspace = true
tokio = { workspace = true, features = ["fs", "rt"] }
tracing.workspace = true
tree-sitter.workspace = true
Expand Down
9 changes: 5 additions & 4 deletions crates/zeph-index/src/chunker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use tree_sitter::{Node, Parser};

use crate::error::{IndexError, Result};
use crate::languages::Lang;

/// One chunk of source code with rich metadata.
Expand Down Expand Up @@ -58,19 +59,19 @@ pub fn chunk_file(
file_path: &str,
lang: Lang,
config: &ChunkerConfig,
) -> anyhow::Result<Vec<CodeChunk>> {
) -> Result<Vec<CodeChunk>> {
let grammar = lang
.grammar()
.ok_or_else(|| anyhow::anyhow!("no grammar for {}", lang.id()))?;
.ok_or_else(|| IndexError::Parse(format!("no grammar for {}", lang.id())))?;

let mut parser = Parser::new();
parser
.set_language(&grammar)
.map_err(|e| anyhow::anyhow!("set_language failed: {e}"))?;
.map_err(|e| IndexError::Parse(format!("set_language failed: {e}")))?;

let tree = parser
.parse(source, None)
.ok_or_else(|| anyhow::anyhow!("parse failed for {file_path}"))?;
.ok_or_else(|| IndexError::Parse(format!("parse failed for {file_path}")))?;

let root = tree.root_node();
let imports = extract_imports(source, &root, lang);
Expand Down
54 changes: 54 additions & 0 deletions crates/zeph-index/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//! Error types for zeph-index.

use std::num::TryFromIntError;

/// Errors that can occur during code indexing operations.
#[derive(Debug, thiserror::Error)]
pub enum IndexError {
/// IO error reading source files.
#[error("IO error: {0}")]
Io(#[from] std::io::Error),

/// `SQLite` database error.
#[error("database error: {0}")]
Sqlite(#[from] sqlx::Error),

/// `SQLite` migration error.
#[error("migration failed: {0}")]
Migration(#[from] sqlx::migrate::MigrateError),

/// Qdrant vector store error.
#[error("Qdrant error: {0}")]
Qdrant(#[from] Box<qdrant_client::QdrantError>),

/// LLM provider error (embedding).
#[error("LLM error: {0}")]
Llm(#[from] zeph_llm::LlmError),

/// JSON serialization/deserialization error.
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),

/// Tree-sitter parsing error.
#[error("parse failed: {0}")]
Parse(String),

/// Unsupported or unrecognized language.
#[error("unsupported language")]
UnsupportedLanguage,

/// File watcher error.
#[error("watcher error: {0}")]
Watcher(#[from] notify::Error),

/// Integer conversion error.
#[error("integer conversion failed: {0}")]
IntConversion(#[from] TryFromIntError),

/// Generic catch-all error.
#[error("{0}")]
Other(String),
}

/// Result type alias using `IndexError`.
pub type Result<T> = std::result::Result<T, IndexError>;
10 changes: 5 additions & 5 deletions crates/zeph-index/src/indexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::sync::Arc;

use crate::chunker::{ChunkerConfig, CodeChunk, chunk_file};
use crate::context::contextualize_for_embedding;
use crate::error::{IndexError, Result};
use crate::languages::{detect_language, is_indexable};
use crate::store::{ChunkInsert, CodeStore};
use zeph_llm::provider::LlmProvider;
Expand Down Expand Up @@ -50,7 +51,7 @@ impl<P: LlmProvider + Clone + 'static> CodeIndexer<P> {
/// # Errors
///
/// Returns an error if the embedding probe or collection setup fails.
pub async fn index_project(&self, root: &Path) -> anyhow::Result<IndexReport> {
pub async fn index_project(&self, root: &Path) -> Result<IndexReport> {
let start = std::time::Instant::now();
let mut report = IndexReport::default();

Expand Down Expand Up @@ -115,7 +116,7 @@ impl<P: LlmProvider + Clone + 'static> CodeIndexer<P> {
/// # Errors
///
/// Returns an error if reading, chunking, or embedding fails.
pub async fn reindex_file(&self, root: &Path, abs_path: &Path) -> anyhow::Result<usize> {
pub async fn reindex_file(&self, root: &Path, abs_path: &Path) -> Result<usize> {
let rel_path = abs_path
.strip_prefix(root)
.unwrap_or(abs_path)
Expand All @@ -127,10 +128,9 @@ impl<P: LlmProvider + Clone + 'static> CodeIndexer<P> {
Ok(created)
}

async fn index_file(&self, abs_path: &Path, rel_path: &str) -> anyhow::Result<(usize, usize)> {
async fn index_file(&self, abs_path: &Path, rel_path: &str) -> Result<(usize, usize)> {
let source = tokio::fs::read_to_string(abs_path).await?;
let lang =
detect_language(abs_path).ok_or_else(|| anyhow::anyhow!("unsupported language"))?;
let lang = detect_language(abs_path).ok_or(IndexError::UnsupportedLanguage)?;

let chunks = chunk_file(&source, rel_path, lang, &self.config.chunker)?;

Expand Down
3 changes: 3 additions & 0 deletions crates/zeph-index/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@

pub mod chunker;
pub mod context;
pub mod error;
pub mod indexer;
pub mod languages;
pub mod repo_map;
pub mod retriever;
pub mod store;
pub mod watcher;

pub use error::{IndexError, Result};
3 changes: 2 additions & 1 deletion crates/zeph-index/src/repo_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::path::Path;

use tree_sitter::Parser;

use crate::error::Result;
use crate::languages::{Lang, detect_language};
use zeph_memory::estimate_tokens;

Expand All @@ -19,7 +20,7 @@ use zeph_memory::estimate_tokens;
/// # Errors
///
/// Returns an error if the file walk fails.
pub fn generate_repo_map(root: &Path, token_budget: usize) -> anyhow::Result<String> {
pub fn generate_repo_map(root: &Path, token_budget: usize) -> Result<String> {
let walker = ignore::WalkBuilder::new(root)
.hidden(true)
.git_ignore(true)
Expand Down
Loading
Loading