supercode-runtime 0.4.16

Optional native model and tool runtime for Supercode
Documentation
//! Provider-neutral request types for the native runtime.

use serde::Serialize;
use supercode_interchange::ChatMessage;

/// A tool advertised to a model.
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct ToolSchema {
    /// Tool name.
    pub name: String,
    /// Description the model uses to decide when to call it.
    pub description: String,
    /// JSON Schema for the tool's input object.
    pub parameters: serde_json::Value,
}

impl ToolSchema {
    /// Construct a tool schema.
    pub fn new(
        name: impl Into<String>,
        description: impl Into<String>,
        parameters: serde_json::Value,
    ) -> Self {
        Self {
            name: name.into(),
            description: description.into(),
            parameters,
        }
    }
}

/// A single model-completion request.
#[derive(Debug, Clone, PartialEq)]
pub struct ChatRequest {
    /// Model id.
    pub model: String,
    /// Full conversation so far.
    pub messages: Vec<ChatMessage>,
    /// Tools to advertise (may be empty).
    pub tools: Vec<ToolSchema>,
    /// Optional sampling temperature.
    pub temperature: Option<f32>,
    /// Optional output token cap.
    pub max_tokens: Option<u32>,
    /// Reasoning/effort level, such as `"low"` or `"high"`.
    pub effort: Option<String>,
    /// Structured-output constraint sent as `response_format`.
    pub response_format: Option<serde_json::Value>,
    /// BP-13 (catalog D9 "Fast mode / service tiers"): the provider service
    /// tier this request asks for (`"auto"`, `"priority"`, `"flex"`, …).
    /// Sent verbatim as the OpenAI-compatible `service_tier` field; `None`
    /// omits it, which is what every pre-BP-13 caller produced.
    pub service_tier: Option<String>,
    /// BP-13 (catalog D9 "Reasoning effort / thinking budgets"): a cap on
    /// reasoning/thinking TOKENS for this request — Claude Code's
    /// `MAX_THINKING_TOKENS`, the budget half of `effort`'s level half.
    /// Sent as the unified `reasoning.max_tokens` field; `None` omits it.
    pub thinking_budget: Option<u32>,
    /// Arbitrary provider-native request fields.
    pub extra_body: serde_json::Map<String, serde_json::Value>,
}

impl ChatRequest {
    /// Construct a minimal request with a model and conversation.
    pub fn new(model: impl Into<String>, messages: Vec<ChatMessage>) -> Self {
        Self {
            model: model.into(),
            messages,
            tools: Vec::new(),
            temperature: None,
            max_tokens: None,
            effort: None,
            response_format: None,
            service_tier: None,
            thinking_budget: None,
            extra_body: serde_json::Map::new(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn minimal_request_has_no_optional_runtime_controls() {
        let request = ChatRequest::new("example/model", vec![ChatMessage::user("hello")]);
        assert_eq!(request.model, "example/model");
        assert_eq!(request.messages.len(), 1);
        assert!(request.tools.is_empty());
        assert_eq!(request.temperature, None);
        assert_eq!(request.max_tokens, None);
        assert!(request.extra_body.is_empty());
    }

    #[test]
    fn schema_constructor_preserves_provider_json() {
        let parameters = serde_json::json!({"type": "object"});
        let schema = ToolSchema::new("read", "Read a file", parameters.clone());
        assert_eq!(schema.name, "read");
        assert_eq!(schema.parameters, parameters);
    }
}