Skip to content
Draft
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
18 changes: 11 additions & 7 deletions app/src/ai/agent_sdk/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,10 +300,8 @@ impl EnvironmentCommandRunner {
if let Some(desc) = &env.description {
println!("Description: {desc}");
}
match &env.base_image {
BaseImage::DockerImage(img) => {
println!("Docker image: {img}");
}
if let Some(BaseImage::DockerImage(img)) = &env.base_image {
println!("Docker image: {img}");
}
if env.github_repos.is_empty() {
println!("Repositories: None");
Expand Down Expand Up @@ -952,7 +950,7 @@ impl EnvironmentCommandRunner {
}

if let Some(new_docker_image) = docker_image {
updated_env.base_image = BaseImage::DockerImage(new_docker_image);
updated_env.base_image = Some(BaseImage::DockerImage(new_docker_image));
}

for repo in add_repos {
Expand Down Expand Up @@ -1121,7 +1119,8 @@ struct EnvironmentInfo {
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
base_image: BaseImage,
#[serde(skip_serializing_if = "Option::is_none")]
base_image: Option<BaseImage>,
github_repos: Vec<GithubRepo>,
setup_commands: Vec<String>,
creator_email: String,
Expand Down Expand Up @@ -1161,7 +1160,12 @@ impl TableFormat for EnvironmentInfo {
Cell::new(&self.id),
Cell::new(&self.name),
Cell::new(description_display),
Cell::new(self.base_image.to_string()),
Cell::new(
self.base_image
.as_ref()
.map(ToString::to_string)
.unwrap_or_default(),
),
Cell::new(github_repos_display),
Cell::new(setup_commands_display),
Cell::new(&self.creator_email),
Expand Down
34 changes: 21 additions & 13 deletions app/src/ai/conversation_details_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1635,7 +1635,6 @@ impl ConversationDetailsPanel {
app: &AppContext,
) -> Box<dyn Element> {
let environment_name = &env_model.name;
let docker_image = env_model.base_image.to_string();

let theme = appearance.theme();
let ui_font_size = appearance.ui_font_size();
Expand Down Expand Up @@ -1702,16 +1701,18 @@ impl ConversationDetailsPanel {
.finish(),
);

section.add_child(
Container::new(render_copyable_field(
"Image",
&docker_image,
CopyButtonKind::DockerImage,
ConversationDetailsPanelAction::CopyDockerImage,
))
.with_margin_bottom(LABEL_VALUE_GAP)
.finish(),
);
if let Some(base_image) = &env_model.base_image {
section.add_child(
Container::new(render_copyable_field(
"Image",
&base_image.to_string(),
CopyButtonKind::DockerImage,
ConversationDetailsPanelAction::CopyDockerImage,
))
.with_margin_bottom(LABEL_VALUE_GAP)
.finish(),
);
}

Container::new(section.finish())
.with_margin_bottom(FIELD_SPACING)
Expand Down Expand Up @@ -2262,8 +2263,15 @@ impl TypedActionView for ConversationDetailsPanel {
// Fetch docker image from environment
if let Ok(server_id) = ServerId::try_from(env_id.as_str()) {
let sync_id = SyncId::ServerId(server_id);
if let Some(env) = CloudAmbientAgentEnvironment::get_by_id(&sync_id, ctx) {
let docker_image = env.model().string_model.base_image.to_string();
let docker_image = CloudAmbientAgentEnvironment::get_by_id(&sync_id, ctx)
.and_then(|env| {
env.model()
.string_model
.base_image
.as_ref()
.map(ToString::to_string)
});
if let Some(docker_image) = docker_image {
ctx.clipboard()
.write(ClipboardContent::plain_text(docker_image));
self.record_copy(CopyButtonKind::DockerImage, ctx);
Expand Down
8 changes: 7 additions & 1 deletion app/src/context_chips/display_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,13 @@ impl DisplayChipMenu {
Some(EnvironmentSidecarData {
name: env.model().string_model.display_name(),
id: env.id.to_string(),
image: env.model().string_model.base_image.to_string(),
image: env
.model()
.string_model
.base_image
.as_ref()
.map(ToString::to_string)
.unwrap_or_default(),
repos_text,
})
}
Expand Down
12 changes: 10 additions & 2 deletions app/src/settings_view/environments_page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,11 @@ impl EnvironmentDisplayData {
id: env.id,
name: model.name.clone(),
description: model.description.clone(),
docker_image: model.base_image.to_string(),
docker_image: model
.base_image
.as_ref()
.map(ToString::to_string)
.unwrap_or_default(),
github_repos: model
.github_repos
.iter()
Expand Down Expand Up @@ -270,7 +274,11 @@ impl EnvironmentsPageView {
name: model.name.clone(),
description: model.description.clone().unwrap_or_default(),
selected_repos: model.github_repos.clone(),
docker_image: model.base_image.to_string(),
docker_image: model
.base_image
.as_ref()
.map(ToString::to_string)
.unwrap_or_default(),
setup_commands: model.setup_commands.clone(),
}
});
Expand Down
8 changes: 4 additions & 4 deletions crates/cloud_object_models/src/cloud_environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,9 @@ pub struct AmbientAgentEnvironment {
/// `github_repos`.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_repos: Option<Vec<SourceRepo>>,
/// Base image specification
#[serde(flatten)]
pub base_image: BaseImage,
/// Optional base image specification
#[serde(flatten, default, skip_serializing_if = "Option::is_none")]
pub base_image: Option<BaseImage>,
/// List of setup commands to run after cloning
#[serde(default)]
pub setup_commands: Vec<String>,
Expand Down Expand Up @@ -212,7 +212,7 @@ impl AmbientAgentEnvironment {
code_forge: None,
github_repos,
source_repos: None,
base_image: BaseImage::DockerImage(docker_image),
base_image: Some(BaseImage::DockerImage(docker_image)),
setup_commands,
providers: ProvidersConfig::default(),
secrets: None,
Expand Down
54 changes: 53 additions & 1 deletion crates/cloud_object_models/src/cloud_environment_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,63 @@ fn deserialize_legacy_environment_without_providers() {
);
assert_eq!(
env.base_image,
BaseImage::DockerImage("ubuntu:latest".into())
Some(BaseImage::DockerImage("ubuntu:latest".into()))
);
assert_eq!(env.setup_commands, vec!["echo hello"]);
}

#[test]
fn deserialize_environment_without_base_image() {
let json = serde_json::json!({
"name": "default-image-env",
"github_repos": [],
"setup_commands": []
});

let env: AmbientAgentEnvironment = serde_json::from_value(json).unwrap();

assert_eq!(env.base_image, None);
}

#[test]
fn base_image_serialization_preserves_wire_format() {
let env = AmbientAgentEnvironment::new(
"pinned-image-env".into(),
None,
vec![],
"ubuntu:latest".into(),
vec![],
);

let json = serde_json::to_value(env).unwrap();

assert_eq!(
json,
serde_json::json!({
"name": "pinned-image-env",
"github_repos": [],
"docker_image": "ubuntu:latest",
"setup_commands": []
})
);
}

#[test]
fn serialize_environment_without_base_image_omits_docker_image() {
let mut env = AmbientAgentEnvironment::new(
"default-image-env".into(),
None,
vec![],
"unused".into(),
vec![],
);
env.base_image = None;

let json = serde_json::to_value(env).unwrap();

assert!(!json.as_object().unwrap().contains_key("docker_image"));
}

#[test]
fn deserialize_gitlab_environment_uses_authoritative_source_repos() {
let json = serde_json::json!({
Expand Down