ClaudeWrapper.Stream (ClaudeWrapper v0.10.0)

Copy Markdown View Source

Lazy Stream combinators over a ClaudeWrapper.DuplexSession turn.

DuplexSession broadcasts a turn's progress to subscribers as {:claude, event} messages (see its module doc for the vocabulary). This module wraps one turn as an Elixir Enumerable.t/0 so the events compose with the standard Stream/Enum toolkit, and adds a few decode-and-project combinators on top. It is a pure ergonomic layer: no new runtime dependencies, no new transport.

One turn per enumeration

stream/3 returns a lazy stream. Like any Stream, it is a recipe: each time it is enumerated it triggers a fresh turn. Pick one transform or terminal per turn. If you need both the text and the ClaudeWrapper.Result.t/0, use collect/1 (it returns both) rather than enumerating twice.

session
|> ClaudeWrapper.Stream.stream("Write a haiku about Elixir.")
|> ClaudeWrapper.Stream.text_deltas()
|> Enum.each(&IO.write/1)

%{text: text, result: %ClaudeWrapper.Result{}} =
  session
  |> ClaudeWrapper.Stream.stream("Summarize this file.")
  |> ClaudeWrapper.Stream.collect()

Elements

The foundational stream/3 yields the same event payloads a subscriber receives (the second element of {:claude, event}): {:system_init, session_id}, {:assistant, msg}, {:stream_event, raw}, {:user, msg}, and the terminal {:result, %Result{}}. A failed turn (including :turn_in_flight) yields a terminal {:error, %ClaudeWrapper.Error{}} instead of {:result, _}.

Early halt

Halting early (e.g. Stream.take/2) stops consumption and unsubscribes, but does not cancel the turn server-side -- the claude subprocess runs the turn to completion. Use ClaudeWrapper.DuplexSession.interrupt/1 for a real mid-turn cancel.

Summary

Types

A decoded turn event, as yielded by stream/3.

Options for stream/3.

Functions

Run the turn to completion, returning both the text and the result.

Keep only the events for which fun returns truthy.

Run the turn to completion and return its ClaudeWrapper.Result.t/0.

Run the turn to completion and return the accumulated answer text.

Wrap one DuplexSession turn as a lazy stream of event/0 values.

Run fun on each event for its side effect, passing the event through.

Project a turn stream to its text deltas (the streamed answer tokens).

Project a turn stream to its extended-thinking deltas.

Project a turn stream to its tool-use starts, as %{id, name} maps.

Types

event()

@type event() ::
  {:system_init, String.t()}
  | {:assistant, map()}
  | {:stream_event, map()}
  | {:user, map()}
  | {:result, ClaudeWrapper.Result.t()}
  | {:error, ClaudeWrapper.Error.t()}

A decoded turn event, as yielded by stream/3.

option()

@type option() :: {:timeout, timeout()}

Options for stream/3.

Functions

collect(events)

@spec collect(Enumerable.t()) :: %{
  text: String.t(),
  result: ClaudeWrapper.Result.t() | nil
}

Run the turn to completion, returning both the text and the result.

%{text: text, result: %ClaudeWrapper.Result{}} =
  ClaudeWrapper.Stream.collect(stream)

result is nil if the turn ended without a result event. Terminal: enumerates the whole turn once (the right call when you need both the text and the result from a single turn).

filter(events, fun)

@spec filter(Enumerable.t(), (event() -> as_boolean(term()))) :: Enumerable.t()

Keep only the events for which fun returns truthy.

A thin pass-through to Stream.filter/2, here for discoverability alongside the projections.

final_result(events)

@spec final_result(Enumerable.t()) :: ClaudeWrapper.Result.t() | nil

Run the turn to completion and return its ClaudeWrapper.Result.t/0.

Returns nil if the turn yielded no result event (e.g. it ended in an {:error, _}). Terminal: enumerates the whole turn.

final_text(events)

@spec final_text(Enumerable.t()) :: String.t()

Run the turn to completion and return the accumulated answer text.

Concatenates every text delta. Terminal: enumerates the whole turn.

stream(session, prompt, opts \\ [])

@spec stream(GenServer.server(), String.t(), [option()]) :: Enumerable.t()

Wrap one DuplexSession turn as a lazy stream of event/0 values.

Enumerating subscribes the calling process, sends prompt, and yields each broadcast event in arrival order, ending with the terminal {:result, %Result{}} (or {:error, %Error{}}).

Options:

tap(events, fun)

@spec tap(Enumerable.t(), (event() -> term())) :: Enumerable.t()

Run fun on each event for its side effect, passing the event through.

A thin pass-through to Stream.each/2.

text_deltas(events)

@spec text_deltas(Enumerable.t()) :: Enumerable.t()

Project a turn stream to its text deltas (the streamed answer tokens).

session
|> ClaudeWrapper.Stream.stream("Say hi.")
|> ClaudeWrapper.Stream.text_deltas()
|> Enum.join()

thinking_deltas(events)

@spec thinking_deltas(Enumerable.t()) :: Enumerable.t()

Project a turn stream to its extended-thinking deltas.

tool_uses(events)

@spec tool_uses(Enumerable.t()) :: Enumerable.t()

Project a turn stream to its tool-use starts, as %{id, name} maps.

Emits one element per tool-use content block the assistant opens. The tool input arrives as later input_json deltas; this surfaces the identity of each tool call.