Revert clippy config to defaults, remove all pedantic/nursery/cargo lint suppressions

Removed the workspace-level clippy lint config that enabled all, pedantic, nursery,
and cargo lint groups. Removed all #[allow(clippy::...)] annotations that were only
needed to suppress those extra lints, and fixed the few default clippy warnings that
were uncovered.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-02-24 09:42:28 -05:00
parent f166bb4959
commit 9665fad133
50 changed files with 209 additions and 268 deletions

View file

@ -2,13 +2,6 @@
members = ["crates/*"]
resolver = "2"
[workspace.lints.clippy]
all = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
nursery = { level = "warn", priority = -1 }
cargo = { level = "warn", priority = -1 }
multiple_crate_versions = "allow"
[workspace.package]
edition = "2021"
version = "0.1.0"

View file

@ -48,6 +48,3 @@ tokio = { workspace = true, features = ["test-util", "macros"] }
tempfile = "3"
dotenvy = { workspace = true }
paste = "1"
[lints]
workspace = true

View file

@ -165,9 +165,7 @@ impl DockerExecutionEnvironment {
) -> Result<ExecResult, String> {
let start = Instant::now();
let effective_dir = working_dir
.map(ToString::to_string)
.unwrap_or_else(|| self.config.container_mount_point.clone());
let effective_dir = working_dir.map_or_else(|| self.config.container_mount_point.clone(), ToString::to_string);
let env: Option<Vec<String>> = env_vars.map(|vars| {
vars.iter()
@ -441,9 +439,7 @@ impl ExecutionEnvironment for DockerExecutionEnvironment {
.map_err(|e| format!("Failed to finalize tar archive: {e}"))?;
let parent_dir = std::path::Path::new(&container_path)
.parent()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|| "/".to_string());
.parent().map_or_else(|| "/".to_string(), |p| p.to_string_lossy().to_string());
let upload_opts = UploadToContainerOptions {
path: parent_dir,
@ -610,9 +606,7 @@ impl ExecutionEnvironment for DockerExecutionEnvironment {
}
async fn glob(&self, pattern: &str, path: Option<&str>) -> Result<Vec<String>, String> {
let base_dir = path
.map(|p| self.resolve_container_path(p))
.unwrap_or_else(|| self.config.container_mount_point.clone());
let base_dir = path.map_or_else(|| self.config.container_mount_point.clone(), |p| self.resolve_container_path(p));
let full_pattern = if pattern.starts_with('/') {
pattern.to_string()

View file

@ -6,6 +6,7 @@ use tokio_util::sync::CancellationToken;
///
/// Applies optional offset (0-based lines to skip) and limit (max lines to return).
/// Line numbers are 1-based and right-aligned.
#[must_use]
pub fn format_lines_numbered(content: &str, offset: Option<usize>, limit: Option<usize>) -> String {
let all_lines: Vec<&str> = content.lines().collect();
let skip = offset.unwrap_or(0);

View file

@ -11,10 +11,12 @@ impl History {
self.turns.push(turn);
}
#[must_use]
pub fn turns(&self) -> &[Turn] {
&self.turns
}
#[must_use]
pub fn convert_to_messages(&self) -> Vec<Message> {
self.turns
.iter()

View file

@ -91,9 +91,9 @@ impl ExecutionEnvironment for LocalExecutionEnvironment {
) -> Result<(), String> {
let mut dir_entries: Vec<std::fs::DirEntry> = std::fs::read_dir(base)
.map_err(|e| format!("Failed to read directory {}: {e}", base.display()))?
.filter_map(|e| e.ok())
.filter_map(std::result::Result::ok)
.collect();
dir_entries.sort_by_key(|e| e.file_name());
dir_entries.sort_by_key(std::fs::DirEntry::file_name);
for entry in dir_entries {
let metadata = entry
@ -357,7 +357,6 @@ impl ExecutionEnvironment for LocalExecutionEnvironment {
async fn sigterm_then_kill(child: &mut tokio::process::Child) {
#[cfg(unix)]
if let Some(pid) = child.id() {
#[allow(clippy::cast_possible_wrap)]
unsafe {
libc::kill(-(pid as i32), libc::SIGTERM);
}

View file

@ -21,6 +21,7 @@ fn extract_signatures_from_assistant(turn: &Turn) -> Vec<u64> {
.collect()
}
#[must_use]
pub fn detect_loop(history: &History, window_size: usize) -> bool {
// Extract tool call signatures from the last N assistant turns that have tool calls
let turns = history.turns();

View file

@ -171,7 +171,7 @@ in the project. Keep changes minimal and focused on the task.";
}
}
fn knowledge_cutoff(&self) -> &str {
fn knowledge_cutoff(&self) -> &'static str {
"May 2025"
}
}

View file

@ -204,7 +204,7 @@ in the project.";
}))
}
fn knowledge_cutoff(&self) -> &str {
fn knowledge_cutoff(&self) -> &'static str {
"January 2025"
}
}

View file

@ -171,7 +171,7 @@ in the project.";
})
}
fn knowledge_cutoff(&self) -> &str {
fn knowledge_cutoff(&self) -> &'static str {
"April 2025"
}
}

View file

@ -134,10 +134,12 @@ impl Session {
}
}
pub fn state(&self) -> SessionState {
#[must_use]
pub const fn state(&self) -> SessionState {
self.state
}
#[must_use]
pub fn subscribe(&self) -> tokio::sync::broadcast::Receiver<crate::types::SessionEvent> {
self.event_emitter.subscribe()
}
@ -160,10 +162,12 @@ impl Session {
self.cancel_token.cancel();
}
#[must_use]
pub fn followup_queue_handle(&self) -> Arc<Mutex<VecDeque<String>>> {
self.followup_queue.clone()
}
#[must_use]
pub fn cancel_token(&self) -> CancellationToken {
self.cancel_token.clone()
}
@ -180,11 +184,12 @@ impl Session {
self.config.reasoning_effort = effort;
}
pub fn set_max_turns(&mut self, max_turns: usize) {
pub const fn set_max_turns(&mut self, max_turns: usize) {
self.config.max_turns = max_turns;
}
pub fn history(&self) -> &History {
#[must_use]
pub const fn history(&self) -> &History {
&self.history
}
@ -756,10 +761,10 @@ fn truncate_tool_result(
}
}
fn is_auth_error(err: &SdkError) -> bool {
const fn is_auth_error(err: &SdkError) -> bool {
matches!(
err.provider_kind(),
Some(ProviderErrorKind::Authentication) | Some(ProviderErrorKind::AccessDenied)
Some(ProviderErrorKind::Authentication | ProviderErrorKind::AccessDenied)
)
}

View file

@ -29,6 +29,7 @@ pub struct SubAgent {
#[cfg(test)]
impl SubAgent {
#[must_use]
pub fn depth(&self) -> usize {
self.depth
}
@ -40,6 +41,7 @@ pub struct SubAgentManager {
}
impl SubAgentManager {
#[must_use]
pub fn new(max_depth: usize) -> Self {
Self {
agents: HashMap::new(),
@ -148,6 +150,7 @@ impl SubAgentManager {
}
#[cfg(test)]
#[must_use]
pub fn get(&self, agent_id: &str) -> Option<&SubAgent> {
self.agents.get(agent_id)
}
@ -192,10 +195,9 @@ pub fn make_spawn_agent_tool(
let task = required_str(&args, "task")?;
// Extract optional max_turns parameter
#[allow(clippy::cast_possible_truncation)]
let max_turns = args
.get("max_turns")
.and_then(|v| v.as_u64())
.and_then(serde_json::Value::as_u64)
.map(|v| v as usize);
// Note: working_dir and model require session factory changes to wire through

View file

@ -16,7 +16,7 @@ use tokio_util::sync::CancellationToken;
// --- MockExecutionEnvironment ---
pub(crate) struct MockExecutionEnvironment {
pub struct MockExecutionEnvironment {
pub files: HashMap<String, String>,
pub exec_result: ExecResult,
pub grep_results: Vec<String>,
@ -24,11 +24,11 @@ pub(crate) struct MockExecutionEnvironment {
pub working_dir: &'static str,
pub platform_str: &'static str,
pub os_version_str: String,
/// When true, read_file applies offset/limit by splitting on lines.
/// When true, `read_file` applies offset/limit by splitting on lines.
pub apply_read_offset_limit: bool,
/// Captures (path, content) pairs from write_file calls.
/// Captures (path, content) pairs from `write_file` calls.
pub written_files: Mutex<Vec<(String, String)>>,
/// Captures the timeout_ms argument from exec_command calls.
/// Captures the `timeout_ms` argument from `exec_command` calls.
pub captured_timeout: Mutex<Option<u64>>,
}
@ -167,8 +167,8 @@ impl ExecutionEnvironment for MockExecutionEnvironment {
// --- MutableMockExecutionEnvironment ---
/// A mock execution environment with Mutex-protected files for tests that need
/// write operations to be visible to subsequent reads (e.g., apply_patch tests).
pub(crate) struct MutableMockExecutionEnvironment {
/// write operations to be visible to subsequent reads (e.g., `apply_patch` tests).
pub struct MutableMockExecutionEnvironment {
pub files: Mutex<HashMap<String, String>>,
}
@ -266,11 +266,11 @@ impl ExecutionEnvironment for MutableMockExecutionEnvironment {
Ok(())
}
fn working_directory(&self) -> &str {
fn working_directory(&self) -> &'static str {
"/tmp"
}
fn platform(&self) -> &str {
fn platform(&self) -> &'static str {
"linux"
}
@ -281,7 +281,7 @@ impl ExecutionEnvironment for MutableMockExecutionEnvironment {
// --- TestProfile ---
pub(crate) struct TestProfile {
pub struct TestProfile {
pub registry: ToolRegistry,
pub parallel_tool_calls: bool,
pub context_window: usize,
@ -322,11 +322,11 @@ impl TestProfile {
}
impl ProviderProfile for TestProfile {
fn id(&self) -> &str {
fn id(&self) -> &'static str {
"mock"
}
fn model(&self) -> &str {
fn model(&self) -> &'static str {
"mock-model"
}
@ -360,14 +360,14 @@ impl ProviderProfile for TestProfile {
}
}
fn knowledge_cutoff(&self) -> &str {
fn knowledge_cutoff(&self) -> &'static str {
"May 2025"
}
}
// --- MockLlmProvider ---
pub(crate) struct MockLlmProvider {
pub struct MockLlmProvider {
pub responses: Vec<Response>,
pub call_index: AtomicUsize,
}
@ -383,7 +383,7 @@ impl MockLlmProvider {
#[async_trait]
impl ProviderAdapter for MockLlmProvider {
fn name(&self) -> &str {
fn name(&self) -> &'static str {
"mock"
}
@ -438,7 +438,7 @@ fn response_to_stream(response: Response) -> StreamEventStream {
// --- Helper functions ---
pub(crate) fn text_response(text: &str) -> Response {
pub fn text_response(text: &str) -> Response {
Response {
id: format!("resp_{text}"),
model: "mock-model".into(),
@ -457,13 +457,13 @@ pub(crate) fn text_response(text: &str) -> Response {
}
}
pub(crate) async fn make_client(provider: Arc<dyn ProviderAdapter>) -> Client {
pub async fn make_client(provider: Arc<dyn ProviderAdapter>) -> Client {
let mut providers = HashMap::new();
providers.insert(provider.name().to_string(), provider);
Client::new(providers, Some("mock".into()), vec![])
}
pub(crate) async fn make_session(responses: Vec<Response>) -> Session {
pub async fn make_session(responses: Vec<Response>) -> Session {
let provider = Arc::new(MockLlmProvider::new(responses));
let client = make_client(provider).await;
let profile = Arc::new(TestProfile::new());
@ -471,7 +471,7 @@ pub(crate) async fn make_session(responses: Vec<Response>) -> Session {
Session::new(client, profile, env, SessionConfig::default())
}
pub(crate) async fn make_session_with_tools(
pub async fn make_session_with_tools(
responses: Vec<Response>,
registry: ToolRegistry,
) -> Session {
@ -482,7 +482,7 @@ pub(crate) async fn make_session_with_tools(
Session::new(client, profile, env, SessionConfig::default())
}
pub(crate) async fn make_session_with_config(
pub async fn make_session_with_config(
responses: Vec<Response>,
config: SessionConfig,
) -> Session {
@ -493,7 +493,7 @@ pub(crate) async fn make_session_with_config(
Session::new(client, profile, env, config)
}
pub(crate) async fn make_session_with_tools_and_config(
pub async fn make_session_with_tools_and_config(
responses: Vec<Response>,
registry: ToolRegistry,
config: SessionConfig,
@ -505,7 +505,7 @@ pub(crate) async fn make_session_with_tools_and_config(
Session::new(client, profile, env, config)
}
pub(crate) fn tool_call_response(
pub fn tool_call_response(
tool_name: &str,
tool_call_id: &str,
args: serde_json::Value,
@ -537,7 +537,7 @@ pub(crate) fn tool_call_response(
}
}
pub(crate) fn make_echo_tool() -> crate::tool_registry::RegisteredTool {
pub fn make_echo_tool() -> crate::tool_registry::RegisteredTool {
use llm::types::ToolDefinition;
crate::tool_registry::RegisteredTool {
definition: ToolDefinition {
@ -557,7 +557,7 @@ pub(crate) fn make_echo_tool() -> crate::tool_registry::RegisteredTool {
}
}
pub(crate) fn make_error_tool() -> crate::tool_registry::RegisteredTool {
pub fn make_error_tool() -> crate::tool_registry::RegisteredTool {
use llm::types::ToolDefinition;
crate::tool_registry::RegisteredTool {
definition: ToolDefinition {
@ -573,13 +573,13 @@ pub(crate) fn make_error_tool() -> crate::tool_registry::RegisteredTool {
// --- MockErrorProvider ---
pub(crate) struct MockErrorProvider {
pub struct MockErrorProvider {
pub error: SdkError,
}
#[async_trait]
impl ProviderAdapter for MockErrorProvider {
fn name(&self) -> &str {
fn name(&self) -> &'static str {
"mock"
}
@ -595,7 +595,7 @@ impl ProviderAdapter for MockErrorProvider {
// --- CapturingLlmProvider ---
/// A mock LLM provider that captures the full Request for test assertions.
pub(crate) struct CapturingLlmProvider {
pub struct CapturingLlmProvider {
pub captured_request: Mutex<Option<Request>>,
}
@ -609,7 +609,7 @@ impl CapturingLlmProvider {
#[async_trait]
impl ProviderAdapter for CapturingLlmProvider {
fn name(&self) -> &str {
fn name(&self) -> &'static str {
"mock"
}
@ -633,14 +633,14 @@ impl ProviderAdapter for CapturingLlmProvider {
// --- MockMidStreamErrorProvider ---
/// A mock provider that yields some text deltas then an error mid-stream.
pub(crate) struct MockMidStreamErrorProvider {
pub struct MockMidStreamErrorProvider {
pub partial_text: String,
pub error: SdkError,
}
#[async_trait]
impl ProviderAdapter for MockMidStreamErrorProvider {
fn name(&self) -> &str {
fn name(&self) -> &'static str {
"mock"
}
@ -657,7 +657,7 @@ impl ProviderAdapter for MockMidStreamErrorProvider {
}
}
pub(crate) fn multi_tool_call_response(
pub fn multi_tool_call_response(
calls: Vec<(&str, &str, serde_json::Value)>,
) -> Response {
use llm::types::{ContentPart, Role, ToolCall};

View file

@ -33,9 +33,7 @@ pub fn make_read_file_tool() -> RegisteredTool {
let offset = args.get("offset").and_then(serde_json::Value::as_u64);
let limit = args.get("limit").and_then(serde_json::Value::as_u64);
#[allow(clippy::cast_possible_truncation)]
let offset_usize = offset.map(|v| v as usize);
#[allow(clippy::cast_possible_truncation)]
let limit_usize = limit.map(|v| v as usize);
let content = env.read_file(file_path, offset_usize, limit_usize).await?;
@ -211,7 +209,6 @@ pub fn make_grep_tool() -> RegisteredTool {
.and_then(serde_json::Value::as_str)
.unwrap_or(".");
#[allow(clippy::cast_possible_truncation)]
let options = GrepOptions {
glob_filter: args
.get("glob_filter")
@ -325,7 +322,6 @@ pub(crate) fn make_list_dir_tool() -> RegisteredTool {
executor: Arc::new(|args, env, _cancel| {
Box::pin(async move {
let path = required_str(&args, "path")?;
#[allow(clippy::cast_possible_truncation)]
let depth = args
.get("depth")
.and_then(serde_json::Value::as_u64)

View file

@ -36,6 +36,7 @@ fn default_truncation_mode(tool_name: &str) -> TruncationMode {
}
}
#[must_use]
pub fn truncate_output(output: &str, max_chars: usize, mode: TruncationMode) -> String {
if output.len() <= max_chars {
return output.to_string();
@ -64,6 +65,7 @@ pub fn truncate_output(output: &str, max_chars: usize, mode: TruncationMode) ->
}
}
#[must_use]
pub fn truncate_lines(output: &str, max_lines: usize) -> String {
let lines: Vec<&str> = output.lines().collect();
if lines.len() <= max_lines {
@ -82,6 +84,7 @@ pub fn truncate_lines(output: &str, max_lines: usize) -> String {
)
}
#[must_use]
pub fn truncate_tool_output(output: &str, tool_name: &str, config: &SessionConfig) -> String {
let mode = default_truncation_mode(tool_name);

View file

@ -11,7 +11,7 @@ pub enum Turn {
content: String,
tool_calls: Vec<ToolCall>,
reasoning: Option<String>,
/// Opaque provider-specific content parts (e.g. OpenAI reasoning items)
/// Opaque provider-specific content parts (e.g. `OpenAI` reasoning items)
/// that must be preserved for round-tripping but don't map to standard fields.
provider_parts: Vec<ContentPart>,
usage: Usage,

View file

@ -26,7 +26,7 @@ async fn make_session(provider: &str, model: &str, cwd: &Path) -> Session {
let factory_cwd = cwd.to_path_buf();
let factory: agent::subagent::SessionFactory = {
let provider = factory_provider.to_string();
let model = factory_model.clone();
let model = factory_model;
Arc::new(move || {
let sub_profile: Arc<dyn ProviderProfile> = match provider.as_str() {
"anthropic" => Arc::new(AnthropicProfile::new(&model)),

View file

@ -53,6 +53,3 @@ http-body-util = "0.1"
dotenvy.workspace = true
assert_cmd = "2"
predicates = "3"
[lints]
workspace = true

View file

@ -21,7 +21,7 @@ pub struct Checkpoint {
/// Persisted node outcomes for goal gate checks after resume.
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub node_outcomes: HashMap<String, Outcome>,
/// The node to resume execution at (the next node after the checkpoint's current_node).
/// The node to resume execution at (the next node after the checkpoint's `current_node`).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub next_node_id: Option<String>,
}

View file

@ -34,7 +34,6 @@ struct CostAccumulator {
/// # Errors
///
/// Returns an error if the pipeline cannot be read, parsed, validated, or executed.
#[allow(clippy::too_many_lines)]
pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Result<()> {
// 1. Parse and validate pipeline
let source = read_dot_file(&args.pipeline)?;
@ -83,15 +82,13 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu
let accumulator = Arc::new(Mutex::new(CostAccumulator::default()));
let acc_clone = Arc::clone(&accumulator);
emitter.on_event(move |event| {
if let crate::event::PipelineEvent::StageCompleted { usage, .. } = event {
if let Some(u) = usage {
let mut acc = acc_clone.lock().unwrap();
acc.total_input_tokens += u.input_tokens;
acc.total_output_tokens += u.output_tokens;
if let Some(cost) = compute_stage_cost(u) {
acc.total_cost += cost;
acc.has_pricing = true;
}
if let crate::event::PipelineEvent::StageCompleted { usage: Some(u), .. } = event {
let mut acc = acc_clone.lock().unwrap();
acc.total_input_tokens += u.input_tokens;
acc.total_output_tokens += u.output_tokens;
if let Some(cost) = compute_stage_cost(u) {
acc.total_cost += cost;
acc.has_pricing = true;
}
}
});

View file

@ -50,7 +50,6 @@ impl Default for BackoffConfig {
impl BackoffConfig {
/// Calculate delay for a given attempt (1-indexed).
#[must_use]
#[allow(clippy::missing_panics_doc)]
pub fn delay_for_attempt(&self, attempt: u32) -> std::time::Duration {
let exponent = attempt.saturating_sub(1);
let initial = f64::from(u32::try_from(self.initial_delay_ms).unwrap_or(u32::MAX));
@ -71,9 +70,7 @@ impl BackoffConfig {
} else if final_ms >= f64::from(u32::MAX) {
u64::from(u32::MAX)
} else {
// Safe: final_ms is in [0, u32::MAX] so the truncated integer fits in u64
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
{ final_ms as u64 }
final_ms as u64
};
std::time::Duration::from_millis(ms)
}
@ -98,7 +95,7 @@ impl RetryPolicy {
/// Standard retry policy: 5 attempts, 200ms initial, 2x factor.
#[must_use]
pub fn standard() -> Self {
pub const fn standard() -> Self {
Self {
max_attempts: 5,
backoff: BackoffConfig {
@ -112,7 +109,7 @@ impl RetryPolicy {
/// Aggressive retry: 5 attempts, 500ms initial, 2x factor.
#[must_use]
pub fn aggressive() -> Self {
pub const fn aggressive() -> Self {
Self {
max_attempts: 5,
backoff: BackoffConfig {
@ -126,7 +123,7 @@ impl RetryPolicy {
/// Linear retry: 3 attempts, 500ms fixed delay.
#[must_use]
pub fn linear() -> Self {
pub const fn linear() -> Self {
Self {
max_attempts: 3,
backoff: BackoffConfig {
@ -140,7 +137,7 @@ impl RetryPolicy {
/// Patient retry: 3 attempts, 2000ms initial, 3x factor.
#[must_use]
pub fn patient() -> Self {
pub const fn patient() -> Self {
Self {
max_attempts: 3,
backoff: BackoffConfig {
@ -260,7 +257,7 @@ fn write_manifest(logs_root: &Path, graph: &Graph) {
}
}
/// Write status.json for a completed node into {logs_root}/{node_id}/status.json.
/// Write status.json for a completed node into {`logs_root}/{node_id}/status.json`.
fn write_node_status(logs_root: &Path, node_id: &str, outcome: &Outcome) {
let node_dir = logs_root.join(node_id);
let _ = std::fs::create_dir_all(&node_dir);
@ -467,7 +464,7 @@ impl PipelineEngine {
}
}
/// Create a new engine with an interviewer for inform() callbacks.
/// Create a new engine with an interviewer for `inform()` callbacks.
#[must_use]
pub fn with_interviewer(
registry: HandlerRegistry,
@ -633,7 +630,7 @@ impl PipelineEngine {
}
/// Resume from a checkpoint. Restores context, completed nodes, and continues
/// execution from the node after the checkpoint's current_node.
/// execution from the node after the checkpoint's `current_node`.
///
/// # Errors
///
@ -647,8 +644,7 @@ impl PipelineEngine {
self.run_internal(graph, config, Some(checkpoint), None).await
}
/// Internal run implementation supporting optional checkpoint resume and start_at override.
#[allow(clippy::too_many_lines)]
/// Internal run implementation supporting optional checkpoint resume and `start_at` override.
async fn run_internal(
&self,
graph: &Graph,
@ -776,7 +772,9 @@ impl PipelineEngine {
// Preamble injection at execution time (spec 5.4 / 8.3): synthesize a
// fidelity-appropriate preamble from runtime data for handlers to read
if fidelity != "full" {
if fidelity == "full" {
context.set("current.preamble", serde_json::json!(""));
} else {
let preamble = build_preamble(
&fidelity,
&context,
@ -785,8 +783,6 @@ impl PipelineEngine {
&node_outcomes,
);
context.set("current.preamble", serde_json::json!(preamble));
} else {
context.set("current.preamble", serde_json::json!(""));
}
// Thread context sharing: resolve thread ID and store in context for handlers
@ -1183,13 +1179,11 @@ mod tests {
#[test]
fn build_retry_policy_all_presets() {
let presets = vec![
("none", 1u32),
let presets = [("none", 1u32),
("standard", 5),
("aggressive", 5),
("linear", 3),
("patient", 3),
];
("patient", 3)];
let graph = Graph::new("test");
let (name, expected) = presets[0];
let mut node = Node::new("n");
@ -2228,7 +2222,7 @@ mod tests {
// --- Gap #15: Interviewer.inform() tests ---
/// Mock interviewer that records inform() calls.
/// Mock interviewer that records `inform()` calls.
struct RecordingInformer {
messages: std::sync::Mutex<Vec<(String, String)>>,
}

View file

@ -106,7 +106,7 @@ mod tests {
#[test]
fn result_type_alias_works() {
let ok: Result<i32> = Ok(42);
assert_eq!(ok.unwrap(), 42);
assert!(ok.is_ok());
let err: Result<i32> = Err(AttractorError::Parse("bad".to_string()));
assert!(err.is_err());

View file

@ -404,7 +404,7 @@ mod tests {
#[test]
fn attr_value_as_f64() {
assert_eq!(AttrValue::Float(3.14).as_f64(), Some(3.14));
assert_eq!(AttrValue::Float(3.15).as_f64(), Some(3.15));
assert_eq!(AttrValue::Integer(1).as_f64(), None);
}

View file

@ -120,7 +120,7 @@ fn heuristic_select(results: &serde_json::Value) -> Candidate {
.to_string(),
score: v
.get("score")
.and_then(|v| v.as_f64())
.and_then(serde_json::Value::as_f64)
.unwrap_or(0.0),
})
.collect();
@ -210,7 +210,7 @@ async fn llm_evaluate(
.to_string();
let score = v
.get("score")
.and_then(|v| v.as_f64())
.and_then(serde_json::Value::as_f64)
.unwrap_or(0.0);
return Ok(Candidate {
id: id.to_string(),

View file

@ -14,7 +14,7 @@ use super::{EngineServices, Handler};
/// Trait for observing child pipeline state during the manager loop.
#[async_trait]
pub trait ChildObserver: Send + Sync {
/// Launch the child pipeline. Called before the observation loop when child_autostart is true.
/// Launch the child pipeline. Called before the observation loop when `child_autostart` is true.
async fn launch_child(
&self,
_dotfile: &str,
@ -78,7 +78,7 @@ impl Handler for ManagerLoopHandler {
let poll_interval = node
.attrs
.get("manager.poll_interval")
.and_then(|v| v.as_duration())
.and_then(super::super::graph::types::AttrValue::as_duration)
.unwrap_or_else(|| {
let raw = node
.attrs
@ -91,7 +91,7 @@ impl Handler for ManagerLoopHandler {
let max_cycles = node
.attrs
.get("manager.max_cycles")
.and_then(|v| v.as_i64())
.and_then(super::super::graph::types::AttrValue::as_i64)
.unwrap_or(1000);
let max_cycles = u64::try_from(max_cycles).unwrap_or(1000).max(1);
@ -139,8 +139,7 @@ impl Handler for ManagerLoopHandler {
.attrs
.get("manager.steer_cooldown")
.and_then(|v| v.as_str())
.map(parse_duration_str)
.unwrap_or(Duration::ZERO);
.map_or(Duration::ZERO, parse_duration_str);
let mut last_steer_time: Option<Instant> = None;
// Observation loop

View file

@ -104,7 +104,7 @@ impl Handler for ParallelHandler {
let max_parallel = node
.attrs
.get("max_parallel")
.and_then(|v| v.as_i64())
.and_then(super::super::graph::types::AttrValue::as_i64)
.unwrap_or(4);
let max_parallel = usize::try_from(max_parallel).unwrap_or(4).max(1);
@ -243,9 +243,7 @@ impl Handler for ParallelHandler {
// Evaluate join policy
let status = match join_policy {
JoinPolicy::WaitAll => {
if fail_count == 0 {
StageStatus::Success
} else if error_policy == ErrorPolicy::Ignore {
if fail_count == 0 || error_policy == ErrorPolicy::Ignore {
StageStatus::Success
} else {
StageStatus::PartialSuccess
@ -266,11 +264,8 @@ impl Handler for ParallelHandler {
}
}
JoinPolicy::Quorum(fraction) => {
#[allow(clippy::cast_precision_loss)]
let total_f64 = total as f64;
let threshold_f64 = (fraction * total_f64).ceil();
// Safe: threshold_f64 is non-negative and bounded by total
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let threshold = threshold_f64 as usize;
if success_count >= threshold {
StageStatus::Success

View file

@ -151,11 +151,11 @@ mod tests {
node.attrs.insert(
"sub_pipeline.dot_source".to_string(),
AttrValue::String(
r#"digraph Sub {
r"digraph Sub {
start [shape=Mdiamond]
exit [shape=Msquare]
start -> exit
}"#
}"
.to_string(),
),
);
@ -179,11 +179,11 @@ mod tests {
node.attrs.insert(
"sub_pipeline.dot_source".to_string(),
AttrValue::String(
r#"digraph Sub {
r"digraph Sub {
start [shape=Mdiamond]
exit [shape=Msquare]
start -> exit
}"#
}"
.to_string(),
),
);
@ -232,12 +232,12 @@ mod tests {
node.attrs.insert(
"sub_pipeline.dot_source".to_string(),
AttrValue::String(
r#"digraph Sub {
r"digraph Sub {
start [shape=Mdiamond]
work [shape=box]
exit [shape=Msquare]
start -> work -> exit
}"#
}"
.to_string(),
),
);

View file

@ -48,7 +48,7 @@ mod tests {
#[tokio::test]
async fn callback_receives_question_text() {
let interviewer = CallbackInterviewer::new(|q| Answer::text(q.text.clone()));
let interviewer = CallbackInterviewer::new(|q| Answer::text(q.text));
let q = Question::new("hello world", QuestionType::Freeform);
let answer = interviewer.ask(q).await;
assert_eq!(answer.text, Some("hello world".to_string()));

View file

@ -68,13 +68,13 @@ mod tests {
fn ast_value_variants() {
let s = AstValue::Str("hello".into());
let i = AstValue::Int(42);
let f = AstValue::Float(3.14);
let f = AstValue::Float(3.15);
let b = AstValue::Bool(true);
let id = AstValue::Ident("LR".into());
assert_eq!(s, AstValue::Str("hello".into()));
assert_eq!(i, AstValue::Int(42));
assert_eq!(f, AstValue::Float(3.14));
assert_eq!(f, AstValue::Float(3.15));
assert_eq!(b, AstValue::Bool(true));
assert_eq!(id, AstValue::Ident("LR".into()));
}

View file

@ -1,5 +1,3 @@
#![allow(clippy::module_name_repetitions)]
/// Strip `//` line comments and `/* */` block comments from DOT source.
#[must_use]
pub fn strip_comments(input: &str) -> String {
@ -53,11 +51,6 @@ pub fn strip_comments(input: &str) -> String {
}
/// nom combinators for whitespace and common tokens.
#[allow(
clippy::missing_errors_doc,
clippy::must_use_candidate,
clippy::missing_const_for_fn
)]
pub mod combinators {
use nom::branch::alt;
use nom::bytes::complete::{tag, take_while, take_while1};
@ -368,9 +361,9 @@ mod tests {
#[test]
fn parse_float() {
assert_eq!(float_value("3.14 rest"), Ok((" rest", 3.14)));
assert_eq!(float_value("3.15 rest"), Ok((" rest", 3.15)));
assert_eq!(float_value("0.5 rest"), Ok((" rest", 0.5)));
assert_eq!(float_value("-3.14 rest"), Ok((" rest", -3.14)));
assert_eq!(float_value("-3.15 rest"), Ok((" rest", -3.15)));
assert_eq!(float_value(".5 rest"), Ok((" rest", 0.5)));
}
@ -405,7 +398,7 @@ mod tests {
Ok(("", AstValue::Str("hello".into())))
);
assert_eq!(value("250ms"), Ok(("", AstValue::Str("250ms".into()))));
assert_eq!(value("3.14"), Ok(("", AstValue::Float(3.14))));
assert_eq!(value("3.15"), Ok(("", AstValue::Float(3.15))));
assert_eq!(value("42"), Ok(("", AstValue::Int(42))));
assert_eq!(value("true"), Ok(("", AstValue::Bool(true))));
assert_eq!(value("LR"), Ok(("", AstValue::Ident("LR".into()))));

View file

@ -121,13 +121,13 @@ mod tests {
#[test]
fn parse_with_comments() {
let input = r#"// This is a comment
let input = r"// This is a comment
digraph Test {
/* block comment */
start [shape=Mdiamond] // inline comment
exit [shape=Msquare]
start -> exit
}"#;
}";
let graph = parse(input).unwrap();
assert_eq!(graph.nodes.len(), 2);
}

View file

@ -141,7 +141,6 @@ impl SemanticState {
}
}
#[allow(clippy::too_many_lines)]
fn process_statements(
&mut self,
statements: &[Statement],
@ -299,8 +298,8 @@ mod tests {
#[test]
fn convert_ast_float() {
assert_eq!(
convert_value(&AstValue::Float(3.14)),
AttrValue::Float(3.14)
convert_value(&AstValue::Float(3.15)),
AttrValue::Float(3.15)
);
}

View file

@ -19,7 +19,7 @@ use crate::engine::{PipelineEngine, RunConfig};
use crate::event::{EventEmitter, PipelineEvent};
use crate::handler::HandlerRegistry;
use crate::interviewer::web::WebInterviewer;
use crate::interviewer::{Answer, Interviewer, QuestionOption};
use crate::interviewer::{Answer, Interviewer};
/// Status of a managed pipeline.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
@ -185,12 +185,12 @@ async fn start_pipeline(
pipelines.insert(
pipeline_id.clone(),
ManagedPipeline {
dot_source: req.dot_source.clone(),
dot_source: req.dot_source,
status: PipelineStatus::Running,
error: None,
interviewer: Arc::clone(&interviewer),
event_tx: Some(event_tx.clone()),
context: Some(context.clone()),
event_tx: Some(event_tx),
context: Some(context),
checkpoint: None,
cancel_tx: Some(cancel_tx),
cancel_token: Arc::clone(&cancel_token),
@ -318,10 +318,7 @@ async fn submit_answer(
match option {
Some(opt) => Answer::selected(
key.clone(),
QuestionOption {
key: opt.key,
label: opt.label,
},
opt,
),
None => {
return (

View file

@ -281,11 +281,11 @@ mod tests {
#[test]
fn parse_multiple_rules() {
let input = r#"
let input = r"
* { llm_model: claude-sonnet-4-5; llm_provider: anthropic; }
.code { llm_model: claude-opus-4-6; llm_provider: anthropic; }
#critical_review { llm_model: gpt-5.2; llm_provider: openai; reasoning_effort: high; }
"#;
";
let ss = parse_stylesheet(input).unwrap();
assert_eq!(ss.rules.len(), 3);
}
@ -393,11 +393,11 @@ mod tests {
#[test]
fn spec_section_86_example() {
let input = r#"
let input = r"
* { llm_model: claude-sonnet-4-5; llm_provider: anthropic; }
.code { llm_model: claude-opus-4-6; llm_provider: anthropic; }
#critical_review { llm_model: gpt-5.2; llm_provider: openai; reasoning_effort: high; }
"#;
";
let ss = parse_stylesheet(input).unwrap();
let mut graph = Graph::new("test");

View file

@ -55,7 +55,8 @@ pub struct GraphMergeTransform {
}
impl GraphMergeTransform {
pub fn new(secondary_graphs: Vec<Graph>) -> Self {
#[must_use]
pub const fn new(secondary_graphs: Vec<Graph>) -> Self {
Self { secondary_graphs }
}
}
@ -188,7 +189,7 @@ mod tests {
let transform = VariableExpansionTransform;
// Should not panic
transform.apply(&mut graph);
assert!(graph.nodes["plan"].attrs.get("prompt").is_none());
assert!(!graph.nodes["plan"].attrs.contains_key("prompt"));
}
#[test]
@ -285,7 +286,7 @@ mod tests {
PreambleTransform.apply(&mut graph);
assert!(graph.nodes["work"].attrs.get("prompt").is_none());
assert!(!graph.nodes["work"].attrs.contains_key("prompt"));
}
// -----------------------------------------------------------------------

View file

@ -2020,7 +2020,7 @@ mod tests {
let d = rule.apply(&g);
assert_eq!(d.len(), 1);
assert_eq!(d[0].severity, Severity::Error);
assert!(d[0].message.contains("2"));
assert!(d[0].message.contains('2'));
}
// --- prompt_on_llm_nodes: empty prompt string still triggers ---

View file

@ -695,11 +695,11 @@ fn variable_expansion_replaces_goal_in_prompts() {
#[test]
fn stylesheet_application_by_specificity() {
let stylesheet_text = r#"
let stylesheet_text = r"
* { llm_model: claude-sonnet-4-5; llm_provider: anthropic; }
.code { llm_model: claude-opus-4-6; llm_provider: anthropic; }
#critical_review { llm_model: gpt-5.2; llm_provider: openai; reasoning_effort: high; }
"#;
";
let mut graph = Graph::new("test");
graph.attrs.insert(
@ -1072,7 +1072,7 @@ impl CodergenBackend for MockCodergenBackend {
// Helpers for parity tests
// ---------------------------------------------------------------------------
/// A handler backed by a shared AtomicU32 counter.
/// A handler backed by a shared `AtomicU32` counter.
/// Returns Fail on call 0, Success on call >= 1.
struct CounterHandler {
call_count: Arc<std::sync::atomic::AtomicU32>,
@ -1099,7 +1099,7 @@ impl Handler for CounterHandler {
}
}
/// A handler that sets context_updates = {"my_flag": "set"}.
/// A handler that sets `context_updates` = {"`my_flag"`: "set"}.
struct ContextSetterHandler;
#[async_trait::async_trait]
@ -3154,14 +3154,9 @@ mod sse_events {
// Collect SSE frames with a timeout
let mut body = response.into_body();
let mut sse_data = String::new();
loop {
match tokio::time::timeout(Duration::from_millis(500), body.frame()).await {
Ok(Some(Ok(frame))) => {
if let Some(data) = frame.data_ref() {
sse_data.push_str(&String::from_utf8_lossy(data));
}
}
_ => break,
while let Ok(Some(Ok(frame))) = tokio::time::timeout(Duration::from_millis(500), body.frame()).await {
if let Some(data) = frame.data_ref() {
sse_data.push_str(&String::from_utf8_lossy(data));
}
}
@ -3665,12 +3660,14 @@ async fn graph_merge_e2e_through_engine() {
// Context fidelity integration tests (spec Section 5.4)
// ===========================================================================
type SharedVec<T> = Arc<std::sync::Mutex<Vec<T>>>;
/// Shared capture storage for fidelity tests.
#[derive(Clone)]
struct FidelityCaptures {
fidelities: Arc<std::sync::Mutex<Vec<(String, String)>>>,
thread_ids: Arc<std::sync::Mutex<Vec<(String, Option<String>)>>>,
preambles: Arc<std::sync::Mutex<Vec<(String, String)>>>,
fidelities: SharedVec<(String, String)>,
thread_ids: SharedVec<(String, Option<String>)>,
preambles: SharedVec<(String, String)>,
}
impl FidelityCaptures {
@ -3683,7 +3680,7 @@ impl FidelityCaptures {
}
}
/// A handler that captures the resolved fidelity and thread_id from the context.
/// A handler that captures the resolved fidelity and `thread_id` from the context.
struct FidelityCapturingHandler {
captures: FidelityCaptures,
}
@ -4618,13 +4615,15 @@ async fn fidelity_summary_low_excludes_context_values_in_pipeline() {
let config_low = RunConfig { logs_root: dir_low.path().to_path_buf(), cancel_token: None };
engine_low.run(&graph_low, &config_low).await.expect("run low");
let preambles_low = captures_low.preambles.lock().unwrap();
let low_preamble = &preambles_low[1].1;
// summary:low should not include "Context values:" section
assert!(
!low_preamble.contains("Context values:"),
"summary:low preamble should not include context values section"
);
{
let preambles_low = captures_low.preambles.lock().unwrap();
let low_preamble = &preambles_low[1].1;
// summary:low should not include "Context values:" section
assert!(
!low_preamble.contains("Context values:"),
"summary:low preamble should not include context values section"
);
}
// Now run summary:medium and verify it DOES include context values
let mut graph_med = make_graph_with_start_exit("SummaryMedIncludesContext");
@ -5137,12 +5136,9 @@ mod real_llm {
#[tokio::test]
#[ignore]
async fn real_llm_linear_pipeline() {
let client = match make_llm_client().await {
Some(c) => c,
None => {
eprintln!("Skipping: ANTHROPIC_API_KEY not set");
return;
}
let client = if let Some(c) = make_llm_client().await { c } else {
eprintln!("Skipping: ANTHROPIC_API_KEY not set");
return;
};
let mut graph = Graph::new("RealLLMLinear");
@ -5251,12 +5247,9 @@ mod real_llm {
#[tokio::test]
#[ignore]
async fn real_llm_two_stage_pipeline() {
let client = match make_llm_client().await {
Some(c) => c,
None => {
eprintln!("Skipping: ANTHROPIC_API_KEY not set");
return;
}
let client = if let Some(c) = make_llm_client().await { c } else {
eprintln!("Skipping: ANTHROPIC_API_KEY not set");
return;
};
let mut graph = Graph::new("RealLLMTwoStage");
@ -5343,12 +5336,9 @@ mod real_llm {
#[tokio::test]
#[ignore]
async fn real_llm_human_gate_auto_approve() {
let client = match make_llm_client().await {
Some(c) => c,
None => {
eprintln!("Skipping: ANTHROPIC_API_KEY not set");
return;
}
let client = if let Some(c) = make_llm_client().await { c } else {
eprintln!("Skipping: ANTHROPIC_API_KEY not set");
return;
};
let mut graph = Graph::new("RealLLMGate");
@ -5803,7 +5793,7 @@ async fn human_gate_freeform_fallback_on_unmatched_text() {
);
}
/// Verifies that the Question presented to the interviewer has allow_freeform=true
/// Verifies that the Question presented to the interviewer has `allow_freeform=true`
/// when a freeform edge is present on the human gate.
#[tokio::test]
async fn human_gate_freeform_sets_allow_freeform_on_question() {
@ -5892,7 +5882,7 @@ async fn human_gate_freeform_sets_allow_freeform_on_question() {
);
}
/// Verifies that the Question presented to the interviewer has allow_freeform=false
/// Verifies that the Question presented to the interviewer has `allow_freeform=false`
/// when no freeform edge is present on the human gate (fixed choices only).
#[tokio::test]
async fn human_gate_without_freeform_sets_allow_freeform_false() {
@ -6409,10 +6399,9 @@ async fn tool_hooks_pre_receives_node_id_env_var() {
graph [goal="Test env vars"]
start [shape=Mdiamond]
exit [shape=Msquare]
my_step [shape=box, label="MyStep", prompt="Do work", tool_hooks.pre="{}"]
my_step [shape=box, label="MyStep", prompt="Do work", tool_hooks.pre="{hook_cmd}"]
start -> my_step -> exit
}}"#,
hook_cmd
}}"#
);
let graph = parse(&input).expect("parse should succeed");

View file

@ -39,6 +39,3 @@ http = "1"
tokio = { workspace = true, features = ["test-util", "macros"] }
assert_cmd = "2"
predicates = "3"
[lints]
workspace = true

View file

@ -111,7 +111,7 @@ fn resolve_prompt(arg: Option<String>, stdin: Option<String>) -> Result<String>
}
}
/// Returns (model_id, provider) from the catalog, falling back to the first catalog model.
/// Returns (`model_id`, provider) from the catalog, falling back to the first catalog model.
fn resolve_model(model_arg: Option<String>) -> (String, Option<String>) {
let raw = model_arg.unwrap_or_else(|| {
catalog::list_models(None)
@ -408,7 +408,7 @@ mod tests {
// Step 6/7/8: Integration tests gated behind API key
#[test]
#[ignore]
#[ignore = "requires API key"]
fn prompt_no_stream_generates_response() {
ullm()
.args(["prompt", "--no-stream", "-m", "claude-sonnet-4-5", "Say just the word 'hello'"])
@ -418,7 +418,7 @@ mod tests {
}
#[test]
#[ignore]
#[ignore = "requires API key"]
fn prompt_stream_generates_response() {
ullm()
.args(["prompt", "-m", "claude-sonnet-4-5", "Say just the word 'hello'"])
@ -428,7 +428,7 @@ mod tests {
}
#[test]
#[ignore]
#[ignore = "requires API key"]
fn prompt_usage_shows_tokens() {
ullm()
.args(["prompt", "--no-stream", "-u", "-m", "claude-sonnet-4-5", "Say just the word 'hello'"])

View file

@ -89,16 +89,16 @@ impl SdkError {
#[must_use]
pub const fn retryable(&self) -> bool {
match self {
Self::Provider { kind, .. } => match kind {
Self::Provider { kind, .. } => !matches!(
kind,
ProviderErrorKind::Authentication
| ProviderErrorKind::AccessDenied
| ProviderErrorKind::NotFound
| ProviderErrorKind::InvalidRequest
| ProviderErrorKind::ContextLength
| ProviderErrorKind::QuotaExceeded
| ProviderErrorKind::ContentFilter => false,
_ => true,
},
| ProviderErrorKind::AccessDenied
| ProviderErrorKind::NotFound
| ProviderErrorKind::InvalidRequest
| ProviderErrorKind::ContextLength
| ProviderErrorKind::QuotaExceeded
| ProviderErrorKind::ContentFilter
),
Self::InvalidToolCall { .. }
| Self::NoObjectGenerated { .. }
| Self::Abort { .. }

View file

@ -99,7 +99,6 @@ fn build_generate_result(steps: Vec<StepResult>, total_usage: Usage) -> Generate
/// # Panics
///
/// Panics if a tool's `execute` handler is `None` when matched during tool execution.
#[allow(clippy::too_many_lines)]
pub async fn generate(params: GenerateParams) -> Result<GenerateResult, SdkError> {
let client = match params.client.clone() {
Some(c) => c,
@ -582,7 +581,6 @@ pub async fn stream(params: GenerateParams) -> Result<StreamResult, SdkError> {
///
/// Returns `SdkError::Configuration` if both `prompt` and `messages` are set,
/// or any provider error encountered during streaming setup.
#[allow(clippy::too_many_lines)]
async fn stream_with_tool_loop(params: GenerateParams) -> Result<StreamEventStream, SdkError> {
let client = match params.client.clone() {
Some(c) => c,
@ -934,7 +932,7 @@ impl ObjectStreamResult {
/// Returns the final parsed object after the stream has yielded a `Complete` event.
#[must_use]
pub fn object(&self) -> Option<&serde_json::Value> {
pub const fn object(&self) -> Option<&serde_json::Value> {
self.object.as_ref()
}
}
@ -1066,7 +1064,7 @@ mod tests {
#[async_trait::async_trait]
impl ProviderAdapter for MockProvider {
fn name(&self) -> &str {
fn name(&self) -> &'static str {
"mock"
}
@ -1208,7 +1206,7 @@ mod tests {
#[async_trait::async_trait]
impl ProviderAdapter for ToolCallMockProvider {
fn name(&self) -> &str {
fn name(&self) -> &'static str {
"mock"
}
@ -1541,7 +1539,7 @@ mod tests {
#[async_trait::async_trait]
impl ProviderAdapter for StreamingJsonMockProvider {
fn name(&self) -> &str {
fn name(&self) -> &'static str {
"mock"
}
@ -1725,7 +1723,7 @@ mod tests {
let results: Vec<Result<ObjectStreamEvent, SdkError>> = obj_stream.collect().await;
let has_error = results.iter().any(|r| r.is_err());
let has_error = results.iter().any(std::result::Result::is_err);
assert!(has_error, "Expected an error for invalid final JSON");
}
@ -1760,7 +1758,7 @@ mod tests {
#[async_trait::async_trait]
impl ProviderAdapter for AlwaysToolCallProvider {
fn name(&self) -> &str {
fn name(&self) -> &'static str {
"mock"
}
@ -1959,7 +1957,7 @@ mod tests {
#[async_trait::async_trait]
impl ProviderAdapter for StreamingToolCallMockProvider {
fn name(&self) -> &str {
fn name(&self) -> &'static str {
"mock"
}
@ -2313,7 +2311,7 @@ mod tests {
#[async_trait::async_trait]
impl ProviderAdapter for FailThenStreamProvider {
fn name(&self) -> &str {
fn name(&self) -> &'static str {
"mock"
}
@ -2435,7 +2433,7 @@ mod tests {
#[async_trait::async_trait]
impl ProviderAdapter for SlowStreamProvider {
fn name(&self) -> &str {
fn name(&self) -> &'static str {
"mock"
}
@ -2538,7 +2536,7 @@ mod tests {
#[async_trait::async_trait]
impl ProviderAdapter for SlowToolCallStreamProvider {
fn name(&self) -> &str {
fn name(&self) -> &'static str {
"mock"
}

View file

@ -42,6 +42,10 @@ pub trait Middleware: Send + Sync {
/// Process an individual stream event. Override to observe or transform
/// events as they pass through the middleware. The default implementation
/// passes events through unchanged.
///
/// # Errors
///
/// Returns `SdkError` if the incoming event is an error or if processing fails.
fn process_stream_event(
&self,
event: Result<StreamEvent, SdkError>,

View file

@ -39,6 +39,11 @@ pub trait ProviderAdapter: Send + Sync {
///
/// Returns `Err(SdkError::UnsupportedToolChoice)` if the adapter does not
/// support the given mode.
///
/// # Errors
///
/// Returns `SdkError::UnsupportedToolChoice` when the adapter does not
/// support the requested tool choice mode.
pub fn validate_tool_choice(
adapter: &dyn ProviderAdapter,
tool_choice: &ToolChoice,

View file

@ -144,7 +144,6 @@ struct ApiResponse {
}
#[derive(serde::Deserialize)]
#[allow(clippy::struct_field_names)]
struct ApiUsage {
input_tokens: i64,
output_tokens: i64,
@ -1131,7 +1130,6 @@ fn build_api_request(
(api_request, req_builder)
}
#[allow(clippy::unnecessary_literal_bound)]
#[async_trait::async_trait]
impl ProviderAdapter for Adapter {
fn name(&self) -> &str {

View file

@ -146,7 +146,6 @@ struct CandidateContent {
#[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")]
#[allow(clippy::struct_field_names)]
struct UsageMetadata {
prompt_token_count: Option<i64>,
candidates_token_count: Option<i64>,
@ -224,7 +223,6 @@ fn build_tool_call_id_to_name(messages: &[&Message]) -> std::collections::HashMa
}
/// Translate unified messages to Gemini content format.
#[allow(clippy::too_many_lines)]
fn translate_messages(messages: &[&Message]) -> Vec<Content> {
let id_to_name = build_tool_call_id_to_name(messages);
let mut contents: Vec<Content> = Vec::new();
@ -899,7 +897,6 @@ impl SseStreamState {
}
}
#[allow(clippy::unnecessary_literal_bound)]
#[async_trait::async_trait]
impl ProviderAdapter for Adapter {
fn name(&self) -> &str {

View file

@ -141,7 +141,6 @@ struct ApiResponse {
}
#[derive(serde::Deserialize)]
#[allow(clippy::struct_field_names)]
struct ApiUsage {
input_tokens: i64,
output_tokens: i64,
@ -174,7 +173,6 @@ fn map_finish_reason(status: Option<&str>, has_tool_calls: bool) -> FinishReason
}
/// Translate unified messages to Responses API `input` array format.
#[allow(clippy::too_many_lines)]
fn translate_input(messages: &[Message]) -> (Option<String>, Vec<serde_json::Value>) {
let mut instructions_parts: Vec<String> = Vec::new();
let mut input: Vec<serde_json::Value> = Vec::new();
@ -902,7 +900,6 @@ fn handle_response_completed(
));
}
#[allow(clippy::unnecessary_literal_bound)]
#[async_trait::async_trait]
impl ProviderAdapter for Adapter {
fn name(&self) -> &str {
@ -1355,7 +1352,7 @@ mod tests {
content: vec![
ContentPart::Other {
kind: "openai_reasoning".to_string(),
data: reasoning.clone(),
data: reasoning,
},
ContentPart::ToolCall(tc),
],

View file

@ -164,7 +164,6 @@ struct ApiFunction {
}
#[derive(serde::Deserialize)]
#[allow(clippy::struct_field_names)]
struct ApiUsage {
prompt_tokens: i64,
completion_tokens: i64,
@ -400,7 +399,6 @@ fn merge_provider_options(
}
}
#[allow(clippy::unnecessary_literal_bound)]
#[async_trait::async_trait]
impl ProviderAdapter for Adapter {
fn name(&self) -> &str {

View file

@ -220,7 +220,7 @@ fn validate_tool_args(args: &serde_json::Value, schema: &serde_json::Value) -> R
Ok(())
}
fn args_type_name(value: &serde_json::Value) -> &'static str {
const fn args_type_name(value: &serde_json::Value) -> &'static str {
match value {
serde_json::Value::Null => "null",
serde_json::Value::Bool(_) => "boolean",
@ -403,7 +403,7 @@ mod tests {
#[tokio::test]
async fn execute_all_tools_with_known_tools() {
let tools = vec![Tool::active(
let tools = [Tool::active(
"greet",
"Greet someone",
serde_json::json!({"type": "object", "properties": {"name": {"type": "string"}}}),
@ -429,7 +429,7 @@ mod tests {
#[tokio::test]
async fn execute_all_tools_with_unknown_tool() {
let tools = vec![];
let tools = [];
let calls = vec![ToolCall::new(
"call_1",
@ -450,7 +450,7 @@ mod tests {
#[tokio::test]
async fn execute_all_tools_handler_error() {
let tools = vec![Tool::active(
let tools = [Tool::active(
"fail",
"Always fails",
serde_json::json!({"type": "object", "properties": {}}),
@ -475,8 +475,7 @@ mod tests {
#[tokio::test]
async fn execute_all_tools_concurrent_multiple() {
let tools = vec![
Tool::active(
let tools = [Tool::active(
"tool_a",
"Tool A",
serde_json::json!({"type": "object", "properties": {}}),
@ -487,8 +486,7 @@ mod tests {
"Tool B",
serde_json::json!({"type": "object", "properties": {}}),
|_args, _ctx| async { Ok(serde_json::json!("result_b")) },
),
];
)];
let calls = vec![
ToolCall::new("call_1", "tool_a", serde_json::json!({})),
@ -506,8 +504,7 @@ mod tests {
#[tokio::test]
async fn execute_all_tools_partial_failure() {
let tools = vec![
Tool::active(
let tools = [Tool::active(
"succeed",
"Succeeds",
serde_json::json!({"type": "object", "properties": {}}),
@ -518,8 +515,7 @@ mod tests {
"Fails",
serde_json::json!({"type": "object", "properties": {}}),
|_args, _ctx| async { Err("boom".to_string()) },
),
];
)];
let calls = vec![
ToolCall::new("call_1", "succeed", serde_json::json!({})),
@ -592,7 +588,7 @@ mod tests {
#[tokio::test]
async fn execute_with_repair_valid_args_no_repair_needed() {
let tools = vec![Tool::active(
let tools = [Tool::active(
"greet",
"Greet someone",
serde_json::json!({"type": "object", "properties": {"name": {"type": "string"}}}),
@ -612,7 +608,7 @@ mod tests {
#[tokio::test]
async fn execute_with_repair_invalid_args_no_repair_fn() {
let tools = vec![Tool::active(
let tools = [Tool::active(
"greet",
"Greet someone",
serde_json::json!({"type": "object", "properties": {"name": {"type": "string"}}, "required": ["name"]}),
@ -632,7 +628,7 @@ mod tests {
#[tokio::test]
async fn execute_with_repair_invalid_args_repair_succeeds() {
let tools = vec![Tool::active(
let tools = [Tool::active(
"greet",
"Greet someone",
serde_json::json!({"type": "object", "properties": {"name": {"type": "string"}}, "required": ["name"]}),
@ -655,7 +651,7 @@ mod tests {
#[tokio::test]
async fn execute_with_repair_invalid_args_repair_fails() {
let tools = vec![Tool::active(
let tools = [Tool::active(
"greet",
"Greet someone",
serde_json::json!({"type": "object", "properties": {"name": {"type": "string"}}, "required": ["name"]}),

View file

@ -55,7 +55,7 @@ pub struct ToolCall {
pub name: String,
pub arguments: serde_json::Value,
pub raw_arguments: Option<String>,
/// Opaque provider-specific metadata (e.g. Gemini thought_signature).
/// Opaque provider-specific metadata (e.g. Gemini `thought_signature`).
/// Preserved across round-trips so the provider can include it when
/// sending conversation history back to the API.
#[serde(skip_serializing_if = "Option::is_none")]
@ -443,7 +443,7 @@ impl ToolChoice {
/// Return the mode string used by `ProviderAdapter::supports_tool_choice`.
#[must_use]
pub fn mode_str(&self) -> &'static str {
pub const fn mode_str(&self) -> &'static str {
match self {
Self::Auto => "auto",
Self::None => "none",
@ -591,7 +591,8 @@ impl StreamEvent {
}
}
pub fn error(error: SdkError) -> Self {
#[must_use]
pub const fn error(error: SdkError) -> Self {
Self::Error { error, raw: None }
}
}
@ -630,7 +631,7 @@ impl From<f64> for TimeoutConfig {
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct AdapterTimeout {
pub connect: f64,
pub request: f64,
@ -692,7 +693,6 @@ impl Default for RetryPolicy {
impl RetryPolicy {
#[must_use]
pub fn delay_for_attempt(&self, attempt: u32) -> f64 {
#[allow(clippy::cast_possible_wrap)]
let delay = self.base_delay * self.backoff_multiplier.powi(attempt as i32);
let delay = delay.min(self.max_delay);

View file

@ -7,6 +7,3 @@ description = "Shared ANSI terminal styling for CLI binaries"
[lib]
doctest = false
[lints]
workspace = true