@coool/pi-adaptive 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/README.md +224 -2
- package/dist/index.d.ts +92 -0
- package/dist/index.js +6891 -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,225 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Pi Adaptive
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Not yet published. Release is gated on maintainer approval.
|
|
4
|
+
|
|
5
|
+
Capability-tier routing and bounded recovery for Pi. You choose the available
|
|
6
|
+
models and their tier order; Adaptive keeps provider failures separate from
|
|
7
|
+
capability gaps. Pi still owns the agent, tools, sessions, and model calls.
|
|
8
|
+
|
|
9
|
+
See the [Pi plan](../../docs/plans/pi-plugin.md) for the design and the
|
|
10
|
+
[test strategy](docs/TEST_STRATEGY.md) for the evidence each release requires.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
After publication:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
pi install npm:@coool/pi-adaptive
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The package declares Pi `>=1.5.0 <2`. The declared range is not itself a
|
|
21
|
+
compatibility test: the packed minimum (1.5.0) and latest Pi hosts run the
|
|
22
|
+
SDK, JSON, and real-terminal matrix before release, and `/adaptive doctor`
|
|
23
|
+
reports the last host version that matrix recorded. For local development, run
|
|
24
|
+
`pi install ./packages/pi` from the repository root after building.
|
|
25
|
+
|
|
26
|
+
## How it decides
|
|
27
|
+
|
|
28
|
+
- **Use an explicit tier, not a classifier.** A user pin wins, then the agent's
|
|
29
|
+
`adaptive_tier` request under Auto; otherwise the tier is `standard`. No prompt
|
|
30
|
+
keywords or extra LLM call decide the tier.
|
|
31
|
+
- **Check fit before preference.** Only your configured, authenticated models
|
|
32
|
+
are candidates. Context size, input modalities, declared tool support, health,
|
|
33
|
+
and available capacity evidence constrain selection before tier order.
|
|
34
|
+
- **Keep productive work on its model.** Ordinary tool continuations stay on the
|
|
35
|
+
selected resource. A recovered preferred model becomes eligible at a later
|
|
36
|
+
safe boundary, not just to make an ongoing run cheaper.
|
|
37
|
+
- **Recover horizontally.** Rate limits, quota exhaustion, timeouts, and outages
|
|
38
|
+
use another eligible resource in the same tier, or wait/park. Availability
|
|
39
|
+
never silently borrows a weaker or stronger tier; 429s do not trip the breaker.
|
|
40
|
+
- **Escalate only on capability evidence.** `adaptive_escalate` requires a reason
|
|
41
|
+
and concrete evidence. Recording it switches nothing; a terminal capability
|
|
42
|
+
failure can continue the same task exactly one tier higher, within its budget.
|
|
43
|
+
|
|
44
|
+
## Models and modes
|
|
45
|
+
|
|
46
|
+
| Picker entry | Meaning |
|
|
47
|
+
| ------------------- | ------------------------------------------------------------------------ |
|
|
48
|
+
| `adaptive/auto` | Use the requested tier, or `standard`; allow evidence-backed escalation. |
|
|
49
|
+
| `adaptive/quick` | Pin the quick tier; recover within it. |
|
|
50
|
+
| `adaptive/standard` | Pin the standard tier; recover within it. |
|
|
51
|
+
| `adaptive/advanced` | Pin the advanced tier; recover within it. |
|
|
52
|
+
|
|
53
|
+
**Observe is the default.** It explains what the same scheduler would choose
|
|
54
|
+
without changing a manually selected model. Choosing an Adaptive entry
|
|
55
|
+
activates routing for that session; choosing any normal model hands control back
|
|
56
|
+
immediately. With observation disabled and a normal model selected, Adaptive is
|
|
57
|
+
off. Off mode does not route or recover work.
|
|
58
|
+
|
|
59
|
+
Pinned tier entries ignore the agent's tier request and do not automatically
|
|
60
|
+
escalate by default. The Preferences dialog can explicitly enable
|
|
61
|
+
`escalateFromPinned`; evidence and task budgets still apply. A concrete model
|
|
62
|
+
pin always wins over automatic routing and recovery. Use Auto for the default
|
|
63
|
+
evidence-backed movement between tiers.
|
|
64
|
+
|
|
65
|
+
The agent tool `adaptive_tier` takes `quick`, `standard`, or `advanced`. Its
|
|
66
|
+
declaration applies from the next user prompt and stays on the session branch
|
|
67
|
+
until replaced. A new user prompt starts a new task at that base tier; it does
|
|
68
|
+
not inherit the last task's escalation. Forking or rewinding follows Pi's branch
|
|
69
|
+
state. `adaptive_escalate` is evidence reporting, not a planning tool.
|
|
70
|
+
|
|
71
|
+
## Providers, capabilities, and settings
|
|
72
|
+
|
|
73
|
+
Configure credentials and models in Pi first. Adaptive uses Pi's available-model
|
|
74
|
+
registry; it does not add providers, fetch a marketplace, or invent tier
|
|
75
|
+
memberships. Do not run multiple automatic routers in the same session.
|
|
76
|
+
|
|
77
|
+
Settings live under Pi's agent directory, normally
|
|
78
|
+
`~/.pi/agent/adaptive/settings.json`. The shared settings include `mode`
|
|
79
|
+
(`off`, `observe`, `active`), ordered `tiers`, and a concrete-model `pin`
|
|
80
|
+
(`null` means unpinned). For example, replace these placeholders with your
|
|
81
|
+
actual Pi provider/model IDs:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"mode": "observe",
|
|
86
|
+
"tiers": {
|
|
87
|
+
"quick": [{ "providerID": "your-provider", "id": "your-quick-model" }],
|
|
88
|
+
"standard": [{ "providerID": "your-provider", "id": "your-standard-model" }],
|
|
89
|
+
"advanced": [{ "providerID": "your-provider", "id": "your-advanced-model" }]
|
|
90
|
+
},
|
|
91
|
+
"pin": null
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
List multiple models in a tier in your preferred order for same-tier recovery.
|
|
96
|
+
Empty tiers do not authorize cross-tier fallback. The interactive tier editor
|
|
97
|
+
and the first-run proposal build tiers from available-model metadata, never
|
|
98
|
+
from prompt classification, and nothing is saved until you confirm.
|
|
99
|
+
|
|
100
|
+
A tier entry can include a Pi-supported thinking level as `variant`, such as
|
|
101
|
+
`"variant": "high"`. Admission checks that exact variant, and dispatch uses it;
|
|
102
|
+
unsupported variants are not silently replaced with a different thinking level.
|
|
103
|
+
|
|
104
|
+
Pi's catalogue does not declare model-level tool capability. Explicit
|
|
105
|
+
declarations live in `adaptive/capabilities.json` beside the settings file:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"models": [{ "providerID": "your-provider", "id": "your-model", "tools": true }]
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Declare support only after verifying that model's tool calls. Missing declarations
|
|
114
|
+
remain unknown; `tools: false` explicitly excludes a model from tool-enabled
|
|
115
|
+
requests. Declarations do not add providers, tier memberships, or credentials.
|
|
116
|
+
Malformed capability files produce field-specific errors rather than guessed
|
|
117
|
+
declarations. Capacity is evidence-driven: unknown capacity is not a promise of
|
|
118
|
+
remaining quota, and not every provider exposes usable capacity readings.
|
|
119
|
+
|
|
120
|
+
## Status, notes, and commands
|
|
121
|
+
|
|
122
|
+
Adaptive uses one footer item for the current tier and model, recovery,
|
|
123
|
+
task escalation, observation of a manual model, tier setup, or degraded
|
|
124
|
+
operation. Waiting countdowns and parked-work status take priority in that same
|
|
125
|
+
item; parked work also has a card. A manual model pick or session change clears
|
|
126
|
+
stale status, and disabling observation clears its manual-model footer.
|
|
127
|
+
|
|
128
|
+
One-line transcript notes stay out of model context. The note verbosity choices
|
|
129
|
+
are `changes` (default: model changes or problems), `all` (every unit), and
|
|
130
|
+
`quiet` (no transcript notes). The footer and receipts remain useful with
|
|
131
|
+
`quiet`; neither depends on note verbosity.
|
|
132
|
+
Real-terminal frames at 80 columns are in [docs/screenshots](docs/screenshots/README.md):
|
|
133
|
+
[first-run proposal](docs/screenshots/first-run-proposal-80x24.txt),
|
|
134
|
+
[routed note](docs/screenshots/routed-note-80x24.txt),
|
|
135
|
+
[parked card](docs/screenshots/parked-card-80x24.txt), and
|
|
136
|
+
[doctor](docs/screenshots/doctor-80x24.txt). Dialogs are checked at 60, 80, and
|
|
137
|
+
120 columns.
|
|
138
|
+
|
|
139
|
+
The `/adaptive` command (also `Ctrl+Alt+A`) uses the same dialog flow as OpenCode:
|
|
140
|
+
|
|
141
|
+
| Command | Purpose |
|
|
142
|
+
| --------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
143
|
+
| `/adaptive` | Open the main menu. |
|
|
144
|
+
| `/adaptive status` | Show tiers, resource health, and available capacity evidence. |
|
|
145
|
+
| `/adaptive why` | Explain the latest routing receipt. |
|
|
146
|
+
| `/adaptive tiers` | Edit ordered tier membership. |
|
|
147
|
+
| `/adaptive health` | Inspect health and manually mark a resource healthy. |
|
|
148
|
+
| `/adaptive resume` | Act on parked work. |
|
|
149
|
+
| `/adaptive cancel` | Cancel parked work. |
|
|
150
|
+
| `/adaptive observe on\|off` | Enable or disable observation of manual-model sessions. |
|
|
151
|
+
| `/adaptive doctor` | Check host compatibility, credentials, capability declarations, storage, router conflicts, health, and parking. |
|
|
152
|
+
|
|
153
|
+
Typed subcommands complete in Pi's argument menu; the first Enter accepts the
|
|
154
|
+
completion and the second runs it.
|
|
155
|
+
|
|
156
|
+
## Waiting, parking, and cancellation
|
|
157
|
+
|
|
158
|
+
Parking keeps work in the required tier:
|
|
159
|
+
|
|
160
|
+
- A known reset within two minutes waits in place with a countdown; Esc cancels.
|
|
161
|
+
- Longer waits persist parked work with its session and a shared local index.
|
|
162
|
+
A cross-process claim permits at most one wake for a parked record.
|
|
163
|
+
- No provider request is dispatched while parked. An initial prompt is saved as
|
|
164
|
+
normal Pi conversation history before parking at the routing boundary, so it
|
|
165
|
+
survives closing and reopening Pi. Cancelling its parked work leaves that
|
|
166
|
+
message in history. Later intercepted prompts are kept in session-only payloads
|
|
167
|
+
until resumed. After partial execution, recovery uses one hidden continuation message telling the agent to
|
|
168
|
+
continue from existing session/workspace state, not replay the original prompt
|
|
169
|
+
or redo completed steps. That small continuation **does** enter model context;
|
|
170
|
+
operational notes and receipts do not.
|
|
171
|
+
- A new user message or manual model selection cancels parked work. User aborts
|
|
172
|
+
never trigger failover or restart. A manual choice of another tier is explicit
|
|
173
|
+
user intent, not automatic cross-tier recovery.
|
|
174
|
+
- Reopening a parked interactive session re-arms its wait; already-due work asks
|
|
175
|
+
for confirmation. An owned wake writes a resumed note unless verbosity is
|
|
176
|
+
quiet. Automatic wakes are limited to **three per task**. Terminal
|
|
177
|
+
infrastructure failures get at most **two automatic restarts per task**;
|
|
178
|
+
capability escalation is separately bounded. Permanent auth/configuration
|
|
179
|
+
errors do not enter recovery loops.
|
|
180
|
+
|
|
181
|
+
Headless print/JSON runs never show dialogs. Detached wake timers are not a
|
|
182
|
+
supported headless continuation mechanism: Pi can exit or invalidate the
|
|
183
|
+
extension context after the owned handler returns. Only an awaited, owned
|
|
184
|
+
recovery may continue that process; otherwise the run ends with a clear
|
|
185
|
+
unavailability error. There is no background daemon.
|
|
186
|
+
|
|
187
|
+
## Privacy
|
|
188
|
+
|
|
189
|
+
Adaptive sends no external telemetry and makes no classifier requests. Normal
|
|
190
|
+
model requests still go through the providers you configured in Pi. Shared
|
|
191
|
+
health, capacity, receipts, and parking metadata stay locally under Pi's agent
|
|
192
|
+
directory; credentials and raw response headers do not belong in those records.
|
|
193
|
+
Receipts are bounded and never injected into prompts. Pi retains its own session
|
|
194
|
+
content, including any input parked before dispatch; the shared parked index
|
|
195
|
+
must contain metadata only, not a second copy of your prompt.
|
|
196
|
+
|
|
197
|
+
## Small router comparison
|
|
198
|
+
|
|
199
|
+
These are **source-audit facts, not runtime benchmarks**, from the
|
|
200
|
+
[2026-11-08 audit](../../research_notes/Pi%20port%20feasibility/pi_auto_model_audit.md).
|
|
201
|
+
They apply only to the inspected versions; none of those packages was run.
|
|
202
|
+
|
|
203
|
+
| Inspected router | Selection and recovery found in source |
|
|
204
|
+
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
205
|
+
| `pi-auto-model` **0.9.2** | Keyword analysis, optional LLM classification (off by default), and utility ranking; its failover target list spans tiers. |
|
|
206
|
+
| `pi-shift-router` **1.8.0** | An LLM judge chooses fast/smart; its failover explicitly stays within the tier. |
|
|
207
|
+
| `@pedro_klein/pi-gateway` **0.6.0** | User-declared tier aliases with same-slot fallback and atomically persisted unhealthy-until times. |
|
|
208
|
+
| Pi Adaptive | Explicit capability tiers without a classifier; same-tier recovery, evidence-backed escalation, and durable parking. |
|
|
209
|
+
|
|
210
|
+
The audit found no durable park-and-auto-resume path in those three inspected
|
|
211
|
+
routers. This does not establish behavior of later versions.
|
|
212
|
+
|
|
213
|
+
## Development and release evidence
|
|
214
|
+
|
|
215
|
+
From the repository root:
|
|
216
|
+
|
|
217
|
+
```sh
|
|
218
|
+
npm run check -- --pkg pi
|
|
219
|
+
npm run verify -- --pkg pi
|
|
220
|
+
npm run verify -- --all
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
See the [test strategy](docs/TEST_STRATEGY.md) for the unit, real-SDK,
|
|
224
|
+
packed-host, and PTY evidence. Publication requires a full, unskipped
|
|
225
|
+
`npm run verify -- --all`; a scoped run alone is not release evidence.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
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 settings file and the Pi version. */
|
|
73
|
+
interface HostSources {
|
|
74
|
+
readonly settings: JsonFile;
|
|
75
|
+
readonly version: string;
|
|
76
|
+
readonly minimum: string;
|
|
77
|
+
readonly testedUpTo: string | undefined;
|
|
78
|
+
}
|
|
79
|
+
interface AdaptiveSources {
|
|
80
|
+
readonly settings: () => Promise<SettingsV1>;
|
|
81
|
+
readonly capabilities: () => Promise<readonly ToolCapability[]>;
|
|
82
|
+
readonly newId: () => string;
|
|
83
|
+
readonly health: SharedHealth;
|
|
84
|
+
readonly parking: ParkingSources;
|
|
85
|
+
readonly capacity: SharedCapacity;
|
|
86
|
+
readonly receipts: JsonTransactionFile;
|
|
87
|
+
readonly preferences: JsonFile;
|
|
88
|
+
}
|
|
89
|
+
type Host = Pick<ExtensionAPI, "registerVirtualModel" | "getActiveTools" | "registerTool" | "on" | "sendMessage" | "sendUserMessage" | "appendEntry" | "registerCommand" | "registerShortcut" | "setModel" | "registerEntryRenderer">;
|
|
90
|
+
declare function registerAdaptive(pi: Host, sources: AdaptiveSources, host: HostSources): void;
|
|
91
|
+
export default function adaptive(pi: Parameters<typeof registerAdaptive>[0]): void;
|
|
92
|
+
export {};
|