Skip to main content

everruns_core/
command_host.rs

1//! Neutral command execution contracts.
2//!
3//! Store-backed context loading, credential-bearing provider resolution, and
4//! completion driver creation live in `everruns-host`.
5
6use std::collections::HashMap;
7
8use async_trait::async_trait;
9
10use crate::command::CommandResult;
11use crate::driver_registry::LlmResponseStream;
12use crate::error::{AgentLoopError, Result};
13use crate::message::{Controls, Message};
14use crate::typed_id::SessionId;
15use crate::user_facing_error::{UserFacingErrorContext, classify_runtime_error_message};
16
17/// Credential-free snapshot of the session's assembled turn context for
18/// command execution.
19///
20/// The host applies capability message filters and prompt contributions before
21/// producing this view. Provider credentials, endpoints, and persisted session
22/// records never cross the contract boundary.
23#[derive(Debug, Clone)]
24pub struct CommandTurnContext {
25    /// Session the command is executing against.
26    pub session_id: SessionId,
27    /// Conversation messages after capability message filters.
28    pub messages: Vec<Message>,
29    /// Merged system prompt including capability contributions.
30    pub system_prompt: String,
31    /// Resolved model name, without credentials.
32    pub model: String,
33    /// Resolved provider integration kind for user-facing error classification.
34    pub provider_type: String,
35    /// Locale resolved from message controls or session defaults.
36    pub resolved_locale: Option<String>,
37}
38
39/// Request for a tool-less, out-of-band completion against the session model.
40#[derive(Debug, Clone, Default)]
41pub struct SessionCompletionRequest {
42    /// System prompts sent in order; empty entries are skipped.
43    pub system_prompts: Vec<String>,
44    /// Conversation messages to complete against.
45    pub messages: Vec<Message>,
46    /// Per-invocation model and reasoning controls.
47    pub controls: Option<Controls>,
48    /// Extra provider metadata. The host adds `session_id` itself.
49    pub metadata: HashMap<String, String>,
50}
51
52/// Successful command completion result.
53#[derive(Debug, Clone)]
54pub struct SessionCompletion {
55    /// Trimmed, non-empty completion text.
56    pub text: String,
57}
58
59/// Streaming command completion result.
60pub struct SessionCompletionStream {
61    /// Provider stream events for progressive output.
62    pub events: LlmResponseStream,
63    /// Credential-free provider/model identity for classifying stream errors.
64    pub context: UserFacingErrorContext,
65}
66
67impl std::fmt::Debug for SessionCompletionStream {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        f.debug_struct("SessionCompletionStream")
70            .field("context", &self.context)
71            .finish()
72    }
73}
74
75/// Command completion failure.
76#[derive(Debug)]
77pub enum SessionCompletionError {
78    /// Request-level failure, such as an unknown model override.
79    InvalidRequest(AgentLoopError),
80    /// The host does not implement streaming completion.
81    StreamingUnsupported,
82    /// Provider/runtime failure with safe classification context.
83    Completion {
84        /// Formatted error chain.
85        error: String,
86        /// Credential-free provider/model identity.
87        context: UserFacingErrorContext,
88    },
89}
90
91impl SessionCompletionError {
92    /// Convert provider failures into a stable command result while allowing
93    /// invalid requests to remain hard errors.
94    pub fn into_command_result(self) -> Result<CommandResult> {
95        match self {
96            Self::InvalidRequest(error) => Err(error),
97            Self::StreamingUnsupported => Err(AgentLoopError::config(
98                "command host does not support streaming completions",
99            )),
100            Self::Completion { error, context } => {
101                let classified = classify_runtime_error_message(&error, &context);
102                Ok(CommandResult {
103                    success: false,
104                    message: classified.fallback_message(),
105                    error_code: Some(classified.code.clone()),
106                    error_fields: classified.error_fields(),
107                })
108            }
109        }
110    }
111}
112
113/// Host facilities available to capability command implementations.
114///
115/// Completions are out-of-band: this contract does not persist messages or
116/// events. Hosts without these facilities use [`DisabledCommandHost`].
117#[async_trait]
118pub trait CommandHost: Send + Sync {
119    /// Assemble the credential-free context a main turn would see.
120    async fn turn_context(&self) -> Result<CommandTurnContext>;
121
122    /// Run a tool-less completion against the resolved session model.
123    async fn completion(
124        &self,
125        request: SessionCompletionRequest,
126    ) -> std::result::Result<SessionCompletion, SessionCompletionError>;
127
128    /// Stream a tool-less completion. The default advertises an unsupported
129    /// capability so commands can fall back to [`Self::completion`].
130    async fn completion_stream(
131        &self,
132        _request: SessionCompletionRequest,
133    ) -> std::result::Result<SessionCompletionStream, SessionCompletionError> {
134        Err(SessionCompletionError::StreamingUnsupported)
135    }
136}
137
138/// Stub used by hosts that do not provide context-aware command facilities.
139pub struct DisabledCommandHost;
140
141#[async_trait]
142impl CommandHost for DisabledCommandHost {
143    async fn turn_context(&self) -> Result<CommandTurnContext> {
144        Err(AgentLoopError::config(
145            "command host does not provide turn-context access",
146        ))
147    }
148
149    async fn completion(
150        &self,
151        _request: SessionCompletionRequest,
152    ) -> std::result::Result<SessionCompletion, SessionCompletionError> {
153        Err(SessionCompletionError::InvalidRequest(
154            AgentLoopError::config("command host does not provide session completions"),
155        ))
156    }
157}