package de.neitzel.roleplay.business; import de.neitzel.roleplay.common.CharacterSet; import de.neitzel.roleplay.common.CharacterSnapshot; import de.neitzel.roleplay.common.Recommendation; import de.neitzel.roleplay.common.SituationSnapshot; import de.neitzel.roleplay.common.TurnContext; import de.neitzel.roleplay.common.UserAction; import de.neitzel.roleplay.fascade.model.CharacterDefinition; import de.neitzel.roleplay.fascade.model.CharacterState; import de.neitzel.roleplay.fascade.model.RecommendationRequest; import de.neitzel.roleplay.fascade.model.ScenarioSetup; import de.neitzel.roleplay.fascade.model.SessionResponse; import de.neitzel.roleplay.fascade.model.SituationState; import de.neitzel.roleplay.fascade.model.TurnRequest; import de.neitzel.roleplay.fascade.model.UserActionRequest; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; /** * Builds {@link TurnContext} (common) from API request models for Ollama calls. * Used for session initialization (scenario only) and turn continuation * (session state + turn request). */ public final class OllamaContextBuilder { private OllamaContextBuilder() { } /** * Builds turn context for session initialization. No user action or * recommendation; recent history is empty. * * @param situation situation state from scenario (may be null) * @param characters character list from scenario (may be null or empty) * @return context for the init two-call pattern */ public static TurnContext forSessionInit(final SituationState situation, final List characters) { SituationSnapshot situationSnapshot = situationToSnapshot(situation); CharacterSet characterSet = charactersToSet(characters); return TurnContext.builder() .currentSituation(situationSnapshot) .characters(characterSet) .userAction(null) .recommendation(null) .recentHistorySummary(null) .build(); } /** * Builds turn context from scenario setup. Uses definitions for rich * character snapshots (personality, goals) in the context. * * @param scenario the scenario from create request (must not be null) * @return context for the init two-call pattern */ public static TurnContext fromScenario(final ScenarioSetup scenario) { SituationState situation = buildSituationFromScenario(scenario); CharacterSet characterSet = characterSetFromScenario(scenario); SituationSnapshot situationSnapshot = situationToSnapshot(situation); return TurnContext.builder() .currentSituation(situationSnapshot) .characters(characterSet) .userAction(null) .recommendation(null) .recentHistorySummary(null) .build(); } /** * Builds turn context for a turn continuation from current session state * and the turn request. * * @param session current session (situation and characters used) * @param turnRequest user action and optional recommendation * @return context for the turn two-call pattern */ public static TurnContext forTurn(final SessionResponse session, final TurnRequest turnRequest) { SituationSnapshot situationSnapshot = situationToSnapshot(session.getSituation()); CharacterSet characterSet = charactersToSet(session.getCharacters()); UserAction userAction = turnRequest.getUserAction() != null ? toUserAction(turnRequest.getUserAction()) : null; Recommendation recommendation = turnRequest.getRecommendation() != null ? toRecommendation(turnRequest.getRecommendation()) : null; return TurnContext.builder() .currentSituation(situationSnapshot) .characters(characterSet) .userAction(userAction) .recommendation(recommendation) .recentHistorySummary("") .build(); } private static SituationSnapshot situationToSnapshot(final SituationState s) { if (s == null) { return SituationSnapshot.builder().build(); } return SituationSnapshot.builder() .setting(s.getSetting()) .currentScene(s.getCurrentScene()) .timeline(s.getTimeline() != null ? new ArrayList<>(s.getTimeline()) : null) .openThreads(s.getOpenThreads() != null ? new ArrayList<>(s.getOpenThreads()) : null) .externalPressures(s.getExternalPressures() != null ? new ArrayList<>(s.getExternalPressures()) : null) .worldStateFlags(s.getWorldStateFlags() != null ? Map.copyOf(s.getWorldStateFlags()) : null) .build(); } private static CharacterSet charactersToSet(final List characters) { if (characters == null || characters.isEmpty()) { return CharacterSet.builder() .userCharacter(null) .aiCharacters(Collections.emptyList()) .build(); } CharacterSnapshot userCharacter = null; List aiCharacters = new ArrayList<>(); for (CharacterState c : characters) { CharacterSnapshot snap = characterStateToSnapshot(c); if (Boolean.TRUE.equals(c.getIsUserCharacter())) { userCharacter = snap; } else { aiCharacters.add(snap); } } return CharacterSet.builder() .userCharacter(userCharacter) .aiCharacters(aiCharacters) .build(); } private static CharacterSnapshot characterStateToSnapshot(final CharacterState c) { if (c == null) { return null; } return CharacterSnapshot.builder() .id(c.getId()) .name(c.getName()) .role(c.getRole()) .personalityTraits(null) .speakingStyle(null) .goals(null) .currentMood(c.getCurrentMood()) .knowledge(c.getKnowledge() != null ? new ArrayList<>(c.getKnowledge()) : null) .relationships(c.getRelationships() != null ? Map.copyOf(c.getRelationships()) : null) .status(c.getStatus()) .recentActionsSummary(c.getRecentActionsSummary() != null ? new ArrayList<>(c.getRecentActionsSummary()) : null) .build(); } private static UserAction toUserAction(final UserActionRequest r) { if (r == null) { return null; } de.neitzel.roleplay.common.ActionType type = toActionType(r.getType()); return UserAction.builder() .type(type) .content(r.getContent()) .selectedSuggestionId(r.getSelectedSuggestionId()) .build(); } private static de.neitzel.roleplay.common.ActionType toActionType(final UserActionRequest.TypeEnum e) { if (e == null) { return null; } return switch (e.value()) { case "speech" -> de.neitzel.roleplay.common.ActionType.SPEECH; case "action" -> de.neitzel.roleplay.common.ActionType.ACTION; case "choice" -> de.neitzel.roleplay.common.ActionType.CHOICE; default -> de.neitzel.roleplay.common.ActionType.ACTION; }; } private static Recommendation toRecommendation(final RecommendationRequest r) { if (r == null) { return null; } return Recommendation.builder() .desiredTone(r.getDesiredTone()) .preferredDirection(r.getPreferredDirection()) .focusCharacters(r.getFocusCharacters() != null ? new ArrayList<>(r.getFocusCharacters()) : null) .build(); } private static SituationState buildSituationFromScenario(final ScenarioSetup scenario) { SituationState situation = new SituationState(); situation.setSetting(scenario.getSetting()); situation.setCurrentScene( scenario.getSetting() != null && scenario.getInitialConflict() != null ? scenario.getSetting() + " " + scenario.getInitialConflict() : scenario.getSetting() != null ? scenario.getSetting() : scenario.getInitialConflict()); return situation; } /** Builds character set for context from scenario definitions (rich snapshots). */ private static CharacterSet characterSetFromScenario(final ScenarioSetup scenario) { CharacterSnapshot userCharacter = null; List aiCharacters = new ArrayList<>(); if (scenario.getUserCharacter() != null) { userCharacter = definitionToSnapshot(scenario.getUserCharacter(), true); } if (scenario.getAiCharacters() != null) { for (CharacterDefinition def : scenario.getAiCharacters()) { aiCharacters.add(definitionToSnapshot(def, false)); } } return CharacterSet.builder() .userCharacter(userCharacter) .aiCharacters(aiCharacters) .build(); } private static CharacterSnapshot definitionToSnapshot(final CharacterDefinition def, final boolean isUser) { if (def == null) { return null; } return CharacterSnapshot.builder() .id(def.getId()) .name(def.getName()) .role(def.getRole()) .personalityTraits(def.getPersonalityTraits() != null ? new ArrayList<>(def.getPersonalityTraits()) : null) .speakingStyle(def.getSpeakingStyle()) .goals(def.getGoals() != null ? new ArrayList<>(def.getGoals()) : null) .currentMood(null) .knowledge(null) .relationships(null) .status(null) .recentActionsSummary(null) .build(); } }