File size: 3,869 Bytes
f500658 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | /**
* Tools Extension
*
* Provides a /tools command to enable/disable tools interactively.
* Tool selection persists across session reloads and respects branch navigation.
*
* Usage:
* 1. Copy this file to ~/.pi/agent/extensions/ or your project's .pi/extensions/
* 2. Use /tools to open the tool selector
*/
import type { ExtensionAPI, ExtensionContext, ToolInfo } from "@earendil-works/pi-coding-agent";
import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
import { Container, type SettingItem, SettingsList } from "@earendil-works/pi-tui";
// State persisted to session
interface ToolsState {
enabledTools: string[];
}
export default function toolsExtension(pi: ExtensionAPI) {
// Track enabled tools
let enabledTools: Set<string> = new Set();
let allTools: ToolInfo[] = [];
// Persist current state
function persistState() {
pi.appendEntry<ToolsState>("tools-config", {
enabledTools: Array.from(enabledTools),
});
}
// Apply current tool selection
function applyTools() {
pi.setActiveTools(Array.from(enabledTools));
}
// Find the last tools-config entry in the current branch
function restoreFromBranch(ctx: ExtensionContext) {
allTools = pi.getAllTools();
// Get entries in current branch only
const branchEntries = ctx.sessionManager.getBranch();
let savedTools: string[] | undefined;
for (const entry of branchEntries) {
if (entry.type === "custom" && entry.customType === "tools-config") {
const data = entry.data as ToolsState | undefined;
if (data?.enabledTools) {
savedTools = data.enabledTools;
}
}
}
if (savedTools) {
// Restore saved tool selection (filter to only tools that still exist)
const allToolNames = allTools.map((t) => t.name);
enabledTools = new Set(savedTools.filter((t: string) => allToolNames.includes(t)));
applyTools();
} else {
// No saved state - sync with currently active tools
enabledTools = new Set(pi.getActiveTools());
}
}
// Register /tools command
pi.registerCommand("tools", {
description: "Enable/disable tools",
handler: async (_args, ctx) => {
if (ctx.mode !== "tui") {
ctx.ui.notify("/tools requires TUI mode", "error");
return;
}
// Refresh tool list
allTools = pi.getAllTools();
await ctx.ui.custom((tui, theme, _kb, done) => {
// Build settings items for each tool
const items: SettingItem[] = allTools.map((tool) => ({
id: tool.name,
label: tool.name,
currentValue: enabledTools.has(tool.name) ? "enabled" : "disabled",
values: ["enabled", "disabled"],
}));
const container = new Container();
container.addChild(
new (class {
render(_width: number) {
return [theme.fg("accent", theme.bold("Tool Configuration")), ""];
}
invalidate() {}
})(),
);
const settingsList = new SettingsList(
items,
Math.min(items.length + 2, 15),
getSettingsListTheme(),
(id, newValue) => {
// Update enabled state and apply immediately
if (newValue === "enabled") {
enabledTools.add(id);
} else {
enabledTools.delete(id);
}
applyTools();
persistState();
},
() => {
// Close dialog
done(undefined);
},
);
container.addChild(settingsList);
const component = {
render(width: number) {
return container.render(width);
},
invalidate() {
container.invalidate();
},
handleInput(data: string) {
settingsList.handleInput?.(data);
tui.requestRender();
},
};
return component;
});
},
});
// Restore state on session start
pi.on("session_start", async (_event, ctx) => {
restoreFromBranch(ctx);
});
// Restore state when navigating the session tree
pi.on("session_tree", async (_event, ctx) => {
restoreFromBranch(ctx);
});
}
|