Skip to main content

lib/coding_agent.ex

defmodule CodingAgent do
  @moduledoc """
  A small library for building coding-agent harnesses on top of OpenRouter
  (via `req_llm`), with each agent conversation running as its own
  `GenServer` session and support for Claude-Code-style skills (directories
  with a `SKILL.md` that the agent can discover and invoke).

  File tools (`read`/`write`/`edit`) operate on an in-memory `path =>
  content` map rather than the real filesystem; `send_message/3` hands that
  map back to the caller so they can persist it (or not) however they like.

  ## Quick start

      CodingAgent.OpenRouter.configure!()  # reads OPENROUTER_API_KEY

      {:ok, pid} = CodingAgent.start_session(
        model: CodingAgent.OpenRouter.model("anthropic/claude-sonnet-4.5"),
        skills_dirs: ["skills"],
        files: %{"lib/foo.ex" => File.read!("lib/foo.ex")}
      )

      {:ok, reply, files} = CodingAgent.send_message(pid, "Fix the failing test in lib/foo.ex")

      # caller decides what to do with the result, e.g. write it back out:
      Enum.each(files, fn {path, content} -> File.write!(path, content) end)

  See `CodingAgent.Session` for the full set of options.
  """

  alias CodingAgent.Session

  @doc "Starts an unsupervised agent session. See `CodingAgent.Session.start_link/1` for options."
  defdelegate start_session(opts \\ []), to: Session, as: :start_link

  @doc "Sends a message to a session and runs the agent loop to completion. Returns `{:ok, reply, files}`."
  defdelegate send_message(session, text, timeout \\ 120_000), to: Session

  @doc """
  Like `send_message/3`, but calls `on_chunk` with each text chunk as the
  model produces it. Returns the same `{:ok, reply, files}` shape once the
  full turn (including any tool round-trips) has finished.
  """
  defdelegate stream_message(session, text, on_chunk, timeout \\ 120_000), to: Session

  @doc "Returns the full conversation context for a session."
  defdelegate context(session), to: Session

  @doc "Returns a session's current in-memory workspace (`path => content`)."
  defdelegate files(session), to: Session
end