1use adk_core::{
2 AdkIdentity, Agent, AppName, Artifacts, CallbackContext, Content, Event, ExecutionIdentity,
3 InvocationContext as InvocationContextTrait, InvocationId, Memory, Part, ReadonlyContext,
4 RequestContext, RunConfig, SecretService, SessionId, UserId,
5};
6use adk_session::Session as AdkSession;
7use async_trait::async_trait;
8use std::collections::HashMap;
9use std::sync::{Arc, RwLock, atomic::AtomicBool};
10
11pub struct MutableSession {
18 inner: Arc<dyn AdkSession>,
20 state: Arc<RwLock<HashMap<String, serde_json::Value>>>,
23 events: Arc<RwLock<Vec<Event>>>,
25}
26
27impl MutableSession {
28 pub fn new(session: Arc<dyn AdkSession>) -> Self {
31 let initial_state = session.state().all();
33 let initial_events = session.events().all();
35
36 Self {
37 inner: session,
38 state: Arc::new(RwLock::new(initial_state)),
39 events: Arc::new(RwLock::new(initial_events)),
40 }
41 }
42
43 pub fn apply_state_delta(&self, delta: &HashMap<String, serde_json::Value>) {
46 if delta.is_empty() {
47 return;
48 }
49
50 let Ok(mut state) = self.state.write() else {
51 tracing::error!("state RwLock poisoned in apply_state_delta — skipping delta");
52 return;
53 };
54 for (key, value) in delta {
55 if !key.starts_with("temp:") {
57 state.insert(key.clone(), value.clone());
58 }
59 }
60 }
61
62 pub fn append_event(&self, event: Event) {
65 let Ok(mut events) = self.events.write() else {
66 tracing::error!("events RwLock poisoned in append_event — event dropped");
67 return;
68 };
69 events.push(event);
70 }
71
72 pub fn events_snapshot(&self) -> Vec<Event> {
75 let Ok(events) = self.events.read() else {
76 tracing::error!("events RwLock poisoned in events_snapshot — returning empty");
77 return Vec::new();
78 };
79 events.clone()
80 }
81
82 pub fn replace_events(&self, new_events: Vec<Event>) {
84 let Ok(mut events) = self.events.write() else {
85 tracing::error!("events RwLock poisoned in replace_events — events unchanged");
86 return;
87 };
88 *events = new_events;
89 }
90
91 pub fn events_len(&self) -> usize {
93 let Ok(events) = self.events.read() else {
94 tracing::error!("events RwLock poisoned in events_len — returning 0");
95 return 0;
96 };
97 events.len()
98 }
99
100 pub fn conversation_history_for_agent_impl(
115 &self,
116 agent_name: Option<&str>,
117 branch: &str,
118 ) -> Vec<adk_core::Content> {
119 let Ok(events) = self.events.read() else {
120 tracing::error!("events RwLock poisoned in conversation_history — returning empty");
121 return Vec::new();
122 };
123 let mut history = Vec::new();
124
125 let mut compaction_boundary = None;
129 for event in events.iter().rev() {
130 if let Some(ref compaction) = event.actions.compaction {
131 history.push(HistoryContent {
132 content: compaction.compacted_content.clone(),
133 invocation_id: None,
134 });
135 compaction_boundary = Some(compaction.end_timestamp);
136 break;
137 }
138 }
139
140 for event in events.iter() {
141 if event.actions.compaction.is_some() {
143 continue;
144 }
145
146 if event.llm_response.partial {
149 continue;
150 }
151
152 if let Some(boundary) = compaction_boundary
154 && event.timestamp <= boundary
155 {
156 continue;
157 }
158
159 if !adk_core::event_belongs_to_branch(branch, &event.branch) {
163 continue;
164 }
165
166 if let Some(name) = agent_name
172 && event.author != "user"
173 && event.author != name
174 {
175 continue;
176 }
177
178 if let Some(content) = &event.llm_response.content {
179 let mut mapped_content = content.clone();
180 mapped_content.role = match (event.author.as_str(), content.role.as_str()) {
181 ("user", _) => "user",
182 (_, "function" | "tool") => content.role.as_str(),
183 _ => "model",
184 }
185 .to_string();
186 history.push(HistoryContent {
187 content: mapped_content,
188 invocation_id: Some(event.invocation_id.clone()),
189 });
190 }
191 }
192
193 close_interrupted_tool_calls(history)
194 }
195}
196
197struct HistoryContent {
198 content: Content,
199 invocation_id: Option<String>,
200}
201
202#[derive(Clone, Copy, PartialEq, Eq, Hash)]
203struct PartLocation {
204 content: usize,
205 part: usize,
206}
207
208struct PendingFunctionCall {
209 location: PartLocation,
210 id: Option<String>,
211 name: String,
212 user_turn: usize,
213 invocation_id: Option<String>,
214}
215
216fn close_interrupted_tool_calls(history: Vec<HistoryContent>) -> Vec<Content> {
226 let mut pending = Vec::<PendingFunctionCall>::new();
227 let mut removed_parts = std::collections::HashSet::<PartLocation>::new();
228 let mut responses_after = HashMap::<usize, Vec<(usize, String, Part)>>::new();
229 let mut user_turn = 0;
230
231 for (content_index, history_content) in history.iter().enumerate() {
235 let content = &history_content.content;
236 for (part_index, part) in content.parts.iter().enumerate() {
237 match part {
238 Part::FunctionCall { name, id, .. } => {
239 pending.push(PendingFunctionCall {
240 location: PartLocation { content: content_index, part: part_index },
241 id: id.clone(),
242 name: name.clone(),
243 user_turn,
244 invocation_id: history_content.invocation_id.clone(),
245 });
246 }
247 Part::FunctionResponse { function_response, id, .. } => {
248 let matching_index = match id.as_deref() {
251 Some(response_id) => {
252 pending.iter().position(|call| call.id.as_deref() == Some(response_id))
253 }
254 None => pending
255 .iter()
256 .position(|call| {
257 call.name == function_response.name
258 && history_content.invocation_id.is_some()
259 && call.invocation_id.as_deref()
260 == history_content.invocation_id.as_deref()
261 })
262 .or_else(|| {
263 let mut same_name =
264 pending.iter().enumerate().filter(|(_, call)| {
265 call.name == function_response.name
266 && (call.invocation_id.is_none()
267 || history_content.invocation_id.is_none())
268 });
269 let first = same_name.next().map(|(index, _)| index);
270 match (first, same_name.next()) {
271 (Some(index), None) => Some(index),
272 _ => None,
273 }
274 }),
275 };
276 if let Some(index) = matching_index {
277 let call = pending.remove(index);
278 if call.user_turn < user_turn {
279 removed_parts
280 .insert(PartLocation { content: content_index, part: part_index });
281 let mut insertion_index = call.location.content;
282 while let Some(next) = history.get(insertion_index + 1) {
283 if next.content.parts.is_empty()
284 || !next
285 .content
286 .parts
287 .iter()
288 .all(|part| matches!(part, Part::FunctionResponse { .. }))
289 {
290 break;
291 }
292 insertion_index += 1;
293 }
294 responses_after.entry(insertion_index).or_default().push((
295 call.location.part,
296 content.role.clone(),
297 part.clone(),
298 ));
299 }
300 }
301 }
302 _ => {}
303 }
304 }
305 if content.role == "user"
306 && content.parts.iter().any(|part| !matches!(part, Part::FunctionResponse { .. }))
307 {
308 user_turn += 1;
309 }
310 }
311
312 for call in pending {
313 if call.user_turn == user_turn {
314 continue;
315 }
316 removed_parts.insert(call.location);
317 }
318
319 let mut normalized = Vec::with_capacity(history.len());
320 for (content_index, history_content) in history.into_iter().enumerate() {
321 let mut content = history_content.content;
322 content.parts = content
323 .parts
324 .into_iter()
325 .enumerate()
326 .filter(|(part_index, _)| {
327 !removed_parts.contains(&PartLocation { content: content_index, part: *part_index })
328 })
329 .map(|(_, part)| part)
330 .collect();
331 if !content.parts.is_empty() {
332 normalized.push(content);
333 }
334 if let Some(mut responses) = responses_after.remove(&content_index) {
335 responses.sort_by_key(|(call_part, _, _)| *call_part);
336 normalized.extend(
337 responses
338 .into_iter()
339 .map(|(_, role, response)| Content { role, parts: vec![response] }),
340 );
341 }
342 }
343
344 normalized
345}
346
347impl adk_core::Session for MutableSession {
348 fn id(&self) -> &str {
349 self.inner.id()
350 }
351
352 fn app_name(&self) -> &str {
353 self.inner.app_name()
354 }
355
356 fn user_id(&self) -> &str {
357 self.inner.user_id()
358 }
359
360 fn state(&self) -> &dyn adk_core::State {
361 self
362 }
363
364 fn conversation_history(&self) -> Vec<adk_core::Content> {
365 self.conversation_history_for_agent_impl(None, "")
366 }
367
368 fn conversation_history_for_agent(&self, agent_name: &str) -> Vec<adk_core::Content> {
369 self.conversation_history_for_agent_impl(Some(agent_name), "")
370 }
371
372 fn conversation_history_scoped(
373 &self,
374 agent_name: Option<&str>,
375 branch: &str,
376 ) -> Vec<adk_core::Content> {
377 self.conversation_history_for_agent_impl(agent_name, branch)
378 }
379}
380
381impl adk_core::State for MutableSession {
382 fn get(&self, key: &str) -> Option<serde_json::Value> {
383 let Ok(state) = self.state.read() else {
384 tracing::error!("state RwLock poisoned in State::get — returning None");
385 return None;
386 };
387 state.get(key).cloned()
388 }
389
390 fn set(&mut self, key: String, value: serde_json::Value) {
391 if let Err(msg) = adk_core::validate_state_key(&key) {
392 tracing::warn!(key = %key, "rejecting invalid state key: {msg}");
393 return;
394 }
395 let Ok(mut state) = self.state.write() else {
396 tracing::error!("state RwLock poisoned in State::set — value dropped");
397 return;
398 };
399 state.insert(key, value);
400 }
401
402 fn all(&self) -> HashMap<String, serde_json::Value> {
403 let Ok(state) = self.state.read() else {
404 tracing::error!("state RwLock poisoned in State::all — returning empty");
405 return HashMap::new();
406 };
407 state.clone()
408 }
409}
410
411pub struct InvocationContext {
417 identity: ExecutionIdentity,
418 agent: Arc<dyn Agent>,
419 user_content: Content,
420 artifacts: Option<Arc<dyn Artifacts>>,
421 memory: Option<Arc<dyn Memory>>,
422 run_config: RunConfig,
423 ended: Arc<AtomicBool>,
424 session: Arc<MutableSession>,
428 request_context: Option<RequestContext>,
432 shared_state: Option<Arc<adk_core::SharedState>>,
434 secret_service: Option<Arc<dyn SecretService>>,
437 cancellation_token: Option<tokio_util::sync::CancellationToken>,
443 orchestration_root_invocation_id: String,
445}
446
447impl InvocationContext {
448 pub fn new_typed(
450 invocation_id: String,
451 agent: Arc<dyn Agent>,
452 user_id: UserId,
453 app_name: AppName,
454 session_id: SessionId,
455 user_content: Content,
456 session: Arc<dyn AdkSession>,
457 ) -> adk_core::Result<Self> {
458 let orchestration_root_invocation_id = invocation_id.clone();
459 let identity = ExecutionIdentity {
460 adk: AdkIdentity { app_name, user_id, session_id },
461 invocation_id: InvocationId::try_from(invocation_id)?,
462 branch: String::new(),
463 agent_name: agent.name().to_string(),
464 };
465 Ok(Self {
466 identity,
467 agent,
468 user_content,
469 artifacts: None,
470 memory: None,
471 run_config: RunConfig::default(),
472 ended: Arc::new(AtomicBool::new(false)),
473 session: Arc::new(MutableSession::new(session)),
474 request_context: None,
475 shared_state: None,
476 secret_service: None,
477 cancellation_token: None,
478 orchestration_root_invocation_id,
479 })
480 }
481
482 pub fn new(
487 invocation_id: String,
488 agent: Arc<dyn Agent>,
489 user_id: String,
490 app_name: String,
491 session_id: String,
492 user_content: Content,
493 session: Arc<dyn AdkSession>,
494 ) -> adk_core::Result<Self> {
495 Self::new_typed(
496 invocation_id,
497 agent,
498 UserId::try_from(user_id)?,
499 AppName::try_from(app_name)?,
500 SessionId::try_from(session_id)?,
501 user_content,
502 session,
503 )
504 }
505
506 pub fn with_mutable_session_typed(
509 invocation_id: String,
510 agent: Arc<dyn Agent>,
511 user_id: UserId,
512 app_name: AppName,
513 session_id: SessionId,
514 user_content: Content,
515 session: Arc<MutableSession>,
516 ) -> adk_core::Result<Self> {
517 let orchestration_root_invocation_id = invocation_id.clone();
518 let identity = ExecutionIdentity {
519 adk: AdkIdentity { app_name, user_id, session_id },
520 invocation_id: InvocationId::try_from(invocation_id)?,
521 branch: String::new(),
522 agent_name: agent.name().to_string(),
523 };
524 Ok(Self {
525 identity,
526 agent,
527 user_content,
528 artifacts: None,
529 memory: None,
530 run_config: RunConfig::default(),
531 ended: Arc::new(AtomicBool::new(false)),
532 session,
533 request_context: None,
534 shared_state: None,
535 secret_service: None,
536 cancellation_token: None,
537 orchestration_root_invocation_id,
538 })
539 }
540
541 pub fn with_mutable_session(
545 invocation_id: String,
546 agent: Arc<dyn Agent>,
547 user_id: String,
548 app_name: String,
549 session_id: String,
550 user_content: Content,
551 session: Arc<MutableSession>,
552 ) -> adk_core::Result<Self> {
553 Self::with_mutable_session_typed(
554 invocation_id,
555 agent,
556 UserId::try_from(user_id)?,
557 AppName::try_from(app_name)?,
558 SessionId::try_from(session_id)?,
559 user_content,
560 session,
561 )
562 }
563
564 pub fn with_branch(mut self, branch: String) -> Self {
566 self.identity.branch = branch;
567 self
568 }
569
570 pub fn with_artifacts(mut self, artifacts: Arc<dyn Artifacts>) -> Self {
572 self.artifacts = Some(artifacts);
573 self
574 }
575
576 pub fn with_memory(mut self, memory: Arc<dyn Memory>) -> Self {
578 self.memory = Some(memory);
579 self
580 }
581
582 pub fn with_run_config(mut self, config: RunConfig) -> Self {
584 self.run_config = config;
585 self
586 }
587
588 pub fn with_request_context(mut self, ctx: RequestContext) -> Self {
596 self.request_context = Some(ctx);
597 self
598 }
599
600 pub fn with_shared_state(mut self, shared: Arc<adk_core::SharedState>) -> Self {
602 self.shared_state = Some(shared);
603 self
604 }
605
606 pub fn with_secret_service(mut self, service: Arc<dyn SecretService>) -> Self {
612 self.secret_service = Some(service);
613 self
614 }
615
616 pub fn with_cancellation_token(mut self, token: tokio_util::sync::CancellationToken) -> Self {
622 self.cancellation_token = Some(token);
623 self
624 }
625
626 pub fn with_orchestration_root_invocation_id(mut self, invocation_id: String) -> Self {
628 self.orchestration_root_invocation_id = invocation_id;
629 self
630 }
631
632 pub fn mutable_session(&self) -> &Arc<MutableSession> {
635 &self.session
636 }
637}
638
639#[async_trait]
640impl ReadonlyContext for InvocationContext {
641 fn invocation_id(&self) -> &str {
642 self.identity.invocation_id.as_ref()
643 }
644
645 fn agent_name(&self) -> &str {
646 self.agent.name()
647 }
648
649 fn user_id(&self) -> &str {
650 self.request_context.as_ref().map_or(self.identity.adk.user_id.as_ref(), |rc| &rc.user_id)
656 }
657
658 fn app_name(&self) -> &str {
659 self.identity.adk.app_name.as_ref()
660 }
661
662 fn session_id(&self) -> &str {
663 self.identity.adk.session_id.as_ref()
664 }
665
666 fn branch(&self) -> &str {
667 &self.identity.branch
668 }
669
670 fn user_content(&self) -> &Content {
671 &self.user_content
672 }
673
674 fn state(&self) -> Option<&dyn adk_core::State> {
675 Some(adk_core::Session::state(self.session.as_ref()))
678 }
679}
680
681#[async_trait]
682impl CallbackContext for InvocationContext {
683 fn artifacts(&self) -> Option<Arc<dyn Artifacts>> {
684 self.artifacts.clone()
685 }
686
687 fn shared_state(&self) -> Option<Arc<adk_core::SharedState>> {
688 self.shared_state.clone()
689 }
690}
691
692#[async_trait]
693impl InvocationContextTrait for InvocationContext {
694 fn agent(&self) -> Arc<dyn Agent> {
695 self.agent.clone()
696 }
697
698 fn memory(&self) -> Option<Arc<dyn Memory>> {
699 self.memory.clone()
700 }
701
702 fn session(&self) -> &dyn adk_core::Session {
703 self.session.as_ref()
704 }
705
706 fn run_config(&self) -> &RunConfig {
707 &self.run_config
708 }
709
710 fn end_invocation(&self) {
711 self.ended.store(true, std::sync::atomic::Ordering::SeqCst);
712 }
713
714 fn ended(&self) -> bool {
715 self.ended.load(std::sync::atomic::Ordering::SeqCst)
716 }
717
718 fn is_cancelled(&self) -> bool {
719 self.cancellation_token
720 .as_ref()
721 .is_some_and(tokio_util::sync::CancellationToken::is_cancelled)
722 }
723
724 fn user_scopes(&self) -> Vec<String> {
725 self.request_context.as_ref().map_or_else(Vec::new, |rc| rc.scopes.clone())
726 }
727
728 fn request_metadata(&self) -> HashMap<String, serde_json::Value> {
729 self.request_context.as_ref().map_or_else(HashMap::new, |rc| {
730 rc.metadata
731 .iter()
732 .map(|(k, v)| (k.clone(), serde_json::Value::String(v.clone())))
733 .collect()
734 })
735 }
736
737 fn orchestration_root_invocation_id(&self) -> &str {
738 &self.orchestration_root_invocation_id
739 }
740
741 async fn get_secret(&self, name: &str) -> adk_core::Result<Option<String>> {
742 let request = adk_core::SecretRequest::new(name)
743 .with_identity(self.app_name(), self.user_id(), self.session_id())
744 .with_invocation_id(self.invocation_id());
745 self.get_secret_for(&request).await
746 }
747
748 async fn get_secret_for(
749 &self,
750 request: &adk_core::SecretRequest,
751 ) -> adk_core::Result<Option<String>> {
752 match &self.secret_service {
753 Some(service) => service.get_secret_for(request).await.map(Some),
754 None => Ok(None),
755 }
756 }
757}