feat(tool): a streaming resolver reports its own status
The streaming progress seam grows from a bare output callback to ToolProgress: output chunks as before, plus an optional ToolStatus — the phase a tool's inner work is in and, when the tool runs model calls of its own, their cumulative spend and the model they ran on. The engine stamps the call's id on it and the sink receives it as an ephemeral ToolStatus event, replacing any earlier one for the call; status is screen-facing, never history. The subagent tool is the first emitter: its inner turn's lifecycle becomes phase transitions (thinking, running, writing output) and a running token total. Every sink decides what a status is worth: the cloud wire forwards it, ACP and the claw have no slot and drop it.
This commit is contained in:
1 parent
7f6c13f608
commit
db75f7c4b8
2 files changed
+7
-5
No files matched your search
@@ -374,7 +374,7 @@ impl IdentityExec {
|
||||
async fn exec(
|
||||
&self,
|
||||
call: ToolCall,
|
||||
progress: &mut (dyn for<'a> FnMut(&'a str) + Send),
|
||||
progress: &mut dyn rhema_core::ToolProgress,
|
||||
) -> ToolResult {
|
||||
let id = match call.args.get("id").and_then(Value::as_str) {
|
||||
Some(id) => id.trim().to_owned(),
|
||||
@@ -477,7 +477,7 @@ impl IdentityExec {
|
||||
let sink = if is_stderr { &mut stderr } else { &mut stdout };
|
||||
sink.extend_from_slice(line.as_bytes());
|
||||
sink.push(b'\n');
|
||||
progress(&format!("{line}\n"));
|
||||
progress.output(&format!("{line}\n"));
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
@@ -565,14 +565,14 @@ impl ToolResolver for IdentityExec {
|
||||
}
|
||||
|
||||
async fn resolve(&self, call: ToolCall, _ctx: &ToolContext) -> ToolResult {
|
||||
self.exec(call, &mut |_| {}).await
|
||||
self.exec(call, &mut rhema_core::OutputOnly(|_| {})).await
|
||||
}
|
||||
|
||||
async fn resolve_streaming(
|
||||
&self,
|
||||
call: ToolCall,
|
||||
ctx: &ToolContext,
|
||||
progress: &mut (dyn for<'a> FnMut(&'a str) + Send),
|
||||
progress: &mut dyn rhema_core::ToolProgress,
|
||||
) -> ToolResult {
|
||||
if ctx.cancelled.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
return ToolResult::failure(call.id, "cancelled");
|
||||
|
||||
@@ -566,9 +566,11 @@ impl EventSink for HubSink {
|
||||
}
|
||||
}
|
||||
// Step bookkeeping is the engine's business, not the wire's;
|
||||
// a running tool's live output lands with its result.
|
||||
// a running tool's live output lands with its result, and a
|
||||
// streaming tool's status has no turn-event shape to ride.
|
||||
ModelEvent::StepStart { .. }
|
||||
| ModelEvent::ToolOutputDelta { .. }
|
||||
| ModelEvent::ToolStatus { .. }
|
||||
| ModelEvent::StepEnd { .. } => return,
|
||||
};
|
||||
self.hub.emit(&self.key, turn);
|
||||
|
||||
Reference in new issue
Block a user