Skip to content

Commit 62a8644

Browse files
authored
Add start session hook (#3497)
* fix * wip * update * wip * fix * clean * small clean * clean * nit * clean * PR * update * nit * PR * subagent hooks wip * clean * runSubagent * fix * reverts * clean * add context * cleanup * nit * fixes * nit * fixes * fix * start session * clean * tests
1 parent 805b869 commit 62a8644

File tree

4 files changed

+735
-1
lines changed

4 files changed

+735
-1
lines changed

src/extension/intents/node/toolCallingLoop.ts

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import * as l10n from '@vscode/l10n';
77
import { Raw } from '@vscode/prompt-tsx';
88
import type { CancellationToken, ChatRequest, ChatResponseProgressPart, ChatResponseReferencePart, ChatResponseStream, ChatResult, LanguageModelToolInformation, Progress } from 'vscode';
99
import { IAuthenticationChatUpgradeService } from '../../../platform/authentication/common/authenticationUpgrade';
10-
import { IChatHookService, StopHookInput, StopHookOutput, SubagentStartHookInput, SubagentStartHookOutput, SubagentStopHookInput, SubagentStopHookOutput } from '../../../platform/chat/common/chatHookService';
10+
import { IChatHookService, SessionStartHookInput, SessionStartHookOutput, StopHookInput, StopHookOutput, SubagentStartHookInput, SubagentStartHookOutput, SubagentStopHookInput, SubagentStopHookOutput } from '../../../platform/chat/common/chatHookService';
1111
import { FetchStreamSource, IResponsePart } from '../../../platform/chat/common/chatMLFetcher';
1212
import { CanceledResult, ChatFetchResponseType, ChatResponse } from '../../../platform/chat/common/commonTypes';
1313
import { IConfigurationService } from '../../../platform/configuration/common/configurationService';
@@ -296,6 +296,46 @@ export abstract class ToolCallingLoop<TOptions extends IToolCallingLoopOptions =
296296
this._logService.trace(`[ToolCallingLoop] Stop hook blocked stopping: ${reasons.join('; ')}`);
297297
}
298298

299+
/**
300+
* Called when a session starts to allow hooks to provide additional context.
301+
* @param input The session start hook input containing source
302+
* @param token Cancellation token
303+
* @returns Result containing additional context from hooks
304+
*/
305+
protected async executeSessionStartHook(input: SessionStartHookInput, token: CancellationToken): Promise<SubagentStartHookResult> {
306+
try {
307+
const results = await this._chatHookService.executeHook('SessionStart', {
308+
toolInvocationToken: this.options.request.toolInvocationToken,
309+
input: input
310+
}, token);
311+
312+
// Collect additionalContext from all successful hook results
313+
const additionalContexts: string[] = [];
314+
for (const result of results) {
315+
if (result.success === true) {
316+
const output = result.output;
317+
if (typeof output === 'object' && output !== null) {
318+
const hookOutput = output as SessionStartHookOutput;
319+
if (hookOutput.additionalContext) {
320+
additionalContexts.push(hookOutput.additionalContext);
321+
this._logService.trace(`[ToolCallingLoop] SessionStart hook provided context: ${hookOutput.additionalContext.substring(0, 100)}...`);
322+
}
323+
}
324+
} else if (result.success === false) {
325+
const errorMessage = typeof result.output === 'string' ? result.output : 'Unknown error';
326+
this._logService.error(`[ToolCallingLoop] SessionStart hook error: ${errorMessage}`);
327+
}
328+
}
329+
330+
return {
331+
additionalContext: additionalContexts.length > 0 ? additionalContexts.join('\n') : undefined
332+
};
333+
} catch (error) {
334+
this._logService.error('[ToolCallingLoop] Error executing SessionStart hook', error);
335+
return {};
336+
}
337+
}
338+
299339
/**
300340
* Called when a subagent starts to allow hooks to provide additional context.
301341
* @param input The subagent start hook input containing agent_id and agent_type
@@ -404,6 +444,38 @@ export abstract class ToolCallingLoop<TOptions extends IToolCallingLoopOptions =
404444
}
405445
}
406446

447+
/**
448+
* Executes start hooks (SessionStart for regular sessions, SubagentStart for subagents).
449+
* Should be called before run() to allow hooks to provide context before the first prompt.
450+
*
451+
* - For subagents: Always executes SubagentStart hook
452+
* - For regular sessions: Only executes SessionStart hook on the first turn
453+
*/
454+
public async runStartHooks(token: CancellationToken): Promise<void> {
455+
// Execute SubagentStart hook for subagent requests, or SessionStart hook for first turn of regular sessions
456+
if (this.options.request.subAgentInvocationId) {
457+
const startHookResult = await this.executeSubagentStartHook({
458+
agent_id: this.options.request.subAgentInvocationId,
459+
agent_type: this.options.request.subAgentName ?? 'default'
460+
}, token);
461+
if (startHookResult.additionalContext) {
462+
this.additionalHookContext = startHookResult.additionalContext;
463+
this._logService.info(`[ToolCallingLoop] SubagentStart hook provided context for subagent ${this.options.request.subAgentInvocationId}`);
464+
}
465+
} else {
466+
const isFirstTurn = this.options.conversation.turns.length === 1;
467+
if (isFirstTurn) {
468+
const startHookResult = await this.executeSessionStartHook({
469+
source: 'new'
470+
}, token);
471+
if (startHookResult.additionalContext) {
472+
this.additionalHookContext = startHookResult.additionalContext;
473+
this._logService.info('[ToolCallingLoop] SessionStart hook provided context for session');
474+
}
475+
}
476+
}
477+
}
478+
407479
public async run(outputStream: ChatResponseStream | undefined, token: CancellationToken): Promise<IToolCallLoopResult> {
408480
let i = 0;
409481
let lastResult: IToolCallSingleResult | undefined;

0 commit comments

Comments
 (0)