Lot 33 : préchauffage (PREWARM) tenu par la façade

PREWARM ne garde plus la conversation au Go. La façade tient
l'historique : elle assemble et envoie la requête du prochain tour ; le
Go garde le compteur des requêtes en vol et ce qui l'annule.

Go (facade_prewarm.go) :
- /internal/v1/prewarm/check : moteur libre d'un vrai travail, loin de
  minuit, en bonne santé, slot visé ;
- /internal/v1/prewarm/begin : rechargement SLOT_PERSIST puis inscription
  comme LA requête en vol ; son annulation est relayée à la façade ;
- /internal/v1/prewarm/end : place rendue, cache repris noté ;
- /internal/v1/oai/begin : prewarm_keep pour l'étape qui le prolonge ;
- fin de tâche planifiée : le déclencheur part à la façade.

Rust (chat/prewarm.rs) :
- assemblage par les mêmes fonctions que le tour, sentinelle « . », un
  seul jeton, non streamé ; abstention (capacités inconnues, tour en
  cours, discussion vide, compaction attendue, assemblage inattendu) ;
- déclencheurs : fin de tour, fin de tâche, changement de discussion ou
  de projet (full : après 3 s) ;
- le tour garde le préchauffage qu'il prolonge (empreintes des messages
  et des options de rendu) et coupe tout de suite les autres ;
- télémétrie kind=prewarm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
This commit is contained in:
Claude committed 2026-10-16 02:52:44 +00:00
1 parent f79b74dac3
commit ff5c6d4571
21 files changed
+1457 -35

No files matched your search

+32 -1
View File
@@ -66,6 +66,8 @@ pub fn is_internal(path: &str) -> bool {
| "/internal/facade/v1/project/switch"
| "/internal/facade/v1/project/drop"
| "/internal/facade/v1/chat/tpl"
| "/internal/facade/v1/chat/prewarm"
| "/internal/facade/v1/chat/prewarm/cancel"
)
}
@@ -107,6 +109,27 @@ pub async fn serve_internal(chat: &Arc<ChatService>, req: Request<Incoming>) ->
None => send_json(StatusCode::OK, &json!({ "ok": false })),
}
}
// PREWARM (lot 33) : déclencheur venu du Go (fin d'une tâche
// planifiée), annulation relayée (une autre requête est partie).
"/internal/facade/v1/chat/prewarm" => {
let v = read_json(req.into_body()).await.unwrap_or_default();
let why = match str_of(&v, "why") {
crate::chat::prewarm::AFTER_TASK => crate::chat::prewarm::AFTER_TASK,
crate::chat::prewarm::SWITCH => crate::chat::prewarm::SWITCH,
_ => crate::chat::prewarm::TURN_END,
};
chat.prewarm_kick(why).await;
send_json(StatusCode::OK, &json!({ "ok": true }))
}
"/internal/facade/v1/chat/prewarm/cancel" => {
let v = read_json(req.into_body()).await.unwrap_or_default();
let id = v.get("id").and_then(Value::as_u64).unwrap_or(0);
let cancelled = chat.shared.prewarm.cancel_id(id);
send_json(
StatusCode::OK,
&json!({ "ok": true, "cancelled": cancelled }),
)
}
"/internal/facade/v1/project/switch" | "/internal/facade/v1/project/drop" => {
let v = read_json(req.into_body()).await.unwrap_or_default();
let slug = str_of(&v, "slug");
@@ -238,8 +261,16 @@ pub async fn serve(
// Corps illisible : champs vides, comme le `_ = Decode` du Go.
"/api/conversations/switch" => {
let v = read_json(body).await.unwrap_or_default();
let before = chat.sessions.conv().id();
match chat.sessions.switch(str_of(&v, "id")).await {
Ok(()) => ok(),
Ok(()) => {
// PREWARM : le préchauffage de l'ancienne est annulé ; avec
// « full », la nouvelle est préparée si on y reste.
if chat.sessions.conv().id() != before {
chat.prewarm_after_switch().await;
}
ok()
}
Err(e) => session_error(e),
}
}