Download src/utils/hooks/execPromptHook.ts from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 6.82 kB
-
https://huggingface.co/chenbhao/codev/resolve/main/src/utils/hooks/execPromptHook.ts
- Command line
-
hf download hf://chenbhao/codev/src/utils/hooks/execPromptHook.ts
-
curl -L -o execPromptHook.ts https://huggingface.co/chenbhao/codev/resolve/main/src/utils/hooks/execPromptHook.ts
6.82 kB
| import { 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, | |
| }), | |
| } | |
| } | |
| } | |