Pi
Minimal terminal coding agent — register Melious as a custom OpenAI-compatible provider via a TypeScript extension
Pi is a minimal, MIT-licensed terminal coding harness from badlogic/pi-mono. It speaks 15+ providers out of the box and lets you wire in custom ones through TypeScript extensions rather than config files. Sessions are tree-structured (you can branch and switch mid-conversation), the agent surface is built around primitives — extensions, skills, prompt templates, AGENTS.md context — and /model swaps the active model at any point. Pointing it at Melious is a few lines of TypeScript dropped into ~/.pi/agent/extensions/melious.ts.
Setup
Install Pi
npm install -g @mariozechner/pi-coding-agentThis is the only documented install path right now. Requires Node.js. After install, pi is on your PATH.
Register Melious as a custom provider
Pi auto-discovers extensions from ~/.pi/agent/extensions/*.ts (global) and .pi/extensions/*.ts (project-local). Drop the file below in either location. Pi compiles and loads it on next launch — no extra config.
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
export default function (pi: ExtensionAPI) {
pi.registerProvider("melious", {
name: "Melious",
baseUrl: "https://api.melious.ai/v1",
apiKey: "MELIOUS_API_KEY",
api: "openai-completions",
models: [
{
id: "<MODEL_ID>",
name: "<Display Name>",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
maxTokens: 4096
}
]
});
}api: "openai-completions" tells Pi to send Chat Completions-shaped requests, which Melious serves natively. apiKey is an env var name — Pi reads MELIOUS_API_KEY from the environment at request time. List one entry per model you plan to use; the next step shows how to skip the manual list.
Or auto-populate models from /v1/models
The extension factory can be async. That lets you fetch our live model catalog and register every model in one go — no manual updates when we add or remove one:
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
export default async function (pi: ExtensionAPI) {
const apiKey = process.env.MELIOUS_API_KEY;
if (!apiKey) return;
const res = await fetch("https://api.melious.ai/v1/models?include_meta=true", {
headers: { Authorization: `Bearer ${apiKey}` },
});
const { data } = await res.json();
pi.registerProvider("melious", {
name: "Melious",
baseUrl: "https://api.melious.ai/v1",
apiKey: "MELIOUS_API_KEY",
api: "openai-completions",
models: data
.filter((m: any) => m._meta?.type === "chat")
.map((m: any) => ({
id: m.id,
name: m._meta?.display_name ?? m.id,
reasoning: m._meta?.capabilities?.reasoning ?? false,
input: m._meta?.capabilities?.vision
? ["text", "image"]
: ["text"],
cost: {
input: m._meta?.pricing?.input_per_1m ?? 0,
output: m._meta?.pricing?.output_per_1m ?? 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: m._meta?.context_length ?? 128000,
maxTokens: m._meta?.max_output_tokens ?? 4096,
})),
});
}The factory runs at startup. Hot-reload from inside Pi via /reload after editing.
Authenticate and run
Export your key and launch:
export MELIOUS_API_KEY=sk-mel-<YOUR_API_KEY>
pi/model inside the session lets you switch between any registered model. pi --list-models shows what got registered. pi --provider melious filters to just our models.
Picking a model
Pi has no built-in capability gating — every entry in your models array is selectable via /model. If you want Pi to label which can call tools or handle vision, set input: ["text", "image"] for vision models and rely on Pi's UI badges; for tool-use, the request will succeed or fail based on what the model actually supports server-side.
Browse melious.ai/hub/models for current IDs, context windows, and capability flags. The async factory above maps _meta.capabilities straight onto Pi's model entry.
Steering routing
Append a flavor suffix to the model id when you register it:
{ id: "<MODEL_ID>:eco", name: "<Display Name> (eco)", /* ... */ }:speed— latency-biased provider selection:price— cheapest available provider:eco— greenest energy mix- (no suffix) — balanced, our default
Register one entry per flavor and switch via /model per session. See Routing for the full decision table.
What's different
- Provider registration is code, not config. Pi's design choice — extensions are TypeScript, hot-reloadable, and live next to your other agent customisations (skills, prompt templates). No JSON to keep in sync with a config schema.
- Model list is whatever you register. Pi doesn't auto-discover models from the provider endpoint unless your extension does it. The async-factory pattern above is the supported way.
- Tool calling on supported models only. Pi sends OpenAI-shape
tool_callsto our Chat Completions endpoint. Not every open-weight model handles tools — check_meta.capabilities.tool_useonGET /v1/models?include_meta=trueor filter at melious.ai/hub/models. - Custom response fields.
environment_impactandbilling_costride on raw responses but Pi's UI doesn't surface them — they're logged on our side and aggregated in your usage dashboard.
When it breaks
Provider 'melious' not found— the extension didn't load. Check it's in~/.pi/agent/extensions/(or your project's.pi/extensions/), the file ends in.ts, and the default export is a function. Runpi --list-modelsto see what registered.MELIOUS_API_KEY is not set— Pi reads the env var named in theapiKeyfield. Export it, or usepi --env-file <path>if you keep it elsewhere.- Async factory throws on startup — Pi loads extensions before the prompt. If your
fetchagainst/v1/modelsfails (network, bad key), the provider doesn't register. Wrap in try/catch and fall back to a staticmodelsarray if you want graceful degradation. /modelshows nothing under Melious — yourmodelsarray is empty (the filter dropped everything). Inspect what/v1/models?include_meta=truereturned and adjust the filter.
Errors and retry patterns: Errors.