import { Injectable } from '@nestjs/common'; import { StudentsService } from '../../students/students.service'; import { StudentAccessScopeFactory } from '../../students/student-access-scope.factory'; import type { AgentToolContext, ToolDef, ToolInputResult } from '../agent-tool.types'; /** Whitelisted input shape for search_students. */ interface SearchStudentsInput { keyword?: string; classId?: number; organizationId?: number; limit?: number; } /** Forbidden input keys — if the model sends these, validation fails. */ const FORBIDDEN_INPUT_KEYS = new Set([ 'userId', 'isSuperAdmin', 'permissions', 'roles', 'ability', 'user', 'password', 'token', ]); @Injectable() export class SearchStudentsTool implements ToolDef { readonly name = 'search_students'; readonly skillKey = 'student'; readonly inputSchema = { type: 'object', properties: { keyword: { type: 'string', description: '搜索关键词(姓名/学号)', maxLength: 100, }, classId: { type: 'integer', description: '班级ID', minimum: 1, }, organizationId: { type: 'integer', description: '校区ID', minimum: 1, }, limit: { type: 'integer', description: '返回条数上限', minimum: 1, maximum: 50, }, }, additionalProperties: false, }; readonly description = '搜索学生,支持关键词、班级、校区筛选。仅返回基础公开字段。'; readonly requiredPermission = 'student:view'; constructor( private readonly studentsService: StudentsService, private readonly scopeFactory: StudentAccessScopeFactory, ) {} validate(input: Record): ToolInputResult { // Reject forbidden keys for (const key of Object.keys(input)) { if (FORBIDDEN_INPUT_KEYS.has(key)) { return { ok: false, error: `不允许的输入字段: ${key}`, }; } } const allowedKeys = new Set(['keyword', 'classId', 'organizationId', 'limit']); for (const key of Object.keys(input)) { if (!allowedKeys.has(key)) { return { ok: false, error: `不允许的输入字段: ${key}` }; } } const result: SearchStudentsInput = {}; if (input.keyword !== undefined) { if (typeof input.keyword !== 'string' || input.keyword.length > 100) { return { ok: false, error: 'keyword 必须是字符串且长度不超过100' }; } result.keyword = input.keyword; } if (input.classId !== undefined) { const id = Number(input.classId); if (!Number.isInteger(id) || id <= 0) { return { ok: false, error: 'classId 必须是正整数' }; } result.classId = id; } if (input.organizationId !== undefined) { const id = Number(input.organizationId); if (!Number.isInteger(id) || id <= 0) { return { ok: false, error: 'organizationId 必须是正整数' }; } result.organizationId = id; } if (input.limit !== undefined) { const limit = Number(input.limit); if (!Number.isInteger(limit) || limit < 1 || limit > 50) { return { ok: false, error: 'limit 必须是 1 到 50 的整数' }; } result.limit = limit; } return { ok: true, value: result }; } async execute(input: SearchStudentsInput, context: AgentToolContext): Promise { const scope = this.scopeFactory.buildScope(context); return this.studentsService.agentSearchStudents(scope, input); } }