Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/the-system/bundle-slot-tool.ts

Raw
import { StringEnum } from "@earendil-works/pi-ai";
import { defineTool, keyHint } from "@earendil-works/pi-coding-agent";
import { Text } from "@earendil-works/pi-tui";
import { Type } from "typebox";
import type { BundleSlot } from "./bundle-slot.js";
import { loadBundleSlot } from "./loaders.js";
import { expandableFallback } from "./tool-rendering.js";

const DESCRIPTION =
	"Reserve a unique System document ID and canonical bundle path from its type and semantic title.";
const GUIDANCE = [
	"Resolve needed clarifications before calling system_bundle_slot.",
	"Call system_bundle_slot once per required bundle after choosing its final type and title; use each returned ID and path exactly.",
];
const BUNDLE_TYPES = ["research", "spec", "phase", "plan", "issue"] as const;

export function createBundleSlotTool(onReserved?: () => void) {
	const reserved = new Set<string>();
	return defineTool({
		name: "system_bundle_slot",
		label: "system_bundle_slot",
		description: DESCRIPTION,
		promptSnippet: "Reserve an ID and path for a System bundle",
		promptGuidelines: GUIDANCE,
		parameters: Type.Object({
			type: StringEnum(BUNDLE_TYPES, {
				description: "System bundle type.",
			}),
			title: Type.String({
				minLength: 1,
				description: "Final semantic bundle title used to derive the slug.",
			}),
		}),
		renderCall(args, theme, context) {
			const text =
				(context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
			text.setText(
				`${theme.fg("toolTitle", theme.bold("system_bundle_slot"))} ${theme.fg("accent", String(args.type ?? "bundle"))} ${theme.fg("toolOutput", `· ${bounded(String(args.title ?? "untitled"), 72)}`)}`,
			);
			return text;
		},
		renderResult(result, { expanded }, theme, context) {
			const text =
				(context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
			const slot = result.details as BundleSlot | undefined;
			if (
				context.isError ||
				!slot ||
				typeof slot.id !== "string" ||
				typeof slot.slug !== "string" ||
				typeof slot.path !== "string"
			) {
				text.setText(
					expandableFallback(
						result.content,
						expanded,
						theme,
						context.isError ? "error" : "muted",
					),
				);
				return text;
			}
			let output = `${theme.fg("success", "✓ reserved")} ${theme.fg("accent", slot.id)}${theme.fg("dim", ` · ${bounded(slot.slug, 40)}`)}`;
			if (!expanded) {
				output += theme.fg(
					"dim",
					` (${keyHint("app.tools.expand", "to expand")})`,
				);
			} else {
				output += [
					`\n${theme.fg("dim", "type ")}${theme.fg("toolOutput", slot.type)}`,
					`${theme.fg("dim", "title ")}${theme.fg("toolOutput", slot.title)}`,
					`${theme.fg("dim", "path ")}${theme.fg("toolOutput", slot.path)}`,
				].join("\n");
			}
			text.setText(output);
			return text;
		},
		async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
			const { reserveBundleSlot } = await loadBundleSlot();
			const slot = reserveBundleSlot(ctx.cwd, params, reserved);
			onReserved?.();
			return {
				content: [
					{
						type: "text" as const,
						text: [
							`Reserved ${slot.type} bundle.`,
							`Title: ${slot.title}`,
							`ID: ${slot.id}`,
							`Path: ${slot.path}`,
							"Use the ID and path exactly.",
						].join("\n"),
					},
				],
				details: slot,
			};
		},
	});
}

function bounded(value: string, limit: number): string {
	return value.length <= limit ? value : `${value.slice(0, limit - 1)}…`;
}