1use std::sync::Arc;
2
3use base64::engine::general_purpose::URL_SAFE_NO_PAD;
4use base64::Engine as _;
5use dashmap::mapref::entry::Entry;
6use rand::rngs::OsRng;
7use rand::RngCore;
8
9use crate::session::{SessionAnchorSnapshot, SessionRequestStart};
10
11use super::*;
12
13const SESSION_ID_ENTROPY_BYTES: usize = 16;
18
19fn generate_random_session_id() -> SessionId {
23 let mut bytes = [0u8; SESSION_ID_ENTROPY_BYTES];
24 OsRng.fill_bytes(&mut bytes);
25 SessionId::new(format!("sess-{}", URL_SAFE_NO_PAD.encode(bytes)))
28}
29
30fn map_session_persist_error(error: SessionPersistError<KernelError>) -> KernelError {
31 match error {
32 SessionPersistError::Session(error) => KernelError::Session(error),
33 SessionPersistError::Persist(error) => error,
34 }
35}
36
37fn parse_tool_call_operation_execution_nonce(
38 operation: &ToolCallOperation,
39) -> Result<Option<crate::execution_nonce::SignedExecutionNonce>, KernelError> {
40 match operation.execution_nonce.as_ref() {
41 Some(value) => Some(serde_json::from_value(value.clone()).map_err(|error| {
42 KernelError::InvalidConstraint(format!(
43 "session tool call execution_nonce is malformed: {error}"
44 ))
45 }))
46 .transpose(),
47 None => Ok(None),
48 }
49}
50
51impl ChioKernel {
52 pub fn open_session(
53 &self,
54 agent_id: AgentId,
55 issued_capabilities: Vec<CapabilityToken>,
56 ) -> Result<SessionId, KernelError> {
57 let session_id = generate_random_session_id();
58
59 self.open_session_with_id(session_id, agent_id, issued_capabilities)
60 }
61
62 pub fn open_session_with_id(
63 &self,
64 session_id: SessionId,
65 agent_id: AgentId,
66 issued_capabilities: Vec<CapabilityToken>,
67 ) -> Result<SessionId, KernelError> {
68 info!(session_id = %session_id, agent_id = %agent_id, "opening session");
69 let session = self.with_sessions_write(|sessions| {
70 let session = Arc::new(Session::new(
71 session_id.clone(),
72 agent_id,
73 issued_capabilities,
74 ));
75 match sessions.entry(session_id.clone()) {
76 Entry::Occupied(_) => Err(KernelError::SessionAlreadyExists(session_id.clone())),
77 Entry::Vacant(entry) => {
78 entry.insert(Arc::clone(&session));
79 Ok(session)
80 }
81 }
82 })?;
83 let session_snapshot = session.session_anchor_snapshot();
84 if let Err(error) = self.persist_session_anchor_snapshot(&session_snapshot, None) {
85 self.with_sessions_write(|sessions| {
86 sessions.remove(&session_id);
87 Ok(())
88 })?;
89 return Err(error);
90 }
91
92 Ok(session_id)
93 }
94
95 pub fn activate_session(&self, session_id: &SessionId) -> Result<(), KernelError> {
97 self.validate_web3_evidence_prerequisites()?;
98 self.with_session_mut(session_id, |session| {
99 session.activate()?;
100 Ok(())
101 })
102 }
103
104 pub fn set_session_auth_context(
106 &self,
107 session_id: &SessionId,
108 auth_context: SessionAuthContext,
109 ) -> Result<(), KernelError> {
110 self.with_session_mut(session_id, |session| {
111 session
112 .set_auth_context_persisted(auth_context, |session_snapshot, supersedes| {
113 self.persist_session_anchor_snapshot(session_snapshot, supersedes)
114 })
115 .map_err(map_session_persist_error)
116 })
117 }
118
119 pub fn set_session_peer_capabilities(
121 &self,
122 session_id: &SessionId,
123 peer_capabilities: PeerCapabilities,
124 ) -> Result<(), KernelError> {
125 self.with_session_mut(session_id, |session| {
126 session.set_peer_capabilities(peer_capabilities);
127 Ok(())
128 })
129 }
130
131 pub fn replace_session_roots(
133 &self,
134 session_id: &SessionId,
135 roots: Vec<RootDefinition>,
136 ) -> Result<(), KernelError> {
137 self.with_session_mut(session_id, |session| {
138 session.replace_roots(roots);
139 Ok(())
140 })
141 }
142
143 pub fn normalized_session_roots(
145 &self,
146 session_id: &SessionId,
147 ) -> Result<Vec<NormalizedRoot>, KernelError> {
148 self.with_session(session_id, |session| Ok(session.normalized_roots()))
149 }
150
151 pub fn enforceable_filesystem_root_paths(
153 &self,
154 session_id: &SessionId,
155 ) -> Result<Vec<String>, KernelError> {
156 self.with_session(session_id, |session| {
157 Ok(session
158 .enforceable_filesystem_roots()
159 .into_iter()
160 .filter_map(|root| root.normalized_filesystem_path().map(str::to_string))
161 .collect())
162 })
163 }
164
165 pub(crate) fn session_enforceable_filesystem_root_paths_owned(
166 &self,
167 session_id: &SessionId,
168 ) -> Result<Vec<String>, KernelError> {
169 self.with_session(session_id, |session| {
170 Ok(session
171 .enforceable_filesystem_roots()
172 .into_iter()
173 .filter_map(|root| root.normalized_filesystem_path().map(str::to_string))
174 .collect())
175 })
176 }
177
178 pub(crate) fn resource_path_within_root(candidate: &str, root: &str) -> bool {
179 if candidate == root {
180 return true;
181 }
182
183 if root == "/" {
184 return candidate.starts_with('/');
185 }
186
187 candidate
188 .strip_prefix(root)
189 .map(|suffix| suffix.starts_with('/'))
190 .unwrap_or(false)
191 }
192
193 pub(crate) fn resource_path_matches_session_roots(
194 path: &str,
195 session_roots: &[String],
196 ) -> bool {
197 if session_roots.is_empty() {
198 return false;
199 }
200
201 session_roots
202 .iter()
203 .any(|root| Self::resource_path_within_root(path, root))
204 }
205
206 pub(crate) fn enforce_resource_roots(
207 &self,
208 context: &OperationContext,
209 operation: &ReadResourceOperation,
210 ) -> Result<(), KernelError> {
211 match operation.classify_uri_for_runtime() {
212 ResourceUriClassification::NonFileSystem { .. } => Ok(()),
213 ResourceUriClassification::EnforceableFileSystem {
214 normalized_path, ..
215 } => {
216 let session_roots =
217 self.session_enforceable_filesystem_root_paths_owned(&context.session_id)?;
218
219 if Self::resource_path_matches_session_roots(&normalized_path, &session_roots) {
220 Ok(())
221 } else {
222 let reason = if session_roots.is_empty() {
223 "no enforceable filesystem roots are available for this session".to_string()
224 } else {
225 format!(
226 "filesystem-backed resource path {normalized_path} is outside the negotiated roots"
227 )
228 };
229
230 Err(KernelError::ResourceRootDenied {
231 uri: operation.uri.clone(),
232 reason,
233 })
234 }
235 }
236 ResourceUriClassification::UnenforceableFileSystem { reason, .. } => {
237 Err(KernelError::ResourceRootDenied {
238 uri: operation.uri.clone(),
239 reason: format!(
240 "filesystem-backed resource URI could not be enforced: {reason}"
241 ),
242 })
243 }
244 }
245 }
246
247 pub(crate) fn build_resource_read_deny_receipt(
248 &self,
249 operation: &ReadResourceOperation,
250 reason: &str,
251 ) -> Result<ChioReceipt, KernelError> {
252 let receipt_content = receipt_content_for_output(None, None)?;
253 let action = ToolCallAction::from_parameters(serde_json::json!({
254 "uri": &operation.uri,
255 }))
256 .map_err(|error| {
257 KernelError::ReceiptSigningFailed(format!(
258 "failed to hash resource read parameters: {error}"
259 ))
260 })?;
261
262 let receipt = self.build_and_sign_receipt(ReceiptParams {
263 request_id: None,
264 capability_id: &operation.capability.id,
265 tool_name: "resources/read",
266 server_id: "session",
267 decision: Decision::Deny {
268 reason: reason.to_string(),
269 guard: "session_roots".to_string(),
270 },
271 action,
272 content_hash: receipt_content.content_hash,
273 canonical_content: receipt_content.canonical_content,
274 metadata: merge_metadata_objects(
275 Some(serde_json::json!({
276 "resource": {
277 "uri": &operation.uri,
278 }
279 })),
280 receipt_attribution_metadata(&operation.capability, None),
281 ),
282 timestamp: current_unix_timestamp(),
283 trust_level: chio_core::receipt::kinds::TrustLevel::default(),
284 tenant_id: None,
285 })?;
286
287 self.record_chio_receipt(&receipt)?;
288 Ok(receipt)
289 }
290
291 pub fn subscribe_session_resource(
293 &self,
294 session_id: &SessionId,
295 capability: &CapabilityToken,
296 agent_id: &str,
297 uri: &str,
298 ) -> Result<(), KernelError> {
299 self.validate_non_tool_capability(capability, agent_id)?;
300
301 if !capability_matches_resource_subscription(capability, uri)? {
302 return Err(KernelError::OutOfScopeResource {
303 uri: uri.to_string(),
304 });
305 }
306
307 if !self.resource_exists(uri)? {
308 return Err(KernelError::ResourceNotRegistered(uri.to_string()));
309 }
310
311 self.with_session_mut(session_id, |session| {
312 session.subscribe_resource(uri.to_string());
313 Ok(())
314 })
315 }
316
317 pub fn unsubscribe_session_resource(
319 &self,
320 session_id: &SessionId,
321 uri: &str,
322 ) -> Result<(), KernelError> {
323 self.with_session_mut(session_id, |session| {
324 session.unsubscribe_resource(uri);
325 Ok(())
326 })
327 }
328
329 pub fn session_has_resource_subscription(
331 &self,
332 session_id: &SessionId,
333 uri: &str,
334 ) -> Result<bool, KernelError> {
335 self.with_session(
336 session_id,
337 |session| Ok(session.is_resource_subscribed(uri)),
338 )
339 }
340
341 pub fn begin_draining_session(&self, session_id: &SessionId) -> Result<(), KernelError> {
343 self.with_session_mut(session_id, |session| {
344 session.begin_draining()?;
345 Ok(())
346 })
347 }
348
349 pub fn close_session(&self, session_id: &SessionId) -> Result<(), KernelError> {
351 self.with_session_mut(session_id, |session| {
352 session
353 .close_persisted(|session_snapshot, supersedes| {
354 self.persist_session_anchor_snapshot(session_snapshot, supersedes)
355 })
356 .map_err(map_session_persist_error)
357 })
358 }
359
360 pub fn session(&self, session_id: &SessionId) -> Option<Session> {
362 self.with_sessions_read(|sessions| {
363 Ok(sessions
364 .get(session_id)
365 .map(|session| session.value().as_ref().clone()))
366 })
367 .ok()
368 .flatten()
369 }
370
371 pub fn session_count(&self) -> usize {
372 self.with_sessions_read(|sessions| Ok(sessions.len()))
373 .unwrap_or(0)
374 }
375
376 pub fn resource_provider_count(&self) -> usize {
377 self.resource_providers.len()
378 }
379
380 pub fn prompt_provider_count(&self) -> usize {
381 self.prompt_providers.len()
382 }
383
384 pub fn begin_session_request(
386 &self,
387 context: &OperationContext,
388 operation_kind: OperationKind,
389 cancellable: bool,
390 ) -> Result<(), KernelError> {
391 let start = self.with_sessions_write(|sessions| {
392 begin_session_request_in_sessions(sessions, context, operation_kind, cancellable)
393 })?;
394 if let Err(error) = self.persist_request_lineage_snapshot(&start) {
395 let _ = self.with_sessions_write(|sessions| {
396 if let Ok(session) = session_from_map(sessions, &start.session.session_id) {
397 session.discard_unpersisted_request_start(&start.lineage.request_id);
398 }
399 Ok(())
400 });
401 return Err(error);
402 }
403 Ok(())
404 }
405
406 fn begin_or_resume_execution_nonce_request(
407 &self,
408 context: &OperationContext,
409 operation_kind: OperationKind,
410 execution_nonce: Option<&crate::execution_nonce::SignedExecutionNonce>,
411 ) -> Result<(), KernelError> {
412 if let Some(nonce) = execution_nonce
413 .filter(|nonce| nonce.nonce.bound_to.request_id == context.request_id.as_str())
414 {
415 let resumed = self.with_sessions_read(|sessions| {
416 let session = session_from_map(sessions, &context.session_id)?;
417 if session.inflight().get(&context.request_id).is_some() {
418 session.validate_execution_nonce_retry(
419 context,
420 operation_kind,
421 nonce.nonce_id(),
422 )?;
423 return Ok(true);
424 }
425 if session.terminal().get(&context.request_id).is_some() {
426 return Err(crate::session::SessionError::ExecutionNonceRetryMismatch {
427 request_id: context.request_id.clone(),
428 }
429 .into());
430 }
431 Ok(false)
432 })?;
433 if resumed {
434 return Ok(());
435 }
436 }
437 self.begin_session_request(context, operation_kind, true)
438 }
439
440 fn finish_execution_nonce_request(
441 &self,
442 context: &OperationContext,
443 response: Option<&ToolCallResponse>,
444 terminal_state: OperationTerminalState,
445 ) -> Result<(), KernelError> {
446 if let Some(nonce) = response
447 .filter(|response| response.output.is_none())
448 .and_then(|response| response.execution_nonce.as_deref())
449 {
450 return self.with_sessions_write(|sessions| {
451 session_from_map(sessions, &context.session_id)?
452 .mark_execution_nonce_pending(&context.request_id, nonce.nonce_id())?;
453 Ok(())
454 });
455 }
456 self.complete_session_request_with_terminal_state(
457 &context.session_id,
458 &context.request_id,
459 terminal_state,
460 )
461 }
462
463 pub fn begin_child_request(
465 &self,
466 parent_context: &OperationContext,
467 request_id: RequestId,
468 operation_kind: OperationKind,
469 progress_token: Option<ProgressToken>,
470 cancellable: bool,
471 ) -> Result<OperationContext, KernelError> {
472 let (child_context, start) = self.with_sessions_write(|sessions| {
473 begin_child_request_in_sessions(
474 sessions,
475 parent_context,
476 request_id,
477 operation_kind,
478 progress_token,
479 cancellable,
480 )
481 })?;
482 if let Err(error) = self.persist_request_lineage_snapshot(&start) {
483 let _ = self.with_sessions_write(|sessions| {
484 if let Ok(session) = session_from_map(sessions, &start.session.session_id) {
485 session.discard_unpersisted_request_start(&start.lineage.request_id);
486 }
487 Ok(())
488 });
489 return Err(error);
490 }
491 Ok(child_context)
492 }
493
494 pub fn complete_session_request(
496 &self,
497 session_id: &SessionId,
498 request_id: &RequestId,
499 ) -> Result<(), KernelError> {
500 self.complete_session_request_with_terminal_state(
501 session_id,
502 request_id,
503 OperationTerminalState::Completed,
504 )
505 }
506
507 pub fn complete_session_request_with_terminal_state(
509 &self,
510 session_id: &SessionId,
511 request_id: &RequestId,
512 terminal_state: OperationTerminalState,
513 ) -> Result<(), KernelError> {
514 self.with_sessions_write(|sessions| {
515 complete_session_request_with_terminal_state_in_sessions(
516 sessions,
517 session_id,
518 request_id,
519 terminal_state,
520 )
521 })
522 }
523
524 fn signed_session_anchor_for_snapshot(
525 &self,
526 snapshot: &SessionAnchorSnapshot,
527 ) -> Result<chio_core::session::SessionAnchor, KernelError> {
528 let body = chio_core::session::SessionAnchorBody::new(
529 snapshot.session_anchor.id().to_string(),
530 chio_core::session::SessionAnchorContext::new(
531 snapshot.session_id.clone(),
532 snapshot.agent_id.clone(),
533 snapshot.auth_context.clone(),
534 chio_core::session::SessionProofBinding::from_auth_context(&snapshot.auth_context),
535 ),
536 snapshot.session_anchor.auth_epoch(),
537 snapshot.session_anchor.issued_at(),
538 self.config.keypair.public_key(),
539 )
540 .map_err(|error| {
541 KernelError::Internal(format!("failed to build session anchor body: {error}"))
542 })?;
543
544 chio_core::session::SessionAnchor::sign(body, &self.config.keypair).map_err(|error| {
545 KernelError::Internal(format!("failed to sign session anchor: {error}"))
546 })
547 }
548
549 fn persist_session_anchor_snapshot(
550 &self,
551 session: &SessionAnchorSnapshot,
552 supersedes_anchor_id: Option<&str>,
553 ) -> Result<(), KernelError> {
554 let anchor = self.signed_session_anchor_for_snapshot(session)?;
555 let anchor_json = serde_json::to_value(&anchor).map_err(|error| {
556 KernelError::Internal(format!("failed to serialize session anchor: {error}"))
557 })?;
558 self.with_receipt_store(|store| {
559 Ok(store.record_session_anchor(
560 session.session_id.as_str(),
561 &anchor.id,
562 &anchor.auth_context_hash,
563 anchor.issued_at,
564 supersedes_anchor_id,
565 &anchor_json,
566 )?)
567 })?;
568 Ok(())
569 }
570
571 fn persist_request_lineage_snapshot(
572 &self,
573 start: &SessionRequestStart,
574 ) -> Result<(), KernelError> {
575 let local_lineage = &start.lineage;
576 let anchor = self.signed_session_anchor_for_snapshot(&start.session)?;
577 let anchor_reference = anchor.reference().map_err(|error| {
578 KernelError::Internal(format!(
579 "failed to derive session anchor reference: {error}"
580 ))
581 })?;
582 let lineage_mode = if local_lineage.parent_request_id.is_some() {
583 chio_core::session::RequestLineageMode::LocalChild
584 } else {
585 chio_core::session::RequestLineageMode::Root
586 };
587 let mut lineage_record = chio_core::session::RequestLineageRecord::new(
588 local_lineage.request_id.clone(),
589 anchor_reference,
590 local_lineage.operation_kind,
591 lineage_mode,
592 local_lineage.started_at,
593 );
594 if let Some(parent_request_id) = local_lineage.parent_request_id.clone() {
595 lineage_record = lineage_record.with_parent_request_id(parent_request_id);
596 }
597 let lineage_json = serde_json::to_value(&lineage_record).map_err(|error| {
598 KernelError::Internal(format!("failed to serialize request lineage: {error}"))
599 })?;
600 self.with_receipt_store(|store| {
601 Ok(store.record_request_lineage(
602 start.session.session_id.as_str(),
603 local_lineage.request_id.as_str(),
604 local_lineage
605 .parent_request_id
606 .as_ref()
607 .map(|value| value.as_str()),
608 Some(anchor.id.as_str()),
609 local_lineage.started_at,
610 None,
611 &lineage_json,
612 )?)
613 })?;
614 Ok(())
615 }
616
617 pub fn request_session_cancellation(
619 &self,
620 session_id: &SessionId,
621 request_id: &RequestId,
622 ) -> Result<(), KernelError> {
623 self.with_session_mut(session_id, |session| {
624 session
625 .request_cancellation(request_id)
626 .map_err(KernelError::from)
627 })
628 }
629
630 pub fn validate_sampling_request(
632 &self,
633 context: &OperationContext,
634 operation: &CreateMessageOperation,
635 ) -> Result<(), KernelError> {
636 self.with_sessions_read(|sessions| {
637 validate_sampling_request_in_sessions(
638 sessions,
639 self.config.allow_sampling,
640 self.config.allow_sampling_tool_use,
641 context,
642 operation,
643 )
644 })
645 }
646
647 pub fn validate_elicitation_request(
649 &self,
650 context: &OperationContext,
651 operation: &CreateElicitationOperation,
652 ) -> Result<(), KernelError> {
653 self.with_sessions_read(|sessions| {
654 validate_elicitation_request_in_sessions(
655 sessions,
656 self.config.allow_elicitation,
657 context,
658 operation,
659 )
660 })
661 }
662
663 pub fn evaluate_tool_call_operation_with_nested_flow_client<C: NestedFlowClient>(
666 &self,
667 context: &OperationContext,
668 operation: &ToolCallOperation,
669 client: &mut C,
670 ) -> Result<ToolCallResponse, KernelError> {
671 self.validate_web3_evidence_prerequisites()?;
672 let execution_nonce = parse_tool_call_operation_execution_nonce(operation)?;
673 self.begin_or_resume_execution_nonce_request(
674 context,
675 OperationKind::ToolCall,
676 execution_nonce.as_ref(),
677 )?;
678
679 let request = ToolCallRequest {
680 request_id: context.request_id.to_string(),
681 capability: operation.capability.clone(),
682 tool_name: operation.tool_name.clone(),
683 server_id: operation.server_id.clone(),
684 agent_id: context.agent_id.clone(),
685 arguments: operation.arguments.clone(),
686 dpop_proof: None,
687 execution_nonce,
688 governed_intent: operation.governed_intent.clone(),
689 approval_token: operation.approval_token.clone(),
690 approval_tokens: operation.approval_tokens.clone(),
691 threshold_approval_proposal: operation.threshold_approval_proposal.clone(),
692 supplemental_authorization: operation.supplemental_authorization.clone(),
693 model_metadata: operation.model_metadata.clone(),
694 federated_origin_kernel_id: None,
695 };
696
697 let result = self.evaluate_tool_call_with_nested_flow_client(
698 context,
699 &request,
700 client,
701 operation.extra_metadata.clone(),
702 );
703 let terminal_state = match &result {
704 Ok(response) => response.terminal_state.clone(),
705 Err(KernelError::RequestCancelled { request_id, reason })
706 if request_id == &context.request_id =>
707 {
708 self.with_session_mut(&context.session_id, |session| {
709 session.request_cancellation(&context.request_id)?;
710 Ok(())
711 })?;
712 OperationTerminalState::Cancelled {
713 reason: reason.clone(),
714 }
715 }
716 _ => OperationTerminalState::Completed,
717 };
718 self.finish_execution_nonce_request(context, result.as_ref().ok(), terminal_state)?;
719 result
720 }
721
722 pub async fn evaluate_tool_call_operation_with_nested_flow_client_async<C: NestedFlowClient>(
729 &self,
730 context: &OperationContext,
731 operation: &ToolCallOperation,
732 client: &mut C,
733 ) -> Result<ToolCallResponse, KernelError> {
734 self.validate_web3_evidence_prerequisites()?;
735 let execution_nonce = parse_tool_call_operation_execution_nonce(operation)?;
736 self.begin_or_resume_execution_nonce_request(
737 context,
738 OperationKind::ToolCall,
739 execution_nonce.as_ref(),
740 )?;
741
742 let request = ToolCallRequest {
743 request_id: context.request_id.to_string(),
744 capability: operation.capability.clone(),
745 tool_name: operation.tool_name.clone(),
746 server_id: operation.server_id.clone(),
747 agent_id: context.agent_id.clone(),
748 arguments: operation.arguments.clone(),
749 dpop_proof: None,
750 execution_nonce,
751 governed_intent: operation.governed_intent.clone(),
752 approval_token: operation.approval_token.clone(),
753 approval_tokens: operation.approval_tokens.clone(),
754 threshold_approval_proposal: operation.threshold_approval_proposal.clone(),
755 supplemental_authorization: operation.supplemental_authorization.clone(),
756 model_metadata: operation.model_metadata.clone(),
757 federated_origin_kernel_id: None,
758 };
759
760 let result = self
761 .evaluate_tool_call_with_nested_flow_client_async(
762 context,
763 &request,
764 client,
765 operation.extra_metadata.clone(),
766 )
767 .await;
768 let terminal_state = match &result {
769 Ok(response) => response.terminal_state.clone(),
770 Err(KernelError::RequestCancelled { request_id, reason })
771 if request_id == &context.request_id =>
772 {
773 self.with_session_mut(&context.session_id, |session| {
774 session.request_cancellation(&context.request_id)?;
775 Ok(())
776 })?;
777 OperationTerminalState::Cancelled {
778 reason: reason.clone(),
779 }
780 }
781 _ => OperationTerminalState::Completed,
782 };
783 self.finish_execution_nonce_request(context, result.as_ref().ok(), terminal_state)?;
784 result
785 }
786
787 pub fn evaluate_session_operation(
793 &self,
794 context: &OperationContext,
795 operation: &SessionOperation,
796 ) -> Result<SessionOperationResponse, KernelError> {
797 let tenant_id = self.resolve_tenant_id_for_session(Some(&context.session_id));
805 let _tenant_request_scope = self
806 .scope_receipt_tenant_id_for_request(context.request_id.as_str(), tenant_id.clone());
807 let _tenant_scope = scope_receipt_tenant_id(tenant_id);
808
809 self.validate_web3_evidence_prerequisites()?;
810 let operation_kind = operation.kind();
811 let should_track_inflight = matches!(
812 operation,
813 SessionOperation::ToolCall(_)
814 | SessionOperation::ReadResource(_)
815 | SessionOperation::GetPrompt(_)
816 | SessionOperation::Complete(_)
817 );
818 let parsed_tool_call_execution_nonce = match operation {
819 SessionOperation::ToolCall(tool_call) => {
820 parse_tool_call_operation_execution_nonce(tool_call)?
821 }
822 _ => None,
823 };
824
825 if should_track_inflight {
826 if matches!(operation, SessionOperation::ToolCall(_)) {
827 self.begin_or_resume_execution_nonce_request(
828 context,
829 operation_kind,
830 parsed_tool_call_execution_nonce.as_ref(),
831 )?;
832 } else {
833 self.begin_session_request(context, operation_kind, true)?;
834 }
835 } else {
836 self.with_session_mut(&context.session_id, |session| {
837 session.validate_context(context)?;
838 session.ensure_operation_allowed(operation_kind)?;
839 Ok(())
840 })?;
841 }
842
843 let evaluation = match operation {
844 SessionOperation::ToolCall(tool_call) => {
845 let request = ToolCallRequest {
846 request_id: context.request_id.to_string(),
847 capability: tool_call.capability.clone(),
848 tool_name: tool_call.tool_name.clone(),
849 server_id: tool_call.server_id.clone(),
850 agent_id: context.agent_id.clone(),
851 arguments: tool_call.arguments.clone(),
852 dpop_proof: None,
853 execution_nonce: parsed_tool_call_execution_nonce,
854 governed_intent: tool_call.governed_intent.clone(),
855 approval_token: tool_call.approval_token.clone(),
856 approval_tokens: tool_call.approval_tokens.clone(),
857 threshold_approval_proposal: tool_call.threshold_approval_proposal.clone(),
858 supplemental_authorization: tool_call.supplemental_authorization.clone(),
859 model_metadata: tool_call.model_metadata.clone(),
860 federated_origin_kernel_id: None,
861 };
862 let session_roots =
863 self.session_enforceable_filesystem_root_paths_owned(&context.session_id)?;
864
865 self.evaluate_tool_call_sync_with_session_context(
869 &request,
870 Some(session_roots.as_slice()),
871 tool_call.extra_metadata.clone(),
872 Some(&context.session_id),
873 )
874 .map(SessionOperationResponse::ToolCall)
875 }
876 SessionOperation::CreateMessage(_) => Err(KernelError::Internal(
877 "sampling/createMessage must be evaluated by an MCP edge with a client transport"
878 .to_string(),
879 )),
880 SessionOperation::CreateElicitation(_) => Err(KernelError::Internal(
881 "elicitation/create must be evaluated by an MCP edge with a client transport"
882 .to_string(),
883 )),
884 SessionOperation::ListRoots => {
885 let roots = self
886 .session(&context.session_id)
887 .ok_or_else(|| KernelError::UnknownSession(context.session_id.clone()))?
888 .roots();
889 Ok(SessionOperationResponse::RootList { roots })
890 }
891 SessionOperation::ListResources => {
892 let resources = self
893 .list_resources_for_session(&context.session_id)?
894 .into_iter()
895 .collect();
896 Ok(SessionOperationResponse::ResourceList { resources })
897 }
898 SessionOperation::ReadResource(resource_read) => {
899 self.evaluate_resource_read(context, resource_read)
900 }
901 SessionOperation::ListResourceTemplates => {
902 let templates = self.list_resource_templates_for_session(&context.session_id)?;
903 Ok(SessionOperationResponse::ResourceTemplateList { templates })
904 }
905 SessionOperation::ListPrompts => {
906 let prompts = self.list_prompts_for_session(&context.session_id)?;
907 Ok(SessionOperationResponse::PromptList { prompts })
908 }
909 SessionOperation::GetPrompt(prompt_get) => self
910 .evaluate_prompt_get(context, prompt_get)
911 .map(|prompt| SessionOperationResponse::PromptGet { prompt }),
912 SessionOperation::Complete(complete) => self
913 .evaluate_completion(context, complete)
914 .map(|completion| SessionOperationResponse::Completion { completion }),
915 SessionOperation::ListCapabilities => {
916 let capabilities = self
917 .session(&context.session_id)
918 .ok_or_else(|| KernelError::UnknownSession(context.session_id.clone()))?
919 .capabilities()
920 .to_vec();
921
922 Ok(SessionOperationResponse::CapabilityList { capabilities })
923 }
924 SessionOperation::Heartbeat => Ok(SessionOperationResponse::Heartbeat),
925 };
926
927 if should_track_inflight {
928 let terminal_state = match &evaluation {
929 Ok(SessionOperationResponse::ToolCall(response)) => response.terminal_state.clone(),
930 _ => OperationTerminalState::Completed,
931 };
932 let response = match &evaluation {
933 Ok(SessionOperationResponse::ToolCall(response)) => Some(response),
934 _ => None,
935 };
936 self.finish_execution_nonce_request(context, response, terminal_state)?;
937 }
938
939 evaluation
940 }
941
942 pub(crate) fn list_resources_for_session(
943 &self,
944 session_id: &SessionId,
945 ) -> Result<Vec<ResourceDefinition>, KernelError> {
946 let session = self
947 .session(session_id)
948 .ok_or_else(|| KernelError::UnknownSession(session_id.clone()))?;
949
950 let mut resources = Vec::new();
951 for provider in &self.resource_providers {
952 resources.extend(provider.list_resources().into_iter().filter(|resource| {
953 session.capabilities().iter().any(|capability| {
954 capability_matches_resource_request(capability, &resource.uri).unwrap_or(false)
955 })
956 }));
957 }
958
959 Ok(resources)
960 }
961
962 pub(crate) fn resource_exists(&self, uri: &str) -> Result<bool, KernelError> {
963 for provider in &self.resource_providers {
964 if provider
965 .list_resources()
966 .iter()
967 .any(|resource| resource.uri == uri)
968 {
969 return Ok(true);
970 }
971
972 if provider.read_resource(uri)?.is_some() {
973 return Ok(true);
974 }
975 }
976
977 Ok(false)
978 }
979
980 pub(crate) fn list_resource_templates_for_session(
981 &self,
982 session_id: &SessionId,
983 ) -> Result<Vec<ResourceTemplateDefinition>, KernelError> {
984 let session = self
985 .session(session_id)
986 .ok_or_else(|| KernelError::UnknownSession(session_id.clone()))?;
987
988 let mut templates = Vec::new();
989 for provider in &self.resource_providers {
990 templates.extend(
991 provider
992 .list_resource_templates()
993 .into_iter()
994 .filter(|template| {
995 session.capabilities().iter().any(|capability| {
996 capability_matches_resource_pattern(capability, &template.uri_template)
997 .unwrap_or(false)
998 })
999 }),
1000 );
1001 }
1002
1003 Ok(templates)
1004 }
1005
1006 pub(crate) fn evaluate_resource_read(
1007 &self,
1008 context: &OperationContext,
1009 operation: &ReadResourceOperation,
1010 ) -> Result<SessionOperationResponse, KernelError> {
1011 self.validate_non_tool_capability(&operation.capability, &context.agent_id)?;
1012
1013 if !capability_matches_resource_request(&operation.capability, &operation.uri)? {
1014 return Err(KernelError::OutOfScopeResource {
1015 uri: operation.uri.clone(),
1016 });
1017 }
1018
1019 match self.enforce_resource_roots(context, operation) {
1020 Ok(()) => {}
1021 Err(KernelError::ResourceRootDenied { reason, .. }) => {
1022 let receipt = self.build_resource_read_deny_receipt(operation, &reason)?;
1023 return Ok(SessionOperationResponse::ResourceReadDenied { receipt });
1024 }
1025 Err(error) => return Err(error),
1026 }
1027
1028 for provider in &self.resource_providers {
1029 if let Some(contents) = provider.read_resource(&operation.uri)? {
1030 return Ok(SessionOperationResponse::ResourceRead { contents });
1031 }
1032 }
1033
1034 Err(KernelError::ResourceNotRegistered(operation.uri.clone()))
1035 }
1036
1037 pub(crate) fn list_prompts_for_session(
1038 &self,
1039 session_id: &SessionId,
1040 ) -> Result<Vec<PromptDefinition>, KernelError> {
1041 let session = self
1042 .session(session_id)
1043 .ok_or_else(|| KernelError::UnknownSession(session_id.clone()))?;
1044
1045 let mut prompts = Vec::new();
1046 for provider in &self.prompt_providers {
1047 prompts.extend(provider.list_prompts().into_iter().filter(|prompt| {
1048 session.capabilities().iter().any(|capability| {
1049 capability_matches_prompt_request(capability, &prompt.name).unwrap_or(false)
1050 })
1051 }));
1052 }
1053
1054 Ok(prompts)
1055 }
1056
1057 pub(crate) fn evaluate_prompt_get(
1058 &self,
1059 context: &OperationContext,
1060 operation: &GetPromptOperation,
1061 ) -> Result<PromptResult, KernelError> {
1062 self.validate_non_tool_capability(&operation.capability, &context.agent_id)?;
1063
1064 if !capability_matches_prompt_request(&operation.capability, &operation.prompt_name)? {
1065 return Err(KernelError::OutOfScopePrompt {
1066 prompt: operation.prompt_name.clone(),
1067 });
1068 }
1069
1070 for provider in &self.prompt_providers {
1071 if let Some(prompt) =
1072 provider.get_prompt(&operation.prompt_name, operation.arguments.clone())?
1073 {
1074 return Ok(prompt);
1075 }
1076 }
1077
1078 Err(KernelError::PromptNotRegistered(
1079 operation.prompt_name.clone(),
1080 ))
1081 }
1082
1083 pub(crate) fn evaluate_completion(
1084 &self,
1085 context: &OperationContext,
1086 operation: &CompleteOperation,
1087 ) -> Result<CompletionResult, KernelError> {
1088 self.validate_non_tool_capability(&operation.capability, &context.agent_id)?;
1089
1090 match &operation.reference {
1091 CompletionReference::Prompt { name } => {
1092 if !capability_matches_prompt_request(&operation.capability, name)? {
1093 return Err(KernelError::OutOfScopePrompt {
1094 prompt: name.clone(),
1095 });
1096 }
1097
1098 for provider in &self.prompt_providers {
1099 if let Some(completion) = provider.complete_prompt_argument(
1100 name,
1101 &operation.argument.name,
1102 &operation.argument.value,
1103 &operation.context_arguments,
1104 )? {
1105 return Ok(completion);
1106 }
1107 }
1108
1109 Err(KernelError::PromptNotRegistered(name.clone()))
1110 }
1111 CompletionReference::Resource { uri } => {
1112 if !capability_matches_resource_pattern(&operation.capability, uri)? {
1113 return Err(KernelError::OutOfScopeResource { uri: uri.clone() });
1114 }
1115
1116 for provider in &self.resource_providers {
1117 if let Some(completion) = provider.complete_resource_argument(
1118 uri,
1119 &operation.argument.name,
1120 &operation.argument.value,
1121 &operation.context_arguments,
1122 )? {
1123 return Ok(completion);
1124 }
1125 }
1126
1127 Err(KernelError::ResourceNotRegistered(uri.clone()))
1128 }
1129 }
1130 }
1131}