Skip to main content

agentkit_core/
lib.rs

1//! Core transcript, content, usage, and cancellation primitives for agentkit.
2//!
3//! This crate provides the foundational data model shared by every crate in
4//! the agentkit workspace. It defines:
5//!
6//! - **Transcript items** ([`Item`], [`ItemKind`]) -- the messages exchanged
7//!   between system, user, assistant, and tools.
8//! - **Content parts** ([`Part`], [`TextPart`], [`ToolCallPart`], etc.) --
9//!   the multimodal pieces that make up each item.
10//! - **Streaming deltas** ([`Delta`]) -- incremental updates emitted while a
11//!   model turn is in progress.
12//! - **Usage tracking** ([`Usage`], [`TokenUsage`], [`CostUsage`]) -- token
13//!   counts and cost accounting reported by providers.
14//! - **Cancellation** ([`CancellationController`], [`CancellationHandle`],
15//!   [`TurnCancellation`]) -- cooperative interruption of running turns.
16//! - **Typed identifiers** ([`SessionId`], [`TurnId`], [`ToolCallId`], etc.)
17//!   -- lightweight newtypes that prevent accidental ID mix-ups.
18//! - **Error types** ([`NormalizeError`], [`ProtocolError`], [`AgentError`])
19//!   -- shared error variants used across the workspace.
20//!
21//! # Example
22//!
23//! ```rust
24//! use agentkit_core::{Item, ItemKind};
25//!
26//! // Build a minimal transcript to feed into the agent loop.
27//! let transcript = vec![
28//!     Item::text(ItemKind::System, "You are a coding assistant."),
29//!     Item::text(ItemKind::User, "What files are in this repo?"),
30//! ];
31//!
32//! assert_eq!(transcript[0].kind, ItemKind::System);
33//! ```
34
35use std::collections::BTreeMap;
36use std::fmt;
37use std::sync::Arc;
38use std::sync::atomic::{AtomicU64, Ordering};
39use std::time::Duration;
40
41use futures_timer::Delay;
42use serde::{Deserialize, Serialize};
43use serde_json::Value;
44use thiserror::Error;
45
46macro_rules! id_newtype {
47    ($(#[$meta:meta])* $name:ident) => {
48        $(#[$meta])*
49        #[derive(
50            Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
51        )]
52        pub struct $name(pub String);
53
54        impl $name {
55            /// Creates a new identifier from any value that can be converted into a [`String`].
56            pub fn new(value: impl Into<String>) -> Self {
57                Self(value.into())
58            }
59        }
60
61        impl fmt::Display for $name {
62            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63                self.0.fmt(f)
64            }
65        }
66
67        impl From<&str> for $name {
68            fn from(value: &str) -> Self {
69                Self::new(value)
70            }
71        }
72
73        impl From<String> for $name {
74            fn from(value: String) -> Self {
75                Self(value)
76            }
77        }
78    };
79}
80
81id_newtype!(
82    /// Identifies an agent session.
83    ///
84    /// A session groups one or more turns that share the same model connection
85    /// and transcript history. Pass this to [`agentkit_loop`] when starting a
86    /// new session.
87    ///
88    /// # Example
89    ///
90    /// ```rust
91    /// use agentkit_core::SessionId;
92    ///
93    /// let id = SessionId::new("coding-agent-1");
94    /// assert_eq!(id.to_string(), "coding-agent-1");
95    /// ```
96    SessionId
97);
98id_newtype!(
99    /// Identifies a single turn within a session.
100    ///
101    /// Each call to the model within a session gets a unique `TurnId`. This is
102    /// used by compaction and reporting to attribute work to specific turns.
103    TurnId
104);
105id_newtype!(
106    /// Identifies a transcript [`Item`].
107    ///
108    /// Providers may assign message IDs to track items across API calls.
109    /// This is optional -- locally constructed items typically leave it as `None`.
110    MessageId
111);
112id_newtype!(
113    /// Identifies a tool call emitted by the model.
114    ///
115    /// The model produces a [`ToolCallPart`] with this ID; the corresponding
116    /// [`ToolResultPart`] references it via `call_id` so the model can match
117    /// results back to requests.
118    ToolCallId
119);
120id_newtype!(
121    /// Identifies a tool result.
122    ///
123    /// Used by providers that assign their own IDs to tool-result payloads.
124    ToolResultId
125);
126id_newtype!(
127    /// Identifies a task tracked by a task manager.
128    ///
129    /// Unlike [`ToolCallId`], which is model/provider-facing, `TaskId` is a
130    /// runtime-facing identifier used to inspect, cancel, or correlate work
131    /// managed by a task scheduler.
132    TaskId
133);
134id_newtype!(
135    /// Provider-assigned identifier for a message.
136    ///
137    /// Some providers return an opaque ID for each completion response;
138    /// this type preserves it for tracing and debugging.
139    ProviderMessageId
140);
141id_newtype!(
142    /// Identifies a binary or text artifact stored externally.
143    ///
144    /// Referenced by [`DataRef::Handle`] when content lives outside the
145    /// transcript (e.g. in an artifact store).
146    ArtifactId
147);
148id_newtype!(
149    /// Identifies a content part within a streaming [`Delta`] sequence.
150    ///
151    /// Deltas reference parts by `PartId` so the consumer can reconstruct the
152    /// full item as chunks arrive.
153    PartId
154);
155id_newtype!(
156    /// Identifies a pending tool-use approval request.
157    ///
158    /// When a tool requires human approval, the loop emits an approval request
159    /// tagged with this ID. The caller responds with a decision keyed to the
160    /// same ID.
161    ApprovalId
162);
163
164/// A map of arbitrary key-value metadata attached to items, parts, and other structures.
165///
166/// Most agentkit types carry a `metadata` field of this type. Providers,
167/// tools, and observers can store domain-specific information here without
168/// changing the core schema.
169///
170/// # Example
171///
172/// ```rust
173/// use agentkit_core::MetadataMap;
174/// use serde_json::json;
175///
176/// let mut meta = MetadataMap::new();
177/// meta.insert("source".into(), json!("user-clipboard"));
178/// assert_eq!(meta["source"], "user-clipboard");
179/// ```
180pub type MetadataMap = BTreeMap<String, Value>;
181
182#[derive(Default)]
183struct CancellationState {
184    generation: AtomicU64,
185}
186
187/// Owner-side handle for broadcasting cancellation to running turns.
188///
189/// Create one `CancellationController` per agent and hand out
190/// [`CancellationHandle`]s to the loop and tool executors. Calling
191/// [`interrupt`](Self::interrupt) bumps an internal generation counter so that
192/// every outstanding [`TurnCancellation`] checkpoint becomes cancelled.
193///
194/// # Example
195///
196/// ```rust
197/// use agentkit_core::CancellationController;
198///
199/// let controller = CancellationController::new();
200/// let handle = controller.handle();
201///
202/// // Before an interrupt the generation is 0.
203/// assert_eq!(handle.generation(), 0);
204///
205/// // Signal cancellation (e.g. from a Ctrl-C handler).
206/// controller.interrupt();
207/// assert_eq!(handle.generation(), 1);
208/// ```
209#[derive(Clone, Default)]
210pub struct CancellationController {
211    state: Arc<CancellationState>,
212}
213
214/// Read-only view of cancellation state, cheaply cloneable.
215///
216/// The loop and tool executors receive a `CancellationHandle` and use it to
217/// create [`TurnCancellation`] checkpoints or poll for interrupts directly.
218///
219/// Obtain one from [`CancellationController::handle`].
220#[derive(Clone, Default)]
221pub struct CancellationHandle {
222    state: Arc<CancellationState>,
223}
224
225/// A snapshot of the cancellation generation at the start of a turn.
226///
227/// Created via [`CancellationHandle::checkpoint`] or [`TurnCancellation::new`],
228/// this value records the generation counter at creation time. If the counter
229/// changes (because [`CancellationController::interrupt`] was called), the
230/// checkpoint reports itself as cancelled.
231///
232/// The agent loop passes a `TurnCancellation` into model and tool calls so
233/// they can bail out cooperatively.
234///
235/// # Example
236///
237/// ```rust
238/// use agentkit_core::{CancellationController, TurnCancellation};
239///
240/// let controller = CancellationController::new();
241/// let checkpoint = TurnCancellation::new(controller.handle());
242///
243/// assert!(!checkpoint.is_cancelled());
244/// controller.interrupt();
245/// assert!(checkpoint.is_cancelled());
246/// ```
247#[derive(Clone, Default)]
248pub struct TurnCancellation {
249    handle: CancellationHandle,
250    generation: u64,
251}
252
253impl CancellationController {
254    /// Creates a new controller with generation starting at 0.
255    pub fn new() -> Self {
256        Self::default()
257    }
258
259    /// Returns a cloneable [`CancellationHandle`] that shares state with this controller.
260    pub fn handle(&self) -> CancellationHandle {
261        CancellationHandle {
262            state: Arc::clone(&self.state),
263        }
264    }
265
266    /// Broadcasts a cancellation by incrementing the generation counter.
267    ///
268    /// Returns the new generation value. All [`TurnCancellation`] checkpoints
269    /// created before this call will report themselves as cancelled.
270    pub fn interrupt(&self) -> u64 {
271        self.state.generation.fetch_add(1, Ordering::SeqCst) + 1
272    }
273}
274
275impl CancellationHandle {
276    /// Returns the current generation counter.
277    pub fn generation(&self) -> u64 {
278        self.state.generation.load(Ordering::SeqCst)
279    }
280
281    /// Creates a [`TurnCancellation`] checkpoint capturing the current generation.
282    pub fn checkpoint(&self) -> TurnCancellation {
283        TurnCancellation {
284            handle: self.clone(),
285            generation: self.generation(),
286        }
287    }
288
289    /// Returns `true` if the generation has changed since `generation`.
290    ///
291    /// # Arguments
292    ///
293    /// * `generation` - The generation value to compare against, typically
294    ///   obtained from a prior call to [`generation`](Self::generation).
295    pub fn is_cancelled_since(&self, generation: u64) -> bool {
296        self.generation() != generation
297    }
298
299    /// Waits asynchronously until the generation changes from `generation`.
300    ///
301    /// Polls the generation counter every 10 ms. Prefer using
302    /// [`TurnCancellation::cancelled`] instead, which captures the generation
303    /// automatically.
304    ///
305    /// # Arguments
306    ///
307    /// * `generation` - The generation value to wait for a change from.
308    pub async fn cancelled_since(&self, generation: u64) {
309        while !self.is_cancelled_since(generation) {
310            Delay::new(Duration::from_millis(10)).await;
311        }
312    }
313}
314
315impl TurnCancellation {
316    /// Creates a checkpoint from the given handle, capturing its current generation.
317    ///
318    /// # Arguments
319    ///
320    /// * `handle` - The [`CancellationHandle`] to observe.
321    pub fn new(handle: CancellationHandle) -> Self {
322        handle.checkpoint()
323    }
324
325    /// Returns the generation that was captured when this checkpoint was created.
326    pub fn generation(&self) -> u64 {
327        self.generation
328    }
329
330    /// Returns `true` if the controller has been interrupted since this checkpoint was created.
331    pub fn is_cancelled(&self) -> bool {
332        self.handle.is_cancelled_since(self.generation)
333    }
334
335    /// Waits asynchronously until this checkpoint becomes cancelled.
336    ///
337    /// Useful in `tokio::select!` to race a model call against user interruption.
338    pub async fn cancelled(&self) {
339        self.handle.cancelled_since(self.generation).await;
340    }
341
342    /// Returns a reference to the underlying [`CancellationHandle`].
343    pub fn handle(&self) -> &CancellationHandle {
344        &self.handle
345    }
346}
347
348/// A single entry in the agent transcript.
349///
350/// An `Item` represents one message or event in the conversation between
351/// the system, user, assistant, and tools. Each item has a [`kind`](ItemKind)
352/// that determines its role and a vector of [`Part`]s that carry its content.
353///
354/// Items are the primary unit of data flowing through the agentkit loop:
355/// callers submit user and system items, the loop appends assistant and tool
356/// items, and compaction strategies operate on the full `Vec<Item>` transcript.
357///
358/// # Example
359///
360/// ```rust
361/// use agentkit_core::{Item, ItemKind};
362///
363/// let user_msg = Item::text(ItemKind::User, "List the workspace crates.");
364///
365/// assert_eq!(user_msg.kind, ItemKind::User);
366/// assert_eq!(user_msg.parts.len(), 1);
367/// ```
368#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
369pub struct Item {
370    /// Optional provider-assigned or user-supplied message identifier.
371    pub id: Option<MessageId>,
372    /// The role of this item in the transcript.
373    pub kind: ItemKind,
374    /// The content parts that make up this item.
375    pub parts: Vec<Part>,
376    /// Arbitrary key-value metadata for this item.
377    pub metadata: MetadataMap,
378}
379
380impl Item {
381    /// Builds an item with the given role and parts.
382    pub fn new(kind: ItemKind, parts: Vec<Part>) -> Self {
383        Self {
384            id: None,
385            kind,
386            parts,
387            metadata: MetadataMap::new(),
388        }
389    }
390
391    /// Builds a single-text-part item.
392    pub fn text(kind: ItemKind, text: impl Into<String>) -> Self {
393        Self::new(kind, vec![Part::Text(TextPart::new(text))])
394    }
395
396    /// Sets the item identifier.
397    pub fn with_id(mut self, id: impl Into<MessageId>) -> Self {
398        self.id = Some(id.into());
399        self
400    }
401
402    /// Replaces the item metadata.
403    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
404        self.metadata = metadata;
405        self
406    }
407
408    /// Appends one part to the item.
409    pub fn push_part(mut self, part: Part) -> Self {
410        self.parts.push(part);
411        self
412    }
413}
414
415/// The role of an [`Item`] in the transcript.
416///
417/// Variants are ordered so that `System < Developer < User < Assistant < Tool < Context`,
418/// which is useful for sorting items by priority during compaction.
419#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
420pub enum ItemKind {
421    /// Instructions provided by the application author (highest priority).
422    System,
423    /// Developer-level instructions that sit between system and user.
424    Developer,
425    /// A message from the end user.
426    User,
427    /// A response generated by the model.
428    Assistant,
429    /// Output from a tool execution.
430    Tool,
431    /// Ambient context injected by context loaders (e.g. project files, docs).
432    Context,
433}
434
435/// A content part within an [`Item`].
436///
437/// Items are composed of one or more parts, each carrying a different kind of
438/// content -- plain text, images, files, tool calls, tool results, or
439/// provider-specific custom payloads.
440///
441/// # Example
442///
443/// ```rust
444/// use agentkit_core::{Part, ToolCallPart};
445/// use serde_json::json;
446///
447/// let parts: Vec<Part> = vec![
448///     Part::text("Reading the config file..."),
449///     Part::ToolCall(ToolCallPart::new(
450///         "call-42",
451///         "fs.read_file",
452///         json!({ "path": "config.toml" }),
453///     )),
454/// ];
455///
456/// assert!(matches!(parts[0], Part::Text(_)));
457/// assert!(matches!(parts[1], Part::ToolCall(_)));
458/// ```
459#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
460pub enum Part {
461    /// Plain text content.
462    Text(TextPart),
463    /// Binary or encoded media (images, audio, video).
464    Media(MediaPart),
465    /// A file attachment.
466    File(FilePart),
467    /// Structured JSON data, optionally validated against a schema.
468    Structured(StructuredPart),
469    /// Model reasoning / chain-of-thought output.
470    Reasoning(ReasoningPart),
471    /// A tool invocation request emitted by the model.
472    ToolCall(ToolCallPart),
473    /// The result returned by a tool after execution.
474    ToolResult(ToolResultPart),
475    /// A provider-specific part that does not fit the standard variants.
476    Custom(CustomPart),
477}
478
479impl Part {
480    /// Builds a text part.
481    pub fn text(text: impl Into<String>) -> Self {
482        Self::Text(TextPart::new(text))
483    }
484
485    /// Builds a media part.
486    pub fn media(modality: Modality, mime_type: impl Into<String>, data: DataRef) -> Self {
487        Self::Media(MediaPart::new(modality, mime_type, data))
488    }
489
490    /// Builds a file part.
491    pub fn file(data: DataRef) -> Self {
492        Self::File(FilePart::new(data))
493    }
494
495    /// Builds a structured part.
496    pub fn structured(value: Value) -> Self {
497        Self::Structured(StructuredPart::new(value))
498    }
499
500    /// Builds a reasoning-summary part.
501    pub fn reasoning(summary: impl Into<String>) -> Self {
502        Self::Reasoning(ReasoningPart::summary(summary))
503    }
504}
505
506/// Discriminant for [`Part`] variants, used in streaming [`Delta`]s.
507///
508/// When a [`Delta::BeginPart`] arrives the consumer uses the `PartKind` to
509/// allocate the right buffer before subsequent append deltas arrive.
510#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
511pub enum PartKind {
512    /// Corresponds to [`Part::Text`].
513    Text,
514    /// Corresponds to [`Part::Media`].
515    Media,
516    /// Corresponds to [`Part::File`].
517    File,
518    /// Corresponds to [`Part::Structured`].
519    Structured,
520    /// Corresponds to [`Part::Reasoning`].
521    Reasoning,
522    /// Corresponds to [`Part::ToolCall`].
523    ToolCall,
524    /// Corresponds to [`Part::ToolResult`].
525    ToolResult,
526    /// Corresponds to [`Part::Custom`].
527    Custom,
528}
529
530/// Plain text content within an [`Item`].
531///
532/// This is the most common part type: user messages, assistant replies, and
533/// system prompts are all represented as `TextPart`s.
534///
535/// # Example
536///
537/// ```rust
538/// use agentkit_core::TextPart;
539///
540/// let part = TextPart::new("Hello, world!");
541/// assert_eq!(part.text, "Hello, world!");
542/// ```
543#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
544pub struct TextPart {
545    /// The text content.
546    pub text: String,
547    /// Arbitrary key-value metadata.
548    pub metadata: MetadataMap,
549}
550
551impl TextPart {
552    /// Builds a text part with empty metadata.
553    pub fn new(text: impl Into<String>) -> Self {
554        Self {
555            text: text.into(),
556            metadata: MetadataMap::new(),
557        }
558    }
559
560    /// Replaces the text-part metadata.
561    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
562        self.metadata = metadata;
563        self
564    }
565}
566
567/// Binary or encoded media content (image, audio, video).
568///
569/// The actual bytes are referenced through a [`DataRef`] which can be inline,
570/// a URI, or a handle to an external artifact store.
571#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
572pub struct MediaPart {
573    /// The kind of media (audio, image, video, or raw binary).
574    pub modality: Modality,
575    /// MIME type of the media, e.g. `"image/png"` or `"audio/wav"`.
576    pub mime_type: String,
577    /// Reference to the media data.
578    pub data: DataRef,
579    /// Arbitrary key-value metadata.
580    pub metadata: MetadataMap,
581}
582
583impl MediaPart {
584    /// Builds a media part with empty metadata.
585    pub fn new(modality: Modality, mime_type: impl Into<String>, data: DataRef) -> Self {
586        Self {
587            modality,
588            mime_type: mime_type.into(),
589            data,
590            metadata: MetadataMap::new(),
591        }
592    }
593
594    /// Replaces the media-part metadata.
595    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
596        self.metadata = metadata;
597        self
598    }
599}
600
601/// The kind of media carried by a [`MediaPart`].
602#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
603pub enum Modality {
604    /// Audio content (e.g. WAV, MP3).
605    Audio,
606    /// Image content (e.g. PNG, JPEG).
607    Image,
608    /// Video content (e.g. MP4).
609    Video,
610    /// Opaque binary data that does not fit another category.
611    Binary,
612}
613
614/// A reference to content data that may live inline, at a URI, or in an artifact store.
615///
616/// Used by [`MediaPart`], [`FilePart`], [`ReasoningPart`], and [`CustomPart`]
617/// to point at their underlying data without dictating storage.
618#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
619pub enum DataRef {
620    /// UTF-8 text stored inline (e.g. base64-encoded image data).
621    InlineText(String),
622    /// Raw bytes stored inline.
623    InlineBytes(Vec<u8>),
624    /// A URI pointing to externally hosted content.
625    Uri(String),
626    /// A handle to an artifact managed by an external store.
627    Handle(ArtifactId),
628}
629
630impl DataRef {
631    /// Stores UTF-8 text inline.
632    pub fn inline_text(text: impl Into<String>) -> Self {
633        Self::InlineText(text.into())
634    }
635
636    /// Stores bytes inline.
637    pub fn inline_bytes(bytes: impl Into<Vec<u8>>) -> Self {
638        Self::InlineBytes(bytes.into())
639    }
640
641    /// References externally hosted content by URI.
642    pub fn uri(uri: impl Into<String>) -> Self {
643        Self::Uri(uri.into())
644    }
645
646    /// References content through an artifact handle.
647    pub fn handle(id: impl Into<ArtifactId>) -> Self {
648        Self::Handle(id.into())
649    }
650}
651
652/// A file attachment within an [`Item`].
653///
654/// Files are distinct from [`MediaPart`] in that they carry an optional
655/// filename and are not necessarily displayable media.
656#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
657pub struct FilePart {
658    /// Optional human-readable filename (e.g. `"report.csv"`).
659    pub name: Option<String>,
660    /// Optional MIME type of the file.
661    pub mime_type: Option<String>,
662    /// Reference to the file data.
663    pub data: DataRef,
664    /// Arbitrary key-value metadata.
665    pub metadata: MetadataMap,
666}
667
668impl FilePart {
669    /// Builds an unnamed file part with empty metadata.
670    pub fn new(data: DataRef) -> Self {
671        Self {
672            name: None,
673            mime_type: None,
674            data,
675            metadata: MetadataMap::new(),
676        }
677    }
678
679    /// Builds a named file part with empty metadata.
680    pub fn named(name: impl Into<String>, data: DataRef) -> Self {
681        Self::new(data).with_name(name)
682    }
683
684    /// Sets the file name.
685    pub fn with_name(mut self, name: impl Into<String>) -> Self {
686        self.name = Some(name.into());
687        self
688    }
689
690    /// Sets the file mime type.
691    pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
692        self.mime_type = Some(mime_type.into());
693        self
694    }
695
696    /// Replaces the file-part metadata.
697    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
698        self.metadata = metadata;
699        self
700    }
701}
702
703/// Structured JSON content, optionally paired with a JSON Schema for validation.
704///
705/// Providers that support structured output (e.g. function-calling mode) may
706/// return a `StructuredPart` instead of free-form text.
707#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
708pub struct StructuredPart {
709    /// The structured data as a JSON [`Value`].
710    pub value: Value,
711    /// An optional JSON Schema that `value` conforms to.
712    pub schema: Option<Value>,
713    /// Arbitrary key-value metadata.
714    pub metadata: MetadataMap,
715}
716
717impl StructuredPart {
718    /// Builds a structured part with empty metadata and no schema.
719    pub fn new(value: Value) -> Self {
720        Self {
721            value,
722            schema: None,
723            metadata: MetadataMap::new(),
724        }
725    }
726
727    /// Sets the optional schema.
728    pub fn with_schema(mut self, schema: Value) -> Self {
729        self.schema = Some(schema);
730        self
731    }
732
733    /// Replaces the structured-part metadata.
734    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
735        self.metadata = metadata;
736        self
737    }
738}
739
740/// Model reasoning or chain-of-thought output.
741///
742/// Some providers expose the model's internal reasoning alongside the final
743/// answer. The reasoning may be a readable summary, opaque data, or both.
744/// The `redacted` flag indicates provider-side filtering.
745#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
746pub struct ReasoningPart {
747    /// A human-readable summary of the model's reasoning.
748    pub summary: Option<String>,
749    /// Opaque or detailed reasoning data.
750    pub data: Option<DataRef>,
751    /// `true` if the provider redacted the full reasoning content.
752    pub redacted: bool,
753    /// Arbitrary key-value metadata.
754    pub metadata: MetadataMap,
755}
756
757impl ReasoningPart {
758    /// Builds a readable reasoning summary.
759    pub fn summary(summary: impl Into<String>) -> Self {
760        Self {
761            summary: Some(summary.into()),
762            data: None,
763            redacted: false,
764            metadata: MetadataMap::new(),
765        }
766    }
767
768    /// Builds a redacted readable reasoning summary.
769    pub fn redacted_summary(summary: impl Into<String>) -> Self {
770        Self::summary(summary).with_redacted(true)
771    }
772
773    /// Sets the optional reasoning data reference.
774    pub fn with_data(mut self, data: DataRef) -> Self {
775        self.data = Some(data);
776        self
777    }
778
779    /// Sets whether the reasoning content was redacted.
780    pub fn with_redacted(mut self, redacted: bool) -> Self {
781        self.redacted = redacted;
782        self
783    }
784
785    /// Replaces the reasoning-part metadata.
786    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
787        self.metadata = metadata;
788        self
789    }
790}
791
792/// A tool invocation request emitted by the model.
793///
794/// The agent loop receives this part, executes the named tool, and appends a
795/// [`ToolResultPart`] back to the transcript for the model to observe.
796///
797/// # Example
798///
799/// ```rust
800/// use agentkit_core::ToolCallPart;
801/// use serde_json::json;
802///
803/// let call = ToolCallPart::new("call-7", "fs.read_file", json!({ "path": "src/main.rs" }));
804///
805/// assert_eq!(call.name, "fs.read_file");
806/// ```
807#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
808pub struct ToolCallPart {
809    /// Unique identifier for this tool call, used to correlate with [`ToolResultPart::call_id`].
810    pub id: ToolCallId,
811    /// The name of the tool to invoke (e.g. `"fs.read_file"`, `"shell.exec"`).
812    pub name: String,
813    /// The JSON arguments to pass to the tool.
814    pub input: Value,
815    /// Arbitrary key-value metadata.
816    pub metadata: MetadataMap,
817}
818
819impl ToolCallPart {
820    /// Builds a tool-call part with empty metadata.
821    pub fn new(id: impl Into<ToolCallId>, name: impl Into<String>, input: Value) -> Self {
822        Self {
823            id: id.into(),
824            name: name.into(),
825            input,
826            metadata: MetadataMap::new(),
827        }
828    }
829
830    /// Replaces the tool-call metadata.
831    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
832        self.metadata = metadata;
833        self
834    }
835}
836
837/// The result of executing a tool, sent back to the model.
838///
839/// Each `ToolResultPart` references the [`ToolCallPart`] it answers via
840/// `call_id`. The `is_error` flag tells the model whether the tool succeeded.
841///
842/// # Example
843///
844/// ```rust
845/// use agentkit_core::{ToolOutput, ToolResultPart};
846///
847/// let result = ToolResultPart::success("call-7", ToolOutput::text("fn main() { ... }"));
848///
849/// assert!(!result.is_error);
850/// ```
851#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
852pub struct ToolResultPart {
853    /// The [`ToolCallId`] of the tool call this result answers.
854    pub call_id: ToolCallId,
855    /// The output produced by the tool.
856    pub output: ToolOutput,
857    /// `true` if the tool execution failed.
858    pub is_error: bool,
859    /// Arbitrary key-value metadata.
860    pub metadata: MetadataMap,
861}
862
863impl ToolResultPart {
864    /// Builds a successful tool-result part with empty metadata.
865    pub fn success(call_id: impl Into<ToolCallId>, output: ToolOutput) -> Self {
866        Self {
867            call_id: call_id.into(),
868            output,
869            is_error: false,
870            metadata: MetadataMap::new(),
871        }
872    }
873
874    /// Builds an error tool-result part with empty metadata.
875    pub fn error(call_id: impl Into<ToolCallId>, output: ToolOutput) -> Self {
876        Self::success(call_id, output).with_is_error(true)
877    }
878
879    /// Sets the error flag explicitly.
880    pub fn with_is_error(mut self, is_error: bool) -> Self {
881        self.is_error = is_error;
882        self
883    }
884
885    /// Replaces the tool-result metadata.
886    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
887        self.metadata = metadata;
888        self
889    }
890}
891
892/// The payload returned by a tool execution.
893///
894/// Tools may return plain text, structured JSON, a composite list of
895/// [`Part`]s, or a collection of files.
896#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
897pub enum ToolOutput {
898    /// Plain text output.
899    Text(String),
900    /// Structured JSON output.
901    Structured(Value),
902    /// A list of content parts (e.g. text + images).
903    Parts(Vec<Part>),
904    /// A list of file attachments.
905    Files(Vec<FilePart>),
906}
907
908impl ToolOutput {
909    /// Builds plain-text tool output.
910    pub fn text(text: impl Into<String>) -> Self {
911        Self::Text(text.into())
912    }
913
914    /// Builds structured tool output.
915    pub fn structured(value: Value) -> Self {
916        Self::Structured(value)
917    }
918
919    /// Builds multipart tool output.
920    pub fn parts(parts: Vec<Part>) -> Self {
921        Self::Parts(parts)
922    }
923
924    /// Builds file-based tool output.
925    pub fn files(files: Vec<FilePart>) -> Self {
926        Self::Files(files)
927    }
928}
929
930/// A provider-specific content part that does not fit the standard variants.
931///
932/// Use this for extensions or experimental features that have not been
933/// promoted to a first-class [`Part`] variant.
934#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
935pub struct CustomPart {
936    /// A free-form string identifying the custom part type.
937    pub kind: String,
938    /// Optional data reference.
939    pub data: Option<DataRef>,
940    /// Optional structured value.
941    pub value: Option<Value>,
942    /// Arbitrary key-value metadata.
943    pub metadata: MetadataMap,
944}
945
946impl CustomPart {
947    /// Builds a custom part with empty metadata.
948    pub fn new(kind: impl Into<String>) -> Self {
949        Self {
950            kind: kind.into(),
951            data: None,
952            value: None,
953            metadata: MetadataMap::new(),
954        }
955    }
956
957    /// Sets the custom part data reference.
958    pub fn with_data(mut self, data: DataRef) -> Self {
959        self.data = Some(data);
960        self
961    }
962
963    /// Sets the custom part structured value.
964    pub fn with_value(mut self, value: Value) -> Self {
965        self.value = Some(value);
966        self
967    }
968
969    /// Replaces the custom-part metadata.
970    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
971        self.metadata = metadata;
972        self
973    }
974}
975
976/// An incremental update emitted while a model turn is streaming.
977///
978/// The provider adapter emits a sequence of `Delta` values that the loop and
979/// reporters consume to reconstruct the full [`Item`] progressively. A typical
980/// sequence looks like:
981///
982/// 1. [`BeginPart`](Self::BeginPart) -- allocates a new part buffer.
983/// 2. One or more [`AppendText`](Self::AppendText) /
984///    [`AppendBytes`](Self::AppendBytes) -- fills the buffer.
985/// 3. [`CommitPart`](Self::CommitPart) -- finalises the part.
986///
987/// # Example
988///
989/// ```rust
990/// use agentkit_core::{Delta, PartId, PartKind};
991///
992/// let deltas = vec![
993///     Delta::BeginPart { part_id: PartId::new("p1"), kind: PartKind::Text },
994///     Delta::AppendText { part_id: PartId::new("p1"), chunk: "Hello".into() },
995///     Delta::AppendText { part_id: PartId::new("p1"), chunk: ", world!".into() },
996/// ];
997///
998/// assert_eq!(deltas.len(), 3);
999/// ```
1000#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1001pub enum Delta {
1002    /// Signals the start of a new part with the given kind.
1003    BeginPart {
1004        /// Identifier for the part being constructed.
1005        part_id: PartId,
1006        /// The kind of part being started.
1007        kind: PartKind,
1008    },
1009    /// Appends a text chunk to an in-progress part.
1010    AppendText {
1011        /// Identifier of the target part.
1012        part_id: PartId,
1013        /// The text chunk to append.
1014        chunk: String,
1015    },
1016    /// Appends raw bytes to an in-progress part.
1017    AppendBytes {
1018        /// Identifier of the target part.
1019        part_id: PartId,
1020        /// The byte chunk to append.
1021        chunk: Vec<u8>,
1022    },
1023    /// Replaces the structured value of an in-progress part wholesale.
1024    ReplaceStructured {
1025        /// Identifier of the target part.
1026        part_id: PartId,
1027        /// The new structured value.
1028        value: Value,
1029    },
1030    /// Sets or replaces the metadata on an in-progress part.
1031    SetMetadata {
1032        /// Identifier of the target part.
1033        part_id: PartId,
1034        /// The new metadata map.
1035        metadata: MetadataMap,
1036    },
1037    /// Finalises a part, providing the fully assembled [`Part`].
1038    CommitPart {
1039        /// The completed part.
1040        part: Part,
1041    },
1042}
1043
1044/// Token and cost usage reported by a model provider for a single turn.
1045///
1046/// Reporters and compaction triggers inspect `Usage` to log progress, enforce
1047/// budgets, and decide when to compact the transcript.
1048///
1049/// # Example
1050///
1051/// ```rust
1052/// use agentkit_core::{TokenUsage, Usage};
1053///
1054/// let usage = Usage::new(
1055///     TokenUsage::new(1500, 200)
1056///         .with_cached_input_tokens(1000)
1057///         .with_cache_write_input_tokens(1200),
1058/// );
1059///
1060/// let tokens = usage.tokens.as_ref().unwrap();
1061/// assert_eq!(tokens.input_tokens, 1500);
1062/// ```
1063#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1064pub struct Usage {
1065    /// Token counts for this turn, if the provider reports them.
1066    pub tokens: Option<TokenUsage>,
1067    /// Monetary cost for this turn, if the provider reports it.
1068    pub cost: Option<CostUsage>,
1069    /// Arbitrary key-value metadata.
1070    pub metadata: MetadataMap,
1071}
1072
1073impl Usage {
1074    /// Builds a usage record with token counts and no cost.
1075    pub fn new(tokens: TokenUsage) -> Self {
1076        Self {
1077            tokens: Some(tokens),
1078            cost: None,
1079            metadata: MetadataMap::new(),
1080        }
1081    }
1082
1083    /// Sets the cost information.
1084    pub fn with_cost(mut self, cost: CostUsage) -> Self {
1085        self.cost = Some(cost);
1086        self
1087    }
1088
1089    /// Replaces the usage metadata.
1090    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
1091        self.metadata = metadata;
1092        self
1093    }
1094}
1095
1096/// Token counts broken down by direction and special categories.
1097#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1098pub struct TokenUsage {
1099    /// Number of tokens in the input (prompt) sent to the model.
1100    pub input_tokens: u64,
1101    /// Number of tokens in the model's output (completion).
1102    pub output_tokens: u64,
1103    /// Tokens consumed by the model's internal reasoning, if reported.
1104    pub reasoning_tokens: Option<u64>,
1105    /// Input tokens served from the provider's prompt cache, if reported.
1106    pub cached_input_tokens: Option<u64>,
1107    /// Input tokens written into the provider's prompt cache, if reported.
1108    pub cache_write_input_tokens: Option<u64>,
1109}
1110
1111impl TokenUsage {
1112    /// Builds token usage with required input and output counts.
1113    pub fn new(input_tokens: u64, output_tokens: u64) -> Self {
1114        Self {
1115            input_tokens,
1116            output_tokens,
1117            reasoning_tokens: None,
1118            cached_input_tokens: None,
1119            cache_write_input_tokens: None,
1120        }
1121    }
1122
1123    /// Sets reasoning token count.
1124    pub fn with_reasoning_tokens(mut self, reasoning_tokens: u64) -> Self {
1125        self.reasoning_tokens = Some(reasoning_tokens);
1126        self
1127    }
1128
1129    /// Sets cached input token count.
1130    pub fn with_cached_input_tokens(mut self, cached_input_tokens: u64) -> Self {
1131        self.cached_input_tokens = Some(cached_input_tokens);
1132        self
1133    }
1134
1135    /// Sets cache-write input token count.
1136    pub fn with_cache_write_input_tokens(mut self, cache_write_input_tokens: u64) -> Self {
1137        self.cache_write_input_tokens = Some(cache_write_input_tokens);
1138        self
1139    }
1140}
1141
1142/// Monetary cost for a single model turn.
1143#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1144pub struct CostUsage {
1145    /// The cost amount as a floating-point number.
1146    pub amount: f64,
1147    /// The ISO 4217 currency code (e.g. `"USD"`).
1148    pub currency: String,
1149    /// An optional provider-specific cost string for display purposes.
1150    pub provider_amount: Option<String>,
1151}
1152
1153impl CostUsage {
1154    /// Builds cost usage with no provider-specific display string.
1155    pub fn new(amount: f64, currency: impl Into<String>) -> Self {
1156        Self {
1157            amount,
1158            currency: currency.into(),
1159            provider_amount: None,
1160        }
1161    }
1162
1163    /// Sets the optional provider-specific display value.
1164    pub fn with_provider_amount(mut self, provider_amount: impl Into<String>) -> Self {
1165        self.provider_amount = Some(provider_amount.into());
1166        self
1167    }
1168}
1169
1170/// The reason a model turn ended.
1171///
1172/// The loop inspects the `FinishReason` to decide whether to execute tool
1173/// calls, request more input, or report an error.
1174#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1175pub enum FinishReason {
1176    /// The model finished generating its response normally.
1177    Completed,
1178    /// The model stopped to invoke one or more tools.
1179    ToolCall,
1180    /// The response was truncated because the token limit was reached.
1181    MaxTokens,
1182    /// The turn was cancelled via [`TurnCancellation`].
1183    Cancelled,
1184    /// The provider blocked the response (e.g. content policy violation).
1185    Blocked,
1186    /// An error occurred during generation.
1187    Error,
1188    /// A provider-specific reason not covered by the standard variants.
1189    Other(String),
1190}
1191
1192/// Read-only view over an [`Item`]'s essential fields.
1193///
1194/// This trait lets downstream crates (compaction, reporting) operate on
1195/// item-like types without depending on the concrete [`Item`] struct.
1196pub trait ItemView {
1197    /// Returns the role of this item.
1198    fn kind(&self) -> ItemKind;
1199    /// Returns the content parts.
1200    fn parts(&self) -> &[Part];
1201    /// Returns the metadata map.
1202    fn metadata(&self) -> &MetadataMap;
1203}
1204
1205impl ItemView for Item {
1206    fn kind(&self) -> ItemKind {
1207        self.kind
1208    }
1209
1210    fn parts(&self) -> &[Part] {
1211        &self.parts
1212    }
1213
1214    fn metadata(&self) -> &MetadataMap {
1215        &self.metadata
1216    }
1217}
1218
1219/// Error returned when content cannot be normalised into the agentkit data model.
1220#[derive(Debug, Error)]
1221pub enum NormalizeError {
1222    /// The content shape is not supported by the current provider adapter.
1223    #[error("unsupported content shape: {0}")]
1224    Unsupported(String),
1225}
1226
1227/// Error indicating an invalid state in the provider protocol.
1228#[derive(Debug, Error)]
1229pub enum ProtocolError {
1230    /// The provider or loop reached a state that violates protocol invariants.
1231    #[error("invalid protocol state: {0}")]
1232    InvalidState(String),
1233}
1234
1235/// Top-level error type that unifies normalisation and protocol errors.
1236///
1237/// Provider adapters and the agent loop surface this type so callers can
1238/// handle both categories uniformly.
1239#[derive(Debug, Error)]
1240pub enum AgentError {
1241    /// A content normalisation error.
1242    #[error(transparent)]
1243    Normalize(#[from] NormalizeError),
1244    /// A protocol-level error.
1245    #[error(transparent)]
1246    Protocol(#[from] ProtocolError),
1247}