@diegopetrucci/pi-extensions 0.1.79 → 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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-annotate-last-message",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "A standalone pi extension that adds /annotate-last-message, a native Glimpse UI for annotating the latest assistant reply.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1,6 +1,5 @@
1
1
  import type {
2
2
  AnnotateLastMessageInlineComment,
3
- AnnotateLastMessageSectionComment,
4
3
  AnnotateLastMessageSubmitPayload,
5
4
  LastAssistantMessageData,
6
5
  LastAssistantMessageLine,
@@ -34,10 +33,7 @@ function formatSectionLineRange(section: LastAssistantMessageSection): string {
34
33
  : `lines ${section.startLine}-${section.endLine}`;
35
34
  }
36
35
 
37
- function formatSectionComment(
38
- comment: AnnotateLastMessageSectionComment,
39
- section: LastAssistantMessageSection | undefined,
40
- ): string {
36
+ function formatSectionComment(section: LastAssistantMessageSection | undefined): string {
41
37
  if (section == null) {
42
38
  return "Unknown section";
43
39
  }
@@ -90,9 +86,7 @@ export function composeAnnotateLastMessagePrompt(
90
86
  if (sectionComments.length > 0) {
91
87
  lines.push("## Section comments");
92
88
  sectionComments.forEach((comment, index) => {
93
- lines.push(
94
- `${index + 1}. ${formatSectionComment(comment, sectionMap.get(comment.sectionId))}`,
95
- );
89
+ lines.push(`${index + 1}. ${formatSectionComment(sectionMap.get(comment.sectionId))}`);
96
90
  lines.push(` ${comment.body.trim()}`);
97
91
  lines.push("");
98
92
  });
@@ -155,13 +155,11 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
155
155
  "gpt-6-luna ",
156
156
  "gpt-6-luna",
157
157
  "gpt-5.6-luna",
158
- "gpt-5.5",
159
158
  "claude-opus-5.5 ",
160
159
  "claude-opus-5.5",
161
160
  "claude-opus-5",
162
161
  "gpt-5.4",
163
162
  "gpt-5.3-codex",
164
- "gpt-5-mini",
165
163
  "grok-4.7 ",
166
164
  "grok-4.7",
167
165
  "grok-4.6",
@@ -187,7 +185,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
187
185
  "gpt-5.4",
188
186
  "gpt-5.3-codex",
189
187
  ],
190
- "opencode-go": ["grok-4.7 ", "grok-4.7", "grok-4.6"],
188
+ "opencode-go": ["grok-4.7 ", "grok-4.7"],
191
189
  openrouter: [
192
190
  "openai/gpt-6-astra ",
193
191
  "openai/gpt-6-astra",
@@ -258,8 +256,6 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
258
256
  "gpt-6-luna ",
259
257
  "gpt-6-luna",
260
258
  "gpt-5.6-luna",
261
- "gpt-5.5",
262
- "gpt-5.3-codex-spark",
263
259
  ],
264
260
  xai: ["grok-4.7 ", "grok-4.7", "grok-4.6", "grok-4.5"],
265
261
  "vercel-ai-gateway": [
@@ -873,12 +869,6 @@ function addAssistantMessageUsage(total: Usage, message: unknown): void {
873
869
  addUsage(total, assistant.usage as Usage);
874
870
  }
875
871
 
876
- function aggregateAssistantUsage(messages: unknown[]): Usage {
877
- const total = createEmptyUsage();
878
- for (const message of messages) addAssistantMessageUsage(total, message);
879
- return total;
880
- }
881
-
882
872
  function addSessionEventUsage(total: Usage, event: unknown): void {
883
873
  const sessionEvent = event as { type?: string; message?: unknown; result?: { usage?: Usage } };
884
874
  if (sessionEvent?.type === "message_end")
@@ -1757,7 +1747,7 @@ export const __test__ = {
1757
1747
  normalizeThinkingLevel,
1758
1748
  resolveThinkingLevel,
1759
1749
  selectCodeReviewerModel,
1760
- aggregateAssistantUsage,
1750
+ createEmptyUsage,
1761
1751
  addAssistantMessageUsage,
1762
1752
  addSessionEventUsage,
1763
1753
  inspectFinalAssistant,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-code-reviewer",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "A standalone pi extension that adds a read-only code_reviewer subagent tool for isolated code reviews.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -252,23 +252,14 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
252
252
  "azure-openai-responses": AZURE_MODEL_PREFERENCES,
253
253
  baseten: [
254
254
  "moonshotai/Kimi-K3",
255
- "moonshotai/Kimi-K2.7-Code ",
256
255
  "zai-org/GLM-5.3 ",
257
256
  "deepseek-ai/DeepSeek-V4-Pro-0813",
258
257
  "deepseek-ai/DeepSeek-V4-Pro",
259
258
  "zai-org/GLM-5.2 ",
260
- "zai-org/GLM-5.1",
261
- "zai-org/GLM-5 ",
262
- "moonshotai/Kimi-K2.6",
263
- "moonshotai/Kimi-K2.5",
264
259
  "nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B",
265
- "nvidia/Nemotron-120B-A12B",
266
260
  "openai/gpt-oss-120b",
267
261
  "deepseek-ai/DeepSeek-V4-Flash-0731",
268
- "thinkingmachines/inkling ",
269
- "thinkingmachines/inkling-small",
270
262
  "zai-org/GLM-5.2-Fast",
271
- "zai-org/GLM-4.7",
272
263
  ],
273
264
  cerebras: ["gpt-oss-120b", "qwen-3.8-27b"],
274
265
  "cloudflare-ai-gateway": [
@@ -312,7 +303,6 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
312
303
  "claude-opus-5",
313
304
  "claude-fable-5",
314
305
  "claude-opus-4.8",
315
- "claude-opus-4.7",
316
306
  "gpt-6-astra ",
317
307
  "gpt-6-astra",
318
308
  "gpt-6.1-sol ",
@@ -323,19 +313,14 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
323
313
  "gpt-6-luna ",
324
314
  "gpt-6-luna",
325
315
  "gpt-5.6-luna",
326
- "gpt-5.5",
327
- "gpt-5.4 ",
328
316
  "gpt-5.3-codex",
329
- "gemini-3.5-flash",
330
317
  "claude-sonnet-5",
331
318
  "claude-sonnet-4.6",
332
- "gpt-5-mini",
333
319
  "grok-4.7 ",
334
320
  "grok-4.7",
335
321
  "grok-4.6",
336
322
  "grok-4.5",
337
323
  "kimi-k3",
338
- "kimi-k2.7-code ",
339
324
  ],
340
325
  google: [
341
326
  "gemini-3.1-pro-preview-customtools",
@@ -433,8 +418,6 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
433
418
  "gpt-6-luna ",
434
419
  "gpt-6-luna",
435
420
  "gpt-5.6-luna",
436
- "gpt-5.5",
437
- "gpt-5.3-codex-spark",
438
421
  ],
439
422
  opencode: [
440
423
  "claude-opus-5-5 ",
@@ -486,12 +469,10 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
486
469
  "mimo-v2.5-pro",
487
470
  "mimo-v2.5",
488
471
  "minimax-m3",
489
- "minimax-m2.7",
490
472
  "kimi-k3",
491
473
  "kimi-k2.7-code ",
492
474
  "grok-4.7 ",
493
475
  "grok-4.7",
494
- "grok-4.6",
495
476
  ],
496
477
  openrouter: [
497
478
  "anthropic/claude-opus-5.5 ",
@@ -608,9 +589,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
608
589
  "Qwen/Qwen3.7-Max",
609
590
  "Qwen/Qwen3.6-Plus",
610
591
  "MiniMaxAI/MiniMax-M3",
611
- "MiniMaxAI/MiniMax-M2.7",
612
592
  "openai/gpt-oss-120b",
613
- "nvidia/nemotron-3-ultra-550b-a55b",
614
593
  ],
615
594
  typesafe: [],
616
595
  "vercel-ai-gateway": [
@@ -646,7 +625,6 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
646
625
  "moonshotai/kimi-k2.7-code ",
647
626
  "moonshotai/kimi-k2.7-code-highspeed",
648
627
  "moonshotai/kimi-k2.6",
649
- "moonshotai/kimi-k2-thinking",
650
628
  "deepseek/deepseek-v4-pro",
651
629
  "deepseek/deepseek-v3.2-thinking",
652
630
  "alibaba/qwen3.7-max",
@@ -668,14 +646,12 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
668
646
  "zai/glm-5.1",
669
647
  ],
670
648
  xai: ["grok-4.7 ", "grok-4.7", "grok-4.6", "grok-4.5", "grok-4.3"],
671
- xiaomi: ["mimo-v2.5-pro-ultraspeed", "mimo-v2.5-pro", "mimo-v2.5"],
672
- "xiaomi-token-plan-ams": ["mimo-v2.5-pro", "mimo-v2.5"],
673
- "xiaomi-token-plan-cn": ["mimo-v2.5-pro", "mimo-v2.5"],
674
- "xiaomi-token-plan-sgp": ["mimo-v2.5-pro", "mimo-v2.5"],
649
+ xiaomi: ["mimo-v2.6-pro-ultraspeed", "mimo-v2.6-pro", "mimo-v2.6-flash"],
650
+ "xiaomi-token-plan-ams": ["mimo-v2.6-pro", "mimo-v2.6-flash"],
651
+ "xiaomi-token-plan-cn": ["mimo-v2.6-pro", "mimo-v2.6-flash"],
652
+ "xiaomi-token-plan-sgp": ["mimo-v2.6-pro", "mimo-v2.6-flash"],
675
653
  nvidia: [
676
654
  "nvidia/nemotron-3-ultra-550b-a55b",
677
- "nvidia/nemotron-3-super-120b-a12b",
678
- "moonshotai/kimi-k3",
679
655
  "moonshotai/kimi-k2.6",
680
656
  "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning",
681
657
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-contrarian",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "description": "A read-only contrarian subagent extension for pi that stress-tests plans, designs, assumptions, and conclusions.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -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",
@@ -275,6 +275,14 @@ Notes:
275
275
  warm request prefix between consecutive LLM calls separated only by a
276
276
  toolResult) illustrates cache invalidation, not proof that amortization is
277
277
  impossible. Set `mid_loop` back to `22` to restore the old parity behavior.
278
+ - `gate.breakEvenThreshold` is a deprecated legacy field retained for
279
+ compatibility. Loaded config always resolves through
280
+ `gate.breakEvenThresholdByState`, so a global-only value (including `0`) has
281
+ no runtime effect, and missing or invalid state values use their existing
282
+ normalized defaults rather than inheriting the global value. Direct callers
283
+ of the exported `resolveBreakEvenThreshold` may still use this field as the
284
+ fallback when they pass an unnormalized gate object. Set explicit per-state
285
+ values instead.
278
286
  - `gate.breakEvenThreshold`'s default of `22` is calibrated from the
279
287
  representative-corpus benchmark at `cachedPriceRatio` r=0.1 (aggressive
280
288
  prompt caching, the common case). It is **provider/ratio-
@@ -243,13 +243,15 @@ export interface PruneGateConfig {
243
243
  /** Fraction of full price still paid for cached ( r ). Default 0.1. */
244
244
  cachedPriceRatio: number;
245
245
  /**
246
- * Default break-even threshold (max future calls to amortize a cache
247
- * bust) used when no state-specific override applies. Recalibrated from
248
- * the representative-corpus benchmark; see DEFAULT_BREAK_EVEN_THRESHOLD
249
- * for the full rationale and ratio-sensitivity caveats.
246
+ * @deprecated Legacy global fallback retained for direct callers that pass
247
+ * an unnormalized gate object to resolveBreakEvenThreshold. normalizeConfig
248
+ * always supplies per-state values, so loaded config does not use this field
249
+ * to fill missing or invalid state values. Recalibrated from the
250
+ * representative-corpus benchmark; see DEFAULT_BREAK_EVEN_THRESHOLD for the
251
+ * full rationale and ratio-sensitivity caveats.
250
252
  */
251
253
  breakEvenThreshold: number;
252
- /** Per-state overrides. Defaults: idle 22, mid_loop 1. */
254
+ /** Per-state overrides. normalizeConfig defaults to idle 22 and mid_loop 1. */
253
255
  breakEvenThresholdByState: { idle: number; mid_loop: number };
254
256
  }
255
257
 
@@ -1631,11 +1633,6 @@ export function computeFileReadRange(args: Record<string, unknown> | undefined):
1631
1633
  return { start, end };
1632
1634
  }
1633
1635
 
1634
- /** True when a range represents a full-file read (starts at line 1, no limit). */
1635
- export function isFullFileReadRange(range: FileReadRange): boolean {
1636
- return range.start === 1 && range.end === undefined;
1637
- }
1638
-
1639
1636
  /**
1640
1637
  * Conservative range-coverage check: does `later` fully cover `earlier`?
1641
1638
  * Implements rules 1+2 above via one unified representation (a full-file
@@ -2268,11 +2265,9 @@ const STRATEGY_ALIASES: Record<string, StrategyKey> = {
2268
2265
  "error-purge": "errorPurge",
2269
2266
  errorpurge: "errorPurge",
2270
2267
  error_purge: "errorPurge",
2271
- errorPurge: "errorPurge",
2272
2268
  "superseded-file-ops": "supersededFileOps",
2273
2269
  supersededfileops: "supersededFileOps",
2274
2270
  superseded_file_ops: "supersededFileOps",
2275
- supersededFileOps: "supersededFileOps",
2276
2271
  };
2277
2272
 
2278
2273
  const STRATEGY_DISPLAY_NAMES: Record<StrategyKey, string> = {
@@ -2315,9 +2310,11 @@ export function parseContextPruningArgs(rawArgs: string): ContextPruningSubcomma
2315
2310
  return { kind: "toggle" };
2316
2311
  case "strategy": {
2317
2312
  const [nameRaw, valueRaw] = rest;
2318
- const key = nameRaw
2319
- ? (STRATEGY_ALIASES[nameRaw.toLowerCase()] ?? STRATEGY_ALIASES[nameRaw])
2320
- : undefined;
2313
+ const normalizedName = nameRaw?.toLowerCase();
2314
+ const key =
2315
+ normalizedName && Object.hasOwn(STRATEGY_ALIASES, normalizedName)
2316
+ ? STRATEGY_ALIASES[normalizedName]
2317
+ : undefined;
2321
2318
  const value = valueRaw?.toLowerCase();
2322
2319
  if (!key || (value !== "on" && value !== "off")) return { kind: "unknown", raw: rawArgs };
2323
2320
  return { kind: "strategy", strategy: key, value: value === "on" };
@@ -2463,7 +2460,6 @@ function notify(
2463
2460
  }
2464
2461
 
2465
2462
  export const __testing = {
2466
- globToRegExp,
2467
2463
  getConfigPath,
2468
2464
  readConfig,
2469
2465
  writeConfig,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-dynamic-context-pruning",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "type": "module",
5
5
  "description": "A pi extension that dynamically prunes stale context to keep sessions lean.",
6
6
  "keywords": [
@@ -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",
@@ -1275,12 +1275,6 @@ function addAssistantMessageUsage(total: Usage, message: unknown): void {
1275
1275
  addUsage(total, assistant.usage as Usage);
1276
1276
  }
1277
1277
 
1278
- function aggregateAssistantUsage(messages: unknown[]): Usage {
1279
- const total = createEmptyUsage();
1280
- for (const message of messages) addAssistantMessageUsage(total, message);
1281
- return total;
1282
- }
1283
-
1284
1278
  function addSessionEventUsage(total: Usage, event: unknown): void {
1285
1279
  const sessionEvent = event as { type?: string; message?: unknown; result?: { usage?: Usage } };
1286
1280
  if (sessionEvent?.type === "message_end")
@@ -1463,7 +1457,7 @@ export const __test__ = {
1463
1457
  isModelAvailabilityError,
1464
1458
  parseModelPreference,
1465
1459
  resolveThinkingLevel,
1466
- aggregateAssistantUsage,
1460
+ createEmptyUsage,
1467
1461
  addSessionEventUsage,
1468
1462
  inspectFinalAssistant,
1469
1463
  classifyRunFailure,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-librarian",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "description": "A pi GitHub research scout with a toggleable local repo checkout cache under the user's OS cache directory.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -202,23 +202,14 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
202
202
  "azure-openai-responses": AZURE_MODEL_PREFERENCES,
203
203
  baseten: [
204
204
  "moonshotai/Kimi-K3",
205
- "moonshotai/Kimi-K2.7-Code ",
206
205
  "zai-org/GLM-5.3 ",
207
206
  "deepseek-ai/DeepSeek-V4-Pro-0813",
208
207
  "deepseek-ai/DeepSeek-V4-Pro",
209
208
  "zai-org/GLM-5.2 ",
210
- "zai-org/GLM-5.1",
211
- "zai-org/GLM-5 ",
212
- "moonshotai/Kimi-K2.6",
213
- "moonshotai/Kimi-K2.5",
214
209
  "nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B",
215
- "nvidia/Nemotron-120B-A12B",
216
210
  "openai/gpt-oss-120b",
217
211
  "deepseek-ai/DeepSeek-V4-Flash-0731",
218
- "thinkingmachines/inkling ",
219
- "thinkingmachines/inkling-small",
220
212
  "zai-org/GLM-5.2-Fast",
221
- "zai-org/GLM-4.7",
222
213
  ],
223
214
  cerebras: ["gpt-oss-120b", "qwen-3.8-27b"],
224
215
  "cloudflare-ai-gateway": [
@@ -262,7 +253,6 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
262
253
  "claude-opus-5",
263
254
  "claude-fable-5",
264
255
  "claude-opus-4.8",
265
- "claude-opus-4.7",
266
256
  "gpt-6-astra ",
267
257
  "gpt-6-astra",
268
258
  "gpt-6.1-sol ",
@@ -273,19 +263,14 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
273
263
  "gpt-6-luna ",
274
264
  "gpt-6-luna",
275
265
  "gpt-5.6-luna",
276
- "gpt-5.5",
277
- "gpt-5.4 ",
278
266
  "gpt-5.3-codex",
279
- "gemini-3.5-flash",
280
267
  "claude-sonnet-5",
281
268
  "claude-sonnet-4.6",
282
- "gpt-5-mini",
283
269
  "grok-4.7 ",
284
270
  "grok-4.7",
285
271
  "grok-4.6",
286
272
  "grok-4.5",
287
273
  "kimi-k3",
288
- "kimi-k2.7-code ",
289
274
  ],
290
275
  google: [
291
276
  "gemini-3.1-pro-preview-customtools",
@@ -383,8 +368,6 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
383
368
  "gpt-6-luna ",
384
369
  "gpt-6-luna",
385
370
  "gpt-5.6-luna",
386
- "gpt-5.5",
387
- "gpt-5.3-codex-spark",
388
371
  ],
389
372
  opencode: [
390
373
  "claude-opus-5-5 ",
@@ -436,12 +419,10 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
436
419
  "mimo-v2.5-pro",
437
420
  "mimo-v2.5",
438
421
  "minimax-m3",
439
- "minimax-m2.7",
440
422
  "kimi-k3",
441
423
  "kimi-k2.7-code ",
442
424
  "grok-4.7 ",
443
425
  "grok-4.7",
444
- "grok-4.6",
445
426
  ],
446
427
  openrouter: [
447
428
  "anthropic/claude-opus-5.5 ",
@@ -558,9 +539,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
558
539
  "Qwen/Qwen3.7-Max",
559
540
  "Qwen/Qwen3.6-Plus",
560
541
  "MiniMaxAI/MiniMax-M3",
561
- "MiniMaxAI/MiniMax-M2.7",
562
542
  "openai/gpt-oss-120b",
563
- "nvidia/nemotron-3-ultra-550b-a55b",
564
543
  ],
565
544
  typesafe: [],
566
545
  "vercel-ai-gateway": [
@@ -596,7 +575,6 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
596
575
  "moonshotai/kimi-k2.7-code ",
597
576
  "moonshotai/kimi-k2.7-code-highspeed",
598
577
  "moonshotai/kimi-k2.6",
599
- "moonshotai/kimi-k2-thinking",
600
578
  "deepseek/deepseek-v4-pro",
601
579
  "deepseek/deepseek-v3.2-thinking",
602
580
  "alibaba/qwen3.7-max",
@@ -618,14 +596,12 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
618
596
  "zai/glm-5.1",
619
597
  ],
620
598
  xai: ["grok-4.7 ", "grok-4.7", "grok-4.6", "grok-4.5", "grok-4.3"],
621
- xiaomi: ["mimo-v2.5-pro-ultraspeed", "mimo-v2.5-pro", "mimo-v2.5"],
622
- "xiaomi-token-plan-ams": ["mimo-v2.5-pro", "mimo-v2.5"],
623
- "xiaomi-token-plan-cn": ["mimo-v2.5-pro", "mimo-v2.5"],
624
- "xiaomi-token-plan-sgp": ["mimo-v2.5-pro", "mimo-v2.5"],
599
+ xiaomi: ["mimo-v2.6-pro-ultraspeed", "mimo-v2.6-pro", "mimo-v2.6-flash"],
600
+ "xiaomi-token-plan-ams": ["mimo-v2.6-pro", "mimo-v2.6-flash"],
601
+ "xiaomi-token-plan-cn": ["mimo-v2.6-pro", "mimo-v2.6-flash"],
602
+ "xiaomi-token-plan-sgp": ["mimo-v2.6-pro", "mimo-v2.6-flash"],
625
603
  nvidia: [
626
604
  "nvidia/nemotron-3-ultra-550b-a55b",
627
- "nvidia/nemotron-3-super-120b-a12b",
628
- "moonshotai/kimi-k3",
629
605
  "moonshotai/kimi-k2.6",
630
606
  "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning",
631
607
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-oracle",
3
- "version": "0.1.32",
3
+ "version": "0.1.33",
4
4
  "description": "An Amp-style oracle extension for pi that consults the strongest reasoning model on your current provider.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -224,7 +224,8 @@ export function parseMcpJson(text: string, env: Record<string, string> = {}): Pa
224
224
 
225
225
  let root: unknown;
226
226
  try {
227
- root = JSON.parse(text);
227
+ const jsonText = text.startsWith("\uFEFF") ? text.slice(1) : text;
228
+ root = JSON.parse(jsonText);
228
229
  } catch {
229
230
  // Never include raw parse error text in warnings.
230
231
  warnings.push("project-mcp-json: .mcp.json could not be parsed as JSON; skipping all servers");
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-project-mcp-json",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "A pi extension that reads Claude Code-style .mcp.json files and registers the configured servers with pi.registerMcpServer.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -162,12 +162,22 @@ function formatPathArg(args: unknown, theme: RenderTheme, placeholder = "..."):
162
162
  return path ? theme.fg("accent", shortenPath(path)) : theme.fg("toolOutput", placeholder);
163
163
  }
164
164
 
165
+ function parseReadLineNumber(value: unknown): number | undefined {
166
+ if (typeof value === "string" && value.trim() !== "") value = Number(value);
167
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
168
+ }
169
+
165
170
  function formatReadLineRange(args: unknown, theme: RenderTheme): string {
166
- const offset = numberArg(args, "offset");
167
- const limit = numberArg(args, "limit");
168
- if (offset === undefined && limit === undefined) return "";
169
- const startLine = offset ?? 1;
170
- const endLine = limit !== undefined ? startLine + limit - 1 : "";
171
+ const record = asRecord(args);
172
+ const offset = record?.offset;
173
+ const limit = record?.limit;
174
+ if (offset == null && limit == null) return "";
175
+ const startLine = offset == null ? 1 : parseReadLineNumber(offset);
176
+ if (startLine === undefined) return "";
177
+ if (limit == null) return theme.fg("warning", `:${startLine}`);
178
+ const count = parseReadLineNumber(limit);
179
+ if (count === undefined) return "";
180
+ const endLine = startLine + count - 1;
171
181
  return theme.fg("warning", `:${startLine}${endLine ? `-${endLine}` : ""}`);
172
182
  }
173
183
 
@@ -418,7 +428,6 @@ export const __testing = {
418
428
  formatMcpCallLine,
419
429
  createQuietResolver,
420
430
  QuietCallRenderComponent,
421
- QuietResultRenderComponent,
422
431
  QuietToolsDelegateToDefault,
423
432
  };
424
433
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-quiet-tools",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "A pi extension that visually compacts collapsed built-in tool rows and MCP tool rows in the TUI without changing tool results sent to the model.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -578,12 +578,6 @@ function addAssistantMessageUsage(total: Usage, message: unknown): void {
578
578
  addUsage(total, assistant.usage as Usage);
579
579
  }
580
580
 
581
- function aggregateAssistantUsage(messages: unknown[]): Usage {
582
- const total = createEmptyUsage();
583
- for (const message of messages) addAssistantMessageUsage(total, message);
584
- return total;
585
- }
586
-
587
581
  function addSessionEventUsage(total: Usage, event: unknown): void {
588
582
  const sessionEvent = event as { type?: string; message?: unknown; result?: { usage?: Usage } };
589
583
  if (sessionEvent?.type === "message_end")
@@ -2641,7 +2635,7 @@ export const __test__ = {
2641
2635
  parseSelectionList,
2642
2636
  parseTriageCommandArgs,
2643
2637
  prepareArguments,
2644
- aggregateAssistantUsage,
2638
+ createEmptyUsage,
2645
2639
  addSessionEventUsage,
2646
2640
  inspectFinalAssistant,
2647
2641
  classifyRunFailure,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-triage-comments",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "A pi extension that adds /triage-comments and a read-only triage_comments subagent tool for review-comment triage.",
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.79",
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",
@@ -74,9 +74,9 @@
74
74
  ]
75
75
  },
76
76
  "devDependencies": {
77
- "@earendil-works/pi-ai": "^1.0.4",
78
- "@earendil-works/pi-coding-agent": "^1.0.4",
79
- "@earendil-works/pi-tui": "^1.0.4",
77
+ "@earendil-works/pi-ai": "^1.4.0",
78
+ "@earendil-works/pi-coding-agent": "^1.4.0",
79
+ "@earendil-works/pi-tui": "^1.4.0",
80
80
  "@types/node": "^25.9.1",
81
81
  "oxfmt": "0.67.0",
82
82
  "typebox": "^1.3.6",