Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
6.8 kB · 211 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212import { randomUUID } from 'crypto'import type { HookEvent } from 'src/entrypoints/agentSdkTypes.js'import { queryModelWithoutStreaming } from '../../services/api/claude.js'import type { ToolUseContext } from '../../Tool.js'import type { Message } from '../../types/message.js'import { createAttachmentMessage } from '../attachments.js'import { createCombinedAbortSignal } from '../combinedAbortSignal.js'import { logForDebugging } from '../debug.js'import { errorMessage } from '../errors.js'import type { HookResult } from '../hooks.js'import { safeParseJSON } from '../json.js'import { createUserMessage, extractTextContent } from '../messages.js'import { getSmallFastModel } from '../model/model.js'import type { PromptHook } from '../settings/types.js'import { asSystemPrompt } from '../systemPromptType.js'import { addArgumentsToPrompt, hookResponseSchema } from './hookHelpers.js'
/** * Execute a prompt-based hook using an LLM */export async function execPromptHook( hook: PromptHook, hookName: string, hookEvent: HookEvent, jsonInput: string, signal: AbortSignal, toolUseContext: ToolUseContext, messages?: Message[], toolUseID?: string,): Promise<HookResult> { // Use provided toolUseID or generate a new one const effectiveToolUseID = toolUseID || `hook-${randomUUID()}` try { // Replace $ARGUMENTS with the JSON input const processedPrompt = addArgumentsToPrompt(hook.prompt, jsonInput) logForDebugging( `Hooks: Processing prompt hook with prompt: ${processedPrompt}`, )
// Create user message directly - no need for processUserInput which would // trigger UserPromptSubmit hooks and cause infinite recursion const userMessage = createUserMessage({ content: processedPrompt })
// Prepend conversation history if provided const messagesToQuery = messages && messages.length > 0 ? [...messages, userMessage] : [userMessage]
logForDebugging( `Hooks: Querying model with ${messagesToQuery.length} messages`, )
// Query the model with Haiku const hookTimeoutMs = hook.timeout ? hook.timeout * 1000 : 30000
// Combined signal: aborts if either the hook signal or timeout triggers const { signal: combinedSignal, cleanup: cleanupSignal } = createCombinedAbortSignal(signal, { timeoutMs: hookTimeoutMs })
try { const response = await queryModelWithoutStreaming({ messages: messagesToQuery, systemPrompt: asSystemPrompt([ `You are evaluating a hook in Claude Code.
Your response must be a JSON object matching one of the following schemas:1. If the condition is met, return: {"ok": true}2. If the condition is not met, return: {"ok": false, "reason": "Reason for why it is not met"}`, ]), thinkingConfig: { type: 'disabled' as const }, tools: toolUseContext.options.tools, signal: combinedSignal, options: { async getToolPermissionContext() { const appState = toolUseContext.getAppState() return appState.toolPermissionContext }, model: hook.model ?? getSmallFastModel(), toolChoice: undefined, isNonInteractiveSession: true, hasAppendSystemPrompt: false, agents: [], querySource: 'hook_prompt', mcpTools: [], agentId: toolUseContext.agentId, outputFormat: { type: 'json_schema', schema: { type: 'object', properties: { ok: { type: 'boolean' }, reason: { type: 'string' }, }, required: ['ok'], additionalProperties: false, }, }, }, })
cleanupSignal()
// Extract text content from response const content = extractTextContent(response.message.content)
// Update response length for spinner display toolUseContext.setResponseLength(length => length + content.length)
const fullResponse = content.trim() logForDebugging(`Hooks: Model response: ${fullResponse}`)
const json = safeParseJSON(fullResponse) if (!json) { logForDebugging( `Hooks: error parsing response as JSON: ${fullResponse}`, ) return { hook, outcome: 'non_blocking_error', message: createAttachmentMessage({ type: 'hook_non_blocking_error', hookName, toolUseID: effectiveToolUseID, hookEvent, stderr: 'JSON validation failed', stdout: fullResponse, exitCode: 1, }), } }
const parsed = hookResponseSchema().safeParse(json) if (!parsed.success) { logForDebugging( `Hooks: model response does not conform to expected schema: ${parsed.error.message}`, ) return { hook, outcome: 'non_blocking_error', message: createAttachmentMessage({ type: 'hook_non_blocking_error', hookName, toolUseID: effectiveToolUseID, hookEvent, stderr: `Schema validation failed: ${parsed.error.message}`, stdout: fullResponse, exitCode: 1, }), } }
// Failed to meet condition if (!parsed.data.ok) { logForDebugging( `Hooks: Prompt hook condition was not met: ${parsed.data.reason}`, ) return { hook, outcome: 'blocking', blockingError: { blockingError: `Prompt hook condition was not met: ${parsed.data.reason}`, command: hook.prompt, }, preventContinuation: true, stopReason: parsed.data.reason, } }
// Condition was met logForDebugging(`Hooks: Prompt hook condition was met`) return { hook, outcome: 'success', message: createAttachmentMessage({ type: 'hook_success', hookName, toolUseID: effectiveToolUseID, hookEvent, content: '', }), } } catch (error) { cleanupSignal()
if (combinedSignal.aborted) { return { hook, outcome: 'cancelled', } } throw error } } catch (error) { const errorMsg = errorMessage(error) logForDebugging(`Hooks: Prompt hook error: ${errorMsg}`) return { hook, outcome: 'non_blocking_error', message: createAttachmentMessage({ type: 'hook_non_blocking_error', hookName, toolUseID: effectiveToolUseID, hookEvent, stderr: `Error executing prompt hook: ${errorMsg}`, stdout: '', exitCode: 1, }), } }}