From 97047358b8318f4a8c0b7b21ce19923b00a687e4 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 23 Jun 2026 13:14:08 +0100 Subject: [PATCH 1/2] Node: Add post user prompt hook Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- nodejs/src/index.ts | 3 ++ nodejs/src/session.ts | 3 ++ nodejs/src/types.ts | 33 +++++++++++++++++ nodejs/test/e2e/hooks_extended.e2e.test.ts | 37 +++++++++++++++++++ ...ed_hook_and_modify_transformed_prompt.yaml | 12 ++++++ 5 files changed, 88 insertions(+) create mode 100644 test/snapshots/hooks_extended/should_invoke_postuserpromptsubmitted_hook_and_modify_transformed_prompt.yaml diff --git a/nodejs/src/index.ts b/nodejs/src/index.ts index 9bf02a32c..2eff479f6 100644 --- a/nodejs/src/index.ts +++ b/nodejs/src/index.ts @@ -97,6 +97,9 @@ export type { PermissionHandler, PermissionRequest, PermissionRequestResult, + PostUserPromptSubmittedHandler, + PostUserPromptSubmittedHookInput, + PostUserPromptSubmittedHookOutput, ProviderConfig, ProviderModelConfig, RemoteSessionMode, diff --git a/nodejs/src/session.ts b/nodejs/src/session.ts index 0ba42ab76..59ce094fe 100644 --- a/nodejs/src/session.ts +++ b/nodejs/src/session.ts @@ -1140,6 +1140,9 @@ export class CopilotSession { postToolUse: this.hooks.onPostToolUse as GenericHandler | undefined, postToolUseFailure: this.hooks.onPostToolUseFailure as GenericHandler | undefined, userPromptSubmitted: this.hooks.onUserPromptSubmitted as GenericHandler | undefined, + postUserPromptSubmitted: this.hooks.onPostUserPromptSubmitted as + | GenericHandler + | undefined, sessionStart: this.hooks.onSessionStart as GenericHandler | undefined, sessionEnd: this.hooks.onSessionEnd as GenericHandler | undefined, errorOccurred: this.hooks.onErrorOccurred as GenericHandler | undefined, diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index bdf02a7b0..ff358c43b 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -1272,6 +1272,34 @@ export type UserPromptSubmittedHandler = ( invocation: { sessionId: string } ) => Promise | UserPromptSubmittedHookOutput | void; +/** + * Input for post-user-prompt-submitted hook. + * + * This hook runs after the runtime has transformed the submitted prompt with + * generated context such as ``, but before the transformed + * prompt is persisted to session history or sent to the model. + */ +export interface PostUserPromptSubmittedHookInput extends BaseHookInput { + prompt: string; + transformedPrompt: string; +} + +/** + * Output for post-user-prompt-submitted hook. + */ +export interface PostUserPromptSubmittedHookOutput { + modifiedTransformedPrompt?: string; + suppressOutput?: boolean; +} + +/** + * Handler for post-user-prompt-submitted hook. + */ +export type PostUserPromptSubmittedHandler = ( + input: PostUserPromptSubmittedHookInput, + invocation: { sessionId: string } +) => Promise | PostUserPromptSubmittedHookOutput | void; + /** * Input for session-start hook */ @@ -1385,6 +1413,11 @@ export interface SessionHooks { */ onUserPromptSubmitted?: UserPromptSubmittedHandler; + /** + * Called after the runtime transforms a submitted prompt and before it is stored. + */ + onPostUserPromptSubmitted?: PostUserPromptSubmittedHandler; + /** * Called when a session starts */ diff --git a/nodejs/test/e2e/hooks_extended.e2e.test.ts b/nodejs/test/e2e/hooks_extended.e2e.test.ts index caa69399e..96f78a68a 100644 --- a/nodejs/test/e2e/hooks_extended.e2e.test.ts +++ b/nodejs/test/e2e/hooks_extended.e2e.test.ts @@ -7,6 +7,7 @@ import { z } from "zod"; import { approveAll, defineTool } from "../../src/index.js"; import type { ErrorOccurredHookInput, + PostUserPromptSubmittedHookInput, PostToolUseFailureHookInput, PostToolUseHookInput, PreToolUseHookInput, @@ -149,6 +150,42 @@ describe("Extended session hooks", async () => { await session.disconnect(); }); + it("should invoke postUserPromptSubmitted hook and modify transformed prompt", async () => { + const inputs: PostUserPromptSubmittedHookInput[] = []; + const session = await client.createSession({ + onPermissionRequest: approveAll, + hooks: { + onPostUserPromptSubmitted: async (input, invocation) => { + inputs.push(input); + expect(invocation.sessionId).toBeTruthy(); + expect(input.prompt).toContain("Say something after prompt transformation"); + expect(input.transformedPrompt).toContain( + "Say something after prompt transformation" + ); + expect(input.transformedPrompt).toContain(""); + + return { + modifiedTransformedPrompt: input.transformedPrompt.replace( + /.*?<\/current_datetime>\n*/s, + "POST_USER_PROMPT_SUBMITTED_HOOK\n" + ), + }; + }, + }, + }); + + const response = await session.sendAndWait({ + prompt: "Say something after prompt transformation", + }); + + expect(inputs.length).toBeGreaterThan(0); + expect(inputs[0].timestamp).toBeInstanceOf(Date); + expect(inputs[0].workingDirectory).toBeDefined(); + expect(response?.data.content ?? "").toContain("POST_USER_PROMPT_SUBMITTED_HOOK"); + + await session.disconnect(); + }); + it("should invoke sessionStart hook", async () => { const inputs: SessionStartHookInput[] = []; const session = await client.createSession({ diff --git a/test/snapshots/hooks_extended/should_invoke_postuserpromptsubmitted_hook_and_modify_transformed_prompt.yaml b/test/snapshots/hooks_extended/should_invoke_postuserpromptsubmitted_hook_and_modify_transformed_prompt.yaml new file mode 100644 index 000000000..002805d8c --- /dev/null +++ b/test/snapshots/hooks_extended/should_invoke_postuserpromptsubmitted_hook_and_modify_transformed_prompt.yaml @@ -0,0 +1,12 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: |- + POST_USER_PROMPT_SUBMITTED_HOOK + Say something after prompt transformation + - role: assistant + content: POST_USER_PROMPT_SUBMITTED_HOOK From 2c4b21bf196578f59618283045c0472872bf4495 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 23 Jun 2026 13:32:14 +0100 Subject: [PATCH 2/2] Node: Regenerate post user prompt hook snapshot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- nodejs/test/e2e/hooks_extended.e2e.test.ts | 12 +++++------- ...submitted_hook_and_modify_transformed_prompt.yaml | 6 +++--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/nodejs/test/e2e/hooks_extended.e2e.test.ts b/nodejs/test/e2e/hooks_extended.e2e.test.ts index 96f78a68a..a9954fa1c 100644 --- a/nodejs/test/e2e/hooks_extended.e2e.test.ts +++ b/nodejs/test/e2e/hooks_extended.e2e.test.ts @@ -158,16 +158,14 @@ describe("Extended session hooks", async () => { onPostUserPromptSubmitted: async (input, invocation) => { inputs.push(input); expect(invocation.sessionId).toBeTruthy(); - expect(input.prompt).toContain("Say something after prompt transformation"); - expect(input.transformedPrompt).toContain( - "Say something after prompt transformation" - ); + expect(input.prompt).toContain("Answer the arithmetic question above"); + expect(input.transformedPrompt).toContain("Answer the arithmetic question above"); expect(input.transformedPrompt).toContain(""); return { modifiedTransformedPrompt: input.transformedPrompt.replace( /.*?<\/current_datetime>\n*/s, - "POST_USER_PROMPT_SUBMITTED_HOOK\n" + "What is 19 + 23? Reply with just the number.\n" ), }; }, @@ -175,13 +173,13 @@ describe("Extended session hooks", async () => { }); const response = await session.sendAndWait({ - prompt: "Say something after prompt transformation", + prompt: "Answer the arithmetic question above.", }); expect(inputs.length).toBeGreaterThan(0); expect(inputs[0].timestamp).toBeInstanceOf(Date); expect(inputs[0].workingDirectory).toBeDefined(); - expect(response?.data.content ?? "").toContain("POST_USER_PROMPT_SUBMITTED_HOOK"); + expect(response?.data.content ?? "").toContain("42"); await session.disconnect(); }); diff --git a/test/snapshots/hooks_extended/should_invoke_postuserpromptsubmitted_hook_and_modify_transformed_prompt.yaml b/test/snapshots/hooks_extended/should_invoke_postuserpromptsubmitted_hook_and_modify_transformed_prompt.yaml index 002805d8c..eb8e2f6de 100644 --- a/test/snapshots/hooks_extended/should_invoke_postuserpromptsubmitted_hook_and_modify_transformed_prompt.yaml +++ b/test/snapshots/hooks_extended/should_invoke_postuserpromptsubmitted_hook_and_modify_transformed_prompt.yaml @@ -6,7 +6,7 @@ conversations: content: ${system} - role: user content: |- - POST_USER_PROMPT_SUBMITTED_HOOK - Say something after prompt transformation + What is 19 + 23? Reply with just the number. + Answer the arithmetic question above. - role: assistant - content: POST_USER_PROMPT_SUBMITTED_HOOK + content: "42"