mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-06 02:48:25 +00:00
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:
parent
f166bb4959
commit
9665fad133
50 changed files with 209 additions and 268 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -48,6 +48,3 @@ tokio = { workspace = true, features = ["test-util", "macros"] }
|
|||
tempfile = "3"
|
||||
dotenvy = { workspace = true }
|
||||
paste = "1"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ in the project.";
|
|||
}))
|
||||
}
|
||||
|
||||
fn knowledge_cutoff(&self) -> &str {
|
||||
fn knowledge_cutoff(&self) -> &'static str {
|
||||
"January 2025"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ in the project.";
|
|||
})
|
||||
}
|
||||
|
||||
fn knowledge_cutoff(&self) -> &str {
|
||||
fn knowledge_cutoff(&self) -> &'static str {
|
||||
"April 2025"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
|
|
|
|||
|
|
@ -53,6 +53,3 @@ http-body-util = "0.1"
|
|||
dotenvy.workspace = true
|
||||
assert_cmd = "2"
|
||||
predicates = "3"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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)>>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()))));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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 ---
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -39,6 +39,3 @@ http = "1"
|
|||
tokio = { workspace = true, features = ["test-util", "macros"] }
|
||||
assert_cmd = "2"
|
||||
predicates = "3"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
|||
|
|
@ -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'"])
|
||||
|
|
|
|||
|
|
@ -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 { .. }
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"]}),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,3 @@ description = "Shared ANSI terminal styling for CLI binaries"
|
|||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue