@vanillagreen/pi-qol 2.5.0 → 2.5.1

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/README.md CHANGED
@@ -52,7 +52,7 @@ Open `/extensions:settings`; settings appear under the **QOL** tab. Project sett
52
52
  - `gatewayPrivacy.enabled`: require zero data retention and no prompt training for main-agent and QOL model requests through OpenRouter and Vercel AI Gateway. Covers QOL compaction, branch summaries, imported-session summaries, handoff and session naming. Keeps other routing options and overrides weaker privacy values. Pi's built-in compaction and branch summaries cannot carry these fields, so on a gateway session model QOL writes them in place of Pi's, whatever `compaction.customEnabled` and `compaction.branchSummaryEnabled` say, and a failed or refused summary cancels the compaction or tree move instead of falling back to Pi's. Custom remote compaction endpoints keep their own privacy policy.
53
53
  - Statusline and editor: `statusline.enabled`, `statusline.showProvider`, `statusline.showAccount`, `replaceFooter`, `compactPrompt`, `showSessionNameTitle`, `showSessionNameWindow`, `inputBottomPaddingLines`, `gitRefreshTimeoutMs`, `showDirtyMarker`, `newlineOnShiftEnter`, `newlineFallbackKey`, `pendingQueue.asciiGreen`, `showImageChips`, `showAttachmentCountInStatus`.
54
54
  - Commands: `enableSessionNameCommand`, `enableHandoffCommand`, `enableScheduleCommand`, `enableContextCommand`, `handoffReviewPrompt`.
55
- - Session naming: `sessionAutoRename.*` (model, fallback model, deterministic fallback, prefix, prompt, limits, notify, debug). `sessionAutoRename.maxTokens` includes the naming model's reasoning tokens. `sessionAutoRename.maxNameChars` limits the title length.
55
+ - Session naming: `sessionAutoRename.*` (model, fallback model, deterministic fallback, prefix, prompt, limits, notify, debug). `sessionAutoRename.maxTokens` includes the naming model's reasoning tokens. The naming request asks for the lowest reasoning effort the model accepts, and turns reasoning off where the model allows it. `sessionAutoRename.maxNameChars` limits the title length.
56
56
  - Session search: `sessionSearch.*` (shortcut, result and row limits, snippets, overlay width, cache TTL, summary model and limits).
57
57
  - Rate limits: `rateLimitAutoResume.*`.
58
58
  - Permission gate: `permissionGate.enabled`, `permissionGate.commands` (comma-separated literal fragments or `/regex/flags`), `permissionGate.previewLines`, `permissionGate.previewChars`.
@@ -18,11 +18,27 @@ type RetryAssistantCall = (
18
18
  },
19
19
  ) => Promise<any>;
20
20
 
21
- export async function complete(model: unknown, context: unknown, options?: unknown, deps: PiAiCompatDeps = {}): Promise<any> {
22
- const rootComplete = (deps.root ?? piAi as any).complete;
23
- if (typeof rootComplete === "function") return await rootComplete(model, context, options);
21
+ async function dispatchCompletion(name: "complete" | "completeSimple", model: unknown, context: unknown, options: unknown, deps: PiAiCompatDeps): Promise<any> {
22
+ const rootFunction = (deps.root ?? piAi as any)[name];
23
+ if (typeof rootFunction === "function") return await rootFunction(model, context, options);
24
24
  const compat = deps.loadCompat ? await deps.loadCompat() : await dynamicImport("@earendil-works/pi-ai/compat") as any;
25
- return await compat.complete(model, context, options);
25
+ return await compat[name](model, context, options);
26
+ }
27
+
28
+ export async function complete(model: unknown, context: unknown, options?: unknown, deps: PiAiCompatDeps = {}): Promise<any> {
29
+ return await dispatchCompletion("complete", model, context, options, deps);
30
+ }
31
+
32
+ export async function completeSimple(model: unknown, context: unknown, options?: unknown, deps: PiAiCompatDeps = {}): Promise<any> {
33
+ return await dispatchCompletion("completeSimple", model, context, options, deps);
34
+ }
35
+
36
+ // The lowest reasoning level the model accepts, as `SimpleStreamOptions.reasoning`
37
+ // takes it. Simple options express "off" by leaving the level out, so a model that
38
+ // can turn reasoning off gets undefined. Levels come from pi-ai's model catalog.
39
+ export function lowestReasoningLevel(model: unknown, deps: { root?: any } = {}): string | undefined {
40
+ const [lowest] = (deps.root ?? piAi as any).getSupportedThinkingLevels(model) as string[];
41
+ return lowest === "off" ? undefined : lowest;
26
42
  }
27
43
 
28
44
  export async function retryAssistantCallCompat(
@@ -1,5 +1,5 @@
1
1
  import type { Message } from "@earendil-works/pi-ai";
2
- import { complete } from "./pi-ai-compat.js";
2
+ import { completeSimple, lowestReasoningLevel } from "./pi-ai-compat.js";
3
3
  import { gatewayPrivacyOnPayload } from "./gateway-privacy.js";
4
4
  import type { ExtensionContext, SessionEntry } from "@earendil-works/pi-coding-agent";
5
5
  import { textFromContent } from "./agent-end.js";
@@ -248,6 +248,21 @@ export function headerRecord(headers: unknown): Record<string, string | null> |
248
248
  return entries.length > 0 ? Object.fromEntries(entries) : undefined;
249
249
  }
250
250
 
251
+ // Pi's agent path sends a request to the base URL the credential resolves, not the
252
+ // catalog's: a Copilot enterprise token names the enterprise proxy host
253
+ // (pi-ai auth/oauth/github-copilot.js `toAuth`, pi-coding-agent
254
+ // core/agent-session.js `_getRequiredRequestAuth`).
255
+ function autoRenameAuth(model: any, auth: { apiKey?: string; baseUrl?: string; env?: Record<string, string>; headers?: unknown }, source: string): AutoRenameAuth {
256
+ return {
257
+ apiKey: auth.apiKey,
258
+ env: auth.env,
259
+ headers: headerRecord(auth.headers),
260
+ label: modelLabel(model),
261
+ model: auth.baseUrl ? { ...model, baseUrl: auth.baseUrl } : model,
262
+ source,
263
+ };
264
+ }
265
+
251
266
  function modelCost(model: any): number {
252
267
  const cost = model?.cost;
253
268
  return Number(cost?.input ?? 0) + Number(cost?.output ?? 0);
@@ -267,8 +282,7 @@ async function cheapestAvailableAutoRenameModel(ctx: ExtensionContext): Promise<
267
282
  for (const model of models) {
268
283
  try {
269
284
  const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
270
- const headers = headerRecord(auth.headers);
271
- if (auth.ok) return { apiKey: auth.apiKey, env: auth.env, headers, label: modelLabel(model), model, source: "cheapest" };
285
+ if (auth.ok) return autoRenameAuth(model, auth, "cheapest");
272
286
  } catch (error) {
273
287
  if (isStaleCtxError(error)) return undefined;
274
288
  // Try the next available model.
@@ -287,12 +301,11 @@ async function resolveAutoRenameModel(ctx: ExtensionContext, configured: string)
287
301
  }
288
302
  try {
289
303
  const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
290
- const headers = headerRecord(auth.headers);
291
304
  if (!auth.ok) {
292
305
  autoRenameDebug(ctx, `auth unavailable for ${modelLabel(model)}: ${auth.error}`, "warning");
293
306
  return undefined;
294
307
  }
295
- return { apiKey: auth.apiKey, env: auth.env, headers, label: modelLabel(model), model, source: configured };
308
+ return autoRenameAuth(model, auth, configured);
296
309
  } catch (error) {
297
310
  if (isStaleCtxError(error)) return undefined;
298
311
  autoRenameDebug(ctx, `auth failed for ${modelLabel(model)}: ${stringifyError(error)}`, "warning");
@@ -329,10 +342,12 @@ export async function generateAutoRenameName(query: string, ctx: ExtensionContex
329
342
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
330
343
  timeout.unref?.();
331
344
  try {
332
- const response = await complete(
345
+ // A reasoning model left at its default effort can spend the whole token
346
+ // cap reasoning and write no title.
347
+ const response = await completeSimple(
333
348
  resolved.model,
334
349
  { messages: [message], systemPrompt: AUTO_RENAME_SYSTEM_PROMPT },
335
- { apiKey: resolved.apiKey, env: resolved.env, headers: resolved.headers, maxTokens, signal: controller.signal, onPayload: gatewayPrivacyOnPayload(ctx) },
350
+ { apiKey: resolved.apiKey, env: resolved.env, headers: resolved.headers, maxTokens, reasoning: lowestReasoningLevel(resolved.model), signal: controller.signal, onPayload: gatewayPrivacyOnPayload(ctx) },
336
351
  );
337
352
  if (response.stopReason === "error") {
338
353
  autoRenameDebug(ctx, `${resolved.label} failed: ${response.errorMessage ?? "unknown error"}`, "warning");
@@ -71,11 +71,15 @@ export function installThinkingTimerPatch(): void {
71
71
  const labelComponents: Text[] = [];
72
72
  for (const child of this.contentContainer.children as any[]) {
73
73
  if (!child || typeof child !== "object") continue;
74
- if (typeof child.setText !== "function") continue;
75
- if (typeof child.text !== "string") continue;
74
+ // Pi's SDK setHiddenThinkingLabel applies one label globally, not per block.
75
+ // Until it supports per-block labels, depend on the private contentContainer
76
+ // and MouseRegion.child (verified at Pi 0.86.0 and 1.0.1).
77
+ const label = child.child ?? child;
78
+ if (typeof label.setText !== "function") continue;
79
+ if (typeof label.text !== "string") continue;
76
80
  const expectedLabel = thinkingLabel(store.cwd);
77
- if (!child.text.includes(expectedLabel) && !child.text.includes("Thinking...")) continue;
78
- labelComponents.push(child as Text);
81
+ if (!label.text.includes(expectedLabel) && !label.text.includes("Thinking...")) continue;
82
+ labelComponents.push(label as Text);
79
83
  }
80
84
  if (labelComponents.length === 0) return;
81
85
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vanillagreen/pi-qol",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "description": "Pi quality-of-life extension: compact statusline/π prompt, reliable multiline input, styled pasted-image chips, session naming/search/context import, scheduled prompts, handoff, permission prompts, notifications, custom compaction, and a collapsed-thinking timer.",
5
5
  "license": "MIT",
6
6
  "keywords": [