feat(backend): modèle unifié streaming/progress/events provider-agnostic + pont app-tauri — foundation #156 (QA verte)
This commit is contained in:
@ -15,7 +15,10 @@ use std::sync::{Arc, Mutex};
|
||||
use async_trait::async_trait;
|
||||
use serde_json::Value;
|
||||
|
||||
use domain::ports::{AgentSession, AgentSessionError, ReplyEvent, ReplyStream};
|
||||
use domain::ports::{
|
||||
AgentSession, AgentSessionError, ReplyEvent, ReplyProgress, ReplyProgressKind,
|
||||
ReplyProgressSource, ReplyProgressStage, ReplyStream,
|
||||
};
|
||||
use domain::sandbox::{SandboxEnforcer, SandboxPlan};
|
||||
use domain::SessionId;
|
||||
|
||||
@ -81,7 +84,28 @@ pub fn parse_event(line: &str) -> Result<ParsedLine, AgentSessionError> {
|
||||
// Début/fin de tour côté moteur : pas de contenu, mais preuve de vivacité ⇒
|
||||
// battement de cœur non terminal (readiness/heartbeat lot 1). Le `Final` vient
|
||||
// toujours de l'`agent_message`, jamais de `turn.completed`.
|
||||
Some("turn.started") | Some("turn.completed") => events.push(ReplyEvent::Heartbeat),
|
||||
Some("turn.started") => {
|
||||
events.push(ReplyEvent::Progress {
|
||||
progress: provider_progress(
|
||||
ReplyProgressKind::Turn,
|
||||
ReplyProgressStage::Started,
|
||||
"tour démarré",
|
||||
"turn.started",
|
||||
),
|
||||
});
|
||||
events.push(ReplyEvent::Heartbeat);
|
||||
}
|
||||
Some("turn.completed") => {
|
||||
events.push(ReplyEvent::Progress {
|
||||
progress: provider_progress(
|
||||
ReplyProgressKind::Turn,
|
||||
ReplyProgressStage::Completed,
|
||||
"tour terminé côté provider",
|
||||
"turn.completed",
|
||||
),
|
||||
});
|
||||
events.push(ReplyEvent::Heartbeat);
|
||||
}
|
||||
Some("item.completed") => {
|
||||
if let Some(item) = value.get("item") {
|
||||
match item.get("type").and_then(Value::as_str) {
|
||||
@ -99,9 +123,20 @@ pub fn parse_event(line: &str) -> Result<ParsedLine, AgentSessionError> {
|
||||
});
|
||||
}
|
||||
// reasoning / command / tout autre item ⇒ activité (label = type).
|
||||
Some(kind) => events.push(ReplyEvent::ToolActivity {
|
||||
label: kind.to_owned(),
|
||||
}),
|
||||
Some(kind) => {
|
||||
events.push(ReplyEvent::Progress {
|
||||
progress: provider_progress(
|
||||
ReplyProgressKind::Tool,
|
||||
ReplyProgressStage::Completed,
|
||||
kind,
|
||||
"item.completed",
|
||||
)
|
||||
.with_tool_name(kind.to_owned()),
|
||||
});
|
||||
events.push(ReplyEvent::ToolActivity {
|
||||
label: kind.to_owned(),
|
||||
});
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
@ -116,6 +151,16 @@ pub fn parse_event(line: &str) -> Result<ParsedLine, AgentSessionError> {
|
||||
})
|
||||
}
|
||||
|
||||
fn provider_progress(
|
||||
kind: ReplyProgressKind,
|
||||
stage: ReplyProgressStage,
|
||||
label: impl Into<String>,
|
||||
native_event: impl Into<String>,
|
||||
) -> ReplyProgress {
|
||||
ReplyProgress::new(ReplyProgressSource::ProviderNative, kind, stage, label)
|
||||
.with_provider_event("codex", native_event)
|
||||
}
|
||||
|
||||
const CODEX_ERROR_FALLBACK: &str = "Codex a renvoyé une erreur.";
|
||||
|
||||
fn non_blank_string(value: Option<&Value>) -> Option<String> {
|
||||
|
||||
Reference in New Issue
Block a user