Claudex.Messages (Claudex v0.6.1)

Copy Markdown View Source

The Messages API — send a conversation to Claude and get its reply.

create/2 waits for the whole reply. stream!/2 gives you a lazy stream of events as they arrive, and stream_to/3 sends those events to a process instead, for a GenServer or LiveView that can't block.

Summary

Functions

Counts the input tokens your messages, system prompt and tools would use. Claude generates nothing, and the call is free — it spends a request against a rate limit of its own, not tokens.

Sends a request to POST /v1/messages and returns the completed message.

Streams a reply, returning a lazy Stream of Claudex.Stream.Event structs.

Runs a stream in its own process and returns straight away, delivering each event to a mailbox.

Functions

count_tokens(client, params)

@spec count_tokens(Claudex.Client.t(), map() | keyword()) ::
  {:ok, non_neg_integer()} | {:error, Claudex.Error.t()}

Counts the input tokens your messages, system prompt and tools would use. Claude generates nothing, and the call is free — it spends a request against a rate limit of its own, not tokens.

{:ok, tokens} =
  Claudex.Messages.count_tokens(client, %{
    model: "claude-opus-5",
    messages: [%{role: "user", content: "Hello, Claude"}]
  })

Only :model and :messages are required — this endpoint has no :max_tokens, and passing one is an error from the API. Everything else that affects the input counts (:system, :tools, :thinking, :tool_choice) is optional and passed through, and :tools takes a module the same way create/2 does.

The number is an estimate; a real request can come out a little different.

create(client, params)

@spec create(Claudex.Client.t(), map() | keyword()) ::
  {:ok, Claudex.Message.t()} | {:error, Claudex.Error.t()}

Sends a request to POST /v1/messages and returns the completed message.

params must include :model, :messages, and :max_tokens. Everything else the Messages API accepts (:system, :temperature, :thinking, ...) is optional and passed straight through, so any parameter the API supports works here — see the Messages API reference.

:tools takes a module that uses Claudex.Tool, a list of them, plain tool maps, or any mix of the two — see Claudex.Tool.list/1.

Every other key in params goes into the JSON request body verbatim.

This function always sends a non-streaming request. Passing stream: true returns {:error, %Claudex.Error{type: :bad_request}}, use stream!/2 or stream_to/3 instead.

Returns {:error, %Claudex.Error{}} for a non-2xx response, a timeout, or a connection failure.

stream!(client, params)

@spec stream!(Claudex.Client.t(), map() | keyword()) :: Enumerable.t()

Streams a reply, returning a lazy Stream of Claudex.Stream.Event structs.

client
|> Claudex.Messages.stream!(params)
|> Enum.each(fn
  %Event.ContentBlockDelta{delta: {:text, chunk}} -> IO.write(chunk)
  _event -> :ok
end)

Nothing happens until you enumerate it, and enumerating owns the request: the process that starts consuming spawns the connection and only reads more of the response when you ask for the next event. Stop enumerating — with Enum.take/2, a break, or an exception — and the request is cancelled.

params takes exactly what create/2 takes; stream: true is set for you. Use Claudex.Stream.final_message/1 if you want the assembled Claudex.Message at the end.

Raises Claudex.Error on a missing parameter, a failed request, or an error the API sends part-way through the stream. Use stream_to/3 if you'd rather have errors delivered as messages than raised.

stream_to(client, params, opts \\ [])

@spec stream_to(Claudex.Client.t(), map() | keyword(), keyword()) ::
  {:ok, Claudex.Stream.Handle.t()} | {:error, Claudex.Error.t()}

Runs a stream in its own process and returns straight away, delivering each event to a mailbox.

params takes exactly what create/2 takes; stream: true is set for you.

Returns {:ok, %Claudex.Stream.Handle{ref: ref}} and then sends:

  • {:claudex, ref, {:event, event}} for each Claudex.Stream.Event
  • {:claudex, ref, {:error, %Claudex.Error{}}} if the request fails
  • {:claudex, ref, :done} when the reply is complete
  • {:claudex, ref, :cancelled} after Claudex.Stream.cancel/1

The forwarding process is linked to the caller, so it dies with it. Pass to: pid to send the messages somewhere other than the calling process.