Packages
An Elixir SDK for the Gemini CLI - build AI-powered applications with Google Gemini.
Current section
Files
Jump to
Current section
Files
lib/gemini_cli_sdk.ex
defmodule GeminiCliSdk do
@moduledoc """
An Elixir SDK for the Gemini CLI.
Provides streaming and synchronous execution of Gemini CLI prompts,
session management, and typed event parsing.
## Streaming
GeminiCliSdk.execute("Explain OTP", %GeminiCliSdk.Options{model: GeminiCliSdk.Models.fast_model()})
|> Enum.each(fn event ->
case event do
%GeminiCliSdk.Types.MessageEvent{role: "assistant", content: text} ->
IO.write(text)
_ -> :ok
end
end)
## Synchronous
{:ok, response} = GeminiCliSdk.run("What is Elixir?")
## Session Management
{:ok, sessions} = GeminiCliSdk.list_sessions()
GeminiCliSdk.resume_session("latest")
|> Enum.each(&IO.inspect/1)
"""
alias GeminiCliSdk.{Error, Options, Types}
@type event :: Types.stream_event()
# --- Streaming execution ---
@doc """
Starts a Gemini CLI session and returns a lazy stream of typed events.
The stream is backed by `Stream.resource/3`. It spawns the `gemini` subprocess
with `--output-format stream-json` and `--prompt` carrying the prompt text, then
yields one event struct per JSONL line. The subprocess is killed and cleaned up
when the stream is halted, fully consumed, or the owning process dies.
"""
@spec execute(String.t(), Options.t()) :: Enumerable.t(event())
def execute(prompt, opts \\ %Options{}) do
opts = Options.validate!(opts)
GeminiCliSdk.Stream.execute(prompt, opts)
end
# --- Synchronous execution ---
@doc """
Executes a prompt and blocks until the CLI produces a final result.
Internally calls `execute/2` and reduces the stream, collecting assistant
message text. Returns `{:ok, response_text}` on success or
`{:error, %Error{}}` on any failure.
"""
@spec run(String.t(), Options.t()) :: {:ok, String.t()} | {:error, Error.t()}
def run(prompt, opts \\ %Options{}) do
prompt
|> execute(opts)
|> Enum.reduce({nil, ""}, fn
%Types.MessageEvent{role: "assistant", content: text}, {status, acc} ->
{status, acc <> (text || "")}
%Types.ResultEvent{status: "success"}, {_status, acc} ->
{:ok, acc}
%Types.ResultEvent{status: status, error: error}, {_status, _acc} ->
{:error,
Error.new(kind: :command_failed, message: error || "CLI returned status: #{status}")}
%Types.ErrorEvent{severity: "fatal"} = event, {_status, _acc} ->
{:error, error_from_event(event)}
_event, acc ->
acc
end)
|> case do
{:ok, text} -> {:ok, text}
{:error, _} = error -> error
{nil, _} -> {:error, Error.new(kind: :no_result, message: "No result received from stream")}
end
end
defp error_from_event(%Types.ErrorEvent{} = event) do
Error.new(
kind: normalize_error_kind(event.kind, event.message),
message: event.message,
details: event.stderr,
context:
%{}
|> maybe_put(:details, event.details)
|> maybe_put(:stderr_truncated?, event.stderr_truncated?),
exit_code: event.exit_code
)
end
defp normalize_error_kind(kind, message) when kind in [nil, :unknown],
do: infer_error_kind(message)
defp normalize_error_kind(kind, _message) when is_atom(kind), do: kind
defp normalize_error_kind(kind, message) when kind in ["", "unknown"],
do: infer_error_kind(message)
defp normalize_error_kind(kind, _message) when is_binary(kind) do
kind
|> String.downcase()
|> String.replace("-", "_")
|> String.to_atom()
end
defp normalize_error_kind(_kind, message), do: infer_error_kind(message)
defp infer_error_kind(message) when is_binary(message) do
if String.match?(message, ~r/auth/i), do: :auth_error, else: :command_failed
end
defp infer_error_kind(_message), do: :command_failed
defp maybe_put(map, _key, nil), do: map
defp maybe_put(map, key, value), do: Map.put(map, key, value)
# --- Session management ---
@doc """
Lists available sessions for the current project.
Runs `gemini --list-sessions` and returns the raw output.
"""
@spec list_sessions(keyword()) :: {:ok, String.t()} | {:error, Error.t()}
defdelegate list_sessions(opts \\ []), to: GeminiCliSdk.Session, as: :list
@spec list_session_entries(keyword()) ::
{:ok, [GeminiCliSdk.Session.Entry.t()]} | {:error, Error.t()}
defdelegate list_session_entries(opts \\ []), to: GeminiCliSdk.Session, as: :list_entries
@doc """
Resumes a previous session and returns a streaming event enumerable.
"""
@spec resume_session(String.t(), Options.t(), String.t() | nil) :: Enumerable.t(event())
defdelegate resume_session(session_id, opts \\ %Options{}, prompt \\ nil),
to: GeminiCliSdk.Session,
as: :resume
@doc """
Deletes a session by index or ID.
"""
@spec delete_session(String.t(), keyword()) :: {:ok, String.t()} | {:error, Error.t()}
defdelegate delete_session(identifier, opts \\ []), to: GeminiCliSdk.Session, as: :delete
# --- Utility ---
@doc """
Returns the installed Gemini CLI version string.
"""
@spec version() :: {:ok, String.t()} | {:error, Error.t()}
def version do
GeminiCliSdk.Command.run(["--version"])
end
end