lash-provider-openai 0.1.0-alpha.113

OpenAI providers for lash: API-key (OpenRouter, OpenAI, vLLM, etc.) and Codex OAuth (ChatGPT Plus/Pro/Team).
Documentation
use crate::responses_shared as shared;
use crate::support::*;

const PROVIDER: &str = "OpenAI-compatible";

impl OpenAiCompatibleProvider {
    pub(crate) fn build_responses_request_body(
        &self,
        req: &LlmRequest,
        stream: bool,
    ) -> Result<Value, LlmTransportError> {
        shared::validate_responses_attachments(req, "OpenAI Responses")?;
        let compat = self.resolved_compat(CompletionEndpoint::Responses);
        let tools = shared::build_tools_with_capabilities(
            PROVIDER,
            req,
            compat.strict_tools,
            &compat.schema_capabilities,
        )?;
        let (instructions, input) =
            shared::build_responses_input(req, shared::ResponsesInputOptions::OPENAI);
        let policy = resolve_generation_policy(
            &req.generation,
            &self.options,
            DEFAULT_MAX_OUTPUT_TOKENS,
            (),
        );
        let mut body = json!({
            "model": req.model,
            "instructions": instructions,
            "input": input,
            "tools": tools,
            "stream": stream,
        });
        apply_max_tokens_field(&mut body, compat.max_tokens_field, policy.max_output_tokens);
        if !req.tools.is_empty() {
            body["tool_choice"] = json!(shared::tool_choice_value(&req.tool_choice));
        }
        if compat.request_fields {
            body["include"] = json!(["reasoning.encrypted_content"]);
            body["parallel_tool_calls"] = json!(!req.tools.is_empty());
            body["text"] = json!({"verbosity": "medium"});
        }
        if compat.store {
            body["store"] = json!(false);
        }
        if let Some(intent) = reasoning_intent(req) {
            compat
                .reasoning_format
                .encode(CompletionEndpoint::Responses, &intent, &mut body)
                .map_err(|error| {
                    reasoning_encode_transport_error(CompletionEndpoint::Responses, &intent, error)
                })?;
        }
        if policy.expose_thinking && body["reasoning"].is_object() {
            body["reasoning"]["summary"] = json!("auto");
        }
        if let Some(output_spec) = &req.output_spec {
            let format = match output_spec {
                LlmOutputSpec::JsonObject => json!({ "type": "json_object" }),
                LlmOutputSpec::JsonSchema(schema) => {
                    let projected = shared::projected_schema(
                        PROVIDER,
                        &schema.schema,
                        &compat.schema_capabilities,
                        SchemaPurpose::StructuredOutput,
                    )?;
                    json!({
                        "type": "json_schema",
                        "name": schema.name,
                        "schema": projected,
                        "strict": schema.strict,
                    })
                }
            };
            if body.get("text").is_none() {
                body["text"] = json!({});
            }
            body["text"]["format"] = format;
        }
        if policy.cache_retention != CacheRetention::None && compat.prompt_cache_key {
            body["prompt_cache_key"] = json!(req.continuation_key());
        }
        if policy.cache_retention == CacheRetention::Long && compat.prompt_cache_retention {
            body["prompt_cache_retention"] = json!("24h");
        }
        Ok(body)
    }

    pub(crate) fn response_parts_from_value(value: &Value) -> Vec<LlmOutputPart> {
        shared::response_parts_from_value(value)
    }

    pub(crate) fn process_sse_event(
        raw: &str,
        state: &mut ResponsesStreamState,
        emitted_parts: Option<&mut Vec<LlmOutputPart>>,
    ) -> Result<(), LlmTransportError> {
        shared::process_sse_event(PROVIDER, raw, state, emitted_parts)
    }

    pub(crate) fn parse_sse_payload(
        payload: &str,
        state: &mut ResponsesStreamState,
    ) -> Result<(), LlmTransportError> {
        shared::parse_sse_payload(PROVIDER, payload, state)
    }
}