Files
dotfiles/home/.pi/agent/extensions/calm/lib/collapsed-thinking.ts
T
Lap Tran 4c6e85c97c Switch firstmate to OpenCode 2 and refresh nix-darwin pins
Point fm at the isolated oc2 wrapper, upgrade Homebrew packages on rebuild, and declare ffmpeg, docker-desktop, puremac, utm, node, and tea. Update nixpkgs, home-manager, and nix-homebrew locks. Track Pi agent config, crew-dispatch, and the Gitea PR helper that home.nix already expected.
2026-09-18 13:58:49 -04:00

83 lines
3.4 KiB
TypeScript

// Pi Calm - gapless collapsed-thinking presentation adapter.
//
// Adapted from the Firstmate project's Calm implementation.
// Copyright (c) 2026 Kun Chen. MIT License - see the LICENSE file in this directory.
//
// Verified against Pi 0.82.0, which exports AssistantMessageComponent with an
// updateContent method. installCalmCollapsedThinkingLayout() probes that exact
// public seam and throws if it is missing; index.ts catches that and skips only
// this adapter with one clear diagnostic instead of blocking Calm or Pi.
//
// How it works: Pi renders a hidden thinking block as one static label row.
// Calm sets that label to the empty string and this adapter filters thinking
// blocks out of the message handed to the stock renderer, so a collapsed
// thinking block occupies zero rows instead of one blank one. The unfiltered
// message is kept on lastMessage so expanding thinking (Ctrl+T) and turning
// Calm off both restore the original reasoning content byte-for-byte. Only
// collapsed thinking is affected: expanded reasoning, assistant text, and tool
// calls render exactly as Pi renders them.
import type { AssistantMessageComponent as PiAssistantMessageComponent } from "@earendil-works/pi-coding-agent";
import * as PiCodingAgent from "@earendil-works/pi-coding-agent";
import { calmHidesTranscriptChrome } from "./visibility.ts";
type AssistantMessage = Parameters<PiAssistantMessageComponent["updateContent"]>[0];
type AssistantMessagePresentationState = {
hiddenThinkingLabel: string;
hideThinkingBlock: boolean;
lastMessage?: AssistantMessage;
};
type CalmCollapsedThinkingPatch = {
hidesThinking: () => boolean;
};
// Keep the introduction-version symbol stable so a compatible upgrade cannot
// double-patch a live process.
const CALM_COLLAPSED_THINKING_PATCH = Symbol.for(
"pi-calm:collapsed-thinking-layout:pi-0.82.0",
);
export function installCalmCollapsedThinkingLayout(): void {
const registry = globalThis as typeof globalThis & {
[key: symbol]: CalmCollapsedThinkingPatch | undefined;
};
const hidesThinking = (): boolean => calmHidesTranscriptChrome();
const installed = registry[CALM_COLLAPSED_THINKING_PATCH];
if (installed) {
installed.hidesThinking = hidesThinking;
return;
}
const patch: CalmCollapsedThinkingPatch = { hidesThinking };
const AssistantMessageComponent = PiCodingAgent.AssistantMessageComponent;
if (typeof AssistantMessageComponent !== "function") {
throw new Error("Pi Calm requires Pi AssistantMessageComponent");
}
const originalUpdateContent = AssistantMessageComponent.prototype.updateContent;
if (typeof originalUpdateContent !== "function") {
throw new Error("Pi Calm requires Pi AssistantMessageComponent.updateContent");
}
AssistantMessageComponent.prototype.updateContent = function (
message: AssistantMessage,
): void {
const state = this as unknown as AssistantMessagePresentationState;
const hideThinking =
state.hiddenThinkingLabel === "" &&
state.hideThinkingBlock &&
patch.hidesThinking();
const presentationMessage = hideThinking
? {
...message,
content: message.content.filter((block) => block.type !== "thinking"),
}
: message;
originalUpdateContent.call(this, presentationMessage);
if (presentationMessage !== message) state.lastMessage = message;
};
registry[CALM_COLLAPSED_THINKING_PATCH] = patch;
}