pi-extensions-tool-display 0.1.1 → 0.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/src/types.ts ADDED
@@ -0,0 +1,105 @@
1
+ export const READ_OUTPUT_MODES = ["hidden", "summary", "preview"] as const;
2
+ export const SEARCH_OUTPUT_MODES = ["hidden", "count", "preview"] as const;
3
+ export const MCP_OUTPUT_MODES = ["hidden", "summary", "preview"] as const;
4
+ export const CUSTOM_TOOL_OVERRIDE_KINDS = ["generic", "mcp"] as const;
5
+ export const CUSTOM_TOOL_OUTPUT_MODES = ["hidden", "summary", "preview"] as const;
6
+ export const BASH_OUTPUT_MODES = ["opencode", "summary", "preview"] as const;
7
+ export const DIFF_VIEW_MODES = ["auto", "split", "unified"] as const;
8
+ export const DIFF_INDICATOR_MODES = ["bars", "classic", "none"] as const;
9
+
10
+ export type ReadOutputMode = (typeof READ_OUTPUT_MODES)[number];
11
+ export type SearchOutputMode = (typeof SEARCH_OUTPUT_MODES)[number];
12
+ export type McpOutputMode = (typeof MCP_OUTPUT_MODES)[number];
13
+ export type CustomToolOverrideKind = (typeof CUSTOM_TOOL_OVERRIDE_KINDS)[number];
14
+ export type CustomToolOutputMode = (typeof CUSTOM_TOOL_OUTPUT_MODES)[number];
15
+ export type BashOutputMode = (typeof BASH_OUTPUT_MODES)[number];
16
+ export type DiffViewMode = (typeof DIFF_VIEW_MODES)[number];
17
+ export type DiffIndicatorMode = (typeof DIFF_INDICATOR_MODES)[number];
18
+
19
+ export const BUILT_IN_TOOL_OVERRIDE_NAMES = [
20
+ "read",
21
+ "grep",
22
+ "find",
23
+ "ls",
24
+ "bash",
25
+ "edit",
26
+ "write",
27
+ ] as const;
28
+
29
+ export type BuiltInToolOverrideName = (typeof BUILT_IN_TOOL_OVERRIDE_NAMES)[number];
30
+
31
+ export interface ToolOverrideOwnership {
32
+ read: boolean;
33
+ grep: boolean;
34
+ find: boolean;
35
+ ls: boolean;
36
+ bash: boolean;
37
+ edit: boolean;
38
+ write: boolean;
39
+ }
40
+
41
+ export interface CustomToolOverrideConfig {
42
+ enabled: boolean;
43
+ kind: CustomToolOverrideKind;
44
+ outputMode: CustomToolOutputMode;
45
+ }
46
+
47
+ export interface ToolDisplayConfig {
48
+ enabled: boolean;
49
+ registerToolOverrides: ToolOverrideOwnership;
50
+ customToolOverrides: Record<string, CustomToolOverrideConfig>;
51
+ enableNativeUserMessageBox: boolean;
52
+ readOutputMode: ReadOutputMode;
53
+ searchOutputMode: SearchOutputMode;
54
+ mcpOutputMode: McpOutputMode;
55
+ previewLines: number;
56
+ expandedPreviewMaxLines: number;
57
+ bashOutputMode: BashOutputMode;
58
+ bashCollapsedLines: number;
59
+ diffViewMode: DiffViewMode;
60
+ diffIndicatorMode: DiffIndicatorMode;
61
+ diffSplitMinWidth: number;
62
+ diffCollapsedLines: number;
63
+ diffWordWrap: boolean;
64
+ showTruncationHints: boolean;
65
+ showRtkCompactionHints: boolean;
66
+ }
67
+
68
+ export const DEFAULT_TOOL_DISPLAY_CONFIG: ToolDisplayConfig = {
69
+ enabled: true,
70
+ registerToolOverrides: {
71
+ read: true,
72
+ grep: true,
73
+ find: true,
74
+ ls: true,
75
+ bash: true,
76
+ edit: true,
77
+ write: true,
78
+ },
79
+ customToolOverrides: {},
80
+ enableNativeUserMessageBox: true,
81
+ readOutputMode: "hidden",
82
+ searchOutputMode: "hidden",
83
+ mcpOutputMode: "hidden",
84
+ previewLines: 8,
85
+ expandedPreviewMaxLines: 4000,
86
+ bashOutputMode: "opencode",
87
+ bashCollapsedLines: 10,
88
+ diffViewMode: "auto",
89
+ diffIndicatorMode: "bars",
90
+ diffSplitMinWidth: 120,
91
+ diffCollapsedLines: 24,
92
+ diffWordWrap: true,
93
+ showTruncationHints: false,
94
+ showRtkCompactionHints: false,
95
+ };
96
+
97
+ export interface ConfigLoadResult {
98
+ config: ToolDisplayConfig;
99
+ error?: string;
100
+ }
101
+
102
+ export interface ConfigSaveResult {
103
+ success: boolean;
104
+ error?: string;
105
+ }
@@ -0,0 +1,64 @@
1
+ import { isRecord } from "./tool-metadata.js";
2
+
3
+ interface MarkdownLike {
4
+ text?: unknown;
5
+ theme?: unknown;
6
+ defaultTextStyle?: unknown;
7
+ }
8
+
9
+ interface UserMessageLike {
10
+ children?: unknown;
11
+ }
12
+
13
+ export interface UserMessageMarkdownState {
14
+ text: string;
15
+ theme: unknown;
16
+ defaultTextStyle?: Record<string, unknown>;
17
+ }
18
+
19
+ function sanitizeDefaultTextStyle(value: unknown): Record<string, unknown> | undefined {
20
+ if (!isRecord(value)) {
21
+ return undefined;
22
+ }
23
+
24
+ const { bgColor: _bgColor, ...rest } = value;
25
+ return Object.keys(rest).length > 0 ? rest : undefined;
26
+ }
27
+
28
+ function isMarkdownLike(value: unknown): value is MarkdownLike {
29
+ return isRecord(value) && typeof value.text === "string" && value.theme !== undefined;
30
+ }
31
+
32
+ function findMarkdownChild(value: unknown): MarkdownLike | undefined {
33
+ if (isMarkdownLike(value)) {
34
+ return value;
35
+ }
36
+ if (!isRecord(value)) {
37
+ return undefined;
38
+ }
39
+
40
+ const children = Array.isArray(value.children) ? value.children : [];
41
+ for (const child of children) {
42
+ const markdownChild = findMarkdownChild(child);
43
+ if (markdownChild) {
44
+ return markdownChild;
45
+ }
46
+ }
47
+
48
+ return undefined;
49
+ }
50
+
51
+ export function extractUserMessageMarkdownState(
52
+ userMessage: UserMessageLike,
53
+ ): UserMessageMarkdownState | undefined {
54
+ const markdownChild = findMarkdownChild(userMessage);
55
+ if (!markdownChild || typeof markdownChild.text !== "string") {
56
+ return undefined;
57
+ }
58
+
59
+ return {
60
+ text: markdownChild.text,
61
+ theme: markdownChild.theme,
62
+ defaultTextStyle: sanitizeDefaultTextStyle(markdownChild.defaultTextStyle),
63
+ };
64
+ }
@@ -0,0 +1,66 @@
1
+ import {
2
+ type ExtensionAPI,
3
+ UserMessageComponent,
4
+ } from "@earendil-works/pi-coding-agent";
5
+ import {
6
+ patchNativeUserMessagePrototype,
7
+ type PatchableUserMessagePrototype,
8
+ type UserMessageTheme,
9
+ } from "./user-message-box-renderer.js";
10
+ import { unregisterUserMessageRenderPrototypePatch } from "./user-message-box-patch.js";
11
+ import type { ToolDisplayConfig } from "./types.js";
12
+ import { onReloadShutdown } from "./extension-lifecycle.js";
13
+
14
+ const registeredNativeUserMessageApis = new WeakSet<ExtensionAPI>();
15
+
16
+ function getUserMessagePrototype(): PatchableUserMessagePrototype {
17
+ return UserMessageComponent.prototype as unknown as PatchableUserMessagePrototype;
18
+ }
19
+
20
+ function patchUserMessageRender(
21
+ getTheme: () => UserMessageTheme | undefined,
22
+ isEnabled: () => boolean,
23
+ ): void {
24
+ patchNativeUserMessagePrototype(
25
+ getUserMessagePrototype(),
26
+ getTheme,
27
+ isEnabled,
28
+ );
29
+ }
30
+
31
+ function restoreUserMessageRender(): void {
32
+ unregisterUserMessageRenderPrototypePatch(getUserMessagePrototype());
33
+ }
34
+
35
+ export default function registerNativeUserMessageBox(
36
+ pi: ExtensionAPI,
37
+ getConfig: () => ToolDisplayConfig,
38
+ ): void {
39
+ if (registeredNativeUserMessageApis.has(pi)) {
40
+ return;
41
+ }
42
+ registeredNativeUserMessageApis.add(pi);
43
+
44
+ let activeTheme: UserMessageTheme | undefined;
45
+
46
+ const getTheme = (): UserMessageTheme | undefined => activeTheme;
47
+ const isEnabled = (): boolean => getConfig().enableNativeUserMessageBox;
48
+
49
+ patchUserMessageRender(getTheme, isEnabled);
50
+
51
+ onReloadShutdown(pi, () => {
52
+ restoreUserMessageRender();
53
+ activeTheme = undefined;
54
+ registeredNativeUserMessageApis.delete(pi);
55
+ });
56
+
57
+ pi.on("before_agent_start", async () => {
58
+ patchUserMessageRender(getTheme, isEnabled);
59
+ });
60
+
61
+ pi.on("session_start", async (_event, ctx) => {
62
+ activeTheme = ctx?.ui?.theme as unknown as UserMessageTheme;
63
+ patchUserMessageRender(getTheme, isEnabled);
64
+ });
65
+
66
+ }
@@ -0,0 +1,72 @@
1
+ export type UserMessageRenderFn = (width: number) => string[];
2
+
3
+ const USER_MESSAGE_PATCH_OWNER = {};
4
+
5
+ export interface PatchableUserMessagePrototype {
6
+ render: UserMessageRenderFn;
7
+ __piUserMessageOriginalRender?: UserMessageRenderFn;
8
+ __piUserMessageNativePatched?: boolean;
9
+ __piUserMessagePatchVersion?: number;
10
+ __piUserMessagePatchOwner?: object;
11
+ }
12
+
13
+ export function unregisterUserMessageRenderPrototypePatch(
14
+ prototype: PatchableUserMessagePrototype,
15
+ ): void {
16
+ const originalRender = prototype.__piUserMessageOriginalRender;
17
+ if (typeof originalRender === "function") {
18
+ prototype.render = originalRender;
19
+ }
20
+
21
+ delete prototype.__piUserMessageOriginalRender;
22
+ delete prototype.__piUserMessageNativePatched;
23
+ delete prototype.__piUserMessagePatchVersion;
24
+ delete prototype.__piUserMessagePatchOwner;
25
+ }
26
+
27
+ export function patchUserMessageRenderPrototype(
28
+ prototype: PatchableUserMessagePrototype,
29
+ patchVersion: number,
30
+ buildRender: (originalRender: UserMessageRenderFn) => UserMessageRenderFn,
31
+ ): void {
32
+ if (typeof prototype.render !== "function") {
33
+ return;
34
+ }
35
+
36
+ const previousOriginalRender = prototype.__piUserMessageOriginalRender;
37
+ const hasPreviousPatch = typeof previousOriginalRender === "function"
38
+ && previousOriginalRender !== prototype.render;
39
+ const isCurrentPatch = prototype.__piUserMessagePatchOwner === USER_MESSAGE_PATCH_OWNER;
40
+ let restoredStalePatch = false;
41
+
42
+ if (hasPreviousPatch && !isCurrentPatch) {
43
+ prototype.render = previousOriginalRender;
44
+ delete prototype.__piUserMessageNativePatched;
45
+ delete prototype.__piUserMessagePatchVersion;
46
+ delete prototype.__piUserMessagePatchOwner;
47
+ restoredStalePatch = true;
48
+ }
49
+
50
+ if (
51
+ !restoredStalePatch
52
+ && prototype.__piUserMessageNativePatched
53
+ && prototype.__piUserMessagePatchVersion === patchVersion
54
+ && typeof prototype.__piUserMessageOriginalRender === "function"
55
+ ) {
56
+ return;
57
+ }
58
+
59
+ if (!prototype.__piUserMessageOriginalRender) {
60
+ prototype.__piUserMessageOriginalRender = prototype.render;
61
+ }
62
+
63
+ const originalRender = prototype.__piUserMessageOriginalRender;
64
+ if (!originalRender) {
65
+ return;
66
+ }
67
+
68
+ prototype.render = buildRender(originalRender);
69
+ prototype.__piUserMessageNativePatched = true;
70
+ prototype.__piUserMessagePatchVersion = patchVersion;
71
+ prototype.__piUserMessagePatchOwner = USER_MESSAGE_PATCH_OWNER;
72
+ }