1mod event_stream;
2mod session_blocking;
3mod support;
4
5use std::path::PathBuf;
6use std::sync::{Arc, Mutex};
7
8use serde_json::Value;
9use tokio::sync::broadcast;
10
11use crate::agent::Agent;
12use crate::approval::ApprovalBroker;
13use crate::config::apply_resolved_model_limits;
14use crate::context::RunContext;
15use crate::context_providers::{
16 assemble_context_fragments, collect_context_fragments, ContextBundle, ContextRequest,
17};
18use crate::events::{RunEvent, RunEventPayload};
19use crate::llm::LlmClient;
20use crate::model::{ModelError, ModelProvider, VvLlmModelProvider};
21use crate::result::{RunResult, RunResumeContext, RunState};
22use crate::run_config::RunConfig;
23use crate::run_handle::{RunEventSenderSlot, RunHandle, RunHandleState, SharedRunResult};
24use crate::runtime::{AgentRuntime, ExecutionContext, RuntimeRunControls};
25use crate::sessions::SessionItem;
26use crate::tools::ToolRegistry;
27use crate::tracing::Span;
28use crate::types::{AgentTask, NoToolPolicy, ToolResultStatus};
29use crate::workspace::LocalWorkspaceBackend;
30
31use event_stream::map_runtime_event;
32pub use event_stream::RunEventStream;
33use session_blocking::block_on_session;
34use support::{
35 apply_input_guardrails, apply_output_guardrails, approval_required, capture_event,
36 completed_from_first_tool_result, effective_event_store, extract_handoff,
37 find_approved_tool_call, insert_context_metadata, max_handoff_depth, merged_tool_policy,
38 ApprovalHook, SingleRunOutcome,
39};
40
41#[derive(Clone, Debug, PartialEq)]
42pub struct NormalizedInput {
43 pub text: String,
44}
45
46impl From<&str> for NormalizedInput {
47 fn from(value: &str) -> Self {
48 Self {
49 text: value.to_string(),
50 }
51 }
52}
53
54impl From<String> for NormalizedInput {
55 fn from(value: String) -> Self {
56 Self { text: value }
57 }
58}
59
60#[derive(Clone)]
61pub struct Runner {
62 model_provider: Arc<dyn ModelProvider>,
63 workspace: PathBuf,
64 tool_registry: ToolRegistry,
65 default_run_config: RunConfig,
66}
67
68impl Runner {
69 pub fn builder() -> RunnerBuilder {
70 RunnerBuilder::default()
71 }
72
73 pub async fn run(
74 &self,
75 agent: &Agent,
76 input: impl Into<NormalizedInput>,
77 ) -> Result<RunResult, String> {
78 self.run_with_config(agent, input, RunConfig::default())
79 .await
80 }
81
82 pub async fn run_with_config(
83 &self,
84 agent: &Agent,
85 input: impl Into<NormalizedInput>,
86 config: RunConfig,
87 ) -> Result<RunResult, String> {
88 self.run_blocking(agent, input.into(), config, None)
89 }
90
91 pub async fn resume(&self, state: RunState) -> Result<RunResult, String> {
92 let (source, approved_ids) = state.into_inner();
93 let Some(resume_context) = source.resume_context().cloned() else {
94 return Err("run state does not include resume context".to_string());
95 };
96 if let Some(result) =
97 self.resume_approved_tool_call(&source, &resume_context, &approved_ids)
98 {
99 return result;
100 }
101 let mut config = resume_context.config;
102 config.metadata.insert(
103 "approved_tool_interruption_ids".to_string(),
104 Value::Array(approved_ids.iter().cloned().map(Value::String).collect()),
105 );
106 let mut result = self
107 .run_with_config(&resume_context.agent, resume_context.input, config)
108 .await
109 .map_err(|error| format!("resume failed: {error}"))?;
110 if result.status() == crate::types::AgentStatus::MaxCycles {
111 result = completed_from_first_tool_result(result);
112 }
113 Ok(result)
114 }
115
116 fn resume_approved_tool_call(
117 &self,
118 source: &RunResult,
119 resume_context: &RunResumeContext,
120 approved_ids: &[String],
121 ) -> Option<Result<RunResult, String>> {
122 let approval = find_approved_tool_call(source.result(), approved_ids)?;
123 let mut registry = self.tool_registry.clone();
124 for handoff in resume_context.agent.handoffs() {
125 if let Err(error) = registry.register(handoff.as_tool_spec(resume_context.agent.name()))
126 {
127 return Some(Err(error));
128 }
129 }
130 for tool in resume_context.agent.tools() {
131 if let Err(error) = registry.register(tool.as_tool_spec()) {
132 return Some(Err(error));
133 }
134 }
135 let workspace = resume_context
136 .config
137 .workspace
138 .clone()
139 .or_else(|| self.default_run_config.workspace.clone())
140 .unwrap_or_else(|| self.workspace.clone());
141 let workspace_backend = resume_context
142 .config
143 .workspace_backend
144 .clone()
145 .or_else(|| self.default_run_config.workspace_backend.clone())
146 .unwrap_or_else(|| Arc::new(LocalWorkspaceBackend::new(workspace.clone())));
147 let mut context = crate::tools::ToolContext {
148 workspace: workspace.clone(),
149 shared_state: source.result().shared_state.clone(),
150 cycle_index: approval.cycle_index,
151 task_id: format!("{}_run", resume_context.agent.name()),
152 metadata: resume_context.agent.metadata().clone(),
153 workspace_backend,
154 model_provider: Some(self.model_provider.clone()),
155 sub_task_runner: None,
156 sub_task_manager: None,
157 execution_backend: None,
158 };
159 context
160 .metadata
161 .extend(resume_context.config.metadata.clone());
162 context
163 .metadata
164 .entry("agent_name".to_string())
165 .or_insert_with(|| Value::String(resume_context.agent.name().to_string()));
166 let tool_result = crate::tools::dispatch_tool_call(®istry, &mut context, &approval.call);
167 if tool_result.status != ToolResultStatus::Success {
168 return Some(Err(tool_result.content));
169 }
170 let mut agent_result = source.result().clone();
171 agent_result.status = crate::types::AgentStatus::Completed;
172 agent_result.final_answer = Some(tool_result.content.clone());
173 if let Some(cycle) = agent_result
174 .cycles
175 .iter_mut()
176 .find(|cycle| cycle.index == approval.cycle_index)
177 {
178 cycle.tool_results.push(tool_result.clone());
179 }
180 agent_result.messages.push(tool_result.to_message());
181 Some(Ok(RunResult::new(
182 resume_context.agent.name().to_string(),
183 agent_result,
184 source.resolved_model().clone(),
185 )
186 .with_resume_context(resume_context.clone())))
187 }
188
189 pub fn run_blocking(
190 &self,
191 agent: &Agent,
192 input: NormalizedInput,
193 config: RunConfig,
194 event_collector: Option<Arc<std::sync::Mutex<Vec<RunEvent>>>>,
195 ) -> Result<RunResult, String> {
196 self.run_blocking_with_event_sender(agent, input, config, event_collector, None)
197 }
198
199 fn run_blocking_with_event_sender(
200 &self,
201 agent: &Agent,
202 input: NormalizedInput,
203 config: RunConfig,
204 event_collector: Option<Arc<std::sync::Mutex<Vec<RunEvent>>>>,
205 event_sender: Option<broadcast::Sender<RunEvent>>,
206 ) -> Result<RunResult, String> {
207 self.run_agent_chain(agent, input, config, event_collector, event_sender)
208 }
209
210 fn run_agent_chain(
211 &self,
212 agent: &Agent,
213 input: NormalizedInput,
214 config: RunConfig,
215 event_collector: Option<Arc<std::sync::Mutex<Vec<RunEvent>>>>,
216 event_sender: Option<broadcast::Sender<RunEvent>>,
217 ) -> Result<RunResult, String> {
218 let (event_store, event_store_fail_closed) =
219 effective_event_store(&self.default_run_config, &config);
220 let mut current_agent = agent.clone();
221 let mut current_input = input;
222 for _ in 0..=max_handoff_depth(&config, ¤t_agent) {
223 let outcome = self.run_single_agent(
224 ¤t_agent,
225 current_input.clone(),
226 config.clone(),
227 event_collector.clone(),
228 event_sender.clone(),
229 )?;
230 let Some(handoff) = outcome.handoff else {
231 return Ok(outcome.result);
232 };
233 let target = current_agent
234 .handoffs()
235 .iter()
236 .find(|candidate| candidate.target().name() == handoff.to_agent)
237 .map(|candidate| candidate.target().clone())
238 .ok_or_else(|| {
239 format!(
240 "handoff target `{}` is not registered on agent `{}`",
241 handoff.to_agent,
242 current_agent.name()
243 )
244 })?;
245 capture_event(
246 event_collector.as_ref(),
247 event_sender.as_ref(),
248 event_store.as_ref(),
249 event_store_fail_closed,
250 RunEvent::new(
251 format!("{}_run", handoff.from_agent),
252 format!("{}_run", handoff.from_agent),
253 handoff.from_agent.clone(),
254 None,
255 RunEventPayload::HandoffStarted {
256 source_agent: handoff.from_agent.clone(),
257 target_agent: handoff.to_agent.clone(),
258 tool_call_id: handoff.tool_call_id.clone(),
259 },
260 ),
261 );
262 capture_event(
263 event_collector.as_ref(),
264 event_sender.as_ref(),
265 event_store.as_ref(),
266 event_store_fail_closed,
267 RunEvent::handoff_completed(
268 format!("{}_run", handoff.from_agent),
269 format!("{}_run", handoff.from_agent),
270 handoff.from_agent.clone(),
271 handoff.to_agent.clone(),
272 handoff.tool_call_id.clone(),
273 ),
274 );
275 current_input = NormalizedInput {
276 text: handoff.input,
277 };
278 current_agent = target;
279 }
280 Err("maximum handoff depth exceeded".to_string())
281 }
282
283 fn run_single_agent(
284 &self,
285 agent: &Agent,
286 input: NormalizedInput,
287 config: RunConfig,
288 event_collector: Option<Arc<std::sync::Mutex<Vec<RunEvent>>>>,
289 event_sender: Option<broadcast::Sender<RunEvent>>,
290 ) -> Result<SingleRunOutcome, String> {
291 let provider = config
292 .model_provider
293 .clone()
294 .or_else(|| self.default_run_config.model_provider.clone())
295 .unwrap_or_else(|| self.model_provider.clone());
296 let model_ref = config
297 .model
298 .clone()
299 .or_else(|| self.default_run_config.model.clone())
300 .or_else(|| agent.model().cloned())
301 .ok_or_else(|| "agent model is not configured".to_string())?;
302 let resolved = provider.resolve(&model_ref).map_err(format_model_error)?;
303 let llm = provider.client(&resolved).map_err(format_model_error)?;
304 let (event_store, event_store_fail_closed) =
305 effective_event_store(&self.default_run_config, &config);
306 let provider_settings = provider.default_settings(&resolved);
307 let settings = provider_settings
308 .merge(agent.model_settings())
309 .merge(
310 self.default_run_config
311 .model_settings
312 .as_ref()
313 .unwrap_or(&crate::model_settings::ModelSettings::default()),
314 )
315 .merge(
316 config
317 .model_settings
318 .as_ref()
319 .unwrap_or(&crate::model_settings::ModelSettings::default()),
320 );
321 let workspace = config
322 .workspace
323 .clone()
324 .or_else(|| self.default_run_config.workspace.clone())
325 .unwrap_or_else(|| self.workspace.clone());
326 let session = config
327 .session
328 .clone()
329 .or_else(|| self.default_run_config.session.clone());
330 let session_items = if let Some(session) = session.as_ref() {
331 block_on_session(session.get_items(None))?
332 } else {
333 Vec::new()
334 };
335 let tool_policy = merged_tool_policy(
336 agent.tool_policy(),
337 &self.default_run_config.tool_policy,
338 &config.tool_policy,
339 );
340 let approval_provider = config
341 .approval_provider
342 .clone()
343 .or_else(|| self.default_run_config.approval_provider.clone());
344 let approval_broker = config
345 .approval_broker
346 .clone()
347 .or_else(|| self.default_run_config.approval_broker.clone())
348 .or_else(|| {
349 approval_provider
350 .as_ref()
351 .map(|_| ApprovalBroker::default())
352 });
353 let approval_timeout = config
354 .approval_timeout
355 .or(self.default_run_config.approval_timeout);
356 let memory_providers = self
357 .default_run_config
358 .memory_providers
359 .iter()
360 .chain(config.memory_providers.iter())
361 .cloned()
362 .collect::<Vec<_>>();
363 let run_context = RunContext {
364 run_id: format!("{}_run", agent.name()),
365 agent_name: agent.name().to_string(),
366 model: Some(model_ref.clone()),
367 workspace: Some(workspace.clone()),
368 metadata: config.metadata.clone(),
369 };
370 let guarded_input = apply_input_guardrails(agent, &run_context, input)?;
371 let input_text = guarded_input.text;
372 let (instructions, context_bundle) =
373 self.build_instructions_with_context(agent, &input_text, &config)?;
374 let mut task = AgentTask::new(
375 format!("{}_run", agent.name()),
376 resolved.model_id.clone(),
377 instructions,
378 input_text.clone(),
379 );
380 task.max_cycles = config
381 .max_cycles
382 .or(self.default_run_config.max_cycles)
383 .or(agent.max_cycles())
384 .unwrap_or(10)
385 .max(1);
386 task.no_tool_policy = NoToolPolicy::Continue;
387 task.metadata = agent.metadata().clone();
388 task.metadata
389 .extend(self.default_run_config.metadata.clone());
390 task.metadata.extend(config.metadata.clone());
391 task.metadata
392 .entry("agent_name".to_string())
393 .or_insert_with(|| Value::String(agent.name().to_string()));
394 if let Some(context_bundle) = context_bundle {
395 insert_context_metadata(&mut task.metadata, &context_bundle);
396 }
397 task.metadata
398 .insert("model_settings".to_string(), settings.to_value());
399 task.metadata.insert(
400 "runtime_model".to_string(),
401 serde_json::to_value(&resolved).unwrap_or(Value::Null),
402 );
403 apply_resolved_model_limits(&mut task, &resolved);
404 task.initial_messages = session_items
405 .iter()
406 .map(SessionItem::to_message)
407 .collect::<Vec<_>>();
408 let mut registry = self.tool_registry.clone();
409 for handoff in agent.handoffs() {
410 registry.register(handoff.as_tool_spec(agent.name()))?;
411 }
412 for tool in agent.tools() {
413 registry.register(tool.as_tool_spec())?;
414 }
415 let mut runtime = AgentRuntime::new(ArcLlmClient(llm))
416 .with_tool_registry(registry)
417 .with_settings_file("__runner_model_provider__")
418 .with_default_backend(resolved.backend.clone());
419 runtime.default_workspace = Some(workspace.clone());
420 runtime.workspace_backend = config
421 .workspace_backend
422 .clone()
423 .or_else(|| self.default_run_config.workspace_backend.clone())
424 .unwrap_or_else(|| Arc::new(LocalWorkspaceBackend::new(workspace.clone())));
425 if let Some(execution_backend) = config
426 .execution_backend
427 .clone()
428 .or_else(|| self.default_run_config.execution_backend.clone())
429 {
430 runtime.execution_backend = execution_backend;
431 }
432 runtime.hooks.extend(agent.hooks().iter().cloned());
433 runtime
434 .hooks
435 .extend(self.default_run_config.hooks.iter().cloned());
436 runtime.hooks.extend(config.hooks.iter().cloned());
437 if approval_required(&tool_policy) {
438 runtime.hooks.push(Arc::new(ApprovalHook::new(
439 tool_policy.clone(),
440 task.metadata.clone(),
441 )));
442 }
443 let log_handler =
444 if event_collector.is_some() || event_sender.is_some() || event_store.is_some() {
445 let collector = event_collector.clone();
446 let event_sender = event_sender.clone();
447 let event_store = event_store.clone();
448 Some(Arc::new(
449 move |event: &str, payload: &std::collections::BTreeMap<String, Value>| {
450 if let Some(mapped) = map_runtime_event(event, payload) {
451 capture_event(
452 collector.as_ref(),
453 event_sender.as_ref(),
454 event_store.as_ref(),
455 event_store_fail_closed,
456 mapped,
457 );
458 }
459 },
460 ) as crate::runtime::RuntimeEventHandler)
461 } else {
462 None
463 };
464 let controls = RuntimeRunControls {
465 log_handler,
466 cancellation_token: config
467 .cancellation_token
468 .clone()
469 .or_else(|| self.default_run_config.cancellation_token.clone()),
470 execution_context: Some(ExecutionContext {
471 metadata: task.metadata.clone(),
472 approval_provider,
473 approval_broker,
474 approval_timeout,
475 memory_providers,
476 ..ExecutionContext::default()
477 }),
478 workspace: Some(workspace),
479 workspace_backend: runtime.workspace_backend.clone().into(),
480 model_provider: Some(provider.clone()),
481 ..RuntimeRunControls::default()
482 };
483 let trace_sink = config
484 .trace_sink
485 .clone()
486 .or_else(|| self.default_run_config.trace_sink.clone());
487 let run_span = Span::new(
488 format!("{}_run", agent.name()),
489 "run",
490 Some(agent.name().to_string()),
491 );
492 let agent_span = Span::new(
493 format!("{}_run", agent.name()),
494 "agent",
495 Some(agent.name().to_string()),
496 );
497 if let Some(trace_sink) = trace_sink.as_ref() {
498 trace_sink.on_span_start(&run_span);
499 trace_sink.on_span_start(&agent_span);
500 }
501 let result = runtime
502 .run_with_controls(task, controls)
503 .map_err(|error| error.to_string())?;
504 if let Some(trace_sink) = trace_sink.as_ref() {
505 trace_sink.on_span_end(&agent_span);
506 trace_sink.on_span_end(&run_span);
507 trace_sink.flush()?;
508 }
509 let result = apply_output_guardrails(agent, &run_context, result);
510 let handoff = extract_handoff(&result);
511 if let Some(session) = session.as_ref() {
512 let mut new_items = Vec::new();
513 new_items.push(SessionItem::User {
514 content: input_text.clone(),
515 });
516 if handoff.is_none() {
517 if let Some(answer) = result.final_answer.as_ref() {
518 new_items.push(SessionItem::Assistant {
519 content: answer.clone(),
520 });
521 }
522 } else if let Some(handoff) = handoff.as_ref() {
523 new_items.push(SessionItem::Assistant {
524 content: format!(
525 "Handed off from {} to {}.",
526 handoff.from_agent, handoff.to_agent
527 ),
528 });
529 if !handoff.input.is_empty() {
530 new_items.push(SessionItem::User {
531 content: handoff.input.clone(),
532 });
533 }
534 }
535 block_on_session(session.add_items(new_items))?;
536 }
537 Ok(SingleRunOutcome {
538 result: RunResult::new(agent.name().to_string(), result, resolved).with_resume_context(
539 RunResumeContext {
540 agent: agent.clone(),
541 input: NormalizedInput {
542 text: input_text.clone(),
543 },
544 config,
545 },
546 ),
547 handoff,
548 })
549 }
550
551 pub async fn stream(
552 &self,
553 agent: &Agent,
554 input: impl Into<NormalizedInput>,
555 ) -> Result<RunEventStream, String> {
556 let handle = self.start(agent, input, RunConfig::default()).await?;
557 Ok(handle.into_event_stream())
558 }
559
560 pub async fn start(
561 &self,
562 agent: &Agent,
563 input: impl Into<NormalizedInput>,
564 mut config: RunConfig,
565 ) -> Result<RunHandle, String> {
566 let cancellation_token = config
567 .cancellation_token
568 .clone()
569 .or_else(|| self.default_run_config.cancellation_token.clone())
570 .unwrap_or_default();
571 config.cancellation_token = Some(cancellation_token.clone());
572 let approval_broker = config
573 .approval_broker
574 .clone()
575 .or_else(|| self.default_run_config.approval_broker.clone())
576 .unwrap_or_default();
577 config.approval_broker = Some(approval_broker.clone());
578
579 let (event_sender, _) = broadcast::channel(1024);
580 let event_collector = Arc::new(Mutex::new(Vec::new()));
581 let event_sender_slot: RunEventSenderSlot =
582 Arc::new(Mutex::new(Some(event_sender.clone())));
583 let state = Arc::new(Mutex::new(RunHandleState::running()));
584 let runner = self.clone();
585 let agent = agent.clone();
586 let input = input.into();
587 let state_for_task = state.clone();
588 let sender_slot_for_task = event_sender_slot.clone();
589 let event_collector_for_task = event_collector.clone();
590 let cancellation_token_for_task = cancellation_token.clone();
591 let join = tokio::task::spawn_blocking(move || {
592 let result = runner.run_blocking_with_event_sender(
593 &agent,
594 input,
595 config,
596 Some(event_collector_for_task),
597 Some(event_sender),
598 );
599 if let Ok(mut state) = state_for_task.lock() {
600 *state = match &result {
601 Ok(_) if cancellation_token_for_task.is_cancelled() => {
602 RunHandleState::cancelled()
603 }
604 Ok(_) => RunHandleState::completed(),
605 Err(error) if cancellation_token_for_task.is_cancelled() => {
606 let mut state = RunHandleState::cancelled();
607 state.error = Some(error.clone());
608 state
609 }
610 Err(error) => RunHandleState::failed(error.clone()),
611 };
612 }
613 if let Ok(mut sender) = sender_slot_for_task.lock() {
614 sender.take();
615 }
616 result
617 });
618 let result = SharedRunResult::new(join);
619 Ok(RunHandle::new(
620 event_sender_slot,
621 event_collector,
622 result,
623 state,
624 cancellation_token,
625 approval_broker,
626 ))
627 }
628
629 fn build_instructions_with_context(
630 &self,
631 agent: &Agent,
632 input_text: &str,
633 config: &RunConfig,
634 ) -> Result<(String, Option<ContextBundle>), String> {
635 let providers = self
636 .default_run_config
637 .context_providers
638 .iter()
639 .chain(config.context_providers.iter())
640 .cloned()
641 .collect::<Vec<_>>();
642 if providers.is_empty() {
643 return Ok((agent.instructions().to_string(), None));
644 }
645 let mut request = ContextRequest::new(agent.name(), input_text);
646 if let Some(max_chars) = config
647 .max_context_chars
648 .or(self.default_run_config.max_context_chars)
649 {
650 request = request.max_prompt_chars(max_chars);
651 }
652 let fragments = collect_context_fragments(&request, &providers)
653 .map_err(|error| format!("context provider failed: {error}"))?;
654 let bundle = assemble_context_fragments(&request, fragments)
655 .map_err(|error| format!("context assembly failed: {error}"))?;
656 let mut instructions = agent.instructions().to_string();
657 if !bundle.prompt.is_empty() {
658 if !instructions.trim().is_empty() {
659 instructions.push_str("\n\n");
660 }
661 instructions.push_str(&bundle.prompt);
662 }
663 Ok((instructions, Some(bundle)))
664 }
665}
666
667#[derive(Default)]
668pub struct RunnerBuilder {
669 model_provider: Option<Arc<dyn ModelProvider>>,
670 settings_file: Option<PathBuf>,
671 default_backend: Option<String>,
672 workspace: Option<PathBuf>,
673 tool_registry: Option<ToolRegistry>,
674 default_run_config: RunConfig,
675}
676
677impl RunnerBuilder {
678 pub fn model_provider(mut self, provider: impl ModelProvider + 'static) -> Self {
679 self.model_provider = Some(Arc::new(provider));
680 self
681 }
682
683 pub fn model_provider_arc(mut self, provider: Arc<dyn ModelProvider>) -> Self {
684 self.model_provider = Some(provider);
685 self
686 }
687
688 pub fn settings_file(mut self, settings_file: impl Into<PathBuf>) -> Self {
689 self.settings_file = Some(settings_file.into());
690 self
691 }
692
693 pub fn default_backend(mut self, default_backend: impl Into<String>) -> Self {
694 self.default_backend = Some(default_backend.into());
695 self
696 }
697
698 pub fn workspace(mut self, workspace: impl Into<PathBuf>) -> Self {
699 self.workspace = Some(workspace.into());
700 self
701 }
702
703 pub fn tool_registry(mut self, registry: ToolRegistry) -> Self {
704 self.tool_registry = Some(registry);
705 self
706 }
707
708 pub fn default_run_config(mut self, config: RunConfig) -> Self {
709 self.default_run_config = config;
710 self
711 }
712
713 pub fn build(self) -> Result<Runner, String> {
714 let model_provider = if let Some(provider) = self.model_provider {
715 provider
716 } else {
717 let settings_file = self
718 .settings_file
719 .unwrap_or_else(|| PathBuf::from("local_settings.json"));
720 let mut provider = VvLlmModelProvider::from_settings_file(settings_file);
721 if let Some(default_backend) = self.default_backend {
722 provider = provider.with_default_backend(default_backend);
723 }
724 Arc::new(provider)
725 };
726 Ok(Runner {
727 model_provider,
728 workspace: self
729 .workspace
730 .unwrap_or_else(|| PathBuf::from("./workspace")),
731 tool_registry: self
732 .tool_registry
733 .unwrap_or_else(crate::tools::build_default_registry),
734 default_run_config: self.default_run_config,
735 })
736 }
737}
738
739#[derive(Clone)]
740struct ArcLlmClient(Arc<dyn LlmClient>);
741
742impl LlmClient for ArcLlmClient {
743 fn complete(
744 &self,
745 request: crate::llm::LlmRequest,
746 ) -> Result<crate::types::LLMResponse, crate::llm::LlmError> {
747 self.0.complete(request)
748 }
749
750 fn complete_with_stream(
751 &self,
752 request: crate::llm::LlmRequest,
753 stream_callback: Option<crate::llm::LlmStreamCallback>,
754 ) -> Result<crate::types::LLMResponse, crate::llm::LlmError> {
755 self.0.complete_with_stream(request, stream_callback)
756 }
757}
758
759fn format_model_error(error: ModelError) -> String {
760 error.to_string()
761}