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 118 119 120 121 122 123 124 125 126 127 128 129 | 7x 7x 7x 7x 7x 7x 10x 4x 2x 2x 2x 2x 5x 1x 5x 5x 5x 5x 5x | import { Injectable } from '@nestjs/common';
import { ActionHandler } from './action-handler.interface';
import {
ActionExecutionResult,
PlanExecutionContext,
ToolMetadata,
} from '../llm-orchestration.interfaces';
import { FinalArgsDto } from './dto/final.args.dto';
import { plainToClass } from 'class-transformer';
import { validate } from 'class-validator';
import { generateToolCall, generateToolCallJson } from '../../utils';
@Injectable()
export class FinalHandler implements ActionHandler {
readonly toolName = 'final';
getMetadata(): ToolMetadata {
return {
name: this.toolName,
description: this.getDefinition(true),
arguments: [
{
name: 'plain',
type: 'string',
description: 'A final concluding message or summary for user.',
required: false,
},
{
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: false,
},
],
};
}
/**
* Generates a tool call example in the specified format.
* @param toolCall - The tool call object to format
* @param useJson - If true, uses JSON format; otherwise uses XML-style format
* @returns Formatted tool call string
*/
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,
plain:
'The task is complete. I have refactored the component and added the new feature.',
},
useJsonFormat,
);
const selectionsExample = this.generateExample(
{
tool_name: this.toolName,
plain:
'I need some decisions before proceeding with the implementation.',
selections:
'[{"question":"Which API style should we use?","selections":["REST","GraphQL"]},{"question":"Which database?","selections":["Postgres","MySQL","SQLite"]}]',
},
useJsonFormat,
);
const definition = `
<${this.toolName}>
Marks the end of a plan. This should be the last action in a sequence when the task is complete.
Parameters:
- "plain": (string, optional) A final concluding message or summary for the user.
- "selections": (string, optional) JSON array of selection groups. Each group is {"question":"...","selections":["a","b","c"]}. The user will see clickable buttons for each selection. Use this when you need the user to make a choice before continuing.
<example>
:${example}
</example>
<example>
${selectionsExample}
</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(FinalArgsDto, args);
const errors = await validate(validatedArgs);
Iif (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: 'Plan marked as final.',
persisted_args: validatedArgs,
execution_log: { output: 'Task completed.', error_message: '' },
};
}
}
|