Skip to main content

lib/agent_sea/provider.ex

defmodule AgentSea.Provider do
  @moduledoc """
  A chat-completion backend (Anthropic, OpenAI, a local model, …).

  Every LLM integration implements this behaviour. Streaming is optional and,
  where supported, returns a lazy `Stream` of normalized chunks built from an
  SSE body — there is no async-generator plumbing.
  """

  @typedoc "A single conversation message. `role` is required; extra keys (e.g. `tool_calls`, `tool_call_id`) are allowed."
  @type message :: %{
          required(:role) => :system | :user | :assistant | :tool,
          required(:content) => term(),
          optional(atom()) => term()
        }

  @typedoc "Provider options (model, api_key, system_prompt, tools, …)."
  @type opts :: keyword()

  @typedoc "A streamed event from `c:stream/2`."
  @type stream_event ::
          {:content, String.t()}
          | {:thinking, String.t()}
          | {:tool_call, map()}
          | :done

  @doc "Run a single (non-streaming) completion."
  @callback complete([message()], opts()) ::
              {:ok, AgentSea.Response.t()} | {:error, term()}

  @doc "Run a streaming completion, returning a lazy stream of `t:stream_event/0`."
  @callback stream([message()], opts()) :: Enumerable.t()

  @doc "Static capabilities for a model id; `nil` for unknown models."
  @callback model_info(model :: String.t()) :: AgentSea.ModelInfo.t() | nil

  @optional_callbacks stream: 2
end