Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ unstable = [
"unstable_session_list",
"unstable_session_model",
"unstable_session_resume",
"unstable_session_usage",
]
unstable_cancel_request = []
unstable_session_config_options = []
Expand All @@ -30,6 +31,7 @@ unstable_session_info_update = []
unstable_session_list = []
unstable_session_model = []
unstable_session_resume = []
unstable_session_usage = []

[[bin]]
name = "generate"
Expand Down
73 changes: 73 additions & 0 deletions src/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2118,6 +2118,10 @@ impl PromptRequest {
pub struct PromptResponse {
/// Indicates why the agent stopped processing the turn.
pub stop_reason: StopReason,
/// Token usage for this turn (optional).
#[cfg(feature = "unstable_session_usage")]
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<Usage>,
/// The _meta property is reserved by ACP to allow clients and agents to attach additional
/// metadata to their interactions. Implementations MUST NOT make assumptions about values at
/// these keys.
Expand All @@ -2132,10 +2136,20 @@ impl PromptResponse {
pub fn new(stop_reason: StopReason) -> Self {
Self {
stop_reason,
#[cfg(feature = "unstable_session_usage")]
usage: None,
meta: None,
}
}

/// Token usage for this turn.
#[cfg(feature = "unstable_session_usage")]
#[must_use]
pub fn usage(mut self, usage: impl IntoOption<Usage>) -> Self {
self.usage = usage.into_option();
self
}

/// The _meta property is reserved by ACP to allow clients and agents to attach additional
/// metadata to their interactions. Implementations MUST NOT make assumptions about values at
/// these keys.
Expand Down Expand Up @@ -2175,6 +2189,65 @@ pub enum StopReason {
Cancelled,
}

/// Token usage information for a prompt turn.
#[cfg(feature = "unstable_session_usage")]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct Usage {
/// Sum of all token types across session.
pub total_tokens: u64,
/// Total input tokens across all turns.
pub input_tokens: u64,
/// Total output tokens across all turns.
pub output_tokens: u64,
/// Total thought/reasoning tokens (for o1/o3 models).
#[serde(skip_serializing_if = "Option::is_none")]
pub thought_tokens: Option<u64>,
/// Total cache read tokens.
#[serde(skip_serializing_if = "Option::is_none")]
pub cached_read_tokens: Option<u64>,
/// Total cache write tokens.
#[serde(skip_serializing_if = "Option::is_none")]
pub cached_write_tokens: Option<u64>,
}

#[cfg(feature = "unstable_session_usage")]
impl Usage {
#[must_use]
pub fn new(total_tokens: u64, input_tokens: u64, output_tokens: u64) -> Self {
Self {
total_tokens,
input_tokens,
output_tokens,
thought_tokens: None,
cached_read_tokens: None,
cached_write_tokens: None,
}
}

/// Total thought/reasoning tokens (for o1/o3 models).
#[must_use]
pub fn thought_tokens(mut self, thought_tokens: impl IntoOption<u64>) -> Self {
self.thought_tokens = thought_tokens.into_option();
self
}

/// Total cache read tokens.
#[must_use]
pub fn cached_read_tokens(mut self, cached_read_tokens: impl IntoOption<u64>) -> Self {
self.cached_read_tokens = cached_read_tokens.into_option();
self
}

/// Total cache write tokens.
#[must_use]
pub fn cached_write_tokens(mut self, cached_write_tokens: impl IntoOption<u64>) -> Self {
self.cached_write_tokens = cached_write_tokens.into_option();
self
}
}

// Model

/// **UNSTABLE**
Expand Down
79 changes: 79 additions & 0 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ pub enum SessionUpdate {
#[cfg(feature = "unstable_session_info_update")]
/// Session metadata has been updated (title, timestamps, custom metadata)
SessionInfoUpdate(SessionInfoUpdate),
/// Context window and cost update for the session.
#[cfg(feature = "unstable_session_usage")]
UsageUpdate(UsageUpdate),
}

/// The current mode of the session has changed
Expand Down Expand Up @@ -245,6 +248,82 @@ impl SessionInfoUpdate {
}
}

/// Context window and cost update for a session.
#[cfg(feature = "unstable_session_usage")]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct UsageUpdate {
/// Tokens currently in context.
pub used: u64,
/// Total context window size in tokens.
pub size: u64,
/// Cumulative session cost (optional).
#[serde(skip_serializing_if = "Option::is_none")]
pub cost: Option<Cost>,
/// The _meta property is reserved by ACP to allow clients and agents to attach additional
/// metadata to their interactions. Implementations MUST NOT make assumptions about values at
/// these keys.
///
/// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}

#[cfg(feature = "unstable_session_usage")]
impl UsageUpdate {
#[must_use]
pub fn new(used: u64, size: u64) -> Self {
Self {
used,
size,
cost: None,
meta: None,
}
}

/// Cumulative session cost (optional).
#[must_use]
pub fn cost(mut self, cost: impl IntoOption<Cost>) -> Self {
self.cost = cost.into_option();
self
}

/// The _meta property is reserved by ACP to allow clients and agents to attach additional
/// metadata to their interactions. Implementations MUST NOT make assumptions about values at
/// these keys.
///
/// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}

/// Cost information for a session.
#[cfg(feature = "unstable_session_usage")]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct Cost {
/// Total cumulative cost for session.
pub amount: f64,
/// ISO 4217 currency code (e.g., "USD", "EUR").
pub currency: String,
}

#[cfg(feature = "unstable_session_usage")]
impl Cost {
#[must_use]
pub fn new(amount: f64, currency: impl Into<String>) -> Self {
Self {
amount,
currency: currency.into(),
}
}
}

/// A streamed item of content
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
Expand Down