gemini-cli-sdk 0.1.0

Rust SDK wrapping Google's Gemini CLI as a subprocess via JSON-RPC 2.0
Documentation
//! Convenience builder functions for test wire messages.
//!
//! Each function produces a `serde_json::Value` shaped exactly as the
//! production Gemini CLI would send it over the JSON-RPC 2.0 channel.
//! Use these together with [`ScenarioBuilder::exchange`] to set up test
//! scenarios without hand-writing JSON.
//!
//! [`ScenarioBuilder::exchange`]: super::ScenarioBuilder::exchange

use serde_json::{json, Value};

/// Create a `session/update` notification carrying an `agent_message_chunk`.
///
/// This is the primary message type for streaming assistant text responses.
///
/// # Example
///
/// ```rust
/// use gemini_cli_sdk::testing::assistant_text;
///
/// let msg = assistant_text("Hello!");
/// assert_eq!(msg["params"]["sessionUpdate"], "agent_message_chunk");
/// assert_eq!(msg["params"]["content"]["text"], "Hello!");
/// ```
pub fn assistant_text(text: &str) -> Value {
    json!({
        "jsonrpc": "2.0",
        "method": "session/update",
        "params": {
            "sessionId": "test-session",
            "sessionUpdate": "agent_message_chunk",
            "content": { "type": "text", "text": text }
        }
    })
}

/// Create a `session/update` notification carrying an `agent_thought_chunk`.
///
/// Thought chunks represent the model's internal reasoning, streamed before
/// the final answer is emitted.
///
/// # Example
///
/// ```rust
/// use gemini_cli_sdk::testing::thought_text;
///
/// let msg = thought_text("Let me think about this…");
/// assert_eq!(msg["params"]["sessionUpdate"], "agent_thought_chunk");
/// ```
pub fn thought_text(text: &str) -> Value {
    json!({
        "jsonrpc": "2.0",
        "method": "session/update",
        "params": {
            "sessionId": "test-session",
            "sessionUpdate": "agent_thought_chunk",
            "content": { "type": "text", "text": text }
        }
    })
}

/// Create a `session/update` notification representing a pending tool call.
///
/// - `id` — unique identifier for this tool invocation (used to correlate with
///   the corresponding [`tool_result`]).
/// - `title` — human-readable name shown in the UI.
/// - `kind` — machine-readable tool category (e.g. `"file_read"`,
///   `"shell_exec"`).
///
/// # Example
///
/// ```rust
/// use gemini_cli_sdk::testing::tool_call;
///
/// let msg = tool_call("tc-1", "Read file", "file_read");
/// assert_eq!(msg["params"]["status"], "pending");
/// assert_eq!(msg["params"]["toolCallId"], "tc-1");
/// ```
pub fn tool_call(id: &str, title: &str, kind: &str) -> Value {
    json!({
        "jsonrpc": "2.0",
        "method": "session/update",
        "params": {
            "sessionId": "test-session",
            "sessionUpdate": "tool_call",
            "toolCallId": id,
            "title": title,
            "kind": kind,
            "status": "pending"
        }
    })
}

/// Create a `session/update` notification representing a completed tool result.
///
/// - `id` — must match the `id` used in the corresponding [`tool_call`].
/// - `text` — the text content of the tool's output.
///
/// # Example
///
/// ```rust
/// use gemini_cli_sdk::testing::tool_result;
///
/// let msg = tool_result("tc-1", "file contents here");
/// assert_eq!(msg["params"]["status"], "completed");
/// assert_eq!(msg["params"]["content"][0]["text"], "file contents here");
/// ```
pub fn tool_result(id: &str, text: &str) -> Value {
    json!({
        "jsonrpc": "2.0",
        "method": "session/update",
        "params": {
            "sessionId": "test-session",
            "sessionUpdate": "tool_call_update",
            "toolCallId": id,
            "status": "completed",
            "content": [{ "type": "text", "text": text }]
        }
    })
}

/// Create a `session/update` notification carrying a structured plan.
///
/// `entries` is a slice of `(content, status)` pairs where `status` is
/// typically `"pending"`, `"in_progress"`, or `"done"`.
///
/// # Example
///
/// ```rust
/// use gemini_cli_sdk::testing::plan;
///
/// let msg = plan(vec![
///     ("Read the source file", "done"),
///     ("Analyse the code", "in_progress"),
///     ("Write the summary", "pending"),
/// ]);
/// assert_eq!(msg["params"]["sessionUpdate"], "plan");
/// assert_eq!(msg["params"]["entries"][0]["status"], "done");
/// ```
pub fn plan(entries: Vec<(&str, &str)>) -> Value {
    let entries: Vec<Value> = entries
        .into_iter()
        .map(|(content, status)| {
            json!({
                "content": content,
                "priority": "normal",
                "status": status,
            })
        })
        .collect();
    json!({
        "jsonrpc": "2.0",
        "method": "session/update",
        "params": {
            "sessionId": "test-session",
            "sessionUpdate": "plan",
            "entries": entries
        }
    })
}

/// Create a JSON-RPC success response representing the terminal result of a
/// `session/prompt` request.
///
/// `stop_reason` is typically `"end_turn"` or `"max_tokens"`.
///
/// # Example
///
/// ```rust
/// use gemini_cli_sdk::testing::prompt_result;
///
/// let msg = prompt_result("end_turn");
/// assert_eq!(msg["result"]["stopReason"], "end_turn");
/// assert!(msg["error"].is_null());
/// ```
pub fn prompt_result(stop_reason: &str) -> Value {
    json!({
        "jsonrpc": "2.0",
        "id": 1,
        "result": {
            "stopReason": stop_reason
        }
    })
}