Skip to main content

af_agent_runtime/
types.rs

1use af_context::{InputId, InteractionId, RunId, SessionId};
2use std::time::Duration;
3use std::time::Instant;
4
5use af_agent_session::{ContentBlock, Event, SessionEvent};
6use af_llm::ChatMessage;
7use async_trait::async_trait;
8
9use crate::{CancellationToken, RuntimeError};
10
11/// Hard limits for one Run.
12#[derive(Debug, Clone)]
13pub struct RuntimeLimits {
14    /// Request image ceiling, clamped to eight; zero disables image input.
15    pub max_images: usize,
16    /// Upper bound on model steps per Run.
17    pub max_steps: u32,
18    /// Maximum tool calls per Run.
19    pub max_tool_calls: u32,
20    /// Upper bound on prompt plus completion tokens.
21    pub max_tokens: u64,
22    /// Maximum tools executed concurrently within a step.
23    pub max_parallel_tools: usize,
24    /// Model attempts before a step fails.
25    pub provider_attempts: u32,
26    /// Deadline for one model attempt or extension call.
27    pub provider_deadline: Duration,
28}
29
30impl Default for RuntimeLimits {
31    fn default() -> Self {
32        Self {
33            max_images: af_llm::images::MAX_INPUT_IMAGES,
34            max_steps: 8,
35            max_tool_calls: 24,
36            max_tokens: 64_000,
37            max_parallel_tools: 4,
38            provider_attempts: 3,
39            provider_deadline: Duration::from_secs(90),
40        }
41    }
42}
43
44/// Everything the loop needs to run one Turn.
45#[derive(Debug, Clone)]
46pub struct TurnRequest {
47    /// Execute only explicit context compaction; never append a user message or invoke tools.
48    pub compact: bool,
49    /// Caller identity and entitlements for this operation.
50    pub context: af_context::RequestContext,
51    /// Session this record belongs to.
52    pub session_id: SessionId,
53    /// Run this record belongs to.
54    pub run_id: RunId,
55    /// Queued input this record refers to.
56    pub input_id: InputId,
57    /// Content blocks carried by this record.
58    pub content: Vec<ContentBlock>,
59    /// Committed Session events the Turn resumes from.
60    pub history: crate::RunHistory,
61}
62
63/// Terminal result of a Turn.
64#[derive(Debug, Clone, PartialEq)]
65pub struct RuntimeOutcome {
66    /// Current lifecycle status.
67    pub status: String,
68    /// Final assistant text, when the Run completed with prose.
69    pub final_text: Option<String>,
70    /// Prompt tokens consumed.
71    pub prompt_tokens: u64,
72    /// Completion tokens produced.
73    pub completion_tokens: u64,
74    /// Interaction the Run is parked on, when it stopped to wait for input.
75    pub waiting_interaction_id: Option<InteractionId>,
76}
77
78/// Fenced append/read access to the Session log for one Run.
79#[async_trait]
80pub trait EventWriter: Send + Sync {
81    /// Append only at this exact sequence under the same fence; never retarget
82    /// stale content to a newer sequence. A mismatch is CompactionConflict.
83    async fn append_at(
84        &self,
85        expected_seq: u64,
86        events: Vec<Event>,
87    ) -> Result<Vec<SessionEvent>, RuntimeError>;
88    /// Current committed sequence; persistent writers override to read metadata only.
89    async fn last_seq(&self) -> Result<u64, RuntimeError> {
90        Ok(self
91            .load_after(0)
92            .await?
93            .last()
94            .map_or(0, |event| event.seq))
95    }
96
97    /// Append events atomically and return them with assigned sequences.
98    async fn append(&self, events: Vec<Event>) -> Result<Vec<SessionEvent>, RuntimeError>;
99    /// Events committed after `seq`.
100    async fn load_after(&self, seq: u64) -> Result<Vec<SessionEvent>, RuntimeError>;
101}
102
103/// Token estimator used for limits and compaction decisions.
104pub trait TokenMeter: Send + Sync {
105    /// Estimated prompt tokens for `messages` under `model`.
106    fn count(&self, model: &str, messages: &[ChatMessage]) -> u64;
107}
108
109/// Character-based estimate (about four characters per token).
110pub struct ApproximateTokenMeter;
111
112impl TokenMeter for ApproximateTokenMeter {
113    fn count(&self, _model: &str, messages: &[ChatMessage]) -> u64 {
114        messages
115            .iter()
116            .map(|message| {
117                message
118                    .content
119                    .as_deref()
120                    .map_or(0, |content| content.chars().count().div_ceil(4) as u64)
121            })
122            .sum()
123    }
124}
125
126/// Produces the summary that replaces old transcript when the context overflows.
127#[async_trait]
128pub trait Compactor: Send + Sync {
129    /// Provider frozen before summarization, or unknown for an unlabelled adapter.
130    fn provider_name(&self) -> Option<&str> {
131        None
132    }
133    /// Stable name recorded in compaction events.
134    fn name(&self) -> &str {
135        "compactor"
136    }
137
138    /// Summarize `transcript`, returning the summary and the usage it cost.
139    async fn summarize(
140        &self,
141        request: &af_llm::CompletionRequest,
142        cancellation: CancellationToken,
143        deadline: Instant,
144    ) -> Result<CompactionResult, RuntimeError>;
145}
146
147/// Summary text and its token usage.
148#[derive(Debug, Clone, PartialEq, Eq)]
149pub struct CompactionResult {
150    /// Origin of token measurements.
151    pub source: af_agent_session::MeteringSource,
152    /// Provider attribution supplied by the compactor.
153    pub provider: Option<String>,
154    /// Short human-readable summary.
155    pub summary: String,
156    /// Prompt tokens consumed.
157    pub prompt_tokens: u64,
158    /// Completion tokens produced.
159    pub completion_tokens: u64,
160}