mirror of
https://github.com/block/buzz.git
synced 2026-10-29 13:31:44 +00:00
chore(buzz-agent): bump goose pin to bf332b9 (unrolled loop) and fix rmcp 3.x breaks
Moves the goose git pin from 305849b to bf332b9, which is past ca52cce (#9574, the unrolled agent loop). Consequences of the bump: - goose now uses rmcp 3.x; buzz-agent's own rmcp dep moves 1 -> 3 so the two do not resolve to different crate versions of the same types. - rmcp 3 renames Content -> ContentBlock and adds fields to ListToolsResult; builtin_client.rs updated to match. - buzz-model-catalog needs an explicit dirs dep after the merge. - TurnTotalState / PricingIdentity, added on main while this branch was away, ported back into types.rs for wire.rs's usage_update_payload. cargo check -p buzz-agent passes. Co-authored-by: Michael Neale <michael.neale@gmail.com> Signed-off-by: Michael Neale <michael.neale@gmail.com>
This commit is contained in:
1192
Cargo.lock
generated
1192
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -29,8 +29,8 @@ path = "tests/bin/fake_mcp.rs"
|
||||
# agent loop itself is goose's; this is the interactive PKCE login that seeds
|
||||
# the token cache that goose (and the desktop model picker) then read.
|
||||
buzz-model-catalog = { path = "../buzz-model-catalog" }
|
||||
goose = { git = "https://github.com/aaif-goose/goose", rev = "305849b71709b95b86ed9f11bd3bc939899c0aab", default-features = false, features = ["rustls-tls"] }
|
||||
goose-provider-types = { git = "https://github.com/aaif-goose/goose", rev = "305849b71709b95b86ed9f11bd3bc939899c0aab" }
|
||||
goose = { git = "https://github.com/aaif-goose/goose", rev = "bf332b983756eaf80353e82e4bb052d589629bf4", default-features = false, features = ["rustls-tls"] }
|
||||
goose-provider-types = { git = "https://github.com/aaif-goose/goose", rev = "bf332b983756eaf80353e82e4bb052d589629bf4" }
|
||||
# Resolution guard: goose pins icu_locale "=2.1.1" (needs icu_collections
|
||||
# ~2.1.1) while url 2.5.x -> idna -> idna_adapter 1.2.2 pulls icu_normalizer
|
||||
# 2.2.0 (needs icu_collections ~2.2.0). Only one 2.x icu_collections can be
|
||||
@@ -46,7 +46,7 @@ serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_yaml = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["json", "rustls", "form"] }
|
||||
rmcp = { version = "1", default-features = false, features = ["client", "transport-child-process"] }
|
||||
rmcp = { version = "3", default-features = false, features = ["client", "transport-child-process"] }
|
||||
arc-swap = "1"
|
||||
getrandom = "0.4"
|
||||
tracing = { workspace = true }
|
||||
|
||||
@@ -25,7 +25,9 @@ use std::sync::Arc;
|
||||
use async_trait::async_trait;
|
||||
use goose::agents::mcp_client::McpClientTrait;
|
||||
use goose::agents::{ExtensionConfig, ToolCallContext};
|
||||
use rmcp::model::{CallToolResult, Content, InitializeResult, JsonObject, ListToolsResult, Tool};
|
||||
use rmcp::model::{
|
||||
CallToolResult, ContentBlock, InitializeResult, JsonObject, ListToolsResult, Tool,
|
||||
};
|
||||
use rmcp::service::ServiceError as Error;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
@@ -79,6 +81,7 @@ impl McpClientTrait for BuiltinClient {
|
||||
tools: vec![Self::load_skill_tool()],
|
||||
next_cursor: None,
|
||||
meta: None,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -90,7 +93,7 @@ impl McpClientTrait for BuiltinClient {
|
||||
_cancel_token: CancellationToken,
|
||||
) -> Result<CallToolResult, Error> {
|
||||
if name != crate::builtin::LOAD_SKILL_TOOL {
|
||||
return Ok(CallToolResult::error(vec![Content::text(format!(
|
||||
return Ok(CallToolResult::error(vec![ContentBlock::text(format!(
|
||||
"unknown tool: {name}"
|
||||
))]));
|
||||
}
|
||||
@@ -100,10 +103,10 @@ impl McpClientTrait for BuiltinClient {
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
let result = crate::builtin::call_load_skill(&args, &self.skills).await;
|
||||
|
||||
let content: Vec<Content> = result
|
||||
let content: Vec<ContentBlock> = result
|
||||
.content
|
||||
.iter()
|
||||
.map(|c| Content::text(c.as_text_lossy()))
|
||||
.map(|c| ContentBlock::text(c.as_text_lossy()))
|
||||
.collect();
|
||||
|
||||
Ok(if result.is_error {
|
||||
|
||||
@@ -163,3 +163,105 @@ pub fn truncate_at_boundary(s: &str, max: usize) -> &str {
|
||||
}
|
||||
&s[..cut]
|
||||
}
|
||||
|
||||
/// Publisher-side billing identity for a turn.
|
||||
///
|
||||
/// Mirrors `buzz_core::agent_turn_metric::PricingIdentity` but is local to
|
||||
/// `buzz-agent` (which does not depend on `buzz-core`). The two structs have
|
||||
/// the same camelCase wire representation and are deserialized identically by
|
||||
/// `buzz-acp`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PricingIdentity {
|
||||
/// Registered billing-namespace identifier (bare lowercase hostname).
|
||||
pub authority: String,
|
||||
/// Actually-requested billable model identifier.
|
||||
pub model: String,
|
||||
/// Cache-write class when applicable; omitted otherwise.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cache_class: Option<String>,
|
||||
}
|
||||
|
||||
/// Tri-state accumulator for provider-reported total tokens within one ACP turn.
|
||||
///
|
||||
/// - `Unseen`: no usage-bearing response observed yet (initial state for each turn).
|
||||
/// - `Exact(n)`: every response so far reported a total; `n` is their sum.
|
||||
/// - `Unknown`: at least one response lacked a total — permanently poisoned for
|
||||
/// this turn. The session-cumulative also transitions to Unknown when any turn
|
||||
/// lands Unknown, and stays there until a new session resets it.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum TurnTotalState {
|
||||
#[default]
|
||||
Unseen,
|
||||
Exact(u64),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl TurnTotalState {
|
||||
/// Add two exact token counts with overflow protection.
|
||||
///
|
||||
/// Returns `Exact(acc + n)` on success or `Unknown` on overflow.
|
||||
/// This is the single implementation of the checked-add / overflow-poisons
|
||||
/// contract; both `fold()` and `merge_session()` call this helper so a
|
||||
/// change to overflow semantics needs to be made in exactly one place.
|
||||
fn checked_exact_sum(acc: u64, n: u64) -> TurnTotalState {
|
||||
match acc.checked_add(n) {
|
||||
Some(sum) => TurnTotalState::Exact(sum),
|
||||
None => TurnTotalState::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
/// Fold one provider-reported total into the current state.
|
||||
///
|
||||
/// `total`: `Some(n)` when the provider included a genuine total on this
|
||||
/// response; `None` when it was absent (e.g. Anthropic, or an OpenAI
|
||||
/// response that omits usage). Absence of a total on any usage-bearing
|
||||
/// response poisons the whole turn.
|
||||
///
|
||||
/// Overflow is handled by `checked_exact_sum`: a saturated value would
|
||||
/// not be a genuine provider-reported total, so overflow → `Unknown`.
|
||||
pub fn fold(self, total: Option<u64>) -> TurnTotalState {
|
||||
match (self, total) {
|
||||
// Already poisoned — stays Unknown regardless.
|
||||
(TurnTotalState::Unknown, _) => TurnTotalState::Unknown,
|
||||
// No total from this response — poison the accumulator.
|
||||
(_, None) => TurnTotalState::Unknown,
|
||||
// First response with a total.
|
||||
(TurnTotalState::Unseen, Some(n)) => TurnTotalState::Exact(n),
|
||||
// Subsequent response — delegate to the shared checked-sum helper.
|
||||
(TurnTotalState::Exact(acc), Some(n)) => Self::checked_exact_sum(acc, n),
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge a completed turn's total state into the session-cumulative state.
|
||||
///
|
||||
/// This is the turn→session boundary accumulation:
|
||||
/// - An `Unseen` turn (no usage-bearing responses) leaves the cumulative unchanged.
|
||||
/// - Any `Unknown` side poisons the session permanently.
|
||||
/// - Two `Exact` values are summed via `checked_exact_sum`; overflow → `Unknown`.
|
||||
///
|
||||
/// The checked-add logic lives in `checked_exact_sum`; both this function and
|
||||
/// `fold()` call that helper so overflow semantics are defined once.
|
||||
pub fn merge_session(self, turn: TurnTotalState) -> TurnTotalState {
|
||||
match (self, turn) {
|
||||
// Either side poisoned → session is poisoned.
|
||||
(TurnTotalState::Unknown, _) | (_, TurnTotalState::Unknown) => TurnTotalState::Unknown,
|
||||
// Turn had no usage-bearing responses → no change to cumulative.
|
||||
(acc, TurnTotalState::Unseen) => acc,
|
||||
// First exact turn — adopt its value.
|
||||
(TurnTotalState::Unseen, TurnTotalState::Exact(n)) => TurnTotalState::Exact(n),
|
||||
// Add to running exact sum — delegate to the shared checked-sum helper.
|
||||
(TurnTotalState::Exact(acc), TurnTotalState::Exact(n)) => {
|
||||
Self::checked_exact_sum(acc, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume the exact value if present; `None` for `Unseen` or `Unknown`.
|
||||
pub fn exact_value(self) -> Option<u64> {
|
||||
match self {
|
||||
TurnTotalState::Exact(n) => Some(n),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ urlencoding = "2"
|
||||
webbrowser = "1"
|
||||
hex = { workspace = true }
|
||||
getrandom = "0.4"
|
||||
dirs = "6"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
||||
Reference in New Issue
Block a user