use chio_core::capability::{
governance::{GovernedApprovalToken, GovernedTransactionIntent, ThresholdApprovalProposal},
scope::ModelMetadata,
token::CapabilityToken,
};
use chio_core::receipt::body::ChioReceipt;
use chio_core::session::{
CreateElicitationOperation, CreateElicitationResult, CreateMessageOperation,
CreateMessageResult, OperationContext, OperationTerminalState, RequestId, RootDefinition,
};
use crate::dpop;
use crate::execution_nonce::SignedExecutionNonce;
use crate::{AgentId, KernelError, ServerId};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Verdict {
Allow,
Deny,
PendingApproval,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ToolCallRequest {
pub request_id: String,
pub capability: CapabilityToken,
pub tool_name: String,
pub server_id: ServerId,
pub agent_id: AgentId,
pub arguments: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dpop_proof: Option<dpop::DpopProof>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub execution_nonce: Option<SignedExecutionNonce>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub governed_intent: Option<GovernedTransactionIntent>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub approval_token: Option<GovernedApprovalToken>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub approval_tokens: Vec<GovernedApprovalToken>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub threshold_approval_proposal: Option<ThresholdApprovalProposal>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub supplemental_authorization:
Option<chio_core::capability::supplemental_authorization::OpaqueSupplementalAuthorization>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model_metadata: Option<ModelMetadata>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub federated_origin_kernel_id: Option<String>,
}
impl ToolCallRequest {
pub fn validate_authorization_extensions(&self) -> Result<(), chio_core::Error> {
self.approval_artifact_digest()?;
Ok(())
}
pub fn approval_artifact_digest(&self) -> Result<Option<String>, chio_core::Error> {
if self.approval_token.is_some() && !self.approval_tokens.is_empty() {
return Err(chio_core::Error::CanonicalJson(
"request supplies both singular and threshold approval tokens".to_string(),
));
}
if let Some(token) = self.approval_token.as_ref() {
return token.artifact_digest().map(Some);
}
if self.approval_tokens.is_empty() {
return if self.threshold_approval_proposal.is_none() {
Ok(None)
} else {
Err(chio_core::Error::CanonicalJson(
"threshold approval proposal has no approval tokens".to_string(),
))
};
}
if self.approval_tokens.len()
> chio_core::capability::threshold_approval::MAX_THRESHOLD_APPROVAL_TOKENS
{
return Err(chio_core::Error::CanonicalJson(format!(
"threshold approval set exceeds {} tokens",
chio_core::capability::threshold_approval::MAX_THRESHOLD_APPROVAL_TOKENS
)));
}
let proposal = self.threshold_approval_proposal.as_ref().ok_or_else(|| {
chio_core::Error::CanonicalJson(
"threshold approval tokens have no signed proposal".to_string(),
)
})?;
let token_digests = self
.approval_tokens
.iter()
.map(GovernedApprovalToken::artifact_digest)
.collect::<Result<Vec<_>, chio_core::Error>>()?;
chio_core::capability::governance::VerifiedApprovalSetBody::new(token_digests, proposal)?
.approval_set_hash()
.map(Some)
}
}
#[derive(Debug)]
pub struct ToolCallResponse {
pub request_id: String,
pub verdict: Verdict,
pub output: Option<ToolCallOutput>,
pub reason: Option<String>,
pub terminal_state: OperationTerminalState,
pub receipt: ChioReceipt,
pub execution_nonce: Option<Box<SignedExecutionNonce>>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ToolCallChunk {
pub data: serde_json::Value,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ToolCallStream {
pub chunks: Vec<ToolCallChunk>,
}
impl ToolCallStream {
pub fn chunk_count(&self) -> u64 {
self.chunks.len() as u64
}
}
pub fn enforce_stream_byte_limit(
stream: &ToolCallStream,
max_total_bytes: u64,
) -> Result<(), KernelError> {
if max_total_bytes == 0 {
return Ok(());
}
let mut total: u64 = 0;
for chunk in &stream.chunks {
let bytes = crate::canonical_json_bytes(&chunk.data)
.map_err(|e| KernelError::Internal(format!("failed to size stream chunk: {e}")))?;
total = total.saturating_add(bytes.len() as u64);
if total > max_total_bytes {
return Err(KernelError::Overloaded {
resource: crate::OverloadResource::StreamBytes,
});
}
}
Ok(())
}
pub fn push_chunk_bounded(
acc: &mut Vec<ToolCallChunk>,
running_bytes: &mut u64,
chunk: ToolCallChunk,
max_total_bytes: u64,
max_chunks: u64,
) -> Result<(), KernelError> {
if max_chunks > 0 && acc.len() as u64 >= max_chunks {
return Err(KernelError::Overloaded {
resource: crate::OverloadResource::StreamChunks,
});
}
let chunk_bytes = crate::canonical_json_bytes(&chunk.data)
.map_err(|e| KernelError::Internal(format!("failed to size stream chunk: {e}")))?
.len() as u64;
let next = running_bytes.saturating_add(chunk_bytes);
if max_total_bytes > 0 && next > max_total_bytes {
return Err(KernelError::Overloaded {
resource: crate::OverloadResource::StreamBytes,
});
}
acc.try_reserve(1).map_err(|_| KernelError::Overloaded {
resource: crate::OverloadResource::Allocation,
})?;
acc.push(chunk);
*running_bytes = next;
Ok(())
}
#[derive(Debug, Clone, PartialEq)]
pub enum ToolCallOutput {
Value(serde_json::Value),
Stream(ToolCallStream),
}
#[derive(Debug, Clone, PartialEq)]
pub enum ToolServerStreamResult {
Complete(ToolCallStream),
Incomplete {
stream: ToolCallStream,
reason: String,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum ToolServerOutput {
Value(serde_json::Value),
Stream(ToolServerStreamResult),
}
pub trait NestedFlowBridge: Send {
fn parent_request_id(&self) -> &RequestId;
fn poll_parent_cancellation(&mut self) -> Result<(), KernelError> {
Ok(())
}
fn list_roots(&mut self) -> Result<Vec<RootDefinition>, KernelError>;
fn create_message(
&mut self,
operation: CreateMessageOperation,
) -> Result<CreateMessageResult, KernelError>;
fn create_elicitation(
&mut self,
operation: CreateElicitationOperation,
) -> Result<CreateElicitationResult, KernelError>;
fn notify_elicitation_completed(&mut self, elicitation_id: &str) -> Result<(), KernelError>;
fn notify_resource_updated(&mut self, uri: &str) -> Result<(), KernelError>;
fn notify_resources_list_changed(&mut self) -> Result<(), KernelError>;
}
pub trait NestedFlowClient: Send {
fn poll_parent_cancellation(
&mut self,
_parent_context: &OperationContext,
) -> Result<(), KernelError> {
Ok(())
}
fn list_roots(
&mut self,
parent_context: &OperationContext,
child_context: &OperationContext,
) -> Result<Vec<RootDefinition>, KernelError>;
fn create_message(
&mut self,
parent_context: &OperationContext,
child_context: &OperationContext,
operation: &CreateMessageOperation,
) -> Result<CreateMessageResult, KernelError>;
fn create_elicitation(
&mut self,
parent_context: &OperationContext,
child_context: &OperationContext,
operation: &CreateElicitationOperation,
) -> Result<CreateElicitationResult, KernelError>;
fn notify_elicitation_completed(
&mut self,
parent_context: &OperationContext,
elicitation_id: &str,
) -> Result<(), KernelError>;
fn notify_resource_updated(
&mut self,
parent_context: &OperationContext,
uri: &str,
) -> Result<(), KernelError>;
fn notify_resources_list_changed(
&mut self,
parent_context: &OperationContext,
) -> Result<(), KernelError>;
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ToolInvocationCost {
pub units: u64,
pub currency: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub breakdown: Option<serde_json::Value>,
}
#[async_trait::async_trait]
pub trait ToolServerConnection: Send + Sync {
fn server_id(&self) -> &str;
fn tool_names(&self) -> Vec<String>;
fn tool_is_read_only(&self, _tool_name: &str) -> bool {
false
}
async fn invoke(
&self,
tool_name: &str,
arguments: serde_json::Value,
nested_flow_bridge: Option<&mut dyn NestedFlowBridge>,
) -> Result<serde_json::Value, KernelError>;
async fn invoke_with_cost(
&self,
tool_name: &str,
arguments: serde_json::Value,
nested_flow_bridge: Option<&mut dyn NestedFlowBridge>,
) -> Result<(serde_json::Value, Option<ToolInvocationCost>), KernelError> {
let value = self
.invoke(tool_name, arguments, nested_flow_bridge)
.await?;
Ok((value, None))
}
fn measures_realized_cost(&self) -> bool {
true
}
async fn invoke_stream(
&self,
tool_name: &str,
arguments: serde_json::Value,
nested_flow_bridge: Option<&mut dyn NestedFlowBridge>,
) -> Result<Option<ToolServerStreamResult>, KernelError> {
let _ = (tool_name, arguments, nested_flow_bridge);
Ok(None)
}
async fn drain_events(&self) -> Result<Vec<ToolServerEvent>, KernelError> {
Ok(vec![])
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ToolServerEvent {
ElicitationCompleted { elicitation_id: String },
ResourceUpdated { uri: String },
ResourcesListChanged,
ToolsListChanged,
PromptsListChanged,
}