Multi-turn session management.
Wraps repeated Exec / ExecResume calls, automatically threading the
session_id so each turn continues the same conversation.
The first turn uses Exec to create a new session. Subsequent turns
use ExecResume with the session ID extracted from the JSON output.
Usage
config = CodexWrapper.Config.new(working_dir: "/path/to/project")
session = CodexWrapper.Session.new(config)
{:ok, session, result} = CodexWrapper.Session.send(session, "What files are in this project?")
{:ok, session, result} = CodexWrapper.Session.send(session, "Now add tests for lib/foo.ex")
# Access history
CodexWrapper.Session.turns(session)
#=> [%Result{...}, %Result{...}]
# Resume a previous session
session = CodexWrapper.Session.resume(config, "session-id-abc")
Summary
Functions
Archive this session (codex archive). Reversible with unarchive/1.
Permanently delete this session (codex delete).
Alias for turns/1.
Get the last result, if any.
Create a new session with the given config.
Resume an existing session by ID.
Send a message in the session. Returns the updated session and result.
Get the session ID (if established).
Send a message and return a stream of events.
Get the total cost across all turns.
Get the number of completed turns.
Get the conversation history (list of results).
Unarchive this session (codex unarchive).
Types
@type t() :: %CodexWrapper.Session{ config: CodexWrapper.Config.t(), exec_opts: keyword(), history: [CodexWrapper.Result.t()], session_id: String.t() | nil }
Functions
Archive this session (codex archive). Reversible with unarchive/1.
Returns {:error, :no_session} if no turn has run yet, since the
session id only exists once the CLI has created it.
@spec delete( t(), keyword() ) :: {:ok, String.t()} | {:error, :no_session | :confirmation_required | term()}
Permanently delete this session (codex delete).
Requires confirm: true, like CodexWrapper.Commands.Archive.delete/3
it delegates to; without it the CLI is not invoked. Returns
{:error, :no_session} if no turn has run yet.
@spec history(t()) :: [CodexWrapper.Result.t()]
Alias for turns/1.
@spec last_result(t()) :: CodexWrapper.Result.t() | nil
Get the last result, if any.
@spec new( CodexWrapper.Config.t(), keyword() ) :: t()
Create a new session with the given config.
Options
Any option accepted by CodexWrapper.exec/2 (exec-level options only):
:model- Model name:profile- Named config profile (--profile); first turn only, sincecodex exec resumedoes not accept--profile:sandbox- Sandbox mode:approval_policy- Approval policy (:untrusted,:on_request,:never):full_auto- Enable full-auto (boolean):dangerously_bypass_approvals_and_sandbox- Bypass all (boolean):skip_git_repo_check- Skip git check (boolean)
@spec resume(CodexWrapper.Config.t(), String.t(), keyword()) :: t()
Resume an existing session by ID.
Send a message in the session. Returns the updated session and result.
The first turn creates a new session via Exec. Subsequent turns use
ExecResume with the session ID from the first result.
Internally uses --json to extract the session ID from NDJSON events.
Get the session ID (if established).
@spec stream(t(), String.t(), keyword()) :: {t(), Enumerable.t()}
Send a message and return a stream of events.
Returns {session, stream}. The returned session is the same
session passed in -- this function does not thread session_id
across turns. If you need multi-turn continuity, use send/3
instead, which runs the turn synchronously and updates session_id
from the final events.
Use stream/3 when you want to observe events from a single turn
(for example, to render intermediate output as the CLI produces it)
and do not need to chain into a follow-up turn on the same thread.
Get the total cost across all turns.
Returns 0.0 as the Codex CLI does not currently expose per-turn cost.
This is provided for API compatibility with future cost reporting.
@spec turn_count(t()) :: non_neg_integer()
Get the number of completed turns.
@spec turns(t()) :: [CodexWrapper.Result.t()]
Get the conversation history (list of results).
Unarchive this session (codex unarchive).
Returns {:error, :no_session} if no turn has run yet.