hookHelpers.ts
1 import { z } from 'zod/v4' 2 import type { Tool } from '../../Tool.js' 3 import { 4 SYNTHETIC_OUTPUT_TOOL_NAME, 5 SyntheticOutputTool, 6 } from '../../tools/SyntheticOutputTool/SyntheticOutputTool.js' 7 import { substituteArguments } from '../argumentSubstitution.js' 8 import { lazySchema } from '../lazySchema.js' 9 import type { SetAppState } from '../messageQueueManager.js' 10 import { hasSuccessfulToolCall } from '../messages.js' 11 import { addFunctionHook } from './sessionHooks.js' 12 13 /** 14 * Schema for hook responses (shared by prompt and agent hooks) 15 */ 16 export const hookResponseSchema = lazySchema(() => 17 z.object({ 18 ok: z.boolean().describe('Whether the condition was met'), 19 reason: z 20 .string() 21 .describe('Reason, if the condition was not met') 22 .optional(), 23 }), 24 ) 25 26 /** 27 * Add hook input JSON to prompt, either replacing $ARGUMENTS placeholder or appending. 28 * Also supports indexed arguments like $ARGUMENTS[0], $ARGUMENTS[1], or shorthand $0, $1, etc. 29 */ 30 export function addArgumentsToPrompt( 31 prompt: string, 32 jsonInput: string, 33 ): string { 34 return substituteArguments(prompt, jsonInput) 35 } 36 37 /** 38 * Create a StructuredOutput tool configured for hook responses. 39 * Reusable by agent hooks and background verification. 40 */ 41 export function createStructuredOutputTool(): Tool { 42 return { 43 ...SyntheticOutputTool, 44 inputSchema: hookResponseSchema(), 45 inputJSONSchema: { 46 type: 'object', 47 properties: { 48 ok: { 49 type: 'boolean', 50 description: 'Whether the condition was met', 51 }, 52 reason: { 53 type: 'string', 54 description: 'Reason, if the condition was not met', 55 }, 56 }, 57 required: ['ok'], 58 additionalProperties: false, 59 }, 60 async prompt(): Promise<string> { 61 return `Use this tool to return your verification result. You MUST call this tool exactly once at the end of your response.` 62 }, 63 } 64 } 65 66 /** 67 * Register a function hook that enforces structured output via SyntheticOutputTool. 68 * Used by ask.tsx, execAgentHook.ts, and background verification. 69 */ 70 export function registerStructuredOutputEnforcement( 71 setAppState: SetAppState, 72 sessionId: string, 73 ): void { 74 addFunctionHook( 75 setAppState, 76 sessionId, 77 'Stop', 78 '', // No matcher - applies to all stops 79 messages => hasSuccessfulToolCall(messages, SYNTHETIC_OUTPUT_TOOL_NAME), 80 `You MUST call the ${SYNTHETIC_OUTPUT_TOOL_NAME} tool to complete this request. Call this tool now.`, 81 { timeout: 5000 }, 82 ) 83 }