Skip to content

Commit 5c99b33

Browse files
senamakelmedullabot
andcommitted
fix(harness): screen hosted extension content
Co-authored-by: Medulla <medulla@tinyhumans.ai>
1 parent 52f8949 commit 5c99b33

3 files changed

Lines changed: 75 additions & 21 deletions

File tree

‎crates/tinyagents-harness/src/context/stats.rs‎

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -69,21 +69,25 @@ pub fn estimate_context_tokens(messages: &[Message], tokenize: impl Fn(&str) ->
6969
messages
7070
.iter()
7171
.map(|message| {
72-
let visible = match message {
73-
Message::User(user) => user
74-
.content
75-
.iter()
76-
.filter_map(|block| match block {
77-
ContentBlock::Text(text) => Some(text.clone()),
78-
ContentBlock::Json(value) | ContentBlock::ProviderExtension(value) => {
79-
Some(value.to_string())
80-
}
81-
_ => None,
82-
})
83-
.collect::<Vec<_>>()
84-
.join("\n"),
85-
_ => message.text(),
72+
let content = match message {
73+
Message::System(message) => &message.content,
74+
Message::User(message) => &message.content,
75+
Message::Assistant(message) => &message.content,
76+
Message::Tool(message) => &message.content,
8677
};
78+
let visible = content
79+
.iter()
80+
.filter_map(|block| match block {
81+
ContentBlock::Text(text) | ContentBlock::Thinking { text, .. } => {
82+
Some(text.clone())
83+
}
84+
ContentBlock::Json(value) | ContentBlock::ProviderExtension(value) => {
85+
Some(value.to_string())
86+
}
87+
_ => None,
88+
})
89+
.collect::<Vec<_>>()
90+
.join("\n");
8791
tokenize(&visible)
8892
})
8993
.sum()

‎crates/tinyagents-harness/src/runtime/agent.rs‎

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,37 @@ impl<State: Send + Sync, Ctx: Send + Sync> AgentHarness<State, Ctx> {
229229
}
230230
}
231231

232+
/// Collects a hosted turn through the streaming driver while preserving the
233+
/// parent's exact capability bundle. Recursive streaming delegation uses
234+
/// this rather than the unary entry point so model deltas and delta
235+
/// middleware remain part of the shared parent event stream.
236+
pub(crate) async fn invoke_agent_streaming_with_host_capabilities(
237+
&self,
238+
host: crate::host::HostCapabilities<State>,
239+
request: AgentTurnRequest,
240+
context: RunContext<Ctx>,
241+
state: &State,
242+
) -> Result<AgentRun>
243+
where
244+
Ctx: 'static,
245+
State: 'static,
246+
{
247+
let stream = self
248+
.invoke_agent_stream_with_host_capabilities(host, request, context, state)
249+
.await?;
250+
futures::pin_mut!(stream);
251+
while let Some(item) = stream.next().await {
252+
match item {
253+
AgentStreamItem::Completed(run) => return Ok(*run),
254+
AgentStreamItem::Failed { error, .. } => return Err(TinyAgentsError::Model(error)),
255+
AgentStreamItem::Event(_) => {}
256+
}
257+
}
258+
Err(TinyAgentsError::Model(
259+
"hosted stream ended without a terminal result".to_string(),
260+
))
261+
}
262+
232263
/// Starts a hosted streaming turn.
233264
///
234265
/// The returned stream is the existing event projection, so host-driven
@@ -598,7 +629,10 @@ async fn screen_user_messages<State: Send + Sync>(
598629
return Err(TinyAgentsError::Validation(reason));
599630
}
600631
}
601-
} else if let tinyinference_llm::message::ContentBlock::Json(value) = block {
632+
} else if let tinyinference_llm::message::ContentBlock::Json(value)
633+
| tinyinference_llm::message::ContentBlock::ProviderExtension(value) =
634+
block
635+
{
602636
let rendered = value.to_string();
603637
match host
604638
.security

‎crates/tinyagents-harness/src/subagent/mod.rs‎

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ use crate::runtime::AgentHarness;
8888
use crate::tool::ToolDispatch;
8989
use tinyinference_llm::message::Message;
9090

91-
impl<State: Send + Sync + 'static, Ctx: Send + Sync> SubAgent<State, Ctx> {
91+
impl<State: Send + Sync + 'static, Ctx: Send + Sync + 'static> SubAgent<State, Ctx> {
9292
/// Creates a sub-agent wrapping `harness` with a stable `name` and
9393
/// `description`.
9494
pub fn new(
@@ -306,9 +306,25 @@ impl<State: Send + Sync + 'static, Ctx: Send + Sync> SubAgent<State, Ctx> {
306306
// capability bundle. The child harness's installed host (including
307307
// no host at all) is intentionally irrelevant here: allowing it
308308
// to decide policy would make delegation authorization bypassable.
309-
self.harness
310-
.invoke_agent_with_host_capabilities(authority.host.clone(), request, ctx, state)
311-
.await?
309+
if streaming {
310+
self.harness
311+
.invoke_agent_streaming_with_host_capabilities(
312+
authority.host.clone(),
313+
request,
314+
ctx,
315+
state,
316+
)
317+
.await?
318+
} else {
319+
self.harness
320+
.invoke_agent_with_host_capabilities(
321+
authority.host.clone(),
322+
request,
323+
ctx,
324+
state,
325+
)
326+
.await?
327+
}
312328
} else if streaming {
313329
self.harness
314330
.invoke_streaming_in_context(state, ctx, messages)
@@ -486,7 +502,7 @@ impl<State: Send + Sync, Ctx: Send + Sync> SubAgentSession<State, Ctx> {
486502
}
487503
}
488504

489-
impl<State: Send + Sync + 'static, Ctx: Send + Sync> SubAgentTool<State, Ctx> {
505+
impl<State: Send + Sync + 'static, Ctx: Send + Sync + 'static> SubAgentTool<State, Ctx> {
490506
/// Default JSON Schema for a sub-agent tool: an object with one required
491507
/// string field named [`SUBAGENT_INPUT_FIELD`].
492508
fn default_parameters() -> Value {
@@ -620,7 +636,7 @@ impl<State: Send + Sync + 'static, Ctx: Send + Sync> SubAgentTool<State, Ctx> {
620636
impl<State, Ctx> ToolDispatch<State, Ctx> for SubAgentTool<State, Ctx>
621637
where
622638
State: Send + Sync + 'static,
623-
Ctx: Send + Sync,
639+
Ctx: Send + Sync + 'static,
624640
{
625641
fn tool(&self) -> Arc<dyn tinytools::Tool> {
626642
Arc::new(SubAgentToolDeclaration {

0 commit comments

Comments
 (0)