From 46bb5dcde8a7eb8aa0a94ba387310c6a8afca232 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 1 Jan 2026 02:04:04 +0100 Subject: [PATCH] fix: enabledModels now supports glob patterns for OAuth providers Added glob pattern support (e.g., github-copilot/*, *sonnet*) to --models and enabledModels. Patterns are matched against both provider/modelId and just modelId, so *sonnet* works without requiring anthropic/*sonnet*. The existing fuzzy substring matching for non-glob patterns is preserved. fixes #337 --- packages/coding-agent/CHANGELOG.md | 1 + packages/coding-agent/README.md | 9 +++-- packages/coding-agent/src/cli/args.ts | 6 +++- .../coding-agent/src/core/model-resolver.ts | 36 +++++++++++++++++++ 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 189a3ece..88a79802 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -211,6 +211,7 @@ Total color count increased from 46 to 50. See [docs/theme.md](docs/theme.md) fo - **Session file validation**: `findMostRecentSession()` now validates session headers before returning, preventing non-session JSONL files from being loaded - **Compaction error handling**: `generateSummary()` and `generateTurnPrefixSummary()` now throw on LLM errors instead of returning empty strings - **Compaction with branched sessions**: Fixed compaction incorrectly including entries from abandoned branches, causing token overflow errors. Compaction now uses `sessionManager.getPath()` to work only on the current branch path, eliminating 80+ lines of duplicate entry collection logic between `prepareCompaction()` and `compact()` +- **enabledModels glob patterns**: `--models` and `enabledModels` now support glob patterns like `github-copilot/*` or `*sonnet*`. Previously, patterns were only matched literally or via substring search. ([#337](https://github.com/badlogic/pi-mono/issues/337)) ## [0.30.2] - 2025-12-26 diff --git a/packages/coding-agent/README.md b/packages/coding-agent/README.md index 58278f2f..2f734254 100644 --- a/packages/coding-agent/README.md +++ b/packages/coding-agent/README.md @@ -497,7 +497,7 @@ Global `~/.pi/agent/settings.json` stores persistent preferences: "defaultProvider": "anthropic", "defaultModel": "claude-sonnet-4-20250514", "defaultThinkingLevel": "medium", - "enabledModels": ["claude-sonnet", "gpt-4o", "gemini-2.5-pro:high"], + "enabledModels": ["anthropic/*", "*gpt*", "gemini-2.5-pro:high"], "queueMode": "one-at-a-time", "shellPath": "C:\\path\\to\\bash.exe", "hideThinkingBlock": false, @@ -529,7 +529,7 @@ Global `~/.pi/agent/settings.json` stores persistent preferences: | `defaultProvider` | Default model provider | - | | `defaultModel` | Default model ID | - | | `defaultThinkingLevel` | Thinking level: `off`, `minimal`, `low`, `medium`, `high`, `xhigh` | - | -| `enabledModels` | Model patterns for cycling (same as `--models` CLI flag) | - | +| `enabledModels` | Model patterns for cycling. Supports glob patterns (`github-copilot/*`, `*sonnet*`) and fuzzy matching. Same as `--models` CLI flag | - | | `queueMode` | Message queue mode: `all` or `one-at-a-time` | `one-at-a-time` | | `shellPath` | Custom bash path (Windows) | auto-detected | | `hideThinkingBlock` | Hide thinking blocks in output (Ctrl+T to toggle) | `false` | @@ -788,7 +788,7 @@ pi [options] [@files...] [messages...] | `--session-dir ` | Directory for session storage and lookup | | `--continue`, `-c` | Continue most recent session | | `--resume`, `-r` | Select session to resume | -| `--models ` | Comma-separated patterns for Ctrl+P cycling (e.g., `sonnet:high,haiku:low`) | +| `--models ` | Comma-separated patterns for Ctrl+P cycling. Supports glob patterns (e.g., `anthropic/*`, `*sonnet*:high`) and fuzzy matching (e.g., `sonnet,haiku:low`) | | `--tools ` | Comma-separated tool list (default: `read,bash,edit,write`) | | `--thinking ` | Thinking level: `off`, `minimal`, `low`, `medium`, `high` | | `--hook ` | Load a hook file (can be used multiple times) | @@ -840,6 +840,9 @@ pi --provider openai --model gpt-4o "Help me refactor" # Model cycling with thinking levels pi --models sonnet:high,haiku:low +# Limit to specific provider with glob pattern +pi --models "github-copilot/*" + # Read-only mode pi --tools read,grep,find,ls -p "Review the architecture" diff --git a/packages/coding-agent/src/cli/args.ts b/packages/coding-agent/src/cli/args.ts index dda8b805..7094ab54 100644 --- a/packages/coding-agent/src/cli/args.ts +++ b/packages/coding-agent/src/cli/args.ts @@ -158,7 +158,8 @@ ${chalk.bold("Options:")} --session Use specific session file --session-dir Directory for session storage and lookup --no-session Don't save session (ephemeral) - --models Comma-separated model patterns for quick cycling with Ctrl+P + --models Comma-separated model patterns for Ctrl+P cycling + Supports globs (anthropic/*, *sonnet*) and fuzzy matching --tools Comma-separated list of tools to enable (default: read,bash,edit,write) Available: read, bash, edit, write, grep, find, ls --thinking Set thinking level: off, minimal, low, medium, high, xhigh @@ -196,6 +197,9 @@ ${chalk.bold("Examples:")} # Limit model cycling to specific models ${APP_NAME} --models claude-sonnet,claude-haiku,gpt-4o + # Limit to a specific provider with glob pattern + ${APP_NAME} --models "github-copilot/*" + # Cycle models with fixed thinking levels ${APP_NAME} --models sonnet:high,haiku:low diff --git a/packages/coding-agent/src/core/model-resolver.ts b/packages/coding-agent/src/core/model-resolver.ts index 981f11f2..d2124413 100644 --- a/packages/coding-agent/src/core/model-resolver.ts +++ b/packages/coding-agent/src/core/model-resolver.ts @@ -5,6 +5,7 @@ import type { ThinkingLevel } from "@mariozechner/pi-agent-core"; import { type Api, type KnownProvider, type Model, modelsAreEqual } from "@mariozechner/pi-ai"; import chalk from "chalk"; +import { minimatch } from "minimatch"; import { isValidThinkingLevel } from "../cli/args.js"; import type { ModelRegistry } from "./model-registry.js"; @@ -172,6 +173,41 @@ export async function resolveModelScope(patterns: string[], modelRegistry: Model const scopedModels: ScopedModel[] = []; for (const pattern of patterns) { + // Check if pattern contains glob characters + if (pattern.includes("*") || pattern.includes("?") || pattern.includes("[")) { + // Extract optional thinking level suffix (e.g., "provider/*:high") + const colonIdx = pattern.lastIndexOf(":"); + let globPattern = pattern; + let thinkingLevel: ThinkingLevel = "off"; + + if (colonIdx !== -1) { + const suffix = pattern.substring(colonIdx + 1); + if (isValidThinkingLevel(suffix)) { + thinkingLevel = suffix; + globPattern = pattern.substring(0, colonIdx); + } + } + + // Match against "provider/modelId" format OR just model ID + // This allows "*sonnet*" to match without requiring "anthropic/*sonnet*" + const matchingModels = availableModels.filter((m) => { + const fullId = `${m.provider}/${m.id}`; + return minimatch(fullId, globPattern, { nocase: true }) || minimatch(m.id, globPattern, { nocase: true }); + }); + + if (matchingModels.length === 0) { + console.warn(chalk.yellow(`Warning: No models match pattern "${pattern}"`)); + continue; + } + + for (const model of matchingModels) { + if (!scopedModels.find((sm) => modelsAreEqual(sm.model, model))) { + scopedModels.push({ model, thinkingLevel }); + } + } + continue; + } + const { model, thinkingLevel, warning } = parseModelPattern(pattern, availableModels); if (warning) {