Skip to content
Merged
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
30 changes: 18 additions & 12 deletions src/agent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -757,12 +757,13 @@ pub async fn run_interactive(
.tool(ListHetznerAvailabilityTool::new())
// Deployment tools for service management
.tool(CreateDeploymentConfigTool::new())
.tool(DeployServiceTool::new(project_path_buf.clone()))
.tool(DeployServiceTool::with_context(project_path_buf.clone(), ExecutionContext::InteractiveCli))
.tool(ListDeploymentConfigsTool::new())
.tool(TriggerDeploymentTool::new())
.tool(GetDeploymentStatusTool::new())
.tool(ListDeploymentsTool::new())
.tool(GetServiceLogsTool::new());
.tool(GetServiceLogsTool::new())
.tool(SetDeploymentSecretsTool::with_context(ExecutionContext::InteractiveCli));

// Add tools based on mode
if is_planning {
Expand Down Expand Up @@ -875,12 +876,13 @@ pub async fn run_interactive(
.tool(ListHetznerAvailabilityTool::new())
// Deployment tools for service management
.tool(CreateDeploymentConfigTool::new())
.tool(DeployServiceTool::new(project_path_buf.clone()))
.tool(DeployServiceTool::with_context(project_path_buf.clone(), ExecutionContext::InteractiveCli))
.tool(ListDeploymentConfigsTool::new())
.tool(TriggerDeploymentTool::new())
.tool(GetDeploymentStatusTool::new())
.tool(ListDeploymentsTool::new())
.tool(GetServiceLogsTool::new());
.tool(GetServiceLogsTool::new())
.tool(SetDeploymentSecretsTool::with_context(ExecutionContext::InteractiveCli));

// Add tools based on mode
if is_planning {
Expand Down Expand Up @@ -984,12 +986,13 @@ pub async fn run_interactive(
.tool(ListHetznerAvailabilityTool::new())
// Deployment tools for service management
.tool(CreateDeploymentConfigTool::new())
.tool(DeployServiceTool::new(project_path_buf.clone()))
.tool(DeployServiceTool::with_context(project_path_buf.clone(), ExecutionContext::InteractiveCli))
.tool(ListDeploymentConfigsTool::new())
.tool(TriggerDeploymentTool::new())
.tool(GetDeploymentStatusTool::new())
.tool(ListDeploymentsTool::new())
.tool(GetServiceLogsTool::new());
.tool(GetServiceLogsTool::new())
.tool(SetDeploymentSecretsTool::with_context(ExecutionContext::InteractiveCli));

// Add tools based on mode
if is_planning {
Expand Down Expand Up @@ -2479,12 +2482,13 @@ pub async fn run_query(
.tool(ListHetznerAvailabilityTool::new())
// Deployment tools for service management
.tool(CreateDeploymentConfigTool::new())
.tool(DeployServiceTool::new(project_path_buf.clone()))
.tool(DeployServiceTool::with_context(project_path_buf.clone(), ExecutionContext::InteractiveCli))
.tool(ListDeploymentConfigsTool::new())
.tool(TriggerDeploymentTool::new())
.tool(GetDeploymentStatusTool::new())
.tool(ListDeploymentsTool::new())
.tool(GetServiceLogsTool::new());
.tool(GetServiceLogsTool::new())
.tool(SetDeploymentSecretsTool::with_context(ExecutionContext::InteractiveCli));

// Add generation tools if this is a generation query
if is_generation {
Expand Down Expand Up @@ -2565,12 +2569,13 @@ pub async fn run_query(
.tool(ListHetznerAvailabilityTool::new())
// Deployment tools for service management
.tool(CreateDeploymentConfigTool::new())
.tool(DeployServiceTool::new(project_path_buf.clone()))
.tool(DeployServiceTool::with_context(project_path_buf.clone(), ExecutionContext::InteractiveCli))
.tool(ListDeploymentConfigsTool::new())
.tool(TriggerDeploymentTool::new())
.tool(GetDeploymentStatusTool::new())
.tool(ListDeploymentsTool::new())
.tool(GetServiceLogsTool::new());
.tool(GetServiceLogsTool::new())
.tool(SetDeploymentSecretsTool::with_context(ExecutionContext::InteractiveCli));

// Add generation tools if this is a generation query
if is_generation {
Expand Down Expand Up @@ -2640,12 +2645,13 @@ pub async fn run_query(
.tool(ListHetznerAvailabilityTool::new())
// Deployment tools for service management
.tool(CreateDeploymentConfigTool::new())
.tool(DeployServiceTool::new(project_path_buf.clone()))
.tool(DeployServiceTool::with_context(project_path_buf.clone(), ExecutionContext::InteractiveCli))
.tool(ListDeploymentConfigsTool::new())
.tool(TriggerDeploymentTool::new())
.tool(GetDeploymentStatusTool::new())
.tool(ListDeploymentsTool::new())
.tool(GetServiceLogsTool::new());
.tool(GetServiceLogsTool::new())
.tool(SetDeploymentSecretsTool::with_context(ExecutionContext::InteractiveCli));

// Add generation tools if this is a generation query
if is_generation {
Expand Down
18 changes: 17 additions & 1 deletion src/agent/tools/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,21 @@
//!
//! See `response.rs` for the complete response formatting infrastructure.

/// Execution context for tools that behave differently in CLI vs server mode.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExecutionContext {
/// Interactive CLI — terminal available, inquire prompts work
InteractiveCli,
/// AG-UI server — no terminal, prompts would hang
HeadlessServer,
}

impl ExecutionContext {
pub fn has_terminal(&self) -> bool {
matches!(self, Self::InteractiveCli)
}
}

mod analyze;
pub mod background;
pub mod compression;
Expand Down Expand Up @@ -175,7 +190,8 @@ pub use platform::{
DeployServiceTool, GetDeploymentStatusTool, GetServiceLogsTool,
ListDeploymentCapabilitiesTool, ListDeploymentConfigsTool, ListDeploymentsTool,
ListHetznerAvailabilityTool, ListOrganizationsTool, ListProjectsTool,
OpenProviderSettingsTool, SelectProjectTool, TriggerDeploymentTool,
OpenProviderSettingsTool, SelectProjectTool, SetDeploymentSecretsTool,
TriggerDeploymentTool,
};
pub use prometheus_connect::PrometheusConnectTool;
pub use prometheus_discover::PrometheusDiscoverTool;
Expand Down
94 changes: 85 additions & 9 deletions src/agent/tools/platform/create_deployment_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ use serde::{Deserialize, Serialize};
use serde_json::json;

use crate::agent::tools::error::{ErrorCategory, format_error_for_llm};
use crate::platform::api::types::CreateDeploymentConfigRequest;
use crate::platform::api::types::{CloudProvider, CloudRunnerConfigInput, CreateDeploymentConfigRequest, build_cloud_runner_config_v2};
use crate::platform::api::{PlatformApiClient, PlatformApiError};
use std::str::FromStr;

/// Arguments for the create deployment config tool
#[derive(Debug, Deserialize)]
Expand All @@ -28,7 +29,7 @@ pub struct CreateDeploymentConfigArgs {
pub branch: String,
/// Target type: "kubernetes" or "cloud_runner"
pub target_type: String,
/// Cloud provider: "gcp" or "hetzner"
/// Cloud provider: "gcp", "hetzner", or "azure"
pub provider: String,
/// Environment ID for deployment
pub environment_id: String,
Expand All @@ -43,6 +44,16 @@ pub struct CreateDeploymentConfigArgs {
/// Enable auto-deploy on push (defaults to true)
#[serde(default = "default_auto_deploy")]
pub auto_deploy_enabled: bool,
/// CPU allocation (for GCP Cloud Run or Azure Container Apps)
pub cpu: Option<String>,
/// Memory allocation (for GCP Cloud Run or Azure Container Apps)
pub memory: Option<String>,
/// Minimum instances/replicas
pub min_instances: Option<i32>,
/// Maximum instances/replicas
pub max_instances: Option<i32>,
/// Whether the service should be publicly accessible
pub is_public: Option<bool>,
}

fn default_auto_deploy() -> bool {
Expand Down Expand Up @@ -84,6 +95,7 @@ A deployment config defines how to build and deploy a service, including:
- Dockerfile location and build context
- Target (Cloud Runner or Kubernetes)
- Port configuration
- CPU/memory allocation (for Cloud Runner deployments)
- Auto-deploy settings

**Required Parameters:**
Expand All @@ -94,7 +106,7 @@ A deployment config defines how to build and deploy a service, including:
- port: Port the service listens on
- branch: Git branch to deploy from (e.g., "main")
- target_type: "kubernetes" or "cloud_runner"
- provider: "gcp" or "hetzner"
- provider: "gcp", "hetzner", or "azure"
- environment_id: Environment to deploy to

**Optional Parameters:**
Expand All @@ -103,6 +115,11 @@ A deployment config defines how to build and deploy a service, including:
- cluster_id: Required for kubernetes target
- registry_id: Container registry ID (provisions new if not provided)
- auto_deploy_enabled: Enable auto-deploy on push (default: true)
- cpu: CPU allocation (e.g., "1" for GCP Cloud Run, "0.5" for Azure ACA)
- memory: Memory allocation (e.g., "512Mi" for GCP, "1.0Gi" for Azure)
- min_instances: Minimum instances/replicas (default: 0)
- max_instances: Maximum instances/replicas (default: 10)
- is_public: Whether the service should be publicly accessible (default: true)

**Prerequisites:**
- User must be authenticated
Expand Down Expand Up @@ -149,7 +166,7 @@ A deployment config defines how to build and deploy a service, including:
},
"provider": {
"type": "string",
"enum": ["gcp", "hetzner"],
"enum": ["gcp", "hetzner", "azure"],
"description": "Cloud provider"
},
"environment_id": {
Expand All @@ -175,6 +192,26 @@ A deployment config defines how to build and deploy a service, including:
"auto_deploy_enabled": {
"type": "boolean",
"description": "Enable auto-deploy on push (default: true)"
},
"cpu": {
"type": "string",
"description": "CPU allocation (e.g., '1' for GCP Cloud Run, '0.5' for Azure ACA)"
},
"memory": {
"type": "string",
"description": "Memory allocation (e.g., '512Mi' for GCP, '1.0Gi' for Azure)"
},
"min_instances": {
"type": "integer",
"description": "Minimum instances/replicas (default: 0)"
},
"max_instances": {
"type": "integer",
"description": "Maximum instances/replicas (default: 10)"
},
"is_public": {
"type": "boolean",
"description": "Whether the service should be publicly accessible (default: true)"
}
},
"required": [
Expand Down Expand Up @@ -222,20 +259,20 @@ A deployment config defines how to build and deploy a service, including:
args.target_type
),
Some(vec![
"Use 'cloud_runner' for GCP Cloud Run or Hetzner containers",
"Use 'cloud_runner' for GCP Cloud Run, Hetzner containers, or Azure Container Apps",
"Use 'kubernetes' for deploying to a K8s cluster",
]),
));
}

// Validate provider
let valid_providers = ["gcp", "hetzner"];
let valid_providers = ["gcp", "hetzner", "azure"];
if !valid_providers.contains(&args.provider.as_str()) {
return Ok(format_error_for_llm(
"create_deployment_config",
ErrorCategory::ValidationFailed,
&format!(
"Invalid provider '{}'. Must be 'gcp' or 'hetzner'",
"Invalid provider '{}'. Must be 'gcp', 'hetzner', or 'azure'",
args.provider
),
Some(vec![
Expand Down Expand Up @@ -266,6 +303,44 @@ A deployment config defines how to build and deploy a service, including:
}
};

// Build cloud runner config if deploying to cloud_runner
let cloud_runner_config = if args.target_type == "cloud_runner" {
let provider_enum = CloudProvider::from_str(&args.provider).ok();

// Fetch provider_account_id from credentials when provider is azure or gcp
let mut gcp_project_id = None;
let mut subscription_id = None;
if let Some(ref provider) = provider_enum {
if matches!(provider, CloudProvider::Gcp | CloudProvider::Azure) {
if let Ok(credential) = client.check_provider_connection(provider, &args.project_id).await {
if let Some(cred) = credential {
match provider {
CloudProvider::Gcp => gcp_project_id = cred.provider_account_id,
CloudProvider::Azure => subscription_id = cred.provider_account_id,
_ => {}
}
}
}
}
}

let config_input = CloudRunnerConfigInput {
provider: provider_enum,
region: None, // Region is set at environment level or by deploy_service
gcp_project_id,
cpu: args.cpu.clone(),
memory: args.memory.clone(),
min_instances: args.min_instances,
max_instances: args.max_instances,
is_public: args.is_public,
subscription_id,
..Default::default()
};
Some(build_cloud_runner_config_v2(&config_input))
} else {
None
};

// Build the request
// Note: Send both field name variants (dockerfile/dockerfilePath, context/buildContext)
// for backend compatibility - different endpoints may expect different field names
Expand All @@ -286,8 +361,9 @@ A deployment config defines how to build and deploy a service, including:
cluster_id: args.cluster_id.clone(),
registry_id: args.registry_id.clone(),
auto_deploy_enabled: args.auto_deploy_enabled,
is_public: None,
cloud_runner_config: None,
is_public: args.is_public,
cloud_runner_config,
secrets: None,
};

// Create the deployment config
Expand Down
Loading
Loading