test/openagents/tools/admitted_catalog_test.exs

58e6347eeb72 · 2 KB

defmodule OpenAgents.Tools.AdmittedCatalogTest do
  use ExUnit.Case, async: true

  alias OpenAgents.Tools.{
    AdmittedCatalog,
    ConnectedRepositoryList,
    ConnectedRepositoryRead,
    ExecutionContext,
    Registry,
    RepoWrite
  }

  test "does not advertise tools outside the captured authority and surface" do
    assert {:ok, snapshot} = Registry.build([ConnectedRepositoryRead, RepoWrite])

    context =
      execution_context("text", ["repository.read"], [
        approval_receipt("sarah.tool.repo_write.v1")
      ])

    names =
      snapshot
      |> AdmittedCatalog.provider_definitions(context, "read and write repository files",
        top_k: 10,
        always_include: ["read_repository_file", "repo_write"]
      )
      |> Enum.map(& &1.name)

    assert "read_repository_file" in names
    refute "repo_write" in names

    voice_names =
      snapshot
      |> AdmittedCatalog.realtime_catalog(
        execution_context("voice", ["repository.read", "repository.write"], [
          approval_receipt("sarah.tool.repo_write.v1")
        ]),
        "read and write repository files",
        top_k: 10,
        always_include: ["read_repository_file", "repo_write"]
      )
      |> Map.fetch!("tools")
      |> Enum.map(& &1["name"])

    assert "read_repository_file" in voice_names
    refute "repo_write" in voice_names
  end

  test "equivalent text and voice contexts advertise the same authorized tool names" do
    assert {:ok, snapshot} =
             Registry.build([ConnectedRepositoryRead, ConnectedRepositoryList])

    opts = [
      top_k: 10,
      always_include: ["read_repository_file", "list_repository_directory"]
    ]

    text_names =
      snapshot
      |> AdmittedCatalog.provider_definitions(
        execution_context("text", ["repository.read"]),
        "inspect connected repository files",
        opts
      )
      |> Enum.map(& &1.name)
      |> Enum.sort()

    voice_names =
      snapshot
      |> AdmittedCatalog.realtime_catalog(
        execution_context("voice", ["repository.read"]),
        "inspect connected repository files",
        opts
      )
      |> Map.fetch!("tools")
      |> Enum.map(& &1["name"])
      |> Enum.sort()

    assert text_names == ["list_repository_directory", "read_repository_file"]
    assert voice_names == text_names
  end

  defp execution_context(surface, authorities, approval_receipts \\ []) do
    %ExecutionContext{
      scope: "browser_conversation",
      scope_ref: "conversation:test",
      surface: surface,
      authorities: MapSet.new(authorities),
      approval_receipts: approval_receipts
    }
  end

  defp approval_receipt(module_id) do
    %{
      "schema" => "sarah.module_approval.v1",
      "approval_class" => "exact_current_user_consent",
      "module_id" => module_id,
      "version" => 1,
      "scope_ref" => "conversation:test",
      "explicit" => true,
      "actor_type" => "person",
      "receipt_ref" => "approval:test"
    }
  end
end