197 lines
6.1 KiB
TypeScript
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,
|
|
};
|
|
}
|
|
}
|