Current section

Files

Jump to
sagents mix.exs
Raw

mix.exs

defmodule Sagents.MixProject do
  use Mix.Project

  @source_url "https://github.com/sagents-ai/sagents"
  @version "0.16.2"

  def project do
    [
      app: :sagents,
      version: @version,
      elixir: "~> 1.17",
      elixirc_paths: elixirc_paths(Mix.env()),
      test_options: [docs: true],
      start_permanent: Mix.env() == :prod,
      aliases: aliases(),
      deps: deps(),
      usage_rules: usage_rules(),
      dialyzer: [
        ignore_warnings: ".dialyzer_ignore.exs",
        plt_file: {:no_warn, "priv/plts/project.plt"},
        plt_core_path: "priv/plts",
        plt_add_apps: [:mix]
      ],
      package: package(),
      docs: docs(),
      name: "Sagents",
      homepage_url: @source_url,
      description: """
      Agent orchestration framework for Elixir, built on top of LangChain.
      Provides AgentServer, middleware system, state management, and more.
      """
    ]
  end

  def application do
    [
      extra_applications: [:logger]
    ]
  end

  def cli do
    [
      preferred_envs: [precommit: :test]
    ]
  end

  defp elixirc_paths(:test), do: ["lib", "test/support"]
  defp elixirc_paths(_), do: ["lib"]

  defp deps do
    [
      # Core dependency - the LangChain library
      {:langchain, ">= 0.14.3"},
      # {:langchain, path: "../my_langchain"},

      # Required dependencies
      {:phoenix_pubsub, "~> 2.1"},
      {:ecto, "~> 3.10 or ~> 3.11"},
      {:jason, "~> 1.0"},

      # Optional dependencies
      {:phoenix, "~> 1.7 or ~> 1.8", optional: true},
      {:horde, "~> 0.10", optional: true},

      # Development dependencies
      {:ex_doc, "~> 0.40", only: :dev, runtime: false},

      # Test dependencies
      {:mimic, "~> 1.8", only: :test},
      # `langchain` carries req_llm as an optional dependency, so it is absent
      # unless something asks for it. Tests that drive an agent through
      # `LangChain.ChatModels.ChatReqLLM` need it present.
      {:req_llm, ">= 1.11.0", only: :test},
      {:opentelemetry_api, "~> 1.4", only: :test},
      {:opentelemetry, "~> 1.5", only: :test, runtime: false},
      {:local_cluster, "~> 2.0", only: :test},

      # AGENTS.md file maintenance
      {:usage_rules, "~> 1.2 and >= 1.2.8", only: :dev, runtime: false},

      # Static analysis
      {:dialyxir, "~> 1.4", only: [:dev], runtime: false},
      {:credo, "~> 1.7", only: [:dev, :test], runtime: false},
      {:sobelow, "~> 0.14", only: [:dev, :test], runtime: false, warn_if_outdated: true},
      {:mix_audit, "~> 2.1", only: [:dev, :test], runtime: false}
    ]
  end

  # Sources `mix usage_rules.sync` writes into AGENTS.md.
  defp usage_rules do
    [
      file: "AGENTS.md",
      usage_rules: [{:usage_rules, sub_rules: ["elixir", "otp"]}]
    ]
  end

  defp aliases do
    [
      precommit: [
        "compile --warnings-as-errors",
        "deps.unlock --unused",
        "format",
        "credo",
        # TODO: Add full CI support for
        # "sobelow",
        "deps.audit",
        "test --include cluster --include slow"
      ]
    ]
  end

  defp docs do
    [
      main: "readme",
      extras: extras(),
      groups_for_extras: [
        Docs: Path.wildcard("docs/*.md")
      ],
      # Internals of Elixir's Registry and of Horde that our docs name because
      # the explanations are not checkable without them. All carry
      # `@moduledoc false`, so ExDoc cannot link them and warns on every
      # mention. Listing them here renders the reference as plain code and
      # drops the warning, rather than making the prose vaguer to stay quiet.
      skip_code_autolink_to: [
        "Horde.RegistryImpl",
        "Registry.Partition",
        "Registry.Supervisor",
        "Registry.Supervisor.init/1",
        "Registry.key_info!/1"
      ],
      groups_for_modules: [
        "Core Agent": [
          Sagents.Agent,
          Sagents.AgentServer,
          Sagents.AgentSupervisor,
          Sagents.State,
          Sagents.AgentUtils,
          Sagents.Fork
        ],
        Middleware: [
          Sagents.Middleware,
          Sagents.MiddlewareEntry,
          Sagents.Middleware.TodoList,
          Sagents.Middleware.FileSystem,
          Sagents.Middleware.HumanInTheLoop,
          Sagents.Middleware.SubAgent,
          Sagents.Middleware.Summarization,
          Sagents.Middleware.ConversationTitle,
          Sagents.Middleware.PatchToolCalls,
          Sagents.Middleware.ProcessContext
        ],
        SubAgents: [
          Sagents.SubAgent,
          Sagents.SubAgent.Task,
          Sagents.SubAgentServer,
          Sagents.SubAgentsDynamicSupervisor
        ],
        FileSystem: [
          Sagents.FileSystem,
          Sagents.FileSystemServer,
          Sagents.FileSystem.FileEntry,
          Sagents.FileSystem.FileMetadata,
          Sagents.FileSystem.FileSystemConfig,
          Sagents.FileSystem.FileSystemState,
          Sagents.FileSystem.FileSystemSupervisor,
          Sagents.FileSystem.Persistence,
          Sagents.FileSystem.Persistence.Disk,
          Sagents.FileSystemCallbacks
        ],
        Persistence: [
          Sagents.Persistence.StateSerializer,
          Sagents.Persistence.Hooks
        ],
        "Process Management": [
          Sagents.Application,
          Sagents.AgentsDynamicSupervisor
        ],
        "Events & Communication": [
          Sagents.PubSub,
          Sagents.Presence
        ],
        "Message Helpers": [
          Sagents.Message.DisplayHelpers
        ],
        "Support Types": [
          Sagents.Todo
        ]
      ],
      skip_undefined_reference_warnings_on: ["CHANGELOG.md"],
      before_closing_head_tag: &before_closing_head_tag/1,
      before_closing_body_tag: &before_closing_body_tag/1
    ]
  end

  defp extras do
    [
      "README.md",
      "CHANGELOG.md",
      "LICENSE",
      "docs/architecture.md",
      "docs/conversations_architecture.md",
      "docs/lifecycle.md",
      "docs/subscriptions_and_presence.md",
      "docs/using_with_a_react_front_end.md",
      "docs/middleware.md",
      "docs/middleware_messaging.md",
      "docs/observability.md",
      "docs/persistence.md",
      "docs/forking.md",
      "docs/filesystem_setup.md",
      "docs/clustering.md",
      "docs/deployment.md"
    ]
  end

  defp package do
    [
      files: [
        "lib",
        "priv/templates",
        "screenshots",
        "docs",
        ".formatter.exs",
        "mix.exs",
        "README*",
        "CHANGELOG*",
        "LICENSE*"
      ],
      maintainers: ["Mark Ericksen"],
      licenses: ["Apache-2.0"],
      links: %{"GitHub" => @source_url}
    ]
  end

  defp before_closing_head_tag(:html) do
    """
    <!-- HTML injected at the end of the <head> element -->
    """
  end

  defp before_closing_head_tag(:epub), do: ""

  defp before_closing_body_tag(:html) do
    """
    <script defer src="https://cdn.jsdelivr.net/npm/mermaid@10.2.3/dist/mermaid.min.js"></script>
    <script>
      let initialized = false;

      window.addEventListener("exdoc:loaded", () => {
        if (!initialized) {
          mermaid.initialize({
            startOnLoad: false,
            theme: document.body.className.includes("dark") ? "dark" : "default"
          });
          initialized = true;
        }

        let id = 0;
        for (const codeEl of document.querySelectorAll("pre code.mermaid")) {
          const preEl = codeEl.parentElement;
          const graphDefinition = codeEl.textContent;
          const graphEl = document.createElement("div");
          const graphId = "mermaid-graph-" + id++;
          mermaid.render(graphId, graphDefinition).then(({svg, bindFunctions}) => {
            graphEl.innerHTML = svg;
            bindFunctions?.(graphEl);
            preEl.insertAdjacentElement("afterend", graphEl);
            preEl.remove();
          });
        }
      });
    </script>
    """
  end

  defp before_closing_body_tag(:epub), do: ""
end