Welcome to the final chapter of the processUserInput tutorial!
In the previous chapter, Shell Command Execution, we gave our application the power to run system commands directly. We have covered how to route inputs, package text, handle images, and execute scripts.
But before we send any of this off to the AI (or "take off"), there is one final safety check. This is where Submission Lifecycle Hooks come in.
Imagine you are at an airport. You have your ticket (Input) and your luggage (Context). You are ready to fly. But before you can board the plane, you must pass through Security.
Security does two things:
Submission Lifecycle Hooks act as this security gate for your application. They allow other plugins or parts of the system to inspect the user's message right before it is finalized.
Goal: "Secret Prevention." The user accidentally pastes an API Key (e.g.,
sk-12345...) into the chat. The system must detect this pattern and block the message instantly so the secret isn't sent to the AI cloud.
blockingError. This stops the process immediately and shows a warning to the user.Let's visualize the "Security Checkpoint" flow.
This logic is the very last step in the processUserInput function. We iterate through a list of registered hooks and let each one inspect the input.
We use a loop because there might be multiple security guards (plugins). One might check for secrets, another might check for profanity, etc.
// processUserInput.ts
// 1. Loop through every active hook
for await (const hookResult of executeUserPromptSubmitHooks(
inputMessage,
appState.toolPermissionContext.mode,
// ... context
)) {
// Logic continues below...
}
Explanation: executeUserPromptSubmitHooks is a generator. It yields results one by one. We wait for each hook to finish its inspection before checking the next one.
This handles our use case. If a hook returns a blockingError, we abort the mission.
// Inside the loop...
if (hookResult.blockingError) {
// 2. Create a system warning message
const blockingMessage = getUserPromptSubmitHookBlockingMessage(
hookResult.blockingError
);
// 3. Return immediately! set shouldQuery to false.
return {
messages: [
createSystemMessage(blockingMessage, 'warning')
],
shouldQuery: false // Stop the AI request
};
}
Explanation: We replace the user's original message with a System Message (warning). By setting shouldQuery: false, the text is never sent to the Large Language Model. The user stays safe.
Sometimes hooks are helpful. They might find related information and want to attach it to the message "luggage."
// Inside the loop...
if (hookResult.additionalContexts && hookResult.additionalContexts.length > 0) {
// 4. Add the extra data to the message list
result.messages.push(
createAttachmentMessage({
type: 'hook_additional_context',
content: hookResult.additionalContexts,
hookName: 'UserPromptSubmit',
})
);
}
Explanation: If the hook returns additionalContexts, we create a special AttachmentMessage. This bundles the extra info with the user's original text, making the AI smarter about the current context.
Sometimes a hook might want to handle the request entirely by itself and just tell the main system to stop, without showing an error.
// Inside the loop...
if (hookResult.preventContinuation) {
// 5. Add a note saying why we stopped
result.messages.push(
createUserMessage({
content: `Operation stopped by hook: ${hookResult.stopReason}`,
})
);
// 6. Stop processing gracefully
result.shouldQuery = false;
return result;
}
Explanation: This is useful if a plugin decides to run a different UI flow or wizard instead of a standard chat response.
Congratulations! You have completed the processUserInput tutorial series.
Let's recap what we've built:
You now understand the complete journey of a user's message, from the moment they press "Enter" to the moment it flies off to the AI. Happy coding!
Generated by Code IQ