kernel/model.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4// Copyright (c) 2026 2youg1 and the sprawling contributors
5
6//! The model seam (seam registry ARCHITECTURE 3).
7//! Every provider call carries a `BuildingPolicy` value; the policy is
8//! *defined* here (kernel cannot name outer crates) and *evaluated* by
9//! `city::policy` — dependency inversion, same as the ledger seam.
10//! The part `crates/kernel/spec/Model.lean` specifies this module.
11
12mod image;
13mod mode;
14mod seam;
15mod usage;
16mod window;
17mod wire;
18
19pub use image::{ImageRef, ImageType};
20pub use mode::{AdmissionRequirement, LandingPolicy, Mode, RunPolicy};
21pub use seam::{ModelRequest, ModelReturn, content_from_message, message_payload};
22pub use usage::{CacheCount, ModelUsage};
23pub use window::Window;
24pub use wire::{
25 BuildingPolicy, Ceiling, ChatMessage, ChatRequest, ChatResponse, ContentBlock, DialectKind,
26 Effort, Increment, MessageBreakpoint, ModelTag, Role, StopReason, SystemBlock, ToolDef,
27};
28
29use crate::error::AxError;
30use crate::tool::ToolCall;
31
32/// Text arriving from a provider before the call has finished.
33///
34/// One argument and no return value, because an increment is not a
35/// decision: nothing downstream may branch on it, and a sink that could
36/// refuse would make a display detail able to fail a call.
37///
38/// **What arrives here is not history.** The record of what the model
39/// said is written once, from [`ModelReturn`], after the call settles.
40/// Increments are a thing to look at while waiting, and the two can
41/// disagree — a provider may revise, and a cut stream leaves increments
42/// behind that no `ModelReturn` ever confirms. Where they disagree the
43/// settled text wins, and that rule is held on the far side of the wire
44/// by the client.
45pub type Increments<'a> = &'a mut dyn FnMut(&Increment);
46
47/// A tool call handed over the moment its block is complete, before the
48/// call it belongs to has settled.
49///
50/// Separate from [`Increments`] because a caller acts on it: it may start
51/// a read-only tool while the model is still generating. **What arrives
52/// here is not history either.** The ledger records tool calls from
53/// [`ModelReturn`] after the call settles; when the call fails, a caller
54/// discards whatever it derived from these. What a caller may start, and
55/// in which order it records the results, is fixed by
56/// `crates/runtime/spec/Turn/Speculation.lean`.
57pub type EarlyCalls<'a> = &'a mut dyn FnMut(&ToolCall);
58
59/// The model port. Production adapter: gateway::endpoint;
60/// second adapter: citysim scripted model. Implementations never sample
61/// clocks or read global state.
62pub trait Model {
63 fn call(&mut self, req: &ModelRequest) -> Result<ModelReturn, AxError>;
64
65 /// The same call, reporting text as it arrives.
66 ///
67 /// The default answers by calling [`Model::call`] and reporting
68 /// nothing, which is the honest behaviour for an adapter that has no
69 /// stream: a caller gets the same `ModelReturn` at the same moment
70 /// and simply sees no increments. An adapter that overrides this owes
71 /// the same `ModelReturn` it would have returned from `call`,
72 /// including the same failures — a stream cut halfway is a read
73 /// error, never a shortened answer.
74 ///
75 /// # Errors
76 /// Whatever [`Model::call`] would fail with.
77 fn call_streaming(
78 &mut self,
79 req: &ModelRequest,
80 _onto: Increments<'_>,
81 ) -> Result<ModelReturn, AxError> {
82 self.call(req)
83 }
84
85 /// The streaming call, also handing over each tool call as soon as it
86 /// is complete.
87 ///
88 /// The default hands nothing over and answers with
89 /// [`Model::call_streaming`], which is honest for an adapter whose
90 /// wire cannot say when a call is complete: the caller simply gets no
91 /// head start. An adapter that overrides this owes the same
92 /// `ModelReturn` and failures as `call_streaming`, and every call it
93 /// hands over equals the one in that `ModelReturn`.
94 ///
95 /// # Errors
96 /// Whatever [`Model::call_streaming`] would fail with.
97 fn call_speculating(
98 &mut self,
99 req: &ModelRequest,
100 onto: Increments<'_>,
101 _early: EarlyCalls<'_>,
102 ) -> Result<ModelReturn, AxError> {
103 self.call_streaming(req, onto)
104 }
105}
106
107#[cfg(feature = "conformance")]
108pub mod conformance;
109
110#[cfg(test)]
111#[allow(
112 clippy::unwrap_used,
113 clippy::expect_used,
114 clippy::panic,
115 clippy::indexing_slicing,
116 reason = "test code"
117)]
118mod tests;