File size: 3,049 Bytes
92d9567 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | defmodule PerplexityMacro.ToolRouter do
@moduledoc "Capability Router — VM owns control, host owns evidence. Sensitive caps require CONFIRM 0x2A."
use GenServer
@confirm_ttl_ms 60_000
def start_link(opts), do: GenServer.start_link(__MODULE__, opts, name: __MODULE__)
@impl true
def init(_opts), do: {:ok, %{confirm: nil}}
@doc "Route RQ → either WASM Box (python) or ResearchBroker (external). Policy: sensitive needs CONFIRM."
def route(req), do: GenServer.call(__MODULE__, {:route, req})
def confirm, do: GenServer.call(__MODULE__, :confirm)
@impl true
def handle_call({:route, %{capability: cap, python: true}=req}, _from, state) do
if PerplexityMacro.Capability.sensitive?(cap) and not valid_confirm?(state.confirm) do
{:reply, {:error, :confirm_required}, state}
else
# WASM Box is isolated — no direct net/fs, tools mediated via recursive route
result = PerplexityMacro.WASMBox.execute(req.code, req.args)
tlv = PerplexityMacro.Evidence.encode_tlv(result)
{:reply, {:ok, tlv}, maybe_consume_confirm(state, cap)}
end
end
def handle_call({:route, %{capability: cap}=req}, _from, state) do
if PerplexityMacro.Capability.sensitive?(cap) and not valid_confirm?(state.confirm) do
{:reply, {:error, :confirm_required}, state}
else
result = dispatch_external(req)
tlv = PerplexityMacro.Evidence.encode_tlv(result)
{:reply, {:ok, tlv}, maybe_consume_confirm(state, cap)}
end
end
def handle_call(:confirm, _from, state) do
tok = %{token: :crypto.strong_rand_bytes(4) |> Base.encode16(), exp: System.monotonic_time(:millisecond) + @confirm_ttl_ms}
{:reply, {:ok, tok}, %{state | confirm: tok}}
end
defp valid_confirm?(nil), do: false
defp valid_confirm?(%{exp: exp}), do: System.monotonic_time(:millisecond) < exp
defp maybe_consume_confirm(state, cap) do
if PerplexityMacro.Capability.sensitive?(cap), do: %{state | confirm: nil}, else: state
end
defp dispatch_external(%{capability: :search}=r), do: PerplexityMacro.Adapters.Search.run(r.query)
defp dispatch_external(%{capability: :wikipedia}=r), do: PerplexityMacro.Adapters.Wikipedia.run(r.query)
defp dispatch_external(%{capability: :mathematica}=r), do: PerplexityMacro.Adapters.Mathematica.run(r.query)
defp dispatch_external(%{capability: :dictionary}=r), do: PerplexityMacro.Adapters.Dictionary.run(r.query)
defp dispatch_external(%{capability: :fetch}=r), do: PerplexityMacro.Adapters.Fetch.run(r.query)
defp dispatch_external(%{capability: :browser}=r), do: PerplexityMacro.Adapters.Browser.run(r.query)
defp dispatch_external(%{capability: :code}=r), do: PerplexityMacro.Adapters.Code.run(r.query)
defp dispatch_external(%{capability: :local_model}=r), do: PerplexityMacro.Adapters.LocalModel.run(r.query)
defp dispatch_external(r), do: {:ok, [%{title: "#{r.capability}", url: "#{r.capability}://", content: inspect(r), score: 1.0, source_hash: :erlang.phash2(r, 0xFFFFFFFF)}]}
end
|