Rename SessionConfig to SessionOptions and McpServerConfig to McpServerSettings

Aligns naming with the convention that "Config" is for file-level configuration
while "Options" and "Settings" describe runtime parameters. Also applies
rustfmt formatting fixes in web_auth.rs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-04-02 06:47:42 -07:00
parent 93eab71892
commit b5f5162c3a
No known key found for this signature in database
75 changed files with 448 additions and 396 deletions

View file

@ -1,11 +1,11 @@
const service = process.argv[2];
type ServiceConfig = {
type ServiceOptions = {
command: string[];
cwd?: string;
};
const services: Record<string, ServiceConfig> = {
const services: Record<string, ServiceOptions> = {
api: {
command: ["fabro", "serve", "--host", "0.0.0.0"],
},

View file

@ -4,7 +4,7 @@ use crate::tools::WebFetchSummarizer;
use crate::truncation;
use crate::{
AgentEvent, AgentProfile, AnthropicProfile, GeminiProfile, LocalSandbox, OpenAiProfile,
Sandbox, Session, SessionConfig, Turn,
Sandbox, Session, SessionOptions, Turn,
subagent::{SessionFactory, SubAgentManager},
};
use clap::{Args, Parser};
@ -13,7 +13,7 @@ use fabro_llm::error::SdkError;
use fabro_llm::middleware::{Middleware, NextFn, NextStreamFn};
use fabro_llm::provider::StreamEventStream;
use fabro_llm::types::{Request, Response};
use fabro_mcp::config::McpServerConfig;
use fabro_mcp::config::McpServerSettings;
use fabro_model::{Catalog, ModelRef, Provider};
use fabro_util::terminal::Styles;
use std::io::{IsTerminal, Write};
@ -355,7 +355,7 @@ impl Middleware for VerboseMiddleware {
pub async fn run_with_args(
args: AgentArgs,
mcp_servers: Vec<McpServerConfig>,
mcp_servers: Vec<McpServerSettings>,
) -> anyhow::Result<()> {
run_with_args_and_client(args, None, mcp_servers).await
}
@ -364,7 +364,7 @@ pub async fn run_with_args(
pub async fn run_with_args_and_client(
args: AgentArgs,
llm_client: Option<Client>,
mcp_servers: Vec<McpServerConfig>,
mcp_servers: Vec<McpServerSettings>,
) -> anyhow::Result<()> {
// Resolve color support once, leak to get 'static lifetime for use across threads
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
@ -420,11 +420,11 @@ pub async fn run_with_args_and_client(
let tool_approval = build_tool_approval(permissions, is_interactive, styles);
let tool_hooks: Arc<dyn ToolHookCallback> = Arc::new(ToolApprovalAdapter(tool_approval));
let config = SessionConfig {
let config = SessionOptions {
tool_hooks: Some(tool_hooks.clone()),
skill_dirs: args.skills_dir.map(|d| vec![d]),
mcp_servers,
..SessionConfig::default()
..SessionOptions::default()
};
// Register subagent tools
@ -464,9 +464,9 @@ pub async fn run_with_args_and_client(
factory_client.clone(),
child_profile,
Arc::clone(&factory_env),
SessionConfig {
SessionOptions {
tool_hooks: factory_hooks.clone(),
..SessionConfig::default()
..SessionOptions::default()
},
None,
)

View file

@ -3,7 +3,7 @@ use std::sync::Arc;
use std::time::Duration;
use fabro_llm::types::ReasoningEffort;
use fabro_mcp::config::McpServerConfig;
use fabro_mcp::config::McpServerSettings;
/// Callback invoked before each tool execution. Return `Ok(())` to allow,
/// `Err(message)` to deny with the given message.
@ -59,7 +59,7 @@ impl ToolHookCallback for ToolApprovalAdapter {
}
#[derive(Clone)]
pub struct SessionConfig {
pub struct SessionOptions {
pub max_turns: usize,
pub max_tool_rounds_per_input: usize,
pub default_command_timeout_ms: u64,
@ -84,15 +84,15 @@ pub struct SessionConfig {
/// Skill directories. `None` = use convention defaults, `Some(dirs)` = use these instead.
pub skill_dirs: Option<Vec<String>>,
/// MCP server configurations to connect to on session startup.
pub mcp_servers: Vec<McpServerConfig>,
pub mcp_servers: Vec<McpServerSettings>,
/// Wall-clock timeout for the entire `process_input` call.
/// When set, the session's cancel token is triggered after this duration.
pub wall_clock_timeout: Option<Duration>,
}
impl std::fmt::Debug for SessionConfig {
impl std::fmt::Debug for SessionOptions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SessionConfig")
f.debug_struct("SessionOptions")
.field("max_turns", &self.max_turns)
.field("max_tool_rounds_per_input", &self.max_tool_rounds_per_input)
.field(
@ -127,7 +127,7 @@ impl std::fmt::Debug for SessionConfig {
}
}
impl Default for SessionConfig {
impl Default for SessionOptions {
fn default() -> Self {
Self {
max_turns: 0,
@ -161,7 +161,7 @@ mod tests {
#[test]
fn default_config_values() {
let config = SessionConfig::default();
let config = SessionOptions::default();
assert_eq!(config.max_turns, 0);
assert_eq!(config.max_tool_rounds_per_input, 0);
assert_eq!(config.default_command_timeout_ms, 10_000);
@ -179,7 +179,7 @@ mod tests {
#[test]
fn default_config_has_compaction_enabled() {
let config = SessionConfig::default();
let config = SessionOptions::default();
assert!(config.enable_context_compaction);
assert_eq!(config.compaction_threshold_percent, 80);
assert_eq!(config.compaction_preserve_turns, 6);
@ -187,7 +187,7 @@ mod tests {
#[test]
fn config_with_custom_values() {
let config = SessionConfig {
let config = SessionOptions {
max_turns: 50,
reasoning_effort: Some(ReasoningEffort::High),
..Default::default()

View file

@ -1,2 +1,2 @@
// Re-export from fabro-sandbox
pub use fabro_sandbox::docker::{DockerSandbox, DockerSandboxConfig};
pub use fabro_sandbox::docker::{DockerSandbox, DockerSandboxOptions};

View file

@ -27,12 +27,12 @@ pub mod types;
pub mod v4a_patch;
pub use agent_profile::AgentProfile;
pub use config::{SessionConfig, ToolApprovalAdapter, ToolHookCallback, ToolHookDecision};
pub use config::{SessionOptions, ToolApprovalAdapter, ToolHookCallback, ToolHookDecision};
#[cfg(feature = "docker")]
pub use docker_sandbox::{DockerSandbox, DockerSandboxConfig};
pub use docker_sandbox::{DockerSandbox, DockerSandboxOptions};
pub use error::{AbortReason, AgentError};
pub use event::EventEmitter;
pub use fabro_mcp::config::McpServerConfig;
pub use fabro_mcp::config::McpServerSettings;
pub use history::History;
pub use local_sandbox::LocalSandbox;
pub use loop_detection::detect_loop;
@ -40,8 +40,8 @@ pub use memory::discover_memory;
pub use profiles::{AnthropicProfile, EnvContext, GeminiProfile, OpenAiProfile};
pub use read_before_write_sandbox::ReadBeforeWriteSandbox;
pub use sandbox::{
DirEntry, ExecResult, GrepOptions, Sandbox, SandboxEvent, SandboxEventCallback, WorktreeConfig,
WorktreeEvent, WorktreeEventCallback, WorktreeSandbox, format_lines_numbered, shell_quote,
DirEntry, ExecResult, GrepOptions, Sandbox, SandboxEvent, SandboxEventCallback, WorktreeEvent,
WorktreeEventCallback, WorktreeOptions, WorktreeSandbox, format_lines_numbered, shell_quote,
};
pub use session::Session;
pub use skills::Skill;

View file

@ -46,15 +46,15 @@ mod tests {
use crate::tool_registry::ToolContext;
use std::collections::HashMap;
use fabro_mcp::config::{McpServerConfig, McpTransport};
use fabro_mcp::config::{McpServerSettings, McpTransport};
use tokio_util::sync::CancellationToken;
fn test_server_config() -> McpServerConfig {
fn test_server_config() -> McpServerSettings {
let test_server = format!(
"{}/../fabro-mcp/tests/test_mcp_server.py",
env!("CARGO_MANIFEST_DIR")
);
McpServerConfig {
McpServerSettings {
name: "test-echo".into(),
transport: McpTransport::Stdio {
command: vec!["python3".into(), test_server],

View file

@ -1,5 +1,5 @@
use crate::agent_profile::AgentProfile;
use crate::config::SessionConfig;
use crate::config::SessionOptions;
use crate::profiles::BaseProfile;
use crate::profiles::assemble_system_prompt;
use crate::sandbox::Sandbox;
@ -25,9 +25,9 @@ impl AnthropicProfile {
model: impl Into<String>,
summarizer: Option<WebFetchSummarizer>,
) -> Self {
let config = SessionConfig {
let config = SessionOptions {
default_command_timeout_ms: 120_000,
..SessionConfig::default()
..SessionOptions::default()
};
let mut registry = ToolRegistry::new();

View file

@ -1,5 +1,5 @@
use crate::agent_profile::AgentProfile;
use crate::config::SessionConfig;
use crate::config::SessionOptions;
use crate::profiles::BaseProfile;
use crate::profiles::assemble_system_prompt;
use crate::sandbox::Sandbox;
@ -28,7 +28,7 @@ impl GeminiProfile {
model: impl Into<String>,
summarizer: Option<WebFetchSummarizer>,
) -> Self {
let config = SessionConfig::default();
let config = SessionOptions::default();
let mut registry = ToolRegistry::new();
register_core_tools(&mut registry, &config, summarizer);

View file

@ -1,5 +1,5 @@
use crate::agent_profile::AgentProfile;
use crate::config::SessionConfig;
use crate::config::SessionOptions;
use crate::profiles::BaseProfile;
use crate::profiles::assemble_system_prompt;
use crate::sandbox::Sandbox;
@ -26,7 +26,7 @@ impl OpenAiProfile {
model: impl Into<String>,
summarizer: Option<WebFetchSummarizer>,
) -> Self {
let config = SessionConfig::default();
let config = SessionOptions::default();
let mut registry = ToolRegistry::new();
register_core_tools(&mut registry, &config, summarizer);

View file

@ -1,7 +1,7 @@
// Re-export all sandbox types from fabro-sandbox.
pub use fabro_sandbox::{
DirEntry, ExecResult, GrepOptions, Sandbox, SandboxEvent, SandboxEventCallback, WorktreeConfig,
WorktreeEvent, WorktreeEventCallback, WorktreeSandbox, format_lines_numbered, shell_quote,
DirEntry, ExecResult, GrepOptions, Sandbox, SandboxEvent, SandboxEventCallback, WorktreeEvent,
WorktreeEventCallback, WorktreeOptions, WorktreeSandbox, format_lines_numbered, shell_quote,
};
// Re-export the delegate_sandbox! macro at crate root so existing

View file

@ -1,6 +1,6 @@
use crate::agent_profile::AgentProfile;
use crate::compaction::{check_context_usage, compact_context};
use crate::config::SessionConfig;
use crate::config::SessionOptions;
use crate::error::{AbortReason, AgentError};
use crate::event::EventEmitter;
use crate::file_tracker::FileTracker;
@ -24,7 +24,7 @@ use fabro_llm::retry;
use fabro_llm::types::{
ContentPart, Message, ReasoningEffort, Request, RetryPolicy, StreamEvent, ToolChoice,
};
use fabro_mcp::config::{McpServerConfig, McpTransport};
use fabro_mcp::config::{McpServerSettings, McpTransport};
use fabro_mcp::connection_manager::McpConnectionManager;
use futures::StreamExt;
use std::collections::{HashMap, VecDeque};
@ -37,7 +37,7 @@ use tracing::{debug, info, warn};
pub struct Session {
id: String,
config: SessionConfig,
config: SessionOptions,
history: History,
event_emitter: EventEmitter,
state: SessionState,
@ -63,7 +63,7 @@ impl Session {
llm_client: Client,
provider_profile: Arc<dyn AgentProfile>,
sandbox: Arc<dyn Sandbox>,
config: SessionConfig,
config: SessionOptions,
subagent_manager: Option<Arc<AsyncMutex<SubAgentManager>>>,
) -> Self {
Self {
@ -203,7 +203,7 @@ impl Session {
/// Resolve `McpTransport::Sandbox` configs by starting the MCP server inside the
/// sandbox and rewriting the transport to `Http` with the sandbox's preview URL.
async fn resolve_sandbox_mcp_servers(&self) -> Vec<McpServerConfig> {
async fn resolve_sandbox_mcp_servers(&self) -> Vec<McpServerSettings> {
let mut resolved = Vec::with_capacity(self.config.mcp_servers.len());
for config in &self.config.mcp_servers {
@ -217,7 +217,7 @@ impl Session {
url = %url,
"Sandbox MCP server started, connecting via HTTP"
);
resolved.push(McpServerConfig {
resolved.push(McpServerSettings {
name: config.name.clone(),
transport: McpTransport::Http { url, headers },
startup_timeout_secs: config.startup_timeout_secs,
@ -1118,7 +1118,7 @@ mod tests {
client,
profile,
env,
SessionConfig::default(),
SessionOptions::default(),
subagent_manager,
)
}
@ -1185,7 +1185,7 @@ mod tests {
tool_call_response("echo", "call_3", serde_json::json!({"text": "c"})),
];
let config = SessionConfig {
let config = SessionOptions {
max_tool_rounds_per_input: 2,
enable_loop_detection: false,
..Default::default()
@ -1208,7 +1208,7 @@ mod tests {
text_response("should not reach"),
];
let config = SessionConfig {
let config = SessionOptions {
max_turns: 3,
..Default::default()
};
@ -1398,7 +1398,7 @@ mod tests {
text_response("Done"),
];
let config = SessionConfig {
let config = SessionOptions {
enable_loop_detection: true,
loop_detection_window: 3,
..Default::default()
@ -1435,7 +1435,7 @@ mod tests {
tool_call_response("echo", "call_2", serde_json::json!({"text": "b"})),
];
let config = SessionConfig {
let config = SessionOptions {
enable_loop_detection: false,
..Default::default()
};
@ -1488,7 +1488,7 @@ mod tests {
let client = make_client(provider).await;
let profile = Arc::new(TestProfile::with_tools(registry));
let env = Arc::new(MockSandbox::default());
let config = SessionConfig {
let config = SessionOptions {
enable_loop_detection: false,
..Default::default()
};
@ -1523,7 +1523,7 @@ mod tests {
let client = make_client(error_provider).await;
let profile = Arc::new(TestProfile::new());
let env = Arc::new(MockSandbox::default());
let mut session = Session::new(client, profile, env, SessionConfig::default(), None);
let mut session = Session::new(client, profile, env, SessionOptions::default(), None);
let result = session.process_input("Hello").await;
assert!(result.is_err());
@ -1602,7 +1602,7 @@ mod tests {
let client = make_client(provider).await;
let profile = Arc::new(TestProfile::with_tools(registry));
let env = Arc::new(MockSandbox::default());
let mut session = Session::new(client, profile, env, SessionConfig::default(), None);
let mut session = Session::new(client, profile, env, SessionOptions::default(), None);
let mut rx = session.subscribe();
session.process_input("Use echo three times").await.unwrap();
@ -1653,7 +1653,7 @@ mod tests {
let registry = ToolRegistry::new();
let profile = Arc::new(TestProfile::with_context_window(registry, 100));
let env = Arc::new(MockSandbox::default());
let mut session = Session::new(client, profile, env, SessionConfig::default(), None);
let mut session = Session::new(client, profile, env, SessionOptions::default(), None);
let mut rx = session.subscribe();
session.process_input(&large_input).await.unwrap();
@ -1675,7 +1675,7 @@ mod tests {
let client = make_client(provider as Arc<dyn ProviderAdapter>).await;
let profile = Arc::new(TestProfile::new());
let env = Arc::new(MockSandbox::default());
let mut session = Session::new(client, profile, env, SessionConfig::default(), None);
let mut session = Session::new(client, profile, env, SessionOptions::default(), None);
// Default reasoning_effort is None
session.set_reasoning_effort(Some(ReasoningEffort::High));
@ -1698,7 +1698,7 @@ mod tests {
// Large context window so short input stays well under 80%
let profile = Arc::new(TestProfile::with_context_window(registry, 200_000));
let env = Arc::new(MockSandbox::default());
let mut session = Session::new(client, profile, env, SessionConfig::default(), None);
let mut session = Session::new(client, profile, env, SessionOptions::default(), None);
let mut rx = session.subscribe();
session.process_input("Hi").await.unwrap();
@ -1827,7 +1827,7 @@ mod tests {
let client = make_client(provider as Arc<dyn ProviderAdapter>).await;
let profile = Arc::new(TestProfile::new());
let env = Arc::new(MockSandbox::default());
let config = SessionConfig {
let config = SessionOptions {
user_instructions: Some("Always use TDD".into()),
..Default::default()
};
@ -1855,7 +1855,7 @@ mod tests {
let client = make_client(provider as Arc<dyn ProviderAdapter>).await;
let profile = Arc::new(TestProfile::new());
let env = Arc::new(MockSandbox::default());
let mut session = Session::new(client, profile, env, SessionConfig::default(), None);
let mut session = Session::new(client, profile, env, SessionOptions::default(), None);
// Intentionally skip initialize(): system prompt remains empty.
session.process_input("test").await.unwrap();
@ -1887,7 +1887,7 @@ mod tests {
text_response("OK after denial"),
];
let config = SessionConfig {
let config = SessionOptions {
tool_hooks: Some(Arc::new(ToolApprovalAdapter(Arc::new(|_name, _args| {
Err("denied by policy".to_string())
})))),
@ -1928,7 +1928,7 @@ mod tests {
text_response("Done"),
];
let config = SessionConfig {
let config = SessionOptions {
tool_hooks: Some(Arc::new(ToolApprovalAdapter(Arc::new(|_name, _args| {
Ok(())
})))),
@ -1964,7 +1964,7 @@ mod tests {
text_response("Done"),
];
let config = SessionConfig {
let config = SessionOptions {
tool_hooks: Some(Arc::new(ToolApprovalAdapter(Arc::new(
move |name, args| {
*captured_clone.lock().unwrap() = Some((name.to_string(), args.clone()));
@ -1995,7 +1995,7 @@ mod tests {
text_response("Done"),
];
let config = SessionConfig {
let config = SessionOptions {
tool_hooks: None,
..Default::default()
};
@ -2026,7 +2026,7 @@ mod tests {
text_response("Done"),
];
let config = SessionConfig {
let config = SessionOptions {
tool_hooks: Some(Arc::new(ToolApprovalAdapter(Arc::new(|_name, _args| {
Err("not allowed".to_string())
})))),
@ -2087,7 +2087,7 @@ mod tests {
let client = make_client(provider as Arc<dyn ProviderAdapter>).await;
let profile = Arc::new(TestProfile::new());
let env = Arc::new(MockSandbox::default());
let mut session = Session::new(client, profile, env, SessionConfig::default(), None);
let mut session = Session::new(client, profile, env, SessionOptions::default(), None);
let result = session.process_input("Hello").await;
assert!(matches!(
@ -2260,7 +2260,7 @@ mod tests {
let registry = ToolRegistry::new();
let profile = Arc::new(TestProfile::with_context_window(registry, 100));
let env = Arc::new(MockSandbox::default());
let config = SessionConfig {
let config = SessionOptions {
enable_context_compaction: true,
compaction_preserve_turns: 1,
..Default::default()
@ -2303,7 +2303,7 @@ mod tests {
let registry = ToolRegistry::new();
let profile = Arc::new(TestProfile::with_context_window(registry, 100));
let env = Arc::new(MockSandbox::default());
let config = SessionConfig {
let config = SessionOptions {
enable_context_compaction: false,
..Default::default()
};
@ -2387,7 +2387,7 @@ mod tests {
let registry = ToolRegistry::new();
let profile = Arc::new(TestProfile::with_context_window(registry, 100));
let env = Arc::new(MockSandbox::default());
let config = SessionConfig {
let config = SessionOptions {
enable_context_compaction: true,
compaction_preserve_turns: 1,
..Default::default()
@ -2491,7 +2491,7 @@ mod tests {
// Tiny context window to force compaction
let profile = Arc::new(TestProfile::with_context_window(registry, 100));
let env = Arc::new(MockSandbox::default());
let config = SessionConfig {
let config = SessionOptions {
enable_context_compaction: true,
compaction_preserve_turns: 1,
..Default::default()
@ -2555,15 +2555,15 @@ mod tests {
#[tokio::test]
async fn mcp_end_to_end_tool_call() {
use fabro_mcp::config::{McpServerConfig, McpTransport};
use fabro_mcp::config::{McpServerSettings, McpTransport};
use std::collections::HashMap;
let test_server = format!(
"{}/../fabro-mcp/tests/test_mcp_server.py",
env!("CARGO_MANIFEST_DIR")
);
let config = SessionConfig {
mcp_servers: vec![McpServerConfig {
let config = SessionOptions {
mcp_servers: vec![McpServerSettings {
name: "test-echo".into(),
transport: McpTransport::Stdio {
command: vec!["python3".into(), test_server],
@ -2696,7 +2696,7 @@ mod tests {
text_response("Should not reach this"),
];
let config = SessionConfig {
let config = SessionOptions {
wall_clock_timeout: Some(std::time::Duration::from_millis(10)),
enable_loop_detection: false,
..Default::default()
@ -2719,7 +2719,7 @@ mod tests {
async fn wall_clock_timeout_does_not_fire_when_session_completes_in_time() {
let responses = vec![text_response("Fast response")];
let config = SessionConfig {
let config = SessionOptions {
wall_clock_timeout: Some(std::time::Duration::from_secs(10)),
..Default::default()
};

View file

@ -456,7 +456,7 @@ pub fn make_close_agent_tool(manager: Arc<AsyncMutex<SubAgentManager>>) -> Regis
#[cfg(test)]
mod tests {
use super::*;
use crate::config::SessionConfig;
use crate::config::SessionOptions;
use crate::test_support::*;
use fabro_llm::provider::ProviderAdapter;
use fabro_llm::types::Role;
@ -491,7 +491,7 @@ mod tests {
let client = make_client(provider as Arc<dyn ProviderAdapter>).await;
let profile = Arc::new(TestProfile::new());
let env = Arc::new(MockSandbox::default());
let session = Session::new(client, profile, env, SessionConfig::default(), None);
let session = Session::new(client, profile, env, SessionOptions::default(), None);
let agent_id = manager.spawn(session, "Do something".into(), 0).unwrap();
let _ = manager.wait(&agent_id).await.unwrap();

View file

@ -1,7 +1,7 @@
pub use fabro_sandbox::test_support::{MockSandbox, MutableMockSandbox};
use crate::agent_profile::AgentProfile;
use crate::config::SessionConfig;
use crate::config::SessionOptions;
use crate::profiles::EnvContext;
use crate::sandbox::*;
use crate::session::Session;
@ -197,7 +197,7 @@ pub async fn make_session(responses: Vec<Response>) -> Session {
let client = make_client(provider).await;
let profile = Arc::new(TestProfile::new());
let env = Arc::new(MockSandbox::default());
Session::new(client, profile, env, SessionConfig::default(), None)
Session::new(client, profile, env, SessionOptions::default(), None)
}
pub async fn make_session_with_tools(responses: Vec<Response>, registry: ToolRegistry) -> Session {
@ -205,10 +205,10 @@ pub async fn make_session_with_tools(responses: Vec<Response>, registry: ToolReg
let client = make_client(provider).await;
let profile = Arc::new(TestProfile::with_tools(registry));
let env = Arc::new(MockSandbox::default());
Session::new(client, profile, env, SessionConfig::default(), None)
Session::new(client, profile, env, SessionOptions::default(), None)
}
pub async fn make_session_with_config(responses: Vec<Response>, config: SessionConfig) -> Session {
pub async fn make_session_with_config(responses: Vec<Response>, config: SessionOptions) -> Session {
let provider = Arc::new(MockLlmProvider::new(responses));
let client = make_client(provider).await;
let profile = Arc::new(TestProfile::new());
@ -219,7 +219,7 @@ pub async fn make_session_with_config(responses: Vec<Response>, config: SessionC
pub async fn make_session_with_tools_and_config(
responses: Vec<Response>,
registry: ToolRegistry,
config: SessionConfig,
config: SessionOptions,
) -> Session {
let provider = Arc::new(MockLlmProvider::new(responses));
let client = make_client(provider).await;

View file

@ -1,4 +1,4 @@
use crate::config::{SessionConfig, ToolHookCallback, ToolHookDecision};
use crate::config::{SessionOptions, ToolHookCallback, ToolHookDecision};
use crate::event::EventEmitter;
use crate::sandbox::Sandbox;
use crate::tool_registry::{RegisteredTool, ToolContext, ToolRegistry};
@ -20,7 +20,7 @@ pub async fn execute_tool_calls(
env: Arc<dyn Sandbox>,
tool_hooks: Option<&Arc<dyn ToolHookCallback>>,
cancel_token: &CancellationToken,
config: &SessionConfig,
config: &SessionOptions,
emitter: &EventEmitter,
session_id: &str,
tool_env: Option<&HashMap<String, String>>,
@ -61,7 +61,7 @@ async fn execute_tool_calls_sequential(
env: Arc<dyn Sandbox>,
tool_hooks: Option<&Arc<dyn ToolHookCallback>>,
cancel_token: &CancellationToken,
config: &SessionConfig,
config: &SessionOptions,
emitter: &EventEmitter,
session_id: &str,
tool_env: Option<&HashMap<String, String>>,
@ -97,7 +97,7 @@ async fn execute_tool_calls_parallel(
env: Arc<dyn Sandbox>,
tool_hooks: Option<&Arc<dyn ToolHookCallback>>,
cancel_token: &CancellationToken,
config: &SessionConfig,
config: &SessionOptions,
emitter: &EventEmitter,
session_id: &str,
tool_env: Option<&HashMap<String, String>>,
@ -144,7 +144,7 @@ pub async fn execute_and_emit_one_tool(
env: Arc<dyn Sandbox>,
tool_hooks: Option<&Arc<dyn ToolHookCallback>>,
cancel_token: CancellationToken,
config: &SessionConfig,
config: &SessionOptions,
emitter: &EventEmitter,
session_id: &str,
tool_env: Option<&HashMap<String, String>>,
@ -171,7 +171,7 @@ async fn execute_and_emit_one_tool_with_lookup(
env: Arc<dyn Sandbox>,
tool_hooks: Option<&Arc<dyn ToolHookCallback>>,
cancel_token: CancellationToken,
config: &SessionConfig,
config: &SessionOptions,
emitter: &EventEmitter,
session_id: &str,
tool_env: Option<&HashMap<String, String>>,
@ -294,7 +294,7 @@ async fn execute_one_tool(
fn truncate_tool_result(
result: &ToolResult,
tool_name: &str,
config: &SessionConfig,
config: &SessionOptions,
) -> ToolResult {
let truncated_content = match &result.content {
serde_json::Value::String(s) => {
@ -461,7 +461,7 @@ mod tests {
let tc = make_tool_call("echo", "call_1", serde_json::json!({"text": "hello"}));
let emitter = EventEmitter::new();
let config = SessionConfig::default();
let config = SessionOptions::default();
let result = execute_and_emit_one_tool(
&tc,
@ -491,7 +491,7 @@ mod tests {
let tc = make_tool_call("echo", "call_1", serde_json::json!({"text": "hello"}));
let emitter = EventEmitter::new();
let config = SessionConfig::default();
let config = SessionOptions::default();
let result = execute_and_emit_one_tool(
&tc,
@ -521,7 +521,7 @@ mod tests {
let tc = make_tool_call("echo", "call_1", serde_json::json!({"text": "hello"}));
let emitter = EventEmitter::new();
let config = SessionConfig::default();
let config = SessionOptions::default();
execute_and_emit_one_tool(
&tc,
@ -556,7 +556,7 @@ mod tests {
let tc = make_tool_call("fail_tool", "call_1", serde_json::json!({}));
let emitter = EventEmitter::new();
let config = SessionConfig::default();
let config = SessionOptions::default();
execute_and_emit_one_tool(
&tc,
@ -588,7 +588,7 @@ mod tests {
let tc = make_tool_call("echo", "call_1", serde_json::json!({"text": "hello"}));
let emitter = EventEmitter::new();
let config = SessionConfig::default();
let config = SessionOptions::default();
let result = execute_and_emit_one_tool(
&tc,
@ -628,7 +628,7 @@ mod tests {
serde_json::json!({"file_path": "a.ts", "content": "new"}),
);
let emitter = EventEmitter::new();
let config = SessionConfig::default();
let config = SessionOptions::default();
let result = execute_and_emit_one_tool(
&tc,
@ -655,7 +655,7 @@ mod tests {
let sandbox = make_guarded_sandbox(HashMap::from([("a.ts".into(), "content".into())]));
let emitter = EventEmitter::new();
let config = SessionConfig::default();
let config = SessionOptions::default();
// First read the file
let read_tc = make_tool_call(
@ -707,7 +707,7 @@ mod tests {
let sandbox = make_guarded_sandbox(HashMap::from([("a.ts".into(), "content".into())]));
let emitter = EventEmitter::new();
let config = SessionConfig::default();
let config = SessionOptions::default();
// Grep matching a.ts
let grep_tc = make_tool_call("grep", "call_1", serde_json::json!({"pattern": "content"}));
@ -759,7 +759,7 @@ mod tests {
serde_json::json!({"file_path": "a.ts", "old_string": "content", "new_string": "updated"}),
);
let emitter = EventEmitter::new();
let config = SessionConfig::default();
let config = SessionOptions::default();
let result = execute_and_emit_one_tool(
&tc,
@ -790,7 +790,7 @@ mod tests {
serde_json::json!({"file_path": "new.ts", "content": "hello"}),
);
let emitter = EventEmitter::new();
let config = SessionConfig::default();
let config = SessionOptions::default();
let result = execute_and_emit_one_tool(
&tc,

View file

@ -1,4 +1,4 @@
use crate::config::SessionConfig;
use crate::config::SessionOptions;
use crate::sandbox::GrepOptions;
use crate::tool_registry::{RegisteredTool, ToolRegistry};
use fabro_llm::client::Client;
@ -44,11 +44,11 @@ fn html_to_markdown(text: &str) -> String {
/// `shell`, `grep`, `glob`, `web_search`, and `web_fetch`.
///
/// The shell tool uses `config` to set its default and max timeouts. Pass a custom
/// `SessionConfig` (e.g. with a longer `default_command_timeout_ms`) for providers
/// `SessionOptions` (e.g. with a longer `default_command_timeout_ms`) for providers
/// that need non-default shell behavior.
pub fn register_core_tools(
registry: &mut ToolRegistry,
config: &SessionConfig,
config: &SessionOptions,
summarizer: Option<WebFetchSummarizer>,
) {
registry.register(make_read_file_tool());
@ -190,11 +190,11 @@ pub fn make_edit_file_tool() -> RegisteredTool {
#[must_use]
pub fn make_shell_tool() -> RegisteredTool {
make_shell_tool_with_config(&SessionConfig::default())
make_shell_tool_with_config(&SessionOptions::default())
}
#[must_use]
pub fn make_shell_tool_with_config(config: &SessionConfig) -> RegisteredTool {
pub fn make_shell_tool_with_config(config: &SessionOptions) -> RegisteredTool {
let default_timeout = config.default_command_timeout_ms;
let max_timeout = config.max_command_timeout_ms;
RegisteredTool {

View file

@ -1,4 +1,4 @@
use crate::config::SessionConfig;
use crate::config::SessionOptions;
/// Round a byte index down to the nearest UTF-8 char boundary.
/// Stable equivalent of `str::floor_char_boundary` (nightly-only).
@ -99,7 +99,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 {
pub fn truncate_tool_output(output: &str, tool_name: &str, config: &SessionOptions) -> String {
let mode = default_truncation_mode(tool_name);
// Char truncation first
@ -180,7 +180,7 @@ mod tests {
// Create an output that is large in chars and many lines
let long_line = "x".repeat(50_000);
let output = format!("{long_line}\n{long_line}");
let config = SessionConfig::default();
let config = SessionOptions::default();
let result = truncate_tool_output(&output, "shell", &config);
// Should have been char-truncated first (30k limit for shell)
assert!(result.len() < output.len());
@ -189,7 +189,7 @@ mod tests {
#[test]
fn config_override_char_limit() {
let output = "x".repeat(5000);
let mut config = SessionConfig::default();
let mut config = SessionOptions::default();
config.tool_output_limits.insert("my_tool".into(), 100);
let result = truncate_tool_output(&output, "my_tool", &config);
assert!(result.len() < output.len());
@ -200,7 +200,7 @@ mod tests {
fn config_override_line_limit() {
let lines: Vec<String> = (1..=100).map(|i| format!("line {i}")).collect();
let output = lines.join("\n");
let mut config = SessionConfig::default();
let mut config = SessionOptions::default();
config.tool_line_limits.insert("my_tool".into(), 10);
let result = truncate_tool_output(&output, "my_tool", &config);
assert!(result.contains("lines omitted"));
@ -209,7 +209,7 @@ mod tests {
#[test]
fn unknown_tool_no_truncation() {
let output = "x".repeat(200);
let config = SessionConfig::default();
let config = SessionOptions::default();
let result = truncate_tool_output(&output, "unknown_tool", &config);
assert_eq!(result, output);
}

View file

@ -1401,7 +1401,7 @@ def gamma():
#[tokio::test]
async fn e2e_through_tool_executor() {
use crate::config::SessionConfig;
use crate::config::SessionOptions;
use crate::session::Session;
use crate::test_support::{
MockLlmProvider, TestProfile, make_client, text_response, tool_call_response,
@ -1451,8 +1451,13 @@ def farewell(name):
let provider = Arc::new(MockLlmProvider::new(responses));
let client = make_client(provider).await;
let profile = Arc::new(TestProfile::with_tools(registry));
let mut session =
Session::new(client, profile, env.clone(), SessionConfig::default(), None);
let mut session = Session::new(
client,
profile,
env.clone(),
SessionOptions::default(),
None,
);
session.initialize().await;
session
.process_input("Update the greeting functions")

View file

@ -6,7 +6,7 @@ use std::sync::Arc;
use fabro_agent::subagent::SessionFactory;
use fabro_agent::{
AgentProfile, AnthropicProfile, GeminiProfile, LocalSandbox, OpenAiProfile, Session,
SessionConfig, SubAgentManager, WebFetchSummarizer,
SessionOptions, SubAgentManager, WebFetchSummarizer,
};
use fabro_llm::client::Client;
use fabro_llm::provider::{Provider, ProviderAdapter};
@ -16,7 +16,7 @@ use fabro_test::{TwinScenario, TwinScenarios, TwinToolCall, twin_openai};
use tokio::sync::Mutex as AsyncMutex;
#[derive(Clone)]
struct OpenAiTwinConfig {
struct OpenAiTwinOptions {
base_url: String,
api_key: String,
}
@ -70,7 +70,7 @@ async fn make_session(
provider: Provider,
model: &str,
cwd: &Path,
twin: Option<OpenAiTwinConfig>,
twin: Option<OpenAiTwinOptions>,
) -> Session {
let client = make_client(provider, twin.as_ref()).await;
let mut profile = build_profile(provider, model, &client);
@ -110,16 +110,16 @@ async fn make_session(
factory_client.clone(),
sub_profile,
sub_env,
SessionConfig::default(),
SessionOptions::default(),
None,
)
});
profile.register_subagent_tools(manager, factory, 0);
let profile: Arc<dyn AgentProfile> = Arc::from(profile);
let config = SessionConfig {
let config = SessionOptions {
max_turns: 20,
..SessionConfig::default()
..SessionOptions::default()
};
Session::new(client, profile, env, config, None)
}
@ -128,8 +128,8 @@ async fn make_session_with_config(
provider: Provider,
model: &str,
cwd: &Path,
config: SessionConfig,
twin: Option<OpenAiTwinConfig>,
config: SessionOptions,
twin: Option<OpenAiTwinOptions>,
) -> Session {
let client = make_client(provider, twin.as_ref()).await;
let profile: Arc<dyn AgentProfile> = Arc::from(build_profile(provider, model, &client));
@ -137,7 +137,7 @@ async fn make_session_with_config(
Session::new(client, profile, env, config, None)
}
async fn make_client(provider: Provider, twin: Option<&OpenAiTwinConfig>) -> Client {
async fn make_client(provider: Provider, twin: Option<&OpenAiTwinOptions>) -> Client {
if provider == Provider::OpenAi && fabro_test::TestMode::from_env().is_twin() {
return make_twin_client(twin.expect("openai twin config should be provided")).await;
}
@ -145,7 +145,7 @@ async fn make_client(provider: Provider, twin: Option<&OpenAiTwinConfig>) -> Cli
Client::from_env().await.expect("Client::from_env failed")
}
async fn make_twin_client(twin: &OpenAiTwinConfig) -> Client {
async fn make_twin_client(twin: &OpenAiTwinOptions) -> Client {
let adapter: Arc<dyn ProviderAdapter> =
Arc::new(OpenAiAdapter::new(twin.api_key.clone()).with_base_url(twin.base_url.clone()));
let mut providers: HashMap<String, Arc<dyn ProviderAdapter>> = HashMap::new();
@ -174,7 +174,7 @@ macro_rules! openai_twin_provider_test {
async fn [<openai_twin_ $scenario>]() {
let tmp = tempfile::tempdir().expect("failed to create tempdir");
let (base_url, api_key) = fabro_test::e2e_openai!();
let twin = OpenAiTwinConfig { base_url, api_key };
let twin = OpenAiTwinOptions { base_url, api_key };
if fabro_test::TestMode::from_env().is_twin() {
load_openai_twin_scenario(stringify!($scenario), &twin.api_key, tmp.path())
.await;
@ -650,10 +650,10 @@ macro_rules! reasoning_effort_tests {
#[fabro_macros::e2e_test($(live($key)),+)]
async fn $test_name() {
let tmp = tempfile::tempdir().expect("failed to create tempdir");
let config = SessionConfig {
let config = SessionOptions {
max_turns: 20,
reasoning_effort: Some(fabro_llm::types::ReasoningEffort::Low),
..SessionConfig::default()
..SessionOptions::default()
};
let mut session =
make_session_with_config($provider, $model, tmp.path(), config, None).await;
@ -728,10 +728,10 @@ macro_rules! loop_detection_tests {
#[fabro_macros::e2e_test($(live($key)),+)]
async fn $test_name() {
let tmp = tempfile::tempdir().expect("failed to create tempdir");
let config = SessionConfig {
let config = SessionOptions {
max_turns: 20,
loop_detection_window: 3,
..SessionConfig::default()
..SessionOptions::default()
};
let mut session =
make_session_with_config($provider, $model, tmp.path(), config, None).await;

View file

@ -3,7 +3,7 @@ use anyhow::Result;
use fabro_agent::cli::run_with_args_and_client;
use fabro_agent::cli::{AgentArgs, OutputFormat, run_with_args};
use fabro_config::mcp::McpServerEntry;
use fabro_mcp::config::McpServerConfig;
use fabro_mcp::config::McpServerSettings;
use crate::args::GlobalArgs;
use crate::user_config;
@ -28,7 +28,7 @@ pub(crate) async fn execute(mut args: AgentArgs, globals: &GlobalArgs) -> Result
globals.server_url.as_deref(),
&cli_settings,
);
let mcp_servers: Vec<McpServerConfig> = cli_settings
let mcp_servers: Vec<McpServerSettings> = cli_settings
.mcp_servers
.into_iter()
.map(|(name, entry): (String, McpServerEntry)| entry.into_config(name))

View file

@ -8,7 +8,7 @@ use fabro_graphviz::graph::{Graph, is_llm_handler_type};
use fabro_llm::client::Client as LlmClient;
use fabro_model::{Catalog, Provider};
use fabro_sandbox::daytona::{DaytonaConfig, detect_repo_info};
use fabro_sandbox::{DockerSandboxConfig, Sandbox, SandboxProvider, SandboxSpec};
use fabro_sandbox::{DockerSandboxOptions, Sandbox, SandboxProvider, SandboxSpec};
use fabro_util::check_report::CheckReport;
use fabro_util::terminal::Styles;
use fabro_workflow::git::{GitSyncStatus, sync_status};
@ -274,9 +274,9 @@ async fn run_preflight(
.await
.map_err(|e| e.to_string()),
SandboxProvider::Docker => SandboxSpec::Docker {
config: DockerSandboxConfig {
config: DockerSandboxOptions {
host_working_directory: working_directory.to_string_lossy().to_string(),
..DockerSandboxConfig::default()
..DockerSandboxOptions::default()
},
}
.build(None)

View file

@ -4,14 +4,14 @@ use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use crate::combine::Combine;
use crate::hook::{HookConfig, HookDefinition};
use crate::hook::{HookDefinition, HookSettings};
use crate::mcp::McpServerEntry;
use crate::project::{self, ProjectConfig};
use crate::run::{
AssetsConfig, CheckpointConfig, GitHubConfig, LlmConfig, PullRequestConfig, SetupConfig,
};
use crate::sandbox::SandboxConfig;
use crate::server::{self, ApiConfig, Features, GitConfig, LogConfig, WebConfig};
use crate::server::{self, ApiConfig, FeaturesConfig, GitConfig, LogConfig, WebConfig};
use crate::settings::FabroSettings;
use crate::user::{self, ExecConfig, ExecutionMode, ServerConfig};
@ -118,7 +118,7 @@ pub struct ConfigLayer {
pub api: Option<ApiConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub features: Option<Features>,
pub features: Option<FeaturesConfig>,
// --- Shared fields ---
#[serde(default, skip_serializing_if = "Option::is_none")]
@ -139,8 +139,8 @@ impl Combine for ConfigLayer {
} else if other.hooks.is_empty() {
self.hooks
} else {
HookConfig { hooks: other.hooks }
.merge(HookConfig { hooks: self.hooks })
HookSettings { hooks: other.hooks }
.merge(HookSettings { hooks: self.hooks })
.hooks
};

View file

@ -153,14 +153,14 @@ impl From<WebConfig> for WebSettings {
}
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize, crate::Combine)]
pub struct Features {
pub struct FeaturesConfig {
pub session_sandboxes: Option<bool>,
/// Experimental: enable automatic retro generation after workflow runs.
pub retros: Option<bool>,
}
impl From<Features> for FeaturesSettings {
fn from(value: Features) -> Self {
impl From<FeaturesConfig> for FeaturesSettings {
fn from(value: FeaturesConfig) -> Self {
Self {
session_sandboxes: value.session_sandboxes.unwrap_or(false),
retros: value.retros.unwrap_or(false),

View file

@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
/// Extracted configuration from a Docker Compose service.
#[derive(Debug, Clone, Default)]
pub(crate) struct ComposeServiceConfig {
pub(crate) struct ComposeServiceSpec {
pub image: Option<String>,
pub build: Option<ComposeBuild>,
pub ports: Vec<u16>,
@ -22,7 +22,7 @@ pub(crate) struct ComposeBuild {
pub(crate) fn parse_compose(
compose_path: &Path,
service_name: &str,
) -> Result<ComposeServiceConfig, String> {
) -> Result<ComposeServiceSpec, String> {
let contents = std::fs::read_to_string(compose_path)
.map_err(|e| format!("failed to read compose file: {e}"))?;
@ -48,7 +48,7 @@ pub(crate) fn parse_compose(
.and_then(|v| v.as_str())
.map(String::from);
Ok(ComposeServiceConfig {
Ok(ComposeServiceSpec {
image,
build,
ports,
@ -158,8 +158,8 @@ fn parse_environment(service: &serde_yaml::Value) -> HashMap<String, String> {
pub(crate) fn parse_compose_multi(
compose_paths: &[PathBuf],
service_name: &str,
) -> Result<ComposeServiceConfig, String> {
let mut merged = ComposeServiceConfig::default();
) -> Result<ComposeServiceSpec, String> {
let mut merged = ComposeServiceSpec::default();
let mut found_service = false;
for path in compose_paths {

View file

@ -23,7 +23,7 @@ pub enum Command {
/// Parsed and resolved devcontainer configuration — everything needed to create a sandbox.
#[derive(Debug, Clone)]
pub struct DevcontainerConfig {
pub struct DevcontainerSpec {
/// Generated Dockerfile content
pub dockerfile: String,
/// Directory for docker build context
@ -159,7 +159,7 @@ pub struct DevcontainerResolver;
impl DevcontainerResolver {
/// path: repo root (or explicit .devcontainer/ path)
pub async fn resolve(path: &Path) -> Result<DevcontainerConfig> {
pub async fn resolve(path: &Path) -> Result<DevcontainerSpec> {
let (json_path, devcontainer) = Self::find_and_parse(path)?;
let repo_root = Self::repo_root_from_json_path(&json_path, path);
let base_dir = json_path.parent().unwrap_or(path);
@ -246,7 +246,7 @@ impl DevcontainerResolver {
check_no_build_context_copies(&dockerfile)?;
return Ok(DevcontainerConfig {
return Ok(DevcontainerSpec {
dockerfile,
build_context: compose_base_dir.to_path_buf(),
build_args: HashMap::new(),
@ -384,7 +384,7 @@ impl DevcontainerResolver {
post_start_commands.push(Self::convert_lifecycle_command(cmd));
}
Ok(DevcontainerConfig {
Ok(DevcontainerSpec {
dockerfile: dockerfile_content,
build_context,
build_args,

View file

@ -9,7 +9,7 @@ pub struct DevcontainerJson {
pub image: Option<String>,
/// Dockerfile build config
pub build: Option<BuildConfig>,
pub build: Option<BuildSpec>,
/// Docker Compose file path(s) (compose mode)
pub docker_compose_file: Option<ComposeFileRef>,
@ -63,7 +63,7 @@ pub struct DevcontainerJson {
/// Build configuration for Dockerfile mode.
#[derive(Debug, Clone, Deserialize)]
pub struct BuildConfig {
pub struct BuildSpec {
/// Path to Dockerfile (relative to devcontainer.json)
pub dockerfile: Option<String>,

View file

@ -3,7 +3,7 @@ use fabro_github::{
create_installation_access_token_for_pr, create_pull_request, enable_auto_merge,
get_pull_request, merge_pull_request, resolve_authenticated_url, sign_app_jwt,
};
use fabro_test::{GitHubAppConfig, GitHubAppState, TwinGitHub};
use fabro_test::{GitHubAppOptions, GitHubAppState, TwinGitHub};
const TEST_RSA_KEY: &str = include_str!("../src/testdata/rsa_private.pem");
@ -16,7 +16,7 @@ fn github_credentials() -> GitHubAppCredentials {
fn standard_app_state() -> GitHubAppState {
let mut state = GitHubAppState::new();
state.register_app(GitHubAppConfig {
state.register_app(GitHubAppOptions {
app_id: "42".into(),
slug: "test-app".into(),
owner_login: "acme".into(),

View file

@ -73,7 +73,7 @@ impl ToolHookCallback for WorkflowToolHookCallback {
#[cfg(test)]
mod tests {
use super::*;
use crate::config::{HookConfig, HookDefinition};
use crate::config::{HookDefinition, HookSettings};
use crate::executor::HookExecutor;
use crate::types::{HookContext, HookResult};
use fabro_types::fixtures;
@ -143,7 +143,7 @@ mod tests {
captured_contexts: captured.clone(),
decision: HookDecision::Proceed,
});
let config = HookConfig {
let config = HookSettings {
hooks: vec![make_hook(HookEvent::PreToolUse)],
};
let runner = Arc::new(HookRunner::with_executor(config, executor));
@ -174,7 +174,7 @@ mod tests {
reason: Some("forbidden".into()),
},
});
let config = HookConfig {
let config = HookSettings {
hooks: vec![make_hook(HookEvent::PreToolUse)],
};
let runner = Arc::new(HookRunner::with_executor(config, executor));
@ -196,7 +196,7 @@ mod tests {
captured_contexts: Arc::new(Mutex::new(Vec::new())),
decision: HookDecision::Proceed,
});
let config = HookConfig {
let config = HookSettings {
hooks: vec![make_hook(HookEvent::PreToolUse)],
};
let runner = Arc::new(HookRunner::with_executor(config, executor));
@ -214,7 +214,7 @@ mod tests {
captured_contexts: captured.clone(),
decision: HookDecision::Proceed,
});
let config = HookConfig {
let config = HookSettings {
hooks: vec![make_hook(HookEvent::PostToolUse)],
};
let runner = Arc::new(HookRunner::with_executor(config, executor));
@ -243,7 +243,7 @@ mod tests {
captured_contexts: captured.clone(),
decision: HookDecision::Proceed,
});
let config = HookConfig {
let config = HookSettings {
hooks: vec![make_hook(HookEvent::PostToolUseFailure)],
};
let runner = Arc::new(HookRunner::with_executor(config, executor));

View file

@ -1 +1 @@
pub use fabro_config::hook::{HookConfig, HookDefinition, HookEvent, HookType, TlsMode};
pub use fabro_config::hook::{HookDefinition, HookEvent, HookSettings, HookType, TlsMode};

View file

@ -313,7 +313,7 @@ impl HookExecutorImpl {
}
};
let config = fabro_agent::SessionConfig::default();
let config = fabro_agent::SessionOptions::default();
let mut registry = fabro_agent::ToolRegistry::new();
fabro_agent::register_core_tools(&mut registry, &config, None);
let tool_defs = registry.definitions();

View file

@ -5,6 +5,6 @@ pub mod runner;
pub mod types;
pub use bridge::WorkflowToolHookCallback;
pub use config::{HookConfig, HookDefinition, HookType, TlsMode};
pub use config::{HookDefinition, HookSettings, HookType, TlsMode};
pub use runner::HookRunner;
pub use types::{HookContext, HookDecision, HookEvent};

View file

@ -4,13 +4,13 @@ use std::sync::Arc;
use fabro_agent::Sandbox;
use crate::config::{HookConfig, HookDefinition};
use crate::config::{HookDefinition, HookSettings};
use crate::executor::{HookExecutor, HookExecutorImpl};
use crate::types::{HookContext, HookDecision};
/// Central orchestrator: filters matching hooks, executes them, merges decisions.
pub struct HookRunner {
config: HookConfig,
config: HookSettings,
executor: Arc<dyn HookExecutor>,
/// Pre-compiled regexes keyed by matcher pattern string.
compiled_matchers: HashMap<String, regex::Regex>,
@ -18,7 +18,7 @@ pub struct HookRunner {
impl HookRunner {
#[must_use]
pub fn new(config: HookConfig) -> Self {
pub fn new(config: HookSettings) -> Self {
let compiled_matchers = Self::compile_matchers(&config);
Self {
config,
@ -29,7 +29,7 @@ impl HookRunner {
/// Create a HookRunner with a custom executor (for testing).
#[cfg(test)]
pub fn with_executor(config: HookConfig, executor: Arc<dyn HookExecutor>) -> Self {
pub fn with_executor(config: HookSettings, executor: Arc<dyn HookExecutor>) -> Self {
let compiled_matchers = Self::compile_matchers(&config);
Self {
config,
@ -38,7 +38,7 @@ impl HookRunner {
}
}
fn compile_matchers(config: &HookConfig) -> HashMap<String, regex::Regex> {
fn compile_matchers(config: &HookSettings) -> HashMap<String, regex::Regex> {
let mut map = HashMap::new();
for hook in &config.hooks {
if let Some(ref pattern) = hook.matcher {
@ -210,7 +210,7 @@ impl HookRunner {
#[cfg(test)]
mod tests {
use super::*;
use crate::config::HookConfig;
use crate::config::HookSettings;
use crate::types::{HookContext, HookEvent, HookResult};
use fabro_types::fixtures;
@ -260,7 +260,7 @@ mod tests {
#[tokio::test]
async fn no_hooks_returns_proceed() {
let runner = HookRunner::new(HookConfig::default());
let runner = HookRunner::new(HookSettings::default());
let ctx = make_context(HookEvent::RunStart);
let sandbox = make_sandbox();
let decision = runner.run(&ctx, sandbox.clone(), None).await;
@ -269,7 +269,7 @@ mod tests {
#[tokio::test]
async fn filters_by_event() {
let config = HookConfig {
let config = HookSettings {
hooks: vec![
make_hook(HookEvent::RunStart, "a"),
make_hook(HookEvent::StageStart, "b"),
@ -291,7 +291,7 @@ mod tests {
async fn matcher_filters_by_node_id() {
let mut hook = make_hook(HookEvent::StageStart, "filtered");
hook.matcher = Some("agent".into());
let config = HookConfig { hooks: vec![hook] };
let config = HookSettings { hooks: vec![hook] };
let runner = HookRunner::with_executor(
config,
Arc::new(MockExecutor {
@ -318,7 +318,7 @@ mod tests {
async fn matcher_filters_by_handler_type() {
let mut hook = make_hook(HookEvent::StageStart, "filtered");
hook.matcher = Some("^agent$".into());
let config = HookConfig { hooks: vec![hook] };
let config = HookSettings { hooks: vec![hook] };
let runner = HookRunner::with_executor(
config,
Arc::new(MockExecutor {
@ -339,7 +339,7 @@ mod tests {
async fn matcher_filters_by_tool_name() {
let mut hook = make_hook(HookEvent::PreToolUse, "tool-filter");
hook.matcher = Some("shell".into());
let config = HookConfig { hooks: vec![hook] };
let config = HookSettings { hooks: vec![hook] };
let runner = HookRunner::with_executor(
config,
Arc::new(MockExecutor {
@ -360,7 +360,7 @@ mod tests {
#[tokio::test]
async fn blocking_hook_block_decision() {
let config = HookConfig {
let config = HookSettings {
hooks: vec![make_hook(HookEvent::RunStart, "blocker")],
};
let runner = HookRunner::with_executor(
@ -381,7 +381,7 @@ mod tests {
async fn blocking_hook_skip_decision() {
let mut hook = make_hook(HookEvent::StageStart, "skipper");
hook.blocking = Some(true);
let config = HookConfig { hooks: vec![hook] };
let config = HookSettings { hooks: vec![hook] };
let runner = HookRunner::with_executor(
config,
Arc::new(MockExecutor {
@ -400,7 +400,7 @@ mod tests {
async fn non_blocking_hook_doesnt_block() {
let mut hook = make_hook(HookEvent::StageComplete, "observer");
hook.blocking = Some(false);
let config = HookConfig { hooks: vec![hook] };
let config = HookSettings { hooks: vec![hook] };
let runner = HookRunner::with_executor(
config,
Arc::new(MockExecutor {
@ -418,7 +418,7 @@ mod tests {
#[tokio::test]
async fn executor_integration_success() {
let config = HookConfig {
let config = HookSettings {
hooks: vec![{
let mut h = make_hook(HookEvent::RunStart, "echo-hook");
h.command = Some("exit 0".into());
@ -434,7 +434,7 @@ mod tests {
#[tokio::test]
async fn executor_integration_block() {
let config = HookConfig {
let config = HookSettings {
hooks: vec![{
let mut h = make_hook(HookEvent::RunStart, "fail-hook");
h.command = Some("exit 1".into());

View file

@ -5,7 +5,7 @@ use crate::retry::retry;
use crate::tools::{RepairToolCallFn, Tool, execute_all_tools_with_repair};
use crate::types::{
FinishReason, GenerateResult, Message, ObjectStreamEvent, ReasoningEffort, Request, Response,
ResponseFormat, ResponseFormatType, RetryPolicy, StepResult, StreamEvent, TimeoutConfig,
ResponseFormat, ResponseFormatType, RetryPolicy, StepResult, StreamEvent, TimeoutOptions,
ToolCall, ToolChoice, ToolDefinition, Usage,
};
use fabro_util::backoff::BackoffPolicy;
@ -303,7 +303,7 @@ pub struct GenerateParams {
pub provider_options: Option<serde_json::Value>,
pub metadata: Option<std::collections::HashMap<String, String>>,
pub max_retries: u32,
pub timeout: Option<TimeoutConfig>,
pub timeout: Option<TimeoutOptions>,
pub client: Option<Arc<Client>>,
/// Cancellation token to abort generation (Section 4.8).
pub abort_signal: Option<CancellationToken>,
@ -445,7 +445,7 @@ impl GenerateParams {
}
#[must_use]
pub const fn timeout(mut self, timeout: TimeoutConfig) -> Self {
pub const fn timeout(mut self, timeout: TimeoutOptions) -> Self {
self.timeout = Some(timeout);
self
}
@ -1558,7 +1558,7 @@ mod tests {
#[test]
fn generate_params_timeout_builder() {
let params = GenerateParams::new("test-model").timeout(TimeoutConfig {
let params = GenerateParams::new("test-model").timeout(TimeoutOptions {
total: Some(30.0),
per_step: Some(10.0),
});
@ -2504,7 +2504,7 @@ mod tests {
|_args, _ctx| async { Ok(serde_json::json!("72F")) },
)])
.max_tool_rounds(1)
.timeout(TimeoutConfig {
.timeout(TimeoutOptions {
total: None,
per_step: Some(0.01), // 10ms timeout, provider takes 5s
})
@ -2635,7 +2635,7 @@ mod tests {
|_args, _ctx| async { Ok(serde_json::json!("72F")) },
)])
.max_tool_rounds(5)
.timeout(TimeoutConfig {
.timeout(TimeoutOptions {
total: Some(0.05), // 50ms total timeout
per_step: None,
})

View file

@ -62,7 +62,7 @@ struct ApiRequest {
#[serde(skip_serializing_if = "Option::is_none")]
system_instruction: Option<SystemInstruction>,
#[serde(skip_serializing_if = "Option::is_none")]
generation_config: Option<GenerationConfig>,
generation_config: Option<GenerationOptions>,
#[serde(skip_serializing_if = "Option::is_none")]
tools: Option<Vec<GeminiToolGroup>>,
#[serde(skip_serializing_if = "Option::is_none")]
@ -82,7 +82,7 @@ struct SystemInstruction {
#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
struct GenerationConfig {
struct GenerationOptions {
#[serde(skip_serializing_if = "Option::is_none")]
temperature: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
@ -421,7 +421,7 @@ fn build_api_request(request: &Request) -> serde_json::Value {
.as_ref()
.map_or((None, None), translate_response_format);
let generation_config = GenerationConfig {
let generation_config = GenerationOptions {
temperature: request.temperature,
max_output_tokens: request.max_tokens,
top_p: request.top_p,

View file

@ -696,12 +696,12 @@ pub use fabro_model::{Model, ModelCosts, ModelFeatures, ModelLimits};
// --- 4.7 Timeouts ---
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TimeoutConfig {
pub struct TimeoutOptions {
pub total: Option<f64>,
pub per_step: Option<f64>,
}
impl From<f64> for TimeoutConfig {
impl From<f64> for TimeoutOptions {
fn from(total: f64) -> Self {
Self {
total: Some(total),

View file

@ -15,7 +15,7 @@ use tokio::time;
use tracing::{debug, error, info, warn};
use crate::client_handler::LoggingClientHandler;
use crate::config::{McpServerConfig, McpTransport};
use crate::config::{McpServerSettings, McpTransport};
enum ClientState {
/// Transport created but handshake not yet performed.
@ -37,7 +37,7 @@ pub struct McpClient {
impl McpClient {
/// Create a new MCP client from config. Does not connect yet — call `initialize()`.
pub fn new(config: &McpServerConfig) -> Result<Self> {
pub fn new(config: &McpServerSettings) -> Result<Self> {
let transport = match &config.transport {
McpTransport::Stdio { command, env } => {
let (program, args) = command.split_first().ok_or_else(|| {

View file

@ -1,4 +1,4 @@
pub use fabro_config::mcp::{
McpServerConfig, McpServerEntry, McpTransport, default_startup_timeout_secs,
McpServerEntry, McpServerSettings, McpTransport, default_startup_timeout_secs,
default_tool_timeout_secs,
};

View file

@ -7,7 +7,7 @@ use rmcp::model::{CallToolResult, RawContent};
use tracing::{error, info};
use crate::client::McpClient;
use crate::config::McpServerConfig;
use crate::config::McpServerSettings;
const MCP_TOOL_NAME_DELIMITER: &str = "__";
@ -102,7 +102,7 @@ impl McpConnectionManager {
/// Returns a list of `(server_name, result)` for each server.
pub async fn start_servers(
&mut self,
configs: &[McpServerConfig],
configs: &[McpServerSettings],
) -> Vec<(String, Result<usize>)> {
let mut results = Vec::new();
@ -122,7 +122,7 @@ impl McpConnectionManager {
results
}
async fn start_one_server(&mut self, config: &McpServerConfig) -> Result<usize> {
async fn start_one_server(&mut self, config: &McpServerSettings) -> Result<usize> {
let client = McpClient::new(config)?;
client.initialize(config.startup_timeout()).await?;
let tools = client.list_tools().await?;

View file

@ -4,12 +4,12 @@ use std::time::Duration;
use fabro_mcp::connection_manager::McpConnectionManager;
use fabro_mcp::client::McpClient;
use fabro_mcp::config::{McpServerConfig, McpTransport};
use fabro_mcp::config::{McpServerSettings, McpTransport};
use fabro_mcp::connection_manager::call_result_to_string;
fn test_server_config() -> McpServerConfig {
fn test_server_config() -> McpServerSettings {
let test_server = format!("{}/tests/test_mcp_server.py", env!("CARGO_MANIFEST_DIR"));
McpServerConfig {
McpServerSettings {
name: "test-echo".into(),
transport: McpTransport::Stdio {
command: vec!["python3".into(), test_server],

View file

@ -4,8 +4,8 @@ use std::time::Duration;
use fabro_agent::tool_registry::RegisteredTool;
use fabro_agent::{
AgentProfile, AnthropicProfile, GeminiProfile, OpenAiProfile, Sandbox, Session, SessionConfig,
SessionEvent, Turn,
AgentProfile, AnthropicProfile, GeminiProfile, OpenAiProfile, Sandbox, Session, SessionEvent,
SessionOptions, Turn,
};
use fabro_llm::client::Client;
use fabro_llm::provider::Provider;
@ -159,14 +159,14 @@ pub async fn run_retro_agent(
let profile: Arc<dyn AgentProfile> = Arc::from(profile);
let config = SessionConfig {
let config = SessionOptions {
max_tool_rounds_per_input: 20,
wall_clock_timeout: Some(Duration::from_secs(180)),
// Disable features not needed for retro analysis
enable_context_compaction: false,
skill_dirs: Some(vec![]),
user_instructions: Some(RETRO_SYSTEM_PROMPT.to_string()),
..SessionConfig::default()
..SessionOptions::default()
};
let mut session = Session::new(

View file

@ -21,7 +21,7 @@ use tokio::time;
use tokio_util::sync::CancellationToken;
/// Configuration for a Docker-based sandbox.
pub struct DockerSandboxConfig {
pub struct DockerSandboxOptions {
/// Docker image to use. Default: `"fabro-agent:latest"`.
pub image: String,
/// Host directory to bind-mount into the container.
@ -42,7 +42,7 @@ pub struct DockerSandboxConfig {
pub env_vars: Vec<String>,
}
impl Default for DockerSandboxConfig {
impl Default for DockerSandboxOptions {
fn default() -> Self {
Self {
image: "fabro-agent:latest".to_string(),
@ -64,7 +64,7 @@ impl Default for DockerSandboxConfig {
/// operations, commands, grep, and glob execute inside the container via `docker exec`.
pub struct DockerSandbox {
docker: Docker,
config: DockerSandboxConfig,
config: DockerSandboxOptions,
container_id: OnceCell<String>,
cached_platform: std::sync::OnceLock<String>,
cached_os_version: std::sync::OnceLock<String>,
@ -77,7 +77,7 @@ impl DockerSandbox {
///
/// Validates Docker daemon connectivity but does NOT create a container.
/// Call `initialize()` to create and start the container.
pub fn new(config: DockerSandboxConfig) -> Result<Self, String> {
pub fn new(config: DockerSandboxOptions) -> Result<Self, String> {
let docker = Docker::connect_with_local_defaults()
.map_err(|e| format!("Failed to connect to Docker daemon: {e}"))?;
Ok(Self {
@ -791,8 +791,8 @@ mod tests {
Docker::connect_with_local_defaults().expect("Docker not available — skipping")
}
fn test_config(host_dir: &str) -> DockerSandboxConfig {
DockerSandboxConfig {
fn test_config(host_dir: &str) -> DockerSandboxOptions {
DockerSandboxOptions {
host_working_directory: host_dir.to_string(),
auto_pull: false,
..Default::default()

View file

@ -32,12 +32,12 @@ pub use read_guard::ReadBeforeWriteSandbox;
pub use sandbox_provider::SandboxProvider;
pub use worktree::{WorktreeConfig, WorktreeEvent, WorktreeEventCallback, WorktreeSandbox};
pub use worktree::{WorktreeEvent, WorktreeEventCallback, WorktreeOptions, WorktreeSandbox};
pub use local::LocalSandbox;
#[cfg(feature = "docker")]
pub use docker::{DockerSandbox, DockerSandboxConfig};
pub use docker::{DockerSandbox, DockerSandboxOptions};
pub use sandbox_record::{SandboxRecord, SandboxRecordExt};

View file

@ -6,7 +6,7 @@ use anyhow::{Context, Result, bail};
#[cfg(feature = "daytona")]
use crate::daytona::DaytonaSandbox;
#[cfg(feature = "docker")]
use crate::docker::{DockerSandbox, DockerSandboxConfig};
use crate::docker::{DockerSandbox, DockerSandboxOptions};
use crate::local::LocalSandbox;
use crate::sandbox_record::SandboxRecord;
@ -30,10 +30,10 @@ pub async fn reconnect(record: &SandboxRecord) -> Result<Box<dyn crate::Sandbox>
.as_deref()
.unwrap_or("/workspace");
let config = DockerSandboxConfig {
let config = DockerSandboxOptions {
host_working_directory: host_dir.to_string(),
container_mount_point: mount_point.to_string(),
..DockerSandboxConfig::default()
..DockerSandboxOptions::default()
};
let sandbox = DockerSandbox::new(config)
.map_err(|e| anyhow::anyhow!("Failed to create Docker sandbox: {e}"))?;

View file

@ -12,7 +12,7 @@ use crate::{Sandbox, SandboxEventCallback};
#[cfg(feature = "daytona")]
use crate::daytona::{DaytonaConfig, DaytonaSandbox, DaytonaSnapshotConfig};
#[cfg(feature = "docker")]
use crate::docker::{DockerSandbox, DockerSandboxConfig};
use crate::docker::{DockerSandbox, DockerSandboxOptions};
use crate::local::LocalSandbox;
#[cfg(feature = "daytona")]
@ -25,7 +25,7 @@ pub enum SandboxSpec {
},
#[cfg(feature = "docker")]
Docker {
config: DockerSandboxConfig,
config: DockerSandboxOptions,
},
#[cfg(feature = "daytona")]
Daytona {
@ -157,7 +157,7 @@ impl SandboxSpec {
}
#[cfg(feature = "docker")]
Self::Docker { config } => {
let mut sandbox = DockerSandbox::new(DockerSandboxConfig {
let mut sandbox = DockerSandbox::new(DockerSandboxOptions {
image: config.image.clone(),
host_working_directory: config.host_working_directory.clone(),
container_mount_point: config.container_mount_point.clone(),

View file

@ -24,7 +24,7 @@ pub enum WorktreeEvent {
pub type WorktreeEventCallback = Arc<dyn Fn(WorktreeEvent) + Send + Sync>;
/// Configuration for a `WorktreeSandbox`.
pub struct WorktreeConfig {
pub struct WorktreeOptions {
pub branch_name: String,
pub base_sha: String,
pub worktree_path: String,
@ -39,14 +39,14 @@ pub struct WorktreeConfig {
/// The inner sandbox's lifecycle is managed separately by the caller.
pub struct WorktreeSandbox {
inner: Arc<dyn Sandbox>,
config: WorktreeConfig,
config: WorktreeOptions,
event_callback: Option<WorktreeEventCallback>,
initialized: std::sync::atomic::AtomicBool,
}
impl WorktreeSandbox {
/// Create a new `WorktreeSandbox` wrapping `inner` with the given configuration.
pub fn new(inner: Arc<dyn Sandbox>, config: WorktreeConfig) -> Self {
pub fn new(inner: Arc<dyn Sandbox>, config: WorktreeOptions) -> Self {
Self {
inner,
config,
@ -357,8 +357,8 @@ mod tests {
use crate::test_support::MockSandbox;
use std::sync::Mutex;
fn make_config(wt_path: &str) -> WorktreeConfig {
WorktreeConfig {
fn make_config(wt_path: &str) -> WorktreeOptions {
WorktreeOptions {
branch_name: "fabro/run/test-branch".to_string(),
base_sha: "abc123def456".to_string(),
worktree_path: wt_path.to_string(),
@ -366,8 +366,8 @@ mod tests {
}
}
fn make_config_skip(wt_path: &str) -> WorktreeConfig {
WorktreeConfig {
fn make_config_skip(wt_path: &str) -> WorktreeOptions {
WorktreeOptions {
branch_name: "fabro/run/test-branch".to_string(),
base_sha: "abc123def456".to_string(),
worktree_path: wt_path.to_string(),
@ -432,7 +432,7 @@ mod tests {
#[tokio::test]
async fn initialize_uses_shell_quoted_values_in_commands() {
let (inner, mock) = make_mock();
let config = WorktreeConfig {
let config = WorktreeOptions {
branch_name: "fabro/run/my-branch".to_string(),
base_sha: "deadbeef".to_string(),
worktree_path: "/tmp/my worktree".to_string(), // path with space
@ -651,7 +651,7 @@ mod tests {
std::fs::write(worktree.join("marker.txt"), "UNIQUE_WORKTREE_MARKER").unwrap();
let inner: Arc<dyn Sandbox> = Arc::new(LocalSandbox::new(original.clone()));
let config = WorktreeConfig {
let config = WorktreeOptions {
branch_name: "test-branch".into(),
base_sha: "abc123".into(),
worktree_path: worktree.to_string_lossy().to_string(),
@ -692,7 +692,7 @@ mod tests {
std::fs::write(worktree.join("worktree_only.txt"), "content").unwrap();
let inner: Arc<dyn Sandbox> = Arc::new(LocalSandbox::new(original.clone()));
let config = WorktreeConfig {
let config = WorktreeOptions {
branch_name: "test-branch".into(),
base_sha: "abc123".into(),
worktree_path: worktree.to_string_lossy().to_string(),
@ -726,7 +726,7 @@ mod tests {
std::fs::write(worktree.join("only_in_worktree.txt"), "worktree content").unwrap();
let inner: Arc<dyn Sandbox> = Arc::new(LocalSandbox::new(original.clone()));
let config = WorktreeConfig {
let config = WorktreeOptions {
branch_name: "test-branch".into(),
base_sha: "abc123".into(),
worktree_path: worktree.to_string_lossy().to_string(),
@ -752,7 +752,7 @@ mod tests {
#[test]
fn accessors_return_config_values() {
let (inner, _mock) = make_mock();
let config = WorktreeConfig {
let config = WorktreeOptions {
branch_name: "my-branch".to_string(),
base_sha: "sha123".to_string(),
worktree_path: "/path/to/wt".to_string(),

View file

@ -13,8 +13,8 @@ fn main() {
return;
}
let web_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
.join("../../../apps/fabro-web");
let web_dir =
PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("../../../apps/fabro-web");
let status = Command::new("bun")
.args(["run", "build"])

View file

@ -41,9 +41,9 @@ use tracing::{error, info};
use crate::demo;
use crate::error::ApiError;
use crate::jwt_auth::{AuthMode, AuthenticatedService};
use crate::static_files;
use crate::sessions as sessions_mod;
use crate::sessions::{SessionStore, new_session_store};
use crate::static_files;
use crate::web_auth;
use fabro_interview::{Answer, Interviewer, QuestionType, WebInterviewer};
use fabro_workflow::context::Context;
@ -174,20 +174,23 @@ pub fn build_router(state: Arc<AppState>, auth_mode: AuthMode) -> Router {
Router::new()
.route("/health", get(health))
.layer(middleware::from_fn_with_state(middleware_state, cookie_and_demo_middleware))
.layer(middleware::from_fn_with_state(
middleware_state,
cookie_and_demo_middleware,
))
.fallback_service(service_fn(move |req: axum_extract::Request| {
let dispatch = dispatch.clone();
async move {
let path = req.uri().path().to_string();
if path.starts_with("/api/v1/") || path.starts_with("/auth/") || path == "/health" {
dispatch.oneshot(req).await
} else if matches!(req.method(), &axum::http::Method::GET | &axum::http::Method::HEAD)
{
} else if matches!(
req.method(),
&axum::http::Method::GET | &axum::http::Method::HEAD
) {
Ok::<_, std::convert::Infallible>(static_files::serve(&path).await)
} else {
Ok::<_, std::convert::Infallible>(
StatusCode::NOT_FOUND.into_response(),
)
Ok::<_, std::convert::Infallible>(StatusCode::NOT_FOUND.into_response())
}
}
}))

View file

@ -73,7 +73,8 @@ fn asset_response(path: &str, bytes: Vec<u8>) -> Response {
*response.status_mut() = StatusCode::OK;
response.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_str(mime.as_ref()).unwrap_or_else(|_| HeaderValue::from_static("application/octet-stream")),
HeaderValue::from_str(mime.as_ref())
.unwrap_or_else(|_| HeaderValue::from_static("application/octet-stream")),
);
response.headers_mut().insert(
header::CACHE_CONTROL,

View file

@ -116,7 +116,10 @@ pub fn api_routes() -> Router<Arc<AppState>> {
pub fn parse_cookie_header(headers: &HeaderMap) -> CookieJar {
let mut jar = CookieJar::new();
if let Some(raw) = headers.get(header::COOKIE).and_then(|value| value.to_str().ok()) {
if let Some(raw) = headers
.get(header::COOKIE)
.and_then(|value| value.to_str().ok())
{
for part in raw.split(';') {
if let Ok(cookie) = Cookie::parse(part.trim().to_string()) {
jar.add_original(cookie.into_owned());
@ -159,7 +162,11 @@ fn features_json(settings: &FabroSettings) -> serde_json::Value {
}
async fn login_github(State(state): State<Arc<AppState>>) -> Response {
let settings = state.settings.read().expect("settings lock poisoned").clone();
let settings = state
.settings
.read()
.expect("settings lock poisoned")
.clone();
let Some(client_id) = settings.client_id().map(str::to_string) else {
return json_response(
StatusCode::CONFLICT,
@ -167,7 +174,10 @@ async fn login_github(State(state): State<Arc<AppState>>) -> Response {
);
};
let Some(web_url) = settings.web.as_ref().map(|web| web.url.clone()) else {
return json_response(StatusCode::CONFLICT, json!({"error": "web.url is not configured"}));
return json_response(
StatusCode::CONFLICT,
json!({"error": "web.url is not configured"}),
);
};
let state_token = format!("fabro-{}", ulid::Ulid::new());
@ -207,9 +217,15 @@ async fn callback_github(
json!({"error": "SESSION_SECRET is not configured"}),
);
};
let settings = state.settings.read().expect("settings lock poisoned").clone();
let settings = state
.settings
.read()
.expect("settings lock poisoned")
.clone();
let cookie_jar = parse_cookie_header(&headers);
let stored_state = cookie_jar.get(OAUTH_STATE_COOKIE_NAME).map(|cookie| cookie.value());
let stored_state = cookie_jar
.get(OAUTH_STATE_COOKIE_NAME)
.map(|cookie| cookie.value());
if stored_state != Some(params.state.as_str()) {
return Redirect::to("/login").into_response();
}
@ -240,7 +256,10 @@ async fn callback_github(
("client_id", client_id.as_str()),
("client_secret", client_secret.as_str()),
("code", params.code.as_str()),
("redirect_uri", format!("{web_url}/auth/callback/github").as_str()),
(
"redirect_uri",
format!("{web_url}/auth/callback/github").as_str(),
),
("state", params.state.as_str()),
])
.send()
@ -279,7 +298,8 @@ async fn callback_github(
.send()
.await
{
Ok(response) if response.status().is_success() => match response.json::<GitHubUser>().await {
Ok(response) if response.status().is_success() => match response.json::<GitHubUser>().await
{
Ok(profile) => profile,
Err(_) => {
return json_response(
@ -321,7 +341,8 @@ async fn callback_github(
.as_ref()
.map(|web| web.auth.allowed_usernames.clone())
.unwrap_or_default();
if !allowed_usernames.is_empty() && !allowed_usernames.iter().any(|user| user == &profile.login) {
if !allowed_usernames.is_empty() && !allowed_usernames.iter().any(|user| user == &profile.login)
{
return Redirect::to("/login?error=unauthorized").into_response();
}
@ -343,13 +364,16 @@ async fn callback_github(
let mut jar = CookieJar::new();
jar.private_mut(&session_key).add(
Cookie::build((SESSION_COOKIE_NAME, serde_json::to_string(&session).unwrap_or_default()))
.path("/")
.http_only(true)
.same_site(SameSite::Lax)
.secure(false)
.max_age(Duration::days(30))
.build(),
Cookie::build((
SESSION_COOKIE_NAME,
serde_json::to_string(&session).unwrap_or_default(),
))
.path("/")
.http_only(true)
.same_site(SameSite::Lax)
.secure(false)
.max_age(Duration::days(30))
.build(),
);
jar.add(
Cookie::build((OAUTH_STATE_COOKIE_NAME, ""))
@ -387,7 +411,11 @@ async fn auth_me(State(state): State<Arc<AppState>>, headers: HeaderMap) -> Resp
return json_response(StatusCode::UNAUTHORIZED, json!({"error": "Unauthorized"}));
};
let settings = state.settings.read().expect("settings lock poisoned").clone();
let settings = state
.settings
.read()
.expect("settings lock poisoned")
.clone();
let demo_mode = parse_cookie_header(&headers)
.get("fabro-demo")
.map(|cookie| cookie.value() == "1")
@ -408,7 +436,11 @@ async fn auth_me(State(state): State<Arc<AppState>>, headers: HeaderMap) -> Resp
}
async fn setup_status(State(state): State<Arc<AppState>>) -> Response {
let settings = state.settings.read().expect("settings lock poisoned").clone();
let settings = state
.settings
.read()
.expect("settings lock poisoned")
.clone();
let configured = settings
.git
.as_ref()
@ -476,7 +508,11 @@ async fn setup_register(
.unwrap_or_else(|_| PathBuf::from("."))
.join(".env");
let mut settings = state.settings.read().expect("settings lock poisoned").clone();
let mut settings = state
.settings
.read()
.expect("settings lock poisoned")
.clone();
let mut git = settings.git.clone().unwrap_or_default();
git.provider = GitProvider::Github;
git.app_id = Some(data.id.to_string());
@ -549,12 +585,13 @@ fn build_server_toml(settings: &FabroSettings, git: &GitSettings) -> String {
"auth".to_string(),
toml::Value::Table({
let mut auth = toml::Table::new();
auth.insert("provider".to_string(), toml::Value::String("github".to_string()));
auth.insert(
"provider".to_string(),
toml::Value::String("github".to_string()),
);
auth.insert(
"allowed_usernames".to_string(),
toml::Value::Array(
allowed.into_iter().map(toml::Value::String).collect(),
),
toml::Value::Array(allowed.into_iter().map(toml::Value::String).collect()),
);
auth
}),
@ -587,7 +624,10 @@ fn build_server_toml(settings: &FabroSettings, git: &GitSettings) -> String {
"git".to_string(),
toml::Value::Table({
let mut git_table = toml::Table::new();
git_table.insert("provider".to_string(), toml::Value::String("github".to_string()));
git_table.insert(
"provider".to_string(),
toml::Value::String("github".to_string()),
);
git_table.insert(
"app_id".to_string(),
toml::Value::String(git.app_id.clone().unwrap_or_default()),
@ -614,7 +654,11 @@ fn write_env_file(path: &PathBuf, updates: &BTreeMap<String, String>) -> std::io
merged.insert(key.trim().to_string(), value.to_string());
}
}
merged.extend(updates.iter().map(|(key, value)| (key.clone(), value.clone())));
merged.extend(
updates
.iter()
.map(|(key, value)| (key.clone(), value.clone())),
);
let body = merged
.into_iter()
.map(|(key, value)| format!("{key}={value}"))

View file

@ -993,9 +993,7 @@ mod route_prefixes {
fabro_server::jwt_auth::AuthMode::Disabled,
);
let cases = [
(Method::POST, "/completions"),
];
let cases = [(Method::POST, "/completions")];
for (method, path) in cases {
let req = Request::builder()

View file

@ -1,7 +1,7 @@
use serde::Deserialize;
#[derive(Debug, Clone, Default, Deserialize, PartialEq)]
pub struct SlackConfig {
pub struct SlackOptions {
pub default_channel: Option<String>,
}
@ -24,13 +24,13 @@ pub fn resolve_credentials() -> Option<SlackCredentials> {
})
}
pub struct SlackRuntimeConfig {
pub config: SlackConfig,
pub struct SlackRuntimeOptions {
pub config: SlackOptions,
pub credentials: SlackCredentials,
}
impl SlackRuntimeConfig {
pub fn new(config: SlackConfig, credentials: SlackCredentials) -> Self {
impl SlackRuntimeOptions {
pub fn new(config: SlackOptions, credentials: SlackCredentials) -> Self {
Self {
config,
credentials,
@ -44,14 +44,14 @@ mod tests {
#[test]
fn parse_empty_toml_defaults() {
let config: SlackConfig = toml::from_str("").unwrap();
let config: SlackOptions = toml::from_str("").unwrap();
assert_eq!(config.default_channel, None);
}
#[test]
fn parse_with_channel() {
let toml_str = r##"default_channel = "#arc-reviews""##;
let config: SlackConfig = toml::from_str(toml_str).unwrap();
let config: SlackOptions = toml::from_str(toml_str).unwrap();
assert_eq!(config.default_channel.as_deref(), Some("#arc-reviews"));
}

View file

@ -491,7 +491,7 @@ use tokio::net::TcpListener as TokioTcpListener;
use tokio::sync::OnceCell;
use tokio::time;
pub use twin_github::AppState as GitHubAppState;
pub use twin_github::state::AppConfig as GitHubAppConfig;
pub use twin_github::state::AppOptions as GitHubAppOptions;
use twin_openai::config::Config as TwinConfig;
/// A shared twin-openai server instance.

View file

@ -4,7 +4,7 @@ pub mod github;
pub mod linear;
pub use github::GitHubTracker;
pub use linear::{LINEAR_API_ENDPOINT, LinearConfig, LinearTracker};
pub use linear::{LINEAR_API_ENDPOINT, LinearOptions, LinearTracker};
/// Shared GraphQL execution used by both provider modules.
///

View file

@ -10,12 +10,12 @@ pub const LINEAR_API_ENDPOINT: &str = "https://api.linear.app/graphql";
const BLOCKS_RELATION_TYPE: &str = "blocks";
#[derive(Clone, Debug)]
pub struct LinearConfig {
pub struct LinearOptions {
pub api_key: String,
pub endpoint: String,
}
impl LinearConfig {
impl LinearOptions {
pub fn new(api_key: String) -> Self {
Self {
api_key,
@ -129,7 +129,7 @@ fn normalize_issue(node: &Value) -> Result<Issue, String> {
async fn execute_graphql(
client: &reqwest::Client,
config: &LinearConfig,
config: &LinearOptions,
query: &str,
variables: Value,
) -> Result<Value, String> {
@ -154,13 +154,13 @@ fn extract_issues(response: &Value) -> Result<Vec<Issue>, String> {
/// A `Tracker` implementation backed by Linear.
pub struct LinearTracker {
config: LinearConfig,
config: LinearOptions,
client: reqwest::Client,
project_slug: String,
}
impl LinearTracker {
pub fn new(config: LinearConfig, client: reqwest::Client, project_slug: String) -> Self {
pub fn new(config: LinearOptions, client: reqwest::Client, project_slug: String) -> Self {
Self {
config,
client,
@ -356,8 +356,8 @@ impl Tracker for LinearTracker {
mod tests {
use super::*;
fn mock_config(server_url: &str) -> LinearConfig {
LinearConfig {
fn mock_config(server_url: &str) -> LinearOptions {
LinearOptions {
api_key: "lin_api_test123".to_string(),
endpoint: format!("{server_url}/graphql"),
}
@ -1070,7 +1070,7 @@ mod tests {
#[tokio::test]
async fn fetch_issues_by_ids_empty() {
let config = LinearConfig::new("unused".to_string());
let config = LinearOptions::new("unused".to_string());
let tracker = LinearTracker::new(config, reqwest::Client::new(), "proj".to_string());
let issues = tracker.fetch_issues_by_ids(&[]).await.unwrap();
assert!(issues.is_empty());

View file

@ -191,12 +191,12 @@ impl HookDefinition {
/// Top-level hook configuration: a list of hook definitions.
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)]
pub struct HookConfig {
pub struct HookSettings {
#[serde(default)]
pub hooks: Vec<HookDefinition>,
}
impl HookConfig {
impl HookSettings {
/// Merge with another config. Concatenates lists; on name collisions, `other` wins.
#[must_use]
pub fn merge(self, other: Self) -> Self {

View file

@ -14,7 +14,7 @@ pub fn default_tool_timeout_secs() -> u64 {
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpServerConfig {
pub struct McpServerSettings {
pub name: String,
pub transport: McpTransport,
#[serde(default = "default_startup_timeout_secs")]
@ -23,7 +23,7 @@ pub struct McpServerConfig {
pub tool_timeout_secs: u64,
}
impl McpServerConfig {
impl McpServerSettings {
#[must_use]
pub fn startup_timeout(&self) -> Duration {
Duration::from_secs(self.startup_timeout_secs)
@ -67,7 +67,7 @@ impl Combine for McpTransport {
/// MCP server entry as it appears in TOML config files (without a `name` field).
///
/// Converted to [`McpServerConfig`] via [`McpServerEntry::into_config`].
/// Converted to [`McpServerSettings`] via [`McpServerEntry::into_config`].
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct McpServerEntry {
#[serde(flatten)]
@ -79,8 +79,8 @@ pub struct McpServerEntry {
}
impl McpServerEntry {
pub fn into_config(self, name: String) -> McpServerConfig {
McpServerConfig {
pub fn into_config(self, name: String) -> McpServerSettings {
McpServerSettings {
name,
transport: self.transport,
startup_timeout_secs: self.startup_timeout_secs,
@ -102,7 +102,7 @@ mod tests {
#[test]
fn stdio_config_construction() {
let config = McpServerConfig {
let config = McpServerSettings {
name: "test-server".into(),
transport: McpTransport::Stdio {
command: vec![
@ -122,7 +122,7 @@ mod tests {
#[test]
fn http_config_construction() {
let config = McpServerConfig {
let config = McpServerSettings {
name: "remote-server".into(),
transport: McpTransport::Http {
url: "https://example.com/mcp".into(),
@ -138,7 +138,7 @@ mod tests {
#[test]
fn serde_round_trip_stdio() {
let config = McpServerConfig {
let config = McpServerSettings {
name: "fs".into(),
transport: McpTransport::Stdio {
command: vec!["node".into(), "server.js".into()],
@ -148,7 +148,7 @@ mod tests {
tool_timeout_secs: 90,
};
let json = serde_json::to_string(&config).unwrap();
let deserialized: McpServerConfig = serde_json::from_str(&json).unwrap();
let deserialized: McpServerSettings = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.name, "fs");
assert_eq!(deserialized.startup_timeout_secs, 15);
assert_eq!(deserialized.tool_timeout_secs, 90);
@ -159,7 +159,7 @@ mod tests {
#[test]
fn serde_round_trip_http() {
let config = McpServerConfig {
let config = McpServerSettings {
name: "remote".into(),
transport: McpTransport::Http {
url: "https://mcp.example.com".into(),
@ -169,7 +169,7 @@ mod tests {
tool_timeout_secs: 60,
};
let json = serde_json::to_string(&config).unwrap();
let deserialized: McpServerConfig = serde_json::from_str(&json).unwrap();
let deserialized: McpServerSettings = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.name, "remote");
assert!(
matches!(deserialized.transport, McpTransport::Http { url, .. } if url == "https://mcp.example.com")
@ -179,7 +179,7 @@ mod tests {
#[test]
fn serde_defaults_applied() {
let json = r#"{"name":"minimal","transport":{"type":"stdio","command":["echo"]}}"#;
let config: McpServerConfig = serde_json::from_str(json).unwrap();
let config: McpServerSettings = serde_json::from_str(json).unwrap();
assert_eq!(config.startup_timeout_secs, 10);
assert_eq!(config.tool_timeout_secs, 60);
}

View file

@ -11,9 +11,9 @@ pub mod sandbox;
pub mod server;
pub mod user;
pub use hook::{HookConfig, HookDefinition, HookEvent, HookType, TlsMode};
pub use hook::{HookDefinition, HookEvent, HookSettings, HookType, TlsMode};
pub use mcp::{
McpServerConfig, McpServerEntry, McpTransport, default_startup_timeout_secs,
McpServerEntry, McpServerSettings, McpTransport, default_startup_timeout_secs,
default_tool_timeout_secs,
};
pub use project::ProjectSettings;

View file

@ -2,7 +2,7 @@ use std::time::Instant;
use sha2::{Digest, Sha256};
use fabro_devcontainer::DevcontainerConfig;
use fabro_devcontainer::DevcontainerSpec;
use crate::event::{EventEmitter, WorkflowRunEvent};
use fabro_agent::sandbox::Sandbox;
@ -16,8 +16,8 @@ pub fn snapshot_name_for_dockerfile(dockerfile: &str) -> String {
format!("devcontainer-{}", &hex[..12])
}
/// Map a `DevcontainerConfig` to a `DaytonaSnapshotConfig`.
pub fn devcontainer_to_snapshot_config(dc: &DevcontainerConfig) -> DaytonaSnapshotConfig {
/// Map a `DevcontainerSpec` to a `DaytonaSnapshotConfig`.
pub fn devcontainer_to_snapshot_config(dc: &DevcontainerSpec) -> DaytonaSnapshotConfig {
DaytonaSnapshotConfig {
name: snapshot_name_for_dockerfile(&dc.dockerfile),
dockerfile: Some(DockerfileSource::Inline(dc.dockerfile.clone())),
@ -460,8 +460,8 @@ mod tests {
assert_eq!(captured.len(), 2);
}
fn test_devcontainer_config(dockerfile: &str) -> DevcontainerConfig {
DevcontainerConfig {
fn test_devcontainer_config(dockerfile: &str) -> DevcontainerSpec {
DevcontainerSpec {
dockerfile: dockerfile.to_string(),
build_context: std::path::PathBuf::from("."),
build_args: HashMap::new(),

View file

@ -5,12 +5,12 @@ use async_trait::async_trait;
use fabro_agent::{
AgentEvent, AgentProfile, AnthropicProfile, GeminiProfile, OpenAiProfile, Sandbox, Session,
SessionConfig, Turn,
SessionOptions, Turn,
subagent::{SessionFactory, SubAgentManager},
};
use fabro_llm::client::Client;
use fabro_llm::types::{Message, Request, Usage};
use fabro_mcp::config::McpServerConfig;
use fabro_mcp::config::McpServerSettings;
use fabro_model::FallbackTarget;
use fabro_model::Provider;
use tokio::fs;
@ -120,7 +120,7 @@ pub struct AgentApiBackend {
fallback_chain: Vec<FallbackTarget>,
sessions: Mutex<HashMap<String, Session>>,
env: HashMap<String, String>,
mcp_servers: Vec<McpServerConfig>,
mcp_servers: Vec<McpServerSettings>,
}
impl AgentApiBackend {
@ -143,7 +143,7 @@ impl AgentApiBackend {
}
#[must_use]
pub fn with_mcp_servers(mut self, servers: Vec<McpServerConfig>) -> Self {
pub fn with_mcp_servers(mut self, servers: Vec<McpServerSettings>) -> Self {
self.mcp_servers = servers;
self
}
@ -178,7 +178,7 @@ impl AgentApiBackend {
sandbox: &Arc<dyn Sandbox>,
env: &HashMap<String, String>,
tool_hooks: Option<Arc<dyn fabro_agent::ToolHookCallback>>,
mcp_servers: Vec<McpServerConfig>,
mcp_servers: Vec<McpServerSettings>,
) -> Result<Session, FabroError> {
let client = Client::from_env()
.await
@ -186,13 +186,13 @@ impl AgentApiBackend {
let mut profile = build_profile(model, provider);
let config = SessionConfig {
let config = SessionOptions {
max_tokens: node.max_tokens(),
reasoning_effort: node.reasoning_effort().parse().ok(),
speed: node.speed().map(String::from),
tool_hooks,
mcp_servers,
..SessionConfig::default()
..SessionOptions::default()
};
let manager = Arc::new(TokioMutex::new(SubAgentManager::new(
@ -222,7 +222,7 @@ impl AgentApiBackend {
factory_client.clone(),
child_profile,
Arc::clone(&factory_env),
SessionConfig::default(),
SessionOptions::default(),
None,
);
if !factory_tool_env.is_empty() {

View file

@ -3,7 +3,7 @@ use std::sync::Arc;
use std::time::Instant;
use async_trait::async_trait;
use fabro_agent::{Sandbox, WorktreeConfig, WorktreeSandbox};
use fabro_agent::{Sandbox, WorktreeOptions, WorktreeSandbox};
use fabro_store::NodeVisitRef;
use fabro_types::RunId;
use tokio::sync::Semaphore;
@ -231,7 +231,7 @@ impl Handler for ParallelHandler {
tracing::debug!(branch = %branch_name, path = %wt_path_str, "Creating worktree for parallel branch");
// Set up worktree via WorktreeSandbox
let wt_config = WorktreeConfig {
let wt_config = WorktreeOptions {
branch_name: branch_name.clone(),
base_sha: bsha.clone(),
worktree_path: wt_path_str.clone(),

View file

@ -46,7 +46,7 @@ struct RunSession {
interviewer: Arc<dyn Interviewer>,
on_node: crate::OnNodeCallback,
lifecycle: LifecycleOptions,
hooks: fabro_hooks::HookConfig,
hooks: fabro_hooks::HookSettings,
sandbox_env: SandboxEnvSpec,
devcontainer: Option<DevcontainerSpec>,
seed_context: Option<Context>,
@ -293,7 +293,7 @@ impl RunSession {
working_directory: working_directory.clone(),
},
SandboxProvider::Docker => SandboxSpec::Docker {
config: fabro_agent::DockerSandboxConfig {
config: fabro_agent::DockerSandboxOptions {
host_working_directory: working_directory.to_string_lossy().to_string(),
..Default::default()
},
@ -349,7 +349,7 @@ impl RunSession {
setup_command_timeout_ms: settings.setup_timeout_ms().unwrap_or(300_000),
devcontainer_phases: Vec::new(),
},
hooks: fabro_hooks::HookConfig {
hooks: fabro_hooks::HookSettings {
hooks: settings.hooks.clone(),
},
sandbox_env,

View file

@ -11,7 +11,7 @@ use chrono::Utc;
use fabro_agent::Sandbox;
use fabro_config::FabroSettings;
use fabro_graphviz::graph::{AttrValue, Edge, Graph, Node};
use fabro_hooks::HookConfig;
use fabro_hooks::HookSettings;
use fabro_interview::AutoApproveInterviewer;
use fabro_sandbox::SandboxSpec;
use fabro_store::InMemoryStore;
@ -200,7 +200,7 @@ async fn execute_test_run_with_options(
devcontainer_phases: vec![],
},
run_options,
hooks: HookConfig { hooks: vec![] },
hooks: HookSettings { hooks: vec![] },
sandbox_env: SandboxEnvSpec {
devcontainer_env: HashMap::new(),
toml_env: HashMap::new(),
@ -251,7 +251,7 @@ async fn execute_runs_start_to_exit_and_returns_final_context() {
devcontainer_phases: vec![],
},
run_options: test_run_options(&run_dir, "run-test"),
hooks: HookConfig { hooks: vec![] },
hooks: HookSettings { hooks: vec![] },
sandbox_env: SandboxEnvSpec {
devcontainer_env: HashMap::new(),
toml_env: HashMap::new(),
@ -314,7 +314,7 @@ async fn run_with_lifecycle(
interviewer: Arc::new(AutoApproveInterviewer),
lifecycle,
run_options,
hooks: HookConfig { hooks: vec![] },
hooks: HookSettings { hooks: vec![] },
sandbox_env: SandboxEnvSpec {
devcontainer_env: HashMap::new(),
toml_env: HashMap::new(),

View file

@ -8,7 +8,7 @@ use fabro_graphviz::graph;
use fabro_hooks::{HookContext, HookDecision, HookEvent, HookRunner};
use fabro_llm::client::Client;
use fabro_sandbox::{
ReadBeforeWriteSandbox, SandboxEventCallback, SandboxSpec, WorkdirStrategy, WorktreeConfig,
ReadBeforeWriteSandbox, SandboxEventCallback, SandboxSpec, WorkdirStrategy, WorktreeOptions,
WorktreeSandbox,
};
use shlex::try_quote;
@ -441,7 +441,7 @@ pub async fn initialize(
.map_err(|e| FabroError::engine(e.to_string()))?;
let mut worktree = WorktreeSandbox::new(
inner,
WorktreeConfig {
WorktreeOptions {
branch_name: plan.branch_name.clone(),
base_sha: plan.base_sha.clone(),
worktree_path: plan.worktree_path.to_string_lossy().into_owned(),
@ -792,7 +792,7 @@ mod tests {
devcontainer_phases: vec![],
},
run_options: test_settings(&run_dir),
hooks: fabro_hooks::HookConfig { hooks: vec![] },
hooks: fabro_hooks::HookSettings { hooks: vec![] },
sandbox_env: SandboxEnvSpec {
devcontainer_env: HashMap::new(),
toml_env: HashMap::from([("TEST_KEY".to_string(), "value".to_string())]),
@ -867,7 +867,7 @@ mod tests {
devcontainer_phases: vec![],
},
run_options: test_settings(&run_dir),
hooks: fabro_hooks::HookConfig { hooks: vec![] },
hooks: fabro_hooks::HookSettings { hooks: vec![] },
sandbox_env: SandboxEnvSpec {
devcontainer_env: HashMap::new(),
toml_env: HashMap::new(),

View file

@ -8,7 +8,7 @@ use fabro_graphviz::graph::Graph;
use fabro_hooks::HookRunner;
use fabro_interview::Interviewer;
use fabro_llm::Provider;
use fabro_mcp::config::McpServerConfig;
use fabro_mcp::config::McpServerSettings;
use fabro_model::FallbackTarget;
use fabro_sandbox::SandboxSpec;
use fabro_store::RunStore;
@ -207,7 +207,7 @@ pub struct LlmSpec {
pub model: String,
pub provider: Provider,
pub fallback_chain: Vec<FallbackTarget>,
pub mcp_servers: Vec<McpServerConfig>,
pub mcp_servers: Vec<McpServerSettings>,
pub dry_run: bool,
}
@ -235,7 +235,7 @@ pub struct InitOptions {
pub interviewer: Arc<dyn Interviewer>,
pub lifecycle: LifecycleOptions,
pub run_options: RunOptions,
pub hooks: fabro_hooks::HookConfig,
pub hooks: fabro_hooks::HookSettings,
pub sandbox_env: SandboxEnvSpec,
pub devcontainer: Option<DevcontainerSpec>,
pub git: Option<GitCheckpointOptions>,

View file

@ -11,7 +11,7 @@ pub struct ImportTransform {
fallback_dir: Option<PathBuf>,
}
struct PlaceholderConfig {
struct PlaceholderOptions {
default_attrs: HashMap<String, AttrValue>,
class_names: Vec<String>,
normalized_class: String,
@ -194,7 +194,7 @@ impl ImportTransform {
graph: &mut Graph,
placeholder_id: &str,
resolved_path: &Path,
placeholder: &PlaceholderConfig,
placeholder: &PlaceholderOptions,
prepared: PreparedImport,
) -> Result<(), String> {
if graph
@ -341,7 +341,7 @@ impl ImportTransform {
fn placeholder_config(
graph: &Graph,
placeholder_id: &str,
) -> Result<PlaceholderConfig, String> {
) -> Result<PlaceholderOptions, String> {
let node = graph
.nodes
.get(placeholder_id)
@ -378,7 +378,7 @@ impl ImportTransform {
));
}
Ok(PlaceholderConfig {
Ok(PlaceholderOptions {
default_attrs,
class_names,
normalized_class: Self::normalize_class_name(placeholder_id),

View file

@ -2010,7 +2010,7 @@ async fn daytona_playwright_mcp_sandbox_transport() {
// 2. Start the Playwright MCP server via the sandbox transport resolution path
let mcp_port = 3100u16;
let mcp_config = fabro_mcp::config::McpServerConfig {
let mcp_config = fabro_mcp::config::McpServerSettings {
name: "playwright".into(),
transport: fabro_mcp::config::McpTransport::Sandbox {
command: vec![
@ -2077,7 +2077,7 @@ async fn daytona_playwright_mcp_sandbox_transport() {
};
eprintln!("Preview URL: {url}");
fabro_mcp::config::McpServerConfig {
fabro_mcp::config::McpServerSettings {
name: mcp_config.name.clone(),
transport: fabro_mcp::config::McpTransport::Http { url, headers },
startup_timeout_secs: mcp_config.startup_timeout_secs,

View file

@ -7157,7 +7157,7 @@ fn subgraph_without_label_no_class_derived() {
// ---------------------------------------------------------------------------
fn hook_runner_from_defs(hooks: Vec<fabro_hooks::HookDefinition>) -> Arc<fabro_hooks::HookRunner> {
Arc::new(fabro_hooks::HookRunner::new(fabro_hooks::HookConfig {
Arc::new(fabro_hooks::HookRunner::new(fabro_hooks::HookSettings {
hooks,
}))
}
@ -7798,9 +7798,9 @@ async fn hook_stage_start_exit_2_blocks() {
#[tokio::test]
async fn hook_config_merge_concatenates() {
use fabro_hooks::{HookConfig, HookDefinition, HookEvent};
use fabro_hooks::{HookDefinition, HookEvent, HookSettings};
let server_hooks = HookConfig {
let server_hooks = HookSettings {
hooks: vec![HookDefinition {
name: Some("server-hook".into()),
event: HookEvent::RunStart,
@ -7812,7 +7812,7 @@ async fn hook_config_merge_concatenates() {
sandbox: Some(false),
}],
};
let run_hooks = HookConfig {
let run_hooks = HookSettings {
hooks: vec![HookDefinition {
name: Some("run-hook".into()),
event: HookEvent::StageComplete,
@ -7833,9 +7833,9 @@ async fn hook_config_merge_concatenates() {
#[tokio::test]
async fn hook_config_merge_run_overrides_by_name() {
use fabro_hooks::{HookConfig, HookDefinition, HookEvent};
use fabro_hooks::{HookDefinition, HookEvent, HookSettings};
let server_hooks = HookConfig {
let server_hooks = HookSettings {
hooks: vec![HookDefinition {
name: Some("shared".into()),
event: HookEvent::RunStart,
@ -7847,7 +7847,7 @@ async fn hook_config_merge_run_overrides_by_name() {
sandbox: Some(false),
}],
};
let run_hooks = HookConfig {
let run_hooks = HookSettings {
hooks: vec![HookDefinition {
name: Some("shared".into()),
event: HookEvent::RunStart,
@ -12574,7 +12574,7 @@ async fn asset_collection_docker_sandbox() {
let host_dir = tempfile::tempdir().unwrap();
let run_dir = tempfile::tempdir().unwrap();
let config = fabro_agent::DockerSandboxConfig {
let config = fabro_agent::DockerSandboxOptions {
host_working_directory: host_dir.path().to_str().unwrap().to_string(),
auto_pull: false,
..Default::default()

View file

@ -6,8 +6,9 @@ use std::path::Path;
use serde::Deserialize;
use crate::state::{
AppConfig, AppState, Comment, Installation, IssueContent, ManifestConversion, OwnerType,
Project, ProjectItem, PullRequest, Release, Repository, StatusOption, TokenInfo, WebhookConfig,
AppOptions, AppState, Comment, Installation, IssueContent, ManifestConversion, OwnerType,
Project, ProjectItem, PullRequest, Release, Repository, StatusOption, TokenInfo,
WebhookOptions,
};
#[derive(Debug, Clone, Deserialize, Default)]
@ -31,7 +32,7 @@ pub struct FixtureState {
#[serde(default)]
pub comments: Vec<FixtureComment>,
#[serde(default)]
pub webhook_config: FixtureWebhookConfig,
pub webhook_config: FixtureWebhookOptions,
pub next_installation_id: Option<u64>,
pub next_pr_number: Option<u64>,
pub viewer_id: Option<String>,
@ -161,7 +162,7 @@ pub struct FixtureComment {
}
#[derive(Debug, Clone, Deserialize, Default)]
pub struct FixtureWebhookConfig {
pub struct FixtureWebhookOptions {
pub url: Option<String>,
pub content_type: Option<String>,
}
@ -180,7 +181,7 @@ impl FixtureState {
for app in self.apps {
let app_id = app.app_id.clone();
let config = AppConfig {
let config = AppOptions {
app_id: app.app_id,
slug: app.slug,
owner_login: app.owner_login,
@ -328,7 +329,7 @@ impl FixtureState {
})
.collect();
state.webhook_config = WebhookConfig {
state.webhook_config = WebhookOptions {
url: self.webhook_config.url,
content_type: self.webhook_config.content_type,
};

View file

@ -112,7 +112,7 @@ pub async fn patch_webhook_config(
#[cfg(test)]
mod tests {
use crate::server::TestServer;
use crate::state::{AppConfig, AppState};
use crate::state::{AppOptions, AppState};
fn test_rsa_key() -> String {
use std::process::Command;
@ -155,7 +155,7 @@ mod tests {
async fn get_app_returns_app_info() {
let pem = test_rsa_key();
let mut state = AppState::new();
state.register_app(AppConfig {
state.register_app(AppOptions {
app_id: "12345".to_string(),
slug: "my-app".to_string(),
owner_login: "my-org".to_string(),
@ -187,7 +187,7 @@ mod tests {
async fn get_app_rejects_invalid_jwt() {
let pem = test_rsa_key();
let mut state = AppState::new();
state.register_app(AppConfig {
state.register_app(AppOptions {
app_id: "12345".to_string(),
slug: "my-app".to_string(),
owner_login: "my-org".to_string(),
@ -213,7 +213,7 @@ mod tests {
async fn get_apps_slug_public() {
let pem = test_rsa_key();
let mut state = AppState::new();
state.register_app(AppConfig {
state.register_app(AppOptions {
app_id: "12345".to_string(),
slug: "my-app".to_string(),
owner_login: "my-org".to_string(),
@ -240,7 +240,7 @@ mod tests {
async fn get_apps_slug_private_returns_404() {
let pem = test_rsa_key();
let mut state = AppState::new();
state.register_app(AppConfig {
state.register_app(AppOptions {
app_id: "12345".to_string(),
slug: "private-app".to_string(),
owner_login: "my-org".to_string(),

View file

@ -92,7 +92,7 @@ pub async fn get_branch(
#[cfg(test)]
mod tests {
use crate::server::TestServer;
use crate::state::{AppConfig, AppState};
use crate::state::{AppOptions, AppState};
fn test_rsa_key() -> String {
use std::process::Command;
@ -169,7 +169,7 @@ mod tests {
async fn branch_exists_returns_200() {
let pem = test_rsa_key();
let mut state = AppState::new();
state.register_app(AppConfig {
state.register_app(AppOptions {
app_id: "100".to_string(),
slug: "test-app".to_string(),
owner_login: "owner".to_string(),
@ -212,7 +212,7 @@ mod tests {
async fn branch_not_found_returns_404() {
let pem = test_rsa_key();
let mut state = AppState::new();
state.register_app(AppConfig {
state.register_app(AppOptions {
app_id: "100".to_string(),
slug: "test-app".to_string(),
owner_login: "owner".to_string(),

View file

@ -613,7 +613,7 @@ fn extract_unquoted_value(s: &str, key: &str) -> Option<String> {
#[cfg(test)]
mod tests {
use crate::server::TestServer;
use crate::state::{AppConfig, AppState, PullRequest};
use crate::state::{AppOptions, AppState, PullRequest};
fn test_rsa_key() -> String {
use std::process::Command;
@ -697,7 +697,7 @@ mod tests {
state: &mut AppState,
pem: &str,
) -> (TestServer, reqwest::Client, String) {
state.register_app(AppConfig {
state.register_app(AppOptions {
app_id: "100".to_string(),
slug: "test-app".to_string(),
owner_login: "owner".to_string(),

View file

@ -177,7 +177,7 @@ pub async fn create_access_token(
#[cfg(test)]
mod tests {
use crate::server::TestServer;
use crate::state::{AppConfig, AppState};
use crate::state::{AppOptions, AppState};
fn test_rsa_key() -> String {
use std::process::Command;
@ -220,7 +220,7 @@ mod tests {
async fn get_installation_returns_id() {
let pem = test_rsa_key();
let mut state = AppState::new();
state.register_app(AppConfig {
state.register_app(AppOptions {
app_id: "100".to_string(),
slug: "test-app".to_string(),
owner_login: "owner".to_string(),
@ -252,7 +252,7 @@ mod tests {
async fn get_installation_returns_404_when_not_installed() {
let pem = test_rsa_key();
let mut state = AppState::new();
state.register_app(AppConfig {
state.register_app(AppOptions {
app_id: "100".to_string(),
slug: "test-app".to_string(),
owner_login: "owner".to_string(),
@ -280,7 +280,7 @@ mod tests {
async fn get_installation_returns_403_when_suspended() {
let pem = test_rsa_key();
let mut state = AppState::new();
state.register_app(AppConfig {
state.register_app(AppOptions {
app_id: "100".to_string(),
slug: "test-app".to_string(),
owner_login: "owner".to_string(),
@ -308,7 +308,7 @@ mod tests {
async fn create_access_token_returns_201() {
let pem = test_rsa_key();
let mut state = AppState::new();
state.register_app(AppConfig {
state.register_app(AppOptions {
app_id: "100".to_string(),
slug: "test-app".to_string(),
owner_login: "owner".to_string(),
@ -346,7 +346,7 @@ mod tests {
async fn create_access_token_returns_422_for_unauthorized_repo() {
let pem = test_rsa_key();
let mut state = AppState::new();
state.register_app(AppConfig {
state.register_app(AppOptions {
app_id: "100".to_string(),
slug: "test-app".to_string(),
owner_login: "owner".to_string(),

View file

@ -302,7 +302,7 @@ fn pr_to_json(pr: &PullRequest) -> serde_json::Value {
#[cfg(test)]
mod tests {
use crate::server::TestServer;
use crate::state::{AppConfig, AppState};
use crate::state::{AppOptions, AppState};
fn test_rsa_key() -> String {
use std::process::Command;
@ -379,7 +379,7 @@ mod tests {
state: &mut AppState,
pem: &str,
) -> (TestServer, reqwest::Client, String) {
state.register_app(AppConfig {
state.register_app(AppOptions {
app_id: "100".to_string(),
slug: "test-app".to_string(),
owner_login: "owner".to_string(),

View file

@ -3,7 +3,7 @@ use std::collections::HashMap;
/// Configuration for a registered GitHub App (user-facing input).
#[derive(Debug, Clone)]
pub struct AppConfig {
pub struct AppOptions {
pub app_id: String,
pub slug: String,
pub owner_login: String,
@ -15,7 +15,7 @@ pub struct AppConfig {
/// Internal enriched app config with derived public key.
#[derive(Debug, Clone)]
pub struct RegisteredApp {
pub config: AppConfig,
pub config: AppOptions,
/// Derived from `private_key_pem` during `register_app`. Used for JWT verification.
pub public_key_pem: String,
}
@ -140,7 +140,7 @@ pub struct Comment {
/// Stores webhook configuration.
#[derive(Debug, Clone, Default)]
pub struct WebhookConfig {
pub struct WebhookOptions {
pub url: Option<String>,
pub content_type: Option<String>,
}
@ -234,7 +234,7 @@ pub struct AppState {
pub releases: HashMap<(String, String), Release>,
pub manifest_conversions: HashMap<String, ManifestConversion>,
pub comments: Vec<Comment>,
pub webhook_config: WebhookConfig,
pub webhook_config: WebhookOptions,
pub next_installation_id: u64,
pub next_pr_number: u64,
pub viewer_id: String,
@ -279,14 +279,14 @@ impl AppState {
releases: HashMap::new(),
manifest_conversions: HashMap::new(),
comments: Vec::new(),
webhook_config: WebhookConfig::default(),
webhook_config: WebhookOptions::default(),
next_installation_id: 1,
next_pr_number: 1,
viewer_id: "U_fakeviewer".to_string(),
}
}
pub fn register_app(&mut self, config: AppConfig) {
pub fn register_app(&mut self, config: AppOptions) {
let public_key_pem = derive_public_key_pem(&config.private_key_pem);
let app_id = config.app_id.clone();
self.apps.insert(
@ -477,7 +477,7 @@ mod tests {
fn can_register_app() {
let pem = test_rsa_key();
let mut state = AppState::new();
state.register_app(AppConfig {
state.register_app(AppOptions {
app_id: "12345".to_string(),
slug: "test-app".to_string(),
owner_login: "test-owner".to_string(),

View file

@ -16,7 +16,7 @@ pub struct ResponsesRequest {
pub stop: Option<Value>,
pub previous_response_id: Option<String>,
pub reasoning: Option<Value>,
pub text: Option<TextConfig>,
pub text: Option<TextOptions>,
pub tools: Option<Vec<Value>>,
pub tool_choice: Option<Value>,
/// Catch-all for fields the twin doesn't use (temperature, top_p, etc.)
@ -319,7 +319,7 @@ fn validate_input_content_part(part: &ContentPart) -> Result<(), OpenAiError> {
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TextConfig {
pub struct TextOptions {
pub format: Option<TextFormat>,
}

View file

@ -21,7 +21,7 @@ const RESPONSES_STREAM_MILESTONES: &[&str] = &[
];
#[derive(Clone)]
struct LiveConfig {
struct LiveOptions {
api: common::ApiClient,
model: String,
}
@ -51,7 +51,7 @@ struct ToolCallObservation {
#[tokio::test]
#[ignore = "requires OPENAI_API_KEY and outbound network"]
async fn live_openai_contract_smoke_suite() {
let Some(config) = LiveConfig::from_env().expect("live config should load") else {
let Some(config) = LiveOptions::from_env().expect("live config should load") else {
eprintln!("skipping live OpenAI smoke suite because OPENAI_API_KEY is not set");
return;
};
@ -203,7 +203,7 @@ async fn live_openai_contract_smoke_suite() {
);
}
impl LiveConfig {
impl LiveOptions {
fn from_env() -> Result<Option<Self>> {
let Some(api_key) = non_empty_env("OPENAI_API_KEY") else {
return Ok(None);
@ -232,7 +232,7 @@ fn collect_failure(failures: &mut Vec<String>, name: &str, result: Result<()>) {
}
}
async fn probe_responses_availability(config: &LiveConfig) -> Result<SurfaceAvailability> {
async fn probe_responses_availability(config: &LiveOptions) -> Result<SurfaceAvailability> {
probe_surface_availability(
&config.api,
"/v1/responses",
@ -246,7 +246,7 @@ async fn probe_responses_availability(config: &LiveConfig) -> Result<SurfaceAvai
.await
}
async fn probe_chat_availability(config: &LiveConfig) -> Result<SurfaceAvailability> {
async fn probe_chat_availability(config: &LiveOptions) -> Result<SurfaceAvailability> {
probe_surface_availability(
&config.api,
"/v1/chat/completions",
@ -284,7 +284,7 @@ async fn probe_surface_availability(
);
}
async fn compare_responses_non_stream_text(config: &LiveConfig) -> Result<()> {
async fn compare_responses_non_stream_text(config: &LiveOptions) -> Result<()> {
let server = common::spawn_server().await?;
let request = json!({
"model": config.model,
@ -319,7 +319,7 @@ async fn compare_responses_non_stream_text(config: &LiveConfig) -> Result<()> {
Ok(())
}
async fn compare_responses_stream_text(config: &LiveConfig) -> Result<()> {
async fn compare_responses_stream_text(config: &LiveOptions) -> Result<()> {
let server = common::spawn_server().await?;
let request = json!({
"model": config.model,
@ -347,7 +347,7 @@ async fn compare_responses_stream_text(config: &LiveConfig) -> Result<()> {
Ok(())
}
async fn compare_responses_structured_output(config: &LiveConfig) -> Result<()> {
async fn compare_responses_structured_output(config: &LiveOptions) -> Result<()> {
let server = common::spawn_server().await?;
let schema = smoke_schema();
let request = json!({
@ -386,7 +386,7 @@ async fn compare_responses_structured_output(config: &LiveConfig) -> Result<()>
Ok(())
}
async fn compare_responses_stream_structured_output(config: &LiveConfig) -> Result<()> {
async fn compare_responses_stream_structured_output(config: &LiveOptions) -> Result<()> {
let server = common::spawn_server().await?;
let schema = smoke_schema();
let request = json!({
@ -436,7 +436,7 @@ async fn compare_responses_stream_structured_output(config: &LiveConfig) -> Resu
Ok(())
}
async fn compare_responses_tool_call(config: &LiveConfig) -> Result<()> {
async fn compare_responses_tool_call(config: &LiveOptions) -> Result<()> {
let server = common::spawn_server().await?;
enqueue_tool_scenario(&server, "responses", &config.model, false).await;
@ -494,7 +494,7 @@ async fn compare_responses_tool_call(config: &LiveConfig) -> Result<()> {
Ok(())
}
async fn compare_responses_stream_tool_call(config: &LiveConfig) -> Result<()> {
async fn compare_responses_stream_tool_call(config: &LiveOptions) -> Result<()> {
let server = common::spawn_server().await?;
enqueue_tool_scenario(&server, "responses", &config.model, true).await;
@ -553,7 +553,7 @@ async fn compare_responses_stream_tool_call(config: &LiveConfig) -> Result<()> {
Ok(())
}
async fn compare_responses_continuation(config: &LiveConfig) -> Result<()> {
async fn compare_responses_continuation(config: &LiveOptions) -> Result<()> {
let server = common::spawn_server().await?;
enqueue_responses_continuation_scenarios(&server, &config.model).await;
@ -656,7 +656,7 @@ async fn compare_responses_continuation(config: &LiveConfig) -> Result<()> {
Ok(())
}
async fn compare_responses_stream_continuation(config: &LiveConfig) -> Result<()> {
async fn compare_responses_stream_continuation(config: &LiveOptions) -> Result<()> {
let server = common::spawn_server().await?;
enqueue_responses_continuation_scenarios(&server, &config.model).await;
@ -746,7 +746,7 @@ async fn compare_responses_stream_continuation(config: &LiveConfig) -> Result<()
Ok(())
}
async fn compare_responses_image_input(config: &LiveConfig) -> Result<()> {
async fn compare_responses_image_input(config: &LiveOptions) -> Result<()> {
let server = common::spawn_server().await?;
let request = json!({
"model": config.model,
@ -782,7 +782,7 @@ async fn compare_responses_image_input(config: &LiveConfig) -> Result<()> {
Ok(())
}
async fn compare_responses_stream_image_input(config: &LiveConfig) -> Result<()> {
async fn compare_responses_stream_image_input(config: &LiveOptions) -> Result<()> {
let server = common::spawn_server().await?;
let request = json!({
"model": config.model,
@ -821,7 +821,7 @@ async fn compare_responses_stream_image_input(config: &LiveConfig) -> Result<()>
Ok(())
}
async fn compare_responses_tool_choice_none(config: &LiveConfig) -> Result<()> {
async fn compare_responses_tool_choice_none(config: &LiveOptions) -> Result<()> {
let server = common::spawn_server().await?;
let request = json!({
"model": config.model,
@ -867,7 +867,7 @@ async fn compare_responses_tool_choice_none(config: &LiveConfig) -> Result<()> {
Ok(())
}
async fn compare_chat_non_stream_text(config: &LiveConfig) -> Result<()> {
async fn compare_chat_non_stream_text(config: &LiveOptions) -> Result<()> {
let server = common::spawn_server().await?;
let request = json!({
"model": config.model,
@ -902,7 +902,7 @@ async fn compare_chat_non_stream_text(config: &LiveConfig) -> Result<()> {
Ok(())
}
async fn compare_chat_stream_text(config: &LiveConfig) -> Result<()> {
async fn compare_chat_stream_text(config: &LiveOptions) -> Result<()> {
let server = common::spawn_server().await?;
let request = json!({
"model": config.model,
@ -925,7 +925,7 @@ async fn compare_chat_stream_text(config: &LiveConfig) -> Result<()> {
Ok(())
}
async fn compare_chat_structured_output(config: &LiveConfig) -> Result<()> {
async fn compare_chat_structured_output(config: &LiveOptions) -> Result<()> {
let server = common::spawn_server().await?;
let schema = smoke_schema();
let request = json!({
@ -964,7 +964,7 @@ async fn compare_chat_structured_output(config: &LiveConfig) -> Result<()> {
Ok(())
}
async fn compare_chat_stream_structured_output(config: &LiveConfig) -> Result<()> {
async fn compare_chat_stream_structured_output(config: &LiveOptions) -> Result<()> {
let server = common::spawn_server().await?;
let schema = smoke_schema();
let request = json!({
@ -1009,7 +1009,7 @@ async fn compare_chat_stream_structured_output(config: &LiveConfig) -> Result<()
Ok(())
}
async fn compare_chat_tool_call(config: &LiveConfig) -> Result<()> {
async fn compare_chat_tool_call(config: &LiveOptions) -> Result<()> {
let server = common::spawn_server().await?;
enqueue_tool_scenario(&server, "chat.completions", &config.model, false).await;
@ -1065,7 +1065,7 @@ async fn compare_chat_tool_call(config: &LiveConfig) -> Result<()> {
Ok(())
}
async fn compare_chat_stream_tool_call(config: &LiveConfig) -> Result<()> {
async fn compare_chat_stream_tool_call(config: &LiveOptions) -> Result<()> {
let server = common::spawn_server().await?;
enqueue_tool_scenario(&server, "chat.completions", &config.model, true).await;
@ -1109,7 +1109,7 @@ async fn compare_chat_stream_tool_call(config: &LiveConfig) -> Result<()> {
Ok(())
}
async fn compare_chat_image_input(config: &LiveConfig) -> Result<()> {
async fn compare_chat_image_input(config: &LiveOptions) -> Result<()> {
let server = common::spawn_server().await?;
let request = json!({
"model": config.model,
@ -1145,7 +1145,7 @@ async fn compare_chat_image_input(config: &LiveConfig) -> Result<()> {
Ok(())
}
async fn compare_chat_stream_image_input(config: &LiveConfig) -> Result<()> {
async fn compare_chat_stream_image_input(config: &LiveOptions) -> Result<()> {
let server = common::spawn_server().await?;
let request = json!({
"model": config.model,
@ -1179,7 +1179,7 @@ async fn compare_chat_stream_image_input(config: &LiveConfig) -> Result<()> {
Ok(())
}
async fn compare_chat_tool_choice_none(config: &LiveConfig) -> Result<()> {
async fn compare_chat_tool_choice_none(config: &LiveOptions) -> Result<()> {
let server = common::spawn_server().await?;
let request = json!({
"model": config.model,