@coool/pi-adaptive 0.0.0-stage → 1.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 @coool
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,127 @@
1
- # Temporary Holding Version
1
+ # Pi Adaptive
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm version](https://img.shields.io/npm/v/@coool/pi-adaptive)](https://www.npmjs.com/package/@coool/pi-adaptive)
4
+ [![npm downloads](https://img.shields.io/npm/dt/@coool/pi-adaptive)](https://www.npmjs.com/package/@coool/pi-adaptive)
5
+
6
+ **Keep working when your models stop working.** Pi Adaptive is a resilience layer for
7
+ [Pi](https://github.com/earendil-works/pi): it spends your subscriptions before your paid
8
+ credits, rides out rate limits, outages, and quota walls, and finishes parked goals on its
9
+ own — across restarts. You configure the models; Adaptive handles everything that goes
10
+ wrong behind them.
11
+
12
+ ```sh
13
+ pi install npm:@coool/pi-adaptive
14
+ ```
15
+
16
+ Requires Pi `>=1.5.0 <2`. Published as
17
+ [`@coool/pi-adaptive`](https://www.npmjs.com/package/@coool/pi-adaptive).
18
+
19
+ ## Why it's different
20
+
21
+ Most routers guess. Adaptive doesn't.
22
+
23
+ - **Subscriptions first, credits last.** Order your tiers — Claude and OpenAI before
24
+ OpenRouter, say. When a subscription limit lifts mid-goal, Adaptive switches back
25
+ automatically instead of burning paid tokens.
26
+ - **No classifier, no keyword sniffing.** Tiers are explicit, pins always win, and moving
27
+ a task up a tier requires recorded evidence — never an LLM judge.
28
+ - **Goals survive restarts.** A known reset waits with a countdown; anything longer parks
29
+ durably and wakes itself later. Recovery continues from the real session state — tool
30
+ effects are never repeated.
31
+ - **Bounded by design, and it tells you.** At most five failed resume deliveries and two
32
+ restarts per task, then a clear note in the transcript. `/adaptive resume` retries on
33
+ your word. Auth and config errors never enter retry loops.
34
+ - **Nothing leaves your machine.** No telemetry, no daemon, no classifier requests.
35
+ Health, receipts, and parked-work metadata stay under Pi's agent directory.
36
+
37
+ Not sure yet? Run it in **Observe mode** (the default): it shows what it would route and
38
+ recover without touching your manually selected model.
39
+
40
+ ## Quick start
41
+
42
+ 1. Install the plugin and start Pi. The **first run proposes tiers** from your configured
43
+ models — confirm or edit them, nothing is saved until you do.
44
+ 2. Pick an Adaptive entry in the model picker — `adaptive/auto` for tier routing, or pin
45
+ `adaptive/quick`, `adaptive/standard`, `adaptive/advanced`. You can also let the agent
46
+ request a tier itself with its `adaptive_tier` tool.
47
+ 3. Work. When something fails, watch the transcript: recovery, parking, and resumes show
48
+ up as one-line notes. `/adaptive status` and `/adaptive why` explain any decision.
49
+
50
+ Real-terminal frames at 80 columns: [screenshots](docs/screenshots/README.md).
51
+
52
+ ## Your goals, unattended
53
+
54
+ When every model in a tier is exhausted, Adaptive parks the goal — session state, partial
55
+ progress, and all — and resumes it when a model recovers:
56
+
57
+ - Reopening Pi re-arms the wait; already-due work asks before resuming.
58
+ - A wake that finds everything still down is free: it re-arms and tries again later.
59
+ - Recovery continues from where the work stopped, via one hidden continuation — the
60
+ original prompt is never replayed.
61
+ - A new message or a manual model pick cancels parked work. You always have the final
62
+ say; user aborts never trigger failover.
63
+
64
+ Headless print/JSON runs stay dialog-free and end with a clear error instead of
65
+ background waits — there is no daemon.
66
+
67
+ ## Setup
68
+
69
+ Configure credentials and models in Pi first; Adaptive routes only what Pi already knows.
70
+ Settings live at `~/.pi/agent/adaptive/settings.json`:
71
+
72
+ ```json
73
+ {
74
+ "mode": "observe",
75
+ "tiers": {
76
+ "quick": [{ "providerID": "your-provider", "id": "your-quick-model" }],
77
+ "standard": [{ "providerID": "your-provider", "id": "your-standard-model" }],
78
+ "advanced": [{ "providerID": "your-provider", "id": "your-advanced-model" }]
79
+ },
80
+ "pin": null
81
+ }
82
+ ```
83
+
84
+ List several models per tier in your preference order — that order is the recovery path.
85
+ A tier entry may carry a Pi thinking level as `"variant": "high"`. The interactive editor
86
+ (`/adaptive tiers`) builds tiers from model metadata, never from prompts.
87
+
88
+ Pi's catalogue doesn't declare tool support, and Adaptive never guesses from a
89
+ name. Declare it from `/adaptive tools` (also in the main menu): pick a model,
90
+ answer whether it calls tools, and confirm — the declaration is stored in
91
+ `adaptive/capabilities.json` beside the settings file. An undeclared model is
92
+ treated as unknown and skipped on tool turns.
93
+
94
+ ## Commands
95
+
96
+ | Command | Purpose |
97
+ | --------------------------- | ------------------------------------------------------------ |
98
+ | `/adaptive` | Main menu (also `Ctrl+Alt+A`). |
99
+ | `/adaptive status` | Tiers, resource health, capacity evidence. |
100
+ | `/adaptive why` | Explain the latest routing decision. |
101
+ | `/adaptive tiers` | Edit ordered tier membership. |
102
+ | `/adaptive tools` | Declare which models can call tools. |
103
+ | `/adaptive health` | Inspect health; manually mark a resource healthy. |
104
+ | `/adaptive resume` | Retry parked work now (bypasses the give-up budget). |
105
+ | `/adaptive cancel` | Cancel parked work. |
106
+ | `/adaptive observe on\|off` | Enable or disable observation of manual-model sessions. |
107
+ | `/adaptive doctor` | Check host, credentials, capabilities, storage, and parking. |
108
+
109
+ ## For comparison
110
+
111
+ A source-level audit of alternative Pi routers (2026-11-08; no runtime benchmarks) found
112
+ keyword or LLM-judge selection in `pi-auto-model` and `pi-shift-router`, and no durable
113
+ park-and-auto-resume path in any of them. Adaptive's approach is explicit tiers,
114
+ evidence-backed escalation, and durable parking. Details in the
115
+ [audit notes](../../research_notes/Pi%20port%20feasibility/pi_auto_model_audit.md).
116
+
117
+ ## Development
118
+
119
+ From the repository root:
120
+
121
+ ```sh
122
+ npm run check -- --pkg pi
123
+ npm run verify -- --pkg pi
124
+ ```
125
+
126
+ The [design plan](../../docs/plans/pi-plugin.md) and
127
+ [test strategy](docs/TEST_STRATEGY.md) cover the architecture and the release evidence.
@@ -0,0 +1,93 @@
1
+ import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import "@earendil-works/pi-ai";
3
+ /** OpenCode model reference (`Model.Ref`): provider, model, optional variant. */
4
+ interface ModelRef {
5
+ readonly providerID: string;
6
+ readonly id: string;
7
+ readonly variant?: string | undefined;
8
+ }
9
+ declare const TIERS: readonly ["quick", "standard", "advanced"];
10
+ type TierName = (typeof TIERS)[number];
11
+ /** Operating modes (GOAL manual control). The plugin defaults to `observe`. */
12
+ declare const OPERATING_MODES: readonly ["off", "observe", "active"];
13
+ type OperatingMode = (typeof OPERATING_MODES)[number];
14
+ /** Current settings schema version. */
15
+ declare const SETTINGS_VERSION = 1;
16
+ interface SettingsV1 {
17
+ readonly version: typeof SETTINGS_VERSION;
18
+ /** OFF does nothing; OBSERVE is the safe default after install (ADP-029/032). */
19
+ readonly mode: OperatingMode;
20
+ /** Ordered per-tier resource lists; order is user policy and never reordered (ADP-005). */
21
+ readonly tiers: Readonly<Record<TierName, readonly ModelRef[]>>;
22
+ /**
23
+ * Global explicit user pin. `null` = no pin. A pin names a concrete model
24
+ * and outranks every other routing input (ADP-002).
25
+ */
26
+ readonly pin: ModelRef | null;
27
+ /** User-configured agent → tier hints (role level of the ladder, ADP-002). */
28
+ readonly agentTiers: Readonly<Record<string, TierName>>;
29
+ /** First-install invitation dismissed (ADP-032: shown once, never nagged). */
30
+ readonly inviteDismissed: boolean;
31
+ }
32
+ interface JsonFile {
33
+ /** Current value, or `undefined` when the file does not exist. */
34
+ read(): Promise<unknown>;
35
+ /**
36
+ * Applies `change` under the lock. A `next` of `undefined` leaves the
37
+ * file untouched (read-only decisions still serialize with writers).
38
+ */
39
+ update<R>(change: (current: unknown) => {
40
+ readonly next: unknown;
41
+ readonly result: R;
42
+ }): Promise<R>;
43
+ }
44
+ /** Async core stores must finish their read/modify/write before releasing the lock. */
45
+ interface JsonTransactionFile {
46
+ read(): Promise<unknown>;
47
+ transaction<R>(change: (current: unknown) => Promise<{
48
+ readonly next: unknown;
49
+ readonly result: R;
50
+ }>): Promise<R>;
51
+ }
52
+ interface SharedHealth {
53
+ readonly file: JsonFile;
54
+ readonly clock: () => number;
55
+ /** Probe ids must be unique across every process sharing the file. */
56
+ readonly newId: () => string;
57
+ }
58
+ interface ToolCapability {
59
+ readonly providerID: string;
60
+ readonly id: string;
61
+ readonly tools: boolean;
62
+ }
63
+ interface ParkingSources {
64
+ readonly file: JsonFile;
65
+ readonly clock: () => number;
66
+ readonly timer: (callback: () => void, delay: number) => () => void;
67
+ }
68
+ interface SharedCapacity {
69
+ readonly file: JsonFile;
70
+ readonly clock: () => number;
71
+ }
72
+ /** What only the packaged entry knows: the writable files and the Pi version. */
73
+ interface HostSources {
74
+ readonly settings: JsonFile;
75
+ readonly capabilities: JsonFile;
76
+ readonly version: string;
77
+ readonly minimum: string;
78
+ readonly testedUpTo: string | undefined;
79
+ }
80
+ interface AdaptiveSources {
81
+ readonly settings: () => Promise<SettingsV1>;
82
+ readonly capabilities: () => Promise<readonly ToolCapability[]>;
83
+ readonly newId: () => string;
84
+ readonly health: SharedHealth;
85
+ readonly parking: ParkingSources;
86
+ readonly capacity: SharedCapacity;
87
+ readonly receipts: JsonTransactionFile;
88
+ readonly preferences: JsonFile;
89
+ }
90
+ type Host = Pick<ExtensionAPI, "registerVirtualModel" | "getActiveTools" | "registerTool" | "on" | "sendMessage" | "sendUserMessage" | "appendEntry" | "registerCommand" | "registerShortcut" | "setModel" | "registerEntryRenderer">;
91
+ declare function registerAdaptive(pi: Host, sources: AdaptiveSources, host: HostSources): void;
92
+ export default function adaptive(pi: Parameters<typeof registerAdaptive>[0]): void;
93
+ export {};