1use super::ScratchpadEntry;
2use crate::core::a2a::message::{A2AMessage, MessageCategory};
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
6#[serde(rename_all = "snake_case")]
7pub enum AgentRole {
8 Coder,
9 Reviewer,
10 Planner,
11 Explorer,
12 Debugger,
13 Tester,
14 Orchestrator,
15}
16
17pub const MAX_ROLE_ID_BYTES: usize = 48;
19
20pub fn role_id(raw: &str) -> Option<String> {
25 let id = raw.trim().to_ascii_lowercase();
26 let valid = id.len() <= MAX_ROLE_ID_BYTES
27 && id.bytes().next().is_some_and(|b| b.is_ascii_lowercase())
28 && id
29 .bytes()
30 .all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || matches!(b, b'_' | b'-'));
31 valid.then_some(id)
32}
33
34impl AgentRole {
35 pub fn from_str_loose(s: &str) -> Self {
36 match s.to_lowercase().as_str() {
37 "review" | "reviewer" | "code_review" => Self::Reviewer,
38 "plan" | "planner" | "architect" => Self::Planner,
39 "explore" | "explorer" | "research" => Self::Explorer,
40 "debug" | "debugger" => Self::Debugger,
41 "test" | "tester" | "qa" => Self::Tester,
42 "orchestrator" | "coordinator" | "manager" => Self::Orchestrator,
43 _ => Self::Coder,
44 }
45 }
46}
47
48#[derive(Debug, Clone)]
49pub struct ContextDepthConfig {
50 pub max_files_full: usize,
51 pub max_files_signatures: usize,
52 pub preferred_mode: &'static str,
53 pub context_budget_ratio: f64,
54}
55
56impl ContextDepthConfig {
57 pub fn for_role(role: AgentRole) -> Self {
58 match role {
59 AgentRole::Coder => Self {
60 max_files_full: 5,
61 max_files_signatures: 15,
62 preferred_mode: "full",
63 context_budget_ratio: 0.7,
64 },
65 AgentRole::Reviewer => Self {
66 max_files_full: 3,
67 max_files_signatures: 20,
68 preferred_mode: "signatures",
69 context_budget_ratio: 0.5,
70 },
71 AgentRole::Planner => Self {
72 max_files_full: 1,
73 max_files_signatures: 10,
74 preferred_mode: "map",
75 context_budget_ratio: 0.3,
76 },
77 AgentRole::Explorer => Self {
78 max_files_full: 2,
79 max_files_signatures: 8,
80 preferred_mode: "map",
81 context_budget_ratio: 0.4,
82 },
83 AgentRole::Debugger => Self {
84 max_files_full: 8,
85 max_files_signatures: 5,
86 preferred_mode: "full",
87 context_budget_ratio: 0.8,
88 },
89 AgentRole::Tester => Self {
90 max_files_full: 4,
91 max_files_signatures: 10,
92 preferred_mode: "full",
93 context_budget_ratio: 0.6,
94 },
95 AgentRole::Orchestrator => Self {
96 max_files_full: 0,
97 max_files_signatures: 5,
98 preferred_mode: "map",
99 context_budget_ratio: 0.2,
100 },
101 }
102 }
103}
104
105impl From<ScratchpadEntry> for A2AMessage {
106 fn from(entry: ScratchpadEntry) -> Self {
107 Self {
108 id: entry.id,
109 from_agent: entry.from_agent,
110 to_agent: entry.to_agent,
111 task_id: entry.task_id,
112 category: MessageCategory::parse_str(&entry.category),
113 priority: entry.priority,
114 privacy: entry.privacy,
115 content: entry.message,
116 metadata: entry.metadata,
117 project_root: entry.project_root,
118 timestamp: entry.timestamp,
119 read_by: entry.read_by,
120 expires_at: entry.expires_at,
121 }
122 }
123}
124
125impl From<A2AMessage> for ScratchpadEntry {
126 fn from(msg: A2AMessage) -> Self {
127 Self {
128 id: msg.id,
129 from_agent: msg.from_agent,
130 to_agent: msg.to_agent,
131 task_id: msg.task_id,
132 category: msg.category.to_string(),
133 priority: msg.priority,
134 privacy: msg.privacy,
135 message: msg.content,
136 metadata: msg.metadata,
137 project_root: msg.project_root,
138 timestamp: msg.timestamp,
139 read_by: msg.read_by,
140 expires_at: msg.expires_at,
141 }
142 }
143}