Repository navigation
Expand file tree
/
Copy pathsubagent.rs
More file actions
121 lines (108 loc) · 4.13 KB
/
Copy pathsubagent.rs
File metadata and controls
121 lines (108 loc) · 4.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
//! Dynamic subagent definitions, sessions, and management tools.
//!
//! Subagents let an engine delegate focused work to named worker agents with
//! their own instructions, allowed tools, resource tags, optional output
//! schemas, and optional long-lived session state. This module also owns
//! session compaction and background progress forwarding for those workers.
use anda_core::{
Agent, AgentContext, AgentOutput, BoxError, CompletionRequest, ContentPart, FunctionDefinition,
Json, Message, ModelEffort, Path, PromptCommand, PutMode, Resource, StateFeatures,
StoreFeatures, ToolOutput, Usage, select_resources, validate_function_name,
};
use async_trait::async_trait;
use candid::Principal;
use cbor2::{from_slice, to_canonical_vec};
use parking_lot::{Mutex, RwLock};
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::{
any::{Any, TypeId},
collections::{BTreeMap, BTreeSet, HashMap, VecDeque},
str::FromStr,
sync::{
Arc,
atomic::{AtomicU64, Ordering},
},
};
use crate::{
context::{AgentCtx, BaseCtx, CompletionRunner, SelfCompaction},
hook::{
AgentHook, BackgroundHandle, BackgroundTaskControls, DynAgentHook, DynToolJsonHook,
PrefixedId, ToolBackgroundHook,
},
memory::{Conversation, ConversationRef, ConversationStatus, Conversations},
truncate_utf8_to_max_bytes, unix_ms,
};
mod agent;
mod args;
mod checkpoint;
mod conversation;
mod manager;
mod runtime;
mod session;
pub use self::agent::SubAgent;
pub use self::args::{SubAgentArgs, SubAgentManagerArgs};
pub use self::checkpoint::{
CheckpointStore, SubAgentCheckpoint, SubAgentCheckpoints, SubAgentHandoff,
};
pub use self::conversation::{ConversationRecords, SubAgentConversationRecorder};
pub use self::manager::{SubAgentManager, SubAgentSet, SubAgentSetManager};
pub use self::runtime::{
ExecutionIdentity, MessageDelivery, SubAgentEvent, SubAgentEventKind, SubAgentEvents,
SubAgentLimits, SubAgentMessage, SubAgentScope, WaitMode,
};
pub use self::session::{BackgroundTaskInfo, SubSession, SubSessionStatus, SubSessions};
use self::args::deserialize_optional_model_effort;
use self::conversation::SubAgentConversationLog;
#[cfg(test)]
use self::session::{STATUS_PROGRESS_MAX_BYTES, progress_text, prompt_and_resources_into_content};
use self::session::{SubAgentInput, SubSessionRunner, resources_into_content};
const CONVERSATION_IDLE_MS: u64 = 60 * 1000; // 1 minute
const CONVERSATION_WAIT_BACKGROUND_TASK_MS: u64 = 60 * 60 * 1000; // 1 hour
// How long an idle session waits for new input before re-running its idle bookkeeping.
const SESSION_INPUT_POLL_MS: u64 = 1000;
const SUBAGENT_STORE_PATH: &str = "subagents";
const SUBAGENT_METADATA_LIST_LIMIT: usize = 8;
const DEFAULT_STOP_REASON: &str = "subagent session stopped";
const DEFAULT_CANCEL_REASON: &str = "subagent session cancelled";
fn summarize_items(items: &[String], limit: usize) -> String {
let mut summary = items
.iter()
.take(limit)
.map(String::as_str)
.collect::<Vec<_>>()
.join(", ");
if items.len() > limit {
if !summary.is_empty() {
summary.push_str(", ");
}
summary.push_str(&format!("and {} more", items.len() - limit));
}
summary
}
fn selected_model_label(model: &str) -> Option<String> {
let model = model.trim();
if model.is_empty() {
None
} else {
Some(model.to_ascii_lowercase())
}
}
/// Resolves a subagent's configured idle timeout (in seconds) into the per-session idle window in
/// milliseconds. `0` keeps the engine default; any positive value is clamped to
/// `[1s, CONVERSATION_WAIT_BACKGROUND_TASK_MS]` so a session can neither expire instantly nor
/// outlive the hard background-task wait ceiling.
fn resolve_idle_timeout_ms(idle_timeout_secs: u64) -> u64 {
if idle_timeout_secs == 0 {
CONVERSATION_IDLE_MS
} else {
idle_timeout_secs
.saturating_mul(1000)
.clamp(1000, CONVERSATION_WAIT_BACKGROUND_TASK_MS)
}
}
fn estimated_content_tokens(content: &[ContentPart]) -> u64 {
content.iter().map(|c| c.estimated_tokens() as u64).sum()
}
#[cfg(test)]
mod tests;