Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | 7x 7x 7x 7x 7x 7x 11x 4x 2x 2x 2x 2x 5x 1x 5x 5x 5x 1x 1x 1x 4x 4x | import { Injectable } from '@nestjs/common';
import { ActionHandler } from './action-handler.interface';
import {
ActionExecutionResult,
PlanExecutionContext,
ToolMetadata,
} from '../llm-orchestration.interfaces';
import { AskUserArgsDto } from './dto/ask-user.args.dto';
import { plainToClass } from 'class-transformer';
import { validate } from 'class-validator';
import { generateToolCall, generateToolCallJson } from '../../utils';
@Injectable()
export class AskUserHandler implements ActionHandler {
readonly toolName = 'ask_user';
getMetadata(): ToolMetadata {
return {
name: this.toolName,
description: this.getDefinition(true),
arguments: [
{
name: 'selections',
type: 'string',
description:
'JSON array of selection groups. Each group is {"question":"...","selections":["a","b","c"]}. Renders as clickable buttons in the UI.',
required: true,
},
],
};
}
private generateExample(
toolCall: Record<string, any>,
useJson: boolean = false,
): string {
return useJson
? generateToolCallJson(toolCall)
: generateToolCall(toolCall);
}
getDefinition(useJsonFormat: boolean = false): string {
const example = this.generateExample(
{
tool_name: this.toolName,
selections:
'[{"question":"Which database should we use?","selections":["Postgres","MySQL","SQLite"]}]',
},
useJsonFormat,
);
const simpleExample = this.generateExample(
{
tool_name: this.toolName,
selections:
'[{"question":"Should I proceed with the refactoring?","selections":["yes","no","skip"]}]',
},
useJsonFormat,
);
const definition = `
<${this.toolName}>
Ask the user a question. Use this when you need user input before continuing. The agent loop will pause and wait for the user's response.
Parameters:
- "selections": (string, required) JSON array of selection groups. Each group is {"question":"...","selections":["a","b","c"]}. The user will see clickable buttons for each selection.
<example>
${example}
</example>
<example>
${simpleExample}
</example>
</${this.toolName}>
`;
return definition.trim();
}
async execute(
args: { [key: string]: any },
context: PlanExecutionContext,
): Promise<ActionExecutionResult> {
// Normalize selections: LLMs may pass selections as a JSON array instead of a string.
if (args.selections && typeof args.selections !== 'string') {
args = { ...args, selections: JSON.stringify(args.selections) };
}
const validatedArgs = plainToClass(AskUserArgsDto, args);
const errors = await validate(validatedArgs);
if (errors.length > 0) {
const errorMessages = errors
.map((err) => Object.values(err.constraints || {}).join(', '))
.join('; ');
return {
status: 'FAILURE',
summary: `Invalid arguments for ${this.toolName}.`,
error_message: errorMessages,
persisted_args: args,
execution_log: { output: '', error_message: errorMessages },
};
}
context.flags.is_final = true;
return {
status: 'SUCCESS',
summary: 'Waiting for user response.',
persisted_args: validatedArgs,
execution_log: {
output: 'Waiting for user response.',
error_message: '',
},
};
}
}
|