use crate::canonical::{select_model, CanonicalError, CanonicalRequest, Event, Model, Provenance};
use crate::config::{fill_absent, lead_with_preamble, strip_unsupported, ResolvedConfig};
use crate::protocol::Protocol;
use crate::registry::Registry;
use crate::transport::TransportResponse;
use super::events::{is_2xx, response_events};
use super::Host;
pub fn generate(
request: CanonicalRequest,
config: ResolvedConfig,
host: &Host,
) -> impl Iterator<Item = Event> {
let stream: Box<dyn Iterator<Item = Event>> = match build_send(request, config, host) {
Ok((proto, resp, streamed, hint)) => response_events(proto, resp, streamed, hint),
Err(e) => Box::new(std::iter::once(Event::Error(e))),
};
stream.chain(std::iter::once(Event::End))
}
fn build_send(
mut request: CanonicalRequest,
mut config: ResolvedConfig,
host: &Host,
) -> Result<
(
&'static dyn Protocol,
TransportResponse,
bool,
Option<String>,
),
CanonicalError,
> {
let models = host.cache.get(&config.provider.name).unwrap_or_default();
let (wire_model, prov) = select_model(&models, &config.model, &config.provider.name)?;
config.model = wire_model;
config.model_from_cache = matches!(prov, Provenance::Cached);
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 streamed = request.stream.unwrap_or(true);
let mut wire = proto.encode(&request, &ctx)?;
wire.set_header("content-type", proto.content_type());
for (k, v) in ctx.beta_headers {
wire.set_header(k, v);
}
wire.timeouts = config.timeouts();
auth.apply(
&mut wire,
&ctx,
&authc,
host.store,
host.clock,
host.transport,
)?;
let resp = host.transport.send(wire)?;
if is_2xx(resp.status) && !config.model_from_cache {
let mut learned = models;
learned.push(Model {
id: config.model.clone(),
default: false,
});
host.cache.put(&config.provider.name, &learned);
}
let hint = (resp.status == 404).then(|| model_hint(&config.model, config.model_from_cache));
Ok((proto, resp, streamed, hint))
}
fn model_hint(model: &str, from_cache: bool) -> String {
if from_cache {
format!(
"`{model}` was in the cache but the provider rejected it; \
the cache may be stale — re-run `bz --list-models`"
)
} else {
format!(
"`{model}` is not in the model cache; \
run `bz --list-models` to refresh or enable partial matching"
)
}
}