use crate::http_util::ProviderHttp;
use crate::registry;
use aigw::anthropic::translate::{AnthropicRequestTranslator, AnthropicResponseTranslator};
use aigw::anthropic::{AuthMode, Transport, TransportConfig};
use aigw_core::translate::{RequestTranslator as _, ResponseTranslator as _};
use async_trait::async_trait;
use byokey_auth::AuthManager;
use byokey_types::{
ChatRequest, ProviderId, RateLimitStore,
traits::{ByteStream, ProviderExecutor, ProviderResponse, Result},
};
use rquest::Client;
use secrecy::SecretString;
use std::sync::Arc;
const DEFAULT_BASE_URL: &str = "https://api.kiro.dev";
const ANTHROPIC_VERSION: &str = "2023-06-01";
pub struct KiroExecutor {
ph: ProviderHttp,
api_key: Option<String>,
base_url: String,
auth: Arc<AuthManager>,
}
#[bon::bon]
impl KiroExecutor {
#[builder]
#[allow(clippy::needless_pass_by_value)]
pub fn new(
http: Client,
auth: Arc<AuthManager>,
api_key: Option<String>,
base_url: Option<String>,
ratelimit: Option<Arc<RateLimitStore>>,
) -> Self {
let mut ph = ProviderHttp::new(http);
if let Some(store) = ratelimit {
ph = ph.with_ratelimit(store, ProviderId::Kiro);
}
let base_url = base_url
.as_deref()
.unwrap_or(DEFAULT_BASE_URL)
.trim_end_matches('/')
.to_owned();
Self {
ph,
api_key,
base_url,
auth,
}
}
async fn bearer_token(&self) -> Result<String> {
crate::http_util::resolve_bearer_token(
self.api_key.as_deref(),
&self.auth,
&ProviderId::Kiro,
)
.await
}
fn build_transport(&self, token: String) -> Result<Transport> {
Transport::new(TransportConfig {
api_key: SecretString::from(token),
auth_mode: AuthMode::Bearer,
base_url: self.base_url.clone(),
version: ANTHROPIC_VERSION.to_owned(),
beta: None,
extra_headers: reqwest::header::HeaderMap::new(),
..Default::default()
})
.map_err(|e| byokey_types::ByokError::Config(e.to_string()))
}
}
#[async_trait]
impl ProviderExecutor for KiroExecutor {
async fn chat_completion(&self, request: ChatRequest) -> Result<ProviderResponse> {
let stream = request.stream;
let token = self.bearer_token().await?;
let transport = self.build_transport(token)?;
let translator = AnthropicRequestTranslator::new(&transport, None);
let aigw_request: aigw_core::model::ChatRequest =
serde_json::from_value(request.into_body())
.map_err(|e| byokey_types::ByokError::Translation(e.to_string()))?;
let translated = translator
.translate_request(&aigw_request)
.map_err(|e| byokey_types::ByokError::Translation(e.to_string()))?;
let mut builder = self.ph.client().post(&translated.url);
for (name, value) in &translated.headers {
if let Ok(v) = value.to_str() {
builder = builder.header(name.as_str(), v);
}
}
let builder = builder
.header("accept-encoding", "identity")
.body(translated.body.to_vec());
let resp = self.ph.send(builder).await?;
if stream {
let byte_stream: ByteStream = ProviderHttp::byte_stream(resp);
Ok(ProviderResponse::Stream(
super::claude::translate_claude_sse(byte_stream),
))
} else {
let resp_bytes = resp.bytes().await.map_err(byokey_types::ByokError::from)?;
let aigw_response = AnthropicResponseTranslator
.translate_response(http::StatusCode::OK, &resp_bytes)
.map_err(|e| byokey_types::ByokError::Translation(e.to_string()))?;
let value = serde_json::to_value(aigw_response)
.map_err(|e| byokey_types::ByokError::Translation(e.to_string()))?;
Ok(ProviderResponse::Complete(value))
}
}
fn supported_models(&self) -> Vec<String> {
registry::models_for_provider(&ProviderId::Kiro)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_executor() -> KiroExecutor {
let (client, auth) = crate::http_util::test_auth();
KiroExecutor::builder().http(client).auth(auth).build()
}
#[test]
fn test_supported_models_non_empty() {
let ex = make_executor();
assert!(!ex.supported_models().is_empty());
}
}