@diegopetrucci/pi-extensions 0.1.80 → 0.1.81

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.
@@ -12,9 +12,12 @@ async function checkDirtyRepo(
12
12
  ctx: ExtensionContext,
13
13
  action: string,
14
14
  ): Promise<{ cancel: boolean } | undefined> {
15
- const { stdout, code } = await pi.exec("git", ["status", "--porcelain"]);
15
+ const { stdout, code, killed } = await pi.exec("git", ["status", "--porcelain"], {
16
+ timeout: 15_000,
17
+ signal: ctx.signal,
18
+ });
16
19
 
17
- if (code !== 0) {
20
+ if (killed || code !== 0) {
18
21
  return;
19
22
  }
20
23
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-dirty-repo-guard",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "A pi extension that prompts before session changes when the current git repo has uncommitted changes.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -34,11 +34,7 @@ For eligible Anthropic Claude models it injects:
34
34
  }
35
35
  ```
36
36
 
37
- It also adds the required Anthropic beta header value:
38
-
39
- ```text
40
- anthropic-beta: fast-mode-2026-02-01
41
- ```
37
+ It also appends the required beta `fast-mode-2026-02-01` to the request's `betas` list, which Pi sends as the `anthropic-beta` header. The extension does not set the `anthropic-beta` header itself, so Pi's own betas are kept.
42
38
 
43
39
  ## Eligibility
44
40
 
@@ -12,9 +12,6 @@ const ANTHROPIC_PROVIDER_ID = "anthropic";
12
12
  const ANTHROPIC_API_ID = "anthropic-messages";
13
13
  const ANTHROPIC_FAST_SPEED = "fast";
14
14
  const ANTHROPIC_FAST_BETA = "fast-mode-2026-02-01";
15
- const CLAUDE_CODE_OAUTH_BETAS = ["claude-code-20250219", "oauth-2025-04-20"];
16
- const FINE_GRAINED_TOOL_STREAMING_BETA = "fine-grained-tool-streaming-2025-05-14";
17
- const INTERLEAVED_THINKING_BETA = "interleaved-thinking-2025-05-14";
18
15
  const ANTHROPIC_SUPPORTED_MODELS = new Set(["claude-opus-4-8", "claude-opus-5", "claude-opus-5-5"]);
19
16
 
20
17
  const OPENAI_PROVIDER_ID = "openai-codex";
@@ -79,11 +76,6 @@ type RecursivePartial<T> = {
79
76
 
80
77
  type PayloadRecord = Record<string, unknown>;
81
78
 
82
- type AnthropicHeaderCompat = {
83
- forceAdaptiveThinking?: boolean;
84
- supportsEagerToolInputStreaming?: boolean;
85
- };
86
-
87
79
  type Eligibility = {
88
80
  eligible: boolean;
89
81
  modelKey: string;
@@ -368,67 +360,17 @@ function getStatusMessage(ctx: ExtensionContext, state: SessionState): string {
368
360
  return `Fast mode is ${describeMode(state)}. Current model: ${eligibility.modelKey}.${injected}`;
369
361
  }
370
362
 
371
- function splitBetaHeader(value: string | null | undefined): string[] {
372
- return (value ?? "")
373
- .split(",")
374
- .map((part) => part.trim())
375
- .filter(Boolean);
376
- }
377
-
378
- async function isAnthropicOAuth(ctx: ExtensionContext): Promise<boolean> {
379
- try {
380
- const resolution = await ctx.modelRegistry.getProviderAuth(ANTHROPIC_PROVIDER_ID);
381
- const apiKey = resolution?.auth.apiKey;
382
- if (typeof apiKey === "string") return apiKey.includes("sk-ant-oat");
383
- } catch {
384
- // Fall back to the synchronous registry snapshot below.
385
- }
386
-
387
- return ctx.model ? ctx.modelRegistry.isUsingOAuth(ctx.model) : false;
388
- }
389
-
390
- function getAnthropicProviderBetas(ctx: ExtensionContext, hasActiveTools: boolean): string[] {
391
- const model = ctx.model;
392
- if (!model) return [];
393
- const compat = model.compat as AnthropicHeaderCompat | undefined;
394
-
395
- const betas: string[] = [];
396
- if (hasActiveTools && compat?.supportsEagerToolInputStreaming === false) {
397
- betas.push(FINE_GRAINED_TOOL_STREAMING_BETA);
398
- }
399
- if (compat?.forceAdaptiveThinking !== true) {
400
- betas.push(INTERLEAVED_THINKING_BETA);
401
- }
402
- return betas;
403
- }
404
-
405
- async function injectAnthropicFastHeader(
406
- headers: Record<string, string | null>,
407
- ctx: ExtensionContext,
408
- state: SessionState,
409
- hasActiveTools: boolean,
410
- ): Promise<void> {
411
- if (state.ultrafast || !isFastEnabled(state)) return;
412
- const eligibility = getEligibility(ctx, false);
413
- if (!eligibility.eligible || eligibility.provider !== "anthropic") return;
414
-
415
- const matchingKeys = Object.keys(headers).filter(
416
- (name) => name.toLowerCase() === "anthropic-beta",
417
- );
418
- const existing = matchingKeys.flatMap((name) => splitBetaHeader(headers[name]));
419
- const matchingModelEntries = Object.entries(ctx.model?.headers ?? {}).filter(
420
- ([name]) => name.toLowerCase() === "anthropic-beta",
421
- );
422
- const modelBetas = matchingModelEntries.flatMap(([, value]) => splitBetaHeader(value));
423
- const providerBetas = getAnthropicProviderBetas(ctx, hasActiveTools);
424
- const oauthBetas = (await isAnthropicOAuth(ctx)) ? CLAUDE_CODE_OAUTH_BETAS : [];
425
- const next = Array.from(
426
- new Set([...modelBetas, ...existing, ...oauthBetas, ...providerBetas, ANTHROPIC_FAST_BETA]),
427
- );
428
-
429
- for (const key of matchingKeys) delete headers[key];
430
- const outputKey = matchingModelEntries.at(-1)?.[0] ?? "anthropic-beta";
431
- headers[outputKey] = next.join(",");
363
+ /**
364
+ * Adds the Fast beta to the request's `betas` list instead of rewriting the
365
+ * `anthropic-beta` header. Pi derives the final beta list from the model and
366
+ * request (mid-conversation effort, server-side fallback, inline tools, OAuth,
367
+ * interleaved thinking, ...) and treats an explicit `anthropic-beta` header as
368
+ * the complete list, so setting that header here would drop any beta this
369
+ * extension does not know about.
370
+ */
371
+ function withAnthropicFastBeta(betas: unknown[] | undefined): unknown[] {
372
+ const existing = betas ?? [];
373
+ return existing.includes(ANTHROPIC_FAST_BETA) ? existing : [...existing, ANTHROPIC_FAST_BETA];
432
374
  }
433
375
 
434
376
  function injectUltrafastPayload(
@@ -461,9 +403,10 @@ function injectFastPayload(
461
403
 
462
404
  if (eligibility.provider === "anthropic") {
463
405
  if ("speed" in payload) return undefined;
406
+ if (payload.betas !== undefined && !Array.isArray(payload.betas)) return undefined;
464
407
  state.lastInjectedAt = Date.now();
465
408
  state.lastInjectedModel = eligibility.modelKey;
466
- return { ...payload, speed: ANTHROPIC_FAST_SPEED };
409
+ return { ...payload, speed: ANTHROPIC_FAST_SPEED, betas: withAnthropicFastBeta(payload.betas) };
467
410
  }
468
411
 
469
412
  if ("service_tier" in payload) return undefined;
@@ -552,12 +495,6 @@ export default function fastExtension(pi: ExtensionAPI) {
552
495
  updateStatus(ctx, state);
553
496
  });
554
497
 
555
- pi.on("before_provider_headers", async (event, ctx) => {
556
- const hasActiveTools =
557
- typeof pi.getActiveTools === "function" && pi.getActiveTools().length > 0;
558
- await injectAnthropicFastHeader(event.headers, ctx, getState(ctx), hasActiveTools);
559
- });
560
-
561
498
  pi.on("before_provider_request", (event, ctx) => {
562
499
  const state = getState(ctx);
563
500
  if (state.ultrafast) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-fast",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "A pi extension that enables Fast mode for supported direct OpenAI API, OpenAI Codex, and Anthropic Claude models based on the currently selected model.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -49,6 +49,7 @@ export default function (pi: ExtensionAPI) {
49
49
  try {
50
50
  const bashResult = await pi.exec("bash", ["-c", command], {
51
51
  timeout: TIMEOUT_MS,
52
+ signal: ctx.signal,
52
53
  });
53
54
 
54
55
  const output = bashResult.stdout || bashResult.stderr || "";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-inline-bash",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "A pi extension that expands inline bash commands in user prompts.",
5
5
  "keywords": [
6
6
  "pi-package",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-extensions",
3
- "version": "0.1.80",
3
+ "version": "0.1.81",
4
4
  "description": "A collection of pi extensions and skills for annotation UIs, context management, workflow audits, contrarian review, review-comment triage, notifications, brrr push alerts, safety guards, GitHub research, repo-local knowledge, todos, tool rendering, model/provider helpers, and Xiaohei-style article illustrations.",
5
5
  "keywords": [
6
6
  "pi-package",