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    /// Token / cost usage produced by the model turn that emitted this item.
379    /// Populated by the loop on assistant items when the provider reports it.
380    pub usage: Option<Usage>,
381    /// Why the model turn that produced this item ended. Populated by the
382    /// loop on assistant items.
383    pub finish_reason: Option<FinishReason>,
384    /// When this item was appended to the transcript. Populated by the loop
385    /// for every appended item.
386    pub created_at: Option<Timestamp>,
387}
388
389impl Item {
390    /// Builds an item with the given role and parts.
391    pub fn new(kind: ItemKind, parts: Vec<Part>) -> Self {
392        Self {
393            id: None,
394            kind,
395            parts,
396            metadata: MetadataMap::new(),
397            usage: None,
398            finish_reason: None,
399            created_at: None,
400        }
401    }
402
403    /// Builds a single-text-part item.
404    pub fn text(kind: ItemKind, text: impl Into<String>) -> Self {
405        Self::new(kind, vec![Part::Text(TextPart::new(text))])
406    }
407
408    /// Builds a [`ItemKind::Notification`] item carrying free-form text.
409    /// Adapters wrap the content in `<system-reminder>` and deliver it as
410    /// a user-role message so the model can react to the notification on
411    /// its next turn without violating tool_use/tool_result pairing.
412    pub fn notification(text: impl Into<String>) -> Self {
413        Self::text(ItemKind::Notification, text)
414    }
415
416    /// Sets the item identifier.
417    pub fn with_id(mut self, id: impl Into<MessageId>) -> Self {
418        self.id = Some(id.into());
419        self
420    }
421
422    /// Replaces the item metadata.
423    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
424        self.metadata = metadata;
425        self
426    }
427
428    /// Appends one part to the item.
429    pub fn push_part(mut self, part: Part) -> Self {
430        self.parts.push(part);
431        self
432    }
433
434    /// Sets the model usage that produced this item.
435    pub fn with_usage(mut self, usage: Usage) -> Self {
436        self.usage = Some(usage);
437        self
438    }
439
440    /// Sets the finish reason for the model turn that produced this item.
441    pub fn with_finish_reason(mut self, reason: FinishReason) -> Self {
442        self.finish_reason = Some(reason);
443        self
444    }
445
446    /// Sets when this item was created.
447    pub fn with_created_at(mut self, ts: Timestamp) -> Self {
448        self.created_at = Some(ts);
449        self
450    }
451}
452
453/// A wall-clock instant carried on items, expressed as milliseconds since
454/// the Unix epoch in UTC. Stamped by the loop when items land in the
455/// transcript so consumers can sort, filter, or expire by age without
456/// depending on a particular date-time crate.
457#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
458pub struct Timestamp(pub u64);
459
460impl Timestamp {
461    /// Captures the current wall-clock time. Returns `Timestamp(0)` on the
462    /// vanishingly unlikely event the system clock is before the epoch.
463    pub fn now() -> Self {
464        let millis = std::time::SystemTime::now()
465            .duration_since(std::time::UNIX_EPOCH)
466            .map(|d| d.as_millis() as u64)
467            .unwrap_or(0);
468        Self(millis)
469    }
470}
471
472/// The role of an [`Item`] in the transcript.
473///
474/// Variants are ordered so that
475/// `System < Developer < User < Assistant < Tool < Context < Notification`,
476/// which is useful for sorting items by priority during compaction.
477#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
478pub enum ItemKind {
479    /// Instructions provided by the application author (highest priority).
480    System,
481    /// Developer-level instructions that sit between system and user.
482    Developer,
483    /// A message from the end user.
484    User,
485    /// A response generated by the model.
486    Assistant,
487    /// Output from a tool execution.
488    Tool,
489    /// Ambient context injected by context loaders (e.g. project files, docs).
490    Context,
491    /// Out-of-band side-channel signal injected mid-conversation:
492    /// background-task completions, environment changes, system reminders.
493    /// Adapters render it as a user-role message wrapped in
494    /// `<system-reminder>` so the model interprets it as a notification
495    /// rather than user input. Distinct from [`ItemKind::Context`] in two
496    /// ways: (1) temporal placement is preserved (Anthropic adapter does
497    /// NOT hoist it to the top-level `system` field), (2) UI hosts can
498    /// filter or render notifications differently from user turns.
499    ///
500    /// Use this when a tool runs in the background and its result
501    /// arrives after the original `tool_use` was already paired and
502    /// closed — emitting another `tool_result` for the same call_id
503    /// would violate the provider schema.
504    Notification,
505}
506
507/// A content part within an [`Item`].
508///
509/// Items are composed of one or more parts, each carrying a different kind of
510/// content -- plain text, images, files, tool calls, tool results, or
511/// provider-specific custom payloads.
512///
513/// # Example
514///
515/// ```rust
516/// use agentkit_core::{Part, ToolCallPart};
517/// use serde_json::json;
518///
519/// let parts: Vec<Part> = vec![
520///     Part::text("Reading the config file..."),
521///     Part::ToolCall(ToolCallPart::new(
522///         "call-42",
523///         "fs_read_file",
524///         json!({ "path": "config.toml" }),
525///     )),
526/// ];
527///
528/// assert!(matches!(parts[0], Part::Text(_)));
529/// assert!(matches!(parts[1], Part::ToolCall(_)));
530/// ```
531#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
532pub enum Part {
533    /// Plain text content.
534    Text(TextPart),
535    /// Binary or encoded media (images, audio, video).
536    Media(MediaPart),
537    /// A file attachment.
538    File(FilePart),
539    /// Structured JSON data, optionally validated against a schema.
540    Structured(StructuredPart),
541    /// Model reasoning / chain-of-thought output.
542    Reasoning(ReasoningPart),
543    /// A tool invocation request emitted by the model.
544    ToolCall(ToolCallPart),
545    /// The result returned by a tool after execution.
546    ToolResult(ToolResultPart),
547    /// A provider-specific part that does not fit the standard variants.
548    Custom(CustomPart),
549}
550
551impl Part {
552    /// Builds a text part.
553    pub fn text(text: impl Into<String>) -> Self {
554        Self::Text(TextPart::new(text))
555    }
556
557    /// Builds a media part.
558    pub fn media(modality: Modality, mime_type: impl Into<String>, data: DataRef) -> Self {
559        Self::Media(MediaPart::new(modality, mime_type, data))
560    }
561
562    /// Builds a file part.
563    pub fn file(data: DataRef) -> Self {
564        Self::File(FilePart::new(data))
565    }
566
567    /// Builds a structured part.
568    pub fn structured(value: Value) -> Self {
569        Self::Structured(StructuredPart::new(value))
570    }
571
572    /// Builds a reasoning-summary part.
573    pub fn reasoning(summary: impl Into<String>) -> Self {
574        Self::Reasoning(ReasoningPart::summary(summary))
575    }
576}
577
578/// Discriminant for [`Part`] variants, used in streaming [`Delta`]s.
579///
580/// When a [`Delta::BeginPart`] arrives the consumer uses the `PartKind` to
581/// allocate the right buffer before subsequent append deltas arrive.
582#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
583pub enum PartKind {
584    /// Corresponds to [`Part::Text`].
585    Text,
586    /// Corresponds to [`Part::Media`].
587    Media,
588    /// Corresponds to [`Part::File`].
589    File,
590    /// Corresponds to [`Part::Structured`].
591    Structured,
592    /// Corresponds to [`Part::Reasoning`].
593    Reasoning,
594    /// Corresponds to [`Part::ToolCall`].
595    ToolCall,
596    /// Corresponds to [`Part::ToolResult`].
597    ToolResult,
598    /// Corresponds to [`Part::Custom`].
599    Custom,
600}
601
602/// Plain text content within an [`Item`].
603///
604/// This is the most common part type: user messages, assistant replies, and
605/// system prompts are all represented as `TextPart`s.
606///
607/// # Example
608///
609/// ```rust
610/// use agentkit_core::TextPart;
611///
612/// let part = TextPart::new("Hello, world!");
613/// assert_eq!(part.text, "Hello, world!");
614/// ```
615#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
616pub struct TextPart {
617    /// The text content.
618    pub text: String,
619    /// Arbitrary key-value metadata.
620    pub metadata: MetadataMap,
621}
622
623impl TextPart {
624    /// Builds a text part with empty metadata.
625    pub fn new(text: impl Into<String>) -> Self {
626        Self {
627            text: text.into(),
628            metadata: MetadataMap::new(),
629        }
630    }
631
632    /// Replaces the text-part metadata.
633    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
634        self.metadata = metadata;
635        self
636    }
637}
638
639/// Binary or encoded media content (image, audio, video).
640///
641/// The actual bytes are referenced through a [`DataRef`] which can be inline,
642/// a URI, or a handle to an external artifact store.
643#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
644pub struct MediaPart {
645    /// The kind of media (audio, image, video, or raw binary).
646    pub modality: Modality,
647    /// MIME type of the media, e.g. `"image/png"` or `"audio/wav"`.
648    pub mime_type: String,
649    /// Reference to the media data.
650    pub data: DataRef,
651    /// Arbitrary key-value metadata.
652    pub metadata: MetadataMap,
653}
654
655impl MediaPart {
656    /// Builds a media part with empty metadata.
657    pub fn new(modality: Modality, mime_type: impl Into<String>, data: DataRef) -> Self {
658        Self {
659            modality,
660            mime_type: mime_type.into(),
661            data,
662            metadata: MetadataMap::new(),
663        }
664    }
665
666    /// Replaces the media-part metadata.
667    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
668        self.metadata = metadata;
669        self
670    }
671}
672
673/// The kind of media carried by a [`MediaPart`].
674#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
675pub enum Modality {
676    /// Audio content (e.g. WAV, MP3).
677    Audio,
678    /// Image content (e.g. PNG, JPEG).
679    Image,
680    /// Video content (e.g. MP4).
681    Video,
682    /// Opaque binary data that does not fit another category.
683    Binary,
684}
685
686/// A reference to content data that may live inline, at a URI, or in an artifact store.
687///
688/// Used by [`MediaPart`], [`FilePart`], [`ReasoningPart`], and [`CustomPart`]
689/// to point at their underlying data without dictating storage.
690#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
691pub enum DataRef {
692    /// UTF-8 text stored inline (e.g. base64-encoded image data).
693    InlineText(String),
694    /// Raw bytes stored inline.
695    InlineBytes(Vec<u8>),
696    /// A URI pointing to externally hosted content.
697    Uri(String),
698    /// A handle to an artifact managed by an external store.
699    Handle(ArtifactId),
700}
701
702impl DataRef {
703    /// Stores UTF-8 text inline.
704    pub fn inline_text(text: impl Into<String>) -> Self {
705        Self::InlineText(text.into())
706    }
707
708    /// Stores bytes inline.
709    pub fn inline_bytes(bytes: impl Into<Vec<u8>>) -> Self {
710        Self::InlineBytes(bytes.into())
711    }
712
713    /// References externally hosted content by URI.
714    pub fn uri(uri: impl Into<String>) -> Self {
715        Self::Uri(uri.into())
716    }
717
718    /// References content through an artifact handle.
719    pub fn handle(id: impl Into<ArtifactId>) -> Self {
720        Self::Handle(id.into())
721    }
722}
723
724/// A file attachment within an [`Item`].
725///
726/// Files are distinct from [`MediaPart`] in that they carry an optional
727/// filename and are not necessarily displayable media.
728#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
729pub struct FilePart {
730    /// Optional human-readable filename (e.g. `"report.csv"`).
731    pub name: Option<String>,
732    /// Optional MIME type of the file.
733    pub mime_type: Option<String>,
734    /// Reference to the file data.
735    pub data: DataRef,
736    /// Arbitrary key-value metadata.
737    pub metadata: MetadataMap,
738}
739
740impl FilePart {
741    /// Builds an unnamed file part with empty metadata.
742    pub fn new(data: DataRef) -> Self {
743        Self {
744            name: None,
745            mime_type: None,
746            data,
747            metadata: MetadataMap::new(),
748        }
749    }
750
751    /// Builds a named file part with empty metadata.
752    pub fn named(name: impl Into<String>, data: DataRef) -> Self {
753        Self::new(data).with_name(name)
754    }
755
756    /// Sets the file name.
757    pub fn with_name(mut self, name: impl Into<String>) -> Self {
758        self.name = Some(name.into());
759        self
760    }
761
762    /// Sets the file mime type.
763    pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
764        self.mime_type = Some(mime_type.into());
765        self
766    }
767
768    /// Replaces the file-part metadata.
769    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
770        self.metadata = metadata;
771        self
772    }
773}
774
775/// Structured JSON content, optionally paired with a JSON Schema for validation.
776///
777/// Providers that support structured output (e.g. function-calling mode) may
778/// return a `StructuredPart` instead of free-form text.
779#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
780pub struct StructuredPart {
781    /// The structured data as a JSON [`Value`].
782    pub value: Value,
783    /// An optional JSON Schema that `value` conforms to.
784    pub schema: Option<Value>,
785    /// Arbitrary key-value metadata.
786    pub metadata: MetadataMap,
787}
788
789impl StructuredPart {
790    /// Builds a structured part with empty metadata and no schema.
791    pub fn new(value: Value) -> Self {
792        Self {
793            value,
794            schema: None,
795            metadata: MetadataMap::new(),
796        }
797    }
798
799    /// Sets the optional schema.
800    pub fn with_schema(mut self, schema: Value) -> Self {
801        self.schema = Some(schema);
802        self
803    }
804
805    /// Replaces the structured-part metadata.
806    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
807        self.metadata = metadata;
808        self
809    }
810}
811
812/// Model reasoning or chain-of-thought output.
813///
814/// Some providers expose the model's internal reasoning alongside the final
815/// answer. The reasoning may be a readable summary, opaque data, or both.
816/// The `redacted` flag indicates provider-side filtering.
817#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
818pub struct ReasoningPart {
819    /// A human-readable summary of the model's reasoning.
820    pub summary: Option<String>,
821    /// Opaque or detailed reasoning data.
822    pub data: Option<DataRef>,
823    /// `true` if the provider redacted the full reasoning content.
824    pub redacted: bool,
825    /// Arbitrary key-value metadata.
826    pub metadata: MetadataMap,
827}
828
829impl ReasoningPart {
830    /// Builds a readable reasoning summary.
831    pub fn summary(summary: impl Into<String>) -> Self {
832        Self {
833            summary: Some(summary.into()),
834            data: None,
835            redacted: false,
836            metadata: MetadataMap::new(),
837        }
838    }
839
840    /// Builds a redacted readable reasoning summary.
841    pub fn redacted_summary(summary: impl Into<String>) -> Self {
842        Self::summary(summary).with_redacted(true)
843    }
844
845    /// Sets the optional reasoning data reference.
846    pub fn with_data(mut self, data: DataRef) -> Self {
847        self.data = Some(data);
848        self
849    }
850
851    /// Sets whether the reasoning content was redacted.
852    pub fn with_redacted(mut self, redacted: bool) -> Self {
853        self.redacted = redacted;
854        self
855    }
856
857    /// Replaces the reasoning-part metadata.
858    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
859        self.metadata = metadata;
860        self
861    }
862}
863
864/// A tool invocation request emitted by the model.
865///
866/// The agent loop receives this part, executes the named tool, and appends a
867/// [`ToolResultPart`] back to the transcript for the model to observe.
868///
869/// # Example
870///
871/// ```rust
872/// use agentkit_core::ToolCallPart;
873/// use serde_json::json;
874///
875/// let call = ToolCallPart::new("call-7", "fs_read_file", json!({ "path": "src/main.rs" }));
876///
877/// assert_eq!(call.name, "fs_read_file");
878/// ```
879#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
880pub struct ToolCallPart {
881    /// Unique identifier for this tool call, used to correlate with [`ToolResultPart::call_id`].
882    pub id: ToolCallId,
883    /// The name of the tool to invoke (e.g. `"fs_read_file"`, `"shell_exec"`).
884    pub name: String,
885    /// The JSON arguments to pass to the tool.
886    pub input: Value,
887    /// Arbitrary key-value metadata.
888    pub metadata: MetadataMap,
889}
890
891impl ToolCallPart {
892    /// Builds a tool-call part with empty metadata.
893    pub fn new(id: impl Into<ToolCallId>, name: impl Into<String>, input: Value) -> Self {
894        Self {
895            id: id.into(),
896            name: name.into(),
897            input,
898            metadata: MetadataMap::new(),
899        }
900    }
901
902    /// Replaces the tool-call metadata.
903    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
904        self.metadata = metadata;
905        self
906    }
907}
908
909/// The result of executing a tool, sent back to the model.
910///
911/// Each `ToolResultPart` references the [`ToolCallPart`] it answers via
912/// `call_id`. The `is_error` flag tells the model whether the tool succeeded.
913///
914/// # Example
915///
916/// ```rust
917/// use agentkit_core::{ToolOutput, ToolResultPart};
918///
919/// let result = ToolResultPart::success("call-7", ToolOutput::text("fn main() { ... }"));
920///
921/// assert!(!result.is_error);
922/// ```
923#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
924pub struct ToolResultPart {
925    /// The [`ToolCallId`] of the tool call this result answers.
926    pub call_id: ToolCallId,
927    /// The output produced by the tool.
928    pub output: ToolOutput,
929    /// `true` if the tool execution failed.
930    pub is_error: bool,
931    /// Arbitrary key-value metadata.
932    pub metadata: MetadataMap,
933}
934
935impl ToolResultPart {
936    /// Builds a successful tool-result part with empty metadata.
937    pub fn success(call_id: impl Into<ToolCallId>, output: ToolOutput) -> Self {
938        Self {
939            call_id: call_id.into(),
940            output,
941            is_error: false,
942            metadata: MetadataMap::new(),
943        }
944    }
945
946    /// Builds an error tool-result part with empty metadata.
947    pub fn error(call_id: impl Into<ToolCallId>, output: ToolOutput) -> Self {
948        Self::success(call_id, output).with_is_error(true)
949    }
950
951    /// Sets the error flag explicitly.
952    pub fn with_is_error(mut self, is_error: bool) -> Self {
953        self.is_error = is_error;
954        self
955    }
956
957    /// Replaces the tool-result metadata.
958    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
959        self.metadata = metadata;
960        self
961    }
962}
963
964/// The payload returned by a tool execution.
965///
966/// Tools may return plain text, structured JSON, a composite list of
967/// [`Part`]s, or a collection of files.
968#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
969pub enum ToolOutput {
970    /// Plain text output.
971    Text(String),
972    /// Structured JSON output.
973    Structured(Value),
974    /// A list of content parts (e.g. text + images).
975    Parts(Vec<Part>),
976    /// A list of file attachments.
977    Files(Vec<FilePart>),
978}
979
980impl ToolOutput {
981    /// Builds plain-text tool output.
982    pub fn text(text: impl Into<String>) -> Self {
983        Self::Text(text.into())
984    }
985
986    /// Builds structured tool output.
987    pub fn structured(value: Value) -> Self {
988        Self::Structured(value)
989    }
990
991    /// Builds multipart tool output.
992    pub fn parts(parts: Vec<Part>) -> Self {
993        Self::Parts(parts)
994    }
995
996    /// Builds file-based tool output.
997    pub fn files(files: Vec<FilePart>) -> Self {
998        Self::Files(files)
999    }
1000}
1001
1002/// A provider-specific content part that does not fit the standard variants.
1003///
1004/// Use this for extensions or experimental features that have not been
1005/// promoted to a first-class [`Part`] variant.
1006#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1007pub struct CustomPart {
1008    /// A free-form string identifying the custom part type.
1009    pub kind: String,
1010    /// Optional data reference.
1011    pub data: Option<DataRef>,
1012    /// Optional structured value.
1013    pub value: Option<Value>,
1014    /// Arbitrary key-value metadata.
1015    pub metadata: MetadataMap,
1016}
1017
1018impl CustomPart {
1019    /// Builds a custom part with empty metadata.
1020    pub fn new(kind: impl Into<String>) -> Self {
1021        Self {
1022            kind: kind.into(),
1023            data: None,
1024            value: None,
1025            metadata: MetadataMap::new(),
1026        }
1027    }
1028
1029    /// Sets the custom part data reference.
1030    pub fn with_data(mut self, data: DataRef) -> Self {
1031        self.data = Some(data);
1032        self
1033    }
1034
1035    /// Sets the custom part structured value.
1036    pub fn with_value(mut self, value: Value) -> Self {
1037        self.value = Some(value);
1038        self
1039    }
1040
1041    /// Replaces the custom-part metadata.
1042    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
1043        self.metadata = metadata;
1044        self
1045    }
1046}
1047
1048/// An incremental update emitted while a model turn is streaming.
1049///
1050/// The provider adapter emits a sequence of `Delta` values that the loop and
1051/// reporters consume to reconstruct the full [`Item`] progressively. A typical
1052/// sequence looks like:
1053///
1054/// 1. [`BeginPart`](Self::BeginPart) -- allocates a new part buffer.
1055/// 2. One or more [`AppendText`](Self::AppendText) /
1056///    [`AppendBytes`](Self::AppendBytes) -- fills the buffer.
1057/// 3. [`CommitPart`](Self::CommitPart) -- finalises the part.
1058///
1059/// # Example
1060///
1061/// ```rust
1062/// use agentkit_core::{Delta, PartId, PartKind};
1063///
1064/// let deltas = vec![
1065///     Delta::BeginPart { part_id: PartId::new("p1"), kind: PartKind::Text },
1066///     Delta::AppendText { part_id: PartId::new("p1"), chunk: "Hello".into() },
1067///     Delta::AppendText { part_id: PartId::new("p1"), chunk: ", world!".into() },
1068/// ];
1069///
1070/// assert_eq!(deltas.len(), 3);
1071/// ```
1072#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1073pub enum Delta {
1074    /// Signals the start of a new part with the given kind.
1075    BeginPart {
1076        /// Identifier for the part being constructed.
1077        part_id: PartId,
1078        /// The kind of part being started.
1079        kind: PartKind,
1080    },
1081    /// Appends a text chunk to an in-progress part.
1082    AppendText {
1083        /// Identifier of the target part.
1084        part_id: PartId,
1085        /// The text chunk to append.
1086        chunk: String,
1087    },
1088    /// Appends raw bytes to an in-progress part.
1089    AppendBytes {
1090        /// Identifier of the target part.
1091        part_id: PartId,
1092        /// The byte chunk to append.
1093        chunk: Vec<u8>,
1094    },
1095    /// Replaces the structured value of an in-progress part wholesale.
1096    ReplaceStructured {
1097        /// Identifier of the target part.
1098        part_id: PartId,
1099        /// The new structured value.
1100        value: Value,
1101    },
1102    /// Sets or replaces the metadata on an in-progress part.
1103    SetMetadata {
1104        /// Identifier of the target part.
1105        part_id: PartId,
1106        /// The new metadata map.
1107        metadata: MetadataMap,
1108    },
1109    /// Finalises a part, providing the fully assembled [`Part`].
1110    CommitPart {
1111        /// The completed part.
1112        part: Part,
1113    },
1114}
1115
1116/// Token and cost usage reported by a model provider for a single turn.
1117///
1118/// Reporters and compaction triggers inspect `Usage` to log progress, enforce
1119/// budgets, and decide when to compact the transcript.
1120///
1121/// # Example
1122///
1123/// ```rust
1124/// use agentkit_core::{TokenUsage, Usage};
1125///
1126/// let usage = Usage::new(
1127///     TokenUsage::new(1500, 200)
1128///         .with_cached_input_tokens(1000)
1129///         .with_cache_write_input_tokens(1200),
1130/// );
1131///
1132/// let tokens = usage.tokens.as_ref().unwrap();
1133/// assert_eq!(tokens.input_tokens, 1500);
1134/// ```
1135#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1136pub struct Usage {
1137    /// Token counts for this turn, if the provider reports them.
1138    pub tokens: Option<TokenUsage>,
1139    /// Monetary cost for this turn, if the provider reports it.
1140    pub cost: Option<CostUsage>,
1141    /// Arbitrary key-value metadata.
1142    pub metadata: MetadataMap,
1143}
1144
1145impl Usage {
1146    /// Builds a usage record with token counts and no cost.
1147    pub fn new(tokens: TokenUsage) -> Self {
1148        Self {
1149            tokens: Some(tokens),
1150            cost: None,
1151            metadata: MetadataMap::new(),
1152        }
1153    }
1154
1155    /// Sets the cost information.
1156    pub fn with_cost(mut self, cost: CostUsage) -> Self {
1157        self.cost = Some(cost);
1158        self
1159    }
1160
1161    /// Replaces the usage metadata.
1162    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
1163        self.metadata = metadata;
1164        self
1165    }
1166}
1167
1168/// Token counts broken down by direction and special categories.
1169#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1170pub struct TokenUsage {
1171    /// Number of tokens in the input (prompt) sent to the model.
1172    pub input_tokens: u64,
1173    /// Number of tokens in the model's output (completion).
1174    pub output_tokens: u64,
1175    /// Tokens consumed by the model's internal reasoning, if reported.
1176    pub reasoning_tokens: Option<u64>,
1177    /// Input tokens served from the provider's prompt cache, if reported.
1178    pub cached_input_tokens: Option<u64>,
1179    /// Input tokens written into the provider's prompt cache, if reported.
1180    pub cache_write_input_tokens: Option<u64>,
1181}
1182
1183impl TokenUsage {
1184    /// Builds token usage with required input and output counts.
1185    pub fn new(input_tokens: u64, output_tokens: u64) -> Self {
1186        Self {
1187            input_tokens,
1188            output_tokens,
1189            reasoning_tokens: None,
1190            cached_input_tokens: None,
1191            cache_write_input_tokens: None,
1192        }
1193    }
1194
1195    /// Sets reasoning token count.
1196    pub fn with_reasoning_tokens(mut self, reasoning_tokens: u64) -> Self {
1197        self.reasoning_tokens = Some(reasoning_tokens);
1198        self
1199    }
1200
1201    /// Sets cached input token count.
1202    pub fn with_cached_input_tokens(mut self, cached_input_tokens: u64) -> Self {
1203        self.cached_input_tokens = Some(cached_input_tokens);
1204        self
1205    }
1206
1207    /// Sets cache-write input token count.
1208    pub fn with_cache_write_input_tokens(mut self, cache_write_input_tokens: u64) -> Self {
1209        self.cache_write_input_tokens = Some(cache_write_input_tokens);
1210        self
1211    }
1212}
1213
1214/// Monetary cost for a single model turn.
1215#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1216pub struct CostUsage {
1217    /// The cost amount as a floating-point number.
1218    pub amount: f64,
1219    /// The ISO 4217 currency code (e.g. `"USD"`).
1220    pub currency: String,
1221    /// An optional provider-specific cost string for display purposes.
1222    pub provider_amount: Option<String>,
1223}
1224
1225impl CostUsage {
1226    /// Builds cost usage with no provider-specific display string.
1227    pub fn new(amount: f64, currency: impl Into<String>) -> Self {
1228        Self {
1229            amount,
1230            currency: currency.into(),
1231            provider_amount: None,
1232        }
1233    }
1234
1235    /// Sets the optional provider-specific display value.
1236    pub fn with_provider_amount(mut self, provider_amount: impl Into<String>) -> Self {
1237        self.provider_amount = Some(provider_amount.into());
1238        self
1239    }
1240}
1241
1242/// The reason a model turn ended.
1243///
1244/// The loop inspects the `FinishReason` to decide whether to execute tool
1245/// calls, request more input, or report an error.
1246#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1247pub enum FinishReason {
1248    /// The model finished generating its response normally.
1249    Completed,
1250    /// The model stopped to invoke one or more tools.
1251    ToolCall,
1252    /// The response was truncated because the token limit was reached.
1253    MaxTokens,
1254    /// The turn was cancelled via [`TurnCancellation`].
1255    Cancelled,
1256    /// The provider blocked the response (e.g. content policy violation).
1257    Blocked,
1258    /// An error occurred during generation.
1259    Error,
1260    /// A provider-specific reason not covered by the standard variants.
1261    Other(String),
1262}
1263
1264/// Error returned when content cannot be normalised into the agentkit data model.
1265#[derive(Debug, Error)]
1266pub enum NormalizeError {
1267    /// The content shape is not supported by the current provider adapter.
1268    #[error("unsupported content shape: {0}")]
1269    Unsupported(String),
1270}
1271
1272/// Error indicating an invalid state in the provider protocol.
1273#[derive(Debug, Error)]
1274pub enum ProtocolError {
1275    /// The provider or loop reached a state that violates protocol invariants.
1276    #[error("invalid protocol state: {0}")]
1277    InvalidState(String),
1278}
1279
1280/// Top-level error type that unifies normalisation and protocol errors.
1281///
1282/// Provider adapters and the agent loop surface this type so callers can
1283/// handle both categories uniformly.
1284#[derive(Debug, Error)]
1285pub enum AgentError {
1286    /// A content normalisation error.
1287    #[error(transparent)]
1288    Normalize(#[from] NormalizeError),
1289    /// A protocol-level error.
1290    #[error(transparent)]
1291    Protocol(#[from] ProtocolError),
1292}