# `ClaudeWrapper.Conversation`
[🔗](https://github.com/genagent/claude_wrapper_ex/blob/main/lib/claude_wrapper/conversation.ex#L1)

Host-side bookkeeping wrapper over a `ClaudeWrapper.DuplexSession`.

`Conversation` keeps a rolling history of `ClaudeWrapper.Result`
structs, cumulative cost, and a turn count on top of an underlying
long-lived `DuplexSession`. The duplex session remains the transport;
this wrapper only adds the accounting that `DuplexSession.send/3` does
not provide on its own.

It is the duplex-flavoured peer of `ClaudeWrapper.Session`, which is
the equivalent bookkeeping shape over transient per-call subprocess
turns. `Conversation` follows the same functional-struct style: a
`%Conversation{}` value threads through `send/2,3`, which returns the
updated struct alongside the turn's `Result`.

Mirrors the Rust crate's `conversation::Conversation`.

## When to use

Reach for `Conversation` when you already want a `DuplexSession`
(long-running host, mid-turn interrupts, broadcast subscribers) and
also want to answer:

  * How much have I spent on this conversation so far? (`total_cost/1`)
  * What is the full history of turns? (`history/1`)
  * How many turns have completed? (`turn_count/1`)

If you do not need bookkeeping, drive the `DuplexSession` directly. If
you want accounting over short-lived per-turn subprocess calls, use
`ClaudeWrapper.Session` instead.

## Usage

    config = ClaudeWrapper.Config.new()
    {:ok, session} = ClaudeWrapper.DuplexSession.start_link(config: config)

    conv = ClaudeWrapper.Conversation.new(session)

    {:ok, conv, _result} = ClaudeWrapper.Conversation.send(conv, "hello")
    {:ok, conv, _result} = ClaudeWrapper.Conversation.send(conv, "and again")

    ClaudeWrapper.Conversation.turn_count(conv)
    #=> 2

    ClaudeWrapper.Conversation.total_cost(conv)
    #=> 0.0123

    ClaudeWrapper.Conversation.close(conv)

## Beyond bookkeeping

`send/2,3` is the only entry point that records history. For
`DuplexSession.subscribe/1`, `DuplexSession.interrupt/2`, and
`DuplexSession.respond_to_permission/3`, reach the inner handle via
`session/1`. Those calls bypass the wrapper's accounting on purpose:
an interrupt still produces a `Result` that the in-flight `send/2,3`
records cleanly when the truncated turn lands.

# `session`

```elixir
@type session() :: GenServer.server()
```

The underlying session reference: the pid or registered name of a
running `ClaudeWrapper.DuplexSession`.

# `t`

```elixir
@type t() :: %ClaudeWrapper.Conversation{
  history: [ClaudeWrapper.Result.t()],
  session: session()
}
```

# `close`

```elixir
@spec close(t()) :: :ok
```

Stop the underlying `DuplexSession`.

Delegates to `DuplexSession.close/1` (graceful stop). Returns `:ok`.
The `%Conversation{}` struct is left as-is; its accumulated history
remains readable after the session is gone.

# `history`

```elixir
@spec history(t()) :: [ClaudeWrapper.Result.t()]
```

The per-turn `Result` history, in arrival order.

# `last_result`

```elixir
@spec last_result(t()) :: ClaudeWrapper.Result.t() | nil
```

The most recent turn's `Result`, or `nil` if no turn has completed.

# `new`

```elixir
@spec new(session()) :: t()
```

Wrap a running `DuplexSession` in a fresh conversation.

The conversation starts with an empty history; the underlying session
is not touched until the first `send/2,3`. `session` is the pid (or
registered name) returned by `DuplexSession.start_link/1`.

# `send`

```elixir
@spec send(t(), String.t(), timeout()) ::
  {:ok, t(), ClaudeWrapper.Result.t()} | {:error, term()}
```

Send a user prompt over the underlying `DuplexSession` and record the
resulting `ClaudeWrapper.Result`.

Returns `{:ok, conversation, result}` with the history-updated
conversation on success, matching `ClaudeWrapper.Session.send/3`'s
return convention. Errors from `DuplexSession.send/3` (e.g.
`{:error, %ClaudeWrapper.Error{kind: :turn_in_flight}}`,
`{:error, %ClaudeWrapper.Error{kind: :duplex_closed}}`) propagate
unchanged and do not update the history.

The `timeout` defaults to the same 120 seconds as
`DuplexSession.send/3`, since the entire turn (cold start + model
latency + tool calls) must complete within it.

# `session`

```elixir
@spec session(t()) :: session()
```

Borrow the underlying `DuplexSession` reference.

Use this for `DuplexSession.subscribe/1`, `DuplexSession.interrupt/2`,
and `DuplexSession.respond_to_permission/3`, which bypass this
wrapper's bookkeeping on purpose.

# `session_id`

```elixir
@spec session_id(t()) :: String.t() | nil
```

The session id assigned by the CLI, or `nil` until the first turn (or
`system/init`) has been observed.

Prefers the most recent turn's `Result.session_id`; falls back to
querying the underlying `DuplexSession` when no turn has recorded one
yet (for example, `system/init` arrived but no turn has completed).

# `total_cost`

```elixir
@spec total_cost(t()) :: float()
```

Cumulative cost in USD across every recorded turn.

Turns whose `Result` carries no `cost_usd` contribute `0.0`.

# `turn_count`

```elixir
@spec turn_count(t()) :: non_neg_integer()
```

Number of turns recorded through `send/2,3`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
