steerable-agent-runtime-native 0.6.31__tar.gz → 0.6.32__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/Cargo.lock +1 -1
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/Cargo.toml +1 -1
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/PKG-INFO +1 -1
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/pyproject.toml +1 -1
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/engine.rs +324 -266
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/tests/test_loop.rs +121 -2
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/.gitignore +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/README.md +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/anthropic_http.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/anthropic_wire.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/budget.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/compat.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/errors.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/executor.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/gemini_http.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/gemini_wire.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/hooks.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/json_py.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/lib.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/model_catalog.json +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/model_resolve.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/openai_http.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/openai_wire.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/presets.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/pricing.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/provider.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/ptc_js.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/python.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/responses_http.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/responses_wire.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/run_code.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/skills.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/sse_http.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/todo.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/tokens.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/types.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/web.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/web_html.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/web_search.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/wire.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/write_lease.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/tests/test_llm_wire.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/tests/test_ptc_js.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/tests/test_run_code.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/tests/test_todo.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/tests/test_web.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/tests/test_write_lease.rs +0 -0
- {steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/uv.lock +0 -0
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/engine.rs
RENAMED
|
@@ -467,19 +467,41 @@ impl<P: LLMProvider, E: ToolExecutor, H: LoopHooks> CoreLoop<P, E, H> {
|
|
|
467
467
|
self.tools.clone()
|
|
468
468
|
};
|
|
469
469
|
let mut llm_started;
|
|
470
|
-
let
|
|
470
|
+
let mut content = String::new();
|
|
471
|
+
let mut reasoning = String::new();
|
|
472
|
+
let mut reasoning_details = Vec::new();
|
|
473
|
+
let mut tool_calls: Vec<ToolCall> = Vec::new();
|
|
474
|
+
let mut token_exhausted;
|
|
475
|
+
let mut stream_cancelled;
|
|
476
|
+
let mut stream_soft_cut;
|
|
477
|
+
let mut stream_wrap_cut;
|
|
478
|
+
let mut stream_idle_cut;
|
|
479
|
+
let mut stream_failed = false;
|
|
480
|
+
// Mid-stream errors share on_request_error with stream_with_options
|
|
481
|
+
// failures. Retry drops this attempt's partial text and re-streams;
|
|
482
|
+
// Fail keeps it and ends the run.
|
|
483
|
+
'stream_attempt: loop {
|
|
484
|
+
content.clear();
|
|
485
|
+
reasoning.clear();
|
|
486
|
+
reasoning_details.clear();
|
|
487
|
+
tool_calls.clear();
|
|
488
|
+
token_exhausted = false;
|
|
489
|
+
stream_cancelled = false;
|
|
490
|
+
stream_soft_cut = false;
|
|
491
|
+
stream_wrap_cut = false;
|
|
492
|
+
stream_idle_cut = None;
|
|
471
493
|
llm_started = std::time::Instant::now();
|
|
472
494
|
emit(LoopEvent::new(
|
|
473
495
|
"llm_request",
|
|
474
496
|
json!({ "round": round_index, "attempt": llm_attempt }),
|
|
475
497
|
));
|
|
476
498
|
self.observe_history(&history);
|
|
477
|
-
match self
|
|
499
|
+
let mut chunks = match self
|
|
478
500
|
.provider
|
|
479
501
|
.stream_with_options(&history, &request_tools, step_tool_choice.as_deref())
|
|
480
502
|
.await
|
|
481
503
|
{
|
|
482
|
-
Ok(chunks) =>
|
|
504
|
+
Ok(chunks) => chunks,
|
|
483
505
|
Err(error) => {
|
|
484
506
|
let message = error.message.clone();
|
|
485
507
|
let kind = error.kind.as_str();
|
|
@@ -508,12 +530,17 @@ impl<P: LLMProvider, E: ToolExecutor, H: LoopHooks> CoreLoop<P, E, H> {
|
|
|
508
530
|
history = rewrite;
|
|
509
531
|
self.observe_history(&history);
|
|
510
532
|
}
|
|
533
|
+
let retry_reason = if reason.is_empty() {
|
|
534
|
+
message.clone()
|
|
535
|
+
} else {
|
|
536
|
+
reason
|
|
537
|
+
};
|
|
511
538
|
emit(LoopEvent::new(
|
|
512
539
|
"hook_action",
|
|
513
540
|
json!({
|
|
514
541
|
"hook": "on_request_error",
|
|
515
542
|
"action": "retry",
|
|
516
|
-
"reason":
|
|
543
|
+
"reason": retry_reason,
|
|
517
544
|
"delayMs": delay_ms,
|
|
518
545
|
"compacted": compacted,
|
|
519
546
|
"round": round_index,
|
|
@@ -524,6 +551,7 @@ impl<P: LLMProvider, E: ToolExecutor, H: LoopHooks> CoreLoop<P, E, H> {
|
|
|
524
551
|
.await;
|
|
525
552
|
}
|
|
526
553
|
llm_attempt += 1;
|
|
554
|
+
continue 'stream_attempt;
|
|
527
555
|
}
|
|
528
556
|
RequestErrorAction::Fail { reason } => {
|
|
529
557
|
let failure_reason = if reason.is_empty() {
|
|
@@ -555,249 +583,323 @@ impl<P: LLMProvider, E: ToolExecutor, H: LoopHooks> CoreLoop<P, E, H> {
|
|
|
555
583
|
0.9,
|
|
556
584
|
&ctx,
|
|
557
585
|
));
|
|
558
|
-
|
|
586
|
+
stream_failed = true;
|
|
587
|
+
break 'stream_attempt;
|
|
559
588
|
}
|
|
560
589
|
}
|
|
561
590
|
}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
let mut stream_progress_accounted = false;
|
|
581
|
-
let mut last_chunk_at = llm_started;
|
|
582
|
-
let stream_budget = if wrap_up {
|
|
583
|
-
self.config
|
|
584
|
-
.wrap_up_tool_timeout_ms
|
|
585
|
-
.map(|limit| (limit.max(50), true))
|
|
586
|
-
} else {
|
|
587
|
-
self.config.soft_timeout_ms.map(|limit| {
|
|
588
|
-
let elapsed =
|
|
589
|
-
u64::try_from(run_started.elapsed().as_millis()).unwrap_or(u64::MAX);
|
|
590
|
-
(limit.saturating_sub(elapsed).max(50), false)
|
|
591
|
-
})
|
|
592
|
-
};
|
|
591
|
+
};
|
|
592
|
+
let mut stream_provider_error = None;
|
|
593
|
+
let mut stream_active_ms = 0_u64;
|
|
594
|
+
let mut stream_chars = 0_u64;
|
|
595
|
+
let mut stream_has_tool = false;
|
|
596
|
+
let mut stream_progress_accounted = false;
|
|
597
|
+
let mut last_chunk_at = llm_started;
|
|
598
|
+
let stream_budget = if wrap_up {
|
|
599
|
+
self.config
|
|
600
|
+
.wrap_up_tool_timeout_ms
|
|
601
|
+
.map(|limit| (limit.max(50), true))
|
|
602
|
+
} else {
|
|
603
|
+
self.config.soft_timeout_ms.map(|limit| {
|
|
604
|
+
let elapsed =
|
|
605
|
+
u64::try_from(run_started.elapsed().as_millis()).unwrap_or(u64::MAX);
|
|
606
|
+
(limit.saturating_sub(elapsed).max(50), false)
|
|
607
|
+
})
|
|
608
|
+
};
|
|
593
609
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
if is_wrap {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
610
|
+
loop {
|
|
611
|
+
let deadline = stream_budget.map(|(limit, is_wrap)| {
|
|
612
|
+
(
|
|
613
|
+
limit.saturating_sub(
|
|
614
|
+
u64::try_from(llm_started.elapsed().as_millis())
|
|
615
|
+
.unwrap_or(u64::MAX),
|
|
616
|
+
),
|
|
617
|
+
is_wrap,
|
|
618
|
+
)
|
|
619
|
+
});
|
|
620
|
+
if let Some((0, is_wrap)) = deadline {
|
|
621
|
+
if is_wrap {
|
|
622
|
+
stream_wrap_cut = true;
|
|
623
|
+
} else {
|
|
624
|
+
stream_soft_cut = true;
|
|
625
|
+
}
|
|
626
|
+
break;
|
|
608
627
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
if self.poll(false).cancel {
|
|
619
|
-
stream_cancelled = true;
|
|
620
|
-
break None;
|
|
621
|
-
}
|
|
622
|
-
if let Some((remaining, is_wrap)) = deadline {
|
|
623
|
-
if remaining <= wait_ms {
|
|
624
|
-
if is_wrap {
|
|
625
|
-
stream_wrap_cut = true;
|
|
626
|
-
} else {
|
|
627
|
-
stream_soft_cut = true;
|
|
628
|
-
}
|
|
628
|
+
let wait_ms = deadline
|
|
629
|
+
.map(|(remaining, _)| remaining.min(50))
|
|
630
|
+
.unwrap_or(50);
|
|
631
|
+
let chunk = loop {
|
|
632
|
+
tokio::select! {
|
|
633
|
+
chunk = chunks.next() => break chunk,
|
|
634
|
+
_ = tokio::time::sleep(std::time::Duration::from_millis(wait_ms)) => {
|
|
635
|
+
if self.poll(false).cancel {
|
|
636
|
+
stream_cancelled = true;
|
|
629
637
|
break None;
|
|
630
638
|
}
|
|
639
|
+
if let Some((remaining, is_wrap)) = deadline {
|
|
640
|
+
if remaining <= wait_ms {
|
|
641
|
+
if is_wrap {
|
|
642
|
+
stream_wrap_cut = true;
|
|
643
|
+
} else {
|
|
644
|
+
stream_soft_cut = true;
|
|
645
|
+
}
|
|
646
|
+
break None;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
631
649
|
}
|
|
632
650
|
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
let Some(chunk) = chunk else {
|
|
636
|
-
break;
|
|
637
|
-
};
|
|
638
|
-
let chunk = match chunk {
|
|
639
|
-
Ok(chunk) => chunk,
|
|
640
|
-
Err(error) => {
|
|
641
|
-
stream_provider_error = Some(error);
|
|
651
|
+
};
|
|
652
|
+
let Some(chunk) = chunk else {
|
|
642
653
|
break;
|
|
654
|
+
};
|
|
655
|
+
let chunk = match chunk {
|
|
656
|
+
Ok(chunk) => chunk,
|
|
657
|
+
Err(error) => {
|
|
658
|
+
stream_provider_error = Some(error);
|
|
659
|
+
break;
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
let now = std::time::Instant::now();
|
|
663
|
+
let gap_ms = u64::try_from(now.duration_since(last_chunk_at).as_millis())
|
|
664
|
+
.unwrap_or(u64::MAX);
|
|
665
|
+
last_chunk_at = now;
|
|
666
|
+
if gap_ms <= self.config.idle_reasoning_gap_ms {
|
|
667
|
+
stream_active_ms = stream_active_ms.saturating_add(gap_ms);
|
|
643
668
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
.
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
669
|
+
let chunk_content_chars = chunk
|
|
670
|
+
.content_delta
|
|
671
|
+
.as_deref()
|
|
672
|
+
.map(|text| text.chars().count() as u64)
|
|
673
|
+
.unwrap_or(0);
|
|
674
|
+
let chunk_reasoning_chars = chunk
|
|
675
|
+
.reasoning_delta
|
|
676
|
+
.as_deref()
|
|
677
|
+
.map(|text| text.chars().count() as u64)
|
|
678
|
+
.unwrap_or(0);
|
|
679
|
+
let chunk_has_tool = chunk.tool_call_delta.is_some();
|
|
680
|
+
if let Some(finish_reason) = chunk.finish_reason.as_deref() {
|
|
681
|
+
if finish_reason == "__soft_timeout_cut__" {
|
|
682
|
+
stream_soft_cut = true;
|
|
683
|
+
break;
|
|
684
|
+
}
|
|
685
|
+
if finish_reason == "__wrap_stream_cut__" {
|
|
686
|
+
stream_wrap_cut = true;
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
if let Some(details) = finish_reason.strip_prefix("__idle_stream_cut__:") {
|
|
690
|
+
let mut parts = details.split(':');
|
|
691
|
+
let trigger = parts.next().unwrap_or("active_ms").to_string();
|
|
692
|
+
let chars = parts
|
|
693
|
+
.next()
|
|
694
|
+
.and_then(|value| value.parse().ok())
|
|
695
|
+
.unwrap_or(0);
|
|
696
|
+
let stale_chars = parts
|
|
697
|
+
.next()
|
|
698
|
+
.and_then(|value| value.parse().ok())
|
|
699
|
+
.unwrap_or(0);
|
|
700
|
+
stream_idle_cut = Some((trigger, chars, stale_chars));
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
667
703
|
}
|
|
668
|
-
if
|
|
669
|
-
|
|
670
|
-
|
|
704
|
+
if let Some(delta) = chunk.content_delta {
|
|
705
|
+
content.push_str(&delta);
|
|
706
|
+
emit(LoopEvent::new("content_delta", json!({ "delta": delta })));
|
|
671
707
|
}
|
|
672
|
-
if let Some(
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
.
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
708
|
+
if let Some(delta) = chunk.reasoning_delta {
|
|
709
|
+
reasoning.push_str(&delta);
|
|
710
|
+
emit(LoopEvent::new("reasoning_delta", json!({ "delta": delta })));
|
|
711
|
+
}
|
|
712
|
+
if let Some(details) = chunk.reasoning_details {
|
|
713
|
+
match details {
|
|
714
|
+
Value::Array(items) => reasoning_details.extend(items),
|
|
715
|
+
item => reasoning_details.push(item),
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if let Some(call) = chunk.tool_call_delta {
|
|
719
|
+
tool_calls.push(call);
|
|
720
|
+
}
|
|
721
|
+
if let Some(usage) = chunk.usage {
|
|
722
|
+
ctx.last_prompt_tokens = usage.prompt_tokens;
|
|
723
|
+
ctx.last_prompt_transcript_len = history.len();
|
|
724
|
+
ctx.last_cached_prompt_tokens = usage.cached_prompt_tokens;
|
|
725
|
+
ctx.last_cache_creation_tokens = usage.cache_creation_tokens;
|
|
726
|
+
ctx.accumulated_prompt_tokens += usage.prompt_tokens;
|
|
727
|
+
ctx.accumulated_completion_tokens += usage.completion_tokens;
|
|
728
|
+
self.last_run_usage = Some(LLMUsage {
|
|
729
|
+
prompt_tokens: ctx.accumulated_prompt_tokens,
|
|
730
|
+
completion_tokens: ctx.accumulated_completion_tokens,
|
|
731
|
+
total_tokens: ctx.accumulated_prompt_tokens
|
|
732
|
+
+ ctx.accumulated_completion_tokens,
|
|
733
|
+
cached_prompt_tokens: usage.cached_prompt_tokens,
|
|
734
|
+
cache_creation_tokens: usage.cache_creation_tokens,
|
|
735
|
+
});
|
|
736
|
+
self.observe_context(&ctx, round_index);
|
|
737
|
+
if let Some(limits) = &self.config.budget {
|
|
738
|
+
let (next, exhausted) = consume_budget(
|
|
739
|
+
&budget_state,
|
|
740
|
+
limits,
|
|
741
|
+
usage.total_tokens,
|
|
742
|
+
usage.cached_prompt_tokens,
|
|
743
|
+
false,
|
|
744
|
+
false,
|
|
745
|
+
);
|
|
746
|
+
budget_state = next;
|
|
747
|
+
if exhausted {
|
|
748
|
+
emit(LoopEvent::new(
|
|
749
|
+
"budget_exhausted",
|
|
750
|
+
json!({ "budget": "tokens", "used": budget_state.tokens_used }),
|
|
751
|
+
));
|
|
752
|
+
emit(completion_event(
|
|
753
|
+
round_index,
|
|
754
|
+
"stop",
|
|
755
|
+
&content,
|
|
756
|
+
&tool_calls,
|
|
757
|
+
ctx.consecutive_tool_errors,
|
|
758
|
+
"budget_exhausted",
|
|
759
|
+
"token budget exceeded",
|
|
760
|
+
1.0,
|
|
761
|
+
&ctx,
|
|
762
|
+
));
|
|
763
|
+
token_exhausted = true;
|
|
764
|
+
break;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
if self.poll(false).cancel {
|
|
769
|
+
stream_cancelled = true;
|
|
684
770
|
break;
|
|
685
771
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
772
|
+
if !stream_has_tool && !chunk_has_tool {
|
|
773
|
+
let delta_chars = chunk_content_chars.saturating_add(chunk_reasoning_chars);
|
|
774
|
+
stream_chars = stream_chars.saturating_add(delta_chars);
|
|
775
|
+
let stale_chars = ctx.reasoning_since_progress.saturating_add(stream_chars);
|
|
776
|
+
let trigger = if self
|
|
777
|
+
.config
|
|
778
|
+
.idle_stream_timeout_ms
|
|
779
|
+
.is_some_and(|limit| stream_active_ms >= limit)
|
|
780
|
+
{
|
|
781
|
+
Some("active_ms")
|
|
782
|
+
} else if self
|
|
783
|
+
.config
|
|
784
|
+
.idle_stream_max_chars
|
|
785
|
+
.is_some_and(|limit| stream_chars >= limit)
|
|
786
|
+
{
|
|
787
|
+
Some("chars")
|
|
788
|
+
} else if self
|
|
789
|
+
.config
|
|
790
|
+
.reasoning_without_progress_chars
|
|
791
|
+
.is_some_and(|limit| stale_chars >= limit)
|
|
792
|
+
{
|
|
793
|
+
Some("no_progress")
|
|
794
|
+
} else {
|
|
795
|
+
None
|
|
796
|
+
};
|
|
797
|
+
if let Some(trigger) = trigger {
|
|
798
|
+
if trigger == "no_progress" {
|
|
799
|
+
ctx.reasoning_since_progress = 0;
|
|
800
|
+
} else {
|
|
801
|
+
ctx.reasoning_since_progress = stale_chars;
|
|
802
|
+
}
|
|
803
|
+
stream_progress_accounted = true;
|
|
804
|
+
stream_idle_cut =
|
|
805
|
+
Some((trigger.to_string(), stream_chars, stale_chars));
|
|
806
|
+
break;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
if chunk_has_tool {
|
|
810
|
+
stream_has_tool = true;
|
|
699
811
|
}
|
|
700
812
|
}
|
|
701
|
-
if let Some(
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
813
|
+
if let Some(error) = stream_provider_error {
|
|
814
|
+
let message = error.message.clone();
|
|
815
|
+
let kind = error.kind.as_str();
|
|
816
|
+
emit(LoopEvent::new(
|
|
817
|
+
"llm_response",
|
|
818
|
+
json!({
|
|
819
|
+
"round": round_index,
|
|
820
|
+
"attempt": llm_attempt,
|
|
821
|
+
"durationMs": llm_started.elapsed().as_millis(),
|
|
822
|
+
"error": message,
|
|
823
|
+
"errorKind": kind,
|
|
824
|
+
}),
|
|
825
|
+
));
|
|
826
|
+
match self
|
|
827
|
+
.hooks
|
|
828
|
+
.on_request_error(&error, &history, round_index)
|
|
829
|
+
.await
|
|
830
|
+
{
|
|
831
|
+
RequestErrorAction::Retry {
|
|
832
|
+
delay_ms,
|
|
833
|
+
reason,
|
|
834
|
+
rewrite,
|
|
835
|
+
} => {
|
|
836
|
+
let compacted = rewrite.is_some();
|
|
837
|
+
if let Some(rewrite) = rewrite {
|
|
838
|
+
history = rewrite;
|
|
839
|
+
self.observe_history(&history);
|
|
840
|
+
}
|
|
841
|
+
let retry_reason = if reason.is_empty() {
|
|
842
|
+
message.clone()
|
|
843
|
+
} else {
|
|
844
|
+
reason
|
|
845
|
+
};
|
|
731
846
|
emit(LoopEvent::new(
|
|
732
|
-
"
|
|
733
|
-
json!({
|
|
847
|
+
"hook_action",
|
|
848
|
+
json!({
|
|
849
|
+
"hook": "on_request_error",
|
|
850
|
+
"action": "retry",
|
|
851
|
+
"reason": retry_reason,
|
|
852
|
+
"delayMs": delay_ms,
|
|
853
|
+
"compacted": compacted,
|
|
854
|
+
"round": round_index,
|
|
855
|
+
}),
|
|
856
|
+
));
|
|
857
|
+
if delay_ms > 0 {
|
|
858
|
+
tokio::time::sleep(std::time::Duration::from_millis(delay_ms))
|
|
859
|
+
.await;
|
|
860
|
+
}
|
|
861
|
+
llm_attempt += 1;
|
|
862
|
+
continue 'stream_attempt;
|
|
863
|
+
}
|
|
864
|
+
RequestErrorAction::Fail { reason } => {
|
|
865
|
+
let failure_reason = if reason.is_empty() {
|
|
866
|
+
format!("llm stream error: {message}")
|
|
867
|
+
} else {
|
|
868
|
+
reason
|
|
869
|
+
};
|
|
870
|
+
emit(LoopEvent::new(
|
|
871
|
+
"error",
|
|
872
|
+
json!({
|
|
873
|
+
"message": message,
|
|
874
|
+
"round": round_index,
|
|
875
|
+
"phase": "llm_stream",
|
|
876
|
+
"kind": kind,
|
|
877
|
+
"provider": error.provider,
|
|
878
|
+
"statusCode": error.status_code,
|
|
879
|
+
"retryAfterMs": error.retry_after_ms,
|
|
880
|
+
}),
|
|
734
881
|
));
|
|
882
|
+
if !content.trim().is_empty() && tool_calls.is_empty() {
|
|
883
|
+
history.push(LLMMessage::text("assistant", content.clone()));
|
|
884
|
+
}
|
|
885
|
+
self.observe_history(&history);
|
|
735
886
|
emit(completion_event(
|
|
736
887
|
round_index,
|
|
737
888
|
"stop",
|
|
738
889
|
&content,
|
|
739
890
|
&tool_calls,
|
|
740
891
|
ctx.consecutive_tool_errors,
|
|
741
|
-
"
|
|
742
|
-
|
|
743
|
-
|
|
892
|
+
"failed",
|
|
893
|
+
failure_reason,
|
|
894
|
+
0.9,
|
|
744
895
|
&ctx,
|
|
745
896
|
));
|
|
746
|
-
|
|
747
|
-
break;
|
|
897
|
+
stream_failed = true;
|
|
898
|
+
break 'stream_attempt;
|
|
748
899
|
}
|
|
749
900
|
}
|
|
750
901
|
}
|
|
751
|
-
if self.poll(false).cancel {
|
|
752
|
-
stream_cancelled = true;
|
|
753
|
-
break;
|
|
754
|
-
}
|
|
755
|
-
if !stream_has_tool && !chunk_has_tool {
|
|
756
|
-
let delta_chars = chunk_content_chars.saturating_add(chunk_reasoning_chars);
|
|
757
|
-
stream_chars = stream_chars.saturating_add(delta_chars);
|
|
758
|
-
let stale_chars = ctx.reasoning_since_progress.saturating_add(stream_chars);
|
|
759
|
-
let trigger = if self
|
|
760
|
-
.config
|
|
761
|
-
.idle_stream_timeout_ms
|
|
762
|
-
.is_some_and(|limit| stream_active_ms >= limit)
|
|
763
|
-
{
|
|
764
|
-
Some("active_ms")
|
|
765
|
-
} else if self
|
|
766
|
-
.config
|
|
767
|
-
.idle_stream_max_chars
|
|
768
|
-
.is_some_and(|limit| stream_chars >= limit)
|
|
769
|
-
{
|
|
770
|
-
Some("chars")
|
|
771
|
-
} else if self
|
|
772
|
-
.config
|
|
773
|
-
.reasoning_without_progress_chars
|
|
774
|
-
.is_some_and(|limit| stale_chars >= limit)
|
|
775
|
-
{
|
|
776
|
-
Some("no_progress")
|
|
777
|
-
} else {
|
|
778
|
-
None
|
|
779
|
-
};
|
|
780
|
-
if let Some(trigger) = trigger {
|
|
781
|
-
if trigger == "no_progress" {
|
|
782
|
-
ctx.reasoning_since_progress = 0;
|
|
783
|
-
} else {
|
|
784
|
-
ctx.reasoning_since_progress = stale_chars;
|
|
785
|
-
}
|
|
786
|
-
stream_progress_accounted = true;
|
|
787
|
-
stream_idle_cut = Some((trigger.to_string(), stream_chars, stale_chars));
|
|
788
|
-
break;
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
if chunk_has_tool {
|
|
792
|
-
stream_has_tool = true;
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
if !stream_progress_accounted && tool_calls.is_empty() {
|
|
796
|
-
ctx.reasoning_since_progress =
|
|
797
|
-
ctx.reasoning_since_progress.saturating_add(stream_chars);
|
|
798
|
-
}
|
|
799
902
|
|
|
800
|
-
if stream_provider_error.is_none() {
|
|
801
903
|
emit(LoopEvent::new(
|
|
802
904
|
"llm_response",
|
|
803
905
|
json!({
|
|
@@ -809,57 +911,13 @@ impl<P: LLMProvider, E: ToolExecutor, H: LoopHooks> CoreLoop<P, E, H> {
|
|
|
809
911
|
"cancelled": stream_cancelled,
|
|
810
912
|
}),
|
|
811
913
|
));
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
let message = error.message.clone();
|
|
816
|
-
let kind = error.kind.as_str();
|
|
817
|
-
emit(LoopEvent::new(
|
|
818
|
-
"llm_response",
|
|
819
|
-
json!({
|
|
820
|
-
"round": round_index,
|
|
821
|
-
"attempt": llm_attempt,
|
|
822
|
-
"durationMs": llm_started.elapsed().as_millis(),
|
|
823
|
-
"error": message,
|
|
824
|
-
"errorKind": kind,
|
|
825
|
-
}),
|
|
826
|
-
));
|
|
827
|
-
let reason = match self
|
|
828
|
-
.hooks
|
|
829
|
-
.on_request_error(&error, &history, round_index)
|
|
830
|
-
.await
|
|
831
|
-
{
|
|
832
|
-
RequestErrorAction::Fail { reason } => reason,
|
|
833
|
-
RequestErrorAction::Retry { reason, .. } if !reason.is_empty() => reason,
|
|
834
|
-
RequestErrorAction::Retry { .. } => format!("llm stream error: {message}"),
|
|
835
|
-
};
|
|
836
|
-
emit(LoopEvent::new(
|
|
837
|
-
"error",
|
|
838
|
-
json!({
|
|
839
|
-
"message": message,
|
|
840
|
-
"round": round_index,
|
|
841
|
-
"phase": "llm_stream",
|
|
842
|
-
"kind": kind,
|
|
843
|
-
"provider": error.provider,
|
|
844
|
-
"statusCode": error.status_code,
|
|
845
|
-
"retryAfterMs": error.retry_after_ms,
|
|
846
|
-
}),
|
|
847
|
-
));
|
|
848
|
-
if !content.trim().is_empty() && tool_calls.is_empty() {
|
|
849
|
-
history.push(LLMMessage::text("assistant", content.clone()));
|
|
914
|
+
if !stream_progress_accounted && tool_calls.is_empty() {
|
|
915
|
+
ctx.reasoning_since_progress =
|
|
916
|
+
ctx.reasoning_since_progress.saturating_add(stream_chars);
|
|
850
917
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
"stop",
|
|
855
|
-
&content,
|
|
856
|
-
&tool_calls,
|
|
857
|
-
ctx.consecutive_tool_errors,
|
|
858
|
-
"failed",
|
|
859
|
-
reason,
|
|
860
|
-
0.9,
|
|
861
|
-
&ctx,
|
|
862
|
-
));
|
|
918
|
+
break 'stream_attempt;
|
|
919
|
+
}
|
|
920
|
+
if stream_failed {
|
|
863
921
|
break;
|
|
864
922
|
}
|
|
865
923
|
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/tests/test_loop.rs
RENAMED
|
@@ -6,8 +6,8 @@ use serde_json::{json, Value};
|
|
|
6
6
|
use steerable_agent_runtime::{
|
|
7
7
|
chunk_stream, BudgetLimit, CompletionDraft, ContentPart, CoreLoop, HookAction, LLMChunkStream,
|
|
8
8
|
LLMError, LLMErrorKind, LLMMessage, LLMProvider, LLMStreamChunk, LLMUsage, LoopConfig,
|
|
9
|
-
LoopContext, LoopEvent, LoopHooks, RoundControl, RouterToolExecutor,
|
|
10
|
-
ScriptedTurn, ToolCall, ToolExecutor, ToolResult, ToolRouter,
|
|
9
|
+
LoopContext, LoopEvent, LoopHooks, RequestErrorAction, RoundControl, RouterToolExecutor,
|
|
10
|
+
ScriptedProvider, ScriptedTurn, ToolCall, ToolExecutor, ToolResult, ToolRouter,
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
fn provider(scripts: Vec<ScriptedTurn>) -> ScriptedProvider {
|
|
@@ -684,6 +684,125 @@ async fn test_provider_error_emits_error_and_failed_completion() {
|
|
|
684
684
|
assert_eq!(events[4].data["reason"], "failing: HTTP 401 (auth)");
|
|
685
685
|
}
|
|
686
686
|
|
|
687
|
+
struct MidStreamThenOk {
|
|
688
|
+
attempts: usize,
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
#[async_trait]
|
|
692
|
+
impl LLMProvider for MidStreamThenOk {
|
|
693
|
+
fn name(&self) -> &str {
|
|
694
|
+
"mid-stream"
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
fn model(&self) -> &str {
|
|
698
|
+
"mid-stream"
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
async fn stream(&mut self, _messages: &[LLMMessage]) -> Result<LLMChunkStream, LLMError> {
|
|
702
|
+
self.attempts += 1;
|
|
703
|
+
if self.attempts == 1 {
|
|
704
|
+
return Ok(Box::pin(async_stream::stream! {
|
|
705
|
+
yield Ok(LLMStreamChunk {
|
|
706
|
+
content_delta: Some("partial".into()),
|
|
707
|
+
..LLMStreamChunk::default()
|
|
708
|
+
});
|
|
709
|
+
yield Err(LLMError::new(
|
|
710
|
+
"openai_compat: transport error: incomplete chunked read",
|
|
711
|
+
LLMErrorKind::Transport,
|
|
712
|
+
));
|
|
713
|
+
}));
|
|
714
|
+
}
|
|
715
|
+
Ok(chunk_stream(vec![
|
|
716
|
+
LLMStreamChunk {
|
|
717
|
+
content_delta: Some("recovered after retry".into()),
|
|
718
|
+
..LLMStreamChunk::default()
|
|
719
|
+
},
|
|
720
|
+
LLMStreamChunk {
|
|
721
|
+
finish_reason: Some("stop".into()),
|
|
722
|
+
..LLMStreamChunk::default()
|
|
723
|
+
},
|
|
724
|
+
]))
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
struct RetryRequestError;
|
|
729
|
+
|
|
730
|
+
#[async_trait]
|
|
731
|
+
impl LoopHooks for RetryRequestError {
|
|
732
|
+
async fn on_request_error(
|
|
733
|
+
&self,
|
|
734
|
+
_error: &LLMError,
|
|
735
|
+
_transcript: &[LLMMessage],
|
|
736
|
+
_round_index: u32,
|
|
737
|
+
) -> RequestErrorAction {
|
|
738
|
+
RequestErrorAction::Retry {
|
|
739
|
+
delay_ms: 0,
|
|
740
|
+
reason: String::new(),
|
|
741
|
+
rewrite: None,
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
#[tokio::test]
|
|
747
|
+
async fn test_mid_stream_transport_error_retries_then_recovers() {
|
|
748
|
+
let mut agent = CoreLoop::new(
|
|
749
|
+
MidStreamThenOk { attempts: 0 },
|
|
750
|
+
RouterToolExecutor::new(ToolRouter::new()),
|
|
751
|
+
)
|
|
752
|
+
.with_hooks(RetryRequestError);
|
|
753
|
+
|
|
754
|
+
let events = agent.run(vec![LLMMessage::text("user", "go")]).await;
|
|
755
|
+
|
|
756
|
+
assert_eq!(final_completion(&events)["status"], "completed");
|
|
757
|
+
let retry = events
|
|
758
|
+
.iter()
|
|
759
|
+
.find(|event| {
|
|
760
|
+
event.kind == "hook_action"
|
|
761
|
+
&& event.data["hook"] == "on_request_error"
|
|
762
|
+
&& event.data["action"] == "retry"
|
|
763
|
+
})
|
|
764
|
+
.expect("mid-stream transport should emit on_request_error retry");
|
|
765
|
+
assert!(retry.data["reason"]
|
|
766
|
+
.as_str()
|
|
767
|
+
.is_some_and(|reason| reason.contains("incomplete chunked read")));
|
|
768
|
+
assert!(!events.iter().any(|event| event.kind == "error"));
|
|
769
|
+
let assistant = agent
|
|
770
|
+
.last_history
|
|
771
|
+
.iter()
|
|
772
|
+
.rev()
|
|
773
|
+
.find(|message| message.role == "assistant")
|
|
774
|
+
.expect("retry should record the recovered assistant text");
|
|
775
|
+
assert_eq!(assistant.content_text(), "recovered after retry");
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
#[tokio::test]
|
|
779
|
+
async fn test_mid_stream_error_keeps_partial_text_when_hook_fails() {
|
|
780
|
+
let mut agent = CoreLoop::new(
|
|
781
|
+
MidStreamThenOk { attempts: 0 },
|
|
782
|
+
RouterToolExecutor::new(ToolRouter::new()),
|
|
783
|
+
);
|
|
784
|
+
|
|
785
|
+
let events = agent.run(vec![LLMMessage::text("user", "go")]).await;
|
|
786
|
+
|
|
787
|
+
assert_eq!(
|
|
788
|
+
events
|
|
789
|
+
.iter()
|
|
790
|
+
.filter(|event| event.kind == "content_delta")
|
|
791
|
+
.filter_map(|event| event.data["delta"].as_str())
|
|
792
|
+
.collect::<Vec<_>>(),
|
|
793
|
+
vec!["partial"]
|
|
794
|
+
);
|
|
795
|
+
assert_eq!(final_completion(&events)["status"], "failed");
|
|
796
|
+
assert_eq!(final_completion(&events)["textLength"], 7);
|
|
797
|
+
let assistant = agent
|
|
798
|
+
.last_history
|
|
799
|
+
.iter()
|
|
800
|
+
.rev()
|
|
801
|
+
.find(|message| message.role == "assistant")
|
|
802
|
+
.expect("failed mid-stream should keep partial assistant text");
|
|
803
|
+
assert_eq!(assistant.content_text(), "partial");
|
|
804
|
+
}
|
|
805
|
+
|
|
687
806
|
struct SlowToolExecutor;
|
|
688
807
|
|
|
689
808
|
#[async_trait]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/budget.rs
RENAMED
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/compat.rs
RENAMED
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/errors.rs
RENAMED
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/executor.rs
RENAMED
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/gemini_http.rs
RENAMED
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/gemini_wire.rs
RENAMED
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/hooks.rs
RENAMED
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/json_py.rs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/model_resolve.rs
RENAMED
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/openai_http.rs
RENAMED
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/openai_wire.rs
RENAMED
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/presets.rs
RENAMED
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/pricing.rs
RENAMED
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/provider.rs
RENAMED
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/ptc_js.rs
RENAMED
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/python.rs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/run_code.rs
RENAMED
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/skills.rs
RENAMED
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/sse_http.rs
RENAMED
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/tokens.rs
RENAMED
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/types.rs
RENAMED
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/web_html.rs
RENAMED
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/web_search.rs
RENAMED
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/src/write_lease.rs
RENAMED
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/tests/test_ptc_js.rs
RENAMED
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/tests/test_todo.rs
RENAMED
|
File without changes
|
{steerable_agent_runtime_native-0.6.31 → steerable_agent_runtime_native-0.6.32}/tests/test_web.rs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|