Skip to content
Open
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
84 changes: 74 additions & 10 deletions crates/aionui-ai-agent/src/manager/acp/agent_session_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ use crate::protocol::events::{
use crate::protocol::send_error::AgentSendError;
use crate::shared_kernel::SessionId as DomainSessionId;
use crate::types::SendMessageData;
use agent_client_protocol::schema::{ContentBlock, LoadSessionRequest, PromptRequest, SessionId, StopReason};
use agent_client_protocol::schema::{
ContentBlock, LoadSessionRequest, PromptRequest, SessionConfigKind, SessionConfigOptionCategory,
SessionConfigSelectOptions, SessionId, SessionModelState, StopReason,
};
use aionui_api_types::SlashCommandItem;
use serde_json::Value;
use tokio::sync::broadcast::error::TryRecvError;
Expand All @@ -28,6 +31,61 @@ pub(super) enum PromptOutcome {
WarningTip { session_id: String, tips: TipsEventData },
}

/// Per the ACP spec, `configOptions` with `category: "model"` is the
/// stable, recommended way for agents to advertise available models.
/// Some agents (e.g. OpenCode) use this exclusively and omit the
/// legacy `models` field. When `models` is absent, derive a
/// `SessionModelState` from the "model" config option so the frontend
/// can still render the model selector.
fn derive_models_from_config_options(
config_options: &[agent_client_protocol::schema::SessionConfigOption],
) -> Option<SessionModelState> {
use agent_client_protocol::schema::{ModelId as AcpModelId, ModelInfo, SessionConfigSelectGroup};
for opt in config_options {
if opt.category.as_ref() != Some(&SessionConfigOptionCategory::Model) {
continue;
}
if let SessionConfigKind::Select(ref select) = opt.kind {
let current_model_id = AcpModelId::new(select.current_value.0.as_ref());
let available: Vec<ModelInfo> = match &select.options {
SessionConfigSelectOptions::Ungrouped(options) => options
.iter()
.map(|o| ModelInfo::new(AcpModelId::new(o.value.0.as_ref()), o.name.clone()))
.collect(),
SessionConfigSelectOptions::Grouped(groups) => groups
.iter()
.flat_map(|g: &SessionConfigSelectGroup| &g.options)
.map(|o| ModelInfo::new(AcpModelId::new(o.value.0.as_ref()), o.name.clone()))
.collect(),
// `SessionConfigSelectOptions` is non-exhaustive; future variants
// are treated as having no model entries.
_ => Vec::new(),
};
if !available.is_empty() {
return Some(SessionModelState::new(current_model_id, available));
}
}
}
Comment on lines +44 to +68
None
}

/// Apply advertised models to the session, preferring the dedicated
/// `models` field and falling back to `configOptions` (category
/// "model") when absent.
fn apply_advertised_models(
session: &mut crate::manager::acp::session::AcpSession,
models: Option<SessionModelState>,
config_options: Option<&[agent_client_protocol::schema::SessionConfigOption]>,
) {
if let Some(models) = models {
session.apply_advertised_models(models);
} else if let Some(config_options) = config_options
&& let Some(models) = derive_models_from_config_options(config_options)
{
session.apply_advertised_models(models);
}
}

impl AcpAgentManager {
/// Establish a fresh ACP session (session/new) and apply desired
/// mode/model/config via reconcile. Does NOT send a prompt and
Expand All @@ -42,9 +100,11 @@ impl AcpAgentManager {

{
let mut session = self.session.write().await;
if let Some(models) = session_response.models {
session.apply_advertised_models(models);
}
apply_advertised_models(
&mut session,
session_response.models,
session_response.config_options.as_deref(),
);
if let Some(modes) = session_response.modes {
session.apply_advertised_modes(modes);
}
Expand Down Expand Up @@ -134,9 +194,11 @@ impl AcpAgentManager {

{
let mut session = self.session.write().await;
if let Some(models) = new_response.models {
session.apply_advertised_models(models);
}
apply_advertised_models(
&mut session,
new_response.models,
new_response.config_options.as_deref(),
);
if let Some(modes) = new_response.modes {
session.apply_advertised_modes(modes);
}
Expand Down Expand Up @@ -185,9 +247,11 @@ impl AcpAgentManager {

{
let mut session = self.session.write().await;
if let Some(models) = load_response.models {
session.apply_advertised_models(models);
}
apply_advertised_models(
&mut session,
load_response.models,
load_response.config_options.as_deref(),
);
if let Some(mut modes) = load_response.modes {
if let Some(db_current) = preloaded_mode {
modes.current_mode_id = db_current.into();
Expand Down
Loading