pi-quiver 3.1.2 → 3.2.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 +4 -0
- package/README.md +5 -3
- package/fast-mode.ts +182 -0
- package/package.json +6 -4
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,10 @@ 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
|
+
## v3.2.0 - 2026-07-12
|
|
12
|
+
|
|
13
|
+
- Add `fast-mode` extension: opt-in Anthropic fast mode for Claude Opus 4.8 (`speed: "fast"` payload + `fast-mode-2026-02-01` beta header), controlled via `fastMode` settings key, `--fast` flag, and `/fast [on|off|status]`. OFF by default. Preserves OAuth identity betas. Requires pi bundling `@earendil-works/pi-coding-agent` >= 0.80.5 (the `before_provider_headers` hook).
|
|
14
|
+
|
|
11
15
|
## v3.1.2 - 2026-07-07
|
|
12
16
|
|
|
13
17
|
- **`fetch` routes GitHub Actions run URLs through `gh`.** `github.com/{owner}/{repo}/actions/runs/{id}` URLs are served by `gh run view {id} --repo {owner}/{repo}` and returned through the existing size gate with a `Source: gh run view ...` header, alongside the existing issue/PR/repo routing. Only the bare run URL routes; deeper paths (`.../runs/{id}/jobs/{jobId}`, `.../actions/workflows/{file}`) fall back to HTTP. Falls back silently when `gh` is absent/unauthenticated/errors; `raw=true` forces the rendered page.
|
package/README.md
CHANGED
|
@@ -66,6 +66,7 @@ A 300 KB changelog page never touches your context window - you get a preview an
|
|
|
66
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
67
|
| `session-name.ts` | `/session-name` | Manual + opt-in automatic session naming, with Ghostty tab rename. OFF by default. |
|
|
68
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 request, any thinking level. `--fast` flag + `/fast [on\|off\|status]`. OFF by default. |
|
|
69
70
|
|
|
70
71
|
Full routing rules, size-gate mechanics, and config: [doc/fetch.md](doc/fetch.md), [doc/doc-to-md.md](doc/doc-to-md.md).
|
|
71
72
|
|
|
@@ -130,18 +131,19 @@ The npm package's bundled JS deps install automatically on `pi install`. A few *
|
|
|
130
131
|
|
|
131
132
|
None is a hard install-time dependency of the package; they are tools you provide in the environment where pi runs.
|
|
132
133
|
|
|
133
|
-
###
|
|
134
|
+
### Opt-in extension config
|
|
134
135
|
|
|
135
136
|
Both are opt-in via `settings.json` (project `.pi/settings.json` overrides the global agent-dir layer):
|
|
136
137
|
|
|
137
138
|
```jsonc
|
|
138
139
|
{
|
|
139
140
|
"sessionAutoName": { "enabled": false, "ghosttyTab": true }, // or boolean shorthand
|
|
140
|
-
"swordHeader": false
|
|
141
|
+
"swordHeader": false, // or { "enabled": true }
|
|
142
|
+
"fastMode": false // or { "enabled": true }
|
|
141
143
|
}
|
|
142
144
|
```
|
|
143
145
|
|
|
144
|
-
`sessionAutoName.enabled` makes one extra short LLM call per session (once, after the first turn) to title it; `false` (default) makes no model calls. 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.
|
|
146
|
+
`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` 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.
|
|
145
147
|
|
|
146
148
|
## Development
|
|
147
149
|
|
package/fast-mode.ts
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fast mode for Claude Opus 4.8.
|
|
3
|
+
*
|
|
4
|
+
* When enabled, injects Anthropic's fast-mode signals into every qualifying
|
|
5
|
+
* Opus 4.8 request on the anthropic-messages API, regardless of thinking level:
|
|
6
|
+
* - payload: { ...payload, speed: "fast" } (before_provider_request)
|
|
7
|
+
* - header: anthropic-beta: ...,fast-mode-2026-02-01 (before_provider_headers)
|
|
8
|
+
*
|
|
9
|
+
* OFF BY DEFAULT. Three control surfaces, lowest precedence first:
|
|
10
|
+
* 1. settings.json "fastMode": true | { "enabled": true } (default false)
|
|
11
|
+
* 2. --fast launch flag (force-on only)
|
|
12
|
+
* 3. /fast [on|off|status] live toggle (wins for session)
|
|
13
|
+
*
|
|
14
|
+
* Header coupling to pi-ai internals: pi assembles `anthropic-beta` AFTER this
|
|
15
|
+
* hook and merges the hook's headers LAST, so setting the header here REPLACES
|
|
16
|
+
* pi's list. For opus-4-8 pi's conditional betas (fine-grained tool streaming,
|
|
17
|
+
* interleaved thinking) are never applied (eager tool streaming defaults on +
|
|
18
|
+
* forceAdaptiveThinking), so the only betas to preserve are the OAuth identity
|
|
19
|
+
* betas. We detect OAuth via the same token marker pi uses and rebuild the
|
|
20
|
+
* exact list. If pi later adds betas for opus-4-8, revisit buildBetaHeader.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
24
|
+
import { resolveConfig } from "./extension-config.ts";
|
|
25
|
+
|
|
26
|
+
export const FAST_MODE_BETA = "fast-mode-2026-02-01";
|
|
27
|
+
export const FAST_SPEED = "fast";
|
|
28
|
+
// Loose prefix: matches dated snapshots (claude-opus-4-8-*). Opus 4.7 is out of
|
|
29
|
+
// scope (D1); a future 4.9 needs a one-line addition here.
|
|
30
|
+
export const FAST_MODE_MODEL_PREFIXES = ["claude-opus-4-8"];
|
|
31
|
+
export const OAUTH_IDENTITY_BETAS = ["claude-code-20250219", "oauth-2025-04-20"];
|
|
32
|
+
const STATUS_KEY = "fast-mode";
|
|
33
|
+
const BETA_HEADER = "anthropic-beta";
|
|
34
|
+
|
|
35
|
+
type Config = { enabled: boolean };
|
|
36
|
+
const DEFAULT_CONFIG: Config = { enabled: false };
|
|
37
|
+
|
|
38
|
+
export function coerce(raw: unknown): Partial<Config> | undefined {
|
|
39
|
+
if (raw === undefined) return undefined;
|
|
40
|
+
if (typeof raw === "boolean") return { enabled: raw };
|
|
41
|
+
if (raw && typeof raw === "object") {
|
|
42
|
+
const o = raw as Record<string, unknown>;
|
|
43
|
+
const out: Partial<Config> = {};
|
|
44
|
+
if (typeof o.enabled === "boolean") out.enabled = o.enabled;
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type ModelLike = { id?: string; api?: string; provider?: string } | undefined;
|
|
51
|
+
|
|
52
|
+
export function shouldInject(enabled: boolean, model: ModelLike): boolean {
|
|
53
|
+
if (!enabled || !model) return false;
|
|
54
|
+
if (model.provider !== "anthropic") return false;
|
|
55
|
+
if (model.api !== "anthropic-messages") return false;
|
|
56
|
+
const id = model.id;
|
|
57
|
+
if (typeof id !== "string") return false;
|
|
58
|
+
return FAST_MODE_MODEL_PREFIXES.some((p) => id.startsWith(p));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function injectSpeed(payload: unknown): unknown {
|
|
62
|
+
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
|
|
63
|
+
return payload;
|
|
64
|
+
}
|
|
65
|
+
return { ...(payload as Record<string, unknown>), speed: FAST_SPEED };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function buildBetaHeader(existing: string | null | undefined, isOAuth: boolean): string {
|
|
69
|
+
const seen = new Set<string>();
|
|
70
|
+
const out: string[] = [];
|
|
71
|
+
const add = (b: string): void => {
|
|
72
|
+
const t = b.trim();
|
|
73
|
+
if (t && !seen.has(t)) {
|
|
74
|
+
seen.add(t);
|
|
75
|
+
out.push(t);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
if (isOAuth) OAUTH_IDENTITY_BETAS.forEach(add);
|
|
79
|
+
if (typeof existing === "string") existing.split(",").forEach(add);
|
|
80
|
+
add(FAST_MODE_BETA);
|
|
81
|
+
return out.join(",");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
type State = { config: boolean; flag: boolean; live: boolean | null };
|
|
85
|
+
|
|
86
|
+
export function resolveEnabled(s: State): boolean {
|
|
87
|
+
if (s.live !== null) return s.live;
|
|
88
|
+
return s.flag || s.config;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export default function (pi: ExtensionAPI) {
|
|
92
|
+
let liveOverride: boolean | null = null;
|
|
93
|
+
let enabled = false;
|
|
94
|
+
|
|
95
|
+
const readFlag = (): boolean => pi.getFlag("fast") === true;
|
|
96
|
+
|
|
97
|
+
const resolveState = (ctx: ExtensionContext): boolean => {
|
|
98
|
+
const config = resolveConfig(ctx.cwd, "fastMode", DEFAULT_CONFIG, coerce).enabled;
|
|
99
|
+
enabled = resolveEnabled({ config, flag: readFlag(), live: liveOverride });
|
|
100
|
+
return enabled;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const refreshStatus = (ctx: ExtensionContext): void => {
|
|
104
|
+
if (!enabled) {
|
|
105
|
+
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
ctx.ui.setStatus(STATUS_KEY, shouldInject(enabled, ctx.model) ? "\u26a1 fast" : "\u26a1 n/a");
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const detectOAuth = async (ctx: ExtensionContext): Promise<boolean | null> => {
|
|
112
|
+
if (!ctx.model) return null;
|
|
113
|
+
try {
|
|
114
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model);
|
|
115
|
+
if (!auth.ok) return null;
|
|
116
|
+
return typeof auth.apiKey === "string" && auth.apiKey.includes("sk-ant-oat");
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
pi.registerFlag("fast", {
|
|
123
|
+
type: "boolean",
|
|
124
|
+
description: "Enable Anthropic fast mode for Opus 4.8 requests this launch",
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
128
|
+
resolveState(ctx);
|
|
129
|
+
refreshStatus(ctx);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
pi.on("model_select", async (_event, ctx) => {
|
|
133
|
+
refreshStatus(ctx);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
pi.on("before_provider_request", (event, ctx) => {
|
|
137
|
+
if (!shouldInject(enabled, ctx.model)) return;
|
|
138
|
+
return injectSpeed(event.payload);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
pi.on("before_provider_headers", async (event, ctx) => {
|
|
142
|
+
if (!shouldInject(enabled, ctx.model)) return;
|
|
143
|
+
if (!event.headers) return;
|
|
144
|
+
const isOAuth = await detectOAuth(ctx);
|
|
145
|
+
if (isOAuth === null) return;
|
|
146
|
+
event.headers[BETA_HEADER] = buildBetaHeader(event.headers[BETA_HEADER], isOAuth);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
pi.registerCommand("fast", {
|
|
150
|
+
description: "Manage Opus 4.8 fast mode: /fast [on|off|status]",
|
|
151
|
+
getArgumentCompletions: (prefix) => {
|
|
152
|
+
const p = prefix.trim().toLowerCase();
|
|
153
|
+
if (p.includes(" ")) return null;
|
|
154
|
+
const matches = ["on", "off", "status"].filter((v) => v.startsWith(p));
|
|
155
|
+
return matches.length ? matches.map((value) => ({ value, label: value })) : null;
|
|
156
|
+
},
|
|
157
|
+
handler: async (args, ctx) => {
|
|
158
|
+
const arg = args.trim().toLowerCase();
|
|
159
|
+
if (arg === "status") {
|
|
160
|
+
const eff = liveOverride !== null ? "live toggle" : readFlag() ? "--fast flag" : "settings.json";
|
|
161
|
+
const model = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "(no model selected)";
|
|
162
|
+
const applies = shouldInject(enabled, ctx.model) ? "applies to current model" : "does not apply to current model";
|
|
163
|
+
ctx.ui.notify(
|
|
164
|
+
`Fast mode is ${enabled ? "on" : "off"} (source: ${eff}). Model: ${model} - ${applies}.`,
|
|
165
|
+
"info",
|
|
166
|
+
);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (arg === "on" || arg === "off") {
|
|
170
|
+
liveOverride = arg === "on";
|
|
171
|
+
} else if (arg === "") {
|
|
172
|
+
liveOverride = !enabled;
|
|
173
|
+
} else {
|
|
174
|
+
ctx.ui.notify("Usage: /fast [on|off|status]", "warning");
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
resolveState(ctx);
|
|
178
|
+
refreshStatus(ctx);
|
|
179
|
+
ctx.ui.notify(`Fast mode ${enabled ? "enabled" : "disabled"}`, "info");
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-quiver",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.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, and a themed ASCII startup header.",
|
|
5
5
|
"author": "Jacek Juraszek",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"doc_to_md.ts",
|
|
33
33
|
"session-name.ts",
|
|
34
34
|
"sword-header.ts",
|
|
35
|
+
"fast-mode.ts",
|
|
35
36
|
"extension-config.ts",
|
|
36
37
|
"scripts/pdf_to_md.py",
|
|
37
38
|
"types/**/*.d.ts",
|
|
@@ -41,7 +42,7 @@
|
|
|
41
42
|
"scripts": {
|
|
42
43
|
"check:agents-core": "node scripts/check-agents-core.mjs",
|
|
43
44
|
"test": "node --test \"*.test.ts\"",
|
|
44
|
-
"typecheck": "npx -y tsc --noEmit --allowImportingTsExtensions --target es2022 --module nodenext --moduleResolution nodenext --strict --skipLibCheck --esModuleInterop --resolveJsonModule --lib es2022 --types node fetch.ts fetch.test.ts doc_to_md.ts doc_to_md.test.ts session-name.ts session-name.test.ts sword-header.ts sword-header.test.ts extension-config.ts types/turndown-plugin-gfm.d.ts",
|
|
45
|
+
"typecheck": "npx -y tsc --noEmit --allowImportingTsExtensions --target es2022 --module nodenext --moduleResolution nodenext --strict --skipLibCheck --esModuleInterop --resolveJsonModule --lib es2022 --types node fetch.ts fetch.test.ts doc_to_md.ts doc_to_md.test.ts session-name.ts session-name.test.ts sword-header.ts sword-header.test.ts fast-mode.ts fast-mode.test.ts extension-config.ts types/turndown-plugin-gfm.d.ts",
|
|
45
46
|
"test:all": "npm run check:agents-core && npm run test && npm run typecheck"
|
|
46
47
|
},
|
|
47
48
|
"pi": {
|
|
@@ -49,7 +50,8 @@
|
|
|
49
50
|
"./fetch.ts",
|
|
50
51
|
"./doc_to_md.ts",
|
|
51
52
|
"./session-name.ts",
|
|
52
|
-
"./sword-header.ts"
|
|
53
|
+
"./sword-header.ts",
|
|
54
|
+
"./fast-mode.ts"
|
|
53
55
|
],
|
|
54
56
|
"image": "https://raw.githubusercontent.com/jjuraszek/pi-quiver/main/pi-quiver.png"
|
|
55
57
|
},
|
|
@@ -86,7 +88,7 @@
|
|
|
86
88
|
},
|
|
87
89
|
"devDependencies": {
|
|
88
90
|
"@earendil-works/pi-ai": "^0.80.3",
|
|
89
|
-
"@earendil-works/pi-coding-agent": "^0.80.
|
|
91
|
+
"@earendil-works/pi-coding-agent": "^0.80.5",
|
|
90
92
|
"@earendil-works/pi-tui": "^0.80.3",
|
|
91
93
|
"@sinclair/typebox": "^0.34.49",
|
|
92
94
|
"@types/jsdom": "^28.0.3",
|