# pi-web

Native `web_search` and secure public-URL `web_fetch` tools for the
[pi coding agent](https://github.com/earendil-works/pi).

pi-web reuses the subscription OAuth sessions already managed by pi:

- OpenAI Codex through the `openai-codex` provider.
- Anthropic Claude through the `anthropic` provider.

It does not read credential files, accept API keys, register replacement
providers, or fall back to environment credentials. Model-backed requests
are sent only to the fixed first-party provider origins after the active
canonical model and OAuth snapshot have passed strict trust checks.

## Requirements

- Node.js `>=22.19.0`.
- pi `>=0.80.10`.
- A pi-managed OAuth login for any model-backed provider route you select.

A no-prompt local fetch can work without either provider being logged in.

## Install

Install from npm through pi:

```bash
pi install npm:@boti-ormandi/pi-web
```

For local development, point pi at a checkout:

```json
{
	"extensions": ["/absolute/path/to/pi-web"]
}
```

Place that in `~/.pi/agent/settings.json` or a project's `.pi/settings.json`,
then reload or restart pi.

## Tools

### `web_search`

Searches the public web through one subscription provider route.

```ts
{
	query: string;
	provider?: "openai-codex" | "anthropic";
	allowed_domains?: string[];
	blocked_domains?: string[];
}
```

Rules:

- `query` must contain at least two characters.
- `provider` is optional. The literal value `auto` is not accepted in tool
  JSON; omit the field to use configured or automatic routing.
- `allowed_domains` and `blocked_domains` are mutually exclusive after empty
  arrays are normalized away.
- Domain entries must be lowercase IDNA hostnames. Schemes, credentials,
  paths, ports, wildcards, and IP literals are rejected.
- A domain filter matches the named domain and its subdomains.
- URL is the only guaranteed source field. Title and snippet are optional.

Filtered searches fail closed: pi-web requires structured public source URLs,
verifies every source against the requested filter, and omits unverified
provider prose. Unfiltered searches may return useful provider text even when
no structured sources are present.

### `web_fetch`

Retrieves one public HTTP(S) URL. With no prompt, it returns bounded retrieved
text. With a prompt, it processes only verified retrieved content.

```ts
{
	url: string;
	prompt?: string;
	provider?: "openai-codex" | "anthropic";
}
```

`prompt` is trimmed. Empty or whitespace-only prompts are treated as omitted
and never trigger subscription processing.

Returned metadata can include the final URL, title, content kind, PDF page
count, and explicit source/input/answer truncation flags. Model IDs, costs,
cache state, account metadata, encrypted provider state, and native response
references are not stable tool details.

## Routing

Provider precedence for model-backed work is:

1. The tool's explicit `provider`.
2. The global config provider when it is not `auto`.
3. The automatic candidate sequence resolved for the current invocation.

Explicit selection is strict. Automatic routing may advance to another
provider only after a pre-semantic resolution, OAuth, catalog, or capability
failure. Once native search, native open, or grounded processing is dispatched,
that provider owns the result or error; pi-web never switches providers after
semantic commitment.

### Search and prompted fetch

| Active trusted OAuth model | Automatic candidate order |
|---|---|
| OpenAI Codex | Active Codex, then inactive Anthropic |
| Anthropic | Active Anthropic, then inactive Codex |
| Neither | Inactive Codex, then inactive Anthropic |

The active model is observed on every tool invocation. Active trusted models
are used directly rather than replaced by a catalog choice.

### No-prompt fetch

No-prompt retrieval intentionally has a different algorithm:

- Explicit/configured Codex: resolve Codex OAuth, perform native open, and
  require retrieval proof.
- Explicit/configured Anthropic: retrieve locally. No Anthropic auth lookup or
  Anthropic request occurs.
- Automatic: use native Codex only when the active model is trusted Codex with
  an OAuth snapshot; otherwise retrieve locally immediately.

No-prompt fetch never performs inactive-provider fallback.

## Provider behavior

### OpenAI Codex

- Search uses the fixed ChatGPT Codex native search endpoint.
- An active trusted Codex model is used directly.
- Inactive Codex selection performs a fresh fixed-protocol catalog preflight,
  then chooses an exact trusted pi model deterministically.
- Fetch uses native open. Prompted fetch requires native retrieval proof before
  a separate ordinary Codex processing request.
- The processing request uses the same model and receives only bounded
  retrieved text plus the normalized prompt.

### Anthropic

- Search uses Anthropic's native server-side web search tool through the
  accepted subscription compatibility envelope.
- The active trusted Anthropic model is used directly. Inactive fallback is the
  exact trusted `claude-haiku-4-5` model when present.
- Fetch retrieval is local. Prompted fetch retrieves and extracts first, then
  performs ordinary grounded Anthropic processing.
- Retrieval or extraction failure occurs before OAuth resolution and makes no
  Anthropic request.
- No-prompt Anthropic fetch is entirely local.

## Configuration

pi-web has one durable config file:

```text
~/.pi/agent/extensions/pi-web/config.json
```

Schema and default:

```json
{
	"provider": "auto"
}
```

Accepted values are `auto`, `openai-codex`, and `anthropic`. `provider` is the
only permitted key. Missing files and `{}` resolve to `auto`; malformed JSON,
extra keys, legacy multi-key configuration, arrays, `null`, and other
primitives produce an observable configuration error.

There is no project-local pi-web config, environment-variable override, or pi
CLI flag.

## Public-URL security boundary

Local retrieval uses a one-shot direct-IP HTTP/HTTPS connector rather than a
global reusable fetch client.

- Only public `http:` and `https:` URLs are accepted.
- Credentials, fragments, non-default ports, and private or special-use IP
  space are rejected.
- Every DNS answer is validated; mixed public/private answers fail closed.
- The connected peer address must match the validated pinned address before a
  request is sent.
- Original HTTP Host and TLS SNI are preserved while the connection remains
  pinned to the validated IP.
- Every redirect is re-resolved and revalidated. HTTPS downgrade is rejected.
- Caller-provided headers, proxy routing, compression, connection reuse, and
  private-address escape switches are not supported.
- Deadlines, redirect count, response bytes, extraction work, provider input,
  and returned output are bounded.
- Abort signals propagate through network retrieval, HTML extraction, and PDF
  page processing.

This is a public-web tool, not a local-network browser or authenticated scraper.

## `/web-status`

`/web-status` is the only slash command added by pi-web. It reports separate
snapshot predictions for:

- Search.
- Prompted fetch.
- No-prompt fetch.

The command may inspect the active model, registry snapshots, OAuth-status
snapshots, validated config, and public version information. It does not
refresh auth, resolve credentials, contact provider or catalog endpoints,
write files, or display credentials, account IDs, or native provider state.

Its output distinguishes active trusted routes, inactive catalog/fallback
preflight, local retrieval, and the fact that request-time OAuth is still
required.

## Output and UI

Collapsed tool rows show the selected route, source/content summary, and any
truncation state. Expanded rows show normalized source metadata and bounded
previews. Preview clipping is renderer-only: the full bounded tool result is
still returned to the agent.

Model-visible output also includes explicit markers when provider text, source
lists, retrieved text, grounded-processing input, or the final answer was
truncated.

## Clean-break migration

Version `0.4.0` replaces the earlier Anthropic-only configurable product with
the strict native OAuth contract above. Removed surfaces include model/tier
controls, alternate fetch backends, generic provider routing, project and
environment configuration layers, caches, citation replay, cost display, and
legacy slash commands.

See [CHANGELOG.md](./CHANGELOG.md) for the complete migration table. Legacy
cache files, when present, are left untouched and are neither read nor deleted.

## Development

```bash
npm install
npm run typecheck
npm test
npm run test:package
```

`npm test` runs deterministic schema, config, trust, routing, provider,
transport, extraction, status, tool-output, and renderer tests.

`npm run test:package` packs the exact npm artifact, rejects private or test
material from the tarball, installs it with the minimum and newest tested pi
host packages against TypeBox `1.1.38` and the newest tested stable TypeBox
`<2`. It loads the extension under Node `22.19.0` and the current Node runtime.
The verification uses isolated temporary homes and does not read the user's pi
credentials or configuration.

Live provider calls are not required for unit, package, or release acceptance.
The Codex native protocol has separate completed live evidence; Anthropic uses
the accepted historical working baseline and deterministic compatibility
fixtures.

## License

MIT. See [LICENSE](./LICENSE).
