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
40 changes: 20 additions & 20 deletions src-tauri/src/agent_sessions/cli/agent_core_bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ use agent_core::interaction::plan_approval::{self, PlanResolution};
use agent_core::session::AgentExecMode;
use agent_core::tools::names as tool_names;

use super::commands::{cli_agent_create, cli_agent_delete, cli_agent_message, cli_agent_run};
use super::commands::{
cli_agent_create, cli_agent_delete, cli_agent_message, cli_agent_run, CliMessageRequest,
CliRunRequest,
};
use super::persistence::{self, CreateCodeSessionParams};

fn run(
Expand Down Expand Up @@ -59,14 +62,14 @@ fn run(
let created_at = session.created_at.clone();

if !params.user_input.trim().is_empty() {
if let Err(err) = cli_agent_run(
session_id.clone(),
params.user_input,
None,
params.ide_context,
params.mode,
params.images,
)
if let Err(err) = cli_agent_run(CliRunRequest {
session_id: session_id.clone(),
user_input: params.user_input,
ide_context: params.ide_context,
mode: params.mode,
images: params.images,
..Default::default()
})
.await
{
tracing::warn!(
Expand Down Expand Up @@ -193,17 +196,14 @@ fn respond_plan_approval(
edited_marker = if edited { " (edited)" } else { "" },
);

cli_agent_message(
params.session_id,
synthetic_content,
params.model,
params.account_id,
None,
Some(AgentExecMode::Build.as_str().to_string()),
None,
None,
None,
)
cli_agent_message(CliMessageRequest {
session_id: params.session_id,
content: synthetic_content,
model: params.model,
account_id: params.account_id,
mode: Some(AgentExecMode::Build.as_str().to_string()),
..Default::default()
})
.await
.map(|_| ())
})
Expand Down
150 changes: 101 additions & 49 deletions src-tauri/src/agent_sessions/cli/commands/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use super::super::persistence;
use super::super::session_runner;
use super::super::types::{KeySource, SessionStatus};
use agent_core::session::IdeContext;
use serde::Serialize;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
Expand All @@ -16,6 +16,68 @@ pub struct CliRunReceipt {
pub status: SessionStatus,
}

/// Start one CLI turn on an existing session row.
///
/// `Default` is derived so callers that only drive a plain prompt (the
/// agent-core bridge, the debug runtime probes) can name just the fields
/// they mean instead of padding the call with positional `None`s.
#[derive(Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CliRunRequest {
pub session_id: String,
pub user_input: String,
pub cli_resume_id: Option<String>,
pub ide_context: Option<IdeContext>,
pub mode: Option<String>,
pub images: Option<Vec<String>>,
}

/// Send a follow-up message on an existing session, optionally switching the
/// model/account first. `turn_intent_id` / `client_message_id` are optional:
/// the frontend pre-assigns them so its optimistic user row and the persisted
/// intent share one identity, while callers without a UI row omit them.
#[derive(Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CliMessageRequest {
pub session_id: String,
pub content: String,
pub model: Option<String>,
pub account_id: Option<String>,
pub ide_context: Option<IdeContext>,
pub mode: Option<String>,
pub images: Option<Vec<String>>,
pub turn_intent_id: Option<String>,
pub client_message_id: Option<String>,
}

/// Identity of a single turn. `turn_intent_id` keys the `turn_intents` row and
/// every `status_changed` broadcast for the turn; `client_message_id`
/// reconciles the frontend's optimistic user message with the persisted one.
#[derive(Debug)]
struct TurnIdentity {
turn_intent_id: String,
client_message_id: String,
}

impl TurnIdentity {
/// Mint a fresh pair for a turn no client pre-assigned ids for.
fn generate() -> Self {
Self::from_client(None, None)
}

/// Adopt whichever halves the client supplied, minting the rest.
fn from_client(turn_intent_id: Option<String>, client_message_id: Option<String>) -> Self {
Self {
turn_intent_id: turn_intent_id.unwrap_or_else(new_id),
client_message_id: client_message_id.unwrap_or_else(new_id),
}
}
}

fn new_id() -> String {
uuid::Uuid::new_v4().to_string()
}

/// Prepend IDE context (open files, git status, etc.) to the user prompt
/// so external CLI agents are aware of the user's IDE state.
fn inject_ide_context_into_prompt(user_input: &str, ide_context: Option<&IdeContext>) -> String {
Expand Down Expand Up @@ -45,38 +107,27 @@ pub async fn cli_agent_tui_release(session_id: String) -> Result<bool, String> {

/// Run a code session (spawn CLI agent in background).
#[tauri::command]
pub async fn cli_agent_run(
session_id: String,
user_input: String,
cli_resume_id: Option<String>,
ide_context: Option<IdeContext>,
mode: Option<String>,
images: Option<Vec<String>>,
) -> Result<(), String> {
cli_agent_run_internal(
pub async fn cli_agent_run(request: CliRunRequest) -> Result<(), String> {
run_turn(request, TurnIdentity::generate()).await
}

/// Shared turn body behind both `cli_agent_run` and `cli_agent_message`:
/// persist acceptance under the registry lock, broadcast `running`, then spawn
/// the background runner.
async fn run_turn(request: CliRunRequest, turn: TurnIdentity) -> Result<(), String> {
let CliRunRequest {
session_id,
user_input,
cli_resume_id,
ide_context,
mode,
images,
uuid::Uuid::new_v4().to_string(),
uuid::Uuid::new_v4().to_string(),
)
.await
}
} = request;
let TurnIdentity {
turn_intent_id,
client_message_id,
} = turn;

#[allow(clippy::too_many_arguments)]
async fn cli_agent_run_internal(
session_id: String,
user_input: String,
cli_resume_id: Option<String>,
ide_context: Option<IdeContext>,
mode: Option<String>,
images: Option<Vec<String>>,
turn_intent_id: String,
client_message_id: String,
) -> Result<(), String> {
tracing::info!(
session_id = %session_id,
has_resume_id = cli_resume_id.is_some(),
Expand Down Expand Up @@ -213,20 +264,19 @@ async fn cli_agent_run_internal(
/// If `model` or `account_id` is provided, updates the session config before
/// re-running so the CLI uses the newly selected model/key.
#[tauri::command]
#[allow(clippy::too_many_arguments)]
pub async fn cli_agent_message(
session_id: String,
content: String,
model: Option<String>,
account_id: Option<String>,
ide_context: Option<IdeContext>,
mode: Option<String>,
images: Option<Vec<String>>,
turn_intent_id: Option<String>,
client_message_id: Option<String>,
) -> Result<CliRunReceipt, String> {
let turn_intent_id = turn_intent_id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
let client_message_id = client_message_id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
pub async fn cli_agent_message(request: CliMessageRequest) -> Result<CliRunReceipt, String> {
let CliMessageRequest {
session_id,
content,
model,
account_id,
ide_context,
mode,
images,
turn_intent_id,
client_message_id,
} = request;
let turn = TurnIdentity::from_client(turn_intent_id, client_message_id);
tracing::info!(
session_id = %session_id,
has_model_override = model.is_some(),
Expand Down Expand Up @@ -377,15 +427,17 @@ pub async fn cli_agent_message(

// Re-run the session with the new message
tracing::info!(session_id = %session_id, "cli_agent_message: dispatching rerun");
cli_agent_run_internal(
session_id.clone(),
content,
cli_resume_id,
ide_context,
mode,
images,
turn_intent_id.clone(),
client_message_id,
let turn_intent_id = turn.turn_intent_id.clone();
run_turn(
CliRunRequest {
session_id: session_id.clone(),
user_input: content,
cli_resume_id,
ide_context,
mode,
images,
},
turn,
)
.await?;
Ok(CliRunReceipt {
Expand Down
Loading
Loading