1use std::{
2 collections::{BTreeMap, BTreeSet},
3 sync::Arc,
4};
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use starweaver_core::{
9 AgentId, ConversationId, Metadata, RunAttachments, RunId, SessionId, TaskId, TraceContext,
10};
11use starweaver_model::{
12 ContentPart, ModelMessage, ModelRequest, ModelRequestPart, ModelResponsePart, ToolReturnPart,
13};
14use starweaver_usage::{
15 PricingEstimate, Usage, UsageAgentTotal, UsageSnapshot, UsageSnapshotEntry,
16 add_optional_pricing,
17};
18
19use crate::{
20 AgentEvent, AgentInfo, AgentStreamQueueRegistry, AgentToolState, BusMessage, DependencyStore,
21 EventBus, HostCapabilities, MessageBus, ModelConfig, NoteStore, ResumableExportOptions,
22 ResumableState, RuntimeEphemeralState, SecurityConfig, ShellEnvironmentSnapshot, StateStore,
23 TASK_SNAPSHOT_EVENT_KIND, Task, TaskManager, TaskSnapshot, ToolCapabilityGrant, ToolConfig,
24 ToolIdWrapper, ToolRuntimeSnapshot, ToolSearchInvalidation, ToolSearchState, runtime_context,
25};
26
27#[derive(Clone, Debug, Deserialize, Serialize)]
29pub struct AgentContext {
30 pub agent_id: AgentId,
32 #[serde(default, skip_serializing_if = "Option::is_none")]
34 pub run_id: Option<RunId>,
35 #[serde(default, skip_serializing_if = "Option::is_none")]
37 pub session_id: Option<SessionId>,
38 #[serde(default, skip_serializing_if = "Option::is_none")]
40 pub parent_run_id: Option<RunId>,
41 #[serde(default, skip_serializing_if = "Option::is_none")]
43 pub parent_task_id: Option<TaskId>,
44 pub conversation_id: ConversationId,
46 #[serde(default, skip_serializing_if = "Vec::is_empty")]
48 pub message_history: Vec<ModelMessage>,
49 #[serde(default, skip_serializing_if = "Vec::is_empty")]
51 pub pending_tool_returns: Vec<ToolReturnPart>,
52 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
54 pub subagent_history: BTreeMap<String, Vec<ModelMessage>>,
55 #[serde(default, skip_serializing_if = "Option::is_none")]
57 pub user_prompts: Option<Vec<ContentPart>>,
58 #[serde(default, skip_serializing_if = "Option::is_none")]
60 pub previous_assistant_response_reference: Option<String>,
61 #[serde(default, skip_serializing_if = "Vec::is_empty")]
63 pub steering_messages: Vec<String>,
64 #[serde(default, skip_serializing_if = "Option::is_none")]
66 pub handoff_message: Option<String>,
67 #[serde(flatten)]
69 pub runtime: RuntimeEphemeralState,
70 #[serde(flatten)]
72 pub tools: AgentToolState,
73 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
75 pub agent_registry: BTreeMap<String, AgentInfo>,
76
77 #[serde(default)]
79 pub usage: Usage,
80 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
82 pub usage_snapshot_entries: BTreeMap<String, UsageSnapshotEntry>,
83 #[serde(default, skip_serializing_if = "ModelConfig::is_default")]
85 pub model_config: ModelConfig,
86 #[serde(default, skip_serializing_if = "ToolConfig::is_default")]
88 pub tool_config: ToolConfig,
89 #[serde(default, skip_serializing_if = "SecurityConfig::is_default")]
91 pub security: SecurityConfig,
92 #[serde(default = "Utc::now")]
94 pub started_at: DateTime<Utc>,
95 #[serde(default, skip_serializing_if = "Option::is_none")]
97 pub ended_at: Option<DateTime<Utc>>,
98 #[serde(default)]
100 pub state: StateStore,
101 #[serde(default)]
103 pub events: EventBus,
104 #[serde(default, skip_serializing_if = "NoteStore::is_empty")]
106 pub notes: NoteStore,
107 #[serde(default)]
109 pub messages: MessageBus,
110 #[serde(default, skip_serializing_if = "TraceContext::is_empty")]
112 pub trace_context: TraceContext,
113 #[serde(default, skip_serializing_if = "Metadata::is_empty")]
115 pub metadata: Metadata,
116 #[serde(skip)]
118 pub dependencies: DependencyStore,
119}
120
121impl AgentContext {
122 #[must_use]
124 pub fn new(agent_id: AgentId) -> Self {
125 let mut agent_registry = BTreeMap::new();
126 agent_registry.insert(
127 agent_id.as_str().to_string(),
128 AgentInfo::new(agent_id.as_str(), agent_id.as_str()),
129 );
130 let mut messages = MessageBus::new();
131 messages.subscribe(agent_id.as_str());
132 Self {
133 agent_id,
134 run_id: None,
135 session_id: None,
136 parent_run_id: None,
137 parent_task_id: None,
138 conversation_id: ConversationId::new(),
139 message_history: Vec::new(),
140 pending_tool_returns: Vec::new(),
141 subagent_history: BTreeMap::new(),
142 user_prompts: None,
143 previous_assistant_response_reference: None,
144 steering_messages: Vec::new(),
145 handoff_message: None,
146 runtime: RuntimeEphemeralState::default(),
147 tools: AgentToolState::default(),
148 agent_registry,
149 usage: Usage::default(),
150 usage_snapshot_entries: BTreeMap::new(),
151 model_config: ModelConfig::default(),
152 tool_config: ToolConfig::default(),
153 security: SecurityConfig::default(),
154 started_at: Utc::now(),
155 ended_at: None,
156 state: StateStore::new(),
157 events: EventBus::new(),
158 notes: NoteStore::new(),
159 messages,
160 trace_context: TraceContext::default(),
161 metadata: Metadata::default(),
162 dependencies: DependencyStore::new(),
163 }
164 }
165
166 #[must_use]
168 pub fn from_state(state: ResumableState) -> Self {
169 let mut context = Self::new(state.agent_id.clone());
170 context.run_id = state.run_id;
171 context.session_id = state.session_id;
172 context.parent_run_id = state.parent_run_id;
173 context.parent_task_id = state.parent_task_id;
174 context.conversation_id = state.conversation_id.unwrap_or_default();
175 context.message_history = state.message_history;
176 context.pending_tool_returns = state.pending_tool_returns;
177 context.subagent_history = state.subagent_history;
178 context.user_prompts = state.user_prompts;
179 context.previous_assistant_response_reference = state.previous_assistant_response_reference;
180 context.steering_messages = state.steering_messages;
181 context.handoff_message = state.handoff_message;
182 context.tools.shell_environment = state.shell_env;
183 context.tools.deferred_call_metadata = state.deferred_tool_metadata;
184 context.agent_registry = state.agent_registry;
185 if context.agent_registry.is_empty() {
186 context.agent_registry.insert(
187 context.agent_id.as_str().to_string(),
188 AgentInfo::new(context.agent_id.as_str(), context.agent_id.as_str()),
189 );
190 }
191 context.security = state.security;
192 context.tools.auto_load_files = state.auto_load_files;
193 context.tools.tasks = TaskManager::from_exported(state.tasks);
194 context.notes = NoteStore::from_map(state.notes);
195 context.tools.loaded_tool_names = state.tool_search_loaded_tools;
196 context.tools.loaded_tool_namespaces = state.tool_search_loaded_namespaces;
197 context.usage = state.usage;
198 context.usage_snapshot_entries = state.usage_snapshot_entries;
199 context.model_config = state.model_config;
200 context.tool_config = state.tool_config;
201 context.started_at = state.started_at;
202 context.state = state.state;
203 context.messages = state.message_bus;
204 context.trace_context = state.trace_snapshot;
205 context.metadata = state.metadata;
206 context
207 }
208
209 #[must_use]
211 pub fn export_state(&self) -> ResumableState {
212 self.export_state_with_options(ResumableExportOptions::curated())
213 }
214
215 #[must_use]
217 pub fn export_full_state(&self) -> ResumableState {
218 self.export_state_with_options(ResumableExportOptions::full())
219 }
220
221 #[must_use]
227 pub fn run_attachments(&self) -> RunAttachments {
228 RunAttachments::from_metadata(self.metadata.clone())
229 }
230
231 #[must_use]
233 pub const fn run_attachment_values(&self) -> &Metadata {
234 &self.metadata
235 }
236
237 pub const fn run_attachment_values_mut(&mut self) -> &mut Metadata {
239 &mut self.metadata
240 }
241
242 pub fn set_run_attachment(
244 &mut self,
245 key: impl Into<String>,
246 value: serde_json::Value,
247 ) -> Option<serde_json::Value> {
248 self.metadata.insert(key.into(), value)
249 }
250
251 pub fn merge_run_attachments(&mut self, attachments: impl Into<Metadata>) {
253 self.metadata.extend(attachments.into());
254 }
255
256 #[must_use]
258 #[allow(clippy::too_many_lines)]
259 pub fn export_state_with_options(&self, options: ResumableExportOptions) -> ResumableState {
260 ResumableState {
261 agent_id: self.agent_id.clone(),
262 run_id: options
263 .include_starweaver_extensions()
264 .then(|| self.run_id.clone())
265 .flatten(),
266 session_id: self.session_id.clone(),
267 parent_run_id: options
268 .include_starweaver_extensions()
269 .then(|| self.parent_run_id.clone())
270 .flatten(),
271 parent_task_id: options
272 .include_starweaver_extensions()
273 .then(|| self.parent_task_id.clone())
274 .flatten(),
275 conversation_id: options
276 .include_starweaver_extensions()
277 .then(|| self.conversation_id.clone()),
278 message_history: if options.include_starweaver_extensions() {
279 self.message_history.clone()
280 } else {
281 Vec::new()
282 },
283 pending_tool_returns: if options.include_starweaver_extensions() {
284 self.pending_tool_returns.clone()
285 } else {
286 Vec::new()
287 },
288 subagent_history: if options.include_subagent() {
289 self.subagent_history.clone()
290 } else {
291 BTreeMap::new()
292 },
293 user_prompts: self.user_prompts.clone(),
294 previous_assistant_response_reference: self
295 .previous_assistant_response_reference
296 .clone(),
297 steering_messages: self.steering_messages.clone(),
298 handoff_message: self.handoff_message.clone(),
299 shell_env: BTreeMap::new(),
300 deferred_tool_metadata: self.tools.deferred_call_metadata.clone(),
301 agent_registry: if options.include_subagent() {
302 self.agent_registry.clone()
303 } else {
304 BTreeMap::new()
305 },
306 approval_required_tools: Vec::new(),
307 approval_required_mcp_servers: Vec::new(),
308 security: if options.include_runtime_policy() {
309 self.security.clone()
310 } else {
311 SecurityConfig::default()
312 },
313 auto_load_files: self.tools.auto_load_files.clone(),
314 tasks: self.tools.tasks.export_tasks(),
315 notes: self.notes.to_map(),
316 tool_search_loaded_tools: self.tools.loaded_tool_names.clone(),
317 tool_search_loaded_namespaces: self.tools.loaded_tool_namespaces.clone(),
318 usage: if options.include_starweaver_extensions() {
319 self.usage.clone()
320 } else {
321 Usage::default()
322 },
323 usage_snapshot_entries: if options.include_usage_ledger() {
324 self.usage_snapshot_entries.clone()
325 } else {
326 BTreeMap::new()
327 },
328 model_config: if options.include_runtime_policy() {
329 self.model_config.clone()
330 } else {
331 ModelConfig::default()
332 },
333 tool_config: if options.include_runtime_policy() {
334 self.tool_config.clone()
335 } else {
336 ToolConfig::default()
337 },
338 started_at: if options.include_starweaver_extensions() {
339 self.started_at
340 } else {
341 DateTime::<Utc>::UNIX_EPOCH
342 },
343 state: if options.include_starweaver_extensions() {
344 self.state.clone()
345 } else {
346 StateStore::new()
347 },
348 message_bus: if options.include_starweaver_extensions() {
349 self.messages.clone()
350 } else {
351 MessageBus::new()
352 },
353 trace_snapshot: if options.include_starweaver_extensions() {
354 self.trace_context.clone()
355 } else {
356 TraceContext::default()
357 },
358 metadata: if options.include_starweaver_extensions() {
359 self.metadata.clone()
360 } else {
361 Metadata::default()
362 },
363 extra: BTreeMap::new(),
364 }
365 }
366
367 pub fn restore_state(&mut self, state: ResumableState) {
369 let dependencies = self.dependencies.clone();
370 let security = self.security.clone();
371 *self = Self::from_state(state);
372 self.dependencies = dependencies;
373 self.security = security;
376 }
377
378 pub fn set_session_id(&mut self, session_id: SessionId) {
380 self.session_id = Some(session_id);
381 }
382
383 #[must_use]
385 pub const fn session_id(&self) -> Option<&SessionId> {
386 self.session_id.as_ref()
387 }
388
389 pub fn prepare_new_run(&mut self) {
391 self.run_id = Some(RunId::new());
392 self.started_at = Utc::now();
393 self.ended_at = None;
394 self.runtime.lifecycle.entered = true;
395 self.runtime.lifecycle.stream_queue_enabled = false;
396 self.runtime.lifecycle.compact_depth = 0;
397 self.runtime.run_toolsets_closed = false;
398 self.runtime.tool_id_wrapper.clear();
399 self.runtime.agent_stream_queues = AgentStreamQueueRegistry::default();
400 if self.parent_run_id.is_none() && !self.metadata.contains_key("parent_agent_id") {
401 self.usage_snapshot_entries.clear();
402 }
403 self.tools.deferred_call_metadata.clear();
404 self.runtime.force_inject_context = false;
405 self.previous_assistant_response_reference = None;
406 self.messages.subscribe(self.agent_id.as_str());
407 }
408
409 pub fn finish_run(&mut self) {
411 self.ended_at = Some(Utc::now());
412 self.runtime.lifecycle.entered = false;
413 }
414
415 #[must_use]
417 pub fn subagent_context(&self, agent_id: impl Into<String>) -> Self {
418 let agent_id = agent_id.into();
419 self.subagent_context_with_agent_id(agent_id.clone(), agent_id)
420 }
421
422 #[must_use]
424 pub fn subagent_context_with_agent_id(
425 &self,
426 agent_name: impl Into<String>,
427 agent_id: impl Into<String>,
428 ) -> Self {
429 let agent_name = agent_name.into();
430 let agent_id = AgentId::from_string(agent_id);
431 let mut child = self.clone();
432 child.agent_id = agent_id.clone();
433 child.run_id = None;
434 child.parent_run_id.clone_from(&self.run_id);
435 child.parent_task_id = None;
436 child.message_history = self
437 .subagent_history
438 .get(agent_id.as_str())
439 .cloned()
440 .unwrap_or_default();
441 child.user_prompts = None;
442 child.previous_assistant_response_reference = None;
443 child.steering_messages = Vec::new();
444 child.handoff_message = None;
445 child.runtime.tool_id_wrapper = ToolIdWrapper::default();
446 child.runtime.tool_tags = Vec::new();
447 child.started_at = Utc::now();
448 child.ended_at = None;
449 child.security = self.security.clone();
450 child.metadata.insert(
451 "parent_agent_id".to_string(),
452 serde_json::json!(self.agent_id.as_str()),
453 );
454 child.metadata.insert(
455 "agent_name".to_string(),
456 serde_json::json!(agent_name.as_str()),
457 );
458 if let Some(run_id) = &self.run_id {
459 child.metadata.insert(
460 "parent_run_id".to_string(),
461 serde_json::json!(run_id.as_str()),
462 );
463 }
464 child.agent_registry.insert(
465 agent_id.as_str().to_string(),
466 AgentInfo::new(agent_id.as_str(), agent_name)
467 .with_parent_agent_id(self.agent_id.as_str()),
468 );
469 child.messages.subscribe(agent_id.as_str());
470 child
471 }
472
473 pub fn absorb_subagent_context(&mut self, child: &Self) {
475 let event_cursor = self.events.len();
476 self.usage = child.usage.clone();
477 self.usage_snapshot_entries
478 .clone_from(&child.usage_snapshot_entries);
479 self.notes = child.notes.clone();
480 self.tools.tasks = child.tools.tasks.clone();
481 self.state = child.state.clone();
482 self.messages = child.messages.clone();
483 self.agent_registry = child.agent_registry.clone();
484 self.subagent_history.insert(
485 child.agent_id.as_str().to_string(),
486 child.message_history.clone(),
487 );
488 for (agent_id, history) in &child.subagent_history {
489 self.subagent_history
490 .entry(agent_id.clone())
491 .or_insert_with(|| history.clone());
492 }
493 for event in child.events.events().iter().skip(event_cursor) {
494 self.events.publish(event.clone());
495 }
496 }
497
498 #[must_use]
500 pub fn with_trace_context(mut self, trace_context: TraceContext) -> Self {
501 self.trace_context = trace_context;
502 self
503 }
504
505 pub fn set_trace_context(&mut self, trace_context: TraceContext) {
507 self.trace_context = trace_context;
508 }
509
510 pub fn record_tool_search_loaded_tool(&mut self, tool_name: impl Into<String>) {
512 push_unique(&mut self.tools.loaded_tool_names, tool_name.into());
513 }
514
515 pub fn record_tool_search_loaded_namespace(&mut self, namespace: impl Into<String>) {
517 push_unique(&mut self.tools.loaded_tool_namespaces, namespace.into());
518 }
519
520 pub fn record_tool_search_loaded(
522 &mut self,
523 tools: impl IntoIterator<Item = impl Into<String>>,
524 namespaces: impl IntoIterator<Item = impl Into<String>>,
525 ) {
526 for tool in tools {
527 self.record_tool_search_loaded_tool(tool);
528 }
529 for namespace in namespaces {
530 self.record_tool_search_loaded_namespace(namespace);
531 }
532 }
533
534 pub fn clear_tool_search_loaded(&mut self) -> ToolSearchInvalidation {
536 ToolSearchInvalidation {
537 removed_tools: std::mem::take(&mut self.tools.loaded_tool_names),
538 removed_namespaces: std::mem::take(&mut self.tools.loaded_tool_namespaces),
539 }
540 }
541
542 pub fn retain_tool_search_loaded(
544 &mut self,
545 mut keep_tool: impl FnMut(&str) -> bool,
546 mut keep_namespace: impl FnMut(&str) -> bool,
547 ) -> ToolSearchInvalidation {
548 ToolSearchInvalidation {
549 removed_tools: retain_matching(&mut self.tools.loaded_tool_names, |tool| {
550 keep_tool(tool)
551 }),
552 removed_namespaces: retain_matching(
553 &mut self.tools.loaded_tool_namespaces,
554 |namespace| keep_namespace(namespace),
555 ),
556 }
557 }
558
559 #[must_use]
561 pub fn tool_search_state(&self) -> ToolSearchState {
562 ToolSearchState {
563 loaded_tools: self.tools.loaded_tool_names.clone(),
564 loaded_namespaces: self.tools.loaded_tool_namespaces.clone(),
565 }
566 }
567
568 pub fn push_message(&mut self, message: ModelMessage) {
570 self.message_history.push(message);
571 }
572
573 pub const fn add_usage(&mut self, usage: &Usage) {
575 self.usage.add_assign(usage);
576 }
577
578 #[must_use]
580 #[allow(clippy::too_many_arguments)]
581 pub fn update_usage_snapshot_entry(
582 &mut self,
583 agent_id: impl Into<String>,
584 agent_name: impl Into<String>,
585 model_id: impl Into<String>,
586 usage: Usage,
587 estimate_pricing: Option<PricingEstimate>,
588 usage_id: Option<String>,
589 source: impl Into<String>,
590 ledger_key: Option<String>,
591 ) -> UsageSnapshot {
592 let agent_id = agent_id.into();
593 let entry = UsageSnapshotEntry {
594 agent_id: agent_id.clone(),
595 agent_name: agent_name.into(),
596 model_id: model_id.into(),
597 usage,
598 estimate_pricing,
599 usage_id,
600 source: source.into(),
601 };
602 self.usage_snapshot_entries
603 .insert(ledger_key.unwrap_or(agent_id), entry);
604 self.build_usage_snapshot()
605 }
606
607 #[must_use]
613 pub fn update_external_usage_snapshot_entry(
614 &mut self,
615 source_id: impl Into<String>,
616 source_name: impl Into<String>,
617 model_id: impl Into<String>,
618 usage: Usage,
619 estimate_pricing: Option<PricingEstimate>,
620 usage_id: Option<String>,
621 ) -> UsageSnapshot {
622 let source_id = source_id.into();
623 let ledger_key = usage_id.as_ref().map_or_else(
624 || format!("external:{source_id}"),
625 |usage_id| format!("external:{source_id}:{usage_id}"),
626 );
627 self.update_usage_snapshot_entry(
628 source_id,
629 source_name,
630 model_id,
631 usage,
632 estimate_pricing,
633 usage_id,
634 "external",
635 Some(ledger_key),
636 )
637 }
638
639 #[must_use]
641 pub fn build_usage_snapshot(&self) -> UsageSnapshot {
642 let mut total_usage = Usage::default();
643 let mut estimate_pricing = None;
644 let mut agent_usages = BTreeMap::<String, UsageAgentTotal>::new();
645 let mut model_usages = BTreeMap::<String, Usage>::new();
646 let mut model_estimate_pricing = BTreeMap::<String, PricingEstimate>::new();
647 let mut entries = self
648 .usage_snapshot_entries
649 .values()
650 .cloned()
651 .collect::<Vec<_>>();
652 entries.sort_by(|left, right| left.agent_id.cmp(&right.agent_id));
653 for entry in &entries {
654 total_usage.add_assign(&entry.usage);
655 add_optional_pricing(&mut estimate_pricing, entry.estimate_pricing.as_ref());
656 if let Some(pricing) = &entry.estimate_pricing {
657 model_estimate_pricing
658 .entry(entry.model_id.clone())
659 .or_default()
660 .add_assign(pricing);
661 }
662 if let Some(agent_total) = agent_usages.get_mut(&entry.agent_id) {
663 agent_total.usage.add_assign(&entry.usage);
664 if agent_total.model_id != entry.model_id {
665 agent_total.model_id = "multiple".to_string();
666 }
667 if agent_total.usage_id != entry.usage_id {
668 agent_total.usage_id = None;
669 }
670 add_optional_pricing(
671 &mut agent_total.estimate_pricing,
672 entry.estimate_pricing.as_ref(),
673 );
674 } else {
675 agent_usages.insert(
676 entry.agent_id.clone(),
677 UsageAgentTotal {
678 agent_name: entry.agent_name.clone(),
679 model_id: entry.model_id.clone(),
680 usage: entry.usage.clone(),
681 estimate_pricing: entry.estimate_pricing.clone(),
682 usage_id: entry.usage_id.clone(),
683 source: entry.source.clone(),
684 },
685 );
686 }
687 model_usages
688 .entry(entry.model_id.clone())
689 .or_default()
690 .add_assign(&entry.usage);
691 }
692 UsageSnapshot {
693 run_id: self
694 .parent_run_id
695 .as_ref()
696 .or(self.run_id.as_ref())
697 .map_or_else(String::new, |run_id| run_id.as_str().to_string()),
698 latest_usage: None,
699 total_usage,
700 estimate_pricing,
701 entries,
702 agent_usages,
703 model_usages,
704 model_estimate_pricing,
705 }
706 }
707
708 #[must_use]
710 pub fn latest_request_usage(&self) -> Option<&Usage> {
711 self.message_history.iter().rev().find_map(|message| {
712 let ModelMessage::Response(response) = message else {
713 return None;
714 };
715 (!response.usage.is_empty()).then_some(&response.usage)
716 })
717 }
718
719 #[must_use]
721 pub fn latest_request_total_tokens(&self) -> Option<u64> {
722 self.latest_request_usage()
723 .and_then(|usage| (usage.total_tokens > 0).then_some(usage.total_tokens))
724 }
725
726 pub fn repair_dangling_tool_calls(&mut self, reason: impl Into<String>) -> usize {
732 let reason = reason.into();
733 let mut pending = Vec::<(String, String)>::new();
734 for message in &self.message_history {
735 match message {
736 ModelMessage::Response(response) => {
737 for part in &response.parts {
738 match part {
739 ModelResponsePart::ToolCall(call)
740 | ModelResponsePart::ProviderToolCall { call, .. } => {
741 pending.push((call.id.clone(), call.name.clone()));
742 }
743 ModelResponsePart::Text { .. }
744 | ModelResponsePart::ProviderText { .. }
745 | ModelResponsePart::Thinking { .. }
746 | ModelResponsePart::ProviderThinking { .. }
747 | ModelResponsePart::NativeToolCall { .. }
748 | ModelResponsePart::NativeToolReturn { .. }
749 | ModelResponsePart::File { .. }
750 | ModelResponsePart::Compaction { .. }
751 | ModelResponsePart::ProviderOpaque { .. } => {}
752 }
753 }
754 }
755 ModelMessage::Request(request) => {
756 for part in &request.parts {
757 if let ModelRequestPart::ToolReturn(tool_return) = part {
758 pending.retain(|(id, _)| id != &tool_return.tool_call_id);
759 }
760 }
761 }
762 }
763 }
764 if pending.is_empty() {
765 return 0;
766 }
767 let repaired_count = pending.len();
768 let mut parts = Vec::with_capacity(repaired_count);
769 for (tool_call_id, name) in pending {
770 let mut metadata = Metadata::default();
771 metadata.insert(
772 "starweaver.repaired_dangling_tool_call".to_string(),
773 serde_json::json!(true),
774 );
775 metadata.insert("reason".to_string(), serde_json::json!(reason.clone()));
776 parts.push(ModelRequestPart::ToolReturn(
777 ToolReturnPart::new(
778 tool_call_id,
779 name,
780 serde_json::json!({
781 "error": "tool_call_interrupted",
782 "message": reason.clone(),
783 }),
784 )
785 .with_error(true)
786 .with_metadata(metadata),
787 ));
788 }
789 self.message_history
790 .push(ModelMessage::Request(ModelRequest {
791 parts,
792 timestamp: Some(Utc::now()),
793 instructions: None,
794 run_id: self.run_id.clone(),
795 conversation_id: Some(self.conversation_id.clone()),
796 metadata: serde_json::json!({
797 "starweaver.repaired_dangling_tool_calls": true,
798 })
799 .as_object()
800 .cloned()
801 .unwrap_or_default(),
802 }));
803 repaired_count
804 }
805
806 pub fn publish_event(&mut self, event: AgentEvent) {
808 self.events.publish(event);
809 }
810
811 #[must_use]
813 pub fn tasks(&self) -> Vec<Task> {
814 self.tools.tasks.list_all()
815 }
816
817 pub fn set_tasks(&mut self, tasks: Vec<Task>) {
819 self.tools.tasks.replace_all(tasks);
820 }
821
822 #[must_use]
824 pub fn task_snapshot(&self) -> TaskSnapshot {
825 TaskSnapshot {
826 tasks: self.tasks(),
827 }
828 }
829
830 pub fn publish_task_snapshot_event(&mut self) {
832 self.publish_event(AgentEvent::new(
833 TASK_SNAPSHOT_EVENT_KIND,
834 self.task_snapshot().into_payload(),
835 ));
836 }
837
838 pub fn enqueue_message(&mut self, message: BusMessage) {
840 self.messages.send(message);
841 }
842
843 pub fn send_message(&mut self, message: BusMessage) -> BusMessage {
845 self.messages.send(message)
846 }
847
848 pub fn consume_messages(&mut self) -> Vec<BusMessage> {
850 self.messages.consume(self.agent_id.as_str())
851 }
852
853 pub fn consume_messages_for(&mut self, agent_id: &str) -> Vec<BusMessage> {
855 self.messages.consume(agent_id)
856 }
857
858 pub fn consume_messages_matching(
860 &mut self,
861 predicate: impl Fn(&BusMessage) -> bool,
862 ) -> Vec<BusMessage> {
863 self.messages
864 .consume_matching(self.agent_id.as_str(), predicate)
865 }
866
867 pub fn subscribe_messages(&mut self) {
869 self.messages.subscribe(self.agent_id.as_str());
870 }
871
872 #[must_use]
874 pub fn get_wrapper_metadata(&self) -> Metadata {
875 let mut metadata = Metadata::default();
876 if let Some(run_id) = &self.run_id {
877 metadata.insert("run_id".to_string(), serde_json::json!(run_id.as_str()));
878 }
879 if let Some(parent_run_id) = &self.parent_run_id {
880 metadata.insert(
881 "parent_run_id".to_string(),
882 serde_json::json!(parent_run_id.as_str()),
883 );
884 }
885 if let Some(parent_task_id) = &self.parent_task_id {
886 metadata.insert(
887 "parent_task_id".to_string(),
888 serde_json::json!(parent_task_id.as_str()),
889 );
890 }
891 metadata.insert(
892 "agent_id".to_string(),
893 serde_json::json!(self.agent_id.as_str()),
894 );
895 for (key, value) in &self.runtime.wrapper_metadata {
896 metadata.insert(key.clone(), value.clone());
897 }
898 metadata
899 }
900
901 #[must_use]
907 pub fn tool_dependency_store(&self) -> DependencyStore {
908 let mut dependencies = self.dependencies.clone();
909 dependencies.insert(self.host_capabilities());
910 dependencies.insert(self.tool_runtime_snapshot());
911 dependencies
912 }
913
914 #[must_use]
920 pub fn filtered_tool_dependency_store(
921 &self,
922 host_capability_names: &BTreeSet<String>,
923 shell_environment: bool,
924 ) -> DependencyStore {
925 let mut dependencies = self.dependencies.clone();
926 dependencies.insert(self.host_capabilities_subset(host_capability_names));
927 dependencies.insert(self.filtered_tool_runtime_snapshot());
928 if shell_environment {
929 dependencies.insert(self.shell_environment_snapshot());
930 }
931 dependencies
932 }
933
934 #[must_use]
940 pub fn strict_tool_dependency_store(
941 &self,
942 host_capability_names: &BTreeSet<String>,
943 shell_environment: bool,
944 ) -> DependencyStore {
945 let mut dependencies = DependencyStore::new();
946 dependencies.insert(self.host_capabilities_subset(host_capability_names));
947 dependencies.insert(self.filtered_tool_runtime_snapshot());
948 if shell_environment {
949 dependencies.insert(self.shell_environment_snapshot());
950 }
951 dependencies
952 }
953
954 #[must_use]
956 pub fn host_capabilities(&self) -> HostCapabilities {
957 HostCapabilities::new(self.dependencies.clone())
958 }
959
960 #[must_use]
962 pub fn host_capabilities_subset(&self, names: &BTreeSet<String>) -> HostCapabilities {
963 HostCapabilities::new(self.dependencies.subset(names))
964 }
965
966 #[must_use]
968 pub fn tool_runtime_snapshot(&self) -> ToolRuntimeSnapshot {
969 ToolRuntimeSnapshot::new(
970 self.model_config.clone(),
971 self.tool_config.clone(),
972 self.tools.shell_environment.clone(),
973 )
974 }
975
976 #[must_use]
978 pub fn filtered_tool_runtime_snapshot(&self) -> ToolRuntimeSnapshot {
979 ToolRuntimeSnapshot::filtered(self.model_config.clone(), self.tool_config.clone())
980 }
981
982 #[must_use]
984 pub fn shell_environment_snapshot(&self) -> ShellEnvironmentSnapshot {
985 ShellEnvironmentSnapshot::new(self.tools.shell_environment.clone())
986 }
987
988 pub fn insert_dependency<T>(&mut self, value: T)
990 where
991 T: Send + Sync + 'static,
992 {
993 self.dependencies.insert(value);
994 }
995
996 pub fn insert_named_dependency<T>(&mut self, name: impl Into<String>, value: T)
998 where
999 T: Send + Sync + 'static,
1000 {
1001 self.dependencies.insert_named(name, value);
1002 }
1003
1004 pub fn grant_tool_capabilities(
1006 &mut self,
1007 tool_name: impl Into<String>,
1008 grant: ToolCapabilityGrant,
1009 ) {
1010 self.runtime
1011 .tool_capability_grants
1012 .insert(tool_name.into(), grant);
1013 }
1014
1015 #[must_use]
1017 pub fn tool_capability_grant(&self, tool_name: &str) -> ToolCapabilityGrant {
1018 self.runtime
1019 .tool_capability_grants
1020 .get(tool_name)
1021 .cloned()
1022 .unwrap_or_default()
1023 }
1024
1025 #[must_use]
1027 pub fn dependency<T>(&self) -> Option<Arc<T>>
1028 where
1029 T: Send + Sync + 'static,
1030 {
1031 self.dependencies.get::<T>()
1032 }
1033
1034 #[must_use]
1036 pub fn named_dependency<T>(&self, name: &str) -> Option<Arc<T>>
1037 where
1038 T: Send + Sync + 'static,
1039 {
1040 self.dependencies.get_named::<T>(name)
1041 }
1042
1043 pub const fn set_context_window(&mut self, context_window: Option<u64>) {
1045 self.model_config.context_window = context_window;
1046 }
1047
1048 pub fn merge_model_config(&mut self, model_config: ModelConfig) {
1050 self.model_config.merge_from(model_config);
1051 }
1052
1053 pub fn set_tool_config(&mut self, mut tool_config: ToolConfig) {
1055 tool_config.normalize();
1056 self.tool_config = tool_config;
1057 }
1058
1059 pub fn merge_tool_config(&mut self, mut tool_config: ToolConfig) {
1061 tool_config.normalize();
1062 let existing_dynamic_patterns = self.tool_config.view_relaxed_text_dynamic_patterns.clone();
1063 for (source, patterns) in existing_dynamic_patterns {
1064 tool_config
1065 .view_relaxed_text_dynamic_patterns
1066 .entry(source)
1067 .or_insert(patterns);
1068 }
1069 self.tool_config = tool_config;
1070 }
1071
1072 #[must_use]
1074 pub fn render_runtime_context(&self, is_user_prompt: bool) -> Option<String> {
1075 runtime_context::render_runtime_context(self, is_user_prompt)
1076 }
1077}
1078
1079fn push_unique(values: &mut Vec<String>, value: String) {
1080 if !value.is_empty() && !values.contains(&value) {
1081 values.push(value);
1082 }
1083}
1084
1085fn retain_matching(values: &mut Vec<String>, mut keep: impl FnMut(&str) -> bool) -> Vec<String> {
1086 let mut retained = Vec::with_capacity(values.len());
1087 let mut removed = Vec::new();
1088 for value in std::mem::take(values) {
1089 if keep(&value) {
1090 retained.push(value);
1091 } else {
1092 removed.push(value);
1093 }
1094 }
1095 *values = retained;
1096 removed
1097}
1098
1099impl Default for AgentContext {
1100 fn default() -> Self {
1101 Self::new(AgentId::default())
1102 }
1103}