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