pi-jev-model-router
Route pi prompts to task-appropriate model tiers with TypeSafe Jev typed judgments. Budget-aware, with automatic fallback.
Package details
Install pi-jev-model-router from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-jev-model-router- Package
pi-jev-model-router- Version
0.6.0- Published
- Sep 29, 2026
- Downloads
- 1,045/mo · 292/wk
- Author
- da-vinci-noob
- License
- MIT
- Types
- extension
- Size
- 154.3 KB
- Dependencies
- 0 dependencies · 3 peers
Pi manifest JSON
{
"image": "https://raw.githubusercontent.com/da-vinci-noob/pi-jev-model-router/main/assets/decision-entry.png",
"extensions": [
"./extensions/pi-jev-model-router/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-jev-model-router
A pi extension that routes every prompt to a task-appropriate model using TypeSafe Jev (System One) typed judgments. You type normally; before the turn starts, Jev reads the request and answers four narrow questions, code composes those into a capability tier, applies your budget policy, and pi switches to the matching model.

- Task-aware — planning goes to reasoners, implementation to coding specialists, chat to cheap fast models.
- Budget-aware — daily/monthly caps downgrade tiers automatically instead of overspending.
- Resilient — each tier is a candidate chain; if a model is unavailable or unauthenticated, the next one is used.
- Visible — the transcript records the chosen model and the exact reason (kind, complexity, capability, reasoning, budget pressure).
- Fails open — a missing key, timeout, or unknown model just warns and runs your prompt on the current model.
How it works
you type a prompt
│
▼
Jev (one request, 4 parallel questions)
• task_kind choice: plan / implement / debug / refactor / review / research / explain / operate / write / chat
• complexity score: trivial → architectural
• capability_deserved score: minimal → maximum (price ignored)
• needs_deep_reasoning noul: yes/no probability
│
▼
code composes the decision
demand = 0.55·complexity + 0.45·capability (+ reasoning nudge)
demand = max(demand, kind floor) # planning/review never go cheap
confidence guard → budget guard → availability guard → cache guard
│
▼
pi.setModel(...) + pi.setThinkingLevel(...) → the turn runs on that model
Jev judges the task, code owns the budget. Changing your spend caps never invalidates the judgment, and the judgment stays a pure semantic read of the request.
Requirements
- pi (
@earendil-works/pi-coding-agent) - Node.js 20+
- A TypeSafe API key with access to
jev-latest— https://typesafe.ai
Install
1. Install pi
npm install -g --ignore-scripts @earendil-works/pi-coding-agent
Verify with pi --version. See the
pi quickstart
for other installation methods and authentication options.
2. Install this package
From npm (recommended):
pi install npm:pi-jev-model-router
From a pinned git ref:
pi install git:github.com/da-vinci-noob/pi-jev-model-router@v0.6.0
From a local checkout:
pi install /absolute/path/to/pi-jev-model-router
Try it once without installing:
pi -e npm:pi-jev-model-router
Manage it like any other pi package:
pi list # show installed packages
pi update --extensions # update packages
pi remove npm:pi-jev-model-router
3. Set your TypeSafe API key
export TYPESAFE_API_KEY=...
Add it to your shell profile to persist it. You can also set apiKey directly in
the config file (see below).
4. Use it
Start pi and type a request. Before the turn runs, the router announces the decision:
jev-router → high openai/gpt-5.3-codex
implement · complexity 1.70/3 · capability 1.55/3 · reasoning 0.82 → high
The status bar shows jev-router:<tier> · $spend · %cap, or jev-router:off when
disabled. No configuration is required — sensible defaults are built in.
Commands
| Command | What it does |
|---|---|
/jev-router |
Status: mode, spend, tier chains, kind specialists, last decision |
/jev-router on / off |
Enable/disable routing |
/jev-router mode auto|confirm|notify |
auto switches silently; confirm asks each turn; notify only tells you |
/jev-router budget daily 10 |
Session-only daily cap (persist it in the config file) |
/jev-router budget monthly 150 |
Session-only monthly cap |
/jev-router why |
Re-run Jev on the last prompt and show the full judgment + decision trace |
/jev-router revert |
Switch back to the model that was active before the last auto-switch |
/jev-router suggest |
Rank your scored models into proposed routes / kindModels and print them (see Ranking models from your own scores) |
/jev-router suggest --write |
Same, and save it as a generated config layer that your own config overrides |
/jev-route <text> |
Classify arbitrary text and show the recommendation without switching |
The model can also call the jev_route tool to ask for a tier recommendation for
a subtask.
What you see
Every decision is a durable entry in the transcript, so the chosen model and its justification are always available:
jev-router → high openai/gpt-5.3-codex
implement · complexity 1.70/3 · capability 1.55/3 · reasoning 0.82 → high
· budget 12% of cap → one tier down
The glyph encodes the action: → switched, = already active (stickiness),
• notify-only mode, × skipped. Expand the entry for the raw judgment: kind
and confidence, complexity, capability deserved, deep-reasoning probability,
composed demand, and budget pressure. Entries are stored in the session but never
sent to the LLM, so they cost no context.
Prompts that are deliberately not routed are shown too, so behaviour is never silently missing:
jev-router · not routed
acknowledgement — staying on the current model
using openrouter/~anthropic/claude-opus-latest
Which models it uses by default
The defaults target OpenRouter, because it exposes a large catalogue through a single provider id. Four capability tiers, each an ordered fallback chain:
| Tier | Order tried |
|---|---|
quick |
~google/gemini-flash-latest → ~openai/gpt-luna-latest → ~z-ai/glm-flash-latest → ~deepseek/deepseek-v4-flash-latest |
standard |
~deepseek/deepseek-pro-latest → openai/gpt-5.4-mini → ~z-ai/glm-latest |
high |
~anthropic/claude-sonnet-latest → ~openai/gpt-terra-latest → ~google/gemini-pro-latest → ~x-ai/grok-latest |
premium |
~anthropic/claude-opus-latest → openai/gpt-5.5 → ~openai/gpt-astra-latest |
xpremium |
empty: off until you configure it (see Extra-premium tier) |
Plus kind specialists, tried before the tier chain when the chosen tier is high
enough (minTier):
| Kind | Specialists |
|---|---|
plan |
~anthropic/claude-opus-latest (≥premium) → ~openai/gpt-astra-latest (≥premium) → ~openai/gpt-terra-latest (≥high) → ~google/gemini-pro-latest (≥standard) |
implement |
openai/gpt-5.3-codex (≥standard) → moonshotai/kimi-k2.7-code → ~anthropic/claude-sonnet-latest |
debug |
openai/gpt-5.3-codex (≥standard) → ~openai/gpt-terra-latest (≥high) → ~anthropic/claude-sonnet-latest |
refactor |
openai/gpt-5.3-codex (≥standard) → moonshotai/kimi-k2.7-code |
review |
~anthropic/claude-opus-latest (≥high) → openai/gpt-5.5 (≥high) → ~anthropic/claude-sonnet-latest |
research |
~google/gemini-pro-latest (≥standard) → moonshotai/kimi-k3 → ~openai/gpt-terra-latest |
explain |
~google/gemini-flash-latest (≥quick) → openai/gpt-5.4-mini → ~google/gemini-pro-latest (≥standard) |
operate |
openai/gpt-5.4-mini (≥standard) → ~deepseek/deepseek-pro-latest |
chat |
~google/gemini-flash-latest → ~openai/gpt-luna-latest → ~z-ai/glm-flash-latest |
write |
~google/gemini-flash-latest (≥quick) → openai/gpt-5.4-mini → ~anthropic/claude-sonnet-latest (≥standard) |
Provider-maintained ~...-latest aliases are used wherever they exist, so the
chains follow new model releases instead of going stale.
Run /jev-router to see this for your own setup, with a ✓/✗ per route
showing what is actually available and authenticated.
Extending to more models and providers
The router is provider-agnostic: it only references models that pi already knows about, so if pi can use a model, the router can route to it. You are never limited to OpenRouter — the defaults are just a convenient starting point.
0. Drop the built-in models entirely (optional)
By default your config is merged over the built-in chains, so a tier you don't mention keeps its defaults. If you'd rather start from nothing and use only your own models, set:
{
"useDefaultModels": false,
"routes": {
"quick": [{ "provider": "openrouter", "model": "~z-ai/glm-flash-latest" }],
"high": [{ "provider": "anthropic", "model": "claude-sonnet-4-5" }]
},
"kindModels": {
"implement": [{ "provider": "openrouter", "model": "moonshotai/kimi-k2.7-code", "minTier": "standard" }]
}
}
With useDefaultModels: false:
- the built-in
routesandkindModelsare gone — not available as a base or as fallback; - tiers or kinds you don't configure are empty, and the router simply skips them (it never invents a model);
- everything that isn't a model list still applies —
endpoint, timeouts,budget,cache, and thekindMinimumTierfloors.
/jev-router prints built-in models: off (config-only) and marks empty tiers
as (none configured). If a tier you need is empty, pi warns on session start.
1. Find the model ids pi knows
pi --list-models # all providers
pi --list-models | grep anthropic
pi --list-models | grep -E 'gpt-5|codex'
The first column is the provider id and the second is the model id. Those are exactly the two fields the config uses.
Providers not yet configured can be added with
pi /login, an API key environment variable, or a custom provider registered by another extension — including local servers such as Ollama or llama.cpp. See the providers docs.
2. Point the tiers at your models
Create ~/.pi/agent/pi-jev-model-router.json (or <project>/.pi/pi-jev-model-router.json).
Anything you set is merged over the defaults, per tier.
{
"routes": {
"quick": [
{ "provider": "openrouter", "model": "~google/gemini-flash-latest" }
],
"standard": [
{ "provider": "anthropic", "model": "claude-haiku-4-5" }
],
"high": [
{ "provider": "anthropic", "model": "claude-sonnet-4-5" },
{ "provider": "openai", "model": "gpt-5.4" }
],
"premium": [
{ "provider": "anthropic", "model": "claude-opus-4-5" },
{ "provider": "openai", "model": "gpt-5.5-pro" }
]
}
}
Each tier is a candidate chain, tried top to bottom. The first model that
exists in pi's catalogue and is authenticated wins; if none are usable the
router steps to the nearest tier instead of failing. Add "thinkingLevel" to any
entry to pin it ("off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
pi clamps it per model).
Mixing providers is fine — put an OpenRouter model and a direct-Anthropic model in the same chain.
3. Add or change task specialists
{
"kindModels": {
"implement": [
{ "provider": "openrouter", "model": "openai/gpt-5.3-codex", "minTier": "standard" },
{ "provider": "anthropic", "model": "claude-sonnet-4-5", "minTier": "standard" }
],
"plan": [
{ "provider": "openai", "model": "gpt-5.5", "minTier": "high" },
{ "provider": "anthropic", "model": "claude-opus-4-5", "minTier": "premium" }
]
}
}
minTier gates a model to a minimum capability tier, so a specialist is only
used when the judgment justifies it. When priorities tie (the default), the
eligible specialist whose minTier is closest to the chosen tier wins, so a
cheap specialist does not win a premium-quality turn.
To rank a specialist up without changing when it is eligible, give it a
numeric priority (default 0, higher first). priority is compared before
minTier, so this keeps a free model eligible from quick and still tries it
ahead of a paid high specialist:
{
"kindModels": {
"write": [
{ "provider": "opencode-go", "model": "space-bunny-free", "minTier": "quick", "priority": 1 },
{ "provider": "openrouter", "model": "~anthropic/claude-sonnet-latest", "minTier": "high" }
]
}
}
Without any priority, ordering is exactly as before.
4. Set the floor per task kind
{
"kindMinimumTier": {
"plan": "high",
"review": "high",
"implement": "standard",
"debug": "standard",
"chat": "quick"
}
}
Known kinds: plan, implement, debug, refactor, review, research,
explain, operate, write, chat.
5. Add or change task kinds
Jev picks one kind per prompt from taskKinds: a label and a one-line
description that Jev uses as the choice criteria. Add your own domains or reword
a built-in description in config, then add matching kindModels and
kindMinimumTier entries:
{
"taskKinds": {
"data": "Querying, cleaning, or transforming datasets and SQL",
"infra": "Provisioning or changing cloud infrastructure and deploy config"
},
"kindModels": {
"data": [{ "provider": "openrouter", "model": "~google/gemini-pro-latest", "minTier": "standard" }]
},
"kindMinimumTier": { "infra": "high" }
}
Entries merge over the built-in kinds (global, then project). A kind can't be
removed, and entries without a non-empty string description are ignored.
Because the question is a Jev choice, the option set is the taxonomy: no
retraining, no prompt parsing.
Config resolution order
Later sources win:
- built-in defaults
~/.pi/agent/pi-jev-model-router.generated.json: onlyroutesandkindModels, written by/jev-router suggest --write~/.pi/agent/pi-jev-model-router.json<cwd>/.pi/pi-jev-model-router.json(trusted projects only)- env:
TYPESAFE_API_KEY,JEV_ROUTER_MODE(auto|confirm|notify),JEV_ROUTER_OFF=1
A full example lives at
extensions/pi-jev-model-router/pi-jev-model-router.example.json.
Run /reload after editing config.
Budget management
Cost is accumulated from each assistant message's computed cost into
~/.pi/agent/pi-jev-model-router-state.json, together with Jev request counts.
pressure = max(today ÷ dailyUsd, month ÷ monthlyUsd)
pressure ≥ softRatio(default0.7) → drop one tierpressure ≥ hardRatio(default0.9) → forcequick, unless the demand score is ≥ 2.5 (clearly architectural), which may stay atstandard
{
"budget": {
"dailyUsd": 5,
"monthlyUsd": 100,
"softRatio": 0.7,
"hardRatio": 0.9
}
}
Omit either cap to disable that dimension. Caps are policy, not a hard stop — they redirect routing, they do not block turns.
Prompt-cache awareness
Switching models discards the provider's prompt cache, and caches are per-model. The next request then re-reads the entire prefix — system prompt, tool schemas, and conversation — at the new model's full input rate. Cache reads are ~10% of input on the major providers, so a switch effectively costs the whole context once, and a switch back costs it again. On a 50k context that is roughly $0.10 on Sonnet; at 200k, roughly $0.45.
The router therefore gates switches instead of making them freely:
- Cache penalty cap — estimates the miss (
contextTokens × new model's input rate, minus the cached rate) and refuses the switch when it exceedsmaxPenaltyUsd. - Dead-band — demand has to clear the current tier's band (
tier ± 0.5) bydeadbandbefore a tier change is considered, so prompts hovering on a boundary stop flapping between two models. - Big-jump bypass — a tier jump of
bypassTierDeltaor more still switches, because that is a genuine capability change rather than a marginal one. - Same-tier swaps count too — a specialist swap such as Sonnet → Codex at the same tier is still a model change, and is priced the same way.
Held turns still record the decision, and say so:
jev-router = high openrouter/~anthropic/claude-sonnet-latest
explain · complexity 0.40/3 · capability 0.30/3 · reasoning 0.20 → standard, held on high to keep the cache
· cache penalty ~$0.186 on 120k tokens — keeping the warm cache
The estimate is a lower bound — real cacheable prefixes include the system prompt
and tool schemas, which contextTokens does not count — and it is skipped
entirely when a model's pricing is unknown, so it never blocks on guesses. Set
cache.aware: false to restore unconditional switching.
{
"cache": {
"aware": true,
"deadband": 0.25,
"maxPenaltyUsd": 0.05,
"bypassTierDelta": 2
}
}
Ranking models from your own scores
Instead of maintaining routes by hand, you can give the router a scores file
and let it propose chains from the models pi actually has. The router never
fetches rankings itself; any script or board export that writes this JSON works.
~/.pi/agent/pi-jev-model-router.scores.json:
{
"models": {
"openrouter/~anthropic/claude-opus-latest": { "score": 0.92, "kinds": { "review": 0.95, "plan": 0.9 } },
"openrouter/~google/gemini-flash-latest": { "score": 0.55, "kinds": { "write": 0.7 } },
"grok-cli/grok-4.6": { "score": 0.8, "cost": { "input": 0, "output": 0 } }
}
}
- Keys are
<provider>/<model id>and must match a model in pi's catalogue exactly. Scored models pi doesn't have are listed as not in the catalogue. scoreplaces the model on a tier by fixed cut-offs (inclusive), belowstandardisquick.xpremiumis never filled; set it by hand. Scores and cut-offs just need the same scale (0-1 by default, or e.g. 0-100 with matching cut-offs), and cut-offs that are not ascending are ignored.- Within a tier, models are ordered by score per cost, using pi's catalogue
price (
input + outputUSD per 1M tokens) unless the entry has acostoverride in the same units. Use the override for flat subscriptions or quota limits: set your own effective price, or0to treat the model as free (free models rank first). Models without any known price go last. kindsscores buildkindModelsfor kinds intaskKinds, each gated (minTier) at the tier its kind score clears.- With
spreadProviderson (the default), a chain never has two entries in a row from the same provider when it can be avoided.
{
"ranking": {
"cutoffs": { "standard": 0.5, "high": 0.7, "premium": 0.85 },
"spreadProviders": true,
"scoresFile": "~/.pi/agent/pi-jev-model-router.scores.json"
}
}
/jev-router suggest prints the proposal. /jev-router suggest --write saves it
to ~/.pi/agent/pi-jev-model-router.generated.json and applies it right away.
If none of the scored models match pi's catalogue, --write warns and leaves
the existing generated file untouched.
That file is loaded before your own config, and your config file is never written. So anything you set by hand wins over the generated file:
- a tier or kind you define replaces the generated one
- an explicit empty list clears it, e.g.
"routes": { "high": [] }or"kindModels": { "plan": [] }(a list with only invalid entries is ignored)
Generated specialists carry a descending priority, so routing tries them in the
proposed order. The built-in chains still fill any tier or kind the generated
file doesn't cover. To route only from your scores and hand edits, set
"useDefaultModels": false.
Extra-premium tier
xpremium sits above premium for the most expensive model class (around
$50 / 1M output tokens). It is off until you give it a chain, and the
built-in models never fill it:
{
"routes": {
"xpremium": [{ "provider": "openrouter", "model": "~openai/gpt-astra-latest", "thinkingLevel": "high" }]
}
}
The demand scale is unchanged, so no prompt that routes to premium today
moves up on its own. A turn is promoted to xpremium only when all of these hold:
- demand already lands on
premium - Jev's kind confidence is known and at least
confidenceThreshold - the budget guards don't step it down (soft pressure drops it back to
premium)
Otherwise the xpremium chain is never tried, not even as a fallback when
premium is unavailable. kindMinimumTier: "xpremium" is treated as premium.
A kind specialist with minTier: "xpremium" only serves promoted turns.
Free models
Tiers describe capability, not price, so there is nowhere in TIERS to put a model
that costs nothing. A free pool is consulted outside the tier scale instead, so
the judgment is untouched and a free model never has to pretend to be a tier.
{
"free": {
"enabled": true,
"policy": "prefer",
"pool": [
{ "provider": "opencode-go", "model": "space-bunny-free", "thinkingLevel": "medium" },
{ "provider": "opencode-go", "model": "longcat-2.5-preview-free", "thinkingLevel": "medium" }
]
}
}
policy: "prefer"tries the pool first, then the kind chains and tier chains.policy: "fallback-only"tries the pool last, after every tier chain.enabled: false(the default) ignores the pool entirely, so an existing config routes exactly as before.
Pool entries match on provider and model exactly, so a missing free model never
falls back to a paid model with the same ID. The first pool entry in the model
catalogue wins; one that is missing falls through to the normal chains. When a pool
model wins, the decision still reports the tier Jev judged, plus a
free pool → <model> note, so the transcript shows why the tier and the model
differ.
A pool model is not on any tier, so tierForModel reports no tier for it and the
status line falls back to jev-router:on. That is deliberate: a free model has no
capability tier.
Free models are usually temporary. If a provider withdraws one from the catalogue, routing skips it and continues down the chains, so a stale pool entry costs nothing but a wasted slot. Only the chosen model is tried: if switching to it fails (for example, no auth for the provider), the turn stays on the current model.
Free and preview models may log or train on prompts, and prefer sends every turn
to the pool. Check the provider's data policy before enabling it.
Configuration reference
| Key | Default | Purpose |
|---|---|---|
enabled |
true |
Master switch |
useDefaultModels |
true |
false drops the built-in model chains so only your config's models are used |
mode |
"auto" |
auto | confirm | notify |
apiKeyEnv / apiKey |
TYPESAFE_API_KEY |
TypeSafe credentials |
endpointEnv / endpoint |
TYPESAFE_API_URL / https://api.typesafe.ai/v1/systemone |
TypeSafe-compatible evaluation endpoint (non-empty env wins over endpoint) |
jevModel |
"jev-latest" |
Jev model alias |
timeoutMs |
3500 |
Jev request timeout (retries 429/529) |
minPromptChars |
12 |
Below this, a prompt counts as a continuation (a short first message is still routed) |
historyTurns |
4 |
Conversation turns included as Jev state |
confidenceThreshold |
0.34 |
Below this, fall back to standard instead of spending premium |
stickiness |
true |
Keep the current model when it is already the chosen one |
routes |
see above | Capability tier candidate chains; xpremium is empty (off) unless you set it |
kindModels |
see above | Task-specialist chains with minTier (gate) and optional priority (rank) |
kindMinimumTier |
see above | Per-kind floor tier |
taskKinds |
the 10 kinds above | Kind labels and descriptions Jev chooses from; merged over the built-ins |
ranking |
cut-offs 0.5 / 0.7 / 0.85, spreadProviders: true |
Scores file and cut-offs for /jev-router suggest |
free |
disabled | Free-model pool consulted outside the tier scale (prefer or fallback-only) |
budget |
no caps | Spend policy |
cache |
aware, cap $0.05, deadband 0.25 |
Prompt-cache-aware switching |
stateFile |
~/.pi/agent/pi-jev-model-router-state.json |
Spend ledger |
Failure behaviour
Routing never blocks your turn. A missing key, network error, timeout (default
3.5 s, retried on 429/529), or unknown model means: warn in the status line and
run the prompt on the current model unchanged. Prompts starting with /, pure
acknowledgements (yes, continue, …), and messages sent by other extensions
are never routed.
Publishing to pi.dev/packages
The pi package gallery is built from npm: it lists
packages that are tagged with the pi-package keyword. There is no separate
submission form — publishing to npm is the submission.
This repository is already prepared for it:
package.jsoncontains"keywords": ["pi-package", ...]package.jsoncontains apimanifest pointing at the extension entry pointpackage.jsoncontainspi.image, which the gallery uses as the preview card
To publish:
# 1. Log in to npm (once)
npm login
# 2. Sanity-check what will be shipped
npm pack --dry-run
# 3. Publish
npm publish --access public
Then:
- The gallery indexes it on its next crawl (usually minutes; allow a few hours).
- Check the listing at
https://pi.dev/packages/pi-jev-model-router. - Anyone can then install it with
pi install npm:pi-jev-model-router.
Releases: bump version in package.json, commit, tag, and re-run
npm publish. Keep the image URL pointed at a released tag or main so it
never 404s.
If the name is taken, publish under a scope (@yourname/pi-jev-model-router)
— the gallery indexes scoped packages too; install with
pi install npm:@yourname/pi-jev-model-router.
Git-only distribution also works (pi install git:github.com/user/repo@v1),
but only npm packages appear in the gallery.
You can also add a GIF or MP4 demo via pi.video (MP4 only, takes precedence
over image).
Development
git clone https://github.com/da-vinci-noob/pi-jev-model-router
cd pi-jev-model-router
# load the package into a throwaway pi run (ignores auto-discovered extensions)
pi -ne -e "$PWD" -p "Explain what an idempotency key does."
# or copy into the auto-discovered location for hot reload
cp -R extensions/pi-jev-model-router ~/.pi/agent/extensions/
Tests and typecheck (the same checks CI runs on every PR):
bun install
bun run typecheck # tsc against the real pi ExtensionAPI types
bun test # router, config, Jev client, budget, and extension load/route tests
Tests stub fetch, so they never hit the network. Tests that load config mock
os.homedir to a temp dir, so your real ~/.pi config doesn't affect them.
Layout:
| File | Role |
|---|---|
extensions/pi-jev-model-router/index.ts |
pi wiring: events, commands, jev_route tool, model switching, transcript entries |
extensions/pi-jev-model-router/config.ts |
config types, defaults, layered loading, task taxonomy |
extensions/pi-jev-model-router/jev.ts |
TypeSafe HTTP client, question definitions, response parsing |
extensions/pi-jev-model-router/router.ts |
composition (decide), tier/kind chains, availability fallback |
extensions/pi-jev-model-router/budget.ts |
spend ledger, caps, pressure |
extensions/pi-jev-model-router/ranking.ts |
scores file, tier cut-offs, provider spread for /jev-router suggest |
No runtime dependencies: the extension talks to TypeSafe with plain fetch. It
imports typebox (tool schema) and @earendil-works/pi-coding-agent (config
directory path), and loads @earendil-works/pi-tui lazily, only when the host
implements registerEntryRenderer. @earendil-works/pi-tui is declared as an
optional peer dependency, so hosts that don't ship it still install and run.
Compatibility with pi builds and forks
ExtensionAPI surfaces differ across pi versions and downstream forks (for
example omp). The extension probes the host at load time and degrades instead
of failing installation:
| Capability | If the host lacks it |
|---|---|
registerEntryRenderer or @earendil-works/pi-tui |
No transcript card; decisions still show in the status bar and notifications |
appendEntry |
Decisions are not persisted as session entries |
ctx.ui.notify / ctx.ui.setStatus |
Silently skipped |
ctx.ui.select |
confirm mode falls back to auto-switching |
ctx.modelRegistry.find / getAvailable |
Reports "model not available in this build" and leaves the current model in place |
registerCommand / registerTool |
Commands and the tool are not registered; event-driven routing still works |
Nothing in the extension throws during load if an optional API is missing, so
pi install, omp install, or any plugin validator will accept it.
License
MIT
