fix(agents): add payment gate on invoke_assessment_expert + progress streaming for assessment
Two hardening fixes for the professional assessment pipeline: 1. Code-level payment verification before dispatching invoke_assessment_expert (prevents bypassing the prompt-only gate) 2. Thread onProgress callback through direct tool chain so run_professional_assessment streams agent_progress events during the 30-45s assessment expert execution Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e809740fdb
commit
a72e718510
|
|
@ -65,6 +65,9 @@ import { ToolType } from '../hooks/tool-hooks.types';
|
|||
// Redis (optional checkpoint persistence)
|
||||
import { RedisClientService } from '../../cache/redis-client.service';
|
||||
|
||||
// Payment (for code-level gate on assessment)
|
||||
import { PaymentClientService } from '../../payment/payment-client.service';
|
||||
|
||||
// ============================================================
|
||||
// Compatibility Types (与 ClaudeAgentServiceV2 的 StreamChunk 兼容)
|
||||
// ============================================================
|
||||
|
|
@ -163,6 +166,8 @@ export class CoordinatorAgentService implements OnModuleInit {
|
|||
private readonly toolHooks: ToolHooksService,
|
||||
// Redis (optional — for checkpoint persistence)
|
||||
@Optional() private readonly redisClient?: RedisClientService,
|
||||
// Payment (optional — for code-level gate on invoke_assessment_expert)
|
||||
@Optional() private readonly paymentClient?: PaymentClientService,
|
||||
) {}
|
||||
|
||||
onModuleInit() {
|
||||
|
|
@ -563,6 +568,23 @@ export class CoordinatorAgentService implements OnModuleInit {
|
|||
let result: { output: string; isError: boolean };
|
||||
|
||||
if (toolType === 'agent') {
|
||||
// ── Fix 2: Code-level payment gate on invoke_assessment_expert ──
|
||||
if (toolName === 'invoke_assessment_expert' && this.paymentClient) {
|
||||
const orders = await this.paymentClient.getUserOrders(userId);
|
||||
const hasPaid = orders.some(
|
||||
(o) => o.serviceType === 'ASSESSMENT' && (o.status === 'PAID' || o.status === 'COMPLETED'),
|
||||
);
|
||||
if (!hasPaid) {
|
||||
return {
|
||||
output: JSON.stringify({
|
||||
error: true,
|
||||
message: '评估服务需要付费。请使用 run_professional_assessment 工具进行正式评估(会自动验证支付状态),或先引导用户完成支付。',
|
||||
}),
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 为 Agent 调用创建 onProgress callback,将进度事件推送到 sink
|
||||
const onProgress = progressEventSink
|
||||
? this.createAgentProgressCallback(toolName, progressEventSink)
|
||||
|
|
@ -571,7 +593,11 @@ export class CoordinatorAgentService implements OnModuleInit {
|
|||
} else if (toolType === 'mcp') {
|
||||
result = await this.mcpClient.executeTool(toolName, toolInput);
|
||||
} else {
|
||||
result = await this.executeDirectTool(toolName, toolInput, userId, conversationId);
|
||||
// ── Fix 1: Progress feedback for run_professional_assessment ──
|
||||
const directOnProgress = (toolName === 'run_professional_assessment' && progressEventSink)
|
||||
? this.createAgentProgressCallback('invoke_assessment_expert', progressEventSink)
|
||||
: undefined;
|
||||
result = await this.executeDirectTool(toolName, toolInput, userId, conversationId, directOnProgress);
|
||||
}
|
||||
|
||||
// PostToolUse hook — 成功
|
||||
|
|
@ -725,6 +751,7 @@ export class CoordinatorAgentService implements OnModuleInit {
|
|||
toolInput: Record<string, unknown>,
|
||||
userId: string,
|
||||
conversationId: string,
|
||||
onProgress?: (text: string) => void,
|
||||
): Promise<{ output: string; isError: boolean }> {
|
||||
// 构建旧版 ConversationContext(ImmigrationToolsService 需要)
|
||||
const legacyContext: OldConversationContext = {
|
||||
|
|
@ -737,6 +764,7 @@ export class CoordinatorAgentService implements OnModuleInit {
|
|||
toolName,
|
||||
toolInput,
|
||||
legacyContext,
|
||||
onProgress,
|
||||
);
|
||||
|
||||
// ImmigrationToolsService 返回结构化对象,序列化为 string 供 Claude 消费
|
||||
|
|
|
|||
|
|
@ -310,6 +310,7 @@ export class ImmigrationToolsService {
|
|||
toolName: string,
|
||||
input: Record<string, unknown>,
|
||||
context: ConversationContext,
|
||||
onProgress?: (text: string) => void,
|
||||
): Promise<unknown> {
|
||||
console.log(`[Tool] Executing ${toolName} with input:`, JSON.stringify(input));
|
||||
|
||||
|
|
@ -369,7 +370,7 @@ export class ImmigrationToolsService {
|
|||
return this.queryUserArtifacts(input, context);
|
||||
|
||||
case 'run_professional_assessment':
|
||||
return this.runProfessionalAssessment(input, context);
|
||||
return this.runProfessionalAssessment(input, context, onProgress);
|
||||
|
||||
default:
|
||||
return { error: `Unknown tool: ${toolName}` };
|
||||
|
|
@ -1434,6 +1435,7 @@ export class ImmigrationToolsService {
|
|||
private async runProfessionalAssessment(
|
||||
input: Record<string, unknown>,
|
||||
context: ConversationContext,
|
||||
onProgress?: (text: string) => void,
|
||||
): Promise<unknown> {
|
||||
const { userInfo, targetCategories, conversationContext } = input as {
|
||||
userInfo: Record<string, unknown>;
|
||||
|
|
@ -1543,11 +1545,12 @@ export class ImmigrationToolsService {
|
|||
|
||||
let assessmentResult: string;
|
||||
try {
|
||||
const opts = onProgress ? { onProgress } : undefined;
|
||||
assessmentResult = await this.assessmentExpert.executeAssessment({
|
||||
userInfo,
|
||||
targetCategories,
|
||||
conversationContext,
|
||||
});
|
||||
}, opts);
|
||||
} catch (error) {
|
||||
console.error('[Tool:run_professional_assessment] Assessment expert failed:', error);
|
||||
return {
|
||||
|
|
|
|||
Loading…
Reference in New Issue