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 generation: ModelGenerationOptions,
161}
162
163#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
164#[serde(rename_all = "camelCase")]
165pub struct ModelGenerationOptions {
166 pub max_output_tokens: Option<u32>,
167 pub temperature: Option<f32>,
168 #[serde(default)]
169 pub stop_sequences: Vec<String>,
170 pub response_format: Option<Value>,
171 pub tool_choice: ModelToolChoice,
172}
173
174#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
175#[serde(tag = "type", rename_all = "snake_case")]
176pub enum ModelToolChoice {
177 #[default]
178 Auto,
179 None,
180}
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
183#[serde(rename_all = "snake_case")]
184pub enum ModelFinish {
185 Stop,
186 ToolCalls,
187 Length,
188 ContentFilter,
189 Other,
190}
191
192impl ModelFinish {
193 pub fn is_complete(self) -> bool {
194 matches!(self, Self::Stop | Self::ToolCalls)
195 }
196}
197
198#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
199#[serde(rename_all = "camelCase")]
200pub struct TokenUsage {
201 pub input_tokens: Option<u64>,
204 pub output_tokens: Option<u64>,
205}
206
207impl TokenUsage {
208 pub const fn zero() -> Self {
210 Self {
211 input_tokens: Some(0),
212 output_tokens: Some(0),
213 }
214 }
215
216 pub fn add_assign(&mut self, value: &Self) {
219 self.input_tokens = add_known(self.input_tokens, value.input_tokens);
220 self.output_tokens = add_known(self.output_tokens, value.output_tokens);
221 }
222}
223
224fn add_known(left: Option<u64>, right: Option<u64>) -> Option<u64> {
225 left.zip(right)
226 .map(|(left, right)| left.saturating_add(right))
227}
228
229#[cfg(test)]
230mod token_usage_tests {
231 use super::TokenUsage;
232
233 #[test]
234 fn aggregate_preserves_unknown_and_saturates_known_values() {
235 let mut usage = TokenUsage::zero();
236 usage.add_assign(&TokenUsage {
237 input_tokens: Some(u64::MAX),
238 output_tokens: Some(3),
239 });
240 usage.add_assign(&TokenUsage {
241 input_tokens: Some(1),
242 output_tokens: None,
243 });
244 assert_eq!(usage.input_tokens, Some(u64::MAX));
245 assert_eq!(usage.output_tokens, None);
246 }
247}
248
249#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
250#[serde(rename_all = "camelCase")]
251pub struct ModelResponse {
252 pub output: Vec<ModelContent>,
253 pub finish: ModelFinish,
254 pub usage: TokenUsage,
255}
256
257#[async_trait]
258pub trait ModelSession: Send + Sync {
259 async fn invoke(
260 &self,
261 operation: &OperationContext,
262 request: ModelRequest,
263 ) -> Result<ModelResponse, PortFailure>;
264}
265
266#[derive(Debug, Clone, PartialEq, Eq)]
267pub struct ConversationMessage {
268 pub role: ModelRole,
269 pub text: String,
270}
271
272#[async_trait]
273pub trait ConversationSession: Send + Sync {
274 async fn load_recent(
275 &self,
276 operation: &OperationContext,
277 limit: usize,
278 ) -> Result<Vec<ConversationMessage>, PortFailure>;
279 async fn append(
280 &self,
281 operation: &OperationContext,
282 messages: &[ConversationMessage],
283 ) -> Result<(), PortFailure>;
284}
285
286#[derive(Debug, Clone, Copy, PartialEq, Eq)]
287pub enum ContextAuditStage {
288 Original,
289 Assembled,
290 Compressed,
291}
292
293#[derive(Debug, Clone)]
294pub struct ContextAuditSnapshot {
295 pub id: String,
296 pub stage: ContextAuditStage,
297 pub purpose: String,
298 pub messages: Vec<ModelMessage>,
299 pub tools: Vec<ModelToolDefinition>,
300 pub protocol_overhead_tokens: u64,
301 pub reserved_output_tokens: u64,
302 pub estimated_tokens: u64,
303 pub request_digest: String,
306}
307
308#[derive(Debug, Clone, Copy, PartialEq, Eq)]
309pub enum ContextDerivationKind {
310 Assemble,
311 Compress,
312 Trim,
313 Reorder,
314}
315
316#[derive(Debug, Clone)]
317pub struct ContextAuditDerivation {
318 pub id: String,
319 pub source_snapshot_id: String,
320 pub target_snapshot_id: String,
321 pub kind: ContextDerivationKind,
322 pub input_tokens: u64,
323 pub output_tokens: u64,
324 pub input_items: usize,
325 pub output_items: usize,
326}
327
328#[derive(Debug, Clone, Copy, PartialEq, Eq)]
329pub enum ModelInvocationAuditState {
330 Prepared,
331 Succeeded,
332 Failed,
333}
334
335#[derive(Debug, Clone)]
336pub struct ModelInvocationAudit {
337 pub id: String,
338 pub request_snapshot_id: String,
339 pub model: String,
340 pub state: ModelInvocationAuditState,
341 pub usage: TokenUsage,
342 pub error_code: Option<String>,
343}
344
345#[async_trait]
346pub trait ContextAuditSession: Send + Sync {
347 async fn snapshot(
348 &self,
349 operation: &OperationContext,
350 snapshot: ContextAuditSnapshot,
351 ) -> Result<(), PortFailure>;
352 async fn derivation(
353 &self,
354 operation: &OperationContext,
355 derivation: ContextAuditDerivation,
356 ) -> Result<(), PortFailure>;
357 async fn model_invocation(
358 &self,
359 operation: &OperationContext,
360 invocation: ModelInvocationAudit,
361 ) -> Result<(), PortFailure>;
362}
363
364#[derive(Debug, Clone, PartialEq, Eq)]
365pub struct WorkspaceEntry {
366 pub path: String,
367 pub is_dir: bool,
368 pub size: Option<u64>,
369}
370
371#[derive(Debug, Clone, PartialEq, Eq)]
372pub struct CommandRequest {
373 pub command: String,
374 pub cwd: Option<String>,
375}
376
377#[derive(Debug, Clone, PartialEq, Eq)]
378pub struct CommandOutput {
379 pub exit_code: i32,
380 pub stdout: String,
381 pub stderr: String,
382 pub truncated: bool,
383}
384
385#[async_trait]
386pub trait WorkspaceSession: Send + Sync {
387 async fn describe(&self, operation: &OperationContext) -> Result<String, PortFailure>;
388 async fn read(&self, operation: &OperationContext, path: &str) -> Result<Vec<u8>, PortFailure>;
389 async fn write(
390 &self,
391 operation: &OperationContext,
392 path: &str,
393 content: &[u8],
394 ) -> Result<(), PortFailure>;
395 async fn list(
396 &self,
397 operation: &OperationContext,
398 path: &str,
399 ) -> Result<Vec<WorkspaceEntry>, PortFailure>;
400 async fn search(
401 &self,
402 operation: &OperationContext,
403 path: &str,
404 query: &str,
405 limit: usize,
406 ) -> Result<Vec<String>, PortFailure>;
407 async fn execute(
408 &self,
409 operation: &OperationContext,
410 request: CommandRequest,
411 ) -> Result<CommandOutput, PortFailure>;
412}
413
414#[derive(Debug, Clone)]
415pub struct ResolvedExecutionContext {
416 pub runtime_instance_id: RuntimeInstanceId,
417 pub agent_id: String,
418 pub runtime_type: String,
419 pub model: String,
420 pub workspace_id: WorkspaceId,
421 pub definition_id: String,
422 pub definition_version: String,
423 pub definition_digest: Option<String>,
424 pub metadata: BTreeMap<String, String>,
425}
426
427#[async_trait]
428pub trait RuntimeInstanceResolver: Send + Sync {
429 async fn resolve(
430 &self,
431 operation: &OperationContext,
432 caller: &CallerScope,
433 id: &RuntimeInstanceId,
434 ) -> Result<ResolvedExecutionContext, PortFailure>;
435}
436
437#[async_trait]
438pub trait TraceSession: Send + Sync {
439 async fn append(
440 &self,
441 operation: &OperationContext,
442 event_type: &str,
443 payload: Value,
444 ) -> Result<(), PortFailure>;
445}
446
447#[derive(Debug, Clone, PartialEq, Eq)]
448pub struct InteractionRequest {
449 pub request_id: String,
450 pub prompt: String,
451}
452
453#[derive(Debug, Clone, PartialEq, Eq)]
454pub struct InteractionResponse {
455 pub text: String,
456}
457
458#[async_trait]
459pub trait InteractionSession: Send + Sync {
460 async fn prepare(
462 &self,
463 operation: &OperationContext,
464 request: InteractionRequest,
465 ) -> Result<(), PortFailure>;
466
467 async fn wait(
469 &self,
470 operation: &OperationContext,
471 request_id: &str,
472 ) -> Result<InteractionResponse, PortFailure>;
473
474 async fn request(
475 &self,
476 operation: &OperationContext,
477 request: InteractionRequest,
478 ) -> Result<InteractionResponse, PortFailure> {
479 let request_id = request.request_id.clone();
480 self.prepare(operation, request).await?;
481 self.wait(operation, &request_id).await
482 }
483}
484
485#[derive(Debug, Clone)]
486pub struct SubagentRequest {
487 pub request_id: String,
488 pub prompt: String,
489 pub max_model_turns: usize,
490}
491
492#[derive(Debug, Clone)]
493pub struct SubagentOutcome {
494 pub answer: String,
495}
496
497#[async_trait]
498pub trait SubagentSession: Send + Sync {
499 async fn execute(
500 &self,
501 operation: &OperationContext,
502 request: SubagentRequest,
503 ) -> Result<SubagentOutcome, PortFailure>;
504}
505
506#[derive(Debug, Clone)]
507pub struct ResourceSnapshot {
508 pub instructions: Vec<String>,
509 pub skills: BTreeMap<String, String>,
510 pub digest: String,
511}
512
513#[async_trait]
514pub trait ResourceSession: Send + Sync {
515 async fn load(&self, operation: &OperationContext) -> Result<ResourceSnapshot, PortFailure>;
516}
517
518#[derive(Debug, Clone)]
519pub struct SessionScope {
520 pub execution_id: runtime_types::ExecutionId,
521 pub conversation_id: Option<ConversationId>,
522 pub resolved: ResolvedExecutionContext,
523}
524
525#[derive(Debug, Clone, PartialEq, Eq)]
526pub struct ExecutionDelegationLease {
527 pub lease_ref: DelegationLeaseRef,
528 pub expires_at_seconds: u64,
529 pub revision: u64,
530}
531
532pub struct ExecutionSessions {
533 pub model: std::sync::Arc<dyn ModelSession>,
534 pub conversation: std::sync::Arc<dyn ConversationSession>,
535 pub context_audit: std::sync::Arc<dyn ContextAuditSession>,
536 pub workspace: std::sync::Arc<dyn WorkspaceSession>,
537 pub trace: std::sync::Arc<dyn TraceSession>,
538 pub interaction: std::sync::Arc<dyn InteractionSession>,
539 pub subagent: std::sync::Arc<dyn SubagentSession>,
540 pub resources: std::sync::Arc<dyn ResourceSession>,
541}
542
543#[async_trait]
544pub trait ExecutionSessionFactory: Send + Sync {
545 async fn establish_delegation(
546 &self,
547 operation: &OperationContext,
548 authority: &RequestAuthority,
549 scope: &SessionScope,
550 ) -> Result<ExecutionDelegationLease, PortFailure>;
551
552 async fn create(
553 &self,
554 operation: &OperationContext,
555 caller: &CallerScope,
556 delegation: &ExecutionDelegationLease,
557 scope: &SessionScope,
558 ) -> Result<ExecutionSessions, PortFailure>;
559
560 async fn renew_delegation(
561 &self,
562 operation: &OperationContext,
563 caller: &CallerScope,
564 delegation: &ExecutionDelegationLease,
565 ) -> Result<ExecutionDelegationLease, PortFailure>;
566
567 async fn revoke_delegation(
568 &self,
569 operation: &OperationContext,
570 caller: &CallerScope,
571 delegation: &ExecutionDelegationLease,
572 ) -> Result<(), PortFailure>;
573
574 async fn submit_input(
577 &self,
578 operation: &OperationContext,
579 caller: &CallerScope,
580 execution_id: &runtime_types::ExecutionId,
581 operation_id: &OperationId,
582 request_id: &str,
583 response: InteractionResponse,
584 ) -> Result<(), PortFailure>;
585}