mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
fix(auto-drive): align observer tool roster with session config
This commit is contained in:
@@ -23,6 +23,7 @@ use tracing::trace;
|
||||
use tracing::warn;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::agent_defaults::{default_agent_configs, enabled_agent_model_specs};
|
||||
use crate::chat_completions::AggregateStreamExt;
|
||||
use crate::chat_completions::stream_chat_completions;
|
||||
use crate::client_common::Prompt;
|
||||
@@ -31,7 +32,6 @@ use crate::client_common::ResponseStream;
|
||||
use crate::client_common::ResponsesApiRequest;
|
||||
use crate::client_common::create_reasoning_param_for_request;
|
||||
use crate::config::Config;
|
||||
use crate::openai_model_info::get_model_info;
|
||||
use crate::config_types::ReasoningEffort as ReasoningEffortConfig;
|
||||
use crate::config_types::ReasoningSummary as ReasoningSummaryConfig;
|
||||
use crate::config_types::TextVerbosity as TextVerbosityConfig;
|
||||
@@ -46,9 +46,14 @@ use crate::flags::CODEX_RS_SSE_FIXTURE;
|
||||
use crate::model_family::ModelFamily;
|
||||
use crate::model_provider_info::ModelProviderInfo;
|
||||
use crate::model_provider_info::WireApi;
|
||||
use crate::openai_model_info::get_model_info;
|
||||
use crate::openai_tools::create_tools_json_for_responses_api;
|
||||
use crate::openai_tools::ConfigShellToolType;
|
||||
use crate::openai_tools::ToolsConfig;
|
||||
use crate::protocol::RateLimitSnapshotEvent;
|
||||
use crate::protocol::SandboxPolicy;
|
||||
use crate::protocol::TokenUsage;
|
||||
use crate::slash_commands::get_enabled_agents;
|
||||
use crate::util::backoff;
|
||||
use code_otel::otel_event_manager::OtelEventManager;
|
||||
use std::sync::Arc;
|
||||
@@ -179,6 +184,74 @@ impl ModelClient {
|
||||
&self.config.code_home
|
||||
}
|
||||
|
||||
pub fn build_tools_config_with_sandbox(
|
||||
&self,
|
||||
sandbox_policy: SandboxPolicy,
|
||||
) -> ToolsConfig {
|
||||
let mut tools_config = ToolsConfig::new(
|
||||
&self.config.model_family,
|
||||
self.config.approval_policy,
|
||||
sandbox_policy.clone(),
|
||||
self.config.include_plan_tool,
|
||||
self.config.include_apply_patch_tool,
|
||||
self.config.tools_web_search_request,
|
||||
self.config.use_experimental_streamable_shell_tool,
|
||||
self.config.include_view_image_tool,
|
||||
);
|
||||
tools_config.web_search_allowed_domains = self.config.tools_web_search_allowed_domains.clone();
|
||||
|
||||
let mut agent_models: Vec<String> = if self.config.agents.is_empty() {
|
||||
default_agent_configs()
|
||||
.into_iter()
|
||||
.filter(|cfg| cfg.enabled)
|
||||
.map(|cfg| cfg.name)
|
||||
.collect()
|
||||
} else {
|
||||
get_enabled_agents(&self.config.agents)
|
||||
};
|
||||
if agent_models.is_empty() {
|
||||
agent_models = enabled_agent_model_specs()
|
||||
.into_iter()
|
||||
.map(|spec| spec.slug.to_string())
|
||||
.collect();
|
||||
}
|
||||
agent_models.sort_by(|a, b| a.to_ascii_lowercase().cmp(&b.to_ascii_lowercase()));
|
||||
agent_models.dedup_by(|a, b| a.eq_ignore_ascii_case(b));
|
||||
tools_config.set_agent_models(agent_models);
|
||||
|
||||
let base_shell_type = tools_config.shell_type.clone();
|
||||
let base_uses_native_shell = matches!(
|
||||
&base_shell_type,
|
||||
ConfigShellToolType::LocalShell | ConfigShellToolType::StreamableShell
|
||||
);
|
||||
|
||||
tools_config.shell_type = match sandbox_policy.clone() {
|
||||
SandboxPolicy::ReadOnly => {
|
||||
if base_uses_native_shell {
|
||||
base_shell_type.clone()
|
||||
} else {
|
||||
ConfigShellToolType::ShellWithRequest {
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
}
|
||||
}
|
||||
}
|
||||
sp @ SandboxPolicy::WorkspaceWrite { .. } => {
|
||||
if base_uses_native_shell {
|
||||
base_shell_type.clone()
|
||||
} else {
|
||||
ConfigShellToolType::ShellWithRequest { sandbox_policy: sp }
|
||||
}
|
||||
}
|
||||
SandboxPolicy::DangerFullAccess => base_shell_type,
|
||||
};
|
||||
|
||||
tools_config
|
||||
}
|
||||
|
||||
pub fn build_tools_config(&self) -> ToolsConfig {
|
||||
self.build_tools_config_with_sandbox(self.config.sandbox_policy.clone())
|
||||
}
|
||||
|
||||
pub fn get_auto_compact_token_limit(&self) -> Option<i64> {
|
||||
self.config.model_auto_compact_token_limit.or_else(|| {
|
||||
get_model_info(&self.config.model_family).and_then(|info| info.auto_compact_token_limit)
|
||||
|
||||
@@ -5,12 +5,11 @@ use std::time::{Duration, Instant, SystemTime};
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use code_browser::global as browser_global;
|
||||
use code_core::agent_defaults::{default_agent_configs, enabled_agent_model_specs};
|
||||
use code_core::config::Config;
|
||||
use code_core::config_types::{AutoDriveSettings, ReasoningEffort};
|
||||
use code_core::debug_logger::DebugLogger;
|
||||
use code_core::model_family::{derive_default_model_family, find_family_for_model};
|
||||
use code_core::ToolsConfig;
|
||||
use code_core::get_openai_tools;
|
||||
use code_core::project_doc::read_auto_drive_docs;
|
||||
use code_core::protocol::SandboxPolicy;
|
||||
use code_core::slash_commands::get_enabled_agents;
|
||||
@@ -46,7 +45,6 @@ use rand::Rng;
|
||||
use super::auto_observer::{
|
||||
build_observer_conversation,
|
||||
start_auto_observer,
|
||||
observer_tools_for_mode,
|
||||
AutoObserverCommand,
|
||||
AutoObserverHandle,
|
||||
ObserverOutcome,
|
||||
@@ -701,40 +699,29 @@ fn run_auto_loop(
|
||||
.build()
|
||||
.context("creating runtime for auto coordinator")?;
|
||||
|
||||
let _browser_enabled = runtime.block_on(async {
|
||||
let browser_enabled = runtime.block_on(async {
|
||||
browser_global::get_browser_manager().await.is_some()
|
||||
});
|
||||
|
||||
let mut tools_config = ToolsConfig::new(
|
||||
&config.model_family,
|
||||
config.approval_policy,
|
||||
config.sandbox_policy.clone(),
|
||||
config.include_plan_tool,
|
||||
config.include_apply_patch_tool,
|
||||
config.tools_web_search_request,
|
||||
config.use_experimental_streamable_shell_tool,
|
||||
config.include_view_image_tool,
|
||||
let read_only_tools_config = client
|
||||
.as_ref()
|
||||
.build_tools_config_with_sandbox(SandboxPolicy::ReadOnly);
|
||||
let read_only_tools = get_openai_tools(
|
||||
&read_only_tools_config,
|
||||
None,
|
||||
browser_enabled,
|
||||
false,
|
||||
);
|
||||
tools_config.web_search_allowed_domains = config.tools_web_search_allowed_domains.clone();
|
||||
|
||||
let mut agent_models: Vec<String> = if config.agents.is_empty() {
|
||||
default_agent_configs()
|
||||
.into_iter()
|
||||
.filter(|cfg| cfg.enabled)
|
||||
.map(|cfg| cfg.name)
|
||||
.collect()
|
||||
} else {
|
||||
get_enabled_agents(&config.agents)
|
||||
};
|
||||
if agent_models.is_empty() {
|
||||
agent_models = enabled_agent_model_specs()
|
||||
.into_iter()
|
||||
.map(|spec| spec.slug.to_string())
|
||||
.collect();
|
||||
}
|
||||
agent_models.sort_by(|a, b| a.to_ascii_lowercase().cmp(&b.to_ascii_lowercase()));
|
||||
agent_models.dedup_by(|a, b| a.eq_ignore_ascii_case(b));
|
||||
tools_config.set_agent_models(agent_models);
|
||||
let full_access_tools_config = client
|
||||
.as_ref()
|
||||
.build_tools_config_with_sandbox(SandboxPolicy::DangerFullAccess);
|
||||
let full_access_tools = get_openai_tools(
|
||||
&full_access_tools_config,
|
||||
None,
|
||||
browser_enabled,
|
||||
false,
|
||||
);
|
||||
|
||||
let auto_instructions = match runtime.block_on(read_auto_drive_docs(config.as_ref())) {
|
||||
Ok(Some(text)) => {
|
||||
@@ -801,6 +788,7 @@ fn run_auto_loop(
|
||||
.send(AutoObserverCommand::Bootstrap {
|
||||
goal_text: goal_text.clone(),
|
||||
environment_details: environment_details.clone(),
|
||||
tools: read_only_tools.clone(),
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
@@ -903,7 +891,7 @@ fn run_auto_loop(
|
||||
environment_details: environment_details.clone(),
|
||||
reason: ObserverReason::Cadence,
|
||||
turn_snapshot: None,
|
||||
tools: observer_tools_for_mode(ObserverMode::Cadence),
|
||||
tools: read_only_tools.clone(),
|
||||
};
|
||||
if handle.tx.send(AutoObserverCommand::Trigger(trigger)).is_err() {
|
||||
warn!("failed to trigger auto observer");
|
||||
@@ -953,6 +941,7 @@ fn run_auto_loop(
|
||||
forced: true,
|
||||
summary: latest_summary.clone(),
|
||||
focus: None,
|
||||
tools: full_access_tools.clone(),
|
||||
}) {
|
||||
Ok(()) => {
|
||||
awaiting_cross_check = true;
|
||||
|
||||
@@ -56,6 +56,7 @@ pub(super) enum AutoObserverCommand {
|
||||
Bootstrap {
|
||||
goal_text: String,
|
||||
environment_details: String,
|
||||
tools: Vec<OpenAiTool>,
|
||||
},
|
||||
Trigger(ObserverTrigger),
|
||||
BeginCrossCheck {
|
||||
@@ -64,42 +65,11 @@ pub(super) enum AutoObserverCommand {
|
||||
forced: bool,
|
||||
summary: Option<String>,
|
||||
focus: Option<String>,
|
||||
tools: Vec<OpenAiTool>,
|
||||
},
|
||||
Stop,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum ObserverToolPolicy {
|
||||
ReadOnly,
|
||||
Limited,
|
||||
FullAudit,
|
||||
}
|
||||
|
||||
impl ObserverToolPolicy {
|
||||
fn for_mode(mode: ObserverMode) -> Self {
|
||||
match mode {
|
||||
ObserverMode::Bootstrap => ObserverToolPolicy::ReadOnly,
|
||||
ObserverMode::Cadence => ObserverToolPolicy::Limited,
|
||||
ObserverMode::CrossCheck => ObserverToolPolicy::FullAudit,
|
||||
}
|
||||
}
|
||||
|
||||
fn tools(self) -> Vec<OpenAiTool> {
|
||||
match self {
|
||||
ObserverToolPolicy::ReadOnly => vec![OpenAiTool::WebSearch(Default::default())],
|
||||
ObserverToolPolicy::Limited => vec![OpenAiTool::WebSearch(Default::default())],
|
||||
ObserverToolPolicy::FullAudit => vec![
|
||||
OpenAiTool::LocalShell {},
|
||||
OpenAiTool::WebSearch(Default::default()),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn observer_tools_for_mode(mode: ObserverMode) -> Vec<OpenAiTool> {
|
||||
ObserverToolPolicy::for_mode(mode).tools()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) enum ObserverReason {
|
||||
Cadence,
|
||||
@@ -180,7 +150,11 @@ pub(super) fn run_observer_once(
|
||||
evaluate_observer(runtime, client, trigger, tx, ObserverMode::Cadence)
|
||||
}
|
||||
|
||||
fn build_bootstrap_trigger(goal_text: &str, environment_details: &str) -> ObserverTrigger {
|
||||
fn build_bootstrap_trigger(
|
||||
goal_text: &str,
|
||||
environment_details: &str,
|
||||
tools: Vec<OpenAiTool>,
|
||||
) -> ObserverTrigger {
|
||||
let prompt = format!(
|
||||
"You are the QA observer. Before automation begins, inspect the repository and outline how to validate completion of the primary goal.\n\nPrimary goal:\n{goal}\n\nEnvironment:\n{environment}\n\nReturn a concise readiness summary and any immediate risks to monitor.",
|
||||
goal = goal_text.trim(),
|
||||
@@ -196,7 +170,7 @@ fn build_bootstrap_trigger(goal_text: &str, environment_details: &str) -> Observ
|
||||
environment_details: environment_details.to_string(),
|
||||
reason: ObserverReason::Cadence,
|
||||
turn_snapshot: None,
|
||||
tools: observer_tools_for_mode(ObserverMode::Bootstrap),
|
||||
tools,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,8 +195,9 @@ fn run_observer_loop(
|
||||
AutoObserverCommand::Bootstrap {
|
||||
goal_text,
|
||||
environment_details,
|
||||
tools,
|
||||
} => {
|
||||
let trigger = build_bootstrap_trigger(&goal_text, &environment_details);
|
||||
let trigger = build_bootstrap_trigger(&goal_text, &environment_details, tools);
|
||||
telemetry.trigger_count += 1;
|
||||
match evaluate_observer(
|
||||
&runtime,
|
||||
@@ -370,6 +345,7 @@ fn run_observer_loop(
|
||||
forced,
|
||||
summary,
|
||||
focus,
|
||||
tools,
|
||||
} => {
|
||||
let summary_clone = summary.clone();
|
||||
let focus_clone = focus.clone();
|
||||
@@ -383,7 +359,7 @@ fn run_observer_loop(
|
||||
focus,
|
||||
},
|
||||
turn_snapshot: None,
|
||||
tools: observer_tools_for_mode(ObserverMode::CrossCheck),
|
||||
tools,
|
||||
};
|
||||
telemetry.trigger_count += 1;
|
||||
match evaluate_observer(
|
||||
|
||||
Reference in New Issue
Block a user