mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-05 02:41:45 +00:00
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:
parent
93eab71892
commit
b5f5162c3a
75 changed files with 448 additions and 396 deletions
|
|
@ -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"],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
// Re-export from fabro-sandbox
|
||||
pub use fabro_sandbox::docker::{DockerSandbox, DockerSandboxConfig};
|
||||
pub use fabro_sandbox::docker::{DockerSandbox, DockerSandboxOptions};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
pub use fabro_config::hook::{HookConfig, HookDefinition, HookEvent, HookType, TlsMode};
|
||||
pub use fabro_config::hook::{HookDefinition, HookEvent, HookSettings, HookType, TlsMode};
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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(|| {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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?;
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
||||
|
|
|
|||
|
|
@ -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}"))?;
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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"])
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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}"))
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue