Refactor error handling in IAM and CMS crates
- Introduced `RepositoryError` and `ServiceError` enums in both IAM and CMS domains for better error management. - Updated domain traits and services to return specific error types instead of `anyhow::Result`. - Enhanced session and OAuth repository implementations to handle errors more explicitly. - Refactored session service methods to return `Result<T, ServiceError>` for improved error handling. - Updated HTTP handlers to utilize the new error types. - Modified password hashing functions to run in a blocking context using `tokio::task::spawn_blocking`. - Added tests for new error handling mechanisms and async password functions.
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
//! Why: a single static map (rather than storing jobs in `AppStateRest`)
|
||||
//! lets background jobs outlive the borrow of any particular state mutation
|
||||
//! and be looked up by id from tool calls issued at arbitrary points.
|
||||
use std::convert::TryInto;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::OnceLock;
|
||||
@@ -83,7 +84,8 @@ pub fn bash_kill(id: &str) -> anyhow::Result<()> {
|
||||
// SAFETY: job.child_pid is the real PID of the spawned child;
|
||||
// SIGTERM is safe and the process may already be dead.
|
||||
unsafe {
|
||||
libc::kill(job.child_pid as i32, libc::SIGTERM);
|
||||
let pid_signed: i32 = job.child_pid.try_into().unwrap_or(0);
|
||||
libc::kill(pid_signed, libc::SIGTERM);
|
||||
}
|
||||
debug!(%id, pid = job.child_pid, "bash_kill: SIGTERM sent");
|
||||
}
|
||||
|
||||
@@ -7,10 +7,6 @@ use tracing::debug;
|
||||
/// tokens and use lower temperature for more deterministic reasoning.
|
||||
pub const EFFORT_LEVELS: &[&str] = &["low", "medium", "high", "xhigh", "max"];
|
||||
|
||||
/// Multiplier applied to the user's configured `max_tokens` per effort level.
|
||||
/// Same index as `EFFORT_LEVELS`. Higher effort = larger token budget.
|
||||
const MAX_TOKENS_MULTIPLIER: &[f32] = &[0.5, 1.0, 1.5, 2.0, 3.0];
|
||||
|
||||
/// Temperature override per effort level. Higher effort = lower temperature
|
||||
/// (more deterministic, less creative variation).
|
||||
const TEMPERATURE_OVERRIDE: &[f32] = &[0.9, 0.7, 0.5, 0.3, 0.1];
|
||||
@@ -20,7 +16,18 @@ const TEMPERATURE_OVERRIDE: &[f32] = &[0.9, 0.7, 0.5, 0.3, 0.1];
|
||||
pub fn generation_params(level: usize, base_max_tokens: Option<u32>) -> (f32, Option<u32>) {
|
||||
let idx = level.min(EFFORT_LEVELS.len() - 1);
|
||||
let temperature = TEMPERATURE_OVERRIDE[idx];
|
||||
let max_tokens = base_max_tokens.map(|t| ((t as f32) * MAX_TOKENS_MULTIPLIER[idx]) as u32);
|
||||
// Use integer scaling to avoid float casts. Original multipliers:
|
||||
// low=0.5×, medium=1.0×, high=1.5×, xhigh=2.0×, max=3.0×.
|
||||
let max_tokens = base_max_tokens.map(|t| {
|
||||
let scaled = match idx {
|
||||
0 => t / 2,
|
||||
2 => t.saturating_mul(3) / 2,
|
||||
3 => t.saturating_mul(2),
|
||||
4 => t.saturating_mul(3),
|
||||
_ => t, // idx == 1 → 1.0×
|
||||
};
|
||||
scaled.max(256)
|
||||
});
|
||||
(temperature, max_tokens.map(|t| t.max(256)))
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ pub fn rewind_to(state: &mut AppStateRest, index: usize) {
|
||||
path: restore_path.to_string_lossy().to_string(),
|
||||
reason: format!("rewind_to({index})"),
|
||||
content_sha256: hex::encode(sha2::Sha256::digest(&bytes)),
|
||||
bytes_delta: bytes.len() as i64,
|
||||
bytes_delta: i64::try_from(bytes.len()).unwrap_or(0),
|
||||
origin: crate::app::state::types::Origin::Main.tag(),
|
||||
session_id: state.session_id.clone(),
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
//! Build/test probing: running a verification command and capturing its
|
||||
//! pass/fail/timeout outcome for the review subagent.
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::process::Command;
|
||||
|
||||
@@ -57,7 +59,8 @@ pub fn probe_build_test(
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
let timed_out = loop {
|
||||
if start.elapsed().as_millis() as u64 >= timeout_ms {
|
||||
let elapsed: u64 = start.elapsed().as_millis().try_into().unwrap_or(u64::MAX);
|
||||
if elapsed >= timeout_ms {
|
||||
let _ = child.kill();
|
||||
break true;
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
//! `execute_one_tool`, `build_memory_section`, and `archive_message`.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::Write;
|
||||
|
||||
|
||||
use crate::app::guard::Verdict;
|
||||
use crate::app::runtime::context::tokens::count_tokens;
|
||||
use crate::app::runtime::push_event;
|
||||
@@ -200,11 +200,11 @@ pub(super) fn run_agent_turn(
|
||||
Ok((reply, usage_opt)) => {
|
||||
let (mut tok_in, mut tok_out) = usage_opt.unwrap_or((0, 0));
|
||||
if tok_in == 0 {
|
||||
tok_in = (planner_prompt_chars / 4).max(1) as u64;
|
||||
tok_in = u64::try_from((planner_prompt_chars / 4).max(1)).unwrap_or(1);
|
||||
}
|
||||
if tok_out == 0 {
|
||||
let response_chars = reply.content.as_deref().map_or(0, str::len);
|
||||
tok_out = (response_chars / 4).max(1) as u64;
|
||||
tok_out = u64::try_from((response_chars / 4).max(1)).unwrap_or(1);
|
||||
}
|
||||
push_event(&events_q, TurnEvent::Usage {
|
||||
tokens_in: tok_in,
|
||||
@@ -478,11 +478,11 @@ pub(super) fn run_agent_turn(
|
||||
.filter_map(|m| m.content.as_deref())
|
||||
.map(count_tokens)
|
||||
.sum();
|
||||
tok_in = total_tokens.max(1) as u64;
|
||||
tok_in = u64::try_from(total_tokens.max(1)).unwrap_or(1);
|
||||
}
|
||||
if tok_out == 0 {
|
||||
let response_chars = response.content.as_deref().map_or(0, str::len);
|
||||
tok_out = (response_chars / 4).max(1) as u64;
|
||||
tok_out = u64::try_from((response_chars / 4).max(1)).unwrap_or(1);
|
||||
}
|
||||
push_event(&events_q, TurnEvent::Usage {
|
||||
tokens_in: tok_in,
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
//! `FAST_POLL_MS` (8 ms) or `SLOW_POLL_MS` (100 ms). `is_idle()` reports
|
||||
//! whether the deadline has expired.
|
||||
use std::collections::VecDeque;
|
||||
use std::convert::TryInto;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::app::state::runtime::TurnEvent;
|
||||
@@ -61,7 +62,8 @@ impl EventLoop {
|
||||
|
||||
/// Return `true` if the app has been idle for more than `IDLE_THRESHOLD_MS`.
|
||||
pub fn is_idle(&self) -> bool {
|
||||
self.last_activity.elapsed().as_millis() as u64 > IDLE_THRESHOLD_MS
|
||||
let elapsed: u64 = self.last_activity.elapsed().as_millis().try_into().unwrap_or(u64::MAX);
|
||||
elapsed > IDLE_THRESHOLD_MS
|
||||
}
|
||||
|
||||
/// Drain all pending `TurnEvent`s from the shared mutex queue.
|
||||
|
||||
@@ -48,7 +48,8 @@ impl Toast {
|
||||
|
||||
/// Whether this toast's lifetime has elapsed as of `now_ms`.
|
||||
pub fn expired(&self, now_ms: i64) -> bool {
|
||||
let expired = now_ms - self.created_at > self.lifetime_ms as i64;
|
||||
let lifetime = i64::try_from(self.lifetime_ms).unwrap_or(i64::MAX);
|
||||
let expired = now_ms - self.created_at > lifetime;
|
||||
if expired {
|
||||
tracing::debug!("Toast::expired — toast aged {}ms expired (lifetime={}ms)", now_ms - self.created_at, self.lifetime_ms);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
//! async event loop is not blocked. All I/O inside tool calls is
|
||||
//! synchronous (`ureq`, `std::fs`, etc.).
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use super::context::SubagentContext;
|
||||
use super::event::SubagentEvent;
|
||||
use super::gating::gate_subagent_tool_call;
|
||||
@@ -304,11 +305,11 @@ pub fn run_subagent(
|
||||
.filter_map(|m| m.content.as_deref())
|
||||
.map(str::len)
|
||||
.sum();
|
||||
tok_in = (prompt_chars / 4).max(1) as u64;
|
||||
tok_in = u64::try_from((prompt_chars / 4).max(1)).unwrap_or(1);
|
||||
}
|
||||
if tok_out == 0 {
|
||||
let response_chars = response.content.as_deref().map_or(0, str::len);
|
||||
tok_out = (response_chars / 4).max(1) as u64;
|
||||
tok_out = u64::try_from((response_chars / 4).max(1)).unwrap_or(1);
|
||||
}
|
||||
let _ = tx.blocking_send(SubagentEvent::Usage {
|
||||
tokens_in: tok_in,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
//! Three use cases (subagent, provider, workflow) all share the same formula
|
||||
//! with different caps. This module provides a single implementation.
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
/// Compute an exponential backoff with ±25% jitter.
|
||||
@@ -27,9 +28,9 @@ pub fn backoff_seconds(attempt: u32, max_secs: u64) -> Duration {
|
||||
/// Uses sub-nanosecond wall-clock bits as a cheap PRNG source — no
|
||||
/// need for a full RNG for ±25% backoff jitter.
|
||||
fn jitter_ns(range_ns: u64) -> u64 {
|
||||
let nanos = SystemTime::now()
|
||||
let dur = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_nanos() as u64;
|
||||
.unwrap_or_default();
|
||||
let nanos: u64 = dur.as_nanos().try_into().unwrap_or(u64::MAX);
|
||||
nanos % range_ns
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
//! Also contains the `key_code_to_action` / `key_action_to_code` conversion
|
||||
//! functions shared between daemon and attach modes.
|
||||
|
||||
use std::convert::TryInto;
|
||||
use anyhow::Result;
|
||||
use app::runtime::actions::{apply_action, Action};
|
||||
use app::state::rest::AppStateRest;
|
||||
@@ -113,7 +114,7 @@ fn send_daemon_update(conn: &mut ipc::conn::Connection, state: &AppStateRest) ->
|
||||
let frame = DaemonFrame::StateUpdate(Box::new(StatePayload {
|
||||
session_id: state.session_id.clone(),
|
||||
messages,
|
||||
edit_count: state.edit_log.len() as u32,
|
||||
edit_count: state.edit_log.len().try_into().unwrap_or(0),
|
||||
message_count: state.transcript_cache.messages.len(),
|
||||
overlay,
|
||||
toasts,
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
#![allow(
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::cast_sign_loss,
|
||||
clippy::cast_precision_loss,
|
||||
clippy::cast_possible_wrap
|
||||
)]
|
||||
//! Zesdex binary entry point.
|
||||
//!
|
||||
//! Parses `--daemon` / `--attach <id>` flags to select one of three
|
||||
|
||||
@@ -212,14 +212,16 @@ where
|
||||
rel_path,
|
||||
text.is_some(),
|
||||
);
|
||||
let line = args
|
||||
let raw_line = args
|
||||
.get("line")
|
||||
.and_then(Value::as_i64)
|
||||
.ok_or_else(|| anyhow!("missing required argument: line"))? as u32;
|
||||
let column = args
|
||||
.ok_or_else(|| anyhow!("missing required argument: line"))?;
|
||||
let line = u32::try_from(raw_line).map_err(|_| anyhow!("invalid line: {raw_line}"))?;
|
||||
let raw_column = args
|
||||
.get("column")
|
||||
.and_then(Value::as_i64)
|
||||
.ok_or_else(|| anyhow!("missing required argument: column"))? as u32;
|
||||
.ok_or_else(|| anyhow!("missing required argument: column"))?;
|
||||
let column = u32::try_from(raw_column).map_err(|_| anyhow!("invalid column: {raw_column}"))?;
|
||||
let server_name = resolve_server_name(ctx, args, &rel_path)?;
|
||||
|
||||
let abs_path = crate::tool::resolve_path(&ctx.workspaces, &rel_path)?;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
//! functions for path resolution, command execution, argument extraction, and edit-log
|
||||
//! persistence. The `all_tools()` function assembles the canonical 37-tool vector exposed
|
||||
//! to the LLM provider.
|
||||
use std::convert::TryInto;
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
use sha2::Digest;
|
||||
@@ -285,11 +286,13 @@ pub fn log_write_edit_tool(
|
||||
let content_sha256 = hex::encode(sha2::Sha256::digest(content_str.as_bytes()));
|
||||
// bytes_delta: for "write" it is the full file length; for "edit" it is |new - old|
|
||||
let bytes_delta = if tool_name == "write" {
|
||||
content_str.len() as i64
|
||||
content_str.len().try_into().unwrap_or(0i64)
|
||||
} else {
|
||||
let old = args.get("old").and_then(|v| v.as_str()).unwrap_or("");
|
||||
let new = args.get("new").and_then(|v| v.as_str()).unwrap_or("");
|
||||
(new.len() as i64 - old.len() as i64).abs()
|
||||
let new_len: i64 = new.len().try_into().unwrap_or(0);
|
||||
let old_len: i64 = old.len().try_into().unwrap_or(0);
|
||||
(new_len - old_len).abs()
|
||||
};
|
||||
tracing::debug!(tool = %tool_name, path = %path, delta = bytes_delta, "logging write/edit tool result");
|
||||
let entry = zesdex_cms::domain::edit_log::EditLogEntry {
|
||||
|
||||
@@ -125,7 +125,7 @@ fn render_input_bar(frame: &mut Frame, area: Rect, state: &crate::app::state::re
|
||||
|
||||
// ── Autocomplete dropdown ────────────────────────────────────────────
|
||||
if state.input.autocomplete_visible && !state.input.autocomplete_candidates.is_empty() {
|
||||
let n = state.input.autocomplete_candidates.len().min(10) as u16;
|
||||
let n = u16::try_from(state.input.autocomplete_candidates.len().min(10)).unwrap_or(10);
|
||||
let dropdown_height = n + 2;
|
||||
let dropdown_area = Rect {
|
||||
x: area.x,
|
||||
@@ -245,7 +245,7 @@ fn render_toasts(frame: &mut Frame, state: &crate::app::state::rest::AppStateRes
|
||||
let mut y: u16 = 1;
|
||||
|
||||
for toast in active.iter().rev().take(4) {
|
||||
let line_count = toast.message.lines().count().max(1) as u16;
|
||||
let line_count = u16::try_from(toast.message.lines().count().max(1)).unwrap_or(1);
|
||||
let h = line_count + 2;
|
||||
let toast_area = Rect {
|
||||
x,
|
||||
|
||||
Reference in New Issue
Block a user