pi-model-sort 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,6 +23,7 @@ Pi's `/model` selector sorts models alphabetically by provider. If you have Anth
23
23
 
24
24
  - **Automatic tracking** — every `/model` switch, `Ctrl+P` cycle, and session restore is recorded with a Unix timestamp
25
25
  - **Sort order** — current model first → most recently used descending → provider/id alphabetical fallback
26
+ - **MRU on startup** — new sessions start on your most recently used model instead of `scopedModels[0]` or the hardcoded provider default order
26
27
  - **Persistent** — usage data lives in `~/.pi/agent/extensions/pi-model-sort.json`, survives restarts
27
28
  - **No config needed** — install and forget; the extension starts tracking on first use
28
29
  - **Zero setup** — with no recorded usage, models fall back to the default alphabetical order
@@ -99,7 +100,7 @@ model_select event fires
99
100
  → Writes to pi-model-sort.json
100
101
  → Next /model opens with updated sort
101
102
 
102
- Session starts
103
+ Session starts (startup / new)
103
104
  → Extension reads pi-model-sort.json
104
105
  → Monkey-patches ModelSelectorComponent.prototype:
105
106
  sortModels — sorts "Scope: all" view
@@ -108,9 +109,12 @@ Session starts
108
109
  → Monkey-patches AgentSession.prototype._cycleScopedModel
109
110
  → Sort order: current model first → most recent → provider/id alphabetical
110
111
  → Patches survive modelRegistry.refresh()
112
+ → Overrides initial model to MRU via pi.setModel()
113
+ if pi core chose a different model (scopedModels[0], defaultModelPerProvider)
114
+ only on startup and new session starts — not resume, reload, or fork
111
115
  ```
112
116
 
113
- **Five patches, full coverage:**
117
+ **Five patches + MRU startup override, full coverage:**
114
118
 
115
119
  | Patch | What it affects |
116
120
  |-------|----------------|
@@ -119,6 +123,7 @@ Session starts
119
123
  | `AgentSession.prototype._cycleScopedModel` | `Ctrl+P` / `Ctrl+Shift+P` cycling order (non-destructive swap, cycling does not update last-used to avoid feedback loop) |
120
124
  | `ModelRegistry.prototype.getAvailable()` | `/scoped-models` config selector, model resolution |
121
125
  | `ModelRegistry.prototype.getAll()` | `--list-models` CLI output |
126
+ | `pi.setModel()` on `session_start` | MRU model selection on startup and `/new` — overrides pi core's default |
122
127
 
123
128
  When no scoped models are configured, Ctrl+P falls through to `_cycleAvailableModel` which calls `getAvailable()` — already sorted by the registry patch.
124
129
 
package/model-sort.ts CHANGED
@@ -26,6 +26,7 @@ import {
26
26
  buildModelKey,
27
27
  CONFIG_FILENAME,
28
28
  type ModelSortConfig,
29
+ parseModelKey,
29
30
  sortByLastUsed,
30
31
  } from "./src/index.js";
31
32
 
@@ -302,12 +303,32 @@ function unpatchCycleScopedModel(): void {
302
303
  origCycleScopedModel = null;
303
304
  }
304
305
 
306
+ // MRU model lookup — finds the most recently used model that exists in the
307
+ // registry and has auth configured. Returns undefined if no usable model found.
308
+
309
+ function findMruModel(
310
+ lastUsed: Record<string, number>,
311
+ registry: { find(provider: string, modelId: string): unknown; hasConfiguredAuth(model: unknown): boolean },
312
+ ): unknown | undefined {
313
+ const sorted = Object.entries(lastUsed).sort(([, a], [, b]) => b - a);
314
+ for (const [key] of sorted) {
315
+ const parsed = parseModelKey(key);
316
+ if (!parsed) continue;
317
+ const [provider, modelId] = parsed;
318
+ const model = registry.find(provider, modelId);
319
+ if (model && registry.hasConfiguredAuth(model)) {
320
+ return model;
321
+ }
322
+ }
323
+ return undefined;
324
+ }
325
+
305
326
  // Extension
306
327
 
307
328
  export default function (pi: ExtensionAPI) {
308
329
  let lastUsed: Record<string, number> = {};
309
330
 
310
- pi.on("session_start", async (_event, ctx) => {
331
+ pi.on("session_start", async (event, ctx) => {
311
332
  const config = readConfig();
312
333
  lastUsed = config.lastUsed;
313
334
 
@@ -317,6 +338,26 @@ export default function (pi: ExtensionAPI) {
317
338
  patchFilterModels(() => lastUsed);
318
339
  patchCycleScopedModel(() => lastUsed);
319
340
 
341
+ // Override initial model to MRU on new sessions.
342
+ // Pi core picks the saved default if in scope, otherwise scopedModels[0].
343
+ // This hijack switches to the most recently used model instead, so your
344
+ // actual usage history determines the default — not alphabetical scope order.
345
+ if (
346
+ (event.reason === "startup" || event.reason === "new") &&
347
+ Object.keys(lastUsed).length > 0
348
+ ) {
349
+ const mruModel = findMruModel(lastUsed, ctx.modelRegistry);
350
+ const currentModel = ctx.model as { provider: string; id: string } | undefined;
351
+ if (
352
+ mruModel &&
353
+ (!currentModel ||
354
+ currentModel.provider !== (mruModel as { provider: string }).provider ||
355
+ currentModel.id !== (mruModel as { id: string }).id)
356
+ ) {
357
+ await pi.setModel(mruModel as Parameters<typeof pi.setModel>[0]);
358
+ }
359
+ }
360
+
320
361
  if (ctx.hasUI) {
321
362
  const count = Object.keys(lastUsed).length;
322
363
  ctx.ui.notify(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-model-sort",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "Sort models in pi's /model selector by last usage — most recently used models appear first",
5
5
  "type": "module",
6
6
  "author": "Tom X Nguyen",
package/src/index.ts CHANGED
@@ -10,6 +10,13 @@ export interface ModelSortConfig {
10
10
  lastUsed: Record<string, number>;
11
11
  }
12
12
 
13
+ /** Parse a model key into [provider, modelId]. Returns undefined if malformed. */
14
+ export function parseModelKey(key: string): [provider: string, modelId: string] | undefined {
15
+ const idx = key.indexOf("/");
16
+ if (idx === -1) return undefined;
17
+ return [key.substring(0, idx), key.substring(idx + 1)];
18
+ }
19
+
13
20
  /** Build a stable model key from provider and model id. */
14
21
  export function buildModelKey(provider: string, modelId: string): string {
15
22
  return `${provider}/${modelId}`;