use crate::canonical::{select_model, CanonicalError, CanonicalRequest, ErrorKind};
use crate::config::{fill_absent, lead_with_preamble, strip_unsupported, ResolvedConfig};
use crate::protocol::{count_from_body, http_error};
use crate::registry::Registry;
use crate::run::{drain, events::is_2xx};
use super::CountIo;
pub(super) fn fetch_count(
mut request: CanonicalRequest,
mut config: ResolvedConfig,
io: &mut CountIo,
) -> Result<u32, CanonicalError> {
let cached = io.cache.get(&config.provider.name).unwrap_or_default();
let (wire_model, _prov) = select_model(&cached, &config.model, &config.provider.name)?;
config.model = wire_model;
let registry = Registry::builtin();
let proto = registry.protocol(config.provider.protocol);
let auth = registry.auth(config.provider.auth);
let beta: Vec<(&str, &str)> = config
.provider
.beta_headers
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect();
let ctx = config.provider_ctx(&beta);
let authc = config.auth_ctx();
fill_absent(&mut request, &config);
lead_with_preamble(&mut request, &config);
strip_unsupported(&mut request, &config);
let count = match proto.count_tokens(&request, &ctx) {
None => return Err(no_count_endpoint(&config.provider.name)),
Some(r) => r?,
};
let mut wire = count.wire;
wire.set_header("content-type", proto.content_type());
for (k, v) in ctx.beta_headers {
wire.set_header(k, v);
}
config.stamp_transport(&mut wire);
auth.apply(&mut wire, &ctx, &authc, io.store, io.clock, io.transport)?;
let resp = io.transport.send(wire)?;
let status = resp.status;
if !is_2xx(status) {
let body = drain(resp.body).unwrap_or_default();
return Err(http_error(&body, status));
}
let body = drain(resp.body).map_err(read_failed)?;
count_from_body(&body, count.token_key)
}
fn no_count_endpoint(provider: &str) -> CanonicalError {
CanonicalError {
kind: ErrorKind::Config,
message: format!(
"provider `{provider}` has no token-count endpoint; \
`--count-tokens` declines rather than fabricate a count — use your own estimate"
),
provider_detail: None,
retry_after_seconds: None,
}
}
fn read_failed(e: std::io::Error) -> CanonicalError {
CanonicalError {
kind: ErrorKind::Transport,
message: format!("failed to read token-count response body: {e}"),
provider_detail: None,
retry_after_seconds: None,
}
}