# pi-merge-gateway-login

[pi](https://github.com/earendil-works/pi-mono) extension that adds the [Merge Dev gateway](https://docs.merge.dev/merge-gateway/) as a model provider through its [OpenAI Responses API](https://docs.merge.dev/merge-gateway/).

## Install

```bash
pi install npm:@iaziz786/pi-merge-gateway-login
# or from git:
pi install git:github.com/iAziz786/pi-merge-gateway-login
```

Then authenticate (prompts for your key, stores it in `~/.pi/agent/auth.json`):

```
/login merge-gateway
```

Permissions: outbound HTTPS to `api-gateway.merge.dev` only. No filesystem or subprocess access. Key stored by pi in `~/.pi/agent/auth.json`.

## Models

The extension registers the gateway's own catalog — every chat-capable model it
serves, under the gateway's model IDs (`GET /v1/models`, with context window,
output cap, input modalities, and reasoning ladder per model taken from the
model's preferred vendor). It is fetched at startup and falls back to five
bundled models when no API key is available or the gateway cannot be reached:

The catalog is scoped to what your organization is allowed to route, and that
changes: it listed 290 models earlier today and now lists one —
`zai/glm-5.3-flash` on its new vendor **pareto**, with every other vendor for that
model (Particle, z.ai, Wafer, Together AI, Baseten, Fireworks, Modal) and every
DeepSeek model returning `400 vendor_restrictions_unavailable`. Nothing in the
extension decides that; the organization's vendor and region restrictions in the
gateway dashboard do.

| bundled model ID | Model | Preferred vendor |
|---|---|---|
| `merge-gateway/zai/glm-5.3-flash` | GLM 5.3 Flash | pareto |
| `merge-gateway/deepseek/deepseek-v4-flash` | DeepSeek V4 Flash (retired, resolved to V4.1) | DeepSeek |
| `merge-gateway/deepseek/deepseek-v4-flash-0731` | DeepSeek V4 Flash 0731 | Particle |
| `merge-gateway/deepseek/deepseek-v4.1-flash` | DeepSeek V4.1 Flash | DeepSeek |
| `merge-gateway/deepseek/deepseek-v4.1-flash-1017` | DeepSeek V4.1 Flash 1017 | Particle |

Pick via `/model` in pi (`omp models merge-gateway` lists them all). Requests are
**not pinned to a vendor**: the gateway picks. That
is deliberate — pi and omp generate session titles, compaction summaries, and handoff
documents through side requests that never run extension hooks, so a model ID the
gateway does not recognize, or a field only the hook could add, would break them (the
vendor-prefixed IDs this extension used to register failed exactly there: `404 Model
'fireworks/deepseek-v4.1-flash' is not supported`, after which compaction fell through
to its largest-context fallback model and died on that model's output cap).

That is also why there is one entry per model rather than one per vendor: pi keys
models by ID alone, so a per-vendor entry would need an invented ID that the gateway
rejects on those unhooked side requests. Vendor selection belongs to the gateway, and
pricing follows it — see below.

All models support:

- **Reasoning:** the thinking ladder is mapped per model from the vendor's declared effort values (DeepSeek takes the full `none` … `max` ladder; GLM folds onto `low` / `high` / `max`); models whose vendor declares none are registered without effort control
- **Prompt caching:** automatic. The extension forwards the pi session ID as the body `prompt_cache_key` (the gateway ignores the `X-Session-Id` header on this surface), which keeps a session on the vendor whose prompt cache is warm and namespaces the provider cache key. Existing keys are preserved.

## Pricing

The gateway routes each request to the **cheapest eligible vendor** and fails over to
the next when that vendor cannot take it, so a fixed card per model would drift. The
extension prices each turn at the vendor that actually served it: `after_provider_response`
fires before the response stream is consumed — and therefore before pi prices the usage —
and rewrites the model's rates from that response's `x-merge-vendor` header, using the
rates from the gateway's own catalog. Input, output, cache-read, and cache-write rates
all follow the serving vendor, and each vendor's peak windows are applied from its `schedule`
in the zone the gateway states (DeepSeek: weekdays 01:00–04:00 and 06:00–10:00 UTC, 2×;
V4.1 Flash 1017 on Particle: daily 04:00–20:30 New York time, 2×, daylight saving followed),
so the displayed cost matches the
gateway's `usage.cost` rather than an estimate.

If the catalog could not be fetched, the bundled cards below remain — one per model, at
the preferred vendor's base rates, which is the route the gateway picks by default.

| Bundle model | Preferred vendor | Input | Output | Cache read |
|---|---|---|---|---|
| `zai/glm-5.3-flash` | pareto | $0.03 | $0.10 | $0.006 |
| `deepseek/deepseek-v4-flash` | DeepSeek | $0.15 | $0.60 | $0.003 |
| `deepseek/deepseek-v4-flash-0731` | Particle | $0.035 | $0.07 | $0.007 |
| `deepseek/deepseek-v4.1-flash` | DeepSeek | $0.15 | $0.60 | $0.003 |
| `deepseek/deepseek-v4.1-flash-1017` | Particle | $0.15 | $0.60 | $0.003 |

Rates are per million tokens and were verified against the gateway's own `usage.cost`
on live probes — cold and cache-hit turns matched to nine decimals. Cache writes are
billed only on some vendors (Anthropic and Bedrock entries, from `cache_write_per_million`).

Every vendor the gateway may choose, with what it charges, comes from `GET /v1/models`
and is applied automatically. For the five bundle models:

| Model | Eligible vendors (input / output / cache read per M) |
|---|---|
| `zai/glm-5.3-flash` | pareto 0.03/0.10/0.006 (only one this account may route to) · particle 0.12/0.40/0.03 · z.ai 0.075/0.25/0.015 · baseten, fireworks, together AI, wafer 0.15/0.50/0.03 · modal 0.45/1.50/0.09 |
| `deepseek/deepseek-v4-flash` | resolved to V4.1 Flash before routing, and billed as it |
| `deepseek/deepseek-v4-flash-0731` | Particle 0.035/0.07/0.007 · Makora 0.09/0.195/0.0196 · Baseten 0.13/0.26/0.028 · Together AI 0.14/0.28/0.03 · Empiriolabs 0.14/0.28 |
| `deepseek/deepseek-v4.1-flash` | DeepSeek 0.15/0.60/0.003 · Particle 0.20/0.80/0.03 · Fireworks 0.22/0.66/0.007 · Baseten 0.30/1.20/0.03 |
| `deepseek/deepseek-v4.1-flash-1017` | Particle 0.15/0.60/0.003 (only vendor listed; peak 0.30/1.20/0.006) |

The DeepSeek rows are what the gateway listed while those models were reachable;
they now return `400 vendor_restrictions_unavailable` on this account. Vendors
without zero data retention: z.ai and Wafer AI on GLM 5.3 Flash, Empiriolabs on
V4 Flash 0731, DeepSeek on the V4.1 family — every other vendor lists
`zero_data_retention: true`.

GLM 5.3 Flash's rates have moved twice: pareto launched 2026-09-22 at
0.03/0.10, particle went 0.015/0.05 → 0.12/0.40, and z.ai went 0.015/0.05 →
0.075/0.25. The card in the table above and `GLM_53_FLASH_COST` track pareto,
which the live path reprices from `x-merge-vendor` regardless — the other
vendors' numbers there come from Merge's model page, since the API catalog
hides vendors the organization cannot route to.

The gateway's own `usage.cost` on a response is the authoritative bill; the headers
carry `x-merge-vendor` (the vendor that served it) and `x-merge-model` (the model it
resolved to). To force a vendor, send `vendor` in the request body or set the
organization's vendor policy in the gateway dashboard.

## Routing and zero data retention

The gateway picks the vendor per request, preferring the cheapest eligible one — today
GLM 5.3 Flash routes to pareto, and it is the only vendor this account may route to.
Without a vendor pin there is no per-session control over *where* a request runs, and
whether a given vendor is even reachable is decided by the organization's vendor and
region restrictions (see the vendor list above for which vendors support zero data
retention — pareto does). Use the organization's routing policy or vendor allow/deny
lists in the gateway dashboard when a workload must stay on specific infrastructure.

Also note the retired `deepseek/deepseek-v4-flash` id: the gateway resolves it to V4.1
Flash, so the response comes back as V4.1 Flash (`x-merge-model:
deepseek/deepseek-v4.1-flash`) at V4.1 rates. Pick `deepseek/deepseek-v4-flash-0731` to
stay on the July snapshot.

## Jev (TypeSafe) judgments in omp

omp ships a TypeSafe judge it uses for thinking-level detection, Smart unexpected-stop
detection, git AI staging, and the `judge()` helper in eval cells. The gateway serves the
same System One contract, but at `/v1/decisions` under the model `typesafe/jev-1.13`
while the client posts to `/v1/systemone` and defaults to `jev-latest`, so
`tools/jev-bridge.ts` adapts path and model id:

```bash
MERGE_GATEWAY_API_KEY=… bun tools/jev-bridge.ts   # keep it running (loopback only)
```

```bash
# ~/.omp/agent/.env — omp loads this into $env at startup
TYPESAFE_BASE_URL=http://127.0.0.1:8787
TYPESAFE_API_KEY=<the same gateway key>
TYPESAFE_DEFAULT_MODEL=typesafe/jev-1.13
```

With the env key present, `AuthStorage.hasAuth("typesafe")` is satisfied, so the default
`providers.judgmentProvider: auto` sends those judgments to Jev through the gateway
instead of the `tiny`/`smol` chat fallback. Jev is not a chat model — it never appears in
`/model`, and chat side requests (titles, compaction) are unaffected. Billing is
$0.042/M input tokens with output free; omp's ledger shows $0 for these calls because the
judgment usage mapper reads token counts, not the gateway's `usage.cost`.

The bridge is repo-local tooling; the npm tarball ships `dist`, `README.md` and
`LICENSE` only.

## Endpoint

```
POST https://api-gateway.merge.dev/v1/openai/responses
Authorization: Bearer <MERGE_GATEWAY_API_KEY>
```

## Test

```bash
bun test
```
