feat(conversation): integrate ClaudeAgentServiceV2 for consulting workflow

- Switch ConversationService to use ClaudeAgentServiceV2
- Pass consultingState and deviceInfo from conversation to context
- Handle state_update chunks and save updated state to database
- Move dotenv to dependencies for migration runtime

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-24 06:43:19 -08:00
parent c0a9710943
commit 9f2bdee8d9
2 changed files with 25 additions and 5 deletions

View File

@ -29,6 +29,7 @@
"@nestjs/websockets": "^10.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"dotenv": "^16.3.0",
"ioredis": "^5.3.0",
"kafkajs": "^2.2.4",
"pg": "^8.11.0",
@ -45,7 +46,6 @@
"@types/node": "^20.10.0",
"@types/socket.io": "^3.0.2",
"@types/uuid": "^9.0.0",
"dotenv": "^16.3.0",
"jest": "^29.7.0",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.0",

View File

@ -11,10 +11,10 @@ import {
MessageType,
} from '../domain/entities/message.entity';
import {
ClaudeAgentService,
ClaudeAgentServiceV2,
ConversationContext,
StreamChunk,
} from '../infrastructure/claude/claude-agent.service';
} from '../infrastructure/claude/claude-agent-v2.service';
export interface CreateConversationDto {
userId: string;
@ -45,7 +45,7 @@ export class ConversationService {
private conversationRepo: Repository<ConversationEntity>,
@InjectRepository(MessageEntity)
private messageRepo: Repository<MessageEntity>,
private claudeAgentService: ClaudeAgentService,
private claudeAgentService: ClaudeAgentServiceV2,
) {}
/**
@ -134,7 +134,7 @@ export class ConversationService {
take: 20, // Last 20 messages for context
});
// Build context with support for multimodal messages
// Build context with support for multimodal messages and consulting state (V2)
const context: ConversationContext = {
userId: dto.userId,
conversationId: dto.conversationId,
@ -149,11 +149,15 @@ export class ConversationService {
}
return msg;
}),
// V2: Pass consulting state from conversation (cast through unknown for JSON/Date compatibility)
consultingState: conversation.consultingState as unknown as ConversationContext['consultingState'],
deviceInfo: conversation.deviceInfo,
};
// Collect full response for saving
let fullResponse = '';
const toolCalls: Array<{ name: string; input: unknown; result: unknown }> = [];
let updatedState: ConversationContext['consultingState'] | undefined;
// Stream response from Claude (with attachments for multimodal support)
for await (const chunk of this.claudeAgentService.sendMessage(
@ -174,11 +178,27 @@ export class ConversationService {
if (lastToolCall) {
lastToolCall.result = chunk.toolResult;
}
} else if (chunk.type === 'state_update' && chunk.newState) {
// V2: Capture updated consulting state
updatedState = chunk.newState;
}
yield chunk;
}
// V2: Save updated consulting state to conversation
if (updatedState) {
// Convert state to JSON-compatible format for database storage
const stateForDb = JSON.parse(JSON.stringify(updatedState));
await this.conversationRepo.update(conversation.id, {
consultingState: stateForDb,
consultingStage: updatedState.currentStageId,
collectedInfo: stateForDb.collectedInfo,
recommendedPrograms: updatedState.assessmentResult?.recommendedPrograms,
conversionPath: updatedState.conversionPath,
});
}
// Save assistant response
const assistantMessage = this.messageRepo.create({
conversationId: dto.conversationId,