Skip to main content

llm_api/
lib.rs

1//! Canonical model invocation and continuation contract. No Agent or transport implementation.
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4mod continuation;
5pub use continuation::Continuation;
6pub use service::Image;
7
8/// Logical model use case resolved by the deployment's LLM implementation.
9#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
10#[serde(transparent)]
11pub struct UseCase(pub String);
12
13/// Logical model mode selected by the product, independent of provider and model identifiers.
14#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
15#[serde(transparent)]
16pub struct ModelMode(pub String);
17
18/// Capabilities required by an invocation, without naming a provider or model.
19#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
20pub struct ModelConstraints {
21    /// The model must accept image input.
22    pub vision: bool,
23    /// The model must support tool calls.
24    pub tool_calling: bool,
25    /// The model must support schema-constrained output.
26    pub structured_output: bool,
27    /// Optional logical reasoning intensity understood by the LLM adapter.
28    pub reasoning: Option<String>,
29}
30
31/// Role of a message in a model conversation.
32#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
33#[serde(rename_all = "snake_case")]
34#[derive(Default)]
35pub enum MessageRole {
36    /// Runtime-supplied instruction.
37    System,
38    /// User-supplied input.
39    #[default]
40    User,
41    /// Model output.
42    Assistant,
43    /// Tool execution output.
44    Tool,
45}
46
47/// One typed part of a model message.
48#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
49#[serde(tag = "type", rename_all = "snake_case")]
50pub enum ContentPart {
51    /// Plain text content.
52    Text {
53        /// Text value.
54        text: String,
55    },
56    /// Scope-owned content-addressed artifact managed by the application artifact service.
57    Artifact {
58        /// Canonical `meow-artifact://` URI.
59        uri: String,
60        /// MIME type copied from the validated artifact metadata.
61        mime_type: String,
62    },
63    /// An image already materialized by the caller.
64    Image { image: Image },
65    /// A model-requested tool invocation.
66    ToolCall(ToolCall),
67    /// Result of an earlier tool invocation.
68    ToolResult {
69        /// Provider-neutral call identifier.
70        call_id: String,
71        /// Structured result payload.
72        result: Value,
73        /// Whether the tool execution failed.
74        is_error: bool,
75    },
76}
77
78/// Provider-neutral conversation message.
79#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
80pub struct Message {
81    /// Speaker role.
82    pub role: MessageRole,
83    /// Ordered message content.
84    pub content: Vec<ContentPart>,
85    /// Private model continuation; never part of a user transcript.
86    #[serde(default, skip_serializing_if = "Option::is_none")]
87    pub continuation: Option<Continuation>,
88}
89
90/// A callable function exposed to the model. Execution policy belongs to the caller.
91#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
92pub struct ToolDefinition {
93    pub name: String,
94    pub description: String,
95    pub input_schema: Value,
96}
97/// Tool call returned by an LLM.
98#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
99pub struct ToolCall {
100    /// Provider-neutral identifier used to correlate the result.
101    pub id: String,
102    /// Requested tool name.
103    pub name: String,
104    /// Structured arguments.
105    pub arguments: Value,
106}
107
108/// One logical LLM invocation.
109#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
110pub struct CompletionRequest {
111    /// Logical routing key resolved by the LLM adapter.
112    pub use_case: UseCase,
113    /// Logical quality/cost mode resolved by the LLM adapter adapter.
114    pub model_mode: ModelMode,
115    /// Conversation input.
116    pub messages: Vec<Message>,
117    /// Tools available to the model.
118    pub tools: Vec<ToolDefinition>,
119    /// Required model capabilities.
120    pub constraints: ModelConstraints,
121    /// Optional maximum number of output tokens.
122    pub max_output_tokens: Option<u32>,
123    /// Whether the caller requested sanitized diagnostic metadata.
124    pub diagnostics: bool,
125}
126
127/// Provider-neutral limits for one frozen logical model route.
128///
129/// `profile_key` is opaque to Runtime. Implementations must change it whenever the concrete
130/// model, tokenizer, or another input-serialization detail changes.
131#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
132pub struct ModelProfile {
133    /// Opaque stable identity of the frozen model and tokenizer route.
134    pub profile_key: String,
135    /// Maximum total context accepted by the concrete model.
136    pub context_window_tokens: u32,
137}
138
139/// Why a model invocation stopped.
140#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
141#[serde(rename_all = "snake_case")]
142#[derive(Default)]
143pub enum FinishReason {
144    /// The model completed normally.
145    #[default]
146    Stop,
147    /// The model requested one or more tools.
148    ToolCalls,
149    /// The configured output limit was reached.
150    Length,
151    /// The LLM adapter cannot map the provider result to a more specific reason.
152    Other,
153}
154
155/// Normalized token accounting reported by an implementation.
156#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
157pub struct TokenUsage {
158    /// Input token count.
159    pub input_tokens: u64,
160    /// Output token count.
161    pub output_tokens: u64,
162    /// Input tokens served from a provider cache when reported.
163    pub cached_input_tokens: Option<u64>,
164    /// Reasoning output tokens when reported separately.
165    pub reasoning_output_tokens: Option<u64>,
166    /// Provider-normalized billable credits consumed by this request.
167    pub credits: Option<u64>,
168}
169
170/// Provider-neutral completion returned to Runtime.
171#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
172pub struct Completion {
173    /// Model response message.
174    pub message: Message,
175    /// Normalized finish reason.
176    pub finish_reason: FinishReason,
177    /// Normalized token accounting.
178    pub usage: Option<TokenUsage>,
179    /// Optional sanitized diagnostics without credentials or provider internals.
180    pub diagnostics: Option<Value>,
181}
182
183pub mod service;
184
185impl MessageRole {
186    pub const fn as_str(self) -> &'static str {
187        match self {
188            Self::System => "system",
189            Self::User => "user",
190            Self::Assistant => "assistant",
191            Self::Tool => "tool",
192        }
193    }
194}
195
196impl Default for Message {
197    fn default() -> Self {
198        Self::text(MessageRole::User, "")
199    }
200}
201impl Message {
202    pub fn text(role: MessageRole, text: impl Into<String>) -> Self {
203        Self {
204            role,
205            content: vec![ContentPart::Text { text: text.into() }],
206            continuation: None,
207        }
208    }
209    pub fn text_content(&self) -> String {
210        self.content
211            .iter()
212            .filter_map(|part| match part {
213                ContentPart::Text { text } => Some(text.as_str()),
214                _ => None,
215            })
216            .collect::<Vec<_>>()
217            .join("\n")
218    }
219}
220
221impl TokenUsage {
222    pub fn total_tokens(self) -> u64 {
223        self.input_tokens.saturating_add(self.output_tokens)
224    }
225}
226impl Message {
227    pub fn with_images(mut self, images: impl IntoIterator<Item = Image>) -> Self {
228        self.content
229            .extend(images.into_iter().map(|image| ContentPart::Image { image }));
230        self
231    }
232}
233
234/// Offline normative schema for persisted model messages.
235pub const MESSAGE_SCHEMA: &str = include_str!("../schema/message.v1.schema.json");