Model Scoping and Resolution Using Provider Patterns in pi-mono
pi-mono implements a provider-centric registry in packages/coding-agent/src/core/model-resolver.ts that enables flexible LLM discovery through pattern matching, glob syntax, and automatic fallback resolution across multiple vendors.
The pi-mono codebase abstracts vendor-specific LLM implementations behind a unified resolution layer. By treating providers as namespaces and supporting expressive selection patterns, the system allows users to specify models via shorthand strings like anthropic/claude-opus-4-6:high or glob patterns like google/*:low. This article examines the core resolution algorithms, scoping mechanisms, and provider patterns that power this abstraction.
Core Concepts of the Provider Pattern
The provider pattern in pi-mono centers on three architectural pillars: default model mappings, expressive pattern parsing, and thinking-level extraction. These components work together in model-resolver.ts to transform user input into concrete ScopedModel instances.
Default Model Per Provider
The system maintains a hard-coded fallback map called defaultModelPerProvider (lines 13-38) that supplies sensible defaults when users specify only a provider name. This ensures that openai alone resolves to a working model without requiring users to memorize exact model IDs.
When resolution occurs, the algorithm checks this map after attempting explicit pattern matches, creating a seamless fallback chain that prioritizes user intent while guaranteeing functionality.
Pattern Parsing and Matching
The parseModelPattern function (lines 27-84) implements a sophisticated parser that handles provider-prefixed identifiers, glob wildcards, and nested colons within model IDs. The parser recognizes the provider/model-id:thinking-level structure while correctly handling edge cases like model IDs that contain colons.
Pattern matching occurs through the tryMatchModel helper (lines 62-94), which implements a four-tier fallback strategy:
- Exact
provider/idstring equality - Case-insensitive ID equality
- Substring matching against
idornamefields - Glob pattern matching against
provider/idor rawid
The system prefers alias models (those marked with isAlias or lacking date suffixes) over dated releases, ensuring users receive stable API versions by default.
Thinking Level Extraction
The resolver extracts optional reasoning intensity via the suffix after the final colon, mapping strings like low, medium, high, or off to the ThinkingLevel enum. Invalid suffixes trigger warnings and are stripped rather than causing resolution failures, maintaining robustness against typos.
The Model Resolution Pipeline
The resolution pipeline transforms abstract patterns into concrete model configurations through three primary entry points: scope resolution for configuration files, CLI resolution for command-line flags, and initial model selection for session startup.
Resolving Model Scopes from Patterns
The resolveModelScope function (lines 95-132) serves as the main entry point for configuration-driven resolution. It accepts an array of pattern strings and a ModelRegistry instance, returning an array of ScopedModel objects.
The implementation performs several critical operations:
- Deduplicates models that match multiple patterns
- Validates that glob patterns resolve to at least one concrete model
- Associates thinking levels with specific model instances
- Filters the registry through
ModelRegistry.getAvailable()to ensure API keys are present
This scoping mechanism enables complex configurations like ["anthropic/claude-opus-4-6:high", "google/*:low"] to resolve to multiple concrete models with varying reasoning intensities.
CLI-Specific Resolution
The resolveCliModel function (lines 66-124) handles command-line flag parsing with stricter validation than the general scoping logic. It processes --provider and --model arguments, applying the same pattern parsing but with explicit error handling for invalid thinking levels.
Unlike resolveModelScope, which silently ignores invalid suffixes, the CLI resolver treats malformed thinking levels as fatal errors, ensuring users receive immediate feedback on typos in interactive usage.
Initial Model Selection
The findInitialModel function (lines 71-119) implements the session startup logic that determines which model to use when beginning a conversation. It establishes a hierarchy of configuration sources:
- CLI arguments (
--provider/--model) - Previously scoped models from configuration files
- Saved default preferences
- First available model with valid API credentials
This cascading priority ensures that explicit user overrides take precedence while maintaining sensible fallbacks for new sessions.
Practical Implementation Examples
The following examples demonstrate how to interact with the resolution system in real-world scenarios.
Resolving Configuration Patterns
This example shows how to resolve multiple model patterns from a configuration file:
import { resolveModelScope } from "@mariozechner/pi-coding-agent";
import { ModelRegistry } from "@mariozechner/pi-ai";
async function getScopedModels() {
const registry = new ModelRegistry(); // reads models.json
const patterns = [
"anthropic/claude-opus-4-6:high", // explicit provider + thinking level
"google/*:low", // glob across all Google models
"*sonnet*:medium" // any provider, alias containing “sonnet”
];
const scoped = await resolveModelScope(patterns, registry);
console.log(scoped);
// Returns: Array<{ model: Model, thinkingLevel?: ThinkingLevel }>
}
CLI Argument Resolution
This example mirrors how the pi CLI handles --provider and --model flags:
import { resolveCliModel } from "@mariozechner/pi-coding-agent";
import { ModelRegistry } from "@mariozechner/pi-ai";
function parseArgs() {
const cliProvider = process.argv.includes("--provider")
? process.argv[process.argv.indexOf("--provider") + 1]
: undefined;
const cliModel = process.argv.includes("--model")
? process.argv[process.argv.indexOf("--model") + 1]
: undefined;
const result = resolveCliModel({
cliProvider,
cliModel,
modelRegistry: new ModelRegistry(),
});
if (result.error) {
console.error(result.error);
process.exit(1);
}
console.log("Using", result.model?.provider + "/" + result.model?.id);
}
Session Initialization
This example demonstrates the startup logic that selects the initial model for a coding session:
import { findInitialModel } from "@mariozechner/pi-coding-agent";
import { ModelRegistry } from "@mariozechner/pi-ai";
async function startSession() {
const registry = new ModelRegistry();
const initial = await findInitialModel({
cliProvider: undefined,
cliModel: undefined,
scopedModels: [], // could be filled from a .pi file
isContinuing: false,
defaultProvider: "openai",
defaultModelId: "gpt-5.1-codex",
defaultThinkingLevel: "medium",
modelRegistry: registry,
});
if (!initial.model) {
console.error("No model could be selected.");
process.exit(1);
}
console.log(`Starting with ${initial.model.provider}/${initial.model.id}`);
}
Summary
- Provider-centric architecture – pi-mono organizes models by vendor in
packages/coding-agent/src/core/model-resolver.ts, enabling shorthand notation likeanthropic/claude-opus-4-6:high. - Hierarchical resolution – The system cascades through exact matches, substring searches, and glob patterns, preferring stable aliases over dated releases.
- Thinking level extraction – Colon suffixes (
:high,:low) are parsed asThinkingLevelenums and attached toScopedModelinstances. - Three entry points –
resolveModelScopehandles configuration files,resolveCliModelmanages strict CLI validation, andfindInitialModelorchestrates session startup priorities. - Vendor-agnostic abstraction – New providers require only updates to
defaultModelPerProviderandModelRegistry, leaving resolution logic unchanged.
Frequently Asked Questions
How does pi-mono handle ambiguous model patterns like wildcards?
The resolveModelScope function in packages/coding-agent/src/core/model-resolver.ts expands glob patterns such as google/* or *sonnet* against the available model registry. It validates that each pattern resolves to at least one concrete model, deduplicates results when multiple patterns match the same model, and returns an array of ScopedModel objects ready for use.
What happens if I specify an invalid thinking level in a model string?
The behavior depends on the resolution context. When using resolveModelScope for configuration files, invalid suffixes after the colon trigger a warning and are stripped, allowing resolution to continue with the matched model but no thinking level. However, resolveCliModel treats invalid thinking levels as fatal errors, immediately returning an error object to prevent silent misconfiguration in interactive CLI usage.
Can I use provider shorthand without specifying an exact model ID?
Yes. The defaultModelPerProvider map (lines 13-38) defines fallback models for every known provider. If you specify only a provider name like anthropic or openai without a model ID, the resolver automatically selects the default model for that vendor. This enables quick experimentation without requiring memorization of specific model identifiers.
How does pi-mono prioritize which model to use when multiple sources are configured?
The findInitialModel function implements a strict priority hierarchy: command-line arguments (--provider/--model) take precedence, followed by models scoped from configuration files, then saved user defaults, and finally the first available model with valid API credentials. This cascading approach ensures explicit user intent always overrides implicit defaults while maintaining reliable fallbacks for new sessions.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →