1use sim_citizen_derive::non_citizen;
2use sim_kernel::{ContentId, Cx, Expr, Object, ObjectCompat, Result, Symbol};
3use sim_lib_net_core::hex_encode;
4
5pub const GATEWAY_REQUEST_OBJECT: &str = "openai-gateway/request";
7pub const GATEWAY_RESPONSE_OBJECT: &str = "openai-gateway/response";
9pub const GATEWAY_RUN_OBJECT: &str = "openai-gateway/run";
11pub const GATEWAY_EVENT_OBJECT: &str = "openai-gateway/event";
13
14#[non_citizen(
19 reason = "gateway request runtime shell; class-backed descriptor is openai/GatewayRequest",
20 kind = "marker",
21 descriptor = "openai/GatewayRequest"
22)]
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct GatewayRequest {
25 id: Option<String>,
26 timestamp_ms: Option<u64>,
27 method: String,
28 path: String,
29 headers: Vec<(String, String)>,
30 body: Vec<u8>,
31}
32
33impl GatewayRequest {
34 pub fn new(
37 method: impl Into<String>,
38 path: impl Into<String>,
39 headers: Vec<(String, String)>,
40 body: Vec<u8>,
41 ) -> Self {
42 Self {
43 id: None,
44 timestamp_ms: None,
45 method: method.into(),
46 path: path.into(),
47 headers,
48 body,
49 }
50 }
51
52 pub fn get(path: impl Into<String>) -> Self {
54 Self::new("GET", path, Vec::new(), Vec::new())
55 }
56
57 pub fn with_metadata(mut self, id: impl Into<String>, timestamp_ms: u64) -> Self {
60 self.id = Some(id.into());
61 self.timestamp_ms = Some(timestamp_ms);
62 self
63 }
64
65 pub fn id(&self) -> Option<&str> {
67 self.id.as_deref()
68 }
69
70 pub fn timestamp_ms(&self) -> Option<u64> {
72 self.timestamp_ms
73 }
74
75 pub fn method(&self) -> &str {
77 &self.method
78 }
79
80 pub fn path(&self) -> &str {
82 &self.path
83 }
84
85 pub fn headers(&self) -> &[(String, String)] {
87 &self.headers
88 }
89
90 pub fn body(&self) -> &[u8] {
92 &self.body
93 }
94
95 pub fn to_expr(&self) -> Expr {
97 Expr::Map(vec![
98 field("object", Expr::String(GATEWAY_REQUEST_OBJECT.to_owned())),
99 optional_string_field("id", self.id.as_deref()),
100 optional_u64_field("timestamp-ms", self.timestamp_ms),
101 field("method", Expr::String(self.method.clone())),
102 field("path", Expr::String(self.path.clone())),
103 field("headers", headers_expr(&self.headers)),
104 field("body", Expr::Bytes(self.body.clone())),
105 ])
106 }
107}
108
109impl Object for GatewayRequest {
110 fn display(&self, _cx: &mut Cx) -> Result<String> {
111 Ok(format!(
112 "#<openai-gateway-request {} {}>",
113 self.method, self.path
114 ))
115 }
116
117 fn as_any(&self) -> &dyn std::any::Any {
118 self
119 }
120}
121
122impl ObjectCompat for GatewayRequest {
123 fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
124 Ok(self.to_expr())
125 }
126}
127
128#[non_citizen(
134 reason = "gateway response runtime shell; class-backed descriptor is openai/GatewayResponse",
135 kind = "marker",
136 descriptor = "openai/GatewayResponse"
137)]
138#[derive(Clone, Debug, PartialEq, Eq)]
139pub struct GatewayResponse {
140 status: u16,
141 headers: Vec<(String, String)>,
142 body: Vec<u8>,
143}
144
145impl GatewayResponse {
146 pub fn new(status: u16, headers: Vec<(String, String)>, body: Vec<u8>) -> Self {
148 Self {
149 status,
150 headers,
151 body,
152 }
153 }
154
155 pub fn json(status: u16, body: Vec<u8>) -> Self {
157 Self::new(
158 status,
159 vec![("Content-Type".to_owned(), "application/json".to_owned())],
160 body,
161 )
162 }
163
164 pub fn text(status: u16, body: impl Into<Vec<u8>>) -> Self {
166 Self::new(
167 status,
168 vec![("Content-Type".to_owned(), "text/plain".to_owned())],
169 body.into(),
170 )
171 }
172
173 pub fn sse(status: u16, body: impl Into<Vec<u8>>) -> Self {
175 Self::new(
176 status,
177 vec![("Content-Type".to_owned(), "text/event-stream".to_owned())],
178 body.into(),
179 )
180 }
181
182 pub fn status(&self) -> u16 {
184 self.status
185 }
186
187 pub fn headers(&self) -> &[(String, String)] {
189 &self.headers
190 }
191
192 pub fn body(&self) -> &[u8] {
194 &self.body
195 }
196
197 pub fn header(&self, name: &str) -> Option<&str> {
200 self.headers
201 .iter()
202 .find(|(key, _)| key.eq_ignore_ascii_case(name))
203 .map(|(_, value)| value.as_str())
204 }
205
206 pub fn to_expr(&self) -> Expr {
208 Expr::Map(vec![
209 field("object", Expr::String(GATEWAY_RESPONSE_OBJECT.to_owned())),
210 field("status", Expr::String(self.status.to_string())),
211 field("headers", headers_expr(&self.headers)),
212 field("body", Expr::Bytes(self.body.clone())),
213 ])
214 }
215}
216
217impl Object for GatewayResponse {
218 fn display(&self, _cx: &mut Cx) -> Result<String> {
219 Ok(format!("#<openai-gateway-response {}>", self.status))
220 }
221
222 fn as_any(&self) -> &dyn std::any::Any {
223 self
224 }
225}
226
227impl ObjectCompat for GatewayResponse {
228 fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
229 Ok(self.to_expr())
230 }
231}
232
233#[non_citizen(
239 reason = "gateway run runtime shell; class-backed descriptor is openai/GatewayRun",
240 kind = "marker",
241 descriptor = "openai/GatewayRun"
242)]
243#[derive(Clone, Debug, PartialEq, Eq)]
244pub struct GatewayRun {
245 id: String,
246 request_content_id: ContentId,
247 status: Symbol,
248 created_at_ms: u64,
249}
250
251impl GatewayRun {
252 pub fn new(id: impl Into<String>, request_content_id: ContentId, created_at_ms: u64) -> Self {
255 Self {
256 id: id.into(),
257 request_content_id,
258 status: Symbol::new("created"),
259 created_at_ms,
260 }
261 }
262
263 pub fn with_status(mut self, status: impl Into<Symbol>) -> Self {
265 self.status = status.into();
266 self
267 }
268
269 pub fn id(&self) -> &str {
271 &self.id
272 }
273
274 pub fn request_content_id(&self) -> &ContentId {
276 &self.request_content_id
277 }
278
279 pub fn status(&self) -> &Symbol {
281 &self.status
282 }
283
284 pub fn created_at_ms(&self) -> u64 {
286 self.created_at_ms
287 }
288
289 pub fn to_expr(&self) -> Expr {
291 Expr::Map(vec![
292 field("object", Expr::String(GATEWAY_RUN_OBJECT.to_owned())),
293 field("id", Expr::String(self.id.clone())),
294 field(
295 "request-content-id",
296 content_id_expr(&self.request_content_id),
297 ),
298 field("status", Expr::Symbol(self.status.clone())),
299 field(
300 "created-at-ms",
301 Expr::String(self.created_at_ms.to_string()),
302 ),
303 ])
304 }
305}
306
307impl Object for GatewayRun {
308 fn display(&self, _cx: &mut Cx) -> Result<String> {
309 Ok(format!("#<openai-gateway-run {}>", self.id))
310 }
311
312 fn as_any(&self) -> &dyn std::any::Any {
313 self
314 }
315}
316
317impl ObjectCompat for GatewayRun {
318 fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
319 Ok(self.to_expr())
320 }
321}
322
323#[non_citizen(
329 reason = "gateway event runtime shell; class-backed descriptor is openai/GatewayEvent",
330 kind = "marker",
331 descriptor = "openai/GatewayEvent"
332)]
333#[derive(Clone, Debug, PartialEq, Eq)]
334pub struct GatewayEvent {
335 id: String,
336 run_id: String,
337 sequence: u64,
338 kind: Symbol,
339 payload: Expr,
340 created_at_ms: u64,
341}
342
343impl GatewayEvent {
344 pub fn new(
347 id: impl Into<String>,
348 run_id: impl Into<String>,
349 sequence: u64,
350 kind: impl Into<Symbol>,
351 payload: Expr,
352 created_at_ms: u64,
353 ) -> Self {
354 Self {
355 id: id.into(),
356 run_id: run_id.into(),
357 sequence,
358 kind: kind.into(),
359 payload,
360 created_at_ms,
361 }
362 }
363
364 pub fn id(&self) -> &str {
366 &self.id
367 }
368
369 pub fn run_id(&self) -> &str {
371 &self.run_id
372 }
373
374 pub fn sequence(&self) -> u64 {
376 self.sequence
377 }
378
379 pub fn kind(&self) -> &Symbol {
381 &self.kind
382 }
383
384 pub fn payload(&self) -> &Expr {
386 &self.payload
387 }
388
389 pub fn created_at_ms(&self) -> u64 {
391 self.created_at_ms
392 }
393
394 pub fn to_expr(&self) -> Expr {
396 Expr::Map(vec![
397 field("object", Expr::String(GATEWAY_EVENT_OBJECT.to_owned())),
398 field("id", Expr::String(self.id.clone())),
399 field("run-id", Expr::String(self.run_id.clone())),
400 field("sequence", Expr::String(self.sequence.to_string())),
401 field("event-kind", Expr::Symbol(self.kind.clone())),
402 field("payload", self.payload.clone()),
403 field(
404 "created-at-ms",
405 Expr::String(self.created_at_ms.to_string()),
406 ),
407 ])
408 }
409}
410
411impl Object for GatewayEvent {
412 fn display(&self, _cx: &mut Cx) -> Result<String> {
413 Ok(format!(
414 "#<openai-gateway-event {} {}>",
415 self.run_id, self.sequence
416 ))
417 }
418
419 fn as_any(&self) -> &dyn std::any::Any {
420 self
421 }
422}
423
424impl ObjectCompat for GatewayEvent {
425 fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
426 Ok(self.to_expr())
427 }
428}
429
430#[derive(Clone)]
435#[non_citizen(
436 reason = "runtime response wrapper; serializable projection is openai/GatewayResponse descriptor",
437 kind = "handle",
438 descriptor = "openai/GatewayResponse"
439)]
440pub struct GatewayResponseValue {
441 response: GatewayResponse,
442}
443
444impl GatewayResponseValue {
445 pub fn new(response: GatewayResponse) -> Self {
447 Self { response }
448 }
449
450 pub fn response(&self) -> &GatewayResponse {
452 &self.response
453 }
454}
455
456impl Object for GatewayResponseValue {
457 fn display(&self, cx: &mut Cx) -> Result<String> {
458 self.response.display(cx)
459 }
460
461 fn as_any(&self) -> &dyn std::any::Any {
462 self
463 }
464}
465
466impl ObjectCompat for GatewayResponseValue {
467 fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
468 Ok(self.response.to_expr())
469 }
470}
471
472pub fn content_id_expr(id: &ContentId) -> Expr {
475 Expr::Map(vec![
476 field("algorithm", Expr::Symbol(id.algorithm.clone())),
477 field("bytes", Expr::Bytes(id.bytes.to_vec())),
478 field("hex", Expr::String(hex_encode(&id.bytes))),
479 ])
480}
481
482pub fn content_id_hex(id: &ContentId) -> String {
484 hex_encode(&id.bytes)
485}
486
487fn headers_expr(headers: &[(String, String)]) -> Expr {
488 let mut sorted = headers.to_vec();
489 sorted.sort_by_key(|(name, value)| (name.to_ascii_lowercase(), value.clone()));
490 Expr::List(
491 sorted
492 .into_iter()
493 .map(|(name, value)| {
494 Expr::Map(vec![
495 field("name", Expr::String(name)),
496 field("value", Expr::String(value)),
497 ])
498 })
499 .collect(),
500 )
501}
502
503fn optional_string_field(name: &str, value: Option<&str>) -> (Expr, Expr) {
504 field(
505 name,
506 value
507 .map(|value| Expr::String(value.to_owned()))
508 .unwrap_or(Expr::Nil),
509 )
510}
511
512fn optional_u64_field(name: &str, value: Option<u64>) -> (Expr, Expr) {
513 field(
514 name,
515 value
516 .map(|value| Expr::String(value.to_string()))
517 .unwrap_or(Expr::Nil),
518 )
519}
520
521use sim_value::build::entry as field;