vtcode-llm 0.175.5

LLM provider abstraction, client implementations, and streaming for VT Code
Documentation
//! Direct OpenAI API-key Decisions transport. Never discovers another credential.

use super::super::backend_setup::OpenAIBackendKind;
use super::OpenAIProvider;
use crate::provider::{ChoiceDecisionRequest, ChoiceDecisionResponse, LLMError, is_standard_decisions_endpoint};

impl OpenAIProvider {
    pub(super) fn decisions_eligible(&self) -> bool {
        self.provider_key_override.is_none()
            && self.custom_provider_auth.is_none()
            && self.api_format_override.is_none()
            && self.openai_chatgpt_auth.is_none()
            && matches!(self.backend_setup.kind(), OpenAIBackendKind::ApiKey)
            && !self.api_key.trim().is_empty()
            && is_standard_decisions_endpoint(&self.base_url)
    }

    pub(super) async fn decide_choice_request(
        &self,
        request: ChoiceDecisionRequest,
    ) -> Result<ChoiceDecisionResponse, LLMError> {
        if !self.decisions_eligible() {
            return Err(decisions_error("Decisions requires a direct OpenAI API-key session at the standard endpoint"));
        }
        send_choice(
            self.decisions_client
                .as_ref()
                .map_err(|_build_error| decisions_error("Decisions client creation failed"))?,
            &format!("{}/decisions", self.base_url.trim_end_matches('/')),
            &self.api_key,
            request,
        )
        .await
    }
}

fn decisions_error(message: &str) -> LLMError {
    LLMError::Provider { message: message.to_owned(), metadata: None }
}

async fn send_choice(
    client: &reqwest::Client,
    url: &str,
    api_key: &str,
    request: ChoiceDecisionRequest,
) -> Result<ChoiceDecisionResponse, LLMError> {
    let payload = request.payload()?;
    let response = client
        .post(url)
        .bearer_auth(api_key)
        .json(&payload)
        .send()
        .await
        .map_err(|_transport_error| decisions_error("Decisions transport failed"))?;
    if !response.status().is_success() {
        // Provider bodies may echo the input. Keep only the HTTP status.
        tracing::debug!(status = response.status().as_u16(), "Decisions request failed");
        return Err(decisions_error(&format!("Decisions HTTP {}", response.status().as_u16())));
    }
    // A single choice answer is small. Bound success bodies as well as errors.
    const MAX_RESPONSE_BYTES: usize = 16 * 1024;
    let mut response = response;
    let mut body = Vec::new();
    while let Some(chunk) = response
        .chunk()
        .await
        .map_err(|_read_error| decisions_error("Decisions response read failed"))?
    {
        if chunk.len() > MAX_RESPONSE_BYTES.saturating_sub(body.len()) {
            return Err(decisions_error("Decisions response exceeds the size limit"));
        }
        body.extend_from_slice(&chunk);
    }
    let payload =
        serde_json::from_slice(&body).map_err(|_parse_error| decisions_error("Malformed Decisions response"))?;
    Ok(ChoiceDecisionResponse::from_payload(&payload, &request))
}

#[cfg(test)]
mod tests;