fix(agents): resolve NestJS route collision for evaluation-rules endpoints

AdminConversationController's GET /:id was intercepting requests to
AdminEvaluationRuleController (matching "evaluation-rules" as an id param).
Similarly, DELETE /:id was matching "cache" as an id.

Changes:
- conversation.module.ts: Register AdminMcpController and
  AdminEvaluationRuleController before AdminConversationController
  (more specific prefixes must come first in NestJS)
- admin-evaluation-rule.controller.ts: Move static routes (POST /test,
  DELETE /cache) before dynamic routes (GET/:id, DELETE/:id)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-02-06 20:37:40 -08:00
parent 51a7589fbf
commit b75d607e2b
2 changed files with 62 additions and 51 deletions

View File

@ -7,6 +7,9 @@
* 2.
* 3. dry-run
* 4.
*
* test, cache:id
* NestJS "test"/"cache" 当作 :id
*/
import {
@ -71,6 +74,45 @@ export class AdminEvaluationRuleController {
}
}
// ─── 静态路由(必须在 :id 之前) ─────────────────────────
// POST /conversations/admin/evaluation-rules/test
@Post('test')
async testEvaluation(
@Headers('authorization') auth: string,
@Body() dto: TestEvaluationDto,
) {
const admin = this.verifyAdmin(auth);
const tenantId = admin.tenantId || null;
if (!dto.stage || !dto.responseText) {
throw new BadRequestException('stage and responseText are required');
}
const result = await this.evaluationGate.evaluate(tenantId, {
stage: dto.stage,
collectedInfo: dto.collectedInfo || null,
assessmentResult: dto.assessmentResult || null,
responseText: dto.responseText,
turnCount: dto.turnCount || 1,
messageCount: dto.messageCount || 1,
hasConverted: false,
agentsUsed: [],
});
return { success: true, data: result };
}
// DELETE /conversations/admin/evaluation-rules/cache
@Delete('cache')
async clearCache(@Headers('authorization') auth: string) {
this.verifyAdmin(auth);
this.evaluationGate.clearCache();
return { success: true, message: 'Cache cleared' };
}
// ─── 集合路由 ──────────────────────────────────────────
// GET /conversations/admin/evaluation-rules
@Get()
async listRules(
@ -97,22 +139,6 @@ export class AdminEvaluationRuleController {
};
}
// GET /conversations/admin/evaluation-rules/:id
@Get(':id')
async getRule(
@Headers('authorization') auth: string,
@Param('id') id: string,
) {
this.verifyAdmin(auth);
const rule = await this.repo.findById(id);
if (!rule) {
return { success: false, error: 'Rule not found' };
}
return { success: true, data: this.toResponse(rule) };
}
// POST /conversations/admin/evaluation-rules
@Post()
@HttpCode(HttpStatus.CREATED)
@ -156,6 +182,24 @@ export class AdminEvaluationRuleController {
return { success: true, data: this.toResponse(saved) };
}
// ─── 动态路由(:id 在静态路由之后) ───────────────────────
// GET /conversations/admin/evaluation-rules/:id
@Get(':id')
async getRule(
@Headers('authorization') auth: string,
@Param('id') id: string,
) {
this.verifyAdmin(auth);
const rule = await this.repo.findById(id);
if (!rule) {
return { success: false, error: 'Rule not found' };
}
return { success: true, data: this.toResponse(rule) };
}
// PUT /conversations/admin/evaluation-rules/:id
@Put(':id')
async updateRule(
@ -233,40 +277,7 @@ export class AdminEvaluationRuleController {
return { success: true, data: this.toResponse(updated) };
}
// POST /conversations/admin/evaluation-rules/test
@Post('test')
async testEvaluation(
@Headers('authorization') auth: string,
@Body() dto: TestEvaluationDto,
) {
const admin = this.verifyAdmin(auth);
const tenantId = admin.tenantId || null;
if (!dto.stage || !dto.responseText) {
throw new BadRequestException('stage and responseText are required');
}
const result = await this.evaluationGate.evaluate(tenantId, {
stage: dto.stage,
collectedInfo: dto.collectedInfo || null,
assessmentResult: dto.assessmentResult || null,
responseText: dto.responseText,
turnCount: dto.turnCount || 1,
messageCount: dto.messageCount || 1,
hasConverted: false,
agentsUsed: [],
});
return { success: true, data: result };
}
// DELETE /conversations/admin/evaluation-rules/cache
@Delete('cache')
async clearCache(@Headers('authorization') auth: string) {
this.verifyAdmin(auth);
this.evaluationGate.clearCache();
return { success: true, message: 'Cache cleared' };
}
// ─── 私有方法 ─────────────────────────────────────────
private toResponse(rule: EvaluationRuleEntity) {
return {

View File

@ -20,7 +20,7 @@ import { ConversationGateway } from '../adapters/inbound/conversation.gateway';
@Module({
imports: [TypeOrmModule.forFeature([ConversationORM, MessageORM, TokenUsageORM, AgentExecutionORM])],
controllers: [ConversationController, InternalConversationController, AdminConversationController, AdminMcpController, AdminEvaluationRuleController],
controllers: [ConversationController, InternalConversationController, AdminMcpController, AdminEvaluationRuleController, AdminConversationController],
providers: [
ConversationService,
ConversationGateway,