pi-quiver 5.2.1 → 5.2.2
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 +8 -0
- package/README.md +1 -1
- package/extensions/fast-mode.ts +79 -14
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,14 @@ 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
|
+
## v5.2.2 - 2026-09-03
|
|
12
|
+
|
|
13
|
+
- fast-mode: discover pi's `anthropic-beta` list at request time by probing
|
|
14
|
+
pi-ai's request assembly (capturing fetch, zero network) instead of
|
|
15
|
+
rebuilding it from a fixed list - fixes 400 "fallbacks: Extra inputs are
|
|
16
|
+
not permitted" on `claude-opus-5` (#11). Probe failure degrades to the
|
|
17
|
+
previous static header.
|
|
18
|
+
|
|
11
19
|
## v5.2.1 - 2026-09-02
|
|
12
20
|
|
|
13
21
|
- sword-header: clip each header line to the terminal width in `render(width)` (ANSI-aware `truncateToWidth`, hard clip). Fixes a hard crash (`Rendered line N exceeds terminal width`) in terminals/panes narrower than 80 columns.
|
package/README.md
CHANGED
|
@@ -184,7 +184,7 @@ survive untouched.
|
|
|
184
184
|
|
|
185
185
|
`herdrTab` (default `true`) mirrors the same curated label to the Herdr tab bar over Herdr's unix socket, independent of `ghosttyTab` - either sink can be toggled off without affecting the other. It's claim-once: the extension adopts a tab only while it still shows its default numeric label (its live 1-based position among the workspace's tabs); a manually renamed tab, or one renamed mid-session by hand, is never touched again for that session - the human's label always wins. On every shutdown (quit, reload, `/new`, resume, fork) a claimed tab's label is restored to its then-current default position, so a successor session in the same pane can claim cleanly. It only ever runs in TUI mode, on an attached TTY, under Herdr (`HERDR_ENV`/`HERDR_TAB_ID`/`HERDR_SOCKET_PATH` all set) - `pi -p`/json/rpc runs and background subagents never touch the tab. Caveats: a hard crash (`kill -9`) skips the restore and leaves the stale label - rename the tab by hand to recover; and the restored label is internally a custom name (Herdr has no clear-to-auto API), so that tab keeps a number-looking label but stops renumbering on later tab closes/reorders.
|
|
186
186
|
|
|
187
|
-
`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.
|
|
187
|
+
`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. The `anthropic-beta` header is discovered at request time by probing pi's own request assembly (no network), so conditional betas pi adds - e.g. `server-side-fallback-2026-07-01` for models with server-side fallback - are preserved alongside `fast-mode-2026-02-01`. If the probe fails, the header falls back to the static OAuth-identity + fast-mode list. 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.
|
|
188
188
|
|
|
189
189
|
`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.
|
|
190
190
|
|
package/extensions/fast-mode.ts
CHANGED
|
@@ -13,16 +13,20 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Header coupling to pi-ai internals: pi assembles `anthropic-beta` AFTER this
|
|
15
15
|
* hook and merges the hook's headers LAST, so setting the header here REPLACES
|
|
16
|
-
* pi's list.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
16
|
+
* pi's list. Instead of guessing that list, the hook discovers it: it probes
|
|
17
|
+
* pi-ai's own request assembly (dynamic import of the lazy anthropic-messages
|
|
18
|
+
* entrypoint) with a capturing fetch - zero network, zero tokens - and unions
|
|
19
|
+
* the captured betas with the fast-mode beta. Probe failure falls back to the
|
|
20
|
+
* static reconstruction (OAUTH_IDENTITY_BETAS stays, demoted to fallback-only
|
|
21
|
+
* seeding). Known limit: the probe context is tool-less, so tool/thinking-
|
|
22
|
+
* conditional betas are captured under fixed defaults - fine while allowlisted
|
|
23
|
+
* models keep forceAdaptiveThinking: true in the catalog. Probe cost: one
|
|
24
|
+
* extra in-process request-assembly pass per qualifying call, no network -
|
|
25
|
+
* per-request by design, no cache.
|
|
22
26
|
*/
|
|
23
27
|
|
|
24
28
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
25
|
-
import type { Usage } from "@earendil-works/pi-ai";
|
|
29
|
+
import type { Context, Model, StreamOptions, Usage } from "@earendil-works/pi-ai";
|
|
26
30
|
import { resolveConfig } from "../lib/extension-config.ts";
|
|
27
31
|
|
|
28
32
|
export const FAST_MODE_BETA = "fast-mode-2026-02-01";
|
|
@@ -82,7 +86,11 @@ export function injectSpeed(payload: unknown): unknown {
|
|
|
82
86
|
return { ...(payload as Record<string, unknown>), speed: FAST_SPEED };
|
|
83
87
|
}
|
|
84
88
|
|
|
85
|
-
export function buildBetaHeader(
|
|
89
|
+
export function buildBetaHeader(
|
|
90
|
+
existing: string | null | undefined,
|
|
91
|
+
isOAuth: boolean,
|
|
92
|
+
probedBetas: string | null = null,
|
|
93
|
+
): string {
|
|
86
94
|
const seen = new Set<string>();
|
|
87
95
|
const out: string[] = [];
|
|
88
96
|
const add = (b: string): void => {
|
|
@@ -92,12 +100,62 @@ export function buildBetaHeader(existing: string | null | undefined, isOAuth: bo
|
|
|
92
100
|
out.push(t);
|
|
93
101
|
}
|
|
94
102
|
};
|
|
95
|
-
if (
|
|
103
|
+
if (probedBetas !== null) probedBetas.split(",").forEach(add);
|
|
104
|
+
else if (isOAuth) OAUTH_IDENTITY_BETAS.forEach(add);
|
|
96
105
|
if (typeof existing === "string") existing.split(",").forEach(add);
|
|
97
106
|
add(FAST_MODE_BETA);
|
|
98
107
|
return out.join(",");
|
|
99
108
|
}
|
|
100
109
|
|
|
110
|
+
export type ProbeAuth = { apiKey?: string; headers?: Record<string, string | null> };
|
|
111
|
+
|
|
112
|
+
// Minimal context for the beta probe: one user message, no tools, no system
|
|
113
|
+
// prompt. Option-dependent betas (fine-grained tool streaming) are therefore
|
|
114
|
+
// probed under fixed defaults - sufficient while the allowlisted models keep
|
|
115
|
+
// forceAdaptiveThinking: true in the catalog; a future allowlisted model with
|
|
116
|
+
// tool- or thinking-conditional betas would have those dropped, same as today.
|
|
117
|
+
const PROBE_CONTEXT: Context = { messages: [{ role: "user", content: "probe", timestamp: 0 }] };
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Discover the anthropic-beta list pi-ai would send for this model + auth by
|
|
121
|
+
* driving pi-ai's own request assembly against a capturing fetch. Zero
|
|
122
|
+
* network, zero tokens: the fetch records the request headers, then throws.
|
|
123
|
+
* Returns the captured header value, or null on any failure - missing
|
|
124
|
+
* optional peer (the guarded dynamic import() below), probe throw before
|
|
125
|
+
* capture, or no header present - and the caller falls back to the static
|
|
126
|
+
* reconstruction. maxRetries: 0 stops pi-ai's retry wrapper from re-driving
|
|
127
|
+
* the intentionally failing probe fetch.
|
|
128
|
+
*/
|
|
129
|
+
export async function probePiBetaHeader(
|
|
130
|
+
model: Model<any>,
|
|
131
|
+
auth: ProbeAuth,
|
|
132
|
+
fetchImpl?: (input: any, init?: any) => Promise<any>,
|
|
133
|
+
): Promise<string | null> {
|
|
134
|
+
let captured: string | null = null;
|
|
135
|
+
const capturingFetch = async (input: any, init?: any): Promise<any> => {
|
|
136
|
+
const headers = init?.headers ?? input?.headers;
|
|
137
|
+
let value: unknown;
|
|
138
|
+
if (headers && typeof headers.get === "function") value = headers.get(BETA_HEADER);
|
|
139
|
+
else if (headers && typeof headers === "object") value = (headers as Record<string, unknown>)[BETA_HEADER];
|
|
140
|
+
if (typeof value === "string") captured = value;
|
|
141
|
+
throw new Error("fast-mode beta probe abort");
|
|
142
|
+
};
|
|
143
|
+
const options: StreamOptions = {
|
|
144
|
+
apiKey: auth.apiKey,
|
|
145
|
+
headers: auth.headers,
|
|
146
|
+
fetch: (fetchImpl ?? capturingFetch) as unknown as StreamOptions["fetch"],
|
|
147
|
+
maxRetries: 0,
|
|
148
|
+
};
|
|
149
|
+
try {
|
|
150
|
+
const { anthropicMessagesApi } = await import("@earendil-works/pi-ai/api/anthropic-messages.lazy");
|
|
151
|
+
const stream = anthropicMessagesApi().stream(model, PROBE_CONTEXT, options);
|
|
152
|
+
await stream.result().catch(() => {});
|
|
153
|
+
} catch {
|
|
154
|
+
return captured;
|
|
155
|
+
}
|
|
156
|
+
return captured;
|
|
157
|
+
}
|
|
158
|
+
|
|
101
159
|
type State = { config: boolean; flag: boolean; live: boolean | null };
|
|
102
160
|
|
|
103
161
|
export function resolveEnabled(s: State): boolean {
|
|
@@ -129,12 +187,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
129
187
|
ctx.ui.setStatus(STATUS_KEY, shouldInject(enabled, ctx.model) ? "\u26a1 fast" : "\u26a1 n/a");
|
|
130
188
|
};
|
|
131
189
|
|
|
132
|
-
|
|
190
|
+
type ResolvedAuth = ProbeAuth & { isOAuth: boolean };
|
|
191
|
+
|
|
192
|
+
const resolveAuth = async (ctx: ExtensionContext): Promise<ResolvedAuth | null> => {
|
|
133
193
|
if (!ctx.model) return null;
|
|
134
194
|
try {
|
|
135
195
|
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model);
|
|
136
196
|
if (!auth.ok) return null;
|
|
137
|
-
return
|
|
197
|
+
return {
|
|
198
|
+
isOAuth: typeof auth.apiKey === "string" && auth.apiKey.includes("sk-ant-oat"),
|
|
199
|
+
apiKey: auth.apiKey,
|
|
200
|
+
headers: auth.headers,
|
|
201
|
+
};
|
|
138
202
|
} catch {
|
|
139
203
|
return null;
|
|
140
204
|
}
|
|
@@ -170,12 +234,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
170
234
|
pendingFastHeader = false;
|
|
171
235
|
return;
|
|
172
236
|
}
|
|
173
|
-
const
|
|
174
|
-
if (
|
|
237
|
+
const auth = await resolveAuth(ctx);
|
|
238
|
+
if (auth === null) {
|
|
175
239
|
pendingFastHeader = false;
|
|
176
240
|
return;
|
|
177
241
|
}
|
|
178
|
-
|
|
242
|
+
const probed = await probePiBetaHeader(ctx.model!, auth);
|
|
243
|
+
event.headers[BETA_HEADER] = buildBetaHeader(event.headers[BETA_HEADER], auth.isOAuth, probed);
|
|
179
244
|
pendingFastHeader = true;
|
|
180
245
|
});
|
|
181
246
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-quiver",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.2",
|
|
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",
|