first pass of merging in warp (doesn't build)

This commit is contained in:
Ryan Ward
2026-07-01 16:08:58 -05:00
parent 2f64909469
commit 4770ac06b5
3662 changed files with 414574 additions and 89772 deletions
+373 -339
View File
@@ -6,29 +6,32 @@
use std::collections::HashMap;
use std::path::PathBuf;
use chrono::{DateTime, Utc};
pub use cloud_object_models::{
AgentModeCommandExecutionPredicate, DEFAULT_COMMAND_EXECUTION_ALLOWLIST,
DEFAULT_COMMAND_EXECUTION_DENYLIST,
};
use indexmap::IndexMap;
use regex::Regex;
use serde::de::Deserializer;
use serde::{Deserialize, Serialize};
use settings::{
define_settings_group, RespectUserSyncSetting, Setting, SupportedPlatforms, SyncToCloud,
};
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
use galaxy_core::execution_mode::AppExecutionMode;
use galaxy_core::features::FeatureFlag;
use warpui::platform::keyboard::KeyCode;
use warpui::platform::OperatingSystem;
use warpui::{AppContext, Entity, ModelContext, SingletonEntity, UpdateModel};
use crate::ai::request_usage_model::RequestLimitInfo;
use crate::auth::AuthStateProvider;
use crate::report_if_error;
use crate::settings::PrivacySettings;
use crate::terminal::CLIAgent;
use crate::workspaces::user_workspaces::UserWorkspaces;
use cfg_if::cfg_if;
use chrono::{DateTime, Utc};
use galaxyui::platform::OperatingSystem;
use galaxyui::{
platform::keyboard::KeyCode, AppContext, Entity, ModelContext, SingletonEntity, UpdateModel,
};
use lazy_static::lazy_static;
use regex::Regex;
use galaxy_core::execution_mode::AppExecutionMode;
use galaxy_core::features::FeatureFlag;
use settings::{define_settings_group, Setting, SupportedPlatforms, SyncToCloud};
use serde::{de::Deserializer, Deserialize, Serialize};
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
pub enum FocusedTerminalInfoEvent {
TerminalInfoUpdated,
@@ -392,7 +395,7 @@ impl ThinkingDisplayMode {
}
}
/// Authentication method for AWS Bedrock.
/// Controls how child-agent message bodies are displayed.
#[derive(
Default,
Debug,
@@ -406,107 +409,193 @@ impl ThinkingDisplayMode {
settings_value::SettingsValue,
)]
#[schemars(
description = "Authentication method for AWS Bedrock.",
description = "Controls how child-agent messages are displayed.",
rename_all = "snake_case"
)]
pub enum BedrockAuthMethod {
pub enum OrchestrationMessageDisplayMode {
/// Show child-agent messages while streaming, then collapse them.
ShowAndCollapse,
/// Keep child-agent message bodies expanded.
AlwaysShow,
/// Keep child-agent message bodies collapsed.
#[default]
Profile,
StaticKeys,
Sso,
AlwaysCollapse,
}
settings::macros::implement_setting_for_enum!(
BedrockAuthMethod,
OrchestrationMessageDisplayMode,
AISettings,
SupportedPlatforms::DESKTOP,
SyncToCloud::Never,
SupportedPlatforms::ALL,
SyncToCloud::Globally(RespectUserSyncSetting::Yes),
private: false,
toml_path: "ai.bedrock.auth_method",
description: "Authentication method for AWS Bedrock.",
toml_path: "agents.warp_agent.other.orchestration_message_display_mode",
description: "Controls how child-agent messages are displayed.",
);
impl BedrockAuthMethod {
impl OrchestrationMessageDisplayMode {
/// Display name for the settings dropdown.
pub fn display_name(&self) -> &'static str {
match self {
BedrockAuthMethod::Profile => "AWS Profile",
BedrockAuthMethod::StaticKeys => "Static Keys",
BedrockAuthMethod::Sso => "SSO",
OrchestrationMessageDisplayMode::ShowAndCollapse => "Show & collapse",
OrchestrationMessageDisplayMode::AlwaysShow => "Always show",
OrchestrationMessageDisplayMode::AlwaysCollapse => "Always collapse",
}
}
pub fn command_palette_description(&self) -> &'static str {
match self {
OrchestrationMessageDisplayMode::ShowAndCollapse => {
"Set child-agent message display: show & collapse"
}
OrchestrationMessageDisplayMode::AlwaysShow => {
"Set child-agent message display: always show"
}
OrchestrationMessageDisplayMode::AlwaysCollapse => {
"Set child-agent message display: always collapse"
}
}
}
/// Whether child-agent message bodies should expand while streaming.
pub fn should_expand_agent_message_body(&self) -> bool {
matches!(
self,
OrchestrationMessageDisplayMode::ShowAndCollapse
| OrchestrationMessageDisplayMode::AlwaysShow
)
}
/// Whether child-agent message bodies should collapse after streaming.
pub fn should_collapse_agent_message_body_on_finish(&self) -> bool {
matches!(self, OrchestrationMessageDisplayMode::ShowAndCollapse)
}
}
/// Controls what happens when a user submits a new prompt while the agent is
/// still responding to an earlier prompt.
///
/// This is the *default* used when a conversation has no explicit auto-queue
/// override. Per-conversation overrides live on `QueuedQueryModel` and take
/// precedence over this setting.
#[derive(
Default,
Debug,
serde::Serialize,
serde::Deserialize,
PartialEq,
Copy,
Clone,
EnumIter,
schemars::JsonSchema,
settings_value::SettingsValue,
)]
#[schemars(
description = "Default behavior when submitting a new prompt while the agent is still responding.",
rename_all = "snake_case"
)]
pub enum PromptSubmissionMode {
/// Cancel the in-flight response and submit the new prompt immediately
/// (default).
#[default]
Interrupt,
/// Hold the new prompt until the in-flight response finishes, then submit.
Queue,
}
settings::macros::implement_setting_for_enum!(
PromptSubmissionMode,
AISettings,
SupportedPlatforms::ALL,
SyncToCloud::Globally(RespectUserSyncSetting::Yes),
private: false,
toml_path: "agents.warp_agent.other.default_prompt_submission_mode",
description: "Default behavior when submitting a new prompt while the agent is still responding.",
feature_flag: FeatureFlag::QueueSlashCommand,
);
impl PromptSubmissionMode {
/// Display name for the settings dropdown.
pub fn display_name(&self) -> &'static str {
match self {
PromptSubmissionMode::Interrupt => "Interrupt response",
PromptSubmissionMode::Queue => "Queue until response finishes",
}
}
pub fn command_palette_description(&self) -> &'static str {
match self {
PromptSubmissionMode::Interrupt => "Set default prompt submission: interrupt response",
PromptSubmissionMode::Queue => {
"Set default prompt submission: queue until response finishes"
}
}
}
}
/// Configuration for a single Bedrock model.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, schemars::JsonSchema)]
#[schemars(description = "Configuration for a single AWS Bedrock model.")]
pub struct BedrockModelConfig {
#[schemars(
description = "The Bedrock model ID (e.g. anthropic.claude-sonnet-4-20250514-v1:0)."
)]
pub model_id: String,
#[schemars(description = "Display name shown in the model picker.")]
pub display_name: String,
#[serde(default)]
#[schemars(description = "Whether the model supports image/vision input.")]
pub vision_supported: bool,
#[serde(default = "default_context_size")]
#[schemars(description = "Maximum context window size in tokens.")]
pub context_size: u32,
}
fn default_context_size() -> u32 {
200_000
}
impl settings_value::SettingsValue for BedrockModelConfig {}
/// Configuration for a single OpenAI-compatible (LiteLLM) model.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, schemars::JsonSchema)]
#[schemars(description = "Configuration for a single OpenAI-compatible model (e.g. via LiteLLM).")]
pub struct OpenAIModelConfig {
#[schemars(
description = "The model ID to send in the API request (e.g. claude-sonnet-4-20250514)."
)]
pub model_id: String,
#[schemars(description = "Display name shown in the model picker.")]
pub display_name: String,
#[serde(default)]
#[schemars(description = "Whether the model supports image/vision input.")]
pub vision_supported: bool,
#[serde(default = "default_context_size")]
#[schemars(description = "Maximum context window size in tokens.")]
pub context_size: u32,
#[serde(default)]
#[schemars(
description = "Optional provider hint (e.g. anthropic, openai, google) for icon display."
)]
pub provider: Option<String>,
}
impl settings_value::SettingsValue for OpenAIModelConfig {}
/// Configuration for a single OpenAI-compatible provider endpoint.
/// What happens when a prompt is submitted while an agent controls an agent-requested
/// long-running command (LRC).
///
/// Multiple providers can be configured simultaneously (e.g. LiteLLM for cloud models,
/// Ollama for local models, etc.). Each provider has its own endpoint, credentials, and model list.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, schemars::JsonSchema)]
#[schemars(
description = "Configuration for an OpenAI-compatible provider endpoint (e.g. LiteLLM, Ollama, vLLM)."
/// Only consulted when [`PromptSubmissionMode`] is `Interrupt`: in `Queue` mode
/// prompts always queue until the full response finishes, so this setting is
/// hidden and ignored.
#[derive(
Default,
Debug,
serde::Serialize,
serde::Deserialize,
PartialEq,
Copy,
Clone,
EnumIter,
schemars::JsonSchema,
settings_value::SettingsValue,
)]
pub struct OpenAIProviderConfig {
#[schemars(description = "Display name for this provider (shown in model picker).")]
pub name: String,
#[schemars(description = "Base URL for the OpenAI-compatible API endpoint.")]
pub base_url: String,
#[serde(default)]
#[schemars(description = "API key for this endpoint (optional if the proxy handles auth).")]
pub api_key: Option<String>,
#[serde(default)]
#[schemars(description = "Models available from this provider.")]
pub models: Vec<OpenAIModelConfig>,
#[schemars(
description = "What happens when a prompt is submitted while an agent controls an agent-requested long-running command.",
rename_all = "snake_case"
)]
pub enum LongRunningCommandSubmissionMode {
/// Send the prompt to the agent immediately, steering it mid-command.
SendImmediately,
/// Queue the prompt and send it to the agent when the command finishes
/// (default).
#[default]
QueueUntilCommandCompletes,
}
impl settings_value::SettingsValue for OpenAIProviderConfig {}
settings::macros::implement_setting_for_enum!(
LongRunningCommandSubmissionMode,
AISettings,
SupportedPlatforms::ALL,
SyncToCloud::Globally(RespectUserSyncSetting::Yes),
private: false,
toml_path: "agents.warp_agent.other.long_running_command_submission_mode",
description: "What happens when a prompt is submitted while an agent controls an agent-requested long-running command.",
feature_flag: FeatureFlag::QueueSlashCommand,
);
impl LongRunningCommandSubmissionMode {
/// Display name for the settings dropdown.
pub fn display_name(&self) -> &'static str {
match self {
LongRunningCommandSubmissionMode::SendImmediately => "Send immediately",
LongRunningCommandSubmissionMode::QueueUntilCommandCompletes => {
"Queue until command finishes"
}
}
}
pub fn command_palette_description(&self) -> &'static str {
match self {
LongRunningCommandSubmissionMode::SendImmediately => {
"Set long-running command submission: send immediately"
}
LongRunningCommandSubmissionMode::QueueUntilCommandCompletes => {
"Set long-running command submission: queue until command finishes"
}
}
}
}
/// Tracks the state of the quota reset banner
#[derive(
@@ -598,148 +687,6 @@ pub enum AgentModeCodingPermissionsType {
AllowReadingSpecificFiles,
}
/// Predicate types to match commands that can be executed by Agent Mode.
#[derive(Debug, Serialize, Deserialize, Clone)]
enum AgentModeCommandExecutionPredicateType {
/// A regex with start (`^`) and end (`$`) anchors.
///
/// We want regex rules to apply to the entire cmd string so we anchor them
/// (there isn't any efficient way to apply to the entire cmd string at match-time).
#[serde(with = "serde_regex")]
AnchoredRegex(Regex),
}
impl AgentModeCommandExecutionPredicateType {
fn new_regex(regex: &str) -> Result<Self, regex::Error> {
// Redundant anchors aren't a problem so we can unconditionally add them.
let anchored_regex = Regex::new(&format!("^{regex}$"))?;
Ok(Self::AnchoredRegex(anchored_regex))
}
fn matches(&self, cmd: &str) -> bool {
match self {
Self::AnchoredRegex(regex) => regex.is_match(cmd),
}
}
}
impl PartialEq for AgentModeCommandExecutionPredicateType {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::AnchoredRegex(a), Self::AnchoredRegex(b)) => {
// Indexing should be safe since they're guaranteed to have at least
// the anchors around them.
let a_unanchored = &a.as_str()[1..a.as_str().len() - 1];
let b_unanchored = &b.as_str()[1..b.as_str().len() - 1];
a_unanchored == b_unanchored
}
}
}
}
impl std::fmt::Display for AgentModeCommandExecutionPredicateType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::AnchoredRegex(regex) => {
write!(f, "{}", &regex.as_str()[1..regex.as_str().len() - 1])
}
}
}
}
/// A wrapper around [`AgentModeCommandExecutionPredicateType`] to enforce
/// the use of the provided constructors rather than direct construction of the variants.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(transparent)]
pub struct AgentModeCommandExecutionPredicate(AgentModeCommandExecutionPredicateType);
impl schemars::JsonSchema for AgentModeCommandExecutionPredicate {
fn schema_name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("AgentModeCommandExecutionPredicate")
}
fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
// In the settings file, predicates are serialized as plain regex strings.
gen.subschema_for::<String>()
}
}
impl AgentModeCommandExecutionPredicate {
pub fn new_regex(regex: &str) -> Result<Self, regex::Error> {
Ok(Self(AgentModeCommandExecutionPredicateType::new_regex(
regex,
)?))
}
pub fn matches(&self, cmd: &str) -> bool {
self.0.matches(cmd)
}
}
impl std::fmt::Display for AgentModeCommandExecutionPredicate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl settings_value::SettingsValue for AgentModeCommandExecutionPredicate {
fn to_file_value(&self) -> serde_json::Value {
serde_json::Value::String(self.to_string())
}
fn from_file_value(value: &serde_json::Value) -> Option<Self> {
value.as_str().and_then(|s| Self::new_regex(s).ok())
}
}
lazy_static! {
// Matches optional args / options for a top-level command.
static ref OPTIONAL_ARGS_REGEX: Regex = Regex::new(r"(\s.*)?").expect("Can parse optional args regex");
}
cfg_if! {
// Compiling the regexes for the default command execution allowlist/denylist can be slow
// in an unoptimized build, so we use empty lists in unit tests.
if #[cfg(test)] {
lazy_static! {
pub static ref DEFAULT_COMMAND_EXECUTION_ALLOWLIST: Vec<AgentModeCommandExecutionPredicate> = vec![];
pub static ref DEFAULT_COMMAND_EXECUTION_DENYLIST: Vec<AgentModeCommandExecutionPredicate> = vec![];
}
} else {
lazy_static! {
pub static ref DEFAULT_COMMAND_EXECUTION_ALLOWLIST: Vec<AgentModeCommandExecutionPredicate> = vec![
AgentModeCommandExecutionPredicate::new_regex(&format!("cat{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default cat rule into regex"),
AgentModeCommandExecutionPredicate::new_regex(&format!("echo{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default echo rule into regex"),
AgentModeCommandExecutionPredicate::new_regex("find .*").expect("Can parse default find rule into regex"),
AgentModeCommandExecutionPredicate::new_regex(&format!("grep{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default grep rule into regex"),
AgentModeCommandExecutionPredicate::new_regex(&format!("ls{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default ls rule into regex"),
AgentModeCommandExecutionPredicate::new_regex("which .*").expect("Can parse default which rule into regex"),
];
pub static ref DEFAULT_COMMAND_EXECUTION_DENYLIST: Vec<AgentModeCommandExecutionPredicate> = vec![
AgentModeCommandExecutionPredicate::new_regex(&format!("bash{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default bash rule into regex"),
AgentModeCommandExecutionPredicate::new_regex(&format!("fish{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default fish rule into regex"),
AgentModeCommandExecutionPredicate::new_regex(&format!("pwsh{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default pwsh rule into regex"),
AgentModeCommandExecutionPredicate::new_regex(&format!("sh{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default sh rule into regex"),
AgentModeCommandExecutionPredicate::new_regex(&format!("zsh{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default zsh rule into regex"),
AgentModeCommandExecutionPredicate::new_regex(&format!("curl{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default curl rule into regex"),
AgentModeCommandExecutionPredicate::new_regex(&format!("eval{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default eval rule into regex"),
AgentModeCommandExecutionPredicate::new_regex(&format!("exec{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default exec rule into regex"),
AgentModeCommandExecutionPredicate::new_regex(&format!("source{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default source rule into regex"),
AgentModeCommandExecutionPredicate::new_regex(&format!("wget{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default wget rule into regex"),
AgentModeCommandExecutionPredicate::new_regex(&format!("dig{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default dig rule into regex"),
AgentModeCommandExecutionPredicate::new_regex(&format!("nslookup{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default nslookup rule into regex"),
AgentModeCommandExecutionPredicate::new_regex(&format!("host{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default host rule into regex"),
AgentModeCommandExecutionPredicate::new_regex(&format!("ssh{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default ssh rule into regex"),
AgentModeCommandExecutionPredicate::new_regex(&format!("scp{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default scp rule into regex"),
AgentModeCommandExecutionPredicate::new_regex(&format!("rsync{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default rsync rule into regex"),
AgentModeCommandExecutionPredicate::new_regex(&format!("telnet{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default telnet rule into regex"),
AgentModeCommandExecutionPredicate::new_regex(&format!("rm{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default rm rule into regex"),
];
}
}
}
/// Maps custom toolbar command regex patterns to CLI agent names.
/// Keys are regex patterns (insertion-ordered), values are serialized CLIAgent names (e.g. "Claude").
/// An empty string value means "Any CLI Agent" (CLIAgent::Unknown).
@@ -1133,8 +1080,21 @@ define_settings_group!(AISettings, settings: [
sync_to_cloud: SyncToCloud::Never,
private: true,
}
// Whether direct Bedrock integration is enabled (client calls Bedrock API directly).
bedrock_enabled: BedrockEnabled {
// Whether or not we should show the one-shot speedbump on Ask-User-Question cards.
//
// Not a user-visible setting - we model it as a setting so we can track state.
// Intentionally NOT cloud-synced: we want users to see the first-time nudge on
// each fresh device, and we avoid a cloud-sync race that would make the flag
// silently stay `false` on new devices after being consumed once elsewhere.
should_show_agent_mode_ask_user_question_speedbump: ShouldShowAgentModeAskUserQuestionSpeedbump {
type: bool,
default: true,
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never,
private: true,
}
// Whether to use locally loaded AWS credentials for Bedrock-enabled requests.
aws_bedrock_credentials_enabled: AwsBedrockCredentialsEnabled {
type: bool,
default: true,
supported_platforms: SupportedPlatforms::DESKTOP,
@@ -1229,70 +1189,19 @@ define_settings_group!(AISettings, settings: [
sync_to_cloud: SyncToCloud::Never,
private: true,
}
// Whether the OpenAI-compatible (LiteLLM) provider is enabled.
openai_enabled: OpenAIEnabled {
// Whether to mint and attach Gemini Enterprise (GEAP) credentials to eligible agent
// requests, routing them through the workspace's Google Cloud project. Only consulted
// when the admin sets the GEAP host to RESPECT_USER_SETTING; ENFORCE bypasses it.
// Prefer [`UserWorkspaces::is_gemini_enterprise_credentials_enabled`] to interpret
// this setting.
gemini_enterprise_credentials_enabled: GeminiEnterpriseCredentialsEnabled {
type: bool,
default: false,
supported_platforms: SupportedPlatforms::DESKTOP,
sync_to_cloud: SyncToCloud::Never,
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
private: false,
toml_path: "ai.openai.enabled",
description: "Whether to use an OpenAI-compatible endpoint (e.g. LiteLLM) for AI requests.",
}
// Base URL for the OpenAI-compatible API endpoint.
// Never synced — machine-local infrastructure (e.g. localhost).
openai_base_url: OpenAIBaseUrl {
type: String,
default: "http://localhost:4000/v1".to_string(),
supported_platforms: SupportedPlatforms::DESKTOP,
sync_to_cloud: SyncToCloud::Never,
private: false,
toml_path: "ai.openai.base_url",
description: "Base URL for the OpenAI-compatible API endpoint (e.g. LiteLLM proxy).",
}
// API key for the OpenAI-compatible endpoint (optional if proxy handles auth).
openai_api_key: OpenAIApiKey {
type: String,
default: String::new(),
supported_platforms: SupportedPlatforms::DESKTOP,
sync_to_cloud: SyncToCloud::Never,
private: false,
toml_path: "ai.openai.api_key",
description: "API key for the OpenAI-compatible endpoint (optional if proxy handles auth).",
}
// Model name to send to the OpenAI-compatible endpoint. Empty = use selected model ID.
// Never synced — tied to the specific endpoint configuration.
openai_model: OpenAIModel {
type: String,
default: String::new(),
supported_platforms: SupportedPlatforms::DESKTOP,
sync_to_cloud: SyncToCloud::Never,
private: false,
toml_path: "ai.openai.model",
description: "Model name to send to the OpenAI-compatible endpoint. Leave empty to use the selected model ID.",
}
// Custom OpenAI-compatible model configurations (fetched from LiteLLM or manually configured).
// Never synced to cloud — these are machine-local (tied to local endpoints).
openai_models: OpenAIModels {
type: Vec<OpenAIModelConfig>,
default: Vec::new(),
supported_platforms: SupportedPlatforms::DESKTOP,
sync_to_cloud: SyncToCloud::Never,
private: false,
toml_path: "ai.openai.models",
description: "Custom OpenAI-compatible model configurations (e.g. from LiteLLM).",
}
// Multiple OpenAI-compatible provider endpoints (LiteLLM, Ollama, vLLM, etc.).
// Each provider has its own name, base_url, api_key, and model list.
// Never synced to cloud — these are machine-local infrastructure configs.
openai_providers: OpenAIProviders {
type: Vec<OpenAIProviderConfig>,
default: Vec::new(),
supported_platforms: SupportedPlatforms::DESKTOP,
sync_to_cloud: SyncToCloud::Never,
private: false,
toml_path: "ai.providers",
description: "Multiple OpenAI-compatible provider endpoints (e.g. LiteLLM, Ollama, local models).",
toml_path: "cloud_platform.third_party_api_keys.gemini_enterprise_credentials_enabled",
description: "Whether Warp should route eligible requests through your workspace's Gemini Enterprise Google Cloud project.",
}
// Whether or not the user wants agent mode requests to use their saved rules.
memory_enabled: MemoryEnabled {
@@ -1397,6 +1306,42 @@ define_settings_group!(AISettings, settings: [
// We model it as a setting so it's only shown once to a given user regardless of the number of
// devices they use.
did_check_to_trigger_oz_launch_modal: DidShowOzLaunchModal {
type: bool,
default: false,
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::No),
private: true,
}
// This is not a user-visible setting - it's merely a one-time flag to track if the
// orchestration launch modal has been shown to the user.
//
// We model it as a setting so it's only shown once to a given user regardless of the number of
// devices they use.
did_check_to_trigger_orchestration_launch_modal: DidShowOrchestrationLaunchModal {
type: bool,
default: false,
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::No),
private: true,
}
// This is not a user-visible setting - it's merely a one-time flag to track if the
// free-AI-removal notice modal has been shown to (or silently marked as seen for) the user.
//
// We model it as a setting so it's only shown once to a given user regardless of the number of
// devices they use.
did_check_to_trigger_free_ai_removal_modal: DidShowFreeAiRemovalModal {
type: bool,
default: false,
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::No),
private: true,
}
// Used to determine whether the "What's new in Oz" section of the agent view
// zero state is expanded or collapsed by default.
should_expand_oz_updates: ShouldExpandOzUpdates {
type: bool,
default: false,
supported_platforms: SupportedPlatforms::ALL,
@@ -1406,16 +1351,16 @@ define_settings_group!(AISettings, settings: [
// Whether or not the user has enabled the ability to use Warp credits even when providing
// their own LLM provider API key.
can_use_warp_credits_with_byok: CanUseWarpCreditsWithByok {
// Whether or not the user has enabled fallback to Warp credits for user-provided models.
can_use_warp_credits_for_fallback: CanUseWarpCreditsForFallback {
type: bool,
default: false,
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never,
private: false,
storage_key: "CanUseWarpCreditsWithByok",
toml_path: "cloud_platform.third_party_api_keys.can_use_warp_credits_with_byok",
description: "Whether Galaxy credits can be used even when providing your own API key.",
description: "Whether Warp credits can be used as a fallback for user-provided models.",
}
should_render_use_agent_footer_for_user_commands: ShouldRenderUseAgentToolbarForUserCommands {
@@ -1478,6 +1423,18 @@ define_settings_group!(AISettings, settings: [
description: "Whether CLI agent Rich Input automatically closes after the user submits a prompt.",
}
// When enabled, the Rich Input editor submits on Ctrl+Enter instead of Enter.
// Enter inserts a newline; Ctrl+Enter submits.
submit_on_ctrl_enter: SubmitRichInputOnCtrlEnter {
type: bool,
default: false,
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
private: false,
toml_path: "agents.third_party.submit_on_ctrl_enter",
description: "When enabled, the Rich Input editor submits on Ctrl+Enter instead of Enter. Enter inserts a newline.",
}
// Maps custom toolbar command regex patterns to specific CLI agents.
// Keys are regex patterns matched against the full command string.
// Values are serialized CLIAgent names (empty string = any agent).
@@ -1507,31 +1464,6 @@ define_settings_group!(AISettings, settings: [
private: true,
}
// This is not a user-visible setting - it tracks whether the FTU model picker callout
// has been shown to the user. We set this to `true` as soon as the callout is first
// displayed (not when it's dismissed), so it never re-appears.
//
// Note: this setting was originally named "dismissed" but we now use it to mean "shown".
// We kept the same setting key so that users who already dismissed the callout on an
// older client don't see it again.
ftu_model_callout_dismissed: FtuModelCalloutDismissed {
type: bool,
default: false,
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never,
private: true,
}
// Tracks whether we've done the one-time auto-open of the conversation list for discoverability.
// Once set to true, the conversation list visibility will be restored from workspace state.
has_auto_opened_conversation_list: HasAutoOpenedConversationList {
type: bool,
default: false,
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never,
private: true,
}
// Whether the ambient agent trial widget has been dismissed by the user.
//
// Not a user-visible setting - we model it as a setting so we can track state.
@@ -1559,6 +1491,18 @@ define_settings_group!(AISettings, settings: [
toml_path: "general.default_tab_config_path",
}
// Whether computer use is enabled for cloud agent conversations started from the Warp app.
// This setting is only used when the AI autonomy setting is AlwaysAsk or not set.
cloud_agent_computer_use_enabled: CloudAgentComputerUseEnabled {
type: bool,
default: galaxy_core::channel::ChannelState::channel().is_dogfood(),
supported_platforms: SupportedPlatforms::DESKTOP,
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
private: false,
toml_path: "agents.warp_agent.other.cloud_agent_computer_use_enabled",
description: "Whether computer use is enabled for cloud agent conversations.",
}
// Whether file-based MCP servers from third-party AI tools (e.g. Claude, Codex) should
// be automatically detected and spawned. Warp-native config files (.warp-core/.mcp.json) are
@@ -1576,6 +1520,19 @@ define_settings_group!(AISettings, settings: [
// Controls how agent thinking/reasoning traces are displayed.
thinking_display_mode: ThinkingDisplayMode,
// Controls how orchestration message bodies are expanded by default.
orchestration_message_display_mode: OrchestrationMessageDisplayMode,
// Default behavior when the user submits a new prompt while the agent is still
// responding. Per-conversation overrides live on `QueuedQueryModel`; this
// setting is the fallback used when a conversation has no explicit override.
default_prompt_submission_mode: PromptSubmissionMode,
// What happens when a prompt is submitted while an agent controls an agent-requested
// long-running command. Only consulted when `default_prompt_submission_mode` is `Interrupt`;
// per-LRC manual overrides live on `QueuedQueryModel`.
long_running_command_submission_mode: LongRunningCommandSubmissionMode,
// Whether agent-executed shell commands should be included in command history
// (up-arrow, Ctrl-R search, inline history menu).
// When false, commands run by the AI agent are excluded from history.
@@ -1659,6 +1616,49 @@ define_settings_group!(AISettings, settings: [
sync_to_cloud: SyncToCloud::Never,
private: true,
}
should_force_disable_cloud_handoff: ShouldForceDisableCloudHandoff {
type: bool,
default: false,
supported_platforms: SupportedPlatforms::DESKTOP,
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
private: false,
toml_path: "agents.warp_agent.other.should_force_disable_cloud_handoff",
description: "Whether to force-disable local-to-cloud handoff.",
}
should_force_disable_ampersand_handoff: ShouldForceDisableAmpersandHandoff {
type: bool,
default: false,
supported_platforms: SupportedPlatforms::DESKTOP,
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
private: false,
toml_path: "agents.warp_agent.other.should_force_disable_ampersand_handoff",
description: "Whether to force-disable the & prefix for cloud handoff compose mode.",
}
auto_handoff_on_sleep_enabled: AutoHandoffOnSleepEnabled {
type: bool,
default: false,
supported_platforms: SupportedPlatforms::MAC,
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
private: false,
toml_path: "agents.warp_agent.other.auto_handoff_on_sleep_enabled",
description: "Whether Warp automatically hands off local agent conversations to cloud when the computer is about to sleep.",
}
// This is not a user-visible setting - it's merely a one-time flag to track if the
// auto-handoff sleep modal has been shown to the user.
//
// We model it as a setting so it's only shown once to a given user regardless of the number of
// devices they use.
did_show_auto_handoff_sleep_modal: DidShowAutoHandoffSleepModal {
type: bool,
default: false,
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::No),
private: true,
}
]);
impl AISettings {
@@ -1668,7 +1668,7 @@ impl AISettings {
CompiledCommandsForCodingAgentToolbar::register(app);
app.update_model(&Self::handle(app), |_me, ctx| {
ctx.subscribe_to_model(&FocusedTerminalInfo::handle(ctx), |_me, event, ctx| {
ctx.subscribe_to_model(&FocusedTerminalInfo::handle(ctx), |_me, _, event, ctx| {
if matches!(event, FocusedTerminalInfoEvent::TerminalInfoUpdated) {
// Pipe the event so that any view that listens for settings changes will be notified.
ctx.emit(AISettingsChangedEvent::IsAnyAIEnabled {
@@ -1839,8 +1839,42 @@ impl AISettings {
*self.file_based_mcp_enabled
}
pub fn is_orchestration_enabled(&self, _app: &galaxyui::AppContext) -> bool {
false
pub fn is_orchestration_enabled(&self, app: &galaxyui::AppContext) -> bool {
self.is_any_ai_enabled(app)
}
/// Returns true when local-to-cloud handoff is effectively enabled.
/// False when the user/org has disabled it, cloud conversations are off,
/// or AI is globally off.
pub fn is_cloud_handoff_enabled(&self, app: &galaxyui::AppContext) -> bool {
if !self.is_any_ai_enabled(app) || *self.should_force_disable_cloud_handoff {
return false;
}
if !FeatureFlag::OzHandoff.is_enabled()
|| !FeatureFlag::HandoffLocalCloud.is_enabled()
|| !cfg!(all(feature = "local_fs", not(target_family = "wasm")))
{
return false;
}
let privacy = PrivacySettings::as_ref(app);
if !privacy.is_cloud_conversation_storage_enabled {
return false;
}
!matches!(
UserWorkspaces::as_ref(app).get_cloud_conversation_storage_enablement_setting(),
crate::workspaces::workspace::AdminEnablementSetting::Disable
)
}
pub fn is_ampersand_handoff_enabled(&self, app: &galaxyui::AppContext) -> bool {
self.is_cloud_handoff_enabled(app) && !*self.should_force_disable_ampersand_handoff
}
pub fn is_auto_handoff_on_sleep_enabled(&self, app: &galaxyui::AppContext) -> bool {
self.is_cloud_handoff_enabled(app)
&& self
.auto_handoff_on_sleep_enabled
.is_supported_on_current_platform()
&& *self.auto_handoff_on_sleep_enabled
}
/// Determines whether a quota reset banner should be displayed to the user.
@@ -1929,11 +1963,7 @@ impl AISettings {
}
pub fn is_command_denylist_editable(&self, app: &AppContext) -> bool {
let set_by_workspace = UserWorkspaces::as_ref(app)
.ai_autonomy_settings()
.has_override_for_execute_commands_denylist();
self.is_any_ai_enabled(app) && !set_by_workspace
self.is_any_ai_enabled(app)
}
pub fn is_command_allowlist_editable(&self, app: &AppContext) -> bool {
@@ -1999,6 +2029,10 @@ impl AISettings {
self.is_any_ai_enabled(app)
}
pub fn is_run_agents_permissions_editable(&self, app: &AppContext) -> bool {
self.is_orchestration_enabled(app)
}
pub fn show_code_suggestion_speedbump(&self, app: &AppContext) -> bool {
self.is_any_ai_enabled(app) && *self.show_code_suggestion_speedbump
}