feat: Add lead-worker model selection and real-time model display in GUI (#2964)
Co-authored-by: jack <jack@deck.local>
This commit is contained in:
@@ -589,6 +589,10 @@ async fn process_message_streaming(
|
||||
// For now, we'll just log them
|
||||
tracing::info!("Received MCP notification in web interface");
|
||||
}
|
||||
Ok(AgentEvent::ModelChange { model, mode }) => {
|
||||
// Log model change
|
||||
tracing::info!("Model changed to {} in {} mode", model, mode);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error in message stream: {}", e);
|
||||
let mut sender = sender.lock().await;
|
||||
|
||||
@@ -928,6 +928,12 @@ impl Session {
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(Ok(AgentEvent::ModelChange { model, mode })) => {
|
||||
// Log model change if in debug mode
|
||||
if self.debug {
|
||||
eprintln!("Model changed to {} in {} mode", model, mode);
|
||||
}
|
||||
}
|
||||
Some(Err(e)) => {
|
||||
eprintln!("Error: {}", e);
|
||||
drop(stream);
|
||||
|
||||
@@ -266,6 +266,9 @@ pub unsafe extern "C" fn goose_agent_send_message(
|
||||
Ok(AgentEvent::McpNotification(_)) => {
|
||||
// TODO: Handle MCP notifications.
|
||||
}
|
||||
Ok(AgentEvent::ModelChange { .. }) => {
|
||||
// Model change events are informational, just continue
|
||||
}
|
||||
Err(e) => {
|
||||
full_response.push_str(&format!("\nError in message stream: {}", e));
|
||||
}
|
||||
|
||||
@@ -165,6 +165,9 @@ async fn execute_recipe(job_id: &str, recipe_path: &str) -> Result<String> {
|
||||
Ok(AgentEvent::McpNotification(_)) => {
|
||||
// Handle notifications if needed
|
||||
}
|
||||
Ok(AgentEvent::ModelChange { .. }) => {
|
||||
// Model change events are informational, just continue
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(anyhow!("Error receiving message from agent: {}", e));
|
||||
}
|
||||
|
||||
@@ -441,6 +441,26 @@ pub async fn backup_config(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/config/current-model",
|
||||
responses(
|
||||
(status = 200, description = "Current model retrieved successfully", body = String),
|
||||
)
|
||||
)]
|
||||
pub async fn get_current_model(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
) -> Result<Json<Value>, StatusCode> {
|
||||
verify_secret_key(&headers, &state)?;
|
||||
|
||||
let current_model = goose::providers::base::get_current_model();
|
||||
|
||||
Ok(Json(serde_json::json!({
|
||||
"model": current_model
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn routes(state: Arc<AppState>) -> Router {
|
||||
Router::new()
|
||||
.route("/config", get(read_all_config))
|
||||
@@ -454,6 +474,7 @@ pub fn routes(state: Arc<AppState>) -> Router {
|
||||
.route("/config/init", post(init_config))
|
||||
.route("/config/backup", post(backup_config))
|
||||
.route("/config/permissions", post(upsert_permissions))
|
||||
.route("/config/current-model", get(get_current_model))
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,10 @@ enum MessageEvent {
|
||||
Finish {
|
||||
reason: String,
|
||||
},
|
||||
ModelChange {
|
||||
model: String,
|
||||
mode: String,
|
||||
},
|
||||
Notification {
|
||||
request_id: String,
|
||||
message: JsonRpcMessage,
|
||||
@@ -233,6 +237,17 @@ async fn handler(
|
||||
}
|
||||
});
|
||||
}
|
||||
Ok(Some(Ok(AgentEvent::ModelChange { model, mode }))) => {
|
||||
if let Err(e) = stream_event(MessageEvent::ModelChange { model, mode }, &tx).await {
|
||||
tracing::error!("Error sending model change through channel: {}", e);
|
||||
let _ = stream_event(
|
||||
MessageEvent::Error {
|
||||
error: e.to_string(),
|
||||
},
|
||||
&tx,
|
||||
).await;
|
||||
}
|
||||
}
|
||||
Ok(Some(Ok(AgentEvent::McpNotification((request_id, n))))) => {
|
||||
if let Err(e) = stream_event(MessageEvent::Notification{
|
||||
request_id: request_id.clone(),
|
||||
@@ -352,6 +367,10 @@ async fn ask_handler(
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(AgentEvent::ModelChange { model, mode }) => {
|
||||
// Log model change for non-streaming
|
||||
tracing::info!("Model changed to {} in {} mode", model, mode);
|
||||
}
|
||||
Ok(AgentEvent::McpNotification(n)) => {
|
||||
// Handle notifications if needed
|
||||
tracing::info!("Received notification: {:?}", n);
|
||||
|
||||
@@ -65,6 +65,7 @@ pub struct Agent {
|
||||
pub enum AgentEvent {
|
||||
Message(Message),
|
||||
McpNotification((String, JsonRpcMessage)),
|
||||
ModelChange { model: String, mode: String },
|
||||
}
|
||||
|
||||
impl Agent {
|
||||
@@ -582,6 +583,26 @@ impl Agent {
|
||||
&toolshim_tools,
|
||||
).await {
|
||||
Ok((response, usage)) => {
|
||||
// Emit model change event if provider is lead-worker
|
||||
let provider = self.provider().await?;
|
||||
if let Some(lead_worker) = provider.as_lead_worker() {
|
||||
// The actual model used is in the usage
|
||||
let active_model = usage.model.clone();
|
||||
let (lead_model, worker_model) = lead_worker.get_model_info();
|
||||
let mode = if active_model == lead_model {
|
||||
"lead"
|
||||
} else if active_model == worker_model {
|
||||
"worker"
|
||||
} else {
|
||||
"unknown"
|
||||
};
|
||||
|
||||
yield AgentEvent::ModelChange {
|
||||
model: active_model,
|
||||
mode: mode.to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
// record usage for the session in the session file
|
||||
if let Some(session_config) = session.clone() {
|
||||
Self::update_session_metrics(session_config, &usage, messages.len()).await?;
|
||||
|
||||
@@ -152,6 +152,9 @@ use async_trait::async_trait;
|
||||
pub trait LeadWorkerProviderTrait {
|
||||
/// Get information about the lead and worker models for logging
|
||||
fn get_model_info(&self) -> (String, String);
|
||||
|
||||
/// Get the currently active model name
|
||||
fn get_active_model(&self) -> String;
|
||||
}
|
||||
|
||||
/// Base trait for AI providers (OpenAI, Anthropic, etc)
|
||||
@@ -207,6 +210,17 @@ pub trait Provider: Send + Sync {
|
||||
fn as_lead_worker(&self) -> Option<&dyn LeadWorkerProviderTrait> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Get the currently active model name
|
||||
/// For regular providers, this returns the configured model
|
||||
/// For LeadWorkerProvider, this returns the currently active model (lead or worker)
|
||||
fn get_active_model_name(&self) -> String {
|
||||
if let Some(lead_worker) = self.as_lead_worker() {
|
||||
lead_worker.get_active_model()
|
||||
} else {
|
||||
self.get_model_config().model_name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -291,6 +291,16 @@ impl LeadWorkerProviderTrait for LeadWorkerProvider {
|
||||
let worker_model = self.worker_provider.get_model_config().model_name;
|
||||
(lead_model, worker_model)
|
||||
}
|
||||
|
||||
/// Get the currently active model name
|
||||
fn get_active_model(&self) -> String {
|
||||
// Read from the global store which was set during complete()
|
||||
use super::base::get_current_model;
|
||||
get_current_model().unwrap_or_else(|| {
|
||||
// Fallback to lead model if no current model is set
|
||||
self.lead_provider.get_model_config().model_name
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -336,19 +346,31 @@ impl Provider for LeadWorkerProvider {
|
||||
"worker"
|
||||
};
|
||||
|
||||
// Get the active model name and update the global store
|
||||
let active_model_name = if turn_count < self.lead_turns || in_fallback {
|
||||
self.lead_provider.get_model_config().model_name.clone()
|
||||
} else {
|
||||
self.worker_provider.get_model_config().model_name.clone()
|
||||
};
|
||||
|
||||
// Update the global current model store
|
||||
super::base::set_current_model(&active_model_name);
|
||||
|
||||
if in_fallback {
|
||||
tracing::info!(
|
||||
"🔄 Using {} provider for turn {} (FALLBACK MODE: {} turns remaining)",
|
||||
"🔄 Using {} provider for turn {} (FALLBACK MODE: {} turns remaining) - Model: {}",
|
||||
provider_type,
|
||||
turn_count + 1,
|
||||
fallback_remaining
|
||||
fallback_remaining,
|
||||
active_model_name
|
||||
);
|
||||
} else {
|
||||
tracing::info!(
|
||||
"Using {} provider for turn {} (lead_turns: {})",
|
||||
"Using {} provider for turn {} (lead_turns: {}) - Model: {}",
|
||||
provider_type,
|
||||
turn_count + 1,
|
||||
self.lead_turns
|
||||
self.lead_turns,
|
||||
active_model_name
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1114,6 +1114,9 @@ async fn run_scheduled_job_internal(
|
||||
Ok(AgentEvent::McpNotification(_)) => {
|
||||
// Handle notifications if needed
|
||||
}
|
||||
Ok(AgentEvent::ModelChange { .. }) => {
|
||||
// Model change events are informational, just continue
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"[Job {}] Error receiving message from agent: {}",
|
||||
|
||||
@@ -136,6 +136,9 @@ async fn run_truncate_test(
|
||||
Ok(AgentEvent::McpNotification(n)) => {
|
||||
println!("MCP Notification: {n:?}");
|
||||
}
|
||||
Ok(AgentEvent::ModelChange { .. }) => {
|
||||
// Model change events are informational, just continue
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error: {:?}", e);
|
||||
return Err(e);
|
||||
|
||||
Reference in New Issue
Block a user