Files
Genarrative/server-node/src/services/customWorldAgentMessageTurnService.ts
2026-04-21 18:27:46 +08:00

197 lines
6.1 KiB
TypeScript

import crypto from 'node:crypto';
import type {
CreatorIntentReadiness,
CustomWorldAgentMessage,
CustomWorldAgentSessionSnapshot,
} from '../../../packages/shared/src/contracts/customWorldAgent.js';
import {
buildPendingClarifications,
evaluateCreatorIntentReadiness,
resolveCreatorIntentStage,
} from './customWorldAgentClarificationService.js';
import {
buildAnchorPackFromIntent,
buildDraftSummaryFromIntent,
buildDraftTitleFromIntent,
type CustomWorldCreatorIntentRecord,
} from './customWorldAgentIntentExtractionService.js';
import type {
CustomWorldAgentSessionRecord,
CustomWorldAgentSessionStore,
} from './customWorldAgentSessionStore.js';
import type { CustomWorldAgentSuggestedActionService } from './customWorldAgentSuggestedActionService.js';
import { CustomWorldAgentSnapshotBuilder } from './customWorldAgentSnapshotBuilder.js';
import {
buildCreatorIntentFromEightAnchorContent,
buildEightAnchorContentFromCreatorIntent,
} from './eightAnchorCompatibilityService.js';
import { EightAnchorSingleTurnService } from './eightAnchorSingleTurnService.js';
function buildDerivedState(
intent: CustomWorldCreatorIntentRecord,
hasUserInput: boolean,
suggestedActionService: CustomWorldAgentSuggestedActionService,
) {
const readiness = evaluateCreatorIntentReadiness(intent);
const pendingClarifications = buildPendingClarifications(intent, readiness);
const stage = resolveCreatorIntentStage({
hasUserInput,
readiness,
});
return {
readiness,
pendingClarifications,
stage,
anchorPack: buildAnchorPackFromIntent(intent, {
completedKeys: readiness.completedKeys,
missingKeys: readiness.missingKeys,
}),
draftProfile: {
title: buildDraftTitleFromIntent(intent),
summary: buildDraftSummaryFromIntent(intent),
},
suggestedActions: suggestedActionService.buildSuggestedActions({
stage,
isReady: readiness.isReady,
}),
};
}
export class CustomWorldAgentMessageTurnService {
constructor(
private readonly sessionStore: CustomWorldAgentSessionStore,
private readonly eightAnchorSingleTurnService: EightAnchorSingleTurnService,
private readonly suggestedActionService: CustomWorldAgentSuggestedActionService,
private readonly snapshotBuilder: CustomWorldAgentSnapshotBuilder,
) {}
async applyMessageTurn(params: {
userId: string;
sessionId: string;
latestUserText: string;
quickFillRequested: boolean;
relatedOperationId?: string | null;
onReplyUpdate?: (text: string) => void;
}) {
const latestSession = (await this.sessionStore.get(
params.userId,
params.sessionId,
)) as CustomWorldAgentSessionRecord | null;
if (!latestSession) {
throw new Error('custom world agent session not found');
}
const shouldPreserveDraftStage =
(latestSession.stage === 'object_refining' ||
latestSession.stage === 'visual_refining') &&
latestSession.draftCards.length > 0;
const assistantTurn = await this.eightAnchorSingleTurnService.streamTurn(
{
currentTurn: latestSession.currentTurn + 1,
progressPercent: latestSession.progressPercent,
quickFillRequested: params.quickFillRequested,
currentAnchorContent: latestSession.anchorContent,
chatHistory: latestSession.messages
.filter(
(message): message is CustomWorldAgentMessage =>
(message.role === 'user' || message.role === 'assistant') &&
Boolean(message.text.trim()),
)
.map((message) => ({
role: message.role,
content: message.text,
})),
},
{
onReplyUpdate: params.onReplyUpdate,
},
);
const nextCreatorIntent = buildCreatorIntentFromEightAnchorContent(
assistantTurn.nextAnchorContent,
);
const progressPercent = Math.max(
0,
Math.min(100, Math.round(assistantTurn.progressPercent)),
);
const creatorIntentReadiness: CreatorIntentReadiness =
progressPercent >= 100
? {
isReady: true,
completedKeys: [
'world_hook',
'player_premise',
'theme_and_tone',
'core_conflict',
'relationship_seed',
'iconic_element',
],
missingKeys: [],
}
: evaluateCreatorIntentReadiness(nextCreatorIntent);
const derivedState = buildDerivedState(
nextCreatorIntent,
true,
this.suggestedActionService,
);
const shouldStayInDraftStage =
shouldPreserveDraftStage && progressPercent >= 100;
const assistantMessage = {
id: `message-${crypto.randomBytes(8).toString('hex')}`,
role: 'assistant',
kind: 'chat',
text: assistantTurn.replyText,
createdAt: new Date().toISOString(),
relatedOperationId: params.relatedOperationId ?? null,
} satisfies CustomWorldAgentMessage;
await this.sessionStore.replaceDerivedState(
params.userId,
params.sessionId,
this.snapshotBuilder.buildMessageTurnState({
latestSession,
nextAnchorContent: assistantTurn.nextAnchorContent,
progressPercent,
replyText: assistantTurn.replyText,
nextCreatorIntent,
creatorIntentReadiness,
derivedDraftProfile: derivedState.draftProfile,
derivedPendingClarifications: derivedState.pendingClarifications,
derivedStage: derivedState.stage,
shouldStayInDraftStage,
}),
);
await this.sessionStore.appendMessage(
params.userId,
params.sessionId,
assistantMessage,
);
return (await this.sessionStore.getSnapshot(
params.userId,
params.sessionId,
)) as CustomWorldAgentSessionSnapshot;
}
deriveInitialSessionState(params: {
seedText: string;
creatorIntent: CustomWorldCreatorIntentRecord;
}) {
const anchorContent = buildEightAnchorContentFromCreatorIntent(
params.creatorIntent,
);
const derivedState = buildDerivedState(
params.creatorIntent,
Boolean(params.seedText),
this.suggestedActionService,
);
return {
anchorContent,
...derivedState,
};
}
}