@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 +21 -0
- package/README.md +126 -2
- package/dist/index.d.ts +93 -0
- package/dist/index.js +7151 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -4
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
|
-
#
|
|
1
|
+
# Pi Adaptive
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@coool/pi-adaptive)
|
|
4
|
+
[](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.
|
package/dist/index.d.ts
ADDED
|
@@ -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 {};
|