Skip to content

Commit 9704735

Browse files
Node: Add post user prompt hook
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 0667a46 commit 9704735

5 files changed

Lines changed: 88 additions & 0 deletions

File tree

nodejs/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ export type {
9797
PermissionHandler,
9898
PermissionRequest,
9999
PermissionRequestResult,
100+
PostUserPromptSubmittedHandler,
101+
PostUserPromptSubmittedHookInput,
102+
PostUserPromptSubmittedHookOutput,
100103
ProviderConfig,
101104
ProviderModelConfig,
102105
RemoteSessionMode,

nodejs/src/session.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,9 @@ export class CopilotSession {
11401140
postToolUse: this.hooks.onPostToolUse as GenericHandler | undefined,
11411141
postToolUseFailure: this.hooks.onPostToolUseFailure as GenericHandler | undefined,
11421142
userPromptSubmitted: this.hooks.onUserPromptSubmitted as GenericHandler | undefined,
1143+
postUserPromptSubmitted: this.hooks.onPostUserPromptSubmitted as
1144+
| GenericHandler
1145+
| undefined,
11431146
sessionStart: this.hooks.onSessionStart as GenericHandler | undefined,
11441147
sessionEnd: this.hooks.onSessionEnd as GenericHandler | undefined,
11451148
errorOccurred: this.hooks.onErrorOccurred as GenericHandler | undefined,

nodejs/src/types.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1272,6 +1272,34 @@ export type UserPromptSubmittedHandler = (
12721272
invocation: { sessionId: string }
12731273
) => Promise<UserPromptSubmittedHookOutput | void> | UserPromptSubmittedHookOutput | void;
12741274

1275+
/**
1276+
* Input for post-user-prompt-submitted hook.
1277+
*
1278+
* This hook runs after the runtime has transformed the submitted prompt with
1279+
* generated context such as `<current_datetime>`, but before the transformed
1280+
* prompt is persisted to session history or sent to the model.
1281+
*/
1282+
export interface PostUserPromptSubmittedHookInput extends BaseHookInput {
1283+
prompt: string;
1284+
transformedPrompt: string;
1285+
}
1286+
1287+
/**
1288+
* Output for post-user-prompt-submitted hook.
1289+
*/
1290+
export interface PostUserPromptSubmittedHookOutput {
1291+
modifiedTransformedPrompt?: string;
1292+
suppressOutput?: boolean;
1293+
}
1294+
1295+
/**
1296+
* Handler for post-user-prompt-submitted hook.
1297+
*/
1298+
export type PostUserPromptSubmittedHandler = (
1299+
input: PostUserPromptSubmittedHookInput,
1300+
invocation: { sessionId: string }
1301+
) => Promise<PostUserPromptSubmittedHookOutput | void> | PostUserPromptSubmittedHookOutput | void;
1302+
12751303
/**
12761304
* Input for session-start hook
12771305
*/
@@ -1385,6 +1413,11 @@ export interface SessionHooks {
13851413
*/
13861414
onUserPromptSubmitted?: UserPromptSubmittedHandler;
13871415

1416+
/**
1417+
* Called after the runtime transforms a submitted prompt and before it is stored.
1418+
*/
1419+
onPostUserPromptSubmitted?: PostUserPromptSubmittedHandler;
1420+
13881421
/**
13891422
* Called when a session starts
13901423
*/

nodejs/test/e2e/hooks_extended.e2e.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { z } from "zod";
77
import { approveAll, defineTool } from "../../src/index.js";
88
import type {
99
ErrorOccurredHookInput,
10+
PostUserPromptSubmittedHookInput,
1011
PostToolUseFailureHookInput,
1112
PostToolUseHookInput,
1213
PreToolUseHookInput,
@@ -149,6 +150,42 @@ describe("Extended session hooks", async () => {
149150
await session.disconnect();
150151
});
151152

153+
it("should invoke postUserPromptSubmitted hook and modify transformed prompt", async () => {
154+
const inputs: PostUserPromptSubmittedHookInput[] = [];
155+
const session = await client.createSession({
156+
onPermissionRequest: approveAll,
157+
hooks: {
158+
onPostUserPromptSubmitted: async (input, invocation) => {
159+
inputs.push(input);
160+
expect(invocation.sessionId).toBeTruthy();
161+
expect(input.prompt).toContain("Say something after prompt transformation");
162+
expect(input.transformedPrompt).toContain(
163+
"Say something after prompt transformation"
164+
);
165+
expect(input.transformedPrompt).toContain("<current_datetime>");
166+
167+
return {
168+
modifiedTransformedPrompt: input.transformedPrompt.replace(
169+
/<current_datetime>.*?<\/current_datetime>\n*/s,
170+
"POST_USER_PROMPT_SUBMITTED_HOOK\n"
171+
),
172+
};
173+
},
174+
},
175+
});
176+
177+
const response = await session.sendAndWait({
178+
prompt: "Say something after prompt transformation",
179+
});
180+
181+
expect(inputs.length).toBeGreaterThan(0);
182+
expect(inputs[0].timestamp).toBeInstanceOf(Date);
183+
expect(inputs[0].workingDirectory).toBeDefined();
184+
expect(response?.data.content ?? "").toContain("POST_USER_PROMPT_SUBMITTED_HOOK");
185+
186+
await session.disconnect();
187+
});
188+
152189
it("should invoke sessionStart hook", async () => {
153190
const inputs: SessionStartHookInput[] = [];
154191
const session = await client.createSession({
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
models:
2+
- claude-sonnet-4.5
3+
conversations:
4+
- messages:
5+
- role: system
6+
content: ${system}
7+
- role: user
8+
content: |-
9+
POST_USER_PROMPT_SUBMITTED_HOOK
10+
Say something after prompt transformation
11+
- role: assistant
12+
content: POST_USER_PROMPT_SUBMITTED_HOOK

0 commit comments

Comments
 (0)