Skip to content
12 changes: 12 additions & 0 deletions src/common/agent_prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,16 @@ pub struct AgentPromptRequest {
/// Optional attachments (blocks, files, etc.) referenced in the prompt.
#[serde(default)]
pub attachments: Vec<AgentAttachment>,

/// Idempotency key for a bootstrap request: set, together with a
/// `server_conversation_token` of `None`, when the request comes from an authenticated
/// server-side injection that must create or reuse exactly one conversation (REMOTE-2661).
/// `None` for an ordinary agent prompt request from a live viewer.
///
/// The key exists so the injector can correlate a retry with its original attempt.
/// Deduplication belongs to the session-sharing service, which claims the key before
/// injecting, so a retry of a key it has already answered does not reach the sharer. The
/// sharer is not required to keep per-key state.
#[serde(default)]
pub idempotency_key: Option<String>,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super-nit: Can we make it clear that this is specifically used for bootstrap in the name? The ai_task associated with this run itself will also have an idempotency key but that's not the one we are using here AFAICT.

}
27 changes: 24 additions & 3 deletions src/sharer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ use crate::common::{
CommandExecutionRequestId, ControlAction, ControlActionFailureReason, ControlActionRequestId,
FeatureSupport, InputOperationId, InputReplicaId, InputUpdate, InputUpdateFailureReason,
OrderedTerminalEvent, ParticipantId, ParticipantList, ParticipantPresenceUpdate, Role,
RoleRequestId, RoleRequestResponse, Selection, SelectionUpdate, SessionId, SessionSecret,
TelemetryContext, UniversalDeveloperInputContext, UniversalDeveloperInputContextUpdate, UserID,
WindowSize, WriteToPtyFailureReason, WriteToPtyRequestId,
RoleRequestId, RoleRequestResponse, Selection, SelectionUpdate, ServerConversationToken,
SessionId, SessionSecret, TelemetryContext, UniversalDeveloperInputContext,
UniversalDeveloperInputContextUpdate, UserID, WindowSize, WriteToPtyFailureReason,
WriteToPtyRequestId,
};

use super::common::Scrollback;
Expand Down Expand Up @@ -579,6 +580,26 @@ pub enum UpstreamMessage {
id: AgentPromptRequestId,
participant_id: ParticipantId,
reason: AgentPromptFailureReason,
/// Echoes the originating request's idempotency key when it was a bootstrap request
/// (REMOTE-2661), so the server can persist the rejection under the same key a
/// caller's retry will look up. `None` for an ordinary rejection.
#[serde(default)]
idempotency_key: Option<String>,
},

/// Reports the conversation the sharer created or reused for a bootstrap agent prompt
/// request — one that carried an `idempotency_key` and no `server_conversation_token`
/// (REMOTE-2661). Never sent for an ordinary agent prompt request, since the server
/// already knows that conversation's token by other means. The server must persist this
/// before the request may be treated as delivered: a lost acknowledgement here is what
/// would otherwise let a retry start a second, independent conversation.
AcknowledgeAgentPromptRequest {
id: AgentPromptRequestId,
participant_id: ParticipantId,
server_conversation_token: ServerConversationToken,
/// Echoes the originating request's idempotency key, which the server correlates
/// against its own pending wait for this bootstrap's result.
idempotency_key: String,
},

/// The given control action request was denied for the specified `reason`.
Expand Down