diff --git a/packages/ai/src/models.generated.ts b/packages/ai/src/models.generated.ts index 289c314b..fc288635 100644 --- a/packages/ai/src/models.generated.ts +++ b/packages/ai/src/models.generated.ts @@ -4092,13 +4092,13 @@ export const MODELS = { reasoning: true, input: ["text"], cost: { - input: 0.3, - output: 1.2, - cacheRead: 0.03, - cacheWrite: 0.375, + input: 0.12, + output: 0.48, + cacheRead: 0, + cacheWrite: 0, }, - contextWindow: 204800, - maxTokens: 131072, + contextWindow: 196608, + maxTokens: 4096, } satisfies Model<"openai-completions">, "mistralai/codestral-2508": { id: "mistralai/codestral-2508", diff --git a/packages/coding-agent/examples/hooks/README.md b/packages/coding-agent/examples/hooks/README.md index cdab51d4..88bc307e 100644 --- a/packages/coding-agent/examples/hooks/README.md +++ b/packages/coding-agent/examples/hooks/README.md @@ -17,6 +17,7 @@ cp permission-gate.ts ~/.pi/agent/hooks/ | Hook | Description | |------|-------------| | `plan-mode.ts` | Claude Code-style plan mode for read-only exploration with `/plan` command | +| `tools.ts` | Interactive `/tools` command to enable/disable tools with session persistence | | `permission-gate.ts` | Prompts for confirmation before dangerous bash commands (rm -rf, sudo, etc.) | | `git-checkpoint.ts` | Creates git stash checkpoints at each turn for code restoration on branch | | `protected-paths.ts` | Blocks writes to protected paths (.env, .git/, node_modules/) | diff --git a/packages/coding-agent/examples/hooks/tools.ts b/packages/coding-agent/examples/hooks/tools.ts new file mode 100644 index 00000000..908c9d18 --- /dev/null +++ b/packages/coding-agent/examples/hooks/tools.ts @@ -0,0 +1,145 @@ +/** + * Tools Hook + * + * 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/hooks/ or your project's .pi/hooks/ + * 2. Use /tools to open the tool selector + */ + +import { getSettingsListTheme } from "@mariozechner/pi-coding-agent"; +import type { HookAPI, HookContext } from "@mariozechner/pi-coding-agent/hooks"; +import { Container, type SettingItem, SettingsList } from "@mariozechner/pi-tui"; + +// State persisted to session +interface ToolsState { + enabledTools: string[]; +} + +export default function toolsHook(pi: HookAPI) { + // Track enabled tools + let enabledTools: Set = new Set(); + let allTools: string[] = []; + + // Persist current state + function persistState() { + pi.appendEntry("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: HookContext) { + 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) + enabledTools = new Set(savedTools.filter((t: string) => allTools.includes(t))); + applyTools(); + } else { + // No saved state - enable all tools by default + enabledTools = new Set(allTools); + } + } + + // Register /tools command + pi.registerCommand("tools", { + description: "Enable/disable tools", + handler: async (_args, ctx) => { + // Refresh tool list + allTools = pi.getAllTools(); + + await ctx.ui.custom((tui, theme, done) => { + // Build settings items for each tool + const items: SettingItem[] = allTools.map((tool) => ({ + id: tool, + label: tool, + currentValue: enabledTools.has(tool) ? "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); + }); + + // Restore state after branching + pi.on("session_branch", async (_event, ctx) => { + restoreFromBranch(ctx); + }); +} diff --git a/packages/coding-agent/src/index.ts b/packages/coding-agent/src/index.ts index 69271da2..3989675a 100644 --- a/packages/coding-agent/src/index.ts +++ b/packages/coding-agent/src/index.ts @@ -161,4 +161,10 @@ export { main } from "./main.js"; // UI components for hooks export { BorderedLoader } from "./modes/interactive/components/bordered-loader.js"; // Theme utilities for custom tools and hooks -export { getMarkdownTheme, Theme, type ThemeColor } from "./modes/interactive/theme/theme.js"; +export { + getMarkdownTheme, + getSelectListTheme, + getSettingsListTheme, + Theme, + type ThemeColor, +} from "./modes/interactive/theme/theme.js";