import { StringEnum } from "@earendil-works/pi-ai"; import type { AgentToolResult, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"; import { Type } from "typebox"; import { goalToolResponse, toToolText, type GoalToolResponse } from "./format.js"; import { createGoal } from "./state.js"; import { TOOL_PROMPT_GUIDELINES } from "./prompts.js"; import type { GoalEntrySource, GoalResult, ThreadGoal } from "./types.js"; const EmptyParams = Type.Object({}); const CreateGoalParams = Type.Object({ objective: Type.String({ description: "Concrete objective to pursue until completion.", }), token_budget: Type.Optional( Type.Integer({ description: "Optional positive integer token budget.", minimum: 1, }), ), }); const UpdateGoalParams = Type.Object({ status: StringEnum(["complete"] as const, { description: "Only complete is accepted. Do not call this until no required work remains.", }), }); export interface ToolHost { getGoal(): ThreadGoal | null; setGoal(goal: ThreadGoal, source: GoalEntrySource, ctx: ExtensionContext): void; completeGoal(source: GoalEntrySource, ctx: ExtensionContext): GoalResult; } function textResult( text: string, goal: ThreadGoal | null, includeCompletionBudgetReport = false, ): AgentToolResult { return { content: [{ type: "text", text }], details: { ...goalToolResponse(goal, includeCompletionBudgetReport), error: null }, }; } function throwToolError(message: string): never { throw new Error(message); } export function registerGoalTools(pi: ExtensionAPI, host: ToolHost): void { pi.registerTool({ name: "get_goal", label: "Get Goal", description: "Get the current Codex-style goal and usage for this pi session.", promptSnippet: "Inspect the current goal, status, token budget, tokens used, and active elapsed time.", promptGuidelines: TOOL_PROMPT_GUIDELINES, parameters: EmptyParams, async execute() { const goal = host.getGoal(); return textResult(toToolText(goal), goal); }, }); pi.registerTool({ name: "create_goal", label: "Create Goal", description: "Create a Codex-style long-running goal for this pi session.", promptSnippet: "Create one goal with an objective and optional positive token budget. Fails when a non-complete goal already exists; replaces a completed goal.", promptGuidelines: TOOL_PROMPT_GUIDELINES, parameters: CreateGoalParams, async execute(_toolCallId, params, _signal, _onUpdate, ctx) { const result = createGoal(host.getGoal(), params.objective, params.token_budget ?? null); if (!result.ok || !result.goal) { throwToolError(result.message); } host.setGoal(result.goal, "tool", ctx); return textResult(toToolText(result.goal), result.goal); }, }); pi.registerTool({ name: "update_goal", label: "Update Goal", description: "Mark the current Codex-style goal complete only after the objective is actually achieved and no required work remains. Do not use this tool just because work is stopping, budget is low, or partial progress looks sufficient.", promptSnippet: "Mark the current goal complete only after an evidence-backed completion audit proves no required work remains.", promptGuidelines: TOOL_PROMPT_GUIDELINES, parameters: UpdateGoalParams, async execute(_toolCallId, _params, _signal, _onUpdate, ctx) { const result = host.completeGoal("tool", ctx); if (!result.ok || !result.goal) { throwToolError(result.message); } return textResult(toToolText(result.goal, true), result.goal, true); }, }); }