feat(backend): modèle unifié streaming/progress/events provider-agnostic + pont app-tauri — foundation #156 (QA verte)

This commit is contained in:
2026-08-06 13:13:17 +02:00
parent 40fa551f32
commit 918116664c
14 changed files with 630 additions and 59 deletions

View File

@ -17,6 +17,7 @@ use application::{
ListProjectsOutput, LiveSessionKind, LiveSessionSnapshot, OpenProjectOutput, ProjectWorkState,
StopLiveAgentOutput, TicketWorkSource, TicketWorkStatus, TurnPage, TurnSource, TurnView,
};
use domain::ports::{ReplyProgress, ReplyProgressKind, ReplyProgressSource, ReplyProgressStage};
use domain::{
AgentBusyState, PageCursor, PageDirection, Project, ProjectId, ProjectSystemPermissions,
ResolvedAgentSystemPermissions, SystemPermissionSet, TurnRole,
@ -3046,6 +3047,119 @@ impl From<domain::ChatAttachment> for ChatAttachmentDto {
}
}
/// DTO source of a canonical non-terminal reply progress event.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ReplyProgressSourceDto {
/// Native provider event.
ProviderNative,
/// Local IdeA observability event.
IdeaLocal,
}
impl From<ReplyProgressSource> for ReplyProgressSourceDto {
fn from(value: ReplyProgressSource) -> Self {
match value {
ReplyProgressSource::ProviderNative => Self::ProviderNative,
ReplyProgressSource::IdeaLocal => Self::IdeaLocal,
}
}
}
/// DTO kind of a canonical non-terminal reply progress event.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ReplyProgressKindDto {
/// Turn lifecycle/progress.
Turn,
/// Assistant message/progress.
Message,
/// Provider tool activity.
Tool,
/// IdeA MCP/tool activity.
Mcp,
/// Unclassified event.
Other,
}
impl From<ReplyProgressKind> for ReplyProgressKindDto {
fn from(value: ReplyProgressKind) -> Self {
match value {
ReplyProgressKind::Turn => Self::Turn,
ReplyProgressKind::Message => Self::Message,
ReplyProgressKind::Tool => Self::Tool,
ReplyProgressKind::Mcp => Self::Mcp,
ReplyProgressKind::Other => Self::Other,
}
}
}
/// DTO stage of a canonical non-terminal reply progress event.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ReplyProgressStageDto {
/// Activity started.
Started,
/// Incremental update.
Delta,
/// Activity completed.
Completed,
/// Informational point event.
Info,
}
impl From<ReplyProgressStage> for ReplyProgressStageDto {
fn from(value: ReplyProgressStage) -> Self {
match value {
ReplyProgressStage::Started => Self::Started,
ReplyProgressStage::Delta => Self::Delta,
ReplyProgressStage::Completed => Self::Completed,
ReplyProgressStage::Info => Self::Info,
}
}
}
/// Canonical, provider-agnostic non-terminal progress/event DTO.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ReplyProgressDto {
/// Event source.
pub source: ReplyProgressSourceDto,
/// Canonical event family.
pub kind: ReplyProgressKindDto,
/// Canonical stage.
pub stage: ReplyProgressStageDto,
/// Short display label.
pub label: String,
/// Optional display excerpt.
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
/// Optional provider identity.
#[serde(skip_serializing_if = "Option::is_none")]
pub provider: Option<String>,
/// Optional native provider event name.
#[serde(skip_serializing_if = "Option::is_none")]
pub native_event: Option<String>,
/// Optional tool/MCP name.
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_name: Option<String>,
}
impl From<ReplyProgress> for ReplyProgressDto {
fn from(value: ReplyProgress) -> Self {
Self {
source: value.source.into(),
kind: value.kind.into(),
stage: value.stage.into(),
label: value.label,
text: value.text,
provider: value.provider,
native_event: value.native_event,
tool_name: value.tool_name,
}
}
}
/// One incremental chunk of a structured agent reply, streamed over the chat
/// session's adapter-owned channel. The serialised wire twin of a
/// [`domain::ports::ReplyEvent`]: the `agent_send` pump maps each turn event to
@ -3077,6 +3191,12 @@ pub enum ReplyChunk {
/// The human-readable activity label.
label: String,
},
/// Canonical non-terminal progress/event emitted before `Final`.
#[serde(rename_all = "camelCase")]
Progress {
/// Canonical event payload.
progress: ReplyProgressDto,
},
/// The deterministic end-of-turn chunk carrying the aggregated final content.
#[serde(rename_all = "camelCase")]
Final {