1use std::{
7 collections::BTreeMap,
8 time::{Duration, Instant},
9};
10
11use async_trait::async_trait;
12use runtime_types::{
13 CallerScope, ConversationId, DelegationLeaseRef, ExecutionId, OperationId, RequestAuthority,
14 RuntimeInstanceId, WorkspaceId,
15};
16use serde::{Deserialize, Serialize};
17use serde_json::Value;
18use thiserror::Error;
19use tokio_util::sync::CancellationToken;
20
21#[derive(Clone, Debug)]
22pub struct OperationContext {
23 pub id: OperationId,
24 pub execution_id: ExecutionId,
25 pub deadline: Instant,
26 pub cancellation: CancellationToken,
27}
28
29impl OperationContext {
30 pub fn remaining(&self) -> Result<Duration, PortFailure> {
31 if self.cancellation.is_cancelled() {
32 return Err(PortFailure::canceled());
33 }
34 self.deadline
35 .checked_duration_since(Instant::now())
36 .filter(|remaining| !remaining.is_zero())
37 .ok_or_else(PortFailure::timeout)
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum CommitDisposition {
43 NotCommitted,
44 Committed,
45 Unknown,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum PortFailureKind {
50 Invalid,
51 NotFound,
52 Forbidden,
53 Conflict,
54 RateLimited,
55 Timeout,
56 Canceled,
57 Unavailable,
58 Protocol,
59 Internal,
60}
61
62#[derive(Debug, Error, Clone)]
63#[error("{code}: {message}")]
64pub struct PortFailure {
65 pub kind: PortFailureKind,
66 pub code: String,
67 pub message: String,
68 pub retryable: bool,
69 pub commit: CommitDisposition,
70}
71
72impl PortFailure {
73 pub fn new(kind: PortFailureKind, code: impl Into<String>, message: impl Into<String>) -> Self {
74 Self {
75 kind,
76 code: code.into(),
77 message: message.into(),
78 retryable: false,
79 commit: CommitDisposition::NotCommitted,
80 }
81 }
82 pub fn timeout() -> Self {
83 Self::new(
84 PortFailureKind::Timeout,
85 "DEADLINE_EXCEEDED",
86 "operation deadline exceeded",
87 )
88 }
89 pub fn canceled() -> Self {
90 Self::new(PortFailureKind::Canceled, "CANCELED", "operation canceled")
91 }
92}
93
94#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
95#[serde(tag = "type", rename_all = "snake_case")]
96pub enum ModelContent {
97 Text {
98 text: String,
99 },
100 ToolUse {
101 id: String,
102 name: String,
103 arguments: Value,
104 },
105 ToolResult {
106 tool_use_id: String,
107 name: String,
108 content: String,
109 failed: bool,
110 },
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
114#[serde(rename_all = "snake_case")]
115pub enum ModelRole {
116 System,
117 User,
118 Assistant,
119 Tool,
120}
121
122#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
123#[serde(rename_all = "camelCase")]
124pub struct ModelMessage {
125 pub role: ModelRole,
126 pub content: Vec<ModelContent>,
127}
128
129impl ModelMessage {
130 pub fn text(role: ModelRole, text: impl Into<String>) -> Self {
131 Self {
132 role,
133 content: vec![ModelContent::Text { text: text.into() }],
134 }
135 }
136}
137
138#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
139#[serde(rename_all = "camelCase")]
140pub struct ModelToolDefinition {
141 pub name: String,
142 pub description: String,
143 pub input_schema: Value,
144}
145
146#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
147#[serde(rename_all = "camelCase")]
148pub struct ModelRequest {
149 pub invocation_id: String,
153 pub model: String,
154 pub messages: Vec<ModelMessage>,
155 pub tools: Vec<ModelToolDefinition>,
156 pub max_output_tokens: Option<u32>,
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
160#[serde(rename_all = "snake_case")]
161pub enum ModelFinish {
162 Stop,
163 ToolCalls,
164 Length,
165 ContentFilter,
166 Other,
167}
168
169impl ModelFinish {
170 pub fn is_complete(self) -> bool {
171 matches!(self, Self::Stop | Self::ToolCalls)
172 }
173}
174
175#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
176#[serde(rename_all = "camelCase")]
177pub struct TokenUsage {
178 pub input_tokens: Option<u64>,
181 pub output_tokens: Option<u64>,
182}
183
184impl TokenUsage {
185 pub const fn zero() -> Self {
187 Self {
188 input_tokens: Some(0),
189 output_tokens: Some(0),
190 }
191 }
192
193 pub fn add_assign(&mut self, value: &Self) {
196 self.input_tokens = add_known(self.input_tokens, value.input_tokens);
197 self.output_tokens = add_known(self.output_tokens, value.output_tokens);
198 }
199}
200
201fn add_known(left: Option<u64>, right: Option<u64>) -> Option<u64> {
202 left.zip(right)
203 .map(|(left, right)| left.saturating_add(right))
204}
205
206#[cfg(test)]
207mod token_usage_tests {
208 use super::TokenUsage;
209
210 #[test]
211 fn aggregate_preserves_unknown_and_saturates_known_values() {
212 let mut usage = TokenUsage::zero();
213 usage.add_assign(&TokenUsage {
214 input_tokens: Some(u64::MAX),
215 output_tokens: Some(3),
216 });
217 usage.add_assign(&TokenUsage {
218 input_tokens: Some(1),
219 output_tokens: None,
220 });
221 assert_eq!(usage.input_tokens, Some(u64::MAX));
222 assert_eq!(usage.output_tokens, None);
223 }
224}
225
226#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
227#[serde(rename_all = "camelCase")]
228pub struct ModelResponse {
229 pub output: Vec<ModelContent>,
230 pub finish: ModelFinish,
231 pub usage: TokenUsage,
232}
233
234#[async_trait]
235pub trait ModelSession: Send + Sync {
236 async fn invoke(
237 &self,
238 operation: &OperationContext,
239 request: ModelRequest,
240 ) -> Result<ModelResponse, PortFailure>;
241}
242
243#[derive(Debug, Clone, PartialEq, Eq)]
244pub struct ConversationMessage {
245 pub role: ModelRole,
246 pub text: String,
247}
248
249#[async_trait]
250pub trait ConversationSession: Send + Sync {
251 async fn load_recent(
252 &self,
253 operation: &OperationContext,
254 limit: usize,
255 ) -> Result<Vec<ConversationMessage>, PortFailure>;
256 async fn append(
257 &self,
258 operation: &OperationContext,
259 messages: &[ConversationMessage],
260 ) -> Result<(), PortFailure>;
261}
262
263#[derive(Debug, Clone, Copy, PartialEq, Eq)]
264pub enum ContextAuditStage {
265 Original,
266 Assembled,
267 Compressed,
268}
269
270#[derive(Debug, Clone)]
271pub struct ContextAuditSnapshot {
272 pub id: String,
273 pub stage: ContextAuditStage,
274 pub purpose: String,
275 pub messages: Vec<ModelMessage>,
276 pub tools: Vec<ModelToolDefinition>,
277 pub protocol_overhead_tokens: u64,
278 pub reserved_output_tokens: u64,
279 pub estimated_tokens: u64,
280 pub request_digest: String,
283}
284
285#[derive(Debug, Clone, Copy, PartialEq, Eq)]
286pub enum ContextDerivationKind {
287 Assemble,
288 Compress,
289 Trim,
290 Reorder,
291}
292
293#[derive(Debug, Clone)]
294pub struct ContextAuditDerivation {
295 pub id: String,
296 pub source_snapshot_id: String,
297 pub target_snapshot_id: String,
298 pub kind: ContextDerivationKind,
299 pub input_tokens: u64,
300 pub output_tokens: u64,
301 pub input_items: usize,
302 pub output_items: usize,
303}
304
305#[derive(Debug, Clone, Copy, PartialEq, Eq)]
306pub enum ModelInvocationAuditState {
307 Prepared,
308 Succeeded,
309 Failed,
310}
311
312#[derive(Debug, Clone)]
313pub struct ModelInvocationAudit {
314 pub id: String,
315 pub request_snapshot_id: String,
316 pub model: String,
317 pub state: ModelInvocationAuditState,
318 pub usage: TokenUsage,
319 pub error_code: Option<String>,
320}
321
322#[async_trait]
323pub trait ContextAuditSession: Send + Sync {
324 async fn snapshot(
325 &self,
326 operation: &OperationContext,
327 snapshot: ContextAuditSnapshot,
328 ) -> Result<(), PortFailure>;
329 async fn derivation(
330 &self,
331 operation: &OperationContext,
332 derivation: ContextAuditDerivation,
333 ) -> Result<(), PortFailure>;
334 async fn model_invocation(
335 &self,
336 operation: &OperationContext,
337 invocation: ModelInvocationAudit,
338 ) -> Result<(), PortFailure>;
339}
340
341#[derive(Debug, Clone, PartialEq, Eq)]
342pub struct WorkspaceEntry {
343 pub path: String,
344 pub is_dir: bool,
345 pub size: Option<u64>,
346}
347
348#[derive(Debug, Clone, PartialEq, Eq)]
349pub struct CommandRequest {
350 pub command: String,
351 pub cwd: Option<String>,
352}
353
354#[derive(Debug, Clone, PartialEq, Eq)]
355pub struct CommandOutput {
356 pub exit_code: i32,
357 pub stdout: String,
358 pub stderr: String,
359 pub truncated: bool,
360}
361
362#[async_trait]
363pub trait WorkspaceSession: Send + Sync {
364 async fn describe(&self, operation: &OperationContext) -> Result<String, PortFailure>;
365 async fn read(&self, operation: &OperationContext, path: &str) -> Result<Vec<u8>, PortFailure>;
366 async fn write(
367 &self,
368 operation: &OperationContext,
369 path: &str,
370 content: &[u8],
371 ) -> Result<(), PortFailure>;
372 async fn list(
373 &self,
374 operation: &OperationContext,
375 path: &str,
376 ) -> Result<Vec<WorkspaceEntry>, PortFailure>;
377 async fn search(
378 &self,
379 operation: &OperationContext,
380 path: &str,
381 query: &str,
382 limit: usize,
383 ) -> Result<Vec<String>, PortFailure>;
384 async fn execute(
385 &self,
386 operation: &OperationContext,
387 request: CommandRequest,
388 ) -> Result<CommandOutput, PortFailure>;
389}
390
391#[derive(Debug, Clone)]
392pub struct ResolvedExecutionContext {
393 pub runtime_instance_id: RuntimeInstanceId,
394 pub agent_id: String,
395 pub runtime_type: String,
396 pub model: String,
397 pub workspace_id: WorkspaceId,
398 pub package_id: String,
399 pub package_version: String,
400 pub package_digest: Option<String>,
401 pub metadata: BTreeMap<String, String>,
402}
403
404#[async_trait]
405pub trait RuntimeInstanceResolver: Send + Sync {
406 async fn resolve(
407 &self,
408 operation: &OperationContext,
409 caller: &CallerScope,
410 id: &RuntimeInstanceId,
411 ) -> Result<ResolvedExecutionContext, PortFailure>;
412}
413
414#[async_trait]
415pub trait TraceSession: Send + Sync {
416 async fn append(
417 &self,
418 operation: &OperationContext,
419 event_type: &str,
420 payload: Value,
421 ) -> Result<(), PortFailure>;
422}
423
424#[derive(Debug, Clone, PartialEq, Eq)]
425pub struct InteractionRequest {
426 pub request_id: String,
427 pub prompt: String,
428}
429
430#[derive(Debug, Clone, PartialEq, Eq)]
431pub struct InteractionResponse {
432 pub text: String,
433}
434
435#[async_trait]
436pub trait InteractionSession: Send + Sync {
437 async fn prepare(
439 &self,
440 operation: &OperationContext,
441 request: InteractionRequest,
442 ) -> Result<(), PortFailure>;
443
444 async fn wait(
446 &self,
447 operation: &OperationContext,
448 request_id: &str,
449 ) -> Result<InteractionResponse, PortFailure>;
450
451 async fn request(
452 &self,
453 operation: &OperationContext,
454 request: InteractionRequest,
455 ) -> Result<InteractionResponse, PortFailure> {
456 let request_id = request.request_id.clone();
457 self.prepare(operation, request).await?;
458 self.wait(operation, &request_id).await
459 }
460}
461
462#[derive(Debug, Clone)]
463pub struct SubagentRequest {
464 pub request_id: String,
465 pub prompt: String,
466 pub max_model_turns: usize,
467}
468
469#[derive(Debug, Clone)]
470pub struct SubagentOutcome {
471 pub answer: String,
472}
473
474#[async_trait]
475pub trait SubagentSession: Send + Sync {
476 async fn execute(
477 &self,
478 operation: &OperationContext,
479 request: SubagentRequest,
480 ) -> Result<SubagentOutcome, PortFailure>;
481}
482
483#[derive(Debug, Clone)]
484pub struct ResourceSnapshot {
485 pub instructions: Vec<String>,
486 pub skills: BTreeMap<String, String>,
487 pub digest: String,
488}
489
490#[async_trait]
491pub trait ResourceSession: Send + Sync {
492 async fn load(&self, operation: &OperationContext) -> Result<ResourceSnapshot, PortFailure>;
493}
494
495#[derive(Debug, Clone)]
496pub struct SessionScope {
497 pub execution_id: runtime_types::ExecutionId,
498 pub conversation_id: Option<ConversationId>,
499 pub resolved: ResolvedExecutionContext,
500}
501
502#[derive(Debug, Clone, PartialEq, Eq)]
503pub struct ExecutionDelegationLease {
504 pub lease_ref: DelegationLeaseRef,
505 pub expires_at_seconds: u64,
506 pub revision: u64,
507}
508
509pub struct ExecutionSessions {
510 pub model: std::sync::Arc<dyn ModelSession>,
511 pub conversation: std::sync::Arc<dyn ConversationSession>,
512 pub context_audit: std::sync::Arc<dyn ContextAuditSession>,
513 pub workspace: std::sync::Arc<dyn WorkspaceSession>,
514 pub trace: std::sync::Arc<dyn TraceSession>,
515 pub interaction: std::sync::Arc<dyn InteractionSession>,
516 pub subagent: std::sync::Arc<dyn SubagentSession>,
517 pub resources: std::sync::Arc<dyn ResourceSession>,
518}
519
520#[async_trait]
521pub trait ExecutionSessionFactory: Send + Sync {
522 async fn establish_delegation(
523 &self,
524 operation: &OperationContext,
525 authority: &RequestAuthority,
526 scope: &SessionScope,
527 ) -> Result<ExecutionDelegationLease, PortFailure>;
528
529 async fn create(
530 &self,
531 operation: &OperationContext,
532 caller: &CallerScope,
533 delegation: &ExecutionDelegationLease,
534 scope: &SessionScope,
535 ) -> Result<ExecutionSessions, PortFailure>;
536
537 async fn renew_delegation(
538 &self,
539 operation: &OperationContext,
540 caller: &CallerScope,
541 delegation: &ExecutionDelegationLease,
542 ) -> Result<ExecutionDelegationLease, PortFailure>;
543
544 async fn revoke_delegation(
545 &self,
546 operation: &OperationContext,
547 caller: &CallerScope,
548 delegation: &ExecutionDelegationLease,
549 ) -> Result<(), PortFailure>;
550
551 async fn submit_input(
554 &self,
555 operation: &OperationContext,
556 caller: &CallerScope,
557 execution_id: &runtime_types::ExecutionId,
558 operation_id: &OperationId,
559 request_id: &str,
560 response: InteractionResponse,
561 ) -> Result<(), PortFailure>;
562}