import { Box, Container, Markdown, type MarkdownTheme, MouseRegion, Text } from "@earendil-works/pi-tui"; import type { ParsedSkillBlock } from "../../../core/agent-session.ts"; import type { MarkdownTransformer } from "../../../core/extensions/types.ts"; import { getMarkdownTheme, theme } from "../theme/theme.ts"; import { keyText } from "./keybinding-hints.ts"; import { createMarkdownTransform } from "./markdown-transform.ts"; /** * Component that renders a skill invocation message with collapsed/expanded state. * Uses same background color as custom messages for visual consistency. * Only renders the skill block itself - user message is rendered separately. */ export class SkillInvocationMessageComponent extends Box { private expanded = false; private skillBlock: ParsedSkillBlock; private markdownTheme: MarkdownTheme; private markdownTransformers: readonly MarkdownTransformer[]; private messageId?: string; private timestamp?: string; constructor( skillBlock: ParsedSkillBlock, markdownTheme: MarkdownTheme = getMarkdownTheme(), outputPad = 1, markdownTransformers: readonly MarkdownTransformer[] = [], messageId?: string, timestamp?: string, ) { super(outputPad, 1, (t) => theme.bg("customMessageBg", t)); this.skillBlock = skillBlock; this.markdownTheme = markdownTheme; this.markdownTransformers = markdownTransformers; this.messageId = messageId; this.timestamp = timestamp; this.updateDisplay(); } setExpanded(expanded: boolean): void { this.expanded = expanded; this.updateDisplay(); } setOutputPad(outputPad: number): void { this.setPaddingX(outputPad); } override invalidate(): void { super.invalidate(); this.updateDisplay(); } private updateDisplay(): void { this.clear(); const content = new Container(); if (this.expanded) { // Expanded: label + skill name header + full content const label = theme.fg("customMessageLabel", `\x1b[1m[skill]\x1b[22m`); content.addChild(new Text(label, 0, 0)); const header = `**${this.skillBlock.name}**\n\n`; const transform = createMarkdownTransform( "custom", () => false, this.markdownTransformers, this.messageId, this.timestamp, ); content.addChild( new Markdown( this.skillBlock.content, 0, 0, this.markdownTheme, { color: (text: string) => theme.fg("customMessageText", text) }, // Transformers see only the skill content, not Pi's header. { transform: (markdown, width) => header + transform(markdown, width) }, ), ); } else { // Collapsed: single line - [skill] name (hint to expand) const line = theme.fg("customMessageLabel", `\x1b[1m[skill]\x1b[22m `) + theme.fg("customMessageText", this.skillBlock.name) + theme.fg("dim", ` (${keyText("app.tools.expand")} to expand)`); content.addChild(new Text(line, 0, 0)); } this.addChild( new MouseRegion(content, (event) => { if (event.type !== "click" || event.button !== "left") return undefined; this.setExpanded(!this.expanded); return { handled: true }; }), ); } }