Building

Web search and web fetch providers

Replace the engine behind the built-in web_search and web_fetch tools.

Every agent has two built-in tools, web_search and web_fetch. By default they run on AgentParley's own engine. A web-provider plugin replaces the engine behind one or both — a user's own Brave key, a private search index, a reader service that turns pages into clean text. Agents keep calling the same tools; only what answers them changes.

search and fetch are independent: implement one or both. Each becomes its own capability that the account owner selects separately under Settings → Engines ("Web search" and "Web fetch").

web.search

web.search(input: { query: string; maxResults: number })
  : { results: { title: string; url: string; snippet?: string }[] }

The official Brave Search plugin, complete:

// plugins/brave-search/index.ts
globalThis.manifest = {
  egress: ["api.search.brave.com"],
  settings: [
    {
      key: "apiKey",
      title: "Brave API key",
      description: "From https://api-dashboard.search.brave.com — the free tier works.",
      type: "secret",
      required: true,
    },
  ],
};

globalThis.web.search = async (input) => {
  const apiKey = await host.settings.getSecret("apiKey");
  const count = Math.min(Math.max(input.maxResults, 1), 20); // Brave caps count at 20
  const response = await host.fetch({
    url: `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(input.query)}&count=${count}`,
    headers: { "X-Subscription-Token": apiKey, Accept: "application/json" },
  });
  if (response.status !== 200) throw new Error(`Brave search API returned ${response.status}`);
  const parsed = JSON.parse(response.body) as { web?: { results?: { title?: string; url?: string; description?: string }[] } };
  const results = (parsed.web?.results ?? [])
    .filter((result) => result.title && result.url)
    .slice(0, input.maxResults)
    .map((result) => ({ title: result.title!, url: result.url!, snippet: result.description ?? "" }));
  return { results };
};

Whatever you return, the platform re-caps it: at most 10 results, titles cut to 300 characters, snippets to 500. The handler must return an object whose results is an array.

web.fetch

web.fetch(input: { url: string }) : { content: string; url?: string; truncated?: boolean }

url in the result defaults to the input URL; truncated defaults to false. The platform caps content at 50,000 characters and sets truncated itself when it cuts.

The sandbox can't reach an arbitrary URL — host.fetch only reaches hosts you declared. A fetch provider therefore goes through a vendor that fetches on your behalf. Wiki Web uses a reader service:

// plugins/wiki-web/index.ts
globalThis.manifest = {
  egress: ["en.wikipedia.org", "r.jina.ai"],
};

globalThis.web.search = async (input) => {
  const url =
    "https://en.wikipedia.org/w/api.php?action=opensearch" +
    `&search=${encodeURIComponent(input.query)}&limit=${input.maxResults}&format=json`;
  const response = await host.fetch({ url });
  const [, titles, snippets, urls] = JSON.parse(response.body) as [string, string[], string[], string[]];
  return {
    results: titles.map((title, index) => ({
      title,
      url: urls[index],
      snippet: snippets[index] ?? "",
    })),
  };
};

globalThis.web.fetch = async (input) => {
  const response = await host.fetch({ url: `https://r.jina.ai/${input.url}` });
  return { content: response.body };
};

Failures are visible, never swapped

If your handler throws or times out, the agent sees a provider failed error from web_search / web_fetch. The platform does not quietly fall back to the built-in engine: the user chose your vendor, your egress and your quota, and that choice is respected. Throw a clear message — it's what the model reads.

What a web provider can reach

The same host surface as a middleware hook with the same permissions: host.fetch, host.settings, host.crypto, host.log, and anything else your code's calls derive. 30 s wall clock per call.

Setup for the user

  1. Install the plugin and fill in its settings (for example the API key).
  2. Settings → Engines: choose it for Web search and/or Web fetch.

The selection is per account: every agent's web_search / web_fetch then goes through your plugin.

See the Brave Search walkthrough.