pi-quiver 3.4.0 → 4.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/CHANGELOG.md +11 -0
- package/README.md +10 -8
- package/{doc_to_md.ts → extensions/doc_to_md.ts} +1 -1
- package/{fast-mode.ts → extensions/fast-mode.ts} +53 -6
- package/{provider-stall-watchdog.ts → extensions/provider-stall-watchdog.ts} +1 -1
- package/{session-name.ts → extensions/session-name.ts} +1 -1
- package/{sword-header.ts → extensions/sword-header.ts} +1 -1
- package/package.json +6 -16
- /package/{fetch.ts → extensions/fetch.ts} +0 -0
- /package/{extension-config.ts → lib/extension-config.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,17 @@ Published to npm as `pi-quiver` (`pi install npm:pi-quiver`). Pushing a
|
|
|
8
8
|
via OIDC trusted publishing. The release helper at
|
|
9
9
|
`.agents/skills/release/scripts/release.sh` cuts the tag; CI publishes.
|
|
10
10
|
|
|
11
|
+
## v4.0.0 - 2026-08-04
|
|
12
|
+
|
|
13
|
+
- **BREAKING: extension sources moved out of the package root** into `extensions/`; the shared config helper moved to `lib/extension-config.ts` (#4). If you disabled an extension through `pi config`, the stored filter (e.g. `-fast-mode.ts`) no longer matches any path and the extension will load again - re-disable it, or update the entry to `extensions/fast-mode.ts`. Hand-written `!fast-mode.ts` entries in `settings.json` are unaffected.
|
|
14
|
+
- **Local-checkout invocation changed:** `pi -e <path>/fetch.ts` is now `pi -e <path>/extensions/fetch.ts`.
|
|
15
|
+
- No behaviour change in any extension; `pi.extensions` is now a single directory entry and typecheck flags moved into `tsconfig.json`.
|
|
16
|
+
|
|
17
|
+
## v3.4.1 - 2026-08-04
|
|
18
|
+
|
|
19
|
+
- **`fast-mode` now corrects reported `usage.cost` to true 2x fast pricing for Opus 4.8/5** (statusline, persisted cost, and pi-cohort `Σ$` per load order) (#6).
|
|
20
|
+
- **Documented Anthropic stall-retry activation.** A session trace confirmed Pi's existing 2/4/8-second exponential backoff was active and that the watchdog retained the retry budget resolved when its extension instance started. Local `retry.maxRetries: 5` now takes effect after a fresh session or explicit reload, with no watchdog cooldown, resubmission path, or runtime code change.
|
|
21
|
+
|
|
11
22
|
## v3.4.0 - 2026-07-30
|
|
12
23
|
|
|
13
24
|
- **`provider-stall-watchdog`: new `firstEventMs` tier (default 20s) fast-fails a request that never streams.** A pre-first-event deadline is armed at every provider request and cleared by the first assistant `message_start`; on expiry the request is aborted and converted to a retryable error, cutting recovery for an unresponsive request from ~240s to ~22s (detection + Pi's 2s backoff). Previously only the 240s mid-stream `recoveryMs` timer caught this case.
|
package/README.md
CHANGED
|
@@ -62,12 +62,12 @@ A 300 KB changelog page never touches your context window - you get a preview an
|
|
|
62
62
|
|
|
63
63
|
| Extension | Tool | What it does |
|
|
64
64
|
| --- | --- | --- |
|
|
65
|
-
| `fetch.ts` | `fetch` | Retrieve URLs over HTTP(S). HTML -> Markdown (Readability extraction, Turndown conversion). Binary saved untouched to a temp file. GitHub issue/PR/repo/actions-run URLs auto-route through `gh` (falls back to HTTP). Same size gate as `doc_to_md`. |
|
|
66
|
-
| `doc_to_md.ts` | `doc_to_md` | Convert a local PDF/DOCX/PPTX to Markdown. High-fidelity via `pymupdf4llm` (run through `uv`); degraded pure-JS fallback (`unpdf`) when `uv`/Python is unavailable or conversion times out. DOCX/PPTX convert via LibreOffice first. |
|
|
67
|
-
| `session-name.ts` | `/session-name` | Manual + opt-in automatic session naming, with Ghostty tab rename. OFF by default. |
|
|
68
|
-
| `sword-header.ts` | `/builtin-header` | Themed ASCII startup header replacing pi's default logo. OFF by default. |
|
|
69
|
-
| `fast-mode.ts` | `/fast` | Inject Anthropic fast-mode (`speed: "fast"` + `anthropic-beta: fast-mode-2026-02-01`) into every Claude Opus 4.8 / Opus 5 request, any thinking level. `--fast` flag + `/fast [on\|off\|status]`. OFF by default. |
|
|
70
|
-
| `provider-stall-watchdog.ts` | - | Opt-in provider-stall recovery, in two tiers: a pre-first-event deadline (`firstEventMs`, 20s) on every provider request in every mode, and the mid-stream pair (warn at 2 min, recover at 4 min) in TUI runs only. Policy D offers each stall to Pi's retry loop until the stall retry budget (`maxStallRetries`, default = `retry.maxRetries`) is exhausted. OFF by default. |
|
|
65
|
+
| `extensions/fetch.ts` | `fetch` | Retrieve URLs over HTTP(S). HTML -> Markdown (Readability extraction, Turndown conversion). Binary saved untouched to a temp file. GitHub issue/PR/repo/actions-run URLs auto-route through `gh` (falls back to HTTP). Same size gate as `doc_to_md`. |
|
|
66
|
+
| `extensions/doc_to_md.ts` | `doc_to_md` | Convert a local PDF/DOCX/PPTX to Markdown. High-fidelity via `pymupdf4llm` (run through `uv`); degraded pure-JS fallback (`unpdf`) when `uv`/Python is unavailable or conversion times out. DOCX/PPTX convert via LibreOffice first. |
|
|
67
|
+
| `extensions/session-name.ts` | `/session-name` | Manual + opt-in automatic session naming, with Ghostty tab rename. OFF by default. |
|
|
68
|
+
| `extensions/sword-header.ts` | `/builtin-header` | Themed ASCII startup header replacing pi's default logo. OFF by default. |
|
|
69
|
+
| `extensions/fast-mode.ts` | `/fast` | Inject Anthropic fast-mode (`speed: "fast"` + `anthropic-beta: fast-mode-2026-02-01`) into every Claude Opus 4.8 / Opus 5 request, any thinking level. `--fast` flag + `/fast [on\|off\|status]`. OFF by default. |
|
|
70
|
+
| `extensions/provider-stall-watchdog.ts` | - | Opt-in provider-stall recovery, in two tiers: a pre-first-event deadline (`firstEventMs`, 20s) on every provider request in every mode, and the mid-stream pair (warn at 2 min, recover at 4 min) in TUI runs only. Policy D offers each stall to Pi's retry loop until the stall retry budget (`maxStallRetries`, default = `retry.maxRetries`) is exhausted. OFF by default. |
|
|
71
71
|
|
|
72
72
|
Full routing rules, size-gate mechanics, and config: [doc/fetch.md](doc/fetch.md), [doc/doc-to-md.md](doc/doc-to-md.md).
|
|
73
73
|
|
|
@@ -120,7 +120,7 @@ pi -e npm:pi-quiver
|
|
|
120
120
|
|
|
121
121
|
```bash
|
|
122
122
|
git clone git@github.com:jjuraszek/pi-quiver.git ~/repos/pi-quiver
|
|
123
|
-
pi -e ~/repos/pi-quiver/fetch.ts
|
|
123
|
+
pi -e ~/repos/pi-quiver/extensions/fetch.ts
|
|
124
124
|
```
|
|
125
125
|
|
|
126
126
|
## Prerequisites
|
|
@@ -150,6 +150,8 @@ These extensions are opt-in via `settings.json` (project `.pi/settings.json` ove
|
|
|
150
150
|
|
|
151
151
|
`sessionAutoName.enabled` makes one extra short LLM call per session (once, after the first turn) to title it; `false` (default) makes no model calls. `fastMode` only affects `claude-opus-4-8` and `claude-opus-5` requests on Anthropic's `anthropic-messages` API; enabling it opts into premium fast-mode pricing. `--fast` forces it on for one launch; `/fast on|off` toggles live. Proxy providers (opencode, cloudflare-ai-gateway) are excluded. `fastMode`'s header injection needs the `before_provider_headers` hook (pi bundling `@earendil-works/pi-coding-agent` >= 0.80.5); on older pi the beta header is silently not sent. See [doc/fetch.md](doc/fetch.md) and [doc/doc-to-md.md](doc/doc-to-md.md) for the ingestion tools' full reference; session-name/sword-header behavior above is complete.
|
|
152
152
|
|
|
153
|
+
`pi-ai` prices every fast request at standard rates - it has no `usage.speed` support and no request-level pricing modifier - so `fastMode` corrects the reported cost itself: a `message_end` handler scales all four `usage.cost` components by `FAST_MODE_COST_MULTIPLIER` (2x) and returns the corrected message. Persisted session JSONL and pi's own native cost display are always exact, since they're written from this corrected message. pi-cohort's live `Σ$` reflects the correction only when pi-quiver's `message_end` handler runs before pi-cohort's - best-effort, depending on extension load order - and is reconciled on pi-cohort's next `session_start` regardless. The upstream fix (teaching `pi-ai`'s `Usage`/`calculateCost` about `usage.speed`) is the better long-term path and is tracked separately.
|
|
154
|
+
|
|
153
155
|
Recommended explicit retry and watchdog settings:
|
|
154
156
|
|
|
155
157
|
```json
|
|
@@ -201,7 +203,7 @@ runtime deps; install them transiently and run the full check:
|
|
|
201
203
|
|
|
202
204
|
```bash
|
|
203
205
|
npm install
|
|
204
|
-
npm run test:all # node --test
|
|
206
|
+
npm run test:all # node --test test/*.test.ts + tsc --noEmit typecheck
|
|
205
207
|
```
|
|
206
208
|
|
|
207
209
|
`npm test` runs the unit tests alone; `npm run typecheck` runs the type pass.
|
|
@@ -250,7 +250,7 @@ let warmPromise: Promise<"warm" | "unavailable"> | null = null;
|
|
|
250
250
|
let uvAvailable: boolean | null = null;
|
|
251
251
|
|
|
252
252
|
function scriptPath(): string {
|
|
253
|
-
return fileURLToPath(new URL("
|
|
253
|
+
return fileURLToPath(new URL("../scripts/pdf_to_md.py", import.meta.url));
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
async function onPath(cmd: string): Promise<boolean> {
|
|
@@ -22,13 +22,29 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
25
|
-
import {
|
|
25
|
+
import type { Usage } from "@earendil-works/pi-ai";
|
|
26
|
+
import { resolveConfig } from "../lib/extension-config.ts";
|
|
26
27
|
|
|
27
28
|
export const FAST_MODE_BETA = "fast-mode-2026-02-01";
|
|
28
29
|
export const FAST_SPEED = "fast";
|
|
29
30
|
// Loose prefixes: match dated snapshots (claude-opus-4-8-*, claude-opus-5-*).
|
|
30
31
|
// Opus 4.7 is out of scope (D1); a future model needs a one-line addition here.
|
|
31
32
|
export const FAST_MODE_MODEL_PREFIXES = ["claude-opus-4-8", "claude-opus-5"];
|
|
33
|
+
|
|
34
|
+
// Anthropic bills Opus 4.8/5 fast mode at exactly 2x standard rates; input,
|
|
35
|
+
// output, and cache read/write all scale by the same factor because caching
|
|
36
|
+
// multipliers "apply on top of fast mode pricing". Rate card:
|
|
37
|
+
// https://docs.claude.com/en/docs/build-with-claude/fast-mode (retrieved 2026-08-04).
|
|
38
|
+
export const FAST_MODE_COST_MULTIPLIER = 2;
|
|
39
|
+
|
|
40
|
+
export function scaleCost(cost: Usage["cost"], multiplier: number): Usage["cost"] {
|
|
41
|
+
const input = cost.input * multiplier;
|
|
42
|
+
const output = cost.output * multiplier;
|
|
43
|
+
const cacheRead = cost.cacheRead * multiplier;
|
|
44
|
+
const cacheWrite = cost.cacheWrite * multiplier;
|
|
45
|
+
return { input, output, cacheRead, cacheWrite, total: input + output + cacheRead + cacheWrite };
|
|
46
|
+
}
|
|
47
|
+
|
|
32
48
|
export const OAUTH_IDENTITY_BETAS = ["claude-code-20250219", "oauth-2025-04-20"];
|
|
33
49
|
const STATUS_KEY = "fast-mode";
|
|
34
50
|
const BETA_HEADER = "anthropic-beta";
|
|
@@ -92,6 +108,10 @@ export function resolveEnabled(s: State): boolean {
|
|
|
92
108
|
export default function (pi: ExtensionAPI) {
|
|
93
109
|
let liveOverride: boolean | null = null;
|
|
94
110
|
let enabled = false;
|
|
111
|
+
// Per-request snapshot. Safe as plain booleans because pi serializes provider
|
|
112
|
+
// requests per session (headers -> request -> message_end, awaited in order).
|
|
113
|
+
let pendingFastSpeed = false;
|
|
114
|
+
let pendingFastHeader = false;
|
|
95
115
|
|
|
96
116
|
const readFlag = (): boolean => pi.getFlag("fast") === true;
|
|
97
117
|
|
|
@@ -135,16 +155,43 @@ export default function (pi: ExtensionAPI) {
|
|
|
135
155
|
});
|
|
136
156
|
|
|
137
157
|
pi.on("before_provider_request", (event, ctx) => {
|
|
138
|
-
if (!shouldInject(enabled, ctx.model))
|
|
139
|
-
|
|
158
|
+
if (!shouldInject(enabled, ctx.model)) {
|
|
159
|
+
pendingFastSpeed = false;
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const next = injectSpeed(event.payload);
|
|
163
|
+
pendingFastSpeed =
|
|
164
|
+
typeof next === "object" && next !== null && (next as Record<string, unknown>).speed === FAST_SPEED;
|
|
165
|
+
return next;
|
|
140
166
|
});
|
|
141
167
|
|
|
142
168
|
pi.on("before_provider_headers", async (event, ctx) => {
|
|
143
|
-
if (!shouldInject(enabled, ctx.model))
|
|
144
|
-
|
|
169
|
+
if (!shouldInject(enabled, ctx.model) || !event.headers) {
|
|
170
|
+
pendingFastHeader = false;
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
145
173
|
const isOAuth = await detectOAuth(ctx);
|
|
146
|
-
if (isOAuth === null)
|
|
174
|
+
if (isOAuth === null) {
|
|
175
|
+
pendingFastHeader = false;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
147
178
|
event.headers[BETA_HEADER] = buildBetaHeader(event.headers[BETA_HEADER], isOAuth);
|
|
179
|
+
pendingFastHeader = true;
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
pi.on("message_end", (event) => {
|
|
183
|
+
const msg = event.message;
|
|
184
|
+
if (msg.role !== "assistant") return;
|
|
185
|
+
const corrected = pendingFastSpeed && pendingFastHeader && !!msg.usage?.cost;
|
|
186
|
+
pendingFastSpeed = false;
|
|
187
|
+
pendingFastHeader = false;
|
|
188
|
+
if (!corrected) return;
|
|
189
|
+
return {
|
|
190
|
+
message: {
|
|
191
|
+
...msg,
|
|
192
|
+
usage: { ...msg.usage, cost: scaleCost(msg.usage.cost, FAST_MODE_COST_MULTIPLIER) },
|
|
193
|
+
},
|
|
194
|
+
};
|
|
148
195
|
});
|
|
149
196
|
|
|
150
197
|
pi.registerCommand("fast", {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { readSettings, resolveConfig, settingsPaths } from "
|
|
2
|
+
import { readSettings, resolveConfig, settingsPaths } from "../lib/extension-config.ts";
|
|
3
3
|
|
|
4
4
|
export const MAX_TIMER_MS = 2_147_483_647;
|
|
5
5
|
export const DEFAULT_CONFIG = {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
21
|
-
import { resolveConfig } from "
|
|
21
|
+
import { resolveConfig } from "../lib/extension-config.ts";
|
|
22
22
|
|
|
23
23
|
// `complete` moved between pi-ai layouts: older builds re-export it from the
|
|
24
24
|
// package index, newer ones expose it only via the `/compat` subpath. A static
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import type { ExtensionAPI, Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
17
|
-
import { resolveConfig } from "
|
|
17
|
+
import { resolveConfig } from "../lib/extension-config.ts";
|
|
18
18
|
|
|
19
19
|
type Config = { enabled: boolean };
|
|
20
20
|
const DEFAULT_CONFIG: Config = { enabled: false };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-quiver",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Personal pack of Pi coding-agent extensions: context-safe fetch, doc_to_md PDF/DOCX/PPTX-to-Markdown conversion, session naming, a themed ASCII startup header, Opus 4.8 fast mode, and a provider-stall watchdog.",
|
|
5
5
|
"author": "Jacek Juraszek",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,13 +28,8 @@
|
|
|
28
28
|
"node": ">=20"
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"session-name.ts",
|
|
34
|
-
"sword-header.ts",
|
|
35
|
-
"fast-mode.ts",
|
|
36
|
-
"provider-stall-watchdog.ts",
|
|
37
|
-
"extension-config.ts",
|
|
31
|
+
"extensions",
|
|
32
|
+
"lib",
|
|
38
33
|
"scripts/pdf_to_md.py",
|
|
39
34
|
"types/**/*.d.ts",
|
|
40
35
|
"README.md",
|
|
@@ -42,18 +37,13 @@
|
|
|
42
37
|
],
|
|
43
38
|
"scripts": {
|
|
44
39
|
"check:agents-core": "node scripts/check-agents-core.mjs",
|
|
45
|
-
"test": "node --test \"
|
|
46
|
-
"typecheck": "npx -y tsc --noEmit
|
|
40
|
+
"test": "node --test \"test/*.test.ts\"",
|
|
41
|
+
"typecheck": "npx -y tsc --noEmit",
|
|
47
42
|
"test:all": "npm run check:agents-core && npm run test && npm run typecheck"
|
|
48
43
|
},
|
|
49
44
|
"pi": {
|
|
50
45
|
"extensions": [
|
|
51
|
-
"./
|
|
52
|
-
"./doc_to_md.ts",
|
|
53
|
-
"./session-name.ts",
|
|
54
|
-
"./sword-header.ts",
|
|
55
|
-
"./fast-mode.ts",
|
|
56
|
-
"./provider-stall-watchdog.ts"
|
|
46
|
+
"./extensions"
|
|
57
47
|
],
|
|
58
48
|
"image": "https://raw.githubusercontent.com/jjuraszek/pi-quiver/main/pi-quiver.png"
|
|
59
49
|
},
|
|
File without changes
|
|
File without changes
|