use sim_citizen_derive::non_citizen;
use sim_kernel::{ContentId, Cx, Expr, Object, ObjectCompat, Result, Symbol};
pub const GATEWAY_REQUEST_OBJECT: &str = "openai-gateway/request";
pub const GATEWAY_RESPONSE_OBJECT: &str = "openai-gateway/response";
pub const GATEWAY_RUN_OBJECT: &str = "openai-gateway/run";
pub const GATEWAY_EVENT_OBJECT: &str = "openai-gateway/event";
#[non_citizen(
reason = "gateway request runtime shell; class-backed descriptor is openai/GatewayRequest",
kind = "marker"
)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GatewayRequest {
id: Option<String>,
timestamp_ms: Option<u64>,
method: String,
path: String,
headers: Vec<(String, String)>,
body: Vec<u8>,
}
impl GatewayRequest {
pub fn new(
method: impl Into<String>,
path: impl Into<String>,
headers: Vec<(String, String)>,
body: Vec<u8>,
) -> Self {
Self {
id: None,
timestamp_ms: None,
method: method.into(),
path: path.into(),
headers,
body,
}
}
pub fn get(path: impl Into<String>) -> Self {
Self::new("GET", path, Vec::new(), Vec::new())
}
pub fn with_metadata(mut self, id: impl Into<String>, timestamp_ms: u64) -> Self {
self.id = Some(id.into());
self.timestamp_ms = Some(timestamp_ms);
self
}
pub fn id(&self) -> Option<&str> {
self.id.as_deref()
}
pub fn timestamp_ms(&self) -> Option<u64> {
self.timestamp_ms
}
pub fn method(&self) -> &str {
&self.method
}
pub fn path(&self) -> &str {
&self.path
}
pub fn headers(&self) -> &[(String, String)] {
&self.headers
}
pub fn body(&self) -> &[u8] {
&self.body
}
pub fn to_expr(&self) -> Expr {
Expr::Map(vec![
field("object", Expr::String(GATEWAY_REQUEST_OBJECT.to_owned())),
optional_string_field("id", self.id.as_deref()),
optional_u64_field("timestamp-ms", self.timestamp_ms),
field("method", Expr::String(self.method.clone())),
field("path", Expr::String(self.path.clone())),
field("headers", headers_expr(&self.headers)),
field("body", Expr::Bytes(self.body.clone())),
])
}
}
impl Object for GatewayRequest {
fn display(&self, _cx: &mut Cx) -> Result<String> {
Ok(format!(
"#<openai-gateway-request {} {}>",
self.method, self.path
))
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
impl ObjectCompat for GatewayRequest {
fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
Ok(self.to_expr())
}
}
#[non_citizen(
reason = "gateway response runtime shell; class-backed descriptor is openai/GatewayResponse",
kind = "marker"
)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GatewayResponse {
status: u16,
headers: Vec<(String, String)>,
body: Vec<u8>,
}
impl GatewayResponse {
pub fn new(status: u16, headers: Vec<(String, String)>, body: Vec<u8>) -> Self {
Self {
status,
headers,
body,
}
}
pub fn json(status: u16, body: Vec<u8>) -> Self {
Self::new(
status,
vec![("Content-Type".to_owned(), "application/json".to_owned())],
body,
)
}
pub fn text(status: u16, body: impl Into<Vec<u8>>) -> Self {
Self::new(
status,
vec![("Content-Type".to_owned(), "text/plain".to_owned())],
body.into(),
)
}
pub fn sse(status: u16, body: impl Into<Vec<u8>>) -> Self {
Self::new(
status,
vec![("Content-Type".to_owned(), "text/event-stream".to_owned())],
body.into(),
)
}
pub fn status(&self) -> u16 {
self.status
}
pub fn headers(&self) -> &[(String, String)] {
&self.headers
}
pub fn body(&self) -> &[u8] {
&self.body
}
pub fn header(&self, name: &str) -> Option<&str> {
self.headers
.iter()
.find(|(key, _)| key.eq_ignore_ascii_case(name))
.map(|(_, value)| value.as_str())
}
pub fn to_expr(&self) -> Expr {
Expr::Map(vec![
field("object", Expr::String(GATEWAY_RESPONSE_OBJECT.to_owned())),
field("status", Expr::String(self.status.to_string())),
field("headers", headers_expr(&self.headers)),
field("body", Expr::Bytes(self.body.clone())),
])
}
}
impl Object for GatewayResponse {
fn display(&self, _cx: &mut Cx) -> Result<String> {
Ok(format!("#<openai-gateway-response {}>", self.status))
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
impl ObjectCompat for GatewayResponse {
fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
Ok(self.to_expr())
}
}
#[non_citizen(
reason = "gateway run runtime shell; class-backed descriptor is openai/GatewayRun",
kind = "marker"
)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GatewayRun {
id: String,
request_content_id: ContentId,
status: Symbol,
created_at_ms: u64,
}
impl GatewayRun {
pub fn new(id: impl Into<String>, request_content_id: ContentId, created_at_ms: u64) -> Self {
Self {
id: id.into(),
request_content_id,
status: Symbol::new("created"),
created_at_ms,
}
}
pub fn with_status(mut self, status: impl Into<Symbol>) -> Self {
self.status = status.into();
self
}
pub fn id(&self) -> &str {
&self.id
}
pub fn request_content_id(&self) -> &ContentId {
&self.request_content_id
}
pub fn status(&self) -> &Symbol {
&self.status
}
pub fn created_at_ms(&self) -> u64 {
self.created_at_ms
}
pub fn to_expr(&self) -> Expr {
Expr::Map(vec![
field("object", Expr::String(GATEWAY_RUN_OBJECT.to_owned())),
field("id", Expr::String(self.id.clone())),
field(
"request-content-id",
content_id_expr(&self.request_content_id),
),
field("status", Expr::Symbol(self.status.clone())),
field(
"created-at-ms",
Expr::String(self.created_at_ms.to_string()),
),
])
}
}
impl Object for GatewayRun {
fn display(&self, _cx: &mut Cx) -> Result<String> {
Ok(format!("#<openai-gateway-run {}>", self.id))
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
impl ObjectCompat for GatewayRun {
fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
Ok(self.to_expr())
}
}
#[non_citizen(
reason = "gateway event runtime shell; class-backed descriptor is openai/GatewayEvent",
kind = "marker"
)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GatewayEvent {
id: String,
run_id: String,
sequence: u64,
kind: Symbol,
payload: Expr,
created_at_ms: u64,
}
impl GatewayEvent {
pub fn new(
id: impl Into<String>,
run_id: impl Into<String>,
sequence: u64,
kind: impl Into<Symbol>,
payload: Expr,
created_at_ms: u64,
) -> Self {
Self {
id: id.into(),
run_id: run_id.into(),
sequence,
kind: kind.into(),
payload,
created_at_ms,
}
}
pub fn id(&self) -> &str {
&self.id
}
pub fn run_id(&self) -> &str {
&self.run_id
}
pub fn sequence(&self) -> u64 {
self.sequence
}
pub fn kind(&self) -> &Symbol {
&self.kind
}
pub fn payload(&self) -> &Expr {
&self.payload
}
pub fn created_at_ms(&self) -> u64 {
self.created_at_ms
}
pub fn to_expr(&self) -> Expr {
Expr::Map(vec![
field("object", Expr::String(GATEWAY_EVENT_OBJECT.to_owned())),
field("id", Expr::String(self.id.clone())),
field("run-id", Expr::String(self.run_id.clone())),
field("sequence", Expr::String(self.sequence.to_string())),
field("event-kind", Expr::Symbol(self.kind.clone())),
field("payload", self.payload.clone()),
field(
"created-at-ms",
Expr::String(self.created_at_ms.to_string()),
),
])
}
}
impl Object for GatewayEvent {
fn display(&self, _cx: &mut Cx) -> Result<String> {
Ok(format!(
"#<openai-gateway-event {} {}>",
self.run_id, self.sequence
))
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
impl ObjectCompat for GatewayEvent {
fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
Ok(self.to_expr())
}
}
#[derive(Clone)]
#[non_citizen(
reason = "runtime response wrapper; serializable projection is openai/GatewayResponse descriptor",
kind = "handle"
)]
pub struct GatewayResponseValue {
response: GatewayResponse,
}
impl GatewayResponseValue {
pub fn new(response: GatewayResponse) -> Self {
Self { response }
}
pub fn response(&self) -> &GatewayResponse {
&self.response
}
}
impl Object for GatewayResponseValue {
fn display(&self, cx: &mut Cx) -> Result<String> {
self.response.display(cx)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
impl ObjectCompat for GatewayResponseValue {
fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
Ok(self.response.to_expr())
}
}
pub fn content_id_expr(id: &ContentId) -> Expr {
Expr::Map(vec![
field("algorithm", Expr::Symbol(id.algorithm.clone())),
field("bytes", Expr::Bytes(id.bytes.to_vec())),
field("hex", Expr::String(bytes_to_hex(&id.bytes))),
])
}
pub fn content_id_hex(id: &ContentId) -> String {
bytes_to_hex(&id.bytes)
}
fn headers_expr(headers: &[(String, String)]) -> Expr {
let mut sorted = headers.to_vec();
sorted.sort_by_key(|(name, value)| (name.to_ascii_lowercase(), value.clone()));
Expr::List(
sorted
.into_iter()
.map(|(name, value)| {
Expr::Map(vec![
field("name", Expr::String(name)),
field("value", Expr::String(value)),
])
})
.collect(),
)
}
fn optional_string_field(name: &str, value: Option<&str>) -> (Expr, Expr) {
field(
name,
value
.map(|value| Expr::String(value.to_owned()))
.unwrap_or(Expr::Nil),
)
}
fn optional_u64_field(name: &str, value: Option<u64>) -> (Expr, Expr) {
field(
name,
value
.map(|value| Expr::String(value.to_string()))
.unwrap_or(Expr::Nil),
)
}
use sim_value::build::entry as field;
pub(crate) fn bytes_to_hex(bytes: &[u8]) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut output = String::with_capacity(bytes.len() * 2);
for byte in bytes {
output.push(char::from(HEX[usize::from(byte >> 4)]));
output.push(char::from(HEX[usize::from(byte & 0x0f)]));
}
output
}