refactor: extract format_wake_prompt/now_unix/build_row into serve_common (closes #169)
This commit is contained in:
parent
dfee0574a5
commit
e28b0a1dab
4 changed files with 103 additions and 172 deletions
89
hive-ag3nt/src/serve_common.rs
Normal file
89
hive-ag3nt/src/serve_common.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
//! Helpers shared between `hive-ag3nt` (agent) and `hive-m1nd` (manager)
|
||||
//! serve loops. Only pure functions with no wire-type dependency live here;
|
||||
//! request/response-flavored helpers (`requeue_inflight`, `ack_turn`, etc.)
|
||||
//! stay in each binary because they use different request enum variants.
|
||||
|
||||
use crate::events::Bus;
|
||||
use crate::mcp::REDELIVERY_HINT;
|
||||
use crate::turn::TurnOutcome;
|
||||
use crate::turn_stats::TurnStatRow;
|
||||
|
||||
/// Assemble the per-turn wake prompt string. The role/tools/etc. live in the
|
||||
/// system prompt; this is just the wake signal body. `unread` is the inbox
|
||||
/// depth after this message was popped. `redelivered` prepends a "may already
|
||||
/// be handled" banner.
|
||||
pub fn format_wake_prompt(from: &str, body: &str, unread: u64, redelivered: bool) -> String {
|
||||
let banner = if redelivered { REDELIVERY_HINT } else { "" };
|
||||
let pending = if unread == 0 {
|
||||
String::new()
|
||||
} else {
|
||||
format!(
|
||||
"\n\n({unread} more message(s) pending in your inbox — call `mcp__hyperhive__recv` \
|
||||
with `max: {unread}` to drain them all in one round-trip before acting.)"
|
||||
)
|
||||
};
|
||||
format!("{banner}Incoming message from `{from}`:\n---\n{body}\n---{pending}")
|
||||
}
|
||||
|
||||
/// Current time as a Unix timestamp (seconds). Returns 0 on any error.
|
||||
pub fn now_unix() -> i64 {
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.ok()
|
||||
.and_then(|d| i64::try_from(d.as_secs()).ok())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Assemble a `TurnStatRow` from the harness's per-turn state. Used by both
|
||||
/// the agent and manager serve loops — the shape is identical, only the
|
||||
/// post-turn count fetch helpers differ (and those stay in each binary).
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn build_row(
|
||||
started_at: i64,
|
||||
ended_at: i64,
|
||||
duration_ms: i64,
|
||||
model: String,
|
||||
wake_from: String,
|
||||
outcome: &TurnOutcome,
|
||||
bus: &Bus,
|
||||
open_threads_count: Option<u64>,
|
||||
open_reminders_count: Option<u64>,
|
||||
) -> TurnStatRow {
|
||||
let cost = bus.last_cost_usage().unwrap_or_default();
|
||||
let ctx = bus.last_ctx_usage().unwrap_or(cost);
|
||||
let tool_calls = bus.take_tool_calls();
|
||||
let tool_call_count: u64 = tool_calls.values().copied().sum();
|
||||
let tool_call_breakdown_json = if tool_calls.is_empty() {
|
||||
None
|
||||
} else {
|
||||
serde_json::to_string(&tool_calls).ok()
|
||||
};
|
||||
let (result_kind, note) = match outcome {
|
||||
TurnOutcome::Ok => ("ok", None),
|
||||
TurnOutcome::Compacted => ("compacted", None),
|
||||
TurnOutcome::PromptTooLong => ("prompt_too_long", None),
|
||||
TurnOutcome::RateLimited => ("rate_limited", None),
|
||||
TurnOutcome::Failed(e) => ("failed", Some(format!("{e:#}"))),
|
||||
};
|
||||
TurnStatRow {
|
||||
started_at,
|
||||
ended_at,
|
||||
duration_ms,
|
||||
model,
|
||||
wake_from,
|
||||
input_tokens: cost.input_tokens,
|
||||
output_tokens: cost.output_tokens,
|
||||
cache_read_input_tokens: cost.cache_read_input_tokens,
|
||||
cache_creation_input_tokens: cost.cache_creation_input_tokens,
|
||||
last_input_tokens: ctx.input_tokens,
|
||||
last_output_tokens: ctx.output_tokens,
|
||||
last_cache_read_input_tokens: ctx.cache_read_input_tokens,
|
||||
last_cache_creation_input_tokens: ctx.cache_creation_input_tokens,
|
||||
tool_call_count,
|
||||
tool_call_breakdown_json,
|
||||
open_threads_count,
|
||||
open_reminders_count,
|
||||
result_kind,
|
||||
note,
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue