use crate::PluginError;
use crate::plugin_ctx::{HttpRequest, HttpResponse, PluginContext};
#[cfg(feature = "model")]
use aiway_protocol::model::Provider;
use std::any::Any;
#[link(wasm_import_module = "aiway")]
unsafe extern "C" {
fn host_request_id(buf_ptr: *mut u8, buf_len: i32) -> i32;
fn host_request_ts() -> i64;
fn host_is_sse() -> i32;
fn host_is_websocket() -> i32;
fn host_get_route_name(buf_ptr: *mut u8, buf_len: i32) -> i32;
fn host_get_routing_url(buf_ptr: *mut u8, buf_len: i32) -> i32;
fn host_get_response_body_size() -> i64;
fn host_set_response_body_size(size: i64);
fn host_log(level: i32, msg_ptr: *const u8, msg_len: i32);
#[cfg(feature = "model")]
fn host_get_model_name(buf_ptr: *mut u8, buf_len: i32) -> i32;
#[cfg(feature = "model")]
fn host_get_model_provider(buf_ptr: *mut u8, buf_len: i32) -> i32;
fn host_http_request(
req_ptr: *const u8,
req_len: i32,
resp_buf_ptr: *mut u8,
resp_buf_len: i32,
) -> i32;
}
pub struct WasmHttpContext;
fn read_host_string(
f: unsafe extern "C" fn(*mut u8, i32) -> i32,
initial_len: i32,
) -> Option<String> {
let mut buf = vec![0u8; initial_len as usize];
let needed = unsafe { f(buf.as_mut_ptr(), initial_len) };
if needed <= 0 {
return None;
}
let needed = needed as usize;
if needed > buf.len() {
buf.resize(needed, 0);
let len = unsafe { f(buf.as_mut_ptr(), needed as i32) };
if len <= 0 {
return None;
}
return Some(String::from_utf8_lossy(&buf[..len as usize]).to_string());
}
Some(String::from_utf8_lossy(&buf[..needed]).to_string())
}
#[cfg(feature = "model")]
fn read_host_bincode<T: serde::de::DeserializeOwned>(
f: unsafe extern "C" fn(*mut u8, i32) -> i32,
initial_len: i32,
) -> Option<T> {
let mut buf = vec![0u8; initial_len as usize];
let needed = unsafe { f(buf.as_mut_ptr(), initial_len) };
if needed <= 0 {
return None;
}
let needed = needed as usize;
if needed > buf.len() {
buf.resize(needed, 0);
let len = unsafe { f(buf.as_mut_ptr(), needed as i32) };
if len <= 0 {
return None;
}
return bincode::deserialize(&buf[..len as usize]).ok();
}
bincode::deserialize(&buf[..needed]).ok()
}
impl PluginContext for WasmHttpContext {
fn request_id(&self) -> String {
read_host_string(host_request_id, 64).unwrap_or_default()
}
fn request_ts(&self) -> i64 {
unsafe { host_request_ts() }
}
fn is_sse(&self) -> bool {
unsafe { host_is_sse() != 0 }
}
fn is_websocket(&self) -> bool {
unsafe { host_is_websocket() != 0 }
}
fn get_route_name(&self) -> Option<String> {
read_host_string(host_get_route_name, 256)
}
fn get_routing_url(&self) -> Option<String> {
read_host_string(host_get_routing_url, 512)
}
fn get_response_body_size(&self) -> Option<i64> {
let v = unsafe { host_get_response_body_size() };
if v < 0 { None } else { Some(v) }
}
fn set_response_body_size(&mut self, size: i64) {
unsafe { host_set_response_body_size(size) }
}
#[cfg(feature = "model")]
fn get_model_name(&self) -> Option<String> {
read_host_string(host_get_model_name, 256)
}
#[cfg(feature = "model")]
fn get_model_provider(&self) -> Option<Provider> {
read_host_bincode(host_get_model_provider, 512)
}
fn log(&self, level: i32, msg: &str) {
let bytes = msg.as_bytes();
unsafe { host_log(level, bytes.as_ptr(), bytes.len() as i32) }
}
fn http_request(&self, req: &HttpRequest) -> Result<HttpResponse, PluginError> {
let req_bytes = bincode::serialize(req)
.map_err(|e| PluginError::HttpError(format!("serialize request failed: {e}")))?;
let mut buf = vec![0u8; 4096];
loop {
let needed = unsafe {
host_http_request(
req_bytes.as_ptr(),
req_bytes.len() as i32,
buf.as_mut_ptr(),
buf.len() as i32,
)
};
if needed < 0 {
return Err(PluginError::HttpError(format!(
"http_request failed with code {needed}"
)));
}
if needed == 0 {
return Err(PluginError::HttpError(
"http_request returned empty response".into(),
));
}
let needed = needed as usize;
if needed > buf.len() {
buf.resize(needed, 0);
continue;
}
return bincode::deserialize(&buf[..needed])
.map_err(|e| PluginError::HttpError(format!("deserialize response failed: {e}")));
}
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}