sim-lib-openai-server 0.1.2

OpenAI-compatible gateway skeleton for SIM.
Documentation
use sim_kernel::{ContentId, Expr, Symbol};

use crate::{
    clock::GatewayClock,
    content_id::content_id_for_expr,
    objects::GatewayEvent,
    routes::{errors::OpenAiRouteError, responses::ResponseIdGenerators},
    storage::GatewayStore,
};

type RouteResult<T> = std::result::Result<T, OpenAiRouteError>;

pub(super) struct EventInput {
    sequence: u64,
    kind: Symbol,
    payload: Expr,
}

impl EventInput {
    pub(super) fn new(sequence: u64, kind: &'static str, payload: Expr) -> Self {
        Self::from_symbol(sequence, Symbol::new(kind), payload)
    }

    pub(super) fn from_symbol(sequence: u64, kind: Symbol, payload: Expr) -> Self {
        Self {
            sequence,
            kind,
            payload,
        }
    }
}

#[derive(Default)]
pub(super) struct EventLog {
    pub(super) content_ids: Vec<ContentId>,
    pub(super) events: Vec<GatewayEvent>,
}

pub(super) fn append_event<S, C>(
    store: &mut S,
    ids: &mut ResponseIdGenerators,
    clock: &mut C,
    run_id: &str,
    input: EventInput,
    store_event: bool,
    event_log: &mut EventLog,
) -> RouteResult<()>
where
    S: GatewayStore,
    C: GatewayClock,
{
    let event = GatewayEvent::new(
        ids.next_event_id().map_err(OpenAiRouteError::internal)?,
        run_id,
        input.sequence,
        input.kind,
        input.payload,
        clock.now_ms().map_err(OpenAiRouteError::internal)?,
    );
    let id = content_id_for_expr(&event.to_expr()).map_err(OpenAiRouteError::internal)?;
    if store_event {
        store
            .put_event(id.clone(), event.clone())
            .map_err(OpenAiRouteError::internal)?;
    }
    event_log.content_ids.push(id);
    event_log.events.push(event);
    Ok(())
}

pub(super) fn response_usage_expr(response: &Expr) -> Expr {
    response_field(response, "usage")
        .cloned()
        .unwrap_or(Expr::Nil)
}

fn response_field<'a>(expr: &'a Expr, name: &str) -> Option<&'a Expr> {
    let Expr::Map(entries) = expr else {
        return None;
    };
    entries.iter().find_map(|(key, value)| match key {
        Expr::Symbol(symbol) if symbol.namespace.is_none() && symbol.name.as_ref() == name => {
            Some(value)
        }
        _ => None,
    })
}