Add ACP agent backend and terminal controls
This commit is contained in:
@@ -92,6 +92,9 @@ impl TryFrom<ServerConversationToken>
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RequestParams {
|
||||
/// Galaxy terminal view that originated the request. This is retained
|
||||
/// locally so ACP-provided Galaxy tools can be pinned to the exact pane.
|
||||
pub terminal_view_id: Option<EntityId>,
|
||||
pub input: Vec<AIAgentInput>,
|
||||
pub conversation_token: Option<ServerConversationToken>,
|
||||
pub forked_from_conversation_token: Option<ServerConversationToken>,
|
||||
@@ -182,6 +185,7 @@ impl RequestParams {
|
||||
#[cfg(test)]
|
||||
pub fn new_for_test() -> Self {
|
||||
Self {
|
||||
terminal_view_id: None,
|
||||
input: vec![],
|
||||
conversation_token: None,
|
||||
forked_from_conversation_token: None,
|
||||
@@ -222,6 +226,19 @@ impl RequestParams {
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies the request's configured secret-redaction policy to text immediately
|
||||
/// before it is handed to an external model or agent.
|
||||
pub(crate) fn redact_text_for_model(&self, text: &mut String) {
|
||||
if self.should_redact_secrets {
|
||||
super::redaction::redact_secrets(text);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn enable_secret_redaction_for_test(&mut self) {
|
||||
self.should_redact_secrets = true;
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
terminal_view_id: Option<EntityId>,
|
||||
session_context: SessionContext,
|
||||
@@ -372,6 +389,7 @@ impl RequestParams {
|
||||
.context_window_limit_for_request(app);
|
||||
|
||||
Self {
|
||||
terminal_view_id,
|
||||
input: request_input.all_inputs().cloned().collect(),
|
||||
conversation_token: conversation.server_conversation_token,
|
||||
forked_from_conversation_token: conversation.forked_from_conversation_token,
|
||||
|
||||
@@ -71,6 +71,7 @@ pub fn convert_conversation_data_to_ai_conversation(
|
||||
|
||||
let agent_conversation_data = match restoration_mode {
|
||||
RestorationMode::Fork => AgentConversationData {
|
||||
agent_backend: Default::default(),
|
||||
server_conversation_token: None,
|
||||
conversation_usage_metadata: usage_metadata,
|
||||
reverted_action_ids: None,
|
||||
@@ -93,6 +94,7 @@ pub fn convert_conversation_data_to_ai_conversation(
|
||||
messages_summarized_up_to: 0,
|
||||
},
|
||||
RestorationMode::Continue => AgentConversationData {
|
||||
agent_backend: Default::default(),
|
||||
server_conversation_token: Some(
|
||||
metadata.server_conversation_token.as_str().to_string(),
|
||||
),
|
||||
|
||||
@@ -12,6 +12,7 @@ fn request_params_with_ask_user_question_enabled(ask_user_question_enabled: bool
|
||||
let model = LLMId::from("test-model");
|
||||
|
||||
RequestParams {
|
||||
terminal_view_id: None,
|
||||
input: vec![],
|
||||
conversation_token: None,
|
||||
forked_from_conversation_token: None,
|
||||
|
||||
@@ -62,8 +62,8 @@ use crate::ai::skills::SkillDescriptor;
|
||||
use crate::code_review::CodeReviewTelemetryEvent;
|
||||
use crate::notebooks::NotebookId;
|
||||
use crate::persistence::model::{
|
||||
AgentConversationData, ContextWindowSegment, ConversationUsageMetadata, ModelTokenUsage,
|
||||
PersistedAutoexecuteMode, ToolUsageMetadata,
|
||||
AcpConversationData, AgentBackend, AgentConversationData, ContextWindowSegment,
|
||||
ConversationUsageMetadata, ModelTokenUsage, PersistedAutoexecuteMode, ToolUsageMetadata,
|
||||
};
|
||||
use crate::persistence::ModelEvent;
|
||||
use crate::server::ids::ServerId;
|
||||
@@ -226,6 +226,9 @@ pub struct AIConversation {
|
||||
/// credits spent, token usage, and tool usage.
|
||||
conversation_usage_metadata: ConversationUsageMetadata,
|
||||
|
||||
/// Runtime responsible for executing this conversation.
|
||||
agent_backend: AgentBackend,
|
||||
|
||||
/// The server-generated unique "token" for this conversation.
|
||||
///
|
||||
/// This must be roundtripped to the server when sending follow-ups within a given conversation.
|
||||
@@ -351,6 +354,18 @@ pub(crate) fn artifact_from_fork_proto(
|
||||
|
||||
impl AIConversation {
|
||||
pub fn new(is_viewing_shared_session: bool, is_cli_agent_transcript: bool) -> Self {
|
||||
Self::new_with_agent_backend(
|
||||
is_viewing_shared_session,
|
||||
is_cli_agent_transcript,
|
||||
AgentBackend::default(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_with_agent_backend(
|
||||
is_viewing_shared_session: bool,
|
||||
is_cli_agent_transcript: bool,
|
||||
agent_backend: AgentBackend,
|
||||
) -> Self {
|
||||
let root_task = Task::new_optimistic_root();
|
||||
Self {
|
||||
id: AIConversationId::new(),
|
||||
@@ -364,6 +379,7 @@ impl AIConversation {
|
||||
status_error: None,
|
||||
has_opened_code_review: false,
|
||||
conversation_usage_metadata: ConversationUsageMetadata::default(),
|
||||
agent_backend,
|
||||
server_conversation_token: None,
|
||||
task_id: None,
|
||||
forked_from_server_conversation_token: None,
|
||||
@@ -522,6 +538,7 @@ impl AIConversation {
|
||||
};
|
||||
|
||||
let (
|
||||
agent_backend,
|
||||
server_conversation_token,
|
||||
forked_from_server_conversation_token,
|
||||
conversation_usage_metadata,
|
||||
@@ -571,6 +588,7 @@ impl AIConversation {
|
||||
AIConversationAutoexecuteMode::default()
|
||||
};
|
||||
(
|
||||
data.agent_backend,
|
||||
server_conversation_token,
|
||||
forked_from_server_conversation_token,
|
||||
conversation_usage_metadata,
|
||||
@@ -590,6 +608,7 @@ impl AIConversation {
|
||||
)
|
||||
} else {
|
||||
(
|
||||
AgentBackend::default(),
|
||||
None,
|
||||
None,
|
||||
ConversationUsageMetadata::default(),
|
||||
@@ -643,6 +662,7 @@ impl AIConversation {
|
||||
code_review: None,
|
||||
has_opened_code_review: false,
|
||||
conversation_usage_metadata,
|
||||
agent_backend,
|
||||
server_conversation_token,
|
||||
task_id: run_id.as_deref().and_then(|id| id.parse().ok()),
|
||||
forked_from_server_conversation_token,
|
||||
@@ -681,6 +701,25 @@ impl AIConversation {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn agent_backend(&self) -> &AgentBackend {
|
||||
&self.agent_backend
|
||||
}
|
||||
|
||||
/// Records a resumable ACP session ID.
|
||||
///
|
||||
/// Returns `false` when called for a native provider conversation.
|
||||
pub fn set_acp_session_id(&mut self, session_id: impl Into<String>) -> bool {
|
||||
let AgentBackend::Acp(AcpConversationData {
|
||||
session_id: current_session_id,
|
||||
..
|
||||
}) = &mut self.agent_backend
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
*current_session_id = Some(session_id.into());
|
||||
true
|
||||
}
|
||||
|
||||
pub fn current_context_tokens(&self) -> u32 {
|
||||
self.current_context_tokens
|
||||
}
|
||||
@@ -2179,8 +2218,10 @@ impl AIConversation {
|
||||
});
|
||||
}
|
||||
|
||||
self.server_conversation_token =
|
||||
Some(ServerConversationToken::new(init_event.conversation_id));
|
||||
if matches!(self.agent_backend, AgentBackend::Provider) {
|
||||
self.server_conversation_token =
|
||||
Some(ServerConversationToken::new(init_event.conversation_id));
|
||||
}
|
||||
let run_id = Some(init_event.run_id).filter(|s| !s.is_empty());
|
||||
self.task_id = run_id.as_deref().and_then(|id| id.parse().ok());
|
||||
Ok(())
|
||||
@@ -3782,6 +3823,7 @@ impl AIConversation {
|
||||
.filter_map(|task| task.source_for_persistence())
|
||||
.collect(),
|
||||
conversation_data: AgentConversationData {
|
||||
agent_backend: self.agent_backend.clone(),
|
||||
server_conversation_token: self
|
||||
.server_conversation_token
|
||||
.clone()
|
||||
|
||||
@@ -8,7 +8,7 @@ use super::{
|
||||
ConversationStatus, RestoreConversationError,
|
||||
};
|
||||
use crate::ai::artifacts::Artifact;
|
||||
use crate::persistence::model::AgentConversationData;
|
||||
use crate::persistence::model::{AcpConversationData, AgentBackend, AgentConversationData};
|
||||
|
||||
fn restored_conversation(conversation_data: Option<AgentConversationData>) -> AIConversation {
|
||||
AIConversation::new_restored(
|
||||
@@ -206,6 +206,57 @@ fn restored_conversation_uses_persisted_remote_child_marker() {
|
||||
assert!(conversation.is_remote_child());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_conversations_default_to_provider_backend() {
|
||||
let conversation = AIConversation::new(false, false);
|
||||
|
||||
assert_eq!(conversation.agent_backend(), &AgentBackend::Provider);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn acp_session_id_is_updated_only_for_acp_conversations() {
|
||||
let mut acp_conversation = AIConversation::new_with_agent_backend(
|
||||
false,
|
||||
false,
|
||||
AgentBackend::Acp(AcpConversationData {
|
||||
agent_id: "codex-acp".to_string(),
|
||||
launch_fingerprint: "launch-123".to_string(),
|
||||
session_id: None,
|
||||
}),
|
||||
);
|
||||
assert!(acp_conversation.set_acp_session_id("session-123"));
|
||||
assert_eq!(
|
||||
acp_conversation.agent_backend(),
|
||||
&AgentBackend::Acp(AcpConversationData {
|
||||
agent_id: "codex-acp".to_string(),
|
||||
launch_fingerprint: "launch-123".to_string(),
|
||||
session_id: Some("session-123".to_string()),
|
||||
})
|
||||
);
|
||||
|
||||
let mut provider_conversation = AIConversation::new(false, false);
|
||||
assert!(!provider_conversation.set_acp_session_id("ignored"));
|
||||
assert_eq!(
|
||||
provider_conversation.agent_backend(),
|
||||
&AgentBackend::Provider
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restored_conversation_uses_persisted_acp_backend() {
|
||||
let backend = AgentBackend::Acp(AcpConversationData {
|
||||
agent_id: "codex-acp".to_string(),
|
||||
launch_fingerprint: "launch-123".to_string(),
|
||||
session_id: Some("session-123".to_string()),
|
||||
});
|
||||
let conversation = restored_conversation(Some(AgentConversationData {
|
||||
agent_backend: backend.clone(),
|
||||
..Default::default()
|
||||
}));
|
||||
|
||||
assert_eq!(conversation.agent_backend(), &backend);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn child_conversation_detection_uses_parent_agent_id() {
|
||||
let conversation_data: AgentConversationData = serde_json::from_str(
|
||||
|
||||
@@ -33,6 +33,7 @@ pub(crate) fn redact_inputs(inputs: &mut [AIAgentInput]) {
|
||||
query,
|
||||
context,
|
||||
referenced_attachments,
|
||||
running_command,
|
||||
..
|
||||
} => {
|
||||
redact_secrets(query);
|
||||
@@ -40,6 +41,11 @@ pub(crate) fn redact_inputs(inputs: &mut [AIAgentInput]) {
|
||||
referenced_attachments
|
||||
.values_mut()
|
||||
.for_each(redact_attachment);
|
||||
if let Some(running_command) = running_command {
|
||||
redact_secrets(&mut running_command.command);
|
||||
redact_secrets(&mut running_command.grid_contents);
|
||||
redact_secrets(&mut running_command.cursor);
|
||||
}
|
||||
}
|
||||
AIAgentInput::AutoCodeDiffQuery { query, context, .. } => {
|
||||
redact_secrets(query);
|
||||
|
||||
Reference in New Issue
Block a user