Watch
0
0
Fork
You've already forked hyperhive
0

hive-runtime: report an empty ACP end_turn as a turn error (#4819)

opencode answers `end_turn` even when its provider rejected the request
with a non-retryable error (401, 400), and forwards nothing over ACP, so
the turn looked like an empty success. A turn that ends with `end_turn`,
no event, no `usage_update` and no `usage` in the prompt response now
fails with `AcpError::EmptyEndTurn`.

A turn that really produced nothing and reported no usage is reported
the same way; that false positive is accepted.

The test agent's plain `end_turn` replies now carry a response `usage`,
so its ordinary turns stay successes; a response `usage` feeds only cost
telemetry, not the compaction watermark. A new `blank` mode keeps the
empty reply for the error case.
This commit is contained in:
atlas 2026-09-30 09:11:36 +02:00
commit f9c6a56ab9
2 changed files with 55 additions and 6 deletions

View file

@ -21,6 +21,7 @@ pub(super) struct StreamMapper {
thought: String,
tools: HashMap<String, ToolCall>,
usage: Option<(u64, u64)>,
emitted: bool,
}
struct ToolCall {
@ -39,6 +40,7 @@ impl StreamMapper {
thought: String::new(),
tools: HashMap::new(),
usage: None,
emitted: false,
}
}
@ -69,6 +71,7 @@ impl StreamMapper {
}
_ => {}
}
self.emitted |= !out.is_empty();
out
}
@ -82,9 +85,20 @@ impl StreamMapper {
for (id, tool) in pending {
out.push(tool_use(&id, &tool.name, &tool.input));
}
self.emitted |= !out.is_empty();
out
}
/// Whether the turn has emitted any event.
pub(super) fn has_content(&self) -> bool {
self.emitted
}
/// Whether the turn has had a `usage_update`.
pub(super) fn has_usage_update(&self) -> bool {
self.usage.is_some()
}
/// The turn's telemetry: context from the last `usage_update`, cost from
/// the `session/prompt` response's `usage` when the agent sends one.
pub(super) fn telemetry(&self, response: &Value, model: Option<&str>) -> Telemetry {