pi-star-router
StarRouter is a focused model-routing extension for Pi. It chooses the best available model and thinking level for each prompt using a deterministic heuristic prompt profiler, Artificial Analysis benchmark data, provider-scoped model pools, strict model i
Package details
Install pi-star-router from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-star-router- Package
pi-star-router- Version
1.1.1- Published
- Jul 19, 2026
- Downloads
- 191/mo · 153/wk
- Author
- ketchh
- License
- MIT
- Types
- extension
- Size
- 316.3 KB
- Dependencies
- 0 dependencies · 3 peers
Pi manifest JSON
{
"extensions": [
"./index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
StarRouter for Pi
StarRouter 1.1.1 is a local, deterministic model-routing extension for Pi. Before a turn, it profiles the prompt, compares benchmark-backed candidates inside one configured provider, and chooses a model plus supported thinking level. It asks for confirmation by default and can abstain when evidence is weak.
There is no remote classifier and no cross-provider fallback. Artificial Analysis data supplies model measurements; prompt text is never sent there.
Requirements and installation
- Node.js
>=22.19.0 - Pi
>=0.80.6(verified with0.80.6through0.80.10)
pi install npm:pi-star-router
Then reload Pi and opt in:
/reload
/router settings
/router on
For a one-off run:
pi -e npm:pi-star-router
Routing is off by default.
Command surface
| Command | Behavior |
|---|---|
/router status |
Reports enabled/disabled state and, when enabled, the latest decision. |
/router on |
Enables routing for subsequent turns in the active session branch. |
/router off |
Disables routing and removes the ambient decision widget. |
/router refresh |
Forces an Artificial Analysis dataset refresh. |
/router settings |
Opens transactional settings in TUI mode. |
Runtime pipeline
- Local prompt profile. English/Italian dictionaries and structural signals detect mechanical transforms, simple output, coding, debugging, architecture, security, research, agentic/tool work, structured output, long context, and vision. Mixed intents are additive: formatting language cannot erase a debugging or security signal.
- Task requirements. The profile sets benchmark weights, context/vision needs, cost and speed sensitivity, a routing tier, and a target thinking level.
- Provider scope. Only currently available text models from
strategy.routingProviderare eligible. An unavailable configured provider produces no silent fallback to another provider. - Candidate generation. Provider filters are applied, then candidates are created only for thinking variants supported by Pi/model metadata.
- Identity match. A Pi model may borrow an Artificial Analysis row only after vendor, family/subfamily, generation/version, moving-alias, and reasoning-class checks. Host-compatible rows are
host-verified. Because AA may not benchmark an aggregator itself, a small explicit allowlist (including OpenRouter) may use an otherwise identity-compatible row asmodel-only; this never relaxes identity gates. - Normalization and ranking. Qualified host/deployment identity is preserved, and true duplicates select one coherent source row rather than combining the best field from each row. Host-verified evidence can contribute AA price, output speed, latency, and context fallback. Token burn is retained as diagnostic evidence but does not affect ranking. Model-only evidence contributes benchmark/model quality only; economics/context must come from Pi metadata and host performance is unavailable. Latency uses end-to-end evidence pool-wide when any candidate has it; TTFT is used only when the entire pool lacks end-to-end measurements.
balancedranks by composite;quality,cheapest, andfastestrank lexicographically by their named primary metric with deterministic tie-breakers. Cheapest/fastest abstain when their primary evidence is absent. - Safety gates. Vision, context capacity, task thinking floors/caps, minimum AA identity match, and a relative quality floor are hard constraints. Standard weak Pareto filtering removes routes that are no better on any observed dimension. Low overall confidence causes abstention unless a trustworthy current route can be kept.
- Hysteresis. The current route is retained when its objective score is within
preferCurrentWithinof the winner, reducing model flapping. - Confirmation and explanation. Auto-accept is off by default. Decisions keep the deterministic recommendation and its basis separate from the applied route and its origin, so a manual alternative cannot be reported as the algorithmic winner. A compact widget records both identities when they differ.
See docs/algorithm.md for the ranking invariants and limitations.
Route confirmation and runtime modes
TUI
The confirmation overlay focuses the current recommendation. Controls are:
↑ / ↓ move focus
Enter confirm the focused route
Esc cancel and keep the current route
There is no separate Space-to-mark step. The layout degrades to a compact view on small terminals. The decision widget above the editor is capped at four lines.
RPC
Route confirmation uses Pi's serializable ctx.ui.select() surface. Decision widgets are plain text lines rather than TUI component factories, so RPC clients receive useful rationale.
Print/headless
/router status prints a plain text result. Transactional settings require TUI and direct users to edit JSON otherwise. If confirmation is required but no interactive UI exists, StarRouter conservatively keeps the current route. Set global ui.autoAcceptRouting only when unattended switching is intentional.
Transactional settings and filters
Open /router settings in TUI mode. All edits stay in an in-memory draft until:
Ctrl+S save the selected global or project draft
Esc cancel without writing
Switching the save target keeps independent global and project drafts. In project scope, global-only controls are visibly inherited and read-only.
The model-filter screen is also transactional:
Enter toggle the focused family/model
Ctrl+S apply the filter draft to the settings draft
Esc cancel filter edits
Ctrl+Shift+S save a named preset without applying the draft
Built-in presets (benchmark-safe, frontier-safe, coding-safe, budget-safe) are templates, not locks. Configuration and saved-preset reads are byte/count/string bounded; malformed, oversized, or prototype-sensitive project JSON is ignored rather than merged.
Configuration and trust boundary
Configuration is loaded from:
- global:
~/.pi/agent/model-router.json - project:
.pi/model-router.json
model-router.json.example is a complete global example. It includes privileged controls and data-source settings. model-router.project.json.example contains only project-safe fields.
Global configuration can control
enabledstrategy.routingProviderui.autoAcceptRouting- all
dataSourcefields, including endpoint and optional API-key environment variable - all project-safe fields below
Project configuration can control only
strategy.objectivestrategy.qualityFloorstrategy.preferCurrentWithinstrategy.minAaMatchstrategy.minRouteConfidenceui.showAdvancedSettingsfiltersmodelOverrides
Project attempts to set enabled, strategy.routingProvider, ui.autoAcceptRouting, or dataSource are ignored. This prevents a checked-out repository from enabling routing, changing providers, auto-accepting switches, redirecting benchmark traffic, or selecting secret headers.
Dataset, network, and cache behavior
StarRouter's only core network dependency is benchmark metadata:
- it requests the configured Artificial Analysis API path;
- if API parsing/fetching fails, it can parse the configured Artificial Analysis page fallback;
- a validated cache is stored atomically at
~/.pi/agent/cache/star-router-public.json; - a fresh validated cache is preferred, and a stale validated cache may be used if both network paths fail; failure to persist an already validated network snapshot is non-fatal for the current run;
/router refreshbypasses a fresh disk cache and reports whether it reached the network or retained a validated stale fallback;- responses, caches, model/profile counts, and external strings are bounded and validated before routing;
- unknown optional AA prompt buckets are ignored while recognized routing profiles remain strict;
- if no trustworthy dataset is available, routing is skipped and the current route is kept.
Fetches are single-flight and bounded by requestTimeoutMs. Dataset/cache rows are validated before use, and obsolete generations cannot overwrite a newer cache. Qualified host labels such as Google (AI Studio), Google (Vertex), and DeepInfra (Turbo) remain distinct. Deployment grouping includes hostApiId, but that field can never certify host economics; host proof uses exact normalized hostLabel/hostSlug aliases. A direct-provider host mismatch is rejected. For the explicit aggregator allowlist only, a hosted mismatch becomes degraded model-only evidence: AA host price/speed/E2E/TTFT/token-burn/context fallback are suppressed, confidence is reduced, and rationale says so. Unhosted page-scrape rows use the same model-only scope. Secret headers are attached only to the official https://artificialanalysis.ai origin; a custom global endpoint does not receive them.
Artificial Analysis is an external source whose page/API schema and measurements may change. /router refresh is therefore an operational refresh, not a guarantee that every provider model will match.
Privacy and persisted state
- Prompt profiling runs locally and deterministically.
- No prompt text, images, Pi conversation content, provider tokens, or model responses are sent to Artificial Analysis.
- There are zero classifier-model calls.
- Session entries persist only enabled state and compact route-decision summaries; recommendation basis and application origin are separate, and cancellation persists a
nullclear marker so an old widget is not resurrected. - The public dataset cache contains benchmark/model metadata, not prompts.
- StarRouter reads provider availability/auth status from Pi but does not log or persist provider secrets.
See SECURITY.md for the full trust model and reporting path.
Reproducible validation
The default suite is deliberately offline. Its preload denies Fetch/WebSocket and the standard in-process Node HTTP(S), HTTP/2, TCP, TLS, UDP, and DNS entry points unless a test replaces fetch() with a local deterministic stub. This is a regression guard, not an operating-system sandbox: it intentionally does not claim to contain malicious child processes or native code.
npm test # alias for test:offline
npm run test:offline # 126 deterministic offline tests
npm run golden # 80/80 checked-in golden prompts
npm run typecheck
npm run smoke
npm audit --audit-level=low
npm pack --dry-run --json
Current release targets: 126 offline tests, 80/80 golden prompts, strict TypeScript, release smoke checks, audit, package-content inspection, and Pi 0.80.6–0.80.10 compatibility CI.
Live OpenRouter catalog observations are opt-in and are not a PR gate:
npm run test:live # 8 tests, one shared request per external source/file
npm run test:all # offline first, then live
The live suite makes two external requests (one shared OpenRouter request and one shared Artificial Analysis request) to check current catalog/schema shape, representative IDs, parseable prices, and model-only aggregator compatibility. It intentionally makes no universal savings claim. To evaluate cost for your workload, record the observation timestamp, exact provider/model IDs, input/output/cache token counts, provider fees, and the baseline selection policy; then rerun the comparison when catalog prices change.
Troubleshooting
Router status → disabled
Run /router on, or save enabled: true in the global file. Project config cannot enable routing.
Configured provider shows unavailable
Authenticate/configure that provider in Pi or change the global routing provider. StarRouter will not borrow another provider silently.
No trustworthy route
The provider may have no benchmarkable available model, an identity/host gate may reject the match, or confidence may be below the configured floor. Keep the current route, inspect filters/overrides, and run /router refresh.
Dataset refresh fails
Check connectivity, the configured timeout, and the Artificial Analysis endpoint. A validated stale cache is used when available; otherwise routing abstains.
Settings unavailable in RPC/print
Edit the appropriate JSON file. Keep privileged fields in the global file.
A model name does not match AA
Add a narrow modelOverrides slug pin after verifying creator, exact version, evidence scope, and reasoning class. The pin resolves alias naming only; contradictory or missing fixed identity evidence is still rejected. A versioned moving alias can be pinned explicitly, with reduced confidence. For aggregator model-only evidence, AA host economics remain suppressed even when pinned.
Repository map
index.ts Pi lifecycle, commands, runtime routing
src/router-core.ts config trust boundary, AA ingestion, ranking
src/prompt-understanding.ts deterministic EN/IT prompt profiler
src/route-choice-screen.ts responsive confirmation and widgets
src/settings-screen.ts transactional settings UI
src/model-filters-screen.ts transactional provider/model filters
src/filter-presets/ built-in and saved presets
docs/algorithm.md algorithm invariants and limitations
docs/index.html self-contained public project page
tests/*.test.ts deterministic offline release suite
tests/live/*.live.test.ts explicit network observations
model-router.json.example complete global example
model-router.project.json.example project-safe example
Release notes and license
See CHANGELOG.md for 1.1.1 fixes and the original 1.0.0 migration notes. Licensed under MIT.