Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
422 changes: 376 additions & 46 deletions app/src/ai/blocklist/inline_action/orchestration_controls.rs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,19 @@ use ai::agent::action::RunAgentsExecutionMode;
use ai::agent::orchestration_config::{OrchestrationConfig, OrchestrationExecutionMode};

use super::{
should_show_auth_secret_picker, should_show_harness_picker, AuthSecretSelection,
choose_harness_variant_for_group, grouped_harness_models, should_show_auth_secret_picker,
should_show_harness_picker, strip_trailing_effort_label, AuthSecretSelection,
OrchestrationEditState,
};
use crate::ai::harness_availability::HarnessModelInfo;

fn harness_model(id: &str, display_name: &str, reasoning_level: Option<&str>) -> HarnessModelInfo {
HarnessModelInfo {
id: id.to_string(),
display_name: display_name.to_string(),
reasoning_level: reasoning_level.map(str::to_string),
}
}

fn remote_claude_state() -> OrchestrationEditState {
OrchestrationEditState::from_run_agents_fields(
Expand All @@ -18,6 +28,34 @@ fn remote_claude_state() -> OrchestrationEditState {
)
}

#[test]
fn effort_suffix_is_stripped_from_harness_model_name() {
assert_eq!(
strip_trailing_effort_label("Claude Sonnet 4.5 (high)", "high"),
"Claude Sonnet 4.5"
);
assert_eq!(
strip_trailing_effort_label("GPT 5.1 - Medium", "medium"),
"GPT 5.1"
);
}

#[test]
fn harness_models_group_by_base_name_and_preserve_effort() {
let models = vec![
harness_model("sonnet-low", "Claude Sonnet 4.5 (low)", Some("low")),
harness_model("sonnet-high", "Claude Sonnet 4.5 (high)", Some("high")),
harness_model("opus-high", "Claude Opus 4.5 (high)", Some("high")),
];
let groups = grouped_harness_models(&models);
assert_eq!(groups.len(), 2);
assert_eq!(groups[0].0, "Claude Sonnet 4.5");
assert_eq!(groups[0].1.len(), 2);

let selected = choose_harness_variant_for_group(&groups[0].1, Some("high"));
assert_eq!(selected.id, "sonnet-high");
}

fn local_config(harness_type: &str, model_id: &str) -> OrchestrationConfig {
OrchestrationConfig {
model_id: model_id.to_string(),
Expand Down
49 changes: 49 additions & 0 deletions app/src/ai/blocklist/inline_action/run_agents_card_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ impl OrchestrationControlAction for RunAgentsCardViewAction {
fn model_changed(model_id: String) -> Self {
Self::ModelChanged { model_id }
}
fn effort_changed(model_id: String) -> Self {
Self::EffortChanged { model_id }
}
fn harness_changed(harness_type: String) -> Self {
Self::HarnessChanged { harness_type }
}
Expand Down Expand Up @@ -192,6 +195,9 @@ pub enum RunAgentsCardViewAction {
ModelChanged {
model_id: String,
},
EffortChanged {
model_id: String,
},
HarnessChanged {
harness_type: String,
},
Expand Down Expand Up @@ -411,6 +417,16 @@ impl RunAgentsCardView {
ctx,
);
}
if let Some(handle) = &me.handles.pickers.effort_picker {
let is_local = !me.state.orch.execution_mode.is_remote();
oc::populate_effort_picker_for_harness(
handle,
&me.state.orch.model_id,
&me.state.orch.harness_type,
is_local,
ctx,
);
}
}
});

Expand Down Expand Up @@ -746,6 +762,25 @@ impl RunAgentsCardView {
Self::subscribe_filterable_picker_close(&handle, ctx);
self.handles.pickers.model_picker = Some(handle);
}
if self.handles.pickers.effort_picker.is_none() {
let initial_model_id = if state.orch.model_id.trim().is_empty() {
initial_model_id_default.clone()
} else {
state.orch.model_id.clone()
};
let is_local = !state.orch.execution_mode.is_remote();
let handle = oc::new_standard_picker_dropdown(&colors, ctx);
Self::set_upward_menu_position(&handle, ctx);
oc::populate_effort_picker_for_harness(
&handle,
&initial_model_id,
&state.orch.harness_type,
is_local,
ctx,
);
Self::subscribe_picker_close(&handle, ctx);
self.handles.pickers.effort_picker = Some(handle);
}

if self.handles.pickers.harness_picker.is_none() {
let handle = oc::new_standard_picker_dropdown(&colors, ctx);
Expand Down Expand Up @@ -1077,6 +1112,20 @@ impl TypedActionView for RunAgentsCardView {
ctx.notify();
}
RunAgentsCardViewAction::ModelChanged { model_id } => {
self.state.orch.model_id = model_id.clone();
if let Some(handle) = &self.handles.pickers.effort_picker {
oc::populate_effort_picker_for_harness(
handle,
&self.state.orch.model_id,
&self.state.orch.harness_type,
!self.state.orch.execution_mode.is_remote(),
ctx,
);
}
self.refresh_accept_button_state(ctx);
ctx.notify();
}
RunAgentsCardViewAction::EffortChanged { model_id } => {
self.state.orch.model_id = model_id.clone();
self.refresh_accept_button_state(ctx);
ctx.notify();
Expand Down
40 changes: 40 additions & 0 deletions app/src/ai/document/orchestration_config_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ pub enum OrchestrationConfigBlockAction {
ModelChanged {
model_id: String,
},
EffortChanged {
model_id: String,
},
HarnessChanged {
harness_type: String,
},
Expand All @@ -146,6 +149,9 @@ impl OrchestrationControlAction for OrchestrationConfigBlockAction {
fn model_changed(model_id: String) -> Self {
Self::ModelChanged { model_id }
}
fn effort_changed(model_id: String) -> Self {
Self::EffortChanged { model_id }
}
fn harness_changed(harness_type: String) -> Self {
Self::HarnessChanged { harness_type }
}
Expand Down Expand Up @@ -257,6 +263,16 @@ impl OrchestrationConfigBlockView {
ctx,
);
}
if let Some(handle) = &me.pickers.effort_picker {
let is_local = !me.edit_state.execution_mode.is_remote();
oc::populate_effort_picker_for_harness(
handle,
&me.edit_state.model_id,
&me.edit_state.harness_type,
is_local,
ctx,
);
}
}
});

Expand Down Expand Up @@ -453,6 +469,16 @@ impl OrchestrationConfigBlockView {
);
self.pickers.model_picker = Some(model_handle);

let effort_handle = oc::new_standard_picker_dropdown(&colors, ctx);
effort_handle.update(ctx, |d, c| d.set_use_overlay_layer(true, c));
oc::populate_effort_picker_for_harness(
&effort_handle,
&display_model_id,
&self.edit_state.harness_type,
is_local,
ctx,
);
self.pickers.effort_picker = Some(effort_handle);
let harness_handle = oc::new_standard_picker_dropdown(&colors, ctx);
harness_handle.update(ctx, |d, c| d.set_use_overlay_layer(true, c));
oc::populate_harness_picker(
Expand Down Expand Up @@ -826,6 +852,20 @@ impl TypedActionView for OrchestrationConfigBlockView {
ctx.notify();
}
OrchestrationConfigBlockAction::ModelChanged { model_id } => {
self.edit_state.model_id = model_id.clone();
if let Some(handle) = &self.pickers.effort_picker {
oc::populate_effort_picker_for_harness(
handle,
&self.edit_state.model_id,
&self.edit_state.harness_type,
!self.edit_state.execution_mode.is_remote(),
ctx,
);
}
self.apply_field_change(ctx);
ctx.notify();
}
OrchestrationConfigBlockAction::EffortChanged { model_id } => {
self.edit_state.model_id = model_id.clone();
self.apply_field_change(ctx);
ctx.notify();
Expand Down
27 changes: 27 additions & 0 deletions app/src/ai/onboarding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,42 @@ use onboarding::OnboardingAuthState;
use warp_core::ui::icons::Icon;
use warpui::{AppContext, SingletonEntity};

use super::execution_profiles::model_menu_items::is_auto;
use super::llms::{DisableReason, LLMInfo, LLMPreferences};
use crate::auth::AuthStateProvider;
use crate::workspaces::user_workspaces::UserWorkspaces;

const DEFAULT_EFFORT_LABEL: &str = "Default";

fn onboarding_base_title(llm: &LLMInfo) -> String {
if is_auto(llm) {
"auto".to_string()
} else if llm.has_reasoning_level() {
llm.base_model_name().to_string()
} else {
llm.menu_display_name()
}
}

fn onboarding_effort_title(llm: &LLMInfo) -> String {
if is_auto(llm) && llm.display_name.starts_with("auto (") {
llm.display_name
.trim_start_matches("auto (")
.trim_end_matches(')')
.to_string()
} else {
llm.reasoning_level()
.unwrap_or_else(|| DEFAULT_EFFORT_LABEL.to_string())
}
}

impl From<&LLMInfo> for OnboardingModelInfo {
fn from(llm: &LLMInfo) -> Self {
Self {
id: llm.id.clone(),
title: llm.display_name.clone(),
base_title: onboarding_base_title(llm),
effort_title: onboarding_effort_title(llm),
icon: llm.provider.icon().unwrap_or(Icon::Oz),
requires_upgrade: matches!(llm.disable_reason, Some(DisableReason::RequiresUpgrade)),
is_default: false,
Expand Down
6 changes: 6 additions & 0 deletions crates/onboarding/src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,20 +89,26 @@ impl OnboardingMainView {
OnboardingModelInfo {
id: LLMId::from("auto"),
title: "Auto".to_string(),
base_title: "Auto".to_string(),
effort_title: "Default".to_string(),
icon: Icon::Oz,
requires_upgrade: false,
is_default: true,
},
OnboardingModelInfo {
id: LLMId::from("claude-sonnet"),
title: "Claude Sonnet".to_string(),
base_title: "Claude Sonnet".to_string(),
effort_title: "Default".to_string(),
icon: Icon::ClaudeLogo,
requires_upgrade: false,
is_default: false,
},
OnboardingModelInfo {
id: LLMId::from("gpt-4o"),
title: "GPT-4o".to_string(),
base_title: "GPT-4o".to_string(),
effort_title: "Default".to_string(),
icon: Icon::OpenAILogo,
requires_upgrade: true,
is_default: false,
Expand Down
Loading