Skip to main content

gemini_cli/
lib.rs

1#![forbid(unsafe_code)]
2//! Async helper around the official Gemini CLI headless `--output-format stream-json` surface.
3//!
4//! The public event types follow the documented stream-json contract, while preserving raw JSON
5//! payloads and tolerating unknown future event kinds.
6
7use std::{
8    future::Future,
9    pin::Pin,
10    sync::{
11        atomic::{AtomicBool, Ordering},
12        Arc,
13    },
14};
15
16use futures_core::Stream;
17use tokio::sync::Notify;
18
19mod builder;
20mod client;
21mod error;
22mod stream_json;
23
24pub use builder::GeminiCliClientBuilder;
25pub use client::GeminiCliClient;
26pub use error::GeminiCliError;
27pub use stream_json::{
28    parse_stream_json_lines, GeminiStreamJsonError, GeminiStreamJsonErrorCode,
29    GeminiStreamJsonEvent, GeminiStreamJsonLine, GeminiStreamJsonLineOutcome,
30    GeminiStreamJsonParser, GeminiStreamJsonResultPayload, GeminiStreamJsonRunRequest,
31    GeminiToolResultError,
32};
33
34pub type DynGeminiStreamJsonEventStream =
35    Pin<Box<dyn Stream<Item = Result<GeminiStreamJsonEvent, GeminiStreamJsonError>> + Send>>;
36
37pub type DynGeminiStreamJsonCompletion =
38    Pin<Box<dyn Future<Output = Result<GeminiStreamJsonCompletion, GeminiCliError>> + Send>>;
39
40#[derive(Clone)]
41pub struct GeminiTerminationHandle {
42    inner: Arc<GeminiTerminationInner>,
43}
44
45#[derive(Debug)]
46struct GeminiTerminationInner {
47    requested: AtomicBool,
48    notify: Notify,
49}
50
51impl GeminiTerminationHandle {
52    fn new() -> Self {
53        Self {
54            inner: Arc::new(GeminiTerminationInner {
55                requested: AtomicBool::new(false),
56                notify: Notify::new(),
57            }),
58        }
59    }
60
61    pub fn request_termination(&self) {
62        if !self.inner.requested.swap(true, Ordering::SeqCst) {
63            self.inner.notify.notify_waiters();
64        }
65    }
66
67    fn is_requested(&self) -> bool {
68        self.inner.requested.load(Ordering::SeqCst)
69    }
70
71    async fn requested(&self) {
72        if self.is_requested() {
73            return;
74        }
75
76        let notified = self.inner.notify.notified();
77        if self.is_requested() {
78            return;
79        }
80
81        notified.await;
82    }
83}
84
85impl std::fmt::Debug for GeminiTerminationHandle {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        f.debug_struct("GeminiTerminationHandle")
88            .field("requested", &self.is_requested())
89            .finish()
90    }
91}
92
93#[derive(Debug, Clone)]
94pub struct GeminiStreamJsonCompletion {
95    pub status: std::process::ExitStatus,
96    pub final_text: Option<String>,
97    pub session_id: Option<String>,
98    pub model: Option<String>,
99    pub raw_result: Option<serde_json::Value>,
100}
101
102pub struct GeminiStreamJsonHandle {
103    pub events: DynGeminiStreamJsonEventStream,
104    pub completion: DynGeminiStreamJsonCompletion,
105}
106
107impl std::fmt::Debug for GeminiStreamJsonHandle {
108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109        f.debug_struct("GeminiStreamJsonHandle")
110            .field("events", &"<stream>")
111            .field("completion", &"<future>")
112            .finish()
113    }
114}
115
116pub struct GeminiStreamJsonControlHandle {
117    pub events: DynGeminiStreamJsonEventStream,
118    pub completion: DynGeminiStreamJsonCompletion,
119    pub termination: GeminiTerminationHandle,
120}
121
122impl std::fmt::Debug for GeminiStreamJsonControlHandle {
123    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124        f.debug_struct("GeminiStreamJsonControlHandle")
125            .field("events", &"<stream>")
126            .field("completion", &"<future>")
127            .field("termination", &self.termination)
128            .finish()
129    }
130}