lib/openagents/tools/module_discover.ex

58e6347eeb72 · 4 KB

defmodule OpenAgents.Tools.ModuleDiscover do
  @moduledoc "Bounded discovery over the exact module registry captured by this turn."

  @behaviour OpenAgents.Tools.Tool

  alias OpenAgents.Modules.{Discovery, Metadata}
  alias OpenAgents.Tools.{ExecutionResult, Tool}

  @impl true
  def specification do
    %Tool{
      module_id: "sarah.tool.module_discover.v1",
      name: "module_discover",
      version: 1,
      description:
        "Searches Sarah's tools by relevance (semantic + lexical over each " <>
          "tool's description), by `tags`, or a combination, without granting " <>
          "execution. The system already selects the tools most relevant to the " <>
          "conversation each turn, so use this only to look further afield.",
      input_schema: input_schema(),
      output_schema: output_schema(),
      side_effect: :read_only,
      required_scope: "browser_conversation",
      required_authority: "module.discover",
      executor: %{id: "sarah.module.discovery", disclosure: "Sarah module discovery"},
      maintainer: "OpenAgents",
      attribution: ["OpenAgentsInc/openagents.com"],
      policy_facets: %{
        "privacy" => "public_catalog_metadata_only",
        "residency" => "application_process"
      },
      module_metadata:
        Metadata.first_party("module.discover", "browser_conversation",
          effect: :read_only,
          privacy: "public_catalog_metadata_only",
          residency: "application_process"
        ),
      timeout_ms: 1_000,
      maximum_input_bytes: 2_048,
      maximum_output_bytes: 32_768,
      implementation: __MODULE__
    }
  end

  @impl true
  def execute(arguments, %{module_registry_snapshot: snapshot}) when not is_nil(snapshot) do
    with {:ok, result} <- Discovery.search(snapshot, arguments) do
      references =
        Enum.map(result["matches"], fn match ->
          "module:#{match["module_id"]}:#{match["version"]}:#{match["artifact_digest"]}"
        end)

      {:ok, %ExecutionResult{result: result, target_receipt_refs: references}}
    end
  end

  def execute(_arguments, _context), do: {:error, :module_registry_unavailable}

  defp input_schema do
    string = %{"type" => "string", "maxLength" => 128}

    %{
      "type" => "object",
      "properties" => %{
        "query" => string,
        "tags" => %{
          "type" => "array",
          "items" => %{"type" => "string", "maxLength" => 64},
          "maxItems" => 16
        },
        "capability" => string,
        "side_effect" => string,
        "data_scope" => string,
        "cost" => string,
        "quality" => string,
        "jurisdiction" => string,
        "publisher" => string,
        "compatibility" => %{"type" => "integer"},
        "include_deprecated" => %{"type" => "boolean"},
        "first" => %{"type" => "integer"}
      },
      "additionalProperties" => false
    }
  end

  defp output_schema do
    string = %{"type" => "string", "maxLength" => 512}

    projection = %{
      "type" => "object",
      "properties" => %{
        "module_id" => string,
        "version" => %{"type" => "integer"},
        "artifact_digest" => string,
        "registry_digest" => string,
        "state" => string,
        "side_effect" => string,
        "approval_class" => string,
        "capability_scopes" => %{"type" => "array", "maxItems" => 32, "items" => string},
        "data_scopes" => %{"type" => "array", "maxItems" => 32, "items" => string},
        "facets" => %{"type" => "object", "properties" => %{}, "additionalProperties" => true},
        "publisher" => string,
        "compatibility" => %{
          "type" => "object",
          "properties" => %{
            "runtime_min" => %{"type" => "integer"},
            "runtime_max" => %{"type" => "integer"}
          },
          "required" => ["runtime_min", "runtime_max"],
          "additionalProperties" => false
        },
        "deprecation" => %{
          "type" => "object",
          "properties" => %{},
          "additionalProperties" => true
        },
        "attribution_required" => %{"type" => "boolean"}
      },
      "required" => [
        "module_id",
        "version",
        "artifact_digest",
        "registry_digest",
        "state",
        "side_effect",
        "approval_class",
        "capability_scopes",
        "data_scopes",
        "facets",
        "publisher",
        "compatibility",
        "attribution_required"
      ],
      "additionalProperties" => false
    }

    %{
      "type" => "object",
      "properties" => %{
        "schema" => string,
        "registry_digest" => string,
        "matches" => %{"type" => "array", "maxItems" => 20, "items" => projection},
        "truncated" => %{"type" => "boolean"}
      },
      "required" => ["schema", "registry_digest", "matches", "truncated"],
      "additionalProperties" => false
    }
  end
end